Implement feed answer groups!

This commit is contained in:
James Grugett 2022-03-13 23:28:30 -05:00
parent 679871dbc5
commit 328593ecb0
2 changed files with 88 additions and 70 deletions

View File

@ -7,7 +7,6 @@ import { Contract } from '../../../common/contract'
import { User } from '../../../common/user' import { User } from '../../../common/user'
import { filterDefined } from '../../../common/util/array' import { filterDefined } from '../../../common/util/array'
import { canAddComment, mapCommentsByBetId } from '../../lib/firebase/comments' import { canAddComment, mapCommentsByBetId } from '../../lib/firebase/comments'
import { fromNow } from '../../lib/util/time'
export type ActivityItem = export type ActivityItem =
| DescriptionItem | DescriptionItem
@ -61,10 +60,8 @@ export type BetGroupItem = BaseActivityItem & {
export type AnswerGroupItem = BaseActivityItem & { export type AnswerGroupItem = BaseActivityItem & {
type: 'answergroup' type: 'answergroup'
bets: Bet[]
comments: Comment[]
answer: Answer answer: Answer
user: User | null | undefined items: ActivityItem[]
} }
export type CloseItem = BaseActivityItem & { export type CloseItem = BaseActivityItem & {
@ -181,13 +178,21 @@ function getAnswerGroups(
(answer) => answer.id === outcome (answer) => answer.id === outcome
) as Answer ) as Answer
const answerItems = groupBets(
answerBets,
answerComments,
DAY_IN_MS,
contract,
user?.id,
true
)
return { return {
id: outcome, id: outcome,
type: 'answergroup' as const, type: 'answergroup' as const,
contract, contract,
answer, answer,
bets: answerBets, items: answerItems,
comments: answerComments,
user, user,
} }
}) })

View File

