Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Multiprocesses components #10

Draft
wants to merge 31 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9b02376
Implement Multielection
selankon Oct 9, 2024
2988e9c
Implement Multielection VoteButton
selankon Oct 10, 2024
8edc360
Fix styles
selankon Oct 10, 2024
ff033f7
Create a submit form controller
selankon Oct 11, 2024
c407b1e
Fix global submit validation
selankon Oct 11, 2024
555beab
Add validator
selankon Oct 11, 2024
ebb20cf
Fix unique key prop
selankon Oct 11, 2024
2e56db8
Fix Layout issues
selankon Oct 11, 2024
626bfc1
Move MultiElection components to ui components
selankon Oct 14, 2024
78a0980
Use new version of MultiProcess provider
selankon Oct 16, 2024
b840340
Fix not render first election
selankon Oct 17, 2024
067cf7d
Show renderWith elections results
selankon Oct 17, 2024
c4ede87
Hide vote menu when is multiprocess
selankon Oct 17, 2024
e803ca5
Unify VoteButton layout
selankon Oct 18, 2024
d82dee3
Recover voting modals
selankon Oct 18, 2024
53c55c8
Fix ConfirmVoteModal.tsx
selankon Oct 18, 2024
c788c60
Add confirmContents to isRenderWith elections
selankon Oct 21, 2024
0a17664
Hide vote button if voted
selankon Oct 21, 2024
b56bb95
Implement multielection voted
selankon Oct 22, 2024
5af2740
Fix confirm modal answers
selankon Oct 22, 2024
1010e38
Add not able to vote on confirmation modal
selankon Oct 22, 2024
ed3d28c
Use strict false
selankon Oct 31, 2024
78ad9d3
Fix no default process
selankon Nov 6, 2024
94213c4
Properly import renderwith
selankon Nov 6, 2024
ad79285
Fix isWeighted
selankon Nov 11, 2024
d1f324e
Fix vote modals
selankon Nov 11, 2024
fb34703
Fix use root election context properly
selankon Nov 12, 2024
f920fa0
Add translations
selankon Nov 12, 2024
09ec70f
Change spreadsheet acces theme
selankon Nov 13, 2024
70cbe70
Fix alert overflow
selankon Nov 13, 2024
d2b3ac8
Update results on renderwith elections
selankon Nov 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@ import { VocdoniEnvironment } from '~constants'
import { translations } from '~i18n/components'
import { clientToSigner } from '~util/wagmi-adapters'
import { RoutesProvider } from './router'
import { ExternalProvider, JsonRpcFetchFunc } from '@ethersproject/providers'

