[offers][feat] tweak offer details submission form (#488)

pull/489/head
Yangshun Tay 2 years ago committed by GitHub
parent 64bc8158c1
commit 21c9d9410a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -275,16 +275,17 @@ export default function OffersSubmissionForm({
/> />
</div> </div>
<FormProvider {...formMethods}> <FormProvider {...formMethods}>
<form className="text-sm" onSubmit={handleSubmit(onSubmit)}> <form
className="space-y-6 text-sm"
onSubmit={handleSubmit(onSubmit)}>
{steps[step]} {steps[step]}
{/* <pre>{JSON.stringify(formMethods.watch(), null, 2)}</pre> */}
{step === 0 && ( {step === 0 && (
<div className="flex justify-end"> <div className="flex justify-end">
<Button <Button
disabled={false} disabled={false}
icon={ArrowRightIcon} icon={ArrowRightIcon}
label="Next" label="Next"
variant="secondary" variant="primary"
onClick={() => { onClick={() => {
goToNextStep(step); goToNextStep(step);
gaEvent({ gaEvent({

@ -10,7 +10,7 @@ import { useFieldArray } from 'react-hook-form';
import { PlusIcon } from '@heroicons/react/20/solid'; import { PlusIcon } from '@heroicons/react/20/solid';
import { TrashIcon } from '@heroicons/react/24/outline'; import { TrashIcon } from '@heroicons/react/24/outline';
import { JobType } from '@prisma/client'; import { JobType } from '@prisma/client';
import { Button, Dialog } from '@tih/ui'; import { Button, Dialog, HorizontalDivider } from '@tih/ui';
import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead'; import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
import type { JobTitleType } from '~/components/shared/JobTitles'; import type { JobTitleType } from '~/components/shared/JobTitles';
@ -45,6 +45,23 @@ type FullTimeOfferDetailsFormProps = Readonly<{
remove: UseFieldArrayRemove; remove: UseFieldArrayRemove;
}>; }>;
function Section({
children,
title,
}: Readonly<{ children: React.ReactNode; title: string }>) {
return (
<div>
<div className="mb-4">
<h3 className="text-lg font-medium leading-6 text-slate-900">
{title}
</h3>
<HorizontalDivider />
</div>
<div className="space-y-4 sm:space-y-6">{children}</div>
</div>
);
}
function FullTimeOfferDetailsForm({ function FullTimeOfferDetailsForm({
index, index,
remove, remove,
@ -77,9 +94,9 @@ function FullTimeOfferDetailsForm({
}, [watchCurrency, index, setValue]); }, [watchCurrency, index, setValue]);
return ( return (
<div className="my-5 rounded-lg border border-slate-200 px-10 py-5"> <div className="space-y-8 rounded-lg border border-slate-200 p-6 sm:space-y-16 sm:p-8">
<div className="mb-5 grid grid-cols-2 space-x-3"> <Section title="Company & Title Information">
<div> <div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
<JobTitlesTypeahead <JobTitlesTypeahead
required={true} required={true}
value={{ value={{
@ -93,19 +110,17 @@ function FullTimeOfferDetailsForm({
} }
}} }}
/> />
<FormTextInput
errorMessage={offerFields?.offersFullTime?.level?.message}
label="Level"
placeholder="e.g. L4, Junior"
required={true}
{...register(`offers.${index}.offersFullTime.level`, {
required: FieldError.REQUIRED,
})}
/>
</div> </div>
<FormTextInput <div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
errorMessage={offerFields?.offersFullTime?.level?.message}
label="Level"
placeholder="e.g. L4, Junior"
required={true}
{...register(`offers.${index}.offersFullTime.level`, {
required: FieldError.REQUIRED,
})}
/>
</div>
<div className="mb-5 grid grid-cols-2 space-x-3">
<div>
<CompaniesTypeahead <CompaniesTypeahead
required={true} required={true}
value={{ value={{
@ -120,165 +135,168 @@ function FullTimeOfferDetailsForm({
} }
}} }}
/> />
<FormSelect
display="block"
errorMessage={offerFields?.location?.message}
label="Location"
options={locationOptions}
placeholder={emptyOption}
required={true}
{...register(`offers.${index}.location`, {
required: FieldError.REQUIRED,
})}
/>
</div> </div>
<FormSelect </Section>
display="block" <Section title="Compensation Details">
errorMessage={offerFields?.location?.message} <div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
label="Location" <FormMonthYearPicker
options={locationOptions} monthLabel="Date Received"
placeholder={emptyOption} monthRequired={true}
required={true} yearLabel=""
{...register(`offers.${index}.location`, { {...register(`offers.${index}.monthYearReceived`, {
required: FieldError.REQUIRED,
})}
/>
</div>
<div className="mb-5 flex grid grid-cols-2 items-start space-x-3">
<FormMonthYearPicker
monthLabel="Date Received"
monthRequired={true}
yearLabel=""
{...register(`offers.${index}.monthYearReceived`, {
required: FieldError.REQUIRED,
})}
/>
</div>
<div className="mb-5">
<FormTextInput
endAddOn={
<FormSelect
borderStyle="borderless"
defaultValue={Currency.SGD}
isLabelHidden={true}
label="Currency"
options={CURRENCY_OPTIONS}
{...register(
`offers.${index}.offersFullTime.totalCompensation.currency`,
{
required: FieldError.REQUIRED,
},
)}
/>
}
endAddOnType="element"
errorMessage={
offerFields?.offersFullTime?.totalCompensation?.value?.message
}
label="Total Compensation (Annual)"
placeholder="0"
required={true}
startAddOn="$"
startAddOnType="label"
type="number"
{...register(
`offers.${index}.offersFullTime.totalCompensation.value`,
{
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
required: FieldError.REQUIRED, required: FieldError.REQUIRED,
})}
/>
<FormTextInput
endAddOn={
<FormSelect
borderStyle="borderless"
defaultValue={Currency.SGD}
isLabelHidden={true}
label="Currency"
options={CURRENCY_OPTIONS}
{...register(
`offers.${index}.offersFullTime.totalCompensation.currency`,
{
required: FieldError.REQUIRED,
},
)}
/>
}
endAddOnType="element"
errorMessage={
offerFields?.offersFullTime?.totalCompensation?.value?.message
}
label="Total Compensation (Annual)"
placeholder="0"
required={true}
startAddOn="$"
startAddOnType="label"
type="number"
{...register(
`offers.${index}.offersFullTime.totalCompensation.value`,
{
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
required: FieldError.REQUIRED,
valueAsNumber: true,
},
)}
/>
</div>
<div className="grid grid-cols-1 gap-6 sm:grid-cols-3">
<FormTextInput
endAddOn={
<FormSelect
borderStyle="borderless"
defaultValue={Currency.SGD}
isLabelHidden={true}
label="Currency"
options={CURRENCY_OPTIONS}
{...register(
`offers.${index}.offersFullTime.baseSalary.currency`,
)}
/>
}
endAddOnType="element"
errorMessage={
offerFields?.offersFullTime?.baseSalary?.value?.message
}
label="Base Salary (Annual)"
placeholder="0"
startAddOn="$"
startAddOnType="label"
type="number"
{...register(`offers.${index}.offersFullTime.baseSalary.value`, {
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
valueAsNumber: true, valueAsNumber: true,
}, })}
)} />
/> <FormTextInput
</div> endAddOn={
<div className="mb-5 grid grid-cols-2 space-x-3"> <FormSelect
<FormTextInput borderStyle="borderless"
endAddOn={ defaultValue={Currency.SGD}
<FormSelect isLabelHidden={true}
borderStyle="borderless" label="Currency"
defaultValue={Currency.SGD} options={CURRENCY_OPTIONS}
isLabelHidden={true} {...register(`offers.${index}.offersFullTime.bonus.currency`)}
label="Currency" />
options={CURRENCY_OPTIONS} }
{...register( endAddOnType="element"
`offers.${index}.offersFullTime.baseSalary.currency`, errorMessage={offerFields?.offersFullTime?.bonus?.value?.message}
)} label="Bonus (Annual)"
/> placeholder="0"
} startAddOn="$"
endAddOnType="element" startAddOnType="label"
errorMessage={offerFields?.offersFullTime?.baseSalary?.value?.message} type="number"
label="Base Salary (Annual)" {...register(`offers.${index}.offersFullTime.bonus.value`, {
placeholder="0" min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
startAddOn="$" valueAsNumber: true,
startAddOnType="label" })}
type="number" />
{...register(`offers.${index}.offersFullTime.baseSalary.value`, { <FormTextInput
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, endAddOn={
valueAsNumber: true, <FormSelect
})} borderStyle="borderless"
/> defaultValue={Currency.SGD}
<FormTextInput isLabelHidden={true}
endAddOn={ label="Currency"
<FormSelect options={CURRENCY_OPTIONS}
borderStyle="borderless" {...register(`offers.${index}.offersFullTime.stocks.currency`)}
defaultValue={Currency.SGD} />
isLabelHidden={true} }
label="Currency" endAddOnType="element"
options={CURRENCY_OPTIONS} errorMessage={offerFields?.offersFullTime?.stocks?.value?.message}
{...register(`offers.${index}.offersFullTime.bonus.currency`)} label="Stocks (Annual)"
/> placeholder="0"
} startAddOn="$"
endAddOnType="element" startAddOnType="label"
errorMessage={offerFields?.offersFullTime?.bonus?.value?.message} type="number"
label="Bonus (Annual)" {...register(`offers.${index}.offersFullTime.stocks.value`, {
placeholder="0" min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
startAddOn="$" valueAsNumber: true,
startAddOnType="label" })}
type="number" />
{...register(`offers.${index}.offersFullTime.bonus.value`, { </div>
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 }, </Section>
valueAsNumber: true, <Section title="Additional Information">
})}
/>
</div>
<div className="mb-5 grid grid-cols-2 space-x-3">
<FormTextInput
endAddOn={
<FormSelect
borderStyle="borderless"
defaultValue={Currency.SGD}
isLabelHidden={true}
label="Currency"
options={CURRENCY_OPTIONS}
{...register(`offers.${index}.offersFullTime.stocks.currency`)}
/>
}
endAddOnType="element"
errorMessage={offerFields?.offersFullTime?.stocks?.value?.message}
label="Stocks (Annual)"
placeholder="0"
startAddOn="$"
startAddOnType="label"
type="number"
{...register(`offers.${index}.offersFullTime.stocks.value`, {
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
valueAsNumber: true,
})}
/>
</div>
<div className="mb-5">
<FormTextArea <FormTextArea
label="Negotiation Strategy / Interview Performance" label="Negotiation Strategy / Interview Performance"
placeholder="e.g. Did well in the behavioral interview / Used competing offers to negotiate for a higher salary" placeholder="e.g. Did well in the behavioral interview / Used competing offers to negotiate for a higher salary"
{...register(`offers.${index}.negotiationStrategy`)} {...register(`offers.${index}.negotiationStrategy`)}
/> />
</div>
<div className="mb-5">
<FormTextArea <FormTextArea
label="Comments" label="Comments"
placeholder="e.g. Benefits offered by the company" placeholder="e.g. Benefits offered by the company"
{...register(`offers.${index}.comments`)} {...register(`offers.${index}.comments`)}
/> />
</div>
<div className="flex justify-end">
{index > 0 && ( {index > 0 && (
<Button <div className="space-y-4 sm:space-y-6">
icon={TrashIcon} <HorizontalDivider />
label="Delete" <div className="flex justify-end">
variant="secondary" <Button
onClick={() => remove(index)} icon={TrashIcon}
/> label="Delete"
variant="tertiary"
onClick={() => {
remove(index);
}}
/>
</div>
</div>
)} )}
</div> </Section>
</div> </div>
); );
} }
@ -308,26 +326,23 @@ function InternshipOfferDetailsForm({
}); });
return ( return (
<div className="my-5 rounded-lg border border-slate-200 px-10 py-5"> <div className="space-y-8 rounded-lg border border-slate-200 p-6 sm:space-y-16 sm:p-8">
<div className="mb-5 grid grid-cols-2 space-x-3"> <Section title="Company & Title Information">
<div> <JobTitlesTypeahead
<JobTitlesTypeahead required={true}
required={true} value={{
value={{ id: watchJobTitle,
id: watchJobTitle, label: getLabelForJobTitleType(watchJobTitle as JobTitleType),
label: getLabelForJobTitleType(watchJobTitle as JobTitleType), value: watchJobTitle,
value: watchJobTitle, }}
}} onSelect={(option) => {
onSelect={(option) => { if (option) {
if (option) { setValue(`offers.${index}.offersIntern.title`, option.value);
setValue(`offers.${index}.offersIntern.title`, option.value); }
} }}
}} />
/>
</div> <div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
</div>
<div className="mb-5 grid grid-cols-2 space-x-3">
<div>
<CompaniesTypeahead <CompaniesTypeahead
required={true} required={true}
value={{ value={{
@ -342,114 +357,115 @@ function InternshipOfferDetailsForm({
} }
}} }}
/> />
<FormSelect
display="block"
errorMessage={offerFields?.location?.message}
label="Location"
options={locationOptions}
placeholder={emptyOption}
required={true}
{...register(`offers.${index}.location`, {
required: FieldError.REQUIRED,
})}
/>
</div> </div>
<FormSelect <div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
display="block" <FormSelect
errorMessage={offerFields?.location?.message} display="block"
label="Location" errorMessage={offerFields?.offersIntern?.internshipCycle?.message}
options={locationOptions} label="Internship Cycle"
placeholder={emptyOption} options={internshipCycleOptions}
required={true} placeholder={emptyOption}
{...register(`offers.${index}.location`, { required={true}
required: FieldError.REQUIRED, {...register(`offers.${index}.offersIntern.internshipCycle`, {
})} required: FieldError.REQUIRED,
/> })}
</div> />
<div className="mb-5 grid grid-cols-2 space-x-3"> <FormSelect
<FormSelect display="block"
display="block" errorMessage={offerFields?.offersIntern?.startYear?.message}
errorMessage={offerFields?.offersIntern?.internshipCycle?.message} label="Internship Year"
label="Internship Cycle" options={yearOptions}
options={internshipCycleOptions} placeholder={emptyOption}
placeholder={emptyOption} required={true}
required={true} {...register(`offers.${index}.offersIntern.startYear`, {
{...register(`offers.${index}.offersIntern.internshipCycle`, { required: FieldError.REQUIRED,
required: FieldError.REQUIRED, valueAsNumber: true,
})} })}
/> />
<FormSelect </div>
display="block" </Section>
errorMessage={offerFields?.offersIntern?.startYear?.message} <Section title="Compensation Details">
label="Internship Year" <div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
options={yearOptions} <FormMonthYearPicker
placeholder={emptyOption} monthLabel="Date Received"
required={true} monthRequired={true}
{...register(`offers.${index}.offersIntern.startYear`, { yearLabel=""
required: FieldError.REQUIRED, {...register(`offers.${index}.monthYearReceived`, {
valueAsNumber: true, required: FieldError.REQUIRED,
})} })}
/> />
</div> <FormTextInput
<div className="mb-5"> endAddOn={
<FormMonthYearPicker <FormSelect
monthLabel="Date Received" borderStyle="borderless"
monthRequired={true} defaultValue={Currency.SGD}
yearLabel="" isLabelHidden={true}
{...register(`offers.${index}.monthYearReceived`, { label="Currency"
required: FieldError.REQUIRED, options={CURRENCY_OPTIONS}
})} {...register(
/> `offers.${index}.offersIntern.monthlySalary.currency`,
</div> {
<div className="mb-5"> required: FieldError.REQUIRED,
<FormTextInput },
endAddOn={ )}
<FormSelect />
borderStyle="borderless" }
defaultValue={Currency.SGD} endAddOnType="element"
isLabelHidden={true} errorMessage={
label="Currency" offerFields?.offersIntern?.monthlySalary?.value?.message
options={CURRENCY_OPTIONS} }
{...register( label="Salary (Monthly)"
`offers.${index}.offersIntern.monthlySalary.currency`, placeholder="0"
{ required={true}
required: FieldError.REQUIRED, startAddOn="$"
}, startAddOnType="label"
)} type="number"
/> {...register(`offers.${index}.offersIntern.monthlySalary.value`, {
} min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
endAddOnType="element" required: FieldError.REQUIRED,
errorMessage={ valueAsNumber: true,
offerFields?.offersIntern?.monthlySalary?.value?.message })}
} />
label="Salary (Monthly)" </div>
placeholder="0" </Section>
required={true} <Section title="Additional Information">
startAddOn="$"
startAddOnType="label"
type="number"
{...register(`offers.${index}.offersIntern.monthlySalary.value`, {
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
required: FieldError.REQUIRED,
valueAsNumber: true,
})}
/>
</div>
<div className="mb-5">
<FormTextArea <FormTextArea
label="Negotiation Strategy / Interview Performance" label="Negotiation Strategy / Interview Performance"
placeholder="e.g. Did well in the behavioral interview. Used competing offers to negotiate for a higher salary." placeholder="e.g. Did well in the behavioral interview. Used competing offers to negotiate for a higher salary."
{...register(`offers.${index}.negotiationStrategy`)} {...register(`offers.${index}.negotiationStrategy`)}
/> />
</div>
<div className="mb-5">
<FormTextArea <FormTextArea
label="Comments" label="Comments"
placeholder="e.g. Encountered similar questions using the Technical Interview Handbook." placeholder="e.g. Encountered similar questions using the Technical Interview Handbook."
{...register(`offers.${index}.comments`)} {...register(`offers.${index}.comments`)}
/> />
</div> </Section>
<div className="flex justify-end"> {index > 0 && (
{index > 0 && ( <div className="space-y-4 sm:space-y-6">
<Button <HorizontalDivider />
icon={TrashIcon} <div className="flex justify-end">
label="Delete" <Button
variant="secondary" icon={TrashIcon}
onClick={() => { label="Delete"
remove(index); variant="tertiary"
}} onClick={() => {
/> remove(index);
)} }}
</div> />
</div>
</div>
)}
</div> </div>
); );
} }
@ -466,7 +482,7 @@ function OfferDetailsFormArray({
const { append, remove, fields } = fieldArrayValues; const { append, remove, fields } = fieldArrayValues;
return ( return (
<div> <div className="space-y-8">
{fields.map((item, index) => { {fields.map((item, index) => {
return ( return (
<div key={item.id}> <div key={item.id}>
@ -483,7 +499,7 @@ function OfferDetailsFormArray({
icon={PlusIcon} icon={PlusIcon}
label="Add another offer" label="Add another offer"
size="lg" size="lg"
variant="tertiary" variant="secondary"
onClick={() => onClick={() =>
append( append(
jobType === JobType.FULLTIME jobType === JobType.FULLTIME
@ -524,7 +540,7 @@ export default function OfferDetailsForm({
jobType === JobType.FULLTIME ? JobTypeLabel.INTERN : JobTypeLabel.FULLTIME; jobType === JobType.FULLTIME ? JobTypeLabel.INTERN : JobTypeLabel.FULLTIME;
return ( return (
<div className="mb-5"> <div className="space-y-6">
<h5 className="mb-8 text-center text-4xl font-bold text-slate-900"> <h5 className="mb-8 text-center text-4xl font-bold text-slate-900">
Fill in your offer details Fill in your offer details
</h5> </h5>

@ -8,7 +8,7 @@ export default function HorizontalDivider({ className }: Props) {
return ( return (
<hr <hr
aria-hidden={true} aria-hidden={true}
className={clsx('my-2 h-0 border-t border-slate-100', className)} className={clsx('my-2 h-0 border-t border-slate-200', className)}
/> />
); );
} }

Loading…
Cancel
Save