From 992d457b8a4431f97bbbd8c0158ce59ab0290ea1 Mon Sep 17 00:00:00 2001 From: Ai Ling <50992674+ailing35@users.noreply.github.com> Date: Thu, 20 Oct 2022 02:46:50 +0800 Subject: [PATCH] [offers][feat] Integrate offers analysis into offers submission (#398) * [offers][fix] Fix minor issues in form * [offers][fix] Use companies typeahead in form * [offers][feat] Fix types and integrate offers analysis * [offers][fix] Fix generate analysis API test --- .../analysis/OfferPercentileAnalysis.tsx | 27 ++ .../offers/analysis/OfferProfileCard.tsx | 61 +++++ .../portal/src/components/offers/constants.ts | 37 +-- .../{components => }/FormMonthYearPicker.tsx | 2 +- .../forms/{components => }/FormRadioList.tsx | 0 .../forms/{components => }/FormSelect.tsx | 0 .../forms/{components => }/FormTextArea.tsx | 0 .../forms/{components => }/FormTextInput.tsx | 0 .../components/offers/forms/OfferAnalysis.tsx | 100 ------- .../BackgroundForm.tsx | 147 +++++++---- .../offers-submission/OfferAnalysis.tsx | 138 ++++++++++ .../OfferDetailsForm.tsx | 244 ++++++++---------- .../OfferProfileSave.tsx | 3 +- apps/portal/src/components/offers/types.ts | 141 +++++----- apps/portal/src/pages/offers/submit.tsx | 46 +++- .../pages/offers/test/generateAnalysis.tsx | 13 +- .../router/offers/offers-analysis-router.ts | 200 +++++++------- 17 files changed, 640 insertions(+), 519 deletions(-) create mode 100644 apps/portal/src/components/offers/analysis/OfferPercentileAnalysis.tsx create mode 100644 apps/portal/src/components/offers/analysis/OfferProfileCard.tsx rename apps/portal/src/components/offers/forms/{components => }/FormMonthYearPicker.tsx (91%) rename apps/portal/src/components/offers/forms/{components => }/FormRadioList.tsx (100%) rename apps/portal/src/components/offers/forms/{components => }/FormSelect.tsx (100%) rename apps/portal/src/components/offers/forms/{components => }/FormTextArea.tsx (100%) rename apps/portal/src/components/offers/forms/{components => }/FormTextInput.tsx (100%) delete mode 100644 apps/portal/src/components/offers/forms/OfferAnalysis.tsx rename apps/portal/src/components/offers/{forms => offers-submission}/BackgroundForm.tsx (65%) create mode 100644 apps/portal/src/components/offers/offers-submission/OfferAnalysis.tsx rename apps/portal/src/components/offers/{forms => offers-submission}/OfferDetailsForm.tsx (69%) rename apps/portal/src/components/offers/{forms => offers-submission}/OfferProfileSave.tsx (96%) diff --git a/apps/portal/src/components/offers/analysis/OfferPercentileAnalysis.tsx b/apps/portal/src/components/offers/analysis/OfferPercentileAnalysis.tsx new file mode 100644 index 00000000..1f4107ca --- /dev/null +++ b/apps/portal/src/components/offers/analysis/OfferPercentileAnalysis.tsx @@ -0,0 +1,27 @@ +import type { Analysis } from '~/types/offers'; + +type OfferPercentileAnalysisProps = Readonly<{ + companyName: string; + offerAnalysis: Analysis; + tab: string; +}>; + +export default function OfferPercentileAnalysis({ + tab, + companyName, + offerAnalysis: { noOfOffers, percentile }, +}: OfferPercentileAnalysisProps) { + return tab === 'Overall' ? ( +

+ Your highest offer is from {companyName}, which is {percentile} percentile + out of {noOfOffers} offers received for the same job type, same level, and + same YOE(+/-1) in the last year. +

+ ) : ( +

+ Your offer from {companyName} is {percentile} percentile out of{' '} + {noOfOffers} offers received in {companyName} for the same job type, same + level, and same YOE(+/-1) in the last year. +

+ ); +} diff --git a/apps/portal/src/components/offers/analysis/OfferProfileCard.tsx b/apps/portal/src/components/offers/analysis/OfferProfileCard.tsx new file mode 100644 index 00000000..9969c953 --- /dev/null +++ b/apps/portal/src/components/offers/analysis/OfferProfileCard.tsx @@ -0,0 +1,61 @@ +import { UserCircleIcon } from '@heroicons/react/24/outline'; + +import { HorizontalDivider } from '~/../../../packages/ui/dist'; +import { formatDate } from '~/utils/offers/time'; + +import { JobType } from '../types'; + +import type { AnalysisOffer } from '~/types/offers'; + +type OfferProfileCardProps = Readonly<{ + offerProfile: AnalysisOffer; +}>; + +export default function OfferProfileCard({ + offerProfile: { + company, + income, + profileName, + totalYoe, + level, + monthYearReceived, + jobType, + location, + title, + previousCompanies, + }, +}: OfferProfileCardProps) { + return ( +
+
+
+ +
+
+

{profileName}

+

Previous company: {previousCompanies[0]}

+

YOE: {totalYoe} year(s)

+
+
+ + +
+
+

{title}

+

+ Company: {company.name}, {location} +

+

Level: {level}

+
+
+

{formatDate(monthYearReceived)}

+

+ {jobType === JobType.FullTime + ? `$${income} / year` + : `$${income} / month`} +

+
+
+
+ ); +} diff --git a/apps/portal/src/components/offers/constants.ts b/apps/portal/src/components/offers/constants.ts index e360a505..f289a91e 100644 --- a/apps/portal/src/components/offers/constants.ts +++ b/apps/portal/src/components/offers/constants.ts @@ -22,29 +22,6 @@ export const titleOptions = [ }, ]; -export const companyOptions = [ - { - label: 'Amazon', - value: 'cl93patjt0000txewdi601mub', - }, - { - label: 'Microsoft', - value: 'cl93patjt0001txewkglfjsro', - }, - { - label: 'Apple', - value: 'cl93patjt0002txewf3ug54m8', - }, - { - label: 'Google', - value: 'cl93patjt0003txewyiaky7xx', - }, - { - label: 'Meta', - value: 'cl93patjt0004txew88wkcqpu', - }, -]; - export const locationOptions = [ { label: 'Singapore, Singapore', @@ -86,26 +63,26 @@ export const internshipCycleOptions = [ export const yearOptions = [ { label: '2021', - value: '2021', + value: 2021, }, { label: '2022', - value: '2022', + value: 2022, }, { label: '2023', - value: '2023', + value: 2023, }, { label: '2024', - value: '2024', + value: 2024, }, ]; export const educationLevelOptions = Object.entries( EducationBackgroundType, -).map(([key, value]) => ({ - label: key, +).map(([, value]) => ({ + label: value, value, })); @@ -129,3 +106,5 @@ export enum FieldError { Number = 'Please fill in a number in this field.', Required = 'Please fill in this field.', } + +export const OVERALL_TAB = 'Overall'; diff --git a/apps/portal/src/components/offers/forms/components/FormMonthYearPicker.tsx b/apps/portal/src/components/offers/forms/FormMonthYearPicker.tsx similarity index 91% rename from apps/portal/src/components/offers/forms/components/FormMonthYearPicker.tsx rename to apps/portal/src/components/offers/forms/FormMonthYearPicker.tsx index 2b6c414a..ca036b0e 100644 --- a/apps/portal/src/components/offers/forms/components/FormMonthYearPicker.tsx +++ b/apps/portal/src/components/offers/forms/FormMonthYearPicker.tsx @@ -4,7 +4,7 @@ import { useFormContext, useWatch } from 'react-hook-form'; import MonthYearPicker from '~/components/shared/MonthYearPicker'; -import { getCurrentMonth, getCurrentYear } from '../../../../utils/offers/time'; +import { getCurrentMonth, getCurrentYear } from '../../../utils/offers/time'; type MonthYearPickerProps = ComponentProps; diff --git a/apps/portal/src/components/offers/forms/components/FormRadioList.tsx b/apps/portal/src/components/offers/forms/FormRadioList.tsx similarity index 100% rename from apps/portal/src/components/offers/forms/components/FormRadioList.tsx rename to apps/portal/src/components/offers/forms/FormRadioList.tsx diff --git a/apps/portal/src/components/offers/forms/components/FormSelect.tsx b/apps/portal/src/components/offers/forms/FormSelect.tsx similarity index 100% rename from apps/portal/src/components/offers/forms/components/FormSelect.tsx rename to apps/portal/src/components/offers/forms/FormSelect.tsx diff --git a/apps/portal/src/components/offers/forms/components/FormTextArea.tsx b/apps/portal/src/components/offers/forms/FormTextArea.tsx similarity index 100% rename from apps/portal/src/components/offers/forms/components/FormTextArea.tsx rename to apps/portal/src/components/offers/forms/FormTextArea.tsx diff --git a/apps/portal/src/components/offers/forms/components/FormTextInput.tsx b/apps/portal/src/components/offers/forms/FormTextInput.tsx similarity index 100% rename from apps/portal/src/components/offers/forms/components/FormTextInput.tsx rename to apps/portal/src/components/offers/forms/FormTextInput.tsx diff --git a/apps/portal/src/components/offers/forms/OfferAnalysis.tsx b/apps/portal/src/components/offers/forms/OfferAnalysis.tsx deleted file mode 100644 index c3625d6d..00000000 --- a/apps/portal/src/components/offers/forms/OfferAnalysis.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useState } from 'react'; -import { UserCircleIcon } from '@heroicons/react/20/solid'; -import { HorizontalDivider, Tabs } from '@tih/ui'; - -const tabs = [ - { - label: 'Overall', - value: 'overall', - }, - { - label: 'Shopee', - value: 'company-id', - }, -]; - -function OfferPercentileAnalysis() { - const result = { - company: 'Shopee', - numberOfOffers: 105, - percentile: 56, - }; - - return ( -

- Your highest offer is from {result.company}, which is {result.percentile}{' '} - percentile out of {result.numberOfOffers} offers received in Singapore for - the same job type, same level, and same YOE in the last year. -

- ); -} - -function OfferProfileCard() { - return ( -
-
-
- -
-
-

profile-name

-

Previous company: Meta, Singapore

-

YOE: 4 years

-
-
- - -
-
-

Software engineer

-

Company: Google, Singapore

-

Level: G4

-
-
-

Sept 2022

-

$125,000 / year

-
-
-
- ); -} - -function TopOfferProfileList() { - return ( - <> - - - - ); -} - -function OfferAnalysisContent() { - return ( - <> - - - - ); -} - -export default function OfferAnalysis() { - const [tab, setTab] = useState('Overall'); - - return ( -
-
- Result -
-
- - - -
-
- ); -} diff --git a/apps/portal/src/components/offers/forms/BackgroundForm.tsx b/apps/portal/src/components/offers/offers-submission/BackgroundForm.tsx similarity index 65% rename from apps/portal/src/components/offers/forms/BackgroundForm.tsx rename to apps/portal/src/components/offers/offers-submission/BackgroundForm.tsx index 61a1c3fe..645d60ca 100644 --- a/apps/portal/src/components/offers/forms/BackgroundForm.tsx +++ b/apps/portal/src/components/offers/offers-submission/BackgroundForm.tsx @@ -2,21 +2,28 @@ import { useFormContext, useWatch } from 'react-hook-form'; import { Collapsible, RadioList } from '@tih/ui'; import { - companyOptions, educationFieldOptions, educationLevelOptions, + emptyOption, + FieldError, locationOptions, titleOptions, } from '~/components/offers/constants'; -import FormRadioList from '~/components/offers/forms/components/FormRadioList'; -import FormSelect from '~/components/offers/forms/components/FormSelect'; -import FormTextInput from '~/components/offers/forms/components/FormTextInput'; +import type { BackgroundPostData } from '~/components/offers/types'; import { JobType } from '~/components/offers/types'; +import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; import { CURRENCY_OPTIONS } from '~/utils/offers/currency/CurrencyEnum'; +import FormRadioList from '../forms/FormRadioList'; +import FormSelect from '../forms/FormSelect'; +import FormTextInput from '../forms/FormTextInput'; + function YoeSection() { - const { register } = useFormContext(); + const { register, formState } = useFormContext<{ + background: BackgroundPostData; + }>(); + const backgroundFields = formState.errors.background; return ( <>
@@ -26,53 +33,62 @@ function YoeSection() {
-
- -
- - -
-
- - -
-
-
+ +
+ + +
+
+ + +
+
); } function FullTimeJobFields() { - const { register } = useFormContext(); + const { register, setValue, formState } = useFormContext<{ + background: BackgroundPostData; + }>(); + const experiencesField = formState.errors.background?.experiences?.[0]; return ( <>
@@ -80,14 +96,16 @@ function FullTimeJobFields() { display="block" label="Title" options={titleOptions} + placeholder={emptyOption} {...register(`background.experiences.0.title`)} /> - +
+ + setValue(`background.experiences.0.companyId`, value) + } + /> +
} endAddOnType="element" + errorMessage={experiencesField?.totalCompensation?.value?.message} label="Total Compensation (Annual)" placeholder="0.00" startAddOn="$" startAddOnType="label" type="number" {...register(`background.experiences.0.totalCompensation.value`, { + min: { message: FieldError.NonNegativeNumber, value: 0 }, valueAsNumber: true, })} /> @@ -134,9 +154,11 @@ function FullTimeJobFields() { {...register(`background.experiences.0.location`)} /> @@ -147,7 +169,11 @@ function FullTimeJobFields() { } function InternshipJobFields() { - const { register } = useFormContext(); + const { register, setValue, formState } = useFormContext<{ + background: BackgroundPostData; + }>(); + const experiencesField = formState.errors.background?.experiences?.[0]; + return ( <>
@@ -155,14 +181,16 @@ function InternshipJobFields() { display="block" label="Title" options={titleOptions} + placeholder={emptyOption} {...register(`background.experiences.0.title`)} /> - +
+ + setValue(`background.experiences.0.companyId`, value) + } + /> +
} endAddOnType="element" + errorMessage={experiencesField?.monthlySalary?.value?.message} label="Salary (Monthly)" placeholder="0.00" startAddOn="$" startAddOnType="label" type="number" - {...register(`background.experiences.0.monthlySalary.value`)} + {...register(`background.experiences.0.monthlySalary.value`, { + min: { message: FieldError.NonNegativeNumber, value: 0 }, + valueAsNumber: true, + })} />
@@ -195,6 +227,7 @@ function InternshipJobFields() { display="block" label="Location" options={locationOptions} + placeholder={emptyOption} {...register(`background.experiences.0.location`)} />
@@ -231,7 +264,7 @@ function CurrentJobSection() { @@ -258,12 +291,14 @@ function EducationSection() { display="block" label="Education Level" options={educationLevelOptions} + placeholder={emptyOption} {...register(`background.educations.0.type`)} /> @@ -287,9 +322,9 @@ export default function BackgroundForm() {
Help us better gauge your offers
-
- This section is optional, but your background information helps us - benchmark your offers. +
+ This section is mostly optional, but your background information helps + us benchmark your offers.
diff --git a/apps/portal/src/components/offers/offers-submission/OfferAnalysis.tsx b/apps/portal/src/components/offers/offers-submission/OfferAnalysis.tsx new file mode 100644 index 00000000..6a0717da --- /dev/null +++ b/apps/portal/src/components/offers/offers-submission/OfferAnalysis.tsx @@ -0,0 +1,138 @@ +import Error from 'next/error'; +import { useEffect } from 'react'; +import { useState } from 'react'; +import { HorizontalDivider, Spinner, Tabs } from '@tih/ui'; + +import { trpc } from '~/utils/trpc'; + +import OfferPercentileAnalysis from '../analysis/OfferPercentileAnalysis'; +import OfferProfileCard from '../analysis/OfferProfileCard'; +import { OVERALL_TAB } from '../constants'; + +import type { + Analysis, + AnalysisHighestOffer, + ProfileAnalysis, +} from '~/types/offers'; + +type OfferAnalysisData = { + offer?: AnalysisHighestOffer; + offerAnalysis?: Analysis; +}; + +type OfferAnalysisContentProps = Readonly<{ + analysis: OfferAnalysisData; + tab: string; +}>; + +function OfferAnalysisContent({ + analysis: { offer, offerAnalysis }, + tab, +}: OfferAnalysisContentProps) { + if (!offerAnalysis || !offer || offerAnalysis.noOfOffers === 0) { + return ( +

+ You are the first to submit an offer for these companies! Check back + later when there are more submissions. +

+ ); + } + return ( + <> + + {offerAnalysis.topPercentileOffers.map((topPercentileOffer) => ( + + ))} + + ); +} + +type OfferAnalysisProps = Readonly<{ + profileId?: string; +}>; + +export default function OfferAnalysis({ profileId }: OfferAnalysisProps) { + const [tab, setTab] = useState(OVERALL_TAB); + const [allAnalysis, setAllAnalysis] = useState(null); + const [analysis, setAnalysis] = useState(null); + + useEffect(() => { + if (tab === OVERALL_TAB) { + setAnalysis({ + offer: allAnalysis?.overallHighestOffer, + offerAnalysis: allAnalysis?.overallAnalysis, + }); + } else { + setAnalysis({ + offer: allAnalysis?.overallHighestOffer, + offerAnalysis: allAnalysis?.companyAnalysis[0], + }); + } + }, [tab, allAnalysis]); + + if (!profileId) { + return null; + } + + const getAnalysisResult = trpc.useQuery( + ['offers.analysis.get', { profileId }], + { + onError(error) { + console.error(error.message); + }, + onSuccess(data) { + setAllAnalysis(data); + }, + }, + ); + + const tabOptions = [ + { + label: OVERALL_TAB, + value: OVERALL_TAB, + }, + { + label: allAnalysis?.overallHighestOffer.company.name || '', + value: allAnalysis?.overallHighestOffer.company.id || '', + }, + ]; + + return ( + <> + {getAnalysisResult.isError && ( + + )} + {!getAnalysisResult.isError && analysis && ( +
+
+ Result +
+ {getAnalysisResult.isLoading ? ( + + ) : ( +
+ + + +
+ )} +
+ )} + + ); +} diff --git a/apps/portal/src/components/offers/forms/OfferDetailsForm.tsx b/apps/portal/src/components/offers/offers-submission/OfferDetailsForm.tsx similarity index 69% rename from apps/portal/src/components/offers/forms/OfferDetailsForm.tsx rename to apps/portal/src/components/offers/offers-submission/OfferDetailsForm.tsx index 9ff722fb..eee37504 100644 --- a/apps/portal/src/components/offers/forms/OfferDetailsForm.tsx +++ b/apps/portal/src/components/offers/offers-submission/OfferDetailsForm.tsx @@ -1,5 +1,9 @@ import { useEffect, useState } from 'react'; -import type { FieldValues, UseFieldArrayReturn } from 'react-hook-form'; +import type { + FieldValues, + UseFieldArrayRemove, + UseFieldArrayReturn, +} from 'react-hook-form'; import { useWatch } from 'react-hook-form'; import { useFormContext } from 'react-hook-form'; import { useFieldArray } from 'react-hook-form'; @@ -7,17 +11,14 @@ import { PlusIcon } from '@heroicons/react/20/solid'; import { TrashIcon } from '@heroicons/react/24/outline'; import { Button, Dialog } from '@tih/ui'; +import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; + import { defaultFullTimeOfferValues, defaultInternshipOfferValues, } from '~/pages/offers/submit'; -import FormMonthYearPicker from './components/FormMonthYearPicker'; -import FormSelect from './components/FormSelect'; -import FormTextArea from './components/FormTextArea'; -import FormTextInput from './components/FormTextInput'; import { - companyOptions, emptyOption, FieldError, internshipCycleOptions, @@ -25,36 +26,40 @@ import { titleOptions, yearOptions, } from '../constants'; -import type { - FullTimeOfferDetailsFormData, - InternshipOfferDetailsFormData, -} from '../types'; +import FormMonthYearPicker from '../forms/FormMonthYearPicker'; +import FormSelect from '../forms/FormSelect'; +import FormTextArea from '../forms/FormTextArea'; +import FormTextInput from '../forms/FormTextInput'; +import type { OfferFormData } from '../types'; import { JobTypeLabel } from '../types'; import { JobType } from '../types'; import { CURRENCY_OPTIONS } from '../../../utils/offers/currency/CurrencyEnum'; type FullTimeOfferDetailsFormProps = Readonly<{ index: number; - setDialogOpen: (isOpen: boolean) => void; + remove: UseFieldArrayRemove; }>; function FullTimeOfferDetailsForm({ index, - setDialogOpen, + remove, }: FullTimeOfferDetailsFormProps) { const { register, formState, setValue } = useFormContext<{ - offers: Array; + offers: Array; }>(); const offerFields = formState.errors.offers?.[index]; const watchCurrency = useWatch({ - name: `offers.${index}.job.totalCompensation.currency`, + name: `offers.${index}.offersFullTime.totalCompensation.currency`, }); useEffect(() => { - setValue(`offers.${index}.job.base.currency`, watchCurrency); - setValue(`offers.${index}.job.bonus.currency`, watchCurrency); - setValue(`offers.${index}.job.stocks.currency`, watchCurrency); + setValue( + `offers.${index}.offersFullTime.baseSalary.currency`, + watchCurrency, + ); + setValue(`offers.${index}.offersFullTime.bonus.currency`, watchCurrency); + setValue(`offers.${index}.offersFullTime.stocks.currency`, watchCurrency); }, [watchCurrency, index, setValue]); return ( @@ -62,48 +67,44 @@ function FullTimeOfferDetailsForm({
-
- +
+
+ + setValue(`offers.${index}.companyId`, value) + } + /> +
-
+
} endAddOnType="element" - errorMessage={offerFields?.job?.totalCompensation?.value?.message} + errorMessage={ + offerFields?.offersFullTime?.totalCompensation?.value?.message + } label="Total Compensation (Annual)" placeholder="0" required={true} startAddOn="$" startAddOnType="label" type="number" - {...register(`offers.${index}.job.totalCompensation.value`, { - min: { message: FieldError.NonNegativeNumber, value: 0 }, - required: FieldError.Required, - valueAsNumber: true, - })} + {...register( + `offers.${index}.offersFullTime.totalCompensation.value`, + { + min: { message: FieldError.NonNegativeNumber, value: 0 }, + required: FieldError.Required, + valueAsNumber: true, + }, + )} />
@@ -160,20 +169,23 @@ function FullTimeOfferDetailsForm({ isLabelHidden={true} label="Currency" options={CURRENCY_OPTIONS} - {...register(`offers.${index}.job.base.currency`, { - required: FieldError.Required, - })} + {...register( + `offers.${index}.offersFullTime.baseSalary.currency`, + { + required: FieldError.Required, + }, + )} /> } endAddOnType="element" - errorMessage={offerFields?.job?.base?.value?.message} + errorMessage={offerFields?.offersFullTime?.baseSalary?.value?.message} label="Base Salary (Annual)" placeholder="0" required={true} startAddOn="$" startAddOnType="label" type="number" - {...register(`offers.${index}.job.base.value`, { + {...register(`offers.${index}.offersFullTime.baseSalary.value`, { min: { message: FieldError.NonNegativeNumber, value: 0 }, required: FieldError.Required, valueAsNumber: true, @@ -186,20 +198,20 @@ function FullTimeOfferDetailsForm({ isLabelHidden={true} label="Currency" options={CURRENCY_OPTIONS} - {...register(`offers.${index}.job.bonus.currency`, { + {...register(`offers.${index}.offersFullTime.bonus.currency`, { required: FieldError.Required, })} /> } endAddOnType="element" - errorMessage={offerFields?.job?.bonus?.value?.message} + errorMessage={offerFields?.offersFullTime?.bonus?.value?.message} label="Bonus (Annual)" placeholder="0" required={true} startAddOn="$" startAddOnType="label" type="number" - {...register(`offers.${index}.job.bonus.value`, { + {...register(`offers.${index}.offersFullTime.bonus.value`, { min: { message: FieldError.NonNegativeNumber, value: 0 }, required: FieldError.Required, valueAsNumber: true, @@ -214,20 +226,20 @@ function FullTimeOfferDetailsForm({ isLabelHidden={true} label="Currency" options={CURRENCY_OPTIONS} - {...register(`offers.${index}.job.stocks.currency`, { + {...register(`offers.${index}.offersFullTime.stocks.currency`, { required: FieldError.Required, })} /> } endAddOnType="element" - errorMessage={offerFields?.job?.stocks?.value?.message} + errorMessage={offerFields?.offersFullTime?.stocks?.value?.message} label="Stocks (Annual)" placeholder="0" required={true} startAddOn="$" startAddOnType="label" type="number" - {...register(`offers.${index}.job.stocks.value`, { + {...register(`offers.${index}.offersFullTime.stocks.value`, { min: { message: FieldError.NonNegativeNumber, value: 0 }, required: FieldError.Required, valueAsNumber: true, @@ -254,7 +266,7 @@ function FullTimeOfferDetailsForm({ icon={TrashIcon} label="Delete" variant="secondary" - onClick={() => setDialogOpen(true)} + onClick={() => remove(index)} /> )}
@@ -264,15 +276,15 @@ function FullTimeOfferDetailsForm({ type InternshipOfferDetailsFormProps = Readonly<{ index: number; - setDialogOpen: (isOpen: boolean) => void; + remove: UseFieldArrayRemove; }>; function InternshipOfferDetailsForm({ index, - setDialogOpen, + remove, }: InternshipOfferDetailsFormProps) { - const { register, formState } = useFormContext<{ - offers: Array; + const { register, formState, setValue } = useFormContext<{ + offers: Array; }>(); const offerFields = formState.errors.offers?.[index]; @@ -282,39 +294,35 @@ function InternshipOfferDetailsForm({
- +
+ + setValue(`offers.${index}.companyId`, value) + } + /> +
@@ -369,20 +378,25 @@ function InternshipOfferDetailsForm({ isLabelHidden={true} label="Currency" options={CURRENCY_OPTIONS} - {...register(`offers.${index}.job.monthlySalary.currency`, { - required: FieldError.Required, - })} + {...register( + `offers.${index}.offersIntern.monthlySalary.currency`, + { + required: FieldError.Required, + }, + )} /> } endAddOnType="element" - errorMessage={offerFields?.job?.monthlySalary?.value?.message} + errorMessage={ + offerFields?.offersIntern?.monthlySalary?.value?.message + } label="Salary (Monthly)" placeholder="0" required={true} startAddOn="$" startAddOnType="label" type="number" - {...register(`offers.${index}.job.monthlySalary.value`, { + {...register(`offers.${index}.offersIntern.monthlySalary.value`, { min: { message: FieldError.NonNegativeNumber, value: 0 }, required: FieldError.Required, valueAsNumber: true, @@ -410,7 +424,7 @@ function InternshipOfferDetailsForm({ label="Delete" variant="secondary" onClick={() => { - setDialogOpen(true); + remove(index); }} /> )} @@ -429,7 +443,6 @@ function OfferDetailsFormArray({ jobType, }: OfferDetailsFormArrayProps) { const { append, remove, fields } = fieldArrayValues; - const [isDialogOpen, setDialogOpen] = useState(false); return (
@@ -437,44 +450,10 @@ function OfferDetailsFormArray({ return (
{jobType === JobType.FullTime ? ( - + ) : ( - + )} - { - remove(index); - setDialogOpen(false); - }} - /> - } - secondaryButton={ -
); })} @@ -501,22 +480,21 @@ export default function OfferDetailsForm() { const [isDialogOpen, setDialogOpen] = useState(false); const { control } = useFormContext(); const fieldArrayValues = useFieldArray({ control, name: 'offers' }); + const { append, remove } = fieldArrayValues; const toggleJobType = () => { - fieldArrayValues.remove(); + remove(); if (jobType === JobType.FullTime) { - setJobType(JobType.Internship); - fieldArrayValues.append(defaultInternshipOfferValues); + setJobType(JobType.Intern); + append(defaultInternshipOfferValues); } else { setJobType(JobType.FullTime); - fieldArrayValues.append(defaultFullTimeOfferValues); + append(defaultFullTimeOfferValues); } }; const switchJobTypeLabel = () => - jobType === JobType.FullTime - ? JobTypeLabel.INTERNSHIP - : JobTypeLabel.FULLTIME; + jobType === JobType.FullTime ? JobTypeLabel.INTERN : JobTypeLabel.FULLTIME; return (
@@ -541,11 +519,11 @@ export default function OfferDetailsForm() {