From 2a2391590068e1b8d962ffa990f9f4ce6a91299d Mon Sep 17 00:00:00 2001 From: Pablo Pettinari Date: Fri, 11 Mar 2022 13:05:54 -0300 Subject: [PATCH 1/3] add hCaptcha button to forms --- .env.local.example | 5 +++- package.json | 1 + src/components/ButtonCaptcha.tsx | 17 ++++++++++++ src/components/forms/ProjectGrantsForm.tsx | 32 ++++++++++++++++++---- src/components/index.ts | 1 + src/types.ts | 4 +++ yarn.lock | 5 ++++ 7 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 src/components/ButtonCaptcha.tsx diff --git a/.env.local.example b/.env.local.example index 7c108ee5..0a3170e1 100644 --- a/.env.local.example +++ b/.env.local.example @@ -35,4 +35,7 @@ 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 sitekey +NEXT_PUBLIC_CAPTCHA_SITEKEY= \ No newline at end of file diff --git a/package.json b/package.json index 75e229d1..4d4a4a30 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,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/ButtonCaptcha.tsx b/src/components/ButtonCaptcha.tsx new file mode 100644 index 00000000..6fd50b1a --- /dev/null +++ b/src/components/ButtonCaptcha.tsx @@ -0,0 +1,17 @@ +import React, { FC } from 'react'; +import HCaptcha from '@hcaptcha/react-hcaptcha'; + +interface Props { + onVerify: (token: string, ekey: string) => void; + onExpire: () => void; +} + +export const ButtonCaptcha: FC = ({ onVerify, onExpire }) => { + return ( + + ); +}; diff --git a/src/components/forms/ProjectGrantsForm.tsx b/src/components/forms/ProjectGrantsForm.tsx index 804ccbb8..cd792ce2 100644 --- a/src/components/forms/ProjectGrantsForm.tsx +++ b/src/components/forms/ProjectGrantsForm.tsx @@ -17,7 +17,7 @@ import { useToast } from '@chakra-ui/react'; import { Select } from 'chakra-react-select'; -import { FC, MouseEvent, useState, useCallback } from 'react'; +import { FC, MouseEvent, useState, useCallback, useEffect } from 'react'; import { useDropzone } from 'react-dropzone'; import { Controller, useForm } from 'react-hook-form'; import { motion } from 'framer-motion'; @@ -47,8 +47,11 @@ import { TOAST_OPTIONS } from '../../constants'; -import { ProjectGrantsFormData, ReferralSource } from '../../types'; +import { Form, ProjectGrantsFormData, ReferralSource } from '../../types'; import { RemoveIcon } from '../UI/icons'; +import { ButtonCaptcha } from '..'; + +interface ProjectGrantsForm extends ProjectGrantsFormData, Form {} const MotionBox = motion(Box); const MotionButton = motion(Button); @@ -69,12 +72,27 @@ export const ProjectGrantsForm: FC = () => { control, setValue, formState: { errors, isValid }, - reset - } = useForm({ + reset, + getValues + } = useForm({ mode: 'onBlur' }); const { shadowBoxControl, setButtonHovered } = useShadowAnimation(); + useEffect(() => { + register('captchaToken', { required: true }); + }); + + const onVerifyCaptcha = (token: string) => { + setValue('captchaToken', token, { shouldValidate: true }); + }; + + const onExpireCaptcha = () => { + // when token expires, reset the captcha field + const { captchaToken, ...values } = getValues(); + reset({ ...values }); + }; + const onDrop = useCallback( files => { const file = files[0]; @@ -93,7 +111,7 @@ export const ProjectGrantsForm: FC = () => { ); const { getRootProps, getInputProps } = useDropzone({ onDrop }); - const onSubmit = (data: ProjectGrantsFormData) => { + const onSubmit = ({ captchaToken, ...data }: ProjectGrantsForm) => { api.projectGrants .submit(data) .then(res => { @@ -927,6 +945,10 @@ export const ProjectGrantsForm: FC = () => { )} /> +
+ +
+
Date: Sat, 12 Mar 2022 12:58:03 -0300 Subject: [PATCH 2/3] refactor and add captcha to all forms --- src/components/ButtonCaptcha.tsx | 17 - src/components/forms/AcademicGrantsForm.tsx | 2564 +++++++-------- src/components/forms/DevconGrantsForm.tsx | 1697 +++++----- src/components/forms/GranteeFinanceForm.tsx | 1302 ++++---- src/components/forms/OfficeHoursForm.tsx | 935 +++--- src/components/forms/ProjectGrantsForm.tsx | 1575 +++++----- src/components/forms/SmallGrantsForm.tsx | 3094 ++++++++++--------- src/components/forms/fields/Captcha.tsx | 29 + src/components/forms/fields/index.tsx | 1 + src/components/forms/index.ts | 1 + src/components/index.ts | 1 - src/types.ts | 2 +- 12 files changed, 5651 insertions(+), 5567 deletions(-) delete mode 100644 src/components/ButtonCaptcha.tsx create mode 100644 src/components/forms/fields/Captcha.tsx create mode 100644 src/components/forms/fields/index.tsx diff --git a/src/components/ButtonCaptcha.tsx b/src/components/ButtonCaptcha.tsx deleted file mode 100644 index 6fd50b1a..00000000 --- a/src/components/ButtonCaptcha.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { FC } from 'react'; -import HCaptcha from '@hcaptcha/react-hcaptcha'; - -interface Props { - onVerify: (token: string, ekey: string) => void; - onExpire: () => void; -} - -export const ButtonCaptcha: FC = ({ onVerify, onExpire }) => { - return ( - - ); -}; diff --git a/src/components/forms/AcademicGrantsForm.tsx b/src/components/forms/AcademicGrantsForm.tsx index 13ec7ab9..8f3ccd90 100644 --- a/src/components/forms/AcademicGrantsForm.tsx +++ b/src/components/forms/AcademicGrantsForm.tsx @@ -17,7 +17,7 @@ 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 { motion } from 'framer-motion'; import Image from 'next/image'; import { useRouter } from 'next/router'; @@ -42,7 +42,10 @@ import { } from './constants'; import { ACADEMIC_GRANTS_THANK_YOU_PAGE_URL, TOAST_OPTIONS } from '../../constants'; -import { AcademicGrantsFormData, ApplyingAs, GrantsReferralSource } from '../../types'; +import { AcademicGrantsFormData, ApplyingAs, BasicForm, GrantsReferralSource } from '../../types'; +import { Captcha } from '.'; + +interface AcademicGrantsFormForm extends AcademicGrantsFormData, BasicForm {} const MotionBox = motion(Box); const MotionButton = motion(Button); @@ -60,6 +63,9 @@ export const AcademicGrantsForm: FC = () => { value: '', label: '' }); + const methods = useForm({ + mode: 'onBlur' + }); const { handleSubmit, register, @@ -67,12 +73,10 @@ export const AcademicGrantsForm: FC = () => { control, formState: { errors, isValid }, reset - } = useForm({ - mode: 'onBlur' - }); + } = methods; const { shadowBoxControl, setButtonHovered } = useShadowAnimation(); - const onSubmit = (data: AcademicGrantsFormData) => { + const onSubmit = ({ captchaToken, ...data }: AcademicGrantsFormForm) => { api.academicGrants .submit(data) .then(res => { @@ -115,1388 +119,1400 @@ 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?.firstName?.type === 'required' && ( + + + First name is required. + + + )} + {errors?.firstName?.type === 'maxLength' && ( + + + First name cannot exceed 40 characters. + + + )} + - {errors?.lastName?.type === 'required' && ( - - - Last name is required. - - - )} - {errors?.lastName?.type === 'maxLength' && ( - - - Last name cannot exceed 80 characters. + + + + Last name - - )} - - - - {!errors?.firstName && !errors?.lastName && ( - - The point of contact for the application. - - )} - - - - - - Email - - - + + - {errors?.email?.type === 'required' && ( - - - Email is required. - - - )} - - - ( - - - - Is the point of contact also the authorised signatory? - - - - { - onChange(value); - handlePOCisAuthorisedSignatory(value); - }} - value={value} - fontSize='input' - colorScheme='white' - mt={4} - > - - - Yes - - - - No - - - - - )} - /> - - - - - - - Name, job title, and email address of the authorised signatory - - + {errors?.lastName?.type === 'required' && ( + + + Last name is required. + + + )} + {errors?.lastName?.type === 'maxLength' && ( + + + Last name cannot exceed 80 characters. + + + )} + + + {!errors?.firstName && !errors?.lastName && ( - 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. + The point of contact for the application. + )} + - + + + + Email + + + - {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?.email?.type === 'required' && ( + + + Email is required. - + + )} + + + ( + + + + Is the point of contact also the authorised signatory? + + - - - {error && ( + {errors?.authorisedSignatoryInformation?.type === 'required' && ( - Please select in which capacity you are applying. + Authorised Signatory Information is required. )} - - - )} - /> - - - - - - If other, please specify - - + {errors?.authorisedSignatoryInformation?.type === 'maxLength' && ( + + + Authorised Signatory Information cannot exceed 255 characters. + + + )} + + + - {errors?.applyingAsOther?.type === 'maxLength' && ( - - - Applying as info cannot exceed 255 characters. + selected.value !== '' }} + defaultValue={{ value: '', label: '' }} + render={({ field: { onChange }, fieldState: { error } }) => ( + + + + In which capacity are you applying? + + + + + )} /> - {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 - - - - - Please indicate the country where the Institution/Lead Investigator is located. - - - - - - {error && ( - - - 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?.applyingAsOther?.type === 'maxLength' && ( + + + Applying as info cannot exceed 255 characters. + + + )} + + + - {errors?.countriesOfTeam?.type === 'maxLength' && ( - - - Countries list cannot exceed 255 characters. + + + + If applying as an Institution, please specify its name - - )} - - - selected.value !== '' }} - defaultValue={{ value: '', label: '' }} - render={({ field: { onChange }, fieldState: { error } }) => ( - - - - Your time zone - - + - - Please choose your current time zone to help us schedule calls. - + + Name of your university program, team, or organization. If you do not have an + organization name, write "N/A". + - - - {error && ( - - - Time zone is required. + {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 + + + + Please indicate the country where the Institution/Lead Investigator is located. + + + + + + Country is required. + + + )} + + )} /> - {errors?.projectName?.type === 'required' && ( - - - Project name is required. + + + + If you are a team of distributed researchers, please indicate where your fellow + researchers are located. - - )} - {errors?.projectName?.type === 'maxLength' && ( - - - Project name cannot exceed 255 characters. - - - )} - + - - - - Brief project summary + + You can write as many countries as needed. - - - - Describe your project in a few sentences (you'll have the chance to go into more - detail in the long form). If it's already underway, provide links to any existing - published work. - - -