Allow for non-binary contracts in contract page and related components
This commit is contained in:
parent
7223fa38bc
commit
5411401a2e
|
@ -33,19 +33,12 @@ export const createContract = functions
|
|||
const creator = await getUser(userId)
|
||||
if (!creator) return { status: 'error', message: 'User not found' }
|
||||
|
||||
const {
|
||||
question,
|
||||
outcomeType,
|
||||
description,
|
||||
initialProb,
|
||||
ante,
|
||||
closeTime,
|
||||
tags,
|
||||
} = data
|
||||
const { question, description, initialProb, ante, closeTime, tags } = data
|
||||
|
||||
if (!question)
|
||||
return { status: 'error', message: 'Missing question field' }
|
||||
|
||||
let outcomeType = data.outcomeType ?? 'BINARY'
|
||||
if (outcomeType !== 'BINARY' && outcomeType !== 'MULTI')
|
||||
return { status: 'error', message: 'Invalid outcomeType' }
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
Contract,
|
||||
getContractFromId,
|
||||
contractPath,
|
||||
contractMetrics,
|
||||
getBinaryProbPercent,
|
||||
} from '../lib/firebase/contracts'
|
||||
import { Row } from './layout/row'
|
||||
import { UserLink } from './user-page'
|
||||
|
@ -159,7 +159,7 @@ function MyContractBets(props: { contract: Contract; bets: Bet[] }) {
|
|||
const { resolution } = contract
|
||||
|
||||
const [collapsed, setCollapsed] = useState(true)
|
||||
const { probPercent } = contractMetrics(contract)
|
||||
const probPercent = getBinaryProbPercent(contract)
|
||||
return (
|
||||
<div
|
||||
tabIndex={0}
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
Contract,
|
||||
contractMetrics,
|
||||
contractPath,
|
||||
getBinaryProbPercent,
|
||||
} from '../lib/firebase/contracts'
|
||||
import { Col } from './layout/col'
|
||||
import dayjs from 'dayjs'
|
||||
|
@ -25,7 +26,7 @@ export function ContractCard(props: {
|
|||
}) {
|
||||
const { contract, showHotVolume, showCloseTime, className } = props
|
||||
const { question, resolution } = contract
|
||||
const { probPercent } = contractMetrics(contract)
|
||||
const probPercent = getBinaryProbPercent(contract)
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
CheckIcon,
|
||||
DotsVerticalIcon,
|
||||
LockClosedIcon,
|
||||
StarIcon,
|
||||
UserIcon,
|
||||
UsersIcon,
|
||||
XIcon,
|
||||
|
@ -21,6 +20,7 @@ import {
|
|||
contractPath,
|
||||
updateContract,
|
||||
tradingAllowed,
|
||||
getBinaryProbPercent,
|
||||
} from '../lib/firebase/contracts'
|
||||
import { useUser } from '../hooks/use-user'
|
||||
import { Linkify } from './linkify'
|
||||
|
@ -33,8 +33,8 @@ import { SiteLink } from './site-link'
|
|||
import { Col } from './layout/col'
|
||||
import { UserLink } from './user-page'
|
||||
import { DateTimeTooltip } from './datetime-tooltip'
|
||||
import { useBets } from '../hooks/use-bets'
|
||||
import { Bet, withoutAnteBets } from '../lib/firebase/bets'
|
||||
import { useBetsWithoutAntes } from '../hooks/use-bets'
|
||||
import { Bet } from '../lib/firebase/bets'
|
||||
import { Comment, mapCommentsByBetId } from '../lib/firebase/comments'
|
||||
import { JoinSpans } from './join-spans'
|
||||
import Textarea from 'react-expanding-textarea'
|
||||
|
@ -302,9 +302,8 @@ function TruncatedComment(props: {
|
|||
|
||||
function FeedQuestion(props: { contract: Contract }) {
|
||||
const { contract } = props
|
||||
const { creatorName, creatorUsername, createdTime, question, resolution } =
|
||||
contract
|
||||
const { probPercent, truePool } = contractMetrics(contract)
|
||||
const { creatorName, creatorUsername, question, resolution } = contract
|
||||
const { truePool } = contractMetrics(contract)
|
||||
|
||||
// Currently hidden on mobile; ideally we'd fit this in somewhere.
|
||||
const closeMessage =
|
||||
|
@ -343,7 +342,7 @@ function FeedQuestion(props: { contract: Contract }) {
|
|||
<ResolutionOrChance
|
||||
className="items-center"
|
||||
resolution={resolution}
|
||||
probPercent={probPercent}
|
||||
probPercent={getBinaryProbPercent(contract)}
|
||||
/>
|
||||
</Col>
|
||||
<TruncatedComment
|
||||
|
@ -642,12 +641,13 @@ export function ContractFeed(props: {
|
|||
betRowClassName?: string
|
||||
}) {
|
||||
const { contract, feedType, betRowClassName } = props
|
||||
const { id } = contract
|
||||
const { id, outcomeType } = contract
|
||||
const isBinary = outcomeType === 'BINARY'
|
||||
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
const user = useUser()
|
||||
|
||||
let bets = useBets(id) ?? props.bets
|
||||
bets = withoutAnteBets(contract, bets)
|
||||
const bets = useBetsWithoutAntes(contract, props.bets) ?? []
|
||||
|
||||
const comments = useComments(id) ?? props.comments
|
||||
|
||||
|
@ -715,7 +715,7 @@ export function ContractFeed(props: {
|
|||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{tradingAllowed(contract) && (
|
||||
{isBinary && tradingAllowed(contract) && (
|
||||
<BetRow contract={contract} className={clsx('mb-2', betRowClassName)} />
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {
|
||||
contractMetrics,
|
||||
Contract,
|
||||
deleteContract,
|
||||
contractPath,
|
||||
tradingAllowed,
|
||||
getBinaryProbPercent,
|
||||
} from '../lib/firebase/contracts'
|
||||
import { Col } from './layout/col'
|
||||
import { Spacer } from './layout/spacer'
|
||||
|
@ -31,62 +31,59 @@ export const ContractOverview = (props: {
|
|||
className?: string
|
||||
}) => {
|
||||
const { contract, bets, comments, folds, className } = props
|
||||
const { resolution, creatorId, creatorName } = contract
|
||||
const { probPercent, truePool } = contractMetrics(contract)
|
||||
const { question, resolution, creatorId, outcomeType } = contract
|
||||
|
||||
const user = useUser()
|
||||
const isCreator = user?.id === creatorId
|
||||
const isBinary = outcomeType === 'BINARY'
|
||||
|
||||
const tweetQuestion = isCreator
|
||||
? contract.question
|
||||
: `${creatorName}: ${contract.question}`
|
||||
const tweetDescription = resolution
|
||||
? `Resolved ${resolution}!`
|
||||
: `Currently ${probPercent} chance, place your bets here:`
|
||||
const url = `https://manifold.markets${contractPath(contract)}`
|
||||
const tweetText = `${tweetQuestion}\n\n${tweetDescription}\n\n${url}`
|
||||
const tweetText = getTweetText(contract, isCreator)
|
||||
|
||||
return (
|
||||
<Col className={clsx('mb-6', className)}>
|
||||
<Row className="justify-between gap-4 px-2">
|
||||
<Col className="gap-4">
|
||||
<div className="text-2xl text-indigo-700 md:text-3xl">
|
||||
<Linkify text={contract.question} />
|
||||
<Linkify text={question} />
|
||||
</div>
|
||||
|
||||
<Row className="items-center justify-between gap-4">
|
||||
<ResolutionOrChance
|
||||
className="md:hidden"
|
||||
resolution={resolution}
|
||||
probPercent={probPercent}
|
||||
large
|
||||
/>
|
||||
|
||||
{tradingAllowed(contract) && (
|
||||
<BetRow
|
||||
contract={contract}
|
||||
{isBinary && (
|
||||
<Row className="items-center justify-between gap-4">
|
||||
<ResolutionOrChance
|
||||
className="md:hidden"
|
||||
labelClassName="hidden"
|
||||
resolution={resolution}
|
||||
probPercent={getBinaryProbPercent(contract)}
|
||||
large
|
||||
/>
|
||||
)}
|
||||
</Row>
|
||||
|
||||
{tradingAllowed(contract) && (
|
||||
<BetRow
|
||||
contract={contract}
|
||||
className="md:hidden"
|
||||
labelClassName="hidden"
|
||||
/>
|
||||
)}
|
||||
</Row>
|
||||
)}
|
||||
|
||||
<ContractDetails contract={contract} />
|
||||
</Col>
|
||||
|
||||
<Col className="hidden items-end justify-between md:flex">
|
||||
<ResolutionOrChance
|
||||
className="items-end"
|
||||
resolution={resolution}
|
||||
probPercent={probPercent}
|
||||
large
|
||||
/>
|
||||
</Col>
|
||||
{isBinary && (
|
||||
<Col className="hidden items-end justify-between md:flex">
|
||||
<ResolutionOrChance
|
||||
className="items-end"
|
||||
resolution={resolution}
|
||||
probPercent={getBinaryProbPercent(contract)}
|
||||
large
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
|
||||
<Spacer h={4} />
|
||||
|
||||
<ContractProbGraph contract={contract} bets={bets} />
|
||||
{isBinary && <ContractProbGraph contract={contract} bets={bets} />}
|
||||
|
||||
<Row className="mt-6 ml-4 hidden items-center justify-between gap-4 sm:flex">
|
||||
{folds.length === 0 ? (
|
||||
|
@ -110,12 +107,9 @@ export const ContractOverview = (props: {
|
|||
<RevealableTagsInput className="mx-4 mt-4" contract={contract} />
|
||||
)}
|
||||
|
||||
<Spacer h={12} />
|
||||
|
||||
{/* Show a delete button for contracts without any trading */}
|
||||
{isCreator && truePool === 0 && (
|
||||
{isCreator && (isBinary ? bets.length <= 2 : bets.length <= 1) && (
|
||||
<>
|
||||
<Spacer h={8} />
|
||||
<button
|
||||
className="btn btn-xs btn-error btn-outline mt-1 max-w-fit self-end"
|
||||
onClick={async (e) => {
|
||||
|
@ -129,6 +123,8 @@ export const ContractOverview = (props: {
|
|||
</>
|
||||
)}
|
||||
|
||||
<Spacer h={12} />
|
||||
|
||||
<ContractFeed
|
||||
contract={contract}
|
||||
bets={bets}
|
||||
|
@ -139,3 +135,22 @@ export const ContractOverview = (props: {
|
|||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
const getTweetText = (contract: Contract, isCreator: boolean) => {
|
||||
const { question, creatorName, resolution, outcomeType } = contract
|
||||
const isBinary = outcomeType === 'BINARY'
|
||||
|
||||
const tweetQuestion = isCreator
|
||||
? question
|
||||
: `${question} Asked by ${creatorName}.`
|
||||
const tweetDescription = resolution
|
||||
? `Resolved ${resolution}!`
|
||||
: isBinary
|
||||
? `Currently ${getBinaryProbPercent(
|
||||
contract
|
||||
)} chance, place your bets here:`
|
||||
: `Submit your own answer:`
|
||||
const url = `https://manifold.markets${contractPath(contract)}`
|
||||
|
||||
return `${tweetQuestion}\n\n${tweetDescription}\n\n${url}`
|
||||
}
|
||||
|
|
|
@ -27,21 +27,9 @@ export function contractPath(contract: Contract) {
|
|||
}
|
||||
|
||||
export function contractMetrics(contract: Contract) {
|
||||
const {
|
||||
pool,
|
||||
phantomShares,
|
||||
totalShares,
|
||||
createdTime,
|
||||
resolutionTime,
|
||||
isResolved,
|
||||
resolutionProbability,
|
||||
} = contract
|
||||
const { pool, createdTime, resolutionTime, isResolved } = contract
|
||||
|
||||
const truePool = pool.YES + pool.NO
|
||||
const prob = resolutionProbability ?? getProbability(totalShares)
|
||||
const probPercent = Math.round(prob * 100) + '%'
|
||||
|
||||
const startProb = getProbability(phantomShares)
|
||||
const truePool = _.sum(Object.values(pool))
|
||||
|
||||
const createdDate = dayjs(createdTime).format('MMM D')
|
||||
|
||||
|
@ -49,7 +37,16 @@ export function contractMetrics(contract: Contract) {
|
|||
? dayjs(resolutionTime).format('MMM D')
|
||||
: undefined
|
||||
|
||||
return { truePool, probPercent, startProb, createdDate, resolvedDate }
|
||||
return { truePool, createdDate, resolvedDate }
|
||||
}
|
||||
|
||||
export function getBinaryProbPercent(contract: Contract) {
|
||||
const { totalShares, resolutionProbability } = contract
|
||||
|
||||
const prob = resolutionProbability ?? getProbability(totalShares)
|
||||
const probPercent = Math.round(prob * 100) + '%'
|
||||
|
||||
return probPercent
|
||||
}
|
||||
|
||||
export function tradingAllowed(contract: Contract) {
|
||||
|
|
|
@ -12,10 +12,10 @@ import { Title } from '../../components/title'
|
|||
import { Spacer } from '../../components/layout/spacer'
|
||||
import { User } from '../../lib/firebase/users'
|
||||
import {
|
||||
contractMetrics,
|
||||
Contract,
|
||||
getContractFromSlug,
|
||||
tradingAllowed,
|
||||
getBinaryProbPercent,
|
||||
} from '../../lib/firebase/contracts'
|
||||
import { SEO } from '../../components/SEO'
|
||||
import { Page } from '../../components/page'
|
||||
|
@ -82,33 +82,27 @@ export default function ContractPage(props: {
|
|||
return <Custom404 />
|
||||
}
|
||||
|
||||
const { creatorId, isResolved, resolution, question } = contract
|
||||
const { creatorId, isResolved, question, outcomeType } = contract
|
||||
|
||||
const isCreator = user?.id === creatorId
|
||||
const isBinary = outcomeType === 'BINARY'
|
||||
const allowTrade = tradingAllowed(contract)
|
||||
const allowResolve = !isResolved && isCreator && !!user
|
||||
const hasSidePanel = isBinary && (allowTrade || allowResolve)
|
||||
|
||||
const { probPercent } = contractMetrics(contract)
|
||||
|
||||
const description = resolution
|
||||
? `Resolved ${resolution}. ${contract.description}`
|
||||
: `${probPercent} chance. ${contract.description}`
|
||||
|
||||
const ogCardProps = {
|
||||
question,
|
||||
probability: probPercent,
|
||||
metadata: contractTextDetails(contract),
|
||||
creatorName: contract.creatorName,
|
||||
creatorUsername: contract.creatorUsername,
|
||||
}
|
||||
// TODO(James): Create SEO props for non-binary contracts.
|
||||
const ogCardProps = isBinary ? getOpenGraphProps(contract) : undefined
|
||||
|
||||
return (
|
||||
<Page wide={allowTrade || allowResolve}>
|
||||
<SEO
|
||||
title={question}
|
||||
description={description}
|
||||
url={`/${props.username}/${props.slug}`}
|
||||
ogCardProps={ogCardProps}
|
||||
/>
|
||||
<Page wide={hasSidePanel}>
|
||||
{ogCardProps && (
|
||||
<SEO
|
||||
title={question}
|
||||
description={ogCardProps.description}
|
||||
url={`/${props.username}/${props.slug}`}
|
||||
ogCardProps={ogCardProps}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Col className="w-full justify-between md:flex-row">
|
||||
<div className="flex-[3] rounded border-0 border-gray-100 bg-white px-2 py-6 md:px-6 md:py-8">
|
||||
|
@ -118,10 +112,10 @@ export default function ContractPage(props: {
|
|||
comments={comments ?? []}
|
||||
folds={folds}
|
||||
/>
|
||||
<BetsSection contract={contract} user={user ?? null} />
|
||||
<BetsSection contract={contract} user={user ?? null} bets={bets} />
|
||||
</div>
|
||||
|
||||
{(allowTrade || allowResolve) && (
|
||||
{hasSidePanel && (
|
||||
<>
|
||||
<div className="md:ml-6" />
|
||||
|
||||
|
@ -140,11 +134,13 @@ export default function ContractPage(props: {
|
|||
)
|
||||
}
|
||||
|
||||
function BetsSection(props: { contract: Contract; user: User | null }) {
|
||||
function BetsSection(props: {
|
||||
contract: Contract
|
||||
user: User | null
|
||||
bets: Bet[]
|
||||
}) {
|
||||
const { contract, user } = props
|
||||
const bets = useBets(contract.id)
|
||||
|
||||
if (!bets || bets.length === 0) return <></>
|
||||
const bets = useBets(contract.id) ?? props.bets
|
||||
|
||||
// Decending creation time.
|
||||
bets.sort((bet1, bet2) => bet2.createdTime - bet1.createdTime)
|
||||
|
@ -168,3 +164,21 @@ function BetsSection(props: { contract: Contract; user: User | null }) {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const getOpenGraphProps = (contract: Contract<'BINARY'>) => {
|
||||
const { resolution, question, creatorName, creatorUsername } = contract
|
||||
const probPercent = getBinaryProbPercent(contract)
|
||||
|
||||
const description = resolution
|
||||
? `Resolved ${resolution}. ${contract.description}`
|
||||
: `${probPercent} chance. ${contract.description}`
|
||||
|
||||
return {
|
||||
question,
|
||||
probability: probPercent,
|
||||
metadata: contractTextDetails(contract),
|
||||
creatorName: creatorName,
|
||||
creatorUsername: creatorUsername,
|
||||
description,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import dayjs from 'dayjs'
|
|||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import Textarea from 'react-expanding-textarea'
|
||||
import { getProbability } from '../../common/calculate'
|
||||
import { parseWordsAsTags } from '../../common/util/parse'
|
||||
import { AmountInput } from '../components/amount-input'
|
||||
import { InfoTooltip } from '../components/info-tooltip'
|
||||
|
@ -15,11 +16,7 @@ import { Page } from '../components/page'
|
|||
import { Title } from '../components/title'
|
||||
import { useUser } from '../hooks/use-user'
|
||||
import { createContract } from '../lib/firebase/api-call'
|
||||
import {
|
||||
contractMetrics,
|
||||
Contract,
|
||||
contractPath,
|
||||
} from '../lib/firebase/contracts'
|
||||
import { Contract, contractPath } from '../lib/firebase/contracts'
|
||||
|
||||
type Prediction = {
|
||||
question: string
|
||||
|
@ -29,7 +26,7 @@ type Prediction = {
|
|||
}
|
||||
|
||||
function toPrediction(contract: Contract): Prediction {
|
||||
const { startProb } = contractMetrics(contract)
|
||||
const startProb = getProbability(contract.phantomShares)
|
||||
return {
|
||||
question: contract.question,
|
||||
description: contract.description,
|
||||
|
|
Loading…
Reference in New Issue
Block a user