manifold/web/pages/[username]/[contractSlug].tsx
James Grugett b2501d8145
Free response (#47)
* 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>
2022-02-17 17:00:19 -06:00

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,
}
}