Hot markets! 🔥

This commit is contained in:
jahooma 2022-01-05 00:32:52 -06:00
parent 4386422f02
commit 1bc323d575
7 changed files with 128 additions and 14 deletions

View File

@ -120,7 +120,7 @@ export function ContractDetails(props: {
<Row className="gap-2 flex-wrap"> <Row className="gap-2 flex-wrap">
{tags.map((tag) => ( {tags.map((tag) => (
<div className="bg-gray-100 px-1"> <div key={tag} className="bg-gray-100 px-1">
<Linkify text={tag} gray /> <Linkify text={tag} gray />
</div> </div>
))} ))}

View File

@ -11,7 +11,7 @@ import { parseTags } from '../lib/util/parse'
import { ContractCard } from './contract-card' import { ContractCard } from './contract-card'
import { Sort, useQueryAndSortParams } from '../hooks/use-sort-and-query-params' import { Sort, useQueryAndSortParams } from '../hooks/use-sort-and-query-params'
function ContractsGrid(props: { contracts: Contract[] }) { export function ContractsGrid(props: { contracts: Contract[] }) {
const [resolvedContracts, activeContracts] = _.partition( const [resolvedContracts, activeContracts] = _.partition(
props.contracts, props.contracts,
(c) => c.isResolved (c) => c.isResolved

View File

@ -1,5 +1,11 @@
import _ from 'lodash'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Contract, listenForContracts } from '../lib/firebase/contracts' import { Bet, listenForRecentBets } from '../lib/firebase/bets'
import {
computeHotContracts,
Contract,
listenForContracts,
} from '../lib/firebase/contracts'
export const useContracts = () => { export const useContracts = () => {
const [contracts, setContracts] = useState<Contract[] | 'loading'>('loading') const [contracts, setContracts] = useState<Contract[] | 'loading'>('loading')
@ -10,3 +16,16 @@ export const useContracts = () => {
return contracts return contracts
} }
export const useHotContracts = () => {
const [recentBets, setRecentBets] = useState<Bet[] | 'loading'>('loading')
useEffect(() => {
const oneDay = 1000 * 60 * 60 * 24
return listenForRecentBets(oneDay, setRecentBets)
}, [])
if (recentBets === 'loading') return 'loading'
return computeHotContracts(recentBets)
}

View File

@ -4,7 +4,9 @@ import {
query, query,
onSnapshot, onSnapshot,
where, where,
getDocs,
} from 'firebase/firestore' } from 'firebase/firestore'
import _ from 'lodash'
import { db } from './init' import { db } from './init'
export type Bet = { export type Bet = {
@ -62,3 +64,34 @@ export function listenForUserBets(
setBets(bets) setBets(bets)
}) })
} }
export function listenForRecentBets(
timePeriodMs: number,
setBets: (bets: Bet[]) => void
) {
const recentQuery = query(
collectionGroup(db, 'bets'),
where('createdTime', '>', Date.now() - timePeriodMs)
)
return onSnapshot(recentQuery, (snap) => {
const bets = snap.docs.map((doc) => doc.data() as Bet)
bets.sort((bet1, bet2) => bet1.createdTime - bet2.createdTime)
setBets(bets)
})
}
export async function getRecentBets(timePeriodMs: number) {
const recentQuery = query(
collectionGroup(db, 'bets'),
where('createdTime', '>', Date.now() - timePeriodMs)
)
const snapshot = await getDocs(recentQuery)
const bets = snapshot.docs.map((doc) => doc.data() as Bet)
bets.sort((bet1, bet2) => bet1.createdTime - bet2.createdTime)
return bets
}

View File

@ -11,9 +11,10 @@ import {
onSnapshot, onSnapshot,
orderBy, orderBy,
getDoc, getDoc,
limit,
} from 'firebase/firestore' } from 'firebase/firestore'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { Bet, getRecentBets } from './bets'
import _ from 'lodash'
export type Contract = { export type Contract = {
id: string id: string
@ -131,3 +132,17 @@ export function listenForContract(
setContract((contractSnap.data() ?? null) as Contract | null) setContract((contractSnap.data() ?? null) as Contract | null)
}) })
} }
export function computeHotContracts(recentBets: Bet[]) {
const contractBets = _.groupBy(recentBets, (bet) => bet.contractId)
const hotContractIds = _.sortBy(Object.keys(contractBets), (contractId) =>
_.sumBy(contractBets[contractId], (bet) => -1 * bet.amount)
).slice(0, 4)
return hotContractIds
}
export async function getHotContracts() {
const oneDay = 1000 * 60 * 60 * 24
const recentBets = await getRecentBets(oneDay)
return computeHotContracts(recentBets)
}

