From 201f6487fcda921040728843808c337e10391efa Mon Sep 17 00:00:00 2001 From: Ai Ling Date: Tue, 11 Oct 2022 15:02:31 +0800 Subject: [PATCH] [offers][feat] Integrate offer profile create API --- .../src/components/offers/OffersTable.tsx | 2 +- .../portal/src/components/offers/constants.ts | 20 +++---- .../offers/forms/BackgroundForm.tsx | 58 +++++++++++-------- .../offers/forms/OfferDetailsForm.tsx | 47 ++++++++++----- .../forms/components/FormMonthYearPicker.tsx | 9 +-- .../offers/forms/components/FormRadioList.tsx | 7 +-- apps/portal/src/components/offers/types.ts | 22 +++++-- apps/portal/src/pages/offers/submit.tsx | 39 +++++++++++-- .../offers}/currency/CurrencyEnum.tsx | 0 .../offers}/currency/CurrencySelector.tsx | 2 +- apps/portal/src/utils/offers/form.tsx | 56 ++++++++++++++++++ .../time/index.tsx => utils/offers/time.tsx} | 0 12 files changed, 191 insertions(+), 71 deletions(-) rename apps/portal/src/{components/offers/util => utils/offers}/currency/CurrencyEnum.tsx (100%) rename apps/portal/src/{components/offers/util => utils/offers}/currency/CurrencySelector.tsx (89%) create mode 100644 apps/portal/src/utils/offers/form.tsx rename apps/portal/src/{components/offers/util/time/index.tsx => utils/offers/time.tsx} (100%) diff --git a/apps/portal/src/components/offers/OffersTable.tsx b/apps/portal/src/components/offers/OffersTable.tsx index ded79547..5ef39748 100644 --- a/apps/portal/src/components/offers/OffersTable.tsx +++ b/apps/portal/src/components/offers/OffersTable.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { HorizontalDivider, Pagination, Select, Tabs } from '@tih/ui'; -import CurrencySelector from '~/components/offers/util/currency/CurrencySelector'; +import CurrencySelector from '~/utils/offers/currency/CurrencySelector'; type TableRow = { company: string; diff --git a/apps/portal/src/components/offers/constants.ts b/apps/portal/src/components/offers/constants.ts index 2a09e9b5..a5dc2706 100644 --- a/apps/portal/src/components/offers/constants.ts +++ b/apps/portal/src/components/offers/constants.ts @@ -29,24 +29,24 @@ export const titleOptions = [ export const companyOptions = [ emptyOption, { - label: 'Bytedance', - value: 'id-abc123', + label: 'Amazon', + value: 'cl93patjt0000txewdi601mub', }, { - label: 'Google', - value: 'id-abc567', + label: 'Microsoft', + value: 'cl93patjt0001txewkglfjsro', }, { - label: 'Meta', - value: 'id-abc456', + label: 'Apple', + value: 'cl93patjt0002txewf3ug54m8', }, { - label: 'Shopee', - value: 'id-abc345', + label: 'Google', + value: 'cl93patjt0003txewyiaky7xx', }, { - label: 'Tik Tok', - value: 'id-abc678', + label: 'Meta', + value: 'cl93patjt0004txew88wkcqpu', }, ]; diff --git a/apps/portal/src/components/offers/forms/BackgroundForm.tsx b/apps/portal/src/components/offers/forms/BackgroundForm.tsx index 02d64af7..171823f1 100644 --- a/apps/portal/src/components/offers/forms/BackgroundForm.tsx +++ b/apps/portal/src/components/offers/forms/BackgroundForm.tsx @@ -12,7 +12,7 @@ import { titleOptions, } from '../constants'; import { JobType } from '../types'; -import { CURRENCY_OPTIONS } from '../util/currency/CurrencyEnum'; +import { CURRENCY_OPTIONS } from '../../../utils/offers/currency/CurrencyEnum'; function YoeSection() { const { register } = useFormContext(); @@ -28,7 +28,9 @@ function YoeSection() { label="Total YOE" placeholder="0" type="number" - {...register(`background.totalYoe`)} + {...register(`background.totalYoe`, { + valueAsNumber: true, + })} />
@@ -37,7 +39,9 @@ function YoeSection() {
@@ -90,7 +96,9 @@ function FullTimeJobFields() { isLabelHidden={true} label="Currency" options={CURRENCY_OPTIONS} - {...register(`background.experience.totalCompensation.currency`)} + {...register( + `background.experiences.0.totalCompensation.currency`, + )} /> } endAddOnType="element" @@ -99,7 +107,9 @@ function FullTimeJobFields() { startAddOn="$" startAddOnType="label" type="number" - {...register(`background.experience.totalCompensation.value`)} + {...register(`background.experiences.0.totalCompensation.value`, { + valueAsNumber: true, + })} />
@@ -107,12 +117,12 @@ function FullTimeJobFields() {
@@ -120,12 +130,14 @@ function FullTimeJobFields() { display="block" label="Location" options={locationOptions} - {...register(`background.experience.location`)} + {...register(`background.experiences.0.location`)} />
@@ -142,13 +154,13 @@ function InternshipJobFields() { display="block" label="Title" options={titleOptions} - {...register(`background.experience.title`)} + {...register(`background.experiences.0.title`)} />
@@ -159,7 +171,7 @@ function InternshipJobFields() { isLabelHidden={true} label="Currency" options={CURRENCY_OPTIONS} - {...register(`background.experience.monthlySalary.currency`)} + {...register(`background.experiences.0.monthlySalary.currency`)} /> } endAddOnType="element" @@ -168,7 +180,7 @@ function InternshipJobFields() { startAddOn="$" startAddOnType="label" type="number" - {...register(`background.experience.monthlySalary.value`)} + {...register(`background.experiences.0.monthlySalary.value`)} />
@@ -176,13 +188,13 @@ function InternshipJobFields() { @@ -194,7 +206,7 @@ function CurrentJobSection() { const { register } = useFormContext(); const watchJobType = useWatch({ defaultValue: JobType.FullTime, - name: 'background.experience.jobType', + name: 'background.experiences.0.jobType', }); return ( @@ -209,7 +221,7 @@ function CurrentJobSection() { isLabelHidden={true} label="Job Type" orientation="horizontal" - {...register('background.experience.jobType')}> + {...register('background.experiences.0.jobType')}> @@ -259,7 +271,7 @@ function EducationSection() { diff --git a/apps/portal/src/components/offers/forms/OfferDetailsForm.tsx b/apps/portal/src/components/offers/forms/OfferDetailsForm.tsx index 4731c861..6348360f 100644 --- a/apps/portal/src/components/offers/forms/OfferDetailsForm.tsx +++ b/apps/portal/src/components/offers/forms/OfferDetailsForm.tsx @@ -23,7 +23,7 @@ import { } from '../constants'; import type { OfferDetailsFormData } from '../types'; import { JobType } from '../types'; -import { CURRENCY_OPTIONS } from '../util/currency/CurrencyEnum'; +import { CURRENCY_OPTIONS } from '../../../utils/offers/currency/CurrencyEnum'; type FullTimeOfferDetailsFormProps = Readonly<{ index: number; @@ -108,6 +108,7 @@ function FullTimeOfferDetailsForm({ type="number" {...register(`offers.${index}.job.totalCompensation.value`, { required: true, + valueAsNumber: true, })} /> @@ -131,7 +132,10 @@ function FullTimeOfferDetailsForm({ startAddOn="$" startAddOnType="label" type="number" - {...register(`offers.${index}.job.base.value`, { required: true })} + {...register(`offers.${index}.job.base.value`, { + required: true, + valueAsNumber: true, + })} />
@@ -175,7 +182,10 @@ function FullTimeOfferDetailsForm({ startAddOn="$" startAddOnType="label" type="number" - {...register(`offers.${index}.job.stocks.value`, { required: true })} + {...register(`offers.${index}.job.stocks.value`, { + required: true, + valueAsNumber: true, + })} />
@@ -287,16 +297,18 @@ function InternshipOfferDetailsForm({ label="Company" options={companyOptions} required={true} - value="Shopee" - {...register(`offers.${index}.companyId`)} + {...register(`offers.${index}.companyId`, { + required: true, + })} />
@@ -305,16 +317,18 @@ function InternshipOfferDetailsForm({ label="Internship Cycle" options={internshipCycleOptions} required={true} - value="Summer" - {...register(`offers.${index}.job.internshipCycle`)} + {...register(`offers.${index}.job.internshipCycle`, { + required: true, + })} />
@@ -331,7 +345,9 @@ function InternshipOfferDetailsForm({ isLabelHidden={true} label="Currency" options={CURRENCY_OPTIONS} - {...register(`offers.${index}.job.monthlySalary.currency`)} + {...register(`offers.${index}.job.monthlySalary.currency`, { + required: true, + })} /> } endAddOnType="element" @@ -341,7 +357,10 @@ function InternshipOfferDetailsForm({ startAddOn="$" startAddOnType="label" type="number" - {...register(`offers.${index}.job.monthlySalary.value`)} + {...register(`offers.${index}.job.monthlySalary.value`, { + required: true, + valueAsNumber: true, + })} />
diff --git a/apps/portal/src/components/offers/forms/components/FormMonthYearPicker.tsx b/apps/portal/src/components/offers/forms/components/FormMonthYearPicker.tsx index 3ebc52da..62967117 100644 --- a/apps/portal/src/components/offers/forms/components/FormMonthYearPicker.tsx +++ b/apps/portal/src/components/offers/forms/components/FormMonthYearPicker.tsx @@ -1,10 +1,9 @@ import type { ComponentProps } from 'react'; -import { forwardRef } from 'react'; import { useFormContext, useWatch } from 'react-hook-form'; import MonthYearPicker from '~/components/shared/MonthYearPicker'; -import { getCurrentMonth, getCurrentYear } from '../../util/time'; +import { getCurrentMonth, getCurrentYear } from '../../../../utils/offers/time'; type MonthYearPickerProps = ComponentProps; @@ -15,7 +14,7 @@ type FormMonthYearPickerProps = Omit< name: string; }; -function FormMonthYearPickerWithRef({ +export default function FormMonthYearPicker({ name, ...rest }: FormMonthYearPickerProps) { @@ -36,7 +35,3 @@ function FormMonthYearPickerWithRef({ /> ); } - -const FormMonthYearPicker = forwardRef(FormMonthYearPickerWithRef); - -export default FormMonthYearPicker; diff --git a/apps/portal/src/components/offers/forms/components/FormRadioList.tsx b/apps/portal/src/components/offers/forms/components/FormRadioList.tsx index 9ce3065d..5fbbd53d 100644 --- a/apps/portal/src/components/offers/forms/components/FormRadioList.tsx +++ b/apps/portal/src/components/offers/forms/components/FormRadioList.tsx @@ -1,5 +1,4 @@ import type { ComponentProps } from 'react'; -import { forwardRef } from 'react'; import { useFormContext } from 'react-hook-form'; import { RadioList } from '@tih/ui'; @@ -7,7 +6,7 @@ type RadioListProps = ComponentProps; type FormRadioListProps = Omit; -function FormRadioListWithRef({ name, ...rest }: FormRadioListProps) { +export default function FormRadioList({ name, ...rest }: FormRadioListProps) { const { setValue } = useFormContext(); return ( ); } - -const FormRadioList = forwardRef(FormRadioListWithRef); - -export default FormRadioList; diff --git a/apps/portal/src/components/offers/types.ts b/apps/portal/src/components/offers/types.ts index b514744e..82ee7140 100644 --- a/apps/portal/src/components/offers/types.ts +++ b/apps/portal/src/components/offers/types.ts @@ -1,10 +1,10 @@ /* eslint-disable no-shadow */ +import type { MonthYear } from '../shared/MonthYearPicker'; + /* * Offer Profile */ -import type { MonthYear } from '../shared/MonthYearPicker'; - export enum JobType { FullTime = 'FULLTIME', Internship = 'INTERNSHIP', @@ -20,7 +20,7 @@ export enum EducationBackgroundType { SelfTaught = 'Self-taught', } -type Money = { +export type Money = { currency: string; value: number; }; @@ -53,6 +53,13 @@ export type OfferDetailsFormData = { negotiationStrategy: string; }; +export type OfferDetailsPostData = Omit< + OfferDetailsFormData, + 'monthYearReceived' +> & { + monthYearReceived: Date; +}; + type SpecificYoe = { domain: string; yoe: number; @@ -88,8 +95,8 @@ type Education = { }; type BackgroundFormData = { - education: Education; - experience: Experience; + educations: Array; + experiences: Array; specificYoes: Array; totalYoe: number; }; @@ -98,3 +105,8 @@ export type SubmitOfferFormData = { background: BackgroundFormData; offers: Array; }; + +export type OfferPostData = { + background: BackgroundFormData; + offers: Array; +}; diff --git a/apps/portal/src/pages/offers/submit.tsx b/apps/portal/src/pages/offers/submit.tsx index 05b25555..c4949bb2 100644 --- a/apps/portal/src/pages/offers/submit.tsx +++ b/apps/portal/src/pages/offers/submit.tsx @@ -8,10 +8,16 @@ import BackgroundForm from '~/components/offers/forms/BackgroundForm'; import OfferAnalysis from '~/components/offers/forms/OfferAnalysis'; import OfferDetailsForm from '~/components/offers/forms/OfferDetailsForm'; import OfferProfileSave from '~/components/offers/forms/OfferProfileSave'; -import type { SubmitOfferFormData } from '~/components/offers/types'; -import { getCurrentMonth, getCurrentYear } from '~/components/offers/util/time'; +import type { + OfferDetailsFormData, + SubmitOfferFormData, +} from '~/components/offers/types'; import type { Month } from '~/components/shared/MonthYearPicker'; +import { cleanObject, removeInvalidMoneyData } from '~/utils/offers/form'; +import { getCurrentMonth, getCurrentYear } from '~/utils/offers/time'; +import { trpc } from '~/utils/trpc'; + function Breadcrumbs() { return (

@@ -82,12 +88,37 @@ export default function OffersSubmissionPage() { const previousStep = () => setFormStep(formStep - 1); - const onSubmit: SubmitHandler = async () => { + const createMutation = trpc.useMutation(['offers.profile.create'], { + onError(error) { + console.error(error.message); + }, + onSuccess() { + alert('offer profile submit success!'); + setFormStep(formStep + 1); + }, + }); + + const onSubmit: SubmitHandler = async (data) => { const result = await trigger(); if (!result) { return; } - setFormStep(formStep + 1); + data = removeInvalidMoneyData(data); + const background = cleanObject(data.background); + const offers = data.offers.map((offer: OfferDetailsFormData) => ({ + ...offer, + monthYearReceived: new Date( + offer.monthYearReceived.year, + offer.monthYearReceived.month, + ), + })); + const postData = { background, offers }; + + postData.background.specificYoes = data.background.specificYoes.filter( + (specificYoe) => specificYoe.domain && specificYoe.yoe > 0, + ); + + createMutation.mutate(postData); }; return ( diff --git a/apps/portal/src/components/offers/util/currency/CurrencyEnum.tsx b/apps/portal/src/utils/offers/currency/CurrencyEnum.tsx similarity index 100% rename from apps/portal/src/components/offers/util/currency/CurrencyEnum.tsx rename to apps/portal/src/utils/offers/currency/CurrencyEnum.tsx diff --git a/apps/portal/src/components/offers/util/currency/CurrencySelector.tsx b/apps/portal/src/utils/offers/currency/CurrencySelector.tsx similarity index 89% rename from apps/portal/src/components/offers/util/currency/CurrencySelector.tsx rename to apps/portal/src/utils/offers/currency/CurrencySelector.tsx index 2ebe5bd5..2ba883b4 100644 --- a/apps/portal/src/components/offers/util/currency/CurrencySelector.tsx +++ b/apps/portal/src/utils/offers/currency/CurrencySelector.tsx @@ -1,6 +1,6 @@ import { Select } from '@tih/ui'; -import { Currency } from '~/components/offers/util/currency/CurrencyEnum'; +import { Currency } from '~/utils/offers/currency/CurrencyEnum'; const currencyOptions = Object.entries(Currency).map(([key, value]) => ({ label: key, diff --git a/apps/portal/src/utils/offers/form.tsx b/apps/portal/src/utils/offers/form.tsx new file mode 100644 index 00000000..16def65d --- /dev/null +++ b/apps/portal/src/utils/offers/form.tsx @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +/** + * Removes empty objects, empty strings, `null`, `undefined`, and `NaN` values from an object. + * Does not remove empty arrays. + * @param object + * @returns object without empty values or objects. + */ +export function cleanObject(object: any) { + Object.entries(object).forEach(([k, v]) => { + if ((v && typeof v === 'object') || Array.isArray(v)) { + cleanObject(v); + } + if ( + (v && + typeof v === 'object' && + !Object.keys(v).length && + !Array.isArray(v)) || + v === null || + v === undefined || + v === '' || + v !== v + ) { + if (Array.isArray(object)) { + const index = object.indexOf(v); + object.splice(index, 1); + } else if (!(v instanceof Date)) { + delete object[k]; + } + } + }); + return object; +} + +/** + * Removes invalid money data from an object. + * If currency is present but value is not present, money object is removed. + * @param object + * @returns object without invalid money data. + */ +export function removeInvalidMoneyData(object: any) { + Object.entries(object).forEach(([k, v]) => { + if ((v && typeof v === 'object') || Array.isArray(v)) { + removeInvalidMoneyData(v); + } + if (k === 'currency') { + if (object.value === undefined) { + delete object[k]; + } else if (object.value === null || object.value !== object.value) { + delete object[k]; + delete object.value; + } + } + }); + return object; +} diff --git a/apps/portal/src/components/offers/util/time/index.tsx b/apps/portal/src/utils/offers/time.tsx similarity index 100% rename from apps/portal/src/components/offers/util/time/index.tsx rename to apps/portal/src/utils/offers/time.tsx