diff --git a/web/components/answers/answers-panel.tsx b/web/components/answers/answers-panel.tsx
index 6e0bfef6..3de99c34 100644
--- a/web/components/answers/answers-panel.tsx
+++ b/web/components/answers/answers-panel.tsx
@@ -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
diff --git a/web/components/contract/contract-tabs.tsx b/web/components/contract/contract-tabs.tsx
index 6d6e6e4f..47dcfd15 100644
--- a/web/components/contract/contract-tabs.tsx
+++ b/web/components/contract/contract-tabs.tsx
@@ -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,43 +49,42 @@ export function ContractTabs(props: {
const betActivity = (
)
- const commentActivity = (
- <>
-
- {outcomeType === 'FREE_RESPONSE' && (
+ const commentActivity =
+ outcomeType === 'FREE_RESPONSE' ? (
+ <>
+
@@ -92,19 +101,39 @@ export function ContractTabs(props: {
)
return (
-
+ <>
+
+ {!user ? (
+
+
+
+
+ ) : (
+ outcomeType === 'BINARY' &&
+ tradingAllowed(contract) && (
+
+ )
+ )}
+ >
)
}
diff --git a/web/components/feed/activity-items.ts b/web/components/feed/activity-items.ts
deleted file mode 100644
index 8e07db84..00000000
--- a/web/components/feed/activity-items.ts
+++ /dev/null
@@ -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()
-}
diff --git a/web/components/feed/contract-activity.tsx b/web/components/feed/contract-activity.tsx
index 45b5d2c7..4dd5d76a 100644
--- a/web/components/feed/contract-activity.tsx
+++ b/web/components/feed/contract-activity.tsx
@@ -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 (
-
+
+
+ {parentComments.map((parent, idx) => (
+
+ {idx !== parentComments.length - 1 ? (
+
+ ) : null}
+
+
+ ))}
+
+ )
+}
+
+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 (
+
+ {answers.map((answer) => (
+
+
+
+
+ ))}
+
)
}
diff --git a/web/components/feed/feed-answer-comment-group.tsx b/web/components/feed/feed-answer-comment-group.tsx
index 86686f1f..e57ad40d 100644
--- a/web/components/feed/feed-answer-comment-group.tsx
+++ b/web/components/feed/feed-answer-comment-group.tsx
@@ -101,7 +101,10 @@ export function FeedAnswerCommentGroup(props: {
}, [answerElementId, router.asPath])
return (
-
+
+
diff --git a/web/components/feed/feed-items.tsx b/web/components/feed/feed-items.tsx
deleted file mode 100644
index 4a121120..00000000
--- a/web/components/feed/feed-items.tsx
+++ /dev/null
@@ -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 (
-
-
- {items.map((item, activityItemIdx) => (
-
- {activityItemIdx !== items.length - 1 ||
- item.type === 'answergroup' ? (
-
- ) : null}
-
-
-
-
- ))}
-
-
- {!user ? (
-
-
-
-
- ) : (
- outcomeType === 'BINARY' &&
- tradingAllowed(contract) && (
-
- )
- )}
-
- )
-}
-
-export function FeedItem(props: { item: ActivityItem }) {
- const { item } = props
-
- switch (item.type) {
- case 'question':
- return
- case 'description':
- return
- case 'bet':
- return
- case 'liquidity':
- return
- case 'answergroup':
- return
- case 'close':
- return
- case 'resolve':
- return
- case 'commentInput':
- return
- case 'commentThread':
- return
- }
-}
-
-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 (
-
-
-
-
-
{' '}
- asked
- {/* Currently hidden on mobile; ideally we'd fit this in somewhere. */}
-
- {isNew || volume === 0 ? (
-
- ) : (
-
- {volumeLabel}
-
- )}
-
-
-
-
user && trackClick(user.id, contract.id)}
- className="text-lg text-indigo-700 sm:text-xl"
- >
- {question}
-
- {isBinary && (
-
- )}
-
-
-
- )
-}
-
-function FeedDescription(props: { contract: Contract }) {
- const { contract } = props
- const { creatorName, creatorUsername } = contract
-
- return (
- <>
-
-
-
- {' '}
- created this market
-
-
- >
- )
-}
-
-function OutcomeIcon(props: { outcome?: string }) {
- const { outcome } = props
- switch (outcome) {
- case 'YES':
- return
- case 'NO':
- return
- case 'CANCEL':
- return
- default:
- return
- }
-}
-
-function FeedResolve(props: { contract: Contract }) {
- const { contract } = props
- const { creatorName, creatorUsername } = contract
-
- const resolution = contract.resolution || 'CANCEL'
-
- const resolutionValue = (contract as NumericContract).resolutionValue
-
- return (
- <>
-
-
-
- {' '}
- resolved this market to{' '}
- {' '}
-
-
-
- >
- )
-}
-
-function FeedClose(props: { contract: Contract }) {
- const { contract } = props
-
- return (
- <>
-
-
-
- Trading closed in this market{' '}
-
-
-
- >
- )
-}