Home: Prob change cards. Sort by daily score. (#925)

* Add dailyScore: product of unique bettors (3 days) and probChanges.day

* Increase memory and duration of scoreContracts

* Home: Smaller prob change card for groups. Use dailyScore for sort order (algolia)

* Add back hover
This commit is contained in:
James Grugett 2022-09-22 16:57:48 -05:00 committed by GitHub
parent eaaa46294a
commit c6d034545a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 238 additions and 154 deletions

View File

@ -57,6 +57,7 @@ export type Contract<T extends AnyContractType = AnyContractType> = {
uniqueBettorIds?: string[] uniqueBettorIds?: string[]
uniqueBettorCount?: number uniqueBettorCount?: number
popularityScore?: number popularityScore?: number
dailyScore?: number
followerCount?: number followerCount?: number
featuredOnHomeRank?: number featuredOnHomeRank?: number
likedByUserIds?: string[] likedByUserIds?: string[]

View File

@ -1,12 +1,14 @@
import * as functions from 'firebase-functions' import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin' import * as admin from 'firebase-admin'
import { Bet } from 'common/bet'
import { uniq } from 'lodash' import { uniq } from 'lodash'
import { Contract } from 'common/contract' import { Bet } from '../../common/bet'
import { Contract } from '../../common/contract'
import { log } from './utils' import { log } from './utils'
import { removeUndefinedProps } from '../../common/util/object'
export const scoreContracts = functions.pubsub export const scoreContracts = functions
.schedule('every 1 hours') .runWith({ memory: '4GB', timeoutSeconds: 540 })
.pubsub.schedule('every 1 hours')
.onRun(async () => { .onRun(async () => {
await scoreContractsInternal() await scoreContractsInternal()
}) })
@ -44,11 +46,22 @@ async function scoreContractsInternal() {
const bettors = bets.docs const bettors = bets.docs
.map((doc) => doc.data() as Bet) .map((doc) => doc.data() as Bet)
.map((bet) => bet.userId) .map((bet) => bet.userId)
const score = uniq(bettors).length const popularityScore = uniq(bettors).length
if (contract.popularityScore !== score)
let dailyScore: number | undefined
if (contract.outcomeType === 'BINARY' && contract.mechanism === 'cpmm-1') {
const percentChange = Math.abs(contract.probChanges.day)
dailyScore = popularityScore * percentChange
}
if (
contract.popularityScore !== popularityScore ||
contract.dailyScore !== dailyScore
) {
await firestore await firestore
.collection('contracts') .collection('contracts')
.doc(contract.id) .doc(contract.id)
.update({ popularityScore: score }) .update(removeUndefinedProps({ popularityScore, dailyScore }))
}
} }
} }

View File

