import _ from 'lodash' import Link from 'next/link' import clsx from 'clsx' import { useEffect, useState } from 'react' import { contractMetrics, Contract, listContracts, getBinaryProb, } from '../lib/firebase/contracts' import { User } from '../lib/firebase/users' import { Col } from './layout/col' import { SiteLink } from './site-link' import { ContractCard } from './contract-card' import { Sort, useQueryAndSortParams } from '../hooks/use-sort-and-query-params' import { Answer } from '../../common/answer' export function ContractsGrid(props: { contracts: Contract[] showHotVolume?: boolean showCloseTime?: boolean }) { const { showCloseTime } = props const [resolvedContracts, activeContracts] = _.partition( props.contracts, (c) => c.isResolved ) const contracts = [...activeContracts, ...resolvedContracts].slice( 0, MAX_CONTRACTS_DISPLAYED ) if (contracts.length === 0) { return (

No markets found. Why not{' '} create one?

) } return ( ) } const MAX_GROUPED_CONTRACTS_DISPLAYED = 6 function CreatorContractsGrid(props: { contracts: Contract[] }) { const { contracts } = props const byCreator = _.groupBy(contracts, (contract) => contract.creatorId) const creator7DayVol = _.mapValues(byCreator, (contracts) => _.sumBy(contracts, (contract) => contract.volume7Days) ) const creatorIds = _.sortBy( Object.keys(byCreator), (creatorId) => -1 * creator7DayVol[creatorId] ) let numContracts = 0 let maxIndex = 0 for (; maxIndex < creatorIds.length; maxIndex++) { numContracts += Math.min( MAX_GROUPED_CONTRACTS_DISPLAYED, byCreator[creatorIds[maxIndex]].length ) if (numContracts > MAX_CONTRACTS_DISPLAYED) break } const creatorIdsSubset = creatorIds.slice(0, maxIndex) return ( {creatorIdsSubset.map((creatorId) => { const { creatorUsername, creatorName } = byCreator[creatorId][0] return ( {creatorName} {byCreator[creatorId].length > MAX_GROUPED_CONTRACTS_DISPLAYED ? ( e.stopPropagation()} > See all ) : (
)} ) })} ) } function TagContractsGrid(props: { contracts: Contract[] }) { const { contracts } = props const contractTags = _.flatMap(contracts, (contract) => { const { tags } = contract return tags.map((tag) => ({ tag, contract, })) }) const groupedByTag = _.groupBy(contractTags, ({ tag }) => tag) const byTag = _.mapValues(groupedByTag, (contractTags) => contractTags.map(({ contract }) => contract) ) const tag7DayVol = _.mapValues(byTag, (contracts) => _.sumBy(contracts, (contract) => contract.volume7Days) ) const tags = _.sortBy( Object.keys(byTag), (creatorId) => -1 * tag7DayVol[creatorId] ) let numContracts = 0 let maxIndex = 0 for (; maxIndex < tags.length; maxIndex++) { numContracts += Math.min( MAX_GROUPED_CONTRACTS_DISPLAYED, byTag[tags[maxIndex]].length ) if (numContracts > MAX_CONTRACTS_DISPLAYED) break } const tagsSubset = tags.slice(0, maxIndex) return ( {tagsSubset.map((tag) => { return ( #{tag} {byTag[tag].length > MAX_GROUPED_CONTRACTS_DISPLAYED ? ( e.stopPropagation()} > See all ) : (
)} ) })} ) } const MAX_CONTRACTS_DISPLAYED = 99 export function SearchableGrid(props: { contracts: Contract[] byOneCreator?: boolean querySortOptions?: { defaultSort: Sort shouldLoadFromStorage?: boolean } }) { const { contracts, byOneCreator, querySortOptions } = props const { query, setQuery, sort, setSort } = useQueryAndSortParams(querySortOptions) const queryWords = query.toLowerCase().split(' ') function check(corpus: String) { return queryWords.every((word) => corpus.toLowerCase().includes(word)) } let matches = contracts.filter( (c) => check(c.question) || check(c.description) || check(c.creatorName) || check(c.creatorUsername) || check(c.lowercaseTags.map((tag) => `#${tag}`).join(' ')) || check( ((c as any).answers ?? []) .map((answer: Answer) => answer.text) .join(' ') ) ) if (sort === 'newest' || sort === 'all') { matches.sort((a, b) => b.createdTime - a.createdTime) } else if (sort === 'resolved') { matches = _.sortBy( matches, (contract) => -1 * (contract.resolutionTime ?? 0) ) } else if (sort === 'oldest') { matches.sort((a, b) => a.createdTime - b.createdTime) } else if (sort === 'close-date' || sort === 'closed') { matches = _.sortBy(matches, ({ volume24Hours }) => -1 * volume24Hours) matches = _.sortBy( matches, (contract) => (sort === 'closed' ? -1 : 1) * (contract.closeTime ?? Infinity) ) const hideClosed = sort === 'closed' matches = matches.filter( ({ closeTime }) => closeTime && closeTime > Date.now() !== hideClosed ) } else if (sort === 'most-traded') { matches.sort((a, b) => b.volume - a.volume) } else if (sort === '24-hour-vol') { // Use lodash for stable sort, so previous sort breaks all ties. matches = _.sortBy(matches, ({ volume7Days }) => -1 * volume7Days) matches = _.sortBy(matches, ({ volume24Hours }) => -1 * volume24Hours) } else if (sort === 'creator' || sort === 'tag') { matches.sort((a, b) => b.volume7Days - a.volume7Days) } else if (sort === 'most-likely') { matches = _.sortBy(matches, (contract) => -getBinaryProb(contract)) } else if (sort === 'least-likely') { // Exclude non-binary contracts matches = matches.filter((contract) => getBinaryProb(contract) !== 0) matches = _.sortBy(matches, (contract) => getBinaryProb(contract)) } if (sort !== 'all') { // Filter for (or filter out) resolved contracts matches = matches.filter((c) => sort === 'resolved' ? c.resolution : !c.resolution ) // Filter out closed contracts. if (sort !== 'closed' && sort !== 'resolved') { matches = matches.filter((c) => !c.closeTime || c.closeTime > Date.now()) } } return (
{/* Show a search input next to a sort dropdown */}
setQuery(e.target.value)} placeholder="Search markets" className="input input-bordered w-full" />
{sort === 'tag' ? ( ) : !byOneCreator && sort === 'creator' ? ( ) : ( )}
) } export function CreatorContractsList(props: { creator: User }) { const { creator } = props const [contracts, setContracts] = useState('loading') useEffect(() => { if (creator?.id) { // TODO: stream changes from firestore listContracts(creator.id).then(setContracts) } }, [creator]) if (contracts === 'loading') return <> return ( ) }