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
ogCardProps.question = 'CHALLENGED:' + contract.question
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
)}
>
)
}