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/lib/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 { 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 { useEffect, useState } from 'react'
import { BinaryOutcomeLabel } from 'web/components/outcome-label'
import { formatMoney } from 'common/lib/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'
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) : []
return {
props: {
contract,
user,
slug: contractSlug,
challengeSlug,
bets,
},
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[]
challengeSlug: string
}) {
props = usePropz(props, getStaticPropz) ?? {
contract: null,
user: null,
challengeSlug: '',
bets: [],
slug: '',
}
const contract = useContractWithPreload(props.contract)
const challenge = useChallenge(props.challengeSlug, contract?.id)
const { user, bets } = props
const currentUser = useUser()
if (!contract || !challenge) {
return (
)
}
if (challenge.acceptances.length >= challenge.maxUses)
return (
)
return (
)
}
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,
creatorsOutcome,
creatorsOutcomeProb,
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 === creatorsOutcome
const amountWon = creatorWon ? acceptances[0].amount : creatorAmount
const yourCost =
((1 - creatorsOutcomeProb) / creatorsOutcomeProb) * 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,
creatorsOutcome,
creatorsOutcomeProb,
creatorAmount
)}
VS
{userCol(user, yourOutcome, 1 - creatorsOutcomeProb, 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,
creatorsOutcome,
creatorsOutcomeProb,
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 ?? window.innerHeight) -
(containerRef?.offsetTop ?? 0) -
bottomBarHeight
const isBinary = contract.outcomeType === 'BINARY'
const isPseudoNumeric = contract.outcomeType === 'PSEUDO_NUMERIC'
const yourCost =
((1 - creatorsOutcomeProb) / creatorsOutcomeProb) * creatorAmount
const userColumn = (
challenger: User | null | undefined,
portfolioHistory: PortfolioMetrics[],
outcome: string,
amount: number
) => {
const lastPortfolioMetrics = last(portfolioHistory)
const prob =
(outcome === creatorsOutcome
? creatorsOutcomeProb
: 1 - creatorsOutcomeProb) * 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,
creatorsOutcome,
creatorAmount
)}
VS
{userColumn(
user?.id === creatorId ? undefined : user,
portfolioHistory,
yourOutcome,
yourCost
)}
)
}