diff --git a/.env.local.example b/.env.local.example index 7c108ee5..59d5f08c 100644 --- a/.env.local.example +++ b/.env.local.example @@ -35,4 +35,8 @@ GOOGLE_ACADEMIC_SHEET_NAME= # google sheet for devcon grants GOOGLE_DEVCON_SPREADSHEET_ID= -GOOGLE_DEVCON_SHEET_NAME= \ No newline at end of file +GOOGLE_DEVCON_SHEET_NAME= + +# captcha +NEXT_PUBLIC_HCAPTCHA_SITEKEY= +HCAPTCHA_SECRET= \ No newline at end of file diff --git a/additional.d.ts b/additional.d.ts new file mode 100644 index 00000000..c64d767a --- /dev/null +++ b/additional.d.ts @@ -0,0 +1,9 @@ +import { Fields, Files } from 'formidable'; +import { IncomingMessage } from 'http'; + +declare module 'next' { + export interface NextApiRequest extends IncomingMessage { + fields?: Fields; + files?: Files; + } +} diff --git a/package.json b/package.json index a6d0408b..0143701a 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@emotion/react": "^11", "@emotion/styled": "^11", "@fontsource/libre-franklin": "^4.5.0", + "@hcaptcha/react-hcaptcha": "^1.1.1", "@mailchimp/mailchimp_marketing": "^3.0.74", "@socialgouv/matomo-next": "^1.2.2", "chakra-react-select": "^3.0.1", diff --git a/src/components/forms/AcademicGrantsForm.tsx b/src/components/forms/AcademicGrantsForm.tsx index f4506c15..56aa81bc 100644 --- a/src/components/forms/AcademicGrantsForm.tsx +++ b/src/components/forms/AcademicGrantsForm.tsx @@ -14,11 +14,12 @@ import { } from '@chakra-ui/react'; import { Select } from 'chakra-react-select'; import { FC, useState } from 'react'; -import { Controller, useForm } from 'react-hook-form'; +import { Controller, FormProvider, useForm } from 'react-hook-form'; import { useRouter } from 'next/router'; import { DropdownIndicator, PageText } from '../UI'; import { SubmitButton } from '../SubmitButton'; +import { Captcha } from '.'; import { api } from './api'; @@ -50,6 +51,9 @@ export const AcademicGrantsForm: FC = () => { value: '', label: '' }); + const methods = useForm({ + mode: 'onBlur' + }); const { handleSubmit, register, @@ -57,9 +61,7 @@ export const AcademicGrantsForm: FC = () => { control, formState: { errors, isValid, isSubmitting }, reset - } = useForm({ - mode: 'onBlur' - }); + } = methods; const onSubmit = async (data: AcademicGrantsFormData) => { return api.academicGrants @@ -104,1363 +106,1375 @@ export const AcademicGrantsForm: FC = () => { pb={{ base: 20, md: 16 }} borderRadius={{ md: '10px' }} > -
- - - - - - First name - - - - - - {errors?.firstName?.type === 'required' && ( - - - First name is required. - - - )} - {errors?.firstName?.type === 'maxLength' && ( - - - First name cannot exceed 40 characters. + + + + + + + + First name - - )} - + + + - - - - Last name - - - - - {errors?.lastName?.type === 'required' && ( - - - Last name is required. - - - )} - {errors?.lastName?.type === 'maxLength' && ( - - - Last name cannot exceed 80 characters. + {errors?.firstName?.type === 'required' && ( + + + First name is required. + + + )} + {errors?.firstName?.type === 'maxLength' && ( + + + First name cannot exceed 40 characters. + + + )} + + + + + + Last name - - )} - - + + - {!errors?.firstName && !errors?.lastName && ( - - The point of contact for the application. - - )} - + {errors?.lastName?.type === 'required' && ( + + + Last name is required. + + + )} + {errors?.lastName?.type === 'maxLength' && ( + + + Last name cannot exceed 80 characters. + + + )} + + - - - - Email - - - + {!errors?.firstName && !errors?.lastName && ( + + The point of contact for the application. + + )} + - {errors?.email?.type === 'required' && ( - - - Email is required. + + + + Email - - )} - - - ( - - - - Is the point of contact also the authorised signatory? + + + + {errors?.email?.type === 'required' && ( + + + Email is required. - - - { - onChange(value); - handlePOCisAuthorisedSignatory(value); - }} - value={value} - fontSize='input' - colorScheme='white' - mt={4} + + )} + + + ( + + + + Is the point of contact also the authorised signatory? + + + + { + onChange(value); + handlePOCisAuthorisedSignatory(value); + }} + value={value} + fontSize='input' + colorScheme='white' + mt={4} + > + + + Yes + + + + No + + + + + )} + /> + + + + - - - Yes - - - - No - - - - - )} - /> - - - - - - - Name, job title, and email address of the authorised signatory + + + Name, job title, and email address of the authorised signatory + + + + + e.g.: John Smith, CEO, john@mycompany.com. This is the person who will sign the + contract. They must be someone who can sign contracts on behalf of the entity. - - - e.g.: John Smith, CEO, john@mycompany.com. This is the person who will sign the - contract. They must be someone who can sign contracts on behalf of the entity. - + - - - {errors?.authorisedSignatoryInformation?.type === 'required' && ( - - - Authorised Signatory Information is required. + {errors?.authorisedSignatoryInformation?.type === 'required' && ( + + + Authorised Signatory Information is required. + + + )} + {errors?.authorisedSignatoryInformation?.type === 'maxLength' && ( + + + Authorised Signatory Information cannot exceed 255 characters. + + + )} + + + + + selected.value !== '' }} + defaultValue={{ value: '', label: '' }} + render={({ field: { onChange }, fieldState: { error } }) => ( + + + + In which capacity are you applying? + + + + + + {errors?.applyingAsOther?.type === 'maxLength' && ( + + + Applying as info cannot exceed 255 characters. + + + )} + + + + + + + + If applying as an Institution, please specify its name + + + + + Name of your university program, team, or organization. If you do not have an + organization name, write "N/A". + + + + + {errors?.company?.type === 'required' && ( + + + Organization name is required. + + + )} + {errors?.company?.type === 'maxLength' && ( + + + Organization name cannot exceed 255 characters. + + + )} + + + selected.value !== '' }} + defaultValue={{ value: '', label: '' }} + render={({ field: { onChange }, fieldState: { error } }) => ( + + + + Country - - )} - - - - - selected.value !== '' }} - defaultValue={{ value: '', label: '' }} - render={({ field: { onChange }, fieldState: { error } }) => ( - - - - In which capacity are you applying? + + + + Please indicate the country where the Institution/Lead Investigator is located. - - - + {error && ( - Please select in which capacity you are applying. + Country is required. )} + + )} + /> + + + + + If you are a team of distributed researchers, please indicate where your fellow + researchers are located. + + + + + You can write as many countries as needed. + + + + + {errors?.countriesOfTeam?.type === 'maxLength' && ( + + + Countries list cannot exceed 255 characters. + - - )} - /> - - - - - - If other, please specify - - - - {errors?.applyingAsOther?.type === 'maxLength' && ( - - - Applying as info cannot exceed 255 characters. + )} + + + selected.value !== '' }} + defaultValue={{ value: '', label: '' }} + render={({ field: { onChange }, fieldState: { error } }) => ( + + + + Your time zone + + + + Please choose your current time zone to help us schedule calls. + + + + + + Time zone is required. + + + )} + + )} /> - {errors?.company?.type === 'required' && ( - - - Organization name is required. - - - )} - {errors?.company?.type === 'maxLength' && ( - - - Organization name cannot exceed 255 characters. + + + + Project name - - )} - - - selected.value !== '' }} - defaultValue={{ value: '', label: '' }} - render={({ field: { onChange }, fieldState: { error } }) => ( - - - - Country + + + + This should be a concise description of the title of your project. + + + + + {errors?.projectName?.type === 'required' && ( + + + Project name is required. - + + )} + {errors?.projectName?.type === 'maxLength' && ( + + + Project name cannot exceed 255 characters. + + + )} + - - Please indicate the country where the Institution/Lead Investigator is located. + + + + Brief project summary + - -