From fb0e16d6196e88e02c3aae5463894e027c877a5f Mon Sep 17 00:00:00 2001 From: Austin Chen Date: Sun, 2 Jan 2022 21:21:25 -0800 Subject: [PATCH] Add a closing date to Create Market (#10) * Preview a slimmed-down version of /Create * Rework dropdown to be on bottom * Parse the close time as just before midnight * Prevent invalid contracts from being created * Prevent trading after contract has closed --- web/components/contract-overview.tsx | 18 +++ web/lib/service/create-contract.ts | 6 +- web/pages/[username]/[contractSlug].tsx | 12 +- web/pages/create.tsx | 160 +++++++++++++++++------- 4 files changed, 144 insertions(+), 52 deletions(-) diff --git a/web/components/contract-overview.tsx b/web/components/contract-overview.tsx index 23f2e7fb..114ba9b9 100644 --- a/web/components/contract-overview.tsx +++ b/web/components/contract-overview.tsx @@ -16,6 +16,19 @@ import { Linkify } from './linkify' import clsx from 'clsx' import { ContractDetails, ResolutionOrChance } from './contract-card' +function ContractCloseTime(props: { contract: Contract }) { + const closeTime = props.contract.closeTime + if (!closeTime) { + return null + } + return ( +
+ Trading {closeTime > Date.now() ? 'closes' : 'closed'} at{' '} + {dayjs(closeTime).format('MMM D, h:mma')} +
+ ) +} + function ContractDescription(props: { contract: Contract isCreator: boolean @@ -127,9 +140,14 @@ export const ContractOverview = (props: { + + + + {((isCreator && !contract.resolution) || contract.description) && ( )} + {/* Show a delete button for contracts without any trading */} diff --git a/web/lib/service/create-contract.ts b/web/lib/service/create-contract.ts index 966115b0..7c7a59df 100644 --- a/web/lib/service/create-contract.ts +++ b/web/lib/service/create-contract.ts @@ -12,7 +12,8 @@ export async function createContract( question: string, description: string, initialProb: number, - creator: User + creator: User, + closeTime?: number ) { const proposedSlug = slugify(question).substring(0, 35) @@ -45,6 +46,9 @@ export async function createContract( createdTime: Date.now(), lastUpdatedTime: Date.now(), } + if (closeTime) { + contract.closeTime = closeTime + } return await pushNewContract(contract) } diff --git a/web/pages/[username]/[contractSlug].tsx b/web/pages/[username]/[contractSlug].tsx index fa9bbac6..8b824c44 100644 --- a/web/pages/[username]/[contractSlug].tsx +++ b/web/pages/[username]/[contractSlug].tsx @@ -53,6 +53,9 @@ export default function ContractPage(props: { const { creatorId, isResolved, resolution, question } = contract const isCreator = user?.id === creatorId + const allowTrade = + !isResolved && (!contract.closeTime || contract.closeTime > Date.now()) + const allowResolve = !isResolved && isCreator && user const { probPercent } = compute(contract) @@ -61,7 +64,7 @@ export default function ContractPage(props: { : `${probPercent} chance. ${contract.description}` return ( - + - {!isResolved && ( + {(allowTrade || allowResolve) && ( <>
- - - {isCreator && user && ( + {allowTrade && } + {allowResolve && ( )} diff --git a/web/pages/create.tsx b/web/pages/create.tsx index 5806f9ec..6ad2bcb1 100644 --- a/web/pages/create.tsx +++ b/web/pages/create.tsx @@ -8,6 +8,9 @@ import { useUser } from '../hooks/use-user' import { path } from '../lib/firebase/contracts' import { createContract } from '../lib/service/create-contract' import { Page } from '../components/page' +import { Row } from '../components/layout/row' +import clsx from 'clsx' +import dayjs from 'dayjs' // Allow user to create a new contract export default function NewContract() { @@ -20,11 +23,33 @@ export default function NewContract() { const [initialProb, setInitialProb] = useState(50) const [question, setQuestion] = useState('') const [description, setDescription] = useState('') + const [closeDate, setCloseDate] = useState('') const [isSubmitting, setIsSubmitting] = useState(false) + const [collapsed, setCollapsed] = useState(true) + + // Given a date string like '2022-04-02', + // return the time just before midnight on that date (in the user's local time), as millis since epoch + function dateToMillis(date: string) { + return dayjs(date) + .set('hour', 23) + .set('minute', 59) + .set('second', 59) + .valueOf() + } + const closeTime = dateToMillis(closeDate) + // We'd like this to look like "Apr 2, 2022, 23:59:59 PM PT" but timezones are hard with dayjs + const formattedCloseTime = new Date(closeTime).toString() + + const isValid = + initialProb > 0 && + initialProb < 100 && + question.length > 0 && + // If set, closeTime must be in the future + (!closeDate || closeTime > Date.now()) async function submit() { - // TODO: add more rigorous error handling for question - if (!creator || !question) return + // TODO: Tell users why their contract is invalid + if (!creator || !isValid) return setIsSubmitting(true) @@ -32,7 +57,8 @@ export default function NewContract() { question, description, initialProb, - creator + creator, + closeTime ) await router.push(path(contract)) } @@ -49,52 +75,94 @@ export default function NewContract() { {/* Create a Tailwind form that takes in all the fields needed for a new contract */} {/* When the form is submitted, create a new contract in the database */}
-
- +
+
+ - setQuestion(e.target.value || '')} - /> + setQuestion(e.target.value || '')} + /> +
+ +
+ + +
- + {/* Collapsible "Advanced" section */} +
+
setCollapsed((collapsed) => !collapsed)} + > + Advanced +
+ +
+ +
+
+ + +
-
- - - -
- - - -
- - - setInitialProb(parseInt(e.target.value))} - /> +
+ + e.stopPropagation()} + onChange={(e) => setCloseDate(e.target.value || '')} + min={new Date().toISOString().split('T')[0]} + value={closeDate} + /> +
+ +
@@ -103,7 +171,7 @@ export default function NewContract() {