Tracks all market activity on a single page

This commit is contained in:
Austin Chen 2022-01-10 04:23:41 -05:00
parent a9b4570fff
commit 5f472aa610
3 changed files with 138 additions and 2 deletions

View File

@ -1,5 +1,9 @@
import { useEffect, useState } from 'react'
import { Comment, listenForComments } from '../lib/firebase/comments'
import {
Comment,
listenForComments,
listenForRecentComments,
} from '../lib/firebase/comments'
export const useComments = (contractId: string) => {
const [comments, setComments] = useState<Comment[] | 'loading'>('loading')
@ -10,3 +14,9 @@ export const useComments = (contractId: string) => {
return comments
}
export const useRecentComments = () => {
const [recentComments, setRecentComments] = useState<Comment[] | undefined>()
useEffect(() => listenForRecentComments(setRecentComments), [])
return recentComments
}

View File

@ -1,6 +1,17 @@
import { doc, collection, onSnapshot, setDoc } from 'firebase/firestore'
import {
doc,
collection,
onSnapshot,
setDoc,
query,
collectionGroup,
getDocs,
where,
orderBy,
} from 'firebase/firestore'
import { db } from './init'
import { User } from './users'
import { listenForValues } from './utils'
// Currently, comments are created after the bet, not atomically with the bet.
// They're uniquely identified by the pair contractId/betId.
@ -58,3 +69,24 @@ export function mapCommentsByBetId(comments: Comment[]) {
}
return map
}
const DAY_IN_MS = 24 * 60 * 60 * 1000
// Define "recent" as "<3 days ago" for now
const recentCommentsQuery = query(
collectionGroup(db, 'comments'),
where('createdTime', '>', Date.now() - 3 * DAY_IN_MS),
orderBy('createdTime', 'desc')
)
export async function getRecentComments() {
const snapshot = await getDocs(recentCommentsQuery)
const comments = snapshot.docs.map((doc) => doc.data() as Comment)
return comments
}
export function listenForRecentComments(
setComments: (comments: Comment[]) => void
) {
return listenForValues<Comment>(recentCommentsQuery, setComments)
}

94
web/pages/activity.tsx Normal file
View File

@ -0,0 +1,94 @@
import _ from 'lodash'
import { ContractFeed } from '../components/contract-feed'
import { Row } from '../components/layout/row'
import { Page } from '../components/page'
import { Title } from '../components/title'
import { useRecentComments } from '../hooks/use-comments'
import { useContracts } from '../hooks/use-contracts'
import { Contract } from '../lib/firebase/contracts'
import { Comment } from '../lib/firebase/comments'
function FeedCard(props: { contract: Contract }) {
const { contract } = props
return (
<div className="card bg-white shadow-md rounded-lg divide-y divide-gray-200 py-6 px-4 mb-4">
<ContractFeed contract={contract} />
</div>
)
}
// This does NOT include comment times, since those aren't part of the contract atm.
// TODO: Maybe store last activity time directly in the contract?
// Pros: simplifies this code; cons: harder to tweak "activity" definition later
function lastActivityTime(contract: Contract) {
return Math.max(
contract.resolutionTime || 0,
contract.lastUpdatedTime,
contract.createdTime
)
}
// Types of activity to surface:
// - Comment on a market
// - New market created
// - Market resolved
function findActiveContracts(
allContracts: Contract[],
recentComments: Comment[]
) {
const idToActivityTime = new Map<string, number>()
function record(contractId: string, time: number) {
// Only record if the time is newer
const oldTime = idToActivityTime.get(contractId)
idToActivityTime.set(contractId, Math.max(oldTime ?? 0, time))
}
let contracts: Contract[] = []
// Find contracts with activity in the last 3 days
const DAY_IN_MS = 24 * 60 * 60 * 1000
for (const contract of allContracts || []) {
if (lastActivityTime(contract) > Date.now() - 3 * DAY_IN_MS) {
contracts.push(contract)
record(contract.id, lastActivityTime(contract))
}
}
// Add every contract that had a recent comment, too
const contractsById = new Map(allContracts.map((c) => [c.id, c]))
for (const comment of recentComments) {
const contract = contractsById.get(comment.contractId)
if (contract) {
contracts.push(contract)
record(contract.id, comment.createdTime)
}
}
contracts = _.uniqBy(contracts, (c) => c.id)
contracts = _.sortBy(contracts, (c) => -(idToActivityTime.get(c.id) ?? 0))
return contracts
}
export default function ActivityPage() {
const contracts = useContracts() || []
const recentComments = useRecentComments() || []
// TODO: Handle static props correctly?
const activeContracts = findActiveContracts(contracts, recentComments)
return (
<Page>
<Title text="Recent Activity" />
{contracts ? (
<Row className="gap-4">
<div>
{activeContracts.map((contract) => (
<FeedCard contract={contract} />
))}
</div>
</Row>
) : (
<></>
)}
</Page>
)
}