Show users position or recent bet with comments
This commit is contained in:
parent
3e600f45b5
commit
d0700c1221
|
@ -31,6 +31,8 @@ type BaseActivityItem = {
|
||||||
|
|
||||||
export type CommentInputItem = BaseActivityItem & {
|
export type CommentInputItem = BaseActivityItem & {
|
||||||
type: 'commentInput'
|
type: 'commentInput'
|
||||||
|
bets: 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,37 @@ function groupBetsAndComments(
|
||||||
return abbrItems
|
return abbrItems
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCommentsWithPositions(
|
||||||
|
bets: Bet[],
|
||||||
|
comments: Comment[],
|
||||||
|
contract: Contract
|
||||||
|
) {
|
||||||
|
function mapBetsByUserId(bets: Bet[]) {
|
||||||
|
return bets.reduce((acc, bet) => {
|
||||||
|
const userId = bet.userId
|
||||||
|
if (!acc[userId]) {
|
||||||
|
acc[userId] = []
|
||||||
|
}
|
||||||
|
acc[userId].push(bet)
|
||||||
|
return acc
|
||||||
|
}, {} as { [userId: string]: Bet[] })
|
||||||
|
}
|
||||||
|
const betsByUserId = mapBetsByUserId(bets)
|
||||||
|
|
||||||
|
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 +395,8 @@ export function getAllContractActivityItems(
|
||||||
type: 'commentInput',
|
type: 'commentInput',
|
||||||
id: 'commentInput',
|
id: 'commentInput',
|
||||||
contract,
|
contract,
|
||||||
|
bets: [],
|
||||||
|
comments: [],
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
items.push(
|
items.push(
|
||||||
|
@ -385,6 +421,8 @@ export function getAllContractActivityItems(
|
||||||
type: 'commentInput',
|
type: 'commentInput',
|
||||||
id: 'commentInput',
|
id: 'commentInput',
|
||||||
contract,
|
contract,
|
||||||
|
bets: [],
|
||||||
|
comments: [],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,32 +505,20 @@ export function getSpecificContractActivityItems(
|
||||||
contract,
|
contract,
|
||||||
hideOutcome: false,
|
hideOutcome: false,
|
||||||
smallAvatar: false,
|
smallAvatar: 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,
|
||||||
|
bets: bets,
|
||||||
|
comments: comments,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,8 +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 { useUserContractBets } from '../../hooks/use-user-bets'
|
import { calculateCpmmSale } from '../../../common/calculate-cpmm'
|
||||||
import { useSaveShares } from '../use-save-shares'
|
|
||||||
|
|
||||||
export function FeedItems(props: {
|
export function FeedItems(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
|
@ -131,29 +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, userId } =
|
let outcome: string | undefined,
|
||||||
comment
|
bought: string | undefined,
|
||||||
|
money: string | undefined
|
||||||
|
|
||||||
const userBets = useUserContractBets(userId, contract.id)
|
const matchedBet = betsBySameUser.find((bet) => bet.id === comment.betId)
|
||||||
const { yesFloorShares, noFloorShares } = useSaveShares(
|
if (matchedBet) {
|
||||||
contract as FullContract<CPMM | DPM, Binary>,
|
outcome = matchedBet.outcome
|
||||||
userBets
|
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
|
||||||
)
|
)
|
||||||
const userPosition = yesFloorShares || noFloorShares
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -171,10 +179,9 @@ export function FeedComment(props: {
|
||||||
username={userUsername}
|
username={userUsername}
|
||||||
name={userName}
|
name={userName}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
{contract.outcomeType === 'BINARY' ? (
|
{!matchedBet && userPosition > 0 && (
|
||||||
userPosition > 0 && (
|
|
||||||
<>
|
<>
|
||||||
{'owns ' + userPosition + ' shares '}
|
{'with ' + userPositionMoney + ' '}
|
||||||
<>
|
<>
|
||||||
{' of '}
|
{' of '}
|
||||||
<OutcomeLabel
|
<OutcomeLabel
|
||||||
|
@ -184,11 +191,10 @@ export function FeedComment(props: {
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
</>
|
</>
|
||||||
)
|
)}
|
||||||
) : (
|
|
||||||
<>
|
<>
|
||||||
{bought} {money}
|
{bought} {money}
|
||||||
{!hideOutcome && (
|
{outcome && !hideOutcome && (
|
||||||
<>
|
<>
|
||||||
{' '}
|
{' '}
|
||||||
of{' '}
|
of{' '}
|
||||||
|
@ -200,7 +206,6 @@ export function FeedComment(props: {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
|
||||||
<RelativeTimestamp time={createdTime} />
|
<RelativeTimestamp time={createdTime} />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -214,20 +219,12 @@ export function FeedComment(props: {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function RelativeTimestamp(props: { time: number }) {
|
export function CommentInput(props: {
|
||||||
const { time } = props
|
contract: Contract
|
||||||
return (
|
bets: Bet[]
|
||||||
<DateTimeTooltip time={time}>
|
comments: Comment[]
|
||||||
<span className="ml-1 whitespace-nowrap text-gray-400">
|
}) {
|
||||||
{fromNow(time)}
|
const { contract, bets, 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('')
|
||||||
|
|
||||||
|
@ -240,14 +237,50 @@ export function CommentInput(props: { contract: Contract }) {
|
||||||
setComment('')
|
setComment('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Should this be oldest bet or most recent bet?
|
||||||
|
const mostRecentCommentableBet = bets
|
||||||
|
.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(), bets)
|
||||||
|
|
||||||
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}
|
||||||
|
@ -278,20 +311,68 @@ 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
|
||||||
|
// TODO: show which of the answers was their majority stake at time of comment for FR?
|
||||||
|
if (contract.outcomeType != 'BINARY') {
|
||||||
|
return [0, 0, 0, 0]
|
||||||
|
}
|
||||||
|
if (bets.length === 0) {
|
||||||
|
return [0, 0, 0, 0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 =
|
const canComment = canCommentOnBet(userId, createdTime, user) && !hideComment
|
||||||
canCommentOnBet(userId, createdTime, user) &&
|
|
||||||
contract.outcomeType !== 'BINARY'
|
|
||||||
|
|
||||||
const [comment, setComment] = useState('')
|
const [comment, setComment] = useState('')
|
||||||
async function submitComment() {
|
async function submitComment() {
|
||||||
|
@ -304,6 +385,7 @@ export function FeedBet(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Row className={'flex w-full gap-2 pt-3'}>
|
||||||
<div>
|
<div>
|
||||||
{isSelf ? (
|
{isSelf ? (
|
||||||
<Avatar
|
<Avatar
|
||||||
|
@ -322,7 +404,10 @@ export function FeedBet(props: {
|
||||||
) : (
|
) : (
|
||||||
<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">
|
||||||
<UserIcon className="h-5 w-5 text-gray-500" aria-hidden="true" />
|
<UserIcon
|
||||||
|
className="h-5 w-5 text-gray-500"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -369,6 +454,7 @@ export function FeedBet(props: {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</Row>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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