1
1
import { Wallet } from '@ethersproject/wallet'
2
2
import { useElection } from '@vocdoni/react-providers'
3
3
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'
5
5
import { FieldValues , FormProvider , useForm , UseFormReturn } from 'react-hook-form'
6
6
import { useConfirm } from '../../layout'
7
- import { QuestionsConfirmation } from './Confirmation'
7
+ import { MultiElectionConfirmation } from './MultiElectionConfirmation'
8
+ import { ElectionStateStorage , RenderWith , SubElectionState , SubmitFormValidation } from './Questions'
8
9
9
10
export const DefaultElectionFormId = 'election-questions'
10
11
11
12
export type QuestionsFormContextState = {
12
13
fmethods : UseFormReturn < any >
13
- vote : ( values : FieldValues ) => Promise < false | void >
14
- }
14
+ } & SpecificFormProviderProps &
15
+ ReturnType < typeof useMultiElectionsProvider >
15
16
16
17
const QuestionsFormContext = createContext < QuestionsFormContextState | undefined > ( undefined )
17
18
@@ -24,66 +25,33 @@ export const useQuestionsForm = () => {
24
25
}
25
26
26
27
export type QuestionsFormProviderProps = {
27
- confirmContents ?: ( election : PublishedElection , answers : FieldValues ) => ReactNode
28
+ confirmContents ?: ( elections : ElectionStateStorage , answers : Record < string , FieldValues > ) => ReactNode
28
29
}
29
30
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
+ }
73
36
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 } )
78
42
79
43
return (
80
44
< 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 >
82
50
</ FormProvider >
83
51
)
84
52
}
85
53
86
- export const getVoteBallot = ( election : PublishedElection , choices : FieldValues ) => {
54
+ export const constructVoteBallot = ( election : PublishedElection , choices : FieldValues ) => {
87
55
let results : number [ ] = [ ]
88
56
switch ( election . resultsType . name ) {
89
57
case ElectionResultsTypeNames . SINGLE_CHOICE_MULTIQUESTION :
@@ -115,3 +83,104 @@ export const getVoteBallot = (election: PublishedElection, choices: FieldValues)
115
83
}
116
84
return results
117
85
}
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
+ }
0 commit comments