commit 7176414e4d6004b1ae0257c167b765ba22b6db42 Author: Ai Ling <hong-ailing@hotmail.com> Date: Mon Oct 10 00:07:24 2022 +0800 [offers][feat] add offers submission formpull/348/head
parent
670777474b
commit
6b4dc5050c
@ -1,3 +1,14 @@
|
|||||||
export default function OffersTitle() {
|
export default function OffersTitle() {
|
||||||
return <h1 className="text-center text-4xl font-bold">Offers Research</h1>;
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex items-end justify-center">
|
||||||
|
<h1 className="text-center text-4xl font-bold">
|
||||||
|
Technical Handbook Offers Repo
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center text-2xl">
|
||||||
|
Benchmark your offers and discuss them with the community
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,309 @@
|
|||||||
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
import { Collapsible, RadioList } from '@tih/ui';
|
||||||
|
|
||||||
|
import FormRadioList from './FormRadioList';
|
||||||
|
import FormSelect from './FormSelect';
|
||||||
|
import FormTextInput from './FormTextInput';
|
||||||
|
import {
|
||||||
|
companyOptions,
|
||||||
|
currencyOptions,
|
||||||
|
locationOptions,
|
||||||
|
titleOptions,
|
||||||
|
} from './OfferDetailsForm';
|
||||||
|
|
||||||
|
function YoeSection() {
|
||||||
|
const { register } = useFormContext();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h6 className="mb-2 text-left text-xl font-medium text-gray-400">
|
||||||
|
Years of Experience (YOE)
|
||||||
|
</h6>
|
||||||
|
|
||||||
|
<div className="mb-5 rounded-lg border border-gray-200 px-10 py-5">
|
||||||
|
<div className="mb-2 grid grid-cols-3 space-x-3">
|
||||||
|
<FormTextInput
|
||||||
|
label="Total YOE"
|
||||||
|
type="number"
|
||||||
|
{...register(`background.totalYoe`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 space-x-3">
|
||||||
|
<Collapsible label="Add specific YOEs by domain">
|
||||||
|
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||||
|
<FormTextInput
|
||||||
|
label="Specific YOE 1"
|
||||||
|
type="number"
|
||||||
|
{...register(`background.specificYoes.0.yoe`)}
|
||||||
|
/>
|
||||||
|
<FormTextInput
|
||||||
|
label="Specific Domain 1"
|
||||||
|
placeholder="e.g. Frontend"
|
||||||
|
{...register(`background.specificYoes.0.domain`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||||
|
<FormTextInput
|
||||||
|
label="Specific YOE 2"
|
||||||
|
type="number"
|
||||||
|
{...register(`background.specificYoes.1.yoe`)}
|
||||||
|
/>
|
||||||
|
<FormTextInput
|
||||||
|
label="Specific Domain 2"
|
||||||
|
placeholder="e.g. Backend"
|
||||||
|
{...register(`background.specificYoes.1.domain`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Collapsible>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FullTimeJobFields() {
|
||||||
|
const { register } = useFormContext();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Title"
|
||||||
|
options={titleOptions}
|
||||||
|
{...register(`background.experience.title`)}
|
||||||
|
/>
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Company"
|
||||||
|
options={companyOptions}
|
||||||
|
{...register(`background.experience.companyId`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5 grid grid-cols-1 space-x-3">
|
||||||
|
<FormTextInput
|
||||||
|
endAddOn={
|
||||||
|
<FormSelect
|
||||||
|
borderStyle="borderless"
|
||||||
|
isLabelHidden={true}
|
||||||
|
label="Currency"
|
||||||
|
options={currencyOptions}
|
||||||
|
{...register(`background.experience.totalCompensation.currency`)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
endAddOnType="element"
|
||||||
|
label="Total Compensation (Annual)"
|
||||||
|
placeholder="0.00"
|
||||||
|
startAddOn="$"
|
||||||
|
startAddOnType="label"
|
||||||
|
type="number"
|
||||||
|
{...register(`background.experience.totalCompensation.value`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Collapsible label="Add more details">
|
||||||
|
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||||
|
<FormTextInput
|
||||||
|
label="Focus / Specialization"
|
||||||
|
placeholder="e.g. Front End"
|
||||||
|
{...register(`background.experience.specialization`)}
|
||||||
|
/>
|
||||||
|
<FormTextInput
|
||||||
|
label="Level"
|
||||||
|
placeholder="e.g. L4, Junior"
|
||||||
|
{...register(`background.experience.level`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Location"
|
||||||
|
options={locationOptions}
|
||||||
|
{...register(`background.experience.location`)}
|
||||||
|
/>
|
||||||
|
<FormTextInput
|
||||||
|
label="Duration (months)"
|
||||||
|
type="number"
|
||||||
|
{...register(`background.experience.durationInMonths`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Collapsible>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function InternshipJobFields() {
|
||||||
|
const { register } = useFormContext();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Title"
|
||||||
|
options={titleOptions}
|
||||||
|
{...register(`background.experience.title`)}
|
||||||
|
/>
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Company"
|
||||||
|
options={companyOptions}
|
||||||
|
{...register(`background.experience.company`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5 grid grid-cols-1 space-x-3">
|
||||||
|
<FormTextInput
|
||||||
|
endAddOn={
|
||||||
|
<FormSelect
|
||||||
|
borderStyle="borderless"
|
||||||
|
isLabelHidden={true}
|
||||||
|
label="Currency"
|
||||||
|
options={currencyOptions}
|
||||||
|
{...register(`background.experience.monthlySalary.currency`)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
endAddOnType="element"
|
||||||
|
label="Salary (Monthly)"
|
||||||
|
placeholder="0.00"
|
||||||
|
startAddOn="$"
|
||||||
|
startAddOnType="label"
|
||||||
|
type="number"
|
||||||
|
{...register(`background.experience.monthlySalary.value`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Collapsible label="Add more details">
|
||||||
|
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||||
|
<FormTextInput
|
||||||
|
label="Focus / Specialization"
|
||||||
|
placeholder="e.g. Front End"
|
||||||
|
{...register(`background.experience.specialization`)}
|
||||||
|
/>
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Location"
|
||||||
|
options={locationOptions}
|
||||||
|
{...register(`background.experience.location`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Collapsible>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CurrentJobSection() {
|
||||||
|
const { register, getValues } = useFormContext();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h6 className="mb-2 text-left text-xl font-medium text-gray-400">
|
||||||
|
Current / Previous Job
|
||||||
|
</h6>
|
||||||
|
<div className="mb-5 rounded-lg border border-gray-200 px-10 py-5">
|
||||||
|
<div className="mb-5">
|
||||||
|
<FormRadioList
|
||||||
|
defaultValue="Full-time"
|
||||||
|
isLabelHidden={true}
|
||||||
|
label="Job Type"
|
||||||
|
orientation="horizontal"
|
||||||
|
{...register('background.experience.jobType')}>
|
||||||
|
<RadioList.Item
|
||||||
|
key="Full-time"
|
||||||
|
label="Full-time"
|
||||||
|
value="Full-time"
|
||||||
|
/>
|
||||||
|
<RadioList.Item
|
||||||
|
key="Internship"
|
||||||
|
label="Internship"
|
||||||
|
value="Internship"
|
||||||
|
/>
|
||||||
|
</FormRadioList>
|
||||||
|
</div>
|
||||||
|
{getValues('background.experience.jobType') === 'Full-time' ? (
|
||||||
|
<FullTimeJobFields />
|
||||||
|
) : (
|
||||||
|
<InternshipJobFields />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const educationLevelOptions = [
|
||||||
|
{
|
||||||
|
label: 'Bachelor',
|
||||||
|
value: 'Bachelor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Masters',
|
||||||
|
value: 'Masters',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Doctorate',
|
||||||
|
value: 'Doctorate',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const fieldOptions = [
|
||||||
|
{
|
||||||
|
label: 'Computer Science',
|
||||||
|
value: 'Computer Science',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Information Security',
|
||||||
|
value: 'Information Security',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Business Analytics',
|
||||||
|
value: 'Business Analytics',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function EducationSection() {
|
||||||
|
const { register } = useFormContext();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h6 className="mb-2 text-left text-xl font-medium text-gray-400">
|
||||||
|
Education
|
||||||
|
</h6>
|
||||||
|
<div className="mb-5 rounded-lg border border-gray-200 px-10 py-5">
|
||||||
|
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Education Level"
|
||||||
|
options={educationLevelOptions}
|
||||||
|
{...register(`background.education.type`)}
|
||||||
|
/>
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Field"
|
||||||
|
options={fieldOptions}
|
||||||
|
{...register(`background.education.field`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Collapsible label="Add more details">
|
||||||
|
<div className="mb-5 grid grid-cols-3 space-x-3">
|
||||||
|
<FormTextInput
|
||||||
|
label="School"
|
||||||
|
placeholder="e.g. National University of Singapore"
|
||||||
|
{...register(`background.experience.specialization`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Collapsible>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BackgroundForm() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h5 className="mb-2 text-center text-4xl font-bold text-gray-900">
|
||||||
|
Help us better gauge your offers
|
||||||
|
</h5>
|
||||||
|
<h6 className="mb-8 text-center text-xl font-light ">
|
||||||
|
This section is optional, but your background information helps us
|
||||||
|
benchmark your offers.
|
||||||
|
</h6>
|
||||||
|
<div>
|
||||||
|
<YoeSection />
|
||||||
|
<CurrentJobSection />
|
||||||
|
<EducationSection />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import type { ComponentProps } from 'react';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
import { RadioList } from '@tih/ui';
|
||||||
|
|
||||||
|
type RadioListProps = ComponentProps<typeof RadioList>;
|
||||||
|
|
||||||
|
type FormRadioListProps = Omit<RadioListProps, 'onChange'>;
|
||||||
|
|
||||||
|
function FormRadioListWithRef(props: FormRadioListProps) {
|
||||||
|
const { name, ...rest } = props;
|
||||||
|
const { setValue } = useFormContext();
|
||||||
|
return (
|
||||||
|
<RadioList
|
||||||
|
{...(rest as RadioListProps)}
|
||||||
|
name={name}
|
||||||
|
onChange={(val) => setValue(name || '', val)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormRadioList = forwardRef(FormRadioListWithRef);
|
||||||
|
|
||||||
|
export default FormRadioList;
|
@ -0,0 +1,27 @@
|
|||||||
|
import type { ComponentProps, ForwardedRef } from 'react';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
import { Select } from '@tih/ui';
|
||||||
|
|
||||||
|
type SelectProps = ComponentProps<typeof Select>;
|
||||||
|
|
||||||
|
type FormSelectProps = Omit<SelectProps, 'onChange'>;
|
||||||
|
|
||||||
|
function FormSelectWithRef(
|
||||||
|
props: FormSelectProps,
|
||||||
|
ref?: ForwardedRef<HTMLSelectElement>,
|
||||||
|
) {
|
||||||
|
const { name } = props;
|
||||||
|
const { setValue } = useFormContext();
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
{...(props as SelectProps)}
|
||||||
|
ref={ref}
|
||||||
|
onChange={(val) => setValue(name || '', val)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormSelect = forwardRef(FormSelectWithRef);
|
||||||
|
|
||||||
|
export default FormSelect;
|
@ -0,0 +1,28 @@
|
|||||||
|
import type { ComponentProps, ForwardedRef } from 'react';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
import type { UseFormRegisterReturn } from 'react-hook-form';
|
||||||
|
|
||||||
|
import { TextArea } from '~/../../../packages/ui/dist';
|
||||||
|
|
||||||
|
type TextAreaProps = ComponentProps<typeof TextArea>;
|
||||||
|
|
||||||
|
type FormTextAreaProps = Omit<TextAreaProps, 'onChange'> &
|
||||||
|
Pick<UseFormRegisterReturn<never>, 'onChange'>;
|
||||||
|
|
||||||
|
function FormTextAreaWithRef(
|
||||||
|
props: FormTextAreaProps,
|
||||||
|
ref?: ForwardedRef<HTMLTextAreaElement>,
|
||||||
|
) {
|
||||||
|
const { onChange, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<TextArea
|
||||||
|
{...(rest as TextAreaProps)}
|
||||||
|
ref={ref}
|
||||||
|
onChange={(_, event) => onChange(event)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormTextArea = forwardRef(FormTextAreaWithRef);
|
||||||
|
|
||||||
|
export default FormTextArea;
|
@ -0,0 +1,27 @@
|
|||||||
|
import type { ComponentProps, ForwardedRef } from 'react';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
import type { UseFormRegisterReturn } from 'react-hook-form';
|
||||||
|
import { TextInput } from '@tih/ui';
|
||||||
|
|
||||||
|
type TextInputProps = ComponentProps<typeof TextInput>;
|
||||||
|
|
||||||
|
type FormTextInputProps = Omit<TextInputProps, 'onChange'> &
|
||||||
|
Pick<UseFormRegisterReturn<never>, 'onChange'>;
|
||||||
|
|
||||||
|
function FormTextInputWithRef(
|
||||||
|
props: FormTextInputProps,
|
||||||
|
ref?: ForwardedRef<HTMLInputElement>,
|
||||||
|
) {
|
||||||
|
const { onChange, ...rest } = props;
|
||||||
|
return (
|
||||||
|
<TextInput
|
||||||
|
{...(rest as TextInputProps)}
|
||||||
|
ref={ref}
|
||||||
|
onChange={(_, event) => onChange(event)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormTextInput = forwardRef(FormTextInputWithRef);
|
||||||
|
|
||||||
|
export default FormTextInput;
|
@ -0,0 +1,102 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { UserCircleIcon } from '@heroicons/react/20/solid';
|
||||||
|
|
||||||
|
import { HorizontalDivider, Tabs } from '~/../../../packages/ui/dist';
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
label: 'Overall',
|
||||||
|
value: 'overall',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Shopee',
|
||||||
|
value: 'company-id',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function OfferPercentileAnalysis() {
|
||||||
|
const result = {
|
||||||
|
company: 'Shopee',
|
||||||
|
numberOfOffers: 105,
|
||||||
|
percentile: 56,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function OfferProfileCard() {
|
||||||
|
return (
|
||||||
|
<div className="my-5 block rounded-lg border p-4">
|
||||||
|
<div className="grid grid-flow-col grid-cols-12 gap-x-10">
|
||||||
|
<div className="col-span-1">
|
||||||
|
<UserCircleIcon width={50} />
|
||||||
|
</div>
|
||||||
|
<div className="col-span-10">
|
||||||
|
<p className="text-sm font-semibold">profile-name</p>
|
||||||
|
<p className="text-xs ">Previous company: Meta, Singapore</p>
|
||||||
|
<p className="text-xs ">YOE: 4 years</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<HorizontalDivider />
|
||||||
|
<div className="grid grid-flow-col grid-cols-2 gap-x-10">
|
||||||
|
<div className="col-span-1 row-span-3">
|
||||||
|
<p className="text-sm font-semibold">Software engineer</p>
|
||||||
|
<p className="text-xs ">Company: Google, Singapore</p>
|
||||||
|
<p className="text-xs ">Level: G4</p>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-1 row-span-3">
|
||||||
|
<p className="text-end text-sm">Sept 2022</p>
|
||||||
|
<p className="text-end text-xl">$125,000 / year</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TopOfferProfileList() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<OfferProfileCard />
|
||||||
|
<OfferProfileCard />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function OfferAnalysisContent() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<OfferPercentileAnalysis />
|
||||||
|
<TopOfferProfileList />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function OfferAnalysis() {
|
||||||
|
const [tab, setTab] = useState('Overall');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h5 className="mb-2 text-center text-4xl font-bold text-gray-900">
|
||||||
|
Result
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div className="mx-40">
|
||||||
|
<Tabs
|
||||||
|
label="Result Navigation"
|
||||||
|
tabs={tabs}
|
||||||
|
value={tab}
|
||||||
|
onChange={setTab}
|
||||||
|
/>
|
||||||
|
<HorizontalDivider className="mb-5" />
|
||||||
|
<OfferAnalysisContent />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,481 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import type {
|
||||||
|
FieldValues,
|
||||||
|
UseFieldArrayRemove,
|
||||||
|
UseFieldArrayReturn,
|
||||||
|
} from 'react-hook-form';
|
||||||
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
import { useFieldArray } from 'react-hook-form';
|
||||||
|
import { PlusIcon } from '@heroicons/react/20/solid';
|
||||||
|
import { TrashIcon } from '@heroicons/react/24/outline';
|
||||||
|
import { Button } from '@tih/ui';
|
||||||
|
|
||||||
|
import FormSelect from './FormSelect';
|
||||||
|
import FormTextArea from './FormTextArea';
|
||||||
|
import FormTextInput from './FormTextInput';
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-shadow
|
||||||
|
export enum JobType {
|
||||||
|
FullTime = 'Full-time',
|
||||||
|
Internship = 'Internship',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const titleOptions = [
|
||||||
|
{
|
||||||
|
label: 'Software engineer',
|
||||||
|
value: 'Software engineer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Frontend engineer',
|
||||||
|
value: 'Frontend engineer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Backend engineer',
|
||||||
|
value: 'Backend engineer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Full-stack engineer',
|
||||||
|
value: 'Full-stack engineer',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const currencyOptions = [
|
||||||
|
{
|
||||||
|
label: 'USD',
|
||||||
|
value: 'USD',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'SGD',
|
||||||
|
value: 'SGD',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'EUR',
|
||||||
|
value: 'EUR',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const companyOptions = [
|
||||||
|
{
|
||||||
|
label: 'Shopee',
|
||||||
|
value: 'id-abc123',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const locationOptions = [
|
||||||
|
{
|
||||||
|
label: 'Singapore, Singapore',
|
||||||
|
value: 'Singapore, Singapore',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const internshipCycleOptions = [
|
||||||
|
{
|
||||||
|
label: 'Summer',
|
||||||
|
value: 'Summer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Winter',
|
||||||
|
value: 'Winter',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Spring',
|
||||||
|
value: 'Spring',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Fall',
|
||||||
|
value: 'Fall',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const yearOptions = [
|
||||||
|
{
|
||||||
|
label: '2021',
|
||||||
|
value: '2021',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '2022',
|
||||||
|
value: '2022',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '2023',
|
||||||
|
value: '2023',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '2024',
|
||||||
|
value: '2024',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
type FullTimeOfferDetailsFormProps = Readonly<{
|
||||||
|
index: number;
|
||||||
|
remove: UseFieldArrayRemove;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
function FullTimeOfferDetailsForm({
|
||||||
|
index,
|
||||||
|
remove,
|
||||||
|
}: FullTimeOfferDetailsFormProps) {
|
||||||
|
const { register } = useFormContext();
|
||||||
|
return (
|
||||||
|
<div className="my-5 rounded-lg border border-gray-200 px-10 py-5">
|
||||||
|
<div className="mb-5 grid grid-cols-3 space-x-3">
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Title"
|
||||||
|
options={titleOptions}
|
||||||
|
required={true}
|
||||||
|
{...register(`offers.${index}.job.title`, { required: true })}
|
||||||
|
/>
|
||||||
|
<FormTextInput
|
||||||
|
label="Focus / Specialization"
|
||||||
|
placeholder="e.g. Front End"
|
||||||
|
required={true}
|
||||||
|
{...register(`offers.${index}.job.specialization`)}
|
||||||
|
/>
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Company"
|
||||||
|
options={companyOptions}
|
||||||
|
required={true}
|
||||||
|
{...register(`offers.${index}.companyId`, { required: true })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5 grid grid-cols-3 space-x-3">
|
||||||
|
<FormTextInput
|
||||||
|
label="Level"
|
||||||
|
placeholder="e.g. L4, Junior"
|
||||||
|
required={true}
|
||||||
|
{...register(`offers.${index}.job.level`, { required: true })}
|
||||||
|
/>
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Location"
|
||||||
|
options={locationOptions}
|
||||||
|
required={true}
|
||||||
|
{...register(`offers.${index}.job.location`, { required: true })}
|
||||||
|
/>
|
||||||
|
<FormTextInput
|
||||||
|
label="Month Received"
|
||||||
|
placeholder="MMM/YYYY"
|
||||||
|
required={true}
|
||||||
|
{...register(`offers.${index}.monthYearReceived`, { required: true })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5">
|
||||||
|
<FormTextInput
|
||||||
|
endAddOn={
|
||||||
|
<FormSelect
|
||||||
|
borderStyle="borderless"
|
||||||
|
isLabelHidden={true}
|
||||||
|
label="Currency"
|
||||||
|
options={currencyOptions}
|
||||||
|
{...register(`offers.${index}.job.totalCompensation.currency`, {
|
||||||
|
required: true,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
endAddOnType="element"
|
||||||
|
label="Total Compensation (Annual)"
|
||||||
|
placeholder="0.00"
|
||||||
|
required={true}
|
||||||
|
startAddOn="$"
|
||||||
|
startAddOnType="label"
|
||||||
|
type="number"
|
||||||
|
{...register(`offers.${index}.job.totalCompensation.value`, {
|
||||||
|
required: true,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5 grid grid-cols-3 space-x-3">
|
||||||
|
<FormTextInput
|
||||||
|
endAddOn={
|
||||||
|
<FormSelect
|
||||||
|
borderStyle="borderless"
|
||||||
|
isLabelHidden={true}
|
||||||
|
label="Currency"
|
||||||
|
options={currencyOptions}
|
||||||
|
{...register(`offers.${index}.job.base.currency`)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
endAddOnType="element"
|
||||||
|
label="Base Salary (Annual)"
|
||||||
|
placeholder="0.00"
|
||||||
|
required={true}
|
||||||
|
startAddOn="$"
|
||||||
|
startAddOnType="label"
|
||||||
|
type="number"
|
||||||
|
{...register(`offers.${index}.job.base.value`)}
|
||||||
|
/>
|
||||||
|
<FormTextInput
|
||||||
|
endAddOn={
|
||||||
|
<FormSelect
|
||||||
|
borderStyle="borderless"
|
||||||
|
isLabelHidden={true}
|
||||||
|
label="Currency"
|
||||||
|
options={currencyOptions}
|
||||||
|
{...register(`offers.${index}.job.bonus.currency`)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
endAddOnType="element"
|
||||||
|
label="Bonus (Annual)"
|
||||||
|
placeholder="0.00"
|
||||||
|
required={true}
|
||||||
|
startAddOn="$"
|
||||||
|
startAddOnType="label"
|
||||||
|
type="number"
|
||||||
|
{...register(`offers.${index}.job.bonus.value`)}
|
||||||
|
/>
|
||||||
|
<FormTextInput
|
||||||
|
endAddOn={
|
||||||
|
<FormSelect
|
||||||
|
borderStyle="borderless"
|
||||||
|
isLabelHidden={true}
|
||||||
|
label="Currency"
|
||||||
|
options={currencyOptions}
|
||||||
|
{...register(`offers.${index}.job.stocks.currency`)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
endAddOnType="element"
|
||||||
|
label="Stocks (Annual)"
|
||||||
|
placeholder="0.00"
|
||||||
|
required={true}
|
||||||
|
startAddOn="$"
|
||||||
|
startAddOnType="label"
|
||||||
|
type="number"
|
||||||
|
{...register(`offers.${index}.job.stocks.value`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5">
|
||||||
|
<FormTextArea
|
||||||
|
label="Negotiation Strategy / Interview Performance"
|
||||||
|
placeholder="e.g. Did well in the behavioral interview / Used competing offers to negotiate for a higher salary"
|
||||||
|
{...register(`offers.${index}.negotiationStrategy`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5">
|
||||||
|
<FormTextArea
|
||||||
|
label="Comments"
|
||||||
|
placeholder="e.g. Benefits offered by the company"
|
||||||
|
{...register(`offers.${index}.comments`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
{index > 0 && (
|
||||||
|
<Button
|
||||||
|
icon={TrashIcon}
|
||||||
|
label="Delete"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => remove(index)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type OfferDetailsFormArrayProps = Readonly<{
|
||||||
|
fieldArrayValues: UseFieldArrayReturn<FieldValues, 'offers', 'id'>;
|
||||||
|
jobType: JobType;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
function OfferDetailsFormArray({
|
||||||
|
fieldArrayValues,
|
||||||
|
jobType,
|
||||||
|
}: OfferDetailsFormArrayProps) {
|
||||||
|
const { append, remove, fields } = fieldArrayValues;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{fields.map((item, index) =>
|
||||||
|
jobType === JobType.FullTime ? (
|
||||||
|
<FullTimeOfferDetailsForm
|
||||||
|
key={`offer.${item.id}`}
|
||||||
|
index={index}
|
||||||
|
remove={remove}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<InternshipOfferDetailsForm
|
||||||
|
key={`offer.${item.id}`}
|
||||||
|
index={index}
|
||||||
|
remove={remove}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
display="block"
|
||||||
|
icon={PlusIcon}
|
||||||
|
label="Add another offer"
|
||||||
|
size="lg"
|
||||||
|
variant="tertiary"
|
||||||
|
onClick={() => append({})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type InternshipOfferDetailsFormProps = Readonly<{
|
||||||
|
index: number;
|
||||||
|
remove: UseFieldArrayRemove;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
function InternshipOfferDetailsForm({
|
||||||
|
index,
|
||||||
|
remove,
|
||||||
|
}: InternshipOfferDetailsFormProps) {
|
||||||
|
const { register } = useFormContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="my-5 rounded-lg border border-gray-200 px-10 py-5">
|
||||||
|
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Title"
|
||||||
|
options={titleOptions}
|
||||||
|
required={true}
|
||||||
|
{...register(`offers.${index}.job.title`)}
|
||||||
|
/>
|
||||||
|
<FormTextInput
|
||||||
|
label="Focus / Specialization"
|
||||||
|
placeholder="e.g. Front End"
|
||||||
|
required={true}
|
||||||
|
{...register(`offers.${index}.job.specialization`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Company"
|
||||||
|
options={companyOptions}
|
||||||
|
required={true}
|
||||||
|
value="Shopee"
|
||||||
|
{...register(`offers.${index}.companyId`)}
|
||||||
|
/>
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Location"
|
||||||
|
options={locationOptions}
|
||||||
|
required={true}
|
||||||
|
value="Singapore, Singapore"
|
||||||
|
{...register(`offers.${index}.location`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5 grid grid-cols-3 space-x-3">
|
||||||
|
<FormTextInput
|
||||||
|
label="Date Received"
|
||||||
|
placeholder="MMM/YYYY"
|
||||||
|
required={true}
|
||||||
|
{...register(`offers.${index}.monthYearReceived`)}
|
||||||
|
/>
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Internship Cycle"
|
||||||
|
options={internshipCycleOptions}
|
||||||
|
required={true}
|
||||||
|
value="Summer"
|
||||||
|
{...register(`offers.${index}.job.internshipCycle`)}
|
||||||
|
/>
|
||||||
|
<FormSelect
|
||||||
|
display="block"
|
||||||
|
label="Internship Year"
|
||||||
|
options={yearOptions}
|
||||||
|
required={true}
|
||||||
|
value="2023"
|
||||||
|
{...register(`offers.${index}.job.startYear`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5">
|
||||||
|
<FormTextInput
|
||||||
|
endAddOn={
|
||||||
|
<FormSelect
|
||||||
|
borderStyle="borderless"
|
||||||
|
isLabelHidden={true}
|
||||||
|
label="Currency"
|
||||||
|
options={currencyOptions}
|
||||||
|
{...register(`offers.${index}.job.monthlySalary.currency`)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
endAddOnType="element"
|
||||||
|
label="Salary (Monthly)"
|
||||||
|
placeholder="0.00"
|
||||||
|
required={true}
|
||||||
|
startAddOn="$"
|
||||||
|
startAddOnType="label"
|
||||||
|
type="number"
|
||||||
|
{...register(`offers.${index}.job.monthlySalary.value`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5">
|
||||||
|
<FormTextArea
|
||||||
|
label="Negotiation Strategy / Interview Performance"
|
||||||
|
placeholder="e.g. Did well in the behavioral interview. Used competing offers to negotiate for a higher salary."
|
||||||
|
{...register(`offers.${index}.negotiationStrategy`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5">
|
||||||
|
<FormTextArea
|
||||||
|
label="Comments"
|
||||||
|
placeholder="e.g. Encountered similar questions using the Technical Interview Handbook."
|
||||||
|
{...register(`offers.${index}.comments`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
{index > 0 && (
|
||||||
|
<Button
|
||||||
|
icon={TrashIcon}
|
||||||
|
label="Delete"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => remove(index)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function OfferDetailsForm() {
|
||||||
|
const [jobType, setJobType] = useState(JobType.FullTime);
|
||||||
|
const { control, register } = useFormContext();
|
||||||
|
const fieldArrayValues = useFieldArray({ control, name: 'offers' });
|
||||||
|
|
||||||
|
const changeJobType = (jobTypeChosen: JobType) => () => {
|
||||||
|
if (jobType === jobTypeChosen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setJobType(jobTypeChosen);
|
||||||
|
fieldArrayValues.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-5">
|
||||||
|
<h5 className="mb-8 text-center text-4xl font-bold text-gray-900">
|
||||||
|
Fill in your offer details
|
||||||
|
</h5>
|
||||||
|
<div className="flex w-full justify-center">
|
||||||
|
<div className="mx-5 w-1/3">
|
||||||
|
<Button
|
||||||
|
display="block"
|
||||||
|
label={JobType.FullTime}
|
||||||
|
size="md"
|
||||||
|
variant={jobType === JobType.FullTime ? 'secondary' : 'tertiary'}
|
||||||
|
onClick={changeJobType(JobType.FullTime)}
|
||||||
|
{...register(`offers.${0}.jobType`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx-5 w-1/3">
|
||||||
|
<Button
|
||||||
|
display="block"
|
||||||
|
label={JobType.Internship}
|
||||||
|
size="md"
|
||||||
|
variant={jobType === JobType.Internship ? 'secondary' : 'tertiary'}
|
||||||
|
onClick={changeJobType(JobType.Internship)}
|
||||||
|
{...register(`offers.${0}.jobType`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<OfferDetailsFormArray
|
||||||
|
fieldArrayValues={fieldArrayValues}
|
||||||
|
jobType={jobType}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { setTimeout } from 'timers';
|
||||||
|
import { CheckIcon, DocumentDuplicateIcon } from '@heroicons/react/20/solid';
|
||||||
|
import { BookmarkSquareIcon, EyeIcon } from '@heroicons/react/24/outline';
|
||||||
|
import { Button, TextInput } from '@tih/ui';
|
||||||
|
|
||||||
|
export default function OfferProfileSave() {
|
||||||
|
const [linkCopied, setLinkCopied] = useState(false);
|
||||||
|
const [isSaving, setSaving] = useState(false);
|
||||||
|
const [isSaved, setSaved] = useState(false);
|
||||||
|
const saveProfile = () => {
|
||||||
|
setSaving(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setSaving(false);
|
||||||
|
setSaved(true);
|
||||||
|
}, 5);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex w-full justify-center">
|
||||||
|
<div className="max-w-2xl text-center">
|
||||||
|
<h5 className="mb-6 text-4xl font-bold text-gray-900">
|
||||||
|
Save for future edits
|
||||||
|
</h5>
|
||||||
|
<p className="mb-2 text-gray-900">We value your privacy.</p>
|
||||||
|
<p className="mb-5 text-gray-900">
|
||||||
|
To keep you offer profile strictly anonymous, only people who have the
|
||||||
|
link below can edit it.
|
||||||
|
</p>
|
||||||
|
<div className="mb-20 grid grid-cols-12 gap-4">
|
||||||
|
<div className="col-span-11">
|
||||||
|
<TextInput
|
||||||
|
disabled={true}
|
||||||
|
isLabelHidden={true}
|
||||||
|
label="Edit link"
|
||||||
|
value="link.myprofile-auto-generate..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
icon={DocumentDuplicateIcon}
|
||||||
|
isLabelHidden={true}
|
||||||
|
label="Copy"
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => setLinkCopied(true)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-5">
|
||||||
|
{linkCopied && (
|
||||||
|
<p className="text-purple-700">Link copied to clipboard!</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mb-5 text-gray-900">
|
||||||
|
If you do not want to keep the edit link, you can opt to save this
|
||||||
|
profile under your user accont. It will still only be editable by you.
|
||||||
|
</p>
|
||||||
|
<div className="mb-20">
|
||||||
|
<Button
|
||||||
|
disabled={isSaved}
|
||||||
|
icon={isSaved ? CheckIcon : BookmarkSquareIcon}
|
||||||
|
isLoading={isSaving}
|
||||||
|
label="Save to user profile"
|
||||||
|
variant="primary"
|
||||||
|
onClick={saveProfile}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-10">
|
||||||
|
<Button icon={EyeIcon} label="View your profile" variant="special" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,198 @@
|
|||||||
|
import { 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';
|
||||||
|
import { Button } from '@tih/ui';
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
type Money = {
|
||||||
|
currency: string;
|
||||||
|
value: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FullTimeJobData = {
|
||||||
|
base: Money;
|
||||||
|
bonus: Money;
|
||||||
|
level: string;
|
||||||
|
specialization: string;
|
||||||
|
stocks: Money;
|
||||||
|
title: string;
|
||||||
|
totalCompensation: Money;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FullTimeOfferFormData = {
|
||||||
|
comments: string;
|
||||||
|
companyId: string;
|
||||||
|
job: FullTimeJobData;
|
||||||
|
jobType: string;
|
||||||
|
location: string;
|
||||||
|
monthYearReceived: string;
|
||||||
|
negotiationStrategy: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type InternshipJobData = {
|
||||||
|
internshipCycle: string;
|
||||||
|
monthlySalary: Money;
|
||||||
|
specialization: string;
|
||||||
|
startYear: number;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type InternshipOfferFormData = {
|
||||||
|
comments: string;
|
||||||
|
companyId: string;
|
||||||
|
job: InternshipJobData;
|
||||||
|
jobType: string;
|
||||||
|
location: string;
|
||||||
|
monthYearReceived: string;
|
||||||
|
negotiationStrategy: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type OfferDetailsFormData = FullTimeOfferFormData | InternshipOfferFormData;
|
||||||
|
|
||||||
|
type SpecificYoe = {
|
||||||
|
domain: string;
|
||||||
|
yoe: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type FullTimeExperience = {
|
||||||
|
level: string;
|
||||||
|
totalCompensation: Money;
|
||||||
|
};
|
||||||
|
|
||||||
|
type InternshipExperience = {
|
||||||
|
monthlySalary: Money;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GeneralExperience = {
|
||||||
|
companyId: string;
|
||||||
|
durationInMonths: number;
|
||||||
|
jobType: string;
|
||||||
|
specialization: string;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Experience =
|
||||||
|
| (FullTimeExperience & GeneralExperience)
|
||||||
|
| (GeneralExperience & InternshipExperience);
|
||||||
|
|
||||||
|
type Education = {
|
||||||
|
endDate: Date;
|
||||||
|
field: string;
|
||||||
|
school: string;
|
||||||
|
startDate: Date;
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BackgroundFormData = {
|
||||||
|
education: Education;
|
||||||
|
experience: Experience;
|
||||||
|
specificYoes: Array<SpecificYoe>;
|
||||||
|
totalYoe: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export type SubmitOfferFormData
|
||||||
|
export type SubmitOfferFormData = {
|
||||||
|
background: BackgroundFormData;
|
||||||
|
offers: Array<OfferDetailsFormData>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function Breadcrumbs() {
|
||||||
|
return (
|
||||||
|
<p className="mb-4 text-right text-sm text-gray-400">
|
||||||
|
{'Offer details > Background > Analysis > Save'}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function OffersSubmissionPage() {
|
||||||
|
const [formStep, setFormStep] = useState(0);
|
||||||
|
const formMethods = useForm<SubmitOfferFormData>({
|
||||||
|
defaultValues: {
|
||||||
|
offers: [
|
||||||
|
{
|
||||||
|
comments: '',
|
||||||
|
companyId: 'Shopee',
|
||||||
|
job: {
|
||||||
|
base: {
|
||||||
|
currency: 'USD',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
bonus: {
|
||||||
|
currency: 'USD',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
level: '',
|
||||||
|
specialization: '',
|
||||||
|
stocks: {
|
||||||
|
currency: 'USD',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
title: '',
|
||||||
|
totalCompensation: {
|
||||||
|
currency: 'USD',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
jobType: 'FULLTIME',
|
||||||
|
monthYearReceived: '',
|
||||||
|
negotiationStrategy: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const nextStep = () => setFormStep(formStep + 1);
|
||||||
|
const previousStep = () => setFormStep(formStep - 1);
|
||||||
|
const formComponents = [
|
||||||
|
<OfferDetailsForm key={0} />,
|
||||||
|
<BackgroundForm key={1} />,
|
||||||
|
<OfferAnalysis key={2} />,
|
||||||
|
<OfferProfileSave key={3} />,
|
||||||
|
];
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<SubmitOfferFormData> = async () => {
|
||||||
|
nextStep();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed h-full w-full overflow-y-scroll">
|
||||||
|
<div className="mb-20 flex justify-center">
|
||||||
|
<div className="my-5 block w-3/4 rounded-lg bg-white py-10 px-10 shadow-lg">
|
||||||
|
<Breadcrumbs />
|
||||||
|
<FormProvider {...formMethods}>
|
||||||
|
<form onSubmit={formMethods.handleSubmit(onSubmit)}>
|
||||||
|
{formComponents[formStep]}
|
||||||
|
{/* <pre>{JSON.stringify(formMethods.watch(), null, 2)}</pre> */}
|
||||||
|
{(formStep === 0 || formStep === 2) && (
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
icon={ArrowRightIcon}
|
||||||
|
label="Next"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={nextStep}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{formStep === 1 && (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Button
|
||||||
|
icon={ArrowLeftIcon}
|
||||||
|
label="Previous"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={previousStep}
|
||||||
|
/>
|
||||||
|
<Button label="Submit" type="submit" variant="primary" />{' '}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</FormProvider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in new issue