mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 12:43:12 +08:00
[offers][feat] tweak offer background submission form (#490)
* [offers][feat] tweak offer background submission form * [offers][feat] tweak breadcrumbs
This commit is contained in:
@ -1,45 +1,50 @@
|
||||
export type BreadcrumbStep = {
|
||||
import clsx from 'clsx';
|
||||
import { ChevronRightIcon } from '@heroicons/react/20/solid';
|
||||
|
||||
export type BreadcrumbStep = Readonly<{
|
||||
label: string;
|
||||
step?: number;
|
||||
};
|
||||
}>;
|
||||
|
||||
type BreadcrumbsProps = Readonly<{
|
||||
currentStep: number;
|
||||
setStep: (nextStep: number) => void;
|
||||
steps: Array<BreadcrumbStep>;
|
||||
steps: ReadonlyArray<BreadcrumbStep>;
|
||||
}>;
|
||||
|
||||
function getPrimaryText(text: string) {
|
||||
return <p className="text-primary-700 text-sm">{text}</p>;
|
||||
}
|
||||
|
||||
function getSlateText(text: string) {
|
||||
return <p className="text-sm text-slate-400">{text}</p>;
|
||||
}
|
||||
|
||||
function getTextWithLink(text: string, onClickHandler: () => void) {
|
||||
return (
|
||||
<p
|
||||
className="hover:text-primary-700 cursor-pointer text-sm text-slate-400 hover:underline hover:underline-offset-2"
|
||||
onClick={onClickHandler}>
|
||||
{text}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export function Breadcrumbs({ steps, currentStep, setStep }: BreadcrumbsProps) {
|
||||
return (
|
||||
<div className="flex space-x-1">
|
||||
{steps.map(({ label, step }, index) => (
|
||||
<div key={label} className="flex space-x-1">
|
||||
{step === currentStep
|
||||
? getPrimaryText(label)
|
||||
: step !== undefined
|
||||
? getTextWithLink(label, () => setStep(step))
|
||||
: getSlateText(label)}
|
||||
{index !== steps.length - 1 && getSlateText('>')}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<nav aria-label="Submit offer stages" className="inline-flex">
|
||||
<ol className="mx-auto flex w-full space-x-2 sm:space-x-4" role="list">
|
||||
{steps.map(({ label, step }, index) => (
|
||||
<li key={step} className="flex items-center">
|
||||
{index > 0 && (
|
||||
<ChevronRightIcon
|
||||
aria-hidden="true"
|
||||
className="h-5 w-5 flex-shrink-0 text-slate-400"
|
||||
/>
|
||||
)}
|
||||
<button
|
||||
aria-current={step === currentStep ? 'page' : undefined}
|
||||
className={clsx(
|
||||
'text-xs font-medium text-slate-600 sm:text-sm',
|
||||
index > 0 && 'ml-4',
|
||||
step != null ? 'hover:text-primary-500' : 'cursor-default',
|
||||
step === currentStep && 'text-primary-500',
|
||||
)}
|
||||
type="button"
|
||||
{...(step != null
|
||||
? {
|
||||
onClick: () => {
|
||||
setStep(step);
|
||||
},
|
||||
}
|
||||
: {})}>
|
||||
{label}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
18
apps/portal/src/components/offers/forms/FormSection.tsx
Normal file
18
apps/portal/src/components/offers/forms/FormSection.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { HorizontalDivider } from '@tih/ui';
|
||||
|
||||
export default function FormSection({
|
||||
children,
|
||||
title,
|
||||
}: Readonly<{ children: React.ReactNode; title: string }>) {
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<h2 className="text-lg font-medium leading-6 text-slate-900">
|
||||
{title}
|
||||
</h2>
|
||||
<HorizontalDivider />
|
||||
</div>
|
||||
<div className="space-y-4 sm:space-y-6">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -264,65 +264,69 @@ export default function OffersSubmissionForm({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={pageRef} className="fixed h-full w-full overflow-y-scroll">
|
||||
<div className="mb-20 flex justify-center">
|
||||
<div className="my-5 block w-full max-w-screen-md rounded-lg bg-white py-10 px-10 shadow-lg">
|
||||
<div className="mb-4 flex justify-end">
|
||||
<div ref={pageRef} className="w-full overflow-y-scroll">
|
||||
<div className="flex justify-center">
|
||||
<div className="block w-full max-w-screen-md overflow-hidden rounded-lg sm:shadow-lg md:my-10">
|
||||
<div className="bg-primary-100 flex justify-center px-4 py-4 sm:px-6 lg:px-8">
|
||||
<Breadcrumbs
|
||||
currentStep={step}
|
||||
setStep={setStep}
|
||||
steps={breadcrumbSteps}
|
||||
/>
|
||||
</div>
|
||||
<FormProvider {...formMethods}>
|
||||
<form
|
||||
className="space-y-6 text-sm"
|
||||
onSubmit={handleSubmit(onSubmit)}>
|
||||
{steps[step]}
|
||||
{step === 0 && (
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
disabled={false}
|
||||
icon={ArrowRightIcon}
|
||||
label="Next"
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
goToNextStep(step);
|
||||
gaEvent({
|
||||
action: 'offers.profile_submission_navigate_next',
|
||||
category: 'submission',
|
||||
label: 'Navigate next',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{step === 1 && (
|
||||
<div className="flex items-center justify-between">
|
||||
<Button
|
||||
icon={ArrowLeftIcon}
|
||||
label="Previous"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setStep(step - 1);
|
||||
gaEvent({
|
||||
action: 'offers.profile_submission_navigation_back',
|
||||
category: 'submission',
|
||||
label: 'Navigate back',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
disabled={isSubmitting || isSubmitSuccessful}
|
||||
isLoading={isSubmitting || isSubmitSuccessful}
|
||||
label="Submit"
|
||||
type="submit"
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</FormProvider>
|
||||
<div className="bg-white p-6 sm:p-10">
|
||||
<FormProvider {...formMethods}>
|
||||
<form
|
||||
className="space-y-6 text-sm"
|
||||
onSubmit={handleSubmit(onSubmit)}>
|
||||
{steps[step]}
|
||||
{step === 0 && (
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
disabled={false}
|
||||
icon={ArrowRightIcon}
|
||||
label="Next"
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
goToNextStep(step);
|
||||
gaEvent({
|
||||
action: 'offers.profile_submission_navigate_next',
|
||||
category: 'submission',
|
||||
label: 'Navigate next',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{step === 1 && (
|
||||
<div className="flex items-center justify-between">
|
||||
<Button
|
||||
addonPosition="start"
|
||||
icon={ArrowLeftIcon}
|
||||
label="Previous"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setStep(step - 1);
|
||||
gaEvent({
|
||||
action: 'offers.profile_submission_navigation_back',
|
||||
category: 'submission',
|
||||
label: 'Navigate back',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
disabled={isSubmitting || isSubmitSuccessful}
|
||||
icon={ArrowRightIcon}
|
||||
isLoading={isSubmitting || isSubmitSuccessful}
|
||||
label="Submit"
|
||||
type="submit"
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</FormProvider>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
} from '~/utils/offers/currency/CurrencyEnum';
|
||||
|
||||
import FormRadioList from '../../forms/FormRadioList';
|
||||
import FormSection from '../../forms/FormSection';
|
||||
import FormSelect from '../../forms/FormSelect';
|
||||
import FormTextInput from '../../forms/FormTextInput';
|
||||
|
||||
@ -29,29 +30,26 @@ function YoeSection() {
|
||||
background: BackgroundPostData;
|
||||
}>();
|
||||
const backgroundFields = formState.errors.background;
|
||||
return (
|
||||
<>
|
||||
<h6 className="mb-2 text-left text-xl font-medium text-slate-400">
|
||||
Years of Experience (YOE)
|
||||
</h6>
|
||||
|
||||
<div className="mb-5 rounded-lg border border-slate-200 px-10 py-5">
|
||||
<div className="mb-2 grid grid-cols-3 space-x-3">
|
||||
<FormTextInput
|
||||
errorMessage={backgroundFields?.totalYoe?.message}
|
||||
label="Total YOE"
|
||||
placeholder="0"
|
||||
required={true}
|
||||
type="number"
|
||||
{...register(`background.totalYoe`, {
|
||||
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
|
||||
required: FieldError.REQUIRED,
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<Collapsible label="Add specific YOEs by domain">
|
||||
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||
return (
|
||||
<FormSection title="Years of Experience (YOE)">
|
||||
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
|
||||
<FormTextInput
|
||||
errorMessage={backgroundFields?.totalYoe?.message}
|
||||
label="Total YOE"
|
||||
placeholder="0"
|
||||
required={true}
|
||||
type="number"
|
||||
{...register(`background.totalYoe`, {
|
||||
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
|
||||
required: FieldError.REQUIRED,
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<Collapsible label="Add specific YOEs by domain">
|
||||
<div className="space-y-4 sm:space-y-6">
|
||||
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
|
||||
<FormTextInput
|
||||
errorMessage={backgroundFields?.specificYoes?.[0]?.yoe?.message}
|
||||
label="Specific YOE 1"
|
||||
@ -63,11 +61,11 @@ function YoeSection() {
|
||||
/>
|
||||
<FormTextInput
|
||||
label="Specific Domain 1"
|
||||
placeholder="e.g. Frontend"
|
||||
placeholder="e.g. Front End"
|
||||
{...register(`background.specificYoes.0.domain`)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
|
||||
<FormTextInput
|
||||
errorMessage={backgroundFields?.specificYoes?.[1]?.yoe?.message}
|
||||
label="Specific YOE 2"
|
||||
@ -79,13 +77,13 @@ function YoeSection() {
|
||||
/>
|
||||
<FormTextInput
|
||||
label="Specific Domain 2"
|
||||
placeholder="e.g. Backend"
|
||||
placeholder="e.g. Back End"
|
||||
{...register(`background.specificYoes.1.domain`)}
|
||||
/>
|
||||
</div>
|
||||
</Collapsible>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
</Collapsible>
|
||||
</FormSection>
|
||||
);
|
||||
}
|
||||
|
||||
@ -107,38 +105,34 @@ function FullTimeJobFields() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||
<div>
|
||||
<JobTitlesTypeahead
|
||||
value={{
|
||||
id: watchJobTitle,
|
||||
label: getLabelForJobTitleType(watchJobTitle as JobTitleType),
|
||||
value: watchJobTitle,
|
||||
}}
|
||||
onSelect={(option) => {
|
||||
if (option) {
|
||||
setValue('background.experiences.0.title', option.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<CompaniesTypeahead
|
||||
value={{
|
||||
id: watchCompanyId,
|
||||
label: watchCompanyName,
|
||||
value: watchCompanyId,
|
||||
}}
|
||||
onSelect={(option) => {
|
||||
if (option) {
|
||||
setValue('background.experiences.0.companyId', option.value);
|
||||
setValue('background.experiences.0.companyName', option.label);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
|
||||
<JobTitlesTypeahead
|
||||
value={{
|
||||
id: watchJobTitle,
|
||||
label: getLabelForJobTitleType(watchJobTitle as JobTitleType),
|
||||
value: watchJobTitle,
|
||||
}}
|
||||
onSelect={(option) => {
|
||||
if (option) {
|
||||
setValue('background.experiences.0.title', option.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<CompaniesTypeahead
|
||||
value={{
|
||||
id: watchCompanyId,
|
||||
label: watchCompanyName,
|
||||
value: watchCompanyId,
|
||||
}}
|
||||
onSelect={(option) => {
|
||||
if (option) {
|
||||
setValue('background.experiences.0.companyId', option.value);
|
||||
setValue('background.experiences.0.companyName', option.label);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-5 grid grid-cols-1 space-x-3">
|
||||
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
|
||||
<FormTextInput
|
||||
endAddOn={
|
||||
<FormSelect
|
||||
@ -166,7 +160,7 @@ function FullTimeJobFields() {
|
||||
/>
|
||||
</div>
|
||||
<Collapsible label="Add more details">
|
||||
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-3">
|
||||
<FormTextInput
|
||||
label="Level"
|
||||
placeholder="e.g. L4, Junior"
|
||||
@ -178,8 +172,6 @@ function FullTimeJobFields() {
|
||||
options={locationOptions}
|
||||
{...register(`background.experiences.0.location`)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||
<FormTextInput
|
||||
errorMessage={experiencesField?.durationInMonths?.message}
|
||||
label="Duration (months)"
|
||||
@ -213,72 +205,64 @@ function InternshipJobFields() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||
<div>
|
||||
<JobTitlesTypeahead
|
||||
value={{
|
||||
id: watchJobTitle,
|
||||
label: getLabelForJobTitleType(watchJobTitle as JobTitleType),
|
||||
value: watchJobTitle,
|
||||
}}
|
||||
onSelect={(option) => {
|
||||
if (option) {
|
||||
setValue('background.experiences.0.title', option.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<CompaniesTypeahead
|
||||
value={{
|
||||
id: watchCompanyId,
|
||||
label: watchCompanyName,
|
||||
value: watchCompanyId,
|
||||
}}
|
||||
onSelect={(option) => {
|
||||
if (option) {
|
||||
setValue('background.experiences.0.companyId', option.value);
|
||||
setValue('background.experiences.0.companyName', option.label);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-5 grid grid-cols-1 space-x-3">
|
||||
<FormTextInput
|
||||
endAddOn={
|
||||
<FormSelect
|
||||
borderStyle="borderless"
|
||||
defaultValue={Currency.SGD}
|
||||
isLabelHidden={true}
|
||||
label="Currency"
|
||||
options={CURRENCY_OPTIONS}
|
||||
{...register(`background.experiences.0.monthlySalary.currency`)}
|
||||
/>
|
||||
}
|
||||
endAddOnType="element"
|
||||
errorMessage={experiencesField?.monthlySalary?.value?.message}
|
||||
label="Salary (Monthly)"
|
||||
placeholder="0.00"
|
||||
startAddOn="$"
|
||||
startAddOnType="label"
|
||||
type="number"
|
||||
{...register(`background.experiences.0.monthlySalary.value`, {
|
||||
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
|
||||
<JobTitlesTypeahead
|
||||
value={{
|
||||
id: watchJobTitle,
|
||||
label: getLabelForJobTitleType(watchJobTitle as JobTitleType),
|
||||
value: watchJobTitle,
|
||||
}}
|
||||
onSelect={(option) => {
|
||||
if (option) {
|
||||
setValue('background.experiences.0.title', option.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<CompaniesTypeahead
|
||||
value={{
|
||||
id: watchCompanyId,
|
||||
label: watchCompanyName,
|
||||
value: watchCompanyId,
|
||||
}}
|
||||
onSelect={(option) => {
|
||||
if (option) {
|
||||
setValue('background.experiences.0.companyId', option.value);
|
||||
setValue('background.experiences.0.companyName', option.label);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Collapsible label="Add more details">
|
||||
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||
<FormTextInput
|
||||
endAddOn={
|
||||
<FormSelect
|
||||
display="block"
|
||||
label="Location"
|
||||
options={locationOptions}
|
||||
placeholder={emptyOption}
|
||||
{...register(`background.experiences.0.location`)}
|
||||
borderStyle="borderless"
|
||||
defaultValue={Currency.SGD}
|
||||
isLabelHidden={true}
|
||||
label="Currency"
|
||||
options={CURRENCY_OPTIONS}
|
||||
{...register(`background.experiences.0.monthlySalary.currency`)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
endAddOnType="element"
|
||||
errorMessage={experiencesField?.monthlySalary?.value?.message}
|
||||
label="Salary (Monthly)"
|
||||
placeholder="0.00"
|
||||
startAddOn="$"
|
||||
startAddOnType="label"
|
||||
type="number"
|
||||
{...register(`background.experiences.0.monthlySalary.value`, {
|
||||
min: { message: FieldError.NON_NEGATIVE_NUMBER, value: 0 },
|
||||
valueAsNumber: true,
|
||||
})}
|
||||
/>
|
||||
<Collapsible label="Add more details">
|
||||
<FormSelect
|
||||
display="block"
|
||||
label="Location"
|
||||
options={locationOptions}
|
||||
placeholder={emptyOption}
|
||||
{...register(`background.experiences.0.location`)}
|
||||
/>
|
||||
</Collapsible>
|
||||
</>
|
||||
);
|
||||
@ -291,85 +275,71 @@ function CurrentJobSection() {
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<h6 className="mb-2 text-left text-xl font-medium text-slate-400">
|
||||
Current / Previous Job
|
||||
</h6>
|
||||
<div className="mb-5 rounded-lg border border-slate-200 px-10 py-5">
|
||||
<div className="mb-5">
|
||||
<FormRadioList
|
||||
defaultValue={watchJobType}
|
||||
isLabelHidden={true}
|
||||
label="Job Type"
|
||||
orientation="horizontal"
|
||||
{...register('background.experiences.0.jobType')}>
|
||||
<RadioList.Item
|
||||
key="Full-time"
|
||||
label="Full-time"
|
||||
value={JobType.FULLTIME}
|
||||
/>
|
||||
<RadioList.Item
|
||||
key="Internship"
|
||||
label="Internship"
|
||||
value={JobType.INTERN}
|
||||
/>
|
||||
</FormRadioList>
|
||||
</div>
|
||||
{watchJobType === JobType.FULLTIME ? (
|
||||
<FullTimeJobFields />
|
||||
) : (
|
||||
<InternshipJobFields />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
<FormSection title="Current / Previous Job">
|
||||
<FormRadioList
|
||||
defaultValue={watchJobType}
|
||||
isLabelHidden={true}
|
||||
label="Job Type"
|
||||
orientation="horizontal"
|
||||
{...register('background.experiences.0.jobType')}>
|
||||
<RadioList.Item
|
||||
key="Full-time"
|
||||
label="Full-time"
|
||||
value={JobType.FULLTIME}
|
||||
/>
|
||||
<RadioList.Item
|
||||
key="Internship"
|
||||
label="Internship"
|
||||
value={JobType.INTERN}
|
||||
/>
|
||||
</FormRadioList>
|
||||
{watchJobType === JobType.FULLTIME ? (
|
||||
<FullTimeJobFields />
|
||||
) : (
|
||||
<InternshipJobFields />
|
||||
)}
|
||||
</FormSection>
|
||||
);
|
||||
}
|
||||
|
||||
function EducationSection() {
|
||||
const { register } = useFormContext();
|
||||
return (
|
||||
<>
|
||||
<h6 className="mb-2 text-left text-xl font-medium text-slate-400">
|
||||
Education
|
||||
</h6>
|
||||
<div className="mb-5 rounded-lg border border-slate-200 px-10 py-5">
|
||||
<div className="mb-5 grid grid-cols-2 space-x-3">
|
||||
<FormSelect
|
||||
display="block"
|
||||
label="Education Level"
|
||||
options={educationLevelOptions}
|
||||
placeholder={emptyOption}
|
||||
{...register(`background.educations.0.type`)}
|
||||
/>
|
||||
<FormSelect
|
||||
display="block"
|
||||
label="Field"
|
||||
options={educationFieldOptions}
|
||||
placeholder={emptyOption}
|
||||
{...register(`background.educations.0.field`)}
|
||||
/>
|
||||
</div>
|
||||
<Collapsible label="Add more details">
|
||||
<div className="mb-5">
|
||||
<FormTextInput
|
||||
label="School"
|
||||
placeholder="e.g. National University of Singapore"
|
||||
{...register(`background.educations.0.school`)}
|
||||
/>
|
||||
</div>
|
||||
</Collapsible>
|
||||
<FormSection title="Education">
|
||||
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
|
||||
<FormSelect
|
||||
display="block"
|
||||
label="Education Level"
|
||||
options={educationLevelOptions}
|
||||
placeholder={emptyOption}
|
||||
{...register(`background.educations.0.type`)}
|
||||
/>
|
||||
<FormSelect
|
||||
display="block"
|
||||
label="Field"
|
||||
options={educationFieldOptions}
|
||||
placeholder={emptyOption}
|
||||
{...register(`background.educations.0.field`)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
<Collapsible label="Add more details">
|
||||
<FormTextInput
|
||||
label="School"
|
||||
placeholder="e.g. National University of Singapore"
|
||||
{...register(`background.educations.0.school`)}
|
||||
/>
|
||||
</Collapsible>
|
||||
</FormSection>
|
||||
);
|
||||
}
|
||||
|
||||
export default function BackgroundForm() {
|
||||
return (
|
||||
<div>
|
||||
<h5 className="mb-8 text-center text-4xl font-bold text-slate-900">
|
||||
<div className="space-y-6">
|
||||
<h2 className="mb-8 text-2xl font-bold text-slate-900 sm:text-center sm:text-4xl">
|
||||
Help us better gauge your offers
|
||||
</h5>
|
||||
<div>
|
||||
</h2>
|
||||
<div className="space-y-8 rounded-lg border border-slate-200 p-6 sm:space-y-16 sm:p-8">
|
||||
<YoeSection />
|
||||
<CurrentJobSection />
|
||||
<EducationSection />
|
||||
|
@ -29,6 +29,7 @@ import {
|
||||
yearOptions,
|
||||
} from '../../constants';
|
||||
import FormMonthYearPicker from '../../forms/FormMonthYearPicker';
|
||||
import FormSection from '../../forms/FormSection';
|
||||
import FormSelect from '../../forms/FormSelect';
|
||||
import FormTextArea from '../../forms/FormTextArea';
|
||||
import FormTextInput from '../../forms/FormTextInput';
|
||||
@ -45,23 +46,6 @@ type FullTimeOfferDetailsFormProps = Readonly<{
|
||||
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({
|
||||
index,
|
||||
remove,
|
||||
@ -95,8 +79,8 @@ function FullTimeOfferDetailsForm({
|
||||
|
||||
return (
|
||||
<div className="space-y-8 rounded-lg border border-slate-200 p-6 sm:space-y-16 sm:p-8">
|
||||
<Section title="Company & Title Information">
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<FormSection title="Company & Title Information">
|
||||
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
|
||||
<JobTitlesTypeahead
|
||||
required={true}
|
||||
value={{
|
||||
@ -120,7 +104,7 @@ function FullTimeOfferDetailsForm({
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
|
||||
<CompaniesTypeahead
|
||||
required={true}
|
||||
value={{
|
||||
@ -147,9 +131,9 @@ function FullTimeOfferDetailsForm({
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
<Section title="Compensation Details">
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
</FormSection>
|
||||
<FormSection title="Compensation Details">
|
||||
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
|
||||
<FormMonthYearPicker
|
||||
monthLabel="Date Received"
|
||||
monthRequired={true}
|
||||
@ -269,8 +253,8 @@ function FullTimeOfferDetailsForm({
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
<Section title="Additional Information">
|
||||
</FormSection>
|
||||
<FormSection title="Additional Information">
|
||||
<FormTextArea
|
||||
label="Negotiation Strategy / Interview Performance"
|
||||
placeholder="e.g. Did well in the behavioral interview / Used competing offers to negotiate for a higher salary"
|
||||
@ -296,7 +280,7 @@ function FullTimeOfferDetailsForm({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
</FormSection>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -327,7 +311,7 @@ function InternshipOfferDetailsForm({
|
||||
|
||||
return (
|
||||
<div className="space-y-8 rounded-lg border border-slate-200 p-6 sm:space-y-16 sm:p-8">
|
||||
<Section title="Company & Title Information">
|
||||
<FormSection title="Company & Title Information">
|
||||
<JobTitlesTypeahead
|
||||
required={true}
|
||||
value={{
|
||||
@ -342,7 +326,7 @@ function InternshipOfferDetailsForm({
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
|
||||
<CompaniesTypeahead
|
||||
required={true}
|
||||
value={{
|
||||
@ -369,7 +353,7 @@ function InternshipOfferDetailsForm({
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
|
||||
<FormSelect
|
||||
display="block"
|
||||
errorMessage={offerFields?.offersIntern?.internshipCycle?.message}
|
||||
@ -394,9 +378,9 @@ function InternshipOfferDetailsForm({
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
<Section title="Compensation Details">
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
</FormSection>
|
||||
<FormSection title="Compensation Details">
|
||||
<div className="grid grid-cols-1 gap-y-4 sm:grid-cols-2 sm:gap-6">
|
||||
<FormMonthYearPicker
|
||||
monthLabel="Date Received"
|
||||
monthRequired={true}
|
||||
@ -438,8 +422,8 @@ function InternshipOfferDetailsForm({
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
<Section title="Additional Information">
|
||||
</FormSection>
|
||||
<FormSection title="Additional Information">
|
||||
<FormTextArea
|
||||
label="Negotiation Strategy / Interview Performance"
|
||||
placeholder="e.g. Did well in the behavioral interview. Used competing offers to negotiate for a higher salary."
|
||||
@ -450,7 +434,7 @@ function InternshipOfferDetailsForm({
|
||||
placeholder="e.g. Encountered similar questions using the Technical Interview Handbook."
|
||||
{...register(`offers.${index}.comments`)}
|
||||
/>
|
||||
</Section>
|
||||
</FormSection>
|
||||
{index > 0 && (
|
||||
<div className="space-y-4 sm:space-y-6">
|
||||
<HorizontalDivider />
|
||||
@ -541,21 +525,19 @@ export default function OfferDetailsForm({
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h5 className="mb-8 text-center text-4xl font-bold text-slate-900">
|
||||
<h2 className="mb-8 text-2xl font-bold text-slate-900 sm:text-center sm:text-4xl">
|
||||
Fill in your offer details
|
||||
</h5>
|
||||
<div>
|
||||
<JobTypeTabs
|
||||
value={jobType}
|
||||
onChange={(newJobType) => {
|
||||
if (newJobType === jobType) {
|
||||
return;
|
||||
}
|
||||
</h2>
|
||||
<JobTypeTabs
|
||||
value={jobType}
|
||||
onChange={(newJobType) => {
|
||||
if (newJobType === jobType) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDialogOpen(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
setDialogOpen(true);
|
||||
}}
|
||||
/>
|
||||
<OfferDetailsFormArray
|
||||
fieldArrayValues={fieldArrayValues}
|
||||
jobType={jobType}
|
||||
|
Reference in New Issue
Block a user