@ -8,6 +8,7 @@ import {
LockClosedIcon, LockClosedIcon,
UserIcon, UserIcon,
UsersIcon, UsersIcon,
PencilAltIcon,
XIcon, XIcon,
} from '@heroicons/react/solid' } from '@heroicons/react/solid'
import dayjs from 'dayjs' import dayjs from 'dayjs'
@ -46,7 +47,6 @@ import { Avatar } from '../avatar'
import { useAdmin } from '../../hooks/use-admin' import { useAdmin } from '../../hooks/use-admin'
import { Answer } from '../../../common/answer' import { Answer } from '../../../common/answer'
import { ActivityItem } from './activity-items' import { ActivityItem } from './activity-items'
import { User } from '../../../common/user'
export function FeedItems(props: { export function FeedItems(props: {
contract: Contract contract: Contract
@ -61,32 +61,15 @@ export function FeedItems(props: {
<div className={clsx(tradingAllowed(contract) ? '' : '-mb-6')}> <div className={clsx(tradingAllowed(contract) ? '' : '-mb-6')}>
{items.map((item, activityItemIdx) => ( {items.map((item, activityItemIdx) => (
<div key={item.id} className="relative pb-6"> <div key={item.id} className="relative pb-6">
{activityItemIdx !== items.length - 1 ? ( {activityItemIdx !== items.length - 1 ||
item.type === 'answergroup' ? (
<span <span
className="absolute top-5 left-5 -ml-px h-[calc(100%-2rem)] w-0.5 bg-gray-200" className="absolute top-5 left-5 -ml-px h-[calc(100%-2rem)] w-0.5 bg-gray-200"
aria-hidden="true" aria-hidden="true"
/> />
) : null} ) : null}
<div className="relative flex items-start space-x-3"> <div className="relative flex items-start space-x-3">
{item.type === 'question' ? ( <FeedItem item={item} />
<FeedQuestion {...item} />
) : item.type === 'description' ? (
<FeedDescription {...item} />
) : item.type === 'createanswer' ? (
<FeedCreateAnswer {...item} />
) : item.type === 'comment' ? (
<FeedComment {...item} />
) : item.type === 'bet' ? (
<FeedBet {...item} />
) : item.type === 'betgroup' ? (
<FeedBetGroup {...item} />
) : item.type === 'answergroup' ? (
<FeedAnswerGroup {...item} />
) : item.type === 'close' ? (
<FeedClose {...item} />
) : item.type === 'resolve' ? (
<FeedResolve {...item} />
) : null}
</div> </div>
</div> </div>
))} ))}
@ -98,6 +81,31 @@ export function FeedItems(props: {
) )
} }
function FeedItem(props: { item: ActivityItem }) {
const { item } = props
switch (item.type) {
case 'question':
return <FeedQuestion {...item} />
case 'description':
return <FeedDescription {...item} />
case 'comment':
return <FeedComment {...item} />
case 'bet':
return <FeedBet {...item} />
case 'createanswer':
return <FeedCreateAnswer {...item} />
case 'betgroup':
return <FeedBetGroup {...item} />
case 'answergroup':
return <FeedAnswerGroup {...item} />
case 'close':
return <FeedClose {...item} />
case 'resolve':
return <FeedResolve {...item} />
}
}
function FeedComment(props: { function FeedComment(props: {
contract: Contract contract: Contract
comment: Comment comment: Comment
@ -106,28 +114,16 @@ function FeedComment(props: {
truncate: boolean truncate: boolean
}) { }) {
const { contract, comment, bet, showOutcomeLabel, truncate } = props const { contract, comment, bet, showOutcomeLabel, truncate } = props
const { createdTime } = contract
const { amount, outcome } = bet const { amount, outcome } = bet
const { text, userUsername, userName, userAvatarUrl } = comment const { text, userUsername, userName, userAvatarUrl, createdTime } = comment
const bought = amount >= 0 ? 'bought' : 'sold' const bought = amount >= 0 ? 'bought' : 'sold'
const money = formatMoney(Math.abs(amount)) const money = formatMoney(Math.abs(amount))
// const answer =
// feedType !== 'multi' &&
// (contract.answers?.find((answer: Answer) => answer?.id === outcome) as
// | Answer
// | undefined)
return ( return (
<> <>
<Avatar username={userUsername} avatarUrl={userAvatarUrl} /> <Avatar username={userUsername} avatarUrl={userAvatarUrl} />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
{/* {answer && (
<div className="text-neutral mb-2" style={{ fontSize: 15 }}>
{answer.text}
</div>
)} */}
<div> <div>
<p className="mt-0.5 text-sm text-gray-500"> <p className="mt-0.5 text-sm text-gray-500">
<UserLink <UserLink
@ -142,7 +138,7 @@ function FeedComment(props: {
of <OutcomeLabel outcome={outcome} /> of <OutcomeLabel outcome={outcome} />
</> </>
)} )}
<Timestamp time={createdTime} /> <RelativeTimestamp time={createdTime} />
</p> </p>
</div> </div>
<TruncatedComment <TruncatedComment
@ -155,7 +151,7 @@ function FeedComment(props: {
) )
} }
function Timestamp(props: { time: number }) { function RelativeTimestamp(props: { time: number }) {
const { time } = props const { time } = props
return ( return (
<DateTimeTooltip time={time}> <DateTimeTooltip time={time}>
@ -227,7 +223,7 @@ function FeedBet(props: {
of <OutcomeLabel outcome={outcome} /> of <OutcomeLabel outcome={outcome} />
</> </>
)} )}
<Timestamp time={createdTime} /> <RelativeTimestamp time={createdTime} />
{canComment && ( {canComment && (
// Allow user to comment in an textarea if they are the creator // Allow user to comment in an textarea if they are the creator
<div className="mt-2"> <div className="mt-2">
@ -421,7 +417,7 @@ export function FeedQuestion(props: {
<> <>
<span className="mx-2"></span> <span className="mx-2"></span>
{contract.closeTime > Date.now() ? 'Closes' : 'Closed'} {contract.closeTime > Date.now() ? 'Closes' : 'Closed'}
<Timestamp time={contract.closeTime || 0} /> <RelativeTimestamp time={contract.closeTime || 0} />
</> </>
) )
@ -497,7 +493,7 @@ function FeedDescription(props: { contract: Contract }) {
name={creatorName} name={creatorName}
username={creatorUsername} username={creatorUsername}
/>{' '} />{' '}
created this market <Timestamp time={contract.createdTime} /> created this market <RelativeTimestamp time={contract.createdTime} />
</div> </div>
<ContractDescription contract={contract} isCreator={isCreator} /> <ContractDescription contract={contract} isCreator={isCreator} />
</div> </div>
@ -518,7 +514,7 @@ function FeedCreateAnswer(props: { contract: Contract; answer: Answer }) {
name={answer.name} name={answer.name}
username={answer.username} username={answer.username}
/>{' '} />{' '}
submitted this answer <Timestamp time={answer.createdTime} /> submitted this answer <RelativeTimestamp time={answer.createdTime} />
</div> </div>
</div> </div>
</> </>
@ -561,7 +557,7 @@ function FeedResolve(props: { contract: Contract }) {
username={creatorUsername} username={creatorUsername}
/>{' '} />{' '}
resolved this market to <OutcomeLabel outcome={resolution} />{' '} resolved this market to <OutcomeLabel outcome={resolution} />{' '}
<Timestamp time={contract.resolutionTime || 0} /> <RelativeTimestamp time={contract.resolutionTime || 0} />
</div> </div>
</div> </div>
</> </>
@ -586,7 +582,7 @@ function FeedClose(props: { contract: Contract }) {
<div className="min-w-0 flex-1 py-1.5"> <div className="min-w-0 flex-1 py-1.5">
<div className="text-sm text-gray-500"> <div className="text-sm text-gray-500">
Trading closed in this market{' '} Trading closed in this market{' '}
<Timestamp time={contract.closeTime || 0} /> <RelativeTimestamp time={contract.closeTime || 0} />
</div> </div>
</div> </div>
</> </>
@ -653,7 +649,7 @@ function FeedBetGroup(props: {
{index !== outcomes.length - 1 && <br />} {index !== outcomes.length - 1 && <br />}
</Fragment> </Fragment>
))} ))}
<Timestamp time={createdTime} /> <RelativeTimestamp time={createdTime} />
</div> </div>
</div> </div>
</> </>
@ -663,33 +659,50 @@ function FeedBetGroup(props: {
function FeedAnswerGroup(props: { function FeedAnswerGroup(props: {
contract: Contract contract: Contract
answer: Answer answer: Answer
bets: Bet[] items: ActivityItem[]
comments: Comment[]
user: User | null | undefined
}) { }) {
const { contract, answer, bets, comments, user } = props const { answer, items } = props
const betGroups = _.groupBy(bets, (bet) => bet.outcome)
const outcomes = Object.keys(betGroups)
// Use the time of the last bet for the entire group
const createdTime = bets[bets.length - 1].createdTime
return ( return (
<> <Col className="gap-2">
<Row className="gap-2 mb-2">
<div> <div>
<div className="relative px-1"> <div className="relative px-1">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200"> <div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200">
<UsersIcon className="h-5 w-5 text-gray-500" aria-hidden="true" /> <PencilAltIcon
className="h-5 w-5 text-gray-500"
aria-hidden="true"
/>
</div> </div>
</div> </div>
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1 mt-1">
<div className="text-neutral mb-2" style={{ fontSize: 15 }}> <div className="text-neutral" style={{ fontSize: 15 }}>
{answer.text} <Linkify text={answer.text} />
</div> </div>
</div> </div>
</> </Row>
{items.map((item, index) => (
<div
key={item.id}
className={clsx(
'relative ml-8',
index !== items.length - 1 && 'pb-4'
)}
>
{index !== items.length - 1 ? (
<span
className="absolute top-5 left-5 -ml-px h-[calc(100%-1rem)] w-0.5 bg-gray-200"
aria-hidden="true"
/>
) : null}
<div className="relative flex items-start space-x-3">
<FeedItem item={item} />
</div>
</div>
))}
</Col>
) )
} }