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: 'Microsoft',
+ value: 'cl93patjt0001txewkglfjsro',
+ },
+ {
+ label: 'Apple',
+ value: 'cl93patjt0002txewf3ug54m8',
},
{
label: 'Google',
- value: 'id-abc567',
+ value: 'cl93patjt0003txewyiaky7xx',
},
{
label: 'Meta',
- value: 'id-abc456',
- },
- {
- label: 'Shopee',
- value: 'id-abc345',
- },
- {
- label: 'Tik Tok',
- value: 'id-abc678',
+ value: 'cl93patjt0004txew88wkcqpu',
},
];
diff --git a/apps/portal/src/components/offers/forms/BackgroundForm.tsx b/apps/portal/src/components/offers/forms/BackgroundForm.tsx
index 0acbbe69..171823f1 100644
--- a/apps/portal/src/components/offers/forms/BackgroundForm.tsx
+++ b/apps/portal/src/components/offers/forms/BackgroundForm.tsx
@@ -1,9 +1,9 @@
import { useFormContext, useWatch } from 'react-hook-form';
import { Collapsible, RadioList } from '@tih/ui';
-import FormRadioList from './FormRadioList';
-import FormSelect from './FormSelect';
-import FormTextInput from './FormTextInput';
+import FormRadioList from './components/FormRadioList';
+import FormSelect from './components/FormSelect';
+import FormTextInput from './components/FormTextInput';
import {
companyOptions,
educationFieldOptions,
@@ -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/OfferAnalysis.tsx b/apps/portal/src/components/offers/forms/OfferAnalysis.tsx
index 3147bdc2..b0b7133c 100644
--- a/apps/portal/src/components/offers/forms/OfferAnalysis.tsx
+++ b/apps/portal/src/components/offers/forms/OfferAnalysis.tsx
@@ -86,8 +86,7 @@ export default function OfferAnalysis() {
Result
-
-
+
;
+ offers: Array;
}>();
return (
@@ -81,10 +82,7 @@ function FullTimeOfferDetailsForm({
required={true}
{...register(`offers.${index}.location`, { required: true })}
/>
-
@@ -110,6 +108,7 @@ function FullTimeOfferDetailsForm({
type="number"
{...register(`offers.${index}.job.totalCompensation.value`, {
required: true,
+ valueAsNumber: true,
})}
/>
@@ -121,7 +120,9 @@ function FullTimeOfferDetailsForm({
isLabelHidden={true}
label="Currency"
options={CURRENCY_OPTIONS}
- {...register(`offers.${index}.job.base.currency`)}
+ {...register(`offers.${index}.job.base.currency`, {
+ required: true,
+ })}
/>
}
endAddOnType="element"
@@ -131,7 +132,10 @@ function FullTimeOfferDetailsForm({
startAddOn="$"
startAddOnType="label"
type="number"
- {...register(`offers.${index}.job.base.value`)}
+ {...register(`offers.${index}.job.base.value`, {
+ required: true,
+ valueAsNumber: true,
+ })}
/>
}
endAddOnType="element"
@@ -150,7 +156,10 @@ function FullTimeOfferDetailsForm({
startAddOn="$"
startAddOnType="label"
type="number"
- {...register(`offers.${index}.job.bonus.value`)}
+ {...register(`offers.${index}.job.bonus.value`, {
+ required: true,
+ valueAsNumber: true,
+ })}
/>
@@ -161,7 +170,9 @@ function FullTimeOfferDetailsForm({
isLabelHidden={true}
label="Currency"
options={CURRENCY_OPTIONS}
- {...register(`offers.${index}.job.stocks.currency`)}
+ {...register(`offers.${index}.job.stocks.currency`, {
+ required: true,
+ })}
/>
}
endAddOnType="element"
@@ -171,7 +182,10 @@ function FullTimeOfferDetailsForm({
startAddOn="$"
startAddOnType="label"
type="number"
- {...register(`offers.${index}.job.stocks.value`)}
+ {...register(`offers.${index}.job.stocks.value`, {
+ required: true,
+ valueAsNumber: true,
+ })}
/>
@@ -251,7 +265,7 @@ function InternshipOfferDetailsForm({
remove,
}: InternshipOfferDetailsFormProps) {
const { register } = useFormContext<{
- offers: Array;
+ offers: Array;
}>();
return (
@@ -262,13 +276,19 @@ function InternshipOfferDetailsForm({
label="Title"
options={titleOptions}
required={true}
- {...register(`offers.${index}.job.title`)}
+ {...register(`offers.${index}.job.title`, {
+ minLength: 1,
+ required: true,
+ })}
/>
@@ -277,40 +297,44 @@ function InternshipOfferDetailsForm({
label="Company"
options={companyOptions}
required={true}
- value="Shopee"
- {...register(`offers.${index}.companyId`)}
+ {...register(`offers.${index}.companyId`, {
+ required: true,
+ })}
/>
-
-
+
+
+
@@ -321,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"
@@ -331,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
new file mode 100644
index 00000000..62967117
--- /dev/null
+++ b/apps/portal/src/components/offers/forms/components/FormMonthYearPicker.tsx
@@ -0,0 +1,37 @@
+import type { ComponentProps } from 'react';
+import { useFormContext, useWatch } from 'react-hook-form';
+
+import MonthYearPicker from '~/components/shared/MonthYearPicker';
+
+import { getCurrentMonth, getCurrentYear } from '../../../../utils/offers/time';
+
+type MonthYearPickerProps = ComponentProps
;
+
+type FormMonthYearPickerProps = Omit<
+ MonthYearPickerProps,
+ 'onChange' | 'value'
+> & {
+ name: string;
+};
+
+export default function FormMonthYearPicker({
+ name,
+ ...rest
+}: FormMonthYearPickerProps) {
+ const { setValue } = useFormContext();
+
+ const value = useWatch({
+ defaultValue: { month: getCurrentMonth(), year: getCurrentYear() },
+ name,
+ });
+
+ return (
+ {
+ setValue(name, val);
+ }}
+ />
+ );
+}
diff --git a/apps/portal/src/components/offers/forms/FormRadioList.tsx b/apps/portal/src/components/offers/forms/components/FormRadioList.tsx
similarity index 69%
rename from apps/portal/src/components/offers/forms/FormRadioList.tsx
rename to apps/portal/src/components/offers/forms/components/FormRadioList.tsx
index 9ce3065d..5fbbd53d 100644
--- a/apps/portal/src/components/offers/forms/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/forms/FormSelect.tsx b/apps/portal/src/components/offers/forms/components/FormSelect.tsx
similarity index 100%
rename from apps/portal/src/components/offers/forms/FormSelect.tsx
rename to apps/portal/src/components/offers/forms/components/FormSelect.tsx
diff --git a/apps/portal/src/components/offers/forms/FormTextArea.tsx b/apps/portal/src/components/offers/forms/components/FormTextArea.tsx
similarity index 100%
rename from apps/portal/src/components/offers/forms/FormTextArea.tsx
rename to apps/portal/src/components/offers/forms/components/FormTextArea.tsx
diff --git a/apps/portal/src/components/offers/forms/FormTextInput.tsx b/apps/portal/src/components/offers/forms/components/FormTextInput.tsx
similarity index 100%
rename from apps/portal/src/components/offers/forms/FormTextInput.tsx
rename to apps/portal/src/components/offers/forms/components/FormTextInput.tsx
diff --git a/apps/portal/src/components/offers/types.ts b/apps/portal/src/components/offers/types.ts
index 17bda427..82ee7140 100644
--- a/apps/portal/src/components/offers/types.ts
+++ b/apps/portal/src/components/offers/types.ts
@@ -1,4 +1,6 @@
/* eslint-disable no-shadow */
+import type { MonthYear } from '../shared/MonthYearPicker';
+
/*
* Offer Profile
*/
@@ -18,7 +20,7 @@ export enum EducationBackgroundType {
SelfTaught = 'Self-taught',
}
-type Money = {
+export type Money = {
currency: string;
value: number;
};
@@ -33,16 +35,6 @@ type FullTimeJobData = {
totalCompensation: Money;
};
-export type FullTimeOfferFormData = {
- comments: string;
- companyId: string;
- job: FullTimeJobData;
- jobType: string;
- location: string;
- monthYearReceived: string;
- negotiationStrategy: string;
-};
-
type InternshipJobData = {
internshipCycle: string;
monthlySalary: Money;
@@ -51,17 +43,22 @@ type InternshipJobData = {
title: string;
};
-export type InternshipOfferFormData = {
+export type OfferDetailsFormData = {
comments: string;
companyId: string;
- job: InternshipJobData;
+ job: FullTimeJobData | InternshipJobData;
jobType: string;
location: string;
- monthYearReceived: string;
+ monthYearReceived: MonthYear;
negotiationStrategy: string;
};
-type OfferDetailsFormData = FullTimeOfferFormData | InternshipOfferFormData;
+export type OfferDetailsPostData = Omit<
+ OfferDetailsFormData,
+ 'monthYearReceived'
+> & {
+ monthYearReceived: Date;
+};
type SpecificYoe = {
domain: string;
@@ -98,8 +95,8 @@ type Education = {
};
type BackgroundFormData = {
- education: Education;
- experience: Experience;
+ educations: Array;
+ experiences: Array;
specificYoes: Array;
totalYoe: number;
};
@@ -108,3 +105,8 @@ export type SubmitOfferFormData = {
background: BackgroundFormData;
offers: Array;
};
+
+export type OfferPostData = {
+ background: BackgroundFormData;
+ offers: Array;
+};
diff --git a/apps/portal/src/components/offers/util/time/index.tsx b/apps/portal/src/components/offers/util/time/index.tsx
deleted file mode 100644
index 86f21ab9..00000000
--- a/apps/portal/src/components/offers/util/time/index.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export function formatDate(value: Date | number | string) {
- const date = new Date(value);
- // Const day = date.toLocaleString('default', { day: '2-digit' });
- const month = date.toLocaleString('default', { month: 'short' });
- const year = date.toLocaleString('default', { year: 'numeric' });
- return `${month} ${year}`;
-}
diff --git a/apps/portal/src/pages/offers/index.tsx b/apps/portal/src/pages/offers/index.tsx
index 27166716..c4dfe8b5 100644
--- a/apps/portal/src/pages/offers/index.tsx
+++ b/apps/portal/src/pages/offers/index.tsx
@@ -3,9 +3,11 @@ import { Select } from '@tih/ui';
import OffersTable from '~/components/offers/OffersTable';
import OffersTitle from '~/components/offers/OffersTitle';
+import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
export default function OffersHomePage() {
const [jobTitleFilter, setjobTitleFilter] = useState('Software engineers');
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [companyFilter, setCompanyFilter] = useState('All companies');
return (
@@ -13,7 +15,7 @@ export default function OffersHomePage() {
-
+
Viewing offers for
diff --git a/apps/portal/src/pages/offers/submit.tsx b/apps/portal/src/pages/offers/submit.tsx
index b85e098c..c4949bb2 100644
--- a/apps/portal/src/pages/offers/submit.tsx
+++ b/apps/portal/src/pages/offers/submit.tsx
@@ -8,7 +8,15 @@ 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 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 (
@@ -23,53 +31,94 @@ const defaultOfferValues = {
{
comments: '',
companyId: '',
- job: {
- base: {
- currency: 'USD',
- value: 0,
- },
- bonus: {
- currency: 'USD',
- value: 0,
- },
- level: '',
- specialization: '',
- stocks: {
- currency: 'USD',
- value: 0,
- },
- title: '',
- totalCompensation: {
- currency: 'USD',
- value: 0,
- },
- },
+ job: {},
jobType: 'FULLTIME',
location: '',
- monthYearReceived: '',
+ monthYearReceived: {
+ month: getCurrentMonth() as Month,
+ year: getCurrentYear(),
+ },
negotiationStrategy: '',
},
],
};
+type FormStep = {
+ component: JSX.Element;
+ hasNext: boolean;
+ hasPrevious: boolean;
+};
+
export default function OffersSubmissionPage() {
const [formStep, setFormStep] = useState(0);
const formMethods = useForm
({
defaultValues: defaultOfferValues,
+ mode: 'all',
});
+ const { handleSubmit, trigger } = formMethods;
- const nextStep = () => setFormStep(formStep + 1);
- const previousStep = () => setFormStep(formStep - 1);
-
- const formComponents = [
- ,
- ,
- ,
- ,
+ const formSteps: Array = [
+ {
+ component: ,
+ hasNext: true,
+ hasPrevious: false,
+ },
+ {
+ component: ,
+ hasNext: false,
+ hasPrevious: true,
+ },
+ { component: , hasNext: true, hasPrevious: false },
+ {
+ component: ,
+ hasNext: false,
+ hasPrevious: false,
+ },
];
- const onSubmit: SubmitHandler = async () => {
- nextStep();
+ const nextStep = async (currStep: number) => {
+ if (currStep === 0) {
+ const result = await trigger('offers');
+ if (!result) {
+ return;
+ }
+ }
+ setFormStep(formStep + 1);
+ };
+
+ const previousStep = () => setFormStep(formStep - 1);
+
+ 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;
+ }
+ 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 (
@@ -78,16 +127,17 @@ export default function OffersSubmissionPage() {