Skip to content

Commit ef03ef0

Browse files
committed
Merge Multielection with the normal form
1 parent c745e91 commit ef03ef0

File tree

7 files changed

+245
-317
lines changed

7 files changed

+245
-317
lines changed
Lines changed: 123 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import { Wallet } from '@ethersproject/wallet'
22
import { useElection } from '@vocdoni/react-providers'
33
import { ElectionResultsTypeNames, PublishedElection } from '@vocdoni/sdk'
4-
import React, { createContext, PropsWithChildren, ReactNode, useContext, useEffect } from 'react'
4+
import React, { createContext, PropsWithChildren, ReactNode, useContext, useEffect, useMemo, useState } from 'react'
55
import { FieldValues, FormProvider, useForm, UseFormReturn } from 'react-hook-form'
66
import { useConfirm } from '../../layout'
7-
import { QuestionsConfirmation } from './Confirmation'
7+
import { MultiElectionConfirmation } from './MultiElectionConfirmation'
8+
import { ElectionStateStorage, RenderWith, SubElectionState, SubmitFormValidation } from './Questions'
89

910
export const DefaultElectionFormId = 'election-questions'
1011

1112
export type QuestionsFormContextState = {
1213
fmethods: UseFormReturn<any>
13-
vote: (values: FieldValues) => Promise<false | void>
14-
}
14+
} & SpecificFormProviderProps &
15+
ReturnType<typeof useMultiElectionsProvider>
1516

1617
const QuestionsFormContext = createContext<QuestionsFormContextState | undefined>(undefined)
1718

@@ -24,66 +25,33 @@ export const useQuestionsForm = () => {
2425
}
2526

2627
export type QuestionsFormProviderProps = {
27-
confirmContents?: (election: PublishedElection, answers: FieldValues) => ReactNode
28+
confirmContents?: (elections: ElectionStateStorage, answers: Record<string, FieldValues>) => ReactNode
2829
}
2930

