Improve Free response UI (#78)
* Add legend to free response graph * Hide answers panel unless resolving. Correctly order answers * No gray background for add answer & resolve panel. Tweak spacing * Max answer length 240 chars * Show answer text in resolution for market page, card instead of number. * Remove remaining answer #'s. Refactor outcome/resolution labels. * Move answer panel back up * Tweak spacing * Update placement of bet button on mobile for FR answer feed item * Fix reversed feed for binary markets * Show multi resolve options * Clean up unused parts of answer item * Lighten resolve buttons * Show answer text in market resolve email
This commit is contained in:
parent
7abc11c146
commit
9c74f88b4a
|
@ -29,3 +29,5 @@ export const getNoneAnswer = (contractId: string, creator: User) => {
|
||||||
text: 'None',
|
text: 'None',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MAX_ANSWER_LENGTH = 240
|
||||||
|
|
|
@ -38,6 +38,8 @@ export type FullContract<
|
||||||
T
|
T
|
||||||
|
|
||||||
export type Contract = FullContract<DPM | CPMM, Binary | Multi | FreeResponse>
|
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 = {
|
export type DPM = {
|
||||||
mechanism: 'dpm-2'
|
mechanism: 'dpm-2'
|
||||||
|
@ -61,18 +63,20 @@ export type Binary = {
|
||||||
outcomeType: 'BINARY'
|
outcomeType: 'BINARY'
|
||||||
initialProbability: number
|
initialProbability: number
|
||||||
resolutionProbability?: number // Used for BINARY markets resolved to MKT
|
resolutionProbability?: number // Used for BINARY markets resolved to MKT
|
||||||
|
resolution?: 'YES' | 'NO' | 'MKT' | 'CANCEL'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Multi = {
|
export type Multi = {
|
||||||
outcomeType: 'MULTI'
|
outcomeType: 'MULTI'
|
||||||
multiOutcomes: string[] // Used for 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 = {
|
export type FreeResponse = {
|
||||||
outcomeType: 'FREE_RESPONSE'
|
outcomeType: 'FREE_RESPONSE'
|
||||||
answers: Answer[] // Used for 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'
|
export type outcomeType = 'BINARY' | 'MULTI' | 'FREE_RESPONSE'
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
} from '../../common/contract'
|
} from '../../common/contract'
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
import { getLoanAmount, getNewMultiBetInfo } from '../../common/new-bet'
|
import { getLoanAmount, getNewMultiBetInfo } from '../../common/new-bet'
|
||||||
import { Answer } from '../../common/answer'
|
import { Answer, MAX_ANSWER_LENGTH } from '../../common/answer'
|
||||||
import { getContract, getValues } from './utils'
|
import { getContract, getValues } from './utils'
|
||||||
import { sendNewAnswerEmail } from './emails'
|
import { sendNewAnswerEmail } from './emails'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
|
@ -31,7 +31,7 @@ export const createAnswer = functions.runWith({ minInstances: 1 }).https.onCall(
|
||||||
if (amount <= 0 || isNaN(amount) || !isFinite(amount))
|
if (amount <= 0 || isNaN(amount) || !isFinite(amount))
|
||||||
return { status: 'error', message: 'Invalid amount' }
|
return { status: 'error', message: 'Invalid amount' }
|
||||||
|
|
||||||
if (!text || typeof text !== 'string' || text.length > 10000)
|
if (!text || typeof text !== 'string' || text.length > MAX_ANSWER_LENGTH)
|
||||||
return { status: 'error', message: 'Invalid text' }
|
return { status: 'error', message: 'Invalid text' }
|
||||||
|
|
||||||
// Run as transaction to prevent race conditions.
|
// Run as transaction to prevent race conditions.
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Answer } from '../../common/answer'
|
||||||
import { Bet } from '../../common/bet'
|
import { Bet } from '../../common/bet'
|
||||||
import { getProbability } from '../../common/calculate'
|
import { getProbability } from '../../common/calculate'
|
||||||
import { Comment } from '../../common/comment'
|
import { Comment } from '../../common/comment'
|
||||||
import { Contract } from '../../common/contract'
|
import { Contract, FreeResponseContract } from '../../common/contract'
|
||||||
import { CREATOR_FEE } from '../../common/fees'
|
import { CREATOR_FEE } from '../../common/fees'
|
||||||
import { PrivateUser, User } from '../../common/user'
|
import { PrivateUser, User } from '../../common/user'
|
||||||
import { formatMoney, formatPercent } from '../../common/util/format'
|
import { formatMoney, formatPercent } from '../../common/util/format'
|
||||||
|
@ -98,6 +98,10 @@ const toDisplayResolution = (
|
||||||
if (resolution === 'MKT' && resolutions) return 'MULTI'
|
if (resolution === 'MKT' && resolutions) return 'MULTI'
|
||||||
if (resolution === 'CANCEL') return 'N/A'
|
if (resolution === 'CANCEL') return 'N/A'
|
||||||
|
|
||||||
|
const answer = (contract as FreeResponseContract).answers?.find(
|
||||||
|
(a) => a.id === resolution
|
||||||
|
)
|
||||||
|
if (answer) return answer.text
|
||||||
return `#${resolution}`
|
return `#${resolution}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { useState } from 'react'
|
|
||||||
|
|
||||||
import { Answer } from '../../../common/answer'
|
import { Answer } from '../../../common/answer'
|
||||||
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
||||||
|
@ -8,19 +7,14 @@ import { Col } from '../layout/col'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { Avatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
import { SiteLink } from '../site-link'
|
import { SiteLink } from '../site-link'
|
||||||
import { BuyButton } from '../yes-no-selector'
|
|
||||||
import { formatPercent } from '../../../common/util/format'
|
import { formatPercent } from '../../../common/util/format'
|
||||||
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
|
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
|
||||||
import { tradingAllowed } from '../../lib/firebase/contracts'
|
import { tradingAllowed } from '../../lib/firebase/contracts'
|
||||||
import { AnswerBetPanel } from './answer-bet-panel'
|
|
||||||
import { Linkify } from '../linkify'
|
import { Linkify } from '../linkify'
|
||||||
import { User } from '../../../common/user'
|
|
||||||
import { ContractActivity } from '../feed/contract-activity'
|
|
||||||
|
|
||||||
export function AnswerItem(props: {
|
export function AnswerItem(props: {
|
||||||
answer: Answer
|
answer: Answer
|
||||||
contract: FullContract<DPM, FreeResponse>
|
contract: FullContract<DPM, FreeResponse>
|
||||||
user: User | null | undefined
|
|
||||||
showChoice: 'radio' | 'checkbox' | undefined
|
showChoice: 'radio' | 'checkbox' | undefined
|
||||||
chosenProb: number | undefined
|
chosenProb: number | undefined
|
||||||
totalChosenProb?: number
|
totalChosenProb?: number
|
||||||
|
@ -30,7 +24,6 @@ export function AnswerItem(props: {
|
||||||
const {
|
const {
|
||||||
answer,
|
answer,
|
||||||
contract,
|
contract,
|
||||||
user,
|
|
||||||
showChoice,
|
showChoice,
|
||||||
chosenProb,
|
chosenProb,
|
||||||
totalChosenProb,
|
totalChosenProb,
|
||||||
|
@ -47,10 +40,6 @@ export function AnswerItem(props: {
|
||||||
const wasResolvedTo =
|
const wasResolvedTo =
|
||||||
resolution === answer.id || (resolutions && resolutions[answer.id])
|
resolution === answer.id || (resolutions && resolutions[answer.id])
|
||||||
|
|
||||||
const [isBetting, setIsBetting] = useState(false)
|
|
||||||
|
|
||||||
const canBet = !isBetting && !showChoice && tradingAllowed(contract)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
@ -63,10 +52,8 @@ export function AnswerItem(props: {
|
||||||
? 'bg-gray-50'
|
? 'bg-gray-50'
|
||||||
: showChoice === 'radio'
|
: showChoice === 'radio'
|
||||||
? 'bg-green-50'
|
? 'bg-green-50'
|
||||||
: 'bg-blue-50',
|
: 'bg-blue-50'
|
||||||
canBet && 'cursor-pointer hover:bg-gray-100'
|
|
||||||
)}
|
)}
|
||||||
onClick={() => canBet && setIsBetting(true)}
|
|
||||||
>
|
>
|
||||||
<Col className="flex-1 gap-3">
|
<Col className="flex-1 gap-3">
|
||||||
<div className="whitespace-pre-line">
|
<div className="whitespace-pre-line">
|
||||||
|
@ -83,124 +70,91 @@ export function AnswerItem(props: {
|
||||||
{/* TODO: Show total pool? */}
|
{/* TODO: Show total pool? */}
|
||||||
<div className="text-base">#{number}</div>
|
<div className="text-base">#{number}</div>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
{isBetting && (
|
|
||||||
<ContractActivity
|
|
||||||
className="hidden md:flex"
|
|
||||||
contract={contract}
|
|
||||||
bets={[]}
|
|
||||||
comments={[]}
|
|
||||||
user={user}
|
|
||||||
filterToOutcome={answer.id}
|
|
||||||
mode="all"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
{isBetting ? (
|
<Row className="items-center justify-end gap-4 self-end sm:self-start">
|
||||||
<AnswerBetPanel
|
{!wasResolvedTo &&
|
||||||
answer={answer}
|
(showChoice === 'checkbox' ? (
|
||||||
contract={contract}
|
<input
|
||||||
closePanel={() => setIsBetting(false)}
|
className="input input-bordered w-24 justify-self-end text-2xl"
|
||||||
className="sm:w-72"
|
type="number"
|
||||||
/>
|
placeholder={`${roundedProb}`}
|
||||||
) : (
|
maxLength={9}
|
||||||
<Row className="items-center justify-end gap-4 self-end sm:self-start">
|
value={chosenProb ? Math.round(chosenProb) : ''}
|
||||||
{!wasResolvedTo &&
|
onChange={(e) => {
|
||||||
(showChoice === 'checkbox' ? (
|
const { value } = e.target
|
||||||
<input
|
const numberValue = value
|
||||||
className="input input-bordered w-24 justify-self-end text-2xl"
|
? parseInt(value.replace(/[^\d]/, ''))
|
||||||
type="number"
|
: 0
|
||||||
placeholder={`${roundedProb}`}
|
if (!isNaN(numberValue)) onChoose(answer.id, numberValue)
|
||||||
maxLength={9}
|
}}
|
||||||
value={chosenProb ? Math.round(chosenProb) : ''}
|
/>
|
||||||
onChange={(e) => {
|
|
||||||
const { value } = e.target
|
|
||||||
const numberValue = value
|
|
||||||
? parseInt(value.replace(/[^\d]/, ''))
|
|
||||||
: 0
|
|
||||||
if (!isNaN(numberValue)) onChoose(answer.id, numberValue)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className={clsx(
|
|
||||||
'text-2xl',
|
|
||||||
tradingAllowed(contract) ? 'text-green-500' : 'text-gray-500'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{probPercent}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{showChoice ? (
|
|
||||||
<div className="form-control py-1">
|
|
||||||
<label className="label cursor-pointer gap-3">
|
|
||||||
<span className="">Choose this answer</span>
|
|
||||||
{showChoice === 'radio' && (
|
|
||||||
<input
|
|
||||||
className={clsx('radio', chosenProb && '!bg-green-500')}
|
|
||||||
type="radio"
|
|
||||||
name="opt"
|
|
||||||
checked={isChosen}
|
|
||||||
onChange={() => onChoose(answer.id, 1)}
|
|
||||||
value={answer.id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{showChoice === 'checkbox' && (
|
|
||||||
<input
|
|
||||||
className={clsx('checkbox', chosenProb && '!bg-blue-500')}
|
|
||||||
type="checkbox"
|
|
||||||
name="opt"
|
|
||||||
checked={isChosen}
|
|
||||||
onChange={() => {
|
|
||||||
if (isChosen) onDeselect(answer.id)
|
|
||||||
else {
|
|
||||||
onChoose(answer.id, 100 * prob)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
value={answer.id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</label>
|
|
||||||
{showChoice === 'checkbox' && (
|
|
||||||
<div className="ml-1">
|
|
||||||
{chosenProb && totalChosenProb
|
|
||||||
? Math.round((100 * chosenProb) / totalChosenProb)
|
|
||||||
: 0}
|
|
||||||
% share
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<div
|
||||||
{tradingAllowed(contract) && (
|
className={clsx(
|
||||||
<BuyButton
|
'text-2xl',
|
||||||
className="btn-md flex-initial justify-end self-end !px-8"
|
tradingAllowed(contract) ? 'text-green-500' : 'text-gray-500'
|
||||||
onClick={() => {
|
)}
|
||||||
setIsBetting(true)
|
>
|
||||||
}}
|
{probPercent}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{showChoice ? (
|
||||||
|
<div className="form-control py-1">
|
||||||
|
<label className="label cursor-pointer gap-3">
|
||||||
|
<span className="">Choose this answer</span>
|
||||||
|
{showChoice === 'radio' && (
|
||||||
|
<input
|
||||||
|
className={clsx('radio', chosenProb && '!bg-green-500')}
|
||||||
|
type="radio"
|
||||||
|
name="opt"
|
||||||
|
checked={isChosen}
|
||||||
|
onChange={() => onChoose(answer.id, 1)}
|
||||||
|
value={answer.id}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{wasResolvedTo && (
|
{showChoice === 'checkbox' && (
|
||||||
<Col className="items-end">
|
<input
|
||||||
<div
|
className={clsx('checkbox', chosenProb && '!bg-blue-500')}
|
||||||
className={clsx(
|
type="checkbox"
|
||||||
'text-xl',
|
name="opt"
|
||||||
resolution === 'MKT' ? 'text-blue-700' : 'text-green-700'
|
checked={isChosen}
|
||||||
)}
|
onChange={() => {
|
||||||
>
|
if (isChosen) onDeselect(answer.id)
|
||||||
Chosen{' '}
|
else {
|
||||||
{resolutions
|
onChoose(answer.id, 100 * prob)
|
||||||
? `${Math.round(resolutions[answer.id])}%`
|
}
|
||||||
: ''}
|
}}
|
||||||
</div>
|
value={answer.id}
|
||||||
<div className="text-2xl text-gray-500">{probPercent}</div>
|
/>
|
||||||
</Col>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</label>
|
||||||
)}
|
{showChoice === 'checkbox' && (
|
||||||
</Row>
|
<div className="ml-1">
|
||||||
)}
|
{chosenProb && totalChosenProb
|
||||||
|
? Math.round((100 * chosenProb) / totalChosenProb)
|
||||||
|
: 0}
|
||||||
|
% share
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
wasResolvedTo && (
|
||||||
|
<Col className="items-end">
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'text-xl',
|
||||||
|
resolution === 'MKT' ? 'text-blue-700' : 'text-green-700'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Chosen{' '}
|
||||||
|
{resolutions ? `${Math.round(resolutions[answer.id])}%` : ''}
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl text-gray-500">{probPercent}</div>
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ export function AnswerResolvePanel(props: {
|
||||||
: 'btn-disabled'
|
: 'btn-disabled'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className="gap-4 rounded bg-gray-50 p-4">
|
<Col className="gap-4 rounded">
|
||||||
<div>Resolve your market</div>
|
<div>Resolve your market</div>
|
||||||
<Col className="gap-4 sm:flex-row sm:items-center">
|
<Col className="gap-4 sm:flex-row sm:items-center">
|
||||||
<ChooseCancelSelector
|
<ChooseCancelSelector
|
||||||
|
|
|
@ -38,7 +38,8 @@ export const AnswersGraph = memo(function AnswersGraph(props: {
|
||||||
|
|
||||||
const { width } = useWindowSize()
|
const { width } = useWindowSize()
|
||||||
|
|
||||||
const labelLength = !width || width > 800 ? 50 : 20
|
const isLargeWidth = !width || width > 800
|
||||||
|
const labelLength = isLargeWidth ? 50 : 20
|
||||||
|
|
||||||
const endTime =
|
const endTime =
|
||||||
resolutionTime || isClosed
|
resolutionTime || isClosed
|
||||||
|
@ -68,16 +69,15 @@ export const AnswersGraph = memo(function AnswersGraph(props: {
|
||||||
answers?.find((answer) => answer.id === outcome)?.text ?? 'None'
|
answers?.find((answer) => answer.id === outcome)?.text ?? 'None'
|
||||||
const answerText =
|
const answerText =
|
||||||
answer.slice(0, labelLength) + (answer.length > labelLength ? '...' : '')
|
answer.slice(0, labelLength) + (answer.length > labelLength ? '...' : '')
|
||||||
const id = `#${outcome}: ${answerText}`
|
|
||||||
|
|
||||||
return { id, data: points }
|
return { id: answerText, data: points }
|
||||||
})
|
})
|
||||||
|
|
||||||
data.reverse()
|
data.reverse()
|
||||||
|
|
||||||
const yTickValues = [0, 25, 50, 75, 100]
|
const yTickValues = [0, 25, 50, 75, 100]
|
||||||
|
|
||||||
const numXTickValues = !width || width < 800 ? 2 : 5
|
const numXTickValues = isLargeWidth ? 5 : 2
|
||||||
const hoursAgo = latestTime.subtract(5, 'hours')
|
const hoursAgo = latestTime.subtract(5, 'hours')
|
||||||
const startDate = dayjs(contract.createdTime).isBefore(hoursAgo)
|
const startDate = dayjs(contract.createdTime).isBefore(hoursAgo)
|
||||||
? new Date(contract.createdTime)
|
? new Date(contract.createdTime)
|
||||||
|
@ -87,8 +87,8 @@ export const AnswersGraph = memo(function AnswersGraph(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="w-full overflow-hidden"
|
className="w-full"
|
||||||
style={{ height: height ?? (!width || width >= 800 ? 350 : 225) }}
|
style={{ height: height ?? (isLargeWidth ? 350 : 250) }}
|
||||||
>
|
>
|
||||||
<ResponsiveLine
|
<ResponsiveLine
|
||||||
data={data}
|
data={data}
|
||||||
|
@ -116,6 +116,32 @@ export const AnswersGraph = memo(function AnswersGraph(props: {
|
||||||
enableArea
|
enableArea
|
||||||
areaOpacity={1}
|
areaOpacity={1}
|
||||||
margin={{ top: 20, right: 28, bottom: 22, left: 40 }}
|
margin={{ top: 20, right: 28, bottom: 22, left: 40 }}
|
||||||
|
legends={[
|
||||||
|
{
|
||||||
|
anchor: 'top-left',
|
||||||
|
direction: 'column',
|
||||||
|
justify: false,
|
||||||
|
translateX: isLargeWidth ? 5 : 2,
|
||||||
|
translateY: 0,
|
||||||
|
itemsSpacing: 0,
|
||||||
|
itemTextColor: 'black',
|
||||||
|
itemDirection: 'left-to-right',
|
||||||
|
itemWidth: isLargeWidth ? 288 : 138,
|
||||||
|
itemHeight: 20,
|
||||||
|
itemBackground: 'white',
|
||||||
|
itemOpacity: 0.9,
|
||||||
|
symbolSize: 12,
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
on: 'hover',
|
||||||
|
style: {
|
||||||
|
itemBackground: 'rgba(255, 255, 255, 1)',
|
||||||
|
itemOpacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { useLayoutEffect, useState } from 'react'
|
||||||
|
|
||||||
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
import { DPM, FreeResponse, FullContract } from '../../../common/contract'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { formatPercent } from '../../../common/util/format'
|
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from '../../hooks/use-user'
|
||||||
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
|
import { getDpmOutcomeProbability } from '../../../common/calculate-dpm'
|
||||||
import { useAnswers } from '../../hooks/use-answers'
|
import { useAnswers } from '../../hooks/use-answers'
|
||||||
|
@ -11,6 +10,7 @@ import { tradingAllowed } from '../../lib/firebase/contracts'
|
||||||
import { AnswerItem } from './answer-item'
|
import { AnswerItem } from './answer-item'
|
||||||
import { CreateAnswerPanel } from './create-answer-panel'
|
import { CreateAnswerPanel } from './create-answer-panel'
|
||||||
import { AnswerResolvePanel } from './answer-resolve-panel'
|
import { AnswerResolvePanel } from './answer-resolve-panel'
|
||||||
|
import { Spacer } from '../layout/spacer'
|
||||||
|
|
||||||
export function AnswersPanel(props: {
|
export function AnswersPanel(props: {
|
||||||
contract: FullContract<DPM, FreeResponse>
|
contract: FullContract<DPM, FreeResponse>
|
||||||
|
@ -31,7 +31,7 @@ export function AnswersPanel(props: {
|
||||||
resolutions ? -1 * resolutions[answer.id] : 0
|
resolutions ? -1 * resolutions[answer.id] : 0
|
||||||
),
|
),
|
||||||
..._.sortBy(
|
..._.sortBy(
|
||||||
otherAnswers,
|
resolution ? [] : otherAnswers,
|
||||||
(answer) => -1 * getDpmOutcomeProbability(contract.totalShares, answer.id)
|
(answer) => -1 * getDpmOutcomeProbability(contract.totalShares, answer.id)
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -82,40 +82,41 @@ export function AnswersPanel(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className="gap-3">
|
<Col className="gap-3">
|
||||||
{sortedAnswers.map((answer) => (
|
{(resolveOption === 'CHOOSE' ||
|
||||||
<AnswerItem
|
resolveOption === 'CHOOSE_MULTIPLE' ||
|
||||||
key={answer.id}
|
resolution === 'MKT') &&
|
||||||
answer={answer}
|
sortedAnswers.map((answer) => (
|
||||||
contract={contract}
|
<AnswerItem
|
||||||
user={user}
|
key={answer.id}
|
||||||
showChoice={showChoice}
|
answer={answer}
|
||||||
chosenProb={chosenAnswers[answer.id]}
|
contract={contract}
|
||||||
totalChosenProb={chosenTotal}
|
showChoice={showChoice}
|
||||||
onChoose={onChoose}
|
chosenProb={chosenAnswers[answer.id]}
|
||||||
onDeselect={onDeselect}
|
totalChosenProb={chosenTotal}
|
||||||
/>
|
onChoose={onChoose}
|
||||||
))}
|
onDeselect={onDeselect}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
{sortedAnswers.length === 0 ? (
|
{sortedAnswers.length === 0 && (
|
||||||
<div className="p-4 text-gray-500">No answers yet...</div>
|
<div className="pb-4 text-gray-500">No answers yet...</div>
|
||||||
) : (
|
|
||||||
<div className="self-end p-4 text-gray-500">
|
|
||||||
None of the above:{' '}
|
|
||||||
{formatPercent(getDpmOutcomeProbability(contract.totalShares, '0'))}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{tradingAllowed(contract) && !resolveOption && (
|
{tradingAllowed(contract) &&
|
||||||
<CreateAnswerPanel contract={contract} />
|
(!resolveOption || resolveOption === 'CANCEL') && (
|
||||||
)}
|
<CreateAnswerPanel contract={contract} />
|
||||||
|
)}
|
||||||
|
|
||||||
{user?.id === creatorId && !resolution && (
|
{user?.id === creatorId && !resolution && (
|
||||||
<AnswerResolvePanel
|
<>
|
||||||
contract={contract}
|
<Spacer h={2} />
|
||||||
resolveOption={resolveOption}
|
<AnswerResolvePanel
|
||||||
setResolveOption={setResolveOption}
|
contract={contract}
|
||||||
chosenAnswers={chosenAnswers}
|
resolveOption={resolveOption}
|
||||||
/>
|
setResolveOption={setResolveOption}
|
||||||
|
chosenAnswers={chosenAnswers}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
} from '../../../common/calculate-dpm'
|
} from '../../../common/calculate-dpm'
|
||||||
import { firebaseLogin } from '../../lib/firebase/users'
|
import { firebaseLogin } from '../../lib/firebase/users'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from '../../../common/bet'
|
||||||
|
import { MAX_ANSWER_LENGTH } from '../../../common/answer'
|
||||||
|
|
||||||
export function CreateAnswerPanel(props: {
|
export function CreateAnswerPanel(props: {
|
||||||
contract: FullContract<DPM, FreeResponse>
|
contract: FullContract<DPM, FreeResponse>
|
||||||
|
@ -75,7 +76,7 @@ export function CreateAnswerPanel(props: {
|
||||||
const currentReturnPercent = (currentReturn * 100).toFixed() + '%'
|
const currentReturnPercent = (currentReturn * 100).toFixed() + '%'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className="gap-4 rounded bg-gray-50 p-4">
|
<Col className="gap-4 rounded">
|
||||||
<Col className="flex-1 gap-2">
|
<Col className="flex-1 gap-2">
|
||||||
<div className="mb-1">Add your answer</div>
|
<div className="mb-1">Add your answer</div>
|
||||||
<Textarea
|
<Textarea
|
||||||
|
@ -84,7 +85,7 @@ export function CreateAnswerPanel(props: {
|
||||||
className="textarea textarea-bordered w-full resize-none"
|
className="textarea textarea-bordered w-full resize-none"
|
||||||
placeholder="Type your answer..."
|
placeholder="Type your answer..."
|
||||||
rows={1}
|
rows={1}
|
||||||
maxLength={10000}
|
maxLength={MAX_ANSWER_LENGTH}
|
||||||
/>
|
/>
|
||||||
<div />
|
<div />
|
||||||
<Col
|
<Col
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { Bet } from '../../common/bet'
|
||||||
import { placeBet, sellShares } from '../lib/firebase/api-call'
|
import { placeBet, sellShares } from '../lib/firebase/api-call'
|
||||||
import { BuyAmountInput, SellAmountInput } from './amount-input'
|
import { BuyAmountInput, SellAmountInput } from './amount-input'
|
||||||
import { InfoTooltip } from './info-tooltip'
|
import { InfoTooltip } from './info-tooltip'
|
||||||
import { OutcomeLabel } from './outcome-label'
|
import { BinaryOutcomeLabel, OutcomeLabel } from './outcome-label'
|
||||||
import {
|
import {
|
||||||
calculatePayoutAfterCorrectBet,
|
calculatePayoutAfterCorrectBet,
|
||||||
calculateShares,
|
calculateShares,
|
||||||
|
@ -58,7 +58,7 @@ export function BetPanel(props: {
|
||||||
<Row className="items-center justify-between gap-2">
|
<Row className="items-center justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
You have {formatWithCommas(Math.floor(shares))}{' '}
|
You have {formatWithCommas(Math.floor(shares))}{' '}
|
||||||
<OutcomeLabel outcome={sharesOutcome} /> shares
|
<BinaryOutcomeLabel outcome={sharesOutcome} /> shares
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@ -147,7 +147,7 @@ export function BetPanelSwitcher(props: {
|
||||||
<Row className="items-center justify-between gap-2">
|
<Row className="items-center justify-between gap-2">
|
||||||
<div>
|
<div>
|
||||||
You have {formatWithCommas(Math.floor(shares))}{' '}
|
You have {formatWithCommas(Math.floor(shares))}{' '}
|
||||||
<OutcomeLabel outcome={sharesOutcome} /> shares
|
<BinaryOutcomeLabel outcome={sharesOutcome} /> shares
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@ -349,11 +349,12 @@ function BuyPanel(props: {
|
||||||
{contract.mechanism === 'dpm-2' ? (
|
{contract.mechanism === 'dpm-2' ? (
|
||||||
<>
|
<>
|
||||||
Estimated
|
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>
|
</div>
|
||||||
|
@ -547,7 +548,12 @@ function SellSharesModal(props: {
|
||||||
|
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
You have {formatWithCommas(Math.floor(shares))}{' '}
|
You have {formatWithCommas(Math.floor(shares))}{' '}
|
||||||
<OutcomeLabel outcome={sharesOutcome} /> shares
|
<OutcomeLabel
|
||||||
|
outcome={sharesOutcome}
|
||||||
|
contract={contract}
|
||||||
|
truncate="long"
|
||||||
|
/>{' '}
|
||||||
|
shares
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SellPanel
|
<SellPanel
|
||||||
|
|
|
@ -250,11 +250,16 @@ function MyContractBets(props: {
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Row className="flex-1 items-center gap-2 text-sm text-gray-500">
|
<Row className="flex-1 items-center gap-2 text-sm text-gray-500">
|
||||||
{isBinary && (
|
{(isBinary || resolution) && (
|
||||||
<>
|
<>
|
||||||
{resolution ? (
|
{resolution ? (
|
||||||
<div>
|
<div>
|
||||||
Resolved <OutcomeLabel outcome={resolution} />
|
Resolved{' '}
|
||||||
|
<OutcomeLabel
|
||||||
|
outcome={resolution}
|
||||||
|
contract={contract}
|
||||||
|
truncate="short"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-primary text-lg">{probPercent}</div>
|
<div className="text-primary text-lg">{probPercent}</div>
|
||||||
|
@ -510,7 +515,15 @@ function BetRow(props: { bet: Bet; contract: Contract; saleBet?: Bet }) {
|
||||||
</td>
|
</td>
|
||||||
{isCPMM && <td>{shares >= 0 ? 'BUY' : 'SELL'}</td>}
|
{isCPMM && <td>{shares >= 0 ? 'BUY' : 'SELL'}</td>}
|
||||||
<td>
|
<td>
|
||||||
<OutcomeLabel outcome={outcome} />
|
{outcome === '0' ? (
|
||||||
|
'ANTE'
|
||||||
|
) : (
|
||||||
|
<OutcomeLabel
|
||||||
|
outcome={outcome}
|
||||||
|
contract={contract}
|
||||||
|
truncate="short"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td>{formatMoney(Math.abs(amount))}</td>
|
<td>{formatMoney(Math.abs(amount))}</td>
|
||||||
{!isCPMM && <td>{saleDisplay}</td>}
|
{!isCPMM && <td>{saleDisplay}</td>}
|
||||||
|
@ -561,7 +574,8 @@ function SellButton(props: { contract: Contract; bet: Bet }) {
|
||||||
>
|
>
|
||||||
<div className="mb-4 text-2xl">
|
<div className="mb-4 text-2xl">
|
||||||
Sell {formatWithCommas(shares)} shares of{' '}
|
Sell {formatWithCommas(shares)} shares of{' '}
|
||||||
<OutcomeLabel outcome={outcome} /> for {formatMoney(saleAmount)}?
|
<OutcomeLabel outcome={outcome} contract={contract} truncate="long" />{' '}
|
||||||
|
for {formatMoney(saleAmount)}?
|
||||||
</div>
|
</div>
|
||||||
{!!loanAmount && (
|
{!!loanAmount && (
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
|
|
|
@ -3,7 +3,7 @@ import Link from 'next/link'
|
||||||
import { ClockIcon, DatabaseIcon, PencilIcon } from '@heroicons/react/outline'
|
import { ClockIcon, DatabaseIcon, PencilIcon } from '@heroicons/react/outline'
|
||||||
import { TrendingUpIcon } from '@heroicons/react/solid'
|
import { TrendingUpIcon } from '@heroicons/react/solid'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { formatMoney, formatPercent } from '../../../common/util/format'
|
import { formatMoney } from '../../../common/util/format'
|
||||||
import { UserLink } from '../user-page'
|
import { UserLink } from '../user-page'
|
||||||
import {
|
import {
|
||||||
Contract,
|
Contract,
|
||||||
|
@ -19,9 +19,20 @@ import { fromNow } from '../../lib/util/time'
|
||||||
import { Avatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
import { Spacer } from '../layout/spacer'
|
import { Spacer } from '../layout/spacer'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { getProbability } from '../../../common/calculate'
|
|
||||||
import { ContractInfoDialog } from './contract-info-dialog'
|
import { ContractInfoDialog } from './contract-info-dialog'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from '../../../common/bet'
|
||||||
|
import {
|
||||||
|
Binary,
|
||||||
|
CPMM,
|
||||||
|
DPM,
|
||||||
|
FreeResponse,
|
||||||
|
FreeResponseContract,
|
||||||
|
FullContract,
|
||||||
|
} from '../../../common/contract'
|
||||||
|
import {
|
||||||
|
BinaryContractOutcomeLabel,
|
||||||
|
FreeResponseOutcomeLabel,
|
||||||
|
} from '../outcome-label'
|
||||||
|
|
||||||
export function ContractCard(props: {
|
export function ContractCard(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
|
@ -30,7 +41,7 @@ export function ContractCard(props: {
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
const { contract, showHotVolume, showCloseTime, className } = props
|
const { contract, showHotVolume, showCloseTime, className } = props
|
||||||
const { question } = contract
|
const { question, outcomeType, resolution } = contract
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -51,54 +62,48 @@ export function ContractCard(props: {
|
||||||
/>
|
/>
|
||||||
<Spacer h={3} />
|
<Spacer h={3} />
|
||||||
|
|
||||||
<Row className="justify-between gap-4">
|
<Row
|
||||||
|
className={clsx(
|
||||||
|
'justify-between gap-4',
|
||||||
|
outcomeType === 'FREE_RESPONSE' && 'flex-col items-start'
|
||||||
|
)}
|
||||||
|
>
|
||||||
<p
|
<p
|
||||||
className="break-words font-medium text-indigo-700"
|
className="break-words font-medium text-indigo-700"
|
||||||
style={{ /* For iOS safari */ wordBreak: 'break-word' }}
|
style={{ /* For iOS safari */ wordBreak: 'break-word' }}
|
||||||
>
|
>
|
||||||
{question}
|
{question}
|
||||||
</p>
|
</p>
|
||||||
<ResolutionOrChance className="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>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ResolutionOrChance(props: {
|
export function BinaryResolutionOrChance(props: {
|
||||||
contract: Contract
|
contract: FullContract<DPM | CPMM, Binary>
|
||||||
large?: boolean
|
large?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
}) {
|
}) {
|
||||||
const { contract, large, className } = props
|
const { contract, large, className } = props
|
||||||
const { resolution, outcomeType } = contract
|
const { resolution } = contract
|
||||||
const isBinary = outcomeType === 'BINARY'
|
|
||||||
const marketClosed = (contract.closeTime || Infinity) < Date.now()
|
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 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 || ''] ?? `#${resolution}`
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className={clsx(large ? 'text-4xl' : 'text-3xl', className)}>
|
<Col className={clsx(large ? 'text-4xl' : 'text-3xl', className)}>
|
||||||
{resolution ? (
|
{resolution ? (
|
||||||
|
@ -108,22 +113,41 @@ export function ResolutionOrChance(props: {
|
||||||
>
|
>
|
||||||
Resolved
|
Resolved
|
||||||
</div>
|
</div>
|
||||||
<div className={resolutionColor}>{resolutionText}</div>
|
<BinaryContractOutcomeLabel
|
||||||
|
contract={contract}
|
||||||
|
resolution={resolution}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
isBinary && (
|
<>
|
||||||
<>
|
<div className={probColor}>{getBinaryProbPercent(contract)}</div>
|
||||||
<div className={probColor}>{getBinaryProbPercent(contract)}</div>
|
<div className={clsx(probColor, large ? 'text-xl' : 'text-base')}>
|
||||||
<div className={clsx(probColor, large ? 'text-xl' : 'text-base')}>
|
chance
|
||||||
chance
|
</div>
|
||||||
</div>
|
</>
|
||||||
</>
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
</Col>
|
</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: {
|
function AbbrContractDetails(props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
showHotVolume?: boolean
|
showHotVolume?: boolean
|
||||||
|
|
|
@ -6,7 +6,11 @@ import { useUser } from '../../hooks/use-user'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { Linkify } from '../linkify'
|
import { Linkify } from '../linkify'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { ContractDetails, ResolutionOrChance } from './contract-card'
|
import {
|
||||||
|
FreeResponseResolution,
|
||||||
|
ContractDetails,
|
||||||
|
BinaryResolutionOrChance,
|
||||||
|
} from './contract-card'
|
||||||
import { Bet } from '../../../common/bet'
|
import { Bet } from '../../../common/bet'
|
||||||
import { Comment } from '../../../common/comment'
|
import { Comment } from '../../../common/comment'
|
||||||
import BetRow from '../bet-row'
|
import BetRow from '../bet-row'
|
||||||
|
@ -18,11 +22,10 @@ export const ContractOverview = (props: {
|
||||||
contract: Contract
|
contract: Contract
|
||||||
bets: Bet[]
|
bets: Bet[]
|
||||||
comments: Comment[]
|
comments: Comment[]
|
||||||
children?: any
|
|
||||||
className?: string
|
className?: string
|
||||||
}) => {
|
}) => {
|
||||||
const { contract, bets, comments, children, className } = props
|
const { contract, bets, className } = props
|
||||||
const { question, resolution, creatorId, outcomeType } = contract
|
const { question, creatorId, outcomeType, resolution } = contract
|
||||||
|
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const isCreator = user?.id === creatorId
|
const isCreator = user?.id === creatorId
|
||||||
|
@ -36,8 +39,8 @@ export const ContractOverview = (props: {
|
||||||
<Linkify text={question} />
|
<Linkify text={question} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(isBinary || resolution) && (
|
{isBinary && (
|
||||||
<ResolutionOrChance
|
<BinaryResolutionOrChance
|
||||||
className="hidden items-end xl:flex"
|
className="hidden items-end xl:flex"
|
||||||
contract={contract}
|
contract={contract}
|
||||||
large
|
large
|
||||||
|
@ -45,15 +48,24 @@ export const ContractOverview = (props: {
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Row className="items-center justify-between gap-4 xl:hidden">
|
{isBinary ? (
|
||||||
{(isBinary || resolution) && (
|
<Row className="items-center justify-between gap-4 xl:hidden">
|
||||||
<ResolutionOrChance contract={contract} />
|
<BinaryResolutionOrChance contract={contract} />
|
||||||
)}
|
|
||||||
|
|
||||||
{isBinary && tradingAllowed(contract) && (
|
{tradingAllowed(contract) && (
|
||||||
<BetRow contract={contract} labelClassName="hidden" />
|
<BetRow contract={contract} labelClassName="hidden" />
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
|
) : (
|
||||||
|
outcomeType === 'FREE_RESPONSE' &&
|
||||||
|
resolution && (
|
||||||
|
<FreeResponseResolution
|
||||||
|
contract={contract}
|
||||||
|
resolution={resolution}
|
||||||
|
truncate="none"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
|
||||||
<ContractDetails
|
<ContractDetails
|
||||||
contract={contract}
|
contract={contract}
|
||||||
|
@ -73,19 +85,13 @@ export const ContractOverview = (props: {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Spacer h={6} />
|
{contract.description && <Spacer h={6} />}
|
||||||
|
|
||||||
<ContractDescription
|
<ContractDescription
|
||||||
className="px-2"
|
className="px-2"
|
||||||
contract={contract}
|
contract={contract}
|
||||||
isCreator={isCreator}
|
isCreator={isCreator}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Spacer h={4} />
|
|
||||||
|
|
||||||
{children}
|
|
||||||
|
|
||||||
<Spacer h={4} />
|
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,9 +97,10 @@ function groupBets(
|
||||||
hideOutcome: boolean
|
hideOutcome: boolean
|
||||||
abbreviated: boolean
|
abbreviated: boolean
|
||||||
smallAvatar: boolean
|
smallAvatar: boolean
|
||||||
|
reversed: boolean
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const { hideOutcome, abbreviated, smallAvatar } = options
|
const { hideOutcome, abbreviated, smallAvatar, reversed } = options
|
||||||
|
|
||||||
const commentsMap = mapCommentsByBetId(comments)
|
const commentsMap = mapCommentsByBetId(comments)
|
||||||
const items: ActivityItem[] = []
|
const items: ActivityItem[] = []
|
||||||
|
@ -171,7 +172,9 @@ function groupBets(
|
||||||
if (group.length > 0) {
|
if (group.length > 0) {
|
||||||
pushGroup()
|
pushGroup()
|
||||||
}
|
}
|
||||||
return abbreviated ? items.slice(-3) : items
|
const abbrItems = abbreviated ? items.slice(-3) : items
|
||||||
|
if (reversed) abbrItems.reverse()
|
||||||
|
return abbrItems
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAnswerGroups(
|
function getAnswerGroups(
|
||||||
|
@ -182,9 +185,10 @@ function getAnswerGroups(
|
||||||
options: {
|
options: {
|
||||||
sortByProb: boolean
|
sortByProb: boolean
|
||||||
abbreviated: boolean
|
abbreviated: boolean
|
||||||
|
reversed: boolean
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const { sortByProb, abbreviated } = options
|
const { sortByProb, abbreviated, reversed } = options
|
||||||
|
|
||||||
let outcomes = _.uniq(bets.map((bet) => bet.outcome)).filter(
|
let outcomes = _.uniq(bets.map((bet) => bet.outcome)).filter(
|
||||||
(outcome) => getOutcomeProbability(contract, outcome) > 0.0001
|
(outcome) => getOutcomeProbability(contract, outcome) > 0.0001
|
||||||
|
@ -208,9 +212,8 @@ function getAnswerGroups(
|
||||||
outcomes = outcomes.slice(-2)
|
outcomes = outcomes.slice(-2)
|
||||||
}
|
}
|
||||||
if (sortByProb) {
|
if (sortByProb) {
|
||||||
outcomes = _.sortBy(
|
outcomes = _.sortBy(outcomes, (outcome) =>
|
||||||
outcomes,
|
getOutcomeProbability(contract, outcome)
|
||||||
(outcome) => -1 * getOutcomeProbability(contract, outcome)
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Sort by recent bet.
|
// Sort by recent bet.
|
||||||
|
@ -233,6 +236,7 @@ function getAnswerGroups(
|
||||||
hideOutcome: true,
|
hideOutcome: true,
|
||||||
abbreviated,
|
abbreviated,
|
||||||
smallAvatar: true,
|
smallAvatar: true,
|
||||||
|
reversed,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (abbreviated) items = items.slice(-2)
|
if (abbreviated) items = items.slice(-2)
|
||||||
|
@ -264,6 +268,8 @@ export function getAllContractActivityItems(
|
||||||
const { abbreviated } = options
|
const { abbreviated } = options
|
||||||
const { outcomeType } = contract
|
const { outcomeType } = contract
|
||||||
|
|
||||||
|
const reversed = !abbreviated
|
||||||
|
|
||||||
bets =
|
bets =
|
||||||
outcomeType === 'BINARY'
|
outcomeType === 'BINARY'
|
||||||
? bets.filter((bet) => !bet.isAnte && !bet.isRedemption)
|
? bets.filter((bet) => !bet.isAnte && !bet.isRedemption)
|
||||||
|
@ -301,12 +307,14 @@ export function getAllContractActivityItems(
|
||||||
{
|
{
|
||||||
sortByProb: true,
|
sortByProb: true,
|
||||||
abbreviated,
|
abbreviated,
|
||||||
|
reversed,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
: groupBets(bets, comments, contract, user?.id, {
|
: groupBets(bets, comments, contract, user?.id, {
|
||||||
hideOutcome: !!filterToOutcome,
|
hideOutcome: !!filterToOutcome,
|
||||||
abbreviated,
|
abbreviated,
|
||||||
smallAvatar: !!filterToOutcome,
|
smallAvatar: !!filterToOutcome,
|
||||||
|
reversed: false,
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -317,14 +325,7 @@ export function getAllContractActivityItems(
|
||||||
items.push({ type: 'resolve', id: `${contract.resolutionTime}`, contract })
|
items.push({ type: 'resolve', id: `${contract.resolutionTime}`, contract })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!abbreviated) {
|
if (reversed) items.reverse()
|
||||||
items.reverse()
|
|
||||||
for (const item of items) {
|
|
||||||
if (item.type === 'answergroup') {
|
|
||||||
item.items.reverse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
@ -362,12 +363,14 @@ export function getRecentContractActivityItems(
|
||||||
{
|
{
|
||||||
sortByProb: false,
|
sortByProb: false,
|
||||||
abbreviated: true,
|
abbreviated: true,
|
||||||
|
reversed: false,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
: groupBets(bets, comments, contract, user?.id, {
|
: groupBets(bets, comments, contract, user?.id, {
|
||||||
hideOutcome: false,
|
hideOutcome: false,
|
||||||
abbreviated: true,
|
abbreviated: true,
|
||||||
smallAvatar: false,
|
smallAvatar: false,
|
||||||
|
reversed: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
return [questionItem, ...items]
|
return [questionItem, ...items]
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { Row } from '../layout/row'
|
||||||
import { createComment, MAX_COMMENT_LENGTH } from '../../lib/firebase/comments'
|
import { createComment, MAX_COMMENT_LENGTH } from '../../lib/firebase/comments'
|
||||||
import { formatMoney, formatPercent } from '../../../common/util/format'
|
import { formatMoney, formatPercent } from '../../../common/util/format'
|
||||||
import { Comment } from '../../../common/comment'
|
import { Comment } from '../../../common/comment'
|
||||||
import { ResolutionOrChance } from '../contract/contract-card'
|
import { BinaryResolutionOrChance } from '../contract/contract-card'
|
||||||
import { SiteLink } from '../site-link'
|
import { SiteLink } from '../site-link'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { UserLink } from '../user-page'
|
import { UserLink } from '../user-page'
|
||||||
|
@ -144,7 +144,12 @@ export function FeedComment(props: {
|
||||||
{!hideOutcome && (
|
{!hideOutcome && (
|
||||||
<>
|
<>
|
||||||
{' '}
|
{' '}
|
||||||
of <OutcomeLabel outcome={outcome} />
|
of{' '}
|
||||||
|
<OutcomeLabel
|
||||||
|
outcome={outcome}
|
||||||
|
contract={contract}
|
||||||
|
truncate="short"
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<RelativeTimestamp time={createdTime} />
|
<RelativeTimestamp time={createdTime} />
|
||||||
|
@ -227,7 +232,12 @@ export function FeedBet(props: {
|
||||||
{!hideOutcome && (
|
{!hideOutcome && (
|
||||||
<>
|
<>
|
||||||
{' '}
|
{' '}
|
||||||
of <OutcomeLabel outcome={outcome} />
|
of{' '}
|
||||||
|
<OutcomeLabel
|
||||||
|
outcome={outcome}
|
||||||
|
contract={contract}
|
||||||
|
truncate="short"
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<RelativeTimestamp time={createdTime} />
|
<RelativeTimestamp time={createdTime} />
|
||||||
|
@ -344,8 +354,11 @@ export function FeedQuestion(props: {
|
||||||
{question}
|
{question}
|
||||||
</SiteLink>
|
</SiteLink>
|
||||||
</Col>
|
</Col>
|
||||||
{(isBinary || resolution) && (
|
{isBinary && (
|
||||||
<ResolutionOrChance className="items-center" contract={contract} />
|
<BinaryResolutionOrChance
|
||||||
|
className="items-center"
|
||||||
|
contract={contract}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
{showDescription && (
|
{showDescription && (
|
||||||
|
@ -449,7 +462,12 @@ function FeedResolve(props: { contract: Contract }) {
|
||||||
name={creatorName}
|
name={creatorName}
|
||||||
username={creatorUsername}
|
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} />
|
<RelativeTimestamp time={contract.resolutionTime || 0} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -482,8 +500,12 @@ function FeedClose(props: { contract: Contract }) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function BetGroupSpan(props: { bets: Bet[]; outcome?: string }) {
|
function BetGroupSpan(props: {
|
||||||
const { bets, outcome } = props
|
contract: Contract
|
||||||
|
bets: Bet[]
|
||||||
|
outcome?: string
|
||||||
|
}) {
|
||||||
|
const { contract, bets, outcome } = props
|
||||||
|
|
||||||
const numberTraders = _.uniqBy(bets, (b) => b.userId).length
|
const numberTraders = _.uniqBy(bets, (b) => b.userId).length
|
||||||
|
|
||||||
|
@ -501,7 +523,12 @@ function BetGroupSpan(props: { bets: Bet[]; outcome?: string }) {
|
||||||
{outcome && (
|
{outcome && (
|
||||||
<>
|
<>
|
||||||
{' '}
|
{' '}
|
||||||
of <OutcomeLabel outcome={outcome} />
|
of{' '}
|
||||||
|
<OutcomeLabel
|
||||||
|
outcome={outcome}
|
||||||
|
contract={contract}
|
||||||
|
truncate="short"
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}{' '}
|
)}{' '}
|
||||||
</span>
|
</span>
|
||||||
|
@ -513,7 +540,7 @@ function FeedBetGroup(props: {
|
||||||
bets: Bet[]
|
bets: Bet[]
|
||||||
hideOutcome: boolean
|
hideOutcome: boolean
|
||||||
}) {
|
}) {
|
||||||
const { bets, hideOutcome } = props
|
const { contract, bets, hideOutcome } = props
|
||||||
|
|
||||||
const betGroups = _.groupBy(bets, (bet) => bet.outcome)
|
const betGroups = _.groupBy(bets, (bet) => bet.outcome)
|
||||||
const outcomes = Object.keys(betGroups)
|
const outcomes = Object.keys(betGroups)
|
||||||
|
@ -535,6 +562,7 @@ function FeedBetGroup(props: {
|
||||||
{outcomes.map((outcome, index) => (
|
{outcomes.map((outcome, index) => (
|
||||||
<Fragment key={outcome}>
|
<Fragment key={outcome}>
|
||||||
<BetGroupSpan
|
<BetGroupSpan
|
||||||
|
contract={contract}
|
||||||
outcome={hideOutcome ? undefined : outcome}
|
outcome={hideOutcome ? undefined : outcome}
|
||||||
bets={betGroups[outcome]}
|
bets={betGroups[outcome]}
|
||||||
/>
|
/>
|
||||||
|
@ -583,7 +611,7 @@ function FeedAnswerGroup(props: {
|
||||||
<UserLink username={username} name={name} /> answered
|
<UserLink username={username} name={name} /> answered
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Row className="align-items justify-between gap-4">
|
<Col className="align-items justify-between gap-4 sm:flex-row">
|
||||||
<span className="whitespace-pre-line text-lg">
|
<span className="whitespace-pre-line text-lg">
|
||||||
<Linkify text={text} />
|
<Linkify text={text} />
|
||||||
</span>
|
</span>
|
||||||
|
@ -599,13 +627,13 @@ function FeedAnswerGroup(props: {
|
||||||
</span>
|
</span>
|
||||||
<BuyButton
|
<BuyButton
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'btn-sm hidden flex-initial !px-6 sm:flex',
|
'btn-sm flex-initial !px-6 sm:flex',
|
||||||
tradingAllowed(contract) ? '' : '!hidden'
|
tradingAllowed(contract) ? '' : '!hidden'
|
||||||
)}
|
)}
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
</Row>
|
</Col>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
@ -628,15 +656,6 @@ function FeedAnswerGroup(props: {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div
|
|
||||||
className={clsx('ml-10 mt-4', tradingAllowed(contract) ? '' : 'hidden')}
|
|
||||||
>
|
|
||||||
<BuyButton
|
|
||||||
className="btn-sm !px-6 sm:hidden"
|
|
||||||
onClick={() => setOpen(true)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,74 @@
|
||||||
|
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: {
|
export function OutcomeLabel(props: {
|
||||||
|
contract: Contract
|
||||||
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT' | string
|
outcome: 'YES' | 'NO' | 'CANCEL' | 'MKT' | string
|
||||||
|
truncate: 'short' | 'long' | 'none'
|
||||||
|
}) {
|
||||||
|
const { outcome, contract, truncate } = props
|
||||||
|
|
||||||
|
if (contract.outcomeType === 'BINARY')
|
||||||
|
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
|
const { outcome } = props
|
||||||
|
|
||||||
if (outcome === 'YES') return <YesLabel />
|
if (outcome === 'YES') return <YesLabel />
|
||||||
if (outcome === 'NO') return <NoLabel />
|
if (outcome === 'NO') return <NoLabel />
|
||||||
if (outcome === 'MKT') return <ProbLabel />
|
if (outcome === 'MKT') return <ProbLabel />
|
||||||
if (outcome === 'CANCEL') return <CancelLabel />
|
return <CancelLabel />
|
||||||
return <AnswerNumberLabel number={outcome} />
|
}
|
||||||
|
|
||||||
|
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() {
|
export function YesLabel() {
|
||||||
|
@ -26,6 +87,32 @@ export function ProbLabel() {
|
||||||
return <span className="text-blue-400">PROB</span>
|
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 }) {
|
export function AnswerNumberLabel(props: { number: string }) {
|
||||||
return <span className="text-primary">#{props.number}</span>
|
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>
|
||||||
|
}
|
||||||
|
|
|
@ -195,7 +195,7 @@ function Button(props: {
|
||||||
color === 'red' && 'bg-red-400 text-white hover:bg-red-500',
|
color === 'red' && 'bg-red-400 text-white hover:bg-red-500',
|
||||||
color === 'yellow' && 'bg-yellow-400 text-white hover:bg-yellow-500',
|
color === 'yellow' && 'bg-yellow-400 text-white hover:bg-yellow-500',
|
||||||
color === 'blue' && 'bg-blue-400 text-white hover:bg-blue-500',
|
color === 'blue' && 'bg-blue-400 text-white hover:bg-blue-500',
|
||||||
color === 'gray' && 'bg-gray-300 text-gray-700 hover:bg-gray-400',
|
color === 'gray' && 'bg-gray-200 text-gray-700 hover:bg-gray-300',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
|
|
@ -98,7 +98,7 @@ export function ContractPageContent(props: FirstArgument<typeof ContractPage>) {
|
||||||
return <Custom404 />
|
return <Custom404 />
|
||||||
}
|
}
|
||||||
|
|
||||||
const { creatorId, isResolved, question, outcomeType } = contract
|
const { creatorId, isResolved, question, outcomeType, resolution } = contract
|
||||||
|
|
||||||
const isCreator = user?.id === creatorId
|
const isCreator = user?.id === creatorId
|
||||||
const isBinary = outcomeType === 'BINARY'
|
const isBinary = outcomeType === 'BINARY'
|
||||||
|
@ -143,8 +143,10 @@ export function ContractPageContent(props: FirstArgument<typeof ContractPage>) {
|
||||||
contract={contract}
|
contract={contract}
|
||||||
bets={bets ?? []}
|
bets={bets ?? []}
|
||||||
comments={comments ?? []}
|
comments={comments ?? []}
|
||||||
>
|
/>
|
||||||
{contract.outcomeType === 'FREE_RESPONSE' && (
|
|
||||||
|
{outcomeType === 'FREE_RESPONSE' &&
|
||||||
|
(!isResolved || resolution === 'MKT') && (
|
||||||
<>
|
<>
|
||||||
<Spacer h={4} />
|
<Spacer h={4} />
|
||||||
<AnswersPanel
|
<AnswersPanel
|
||||||
|
@ -153,7 +155,6 @@ export function ContractPageContent(props: FirstArgument<typeof ContractPage>) {
|
||||||
<Spacer h={4} />
|
<Spacer h={4} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ContractOverview>
|
|
||||||
|
|
||||||
{contract.isResolved && (
|
{contract.isResolved && (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -8,8 +8,9 @@ import {
|
||||||
import { DOMAIN } from '../../../../common/envs/constants'
|
import { DOMAIN } from '../../../../common/envs/constants'
|
||||||
import { AnswersGraph } from '../../../components/answers/answers-graph'
|
import { AnswersGraph } from '../../../components/answers/answers-graph'
|
||||||
import {
|
import {
|
||||||
ResolutionOrChance,
|
BinaryResolutionOrChance,
|
||||||
ContractDetails,
|
ContractDetails,
|
||||||
|
FreeResponseResolution,
|
||||||
} from '../../../components/contract/contract-card'
|
} from '../../../components/contract/contract-card'
|
||||||
import { ContractProbGraph } from '../../../components/contract/contract-prob-graph'
|
import { ContractProbGraph } from '../../../components/contract/contract-prob-graph'
|
||||||
import { Col } from '../../../components/layout/col'
|
import { Col } from '../../../components/layout/col'
|
||||||
|
@ -118,8 +119,14 @@ function ContractEmbed(props: { contract: Contract; bets: Bet[] }) {
|
||||||
hideShareButtons
|
hideShareButtons
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{(isBinary || resolution) && (
|
{isBinary && <BinaryResolutionOrChance contract={contract} />}
|
||||||
<ResolutionOrChance contract={contract} />
|
|
||||||
|
{outcomeType === 'FREE_RESPONSE' && resolution && (
|
||||||
|
<FreeResponseResolution
|
||||||
|
contract={contract}
|
||||||
|
resolution={resolution}
|
||||||
|
truncate="long"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user