[offers][feat] add offers submission form

commit 7176414e4d6004b1ae0257c167b765ba22b6db42
Author: Ai Ling <hong-ailing@hotmail.com>
Date:   Mon Oct 10 00:07:24 2022 +0800

    [offers][feat] add offers submission form
pull/348/head
Ai Ling 3 years ago
parent 670777474b
commit 6b4dc5050c

@ -2,7 +2,7 @@ import type { ProductNavigationItems } from '~/components/global/ProductNavigati
const navigation: ProductNavigationItems = [
{ href: '/offers', name: 'Home' },
{ href: '/submit', name: 'Benchmark your offer' },
{ href: '/offers/submit', name: 'Benchmark your offer' },
];
const config = {

@ -1,3 +1,14 @@
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>
);
}

@ -1,9 +1,73 @@
import { useState } from 'react';
import { Select } from '@tih/ui';
import OffersTable from '~/components/offers/OffersTable';
import OffersTitle from '~/components/offers/OffersTitle';
export default function OffersHomePage() {
const [jobTitleFilter, setjobTitleFilter] = useState('Software engineers');
const [companyFilter, setCompanyFilter] = useState('All companies');
return (
<main className="flex-1 overflow-y-auto">
<div className="flex h-full items-center justify-center">
<div className="grid-rows grid h-1/3 bg-gray-100">
<OffersTitle />
<div className="flex items-start justify-center">
<div className="flex items-center">
Viewing offers for
<div className="mx-4">
<Select
isLabelHidden={true}
label="Select a job title"
options={[
{
label: 'Software engineers',
value: 'Software engineers',
},
{
label: 'Frontend engineers',
value: 'Frontend engineers',
},
{
label: 'Backend engineers',
value: 'Backend engineers',
},
{
label: 'Full-stack engineers',
value: 'Full-stack engineers',
},
]}
value={jobTitleFilter}
onChange={setjobTitleFilter}
/>
</div>
in
<div className="mx-4">
<Select
isLabelHidden={true}
label="Select a company"
options={[
{
label: 'All companies',
value: 'All companies',
},
{
label: 'Shopee',
value: 'Shopee',
},
{
label: 'Meta',
value: 'Meta',
},
]}
value={companyFilter}
onChange={setCompanyFilter}
/>
</div>
</div>
</div>
</div>
<div className="mt-10 flex justify-center">
<OffersTable />
</div>
</main>

@ -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…
Cancel
Save