b2501d8145
* Answer datatype and MULTI outcome type for Contract
* Create free answer contract
* Automatically sort Tailwind classes with Prettier (#45)
* Add Prettier Tailwind plugin
* Autoformat Tailwind classes with Prettier
* Allow for non-binary contracts in contract page and related components
* logo with white inside, transparent bg
* Create answer
* Some UI for showing answers
* Answer bet panel
* Convert rest of calcuate file to generic multi contracts
* Working betting with ante'd NONE answer
* Numbered answers. Layout & calculation tweaks
* Can bet. More layout tweaks!
* Resolve answer UI
* Resolve multi market
* Resolved market UI
* Fix feed and cards for multi contracts
* Sell bets. Various fixes
* Tweaks for trades page
* Always dev mode
* Create answer bet has isAnte: true
* Fix card showing 0% for multi contracts
* Fix grouped bets feed for multi outcomes
* None option converted to none of the above label at bottom of list. Button to resolve none.
* Tweaks to no answers yet, resolve button layout
* Show ante bets on new answers in the feed
* Update placeholder text for description
* Consolidate firestore rules for subcollections
* Remove Contract and Bet type params. Use string type for outcomes.
* Increase char limit to 10k for answers. Preserve line breaks.
* Don't show resolve options after answer chosen
* Fix type error in script
* Remove NONE resolution option
* Change outcomeType to include 'MULTI' and 'FREE_RESPONSE'
* Show bet probability change and payout when creating answer
* User info change: also change answers
* Append answers to contract field 'answers'
* sort trades by resolved
* Don't include trailing !:,.; in links
* Stop flooring inputs into formatMoney
* Revert "Stop flooring inputs into formatMoney"
This reverts commit 2f7ab18429
.
* Consistently floor user.balance
* Expand create panel on focus
From Richard Hanania's feedback
* welcome email: include link to manifold
* Fix home page in dev on branches that are not free-response
* Close emails (#50)
* script init for stephen dev
* market close emails
* order of operations
* template email
* sendMarketCloseEmail: handle unsubscribe
* remove debugging
* marketCloseEmails: every hour
* sendMarketCloseEmails: check undefined
* marketCloseEmails: "every hour" => "every 1 hours"
* Set up a read API using Vercel serverless functions (#49)
* Set up read API using Vercel serverless functions
Featuring:
/api/v0/markets
/api/v0/market/[contractId]
/api/v0/slug/[contractSlug]
* Include tags in API
* Tweaks. Remove filter for only binary contract
* Fix bet probability change for NO bets
* Put back isProd calculation
Co-authored-by: Austin Chen <akrolsmir@gmail.com>
Co-authored-by: mantikoros <sgrugett@gmail.com>
Co-authored-by: mantikoros <95266179+mantikoros@users.noreply.github.com>
206 lines
6.0 KiB
TypeScript
206 lines
6.0 KiB
TypeScript
import React from 'react'
|
|
|
|
import { useContractWithPreload } from '../../hooks/use-contract'
|
|
import { ContractOverview } from '../../components/contract-overview'
|
|
import { BetPanel } from '../../components/bet-panel'
|
|
import { Col } from '../../components/layout/col'
|
|
import { useUser } from '../../hooks/use-user'
|
|
import { ResolutionPanel } from '../../components/resolution-panel'
|
|
import { ContractBetsTable, MyBetsSummary } from '../../components/bets-list'
|
|
import { useBets } from '../../hooks/use-bets'
|
|
import { Title } from '../../components/title'
|
|
import { Spacer } from '../../components/layout/spacer'
|
|
import { User } from '../../lib/firebase/users'
|
|
import {
|
|
Contract,
|
|
getContractFromSlug,
|
|
tradingAllowed,
|
|
getBinaryProbPercent,
|
|
} from '../../lib/firebase/contracts'
|
|
import { SEO } from '../../components/SEO'
|
|
import { Page } from '../../components/page'
|
|
import { contractTextDetails } from '../../components/contract-card'
|
|
import { Bet, listAllBets } from '../../lib/firebase/bets'
|
|
import { Comment, listAllComments } from '../../lib/firebase/comments'
|
|
import Custom404 from '../404'
|
|
import { getFoldsByTags } from '../../lib/firebase/folds'
|
|
import { Fold } from '../../../common/fold'
|
|
import { useFoldsWithTags } from '../../hooks/use-fold'
|
|
import { listAllAnswers } from '../../lib/firebase/answers'
|
|
import { Answer } from '../../../common/answer'
|
|
import { AnswersPanel } from '../../components/answers-panel'
|
|
|
|
export async function getStaticProps(props: {
|
|
params: { username: string; contractSlug: string }
|
|
}) {
|
|
const { username, contractSlug } = props.params
|
|
const contract = (await getContractFromSlug(contractSlug)) || null
|
|
const contractId = contract?.id
|
|
|
|
const foldsPromise = getFoldsByTags(contract?.tags ?? [])
|
|
|
|
const [bets, comments, answers] = await Promise.all([
|
|
contractId ? listAllBets(contractId) : [],
|
|
contractId ? listAllComments(contractId) : [],
|
|
contractId && contract.outcomeType === 'FREE_RESPONSE'
|
|
? listAllAnswers(contractId)
|
|
: [],
|
|
])
|
|
|
|
const folds = await foldsPromise
|
|
|
|
return {
|
|
props: {
|
|
contract,
|
|
username,
|
|
slug: contractSlug,
|
|
bets,
|
|
comments,
|
|
answers,
|
|
folds,
|
|
},
|
|
|
|
revalidate: 60, // regenerate after a minute
|
|
}
|
|
}
|
|
|
|
export async function getStaticPaths() {
|
|
return { paths: [], fallback: 'blocking' }
|
|
}
|
|
|
|
export default function ContractPage(props: {
|
|
contract: Contract | null
|
|
username: string
|
|
bets: Bet[]
|
|
comments: Comment[]
|
|
answers: Answer[]
|
|
slug: string
|
|
folds: Fold[]
|
|
}) {
|
|
const user = useUser()
|
|
|
|
const contract = useContractWithPreload(props.slug, props.contract)
|
|
const { bets, comments } = props
|
|
|
|
const folds = (useFoldsWithTags(contract?.tags) ?? props.folds).filter(
|
|
(fold) => fold.followCount > 1 || user?.id === fold.curatorId
|
|
)
|
|
|
|
if (!contract) {
|
|
return <Custom404 />
|
|
}
|
|
|
|
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)
|
|
|
|
// TODO(James): Create SEO props for non-binary contracts.
|
|
const ogCardProps = isBinary ? getOpenGraphProps(contract) : undefined
|
|
|
|
return (
|
|
<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">
|
|
<ContractOverview
|
|
contract={contract}
|
|
bets={bets ?? []}
|
|
comments={comments ?? []}
|
|
folds={folds}
|
|
>
|
|
{contract.outcomeType === 'FREE_RESPONSE' && (
|
|
<>
|
|
<Spacer h={4} />
|
|
<AnswersPanel
|
|
contract={contract as any}
|
|
answers={props.answers}
|
|
/>
|
|
<Spacer h={4} />
|
|
<div className="divider before:bg-gray-300 after:bg-gray-300" />
|
|
</>
|
|
)}
|
|
</ContractOverview>
|
|
|
|
<BetsSection contract={contract} user={user ?? null} bets={bets} />
|
|
</div>
|
|
|
|
{hasSidePanel && (
|
|
<>
|
|
<div className="md:ml-6" />
|
|
|
|
<Col className="flex-1">
|
|
{allowTrade && (
|
|
<BetPanel className="hidden lg:inline" contract={contract} />
|
|
)}
|
|
{allowResolve && (
|
|
<ResolutionPanel creator={user} contract={contract} />
|
|
)}
|
|
</Col>
|
|
</>
|
|
)}
|
|
</Col>
|
|
</Page>
|
|
)
|
|
}
|
|
|
|
function BetsSection(props: {
|
|
contract: Contract
|
|
user: User | null
|
|
bets: Bet[]
|
|
}) {
|
|
const { contract, user } = props
|
|
const isBinary = contract.outcomeType === 'BINARY'
|
|
const bets = useBets(contract.id) ?? props.bets
|
|
|
|
// Decending creation time.
|
|
bets.sort((bet1, bet2) => bet2.createdTime - bet1.createdTime)
|
|
|
|
const userBets = user && bets.filter((bet) => bet.userId === user.id)
|
|
|
|
if (!userBets || userBets.length === 0) return <></>
|
|
|
|
return (
|
|
<div>
|
|
<Title className="px-2" text="Your trades" />
|
|
{isBinary && (
|
|
<>
|
|
<MyBetsSummary className="px-2" contract={contract} bets={userBets} />
|
|
<Spacer h={6} />
|
|
</>
|
|
)}
|
|
<ContractBetsTable contract={contract} bets={userBets} />
|
|
<Spacer h={12} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const getOpenGraphProps = (contract: Contract) => {
|
|
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,
|
|
}
|
|
}
|