Tracks all market activity on a single page
This commit is contained in:
parent
a9b4570fff
commit
5f472aa610
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
94
web/pages/activity.tsx
Normal 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>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user