Remove remaining answer #'s. Refactor outcome/resolution labels.

This commit is contained in:
James Grugett 2022-04-18 01:24:31 -05:00
parent 1f797f491f
commit 20ccc32627
8 changed files with 249 additions and 100 deletions

View File

@ -38,6 +38,8 @@ export type FullContract<
T
export type Contract = FullContract<DPM | CPMM, Binary | Multi | FreeResponse>
export type BinaryContract = FullContract<DPM | CPMM, Binary>
export type FreeResponseContract = FullContract<DPM | CPMM, FreeResponse>
export type DPM = {
mechanism: 'dpm-2'
@ -61,18 +63,20 @@ export type Binary = {
outcomeType: 'BINARY'
initialProbability: number
resolutionProbability?: number // Used for BINARY markets resolved to MKT
resolution?: 'YES' | 'NO' | 'MKT' | 'CANCEL'
}
export type Multi = {
outcomeType: 'MULTI'
multiOutcomes: string[] // Used for outcomeType 'MULTI'.
resolutions?: { [outcome: string]: number } // Used for PROB
resolutions?: { [outcome: string]: number } // Used for MKT resolution.
}
export type FreeResponse = {
outcomeType: 'FREE_RESPONSE'
answers: Answer[] // Used for outcomeType 'FREE_RESPONSE'.
resolutions?: { [outcome: string]: number } // Used for PROB
resolution?: string | 'MKT' | 'CANCEL'
resolutions?: { [outcome: string]: number } // Used for MKT resolution.
}
export type outcomeType = 'BINARY' | 'MULTI' | 'FREE_RESPONSE'

View File

@ -19,7 +19,7 @@ import { Bet } from '../../common/bet'
import { placeBet, sellShares } from '../lib/firebase/api-call'
import { BuyAmountInput, SellAmountInput } from './amount-input'
import { InfoTooltip } from './info-tooltip'
import { OutcomeLabel } from './outcome-label'
import { BinaryOutcomeLabel, OutcomeLabel } from './outcome-label'
import {
calculatePayoutAfterCorrectBet,
calculateShares,
@ -58,7 +58,7 @@ export function BetPanel(props: {
<Row className="items-center justify-between gap-2">
<div>
You have {formatWithCommas(Math.floor(shares))}{' '}
<OutcomeLabel outcome={sharesOutcome} /> shares
<BinaryOutcomeLabel outcome={sharesOutcome} /> shares
</div>
<button
@ -147,7 +147,7 @@ export function BetPanelSwitcher(props: {
<Row className="items-center justify-between gap-2">
<div>
You have {formatWithCommas(Math.floor(shares))}{' '}
<OutcomeLabel outcome={sharesOutcome} /> shares
<BinaryOutcomeLabel outcome={sharesOutcome} /> shares
</div>
<button
@ -349,11 +349,12 @@ function BuyPanel(props: {
{contract.mechanism === 'dpm-2' ? (
<>
Estimated
<br /> payout if <OutcomeLabel outcome={betChoice ?? 'YES'} />
<br /> payout if{' '}
<BinaryOutcomeLabel outcome={betChoice ?? 'YES'} />
</>
) : (
<>
Payout if <OutcomeLabel outcome={betChoice ?? 'YES'} />
Payout if <BinaryOutcomeLabel outcome={betChoice ?? 'YES'} />
</>
)}
</div>
@ -547,7 +548,12 @@ function SellSharesModal(props: {
<div className="mb-6">
You have {formatWithCommas(Math.floor(shares))}{' '}
<OutcomeLabel outcome={sharesOutcome} /> shares
<OutcomeLabel
outcome={sharesOutcome}
contract={contract}
truncate="long"
/>{' '}
shares
</div>
<SellPanel

View File

@ -250,11 +250,16 @@ function MyContractBets(props: {
</Row>
<Row className="flex-1 items-center gap-2 text-sm text-gray-500">
{isBinary && (
{(isBinary || resolution) && (
<>
{resolution ? (
<div>
Resolved <OutcomeLabel outcome={resolution} />
Resolved{' '}
<OutcomeLabel
outcome={resolution}
contract={contract}
truncate="short"
/>
</div>
) : (
<div className="text-primary text-lg">{probPercent}</div>
@ -507,7 +512,15 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
</td>
{isCPMM && <td>{shares >= 0 ? 'BUY' : 'SELL'}</td>}
<td>
<OutcomeLabel outcome={outcome} />
{outcome === '0' ? (
'ANTE'
) : (
<OutcomeLabel
outcome={outcome}
contract={contract}
truncate="short"
/>
)}
</td>
<td>{formatMoney(Math.abs(amount))}</td>
{!isCPMM && <td>{saleDisplay}</td>}
@ -558,7 +571,8 @@ function SellButton(props: { contract: Contract; bet: Bet }) {
>
<div className="mb-4 text-2xl">
Sell {formatWithCommas(shares)} shares of{' '}
<OutcomeLabel outcome={outcome} /> for {formatMoney(saleAmount)}?
<OutcomeLabel outcome={outcome} contract={contract} truncate="long" />{' '}
for {formatMoney(saleAmount)}?
</div>
{!!loanAmount && (
<div className="mt-2">

View File

@ -3,7 +3,7 @@ import Link from 'next/link'
import { ClockIcon, DatabaseIcon, PencilIcon } from '@heroicons/react/outline'
import { TrendingUpIcon } from '@heroicons/react/solid'
import { Row } from '../layout/row'
import { formatMoney, formatPercent } from '../../../common/util/format'
import { formatMoney } from '../../../common/util/format'
import { UserLink } from '../user-page'
import {
Contract,
@ -19,10 +19,20 @@ import { fromNow } from '../../lib/util/time'
import { Avatar } from '../avatar'
import { Spacer } from '../layout/spacer'
import { useState } from 'react'
import { getProbability } from '../../../common/calculate'
import { ContractInfoDialog } from './contract-info-dialog'
import { Bet } from '../../../common/bet'
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
import {
Binary,
CPMM,
DPM,
FreeResponse,
FreeResponseContract,
FullContract,
} from '../../../common/contract'
import {
BinaryContractOutcomeLabel,
FreeResponseOutcomeLabel,
} from '../outcome-label'
export function ContractCard(props: {
contract: Contract
@ -31,7 +41,7 @@ export function ContractCard(props: {
className?: string
}) {
const { contract, showHotVolume, showCloseTime, className } = props
const { question, outcomeType } = contract
const { question, outcomeType, resolution } = contract
return (
<div>
@ -64,71 +74,36 @@ export function ContractCard(props: {
>
{question}
</p>
<ResolutionOrChance
className={clsx(
outcomeType === 'FREE_RESPONSE' ? 'items-start' : 'items-center'
)}
contract={contract}
/>
{outcomeType === 'BINARY' && (
<BinaryResolutionOrChance
className="items-center"
contract={contract}
/>
)}
{outcomeType === 'FREE_RESPONSE' && resolution && (
<FreeResponseResolution
contract={contract as FullContract<DPM, FreeResponse>}
resolution={resolution}
truncate="long"
/>
)}
</Row>
</div>
</div>
)
}
function getAnswerResolutionText(
contract: FullContract<DPM, FreeResponse>,
resolution: string
) {
const { answers } = contract
const chosen = answers?.find((answer) => answer.id === resolution)
if (chosen) {
return chosen.text.slice(0, 50)
}
return undefined
}
export function ResolutionOrChance(props: {
contract: Contract
export function BinaryResolutionOrChance(props: {
contract: FullContract<DPM | CPMM, Binary>
large?: boolean
className?: string
}) {
const { contract, large, className } = props
const { resolution, outcomeType } = contract
const isBinary = outcomeType === 'BINARY'
const { resolution } = contract
const marketClosed = (contract.closeTime || Infinity) < Date.now()
const resolutionColor =
{
YES: 'text-primary',
NO: 'text-red-400',
MKT: 'text-blue-400',
CANCEL: 'text-yellow-400',
'': '', // Empty if unresolved
}[resolution || ''] ?? 'text-primary'
const probColor = marketClosed ? 'text-gray-400' : 'text-primary'
const resolutionText = {
YES: 'YES',
NO: 'NO',
MKT: isBinary
? formatPercent(
contract.resolutionProbability ?? getProbability(contract)
)
: 'MULTI',
CANCEL: 'N/A',
'': '',
}[resolution || '']
const answerText =
resolution &&
outcomeType === 'FREE_RESPONSE' &&
getAnswerResolutionText(
contract as FullContract<DPM, FreeResponse>,
resolution
)
return (
<Col className={clsx(large ? 'text-4xl' : 'text-3xl', className)}>
{resolution ? (
@ -138,26 +113,41 @@ export function ResolutionOrChance(props: {
>
Resolved
</div>
{resolutionText ? (
<div className={resolutionColor}>{resolutionText}</div>
) : (
<div className={clsx(resolutionColor, 'text-xl')}>{answerText}</div>
)}
<BinaryContractOutcomeLabel
contract={contract}
resolution={resolution}
/>
</>
) : (
isBinary && (
<>
<div className={probColor}>{getBinaryProbPercent(contract)}</div>
<div className={clsx(probColor, large ? 'text-xl' : 'text-base')}>
chance
</div>
</>
)
<>
<div className={probColor}>{getBinaryProbPercent(contract)}</div>
<div className={clsx(probColor, large ? 'text-xl' : 'text-base')}>
chance
</div>
</>
)}
</Col>
)
}
export function FreeResponseResolution(props: {
contract: FreeResponseContract
resolution: string
truncate: 'short' | 'long' | 'none'
}) {
const { contract, resolution, truncate } = props
return (
<Col className="text-xl">
<div className={clsx('text-base text-gray-500')}>Resolved</div>
<FreeResponseOutcomeLabel
contract={contract}
resolution={resolution}
truncate={truncate}
/>
</Col>
)
}
function AbbrContractDetails(props: {
contract: Contract
showHotVolume?: boolean

View File

@ -6,7 +6,11 @@ import { useUser } from '../../hooks/use-user'
import { Row } from '../layout/row'
import { Linkify } from '../linkify'
import clsx from 'clsx'
import { ContractDetails, ResolutionOrChance } from './contract-card'
import {
FreeResponseResolution,
ContractDetails,
BinaryResolutionOrChance,
} from './contract-card'
import { Bet } from '../../../common/bet'
import { Comment } from '../../../common/comment'
import BetRow from '../bet-row'
@ -21,7 +25,7 @@ export const ContractOverview = (props: {
className?: string
}) => {
const { contract, bets, className } = props
const { question, creatorId, outcomeType } = contract
const { question, creatorId, outcomeType, resolution } = contract
const user = useUser()
const isCreator = user?.id === creatorId
@ -36,7 +40,7 @@ export const ContractOverview = (props: {
</div>
{isBinary && (
<ResolutionOrChance
<BinaryResolutionOrChance
className="hidden items-end xl:flex"
contract={contract}
large
@ -46,14 +50,21 @@ export const ContractOverview = (props: {
{isBinary ? (
<Row className="items-center justify-between gap-4 xl:hidden">
<ResolutionOrChance contract={contract} />
<BinaryResolutionOrChance contract={contract} />
{tradingAllowed(contract) && (
<BetRow contract={contract} labelClassName="hidden" />
)}
</Row>
) : (
<ResolutionOrChance contract={contract} />
outcomeType === 'FREE_RESPONSE' &&
resolution && (
<FreeResponseResolution
contract={contract}
resolution={resolution}
truncate="none"
/>
)
)}
<ContractDetails

View File

@ -26,7 +26,7 @@ import { Row } from '../layout/row'
import { createComment, MAX_COMMENT_LENGTH } from '../../lib/firebase/comments'
import { formatMoney, formatPercent } from '../../../common/util/format'
import { Comment } from '../../../common/comment'
import { ResolutionOrChance } from '../contract/contract-card'
import { BinaryResolutionOrChance } from '../contract/contract-card'
import { SiteLink } from '../site-link'
import { Col } from '../layout/col'
import { UserLink } from '../user-page'
@ -144,7 +144,12 @@ export function FeedComment(props: {
{!hideOutcome && (
<>
{' '}
of <OutcomeLabel outcome={outcome} />
of{' '}
<OutcomeLabel
outcome={outcome}
contract={contract}
truncate="short"
/>
</>
)}
<RelativeTimestamp time={createdTime} />
@ -227,7 +232,12 @@ export function FeedBet(props: {
{!hideOutcome && (
<>
{' '}
of <OutcomeLabel outcome={outcome} />
of{' '}
<OutcomeLabel
outcome={outcome}
contract={contract}
truncate="short"
/>
</>
)}
<RelativeTimestamp time={createdTime} />
@ -345,7 +355,10 @@ export function FeedQuestion(props: {
</SiteLink>
</Col>
{isBinary && (
<ResolutionOrChance className="items-center" contract={contract} />
<BinaryResolutionOrChance
className="items-center"
contract={contract}
/>
)}
</Col>
{showDescription && (
@ -449,7 +462,12 @@ function FeedResolve(props: { contract: Contract }) {
name={creatorName}
username={creatorUsername}
/>{' '}
resolved this market to <OutcomeLabel outcome={resolution} />{' '}
resolved this market to{' '}
<OutcomeLabel
outcome={resolution}
contract={contract}
truncate="long"
/>{' '}
<RelativeTimestamp time={contract.resolutionTime || 0} />
</div>
</div>
@ -482,8 +500,12 @@ function FeedClose(props: { contract: Contract }) {
)
}
function BetGroupSpan(props: { bets: Bet[]; outcome?: string }) {
const { bets, outcome } = props
function BetGroupSpan(props: {
contract: Contract
bets: Bet[]
outcome?: string
}) {
const { contract, bets, outcome } = props
const numberTraders = _.uniqBy(bets, (b) => b.userId).length
@ -501,7 +523,12 @@ function BetGroupSpan(props: { bets: Bet[]; outcome?: string }) {
{outcome && (
<>
{' '}
of <OutcomeLabel outcome={outcome} />
of{' '}
<OutcomeLabel
outcome={outcome}
contract={contract}
truncate="short"
/>
</>
)}{' '}
</span>
@ -513,7 +540,7 @@ function FeedBetGroup(props: {
bets: Bet[]
hideOutcome: boolean
}) {
const { bets, hideOutcome } = props
const { contract, bets, hideOutcome } = props
const betGroups = _.groupBy(bets, (bet) => bet.outcome)
const outcomes = Object.keys(betGroups)
@ -535,6 +562,7 @@ function FeedBetGroup(props: {
{outcomes.map((outcome, index) => (
<Fragment key={outcome}>
<BetGroupSpan
contract={contract}
outcome={hideOutcome ? undefined : outcome}
bets={betGroups[outcome]}
/>

View File

@ -1,13 +1,76 @@
import { Answer } from '../../common/answer'
import { getProbability } from '../../common/calculate'
import {
Binary,
Contract,
CPMM,
DPM,
FreeResponse,
FreeResponseContract,
FullContract,
} from '../../common/contract'
import { formatPercent } from '../../common/util/format'
export function OutcomeLabel(props: {
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT' | string
contract: Contract
truncate: 'short' | 'long' | 'none'
}) {
const { outcome, contract, truncate } = props
const binaryOutcomes = ['YES', 'NO', 'CANCEL', 'MKT']
if (binaryOutcomes.includes(outcome)) {
return <BinaryOutcomeLabel outcome={outcome as any} />
}
return (
<FreeResponseOutcomeLabel
contract={contract as FullContract<DPM, FreeResponse>}
resolution={outcome}
truncate={truncate}
/>
)
}
export function BinaryOutcomeLabel(props: {
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT'
}) {
const { outcome } = props
if (outcome === 'YES') return <YesLabel />
if (outcome === 'NO') return <NoLabel />
if (outcome === 'MKT') return <ProbLabel />
if (outcome === 'CANCEL') return <CancelLabel />
return <AnswerNumberLabel number={outcome} />
return <CancelLabel />
}
export function BinaryContractOutcomeLabel(props: {
contract: FullContract<DPM | CPMM, Binary>
resolution: 'YES' | 'NO' | 'CANCEL' | 'MKT'
}) {
const { contract, resolution } = props
if (resolution === 'MKT') {
const prob = contract.resolutionProbability ?? getProbability(contract)
return <ProbPercentLabel prob={prob} />
}
return <BinaryOutcomeLabel outcome={resolution} />
}
export function FreeResponseOutcomeLabel(props: {
contract: FreeResponseContract
resolution: string | 'CANCEL' | 'MKT'
truncate: 'short' | 'long' | 'none'
}) {
const { contract, resolution, truncate } = props
if (resolution === 'CANCEL') return <CancelLabel />
if (resolution === 'MKT') return <MultiLabel />
const { answers } = contract
const chosen = answers?.find((answer) => answer.id === resolution)
if (!chosen) return <AnswerNumberLabel number={resolution} />
return <AnswerLabel answer={chosen} truncate={truncate} />
}
export function YesLabel() {
@ -26,6 +89,32 @@ export function ProbLabel() {
return <span className="text-blue-400">PROB</span>
}
export function MultiLabel() {
return <span className="text-blue-400">MULTI</span>
}
export function ProbPercentLabel(props: { prob: number }) {
const { prob } = props
return <span className="text-blue-400">{formatPercent(prob)}</span>
}
export function AnswerNumberLabel(props: { number: string }) {
return <span className="text-primary">#{props.number}</span>
}
export function AnswerLabel(props: {
answer: Answer
truncate: 'short' | 'long' | 'none'
}) {
const { answer, truncate } = props
const { text } = answer
let truncated = text
if (truncate === 'short' && text.length > 20) {
truncated = text.slice(0, 10) + '...' + text.slice(-10)
} else if (truncate === 'long' && text.length > 75) {
truncated = text.slice(0, 75) + '...'
}
return <span className="text-primary">{truncated}</span>
}

View File

@ -8,8 +8,9 @@ import {
import { DOMAIN } from '../../../../common/envs/constants'
import { AnswersGraph } from '../../../components/answers/answers-graph'
import {
ResolutionOrChance,
BinaryResolutionOrChance,
ContractDetails,
FreeResponseResolution,
} from '../../../components/contract/contract-card'
import { ContractProbGraph } from '../../../components/contract/contract-prob-graph'
import { Col } from '../../../components/layout/col'
@ -118,8 +119,14 @@ function ContractEmbed(props: { contract: Contract; bets: Bet[] }) {
hideShareButtons
/>
{(isBinary || resolution) && (
<ResolutionOrChance contract={contract} />
{isBinary && <BinaryResolutionOrChance contract={contract} />}
{outcomeType === 'FREE_RESPONSE' && resolution && (
<FreeResponseResolution
contract={contract}
resolution={resolution}
truncate="long"
/>
)}
</Row>