// From https://tailwindui.com/components/application-ui/lists/feeds import React, { Fragment, useRef, useState } from 'react' import * as _ from 'lodash' import { BanIcon, CheckIcon, DotsVerticalIcon, LockClosedIcon, UserIcon, UsersIcon, XIcon, } from '@heroicons/react/solid' import clsx from 'clsx' import Textarea from 'react-expanding-textarea' import { OutcomeLabel } from '../outcome-label' import { contractMetrics, Contract, contractPath, tradingAllowed, } from '../../lib/firebase/contracts' import { useUser } from '../../hooks/use-user' import { Linkify } from '../linkify' import { Row } from '../layout/row' import { createComment, MAX_COMMENT_LENGTH } from '../../lib/firebase/comments' import { formatMoney, formatPercent } from '../../../common/util/format' import { Comment } from '../../../common/comment' import { BinaryResolutionOrChance } from '../contract/contract-card' import { SiteLink } from '../site-link' import { Col } from '../layout/col' import { UserLink } from '../user-page' import { DateTimeTooltip } from '../datetime-tooltip' import { Bet } from '../../lib/firebase/bets' import { JoinSpans } from '../join-spans' import { fromNow } from '../../lib/util/time' import BetRow from '../bet-row' import { Avatar } from '../avatar' import { Answer } from '../../../common/answer' import { ActivityItem, GENERAL_COMMENTS_OUTCOME_ID } from './activity-items' import { Binary, CPMM, DPM, FreeResponse, FullContract, } from '../../../common/contract' import { BuyButton } from '../yes-no-selector' import { getDpmOutcomeProbability } from '../../../common/calculate-dpm' import { AnswerBetPanel } from '../answers/answer-bet-panel' import { useSaveSeenContract } from '../../hooks/use-seen-contracts' import { User } from '../../../common/user' import { Modal } from '../layout/modal' import { trackClick } from '../../lib/firebase/tracking' import { firebaseLogin } from '../../lib/firebase/users' import { DAY_MS } from '../../../common/util/time' import NewContractBadge from '../new-contract-badge' import { calculateCpmmSale } from '../../../common/calculate-cpmm' export function FeedItems(props: { contract: Contract items: ActivityItem[] className?: string betRowClassName?: string }) { const { contract, items, className, betRowClassName } = props const { outcomeType } = contract const ref = useRef(null) useSaveSeenContract(ref, contract) return (
{items.map((item, activityItemIdx) => (
{activityItemIdx !== items.length - 1 || item.type === 'answergroup' ? (
))}
{outcomeType === 'BINARY' && tradingAllowed(contract) && ( )}
) } function FeedItem(props: { item: ActivityItem }) { const { item } = props switch (item.type) { case 'question': return case 'description': return case 'comment': return case 'bet': return case 'betgroup': return case 'answergroup': return case 'answer': return case 'close': return case 'resolve': return case 'commentInput': return } } export function FeedComment(props: { contract: Contract comment: Comment betsBySameUser: Bet[] hideOutcome: boolean truncate: boolean smallAvatar: boolean }) { const { contract, comment, betsBySameUser, hideOutcome, truncate, smallAvatar, } = props const { text, userUsername, userName, userAvatarUrl, createdTime } = comment let outcome: string | undefined, bought: string | undefined, money: string | undefined const matchedBet = betsBySameUser.find((bet) => bet.id === comment.betId) if (matchedBet) { outcome = matchedBet.outcome bought = matchedBet.amount >= 0 ? 'bought' : 'sold' money = formatMoney(Math.abs(matchedBet.amount)) } // Only calculated if they don't have a matching bet const { userPosition, userPositionMoney, yesFloorShares, noFloorShares } = getBettorsPosition( contract, comment.createdTime, matchedBet ? [] : betsBySameUser ) return ( <>

{' '} {!matchedBet && userPosition > 0 && ( <> {'had ' + userPositionMoney + ' '} <> {' of '} noFloorShares ? 'YES' : 'NO'} contract={contract} truncate="short" /> )} <> {bought} {money} {outcome && !hideOutcome && ( <> {' '} of{' '} )}

) } export function CommentInput(props: { contract: Contract betsByCurrentUser: Bet[] comments: Comment[] // Only for free response comment inputs answerOutcome?: string }) { const { contract, betsByCurrentUser, comments, answerOutcome } = props const user = useUser() const [comment, setComment] = useState('') const [focused, setFocused] = useState(false) // Should this be oldest bet or most recent bet? const mostRecentCommentableBet = betsByCurrentUser .filter((bet) => { if ( canCommentOnBet(bet, user) && // The bet doesn't already have a comment !comments.some((comment) => comment.betId == bet.id) ) { if (!answerOutcome) return true // If we're in free response, don't allow commenting on ante bet return ( bet.outcome !== GENERAL_COMMENTS_OUTCOME_ID && answerOutcome === bet.outcome ) } return false }) .sort((b1, b2) => b1.createdTime - b2.createdTime) .pop() const { id } = mostRecentCommentableBet || { id: undefined } async function submitComment(betId: string | undefined) { if (!user) { return await firebaseLogin() } if (!comment) return await createComment(contract.id, comment, user, betId, answerOutcome) setComment('') } const { userPosition, userPositionMoney, yesFloorShares, noFloorShares } = getBettorsPosition(contract, Date.now(), betsByCurrentUser) return ( <>
{mostRecentCommentableBet && ( )} {!mostRecentCommentableBet && user && userPosition > 0 && ( <> {'You have ' + userPositionMoney + ' '} <> {' of '} noFloorShares ? 'YES' : 'NO'} contract={contract} truncate="short" /> )} {(answerOutcome === undefined || focused) && (