Simplify market creation page (#268)
* Simplify market creation page * Minor improvements * Prettier
This commit is contained in:
parent
fc6b36b690
commit
e94d204a87
36
web/components/choices-toggle-group.tsx
Normal file
36
web/components/choices-toggle-group.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { Row } from './layout/row'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
|
export function ChoicesToggleGroup(props: {
|
||||||
|
currentChoice: number
|
||||||
|
setChoice: (p: number) => void
|
||||||
|
choices: number[]
|
||||||
|
titles: string[]
|
||||||
|
isSubmitting?: boolean
|
||||||
|
}) {
|
||||||
|
const { currentChoice, setChoice, titles, choices, isSubmitting } = props
|
||||||
|
const baseButtonClassName = 'btn btn-outline btn-md sm:btn-md normal-case'
|
||||||
|
const activeClasss =
|
||||||
|
'bg-indigo-600 focus:bg-indigo-600 hover:bg-indigo-600 text-white'
|
||||||
|
return (
|
||||||
|
<Row className={'mt-2 items-center gap-2'}>
|
||||||
|
<div className={'btn-group justify-stretch'}>
|
||||||
|
{choices.map((choice, i) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={choice + i + ''}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
className={clsx(
|
||||||
|
baseButtonClassName,
|
||||||
|
currentChoice === choice ? activeClasss : ''
|
||||||
|
)}
|
||||||
|
onClick={() => setChoice(choice)}
|
||||||
|
>
|
||||||
|
{titles[i]}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ import { useEffect, useState } from 'react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import Textarea from 'react-expanding-textarea'
|
import Textarea from 'react-expanding-textarea'
|
||||||
|
|
||||||
import { Spacer } from 'web/components/layout/spacer'
|
import { Spacer } from 'web/components/layout/spacer'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { Contract, contractPath } from 'web/lib/firebase/contracts'
|
import { Contract, contractPath } from 'web/lib/firebase/contracts'
|
||||||
|
@ -11,14 +10,14 @@ import { createContract } from 'web/lib/firebase/api-call'
|
||||||
import { FIXED_ANTE, MINIMUM_ANTE } from 'common/antes'
|
import { FIXED_ANTE, MINIMUM_ANTE } from 'common/antes'
|
||||||
import { InfoTooltip } from 'web/components/info-tooltip'
|
import { InfoTooltip } from 'web/components/info-tooltip'
|
||||||
import { Page } from 'web/components/page'
|
import { Page } from 'web/components/page'
|
||||||
import { Title } from 'web/components/title'
|
|
||||||
import { ProbabilitySelector } from 'web/components/probability-selector'
|
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
import { MAX_DESCRIPTION_LENGTH, outcomeType } from 'common/contract'
|
import { MAX_DESCRIPTION_LENGTH, outcomeType } from 'common/contract'
|
||||||
import { formatMoney } from 'common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { useHasCreatedContractToday } from 'web/hooks/use-has-created-contract-today'
|
import { useHasCreatedContractToday } from 'web/hooks/use-has-created-contract-today'
|
||||||
import { removeUndefinedProps } from '../../common/util/object'
|
import { removeUndefinedProps } from 'common/util/object'
|
||||||
import { CATEGORIES } from 'common/categories'
|
import { CATEGORIES } from 'common/categories'
|
||||||
|
import { ChoicesToggleGroup } from 'web/components/choices-toggle-group'
|
||||||
|
import { CalculatorIcon } from '@heroicons/react/outline'
|
||||||
|
|
||||||
export default function Create() {
|
export default function Create() {
|
||||||
const [question, setQuestion] = useState('')
|
const [question, setQuestion] = useState('')
|
||||||
|
@ -26,13 +25,13 @@ export default function Create() {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<div className="mx-auto w-full max-w-2xl">
|
<div className="mx-auto w-full max-w-2xl">
|
||||||
<Title text="Create a new prediction market" />
|
<div className="rounded-lg px-6 py-4">
|
||||||
|
|
||||||
<div className="rounded-lg bg-gray-100 px-6 py-4 shadow-md">
|
|
||||||
<form>
|
<form>
|
||||||
<div className="form-control w-full">
|
<div className="form-control w-full">
|
||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="mb-1">Question</span>
|
<span className="mb-1">
|
||||||
|
Question<span className={'text-red-700'}>*</span>
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<Textarea
|
<Textarea
|
||||||
|
@ -69,6 +68,8 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
const [minString, setMinString] = useState('')
|
const [minString, setMinString] = useState('')
|
||||||
const [maxString, setMaxString] = useState('')
|
const [maxString, setMaxString] = useState('')
|
||||||
const [description, setDescription] = useState('')
|
const [description, setDescription] = useState('')
|
||||||
|
const [showCalendar, setShowCalendar] = useState(false)
|
||||||
|
const [showNumInput, setShowNumInput] = useState(false)
|
||||||
|
|
||||||
const [category, setCategory] = useState<string>('')
|
const [category, setCategory] = useState<string>('')
|
||||||
// const [tagText, setTagText] = useState<string>(tag ?? '')
|
// const [tagText, setTagText] = useState<string>(tag ?? '')
|
||||||
|
@ -118,6 +119,12 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
min < max &&
|
min < max &&
|
||||||
max - min > 0.01))
|
max - min > 0.01))
|
||||||
|
|
||||||
|
function setCloseDateInDays(days: number) {
|
||||||
|
setShowCalendar(days === 0)
|
||||||
|
const newCloseDate = dayjs().add(days, 'day').format('YYYY-MM-DDT23:59')
|
||||||
|
setCloseDate(newCloseDate)
|
||||||
|
}
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
// TODO: Tell users why their contract is invalid
|
// TODO: Tell users why their contract is invalid
|
||||||
if (!creator || !isValid) return
|
if (!creator || !isValid) return
|
||||||
|
@ -146,15 +153,15 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
|
|
||||||
const descriptionPlaceholder =
|
const descriptionPlaceholder =
|
||||||
outcomeType === 'BINARY'
|
outcomeType === 'BINARY'
|
||||||
? `e.g. This market resolves to "YES" if, two weeks after closing, the...`
|
? `e.g. This question resolves to "YES" if they receive the majority of votes...`
|
||||||
: `e.g. I will choose the answer according to...`
|
: `e.g. I will choose the answer according to...`
|
||||||
|
|
||||||
if (!creator) return <></>
|
if (!creator) return <></>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<label className="label">
|
<label className="label mt-1">
|
||||||
<span className="mb-1">Answer type</span>
|
<span className="my-1">Answer type</span>
|
||||||
</label>
|
</label>
|
||||||
<Row className="form-control gap-2">
|
<Row className="form-control gap-2">
|
||||||
<label className="label cursor-pointer gap-2">
|
<label className="label cursor-pointer gap-2">
|
||||||
|
@ -165,6 +172,7 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
checked={outcomeType === 'BINARY'}
|
checked={outcomeType === 'BINARY'}
|
||||||
value="BINARY"
|
value="BINARY"
|
||||||
onChange={() => setOutcomeType('BINARY')}
|
onChange={() => setOutcomeType('BINARY')}
|
||||||
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
<span className="label-text">Yes / No</span>
|
<span className="label-text">Yes / No</span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -177,6 +185,7 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
checked={outcomeType === 'FREE_RESPONSE'}
|
checked={outcomeType === 'FREE_RESPONSE'}
|
||||||
value="FREE_RESPONSE"
|
value="FREE_RESPONSE"
|
||||||
onChange={() => setOutcomeType('FREE_RESPONSE')}
|
onChange={() => setOutcomeType('FREE_RESPONSE')}
|
||||||
|
disabled={isSubmitting}
|
||||||
/>
|
/>
|
||||||
<span className="label-text">Free response</span>
|
<span className="label-text">Free response</span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -196,14 +205,46 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
|
|
||||||
{outcomeType === 'BINARY' && (
|
{outcomeType === 'BINARY' && (
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<label className="label">
|
<Row className="label justify-start">
|
||||||
<span className="mb-1">Initial probability</span>
|
<span className="mb-1">How likely is it to happen?</span>
|
||||||
</label>
|
<CalculatorIcon
|
||||||
|
className={clsx(
|
||||||
<ProbabilitySelector
|
'ml-2 cursor-pointer rounded-md',
|
||||||
probabilityInt={initialProb}
|
'hover:bg-gray-200',
|
||||||
setProbabilityInt={setInitialProb}
|
showNumInput && 'stroke-indigo-700'
|
||||||
|
)}
|
||||||
|
height={20}
|
||||||
|
onClick={() => setShowNumInput(!showNumInput)}
|
||||||
/>
|
/>
|
||||||
|
</Row>
|
||||||
|
<Row className={'w-full items-center sm:gap-2'}>
|
||||||
|
<ChoicesToggleGroup
|
||||||
|
currentChoice={initialProb}
|
||||||
|
setChoice={setInitialProb}
|
||||||
|
choices={[25, 50, 75]}
|
||||||
|
titles={['Unlikely', 'Unsure', 'Likely']}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
/>
|
||||||
|
{showNumInput && (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={initialProb}
|
||||||
|
className={
|
||||||
|
'max-w-[16%] sm:max-w-[15%] ' +
|
||||||
|
'input-bordered input-md mt-2 rounded-md p-1 text-lg sm:p-4'
|
||||||
|
}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
min={10}
|
||||||
|
max={90}
|
||||||
|
onChange={(e) =>
|
||||||
|
setInitialProb(parseInt(e.target.value.substring(0, 2)))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span className={'mt-2'}>%</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -285,18 +326,41 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
|
|
||||||
<div className="form-control mb-1 items-start">
|
<div className="form-control mb-1 items-start">
|
||||||
<label className="label mb-1 gap-2">
|
<label className="label mb-1 gap-2">
|
||||||
<span>Market close</span>
|
<span>Question expires in a:</span>
|
||||||
<InfoTooltip text="Trading will be halted after this time (local timezone)." />
|
<InfoTooltip text="Betting will be halted after this date (local timezone)." />
|
||||||
</label>
|
</label>
|
||||||
|
<Row className={'w-full items-center gap-2'}>
|
||||||
|
<ChoicesToggleGroup
|
||||||
|
currentChoice={
|
||||||
|
closeDate
|
||||||
|
? [1, 7, 30, 365, 0].includes(
|
||||||
|
dayjs(closeDate).diff(dayjs(), 'day')
|
||||||
|
)
|
||||||
|
? dayjs(closeDate).diff(dayjs(), 'day')
|
||||||
|
: 0
|
||||||
|
: -1
|
||||||
|
}
|
||||||
|
setChoice={setCloseDateInDays}
|
||||||
|
choices={[1, 7, 30, 0]}
|
||||||
|
titles={['Day', 'Week', 'Month', 'Custom']}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
{showCalendar && (
|
||||||
<input
|
<input
|
||||||
type="datetime-local"
|
type={'date'}
|
||||||
className="input input-bordered"
|
className="input input-bordered mt-4"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onChange={(e) => setCloseDate(e.target.value || '')}
|
onChange={(e) =>
|
||||||
|
setCloseDate(
|
||||||
|
dayjs(e.target.value).format('YYYY-MM-DDT23:59') || ''
|
||||||
|
)
|
||||||
|
}
|
||||||
min={Date.now()}
|
min={Date.now()}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
value={closeDate}
|
value={dayjs(closeDate).format('YYYY-MM-DD')}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Spacer h={4} />
|
<Spacer h={4} />
|
||||||
|
@ -311,7 +375,12 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
{deservesDailyFreeMarket ? (
|
{deservesDailyFreeMarket ? (
|
||||||
<div className="label-text text-primary pl-1">FREE</div>
|
<div className="label-text text-primary pl-1">
|
||||||
|
<span className={'label-text text-neutral line-through '}>
|
||||||
|
{formatMoney(ante)}
|
||||||
|
</span>{' '}
|
||||||
|
FREE
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="label-text text-neutral pl-1">
|
<div className="label-text text-neutral pl-1">
|
||||||
{formatMoney(ante)}
|
{formatMoney(ante)}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user