Skip to content

Commit

Permalink
Move Questions to custom folder and split and export all its components
Browse files Browse the repository at this point in the history
  • Loading branch information
elboletaire committed May 23, 2024
1 parent 3411e0f commit ce7df74
Show file tree
Hide file tree
Showing 8 changed files with 554 additions and 522 deletions.
522 changes: 0 additions & 522 deletions packages/chakra-components/src/components/Election/Questions.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Button } from '@chakra-ui/button'
import { Box, Text } from '@chakra-ui/layout'
import { ModalBody, ModalCloseButton, ModalFooter, ModalHeader } from '@chakra-ui/modal'
import { chakra, omitThemingProps, useMultiStyleConfig } from '@chakra-ui/system'
import { useClient } from '@vocdoni/react-providers'
import { ElectionResultsTypeNames, PublishedElection } from '@vocdoni/sdk'
import { FieldValues } from 'react-hook-form'
import { useConfirm } from '../../layout'

export type QuestionsConfirmationProps = {
answers: FieldValues
election: PublishedElection
}

export const QuestionsConfirmation = ({ answers, election, ...rest }: QuestionsConfirmationProps) => {
const mstyles = useMultiStyleConfig('ConfirmModal')
const styles = useMultiStyleConfig('QuestionsConfirmation', rest)
const { cancel, proceed } = useConfirm()
const props = omitThemingProps(rest)
const { localize } = useClient()

return (
<>
<ModalHeader sx={mstyles.header}>{localize('confirm.title')}</ModalHeader>
<ModalCloseButton sx={mstyles.close} />
<ModalBody sx={mstyles.body}>
<Box {...props} sx={styles.box}>
<Text sx={styles.description}>{localize('vote.confirm')}</Text>
{election.questions.map((q, k) => {
if (election.resultsType.name === ElectionResultsTypeNames.SINGLE_CHOICE_MULTIQUESTION) {
const choice = q.choices.find((v) => v.value === parseInt(answers[k.toString()], 10))
return (
<chakra.div key={k} __css={styles.question}>
<chakra.div __css={styles.title}>{q.title.default}</chakra.div>
<chakra.div __css={styles.answer}>{choice?.title.default}</chakra.div>
</chakra.div>
)
}
const choices = answers[0]
.map((a: string) =>
q.choices[Number(a)] ? q.choices[Number(a)].title.default : localize('vote.abstain')
)
.map((a: string) => (
<span>
- {a}
<br />
</span>
))

return (
<chakra.div key={k} __css={styles.question}>
<chakra.div __css={styles.title}>{q.title.default}</chakra.div>
<chakra.div __css={styles.answer}>{choices}</chakra.div>
</chakra.div>
)
})}
</Box>
</ModalBody>
<ModalFooter sx={mstyles.footer}>
<Button onClick={cancel!} variant='ghost' sx={mstyles.cancel}>
{localize('confirm.cancel')}
</Button>
<Button onClick={proceed!} sx={mstyles.confirm}>
{localize('confirm.confirm')}
</Button>
</ModalFooter>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import { Checkbox } from '@chakra-ui/checkbox'
import { FormControl, FormErrorMessage } from '@chakra-ui/form-control'
import { Stack } from '@chakra-ui/layout'
import { Radio, RadioGroup } from '@chakra-ui/radio'
import { chakra, ChakraProps, useMultiStyleConfig } from '@chakra-ui/system'
import { useElection } from '@vocdoni/react-providers'
import { ElectionResultsTypeNames, ElectionStatus, IQuestion, PublishedElection } from '@vocdoni/sdk'
import { Controller, useFormContext } from 'react-hook-form'
import { Markdown } from '../../layout'
import { QuestionTip } from './Tip'
import { QuestionsTypeBadge } from './TypeBadge'

export type QuestionProps = {
index: string
question: IQuestion
}

export type QuestionFieldProps = ChakraProps & QuestionProps

export const QuestionField = ({ question, index }: QuestionFieldProps) => {
const styles = useMultiStyleConfig('ElectionQuestions')
const {
formState: { errors },
} = useFormContext()

return (
<chakra.div __css={styles.question}>
<chakra.div __css={styles.typeBadgeWrapper}>
<QuestionsTypeBadge />
</chakra.div>
<FormControl isInvalid={!!errors[index]}>
<chakra.div __css={styles.header}>
<chakra.label __css={styles.title}>{question.title.default}</chakra.label>
</chakra.div>
<chakra.div __css={styles.body}>
{question.description && (
<chakra.div __css={styles.description}>
<Markdown>{question.description.default}</Markdown>
</chakra.div>
)}
<FieldSwitcher index={index} question={question} />
<QuestionTip />
</chakra.div>
</FormControl>
</chakra.div>
)
}

export const FieldSwitcher = (props: QuestionProps) => {
const { election } = useElection()

if (!(election instanceof PublishedElection)) return null

switch (election?.resultsType.name) {
case ElectionResultsTypeNames.MULTIPLE_CHOICE:
return <MultiChoice {...props} />
case ElectionResultsTypeNames.APPROVAL:
return <ApprovalChoice {...props} />
case ElectionResultsTypeNames.SINGLE_CHOICE_MULTIQUESTION:
default:
return <SingleChoice {...props} />
}
}

export const MultiChoice = ({ index, question }: QuestionProps) => {
const styles = useMultiStyleConfig('ElectionQuestions')
const {
election,
isAbleToVote,
loading: { voting },
localize,
} = useElection()
const { control, trigger, watch, getValues } = useFormContext()
const values = watch(index) || []

if (!(election instanceof PublishedElection)) return null

const isNotAbleToVote = election?.status !== ElectionStatus.ONGOING || !isAbleToVote || voting

if (!(election && election.resultsType.name === ElectionResultsTypeNames.MULTIPLE_CHOICE)) {
return null
}

const choices = [...question.choices]
// Put can abstain on a separated variable to avoid typing errors on validation function
const canAbstain = election.resultsType.properties.canAbstain

return (
<Stack sx={styles.stack}>
<Controller
control={control}
disabled={isNotAbleToVote}
rules={{
validate: (v) => {
// allow a single selection if is an abstain
if (canAbstain && v && v.length < election.voteType.maxCount!) return true

return (
(v && v.length === election.voteType.maxCount) ||
localize('validation.choices_count', { count: election.voteType.maxCount })
)
},
}}
name={index}
render={({ field: { ref, onChange, ...restField }, fieldState: { error: fieldError } }) => {
// Determine if the checkbox should be disabled because maximum number of choices has been reached
const currentValues = getValues(index) || []

return (
<>
{choices.map((choice, ck) => {
const maxSelected =
currentValues.length >= election.voteType.maxCount! &&
!currentValues.includes(choice.value.toString())
return (
<Checkbox
{...restField}
key={ck}
sx={styles.checkbox}
value={choice.value.toString()}
isDisabled={isNotAbleToVote || maxSelected}
isChecked={currentValues.includes(choice.value.toString())}
onChange={(e) => {
if (values.includes(e.target.value)) {
onChange(values.filter((v: string) => v !== e.target.value))
} else {
if (maxSelected) return
onChange([...values, e.target.value])
}
trigger(index) // Manually trigger validation
}}
>
{choice.title.default}
</Checkbox>
)
})}
<FormErrorMessage sx={styles.error}>{fieldError?.message as string}</FormErrorMessage>
</>
)
}}
/>
</Stack>
)
}

export const ApprovalChoice = ({ index, question }: QuestionProps) => {
const styles = useMultiStyleConfig('ElectionQuestions')
const {
election,
isAbleToVote,
loading: { voting },
localize,
} = useElection()
const { control, watch } = useFormContext()
const values = watch(index) || []

if (!(election instanceof PublishedElection)) return null

const isNotAbleToVote = election?.status !== ElectionStatus.ONGOING || !isAbleToVote || voting

if (!(election && election.resultsType.name === ElectionResultsTypeNames.APPROVAL)) {
return null
}

const choices = [...question.choices]

return (
<Stack sx={styles.stack}>
<Controller
control={control}
disabled={isNotAbleToVote}
rules={{
validate: (v) => {
return (v && v.length > 0) || localize('validation.at_least_one')
},
}}
name={index}
render={({ field: { ref, onChange, ...restField }, fieldState: { error: fieldError } }) => {
return (
<>
{choices.map((choice, ck) => {
return (
<Checkbox
{...restField}
key={ck}
sx={styles.checkbox}
value={choice.value.toString()}
isDisabled={isNotAbleToVote}
onChange={(e) => {
if (values.includes(e.target.value)) {
onChange(values.filter((v: string) => v !== e.target.value))
} else {
onChange([...values, e.target.value])
}
}}
>
{choice.title.default}
</Checkbox>
)
})}
<FormErrorMessage sx={styles.error}>{fieldError?.message as string}</FormErrorMessage>
</>
)
}}
/>
</Stack>
)
}

export const SingleChoice = ({ index, question }: QuestionProps) => {
const styles = useMultiStyleConfig('ElectionQuestions')
const {
election,
isAbleToVote,
loading: { voting },
localize,
} = useElection()
const {
formState: { errors },
control,
} = useFormContext()

if (!(election instanceof PublishedElection)) return null

const disabled = election?.status !== ElectionStatus.ONGOING || !isAbleToVote || voting
return (
<Controller
control={control}
disabled={disabled}
rules={{
required: localize('validation.required'),
}}
name={index}
render={({ field }) => (
<RadioGroup sx={styles.radioGroup} {...field} isDisabled={disabled}>
<Stack direction='column' sx={styles.stack}>
{question.choices.map((choice, ck) => (
<Radio key={ck} sx={styles.radio} value={choice.value.toString()}>
{choice.title.default}
</Radio>
))}
</Stack>
<FormErrorMessage sx={styles.error}>{errors[index]?.message as string}</FormErrorMessage>
</RadioGroup>
)}
/>
)
}
Loading

0 comments on commit ce7df74

Please sign in to comment.