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 dayjs from 'dayjs'
|
||||
import Textarea from 'react-expanding-textarea'
|
||||
|
||||
import { Spacer } from 'web/components/layout/spacer'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
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 { InfoTooltip } from 'web/components/info-tooltip'
|
||||
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 { MAX_DESCRIPTION_LENGTH, outcomeType } from 'common/contract'
|
||||
import { formatMoney } from 'common/util/format'
|
||||
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 { ChoicesToggleGroup } from 'web/components/choices-toggle-group'
|
||||
import { CalculatorIcon } from '@heroicons/react/outline'
|
||||
|
||||
export default function Create() {
|
||||
const [question, setQuestion] = useState('')
|
||||
|
@ -26,13 +25,13 @@ export default function Create() {
|
|||
return (
|
||||
<Page>
|
||||
<div className="mx-auto w-full max-w-2xl">
|
||||
<Title text="Create a new prediction market" />
|
||||
|
||||
<div className="rounded-lg bg-gray-100 px-6 py-4 shadow-md">
|
||||
<div className="rounded-lg px-6 py-4">
|
||||
<form>
|
||||
<div className="form-control w-full">
|
||||
<label className="label">
|
||||
<span className="mb-1">Question</span>
|
||||
<span className="mb-1">
|
||||
Question<span className={'text-red-700'}>*</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<Textarea
|
||||
|
@ -69,6 +68,8 @@ export function NewContract(props: { question: string; tag?: string }) {
|
|||
const [minString, setMinString] = useState('')
|
||||
const [maxString, setMaxString] = useState('')
|
||||
const [description, setDescription] = useState('')
|
||||
const [showCalendar, setShowCalendar] = useState(false)
|
||||
const [showNumInput, setShowNumInput] = useState(false)
|
||||
|
||||
const [category, setCategory] = useState<string>('')
|
||||
// const [tagText, setTagText] = useState<string>(tag ?? '')
|
||||
|
@ -118,6 +119,12 @@ export function NewContract(props: { question: string; tag?: string }) {
|
|||
min < max &&
|
||||
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() {
|
||||
// TODO: Tell users why their contract is invalid
|
||||
if (!creator || !isValid) return
|
||||
|
@ -146,15 +153,15 @@ export function NewContract(props: { question: string; tag?: string }) {
|
|||
|
||||
const descriptionPlaceholder =
|
||||
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...`
|
||||
|
||||
if (!creator) return <></>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label className="label">
|
||||
<span className="mb-1">Answer type</span>
|
||||
<label className="label mt-1">
|
||||
<span className="my-1">Answer type</span>
|
||||
</label>
|
||||
<Row className="form-control gap-2">
|
||||
<label className="label cursor-pointer gap-2">
|
||||
|
@ -165,6 +172,7 @@ export function NewContract(props: { question: string; tag?: string }) {
|
|||
checked={outcomeType === 'BINARY'}
|
||||
value="BINARY"
|
||||
onChange={() => setOutcomeType('BINARY')}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<span className="label-text">Yes / No</span>
|
||||
</label>
|
||||
|
@ -177,6 +185,7 @@ export function NewContract(props: { question: string; tag?: string }) {
|
|||
checked={outcomeType === 'FREE_RESPONSE'}
|
||||
value="FREE_RESPONSE"
|
||||
onChange={() => setOutcomeType('FREE_RESPONSE')}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<span className="label-text">Free response</span>
|
||||
</label>
|
||||
|
@ -196,14 +205,46 @@ export function NewContract(props: { question: string; tag?: string }) {
|
|||
|
||||
{outcomeType === 'BINARY' && (
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="mb-1">Initial probability</span>
|
||||
</label>
|
||||
|
||||
<ProbabilitySelector
|
||||
probabilityInt={initialProb}
|
||||
setProbabilityInt={setInitialProb}
|
||||
/>
|
||||
<Row className="label justify-start">
|
||||
<span className="mb-1">How likely is it to happen?</span>
|
||||
<CalculatorIcon
|
||||
className={clsx(
|
||||
'ml-2 cursor-pointer rounded-md',
|
||||
'hover:bg-gray-200',
|
||||
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>
|
||||
)}
|
||||
|
||||
|
@ -285,18 +326,41 @@ export function NewContract(props: { question: string; tag?: string }) {
|
|||
|
||||
<div className="form-control mb-1 items-start">
|
||||
<label className="label mb-1 gap-2">
|
||||
<span>Market close</span>
|
||||
<InfoTooltip text="Trading will be halted after this time (local timezone)." />
|
||||
<span>Question expires in a:</span>
|
||||
<InfoTooltip text="Betting will be halted after this date (local timezone)." />
|
||||
</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
className="input input-bordered"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(e) => setCloseDate(e.target.value || '')}
|
||||
min={Date.now()}
|
||||
disabled={isSubmitting}
|
||||
value={closeDate}
|
||||
/>
|
||||
<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
|
||||
type={'date'}
|
||||
className="input input-bordered mt-4"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(e) =>
|
||||
setCloseDate(
|
||||
dayjs(e.target.value).format('YYYY-MM-DDT23:59') || ''
|
||||
)
|
||||
}
|
||||
min={Date.now()}
|
||||
disabled={isSubmitting}
|
||||
value={dayjs(closeDate).format('YYYY-MM-DD')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Spacer h={4} />
|
||||
|
@ -311,7 +375,12 @@ export function NewContract(props: { question: string; tag?: string }) {
|
|||
)}
|
||||
</label>
|
||||
{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">
|
||||
{formatMoney(ante)}
|
||||
|
|
Loading…
Reference in New Issue
Block a user