Skip to content

Commit

Permalink
Mark user as Coordinator, Organizer and/or Beneficiary (#1595)
Browse files Browse the repository at this point in the history
* admin/users edit roles

* updated validationSchema

* added beneficiary form

* disallow beneficiary toggle

* remove debug logs

* show disabled checkbox tooltip
  • Loading branch information
tongo-angelov authored Sep 20, 2023
1 parent f0d0476 commit 69da4fc
Show file tree
Hide file tree
Showing 13 changed files with 592 additions and 143 deletions.
7 changes: 6 additions & 1 deletion public/locales/bg/person.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@
"address": "Адрес",
"organizer": "Организатор",
"coordinator": "Координатор",
"beneficiary": "Бенефициент"
"beneficiary": "Бенефициент",
"organizerRelation": "Отношения с организатор",
"countryCode": "Държава",
"city": "Град",
"description": "Описание",
"disabled-tooltip": "Ролята не може да бъде премахната, потребителя е {{role}} в {{campaigns}} кампании"
},
"cta": {
"create": "Създай",
Expand Down
7 changes: 6 additions & 1 deletion public/locales/en/person.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@
"address": "Address",
"organizer": "Organizer",
"coordinator": "Coordinator",
"beneficiary": "Beneficiary"
"beneficiary": "Beneficiary",
"organizerRelation": "Organizer relation",
"countryCode": "Country code",
"city": "City",
"description": "Description",
"disabled-tooltip": "Cannot remove role because the user is {{role}} in {{campaigns}} campaigns"
},
"cta": {
"create": "Create",
Expand Down
2 changes: 1 addition & 1 deletion src/components/admin/organizers/grid/DeleteModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default observer(function DeleteModal() {
const { hideDelete, selectedRecord } = ModalStore

const mutation = useMutation<AxiosResponse<OrganizerResponse>, AxiosError<ApiErrors>, string>({
mutationFn: deleteOrganizer(selectedRecord.id),
mutationFn: deleteOrganizer(),
onError: () => AlertStore.show(t('admin.alerts.delete-error'), 'error'),
onSuccess: () => {
hideDelete()
Expand Down
36 changes: 30 additions & 6 deletions src/components/common/form/CheckboxField.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,48 @@
import { ChangeEvent } from 'react'

import { useField } from 'formik'
import { useTranslation } from 'next-i18next'
import { Checkbox, FormControl, FormControlLabel, FormHelperText } from '@mui/material'
import { Checkbox, FormControl, FormControlLabel, FormHelperText, Tooltip } from '@mui/material'

import { TranslatableField, translateError } from 'common/form/validation'

export type CheckboxFieldProps = {
name: string
disabled?: boolean
onChange?: (e: ChangeEvent<HTMLInputElement>) => void
label: string | number | React.ReactElement
disabledTooltip?: string
}

export default function CheckboxField({ name, label }: CheckboxFieldProps) {
export default function CheckboxField({
name,
disabled,
onChange: handleChange,
label,
disabledTooltip,
}: CheckboxFieldProps) {
const { t } = useTranslation()
const [field, meta] = useField(name)
const helperText = meta.touched ? translateError(meta.error as TranslatableField, t) : ''
return (
<FormControl required component="fieldset" error={Boolean(meta.error) && Boolean(meta.touched)}>
<FormControlLabel
label={typeof label === 'string' ? `${t(label)}` : label}
control={<Checkbox color="primary" checked={Boolean(field.value)} {...field} />}
/>
<Tooltip title={disabled && disabledTooltip} arrow>
<FormControlLabel
label={typeof label === 'string' ? `${t(label)}` : label}
control={
<Checkbox
color="primary"
checked={Boolean(field.value)}
disabled={disabled}
{...field}
onChange={(e) => {
field.onChange(e)
if (handleChange) handleChange(e)
}}
/>
}
/>
</Tooltip>
{Boolean(meta.error) && <FormHelperText error>{helperText}</FormHelperText>}
</FormControl>
)
Expand Down
4 changes: 4 additions & 0 deletions src/components/common/person/PersonForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ const validationSchema: yup.SchemaOf<PersonFormData> = yup.object().defined().sh
companyNumber: yup.string(),
legalPersonName: name,
address: yup.string(),
// Roles
isBeneficiary: yup.bool().notRequired(),
isCoordinator: yup.bool().notRequired(),
isOrganizer: yup.bool().notRequired(),
})

const defaults: PersonFormData = {
Expand Down
220 changes: 220 additions & 0 deletions src/components/common/person/grid/CreateForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import React, { useState } from 'react'
import * as yup from 'yup'
import { Grid } from '@mui/material'

import GenericForm from 'components/common/form/GenericForm'
import { name, phone, email } from 'common/form/validation'
import SubmitButton from 'components/common/form/SubmitButton'
import FormTextField from 'components/common/form/FormTextField'
import EmailField from 'components/common/form/EmailField'
import { AdminPersonFormData, AdminPersonResponse } from 'gql/person'
import { useMutation } from '@tanstack/react-query'
import { AxiosError, AxiosResponse } from 'axios'
import { ApiErrors } from 'service/apiErrors'
import { useCreatePerson } from 'service/person'
import { AlertStore } from 'stores/AlertStore'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
import { routes } from 'common/routes'
import CheckboxField from 'components/common/form/CheckboxField'
import { useCreateCoordinator } from 'service/coordinator'
import { CoordinatorResponse, CoorinatorInput } from 'gql/coordinators'
import { createOrganizer } from 'service/organizer'
import { OrganizerInput } from 'gql/organizer'
import { BeneficiaryFormData, BeneficiaryListResponse } from 'gql/beneficiary'
import { BeneficiaryType } from 'components/admin/beneficiary/BeneficiaryTypes'
import { useCreateBeneficiary } from 'service/beneficiary'
import OrganizerRelationSelect from 'components/admin/beneficiary/OrganizerRelationSelect'
import CountrySelect from 'components/admin/countries/CountrySelect'
import CitySelect from 'components/admin/cities/CitySelect'

const validationSchema = yup
.object()
.defined()
.shape({
firstName: name.required(),
lastName: name.required(),
email: email.required(),
phone: phone.notRequired(),
isCoordinator: yup.bool().required(),
isOrganizer: yup.bool().required(),
isBeneficiary: yup.bool().required(),
description: yup.string().notRequired(),
cityId: yup.string().when('isBeneficiary', {
is: true,
then: yup.string().required(),
otherwise: yup.string().notRequired(),
}),
countryCode: yup.string().when('isBeneficiary', {
is: true,
then: yup.string().required(),
otherwise: yup.string().notRequired(),
}),
organizerRelation: yup.string().when('isBeneficiary', {
is: true,
then: yup.string().required(),
otherwise: yup.string().notRequired(),
}),
})

const initialValues: AdminPersonFormData = {
firstName: '',
lastName: '',
email: '',
phone: '',
isOrganizer: false,
isCoordinator: false,
isBeneficiary: false,
countryCode: 'BG',
cityId: '',
description: '',
organizerRelation: 'none',
}

export default function CreateForm() {
const router = useRouter()
const { t } = useTranslation()
const [showBenefactor, setShowBenefactor] = useState<boolean>(false)

const mutation = useMutation<
AxiosResponse<AdminPersonResponse>,
AxiosError<ApiErrors>,
AdminPersonFormData
>({
mutationFn: useCreatePerson(),
onError: () => AlertStore.show(t('common:alerts.error'), 'error'),
})

const coordinatorCreateMutation = useMutation<
AxiosResponse<CoordinatorResponse>,
AxiosError<ApiErrors>,
CoorinatorInput
>({
mutationFn: useCreateCoordinator(),
onError: () => AlertStore.show(t('common:alerts.error'), 'error'),
})

const organizerCreateMutation = useMutation<
AxiosResponse<CoordinatorResponse>,
AxiosError<ApiErrors>,
OrganizerInput
>({
mutationFn: createOrganizer(),
onError: () => AlertStore.show(t('common:alerts.error'), 'error'),
})

const beneficiaryCreateMutation = useMutation<
AxiosResponse<BeneficiaryListResponse>,
AxiosError<ApiErrors>,
BeneficiaryFormData
>({
mutationFn: useCreateBeneficiary(),
onError: () => AlertStore.show(t('common:alerts.error'), 'error'),
})

async function handleSubmit(values: AdminPersonFormData) {
const { data: userResponse } = await mutation.mutateAsync(values)

if (values.isCoordinator) coordinatorCreateMutation.mutate({ personId: userResponse.id })

if (values.isOrganizer) organizerCreateMutation.mutate({ personId: userResponse.id })

if (values.isBeneficiary)
beneficiaryCreateMutation.mutate({
type: BeneficiaryType.individual,
personId: userResponse.id,
countryCode: values.countryCode,
cityId: values.cityId,
organizerRelation: values.organizerRelation,
description: values.description,
campaigns: [],
})

AlertStore.show(t('common:alerts.success'), 'success')
router.push(routes.admin.person.index)
}

return (
<Grid container direction="column" component="section">
<GenericForm
onSubmit={handleSubmit}
initialValues={initialValues}
validationSchema={validationSchema}>
<Grid container spacing={3}>
<Grid item xs={12} sm={6}>
<FormTextField
autoFocus
type="text"
label="person:admin.fields.first-name"
name="firstName"
autoComplete="first-name"
/>
</Grid>
<Grid item xs={12} sm={6}>
<FormTextField
type="text"
label="person:admin.fields.last-name"
name="lastName"
autoComplete="family-name"
/>
</Grid>
<Grid item xs={12}>
<EmailField label="person:admin.fields.email" name="email" />
</Grid>
<Grid item xs={12}>
<FormTextField
type="tel"
name="phone"
inputMode="tel"
autoComplete="tel"
label="person:admin.fields.phone"
/>
</Grid>
<Grid item xs={4}>
<CheckboxField name="isOrganizer" label="person:admin.fields.organizer" />
</Grid>
<Grid item xs={4}>
<CheckboxField name="isCoordinator" label="person:admin.fields.coordinator" />
</Grid>
<Grid item xs={4}>
<CheckboxField
name="isBeneficiary"
label="person:admin.fields.beneficiary"
onChange={(e) => {
setShowBenefactor(e.target.checked)
}}
/>
</Grid>
{showBenefactor && (
<>
<Grid item xs={12}>
<OrganizerRelationSelect
name="organizerRelation"
label="person:admin.fields.organizerRelation"
/>
</Grid>
<Grid item xs={6}>
<CountrySelect />
</Grid>
<Grid item xs={6}>
<CitySelect name="cityId" />
</Grid>
<Grid item xs={12}>
<FormTextField
type="text"
name="description"
label="person:admin.fields.description"
multiline
rows={2}
/>
</Grid>
</>
)}
<Grid item xs={4} margin="auto">
<SubmitButton fullWidth label="person:admin.cta.create" />
</Grid>
</Grid>
</GenericForm>
</Grid>
)
}
4 changes: 2 additions & 2 deletions src/components/common/person/grid/CreatePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Container } from '@mui/material'

import AdminLayout from 'components/common/navigation/AdminLayout'
import AdminContainer from 'components/common/navigation/AdminContainer'
import PersonForm from './PersonForm'
import CreateForm from './CreateForm'

export default function CreatePage() {
const { t } = useTranslation()
Expand All @@ -13,7 +13,7 @@ export default function CreatePage() {
<AdminLayout>
<AdminContainer title={t('person:admin.headings.create')}>
<Container maxWidth="md" sx={{ py: 5 }}>
<PersonForm />
<CreateForm />
</Container>
</AdminContainer>
</AdminLayout>
Expand Down
Loading

0 comments on commit 69da4fc

Please sign in to comment.