View File

@ -3,26 +3,42 @@ import React from 'react'
import { useUser } from '../hooks/use-user' import { useUser } from '../hooks/use-user'
import Markets from './markets' import Markets from './markets'
import LandingPage from './landing-page' import LandingPage from './landing-page'
import { Contract, listAllContracts } from '../lib/firebase/contracts' import {
Contract,
getHotContracts,
listAllContracts,
} from '../lib/firebase/contracts'
import _ from 'lodash'
export async function getStaticProps() { export async function getStaticProps() {
const contracts = await listAllContracts().catch((_) => []) const [contracts, hotContractIds] = await Promise.all([
listAllContracts().catch((_) => []),
getHotContracts().catch(() => []),
])
return { return {
props: { props: {
contracts, contracts,
hotContractIds,
}, },
revalidate: 60, // regenerate after a minute revalidate: 60, // regenerate after a minute
} }
} }
const Home = (props: { contracts: Contract[] }) => { const Home = (props: { contracts: Contract[]; hotContractIds: string[] }) => {
const user = useUser() const user = useUser()
if (user === undefined) return <></> if (user === undefined) return <></>
return user ? <Markets contracts={props.contracts} /> : <LandingPage /> return user ? (
<Markets
contracts={props.contracts}
hotContractIds={props.hotContractIds}
/>
) : (
<LandingPage />
)
} }
export default Home export default Home

View File

@ -1,30 +1,61 @@
import { SearchableGrid } from '../components/contracts-list' import _ from 'lodash'
import { ContractsGrid, SearchableGrid } from '../components/contracts-list'
import { Spacer } from '../components/layout/spacer'
import { Page } from '../components/page' import { Page } from '../components/page'
import { useContracts } from '../hooks/use-contracts' import { Title } from '../components/title'
import { useContracts, useHotContracts } from '../hooks/use-contracts'
import { useQueryAndSortParams } from '../hooks/use-sort-and-query-params' import { useQueryAndSortParams } from '../hooks/use-sort-and-query-params'
import { Contract, listAllContracts } from '../lib/firebase/contracts' import {
Contract,
getHotContracts,
listAllContracts,
} from '../lib/firebase/contracts'
export async function getStaticProps() { export async function getStaticProps() {
const contracts = await listAllContracts().catch((_) => []) const [contracts, hotContractIds] = await Promise.all([
listAllContracts().catch((_) => []),
getHotContracts().catch(() => []),
])
return { return {
props: { props: {
contracts, contracts,
hotContractIds,
}, },
revalidate: 60, // regenerate after a minute revalidate: 60, // regenerate after a minute
} }
} }
export default function Markets(props: { contracts: Contract[] }) { export default function Markets(props: {
contracts: Contract[]
hotContractIds: string[]
}) {
const contracts = useContracts() const contracts = useContracts()
const { query, setQuery, sort, setSort } = useQueryAndSortParams() const { query, setQuery, sort, setSort } = useQueryAndSortParams()
const hotContractIds = useHotContracts()
const readyHotContractIds =
hotContractIds === 'loading' ? props.hotContractIds : hotContractIds
const readyContracts = contracts === 'loading' ? props.contracts : contracts
const hotContracts = readyHotContractIds.map(
(hotId) =>
_.find(readyContracts, (contract) => contract.id === hotId) as Contract
)
return ( return (
<Page> <Page>
<div className="w-full bg-indigo-50 border-2 border-indigo-100 p-6 rounded-lg shadow-md">
<Title className="mt-0" text="🔥 Markets" />
<ContractsGrid contracts={hotContracts} />
</div>
<Spacer h={10} />
{(props.contracts || contracts !== 'loading') && ( {(props.contracts || contracts !== 'loading') && (
<SearchableGrid <SearchableGrid
contracts={contracts === 'loading' ? props.contracts : contracts} contracts={readyContracts}
query={query} query={query}
setQuery={setQuery} setQuery={setQuery}
sort={sort} sort={sort}