diff --git a/.gitignore b/.gitignore index ad5e84e..38a9949 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,6 @@ vite.config.ts.timestamp-*.mjs .vscode .yarn .yarnrc* +.idea package.json.backup diff --git a/package.json b/package.json index 25cde20..c502c4f 100644 --- a/package.json +++ b/package.json @@ -44,5 +44,5 @@ "engines": { "npm": "please use yarn", "yarn": ">= 1.19.1 && < 2" - } + } } diff --git a/packages/chakra-components/src/components/Election/Questions/Confirmation.tsx b/packages/chakra-components/src/components/Election/Questions/Confirmation.tsx index dc79b41..7fb5143 100644 --- a/packages/chakra-components/src/components/Election/Questions/Confirmation.tsx +++ b/packages/chakra-components/src/components/Election/Questions/Confirmation.tsx @@ -3,55 +3,69 @@ 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 { ElectionResultsTypeNames } from '@vocdoni/sdk' import { FieldValues } from 'react-hook-form' import { useConfirm } from '../../layout' +import { FormFieldValues } from './Form' +import { ElectionStateStorage } from './Questions' export type QuestionsConfirmationProps = { - answers: FieldValues - election: PublishedElection + answers: FormFieldValues + elections: ElectionStateStorage } -export const QuestionsConfirmation = ({ answers, election, ...rest }: QuestionsConfirmationProps) => { +export const QuestionsConfirmation = ({ answers, elections, ...rest }: QuestionsConfirmationProps) => { const mstyles = useMultiStyleConfig('ConfirmModal') const styles = useMultiStyleConfig('QuestionsConfirmation', rest) const { cancel, proceed } = useConfirm() const props = omitThemingProps(rest) const { localize } = useClient() - return ( <> {localize('confirm.title')} + {localize('vote.confirm')} - {localize('vote.confirm')} - {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)) + {Object.values(elections).map(({ election, isAbleToVote }) => { + if (!isAbleToVote) return ( - - {q.title.default} - {choice?.title.default} + + {election.title.default} + {localize('vote.not_able_to_vote')} ) - } - const choices = answers[0] - .map((a: string) => - q.choices[Number(a)] ? q.choices[Number(a)].title.default : localize('vote.abstain') - ) - .map((a: string) => ( - - - {a} -
-
- )) - return ( - - {q.title.default} - {choices} - + <> + {election.questions.map((q, k) => { + if (election.resultsType.name === ElectionResultsTypeNames.SINGLE_CHOICE_MULTIQUESTION) { + const choice = q.choices.find((v) => v.value === parseInt(answers[election.id][k.toString()], 10)) + return ( + + {q.title.default} + {choice?.title.default} + + ) + } + const choices = answers[election.id][0] + .map((a: string) => + q.choices[Number(a)] ? q.choices[Number(a)].title.default : localize('vote.abstain') + ) + .map((a: string) => ( + + - {a} +
+
+ )) + + return ( + + {q.title.default} + {choices} + + ) + })} + ) })}
diff --git a/packages/chakra-components/src/components/Election/Questions/Fields.tsx b/packages/chakra-components/src/components/Election/Questions/Fields.tsx index 946f436..ea72710 100644 --- a/packages/chakra-components/src/components/Election/Questions/Fields.tsx +++ b/packages/chakra-components/src/components/Election/Questions/Fields.tsx @@ -12,19 +12,26 @@ import { QuestionTip } from './Tip' export type QuestionProps = { index: string question: IQuestion + isDisabled?: boolean } export type QuestionFieldProps = ChakraProps & QuestionProps -export const QuestionField = ({ question, index }: QuestionFieldProps) => { +export const QuestionField = ({ question, index, isDisabled }: QuestionFieldProps) => { const styles = useMultiStyleConfig('ElectionQuestions') const { formState: { errors }, } = useFormContext() + const [election, qi] = index.split('.') + const questionIndex = Number(qi) + let isInvalid = false + if (errors[election] && Array.isArray(errors[election]) && errors[election][questionIndex]) { + isInvalid = !!errors[election][questionIndex] + } return ( - + {question.title.default} @@ -34,7 +41,7 @@ export const QuestionField = ({ question, index }: QuestionFieldProps) => { {question.description.default} )} - + @@ -58,7 +65,7 @@ export const FieldSwitcher = (props: QuestionProps) => { } } -export const MultiChoice = ({ index, question }: QuestionProps) => { +export const MultiChoice = ({ index, question, isDisabled }: QuestionProps) => { const styles = useMultiStyleConfig('ElectionQuestions') const { election, @@ -71,7 +78,7 @@ export const MultiChoice = ({ index, question }: QuestionProps) => { if (!(election instanceof PublishedElection)) return null - const isNotAbleToVote = election?.status !== ElectionStatus.ONGOING || !isAbleToVote || voting + const disabled = election?.status !== ElectionStatus.ONGOING || !isAbleToVote || voting || isDisabled if (!(election && election.resultsType.name === ElectionResultsTypeNames.MULTIPLE_CHOICE)) { return null @@ -85,7 +92,7 @@ export const MultiChoice = ({ index, question }: QuestionProps) => { { // allow a single selection if is an abstain @@ -114,7 +121,7 @@ export const MultiChoice = ({ index, question }: QuestionProps) => { key={ck} sx={styles.checkbox} value={choice.value.toString()} - isDisabled={isNotAbleToVote || maxSelected} + isDisabled={disabled || maxSelected} isChecked={currentValues.includes(choice.value.toString())} onChange={(e) => { if (values.includes(e.target.value)) { @@ -139,7 +146,7 @@ export const MultiChoice = ({ index, question }: QuestionProps) => { ) } -export const ApprovalChoice = ({ index, question }: QuestionProps) => { +export const ApprovalChoice = ({ index, question, isDisabled }: QuestionProps) => { const styles = useMultiStyleConfig('ElectionQuestions') const { election, @@ -152,7 +159,7 @@ export const ApprovalChoice = ({ index, question }: QuestionProps) => { if (!(election instanceof PublishedElection)) return null - const isNotAbleToVote = election?.status !== ElectionStatus.ONGOING || !isAbleToVote || voting + const disabled = election?.status !== ElectionStatus.ONGOING || !isAbleToVote || voting || isDisabled if (!(election && election.resultsType.name === ElectionResultsTypeNames.APPROVAL)) { return null @@ -164,7 +171,7 @@ export const ApprovalChoice = ({ index, question }: QuestionProps) => { { return (v && v.length > 0) || localize('validation.at_least_one') @@ -181,7 +188,7 @@ export const ApprovalChoice = ({ index, question }: QuestionProps) => { key={ck} sx={styles.checkbox} value={choice.value.toString()} - isDisabled={isNotAbleToVote} + isDisabled={disabled} onChange={(e) => { if (values.includes(e.target.value)) { onChange(values.filter((v: string) => v !== e.target.value)) @@ -203,7 +210,7 @@ export const ApprovalChoice = ({ index, question }: QuestionProps) => { ) } -export const SingleChoice = ({ index, question }: QuestionProps) => { +export const SingleChoice = ({ index, question, isDisabled }: QuestionProps) => { const styles = useMultiStyleConfig('ElectionQuestions') const { election, @@ -218,7 +225,7 @@ export const SingleChoice = ({ index, question }: QuestionProps) => { if (!(election instanceof PublishedElection)) return null - const disabled = election?.status !== ElectionStatus.ONGOING || !isAbleToVote || voting + const disabled = election?.status !== ElectionStatus.ONGOING || !isAbleToVote || voting || isDisabled return ( { required: localize('validation.required'), }} name={index} - render={({ field }) => ( + render={({ field, fieldState: { error: fieldError } }) => ( {question.choices.map((choice, ck) => ( @@ -236,7 +243,7 @@ export const SingleChoice = ({ index, question }: QuestionProps) => { ))} - {errors[index]?.message as string} + {fieldError?.message as string} )} /> diff --git a/packages/chakra-components/src/components/Election/Questions/Form.tsx b/packages/chakra-components/src/components/Election/Questions/Form.tsx index 4306fdf..4860d89 100644 --- a/packages/chakra-components/src/components/Election/Questions/Form.tsx +++ b/packages/chakra-components/src/components/Election/Questions/Form.tsx @@ -1,15 +1,27 @@ import { Wallet } from '@ethersproject/wallet' import { useElection } from '@vocdoni/react-providers' import { ElectionResultsTypeNames, PublishedElection } from '@vocdoni/sdk' -import React, { createContext, PropsWithChildren, ReactNode, useContext, useEffect } from 'react' +import React, { + createContext, + PropsWithChildren, + ReactNode, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from 'react' import { FieldValues, FormProvider, useForm, UseFormReturn } from 'react-hook-form' import { useConfirm } from '../../layout' import { QuestionsConfirmation } from './Confirmation' +import { ElectionStateStorage, RenderWith, SubElectionState } from './Questions' + +export type FormFieldValues = Record export type QuestionsFormContextState = { - fmethods: UseFormReturn - vote: (values: FieldValues) => Promise -} + fmethods: UseFormReturn +} & SpecificFormProviderProps & + ReturnType const QuestionsFormContext = createContext(undefined) @@ -22,20 +34,153 @@ export const useQuestionsForm = () => { } export type QuestionsFormProviderProps = { - confirmContents?: (election: PublishedElection, answers: FieldValues) => ReactNode + confirmContents?: (elections: ElectionStateStorage, answers: FormFieldValues) => ReactNode +} + +// Props that must not be shared with ElectionQuestionsProps +export type SpecificFormProviderProps = { + renderWith?: RenderWith[] +} + +export const QuestionsFormProvider: React.FC< + PropsWithChildren +> = ({ children, ...props }) => { + const fmethods = useForm() + const multiElections = useMultiElectionsProvider({ fmethods, ...props }) + const value = { fmethods, renderWith: props.renderWith, ...multiElections } + + return ( + + {children} + + ) } -export const QuestionsFormProvider: React.FC> = ({ +export const constructVoteBallot = (election: PublishedElection, choices: FieldValues) => { + let results: number[] = [] + switch (election.resultsType.name) { + case ElectionResultsTypeNames.SINGLE_CHOICE_MULTIQUESTION: + results = election.questions.map((q, k) => parseInt(choices[k.toString()], 10)) + break + case ElectionResultsTypeNames.MULTIPLE_CHOICE: + results = Object.values(choices) + .pop() + .map((v: string) => parseInt(v, 10)) + // map proper abstain ids + if (election.resultsType.properties.canAbstain && results.length < election.voteType.maxCount!) { + let abs = 0 + while (results.length < (election.voteType.maxCount || 1)) { + results.push(parseInt(election.resultsType.properties.abstainValues[abs++], 10)) + } + } + break + case ElectionResultsTypeNames.APPROVAL: + results = election.questions[0].choices.map((c, k) => { + if (choices[0].includes(k.toString())) { + return 1 + } else { + return 0 + } + }) + break + default: + throw new Error('Unknown or invalid election type') + } + return results +} + +const useMultiElectionsProvider = ({ + fmethods, confirmContents, - children, -}) => { - const fmethods = useForm() + ...rest +}: { fmethods: UseFormReturn } & QuestionsFormProviderProps & SpecificFormProviderProps) => { const { confirm } = useConfirm() - const { election, client, vote: bvote } = useElection() + // State to manually disable the form + const [isDisabled, setIsDisabled] = useState(false) + const { client, isAbleToVote: rootIsAbleToVote, voted: rootVoted, election, loaded: rootLoaded, vote } = useElection() // Root Election + // State to store on memory the loaded elections to pass it into confirm modal to show the info + const [electionsStates, setElectionsStates] = useState({}) + const [voting, setVoting] = useState(false) + + // Util to check if the electionsStates object contains elections and is not empty + const _electionsCount = Object.values(electionsStates).length + const voted = useMemo( + () => + electionsStates && _electionsCount > 0 && Object.values(electionsStates).every(({ voted }) => voted) + ? 'true' + : null, + [electionsStates, _electionsCount] + ) + + const isAbleToVote = useMemo( + () => + electionsStates && _electionsCount > 0 && Object.values(electionsStates).some(({ isAbleToVote }) => isAbleToVote), + [electionsStates, _electionsCount] + ) + + const loaded = useMemo(() => { + let renderWithCached = true + if (rest.renderWith?.length) { + renderWithCached = _electionsCount === rest.renderWith?.length + 1 + } + return ( + electionsStates && + _electionsCount > 0 && + renderWithCached && // If the amount of elections is the same as the amount of subelections + root election + Object.values(electionsStates).every(({ loaded }) => loaded.election) + ) + }, [rest.renderWith?.length, electionsStates, _electionsCount]) + + // Add an election to the storage + const addElection = useCallback( + (electionState: SubElectionState) => { + setElectionsStates((prev) => ({ + ...prev, + [(electionState.election as PublishedElection).id]: electionState, + })) + }, + [setElectionsStates] + ) + + // Root election state to be added to the state storage + const rootElectionState: SubElectionState | null = useMemo(() => { + if (!election || !(election instanceof PublishedElection)) return null + return { + vote, + election, + isAbleToVote: rootIsAbleToVote, + voted: rootVoted, + loaded: rootLoaded, + } + }, [vote, election, rootIsAbleToVote, rootVoted, rootLoaded]) - const vote = async (values: FieldValues) => { - if (!election || !(election instanceof PublishedElection)) { - console.warn('vote attempt with no valid election defined') + // reset form if account gets disconnected + useEffect(() => { + if ( + (typeof client.wallet === 'undefined' || Object.values(client.wallet).length === 0) && + Object.keys(electionsStates).length > 0 + ) { + fmethods.reset({ + ...Object.values(electionsStates).reduce((acc, { election }) => ({ ...acc, [election.id]: '' }), {}), + }) + } + }, [client, electionsStates, fmethods]) + + // Add the root election to the state to elections cache + useEffect(() => { + if (!rootElectionState || !rootElectionState.election) return + const actualState = electionsStates[rootElectionState.election.id] + if ( + (!actualState && rootElectionState.loaded.election) || + (actualState && rootElectionState.isAbleToVote !== actualState?.isAbleToVote) + ) { + addElection(rootElectionState) + } + }, [rootElectionState, electionsStates]) + + const voteAll = async (values: FormFieldValues) => { + if (!electionsStates || Object.keys(electionsStates).length === 0) { + console.warn('vote attempt with no valid elections defined') return false } @@ -43,66 +188,37 @@ export const QuestionsFormProvider: React.FC + ) )) ) { return false } - let results: number[] = [] - switch (election.resultsType.name) { - case ElectionResultsTypeNames.SINGLE_CHOICE_MULTIQUESTION: - results = election.questions.map((q, k) => parseInt(values[k.toString()], 10)) - break - case ElectionResultsTypeNames.MULTIPLE_CHOICE: - results = Object.values(values) - .pop() - .map((v: string) => parseInt(v, 10)) - // map proper abstain ids - if (election.resultsType.properties.canAbstain && results.length < election.voteType.maxCount!) { - let abs = 0 - while (results.length < (election.voteType.maxCount || 1)) { - results.push(parseInt(election.resultsType.properties.abstainValues[abs++], 10)) - } - } - break - case ElectionResultsTypeNames.APPROVAL: - results = election.questions[0].choices.map((c, k) => { - if (values[0].includes(k.toString())) { - return 1 - } else { - return 0 - } - }) - break - default: - throw new Error('Unknown or invalid election type') - } - - return bvote(results) - } - - // reset form if account gets disconnected - useEffect(() => { - if ( - typeof client.wallet !== 'undefined' || - !election || - !(election instanceof PublishedElection) || - !election?.questions - ) - return + setVoting(true) - fmethods.reset({ - ...election.questions.reduce((acc, question, index) => ({ ...acc, [index]: '' }), {}), + const votingList = Object.entries(electionsStates).map(([key, { election, vote, isAbleToVote }]) => { + if (!(election instanceof PublishedElection) || !values[election.id] || !isAbleToVote) { + return Promise.resolve() + } + const votePackage = constructVoteBallot(election, values[election.id]) + return vote(votePackage) }) - }, [client, election, fmethods]) + return Promise.all(votingList).finally(() => setVoting(false)) + } - return ( - - {children} - - ) + return { + voting, + voteAll, + rootClient: client, + elections: electionsStates, + addElection, + isAbleToVote, + voted, + isDisabled, + setIsDisabled, + loaded, + } } diff --git a/packages/chakra-components/src/components/Election/Questions/Questions.tsx b/packages/chakra-components/src/components/Election/Questions/Questions.tsx index 6538f6b..53f23d9 100644 --- a/packages/chakra-components/src/components/Election/Questions/Questions.tsx +++ b/packages/chakra-components/src/components/Election/Questions/Questions.tsx @@ -1,26 +1,74 @@ import { Alert, AlertIcon } from '@chakra-ui/alert' import { chakra, ChakraProps, useMultiStyleConfig } from '@chakra-ui/system' -import { useElection } from '@vocdoni/react-providers' +import { ElectionProvider, ElectionState, useElection } from '@vocdoni/react-providers' import { IQuestion, PublishedElection } from '@vocdoni/sdk' -import { FieldValues, SubmitErrorHandler } from 'react-hook-form' -import { QuestionField } from './Fields' -import { QuestionsFormProvider, QuestionsFormProviderProps, useQuestionsForm } from './Form' +import { FieldValues, SubmitErrorHandler, SubmitHandler } from 'react-hook-form' +import { QuestionField, QuestionProps } from './Fields' +import { FormFieldValues, QuestionsFormProvider, QuestionsFormProviderProps, useQuestionsForm } from './Form' import { QuestionsTypeBadge } from './TypeBadge' -import { Voted } from './Voted' +import { MultiElectionVoted, Voted } from './Voted' +import React, { useEffect, useMemo } from 'react' + +export type RenderWith = { + id: string +} + +export type ExtendedSubmitHandler = ( + onSubmit: SubmitHandler, + ...args: [...Parameters>] +) => ReturnType> export type ElectionQuestionsFormProps = ChakraProps & { onInvalid?: SubmitErrorHandler + onSubmit?: ExtendedSubmitHandler + formId?: string } export type ElectionQuestionsProps = ElectionQuestionsFormProps & QuestionsFormProviderProps -export const ElectionQuestions = ({ confirmContents, ...props }: ElectionQuestionsProps) => ( - - - -) +export const ElectionQuestions = ({ confirmContents, ...props }: ElectionQuestionsProps) => { + return ( + + + + ) +} + +export const ElectionQuestionsForm = ({ formId, onSubmit, onInvalid, ...rest }: ElectionQuestionsFormProps) => { + const styles = useMultiStyleConfig('ElectionQuestions') + const { loaded, fmethods, voteAll, renderWith, isDisabled } = useQuestionsForm() + const { ConnectButton, election } = useElection() // use Root election information + + const { handleSubmit } = fmethods + + if (!(election instanceof PublishedElection)) return null + + return ( + { + if (onSubmit) { + return onSubmit(voteAll, ...params) + } + return voteAll(params[0]) + }, onInvalid)} + id={formId ?? `election-questions-${election.id}`} + __css={styles.form} + > + + + {loaded && } + {renderWith?.length > 0 && + renderWith.map(({ id }) => ( + + + + ))} + + + ) +} -export const ElectionQuestionsForm = (props: ElectionQuestionsFormProps) => { +export const ElectionQuestion = ({ isDisabled, ...props }: Pick & ChakraProps) => { const { election, voted, @@ -28,15 +76,13 @@ export const ElectionQuestionsForm = (props: ElectionQuestionsFormProps) => { localize, isAbleToVote, } = useElection() - const { fmethods, vote } = useQuestionsForm() const styles = useMultiStyleConfig('ElectionQuestions') const questions: IQuestion[] | undefined = (election as PublishedElection)?.questions - const { onInvalid, ...rest } = props if (!(election instanceof PublishedElection)) return null if (voted && !isAbleToVote) { - return + return null } if (!questions || (questions && !questions?.length)) { @@ -49,22 +95,70 @@ export const ElectionQuestionsForm = (props: ElectionQuestionsFormProps) => { } return ( - + -
- - - - {questions.map((question, qk) => ( - - ))} - {error && ( - - - {error} - - )} - + + + + {questions.map((question, qk) => ( + + ))} + {error && ( + + + {error} + + )}
) } + +export type SubElectionState = { election: PublishedElection } & Pick< + ElectionState, + 'vote' | 'isAbleToVote' | 'voted' | 'loaded' +> +export type ElectionStateStorage = Record + +export const SubElectionQuestions = (props: ChakraProps) => { + const { rootClient, addElection, elections, isDisabled, loaded: renderWithLoaded } = useQuestionsForm() + const { election, setClient, vote, clearClient, isAbleToVote, voted, loaded } = useElection() + + const subElectionState: SubElectionState | null = useMemo(() => { + if (!election || !(election instanceof PublishedElection)) return null + return { + vote, + election, + isAbleToVote, + voted, + loaded, + } + }, [vote, election, isAbleToVote, voted, loaded]) + + // clear session of local context when login out + useEffect(() => { + if (rootClient.wallet === undefined || Object.keys(rootClient.wallet).length === 0) { + clearClient() + } + }, [rootClient]) + + // ensure the client is set to the root one + useEffect(() => { + setClient(rootClient) + }, [election, rootClient]) + + // Add the sub election to the state cache + useEffect(() => { + if (!subElectionState || !subElectionState.election) return + const actualState = elections[subElectionState.election.id] + if ( + (!actualState && subElectionState.loaded.election) || + (actualState && subElectionState.isAbleToVote !== actualState?.isAbleToVote) + ) { + addElection(subElectionState) + } + }, [subElectionState, elections]) + + if (!renderWithLoaded) return null + + return +} diff --git a/packages/chakra-components/src/components/Election/Questions/Voted.tsx b/packages/chakra-components/src/components/Election/Questions/Voted.tsx index 0a3b055..d322dcd 100644 --- a/packages/chakra-components/src/components/Election/Questions/Voted.tsx +++ b/packages/chakra-components/src/components/Election/Questions/Voted.tsx @@ -1,16 +1,39 @@ import { Alert, AlertDescription, AlertIcon, AlertTitle } from '@chakra-ui/alert' import { Link } from '@chakra-ui/layout' -import { useMultiStyleConfig } from '@chakra-ui/system' +import { chakra, useMultiStyleConfig } from '@chakra-ui/system' import { useClient, useElection } from '@vocdoni/react-providers' import reactStringReplace from 'react-string-replace' import { environment } from '../../../environment' +import { useQuestionsForm } from './Form' + +export const MultiElectionVoted = () => { + const { voted, elections } = useQuestionsForm() + if (!voted) { + return null + } + const votes = Object.values(elections) + .map((e) => e.voted) + .filter((voted) => voted !== null) + return +} export const Voted = () => { - const { env } = useClient() - const { localize, voted } = useElection() + const { voted } = useElection() + if (!voted) { + return null + } + return +} + +interface IVotedLogicProps { + votes: string[] +} + +const VotedLogic = ({ votes }: IVotedLogicProps) => { + const { localize } = useElection() const styles = useMultiStyleConfig('ElectionQuestions') - if (!voted) { + if (!(votes?.length > 0)) { return null } @@ -28,12 +51,49 @@ export const Voted = () => { {localize('vote.voted_title')} - {reactStringReplace(localize('vote.voted_description', { id: voted }), voted, (match, k) => ( - - {match} - - ))} + {votes.length === 1 ? : } ) } + +const SingleElectionVoted = ({ voted }: { voted: string }) => { + const { localize } = useElection() + const { env } = useClient() + const styles = useMultiStyleConfig('ElectionQuestions') + return ( + <> + {reactStringReplace(localize('vote.voted_description', { id: voted }), voted, (match, k) => ( + + {match} + + ))} + + ) +} + +const MultipleElectionVoted = ({ votes }: IVotedLogicProps) => { + const { localize } = useElection() + const { env } = useClient() + const styles = useMultiStyleConfig('ElectionQuestions') + const votesString = votes.join(',') + return ( + + {reactStringReplace(localize('vote.voted_description_multielection', { ids: votesString }), votesString, () => ( + <> + {votes.map((voted) => ( + + {voted} + + ))} + + ))} + + ) +} diff --git a/packages/chakra-components/src/components/Election/VoteButton.tsx b/packages/chakra-components/src/components/Election/VoteButton.tsx index 2612751..85e1a66 100644 --- a/packages/chakra-components/src/components/Election/VoteButton.tsx +++ b/packages/chakra-components/src/components/Election/VoteButton.tsx @@ -2,13 +2,41 @@ import { ButtonProps } from '@chakra-ui/button' import { Text } from '@chakra-ui/layout' import { chakra, useMultiStyleConfig } from '@chakra-ui/system' import { Signer } from '@ethersproject/abstract-signer' -import { useClient, useElection } from '@vocdoni/react-providers' +import { ElectionState, useClient, useElection } from '@vocdoni/react-providers' import { ElectionStatus, InvalidElection, PublishedElection } from '@vocdoni/sdk' import { useEffect, useState } from 'react' import { Button } from '../layout/Button' import { results } from './Results' +import { useQuestionsForm } from './Questions' export const VoteButton = (props: ButtonProps) => { + const election = useElection() + try { + const questionForm = useQuestionsForm() + return + } catch (e) { + return + } +} + +export const MultiElectionVoteButton = (props: ButtonProps) => { + const { isAbleToVote, voting, voted } = useQuestionsForm() + const election = useElection() // use Root election information + + return ( + + ) +} + +export const VoteButtonLogic = ({ + electionState, + ...props +}: { + electionState: ElectionState +} & ButtonProps) => { const { connected } = useClient() const { client, @@ -21,7 +49,7 @@ export const VoteButton = (props: ButtonProps) => { localize, sik: { signature }, sikSignature, - } = useElection() + } = electionState const [loading, setLoading] = useState(false) if (!election || election instanceof InvalidElection) { @@ -36,8 +64,8 @@ export const VoteButton = (props: ButtonProps) => { const button: ButtonProps = { type: 'submit', - ...props, form: `election-questions-${election.id}`, + ...props, isDisabled, isLoading: voting, children: voted && isAbleToVote ? localize('vote.button_update') : localize('vote.button'), @@ -98,7 +126,7 @@ export const VoteWeight = () => { })() }, [client, election]) - if (!weight || !election || !(election instanceof PublishedElection)) return + if (!weight || !election || !(election instanceof PublishedElection)) return null return ( diff --git a/packages/chakra-components/src/i18n/locales.ts b/packages/chakra-components/src/i18n/locales.ts index b7ffb09..7007dd1 100644 --- a/packages/chakra-components/src/i18n/locales.ts +++ b/packages/chakra-components/src/i18n/locales.ts @@ -102,6 +102,7 @@ export const locales = { confirm: 'Please confirm your choices:', sign: 'Sign first', voted_description: 'Your vote id is {{ id }}. You can use it to verify your vote.', + voted_description_multielection: 'Your vote ids are: {{ ids }} You can use its to verify your votes.', voted_title: 'Your vote was successfully cast!', weight: 'Your voting power is: ', }, diff --git a/packages/chakra-components/src/theme/questions.ts b/packages/chakra-components/src/theme/questions.ts index 3bbc610..a53f48f 100644 --- a/packages/chakra-components/src/theme/questions.ts +++ b/packages/chakra-components/src/theme/questions.ts @@ -2,13 +2,16 @@ import { createMultiStyleConfigHelpers } from '@chakra-ui/styled-system' import { theme } from '@chakra-ui/theme' export const questionsAnatomy = [ - // main content wrapper + // Question wrapper 'wrapper', // alert messages (voted or no questions available) 'alert', 'alertTitle', 'alertDescription', + 'alertDescriptionWrapper', // Wrapper for multielection voted message 'alertLink', + // elections wrapper for multielections + 'elections', // question wrapper 'question', // question header @@ -30,6 +33,8 @@ export const questionsAnatomy = [ 'checkbox', // form error message 'error', + // form wrapper + 'form', ] export const questionsConfirmationAnatomy = [