diff --git a/web/components/contract/contract-card.tsx b/web/components/contract/contract-card.tsx
index 367a5401..ab2f0bfc 100644
--- a/web/components/contract/contract-card.tsx
+++ b/web/components/contract/contract-card.tsx
@@ -7,6 +7,7 @@ import { Col } from '../layout/col'
import {
BinaryContract,
Contract,
+ CPMMBinaryContract,
FreeResponseContract,
MultipleChoiceContract,
NumericContract,
@@ -32,6 +33,8 @@ import { track } from '@amplitude/analytics-browser'
import { trackCallback } from 'web/lib/service/analytics'
import { getMappedValue } from 'common/pseudo-numeric'
import { Tooltip } from '../tooltip'
+import { SiteLink } from '../site-link'
+import { ProbChange } from './prob-change-table'
export function ContractCard(props: {
contract: Contract
@@ -379,3 +382,34 @@ export function PseudoNumericResolutionOrExpectation(props: {
)
}
+
+export function ContractCardProbChange(props: {
+ contract: CPMMBinaryContract
+ noLinkAvatar?: boolean
+ className?: string
+}) {
+ const { contract, noLinkAvatar, className } = props
+ return (
+
+
+
+
+ {contract.question}
+
+
+
+
+ )
+}
diff --git a/web/components/contract/contracts-grid.tsx b/web/components/contract/contracts-grid.tsx
index 3da9a5d5..4f573fb8 100644
--- a/web/components/contract/contracts-grid.tsx
+++ b/web/components/contract/contracts-grid.tsx
@@ -2,7 +2,7 @@ import { Contract } from 'web/lib/firebase/contracts'
import { User } from 'web/lib/firebase/users'
import { Col } from '../layout/col'
import { SiteLink } from '../site-link'
-import { ContractCard } from './contract-card'
+import { ContractCard, ContractCardProbChange } from './contract-card'
import { ShowTime } from './contract-details'
import { ContractSearch } from '../contract-search'
import { useCallback } from 'react'
@@ -10,6 +10,7 @@ import clsx from 'clsx'
import { LoadingIndicator } from '../loading-indicator'
import { VisibilityObserver } from '../visibility-observer'
import Masonry from 'react-masonry-css'
+import { CPMMBinaryContract} from 'common/contract'
export type ContractHighlightOptions = {
contractIds?: string[]
@@ -25,6 +26,7 @@ export function ContractsGrid(props: {
hideQuickBet?: boolean
hideGroupLink?: boolean
noLinkAvatar?: boolean
+ showProbChange?: boolean
}
highlightOptions?: ContractHighlightOptions
trackingPostfix?: string
@@ -39,7 +41,8 @@ export function ContractsGrid(props: {
highlightOptions,
trackingPostfix,
} = props
- const { hideQuickBet, hideGroupLink, noLinkAvatar } = cardUIOptions || {}
+ const { hideQuickBet, hideGroupLink, noLinkAvatar, showProbChange } =
+ cardUIOptions || {}
const { contractIds, highlightClassName } = highlightOptions || {}
const onVisibilityUpdated = useCallback(
(visible) => {
@@ -73,24 +76,31 @@ export function ContractsGrid(props: {
className="-ml-4 flex w-auto"
columnClassName="pl-4 bg-clip-padding"
>
- {contracts.map((contract) => (
- onContractClick(contract) : undefined
- }
- 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
- )}
- />
- ))}
+ {contracts.map((contract) =>
+ showProbChange && contract.mechanism === 'cpmm-1' ? (
+
+ ) : (
+ onContractClick(contract) : undefined
+ }
+ 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
+ )}
+ />
+ )
+ )}
{loadMore && (
- const { positiveChanges, negativeChanges } = changes
+ const [positiveChanges, negativeChanges] = partition(
+ changes,
+ (c) => c.probChanges.day > 0
+ )
const threshold = 0.01
const positiveAboveThreshold = positiveChanges.filter(
@@ -53,10 +55,18 @@ export function ProbChangeTable(props: {
)
}
-function ProbChangeRow(props: { contract: CPMMContract }) {
- const { contract } = props
+export function ProbChangeRow(props: {
+ contract: CPMMContract
+ className?: string
+}) {
+ const { contract, className } = props
return (
-
+
{
const [contracts, setContracts] = useState()
@@ -26,6 +29,29 @@ export const useContracts = () => {
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('', {
+ 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()
export const getCachedContracts = async () =>
q.fetchQuery(['contracts'], () => listAllContracts(1000), {
diff --git a/web/hooks/use-group.ts b/web/hooks/use-group.ts
index 9bcb59cd..e918aa8c 100644
--- a/web/hooks/use-group.ts
+++ b/web/hooks/use-group.ts
@@ -104,7 +104,7 @@ export const useMemberGroupIds = (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 userId = user?.id
diff --git a/web/hooks/use-prob-changes.tsx b/web/hooks/use-prob-changes.tsx
index f3a6eee9..f2f3ce13 100644
--- a/web/hooks/use-prob-changes.tsx
+++ b/web/hooks/use-prob-changes.tsx
@@ -1,75 +1,47 @@
-import { useFirestoreQueryData } from '@react-query-firebase/firestore'
-import { CPMMContract } from 'common/contract'
-import { MINUTE_MS } from 'common/util/time'
-import { useQuery, useQueryClient } from 'react-query'
+import { CPMMBinaryContract } from 'common/contract'
+import { sortBy, uniqBy } from 'lodash'
+import { useQuery } from 'react-query'
import {
- getProbChangesNegative,
- getProbChangesPositive,
-} from 'web/lib/firebase/contracts'
-import { getValues } from 'web/lib/firebase/utils'
-import { getIndexName, searchClient } from 'web/lib/service/algolia'
+ probChangeAscendingIndex,
+ probChangeDescendingIndex,
+} from 'web/lib/service/algolia'
-export const useProbChangesAlgolia = (userId: string) => {
- const { data: positiveData } = useQuery(['prob-change-day', userId], () =>
- searchClient
- .initIndex(getIndexName('prob-change-day'))
- .search('', {
- facetFilters: ['uniqueBettorIds:' + userId, 'isResolved:false'],
- })
- )
- const { data: negativeData } = useQuery(
- ['prob-change-day-ascending', userId],
- () =>
- searchClient
- .initIndex(getIndexName('prob-change-day-ascending'))
- .search('', {
- facetFilters: ['uniqueBettorIds:' + userId, 'isResolved:false'],
- })
- )
+export const useProbChanges = (
+ filters: { bettorId?: string; groupSlugs?: string[] } = {}
+) => {
+ const { bettorId, groupSlugs } = filters
- if (!positiveData || !negativeData) {
- return undefined
+ const bettorFilter = bettorId ? `uniqueBettorIds:${bettorId}` : ''
+ const groupFilters = groupSlugs
+ ? groupSlugs.map((slug) => `groupLinks.slug:${slug}`)
+ : []
+
+ const facetFilters = [
+ 'isResolved:false',
+ 'outcomeType:BINARY',
+ bettorFilter,
+ groupFilters,
+ ]
+ const searchParams = {
+ facetFilters,
+ hitsPerPage: 50,
}
- return {
- positiveChanges: positiveData.hits
- .filter((c) => c.probChanges && c.probChanges.day > 0)
- .filter((c) => c.outcomeType === 'BINARY'),
- negativeChanges: negativeData.hits
- .filter((c) => c.probChanges && c.probChanges.day < 0)
- .filter((c) => c.outcomeType === 'BINARY'),
- }
-}
-
-export const useProbChanges = (userId: string) => {
- const { data: positiveChanges } = useFirestoreQueryData(
- ['prob-changes-day-positive', userId],
- getProbChangesPositive(userId)
- )
- const { data: negativeChanges } = useFirestoreQueryData(
- ['prob-changes-day-negative', userId],
- 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 }
- )
- }
+ const { data: positiveChanges } = useQuery(
+ ['prob-change-day', groupSlugs],
+ () => probChangeDescendingIndex.search('', searchParams)
+ )
+ const { data: negativeChanges } = useQuery(
+ ['prob-change-day-ascending', groupSlugs],
+ () => probChangeAscendingIndex.search('', searchParams)
+ )
+
+ if (!positiveChanges || !negativeChanges) return undefined
+
+ const hits = uniqBy(
+ [...positiveChanges.hits, ...negativeChanges.hits],
+ (c) => c.id
+ )
+
+ return sortBy(hits, (c) => Math.abs(c.probChanges.day)).reverse()
}
diff --git a/web/lib/firebase/contracts.ts b/web/lib/firebase/contracts.ts
index 33f6533b..927f7187 100644
--- a/web/lib/firebase/contracts.ts
+++ b/web/lib/firebase/contracts.ts
@@ -16,7 +16,7 @@ import {
import { partition, sortBy, sum, uniqBy } from 'lodash'
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 { formatMoney, formatPercent } from 'common/util/format'
import { DAY_MS } from 'common/util/time'
@@ -426,21 +426,3 @@ export async function getRecentBetsAndComments(contract: Contract) {
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
-
-export const getProbChangesNegative = (userId: string) =>
- query(
- contracts,
- where('uniqueBettorIds', 'array-contains', userId),
- where('probChanges.day', '<', 0),
- orderBy('probChanges.day', 'asc'),
- limit(10)
- ) as Query
diff --git a/web/lib/service/algolia.ts b/web/lib/service/algolia.ts
index 3b6648a1..29cbd6bf 100644
--- a/web/lib/service/algolia.ts
+++ b/web/lib/service/algolia.ts
@@ -13,3 +13,13 @@ export const searchIndexName =
export const getIndexName = (sort: string) => {
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')
+)
diff --git a/web/pages/daily-movers.tsx b/web/pages/daily-movers.tsx
index 3b709d89..0a17e9e2 100644
--- a/web/pages/daily-movers.tsx
+++ b/web/pages/daily-movers.tsx
@@ -2,14 +2,17 @@ import { ProbChangeTable } from 'web/components/contract/prob-change-table'
import { Col } from 'web/components/layout/col'
import { Page } from 'web/components/page'
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 { useUser } from 'web/hooks/use-user'
export default function DailyMovers() {
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')
diff --git a/web/pages/home/edit.tsx b/web/pages/home/edit.tsx
index 48e10c6c..8c5f8ab5 100644
--- a/web/pages/home/edit.tsx
+++ b/web/pages/home/edit.tsx
@@ -28,7 +28,7 @@ export default function Home() {
}
const groups = useMemberGroupsSubscription(user)
- const { sections } = getHomeItems(groups, homeSections)
+ const { sections } = getHomeItems(groups ?? [], homeSections)
return (
diff --git a/web/pages/home/index.tsx b/web/pages/home/index.tsx
index 83bcb15b..f13fc200 100644
--- a/web/pages/home/index.tsx
+++ b/web/pages/home/index.tsx
@@ -8,6 +8,7 @@ import {
import { PlusCircleIcon, XCircleIcon } from '@heroicons/react/outline'
import clsx from 'clsx'
import { toast, Toaster } from 'react-hot-toast'
+import { Dictionary } from 'lodash'
import { Page } from 'web/components/page'
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 { usePortfolioHistory } from 'web/hooks/use-portfolio-history'
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 { calculatePortfolioProfit } from 'common/calculate-metrics'
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 { PillButton } from 'web/components/buttons/pill-button'
import { filterDefined } from 'common/util/array'
@@ -43,6 +43,8 @@ import { updateUser } from 'web/lib/firebase/users'
import { isArray, keyBy } from 'lodash'
import { usePrefetch } from 'web/hooks/use-prefetch'
import { Title } from 'web/components/title'
+import { CPMMBinaryContract } from 'common/contract'
+import { useContractsByDailyScoreGroups } from 'web/hooks/use-contracts'
export default function Home() {
const user = useUser()
@@ -54,20 +56,19 @@ export default function Home() {
const groups = useMemberGroupsSubscription(user)
- const { sections } = getHomeItems(groups, user?.homeSections ?? [])
+ const { sections } = getHomeItems(groups ?? [], user?.homeSections ?? [])
useEffect(() => {
- if (
- user &&
- !user.homeSections &&
- sections.length > 0 &&
- groups.length > 0
- ) {
+ if (user && !user.homeSections && sections.length > 0 && groups) {
// Save initial home sections.
updateUser(user.id, { homeSections: sections.map((s) => s.id) })
}
}, [user, sections, groups])
+ const groupContracts = useContractsByDailyScoreGroups(
+ groups?.map((g) => g.slug)
+ )
+
return (
@@ -81,9 +82,13 @@ export default function Home() {
- {sections.map((section) => renderSection(section, user, groups))}
+ <>
+ {sections.map((section) =>
+ renderSection(section, user, groups, groupContracts)
+ )}
-
+
+ >
-
+
)
}
function DailyMoversSection(props: { userId: string | null | undefined }) {
const { userId } = props
- const changes = useProbChangesAlgolia(userId ?? '')
+ const changes = useProbChanges({ bettorId: userId ?? undefined })?.filter(
+ (c) => Math.abs(c.probChanges.day) >= 0.01
+ )
- if (changes) {
- const { positiveChanges, negativeChanges } = changes
- if (
- !positiveChanges.find((c) => c.probChanges.day >= 0.01) ||
- !negativeChanges.find((c) => c.probChanges.day <= -0.01)
- )
- return null
+ if (changes && changes.length === 0) {
+ return null
}
return (
@@ -332,6 +351,10 @@ export function TrendingGroupsSection(props: {
const count = full ? 100 : 25
const chosenGroups = groups.slice(0, count)
+ if (chosenGroups.length === 0) {
+ return null
+ }
+
return (