Move to tailwindui for create market page (#332)
* Move to tailwindui * Remove commented code * Prettier * Show custom prob toggle, limit to 5-95% * match left margin * Show prob, date, time, other ui changes
This commit is contained in:
parent
92df092ad3
commit
d69f4f9a0a
|
@ -1,36 +1,56 @@
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
|
import { RadioGroup } from '@headlessui/react'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
export function ChoicesToggleGroup(props: {
|
export function ChoicesToggleGroup(props: {
|
||||||
currentChoice: number
|
currentChoice: number | string
|
||||||
setChoice: (p: number) => void
|
choicesMap: { [key: string]: string | number }
|
||||||
choices: number[]
|
|
||||||
titles: string[]
|
|
||||||
isSubmitting?: boolean
|
isSubmitting?: boolean
|
||||||
|
setChoice: (p: number | string) => void
|
||||||
|
className?: string
|
||||||
|
children?: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
const { currentChoice, setChoice, titles, choices, isSubmitting } = props
|
const {
|
||||||
const baseButtonClassName = 'btn btn-outline btn-md sm:btn-md normal-case'
|
currentChoice,
|
||||||
const activeClasss =
|
setChoice,
|
||||||
'bg-indigo-600 focus:bg-indigo-600 hover:bg-indigo-600 text-white'
|
isSubmitting,
|
||||||
|
choicesMap,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
} = props
|
||||||
return (
|
return (
|
||||||
<Row className={'mt-2 items-center gap-2'}>
|
<Row className={'mt-2 items-center gap-2'}>
|
||||||
<div className={'btn-group justify-stretch'}>
|
<RadioGroup
|
||||||
{choices.map((choice, i) => {
|
value={currentChoice.toString()}
|
||||||
return (
|
onChange={(str) => null}
|
||||||
<button
|
className="mt-2"
|
||||||
key={choice + i + ''}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
className={clsx(
|
|
||||||
baseButtonClassName,
|
|
||||||
currentChoice === choice ? activeClasss : ''
|
|
||||||
)}
|
|
||||||
onClick={() => setChoice(choice)}
|
|
||||||
>
|
>
|
||||||
{titles[i]}
|
<div className={`grid grid-cols-12 gap-3`}>
|
||||||
</button>
|
{Object.keys(choicesMap).map((choiceKey) => (
|
||||||
|
<RadioGroup.Option
|
||||||
|
key={choiceKey}
|
||||||
|
value={choicesMap[choiceKey]}
|
||||||
|
onClick={() => setChoice(choicesMap[choiceKey])}
|
||||||
|
className={({ active }) =>
|
||||||
|
clsx(
|
||||||
|
active ? 'ring-2 ring-indigo-500 ring-offset-2' : '',
|
||||||
|
currentChoice === choicesMap[choiceKey]
|
||||||
|
? 'border-transparent bg-indigo-500 text-white hover:bg-indigo-600'
|
||||||
|
: 'border-gray-200 bg-white text-gray-900 hover:bg-gray-50',
|
||||||
|
'flex cursor-pointer items-center justify-center rounded-md border py-3 px-3 text-sm font-medium normal-case',
|
||||||
|
"hover:ring-offset-2' hover:ring-2 hover:ring-indigo-500",
|
||||||
|
className
|
||||||
)
|
)
|
||||||
})}
|
}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
>
|
||||||
|
<RadioGroup.Label as="span">{choiceKey}</RadioGroup.Label>
|
||||||
|
</RadioGroup.Option>
|
||||||
|
))}
|
||||||
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -204,7 +204,7 @@ function EditableCloseDate(props: {
|
||||||
|
|
||||||
const [isEditingCloseTime, setIsEditingCloseTime] = useState(false)
|
const [isEditingCloseTime, setIsEditingCloseTime] = useState(false)
|
||||||
const [closeDate, setCloseDate] = useState(
|
const [closeDate, setCloseDate] = useState(
|
||||||
closeTime && dayjs(closeTime).format('YYYY-MM-DDT23:59')
|
closeTime && dayjs(closeTime).format('YYYY-MM-DDTHH:mm')
|
||||||
)
|
)
|
||||||
|
|
||||||
const isSameYear = dayjs(closeTime).isSame(dayjs(), 'year')
|
const isSameYear = dayjs(closeTime).isSame(dayjs(), 'year')
|
||||||
|
|
|
@ -17,7 +17,6 @@ import { useHasCreatedContractToday } from 'web/hooks/use-has-created-contract-t
|
||||||
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 { 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('')
|
||||||
|
@ -68,8 +67,6 @@ 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 ?? '')
|
||||||
|
@ -88,21 +85,26 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
|
|
||||||
// const [anteError, setAnteError] = useState<string | undefined>()
|
// const [anteError, setAnteError] = useState<string | undefined>()
|
||||||
// By default, close the market a week from today
|
// By default, close the market a week from today
|
||||||
const weekFromToday = dayjs().add(7, 'day').format('YYYY-MM-DDT23:59')
|
const weekFromToday = dayjs().add(7, 'day').format('YYYY-MM-DD')
|
||||||
const [closeDate, setCloseDate] = useState<undefined | string>(weekFromToday)
|
const [closeDate, setCloseDate] = useState<undefined | string>(weekFromToday)
|
||||||
|
const [closeHoursMinutes, setCloseHoursMinutes] = useState<string>('23:59')
|
||||||
|
const [probErrorText, setProbErrorText] = useState('')
|
||||||
|
const [marketInfoText, setMarketInfoText] = useState('')
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
|
|
||||||
const closeTime = closeDate ? dayjs(closeDate).valueOf() : undefined
|
const closeTime = closeDate
|
||||||
|
? dayjs(`${closeDate}T${closeHoursMinutes}`).valueOf()
|
||||||
|
: undefined
|
||||||
|
|
||||||
const balance = creator?.balance || 0
|
const balance = creator?.balance || 0
|
||||||
|
|
||||||
const min = minString ? parseFloat(minString) : undefined
|
const min = minString ? parseFloat(minString) : undefined
|
||||||
const max = maxString ? parseFloat(maxString) : undefined
|
const max = maxString ? parseFloat(maxString) : undefined
|
||||||
|
// get days from today until the end of this year:
|
||||||
|
const daysLeftInTheYear = dayjs().endOf('year').diff(dayjs(), 'day')
|
||||||
|
|
||||||
const isValid =
|
const isValid =
|
||||||
initialProb > 0 &&
|
(outcomeType === 'BINARY' ? initialProb >= 5 && initialProb <= 95 : true) &&
|
||||||
initialProb < 100 &&
|
|
||||||
question.length > 0 &&
|
question.length > 0 &&
|
||||||
ante !== undefined &&
|
ante !== undefined &&
|
||||||
ante !== null &&
|
ante !== null &&
|
||||||
|
@ -122,8 +124,7 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
max - min > 0.01))
|
max - min > 0.01))
|
||||||
|
|
||||||
function setCloseDateInDays(days: number) {
|
function setCloseDateInDays(days: number) {
|
||||||
setShowCalendar(days === 0)
|
const newCloseDate = dayjs().add(days, 'day').format('YYYY-MM-DD')
|
||||||
const newCloseDate = dayjs().add(days, 'day').format('YYYY-MM-DDT23:59')
|
|
||||||
setCloseDate(newCloseDate)
|
setCloseDate(newCloseDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,90 +164,83 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<label className="label mt-1">
|
<label className="label mt-1">
|
||||||
<span className="my-1">Answer type</span>
|
<span className="mt-1">Answer type</span>
|
||||||
</label>
|
</label>
|
||||||
<Row className="form-control gap-2">
|
<ChoicesToggleGroup
|
||||||
<label className="label cursor-pointer gap-2">
|
currentChoice={outcomeType}
|
||||||
<input
|
setChoice={(choice) => {
|
||||||
className="radio"
|
if (choice === 'NUMERIC')
|
||||||
type="radio"
|
setMarketInfoText(
|
||||||
name="opt"
|
'Numeric markets are still experimental and subject to major revisions.'
|
||||||
checked={outcomeType === 'BINARY'}
|
)
|
||||||
value="BINARY"
|
else if (choice === 'FREE_RESPONSE')
|
||||||
onChange={() => setOutcomeType('BINARY')}
|
setMarketInfoText(
|
||||||
disabled={isSubmitting}
|
'Users can submit their own answers to this market.'
|
||||||
|
)
|
||||||
|
else setMarketInfoText('')
|
||||||
|
setOutcomeType(choice as outcomeType)
|
||||||
|
}}
|
||||||
|
choicesMap={{
|
||||||
|
'Yes / No': 'BINARY',
|
||||||
|
'Free response': 'FREE_RESPONSE',
|
||||||
|
Numeric: 'NUMERIC',
|
||||||
|
}}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
className={'col-span-4'}
|
||||||
/>
|
/>
|
||||||
<span className="label-text">Yes / No</span>
|
{marketInfoText && (
|
||||||
</label>
|
<div className="mt-2 ml-1 text-sm text-indigo-700">
|
||||||
|
{marketInfoText}
|
||||||
<label className="label cursor-pointer gap-2">
|
</div>
|
||||||
<input
|
)}
|
||||||
className="radio"
|
|
||||||
type="radio"
|
|
||||||
name="opt"
|
|
||||||
checked={outcomeType === 'FREE_RESPONSE'}
|
|
||||||
value="FREE_RESPONSE"
|
|
||||||
onChange={() => setOutcomeType('FREE_RESPONSE')}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
/>
|
|
||||||
<span className="label-text">Free response</span>
|
|
||||||
</label>
|
|
||||||
<label className="label cursor-pointer gap-2">
|
|
||||||
<input
|
|
||||||
className="radio"
|
|
||||||
type="radio"
|
|
||||||
name="opt"
|
|
||||||
checked={outcomeType === 'NUMERIC'}
|
|
||||||
value="NUMERIC"
|
|
||||||
onChange={() => setOutcomeType('NUMERIC')}
|
|
||||||
/>
|
|
||||||
<span className="label-text">Numeric (experimental)</span>
|
|
||||||
</label>
|
|
||||||
</Row>
|
|
||||||
<Spacer h={4} />
|
<Spacer h={4} />
|
||||||
|
|
||||||
{outcomeType === 'BINARY' && (
|
{outcomeType === 'BINARY' && (
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<Row className="label justify-start">
|
<Row className="label justify-start">
|
||||||
<span className="mb-1">How likely is it to happen?</span>
|
<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>
|
||||||
<Row className={'w-full items-center sm:gap-2'}>
|
<Row className={'justify-start'}>
|
||||||
<ChoicesToggleGroup
|
<ChoicesToggleGroup
|
||||||
currentChoice={initialProb}
|
currentChoice={initialProb}
|
||||||
setChoice={setInitialProb}
|
setChoice={(option) => {
|
||||||
choices={[25, 50, 75]}
|
setProbErrorText('')
|
||||||
titles={['Unlikely', 'Unsure', 'Likely']}
|
setInitialProb(option as number)
|
||||||
|
}}
|
||||||
|
choicesMap={{
|
||||||
|
Unlikely: 25,
|
||||||
|
'Not Sure': 50,
|
||||||
|
Likely: 75,
|
||||||
|
}}
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
/>
|
className={'col-span-4 sm:col-span-3'}
|
||||||
{showNumInput && (
|
>
|
||||||
<>
|
<div className={'col-span-6 sm:col-span-3'}>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={initialProb}
|
value={initialProb}
|
||||||
className={
|
className={
|
||||||
'max-w-[16%] sm:max-w-[15%] ' +
|
'input-bordered input-md rounded-md border-gray-300 pr-2 text-lg'
|
||||||
'input-bordered input-md mt-2 rounded-md p-1 text-lg sm:p-4'
|
|
||||||
}
|
}
|
||||||
|
min={5}
|
||||||
|
max={95}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
min={10}
|
onChange={(e) => {
|
||||||
max={90}
|
// show error if prob is less than 5 or greater than 95:
|
||||||
onChange={(e) =>
|
const prob = parseInt(e.target.value)
|
||||||
setInitialProb(parseInt(e.target.value.substring(0, 2)))
|
setInitialProb(prob)
|
||||||
}
|
if (prob < 5 || prob > 95)
|
||||||
|
setProbErrorText('Probability must be between 5% and 95%')
|
||||||
|
else setProbErrorText('')
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<span className={'mt-2'}>%</span>
|
<span className={'mt-2 ml-0.5'}>%</span>
|
||||||
</>
|
</div>
|
||||||
)}
|
</ChoicesToggleGroup>
|
||||||
</Row>
|
</Row>
|
||||||
|
{probErrorText && (
|
||||||
|
<div className="text-error mt-2 ml-1 text-sm">{probErrorText}</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -289,7 +283,7 @@ 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 className="mb-1">Description</span>
|
<span className="mb-1">Description</span>
|
||||||
<InfoTooltip text="Optional. Describe how you will resolve this market." />
|
<InfoTooltip text="Optional. Describe how you will resolve this question." />
|
||||||
</label>
|
</label>
|
||||||
<Textarea
|
<Textarea
|
||||||
className="textarea textarea-bordered w-full resize-none"
|
className="textarea textarea-bordered w-full resize-none"
|
||||||
|
@ -328,41 +322,47 @@ 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>Question expires in a:</span>
|
<span>Question closes in:</span>
|
||||||
<InfoTooltip text="Betting will be halted after this date (local timezone)." />
|
<InfoTooltip text="Betting will be halted after this time (local timezone)." />
|
||||||
</label>
|
</label>
|
||||||
<Row className={'w-full items-center gap-2'}>
|
<Row className={'w-full items-center gap-2'}>
|
||||||
<ChoicesToggleGroup
|
<ChoicesToggleGroup
|
||||||
currentChoice={
|
currentChoice={dayjs(`${closeDate}T23:59`).diff(dayjs(), 'day')}
|
||||||
closeDate
|
setChoice={(choice) => {
|
||||||
? [1, 7, 30, 365, 0].includes(
|
setCloseDateInDays(choice as number)
|
||||||
dayjs(closeDate).diff(dayjs(), 'day')
|
}}
|
||||||
)
|
choicesMap={{
|
||||||
? dayjs(closeDate).diff(dayjs(), 'day')
|
'A day': 1,
|
||||||
: 0
|
'A week': 7,
|
||||||
: -1
|
'30 days': 30,
|
||||||
}
|
'This year': daysLeftInTheYear,
|
||||||
setChoice={setCloseDateInDays}
|
}}
|
||||||
choices={[1, 7, 30, 0]}
|
|
||||||
titles={['Day', 'Week', 'Month', 'Custom']}
|
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
|
className={'col-span-4 sm:col-span-2'}
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
{showCalendar && (
|
<Row>
|
||||||
<input
|
<input
|
||||||
type={'date'}
|
type={'date'}
|
||||||
className="input input-bordered mt-4"
|
className="input input-bordered mt-4"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setCloseDate(
|
setCloseDate(dayjs(e.target.value).format('YYYY-MM-DD') || '')
|
||||||
dayjs(e.target.value).format('YYYY-MM-DDT23:59') || ''
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
min={Date.now()}
|
min={Date.now()}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
value={dayjs(closeDate).format('YYYY-MM-DD')}
|
value={dayjs(closeDate).format('YYYY-MM-DD')}
|
||||||
/>
|
/>
|
||||||
)}
|
<input
|
||||||
|
type={'time'}
|
||||||
|
className="input input-bordered mt-4 ml-2"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
onChange={(e) => setCloseHoursMinutes(e.target.value)}
|
||||||
|
min={'00:00'}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
value={closeHoursMinutes}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Spacer h={4} />
|
<Spacer h={4} />
|
||||||
|
@ -373,7 +373,7 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
{mustWaitForDailyFreeMarketStatus != 'loading' &&
|
{mustWaitForDailyFreeMarketStatus != 'loading' &&
|
||||||
mustWaitForDailyFreeMarketStatus && (
|
mustWaitForDailyFreeMarketStatus && (
|
||||||
<InfoTooltip
|
<InfoTooltip
|
||||||
text={`Cost to create your market. This amount is used to subsidize trading.`}
|
text={`Cost to create your question. This amount is used to subsidize betting.`}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
|
@ -432,7 +432,7 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
submit()
|
submit()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isSubmitting ? 'Creating...' : 'Create market'}
|
{isSubmitting ? 'Creating...' : 'Create question'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user