@ -7,6 +7,7 @@ import { Col } from '../layout/col'
import { import {
BinaryContract, BinaryContract,
Contract, Contract,
CPMMBinaryContract,
FreeResponseContract, FreeResponseContract,
MultipleChoiceContract, MultipleChoiceContract,
NumericContract, NumericContract,
@ -32,6 +33,8 @@ import { track } from '@amplitude/analytics-browser'
import { trackCallback } from 'web/lib/service/analytics' import { trackCallback } from 'web/lib/service/analytics'
import { getMappedValue } from 'common/pseudo-numeric' import { getMappedValue } from 'common/pseudo-numeric'
import { Tooltip } from '../tooltip' import { Tooltip } from '../tooltip'
import { SiteLink } from '../site-link'
import { ProbChange } from './prob-change-table'
export function ContractCard(props: { export function ContractCard(props: {
contract: Contract contract: Contract
@ -379,3 +382,34 @@ export function PseudoNumericResolutionOrExpectation(props: {
</Col> </Col>
) )
} }
export function ContractCardProbChange(props: {
contract: CPMMBinaryContract
noLinkAvatar?: boolean
className?: string
}) {
const { contract, noLinkAvatar, className } = props
return (
<Col
className={clsx(
className,
'mb-4 rounded-lg bg-white shadow hover:bg-gray-100 hover:shadow-lg'
)}
>
<AvatarDetails
contract={contract}
className={'px-6 pt-4'}
noLink={noLinkAvatar}
/>
<Row className={clsx('items-start justify-between gap-4 ', className)}>
<SiteLink
className="pl-6 pr-0 pt-2 pb-4 font-semibold text-indigo-700"
href={contractPath(contract)}
>
<span className="line-clamp-3">{contract.question}</span>
</SiteLink>
<ProbChange className="py-2 pr-4" contract={contract} />
</Row>
</Col>
)
}

View File

@ -2,7 +2,7 @@ import { Contract } from 'web/lib/firebase/contracts'
import { User } from 'web/lib/firebase/users' import { User } from 'web/lib/firebase/users'
import { Col } from '../layout/col' import { Col } from '../layout/col'
import { SiteLink } from '../site-link' import { SiteLink } from '../site-link'
import { ContractCard } from './contract-card' import { ContractCard, ContractCardProbChange } from './contract-card'
import { ShowTime } from './contract-details' import { ShowTime } from './contract-details'
import { ContractSearch } from '../contract-search' import { ContractSearch } from '../contract-search'
import { useCallback } from 'react' import { useCallback } from 'react'
@ -10,6 +10,7 @@ import clsx from 'clsx'
import { LoadingIndicator } from '../loading-indicator' import { LoadingIndicator } from '../loading-indicator'
import { VisibilityObserver } from '../visibility-observer' import { VisibilityObserver } from '../visibility-observer'
import Masonry from 'react-masonry-css' import Masonry from 'react-masonry-css'
import { CPMMBinaryContract} from 'common/contract'
export type ContractHighlightOptions = { export type ContractHighlightOptions = {
contractIds?: string[] contractIds?: string[]
@ -25,6 +26,7 @@ export function ContractsGrid(props: {
hideQuickBet?: boolean hideQuickBet?: boolean
hideGroupLink?: boolean hideGroupLink?: boolean
noLinkAvatar?: boolean noLinkAvatar?: boolean
showProbChange?: boolean
} }
highlightOptions?: ContractHighlightOptions highlightOptions?: ContractHighlightOptions
trackingPostfix?: string trackingPostfix?: string
@ -39,7 +41,8 @@ export function ContractsGrid(props: {
highlightOptions, highlightOptions,
trackingPostfix, trackingPostfix,
} = props } = props
const { hideQuickBet, hideGroupLink, noLinkAvatar } = cardUIOptions || {} const { hideQuickBet, hideGroupLink, noLinkAvatar, showProbChange } =
cardUIOptions || {}
const { contractIds, highlightClassName } = highlightOptions || {} const { contractIds, highlightClassName } = highlightOptions || {}
const onVisibilityUpdated = useCallback( const onVisibilityUpdated = useCallback(
(visible) => { (visible) => {
@ -73,24 +76,31 @@ export function ContractsGrid(props: {
className="-ml-4 flex w-auto" className="-ml-4 flex w-auto"
columnClassName="pl-4 bg-clip-padding" columnClassName="pl-4 bg-clip-padding"
> >
{contracts.map((contract) => ( {contracts.map((contract) =>
<ContractCard showProbChange && contract.mechanism === 'cpmm-1' ? (
contract={contract} <ContractCardProbChange
key={contract.id} key={contract.id}
showTime={showTime} contract={contract as CPMMBinaryContract}
onClick={ />
onContractClick ? () => onContractClick(contract) : undefined ) : (
} <ContractCard
noLinkAvatar={noLinkAvatar} contract={contract}
hideQuickBet={hideQuickBet} key={contract.id}
hideGroupLink={hideGroupLink} showTime={showTime}
trackingPostfix={trackingPostfix} onClick={
className={clsx( onContractClick ? () => onContractClick(contract) : undefined
'mb-4 break-inside-avoid-column overflow-hidden', // prevent content from wrapping (needs overflow on firefox) }
contractIds?.includes(contract.id) && highlightClassName noLinkAvatar={noLinkAvatar}
)} hideQuickBet={hideQuickBet}
/> hideGroupLink={hideGroupLink}
))} trackingPostfix={trackingPostfix}
className={clsx(
'mb-4 break-inside-avoid-column overflow-hidden', // prevent content from wrapping (needs overflow on firefox)
contractIds?.includes(contract.id) && highlightClassName
)}
/>
)
)}
</Masonry> </Masonry>
{loadMore && ( {loadMore && (
<VisibilityObserver <VisibilityObserver

View File

@ -1,4 +1,5 @@
import clsx from 'clsx' import clsx from 'clsx'
import { partition } from 'lodash'
import { contractPath } from 'web/lib/firebase/contracts' import { contractPath } from 'web/lib/firebase/contracts'
import { CPMMContract } from 'common/contract' import { CPMMContract } from 'common/contract'
import { formatPercent } from 'common/util/format' import { formatPercent } from 'common/util/format'
@ -8,16 +9,17 @@ import { Row } from '../layout/row'
import { LoadingIndicator } from '../loading-indicator' import { LoadingIndicator } from '../loading-indicator'
export function ProbChangeTable(props: { export function ProbChangeTable(props: {
changes: changes: CPMMContract[] | undefined
| { positiveChanges: CPMMContract[]; negativeChanges: CPMMContract[] }
| undefined
full?: boolean full?: boolean
}) { }) {
const { changes, full } = props const { changes, full } = props
if (!changes) return <LoadingIndicator /> if (!changes) return <LoadingIndicator />
const { positiveChanges, negativeChanges } = changes const [positiveChanges, negativeChanges] = partition(
changes,
(c) => c.probChanges.day > 0
)
const threshold = 0.01 const threshold = 0.01
const positiveAboveThreshold = positiveChanges.filter( const positiveAboveThreshold = positiveChanges.filter(
@ -53,10 +55,18 @@ export function ProbChangeTable(props: {
) )
} }
function ProbChangeRow(props: { contract: CPMMContract }) { export function ProbChangeRow(props: {
const { contract } = props contract: CPMMContract
className?: string
}) {
const { contract, className } = props
return ( return (
<Row className="items-center justify-between gap-4 hover:bg-gray-100"> <Row
className={clsx(
'items-center justify-between gap-4 hover:bg-gray-100',
className
)}
>
<SiteLink <SiteLink
className="p-4 pr-0 font-semibold text-indigo-700" className="p-4 pr-0 font-semibold text-indigo-700"
href={contractPath(contract)} href={contractPath(contract)}

View File

@ -11,10 +11,13 @@ import {
trendingContractsQuery, trendingContractsQuery,
getContractsQuery, getContractsQuery,
} from 'web/lib/firebase/contracts' } from 'web/lib/firebase/contracts'
import { QueryClient, useQueryClient } from 'react-query' import { QueryClient, useQuery, useQueryClient } from 'react-query'
import { MINUTE_MS } from 'common/util/time' import { MINUTE_MS } from 'common/util/time'
import { query, limit } from 'firebase/firestore' import { query, limit } from 'firebase/firestore'
import { Sort } from 'web/components/contract-search' import { Sort } from 'web/components/contract-search'
import { dailyScoreIndex } from 'web/lib/service/algolia'
import { CPMMBinaryContract } from 'common/contract'
import { zipObject } from 'lodash'
export const useContracts = () => { export const useContracts = () => {
const [contracts, setContracts] = useState<Contract[] | undefined>() const [contracts, setContracts] = useState<Contract[] | undefined>()
@ -26,6 +29,29 @@ export const useContracts = () => {
return contracts return contracts
} }
export const useContractsByDailyScoreGroups = (
groupSlugs: string[] | undefined
) => {
const facetFilters = ['isResolved:false']
const { data } = useQuery(['daily-score', groupSlugs], () =>
Promise.all(
(groupSlugs ?? []).map((slug) =>
dailyScoreIndex.search<CPMMBinaryContract>('', {
facetFilters: [...facetFilters, `groupLinks.slug:${slug}`],
})
)
)
)
if (!groupSlugs || !data || data.length !== groupSlugs.length)
return undefined
return zipObject(
groupSlugs,
data.map((d) => d.hits.filter((c) => c.dailyScore))
)
}
const q = new QueryClient() const q = new QueryClient()
export const getCachedContracts = async () => export const getCachedContracts = async () =>
q.fetchQuery(['contracts'], () => listAllContracts(1000), { q.fetchQuery(['contracts'], () => listAllContracts(1000), {

View File

@ -104,7 +104,7 @@ export const useMemberGroupIds = (user: User | null | undefined) => {
} }
export function useMemberGroupsSubscription(user: User | null | undefined) { export function useMemberGroupsSubscription(user: User | null | undefined) {
const cachedGroups = useMemberGroups(user?.id) ?? [] const cachedGroups = useMemberGroups(user?.id)
const [groups, setGroups] = useState(cachedGroups) const [groups, setGroups] = useState(cachedGroups)
const userId = user?.id const userId = user?.id

View File

@ -1,75 +1,47 @@
import { useFirestoreQueryData } from '@react-query-firebase/firestore' import { CPMMBinaryContract } from 'common/contract'
import { CPMMContract } from 'common/contract' import { sortBy, uniqBy } from 'lodash'
import { MINUTE_MS } from 'common/util/time' import { useQuery } from 'react-query'
import { useQuery, useQueryClient } from 'react-query'
import { import {
getProbChangesNegative, probChangeAscendingIndex,
getProbChangesPositive, probChangeDescendingIndex,
} from 'web/lib/firebase/contracts' } from 'web/lib/service/algolia'
import { getValues } from 'web/lib/firebase/utils'
import { getIndexName, searchClient } from 'web/lib/service/algolia'
export const useProbChangesAlgolia = (userId: string) => { export const useProbChanges = (
const { data: positiveData } = useQuery(['prob-change-day', userId], () => filters: { bettorId?: string; groupSlugs?: string[] } = {}
searchClient ) => {
.initIndex(getIndexName('prob-change-day')) const { bettorId, groupSlugs } = filters
.search<CPMMContract>('', {
facetFilters: ['uniqueBettorIds:' + userId, 'isResolved:false'],
})
)
const { data: negativeData } = useQuery(
['prob-change-day-ascending', userId],
() =>
searchClient
.initIndex(getIndexName('prob-change-day-ascending'))
.search<CPMMContract>('', {
facetFilters: ['uniqueBettorIds:' + userId, 'isResolved:false'],
})
)
if (!positiveData || !negativeData) { const bettorFilter = bettorId ? `uniqueBettorIds:${bettorId}` : ''
return undefined const groupFilters = groupSlugs
? groupSlugs.map((slug) => `groupLinks.slug:${slug}`)
: []
const facetFilters = [
'isResolved:false',
'outcomeType:BINARY',
bettorFilter,
groupFilters,
]
const searchParams = {
facetFilters,
hitsPerPage: 50,
} }
return { const { data: positiveChanges } = useQuery(
positiveChanges: positiveData.hits ['prob-change-day', groupSlugs],
.filter((c) => c.probChanges && c.probChanges.day > 0) () => probChangeDescendingIndex.search<CPMMBinaryContract>('', searchParams)
.filter((c) => c.outcomeType === 'BINARY'), )
negativeChanges: negativeData.hits const { data: negativeChanges } = useQuery(
.filter((c) => c.probChanges && c.probChanges.day < 0) ['prob-change-day-ascending', groupSlugs],
.filter((c) => c.outcomeType === 'BINARY'), () => probChangeAscendingIndex.search<CPMMBinaryContract>('', searchParams)
} )
}
if (!positiveChanges || !negativeChanges) return undefined
export const useProbChanges = (userId: string) => {
const { data: positiveChanges } = useFirestoreQueryData( const hits = uniqBy(
['prob-changes-day-positive', userId], [...positiveChanges.hits, ...negativeChanges.hits],
getProbChangesPositive(userId) (c) => c.id
) )
const { data: negativeChanges } = useFirestoreQueryData(
['prob-changes-day-negative', userId], return sortBy(hits, (c) => Math.abs(c.probChanges.day)).reverse()
getProbChangesNegative(userId)
)
if (!positiveChanges || !negativeChanges) {
return undefined
}
return { positiveChanges, negativeChanges }
}
export const usePrefetchProbChanges = (userId: string | undefined) => {
const queryClient = useQueryClient()
if (userId) {
queryClient.prefetchQuery(
['prob-changes-day-positive', userId],
() => getValues(getProbChangesPositive(userId)),
{ staleTime: MINUTE_MS }
)
queryClient.prefetchQuery(
['prob-changes-day-negative', userId],
() => getValues(getProbChangesNegative(userId)),
{ staleTime: MINUTE_MS }
)
}
} }

View File

@ -16,7 +16,7 @@ import {
import { partition, sortBy, sum, uniqBy } from 'lodash' import { partition, sortBy, sum, uniqBy } from 'lodash'
import { coll, getValues, listenForValue, listenForValues } from './utils' import { coll, getValues, listenForValue, listenForValues } from './utils'
import { BinaryContract, Contract, CPMMContract } from 'common/contract' import { BinaryContract, Contract } from 'common/contract'
import { chooseRandomSubset } from 'common/util/random' import { chooseRandomSubset } from 'common/util/random'
import { formatMoney, formatPercent } from 'common/util/format' import { formatMoney, formatPercent } from 'common/util/format'
import { DAY_MS } from 'common/util/time' import { DAY_MS } from 'common/util/time'
@ -426,21 +426,3 @@ export async function getRecentBetsAndComments(contract: Contract) {
recentComments, recentComments,
} }
} }
export const getProbChangesPositive = (userId: string) =>
query(
contracts,
where('uniqueBettorIds', 'array-contains', userId),
where('probChanges.day', '>', 0),
orderBy('probChanges.day', 'desc'),
limit(10)
) as Query<CPMMContract>
export const getProbChangesNegative = (userId: string) =>
query(
contracts,
where('uniqueBettorIds', 'array-contains', userId),
where('probChanges.day', '<', 0),
orderBy('probChanges.day', 'asc'),
limit(10)
) as Query<CPMMContract>

View File

@ -13,3 +13,13 @@ export const searchIndexName =
export const getIndexName = (sort: string) => { export const getIndexName = (sort: string) => {
return `${indexPrefix}contracts-${sort}` return `${indexPrefix}contracts-${sort}`
} }
export const probChangeDescendingIndex = searchClient.initIndex(
getIndexName('prob-change-day')
)
export const probChangeAscendingIndex = searchClient.initIndex(
getIndexName('prob-change-day-ascending')
)
export const dailyScoreIndex = searchClient.initIndex(
getIndexName('daily-score')
)

View File

@ -2,14 +2,17 @@ import { ProbChangeTable } from 'web/components/contract/prob-change-table'
import { Col } from 'web/components/layout/col' import { Col } from 'web/components/layout/col'
import { Page } from 'web/components/page' import { Page } from 'web/components/page'
import { Title } from 'web/components/title' import { Title } from 'web/components/title'
import { useProbChangesAlgolia } from 'web/hooks/use-prob-changes' import { useProbChanges } from 'web/hooks/use-prob-changes'
import { useTracking } from 'web/hooks/use-tracking' import { useTracking } from 'web/hooks/use-tracking'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
export default function DailyMovers() { export default function DailyMovers() {
const user = useUser() const user = useUser()
const bettorId = user?.id ?? undefined
const changes = useProbChangesAlgolia(user?.id ?? '') const changes = useProbChanges({ bettorId })?.filter(
(c) => Math.abs(c.probChanges.day) >= 0.01
)
useTracking('view daily movers') useTracking('view daily movers')

View File

@ -28,7 +28,7 @@ export default function Home() {
} }
const groups = useMemberGroupsSubscription(user) const groups = useMemberGroupsSubscription(user)
const { sections } = getHomeItems(groups, homeSections) const { sections } = getHomeItems(groups ?? [], homeSections)
return ( return (
<Page> <Page>

View File

@ -8,6 +8,7 @@ import {
import { PlusCircleIcon, XCircleIcon } from '@heroicons/react/outline' import { PlusCircleIcon, XCircleIcon } from '@heroicons/react/outline'
import clsx from 'clsx' import clsx from 'clsx'
import { toast, Toaster } from 'react-hot-toast' import { toast, Toaster } from 'react-hot-toast'
import { Dictionary } from 'lodash'
import { Page } from 'web/components/page' import { Page } from 'web/components/page'
import { Col } from 'web/components/layout/col' import { Col } from 'web/components/layout/col'
@ -31,11 +32,10 @@ import { ProbChangeTable } from 'web/components/contract/prob-change-table'
import { groupPath, joinGroup, leaveGroup } from 'web/lib/firebase/groups' import { groupPath, joinGroup, leaveGroup } from 'web/lib/firebase/groups'
import { usePortfolioHistory } from 'web/hooks/use-portfolio-history' import { usePortfolioHistory } from 'web/hooks/use-portfolio-history'
import { formatMoney } from 'common/util/format' import { formatMoney } from 'common/util/format'
import { useProbChangesAlgolia } from 'web/hooks/use-prob-changes' import { useProbChanges } from 'web/hooks/use-prob-changes'
import { ProfitBadge } from 'web/components/bets-list' import { ProfitBadge } from 'web/components/bets-list'
import { calculatePortfolioProfit } from 'common/calculate-metrics' import { calculatePortfolioProfit } from 'common/calculate-metrics'
import { hasCompletedStreakToday } from 'web/components/profile/betting-streak-modal' import { hasCompletedStreakToday } from 'web/components/profile/betting-streak-modal'
import { useContractsQuery } from 'web/hooks/use-contracts'
import { ContractsGrid } from 'web/components/contract/contracts-grid' import { ContractsGrid } from 'web/components/contract/contracts-grid'
import { PillButton } from 'web/components/buttons/pill-button' import { PillButton } from 'web/components/buttons/pill-button'
import { filterDefined } from 'common/util/array' import { filterDefined } from 'common/util/array'
@ -43,6 +43,8 @@ import { updateUser } from 'web/lib/firebase/users'
import { isArray, keyBy } from 'lodash' import { isArray, keyBy } from 'lodash'
import { usePrefetch } from 'web/hooks/use-prefetch' import { usePrefetch } from 'web/hooks/use-prefetch'
import { Title } from 'web/components/title' import { Title } from 'web/components/title'
import { CPMMBinaryContract } from 'common/contract'
import { useContractsByDailyScoreGroups } from 'web/hooks/use-contracts'
export default function Home() { export default function Home() {
const user = useUser() const user = useUser()
@ -54,20 +56,19 @@ export default function Home() {
const groups = useMemberGroupsSubscription(user) const groups = useMemberGroupsSubscription(user)
const { sections } = getHomeItems(groups, user?.homeSections ?? []) const { sections } = getHomeItems(groups ?? [], user?.homeSections ?? [])
useEffect(() => { useEffect(() => {
if ( if (user && !user.homeSections && sections.length > 0 && groups) {
user &&
!user.homeSections &&
sections.length > 0 &&
groups.length > 0
) {
// Save initial home sections. // Save initial home sections.
updateUser(user.id, { homeSections: sections.map((s) => s.id) }) updateUser(user.id, { homeSections: sections.map((s) => s.id) })
} }
}, [user, sections, groups]) }, [user, sections, groups])
const groupContracts = useContractsByDailyScoreGroups(
groups?.map((g) => g.slug)
)
return ( return (
<Page> <Page>
<Toaster /> <Toaster />
@ -81,9 +82,13 @@ export default function Home() {
<DailyStats user={user} /> <DailyStats user={user} />
</Row> </Row>
{sections.map((section) => renderSection(section, user, groups))} <>
{sections.map((section) =>
renderSection(section, user, groups, groupContracts)
)}
<TrendingGroupsSection user={user} /> <TrendingGroupsSection user={user} />
</>
</Col> </Col>
<button <button
type="button" type="button"
@ -134,7 +139,8 @@ export const getHomeItems = (groups: Group[], sections: string[]) => {
function renderSection( function renderSection(
section: { id: string; label: string }, section: { id: string; label: string },
user: User | null | undefined, user: User | null | undefined,
groups: Group[] groups: Group[] | undefined,
groupContracts: Dictionary<CPMMBinaryContract[]> | undefined
) { ) {
const { id, label } = section const { id, label } = section
if (id === 'daily-movers') { if (id === 'daily-movers') {
@ -156,8 +162,23 @@ function renderSection(
<SearchSection key={id} label={label} sort={sort.value} user={user} /> <SearchSection key={id} label={label} sort={sort.value} user={user} />
) )
const group = groups.find((g) => g.id === id) if (groups && groupContracts) {
if (group) return <GroupSection key={id} group={group} user={user} /> const group = groups.find((g) => g.id === id)
if (group) {
const contracts = groupContracts[group.slug].filter(
(c) => Math.abs(c.probChanges.day) >= 0.01
)
if (contracts.length === 0) return null
return (
<GroupSection
key={id}
group={group}
user={user}
contracts={contracts}
/>
)
}
}
return null return null
} }
@ -207,7 +228,6 @@ function SearchSection(props: {
defaultPill={pill} defaultPill={pill}
noControls noControls
maxResults={6} maxResults={6}
headerClassName="sticky"
persistPrefix={`home-${sort}`} persistPrefix={`home-${sort}`}
/> />
</Col> </Col>
@ -217,10 +237,9 @@ function SearchSection(props: {
function GroupSection(props: { function GroupSection(props: {
group: Group group: Group
user: User | null | undefined | undefined user: User | null | undefined | undefined
contracts: CPMMBinaryContract[]
}) { }) {
const { group, user } = props const { group, user, contracts } = props
const contracts = useContractsQuery('score', 4, { groupSlug: group.slug })
return ( return (
<Col> <Col>
@ -245,22 +264,22 @@ function GroupSection(props: {
<XCircleIcon className={'h-5 w-5 flex-shrink-0'} aria-hidden="true" /> <XCircleIcon className={'h-5 w-5 flex-shrink-0'} aria-hidden="true" />
</Button> </Button>
</SectionHeader> </SectionHeader>
<ContractsGrid contracts={contracts} /> <ContractsGrid
contracts={contracts.slice(0, 4)}
cardUIOptions={{ showProbChange: true }}
/>
</Col> </Col>
) )
} }
function DailyMoversSection(props: { userId: string | null | undefined }) { function DailyMoversSection(props: { userId: string | null | undefined }) {
const { userId } = props const { userId } = props
const changes = useProbChangesAlgolia(userId ?? '') const changes = useProbChanges({ bettorId: userId ?? undefined })?.filter(
(c) => Math.abs(c.probChanges.day) >= 0.01
)
if (changes) { if (changes && changes.length === 0) {
const { positiveChanges, negativeChanges } = changes return null
if (
!positiveChanges.find((c) => c.probChanges.day >= 0.01) ||
!negativeChanges.find((c) => c.probChanges.day <= -0.01)
)
return null
} }
return ( return (
@ -332,6 +351,10 @@ export function TrendingGroupsSection(props: {
const count = full ? 100 : 25 const count = full ? 100 : 25
const chosenGroups = groups.slice(0, count) const chosenGroups = groups.slice(0, count)
if (chosenGroups.length === 0) {
return null
}
return ( return (
<Col className={className}> <Col className={className}>
<SectionHeader label="Trending groups" href="/explore-groups"> <SectionHeader label="Trending groups" href="/explore-groups">