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}
+
+ )}
)
}
+
+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 = [