mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 04:33:42 +08:00
[offers][fix] Fix offers form experience section (#427)
This commit is contained in:
@ -1,15 +1,9 @@
|
|||||||
import { useRouter } from 'next/router';
|
|
||||||
// Import { useState } from 'react';
|
// Import { useState } from 'react';
|
||||||
// import { setTimeout } from 'timers';
|
// import { setTimeout } from 'timers';
|
||||||
import { DocumentDuplicateIcon } from '@heroicons/react/20/solid';
|
import { DocumentDuplicateIcon } from '@heroicons/react/20/solid';
|
||||||
import { EyeIcon } from '@heroicons/react/24/outline';
|
|
||||||
import { Button, TextInput, useToast } from '@tih/ui';
|
import { Button, TextInput, useToast } from '@tih/ui';
|
||||||
|
|
||||||
import {
|
import { copyProfileLink, getProfileLink } from '~/utils/offers/link';
|
||||||
copyProfileLink,
|
|
||||||
getProfileLink,
|
|
||||||
getProfilePath,
|
|
||||||
} from '~/utils/offers/link';
|
|
||||||
|
|
||||||
type OfferProfileSaveProps = Readonly<{
|
type OfferProfileSaveProps = Readonly<{
|
||||||
profileId: string;
|
profileId: string;
|
||||||
@ -23,7 +17,6 @@ export default function OffersProfileSave({
|
|||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
// Const [isSaving, setSaving] = useState(false);
|
// Const [isSaving, setSaving] = useState(false);
|
||||||
// const [isSaved, setSaved] = useState(false);
|
// const [isSaved, setSaved] = useState(false);
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// Const saveProfile = () => {
|
// Const saveProfile = () => {
|
||||||
// setSaving(true);
|
// setSaving(true);
|
||||||
@ -82,14 +75,6 @@ export default function OffersProfileSave({
|
|||||||
onClick={saveProfile}
|
onClick={saveProfile}
|
||||||
/>
|
/>
|
||||||
</div> */}
|
</div> */}
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
icon={EyeIcon}
|
|
||||||
label="View your profile"
|
|
||||||
variant="special"
|
|
||||||
onClick={() => router.push(getProfilePath(profileId, token))}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import { EyeIcon } from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
|
import { Button } from '~/../../../packages/ui/dist';
|
||||||
|
import { getProfilePath } from '~/utils/offers/link';
|
||||||
|
|
||||||
|
import OfferAnalysis from '../offerAnalysis/OfferAnalysis';
|
||||||
|
|
||||||
|
import type { ProfileAnalysis } from '~/types/offers';
|
||||||
|
|
||||||
|
type Props = Readonly<{
|
||||||
|
analysis?: ProfileAnalysis | null;
|
||||||
|
isError: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
profileId?: string;
|
||||||
|
token?: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function OffersSubmissionAnalysis({
|
||||||
|
analysis,
|
||||||
|
isError,
|
||||||
|
isLoading,
|
||||||
|
profileId = '',
|
||||||
|
token = '',
|
||||||
|
}: Props) {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h5 className="mb-8 text-center text-4xl font-bold text-slate-900">
|
||||||
|
Result
|
||||||
|
</h5>
|
||||||
|
<OfferAnalysis
|
||||||
|
key={3}
|
||||||
|
allAnalysis={analysis}
|
||||||
|
isError={isError}
|
||||||
|
isLoading={isLoading}
|
||||||
|
/>
|
||||||
|
<div className="mt-8 text-center">
|
||||||
|
<Button
|
||||||
|
icon={EyeIcon}
|
||||||
|
label="View your profile"
|
||||||
|
variant="special"
|
||||||
|
onClick={() => router.push(getProfilePath(profileId, token))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -15,16 +15,17 @@ import type {
|
|||||||
} from '~/components/offers/types';
|
} from '~/components/offers/types';
|
||||||
import type { Month } from '~/components/shared/MonthYearPicker';
|
import type { Month } from '~/components/shared/MonthYearPicker';
|
||||||
|
|
||||||
import { cleanObject, removeInvalidMoneyData } from '~/utils/offers/form';
|
import {
|
||||||
|
cleanObject,
|
||||||
|
removeEmptyObjects,
|
||||||
|
removeInvalidMoneyData,
|
||||||
|
} from '~/utils/offers/form';
|
||||||
import { getCurrentMonth, getCurrentYear } from '~/utils/offers/time';
|
import { getCurrentMonth, getCurrentYear } from '~/utils/offers/time';
|
||||||
import { trpc } from '~/utils/trpc';
|
import { trpc } from '~/utils/trpc';
|
||||||
|
|
||||||
import OfferAnalysis from '../offerAnalysis/OfferAnalysis';
|
import OffersSubmissionAnalysis from './OffersSubmissionAnalysis';
|
||||||
|
|
||||||
import type {
|
import type { ProfileAnalysis } from '~/types/offers';
|
||||||
CreateOfferProfileResponse,
|
|
||||||
ProfileAnalysis,
|
|
||||||
} from '~/types/offers';
|
|
||||||
|
|
||||||
const defaultOfferValues = {
|
const defaultOfferValues = {
|
||||||
comments: '',
|
comments: '',
|
||||||
@ -73,15 +74,12 @@ type Props = Readonly<{
|
|||||||
|
|
||||||
export default function OffersSubmissionForm({
|
export default function OffersSubmissionForm({
|
||||||
initialOfferProfileValues = defaultOfferProfileValues,
|
initialOfferProfileValues = defaultOfferProfileValues,
|
||||||
profileId,
|
profileId: editProfileId = '',
|
||||||
token,
|
token: editToken = '',
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [formStep, setFormStep] = useState(0);
|
const [formStep, setFormStep] = useState(0);
|
||||||
const [createProfileResponse, setCreateProfileResponse] =
|
const [profileId, setProfileId] = useState(editProfileId);
|
||||||
useState<CreateOfferProfileResponse>({
|
const [token, setToken] = useState(editToken);
|
||||||
id: profileId || '',
|
|
||||||
token: token || '',
|
|
||||||
});
|
|
||||||
const [analysis, setAnalysis] = useState<ProfileAnalysis | null>(null);
|
const [analysis, setAnalysis] = useState<ProfileAnalysis | null>(null);
|
||||||
|
|
||||||
const pageRef = useRef<HTMLDivElement>(null);
|
const pageRef = useRef<HTMLDivElement>(null);
|
||||||
@ -125,11 +123,7 @@ export default function OffersSubmissionForm({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: (
|
component: (
|
||||||
<OffersProfileSave
|
<OffersProfileSave key={2} profileId={profileId} token={token} />
|
||||||
key={2}
|
|
||||||
profileId={createProfileResponse.id || ''}
|
|
||||||
token={createProfileResponse.token}
|
|
||||||
/>
|
|
||||||
),
|
),
|
||||||
hasNext: true,
|
hasNext: true,
|
||||||
hasPrevious: false,
|
hasPrevious: false,
|
||||||
@ -137,17 +131,13 @@ export default function OffersSubmissionForm({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: (
|
component: (
|
||||||
<div>
|
<OffersSubmissionAnalysis
|
||||||
<h5 className="mb-8 text-center text-4xl font-bold text-slate-900">
|
analysis={analysis}
|
||||||
Result
|
isError={generateAnalysisMutation.isError}
|
||||||
</h5>
|
isLoading={generateAnalysisMutation.isLoading}
|
||||||
<OfferAnalysis
|
profileId={profileId}
|
||||||
key={3}
|
token={token}
|
||||||
allAnalysis={analysis}
|
/>
|
||||||
isError={generateAnalysisMutation.isError}
|
|
||||||
isLoading={generateAnalysisMutation.isLoading}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
),
|
),
|
||||||
hasNext: false,
|
hasNext: false,
|
||||||
hasPrevious: true,
|
hasPrevious: true,
|
||||||
@ -184,7 +174,8 @@ export default function OffersSubmissionForm({
|
|||||||
generateAnalysisMutation.mutate({
|
generateAnalysisMutation.mutate({
|
||||||
profileId: data?.id || '',
|
profileId: data?.id || '',
|
||||||
});
|
});
|
||||||
setCreateProfileResponse(data);
|
setProfileId(data.id);
|
||||||
|
setToken(data.token);
|
||||||
setFormStep(formStep + 1);
|
setFormStep(formStep + 1);
|
||||||
scrollToTop();
|
scrollToTop();
|
||||||
},
|
},
|
||||||
@ -197,6 +188,7 @@ export default function OffersSubmissionForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
data = removeInvalidMoneyData(data);
|
data = removeInvalidMoneyData(data);
|
||||||
|
data.offers = removeEmptyObjects(data.offers);
|
||||||
|
|
||||||
const background = cleanObject(data.background);
|
const background = cleanObject(data.background);
|
||||||
background.specificYoes = data.background.specificYoes.filter(
|
background.specificYoes = data.background.specificYoes.filter(
|
||||||
|
@ -18,7 +18,6 @@ import {
|
|||||||
CURRENCY_OPTIONS,
|
CURRENCY_OPTIONS,
|
||||||
} from '~/utils/offers/currency/CurrencyEnum';
|
} from '~/utils/offers/currency/CurrencyEnum';
|
||||||
|
|
||||||
import FormMonthYearPicker from '../../forms/FormMonthYearPicker';
|
|
||||||
import FormRadioList from '../../forms/FormRadioList';
|
import FormRadioList from '../../forms/FormRadioList';
|
||||||
import FormSelect from '../../forms/FormSelect';
|
import FormSelect from '../../forms/FormSelect';
|
||||||
import FormTextInput from '../../forms/FormTextInput';
|
import FormTextInput from '../../forms/FormTextInput';
|
||||||
@ -235,7 +234,6 @@ function InternshipJobFields() {
|
|||||||
function CurrentJobSection() {
|
function CurrentJobSection() {
|
||||||
const { register } = useFormContext();
|
const { register } = useFormContext();
|
||||||
const watchJobType = useWatch({
|
const watchJobType = useWatch({
|
||||||
defaultValue: JobType.FULLTIME,
|
|
||||||
name: 'background.experiences.0.jobType',
|
name: 'background.experiences.0.jobType',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -247,7 +245,7 @@ function CurrentJobSection() {
|
|||||||
<div className="mb-5 rounded-lg border border-slate-200 px-10 py-5">
|
<div className="mb-5 rounded-lg border border-slate-200 px-10 py-5">
|
||||||
<div className="mb-5">
|
<div className="mb-5">
|
||||||
<FormRadioList
|
<FormRadioList
|
||||||
defaultValue={JobType.FULLTIME}
|
defaultValue={watchJobType}
|
||||||
isLabelHidden={true}
|
isLabelHidden={true}
|
||||||
label="Job Type"
|
label="Job Type"
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
@ -306,22 +304,6 @@ function EducationSection() {
|
|||||||
{...register(`background.educations.0.school`)}
|
{...register(`background.educations.0.school`)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 space-x-3">
|
|
||||||
<FormMonthYearPicker
|
|
||||||
monthLabel="Candidature Start"
|
|
||||||
yearLabel=""
|
|
||||||
{...register(`background.educations.0.startDate`, {
|
|
||||||
required: FieldError.REQUIRED,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<FormMonthYearPicker
|
|
||||||
monthLabel="Candidature End"
|
|
||||||
yearLabel=""
|
|
||||||
{...register(`background.educations.0.endDate`, {
|
|
||||||
required: FieldError.REQUIRED,
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -34,7 +34,17 @@ export default function OffersEditPage() {
|
|||||||
experiences:
|
experiences:
|
||||||
experiences.length === 0
|
experiences.length === 0
|
||||||
? [{ jobType: JobType.FULLTIME }]
|
? [{ jobType: JobType.FULLTIME }]
|
||||||
: experiences,
|
: experiences.map((exp) => ({
|
||||||
|
companyId: exp.company?.id,
|
||||||
|
durationInMonths: exp.durationInMonths,
|
||||||
|
id: exp.id,
|
||||||
|
jobType: exp.jobType,
|
||||||
|
level: exp.level,
|
||||||
|
location: exp.location,
|
||||||
|
monthlySalary: exp.monthlySalary,
|
||||||
|
title: exp.title,
|
||||||
|
totalCompensation: exp.totalCompensation,
|
||||||
|
})),
|
||||||
id,
|
id,
|
||||||
specificYoes,
|
specificYoes,
|
||||||
totalYoe,
|
totalYoe,
|
||||||
|
@ -285,11 +285,7 @@ export const offersProfileRouter = createRouter()
|
|||||||
},
|
},
|
||||||
experiences: {
|
experiences: {
|
||||||
create: input.background.experiences.map(async (x) => {
|
create: input.background.experiences.map(async (x) => {
|
||||||
if (
|
if (x.jobType === JobType.FULLTIME) {
|
||||||
x.jobType === JobType.FULLTIME &&
|
|
||||||
x.totalCompensation?.currency != null &&
|
|
||||||
x.totalCompensation?.value != null
|
|
||||||
) {
|
|
||||||
if (x.companyId) {
|
if (x.companyId) {
|
||||||
return {
|
return {
|
||||||
company: {
|
company: {
|
||||||
@ -301,18 +297,21 @@ export const offersProfileRouter = createRouter()
|
|||||||
jobType: x.jobType,
|
jobType: x.jobType,
|
||||||
level: x.level,
|
level: x.level,
|
||||||
title: x.title,
|
title: x.title,
|
||||||
totalCompensation: {
|
totalCompensation:
|
||||||
create: {
|
x.totalCompensation != null
|
||||||
baseCurrency: baseCurrencyString,
|
? {
|
||||||
baseValue: await convert(
|
create: {
|
||||||
x.totalCompensation.value,
|
baseCurrency: baseCurrencyString,
|
||||||
x.totalCompensation.currency,
|
baseValue: await convert(
|
||||||
baseCurrencyString,
|
x.totalCompensation.value,
|
||||||
),
|
x.totalCompensation.currency,
|
||||||
currency: x.totalCompensation.currency,
|
baseCurrencyString,
|
||||||
value: x.totalCompensation.value,
|
),
|
||||||
},
|
currency: x.totalCompensation.currency,
|
||||||
},
|
value: x.totalCompensation.value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -321,25 +320,24 @@ export const offersProfileRouter = createRouter()
|
|||||||
level: x.level,
|
level: x.level,
|
||||||
location: x.location,
|
location: x.location,
|
||||||
title: x.title,
|
title: x.title,
|
||||||
totalCompensation: {
|
totalCompensation:
|
||||||
create: {
|
x.totalCompensation != null
|
||||||
baseCurrency: baseCurrencyString,
|
? {
|
||||||
baseValue: await convert(
|
create: {
|
||||||
x.totalCompensation.value,
|
baseCurrency: baseCurrencyString,
|
||||||
x.totalCompensation.currency,
|
baseValue: await convert(
|
||||||
baseCurrencyString,
|
x.totalCompensation.value,
|
||||||
),
|
x.totalCompensation.currency,
|
||||||
currency: x.totalCompensation.currency,
|
baseCurrencyString,
|
||||||
value: x.totalCompensation.value,
|
),
|
||||||
},
|
currency: x.totalCompensation.currency,
|
||||||
},
|
value: x.totalCompensation.value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (
|
if (x.jobType === JobType.INTERN) {
|
||||||
x.jobType === JobType.INTERN &&
|
|
||||||
x.monthlySalary?.currency != null &&
|
|
||||||
x.monthlySalary?.value != null
|
|
||||||
) {
|
|
||||||
if (x.companyId) {
|
if (x.companyId) {
|
||||||
return {
|
return {
|
||||||
company: {
|
company: {
|
||||||
@ -349,36 +347,42 @@ export const offersProfileRouter = createRouter()
|
|||||||
},
|
},
|
||||||
durationInMonths: x.durationInMonths,
|
durationInMonths: x.durationInMonths,
|
||||||
jobType: x.jobType,
|
jobType: x.jobType,
|
||||||
monthlySalary: {
|
monthlySalary:
|
||||||
create: {
|
x.monthlySalary != null
|
||||||
baseCurrency: baseCurrencyString,
|
? {
|
||||||
baseValue: await convert(
|
create: {
|
||||||
x.monthlySalary.value,
|
baseCurrency: baseCurrencyString,
|
||||||
x.monthlySalary.currency,
|
baseValue: await convert(
|
||||||
baseCurrencyString,
|
x.monthlySalary.value,
|
||||||
),
|
x.monthlySalary.currency,
|
||||||
currency: x.monthlySalary.currency,
|
baseCurrencyString,
|
||||||
value: x.monthlySalary.value,
|
),
|
||||||
},
|
currency: x.monthlySalary.currency,
|
||||||
},
|
value: x.monthlySalary.value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
title: x.title,
|
title: x.title,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
durationInMonths: x.durationInMonths,
|
durationInMonths: x.durationInMonths,
|
||||||
jobType: x.jobType,
|
jobType: x.jobType,
|
||||||
monthlySalary: {
|
monthlySalary:
|
||||||
create: {
|
x.monthlySalary != null
|
||||||
baseCurrency: baseCurrencyString,
|
? {
|
||||||
baseValue: await convert(
|
create: {
|
||||||
x.monthlySalary.value,
|
baseCurrency: baseCurrencyString,
|
||||||
x.monthlySalary.currency,
|
baseValue: await convert(
|
||||||
baseCurrencyString,
|
x.monthlySalary.value,
|
||||||
),
|
x.monthlySalary.currency,
|
||||||
currency: x.monthlySalary.currency,
|
baseCurrencyString,
|
||||||
value: x.monthlySalary.value,
|
),
|
||||||
},
|
currency: x.monthlySalary.currency,
|
||||||
},
|
value: x.monthlySalary.value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
title: x.title,
|
title: x.title,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -710,6 +714,7 @@ export const offersProfileRouter = createRouter()
|
|||||||
data: {
|
data: {
|
||||||
companyId: exp.companyId, // TODO: check if can change with connect or whether there is a difference
|
companyId: exp.companyId, // TODO: check if can change with connect or whether there is a difference
|
||||||
durationInMonths: exp.durationInMonths,
|
durationInMonths: exp.durationInMonths,
|
||||||
|
jobType: exp.jobType as JobType,
|
||||||
level: exp.level,
|
level: exp.level,
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
@ -818,18 +823,20 @@ export const offersProfileRouter = createRouter()
|
|||||||
level: exp.level,
|
level: exp.level,
|
||||||
location: exp.location,
|
location: exp.location,
|
||||||
title: exp.title,
|
title: exp.title,
|
||||||
totalCompensation: {
|
totalCompensation: exp.totalCompensation
|
||||||
create: {
|
? {
|
||||||
baseCurrency: baseCurrencyString,
|
create: {
|
||||||
baseValue: await convert(
|
baseCurrency: baseCurrencyString,
|
||||||
exp.totalCompensation.value,
|
baseValue: await convert(
|
||||||
exp.totalCompensation.currency,
|
exp.totalCompensation.value,
|
||||||
baseCurrencyString,
|
exp.totalCompensation.currency,
|
||||||
),
|
baseCurrencyString,
|
||||||
currency: exp.totalCompensation.currency,
|
),
|
||||||
value: exp.totalCompensation.value,
|
currency: exp.totalCompensation.currency,
|
||||||
},
|
value: exp.totalCompensation.value,
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -32,6 +32,33 @@ export function cleanObject(object: any) {
|
|||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes empty objects from an object.
|
||||||
|
* @param object
|
||||||
|
* @returns object without empty values or objects.
|
||||||
|
*/
|
||||||
|
export function removeEmptyObjects(object: any) {
|
||||||
|
Object.entries(object).forEach(([k, v]) => {
|
||||||
|
if ((v && typeof v === 'object') || Array.isArray(v)) {
|
||||||
|
removeEmptyObjects(v);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
v &&
|
||||||
|
typeof v === 'object' &&
|
||||||
|
!Object.keys(v).length &&
|
||||||
|
!Array.isArray(v)
|
||||||
|
) {
|
||||||
|
if (Array.isArray(object)) {
|
||||||
|
const index = object.indexOf(v);
|
||||||
|
object.splice(index, 1);
|
||||||
|
} else if (!(v instanceof Date)) {
|
||||||
|
delete object[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes invalid money data from an object.
|
* Removes invalid money data from an object.
|
||||||
* If currency is present but value is not present, money object is removed.
|
* If currency is present but value is not present, money object is removed.
|
||||||
|
Reference in New Issue
Block a user