30-
export const QuestionsFormProvider: React.FC<PropsWithChildren<QuestionsFormProviderProps>> = ({
31-
confirmContents,
32-
children,
33-
}) => {
34-
const fmethods = useForm()
35-
const { confirm } = useConfirm()
36-
const { election, client, vote: bvote } = useElection()
37-
38-
const vote = async (values: Record<string, FieldValues>) => {
39-
if (!election || !(election instanceof PublishedElection)) {
40-
console.warn('vote attempt with no valid election defined')
41-
return false
42-
}
43-
44-
const electionChoices = values[election.id]
45-
46-
if (
47-
client.wallet instanceof Wallet &&
48-
!(await confirm(
49-
typeof confirmContents === 'function' ? (
50-
confirmContents(election, electionChoices)
51-
) : (
52-
<QuestionsConfirmation election={election} answers={electionChoices} />
53-
)
54-
))
55-
) {
56-
return false
57-
}
58-
59-
const votePackage = getVoteBallot(election, electionChoices)
60-
61-
return bvote(votePackage)
62-
}
63-
64-
// reset form if account gets disconnected
65-
useEffect(() => {
66-
if (
67-
typeof client.wallet !== 'undefined' ||
68-
!election ||
69-
!(election instanceof PublishedElection) ||
70-
!election?.questions
71-
)
72-
return
31+
// Props that must not be shared with ElectionQuestionsProps
32+
export type SpecificFormProviderProps = {
33+
renderWith?: RenderWith[]
34+
validate?: SubmitFormValidation
35+
}
7336

74-
fmethods.reset({
75-
...election.questions.reduce((acc, question, index) => ({ ...acc, [index]: '' }), {}),
76-
})
77-
}, [client, election, fmethods])
37+
export const QuestionsFormProvider: React.FC<
38+
PropsWithChildren<QuestionsFormProviderProps & SpecificFormProviderProps>
39+
> = ({ children, ...props }) => {
40+
const fmethods = useForm()
41+
const multiElections = useMultiElectionsProvider({ fmethods, ...props })
7842

7943
return (
8044
<FormProvider {...fmethods}>
81-
<QuestionsFormContext.Provider value={{ fmethods, vote }}>{children}</QuestionsFormContext.Provider>
45+
<QuestionsFormContext.Provider
46+
value={{ fmethods, renderWith: props.renderWith, validate: props.validate, ...multiElections }}
47+
>
48+
{children}
49+
</QuestionsFormContext.Provider>
8250
</FormProvider>
8351
)
8452
}
8553

86-
export const getVoteBallot = (election: PublishedElection, choices: FieldValues) => {
54+
export const constructVoteBallot = (election: PublishedElection, choices: FieldValues) => {
8755
let results: number[] = []
8856
switch (election.resultsType.name) {
8957
case ElectionResultsTypeNames.SINGLE_CHOICE_MULTIQUESTION:
@@ -115,3 +83,104 @@ export const getVoteBallot = (election: PublishedElection, choices: FieldValues)
11583
}
11684
return results
11785
}
86+
87+
const useMultiElectionsProvider = ({
88+
fmethods,
89+
confirmContents,
90+
}: { fmethods: UseFormReturn } & QuestionsFormProviderProps) => {
91+
const { confirm } = useConfirm()
92+
const { client, isAbleToVote: rootIsAbleToVote, voted: rootVoted, election, vote } = useElection() // Root Election
93+
// State to store on memory the loaded elections to pass it into confirm modal to show the info
94+
const [electionsStates, setElectionsStates] = useState<ElectionStateStorage>({})
95+
const [voting, setVoting] = useState<boolean>(false)
96+
97+
const voted = useMemo(
98+
() => (electionsStates && Object.values(electionsStates).every(({ voted }) => voted) ? 'true' : null),
99+
[electionsStates]
100+
)
101+
102+
const isAbleToVote = useMemo(
103+
() => electionsStates && Object.values(electionsStates).some(({ isAbleToVote }) => isAbleToVote),
104+
[electionsStates]
105+
)
106+
107+
// Add an election to the storage
108+
const addElection = (electionState: SubElectionState) => {
109+
setElectionsStates((prev) => ({
110+
...prev,
111+
[(electionState.election as PublishedElection).id]: electionState,
112+
}))
113+
}
114+
115+
// Root election state to be added to the state storage
116+
const rootElectionState: SubElectionState | null = useMemo(() => {
117+
if (!election || !(election instanceof PublishedElection)) return null
118+
return {
119+
vote,
120+
election,
121+
isAbleToVote: rootIsAbleToVote,
122+
voted: rootVoted,
123+
}
124+
}, [vote, election, rootIsAbleToVote, rootVoted])
125+
126+
// reset form if account gets disconnected
127+
useEffect(() => {
128+
if (typeof client.wallet !== 'undefined') return
129+
130+
setElectionsStates({})
131+
fmethods.reset({
132+
...Object.values(electionsStates).reduce((acc, { election }) => ({ ...acc, [election.id]: '' }), {}),
133+
})
134+
}, [client, electionsStates, fmethods])
135+
136+
// Add the root election to the state to elections cache
137+
useEffect(() => {
138+
if (!rootElectionState || !rootElectionState.election) return
139+
const actualState = electionsStates[rootElectionState.election.id]
140+
if (rootElectionState.vote === actualState?.vote || rootElectionState.isAbleToVote === actualState?.isAbleToVote) {
141+
return
142+
}
143+
addElection(rootElectionState)
144+
}, [rootElectionState, electionsStates, election])
145+
146+
const voteAll = async (values: Record<string, FieldValues>) => {
147+
if (!electionsStates || Object.keys(electionsStates).length === 0) {
148+
console.warn('vote attempt with no valid elections not defined')
149+
return false
150+
}
151+
152+
if (
153+
client.wallet instanceof Wallet &&
154+
!(await confirm(
155+
typeof confirmContents === 'function' ? (
156+
confirmContents(electionsStates, values)
157+
) : (
158+
<MultiElectionConfirmation elections={electionsStates} answers={values} />
159+
)
160+
))
161+
) {
162+
return false
163+
}
164+
165+
setVoting(true)
166+
167+
const votingList = Object.entries(electionsStates).map(([key, { election, vote, voted, isAbleToVote }]) => {
168+
if (!(election instanceof PublishedElection) || !values[election.id] || !isAbleToVote) {
169+
return Promise.resolve()
170+
}
171+
const votePackage = constructVoteBallot(election, values[election.id])
172+
return vote(votePackage)
173+
})
174+
return Promise.all(votingList).finally(() => setVoting(false))
175+
}
176+
177+
return {
178+
voting,
179+
voteAll,
180+
rootClient: client,
181+
elections: electionsStates,
182+
addElection,
183+
isAbleToVote,
184+
voted,
185+
}
186+
}

packages/chakra-components/src/components/Election/Questions/MultiElectionConfirmation.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import { chakra, omitThemingProps, useMultiStyleConfig } from '@chakra-ui/system
55
import { useClient } from '@vocdoni/react-providers'
66
import { ElectionResultsTypeNames } from '@vocdoni/sdk'
77
import { FieldValues } from 'react-hook-form'
8-
import { ElectionStateStorage } from './MultiElectionContext'
98
import { useConfirm } from '../../layout'
9+
import { ElectionStateStorage } from './Questions'
1010

1111
export type MultiElectionConfirmationProps = {
1212
answers: Record<string, FieldValues>
1313
elections: ElectionStateStorage
1414
}
1515

16+
// todo(kon): refactor this to merge it with the current Confirmation modal
1617
export const MultiElectionConfirmation = ({ answers, elections, ...rest }: MultiElectionConfirmationProps) => {
1718
const mstyles = useMultiStyleConfig('ConfirmModal')
1819
const styles = useMultiStyleConfig('QuestionsConfirmation', rest)
@@ -26,13 +27,13 @@ export const MultiElectionConfirmation = ({ answers, elections, ...rest }: Multi
2627
<ModalBody sx={mstyles.body}>
2728
<Text sx={styles.description}>{localize('vote.confirm')}</Text>
2829
{Object.values(elections).map(({ election, voted, isAbleToVote }) => {
29-
if (voted)
30-
return (
31-
<chakra.div __css={styles.question} key={election.id}>
32-
<chakra.div __css={styles.title}>{election.title.default}</chakra.div>
33-
<chakra.div __css={styles.answer}>{localize('vote.already_voted')}</chakra.div>
34-
</chakra.div>
35-
)
30+
// if (voted)
31+
// return (
32+
// <chakra.div __css={styles.question} key={election.id}>
33+
// <chakra.div __css={styles.title}>{election.title.default}</chakra.div>
34+
// <chakra.div __css={styles.answer}>{localize('vote.already_voted')}</chakra.div>
35+
// </chakra.div>
36+
// )
3637
if (!isAbleToVote)
3738
return (
3839
<chakra.div __css={styles.question} key={election.id}>
@@ -42,6 +43,7 @@ export const MultiElectionConfirmation = ({ answers, elections, ...rest }: Multi
4243
)
4344
return (
4445
<Box key={election.id} {...props} sx={styles.box}>
46+
{/*todo(kon): refactor to add election title and if already voted but can overwrite*/}
4547
{election.questions.map((q, k) => {
4648
if (election.resultsType.name === ElectionResultsTypeNames.SINGLE_CHOICE_MULTIQUESTION) {
4749
const choice = q.choices.find((v) => v.value === parseInt(answers[election.id][k.toString()], 10))

packages/chakra-components/src/components/Election/Questions/MultiElectionContext.tsx

Lines changed: 0 additions & 127 deletions
This file was deleted.

0 commit comments

Comments
 (0)