Clean up contract overview code (#823)
* Don't call Date.now a million times in answers graph * Refactor contract overview code so that it's easier to understand
This commit is contained in:
parent
8d853815d6
commit
7508d86c73
|
@ -18,19 +18,20 @@ export const AnswersGraph = memo(function AnswersGraph(props: {
|
|||
}) {
|
||||
const { contract, bets, height } = props
|
||||
const { createdTime, resolutionTime, closeTime, answers } = contract
|
||||
const now = Date.now()
|
||||
|
||||
const { probsByOutcome, sortedOutcomes } = computeProbsByOutcome(
|
||||
bets,
|
||||
contract
|
||||
)
|
||||
|
||||
const isClosed = !!closeTime && Date.now() > closeTime
|
||||
const isClosed = !!closeTime && now > closeTime
|
||||
const latestTime = dayjs(
|
||||
resolutionTime && isClosed
|
||||
? Math.min(resolutionTime, closeTime)
|
||||
: isClosed
|
||||
? closeTime
|
||||
: resolutionTime ?? Date.now()
|
||||
: resolutionTime ?? now
|
||||
)
|
||||
|
||||
const { width } = useWindowSize()
|
||||
|
@ -71,14 +72,14 @@ export const AnswersGraph = memo(function AnswersGraph(props: {
|
|||
const yTickValues = [0, 25, 50, 75, 100]
|
||||
|
||||
const numXTickValues = isLargeWidth ? 5 : 2
|
||||
const startDate = new Date(contract.createdTime)
|
||||
const endDate = dayjs(startDate).add(1, 'hour').isAfter(latestTime)
|
||||
? latestTime.add(1, 'hours').toDate()
|
||||
: latestTime.toDate()
|
||||
const includeMinute = dayjs(endDate).diff(startDate, 'hours') < 2
|
||||
const startDate = dayjs(contract.createdTime)
|
||||
const endDate = startDate.add(1, 'hour').isAfter(latestTime)
|
||||
? latestTime.add(1, 'hours')
|
||||
: latestTime
|
||||
const includeMinute = endDate.diff(startDate, 'hours') < 2
|
||||
|
||||
const multiYear = !dayjs(startDate).isSame(latestTime, 'year')
|
||||
const lessThanAWeek = dayjs(startDate).add(1, 'week').isAfter(latestTime)
|
||||
const multiYear = !startDate.isSame(latestTime, 'year')
|
||||
const lessThanAWeek = startDate.add(1, 'week').isAfter(latestTime)
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -96,16 +97,16 @@ export const AnswersGraph = memo(function AnswersGraph(props: {
|
|||
}}
|
||||
xScale={{
|
||||
type: 'time',
|
||||
min: startDate,
|
||||
max: endDate,
|
||||
min: startDate.toDate(),
|
||||
max: endDate.toDate(),
|
||||
}}
|
||||
xFormat={(d) =>
|
||||
formatTime(+d.valueOf(), multiYear, lessThanAWeek, lessThanAWeek)
|
||||
formatTime(now, +d.valueOf(), multiYear, lessThanAWeek, lessThanAWeek)
|
||||
}
|
||||
axisBottom={{
|
||||
tickValues: numXTickValues,
|
||||
format: (time) =>
|
||||
formatTime(+time, multiYear, lessThanAWeek, includeMinute),
|
||||
formatTime(now, +time, multiYear, lessThanAWeek, includeMinute),
|
||||
}}
|
||||
colors={[
|
||||
'#fca5a5', // red-300
|
||||
|
@ -158,23 +159,20 @@ function formatPercent(y: DatumValue) {
|
|||
}
|
||||
|
||||
function formatTime(
|
||||
now: number,
|
||||
time: number,
|
||||
includeYear: boolean,
|
||||
includeHour: boolean,
|
||||
includeMinute: boolean
|
||||
) {
|
||||
const d = dayjs(time)
|
||||
|
||||
if (
|
||||
d.add(1, 'minute').isAfter(Date.now()) &&
|
||||
d.subtract(1, 'minute').isBefore(Date.now())
|
||||
)
|
||||
if (d.add(1, 'minute').isAfter(now) && d.subtract(1, 'minute').isBefore(now))
|
||||
return 'Now'
|
||||
|
||||
let format: string
|
||||
if (d.isSame(Date.now(), 'day')) {
|
||||
if (d.isSame(now, 'day')) {
|
||||
format = '[Today]'
|
||||
} else if (d.add(1, 'day').isSame(Date.now(), 'day')) {
|
||||
} else if (d.add(1, 'day').isSame(now, 'day')) {
|
||||
format = '[Yesterday]'
|
||||
} else {
|
||||
format = 'MMM D'
|
||||
|
|
|
@ -6,6 +6,7 @@ import Textarea from 'react-expanding-textarea'
|
|||
import { Contract, MAX_DESCRIPTION_LENGTH } from 'common/contract'
|
||||
import { exhibitExts, parseTags } from 'common/util/parse'
|
||||
import { useAdmin } from 'web/hooks/use-admin'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { updateContract } from 'web/lib/firebase/contracts'
|
||||
import { Row } from '../layout/row'
|
||||
import { Content } from '../editor'
|
||||
|
@ -17,11 +18,12 @@ import { insertContent } from '../editor/utils'
|
|||
|
||||
export function ContractDescription(props: {
|
||||
contract: Contract
|
||||
isCreator: boolean
|
||||
className?: string
|
||||
}) {
|
||||
const { contract, isCreator, className } = props
|
||||
const { contract, className } = props
|
||||
const isAdmin = useAdmin()
|
||||
const user = useUser()
|
||||
const isCreator = user?.id === contract.creatorId
|
||||
return (
|
||||
<div className={clsx('mt-2 text-gray-700', className)}>
|
||||
{isCreator || isAdmin ? (
|
||||
|
|
|
@ -30,7 +30,6 @@ import { SiteLink } from 'web/components/site-link'
|
|||
import { getGroupLinkToDisplay, groupPath } from 'web/lib/firebase/groups'
|
||||
import { insertContent } from '../editor/utils'
|
||||
import { contractMetrics } from 'common/contract-details'
|
||||
import { User } from 'common/user'
|
||||
import { UserLink } from 'web/components/user-link'
|
||||
import { FeaturedContractBadge } from 'web/components/contract/featured-contract-badge'
|
||||
import { Tooltip } from 'web/components/tooltip'
|
||||
|
@ -138,11 +137,9 @@ export function AbbrContractDetails(props: {
|
|||
|
||||
export function ContractDetails(props: {
|
||||
contract: Contract
|
||||
user: User | null | undefined
|
||||
isCreator?: boolean
|
||||
disabled?: boolean
|
||||
}) {
|
||||
const { contract, isCreator, disabled } = props
|
||||
const { contract, disabled } = props
|
||||
const {
|
||||
closeTime,
|
||||
creatorName,
|
||||
|
@ -153,6 +150,7 @@ export function ContractDetails(props: {
|
|||
} = contract
|
||||
const { volumeLabel, resolvedDate } = contractMetrics(contract)
|
||||
const user = useUser()
|
||||
const isCreator = user?.id === creatorId
|
||||
const [open, setOpen] = useState(false)
|
||||
const { width } = useWindowSize()
|
||||
const isMobile = (width ?? 0) < 600
|
||||
|
@ -279,12 +277,12 @@ export function ContractDetails(props: {
|
|||
|
||||
export function ExtraMobileContractDetails(props: {
|
||||
contract: Contract
|
||||
user: User | null | undefined
|
||||
forceShowVolume?: boolean
|
||||
}) {
|
||||
const { contract, user, forceShowVolume } = props
|
||||
const { contract, forceShowVolume } = props
|
||||
const { volume, resolutionTime, closeTime, creatorId, uniqueBettorCount } =
|
||||
contract
|
||||
const user = useUser()
|
||||
const uniqueBettors = uniqueBettorCount ?? 0
|
||||
const { resolvedDate } = contractMetrics(contract)
|
||||
const volumeTranslation =
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { tradingAllowed } from 'web/lib/firebase/contracts'
|
||||
import { Col } from '../layout/col'
|
||||
|
@ -16,136 +15,154 @@ import {
|
|||
import { Bet } from 'common/bet'
|
||||
import BetButton from '../bet-button'
|
||||
import { AnswersGraph } from '../answers/answers-graph'
|
||||
import { Contract, CPMMBinaryContract } from 'common/contract'
|
||||
import { ContractDescription } from './contract-description'
|
||||
import {
|
||||
Contract,
|
||||
BinaryContract,
|
||||
CPMMContract,
|
||||
CPMMBinaryContract,
|
||||
FreeResponseContract,
|
||||
MultipleChoiceContract,
|
||||
NumericContract,
|
||||
PseudoNumericContract,
|
||||
} from 'common/contract'
|
||||
import { ContractDetails, ExtraMobileContractDetails } from './contract-details'
|
||||
import { NumericGraph } from './numeric-graph'
|
||||
import { ExtraContractActionsRow } from 'web/components/contract/extra-contract-actions-row'
|
||||
|
||||
const OverviewQuestion = (props: { text: string }) => (
|
||||
<Linkify className="text-2xl text-indigo-700 md:text-3xl" text={props.text} />
|
||||
)
|
||||
|
||||
const BetWidget = (props: { contract: CPMMContract }) => {
|
||||
const user = useUser()
|
||||
return (
|
||||
<Col>
|
||||
<BetButton contract={props.contract} />
|
||||
{!user && (
|
||||
<div className="mt-1 text-center text-sm text-gray-500">
|
||||
(with play money!)
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
const NumericOverview = (props: { contract: NumericContract }) => {
|
||||
const { contract } = props
|
||||
return (
|
||||
<Col className="gap-1 md:gap-2">
|
||||
<Col className="gap-3 px-2 sm:gap-4">
|
||||
<ContractDetails contract={contract} />
|
||||
<Row className="justify-between gap-4">
|
||||
<OverviewQuestion text={contract.question} />
|
||||
<NumericResolutionOrExpectation
|
||||
contract={contract}
|
||||
className="hidden items-end xl:flex"
|
||||
/>
|
||||
</Row>
|
||||
<NumericResolutionOrExpectation
|
||||
className="items-center justify-between gap-4 xl:hidden"
|
||||
contract={contract}
|
||||
/>
|
||||
</Col>
|
||||
<NumericGraph contract={contract} />
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
const BinaryOverview = (props: { contract: BinaryContract; bets: Bet[] }) => {
|
||||
const { contract, bets } = props
|
||||
return (
|
||||
<Col className="gap-1 md:gap-2">
|
||||
<Col className="gap-3 px-2 sm:gap-4">
|
||||
<ContractDetails contract={contract} />
|
||||
<Row className="justify-between gap-4">
|
||||
<OverviewQuestion text={contract.question} />
|
||||
<BinaryResolutionOrChance
|
||||
className="hidden items-end xl:flex"
|
||||
contract={contract}
|
||||
large
|
||||
/>
|
||||
</Row>
|
||||
<Row className="items-center justify-between gap-4 xl:hidden">
|
||||
<BinaryResolutionOrChance contract={contract} />
|
||||
<ExtraMobileContractDetails contract={contract} />
|
||||
{tradingAllowed(contract) && (
|
||||
<BetWidget contract={contract as CPMMBinaryContract} />
|
||||
)}
|
||||
</Row>
|
||||
</Col>
|
||||
<ContractProbGraph contract={contract} bets={[...bets].reverse()} />
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
const ChoiceOverview = (props: {
|
||||
contract: FreeResponseContract | MultipleChoiceContract
|
||||
bets: Bet[]
|
||||
}) => {
|
||||
const { contract, bets } = props
|
||||
const { question, resolution } = contract
|
||||
return (
|
||||
<Col className="gap-1 md:gap-2">
|
||||
<Col className="gap-3 px-2 sm:gap-4">
|
||||
<ContractDetails contract={contract} />
|
||||
<OverviewQuestion text={question} />
|
||||
{resolution && (
|
||||
<FreeResponseResolutionOrChance contract={contract} truncate="none" />
|
||||
)}
|
||||
</Col>
|
||||
<Col className={'mb-1 gap-y-2'}>
|
||||
<AnswersGraph contract={contract} bets={[...bets].reverse()} />
|
||||
<ExtraMobileContractDetails
|
||||
contract={contract}
|
||||
forceShowVolume={true}
|
||||
/>
|
||||
</Col>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
const PseudoNumericOverview = (props: {
|
||||
contract: PseudoNumericContract
|
||||
bets: Bet[]
|
||||
}) => {
|
||||
const { contract, bets } = props
|
||||
return (
|
||||
<Col className="gap-1 md:gap-2">
|
||||
<Col className="gap-3 px-2 sm:gap-4">
|
||||
<ContractDetails contract={contract} />
|
||||
<Row className="justify-between gap-4">
|
||||
<OverviewQuestion text={contract.question} />
|
||||
<PseudoNumericResolutionOrExpectation
|
||||
contract={contract}
|
||||
className="hidden items-end xl:flex"
|
||||
/>
|
||||
</Row>
|
||||
<Row className="items-center justify-between gap-4 xl:hidden">
|
||||
<PseudoNumericResolutionOrExpectation contract={contract} />
|
||||
<ExtraMobileContractDetails contract={contract} />
|
||||
{tradingAllowed(contract) && <BetWidget contract={contract} />}
|
||||
</Row>
|
||||
</Col>
|
||||
<ContractProbGraph contract={contract} bets={[...bets].reverse()} />
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
export const ContractOverview = (props: {
|
||||
contract: Contract
|
||||
bets: Bet[]
|
||||
className?: string
|
||||
}) => {
|
||||
const { contract, bets, className } = props
|
||||
const { question, creatorId, outcomeType, resolution } = contract
|
||||
|
||||
const user = useUser()
|
||||
const isCreator = user?.id === creatorId
|
||||
|
||||
const isBinary = outcomeType === 'BINARY'
|
||||
const isPseudoNumeric = outcomeType === 'PSEUDO_NUMERIC'
|
||||
|
||||
return (
|
||||
<Col className={clsx('mb-6', className)}>
|
||||
<Col className="gap-3 px-2 sm:gap-4">
|
||||
<ContractDetails
|
||||
contract={contract}
|
||||
user={user}
|
||||
isCreator={isCreator}
|
||||
/>
|
||||
<Row className="justify-between gap-4">
|
||||
<div className="text-2xl text-indigo-700 md:text-3xl">
|
||||
<Linkify text={question} />
|
||||
</div>
|
||||
<Row className={'hidden gap-3 xl:flex'}>
|
||||
{isBinary && (
|
||||
<BinaryResolutionOrChance
|
||||
className="items-end"
|
||||
contract={contract}
|
||||
large
|
||||
/>
|
||||
)}
|
||||
|
||||
{isPseudoNumeric && (
|
||||
<PseudoNumericResolutionOrExpectation
|
||||
contract={contract}
|
||||
className="items-end"
|
||||
/>
|
||||
)}
|
||||
|
||||
{outcomeType === 'NUMERIC' && (
|
||||
<NumericResolutionOrExpectation
|
||||
contract={contract}
|
||||
className="items-end"
|
||||
/>
|
||||
)}
|
||||
</Row>
|
||||
</Row>
|
||||
|
||||
{isBinary ? (
|
||||
<Row className="items-center justify-between gap-4 xl:hidden">
|
||||
<BinaryResolutionOrChance contract={contract} />
|
||||
<ExtraMobileContractDetails contract={contract} user={user} />
|
||||
{tradingAllowed(contract) && (
|
||||
<Row>
|
||||
<Col>
|
||||
<BetButton contract={contract as CPMMBinaryContract} />
|
||||
{!user && (
|
||||
<div className="mt-1 text-center text-sm text-gray-500">
|
||||
(with play money!)
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
</Row>
|
||||
) : isPseudoNumeric ? (
|
||||
<Row className="items-center justify-between gap-4 xl:hidden">
|
||||
<PseudoNumericResolutionOrExpectation contract={contract} />
|
||||
<ExtraMobileContractDetails contract={contract} user={user} />
|
||||
{tradingAllowed(contract) && (
|
||||
<Row>
|
||||
<Col>
|
||||
<BetButton contract={contract} />
|
||||
{!user && (
|
||||
<div className="mt-1 text-center text-sm text-gray-500">
|
||||
(with play money!)
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
</Row>
|
||||
) : (
|
||||
(outcomeType === 'FREE_RESPONSE' ||
|
||||
outcomeType === 'MULTIPLE_CHOICE') &&
|
||||
resolution && (
|
||||
<FreeResponseResolutionOrChance
|
||||
contract={contract}
|
||||
truncate="none"
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
||||
{outcomeType === 'NUMERIC' && (
|
||||
<Row className="items-center justify-between gap-4 xl:hidden">
|
||||
<NumericResolutionOrExpectation contract={contract} />
|
||||
</Row>
|
||||
)}
|
||||
</Col>
|
||||
<div className={'my-1 md:my-2'}></div>
|
||||
{(isBinary || isPseudoNumeric) && (
|
||||
<ContractProbGraph contract={contract} bets={[...bets].reverse()} />
|
||||
)}{' '}
|
||||
{(outcomeType === 'FREE_RESPONSE' ||
|
||||
outcomeType === 'MULTIPLE_CHOICE') && (
|
||||
<Col className={'mb-1 gap-y-2'}>
|
||||
<AnswersGraph contract={contract} bets={[...bets].reverse()} />
|
||||
<ExtraMobileContractDetails
|
||||
contract={contract}
|
||||
user={user}
|
||||
forceShowVolume={true}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
{outcomeType === 'NUMERIC' && <NumericGraph contract={contract} />}
|
||||
<ExtraContractActionsRow user={user} contract={contract} />
|
||||
<ContractDescription
|
||||
className="px-2"
|
||||
contract={contract}
|
||||
isCreator={isCreator}
|
||||
/>
|
||||
</Col>
|
||||
)
|
||||
const { contract, bets } = props
|
||||
switch (contract.outcomeType) {
|
||||
case 'BINARY':
|
||||
return <BinaryOverview contract={contract} bets={bets} />
|
||||
case 'NUMERIC':
|
||||
return <NumericOverview contract={contract} />
|
||||
case 'PSEUDO_NUMERIC':
|
||||
return <PseudoNumericOverview contract={contract} bets={bets} />
|
||||
case 'FREE_RESPONSE':
|
||||
case 'MULTIPLE_CHOICE':
|
||||
return <ChoiceOverview contract={contract} bets={bets} />
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Row } from '../layout/row'
|
|||
import { Contract } from 'web/lib/firebase/contracts'
|
||||
import React, { useState } from 'react'
|
||||
import { Button } from 'web/components/button'
|
||||
import { User } from 'common/user'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { ShareModal } from './share-modal'
|
||||
import { FollowMarketButton } from 'web/components/follow-market-button'
|
||||
import { LikeMarketButton } from 'web/components/contract/like-market-button'
|
||||
|
@ -15,12 +15,10 @@ import { withTracking } from 'web/lib/service/analytics'
|
|||
import { CreateChallengeModal } from 'web/components/challenges/create-challenge-modal'
|
||||
import { CHALLENGES_ENABLED } from 'common/challenge'
|
||||
|
||||
export function ExtraContractActionsRow(props: {
|
||||
contract: Contract
|
||||
user: User | undefined | null
|
||||
}) {
|
||||
const { user, contract } = props
|
||||
export function ExtraContractActionsRow(props: { contract: Contract }) {
|
||||
const { contract } = props
|
||||
const { outcomeType, resolution } = contract
|
||||
const user = useUser()
|
||||
const [isShareOpen, setShareOpen] = useState(false)
|
||||
const [openCreateChallengeModal, setOpenCreateChallengeModal] =
|
||||
useState(false)
|
||||
|
|
|
@ -36,6 +36,8 @@ import { useSaveReferral } from 'web/hooks/use-save-referral'
|
|||
import { User } from 'common/user'
|
||||
import { ContractComment } from 'common/comment'
|
||||
import { getOpenGraphProps } from 'common/contract-details'
|
||||
import { ContractDescription } from 'web/components/contract/contract-description'
|
||||
import { ExtraContractActionsRow } from 'web/components/contract/extra-contract-actions-row'
|
||||
import {
|
||||
ContractLeaderboard,
|
||||
ContractTopTrades,
|
||||
|
@ -232,6 +234,8 @@ export function ContractPageContent(
|
|||
)}
|
||||
|
||||
<ContractOverview contract={contract} bets={nonChallengeBets} />
|
||||
<ExtraContractActionsRow contract={contract} />
|
||||
<ContractDescription className="mb-6 px-2" contract={contract} />
|
||||
|
||||
{outcomeType === 'NUMERIC' && (
|
||||
<AlertBox
|
||||
|
|
|
@ -103,7 +103,7 @@ export function ContractEmbed(props: { contract: Contract; bets: Bet[] }) {
|
|||
<Spacer h={3} />
|
||||
|
||||
<Row className="items-center justify-between gap-4 px-2">
|
||||
<ContractDetails contract={contract} user={null} disabled />
|
||||
<ContractDetails contract={contract} disabled />
|
||||
|
||||
{(isBinary || isPseudoNumeric) &&
|
||||
tradingAllowed(contract) &&
|
||||
|
|
Loading…
Reference in New Issue
Block a user