Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
elboletaire committed Jun 13, 2024
1 parent a7c1033 commit c951b68
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 17 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/netlify.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
run: yarn build
env:
VOCDONI_ENVIRONMENT: stg
PROCESS_IDS: '["4ae20a8eb4caa52f5588f7bb9f3c6d6b7cf003a5b03f4589edea100000000290","4ae20a8eb4caa52f5588f7bb9f3c6d6b7cf003a5b03f4589edea10000000027d"]'
PROCESS_IDS: '["4ae20a8eb4ca8bd340fc16a71ae591b88418c42e799705b9807302000000000f"]'

- name: Deploy to Netlify
uses: nwtgck/[email protected]
Expand Down
17 changes: 9 additions & 8 deletions src/components/Process/Aside.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Box, Button, Card, Flex, Link, Text } from '@chakra-ui/react'
import { Box, Button, Card, Flex, FlexProps, Link, Text } from '@chakra-ui/react'
import { ConnectButton } from '@rainbow-me/rainbowkit'
import { VoteButton as CVoteButton, environment, SpreadsheetAccess, VoteWeight } from '@vocdoni/chakra-components'
import { useClient, useElection } from '@vocdoni/react-providers'
Expand Down Expand Up @@ -183,20 +183,23 @@ const ProcessAside = () => {
)
}

