import { fromPropz, usePropz } from 'web/hooks/use-propz' import { Contract, contractPath, getContractFromSlug, } from 'web/lib/firebase/contracts' import { useContractWithPreload } from 'web/hooks/use-contract' import { DOMAIN } from 'common/envs/constants' import { Col } from 'web/components/layout/col' import { SiteLink } from 'web/components/site-link' import { Spacer } from 'web/components/layout/spacer' import { Row } from 'web/components/layout/row' import { Challenge } from 'common/challenge' import { getChallenge, getChallengeUrl, useChallenge, } from 'web/lib/firebase/challenges' import { getPortfolioHistory, getUserByUsername } from 'web/lib/firebase/users' import { PortfolioMetrics, User } from 'common/user' import { Page } from 'web/components/page' import { useUser, useUserById } from 'web/hooks/use-user' import { AcceptChallengeButton } from 'web/components/challenges/accept-challenge-button' import { Avatar } from 'web/components/avatar' import { UserLink } from 'web/components/user-page' import React, { useEffect, useState } from 'react' import { BinaryOutcomeLabel } from 'web/components/outcome-label' import { formatMoney } from 'common/util/format' import { last } from 'lodash' import { LoadingIndicator } from 'web/components/loading-indicator' import { useWindowSize } from 'web/hooks/use-window-size' import { Bet, listAllBets } from 'web/lib/firebase/bets' import Confetti from 'react-confetti' import { BinaryResolutionOrChance, PseudoNumericResolutionOrExpectation, } from 'web/components/contract/contract-card' import { ContractProbGraph } from 'web/components/contract/contract-prob-graph' import { SEO } from 'web/components/SEO' import { getOpenGraphProps } from 'web/components/contract/contract-card-preview' import Custom404 from 'web/pages/404' import { useSaveReferral } from 'web/hooks/use-save-referral' export const getStaticProps = fromPropz(getStaticPropz) export async function getStaticPropz(props: { params: { username: string; contractSlug: string; challengeSlug: string } }) { const { username, contractSlug, challengeSlug } = props.params const contract = (await getContractFromSlug(contractSlug)) || null const user = (await getUserByUsername(username)) || null const bets = contract?.id ? await listAllBets(contract.id) : [] const challenge = contract?.id ? await getChallenge(challengeSlug, contract.id) : null return { props: { contract, user, slug: contractSlug, challengeSlug, bets, challenge, }, revalidate: 60, // regenerate after a minute } } export async function getStaticPaths() { return { paths: [], fallback: 'blocking' } } export default function ChallengePage(props: { contract: Contract | null user: User slug: string bets: Bet[] challenge: Challenge | null challengeSlug: string }) { props = usePropz(props, getStaticPropz) ?? { contract: null, user: null, challengeSlug: '', bets: [], challenge: null, slug: '', } const contract = useContractWithPreload(props.contract) ?? props.contract const challenge = useChallenge(props.challengeSlug, contract?.id) ?? props.challenge const { user, bets } = props const currentUser = useUser() useSaveReferral(currentUser, { defaultReferrerUsername: challenge?.creatorUsername, }) if (!contract || !challenge) return const ogCardProps = getOpenGraphProps(contract) ogCardProps.creatorUsername = challenge.creatorUsername ogCardProps.creatorName = challenge.creatorName ogCardProps.creatorAvatarUrl = challenge.creatorAvatarUrl return ( {challenge.acceptances.length >= challenge.maxUses ? ( ) : ( )} ) } const userRow = (challenger: User) => ( ) function ClosedChallengeContent(props: { contract: Contract challenge: Challenge creator: User bets: Bet[] }) { const { contract, challenge, creator, bets } = props const { resolution } = contract const { acceptances, creatorAmount, creatorOutcome, creatorOutcomeProb, yourOutcome, } = challenge const user = useUserById(acceptances[0].userId) const [showConfetti, setShowConfetti] = useState(false) const { width, height } = useWindowSize() useEffect(() => { if (acceptances.length === 0) return if (acceptances[0].createdTime > Date.now() - 1000 * 60) setShowConfetti(true) }, [acceptances]) const creatorWon = resolution === creatorOutcome const amountWon = creatorWon ? acceptances[0].amount : creatorAmount const yourCost = ((1 - creatorOutcomeProb) / creatorOutcomeProb) * creatorAmount if (!user) return const userWonCol = (user: User, amount: number) => ( 🥇 🥇 WON {formatMoney(amount)} ) const userLostCol = (challenger: User, amount: number) => ( {userRow(challenger)} LOST {formatMoney(amount)} ) const userCol = ( challenger: User, outcome: string, prob: number, amount: number ) => ( {userRow(challenger)} is betting {formatMoney(amount)} {' on '} at{' '} {Math.round(prob * 100)}% ) return ( <> {showConfetti && ( )} {!resolution && ( ⚔️️ Challenge Accepted ⚔️️ )} {resolution == 'YES' || resolution == 'NO' ? ( {userWonCol(creatorWon ? creator : user, amountWon)} {userLostCol(creatorWon ? user : creator, amountWon)} ) : ( {userCol( creator, creatorOutcome, creatorOutcomeProb, creatorAmount )} VS {userCol(user, yourOutcome, 1 - creatorOutcomeProb, yourCost)} )} ) } function ChallengeContract(props: { contract: Contract; bets: Bet[] }) { const { contract, bets } = props const { question } = contract const href = `https://${DOMAIN}${contractPath(contract)}` const isBinary = contract.outcomeType === 'BINARY' const isPseudoNumeric = contract.outcomeType === 'PSEUDO_NUMERIC' return (
{question} {isBinary && } {isPseudoNumeric && ( )} {(isBinary || isPseudoNumeric) && ( )}
) } function OpenChallengeContent(props: { contract: Contract challenge: Challenge creator: User user: User | null | undefined bets: Bet[] }) { const { contract, challenge, creator, user, bets } = props const { question } = contract const { creatorAmount, creatorId, creatorOutcome, creatorOutcomeProb, yourOutcome, } = challenge const [creatorPortfolioHistory, setUsersCreatorPortfolioHistory] = useState< PortfolioMetrics[] >([]) const [portfolioHistory, setUsersPortfolioHistory] = useState< PortfolioMetrics[] >([]) useEffect(() => { getPortfolioHistory(creator.id).then(setUsersCreatorPortfolioHistory) if (user) getPortfolioHistory(user.id).then(setUsersPortfolioHistory) }, [creator.id, user]) const href = `https://${DOMAIN}${contractPath(contract)}` const { width, height } = useWindowSize() const [containerRef, setContainerRef] = useState(null) const bottomBarHeight = (width ?? 0) < 1024 ? 58 : 0 const remainingHeight = (height ?? 0) - (containerRef?.offsetTop ?? 0) - bottomBarHeight const isBinary = contract.outcomeType === 'BINARY' const isPseudoNumeric = contract.outcomeType === 'PSEUDO_NUMERIC' const yourCost = ((1 - creatorOutcomeProb) / creatorOutcomeProb) * creatorAmount const userColumn = ( challenger: User | null | undefined, portfolioHistory: PortfolioMetrics[], outcome: string, amount: number ) => { const lastPortfolioMetrics = last(portfolioHistory) const prob = (outcome === creatorOutcome ? creatorOutcomeProb : 1 - creatorOutcomeProb) * 100 return ( {challenger ? ( userRow(challenger) ) : ( Your name here )} is betting {formatMoney(amount)} {' on '} at{' '} {Math.round(prob)}% {/*// It could be fun to show each user's portfolio history here*/} {/*// Also show how many challenges they've won*/} {/**/}
Portfolio value
{challenger ? formatMoney( (lastPortfolioMetrics?.balance ?? 0) + (lastPortfolioMetrics?.investmentValue ?? 0) ) : 'xxxx'}
) } return ( <> {(isBinary || isPseudoNumeric) && (
)} {question} {userColumn( creator, creatorPortfolioHistory, creatorOutcome, creatorAmount )} VS {userColumn( user?.id === creatorId ? undefined : user, portfolioHistory, yourOutcome, yourCost )} ) }