multipe choice answers

This commit is contained in:
mantikoros 2022-07-27 13:18:40 -07:00
parent 013ff1d941
commit ea80c06e45
4 changed files with 99 additions and 4 deletions

View File

@ -4,13 +4,19 @@ import { JSONContent } from '@tiptap/core'
import { GroupLink } from 'common/group' import { GroupLink } from 'common/group'
export type AnyMechanism = DPM | CPMM export type AnyMechanism = DPM | CPMM
export type AnyOutcomeType = Binary | PseudoNumeric | FreeResponse | Numeric export type AnyOutcomeType =
| Binary
| MultipleChoice
| PseudoNumeric
| FreeResponse
| Numeric
export type AnyContractType = export type AnyContractType =
| (CPMM & Binary) | (CPMM & Binary)
| (CPMM & PseudoNumeric) | (CPMM & PseudoNumeric)
| (DPM & Binary) | (DPM & Binary)
| (DPM & FreeResponse) | (DPM & FreeResponse)
| (DPM & Numeric) | (DPM & Numeric)
| (DPM & MultipleChoice)
export type Contract<T extends AnyContractType = AnyContractType> = { export type Contract<T extends AnyContractType = AnyContractType> = {
id: string id: string
@ -57,6 +63,7 @@ export type BinaryContract = Contract & Binary
export type PseudoNumericContract = Contract & PseudoNumeric export type PseudoNumericContract = Contract & PseudoNumeric
export type NumericContract = Contract & Numeric export type NumericContract = Contract & Numeric
export type FreeResponseContract = Contract & FreeResponse export type FreeResponseContract = Contract & FreeResponse
export type MultipleChoiceContract = Contract & MultipleChoice
export type DPMContract = Contract & DPM export type DPMContract = Contract & DPM
export type CPMMContract = Contract & CPMM export type CPMMContract = Contract & CPMM
export type DPMBinaryContract = BinaryContract & DPM export type DPMBinaryContract = BinaryContract & DPM
@ -104,6 +111,13 @@ export type FreeResponse = {
resolutions?: { [outcome: string]: number } // Used for MKT resolution. resolutions?: { [outcome: string]: number } // Used for MKT resolution.
} }
export type MultipleChoice = {
outcomeType: 'MULTIPLE_CHOICE'
answers: Answer[]
resolution?: string | 'MKT' | 'CANCEL'
resolutions?: { [outcome: string]: number } // Used for MKT resolution.
}
export type Numeric = { export type Numeric = {
outcomeType: 'NUMERIC' outcomeType: 'NUMERIC'
bucketCount: number bucketCount: number
@ -118,6 +132,7 @@ export type resolution = 'YES' | 'NO' | 'MKT' | 'CANCEL'
export const RESOLUTIONS = ['YES', 'NO', 'MKT', 'CANCEL'] as const export const RESOLUTIONS = ['YES', 'NO', 'MKT', 'CANCEL'] as const
export const OUTCOME_TYPES = [ export const OUTCOME_TYPES = [
'BINARY', 'BINARY',
'MULTIPLE_CHOICE',
'FREE_RESPONSE', 'FREE_RESPONSE',
'PSEUDO_NUMERIC', 'PSEUDO_NUMERIC',
'NUMERIC', 'NUMERIC',

View File

@ -0,0 +1,65 @@
import { MAX_ANSWER_LENGTH } from 'common/answer'
import { useState } from 'react'
import Textarea from 'react-expanding-textarea'
import { XIcon } from '@heroicons/react/solid'
import { Col } from '../layout/col'
import { Row } from '../layout/row'
export function MultipleChoiceAnswers(props: {
setAnswers: (answers: string[]) => void
}) {
const [answers, setInternalAnswers] = useState(['', '', ''])
const setAnswer = (i: number, answer: string) => {
const newAnswers = setElement(answers, i, answer)
setInternalAnswers(newAnswers)
props.setAnswers(newAnswers)
}
const removeAnswer = (i: number) => {
const newAnswers = answers.slice(0, i).concat(answers.slice(i + 1))
setInternalAnswers(newAnswers)
props.setAnswers(newAnswers)
}
const addAnswer = () => setAnswer(answers.length, '')
return (
<Col>
{answers.map((answer, i) => (
<Row className="mb-2 items-center align-middle">
{i + 1}.{' '}
<Textarea
value={answer}
onChange={(e) => setAnswer(i, e.target.value)}
className="textarea textarea-bordered ml-2 w-full resize-none"
placeholder="Type your answer..."
rows={1}
maxLength={MAX_ANSWER_LENGTH}
/>
{answers.length > 2 && (
<button
className="btn btn-xs btn-outline ml-2"
onClick={() => removeAnswer(i)}
>
<XIcon className="h-4 w-4 flex-shrink-0" />
</button>
)}
</Row>
))}
<Row className="justify-end">
<button className="btn btn-outline btn-xs" onClick={addAnswer}>
Add answer
</button>
</Row>
</Col>
)
}
const setElement = <T,>(array: T[], i: number, elem: T) => {
const newArray = array.concat()
newArray[i] = elem
return newArray
}

View File

@ -7,6 +7,7 @@ import {
BinaryContract, BinaryContract,
Contract, Contract,
FreeResponseContract, FreeResponseContract,
MultipleChoiceContract,
resolution, resolution,
} from 'common/contract' } from 'common/contract'
import { formatLargeNumber, formatPercent } from 'common/util/format' import { formatLargeNumber, formatPercent } from 'common/util/format'
@ -77,7 +78,7 @@ export function BinaryContractOutcomeLabel(props: {
} }
export function FreeResponseOutcomeLabel(props: { export function FreeResponseOutcomeLabel(props: {
contract: FreeResponseContract contract: FreeResponseContract | MultipleChoiceContract
resolution: string | 'CANCEL' | 'MKT' resolution: string | 'CANCEL' | 'MKT'
truncate: 'short' | 'long' | 'none' truncate: 'short' | 'long' | 'none'
answerClassName?: string answerClassName?: string

View File

@ -31,6 +31,7 @@ import { Checkbox } from 'web/components/checkbox'
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth' import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
import { Title } from 'web/components/title' import { Title } from 'web/components/title'
import { SEO } from 'web/components/SEO' import { SEO } from 'web/components/SEO'
import { MultipleChoiceAnswers } from 'web/components/answers/multiple-choice-answers'
export const getServerSideProps = redirectIfLoggedOut('/') export const getServerSideProps = redirectIfLoggedOut('/')
@ -116,6 +117,8 @@ export function NewContract(props: {
const [isLogScale, setIsLogScale] = useState<boolean>(!!params?.isLogScale) const [isLogScale, setIsLogScale] = useState<boolean>(!!params?.isLogScale)
const [initialValueString, setInitialValueString] = useState(initValue) const [initialValueString, setInitialValueString] = useState(initValue)
const [answers, setAnswers] = useState<string[]>([]) // for multiple choice
useEffect(() => { useEffect(() => {
if (groupId && creator) if (groupId && creator)
getGroup(groupId).then((group) => { getGroup(groupId).then((group) => {
@ -160,6 +163,10 @@ export function NewContract(props: {
// get days from today until the end of this year: // get days from today until the end of this year:
const daysLeftInTheYear = dayjs().endOf('year').diff(dayjs(), 'day') const daysLeftInTheYear = dayjs().endOf('year').diff(dayjs(), 'day')
const isValidMultipleChoice = answers.every(
(answer) => answer.trim().length > 0
)
const isValid = const isValid =
(outcomeType === 'BINARY' ? initialProb >= 5 && initialProb <= 95 : true) && (outcomeType === 'BINARY' ? initialProb >= 5 && initialProb <= 95 : true) &&
question.length > 0 && question.length > 0 &&
@ -178,7 +185,8 @@ export function NewContract(props: {
min < max && min < max &&
max - min > 0.01 && max - min > 0.01 &&
min < initialValue && min < initialValue &&
initialValue < max)) initialValue < max)) &&
(outcomeType !== 'MULTIPLE_CHOICE' || isValidMultipleChoice)
const [errorText, setErrorText] = useState<string>('') const [errorText, setErrorText] = useState<string>('')
useEffect(() => { useEffect(() => {
@ -221,6 +229,7 @@ export function NewContract(props: {
max, max,
initialValue, initialValue,
isLogScale, isLogScale,
answers,
groupId: selectedGroup?.id, groupId: selectedGroup?.id,
}) })
) )
@ -259,10 +268,11 @@ export function NewContract(props: {
'Users can submit their own answers to this market.' 'Users can submit their own answers to this market.'
) )
else setMarketInfoText('') else setMarketInfoText('')
setOutcomeType(choice as 'BINARY' | 'FREE_RESPONSE') setOutcomeType(choice as outcomeType)
}} }}
choicesMap={{ choicesMap={{
'Yes / No': 'BINARY', 'Yes / No': 'BINARY',
'Multiple choice': 'MULTIPLE_CHOICE',
'Free response': 'FREE_RESPONSE', 'Free response': 'FREE_RESPONSE',
Numeric: 'PSEUDO_NUMERIC', Numeric: 'PSEUDO_NUMERIC',
}} }}
@ -277,6 +287,10 @@ export function NewContract(props: {
<Spacer h={6} /> <Spacer h={6} />
{outcomeType === 'MULTIPLE_CHOICE' && (
<MultipleChoiceAnswers setAnswers={setAnswers} />
)}
{outcomeType === 'PSEUDO_NUMERIC' && ( {outcomeType === 'PSEUDO_NUMERIC' && (
<> <>
<div className="form-control mb-2 items-start"> <div className="form-control mb-2 items-start">