Show comments position (#110)
* Add betting activity back to feed * Show position in bin. markets, no comments on bets * Degroup bets on Bets tab * Show users position or recent bet with comments * Add tooltip on answer to FR comments * Style improvements * Only use bets by current user for comment input
This commit is contained in:
parent
5cb6ee3bca
commit
78997c1e45
|
@ -31,6 +31,8 @@ type BaseActivityItem = {
|
||||||
|
|
||||||
export type CommentInputItem = BaseActivityItem & {
|
export type CommentInputItem = BaseActivityItem & {
|
||||||
type: 'commentInput'
|
type: 'commentInput'
|
||||||
|
betsByCurrentUser: Bet[]
|
||||||
|
comments: Comment[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DescriptionItem = BaseActivityItem & {
|
export type DescriptionItem = BaseActivityItem & {
|
||||||
|
@ -48,12 +50,13 @@ export type BetItem = BaseActivityItem & {
|
||||||
bet: Bet
|
bet: Bet
|
||||||
hideOutcome: boolean
|
hideOutcome: boolean
|
||||||
smallAvatar: boolean
|
smallAvatar: boolean
|
||||||
|
hideComment?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CommentItem = BaseActivityItem & {
|
export type CommentItem = BaseActivityItem & {
|
||||||
type: 'comment'
|
type: 'comment'
|
||||||
comment: Comment
|
comment: Comment
|
||||||
bet: Bet | undefined
|
betsBySameUser: Bet[]
|
||||||
hideOutcome: boolean
|
hideOutcome: boolean
|
||||||
truncate: boolean
|
truncate: boolean
|
||||||
smallAvatar: boolean
|
smallAvatar: boolean
|
||||||
|
@ -129,7 +132,7 @@ function groupBets(
|
||||||
type: 'comment' as const,
|
type: 'comment' as const,
|
||||||
id: bet.id,
|
id: bet.id,
|
||||||
comment,
|
comment,
|
||||||
bet,
|
betsBySameUser: [bet],
|
||||||
contract,
|
contract,
|
||||||
hideOutcome,
|
hideOutcome,
|
||||||
truncate: abbreviated,
|
truncate: abbreviated,
|
||||||
|
@ -280,7 +283,7 @@ function groupBetsAndComments(
|
||||||
id: comment.id,
|
id: comment.id,
|
||||||
contract: contract,
|
contract: contract,
|
||||||
comment,
|
comment,
|
||||||
bet: undefined,
|
betsBySameUser: [],
|
||||||
truncate: abbreviated,
|
truncate: abbreviated,
|
||||||
hideOutcome: true,
|
hideOutcome: true,
|
||||||
smallAvatar,
|
smallAvatar,
|
||||||
|
@ -308,6 +311,27 @@ function groupBetsAndComments(
|
||||||
return abbrItems
|
return abbrItems
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCommentsWithPositions(
|
||||||
|
bets: Bet[],
|
||||||
|
comments: Comment[],
|
||||||
|
contract: Contract
|
||||||
|
) {
|
||||||
|
const betsByUserId = _.groupBy(bets, (bet) => bet.userId)
|
||||||
|
|
||||||
|
const items = comments.map((comment) => ({
|
||||||
|
type: 'comment' as const,
|
||||||
|
id: comment.id,
|
||||||
|
contract: contract,
|
||||||
|
comment,
|
||||||
|
betsBySameUser: bets.length === 0 ? [] : betsByUserId[comment.userId] ?? [],
|
||||||
|
truncate: true,
|
||||||
|
hideOutcome: false,
|
||||||
|
smallAvatar: false,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
export function getAllContractActivityItems(
|
export function getAllContractActivityItems(
|
||||||
contract: Contract,
|
contract: Contract,
|
||||||
bets: Bet[],
|
bets: Bet[],
|
||||||
|
@ -361,6 +385,8 @@ export function getAllContractActivityItems(
|
||||||
type: 'commentInput',
|
type: 'commentInput',
|
||||||
id: 'commentInput',
|
id: 'commentInput',
|
||||||
contract,
|
contract,
|
||||||
|
betsByCurrentUser: [],
|
||||||
|
comments: [],
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
items.push(
|
items.push(
|
||||||
|
@ -385,6 +411,8 @@ export function getAllContractActivityItems(
|
||||||
type: 'commentInput',
|
type: 'commentInput',
|
||||||
id: 'commentInput',
|
id: 'commentInput',
|
||||||
contract,
|
contract,
|
||||||
|
betsByCurrentUser: [],
|
||||||
|
comments: [],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,24 +460,13 @@ export function getRecentContractActivityItems(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const onlyUsersBetsOrBetsWithComments = bets.filter((bet) =>
|
|
||||||
comments.some(
|
|
||||||
(comment) => comment.betId === bet.id || bet.userId === user?.id
|
|
||||||
)
|
|
||||||
)
|
|
||||||
items.push(
|
items.push(
|
||||||
...groupBetsAndComments(
|
...groupBetsAndComments(bets, comments, contract, user?.id, {
|
||||||
onlyUsersBetsOrBetsWithComments,
|
hideOutcome: false,
|
||||||
comments,
|
abbreviated: true,
|
||||||
contract,
|
smallAvatar: false,
|
||||||
user?.id,
|
reversed: true,
|
||||||
{
|
})
|
||||||
hideOutcome: false,
|
|
||||||
abbreviated: true,
|
|
||||||
smallAvatar: false,
|
|
||||||
reversed: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,37 +488,29 @@ export function getSpecificContractActivityItems(
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 'bets':
|
case 'bets':
|
||||||
items.push(
|
items.push(
|
||||||
...groupBets(bets, comments, contract, user?.id, {
|
...bets.map((bet) => ({
|
||||||
|
type: 'bet' as const,
|
||||||
|
id: bet.id,
|
||||||
|
bet,
|
||||||
|
contract,
|
||||||
hideOutcome: false,
|
hideOutcome: false,
|
||||||
abbreviated: false,
|
|
||||||
smallAvatar: false,
|
smallAvatar: false,
|
||||||
reversed: false,
|
hideComment: true,
|
||||||
})
|
}))
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'comments':
|
case 'comments':
|
||||||
const onlyBetsWithComments = bets.filter((bet) =>
|
items.push(...getCommentsWithPositions(bets, comments, contract))
|
||||||
comments.some((comment) => comment.betId === bet.id)
|
|
||||||
)
|
|
||||||
items.push(
|
|
||||||
...groupBetsAndComments(
|
|
||||||
onlyBetsWithComments,
|
|
||||||
comments,
|
|
||||||
contract,
|
|
||||||
user?.id,
|
|
||||||
{
|
|
||||||
hideOutcome: false,
|
|
||||||
abbreviated: false,
|
|
||||||
smallAvatar: false,
|
|
||||||
reversed: false,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
items.push({
|
items.push({
|
||||||
type: 'commentInput',
|
type: 'commentInput',
|
||||||
id: 'commentInput',
|
id: 'commentInput',
|
||||||
contract,
|
contract,
|
||||||
|
betsByCurrentUser: user
|
||||||
|
? bets.filter((bet) => bet.userId === user.id)
|
||||||
|
: [],
|
||||||
|
comments: comments,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,13 @@ import BetRow from '../bet-row'
|
||||||
import { Avatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
import { Answer } from '../../../common/answer'
|
import { Answer } from '../../../common/answer'
|
||||||
import { ActivityItem } from './activity-items'
|
import { ActivityItem } from './activity-items'
|
||||||
import { FreeResponse, FullContract } from '../../../common/contract'
|
import {
|
||||||
|
Binary,
|
||||||
|
CPMM,
|
||||||
|
DPM,
|
||||||
|
FreeResponse,
|
||||||
|
FullContract,
|
||||||
|
} from '../../../common/contract'
|
||||||
import { BuyButton } from '../yes-no-selector'
|
import { BuyButton } from '../yes-no-selector'
|
||||||
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
|
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
|
||||||
import { AnswerBetPanel } from '../answers/answer-bet-panel'
|
import { AnswerBetPanel } from '../answers/answer-bet-panel'
|
||||||
|
@ -50,6 +56,7 @@ import { trackClick } from '../../lib/firebase/tracking'
|
||||||
import { firebaseLogin } from '../../lib/firebase/users'
|
import { firebaseLogin } from '../../lib/firebase/users'
|
||||||
import { DAY_MS } from '../../../common/util/time'
|
import { DAY_MS } from '../../../common/util/time'
|
||||||
import NewContractBadge from '../new-contract-badge'
|
import NewContractBadge from '../new-contract-badge'
|
||||||
|
import { calculateCpmmSale } from '../../../common/calculate-cpmm'
|
||||||
|
|
||||||
export function FeedItems(props: {
|
export function FeedItems(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
|
@ -123,21 +130,38 @@ function FeedItem(props: { item: ActivityItem }) {
|
||||||
export function FeedComment(props: {
|
export function FeedComment(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
comment: Comment
|
comment: Comment
|
||||||
bet: Bet | undefined
|
betsBySameUser: Bet[]
|
||||||
hideOutcome: boolean
|
hideOutcome: boolean
|
||||||
truncate: boolean
|
truncate: boolean
|
||||||
smallAvatar: boolean
|
smallAvatar: boolean
|
||||||
}) {
|
}) {
|
||||||
const { contract, comment, bet, hideOutcome, truncate, smallAvatar } = props
|
const {
|
||||||
let money: string | undefined
|
contract,
|
||||||
let outcome: string | undefined
|
comment,
|
||||||
let bought: string | undefined
|
betsBySameUser,
|
||||||
if (bet) {
|
hideOutcome,
|
||||||
outcome = bet.outcome
|
truncate,
|
||||||
bought = bet.amount >= 0 ? 'bought' : 'sold'
|
smallAvatar,
|
||||||
money = formatMoney(Math.abs(bet.amount))
|
} = props
|
||||||
}
|
|
||||||
const { text, userUsername, userName, userAvatarUrl, createdTime } = comment
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -155,18 +179,33 @@ export function FeedComment(props: {
|
||||||
username={userUsername}
|
username={userUsername}
|
||||||
name={userName}
|
name={userName}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
{bought} {money}
|
{!matchedBet && userPosition > 0 && (
|
||||||
{!hideOutcome && (
|
|
||||||
<>
|
<>
|
||||||
{' '}
|
{'with ' + userPositionMoney + ' '}
|
||||||
of{' '}
|
<>
|
||||||
<OutcomeLabel
|
{' of '}
|
||||||
outcome={outcome ? outcome : ''}
|
<OutcomeLabel
|
||||||
contract={contract}
|
outcome={yesFloorShares > noFloorShares ? 'YES' : 'NO'}
|
||||||
truncate="short"
|
contract={contract}
|
||||||
/>
|
truncate="short"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<>
|
||||||
|
{bought} {money}
|
||||||
|
{outcome && !hideOutcome && (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
of{' '}
|
||||||
|
<OutcomeLabel
|
||||||
|
outcome={outcome ? outcome : ''}
|
||||||
|
contract={contract}
|
||||||
|
truncate="short"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
<RelativeTimestamp time={createdTime} />
|
<RelativeTimestamp time={createdTime} />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -180,20 +219,12 @@ export function FeedComment(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function RelativeTimestamp(props: { time: number }) {
|
export function CommentInput(props: {
|
||||||
const { time } = props
|
contract: Contract
|
||||||
return (
|
betsByCurrentUser: Bet[]
|
||||||
<DateTimeTooltip time={time}>
|
comments: Comment[]
|
||||||
<span className="ml-1 whitespace-nowrap text-gray-400">
|
}) {
|
||||||
{fromNow(time)}
|
const { contract, betsByCurrentUser, comments } = props
|
||||||
</span>
|
|
||||||
</DateTimeTooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function CommentInput(props: { contract: Contract }) {
|
|
||||||
// see if we can comment input on any bet:
|
|
||||||
const { contract } = props
|
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const [comment, setComment] = useState('')
|
const [comment, setComment] = useState('')
|
||||||
|
|
||||||
|
@ -206,14 +237,50 @@ export function CommentInput(props: { contract: Contract }) {
|
||||||
setComment('')
|
setComment('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Should this be oldest bet or most recent bet?
|
||||||
|
const mostRecentCommentableBet = betsByCurrentUser
|
||||||
|
.filter(
|
||||||
|
(bet) =>
|
||||||
|
canCommentOnBet(bet.userId, bet.createdTime, user) &&
|
||||||
|
!comments.some((comment) => comment.betId == bet.id)
|
||||||
|
)
|
||||||
|
.sort((b1, b2) => b1.createdTime - b2.createdTime)
|
||||||
|
.pop()
|
||||||
|
|
||||||
|
if (mostRecentCommentableBet) {
|
||||||
|
return (
|
||||||
|
<FeedBet
|
||||||
|
contract={contract}
|
||||||
|
bet={mostRecentCommentableBet}
|
||||||
|
hideOutcome={false}
|
||||||
|
smallAvatar={false}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const { userPosition, userPositionMoney, yesFloorShares, noFloorShares } =
|
||||||
|
getBettorsPosition(contract, Date.now(), betsByCurrentUser)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row className={'flex w-full gap-2 pt-5'}>
|
<Row className={'flex w-full gap-2 pt-3'}>
|
||||||
<div>
|
<div>
|
||||||
<Avatar avatarUrl={user?.avatarUrl} username={user?.username} />
|
<Avatar avatarUrl={user?.avatarUrl} username={user?.username} />
|
||||||
</div>
|
</div>
|
||||||
<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">
|
||||||
|
{user && userPosition > 0 && (
|
||||||
|
<>
|
||||||
|
{'You with ' + userPositionMoney + ' '}
|
||||||
|
<>
|
||||||
|
{' of '}
|
||||||
|
<OutcomeLabel
|
||||||
|
outcome={yesFloorShares > noFloorShares ? 'YES' : 'NO'}
|
||||||
|
contract={contract}
|
||||||
|
truncate="short"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<Textarea
|
<Textarea
|
||||||
value={comment}
|
value={comment}
|
||||||
|
@ -244,18 +311,76 @@ export function CommentInput(props: { contract: Contract }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RelativeTimestamp(props: { time: number }) {
|
||||||
|
const { time } = props
|
||||||
|
return (
|
||||||
|
<DateTimeTooltip time={time}>
|
||||||
|
<span className="ml-1 whitespace-nowrap text-gray-400">
|
||||||
|
{fromNow(time)}
|
||||||
|
</span>
|
||||||
|
</DateTimeTooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBettorsPosition(
|
||||||
|
contract: Contract,
|
||||||
|
createdTime: number,
|
||||||
|
bets: Bet[]
|
||||||
|
) {
|
||||||
|
let yesFloorShares = 0,
|
||||||
|
yesShares = 0,
|
||||||
|
noShares = 0,
|
||||||
|
noFloorShares = 0
|
||||||
|
|
||||||
|
const emptyReturn = {
|
||||||
|
userPosition: 0,
|
||||||
|
userPositionMoney: 0,
|
||||||
|
yesFloorShares,
|
||||||
|
noFloorShares,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: show which of the answers was their majority stake at time of comment for FR?
|
||||||
|
if (contract.outcomeType != 'BINARY') {
|
||||||
|
return emptyReturn
|
||||||
|
}
|
||||||
|
if (bets.length === 0) {
|
||||||
|
return emptyReturn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the majority shares they had when they made the comment
|
||||||
|
const betsBefore = bets.filter((prevBet) => prevBet.createdTime < createdTime)
|
||||||
|
const [yesBets, noBets] = _.partition(
|
||||||
|
betsBefore ?? [],
|
||||||
|
(bet) => bet.outcome === 'YES'
|
||||||
|
)
|
||||||
|
yesShares = _.sumBy(yesBets, (bet) => bet.shares)
|
||||||
|
noShares = _.sumBy(noBets, (bet) => bet.shares)
|
||||||
|
yesFloorShares = Math.floor(yesShares)
|
||||||
|
noFloorShares = Math.floor(noShares)
|
||||||
|
|
||||||
|
const userPosition = yesFloorShares || noFloorShares
|
||||||
|
const { saleValue } = calculateCpmmSale(
|
||||||
|
contract as FullContract<CPMM, Binary>,
|
||||||
|
yesShares || noShares,
|
||||||
|
yesFloorShares > noFloorShares ? 'YES' : 'NO'
|
||||||
|
)
|
||||||
|
const userPositionMoney = formatMoney(Math.abs(saleValue))
|
||||||
|
return { userPosition, userPositionMoney, yesFloorShares, noFloorShares }
|
||||||
|
}
|
||||||
|
|
||||||
export function FeedBet(props: {
|
export function FeedBet(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
bet: Bet
|
bet: Bet
|
||||||
hideOutcome: boolean
|
hideOutcome: boolean
|
||||||
smallAvatar: boolean
|
smallAvatar: boolean
|
||||||
|
hideComment?: boolean
|
||||||
bettor?: User // If set: reveal bettor identity
|
bettor?: User // If set: reveal bettor identity
|
||||||
}) {
|
}) {
|
||||||
const { contract, bet, hideOutcome, smallAvatar, bettor } = props
|
const { contract, bet, hideOutcome, smallAvatar, bettor, hideComment } = props
|
||||||
const { id, amount, outcome, createdTime, userId } = bet
|
const { id, amount, outcome, createdTime, userId } = bet
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const isSelf = user?.id === userId
|
const isSelf = user?.id === userId
|
||||||
const canComment = canCommentOnBet(userId, createdTime, user)
|
const canComment = canCommentOnBet(userId, createdTime, user) && !hideComment
|
||||||
|
|
||||||
const [comment, setComment] = useState('')
|
const [comment, setComment] = useState('')
|
||||||
async function submitComment() {
|
async function submitComment() {
|
||||||
|
@ -268,71 +393,76 @@ export function FeedBet(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<Row className={'flex w-full gap-2 pt-3'}>
|
||||||
{isSelf ? (
|
<div>
|
||||||
<Avatar
|
{isSelf ? (
|
||||||
className={clsx(smallAvatar && 'ml-1')}
|
<Avatar
|
||||||
size={smallAvatar ? 'sm' : undefined}
|
className={clsx(smallAvatar && 'ml-1')}
|
||||||
avatarUrl={user.avatarUrl}
|
size={smallAvatar ? 'sm' : undefined}
|
||||||
username={user.username}
|
avatarUrl={user.avatarUrl}
|
||||||
/>
|
username={user.username}
|
||||||
) : bettor ? (
|
/>
|
||||||
<Avatar
|
) : bettor ? (
|
||||||
className={clsx(smallAvatar && 'ml-1')}
|
<Avatar
|
||||||
size={smallAvatar ? 'sm' : undefined}
|
className={clsx(smallAvatar && 'ml-1')}
|
||||||
avatarUrl={bettor.avatarUrl}
|
size={smallAvatar ? 'sm' : undefined}
|
||||||
username={bettor.username}
|
avatarUrl={bettor.avatarUrl}
|
||||||
/>
|
username={bettor.username}
|
||||||
) : (
|
/>
|
||||||
<div className="relative px-1">
|
) : (
|
||||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200">
|
<div className="relative px-1">
|
||||||
<UserIcon className="h-5 w-5 text-gray-500" aria-hidden="true" />
|
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200">
|
||||||
</div>
|
<UserIcon
|
||||||
</div>
|
className="h-5 w-5 text-gray-500"
|
||||||
)}
|
aria-hidden="true"
|
||||||
</div>
|
/>
|
||||||
<div className={'min-w-0 flex-1 py-1.5'}>
|
</div>
|
||||||
<div className="text-sm text-gray-500">
|
|
||||||
<span>{isSelf ? 'You' : bettor ? bettor.name : 'A trader'}</span>{' '}
|
|
||||||
{bought} {money}
|
|
||||||
{!hideOutcome && (
|
|
||||||
<>
|
|
||||||
{' '}
|
|
||||||
of{' '}
|
|
||||||
<OutcomeLabel
|
|
||||||
outcome={outcome}
|
|
||||||
contract={contract}
|
|
||||||
truncate="short"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<RelativeTimestamp time={createdTime} />
|
|
||||||
{(canComment || comment) && (
|
|
||||||
<div className="mt-2">
|
|
||||||
<Textarea
|
|
||||||
value={comment}
|
|
||||||
onChange={(e) => setComment(e.target.value)}
|
|
||||||
className="textarea textarea-bordered w-full resize-none"
|
|
||||||
placeholder="Add a comment..."
|
|
||||||
rows={3}
|
|
||||||
maxLength={MAX_COMMENT_LENGTH}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
|
||||||
submitComment()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="btn btn-outline btn-sm text-transform: mt-1 capitalize"
|
|
||||||
onClick={submitComment}
|
|
||||||
disabled={!canComment}
|
|
||||||
>
|
|
||||||
Comment
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className={'min-w-0 flex-1 py-1.5'}>
|
||||||
|
<div className="text-sm text-gray-500">
|
||||||
|
<span>{isSelf ? 'You' : bettor ? bettor.name : 'A trader'}</span>{' '}
|
||||||
|
{bought} {money}
|
||||||
|
{!hideOutcome && (
|
||||||
|
<>
|
||||||
|
{' '}
|
||||||
|
of{' '}
|
||||||
|
<OutcomeLabel
|
||||||
|
outcome={outcome}
|
||||||
|
contract={contract}
|
||||||
|
truncate="short"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<RelativeTimestamp time={createdTime} />
|
||||||
|
{(canComment || comment) && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<Textarea
|
||||||
|
value={comment}
|
||||||
|
onChange={(e) => setComment(e.target.value)}
|
||||||
|
className="textarea textarea-bordered w-full resize-none"
|
||||||
|
placeholder="Add a comment..."
|
||||||
|
rows={3}
|
||||||
|
maxLength={MAX_COMMENT_LENGTH}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||||
|
submitComment()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="btn btn-outline btn-sm text-transform: mt-1 capitalize"
|
||||||
|
onClick={submitComment}
|
||||||
|
disabled={!canComment}
|
||||||
|
>
|
||||||
|
Comment
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import clsx from 'clsx'
|
|
||||||
import { Answer } from '../../common/answer'
|
import { Answer } from '../../common/answer'
|
||||||
import { getProbability } from '../../common/calculate'
|
import { getProbability } from '../../common/calculate'
|
||||||
import {
|
import {
|
||||||
|
@ -11,6 +10,7 @@ import {
|
||||||
FullContract,
|
FullContract,
|
||||||
} from '../../common/contract'
|
} from '../../common/contract'
|
||||||
import { formatPercent } from '../../common/util/format'
|
import { formatPercent } from '../../common/util/format'
|
||||||
|
import { ClientRender } from './client-render'
|
||||||
|
|
||||||
export function OutcomeLabel(props: {
|
export function OutcomeLabel(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
|
@ -72,11 +72,13 @@ export function FreeResponseOutcomeLabel(props: {
|
||||||
const chosen = answers?.find((answer) => answer.id === resolution)
|
const chosen = answers?.find((answer) => answer.id === resolution)
|
||||||
if (!chosen) return <AnswerNumberLabel number={resolution} />
|
if (!chosen) return <AnswerNumberLabel number={resolution} />
|
||||||
return (
|
return (
|
||||||
<AnswerLabel
|
<FreeResponseAnswerToolTip text={chosen.text}>
|
||||||
answer={chosen}
|
<AnswerLabel
|
||||||
truncate={truncate}
|
answer={chosen}
|
||||||
className={answerClassName}
|
truncate={truncate}
|
||||||
/>
|
className={answerClassName}
|
||||||
|
/>
|
||||||
|
</FreeResponseAnswerToolTip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,3 +128,23 @@ export function AnswerLabel(props: {
|
||||||
|
|
||||||
return <span className={className}>{truncated}</span>
|
return <span className={className}>{truncated}</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function FreeResponseAnswerToolTip(props: {
|
||||||
|
text: string
|
||||||
|
children?: React.ReactNode
|
||||||
|
}) {
|
||||||
|
const { text } = props
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ClientRender>
|
||||||
|
<span
|
||||||
|
className="tooltip hidden cursor-default sm:inline-block"
|
||||||
|
data-tip={text}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</span>
|
||||||
|
</ClientRender>
|
||||||
|
<span className="whitespace-nowrap sm:hidden">{props.children}</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -287,7 +287,7 @@ function ContractTopTrades(props: {
|
||||||
<FeedComment
|
<FeedComment
|
||||||
contract={contract}
|
contract={contract}
|
||||||
comment={commentsById[topCommentId]}
|
comment={commentsById[topCommentId]}
|
||||||
bet={betsById[topCommentId]}
|
betsBySameUser={[betsById[topCommentId]]}
|
||||||
hideOutcome={false}
|
hideOutcome={false}
|
||||||
truncate={false}
|
truncate={false}
|
||||||
smallAvatar={false}
|
smallAvatar={false}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user