Show users position or recent bet with comments

This commit is contained in:
Ian Philips 2022-04-29 10:39:59 -06:00
parent 3e600f45b5
commit d0700c1221
3 changed files with 261 additions and 149 deletions

View File

@ -31,6 +31,8 @@ type BaseActivityItem = {
export type CommentInputItem = BaseActivityItem & {
type: 'commentInput'
bets: Bet[]
comments: Comment[]
}
export type DescriptionItem = BaseActivityItem & {
@ -48,12 +50,13 @@ export type BetItem = BaseActivityItem & {
bet: Bet
hideOutcome: boolean
smallAvatar: boolean
hideComment?: boolean
}
export type CommentItem = BaseActivityItem & {
type: 'comment'
comment: Comment
bet: Bet | undefined
betsBySameUser: Bet[]
hideOutcome: boolean
truncate: boolean
smallAvatar: boolean
@ -129,7 +132,7 @@ function groupBets(
type: 'comment' as const,
id: bet.id,
comment,
bet,
betsBySameUser: [bet],
contract,
hideOutcome,
truncate: abbreviated,
@ -280,7 +283,7 @@ function groupBetsAndComments(
id: comment.id,
contract: contract,
comment,
bet: undefined,
betsBySameUser: [],
truncate: abbreviated,
hideOutcome: true,
smallAvatar,
@ -308,6 +311,37 @@ function groupBetsAndComments(
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(
contract: Contract,
bets: Bet[],
@ -361,6 +395,8 @@ export function getAllContractActivityItems(
type: 'commentInput',
id: 'commentInput',
contract,
bets: [],
comments: [],
})
} else {
items.push(
@ -385,6 +421,8 @@ export function getAllContractActivityItems(
type: 'commentInput',
id: 'commentInput',
contract,
bets: [],
comments: [],
})
}
@ -467,32 +505,20 @@ export function getSpecificContractActivityItems(
contract,
hideOutcome: false,
smallAvatar: false,
hideComment: true,
}))
)
break
case 'comments':
const onlyBetsWithComments = bets.filter((bet) =>
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(...getCommentsWithPositions(bets, comments, contract))
items.push({
type: 'commentInput',
id: 'commentInput',
contract,
bets: bets,
comments: comments,
})
break
}

View File

@ -56,8 +56,7 @@ 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 { useUserContractBets } from '../../hooks/use-user-bets'
import { useSaveShares } from '../use-save-shares'
import { calculateCpmmSale } from '../../../common/calculate-cpmm'
export function FeedItems(props: {
contract: Contract
@ -131,29 +130,38 @@ function FeedItem(props: { item: ActivityItem }) {
export function FeedComment(props: {
contract: Contract
comment: Comment
bet: Bet | undefined
betsBySameUser: Bet[]
hideOutcome: boolean
truncate: boolean
smallAvatar: boolean
}) {
const { contract, comment, bet, hideOutcome, truncate, smallAvatar } = props
let money: string | undefined
let outcome: string | undefined
let bought: string | undefined
if (bet) {
outcome = bet.outcome
bought = bet.amount >= 0 ? 'bought' : 'sold'
money = formatMoney(Math.abs(bet.amount))
}
const { text, userUsername, userName, userAvatarUrl, createdTime, userId } =
comment
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 userBets = useUserContractBets(userId, contract.id)
const { yesFloorShares, noFloorShares } = useSaveShares(
contract as FullContract<CPMM | DPM, Binary>,
userBets
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
)
const userPosition = yesFloorShares || noFloorShares
return (
<>
@ -171,10 +179,9 @@ export function FeedComment(props: {
username={userUsername}
name={userName}
/>{' '}
{contract.outcomeType === 'BINARY' ? (
userPosition > 0 && (
{!matchedBet && userPosition > 0 && (
<>
{'owns ' + userPosition + ' shares '}
{'with ' + userPositionMoney + ' '}
<>
{' of '}
<OutcomeLabel
@ -184,11 +191,10 @@ export function FeedComment(props: {
/>
</>
</>
)
) : (
)}
<>
{bought} {money}
{!hideOutcome && (
{outcome && !hideOutcome && (
<>
{' '}
of{' '}
@ -200,7 +206,6 @@ export function FeedComment(props: {
</>
)}
</>
)}
<RelativeTimestamp time={createdTime} />
</p>
</div>
@ -214,20 +219,12 @@ export function FeedComment(props: {
)
}
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>
)
}
export function CommentInput(props: { contract: Contract }) {
// see if we can comment input on any bet:
const { contract } = props
export function CommentInput(props: {
contract: Contract
bets: Bet[]
comments: Comment[]
}) {
const { contract, bets, comments } = props
const user = useUser()
const [comment, setComment] = useState('')
@ -240,14 +237,50 @@ export function CommentInput(props: { contract: Contract }) {
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 (
<>
<Row className={'flex w-full gap-2 pt-5'}>
<Row className={'flex w-full gap-2 pt-3'}>
<div>
<Avatar avatarUrl={user?.avatarUrl} username={user?.username} />
</div>
<div className={'min-w-0 flex-1 py-1.5'}>
<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">
<Textarea
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: {
contract: Contract
bet: Bet
hideOutcome: boolean
smallAvatar: boolean
hideComment?: boolean
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 user = useUser()
const isSelf = user?.id === userId
const canComment =
canCommentOnBet(userId, createdTime, user) &&
contract.outcomeType !== 'BINARY'
const canComment = canCommentOnBet(userId, createdTime, user) && !hideComment
const [comment, setComment] = useState('')
async function submitComment() {
@ -304,6 +385,7 @@ export function FeedBet(props: {
return (
<>
<Row className={'flex w-full gap-2 pt-3'}>
<div>
{isSelf ? (
<Avatar
@ -322,7 +404,10 @@ export function FeedBet(props: {
) : (
<div className="relative px-1">
<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>
)}
@ -369,6 +454,7 @@ export function FeedBet(props: {
)}
</div>
</div>
</Row>
</>
)
}

View File

@ -287,7 +287,7 @@ function ContractTopTrades(props: {
<FeedComment
contract={contract}
comment={commentsById[topCommentId]}
bet={betsById[topCommentId]}
betsBySameUser={[betsById[topCommentId]]}
hideOutcome={false}
truncate={false}
smallAvatar={false}