De-feedify contract comments lists
This commit is contained in:
parent
4cde587ae6
commit
1758e9edc9
|
@ -11,7 +11,6 @@ import { AnswerItem } from './answer-item'
|
|||
import { CreateAnswerPanel } from './create-answer-panel'
|
||||
import { AnswerResolvePanel } from './answer-resolve-panel'
|
||||
import { Spacer } from '../layout/spacer'
|
||||
import { ActivityItem } from '../feed/activity-items'
|
||||
import { User } from 'common/user'
|
||||
import { getOutcomeProbability } from 'common/calculate'
|
||||
import { Answer } from 'common/answer'
|
||||
|
@ -176,7 +175,6 @@ function getAnswerItems(
|
|||
type: 'answer' as const,
|
||||
contract,
|
||||
answer,
|
||||
items: [] as ActivityItem[],
|
||||
user,
|
||||
}
|
||||
})
|
||||
|
@ -186,7 +184,6 @@ function getAnswerItems(
|
|||
function OpenAnswer(props: {
|
||||
contract: FreeResponseContract | MultipleChoiceContract
|
||||
answer: Answer
|
||||
items: ActivityItem[]
|
||||
type: string
|
||||
}) {
|
||||
const { answer, contract } = props
|
||||
|
|
|
@ -1,18 +1,24 @@
|
|||
import { Bet } from 'common/bet'
|
||||
import { Contract } from 'common/contract'
|
||||
import { Contract, CPMMBinaryContract } from 'common/contract'
|
||||
import { ContractComment } from 'common/comment'
|
||||
import { User } from 'common/user'
|
||||
import {
|
||||
ContractActivity,
|
||||
ContractCommentsActivity,
|
||||
ContractBetsActivity,
|
||||
FreeResponseContractCommentsActivity,
|
||||
} from '../feed/contract-activity'
|
||||
import { ContractBetsTable, BetsSummary } from '../bets-list'
|
||||
import { Spacer } from '../layout/spacer'
|
||||
import { Tabs } from '../layout/tabs'
|
||||
import { Col } from '../layout/col'
|
||||
import { tradingAllowed } from 'web/lib/firebase/contracts'
|
||||
import { CommentTipMap } from 'web/hooks/use-tip-txns'
|
||||
import { useBets } from 'web/hooks/use-bets'
|
||||
import { useComments } from 'web/hooks/use-comments'
|
||||
import { useLiquidity } from 'web/hooks/use-liquidity'
|
||||
import { SignUpPrompt } from '../sign-up-prompt'
|
||||
import { PlayMoneyDisclaimer } from '../play-money-disclaimer'
|
||||
import BetButton from '../bet-button'
|
||||
|
||||
export function ContractTabs(props: {
|
||||
contract: Contract
|
||||
|
@ -21,14 +27,18 @@ export function ContractTabs(props: {
|
|||
comments: ContractComment[]
|
||||
tips: CommentTipMap
|
||||
}) {
|
||||
const { contract, user, bets, tips } = props
|
||||
const { contract, user, tips } = props
|
||||
const { outcomeType } = contract
|
||||
|
||||
const updatedBets = useBets(contract.id, {
|
||||
filterChallenges: false,
|
||||
filterRedemptions: true,
|
||||
})
|
||||
const bets = updatedBets ?? props.bets
|
||||
const userBets = user && bets.filter((bet) => bet.userId === user.id)
|
||||
const visibleBets = bets.filter(
|
||||
(bet) => !bet.isAnte && !bet.isRedemption && bet.amount !== 0
|
||||
)
|
||||
|
||||
const liquidityProvisions =
|
||||
useLiquidity(contract.id)?.filter((l) => !l.isAnte && l.amount > 0) ?? []
|
||||
|
||||
|
@ -39,42 +49,41 @@ export function ContractTabs(props: {
|
|||
const betActivity = (
|
||||
<ContractBetsActivity
|
||||
contract={contract}
|
||||
bets={bets}
|
||||
bets={visibleBets}
|
||||
liquidityProvisions={liquidityProvisions}
|
||||
/>
|
||||
)
|
||||
|
||||
const commentActivity = (
|
||||
const commentActivity =
|
||||
outcomeType === 'FREE_RESPONSE' ? (
|
||||
<>
|
||||
<ContractActivity
|
||||
<FreeResponseContractCommentsActivity
|
||||
contract={contract}
|
||||
bets={bets}
|
||||
bets={visibleBets}
|
||||
comments={comments}
|
||||
tips={tips}
|
||||
user={user}
|
||||
mode={
|
||||
contract.outcomeType === 'FREE_RESPONSE'
|
||||
? 'free-response-comment-answer-groups'
|
||||
: 'comments'
|
||||
}
|
||||
betRowClassName="!mt-0 xl:hidden"
|
||||
/>
|
||||
{outcomeType === 'FREE_RESPONSE' && (
|
||||
<Col className={'mt-8 flex w-full '}>
|
||||
<div className={'text-md mt-8 mb-2 text-left'}>General Comments</div>
|
||||
<div className={'mb-4 w-full border-b border-gray-200'} />
|
||||
<ContractActivity
|
||||
<ContractCommentsActivity
|
||||
contract={contract}
|
||||
bets={bets}
|
||||
bets={visibleBets}
|
||||
comments={comments}
|
||||
tips={tips}
|
||||
user={user}
|
||||
mode={'comments'}
|
||||
betRowClassName="!mt-0 xl:hidden"
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<ContractCommentsActivity
|
||||
contract={contract}
|
||||
bets={visibleBets}
|
||||
comments={comments}
|
||||
tips={tips}
|
||||
user={user}
|
||||
/>
|
||||
)
|
||||
|
||||
const yourTrades = (
|
||||
|
@ -92,6 +101,7 @@ export function ContractTabs(props: {
|
|||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
currentPageForAnalytics={'contract'}
|
||||
tabs={[
|
||||
|
@ -100,11 +110,30 @@ export function ContractTabs(props: {
|
|||
content: commentActivity,
|
||||
badge: `${comments.length}`,
|
||||
},
|
||||
{ title: 'Bets', content: betActivity, badge: `${visibleBets.length}` },
|
||||
{
|
||||
title: 'Bets',
|
||||
content: betActivity,
|
||||
badge: `${visibleBets.length}`,
|
||||
},
|
||||
...(!user || !userBets?.length
|
||||
? []
|
||||
: [{ title: 'Your bets', content: yourTrades }]),
|
||||
]}
|
||||
/>
|
||||
{!user ? (
|
||||
<Col className="mt-4 max-w-sm items-center xl:hidden">
|
||||
<SignUpPrompt />
|
||||
<PlayMoneyDisclaimer />
|
||||
</Col>
|
||||
) : (
|
||||
outcomeType === 'BINARY' &&
|
||||
tradingAllowed(contract) && (
|
||||
<BetButton
|
||||
contract={contract as CPMMBinaryContract}
|
||||
className="mb-2 !mt-0 xl:hidden"
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
import { uniq, sortBy } from 'lodash'
|
||||
|
||||
import { Answer } from 'common/answer'
|
||||
import { Bet } from 'common/bet'
|
||||
import { getOutcomeProbability } from 'common/calculate'
|
||||
import { ContractComment } from 'common/comment'
|
||||
import { Contract, FreeResponseContract } from 'common/contract'
|
||||
import { User } from 'common/user'
|
||||
import { CommentTipMap } from 'web/hooks/use-tip-txns'
|
||||
|
||||
export type ActivityItem =
|
||||
| DescriptionItem
|
||||
| QuestionItem
|
||||
| AnswerGroupItem
|
||||
| CloseItem
|
||||
| ResolveItem
|
||||
| CommentInputItem
|
||||
| CommentThreadItem
|
||||
|
||||
type BaseActivityItem = {
|
||||
id: string
|
||||
contract: Contract
|
||||
}
|
||||
|
||||
export type CommentInputItem = BaseActivityItem & {
|
||||
type: 'commentInput'
|
||||
betsByCurrentUser: Bet[]
|
||||
commentsByCurrentUser: ContractComment[]
|
||||
}
|
||||
|
||||
export type DescriptionItem = BaseActivityItem & {
|
||||
type: 'description'
|
||||
}
|
||||
|
||||
export type QuestionItem = BaseActivityItem & {
|
||||
type: 'question'
|
||||
contractPath?: string
|
||||
}
|
||||
|
||||
export type CommentThreadItem = BaseActivityItem & {
|
||||
type: 'commentThread'
|
||||
parentComment: ContractComment
|
||||
comments: ContractComment[]
|
||||
tips: CommentTipMap
|
||||
bets: Bet[]
|
||||
}
|
||||
|
||||
export type AnswerGroupItem = BaseActivityItem & {
|
||||
type: 'answergroup'
|
||||
user: User | undefined | null
|
||||
answer: Answer
|
||||
comments: ContractComment[]
|
||||
tips: CommentTipMap
|
||||
bets: Bet[]
|
||||
}
|
||||
|
||||
export type CloseItem = BaseActivityItem & {
|
||||
type: 'close'
|
||||
}
|
||||
|
||||
export type ResolveItem = BaseActivityItem & {
|
||||
type: 'resolve'
|
||||
}
|
||||
|
||||
function getAnswerAndCommentInputGroups(
|
||||
contract: FreeResponseContract,
|
||||
bets: Bet[],
|
||||
comments: ContractComment[],
|
||||
tips: CommentTipMap,
|
||||
user: User | undefined | null
|
||||
) {
|
||||
let outcomes = uniq(bets.map((bet) => bet.outcome))
|
||||
outcomes = sortBy(outcomes, (outcome) =>
|
||||
getOutcomeProbability(contract, outcome)
|
||||
)
|
||||
|
||||
const answerGroups = outcomes
|
||||
.map((outcome) => {
|
||||
const answer = contract.answers?.find(
|
||||
(answer) => answer.id === outcome
|
||||
) as Answer
|
||||
|
||||
return {
|
||||
id: outcome,
|
||||
type: 'answergroup' as const,
|
||||
contract,
|
||||
user,
|
||||
answer,
|
||||
comments,
|
||||
tips,
|
||||
bets,
|
||||
}
|
||||
})
|
||||
.filter((group) => group.answer) as ActivityItem[]
|
||||
return answerGroups
|
||||
}
|
||||
|
||||
function getCommentThreads(
|
||||
bets: Bet[],
|
||||
comments: ContractComment[],
|
||||
tips: CommentTipMap,
|
||||
contract: Contract
|
||||
) {
|
||||
const parentComments = comments.filter((comment) => !comment.replyToCommentId)
|
||||
|
||||
const items = parentComments.map((comment) => ({
|
||||
type: 'commentThread' as const,
|
||||
id: comment.id,
|
||||
contract: contract,
|
||||
comments: comments,
|
||||
parentComment: comment,
|
||||
bets: bets,
|
||||
tips,
|
||||
}))
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
function commentIsGeneralComment(comment: ContractComment, contract: Contract) {
|
||||
return (
|
||||
comment.answerOutcome === undefined &&
|
||||
(contract.outcomeType === 'FREE_RESPONSE'
|
||||
? comment.betId === undefined
|
||||
: true)
|
||||
)
|
||||
}
|
||||
|
||||
export function getSpecificContractActivityItems(
|
||||
contract: Contract,
|
||||
bets: Bet[],
|
||||
comments: ContractComment[],
|
||||
tips: CommentTipMap,
|
||||
user: User | null | undefined,
|
||||
options: {
|
||||
mode: 'comments' | 'free-response-comment-answer-groups'
|
||||
}
|
||||
) {
|
||||
const { mode } = options
|
||||
let items = [] as ActivityItem[]
|
||||
|
||||
switch (mode) {
|
||||
case 'comments': {
|
||||
const nonFreeResponseComments = comments.filter((comment) =>
|
||||
commentIsGeneralComment(comment, contract)
|
||||
)
|
||||
const nonFreeResponseBets =
|
||||
contract.outcomeType === 'FREE_RESPONSE' ? [] : bets
|
||||
items.push(
|
||||
...getCommentThreads(
|
||||
nonFreeResponseBets,
|
||||
nonFreeResponseComments,
|
||||
tips,
|
||||
contract
|
||||
)
|
||||
)
|
||||
|
||||
items.push({
|
||||
type: 'commentInput',
|
||||
id: 'commentInput',
|
||||
contract,
|
||||
betsByCurrentUser: nonFreeResponseBets.filter(
|
||||
(bet) => bet.userId === user?.id
|
||||
),
|
||||
commentsByCurrentUser: nonFreeResponseComments.filter(
|
||||
(comment) => comment.userId === user?.id
|
||||
),
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'free-response-comment-answer-groups':
|
||||
items.push(
|
||||
...getAnswerAndCommentInputGroups(
|
||||
contract as FreeResponseContract,
|
||||
bets,
|
||||
comments,
|
||||
tips,
|
||||
user
|
||||
)
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
return items.reverse()
|
||||
}
|
|
@ -1,16 +1,16 @@
|
|||
import { Contract } from 'web/lib/firebase/contracts'
|
||||
import { Contract, FreeResponseContract } from 'common/contract'
|
||||
import { ContractComment } from 'common/comment'
|
||||
import { Answer } from 'common/answer'
|
||||
import { Bet } from 'common/bet'
|
||||
import { useBets } from 'web/hooks/use-bets'
|
||||
import { getSpecificContractActivityItems } from './activity-items'
|
||||
import { FeedItems } from './feed-items'
|
||||
import { getOutcomeProbability } from 'common/calculate'
|
||||
import { FeedBet } from './feed-bets'
|
||||
import { FeedLiquidity } from './feed-liquidity'
|
||||
import { FeedAnswerCommentGroup } from './feed-answer-comment-group'
|
||||
import { FeedCommentThread, CommentInput } from './feed-comments'
|
||||
import { User } from 'common/user'
|
||||
import { useContractWithPreload } from 'web/hooks/use-contract'
|
||||
import { CommentTipMap } from 'web/hooks/use-tip-txns'
|
||||
import { LiquidityProvision } from 'common/liquidity-provision'
|
||||
import { sortBy } from 'lodash'
|
||||
import { sortBy, uniq } from 'lodash'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
|
||||
export function ContractBetsActivity(props: {
|
||||
|
@ -20,12 +20,8 @@ export function ContractBetsActivity(props: {
|
|||
}) {
|
||||
const { contract, bets, liquidityProvisions } = props
|
||||
|
||||
// Remove first bet (which is the ante):
|
||||
const displayedBets =
|
||||
contract.outcomeType === 'FREE_RESPONSE' ? bets.slice(1) : bets
|
||||
|
||||
const items = [
|
||||
...displayedBets.map((bet) => ({
|
||||
...bets.map((bet) => ({
|
||||
type: 'bet' as const,
|
||||
id: bet.id + '-' + bet.isSold,
|
||||
bet,
|
||||
|
@ -58,44 +54,100 @@ export function ContractBetsActivity(props: {
|
|||
)
|
||||
}
|
||||
|
||||
export function ContractActivity(props: {
|
||||
export function ContractCommentsActivity(props: {
|
||||
contract: Contract
|
||||
bets: Bet[]
|
||||
comments: ContractComment[]
|
||||
tips: CommentTipMap
|
||||
user: User | null | undefined
|
||||
mode: 'comments' | 'free-response-comment-answer-groups'
|
||||
contractPath?: string
|
||||
className?: string
|
||||
betRowClassName?: string
|
||||
}) {
|
||||
const { user, mode, tips, className, betRowClassName } = props
|
||||
const { bets, contract, comments, user, tips } = props
|
||||
|
||||
const contract = useContractWithPreload(props.contract) ?? props.contract
|
||||
const comments = props.comments
|
||||
const updatedBets = useBets(contract.id, {
|
||||
filterChallenges: false,
|
||||
filterRedemptions: true,
|
||||
})
|
||||
const bets = (updatedBets ?? props.bets).filter(
|
||||
(bet) => !bet.isRedemption && bet.amount !== 0
|
||||
const nonFreeResponseComments = comments.filter(
|
||||
(comment) =>
|
||||
comment.answerOutcome === undefined &&
|
||||
(contract.outcomeType === 'FREE_RESPONSE'
|
||||
? comment.betId === undefined
|
||||
: true)
|
||||
)
|
||||
const items = getSpecificContractActivityItems(
|
||||
contract,
|
||||
bets,
|
||||
comments,
|
||||
tips,
|
||||
user,
|
||||
{ mode }
|
||||
const nonFreeResponseBets =
|
||||
contract.outcomeType === 'FREE_RESPONSE' ? [] : bets
|
||||
|
||||
const betsByCurrentUser = nonFreeResponseBets.filter(
|
||||
(bet) => bet.userId === user?.id
|
||||
)
|
||||
const commentsByCurrentUser = nonFreeResponseComments.filter(
|
||||
(comment) => comment.userId === user?.id
|
||||
)
|
||||
|
||||
const parentComments = comments.filter((comment) => !comment.replyToCommentId)
|
||||
|
||||
return (
|
||||
<FeedItems
|
||||
<div>
|
||||
<CommentInput
|
||||
contract={contract}
|
||||
items={items}
|
||||
className={className}
|
||||
betRowClassName={betRowClassName}
|
||||
user={user}
|
||||
betsByCurrentUser={betsByCurrentUser}
|
||||
commentsByCurrentUser={commentsByCurrentUser}
|
||||
/>
|
||||
{parentComments.map((parent, idx) => (
|
||||
<div key={parent.id} className={'relative pb-4'}>
|
||||
{idx !== parentComments.length - 1 ? (
|
||||
<span
|
||||
className="absolute top-5 left-5 -ml-px h-[calc(100%-2rem)] w-0.5 bg-gray-200"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : null}
|
||||
<FeedCommentThread
|
||||
contract={contract}
|
||||
parentComment={parent}
|
||||
comments={comments}
|
||||
tips={tips}
|
||||
bets={bets}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function FreeResponseContractCommentsActivity(props: {
|
||||
contract: FreeResponseContract
|
||||
bets: Bet[]
|
||||
comments: ContractComment[]
|
||||
tips: CommentTipMap
|
||||
user: User | null | undefined
|
||||
}) {
|
||||
const { bets, contract, comments, user, tips } = props
|
||||
|
||||
let outcomes = uniq(bets.map((bet) => bet.outcome))
|
||||
outcomes = sortBy(outcomes, (outcome) =>
|
||||
getOutcomeProbability(contract, outcome)
|
||||
)
|
||||
|
||||
const answers = outcomes
|
||||
.map((outcome) => {
|
||||
return contract.answers.find((answer) => answer.id === outcome) as Answer
|
||||
})
|
||||
.filter((answer) => answer != null)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{answers.map((answer) => (
|
||||
<div key={answer.id} className={'relative pb-4'}>
|
||||
<span
|
||||
className="absolute top-5 left-5 -ml-px h-[calc(100%-2rem)] w-0.5 bg-gray-200"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<FeedAnswerCommentGroup
|
||||
contract={contract}
|
||||
user={user}
|
||||
answer={answer}
|
||||
comments={comments}
|
||||
tips={tips}
|
||||
bets={bets}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -101,7 +101,10 @@ export function FeedAnswerCommentGroup(props: {
|
|||
}, [answerElementId, router.asPath])
|
||||
|
||||
return (
|
||||
<Col className={'relative flex-1 gap-3'} key={answer.id + 'comment'}>
|
||||
<Col
|
||||
className={'relative flex-1 items-start gap-3'}
|
||||
key={answer.id + 'comment'}
|
||||
>
|
||||
<Row
|
||||
className={clsx(
|
||||
'flex gap-3 space-x-3 pt-4 transition-all duration-1000',
|
||||
|
|
|
@ -36,9 +36,8 @@ export function FeedCommentThread(props: {
|
|||
tips: CommentTipMap
|
||||
parentComment: ContractComment
|
||||
bets: Bet[]
|
||||
smallAvatar?: boolean
|
||||
}) {
|
||||
const { contract, comments, bets, tips, smallAvatar, parentComment } = props
|
||||
const { contract, comments, bets, tips, parentComment } = props
|
||||
const [showReply, setShowReply] = useState(false)
|
||||
const [replyToUser, setReplyToUser] = useState<{
|
||||
id: string
|
||||
|
@ -58,7 +57,7 @@ export function FeedCommentThread(props: {
|
|||
}
|
||||
|
||||
return (
|
||||
<Col className={'w-full gap-3 pr-1'}>
|
||||
<Col className={'relative w-full items-start gap-3 pr-1'}>
|
||||
<span
|
||||
className="absolute top-5 left-5 -ml-px h-[calc(100%-2rem)] w-0.5 bg-gray-200"
|
||||
aria-hidden="true"
|
||||
|
@ -68,7 +67,6 @@ export function FeedCommentThread(props: {
|
|||
commentsList={commentsList}
|
||||
betsByUserId={betsByUserId}
|
||||
tips={tips}
|
||||
smallAvatar={smallAvatar}
|
||||
bets={bets}
|
||||
scrollAndOpenReplyInput={scrollAndOpenReplyInput}
|
||||
/>
|
||||
|
|
|
@ -1,279 +0,0 @@
|
|||
// From https://tailwindui.com/components/application-ui/lists/feeds
|
||||
import React from 'react'
|
||||
import {
|
||||
BanIcon,
|
||||
CheckIcon,
|
||||
LockClosedIcon,
|
||||
XIcon,
|
||||
} from '@heroicons/react/solid'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { OutcomeLabel } from '../outcome-label'
|
||||
import {
|
||||
Contract,
|
||||
contractPath,
|
||||
tradingAllowed,
|
||||
} from 'web/lib/firebase/contracts'
|
||||
import { BinaryResolutionOrChance } from '../contract/contract-card'
|
||||
import { SiteLink } from '../site-link'
|
||||
import { Col } from '../layout/col'
|
||||
import { UserLink } from '../user-page'
|
||||
import BetButton from '../bet-button'
|
||||
import { Avatar } from '../avatar'
|
||||
import { ActivityItem } from './activity-items'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { trackClick } from 'web/lib/firebase/tracking'
|
||||
import { DAY_MS } from 'common/util/time'
|
||||
import NewContractBadge from '../new-contract-badge'
|
||||
import { RelativeTimestamp } from '../relative-timestamp'
|
||||
import { FeedAnswerCommentGroup } from 'web/components/feed/feed-answer-comment-group'
|
||||
import {
|
||||
FeedCommentThread,
|
||||
CommentInput,
|
||||
} from 'web/components/feed/feed-comments'
|
||||
import { FeedBet } from 'web/components/feed/feed-bets'
|
||||
import { CPMMBinaryContract, NumericContract } from 'common/contract'
|
||||
import { FeedLiquidity } from './feed-liquidity'
|
||||
import { BetSignUpPrompt } from '../sign-up-prompt'
|
||||
import { User } from 'common/user'
|
||||
import { PlayMoneyDisclaimer } from '../play-money-disclaimer'
|
||||
import { contractMetrics } from 'common/contract-details'
|
||||
|
||||
export function FeedItems(props: {
|
||||
contract: Contract
|
||||
items: ActivityItem[]
|
||||
className?: string
|
||||
betRowClassName?: string
|
||||
user: User | null | undefined
|
||||
}) {
|
||||
const { contract, items, className, betRowClassName, user } = props
|
||||
const { outcomeType } = contract
|
||||
|
||||
return (
|
||||
<div className={clsx('flow-root', className)}>
|
||||
<div className={clsx(tradingAllowed(contract) ? '' : '-mb-6')}>
|
||||
{items.map((item, activityItemIdx) => (
|
||||
<div key={item.id} className={'relative pb-4'}>
|
||||
{activityItemIdx !== items.length - 1 ||
|
||||
item.type === 'answergroup' ? (
|
||||
<span
|
||||
className="absolute top-5 left-5 -ml-px h-[calc(100%-2rem)] w-0.5 bg-gray-200"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : null}
|
||||
<div className="relative flex-col items-start space-x-3">
|
||||
<FeedItem item={item} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!user ? (
|
||||
<Col className="mt-4 max-w-sm items-center xl:hidden">
|
||||
<BetSignUpPrompt />
|
||||
<PlayMoneyDisclaimer />
|
||||
</Col>
|
||||
) : (
|
||||
outcomeType === 'BINARY' &&
|
||||
tradingAllowed(contract) && (
|
||||
<BetButton
|
||||
contract={contract as CPMMBinaryContract}
|
||||
className={clsx('mb-2', betRowClassName)}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function FeedItem(props: { item: ActivityItem }) {
|
||||
const { item } = props
|
||||
|
||||
switch (item.type) {
|
||||
case 'question':
|
||||
return <FeedQuestion {...item} />
|
||||
case 'description':
|
||||
return <FeedDescription {...item} />
|
||||
case 'bet':
|
||||
return <FeedBet {...item} />
|
||||
case 'liquidity':
|
||||
return <FeedLiquidity {...item} />
|
||||
case 'answergroup':
|
||||
return <FeedAnswerCommentGroup {...item} />
|
||||
case 'close':
|
||||
return <FeedClose {...item} />
|
||||
case 'resolve':
|
||||
return <FeedResolve {...item} />
|
||||
case 'commentInput':
|
||||
return <CommentInput {...item} />
|
||||
case 'commentThread':
|
||||
return <FeedCommentThread {...item} />
|
||||
}
|
||||
}
|
||||
|
||||
export function FeedQuestion(props: {
|
||||
contract: Contract
|
||||
contractPath?: string
|
||||
}) {
|
||||
const { contract } = props
|
||||
const {
|
||||
creatorName,
|
||||
creatorUsername,
|
||||
question,
|
||||
outcomeType,
|
||||
volume,
|
||||
createdTime,
|
||||
isResolved,
|
||||
} = contract
|
||||
const { volumeLabel } = contractMetrics(contract)
|
||||
const isBinary = outcomeType === 'BINARY'
|
||||
const isNew = createdTime > Date.now() - DAY_MS && !isResolved
|
||||
const user = useUser()
|
||||
|
||||
return (
|
||||
<div className={'flex gap-2'}>
|
||||
<Avatar
|
||||
username={contract.creatorUsername}
|
||||
avatarUrl={contract.creatorAvatarUrl}
|
||||
/>
|
||||
<div className="min-w-0 flex-1 py-1.5">
|
||||
<div className="mb-2 text-sm text-gray-500">
|
||||
<UserLink
|
||||
className="text-gray-900"
|
||||
name={creatorName}
|
||||
username={creatorUsername}
|
||||
/>{' '}
|
||||
asked
|
||||
{/* Currently hidden on mobile; ideally we'd fit this in somewhere. */}
|
||||
<div className="relative -top-2 float-right ">
|
||||
{isNew || volume === 0 ? (
|
||||
<NewContractBadge />
|
||||
) : (
|
||||
<span className="hidden text-gray-400 sm:inline">
|
||||
{volumeLabel}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Col className="items-start justify-between gap-2 sm:flex-row sm:gap-4">
|
||||
<SiteLink
|
||||
href={
|
||||
props.contractPath ? props.contractPath : contractPath(contract)
|
||||
}
|
||||
onClick={() => user && trackClick(user.id, contract.id)}
|
||||
className="text-lg text-indigo-700 sm:text-xl"
|
||||
>
|
||||
{question}
|
||||
</SiteLink>
|
||||
{isBinary && (
|
||||
<BinaryResolutionOrChance
|
||||
className="items-center"
|
||||
contract={contract}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function FeedDescription(props: { contract: Contract }) {
|
||||
const { contract } = props
|
||||
const { creatorName, creatorUsername } = contract
|
||||
|
||||
return (
|
||||
<>
|
||||
<Avatar
|
||||
username={contract.creatorUsername}
|
||||
avatarUrl={contract.creatorAvatarUrl}
|
||||
/>
|
||||
<div className="min-w-0 flex-1 py-1.5">
|
||||
<div className="text-sm text-gray-500">
|
||||
<UserLink
|
||||
className="text-gray-900"
|
||||
name={creatorName}
|
||||
username={creatorUsername}
|
||||
/>{' '}
|
||||
created this market <RelativeTimestamp time={contract.createdTime} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function OutcomeIcon(props: { outcome?: string }) {
|
||||
const { outcome } = props
|
||||
switch (outcome) {
|
||||
case 'YES':
|
||||
return <CheckIcon className="h-5 w-5 text-gray-500" aria-hidden="true" />
|
||||
case 'NO':
|
||||
return <XIcon className="h-5 w-5 text-gray-500" aria-hidden="true" />
|
||||
case 'CANCEL':
|
||||
return <BanIcon className="h-5 w-5 text-gray-500" aria-hidden="true" />
|
||||
default:
|
||||
return <CheckIcon className="h-5 w-5 text-gray-500" aria-hidden="true" />
|
||||
}
|
||||
}
|
||||
|
||||
function FeedResolve(props: { contract: Contract }) {
|
||||
const { contract } = props
|
||||
const { creatorName, creatorUsername } = contract
|
||||
|
||||
const resolution = contract.resolution || 'CANCEL'
|
||||
|
||||
const resolutionValue = (contract as NumericContract).resolutionValue
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div className="relative px-1">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200">
|
||||
<OutcomeIcon outcome={resolution} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 py-1.5">
|
||||
<div className="text-sm text-gray-500">
|
||||
<UserLink
|
||||
className="text-gray-900"
|
||||
name={creatorName}
|
||||
username={creatorUsername}
|
||||
/>{' '}
|
||||
resolved this market to{' '}
|
||||
<OutcomeLabel
|
||||
outcome={resolution}
|
||||
value={resolutionValue}
|
||||
contract={contract}
|
||||
truncate="long"
|
||||
/>{' '}
|
||||
<RelativeTimestamp time={contract.resolutionTime || 0} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function FeedClose(props: { contract: Contract }) {
|
||||
const { contract } = props
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<div className="relative px-1">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200">
|
||||
<LockClosedIcon
|
||||
className="h-5 w-5 text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 py-1.5">
|
||||
<div className="text-sm text-gray-500">
|
||||
Trading closed in this market{' '}
|
||||
<RelativeTimestamp time={contract.closeTime || 0} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue
Block a user