-
- {isCreator ? 'You' : 'A trader'}
- {' '}
- placed {formatMoney(amount)} on
{' '}
+
{isCreator ? 'You' : 'A trader'} placed{' '}
+ {formatMoney(amount)} on
{' '}
{isCreator && (
// Allow user to comment in an textarea if they are the creator
@@ -193,7 +199,41 @@ export function ContractDescription(props: {
)
}
-function FeedStart(props: { contract: Contract }) {
+function FeedQuestion(props: { contract: Contract }) {
+ const { contract } = props
+ const { probPercent } = compute(contract)
+
+ return (
+ <>
+
+
+
+ {contract.creatorName} asked{' '}
+
+
+
+
+ {contract.question}
+
+
+
+
+
+ >
+ )
+}
+
+function FeedDescription(props: { contract: Contract }) {
const { contract } = props
const user = useUser()
const isCreator = user?.id === contract.creatorId
@@ -314,12 +354,19 @@ function toFeedComment(bet: Bet, comment: Comment) {
}
}
+const DAY_IN_MS = 24 * 60 * 60 * 1000
+
// Group together bets that are:
-// - Within 24h of the first in the group
+// - Within `windowMs` of the first in the group
// - Do not have a comment
// - Were not created by this user
// Return a list of ActivityItems
-function group(bets: Bet[], comments: Comment[], userId?: string) {
+function groupBets(
+ bets: Bet[],
+ comments: Comment[],
+ windowMs: number,
+ userId?: string
+) {
const commentsMap = mapCommentsByBetId(comments)
const items: any[] = []
let group: Bet[] = []
@@ -349,9 +396,9 @@ function group(bets: Bet[], comments: Comment[], userId?: string) {
} else {
if (
group.length > 0 &&
- dayjs(bet.createdTime).diff(dayjs(group[0].createdTime), 'hour') > 24
+ bet.createdTime - group[0].createdTime > windowMs
) {
- // More than 24h has passed; start a new group
+ // More than `windowMs` has passed; start a new group
pushGroup()
}
group.push(bet)
@@ -398,8 +445,7 @@ function FeedBetGroup(props: { activityItem: any }) {
- {traderCount} traders placed{' '}
- {yesSpan}
+ {traderCount} traders placed {yesSpan}
{yesAmount && noAmount ? ' and ' : ''}
{noSpan}
@@ -415,8 +461,12 @@ type ActivityItem = {
type: 'bet' | 'comment' | 'start' | 'betgroup' | 'close' | 'resolve'
}
-export function ContractFeed(props: { contract: Contract }) {
- const { contract } = props
+export function ContractFeed(props: {
+ contract: Contract
+ // Feed types: 'activity' = Activity feed, 'market' = Comments feed on a market
+ feedType: 'activity' | 'market'
+}) {
+ const { contract, feedType } = props
const { id } = contract
const user = useUser()
@@ -426,9 +476,11 @@ export function ContractFeed(props: { contract: Contract }) {
let comments = useComments(id)
if (comments === 'loading') comments = []
+ const groupWindow = feedType == 'activity' ? 10 * DAY_IN_MS : DAY_IN_MS
+
const allItems = [
{ type: 'start', id: 0 },
- ...group(bets, comments, user?.id),
+ ...groupBets(bets, comments, groupWindow, user?.id),
]
if (contract.closeTime && contract.closeTime <= Date.now()) {
allItems.push({ type: 'close', id: `${contract.closeTime}` })
@@ -451,7 +503,11 @@ export function ContractFeed(props: { contract: Contract }) {
) : null}
{activityItem.type === 'start' ? (
-
+ feedType == 'activity' ? (
+
+ ) : (
+
+ )
) : activityItem.type === 'comment' ? (
) : activityItem.type === 'bet' ? (
diff --git a/web/components/contract-overview.tsx b/web/components/contract-overview.tsx
index 6eff1bda..0bfb4909 100644
--- a/web/components/contract-overview.tsx
+++ b/web/components/contract-overview.tsx
@@ -91,7 +91,7 @@ export const ContractOverview = (props: {
>
)}
-
+
)
}
diff --git a/web/hooks/use-comments.ts b/web/hooks/use-comments.ts
index 0a9dead3..bedc7598 100644
--- a/web/hooks/use-comments.ts
+++ b/web/hooks/use-comments.ts
@@ -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
('loading')
@@ -10,3 +14,9 @@ export const useComments = (contractId: string) => {
return comments
}
+
+export const useRecentComments = () => {
+ const [recentComments, setRecentComments] = useState()
+ useEffect(() => listenForRecentComments(setRecentComments), [])
+ return recentComments
+}
diff --git a/web/lib/firebase/comments.ts b/web/lib/firebase/comments.ts
index c55e16eb..071dab7d 100644
--- a/web/lib/firebase/comments.ts
+++ b/web/lib/firebase/comments.ts
@@ -1,5 +1,15 @@
-import { doc, collection, onSnapshot, setDoc } from 'firebase/firestore'
-
+import {
+ doc,
+ collection,
+ onSnapshot,
+ setDoc,
+ query,
+ collectionGroup,
+ getDocs,
+ where,
+ orderBy,
+} from 'firebase/firestore'
+import { listenForValues } from './utils'
import { db } from './init'
import { User } from '../../../common/user'
import { Comment } from '../../../common/comment'
@@ -48,3 +58,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(recentCommentsQuery, setComments)
+}
diff --git a/web/pages/activity.tsx b/web/pages/activity.tsx
new file mode 100644
index 00000000..4feb494c
--- /dev/null
+++ b/web/pages/activity.tsx
@@ -0,0 +1,99 @@
+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 (
+
+
+
+ )
+}
+
+// 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()
+ 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 function ActivityFeed() {
+ const contracts = useContracts() || []
+ const recentComments = useRecentComments() || []
+ // TODO: Handle static props correctly?
+ const activeContracts = findActiveContracts(contracts, recentComments)
+ return contracts ? (
+ <>
+
+
+
+ {activeContracts.map((contract) => (
+
+ ))}
+
+
+ >
+ ) : (
+ <>>
+ )
+}
+
+export default function ActivityPage() {
+ return (
+
+
+
+ )
+}
diff --git a/web/pages/create.tsx b/web/pages/create.tsx
index f431a37e..7e00191b 100644
--- a/web/pages/create.tsx
+++ b/web/pages/create.tsx
@@ -14,6 +14,7 @@ import { AdvancedPanel } from '../components/advanced-panel'
import { createContract } from '../lib/firebase/api-call'
import { Row } from '../components/layout/row'
import { AmountInput } from '../components/amount-input'
+import { ActivityFeed } from './activity'
// Allow user to create a new contract
export default function NewContract() {
@@ -210,11 +211,9 @@ export default function NewContract() {
-
+
-
-
- {creator &&
}
+
)
}