mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2026-04-27 03:52:34 +08:00
[offers][feat] Integrate offers profile edit (#403)
* [offers][fix] Fix offer analysis and save * [offers][fix] Fix profile view page * [offers][feat] Add offers profile edit
This commit is contained in:
@@ -5,20 +5,20 @@ export const emptyOption = '----';
|
||||
// TODO: use enums
|
||||
export const titleOptions = [
|
||||
{
|
||||
label: 'Software engineer',
|
||||
value: 'Software engineer',
|
||||
label: 'Software Engineer',
|
||||
value: 'Software Engineer',
|
||||
},
|
||||
{
|
||||
label: 'Frontend engineer',
|
||||
value: 'Frontend engineer',
|
||||
label: 'Frontend Engineer',
|
||||
value: 'Frontend Engineer',
|
||||
},
|
||||
{
|
||||
label: 'Backend engineer',
|
||||
value: 'Backend engineer',
|
||||
label: 'Backend Engineer',
|
||||
value: 'Backend Engineer',
|
||||
},
|
||||
{
|
||||
label: 'Full-stack engineer',
|
||||
value: 'Full-stack engineer',
|
||||
label: 'Full-stack Engineer',
|
||||
value: 'Full-stack Engineer',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -95,10 +95,18 @@ export const educationFieldOptions = [
|
||||
label: 'Information Security',
|
||||
value: 'Information Security',
|
||||
},
|
||||
{
|
||||
label: 'Information Systems',
|
||||
value: 'Information Systems',
|
||||
},
|
||||
{
|
||||
label: 'Business Analytics',
|
||||
value: 'Business Analytics',
|
||||
},
|
||||
{
|
||||
label: 'Data Science and Analytics',
|
||||
value: 'Data Science and Analytics',
|
||||
},
|
||||
];
|
||||
|
||||
export enum FieldError {
|
||||
|
||||
@@ -1,13 +1,30 @@
|
||||
import { useRouter } from 'next/router';
|
||||
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() {
|
||||
import {
|
||||
copyProfileLink,
|
||||
getProfileLink,
|
||||
getProfilePath,
|
||||
} from '~/utils/offers/link';
|
||||
|
||||
type OfferProfileSaveProps = Readonly<{
|
||||
profileId: string;
|
||||
token?: string;
|
||||
}>;
|
||||
|
||||
export default function OfferProfileSave({
|
||||
profileId,
|
||||
token,
|
||||
}: OfferProfileSaveProps) {
|
||||
const [linkCopied, setLinkCopied] = useState(false);
|
||||
const [isSaving, setSaving] = useState(false);
|
||||
const [isSaved, setSaved] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
const saveProfile = () => {
|
||||
setSaving(true);
|
||||
setTimeout(() => {
|
||||
@@ -27,13 +44,13 @@ export default function OfferProfileSave() {
|
||||
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="mb-5 grid grid-cols-12 gap-4">
|
||||
<div className="col-span-11">
|
||||
<TextInput
|
||||
disabled={true}
|
||||
isLabelHidden={true}
|
||||
label="Edit link"
|
||||
value="link.myprofile-auto-generate..."
|
||||
value={getProfileLink(profileId, token)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
@@ -41,10 +58,12 @@ export default function OfferProfileSave() {
|
||||
isLabelHidden={true}
|
||||
label="Copy"
|
||||
variant="primary"
|
||||
onClick={() => setLinkCopied(true)}
|
||||
onClick={() => {
|
||||
copyProfileLink(profileId, token), setLinkCopied(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<div className="mb-20">
|
||||
{linkCopied && (
|
||||
<p className="text-purple-700">Link copied to clipboard!</p>
|
||||
)}
|
||||
@@ -60,13 +79,18 @@ export default function OfferProfileSave() {
|
||||
disabled={isSaved}
|
||||
icon={isSaved ? CheckIcon : BookmarkSquareIcon}
|
||||
isLoading={isSaving}
|
||||
label="Save to user profile"
|
||||
label={isSaved ? 'Saved to user profile' : 'Save to user profile'}
|
||||
variant="primary"
|
||||
onClick={saveProfile}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-10">
|
||||
<Button icon={EyeIcon} label="View your profile" variant="special" />
|
||||
<Button
|
||||
icon={EyeIcon}
|
||||
label="View your profile"
|
||||
variant="special"
|
||||
onClick={() => router.push(getProfilePath(profileId, token))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,243 @@
|
||||
import { useRef, 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 { Breadcrumbs } from '~/components/offers/Breadcrumb';
|
||||
import OfferAnalysis from '~/components/offers/offersSubmission/analysis/OfferAnalysis';
|
||||
import OfferProfileSave from '~/components/offers/offersSubmission/OfferProfileSave';
|
||||
import BackgroundForm from '~/components/offers/offersSubmission/submissionForm/BackgroundForm';
|
||||
import OfferDetailsForm from '~/components/offers/offersSubmission/submissionForm/OfferDetailsForm';
|
||||
import type {
|
||||
OfferFormData,
|
||||
OffersProfileFormData,
|
||||
} from '~/components/offers/types';
|
||||
import { JobType } from '~/components/offers/types';
|
||||
import type { Month } from '~/components/shared/MonthYearPicker';
|
||||
|
||||
import { cleanObject, removeInvalidMoneyData } from '~/utils/offers/form';
|
||||
import { getCurrentMonth, getCurrentYear } from '~/utils/offers/time';
|
||||
import { trpc } from '~/utils/trpc';
|
||||
|
||||
import type { CreateOfferProfileResponse } from '~/types/offers';
|
||||
|
||||
const defaultOfferValues = {
|
||||
comments: '',
|
||||
companyId: '',
|
||||
jobType: JobType.FullTime,
|
||||
location: '',
|
||||
monthYearReceived: {
|
||||
month: getCurrentMonth() as Month,
|
||||
year: getCurrentYear(),
|
||||
},
|
||||
negotiationStrategy: '',
|
||||
};
|
||||
|
||||
export const defaultFullTimeOfferValues = {
|
||||
...defaultOfferValues,
|
||||
jobType: JobType.FullTime,
|
||||
};
|
||||
|
||||
export const defaultInternshipOfferValues = {
|
||||
...defaultOfferValues,
|
||||
jobType: JobType.Intern,
|
||||
};
|
||||
|
||||
const defaultOfferProfileValues = {
|
||||
background: {
|
||||
educations: [],
|
||||
experiences: [{ jobType: JobType.FullTime }],
|
||||
specificYoes: [],
|
||||
totalYoe: 0,
|
||||
},
|
||||
offers: [defaultOfferValues],
|
||||
};
|
||||
|
||||
type FormStep = {
|
||||
component: JSX.Element;
|
||||
hasNext: boolean;
|
||||
hasPrevious: boolean;
|
||||
label: string;
|
||||
};
|
||||
|
||||
type Props = Readonly<{
|
||||
initialOfferProfileValues?: OffersProfileFormData;
|
||||
profileId?: string;
|
||||
token?: string;
|
||||
}>;
|
||||
|
||||
export default function OffersSubmissionForm({
|
||||
initialOfferProfileValues = defaultOfferProfileValues,
|
||||
profileId,
|
||||
token,
|
||||
}: Props) {
|
||||
const [formStep, setFormStep] = useState(0);
|
||||
const [createProfileResponse, setCreateProfileResponse] =
|
||||
useState<CreateOfferProfileResponse>({
|
||||
id: profileId || '',
|
||||
token: token || '',
|
||||
});
|
||||
|
||||
const pageRef = useRef<HTMLDivElement>(null);
|
||||
const scrollToTop = () =>
|
||||
pageRef.current?.scrollTo({ behavior: 'smooth', top: 0 });
|
||||
const formMethods = useForm<OffersProfileFormData>({
|
||||
defaultValues: initialOfferProfileValues,
|
||||
mode: 'all',
|
||||
});
|
||||
const { handleSubmit, trigger } = formMethods;
|
||||
|
||||
const formSteps: Array<FormStep> = [
|
||||
{
|
||||
component: <OfferDetailsForm key={0} />,
|
||||
hasNext: true,
|
||||
hasPrevious: false,
|
||||
label: 'Offer details',
|
||||
},
|
||||
{
|
||||
component: <BackgroundForm key={1} />,
|
||||
hasNext: false,
|
||||
hasPrevious: true,
|
||||
label: 'Background',
|
||||
},
|
||||
{
|
||||
component: <OfferAnalysis key={2} profileId={createProfileResponse.id} />,
|
||||
hasNext: true,
|
||||
hasPrevious: false,
|
||||
label: 'Analysis',
|
||||
},
|
||||
{
|
||||
component: (
|
||||
<OfferProfileSave
|
||||
key={3}
|
||||
profileId={createProfileResponse.id || ''}
|
||||
token={createProfileResponse.token}
|
||||
/>
|
||||
),
|
||||
hasNext: false,
|
||||
hasPrevious: false,
|
||||
label: 'Save',
|
||||
},
|
||||
];
|
||||
|
||||
const formStepsLabels = formSteps.map((step) => step.label);
|
||||
|
||||
const nextStep = async (currStep: number) => {
|
||||
if (currStep === 0) {
|
||||
const result = await trigger('offers');
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
setFormStep(formStep + 1);
|
||||
scrollToTop();
|
||||
};
|
||||
|
||||
const previousStep = () => {
|
||||
setFormStep(formStep - 1);
|
||||
scrollToTop();
|
||||
};
|
||||
|
||||
const generateAnalysisMutation = trpc.useMutation(
|
||||
['offers.analysis.generate'],
|
||||
{
|
||||
onError(error) {
|
||||
console.error(error.message);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const mutationpath =
|
||||
profileId && token ? 'offers.profile.update' : 'offers.profile.create';
|
||||
|
||||
const createOrUpdateMutation = trpc.useMutation([mutationpath], {
|
||||
onError(error) {
|
||||
console.error(error.message);
|
||||
},
|
||||
onSuccess(data) {
|
||||
generateAnalysisMutation.mutate({
|
||||
profileId: data?.id || '',
|
||||
});
|
||||
setCreateProfileResponse(data);
|
||||
setFormStep(formStep + 1);
|
||||
scrollToTop();
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit: SubmitHandler<OffersProfileFormData> = async (data) => {
|
||||
const result = await trigger();
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
data = removeInvalidMoneyData(data);
|
||||
|
||||
const background = cleanObject(data.background);
|
||||
background.specificYoes = data.background.specificYoes.filter(
|
||||
(specificYoe) => specificYoe.domain && specificYoe.yoe > 0,
|
||||
);
|
||||
if (Object.entries(background.experiences[0]).length === 1) {
|
||||
background.experiences = [];
|
||||
}
|
||||
|
||||
const offers = data.offers.map((offer: OfferFormData) => ({
|
||||
...offer,
|
||||
monthYearReceived: new Date(
|
||||
offer.monthYearReceived.year,
|
||||
offer.monthYearReceived.month - 1, // Convert month to monthIndex
|
||||
),
|
||||
}));
|
||||
|
||||
if (profileId && token) {
|
||||
createOrUpdateMutation.mutate({
|
||||
background,
|
||||
id: profileId,
|
||||
offers,
|
||||
token,
|
||||
});
|
||||
} else {
|
||||
createOrUpdateMutation.mutate({ background, offers });
|
||||
}
|
||||
};
|
||||
|
||||
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">
|
||||
<Breadcrumbs currentStep={formStep} stepLabels={formStepsLabels} />
|
||||
</div>
|
||||
<FormProvider {...formMethods}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{formSteps[formStep].component}
|
||||
{/* <pre>{JSON.stringify(formMethods.watch(), null, 2)}</pre> */}
|
||||
{formSteps[formStep].hasNext && (
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
disabled={false}
|
||||
icon={ArrowRightIcon}
|
||||
label="Next"
|
||||
variant="secondary"
|
||||
onClick={() => nextStep(formStep)}
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
import Error from 'next/error';
|
||||
import { useEffect } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { HorizontalDivider, Spinner, Tabs } from '@tih/ui';
|
||||
|
||||
import { trpc } from '~/utils/trpc';
|
||||
|
||||
import OfferPercentileAnalysis from '../analysis/OfferPercentileAnalysis';
|
||||
import OfferProfileCard from '../analysis/OfferProfileCard';
|
||||
import { OVERALL_TAB } from '../constants';
|
||||
import OfferPercentileAnalysis from './OfferPercentileAnalysis';
|
||||
import OfferProfileCard from './OfferProfileCard';
|
||||
import { OVERALL_TAB } from '../../constants';
|
||||
|
||||
import type {
|
||||
Analysis,
|
||||
@@ -105,34 +104,32 @@ export default function OfferAnalysis({ profileId }: OfferAnalysisProps) {
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{getAnalysisResult.isError && (
|
||||
<Error
|
||||
statusCode={404}
|
||||
title="An error occurred while generating profile analysis."
|
||||
/>
|
||||
)}
|
||||
{!getAnalysisResult.isError && analysis && (
|
||||
<div>
|
||||
<h5 className="mb-2 text-center text-4xl font-bold text-gray-900">
|
||||
Result
|
||||
</h5>
|
||||
{getAnalysisResult.isLoading ? (
|
||||
<Spinner className="m-10" display="block" size="lg" />
|
||||
) : (
|
||||
<div>
|
||||
<Tabs
|
||||
label="Result Navigation"
|
||||
tabs={tabOptions}
|
||||
value={tab}
|
||||
onChange={setTab}
|
||||
/>
|
||||
<HorizontalDivider className="mb-5" />
|
||||
<OfferAnalysisContent analysis={analysis} tab={tab} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
analysis && (
|
||||
<div>
|
||||
<h5 className="mb-2 text-center text-4xl font-bold text-gray-900">
|
||||
Result
|
||||
</h5>
|
||||
{getAnalysisResult.isError && (
|
||||
<p className="m-10 text-center">
|
||||
An error occurred while generating profile analysis.
|
||||
</p>
|
||||
)}
|
||||
{getAnalysisResult.isLoading && (
|
||||
<Spinner className="m-10" display="block" size="lg" />
|
||||
)}
|
||||
{!getAnalysisResult.isError && !getAnalysisResult.isLoading && (
|
||||
<div>
|
||||
<Tabs
|
||||
label="Result Navigation"
|
||||
tabs={tabOptions}
|
||||
value={tab}
|
||||
onChange={setTab}
|
||||
/>
|
||||
<HorizontalDivider className="mb-5" />
|
||||
<OfferAnalysisContent analysis={analysis} tab={tab} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { UserCircleIcon } from '@heroicons/react/24/outline';
|
||||
import { HorizontalDivider } from '~/../../../packages/ui/dist';
|
||||
import { formatDate } from '~/utils/offers/time';
|
||||
|
||||
import { JobType } from '../types';
|
||||
import { JobType } from '../../types';
|
||||
|
||||
import type { AnalysisOffer } from '~/types/offers';
|
||||
|
||||
@@ -15,9 +15,9 @@ import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
|
||||
|
||||
import { CURRENCY_OPTIONS } from '~/utils/offers/currency/CurrencyEnum';
|
||||
|
||||
import FormRadioList from '../forms/FormRadioList';
|
||||
import FormSelect from '../forms/FormSelect';
|
||||
import FormTextInput from '../forms/FormTextInput';
|
||||
import FormRadioList from '../../forms/FormRadioList';
|
||||
import FormSelect from '../../forms/FormSelect';
|
||||
import FormTextInput from '../../forms/FormTextInput';
|
||||
|
||||
function YoeSection() {
|
||||
const { register, formState } = useFormContext<{
|
||||
@@ -16,8 +16,7 @@ import CompaniesTypeahead from '~/components/shared/CompaniesTypeahead';
|
||||
import {
|
||||
defaultFullTimeOfferValues,
|
||||
defaultInternshipOfferValues,
|
||||
} from '~/pages/offers/submit';
|
||||
|
||||
} from '../OffersSubmissionForm';
|
||||
import {
|
||||
emptyOption,
|
||||
FieldError,
|
||||
@@ -25,15 +24,15 @@ import {
|
||||
locationOptions,
|
||||
titleOptions,
|
||||
yearOptions,
|
||||
} from '../constants';
|
||||
import FormMonthYearPicker from '../forms/FormMonthYearPicker';
|
||||
import FormSelect from '../forms/FormSelect';
|
||||
import FormTextArea from '../forms/FormTextArea';
|
||||
import FormTextInput from '../forms/FormTextInput';
|
||||
import type { OfferFormData } from '../types';
|
||||
import { JobTypeLabel } from '../types';
|
||||
import { JobType } from '../types';
|
||||
import { CURRENCY_OPTIONS } from '../../../utils/offers/currency/CurrencyEnum';
|
||||
} from '../../constants';
|
||||
import FormMonthYearPicker from '../../forms/FormMonthYearPicker';
|
||||
import FormSelect from '../../forms/FormSelect';
|
||||
import FormTextArea from '../../forms/FormTextArea';
|
||||
import FormTextInput from '../../forms/FormTextInput';
|
||||
import type { OfferFormData } from '../../types';
|
||||
import { JobTypeLabel } from '../../types';
|
||||
import { JobType } from '../../types';
|
||||
import { CURRENCY_OPTIONS } from '../../../../utils/offers/currency/CurrencyEnum';
|
||||
|
||||
type FullTimeOfferDetailsFormProps = Readonly<{
|
||||
index: number;
|
||||
@@ -5,6 +5,7 @@ import { Button, HorizontalDivider, Spinner, TextArea } from '@tih/ui';
|
||||
|
||||
import ExpandableCommentCard from '~/components/offers/profile/comments/ExpandableCommentCard';
|
||||
|
||||
import { copyProfileLink } from '~/utils/offers/link';
|
||||
import { trpc } from '~/utils/trpc';
|
||||
|
||||
import type { OffersDiscussion, Reply } from '~/types/offers';
|
||||
@@ -84,19 +85,6 @@ export default function ProfileComments({
|
||||
}
|
||||
}
|
||||
|
||||
function handleCopyEditLink() {
|
||||
// TODO: Add notification
|
||||
navigator.clipboard.writeText(
|
||||
`${window.location.origin}/offers/profile/${profileId}?token=${token}`,
|
||||
);
|
||||
}
|
||||
|
||||
function handleCopyPublicLink() {
|
||||
navigator.clipboard.writeText(
|
||||
`${window.location.origin}/offers/profile/${profileId}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="col-span-10 pt-4">
|
||||
@@ -116,7 +104,7 @@ export default function ProfileComments({
|
||||
label="Copy profile edit link"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={handleCopyEditLink}
|
||||
onClick={() => copyProfileLink(profileId, token)}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
@@ -127,7 +115,7 @@ export default function ProfileComments({
|
||||
label="Copy public link"
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={handleCopyPublicLink}
|
||||
onClick={() => copyProfileLink(profileId)}
|
||||
/>
|
||||
</div>
|
||||
<h2 className="mt-2 mb-6 text-2xl font-bold">Discussions</h2>
|
||||
|
||||
@@ -27,10 +27,10 @@ export default function ProfileDetails({
|
||||
);
|
||||
}
|
||||
if (selectedTab === 'offers') {
|
||||
if (offers && offers.length !== 0) {
|
||||
if (offers.length !== 0) {
|
||||
return (
|
||||
<>
|
||||
{[...offers].map((offer) => (
|
||||
{offers.map((offer) => (
|
||||
<OfferCard key={offer.id} offer={offer} />
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
BookmarkSquareIcon,
|
||||
@@ -11,6 +12,8 @@ import { Button, Dialog, Spinner, Tabs } from '@tih/ui';
|
||||
import ProfilePhotoHolder from '~/components/offers/profile/ProfilePhotoHolder';
|
||||
import type { BackgroundCard } from '~/components/offers/types';
|
||||
|
||||
import { getProfileEditPath } from '~/utils/offers/link';
|
||||
|
||||
type ProfileHeaderProps = Readonly<{
|
||||
background?: BackgroundCard;
|
||||
handleDelete: () => void;
|
||||
@@ -29,6 +32,12 @@ export default function ProfileHeader({
|
||||
setSelectedTab,
|
||||
}: ProfileHeaderProps) {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
const { offerProfileId = '', token = '' } = router.query;
|
||||
|
||||
const handleEditClick = () => {
|
||||
router.push(getProfileEditPath(offerProfileId as string, token as string));
|
||||
};
|
||||
|
||||
function renderActionList() {
|
||||
return (
|
||||
@@ -48,6 +57,7 @@ export default function ProfileHeader({
|
||||
label="Edit"
|
||||
size="md"
|
||||
variant="tertiary"
|
||||
onClick={handleEditClick}
|
||||
/>
|
||||
<Button
|
||||
disabled={isLoading}
|
||||
@@ -119,9 +129,11 @@ export default function ProfileHeader({
|
||||
<div className="flex flex-row">
|
||||
<BuildingOffice2Icon className="mr-2.5 h-5" />
|
||||
<span className="mr-2 font-bold">Current:</span>
|
||||
<span>{`${background?.experiences[0].companyName ?? '-'} ${
|
||||
background?.experiences[0].jobLevel
|
||||
} ${background?.experiences[0].jobTitle}`}</span>
|
||||
<span>
|
||||
{`${background?.experiences[0]?.companyName ?? '-'} ${
|
||||
background?.experiences[0]?.jobLevel || ''
|
||||
} ${background?.experiences[0]?.jobTitle || ''}`}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
<CalendarDaysIcon className="mr-2.5 h-5" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
import { convertCurrencyToString } from '~/utils/offers/currency';
|
||||
import { convertMoneyToString } from '~/utils/offers/currency';
|
||||
import { formatDate } from '~/utils/offers/time';
|
||||
|
||||
import type { DashboardOffer } from '~/types/offers';
|
||||
@@ -21,7 +21,7 @@ export default function OfferTableRow({
|
||||
</th>
|
||||
<td className="py-4 px-6">{title}</td>
|
||||
<td className="py-4 px-6">{totalYoe}</td>
|
||||
<td className="py-4 px-6">{convertCurrencyToString(income)}</td>
|
||||
<td className="py-4 px-6">{convertMoneyToString(income)}</td>
|
||||
<td className="py-4 px-6">{formatDate(monthYearReceived)}</td>
|
||||
<td className="space-x-4 py-4 px-6">
|
||||
<Link
|
||||
|
||||
Reference in New Issue
Block a user