@@ -95,19 +128,21 @@ function FullTimeOfferDetailsForm({
label="Currency"
options={CURRENCY_OPTIONS}
{...register(`offers.${index}.job.totalCompensation.currency`, {
- required: true,
+ required: FieldError.Required,
})}
/>
}
endAddOnType="element"
+ errorMessage={offerFields?.job?.totalCompensation?.value?.message}
label="Total Compensation (Annual)"
- placeholder="0.00"
+ placeholder="0"
required={true}
startAddOn="$"
startAddOnType="label"
type="number"
{...register(`offers.${index}.job.totalCompensation.value`, {
- required: true,
+ min: { message: FieldError.NonNegativeNumber, value: 0 },
+ required: FieldError.Required,
valueAsNumber: true,
})}
/>
@@ -121,19 +156,21 @@ function FullTimeOfferDetailsForm({
label="Currency"
options={CURRENCY_OPTIONS}
{...register(`offers.${index}.job.base.currency`, {
- required: true,
+ required: FieldError.Required,
})}
/>
}
endAddOnType="element"
+ errorMessage={offerFields?.job?.base?.value?.message}
label="Base Salary (Annual)"
- placeholder="0.00"
+ placeholder="0"
required={true}
startAddOn="$"
startAddOnType="label"
type="number"
{...register(`offers.${index}.job.base.value`, {
- required: true,
+ min: { message: FieldError.NonNegativeNumber, value: 0 },
+ required: FieldError.Required,
valueAsNumber: true,
})}
/>
@@ -145,19 +182,21 @@ function FullTimeOfferDetailsForm({
label="Currency"
options={CURRENCY_OPTIONS}
{...register(`offers.${index}.job.bonus.currency`, {
- required: true,
+ required: FieldError.Required,
})}
/>
}
endAddOnType="element"
+ errorMessage={offerFields?.job?.bonus?.value?.message}
label="Bonus (Annual)"
- placeholder="0.00"
+ placeholder="0"
required={true}
startAddOn="$"
startAddOnType="label"
type="number"
{...register(`offers.${index}.job.bonus.value`, {
- required: true,
+ min: { message: FieldError.NonNegativeNumber, value: 0 },
+ required: FieldError.Required,
valueAsNumber: true,
})}
/>
@@ -171,19 +210,21 @@ function FullTimeOfferDetailsForm({
label="Currency"
options={CURRENCY_OPTIONS}
{...register(`offers.${index}.job.stocks.currency`, {
- required: true,
+ required: FieldError.Required,
})}
/>
}
endAddOnType="element"
+ errorMessage={offerFields?.job?.stocks?.value?.message}
label="Stocks (Annual)"
- placeholder="0.00"
+ placeholder="0"
required={true}
startAddOn="$"
startAddOnType="label"
type="number"
{...register(`offers.${index}.job.stocks.value`, {
- required: true,
+ min: { message: FieldError.NonNegativeNumber, value: 0 },
+ required: FieldError.Required,
valueAsNumber: true,
})}
/>
@@ -208,7 +249,7 @@ function FullTimeOfferDetailsForm({
icon={TrashIcon}
label="Delete"
variant="secondary"
- onClick={() => remove(index)}
+ onClick={() => setDialogOpen(true)}
/>
)}
@@ -216,125 +257,103 @@ function FullTimeOfferDetailsForm({
);
}
-type OfferDetailsFormArrayProps = Readonly<{
- fieldArrayValues: UseFieldArrayReturn
;
- jobType: JobType;
-}>;
-
-function OfferDetailsFormArray({
- fieldArrayValues,
- jobType,
-}: OfferDetailsFormArrayProps) {
- const { append, remove, fields } = fieldArrayValues;
- return (
-
- {fields.map((item, index) =>
- jobType === JobType.FullTime ? (
-
- ) : (
-
- ),
- )}
-
- );
-}
-
type InternshipOfferDetailsFormProps = Readonly<{
index: number;
- remove: UseFieldArrayRemove;
+ setDialogOpen: (isOpen: boolean) => void;
}>;
function InternshipOfferDetailsForm({
index,
- remove,
+ setDialogOpen,
}: InternshipOfferDetailsFormProps) {
- const { register } = useFormContext<{
- offers: Array;
+ const { register, formState } = useFormContext<{
+ offers: Array;
}>();
+ const offerFields = formState.errors.offers?.[index];
+
return (
-
-
Date received:
+
@@ -346,19 +365,21 @@ function InternshipOfferDetailsForm({
label="Currency"
options={CURRENCY_OPTIONS}
{...register(`offers.${index}.job.monthlySalary.currency`, {
- required: true,
+ required: FieldError.Required,
})}
/>
}
endAddOnType="element"
+ errorMessage={offerFields?.job?.monthlySalary?.value?.message}
label="Salary (Monthly)"
- placeholder="0.00"
+ placeholder="0"
required={true}
startAddOn="$"
startAddOnType="label"
type="number"
{...register(`offers.${index}.job.monthlySalary.value`, {
- required: true,
+ min: { message: FieldError.NonNegativeNumber, value: 0 },
+ required: FieldError.Required,
valueAsNumber: true,
})}
/>
@@ -383,7 +404,9 @@ function InternshipOfferDetailsForm({
icon={TrashIcon}
label="Delete"
variant="secondary"
- onClick={() => remove(index)}
+ onClick={() => {
+ setDialogOpen(true);
+ }}
/>
)}
@@ -391,20 +414,97 @@ function InternshipOfferDetailsForm({
);
}
+type OfferDetailsFormArrayProps = Readonly<{
+ fieldArrayValues: UseFieldArrayReturn
;
+ jobType: JobType;
+}>;
+
+function OfferDetailsFormArray({
+ fieldArrayValues,
+ jobType,
+}: OfferDetailsFormArrayProps) {
+ const { append, remove, fields } = fieldArrayValues;
+ const [isDialogOpen, setDialogOpen] = useState(false);
+
+ return (
+
+ {fields.map((item, index) => {
+ return (
+
+ {jobType === JobType.FullTime ? (
+
+ ) : (
+
+ )}
+
+
+ );
+ })}
+
+ );
+}
+
export default function OfferDetailsForm() {
const [jobType, setJobType] = useState(JobType.FullTime);
+ const [isDialogOpen, setDialogOpen] = useState(false);
const { control, register } = useFormContext();
const fieldArrayValues = useFieldArray({ control, name: 'offers' });
- const changeJobType = (jobTypeChosen: JobType) => () => {
- if (jobType === jobTypeChosen) {
- return;
+ const toggleJobType = () => {
+ if (jobType === JobType.FullTime) {
+ setJobType(JobType.Internship);
+ } else {
+ setJobType(JobType.FullTime);
}
-
- setJobType(jobTypeChosen);
fieldArrayValues.remove();
};
+ const switchJobTypeLabel = () =>
+ jobType === JobType.FullTime
+ ? JobTypeLabel.INTERNSHIP
+ : JobTypeLabel.FULLTIME;
+
return (
@@ -414,20 +514,30 @@ export default function OfferDetailsForm() {
@@ -436,6 +546,32 @@ export default function OfferDetailsForm() {
fieldArrayValues={fieldArrayValues}
jobType={jobType}
/>
+
);
}
diff --git a/apps/portal/src/components/offers/forms/components/FormMonthYearPicker.tsx b/apps/portal/src/components/offers/forms/components/FormMonthYearPicker.tsx
index 62967117..2b6c414a 100644
--- a/apps/portal/src/components/offers/forms/components/FormMonthYearPicker.tsx
+++ b/apps/portal/src/components/offers/forms/components/FormMonthYearPicker.tsx
@@ -1,4 +1,5 @@
import type { ComponentProps } from 'react';
+import { forwardRef } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import MonthYearPicker from '~/components/shared/MonthYearPicker';
@@ -14,7 +15,7 @@ type FormMonthYearPickerProps = Omit<
name: string;
};
-export default function FormMonthYearPicker({
+function FormMonthYearPickerWithRef({
name,
...rest
}: FormMonthYearPickerProps) {
@@ -35,3 +36,7 @@ export default function FormMonthYearPicker({
/>
);
}
+
+const FormMonthYearPicker = forwardRef(FormMonthYearPickerWithRef);
+
+export default FormMonthYearPicker;
diff --git a/apps/portal/src/components/offers/types.ts b/apps/portal/src/components/offers/types.ts
index dfc46b6b..3ddf56b4 100644
--- a/apps/portal/src/components/offers/types.ts
+++ b/apps/portal/src/components/offers/types.ts
@@ -1,4 +1,3 @@
-/* eslint-disable no-shadow */
import type { MonthYear } from '~/components/shared/MonthYearPicker';
/*
@@ -10,6 +9,11 @@ export enum JobType {
Internship = 'INTERNSHIP',
}
+export const JobTypeLabel = {
+ FULLTIME: 'Full-time',
+ INTERNSHIP: 'Internship',
+};
+
export enum EducationBackgroundType {
Bachelor = 'Bachelor',
Diploma = 'Diploma',
@@ -43,16 +47,27 @@ type InternshipJobData = {
title: string;
};
-export type OfferDetailsFormData = {
+type OfferDetailsGeneralData = {
comments: string;
companyId: string;
- job: FullTimeJobData | InternshipJobData;
jobType: string;
location: string;
monthYearReceived: MonthYear;
negotiationStrategy: string;
};
+export type FullTimeOfferDetailsFormData = OfferDetailsGeneralData & {
+ job: FullTimeJobData;
+};
+
+export type InternshipOfferDetailsFormData = OfferDetailsGeneralData & {
+ job: InternshipJobData;
+};
+
+export type OfferDetailsFormData =
+ | FullTimeOfferDetailsFormData
+ | InternshipOfferDetailsFormData;
+
export type OfferDetailsPostData = Omit<
OfferDetailsFormData,
'monthYearReceived'
diff --git a/apps/portal/src/pages/offers/submit.tsx b/apps/portal/src/pages/offers/submit.tsx
index 2ce5fd36..f0a46984 100644
--- a/apps/portal/src/pages/offers/submit.tsx
+++ b/apps/portal/src/pages/offers/submit.tsx
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import { useRef, useState } from 'react';
import type { SubmitHandler } from 'react-hook-form';
import { FormProvider, useForm } from 'react-hook-form';
import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/20/solid';
@@ -51,6 +51,9 @@ type FormStep = {
export default function OffersSubmissionPage() {
const [formStep, setFormStep] = useState(0);
+ const pageRef = useRef(null);
+ const scrollToTop = () =>
+ pageRef.current?.scrollTo({ behavior: 'smooth', top: 0 });
const formMethods = useForm({
defaultValues: defaultOfferValues,
mode: 'all',
@@ -94,9 +97,13 @@ export default function OffersSubmissionPage() {
}
}
setFormStep(formStep + 1);
+ scrollToTop();
};
- const previousStep = () => setFormStep(formStep - 1);
+ const previousStep = () => {
+ setFormStep(formStep - 1);
+ scrollToTop();
+ };
const createMutation = trpc.useMutation(['offers.profile.create'], {
onError(error) {
@@ -105,6 +112,7 @@ export default function OffersSubmissionPage() {
onSuccess() {
alert('offer profile submit success!');
setFormStep(formStep + 1);
+ scrollToTop();
},
});
@@ -135,7 +143,7 @@ export default function OffersSubmissionPage() {
};
return (
-
+
diff --git a/packages/eslint-config-tih/index.js b/packages/eslint-config-tih/index.js
index 8ee2fa69..3894879c 100644
--- a/packages/eslint-config-tih/index.js
+++ b/packages/eslint-config-tih/index.js
@@ -43,7 +43,7 @@ module.exports = {
'no-else-return': [ERROR, { allowElseIf: false }],
'no-extra-boolean-cast': ERROR,
'no-lonely-if': ERROR,
- 'no-shadow': ERROR,
+ 'no-shadow': OFF,
'no-unused-vars': OFF, // Use @typescript-eslint/no-unused-vars instead.
'object-shorthand': ERROR,
'one-var': [ERROR, 'never'],
@@ -100,6 +100,7 @@ module.exports = {
'@typescript-eslint/no-for-in-array': ERROR,
'@typescript-eslint/no-non-null-assertion': OFF,
'@typescript-eslint/no-unused-vars': [ERROR, { argsIgnorePattern: '^_' }],
+ '@typescript-eslint/no-shadow': ERROR,
'@typescript-eslint/prefer-optional-chain': ERROR,
'@typescript-eslint/require-array-sort-compare': ERROR,
'@typescript-eslint/restrict-plus-operands': ERROR,