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

WIP census crud and CSP auth #1001

Draft
wants to merge 12 commits into
base: develop
Choose a base branch
from
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@vocdoni/rainbowkit-wallets": "^0.2.2",
"@vocdoni/sdk": "~0.9.1",
"chakra-react-select": "^5.0.1",
"crisp-sdk-web": "^1.0.25",
"date-fns": "^4.1.0",
"framer-motion": "^10.12.18",
"i18next": "^23.16.0",
Expand Down
70 changes: 70 additions & 0 deletions src/components/Census/Create.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Box, Button, FormControl, FormErrorMessage, FormLabel, Heading, Select, Stack, VStack } from '@chakra-ui/react'
import { useOrganization } from '@vocdoni/react-providers'
import { useForm } from 'react-hook-form'
import { Trans, useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import { Routes } from '~routes'
import { CensusCreatePayload, useCreateCensus } from '~src/queries/census'

const CreateCensus = () => {
const navigate = useNavigate()
const { t } = useTranslation()
const { organization } = useOrganization()
const createCensus = useCreateCensus()
const { register, handleSubmit } = useForm<CensusCreatePayload>({
defaultValues: {
type: 'sms_or_mail',
},
})

if (!organization) return null

const onSubmit = async (values: CensusCreatePayload) => {
const { censusID } = await createCensus.mutateAsync({
...values,
orgAddress: organization.address,
})

// Navigate to participants page
navigate(Routes.dashboard.census.participants.replace(':id', censusID))
}

return (
<Box>
<Heading size='lg' mb={6}>
<Trans i18nKey='census.create.title'>Create Census</Trans>
</Heading>

<form onSubmit={handleSubmit(onSubmit)}>
<VStack spacing={4}>
<FormControl isInvalid={createCensus.isError}>
<FormLabel>{t('census.create.type', { defaultValue: 'Type' })}</FormLabel>
<Select {...register('type')}>
<option value='sms_or_mail'>
<Trans i18nKey='census.type.sms_or_mail'>SMS or Email</Trans>
</option>
<option value='sms'>
<Trans i18nKey='census.type.sms'>SMS</Trans>
</option>
<option value='mail'>
<Trans i18nKey='census.type.mail'>Email</Trans>
</option>
</Select>
{createCensus.isError && <FormErrorMessage>{createCensus.error.message}</FormErrorMessage>}
</FormControl>

<Stack direction='row' spacing={4} justify='flex-end'>
<Button variant='outline' onClick={() => navigate(Routes.dashboard.census.list)}>
<Trans i18nKey='common.cancel'>Cancel</Trans>
</Button>
<Button type='submit' colorScheme='primary' isLoading={createCensus.isPending}>
<Trans i18nKey='common.cancel'>Create</Trans>
</Button>
</Stack>
</VStack>
</form>
</Box>
)
}

export default CreateCensus
101 changes: 101 additions & 0 deletions src/components/Census/List.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { ViewIcon } from '@chakra-ui/icons'
import {
Alert,
AlertDescription,
Box,
Button,
Flex,
Heading,
IconButton,
Table,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
} from '@chakra-ui/react'
import { useOrganization } from '@vocdoni/react-providers'
import { Trans } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import { Routes } from '~routes'
import { Census, useCensusList } from '~src/queries/census'

const CensusList = () => {
const navigate = useNavigate()
const { organization } = useOrganization()

if (!organization) return null

const { data: censuses, error, isError } = useCensusList(organization.address)

const handleViewCensus = (census: Census) => {
navigate(Routes.dashboard.census.view.replace(':id', census.id))
}

return (
<Box>
<Flex justifyContent='space-between' alignItems='center' mb={6}>
<Heading size='lg'>
<Trans>Census</Trans>
</Heading>
<Button colorScheme='primary' onClick={() => navigate(Routes.dashboard.census.create)}>
<Trans>Create Census</Trans>
</Button>
</Flex>

<Table variant='simple'>
<Thead>
<Tr>
<Th>
<Trans>ID</Trans>
</Th>
<Th>
<Trans>Type</Trans>
</Th>
<Th>
<Trans>Created At</Trans>
</Th>
<Th>
<Trans>Actions</Trans>
</Th>
</Tr>
</Thead>
<Tbody>
{!censuses?.length ? (
<Tr>
<Td colSpan={4} textAlign='center'>
<Text color='gray.500'>
<Trans>No census found</Trans>
</Text>
</Td>
</Tr>
) : (
censuses.map((census) => (
<Tr key={census.id}>
<Td>{census.id}</Td>
<Td>{census.type}</Td>
<Td>{new Date(census.createdAt).toLocaleString()}</Td>
<Td>
<IconButton
aria-label='View census'
icon={<ViewIcon />}
size='sm'
onClick={() => handleViewCensus(census)}
/>
</Td>
</Tr>
))
)}
</Tbody>
</Table>
{isError && (
<Alert status='error'>
<AlertDescription>{error.message}</AlertDescription>
</Alert>
)}
</Box>
)
}

export default CensusList
153 changes: 153 additions & 0 deletions src/components/Census/Participants.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import {
Box,
Button,
FormControl,
FormLabel,
Heading,
Input,
Stack,
Table,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
VStack,
} from '@chakra-ui/react'
import { useOrganization } from '@vocdoni/react-providers'
import { useState } from 'react'
import { useForm } from 'react-hook-form'
import { Trans } from 'react-i18next'
import { useNavigate, useParams } from 'react-router-dom'
import { Routes } from '~routes'
import { ParticipantPayload, useAddParticipants, useCensusInfo, usePublishCensus } from '~src/queries/census'

const CensusParticipants = () => {
const navigate = useNavigate()
const { organization } = useOrganization()
const { id } = useParams<{ id: string }>()
const { data: census } = useCensusInfo(id!)
const addParticipants = useAddParticipants(id!)
const publishCensus = usePublishCensus(id!)

const {
register,
handleSubmit,
reset,
formState: { isSubmitting },
} = useForm<ParticipantPayload>()

const [participants, setParticipants] = useState<ParticipantPayload[]>([])

if (!organization || !census) return null

const handleAddParticipant = (data: ParticipantPayload) => {
if (!data.email && !data.phone) return

setParticipants((prev) => [...prev, data])
reset()
}

const handleSaveAndPublish = async () => {
if (!participants.length) return

await addParticipants.mutateAsync({ participants })
await publishCensus.mutateAsync()

navigate(Routes.dashboard.census.list)
}

return (
<Box>
<Heading size='lg' mb={6}>
<Trans>Add Participants</Trans>
</Heading>

<form onSubmit={handleSubmit(handleAddParticipant)}>
<VStack spacing={4} align='stretch' maxW='md' mb={8}>
<FormControl>
<FormLabel>
<Trans>ParticipantNo</Trans>
</FormLabel>
<Input type='participantNo' placeholder='Participant number' {...register('participantNo')} />
</FormControl>
<FormControl>
<FormLabel>
<Trans>Email</Trans>
</FormLabel>
<Input type='email' placeholder='Enter email' {...register('email')} />
</FormControl>

<FormControl>
<FormLabel>
<Trans>Phone</Trans>
</FormLabel>
<Input type='tel' placeholder='Enter phone number' {...register('phone')} />
</FormControl>

<Button type='submit' colorScheme='primary' isLoading={isSubmitting}>
<Trans>Add Participant</Trans>
</Button>
</VStack>
</form>

<Box mb={8}>
<Heading size='md' mb={4}>
<Trans>Participants List</Trans>
</Heading>

<Table variant='simple'>
<Thead>
<Tr>
<Th>
<Trans>ID</Trans>
</Th>
<Th>
<Trans>Email</Trans>
</Th>
<Th>
<Trans>Phone</Trans>
</Th>
</Tr>
</Thead>
<Tbody>
{participants.length === 0 ? (
<Tr>
<Td colSpan={3} textAlign='center'>
<Text color='gray.500'>
<Trans>No participants added yet</Trans>
</Text>
</Td>
</Tr>
) : (
participants.map((participant) => (
<Tr key={participant.participantNo}>
<Td>{participant.participantNo}</Td>
<Td>{participant.email || '-'}</Td>
<Td>{participant.phone || '-'}</Td>
</Tr>
))
)}
</Tbody>
</Table>
</Box>

<Stack direction='row' spacing={4} justify='flex-end'>
<Button variant='outline' onClick={() => navigate(Routes.dashboard.census.list)}>
<Trans>Cancel</Trans>
</Button>
<Button
colorScheme='primary'
onClick={handleSaveAndPublish}
isLoading={addParticipants.isPending || publishCensus.isPending}
isDisabled={participants.length === 0}
>
<Trans>Save & Publish</Trans>
</Button>
</Stack>
</Box>
)
}

export default CensusParticipants
Loading
Loading