import React, { memo, useEffect, useMemo, useRef, 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 { BetsSummary } from 'web/components/bet-summary' import { listAllComments } from 'web/lib/firebase/comments' import { ContractComment } from 'common/comment' import { ScrollToTopButton } from 'web/components/scroll-to-top-button' import { Answer } from 'common/answer' import { useEvent } from 'web/hooks/use-event' 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) : [] const comments = contractId ? await listAllComments(contractId) : [] return { props: { contract, // Limit the data sent to the client. Client will still load all bets/comments directly. bets: bets.slice(0, 5000), comments: comments.slice(0, 1000), }, revalidate: 5, // regenerate after five seconds } } export async function getStaticPaths() { return { paths: [], fallback: 'blocking' } } export default function ContractPage(props: { contract: Contract | null bets: Bet[] comments: ContractComment[] backToHome?: () => void }) { props = usePropz(props, getStaticPropz) ?? { contract: null, bets: [], comments: [], } 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, comments } = props const contract = useContractWithPreload(props.contract) ?? props.contract const user = useUser() const isCreator = user?.id === contract.creatorId 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, }) const [answerResponse, setAnswerResponse] = useState( undefined ) const tabsContainerRef = useRef(null) const onAnswerCommentClick = useEvent((answer: Answer) => { setAnswerResponse(answer) if (tabsContainerRef.current) { tabsContainerRef.current.scrollIntoView({ behavior: 'smooth' }) } else { console.error('no ref to scroll to') } }) const onCancelAnswerResponse = useEvent(() => setAnswerResponse(undefined)) return ( {isCreator && ( )} ) : (
) } > {showConfetti && ( )} {ogCardProps && ( )} {backToHome && ( )} {outcomeType === 'NUMERIC' && ( )} {(outcomeType === 'FREE_RESPONSE' || outcomeType === 'MULTIPLE_CHOICE') && ( <> )} {outcomeType === 'NUMERIC' && allowTrade && ( )} {isResolved && ( <>
)}
{!isCreator && } ) } 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> ) } )