diff --git a/web/components/bet-summary.tsx b/web/components/bet-summary.tsx new file mode 100644 index 00000000..aa64da43 --- /dev/null +++ b/web/components/bet-summary.tsx @@ -0,0 +1,120 @@ +import { sumBy } from 'lodash' +import clsx from 'clsx' + +import { Bet } from 'web/lib/firebase/bets' +import { formatMoney, formatWithCommas } from 'common/util/format' +import { Col } from './layout/col' +import { Contract } from 'web/lib/firebase/contracts' +import { Row } from './layout/row' +import { YesLabel, NoLabel } from './outcome-label' +import { + calculatePayout, + getContractBetMetrics, + getProbability, +} from 'common/calculate' +import { InfoTooltip } from './info-tooltip' +import { ProfitBadge } from './profit-badge' + +export function BetsSummary(props: { + contract: Contract + userBets: Bet[] + className?: string +}) { + const { contract, className } = props + const { resolution, outcomeType } = contract + const isBinary = outcomeType === 'BINARY' + + const bets = props.userBets.filter((b) => !b.isAnte) + const { profitPercent, payout, profit, invested } = getContractBetMetrics( + contract, + bets + ) + + const excludeSales = bets.filter((b) => !b.isSold && !b.sale) + const yesWinnings = sumBy(excludeSales, (bet) => + calculatePayout(contract, bet, 'YES') + ) + const noWinnings = sumBy(excludeSales, (bet) => + calculatePayout(contract, bet, 'NO') + ) + + const position = yesWinnings - noWinnings + + const prob = isBinary ? getProbability(contract) : 0 + const expectation = prob * yesWinnings + (1 - prob) * noWinnings + + if (bets.length === 0) return <> + + return ( + + + {resolution ? ( + +
Payout
+
+ {formatMoney(payout)}{' '} + +
+ + ) : isBinary ? ( + +
+ Position{' '} + +
+
+ {position > 1e-7 ? ( + <> + {formatWithCommas(position)} + + ) : position < -1e-7 ? ( + <> + {formatWithCommas(-position)} + + ) : ( + '——' + )} +
+ + ) : ( + +
+ Expectation{''} + +
+
{formatMoney(payout)}
+ + )} + + +
+ Invested{' '} + +
+
{formatMoney(invested)}
+ + + {isBinary && !resolution && ( + +
+ Expectation{' '} + +
+
{formatMoney(expectation)}
+ + )} + + +
+ Profit{' '} + +
+
+ {formatMoney(profit)} + +
+ +
+ + ) +} diff --git a/web/components/bets-list.tsx b/web/components/bets-list.tsx index dbb2db56..5a95f22f 100644 --- a/web/components/bets-list.tsx +++ b/web/components/bets-list.tsx @@ -22,7 +22,7 @@ import { import { Row } from './layout/row' import { sellBet } from 'web/lib/firebase/api' import { ConfirmationButton } from './confirmation-button' -import { OutcomeLabel, YesLabel, NoLabel } from './outcome-label' +import { OutcomeLabel } from './outcome-label' import { LoadingIndicator } from './loading-indicator' import { SiteLink } from './site-link' import { @@ -38,14 +38,14 @@ import { NumericContract } from 'common/contract' import { formatNumericProbability } from 'common/pseudo-numeric' import { useUser } from 'web/hooks/use-user' import { useUserBets } from 'web/hooks/use-user-bets' -import { SellSharesModal } from './sell-modal' import { useUnfilledBets } from 'web/hooks/use-bets' import { LimitBet } from 'common/bet' -import { floatingEqual } from 'common/util/math' import { Pagination } from './pagination' import { LimitOrderTable } from './limit-bets' import { UserLink } from 'web/components/user-link' import { useUserBetContracts } from 'web/hooks/use-contracts' +import { BetsSummary } from './bet-summary' +import { ProfitBadge } from './profit-badge' type BetSort = 'newest' | 'profit' | 'closeTime' | 'value' type BetFilter = 'open' | 'limit_bet' | 'sold' | 'closed' | 'resolved' | 'all' @@ -337,8 +337,7 @@ function ContractBets(props: { {contract.mechanism === 'cpmm-1' && limitBets.length > 0 && ( @@ -364,125 +363,6 @@ function ContractBets(props: { ) } -export function BetsSummary(props: { - contract: Contract - bets: Bet[] - isYourBets: boolean - className?: string -}) { - const { contract, isYourBets, className } = props - const { resolution, closeTime, outcomeType, mechanism } = contract - const isBinary = outcomeType === 'BINARY' - const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC' - const isCpmm = mechanism === 'cpmm-1' - const isClosed = closeTime && Date.now() > closeTime - - const bets = props.bets.filter((b) => !b.isAnte) - const { hasShares, invested, profitPercent, payout, profit, totalShares } = - getContractBetMetrics(contract, bets) - - const excludeSales = bets.filter((b) => !b.isSold && !b.sale) - const yesWinnings = sumBy(excludeSales, (bet) => - calculatePayout(contract, bet, 'YES') - ) - const noWinnings = sumBy(excludeSales, (bet) => - calculatePayout(contract, bet, 'NO') - ) - - const [showSellModal, setShowSellModal] = useState(false) - const user = useUser() - - const sharesOutcome = floatingEqual(totalShares.YES ?? 0, 0) - ? floatingEqual(totalShares.NO ?? 0, 0) - ? undefined - : 'NO' - : 'YES' - - const canSell = - isYourBets && - isCpmm && - (isBinary || isPseudoNumeric) && - !isClosed && - !resolution && - hasShares && - sharesOutcome && - user - - return ( - - - -
- Invested -
-
{formatMoney(invested)}
- - -
Profit
-
- {formatMoney(profit)} -
- - {canSell && ( - <> - - {showSellModal && ( - - )} - - )} -
- - {resolution ? ( - -
Payout
-
- {formatMoney(payout)}{' '} - -
- - ) : isBinary ? ( - <> - -
- Payout if -
-
- {formatMoney(yesWinnings)} -
- - -
- Payout if -
-
{formatMoney(noWinnings)}
- - - ) : ( - -
- Expected value -
-
{formatMoney(payout)}
- - )} -
- - ) -} - export function ContractBetsTable(props: { contract: Contract bets: Bet[] @@ -750,30 +630,3 @@ function SellButton(props: { ) } - -export function ProfitBadge(props: { - profitPercent: number - round?: boolean - className?: string -}) { - const { profitPercent, round, className } = props - if (!profitPercent) return null - const colors = - profitPercent > 0 - ? 'bg-green-100 text-green-800' - : 'bg-red-100 text-red-800' - - return ( - - {(profitPercent > 0 ? '+' : '') + - profitPercent.toFixed(round ? 0 : 1) + - '%'} - - ) -} diff --git a/web/components/contract/contract-tabs.tsx b/web/components/contract/contract-tabs.tsx index 17471796..bd3204ed 100644 --- a/web/components/contract/contract-tabs.tsx +++ b/web/components/contract/contract-tabs.tsx @@ -9,7 +9,7 @@ import { groupBy, sortBy } from 'lodash' import { Bet } from 'common/bet' import { Contract } from 'common/contract' import { PAST_BETS } from 'common/user' -import { ContractBetsTable, BetsSummary } from '../bets-list' +import { ContractBetsTable } from '../bets-list' import { Spacer } from '../layout/spacer' import { Tabs } from '../layout/tabs' import { Col } from '../layout/col' @@ -17,59 +17,45 @@ import { LoadingIndicator } from 'web/components/loading-indicator' import { useComments } from 'web/hooks/use-comments' import { useLiquidity } from 'web/hooks/use-liquidity' import { useTipTxns } from 'web/hooks/use-tip-txns' -import { useUser } from 'web/hooks/use-user' import { capitalize } from 'lodash' import { DEV_HOUSE_LIQUIDITY_PROVIDER_ID, HOUSE_LIQUIDITY_PROVIDER_ID, } from 'common/antes' -import { useIsMobile } from 'web/hooks/use-is-mobile' +import { buildArray } from 'common/util/array' -export function ContractTabs(props: { contract: Contract; bets: Bet[] }) { - const { contract, bets } = props - - const isMobile = useIsMobile() - const user = useUser() - const userBets = - user && bets.filter((bet) => !bet.isAnte && bet.userId === user.id) +export function ContractTabs(props: { + contract: Contract + bets: Bet[] + userBets: Bet[] +}) { + const { contract, bets, userBets } = props const yourTrades = (
- - +
) + const tabs = buildArray( + { + title: 'Comments', + content: , + }, + { + title: capitalize(PAST_BETS), + content: , + }, + userBets.length > 0 && { + title: 'Your trades', + content: yourTrades, + } + ) + return ( - , - }, - { - title: capitalize(PAST_BETS), - content: , - }, - ...(!user || !userBets?.length - ? [] - : [ - { - title: isMobile ? `You` : `Your ${PAST_BETS}`, - content: yourTrades, - }, - ]), - ]} - /> + ) } diff --git a/web/components/profit-badge.tsx b/web/components/profit-badge.tsx new file mode 100644 index 00000000..f82159e6 --- /dev/null +++ b/web/components/profit-badge.tsx @@ -0,0 +1,28 @@ +import clsx from 'clsx' + +export function ProfitBadge(props: { + profitPercent: number + round?: boolean + className?: string +}) { + const { profitPercent, round, className } = props + if (!profitPercent) return null + const colors = + profitPercent > 0 + ? 'bg-green-100 text-green-800' + : 'bg-red-100 text-red-800' + + return ( + + {(profitPercent > 0 ? '+' : '') + + profitPercent.toFixed(round ? 0 : 1) + + '%'} + + ) +} diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx index 38df2fbf..1dde2f95 100644 --- a/web/pages/[username]/[contractSlug].tsx +++ b/web/pages/[username]/[contractSlug].tsx @@ -1,5 +1,6 @@ import React, { memo, useEffect, useMemo, useState } from 'react' import { ArrowLeftIcon } from '@heroicons/react/outline' +import dayjs from 'dayjs' import { useContractWithPreload } from 'web/hooks/use-contract' import { ContractOverview } from 'web/components/contract/contract-overview' @@ -44,8 +45,7 @@ import { useAdmin } from 'web/hooks/use-admin' import { BetSignUpPrompt } from 'web/components/sign-up-prompt' import { PlayMoneyDisclaimer } from 'web/components/play-money-disclaimer' import BetButton from 'web/components/bet-button' - -import dayjs from 'dayjs' +import { BetsSummary } from 'web/components/bet-summary' export const getStaticProps = fromPropz(getStaticPropz) export async function getStaticPropz(props: { @@ -167,6 +167,10 @@ export function ContractPageContent( [bets] ) + const userBets = user + ? bets.filter((bet) => !bet.isAnte && bet.userId === user.id) + : [] + const [showConfetti, setShowConfetti] = useState(false) useEffect(() => { @@ -248,7 +252,14 @@ export function ContractPageContent( )} - + + + + {!user ? ( diff --git a/web/pages/home/index.tsx b/web/pages/home/index.tsx index b42b37bb..ba2851bf 100644 --- a/web/pages/home/index.tsx +++ b/web/pages/home/index.tsx @@ -33,7 +33,6 @@ import { groupPath, joinGroup, leaveGroup } from 'web/lib/firebase/groups' import { usePortfolioHistory } from 'web/hooks/use-portfolio-history' import { formatMoney } from 'common/util/format' import { useProbChanges } from 'web/hooks/use-prob-changes' -import { ProfitBadge } from 'web/components/bets-list' import { calculatePortfolioProfit } from 'common/calculate-metrics' import { hasCompletedStreakToday } from 'web/components/profile/betting-streak-modal' import { ContractsGrid } from 'web/components/contract/contracts-grid' @@ -45,6 +44,7 @@ import { usePrefetch } from 'web/hooks/use-prefetch' import { Title } from 'web/components/title' import { CPMMBinaryContract } from 'common/contract' import { useContractsByDailyScoreGroups } from 'web/hooks/use-contracts' +import { ProfitBadge } from 'web/components/profit-badge' import { LoadingIndicator } from 'web/components/loading-indicator' export default function Home() {