From 32bbb45f4e0da906afb405bc0f673e50ac7c32e4 Mon Sep 17 00:00:00 2001 From: Ai Ling <50992674+ailing35@users.noreply.github.com> Date: Sat, 5 Nov 2022 14:33:44 +0800 Subject: [PATCH] [offers][feat] Add form validation for typeaheads (#508) --- .../offers/forms/FormCitiesTypeahead.tsx | 37 ++++ .../offers/forms/FormCompaniesTypeahead.tsx | 36 +++ .../offers/forms/FormJobTitlesTypeahead.tsx | 34 +++ .../offersSubmission/OffersSubmissionForm.tsx | 3 +- .../submissionForm/BackgroundForm.tsx | 143 ++---------- .../submissionForm/OfferDetailsForm.tsx | 208 +++++++----------- 6 files changed, 217 insertions(+), 244 deletions(-) create mode 100644 apps/portal/src/components/offers/forms/FormCitiesTypeahead.tsx create mode 100644 apps/portal/src/components/offers/forms/FormCompaniesTypeahead.tsx create mode 100644 apps/portal/src/components/offers/forms/FormJobTitlesTypeahead.tsx diff --git a/apps/portal/src/components/offers/forms/FormCitiesTypeahead.tsx b/apps/portal/src/components/offers/forms/FormCitiesTypeahead.tsx new file mode 100644 index 00000000..8fdd2e60 --- /dev/null +++ b/apps/portal/src/components/offers/forms/FormCitiesTypeahead.tsx @@ -0,0 +1,37 @@ +import type { ComponentProps } from 'react'; +import { useFormContext, useWatch } from 'react-hook-form'; + +import CitiesTypeahead from '~/components/shared/CitiesTypeahead'; + +type Props = Omit< + ComponentProps, + 'onSelect' | 'value' +> & { + names: { label: string; value: string }; +}; + +export default function FormCitiesTypeahead({ names, ...props }: Props) { + const { setValue } = useFormContext(); + const watchCityId = useWatch({ + name: names.value, + }); + const watchCityName = useWatch({ + name: names.label, + }); + + return ( + { + setValue(names.value, option?.value); + setValue(names.label, option?.value); + }} + /> + ); +} diff --git a/apps/portal/src/components/offers/forms/FormCompaniesTypeahead.tsx b/apps/portal/src/components/offers/forms/FormCompaniesTypeahead.tsx new file mode 100644 index 00000000..d236438f --- /dev/null +++ b/apps/portal/src/components/offers/forms/FormCompaniesTypeahead.tsx @@ -0,0 +1,36 @@ +import type { ComponentProps } from 'react'; +import { useFormContext, useWatch } from 'react-hook-form'; + +import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; + +type Props = Omit< + ComponentProps, + 'onSelect' | 'value' +> & { + names: { label: string; value: string }; +}; + +export default function FormCompaniesTypeahead({ names, ...props }: Props) { + const { setValue } = useFormContext(); + const watchCompanyId = useWatch({ + name: names.value, + }); + const watchCompanyName = useWatch({ + name: names.label, + }); + + return ( + { + setValue(names.value, option?.value); + setValue(names.label, option?.value); + }} + /> + ); +} diff --git a/apps/portal/src/components/offers/forms/FormJobTitlesTypeahead.tsx b/apps/portal/src/components/offers/forms/FormJobTitlesTypeahead.tsx new file mode 100644 index 00000000..fa35d320 --- /dev/null +++ b/apps/portal/src/components/offers/forms/FormJobTitlesTypeahead.tsx @@ -0,0 +1,34 @@ +import type { ComponentProps } from 'react'; +import { useFormContext, useWatch } from 'react-hook-form'; + +import type { JobTitleType } from '~/components/shared/JobTitles'; +import { getLabelForJobTitleType } from '~/components/shared/JobTitles'; +import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead'; + +type Props = Omit< + ComponentProps, + 'onSelect' | 'value' +> & { + name: string; +}; + +export default function FormJobTitlesTypeahead({ name, ...props }: Props) { + const { setValue } = useFormContext(); + const watchJobTitle = useWatch({ + name, + }); + + return ( + { + setValue(name, option?.value); + }} + /> + ); +} diff --git a/apps/portal/src/components/offers/offersSubmission/OffersSubmissionForm.tsx b/apps/portal/src/components/offers/offersSubmission/OffersSubmissionForm.tsx index f27e7cce..a927521f 100644 --- a/apps/portal/src/components/offers/offersSubmission/OffersSubmissionForm.tsx +++ b/apps/portal/src/components/offers/offersSubmission/OffersSubmissionForm.tsx @@ -33,13 +33,13 @@ const defaultOfferValues = { cityId: '', comments: '', companyId: '', - jobTitle: '', jobType: JobType.FULLTIME, monthYearReceived: { month: getCurrentMonth() as Month, year: getCurrentYear(), }, negotiationStrategy: '', + title: '', }; export const defaultFullTimeOfferValues = { @@ -108,6 +108,7 @@ export default function OffersSubmissionForm({ const pageRef = useRef(null); const scrollToTop = () => pageRef.current?.scrollTo({ behavior: 'smooth', top: 0 }); + const formMethods = useForm({ defaultValues: initialOfferProfileValues, mode: 'all', diff --git a/apps/portal/src/components/offers/offersSubmission/submissionForm/BackgroundForm.tsx b/apps/portal/src/components/offers/offersSubmission/submissionForm/BackgroundForm.tsx index e8d6cb50..63e0148b 100644 --- a/apps/portal/src/components/offers/offersSubmission/submissionForm/BackgroundForm.tsx +++ b/apps/portal/src/components/offers/offersSubmission/submissionForm/BackgroundForm.tsx @@ -4,11 +4,6 @@ import { Collapsible, RadioList } from '@tih/ui'; import { FieldError } from '~/components/offers/constants'; import type { BackgroundPostData } from '~/components/offers/types'; -import CitiesTypeahead from '~/components/shared/CitiesTypeahead'; -import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; -import type { JobTitleType } from '~/components/shared/JobTitles'; -import { getLabelForJobTitleType } from '~/components/shared/JobTitles'; -import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead'; import { Currency, @@ -17,6 +12,9 @@ import { import { EducationFieldOptions } from '../../EducationFields'; import { EducationLevelOptions } from '../../EducationLevels'; +import FormCitiesTypeahead from '../../forms/FormCitiesTypeahead'; +import FormCompaniesTypeahead from '../../forms/FormCompaniesTypeahead'; +import FormJobTitlesTypeahead from '../../forms/FormJobTitlesTypeahead'; import FormRadioList from '../../forms/FormRadioList'; import FormSection from '../../forms/FormSection'; import FormSelect from '../../forms/FormSelect'; @@ -85,56 +83,19 @@ function YoeSection() { } function FullTimeJobFields() { - const { register, setValue, formState } = useFormContext<{ + const { register, formState } = useFormContext<{ background: BackgroundPostData; }>(); const experiencesField = formState.errors.background?.experiences?.[0]; - const watchJobTitle = useWatch({ - name: 'background.experiences.0.title', - }); - const watchCompanyId = useWatch({ - name: 'background.experiences.0.companyId', - }); - const watchCompanyName = useWatch({ - name: 'background.experiences.0.companyName', - }); - const watchCityId = useWatch({ - name: 'background.experiences.0.cityId', - }); - const watchCityName = useWatch({ - name: 'background.experiences.0.cityName', - }); - return ( <>
- { - if (option) { - setValue('background.experiences.0.title', option.value); - } - }} - /> - { - if (option) { - setValue('background.experiences.0.companyId', option.value); - setValue('background.experiences.0.companyName', option.label); - } else { - setValue('background.experiences.0.companyId', ''); - setValue('background.experiences.0.companyName', ''); - } + +
@@ -172,21 +133,10 @@ function FullTimeJobFields() { placeholder="e.g. L4, Junior" {...register(`background.experiences.0.level`)} /> - { - if (option) { - setValue('background.experiences.0.cityId', option.value); - setValue('background.experiences.0.cityName', option.label); - } else { - setValue('background.experiences.0.cityId', ''); - setValue('background.experiences.0.cityName', ''); - } + (); const experiencesField = formState.errors.background?.experiences?.[0]; - const watchJobTitle = useWatch({ - name: 'background.experiences.0.title', - }); - const watchCompanyId = useWatch({ - name: 'background.experiences.0.companyId', - }); - const watchCompanyName = useWatch({ - name: 'background.experiences.0.companyName', - }); - const watchCityId = useWatch({ - name: 'background.experiences.0.cityId', - }); - const watchCityName = useWatch({ - name: 'background.experiences.0.cityName', - }); - return ( <>
- { - if (option) { - setValue('background.experiences.0.title', option.value); - } - }} - /> - { - if (option) { - setValue('background.experiences.0.companyId', option.value); - setValue('background.experiences.0.companyName', option.label); - } + +
@@ -280,21 +196,10 @@ function InternshipJobFields() { />
- { - if (option) { - setValue('background.experiences.0.cityId', option.value); - setValue('background.experiences.0.cityName', option.label); - } else { - setValue('background.experiences.0.cityId', ''); - setValue('background.experiences.0.cityName', ''); - } +
); -} \ No newline at end of file +} diff --git a/apps/portal/src/components/offers/offersSubmission/submissionForm/OfferDetailsForm.tsx b/apps/portal/src/components/offers/offersSubmission/submissionForm/OfferDetailsForm.tsx index e0b1828e..07920889 100644 --- a/apps/portal/src/components/offers/offersSubmission/submissionForm/OfferDetailsForm.tsx +++ b/apps/portal/src/components/offers/offersSubmission/submissionForm/OfferDetailsForm.tsx @@ -4,6 +4,7 @@ import type { UseFieldArrayRemove, UseFieldArrayReturn, } from 'react-hook-form'; +import { Controller } from 'react-hook-form'; import { useWatch } from 'react-hook-form'; import { useFormContext } from 'react-hook-form'; import { useFieldArray } from 'react-hook-form'; @@ -12,17 +13,14 @@ import { TrashIcon } from '@heroicons/react/24/outline'; import { JobType } from '@prisma/client'; import { Button, Dialog, HorizontalDivider } from '@tih/ui'; -import CitiesTypeahead from '~/components/shared/CitiesTypeahead'; -import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; -import type { JobTitleType } from '~/components/shared/JobTitles'; -import { getLabelForJobTitleType } from '~/components/shared/JobTitles'; -import JobTitlesTypeahead from '~/components/shared/JobTitlesTypahead'; - import { defaultFullTimeOfferValues, defaultInternshipOfferValues, } from '../OffersSubmissionForm'; import { FieldError, JobTypeLabel } from '../../constants'; +import FormCitiesTypeahead from '../../forms/FormCitiesTypeahead'; +import FormCompaniesTypeahead from '../../forms/FormCompaniesTypeahead'; +import FormJobTitlesTypeahead from '../../forms/FormJobTitlesTypeahead'; import FormMonthYearPicker from '../../forms/FormMonthYearPicker'; import FormSection from '../../forms/FormSection'; import FormSelect from '../../forms/FormSelect'; @@ -46,26 +44,11 @@ function FullTimeOfferDetailsForm({ index, remove, }: FullTimeOfferDetailsFormProps) { - const { register, formState, setValue } = useFormContext<{ + const { register, formState, setValue, control } = useFormContext<{ offers: Array; }>(); const offerFields = formState.errors.offers?.[index]; - const watchJobTitle = useWatch({ - name: `offers.${index}.offersFullTime.title`, - }); - const watchCompanyId = useWatch({ - name: `offers.${index}.companyId`, - }); - const watchCompanyName = useWatch({ - name: `offers.${index}.companyName`, - }); - const watchCityId = useWatch({ - name: `offers.${index}.cityId`, - }); - const watchCityName = useWatch({ - name: `offers.${index}.cityName`, - }); const watchCurrency = useWatch({ name: `offers.${index}.offersFullTime.totalCompensation.currency`, }); @@ -83,18 +66,17 @@ function FullTimeOfferDetailsForm({
- { - if (option) { - setValue(`offers.${index}.offersFullTime.title`, option.value); - } - }} + ( + + )} + rules={{ required: true }} />
- { - if (option) { - setValue(`offers.${index}.companyId`, option.value); - setValue(`offers.${index}.companyName`, option.label); - } - }} + ( + + )} + rules={{ required: true }} /> - { - if (option) { - setValue(`offers.${index}.cityId`, option.value); - setValue(`offers.${index}.cityName`, option.label); - } else { - setValue(`offers.${index}.cityId`, ''); - setValue(`offers.${index}.cityName`, ''); - } - }} + ( + + )} + rules={{ required: true }} />
@@ -303,76 +283,56 @@ function InternshipOfferDetailsForm({ index, remove, }: InternshipOfferDetailsFormProps) { - const { register, formState, setValue } = useFormContext<{ + const { register, formState, control } = useFormContext<{ offers: Array; }>(); const offerFields = formState.errors.offers?.[index]; - const watchJobTitle = useWatch({ - name: `offers.${index}.offersIntern.title`, - }); - const watchCompanyId = useWatch({ - name: `offers.${index}.companyId`, - }); - const watchCompanyName = useWatch({ - name: `offers.${index}.companyName`, - }); - const watchCityId = useWatch({ - name: `offers.${index}.cityId`, - }); - const watchCityName = useWatch({ - name: `offers.${index}.cityName`, - }); - return (
- { - if (option) { - setValue(`offers.${index}.offersIntern.title`, option.value); - } - }} + ( + + )} + rules={{ required: true }} /> -
- { - if (option) { - setValue(`offers.${index}.companyId`, option.value); - setValue(`offers.${index}.companyName`, option.label); - } - }} + ( + + )} + rules={{ required: true }} /> - { - if (option) { - setValue(`offers.${index}.cityId`, option.value); - setValue(`offers.${index}.cityName`, option.label); - } else { - setValue(`offers.${index}.cityId`, ''); - setValue(`offers.${index}.cityName`, ''); - } - }} + ( + + )} + rules={{ required: true }} />