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:
Ian Philips 2022-05-25 11:54:28 -06:00 committed by GitHub
parent 92df092ad3
commit d69f4f9a0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 141 additions and 121 deletions

View File

@ -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 + ''} >
<div className={`grid grid-cols-12 gap-3`}>
{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} disabled={isSubmitting}
className={clsx(
baseButtonClassName,
currentChoice === choice ? activeClasss : ''
)}
onClick={() => setChoice(choice)}
> >
{titles[i]} <RadioGroup.Label as="span">{choiceKey}</RadioGroup.Label>
</button> </RadioGroup.Option>
) ))}
})} {children}
</div> </div>
</RadioGroup>
</Row> </Row>
) )
} }

View File

@ -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')

View File

@ -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.'
/> )
<span className="label-text">Yes / No</span> else setMarketInfoText('')
</label> setOutcomeType(choice as outcomeType)
}}
<label className="label cursor-pointer gap-2"> choicesMap={{
<input 'Yes / No': 'BINARY',
className="radio" 'Free response': 'FREE_RESPONSE',
type="radio" Numeric: 'NUMERIC',
name="opt" }}
checked={outcomeType === 'FREE_RESPONSE'} isSubmitting={isSubmitting}
value="FREE_RESPONSE" className={'col-span-4'}
onChange={() => setOutcomeType('FREE_RESPONSE')} />
disabled={isSubmitting} {marketInfoText && (
/> <div className="mt-2 ml-1 text-sm text-indigo-700">
<span className="label-text">Free response</span> {marketInfoText}
</label> </div>
<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>