export const App = () => {
const { data } = useWalletClient()
const { t } = useTranslation()

let signer: Signer = {} as Signer
if (data) {
signer = clientToSigner(data)
signer = clientToSigner({
transport: data.transport as ExternalProvider | JsonRpcFetchFunc,
chain: data.chain,
account: data.account,
})
}

return (
Expand Down
19 changes: 11 additions & 8 deletions src/components/Process/Aside.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,16 @@ const ProcessAside = () => {
} = useElection()
const { isConnected } = useAccount()
const { env, clear } = useClient()

if (!election || !(election instanceof PublishedElection)) return null

const census: CensusMeta = dotobject(election?.meta || {}, 'census')

const isMultiProcess = !!election.get('multiprocess')
const renderVoteMenu =
voted ||
(voting && election?.electionType.anonymous) ||
(hasOverwriteEnabled(election) && isInCensus && votesLeft > 0 && voted)
!isMultiProcess &&
(voted ||
(voting && election?.electionType.anonymous) ||
(hasOverwriteEnabled(election) && isInCensus && votesLeft > 0 && voted))

const showVoters =
election?.status !== ElectionStatus.CANCELED &&
Expand Down Expand Up @@ -197,7 +199,8 @@ const ProcessAside = () => {
)
}

export const VoteButton = ({ ...props }: FlexProps) => {
type VoteButtonProps = FlexProps
export const VoteButton = (props: VoteButtonProps) => {
const { t } = useTranslation()
const { election, connected, isAbleToVote, isInCensus } = useElection()
const { isConnected } = useAccount()
Expand All @@ -215,8 +218,7 @@ export const VoteButton = ({ ...props }: FlexProps) => {
return null
}

const isWeighted = election?.census.weight !== election?.census.size

const isWeighted = Number(election?.census.weight) !== election?.census.size
const isBlindCsp = census?.type === 'csp' && election?.meta.csp?.service === 'vocdoni-blind-csp'

return (
Expand Down Expand Up @@ -271,7 +273,8 @@ export const VoteButton = ({ ...props }: FlexProps) => {
mb={4}
sx={{
'&::disabled': {
opacity: '0.8',
// opacity: '0.8',
display: 'none',
},
}}
/>
Expand Down
170 changes: 141 additions & 29 deletions src/components/Process/Chained.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,54 @@
import { Box, Progress } from '@chakra-ui/react'
import { Box, Flex, Progress, useBreakpointValue } from '@chakra-ui/react'
import { ConnectButton } from '@rainbow-me/rainbowkit'
import { ElectionQuestions, ElectionResults, SpreadsheetAccess } from '@vocdoni/chakra-components'
import {
ElectionQuestions,
ElectionQuestionsForm,
ElectionResults,
QuestionsFormProvider,
RenderWith,

Check failure on line 8 in src/components/Process/Chained.tsx

View workflow job for this annotation

GitHub Actions / build-stg

Module '"@vocdoni/chakra-components"' has no exported member 'RenderWith'.
SpreadsheetAccess,
} from '@vocdoni/chakra-components'
import { ElectionProvider, useElection } from '@vocdoni/react-providers'
import { InvalidElection, IVotePackage, PublishedElection, VocdoniSDKClient } from '@vocdoni/sdk'
import { useEffect, useState } from 'react'
import { PropsWithChildren, useEffect, useState } from 'react'
import { Trans } from 'react-i18next'
import { VoteButton } from './Aside'
import { ChainedProvider, useChainedProcesses } from './ChainedContext'
import { ConfirmVoteModal } from './ConfirmVoteModal'
import { VoteButton } from '~components/Process/Aside'
import BlindCSPConnect from '~components/Process/BlindCSPConnect'
import { ConfirmVoteModal } from '~components/Process/ConfirmVoteModal'
import { MultiElectionSuccessVoteModal, SuccessVoteModal } from '~components/Process/SuccessVoteModal'
import VotingVoteModal, { MultiElectionVotingVoteModal } from '~components/Process/VotingVoteModal'
import { ChainedProvider, useChainedProcesses } from './ChainedContext'

type ChainedProcessesInnerProps = {
connected: boolean
}

const VoteButtonContainer = ({ children }: PropsWithChildren) => {
const isBreakPoint = useBreakpointValue({ base: true, lg2: false })
if (isBreakPoint) {
return (
<Box
position='sticky'
bottom={0}
left={0}
bgColor='process.aside.aside_footer_mbl_border'
pt={1}
display={isBreakPoint ? 'block' : 'none'}
>
{children}
</Box>
)
}
return (
<Box position='sticky' bottom={0} left={0} pb={1} pt={1} display={isBreakPoint ? 'none' : 'block'}>
{children}
</Box>
)
}

const ChainedProcessesInner = ({ connected }: ChainedProcessesInnerProps) => {
const { election, voted, setClient, clearClient } = useElection()
const { processes, client, current, setProcess, setCurrent } = useChainedProcesses()
const { processes, client, current, setProcess, setCurrent, root } = useChainedProcesses()

// clear session of local context when login out
useEffect(() => {
Expand All @@ -26,7 +58,9 @@

// ensure the client is set to the root one
useEffect(() => {
setClient(client)
if (election.id !== root.id) {
setClient(client)
}
}, [client, election])

// fetch current process and process flow logic
Expand All @@ -40,6 +74,7 @@
// fetch votes info
const next = await getNextProcessInFlow(client, voted, meta)

if (typeof next === 'undefined') return // If cannot found next process, return
if (typeof processes[next] === 'undefined') {
const election = await client.fetchElection(next)
setProcess(next, election)
Expand All @@ -48,6 +83,18 @@
})()
}, [processes, current, voted, client])

const [renderWith, setRenderWith] = useState<RenderWith[]>([])
// Effect to set renderWith component state.
useEffect(() => {
if (!current || processes[current] instanceof InvalidElection) return
const currentElection = processes[current]
const meta = currentElection.get('multiprocess')
if (meta && meta.renderWith) {
setRenderWith(meta.renderWith)
}
}, [current, processes])
const isRenderWith = renderWith.length > 0

if (!current || !processes[current]) {
return <Progress w='full' size='xs' isIndeterminate />
}
Expand All @@ -56,15 +103,33 @@
return <Trans i18nKey='error.election_is_invalid'>Invalid election</Trans>
}

if (isRenderWith) {
return (
<QuestionsFormProvider
renderWith={renderWith}

Check failure on line 109 in src/components/Process/Chained.tsx

View workflow job for this annotation

GitHub Actions / build-stg

Type '{ children: Element[]; renderWith: RenderWith[]; confirmContents: (elections: PublishedElection, answers: FieldValues) => Element; }' is not assignable to type 'IntrinsicAttributes & QuestionsFormProviderProps & { children?: ReactNode; }'.
confirmContents={(elections, answers) => <ConfirmVoteModal elections={elections} answers={answers} />}

Check failure on line 110 in src/components/Process/Chained.tsx

View workflow job for this annotation

GitHub Actions / build-stg

Type '{ elections: PublishedElection; answers: FieldValues; }' is not assignable to type 'IntrinsicAttributes & QuestionsConfirmationProps'.
>
<ElectionQuestionsForm />
<VoteButtonContainer>
<VoteButton />
</VoteButtonContainer>
<MultiElectionVotingVoteModal />
<MultiElectionSuccessVoteModal />
</QuestionsFormProvider>
)
}

return (
<Box className='md-sizes' mb='100px' pt='25px'>
<>
<ElectionQuestions
confirmContents={(election, answers) => <ConfirmVoteModal election={election} answers={answers} />}
confirmContents={(elections, answers) => <ConfirmVoteModal elections={elections} answers={answers} />}

Check failure on line 125 in src/components/Process/Chained.tsx

View workflow job for this annotation

GitHub Actions / build-stg

Type '{ elections: PublishedElection; answers: FieldValues; }' is not assignable to type 'IntrinsicAttributes & QuestionsConfirmationProps'.
/>
<Box position='sticky' bottom={0} left={0} pb={1} pt={1} display={{ base: 'none', lg2: 'block' }}>
<VoteButtonContainer>
<VoteButton />
</Box>
</Box>
</VoteButtonContainer>
<VotingVoteModal />
<SuccessVoteModal />
</>
)
}

Expand Down Expand Up @@ -114,29 +179,43 @@
return <Progress w='full' size='xs' isIndeterminate />
}

if (processes[current] instanceof InvalidElection) {
return <Trans i18nKey='error.election_is_invalid'>Invalid election</Trans>
}

const isBlindCsp = election.get('census.type') === 'csp' && election?.meta.csp?.service === 'vocdoni-blind-csp'

return (
<>
<ElectionProvider key={current} election={processes[current]} ConnectButton={ConnectButton} fetchCensus>
<Box className='md-sizes' mb='100px' pt='25px'>
{current === election.id ? (
<ChainedProcessesInner connected={connected} />
</ElectionProvider>
{!connected && election.get('census.type') === 'spreadsheet' && <SpreadsheetAccess />}
{isBlindCsp && !connected && <BlindCSPConnect />}
</>
) : (
<ElectionProvider key={current} election={processes[current]} ConnectButton={ConnectButton} fetchCensus>
<ChainedProcessesInner connected={connected} />
</ElectionProvider>
)}
<VoteButtonContainer>
<Flex justifyContent='center' alignItems='center' direction={'column'} py={3} px={{ base: 3, lg2: 0 }}>
{!connected && election.get('census.type') === 'spreadsheet' && <SpreadsheetAccess />}
{isBlindCsp && !connected && <BlindCSPConnect />}
</Flex>
</VoteButtonContainer>
</Box>
)
}

const ChainedResultsWrapper = () => {
// note election context refers to the root election here, ALWAYS
const { election, client } = useElection()
const { election, client, voted } = useElection()
const { processes, setProcess } = useChainedProcesses()
const [loaded, setLoaded] = useState<boolean>(false)
const [loading, setLoading] = useState<boolean>(false)
const [sorted, setSorted] = useState<string[]>([])

// Get elections information
useEffect(() => {
if (!election || election instanceof InvalidElection || loading || loaded) return

setLoading(true)
;(async () => {
try {
Expand All @@ -154,6 +233,16 @@
})()
}, [election])

// Reset loaded state when root election voted changes.
// On renderWith elections this will update all the render elections.
// However, this fix won't work on chained processes since we should check voted state for all processes to update
// results on real time.
useEffect(() => {
if (voted) {
setLoaded(false)
}
}, [voted])

if (!loaded) {
return <Progress w='full' size='xs' isIndeterminate />
}
Expand Down Expand Up @@ -204,12 +293,16 @@
ids.push(meta.default)
}

if (!meta.conditions) {
return ids
if (meta.renderWith) {
for (const renderWith of meta.renderWith) {
ids.push(renderWith.id)
}
}

for (const condition of meta.conditions) {
ids.push(condition.goto)
if (meta.conditions) {
for (const condition of meta.conditions) {
ids.push(condition.goto)
}
}

return ids
Expand All @@ -229,7 +322,7 @@
processes[id] = election

const meta = election.get('multiprocess')
if (meta && meta.default && !visited.has(meta.default)) {
if (meta && (meta.default || meta.renderWith) && !visited.has(meta.default)) {
const idsToFetch = getProcessIdsInFlowStep(meta)
for (const nextId of idsToFetch) {
await loadProcess(nextId)
Expand All @@ -245,6 +338,16 @@
}
}

// Add renderWith processes
if (meta.renderWith) {
for (const renderWithElection of meta.renderWith as RenderWith[]) {
if (!visited.has(renderWithElection.id)) {
visited.add(renderWithElection.id)
ids.push(renderWithElection.id)
}
}
}

// Add defaults after conditions
if (!visited.has(meta.default)) {
visited.add(meta.default)
Expand All @@ -259,6 +362,7 @@
if (meta) {
initialIds.push(...getProcessIdsInFlowStep(meta))
}

for (const id of initialIds) {
await loadProcess(id)
}
Expand All @@ -274,7 +378,15 @@
goto: string
}

type FlowNode = {
default: string
conditions?: FlowCondition[]
}
// FlowNode can have or conditions or renderWith, but not both
export type FlowNode =
| {
conditions?: FlowCondition[]
renderWith?: never
default: string
}
| {
conditions?: never
renderWith: RenderWith[]
default?: string // Default is optional for renderWith elections
}
Loading