export const VoteButton = ({ setQuestionsTab, ...props }: { setQuestionsTab: () => void }) => {
export const VoteButton = ({ ...props }: FlexProps) => {
const { t } = useTranslation()
const { election, connected, isAbleToVote, isInCensus } = useElection()
const { isConnected } = useAccount()

if (!(election instanceof PublishedElection)) return null
if (!(election instanceof PublishedElection)) {
return null
}

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

if (
election?.status === ElectionStatus.CANCELED ||
(isConnected && !isInCensus && !['spreadsheet', 'csp'].includes(census?.type))
)
(isConnected && !isInCensus && !['spreadsheet', 'csp'].includes(census!.type))
) {
return null
}

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

Expand Down Expand Up @@ -243,14 +246,12 @@ export const VoteButton = ({ setQuestionsTab, ...props }: { setQuestionsTab: ()
}}
</ConnectButton.Custom>
)}
{census?.type === 'spreadsheet' && !connected && <SpreadsheetAccess />}
{isAbleToVote && (
<>
<CVoteButton
w='60%'
fontSize='lg'
height='50px'
onClick={setQuestionsTab}
mb={4}
sx={{
'&::disabled': {
Expand Down
131 changes: 131 additions & 0 deletions src/components/Process/Chained.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { Box, Progress } from '@chakra-ui/react'
import { ConnectButton } from '@rainbow-me/rainbowkit'
import { ElectionQuestions, SpreadsheetAccess } from '@vocdoni/chakra-components'
import { ElectionProvider, useElection } from '@vocdoni/react-providers'
import { ArchivedElection, InvalidElection, PublishedElection, VocdoniSDKClient } from '@vocdoni/sdk'
import { useEffect } from 'react'
import { Trans } from 'react-i18next'
import { VoteButton } from './Aside'
import { ChainedProvider, useChainedProcesses } from './ChainedContext'

import '@rainbow-me/rainbowkit/styles.css'
import { ConfirmVoteModal } from './ConfirmVoteModal'

type ChainedProcessesProps = {
root?: PublishedElection | ArchivedElection | InvalidElection
}

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

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

// fetch current process and process flow logic
useEffect(() => {
if (!current || processes[current] instanceof InvalidElection || !voted) return

const currentElection = processes[current]
const meta = currentElection.get('multiprocess')
if (!meta || (!meta.root && !meta.conditions && !meta.default)) return
;(async () => {
// fetch votes info
const next = await getNextProcessInFlow(client, voted, meta)

if (typeof processes[next] === 'undefined') {
const election = await client.fetchElection(next)
setProcess(next, election)
setCurrent(next)
}
})()
}, [processes, current, voted, client])

if (!current || !processes[current]) {
return <Progress w='full' size='xs' isIndeterminate />
}

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

return (
<Box className='md-sizes' mb='100px' pt='25px'>
<ElectionQuestions
confirmContents={(election, answers) => <ConfirmVoteModal election={election} answers={answers} />}
/>
<Box position='sticky' bottom={0} left={0} pb={1} pt={1} display={{ base: 'none', lg2: 'block' }}>
<VoteButton />
</Box>
</Box>
)
}

// Note this logic has been thought for single-choice single-question processes.
// The brainfuck required to make it work for other implementations requires a
// thoroughful assessment of the entire feature.
// Also, processes cannot be secret... otherwise we cannot get the vote packages
// and know what the users voted
const getNextProcessInFlow = async (client: VocdoniSDKClient, voted: string, meta: any) => {
if (!meta.conditions) {
return meta.default
}

const ivote = await client.voteService.info(voted)

if (!(ivote.package as any).votes) {
throw new Error('vote package is secret, cannot continue with the flow')
}

const [choice] = (ivote.package as any).votes

// loop over conditions finding the selected choice
for (const condition of meta.conditions) {
if (choice === condition.choice && condition.question === 0) {
return condition.goto
}
}

return meta.default
}

export const ChainedProcesses = ({ root }: ChainedProcessesProps) => {
const { client } = useElection()
if (!root) {
return <Progress w='full' size='xs' isIndeterminate />
}

return (
<ChainedProvider root={root as PublishedElection} client={client}>
<ChainedProcessesWrapper />
</ChainedProvider>
)
}

const ChainedProcessesWrapper = () => {
// note election context refers to the root election here, ALWAYS
const { connected, election } = useElection()
const { processes, current, root, setCurrent } = useChainedProcesses()

// set current to root if login out
useEffect(() => {
if (connected) return

setCurrent(root.id)
}, [connected])

if (!current || !processes[current] || !election || election instanceof InvalidElection) {
return <Progress w='full' size='xs' isIndeterminate />
}

return (
<>
<ElectionProvider key={current} election={processes[current]} ConnectButton={ConnectButton} fetchCensus>
<ChainedProcessesInner />
</ElectionProvider>
{!connected && election.get('census.type') === 'spreadsheet' && <SpreadsheetAccess />}
</>
)
}
48 changes: 48 additions & 0 deletions src/components/Process/ChainedContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { PublishedElection, VocdoniSDKClient } from '@vocdoni/sdk'
import { createContext, FC, PropsWithChildren, useContext, useState } from 'react'

type Processes = {
[key: string]: PublishedElection
}

type ChainedContextState = {
processes: Processes
current: string | null
client: VocdoniSDKClient
root: PublishedElection
setProcess: (id: string, process: PublishedElection) => void
setCurrent: (id: string | null) => void
}

type ChainedProviderProps = {
client: VocdoniSDKClient
root: PublishedElection
}

const ChainedContext = createContext<ChainedContextState | undefined>(undefined)

export const ChainedProvider: FC<PropsWithChildren<ChainedProviderProps>> = ({ children, root, client }) => {
const [processes, setProcesses] = useState<Processes>(root ? { [root.id]: root } : {})
const [current, setCurrent] = useState<string | null>(root ? root.id : null)

const setProcess = (id: string, process: PublishedElection) => {
setProcesses((prev) => ({
...prev,
[id]: process,
}))
}

return (
<ChainedContext.Provider value={{ processes, client, current, root, setProcess, setCurrent }}>
{children}
</ChainedContext.Provider>
)
}

export const useChainedProcesses = () => {
const context = useContext(ChainedContext)
if (!context) {
throw new Error('useProcesses must be used within a ProcessesProvider')
}
return context
}
13 changes: 6 additions & 7 deletions src/components/Process/View.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { AspectRatio, Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react'
import { ElectionQuestions, ElectionResults } from '@vocdoni/chakra-components'
import { ElectionResults } from '@vocdoni/chakra-components'
import { useElection } from '@vocdoni/react-providers'
import { ElectionStatus, PublishedElection } from '@vocdoni/sdk'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ReactPlayer from 'react-player'
import ProcessAside, { VoteButton } from './Aside'
import { ConfirmVoteModal } from './ConfirmVoteModal'
import { ChainedProcesses } from './Chained'
import Header from './Header'
import { SuccessVoteModal } from './SuccessVoteModal'

Expand All @@ -23,8 +23,6 @@ export const ProcessView = () => {
setTabIndex(index)
}

const setQuestionsTab = () => setTabIndex(0)

useEffect(() => {
const handleScroll = () => {
if (!videoRef.current) return
Expand Down Expand Up @@ -115,7 +113,7 @@ export const ProcessView = () => {
</TabList>
<TabPanels>
<TabPanel>
<Box ref={electionRef} className='md-sizes' mb='100px' pt='25px'>
{/* <Box ref={electionRef} className='md-sizes' mb='100px' pt='25px'>
<ElectionQuestions
onInvalid={(args) => {
setFormErrors(args)
Expand All @@ -125,7 +123,8 @@ export const ProcessView = () => {
</Box>
<Box position='sticky' bottom={0} left={0} pb={1} pt={1} display={{ base: 'none', lg2: 'block' }}>
<VoteButton setQuestionsTab={setQuestionsTab} />
</Box>
</Box> */}
<ChainedProcesses root={election} />
</TabPanel>
<TabPanel mb={20}>
<ElectionResults />
Expand Down Expand Up @@ -157,7 +156,7 @@ export const ProcessView = () => {
pt={1}
display={{ base: 'block', lg2: 'none' }}
>
<VoteButton setQuestionsTab={setQuestionsTab} />
<VoteButton />
</Box>

<SuccessVoteModal />
Expand Down
2 changes: 1 addition & 1 deletion src/elements/Vote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const Vote = () => {

return (
<OrganizationProvider id={election.organizationId}>
<ElectionProvider election={election} fetchCensus autoUpdate>
<ElectionProvider key='root-election' election={election} fetchCensus autoUpdate>
<ProcessView />
</ElectionProvider>
</OrganizationProvider>
Expand Down

0 comments on commit c951b68

Please sign in to comment.