diff --git a/web/components/contract-feed.tsx b/web/components/contract-feed.tsx
index c17bf586..7e11ef73 100644
--- a/web/components/contract-feed.tsx
+++ b/web/components/contract-feed.tsx
@@ -1,12 +1,15 @@
// From https://tailwindui.com/components/application-ui/lists/feeds
-import { Fragment } from 'react'
+import { Fragment, useState } from 'react'
import { ChatAltIcon, TagIcon, UserCircleIcon } from '@heroicons/react/solid'
import { useBets } from '../hooks/use-bets'
-import { Bet } from '../lib/firebase/bets'
+import { Bet, createComment } from '../lib/firebase/bets'
import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { Contract } from '../lib/firebase/contracts'
import { OutcomeLabel } from './outcome-label'
+import { useUser } from '../hooks/use-user'
+import { User } from '../lib/firebase/users'
+import { Linkify } from './linkify'
dayjs.extend(relativeTime)
const activity = [
@@ -55,44 +58,61 @@ function classNames(...classes) {
function FeedComment(props: { activityItem: any }) {
const { activityItem } = props
+ const { person, text, date, amount, outcome, createdTime } = activityItem
return (
<>

-
+
-
- Commented {activityItem.date}
+
+ {person.name}
+ {' '}
+ placed M$ {amount} on {' '}
+
-
{activityItem.comment}
+
+
+
>
)
}
-function FeedBet(props: { activityItem: any }) {
- const { activityItem } = props
- const { amount, outcome, createdTime } = activityItem
+function Timestamp(props: { time: number }) {
+ const { time } = props
+ return (
+
+ {dayjs(time).fromNow()}
+
+ )
+}
+
+function FeedBet(props: { activityItem: any; user: User }) {
+ const { activityItem, user } = props
+ const { id, contractId, amount, outcome, createdTime } = activityItem
+ const isCreator = user.id == activityItem.userId
+
+ const [comment, setComment] = useState('')
+ async function submitComment() {
+ await createComment(contractId, id, comment, user)
+ }
return (
<>
@@ -107,14 +127,26 @@ function FeedBet(props: { activityItem: any }) {
-
Someone placed M$ {amount} on{' '}
-
{' '}
-
- {dayjs(createdTime).fromNow()}
-
+
{isCreator ? 'You' : 'Someone'}{' '}
+ placed M$ {amount} on
{' '}
+
+ {isCreator && (
+ // Allow user to comment in an textarea if they are the creator
+
+
+ )}
>
@@ -176,6 +208,7 @@ function FeedTags(props: { activityItem: any }) {
function toFeedBet(bet: Bet) {
return {
id: bet.id,
+ contractId: bet.contractId,
userId: bet.userId,
type: 'bet',
amount: bet.amount,
@@ -185,17 +218,41 @@ function toFeedBet(bet: Bet) {
}
}
+function toComment(bet: Bet) {
+ return {
+ id: bet.id,
+ contractId: bet.contractId,
+ userId: bet.userId,
+ type: 'comment',
+ amount: bet.amount,
+ outcome: bet.outcome,
+ createdTime: bet.createdTime,
+ date: dayjs(bet.createdTime).fromNow(),
+
+ // Invariant: bet.comment exists
+ text: bet.comment!.text,
+ person: {
+ href: `/${bet.comment!.userUsername}`,
+ name: bet.comment!.userName,
+ avatarUrl: bet.comment!.userAvatarUrl,
+ },
+ }
+}
+
+function toActivityItem(bet: Bet) {
+ return bet.comment ? toComment(bet) : toFeedBet(bet)
+}
+
export function ContractFeed(props: { contract: Contract }) {
const { contract } = props
const { id } = contract
+ const user = useUser()
let bets = useBets(id)
if (bets === 'loading') bets = []
- // const allItems = [...bets.map(toFeedBet), ...activity]
- const allItems = bets.map(toFeedBet)
-
// TODO: aggregate bets across each day window
+ const allItems = bets.map(toActivityItem)
return (
@@ -213,7 +270,7 @@ export function ContractFeed(props: { contract: Contract }) {
{activityItem.type === 'comment' ? (
) : activityItem.type === 'bet' ? (
-
+
) : activityItem.type === 'tags' ? (
) : null}
diff --git a/web/lib/firebase/bets.ts b/web/lib/firebase/bets.ts
index b09d8f8b..5df0b543 100644
--- a/web/lib/firebase/bets.ts
+++ b/web/lib/firebase/bets.ts
@@ -4,8 +4,11 @@ import {
query,
onSnapshot,
where,
+ doc,
+ updateDoc,
} from 'firebase/firestore'
import { db } from './init'
+import { User } from './users'
export type Bet = {
id: string
@@ -22,11 +25,22 @@ export type Bet = {
sale?: {
amount: number // amount user makes from sale
betId: string // id of bet being sold
+ // TODO: add sale time?
}
isSold?: boolean // true if this BUY bet has been sold
createdTime: number
+
+ // Currently, comments are created after the bet, not atomically with the bet.
+ comment?: {
+ text: string
+ createdTime: number
+ // Denormalized, for rendering comments
+ userName?: string
+ userUsername?: string
+ userAvatarUrl?: string
+ }
}
function getBetsCollection(contractId: string) {
@@ -61,3 +75,21 @@ export function listenForUserBets(
setBets(bets)
})
}
+
+export async function createComment(
+ contractId: string,
+ betId: string,
+ text: string,
+ commenter: User
+) {
+ const betRef = doc(getBetsCollection(contractId), betId)
+ return await updateDoc(betRef, {
+ comment: {
+ text: text,
+ createdTime: Date.now(),
+ userName: commenter.name,
+ userUsername: commenter.username,
+ userAvatarUrl: commenter.avatarUrl,
+ },
+ })
+}