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' import { BetPanel } from 'web/components/bet-panel' import { Col } from 'web/components/layout/col' import { useUser } from 'web/hooks/use-user' import { ResolutionPanel } from 'web/components/resolution-panel' import { Spacer } from 'web/components/layout/spacer' import { Contract, getContractFromSlug, getRecommendedContracts, tradingAllowed, } from 'web/lib/firebase/contracts' import { SEO } from 'web/components/SEO' import { Page } from 'web/components/page' import { Bet, listAllBets } from 'web/lib/firebase/bets' import Custom404 from '../404' import { AnswersPanel } from 'web/components/answers/answers-panel' import { fromPropz, usePropz } from 'web/hooks/use-propz' import { ContractTabs } from 'web/components/contract/contract-tabs' import { FullscreenConfetti } from 'web/components/fullscreen-confetti' import { NumericBetPanel } from 'web/components/numeric-bet-panel' import { NumericResolutionPanel } from 'web/components/numeric-resolution-panel' import { useIsIframe } from 'web/hooks/use-is-iframe' import ContractEmbedPage from '../embed/[username]/[contractSlug]' import { useBets } from 'web/hooks/use-bets' import { CPMMBinaryContract } from 'common/contract' import { AlertBox } from 'web/components/alert-box' import { useTracking } from 'web/hooks/use-tracking' import { useSaveReferral } from 'web/hooks/use-save-referral' import { getOpenGraphProps } from 'common/contract-details' import { ContractDescription } from 'web/components/contract/contract-description' import { ContractLeaderboard, ContractTopTrades, } from 'web/components/contract/contract-leaderboard' import { ContractsGrid } from 'web/components/contract/contracts-grid' import { Title } from 'web/components/title' import { usePrefetch } from 'web/hooks/use-prefetch' 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 { BetsSummary } from 'web/components/bet-summary' export const getStaticProps = fromPropz(getStaticPropz) export async function getStaticPropz(props: { params: { username: string; contractSlug: string } }) { const { contractSlug } = props.params const contract = (await getContractFromSlug(contractSlug)) || null const contractId = contract?.id const bets = contractId ? await listAllBets(contractId) : [] return { // Limit the data sent to the client. Client will still load all bets directly. props: { contract, bets: bets.slice(0, 5000) }, revalidate: 5, // regenerate after five seconds } } export async function getStaticPaths() { return { paths: [], fallback: 'blocking' } } export default function ContractPage(props: { contract: Contract | null bets: Bet[] backToHome?: () => void }) { props = usePropz(props, getStaticPropz) ?? { contract: null, bets: [] } const inIframe = useIsIframe() if (inIframe) { return } const { contract } = props if (!contract) { return } return } // requires an admin to resolve a week after market closes export function needsAdminToResolve(contract: Contract) { return !contract.isResolved && dayjs().diff(contract.closeTime, 'day') > 7 } export function ContractPageSidebar(props: { contract: Contract }) { const { contract } = props const { creatorId, isResolved, outcomeType } = contract const user = useUser() const isCreator = user?.id === creatorId const isBinary = outcomeType === 'BINARY' const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC' const isNumeric = outcomeType === 'NUMERIC' const allowTrade = tradingAllowed(contract) const isAdmin = useAdmin() const allowResolve = !isResolved && (isCreator || (needsAdminToResolve(contract) && isAdmin)) && !!user const hasSidePanel = (isBinary || isNumeric || isPseudoNumeric) && (allowTrade || allowResolve) return hasSidePanel ? ( {allowTrade && (isNumeric ? ( ) : ( ))} {allowResolve && (isNumeric || isPseudoNumeric ? ( ) : ( ))} ) : null } export function ContractPageContent( props: Parameters[0] & { contract: Contract } ) { const { backToHome } = props const contract = useContractWithPreload(props.contract) ?? props.contract const user = useUser() usePrefetch(user?.id) useTracking( 'view market', { slug: contract.slug, contractId: contract.id, creatorId: contract.creatorId, }, true ) const bets = useBets(contract.id) ?? props.bets const nonChallengeBets = useMemo( () => bets.filter((b) => !b.challengeSlug), [bets] ) const userBets = user ? bets.filter((bet) => !bet.isAnte && bet.userId === user.id) : [] const [showConfetti, setShowConfetti] = useState(false) useEffect(() => { const shouldSeeConfetti = !!( user && contract.creatorId === user.id && Date.now() - contract.createdTime < 10 * 1000 ) setShowConfetti(shouldSeeConfetti) }, [contract, user]) const { isResolved, question, outcomeType } = contract const allowTrade = tradingAllowed(contract) const ogCardProps = getOpenGraphProps(contract) useSaveReferral(user, { defaultReferrerUsername: contract.creatorUsername, contractId: contract.id, }) return ( }> {showConfetti && ( )} {ogCardProps && ( )} {backToHome && ( )} {outcomeType === 'NUMERIC' && ( )} {(outcomeType === 'FREE_RESPONSE' || outcomeType === 'MULTIPLE_CHOICE') && ( <> )} {outcomeType === 'NUMERIC' && allowTrade && ( )} {isResolved && ( <>
)} {!user ? ( ) : ( outcomeType === 'BINARY' && allowTrade && ( ) )}
) } const RecommendedContractsWidget = memo( function RecommendedContractsWidget(props: { contract: Contract }) { const { contract } = props const user = useUser() const [recommendations, setRecommendations] = useState([]) useEffect(() => { if (user) { getRecommendedContracts(contract, user.id, 6).then(setRecommendations) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [contract.id, user?.id]) if (recommendations.length === 0) { return null } return ( <ContractsGrid contracts={recommendations} trackingPostfix=" recommended" /> </Col> ) } )