import { Bet } from 'common/bet' import { Comment } from 'common/comment' import { resolvedPayout } from 'common/calculate' import { Contract } from 'common/contract' import { formatMoney } from 'common/util/format' import { groupBy, mapValues, sumBy, sortBy, keyBy } from 'lodash' import { useState, useMemo, useEffect } from 'react' import { CommentTipMap } from 'web/hooks/use-tip-txns' import { useUserById } from 'web/hooks/use-user' import { listUsers, User } from 'web/lib/firebase/users' import { FeedBet } from '../feed/feed-bets' import { FeedComment } from '../feed/feed-comments' import { Spacer } from '../layout/spacer' import { Leaderboard } from '../leaderboard' import { Title } from '../title' export function ContractLeaderboard(props: { contract: Contract bets: Bet[] }) { const { contract, bets } = props const [users, setUsers] = useState() const { userProfits, top5Ids } = useMemo(() => { // Create a map of userIds to total profits (including sales) const openBets = bets.filter((bet) => !bet.isSold && !bet.sale) const betsByUser = groupBy(openBets, 'userId') const userProfits = mapValues(betsByUser, (bets) => sumBy(bets, (bet) => resolvedPayout(contract, bet) - bet.amount) ) // Find the 5 users with the most profits const top5Ids = Object.entries(userProfits) .sort(([_i1, p1], [_i2, p2]) => p2 - p1) .filter(([, p]) => p > 0) .slice(0, 5) .map(([id]) => id) return { userProfits, top5Ids } }, [contract, bets]) useEffect(() => { if (top5Ids.length > 0) { listUsers(top5Ids).then((users) => { const sortedUsers = sortBy(users, (user) => -userProfits[user.id]) setUsers(sortedUsers) }) } }, [userProfits, top5Ids]) return users && users.length > 0 ? ( formatMoney(userProfits[user.id] || 0), }, ]} className="mt-12 max-w-sm" /> ) : null } export function ContractTopTrades(props: { contract: Contract bets: Bet[] comments: Comment[] tips: CommentTipMap }) { const { contract, bets, comments, tips } = props const commentsById = keyBy(comments, 'id') const betsById = keyBy(bets, 'id') // If 'id2' is the sale of 'id1', both are logged with (id2 - id1) of profit // Otherwise, we record the profit at resolution time const profitById: Record = {} for (const bet of bets) { if (bet.sale) { const originalBet = betsById[bet.sale.betId] const profit = bet.sale.amount - originalBet.amount profitById[bet.id] = profit profitById[originalBet.id] = profit } else { profitById[bet.id] = resolvedPayout(contract, bet) - bet.amount } } // Now find the betId with the highest profit const topBetId = sortBy(bets, (b) => -profitById[b.id])[0]?.id const topBettor = useUserById(betsById[topBetId]?.userId) // And also the commentId of the comment with the highest profit const topCommentId = sortBy( comments, (c) => c.betId && -profitById[c.betId] )[0]?.id return (
{topCommentId && profitById[topCommentId] > 0 && ( <> <div className="relative flex items-start space-x-3 rounded-md bg-gray-50 px-2 py-4"> <FeedComment contract={contract} comment={commentsById[topCommentId]} tips={tips[topCommentId]} betsBySameUser={[betsById[topCommentId]]} truncate={false} smallAvatar={false} /> </div> <div className="mt-2 text-sm text-gray-500"> {commentsById[topCommentId].userName} made{' '} {formatMoney(profitById[topCommentId] || 0)}! </div> <Spacer h={16} /> </> )} {/* If they're the same, only show the comment; otherwise show both */} {topBettor && topBetId !== topCommentId && profitById[topBetId] > 0 && ( <> <Title text="💸 Smartest money" className="!mt-0" /> <div className="relative flex items-start space-x-3 rounded-md bg-gray-50 px-2 py-4"> <FeedBet contract={contract} bet={betsById[topBetId]} hideOutcome={false} smallAvatar={false} /> </div> <div className="mt-2 text-sm text-gray-500"> {topBettor?.name} made {formatMoney(profitById[topBetId] || 0)}! </div> </> )} </div> ) }