|
|
|
@ -1,3 +1,5 @@
|
|
|
|
|
import Error from 'next/error';
|
|
|
|
|
import { useRouter } from 'next/router';
|
|
|
|
|
import { useState } from 'react';
|
|
|
|
|
import {
|
|
|
|
|
AcademicCapIcon,
|
|
|
|
@ -13,13 +15,129 @@ import {
|
|
|
|
|
import { Button, Dialog, Tabs } from '@tih/ui';
|
|
|
|
|
|
|
|
|
|
import EducationCard from '~/components/offers/profile/EducationCard';
|
|
|
|
|
import type { OfferEntity } from '~/components/offers/profile/OfferCard';
|
|
|
|
|
import OfferCard from '~/components/offers/profile/OfferCard';
|
|
|
|
|
import ProfilePhotoHolder from '~/components/offers/profile/ProfilePhotoHolder';
|
|
|
|
|
import type { BackgroundCard } from '~/components/offers/types';
|
|
|
|
|
import { EducationBackgroundType } from '~/components/offers/types';
|
|
|
|
|
|
|
|
|
|
import { formatDate } from '~/utils/offers/time';
|
|
|
|
|
import { trpc } from '~/utils/trpc';
|
|
|
|
|
export default function OfferProfile() {
|
|
|
|
|
const ErrorPage = (
|
|
|
|
|
<Error statusCode={404} title="Requested profile does not exist." />
|
|
|
|
|
);
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
const { offerProfileId, token = '' } = router.query;
|
|
|
|
|
const [isEditable, setIsEditable] = useState(false);
|
|
|
|
|
const [background, setBackground] = useState<BackgroundCard>();
|
|
|
|
|
const [offers, setOffers] = useState<Array<OfferEntity>>([]);
|
|
|
|
|
const [selectedTab, setSelectedTab] = useState('offers');
|
|
|
|
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
|
|
|
|
|
|
|
|
const detailsQuery = trpc.useQuery(
|
|
|
|
|
[
|
|
|
|
|
'offers.profile.listOne',
|
|
|
|
|
{ profileId: offerProfileId as string, token: token as string },
|
|
|
|
|
],
|
|
|
|
|
{
|
|
|
|
|
enabled: typeof offerProfileId === 'string',
|
|
|
|
|
onSuccess: (data) => {
|
|
|
|
|
const filteredOffers: Array<OfferEntity> = data!.offers.map((res) => {
|
|
|
|
|
if (res.OffersFullTime) {
|
|
|
|
|
const filteredOffer: OfferEntity = {
|
|
|
|
|
base: res.OffersFullTime.baseSalary.value
|
|
|
|
|
? `${res.OffersFullTime.baseSalary.value} ${res.OffersFullTime.baseSalary.currency}`
|
|
|
|
|
: '',
|
|
|
|
|
bonus: res.OffersFullTime.bonus.value
|
|
|
|
|
? `${res.OffersFullTime.bonus.value} ${res.OffersFullTime.bonus.currency}`
|
|
|
|
|
: '',
|
|
|
|
|
companyName: res.company.name,
|
|
|
|
|
id: res.OffersFullTime.id,
|
|
|
|
|
jobLevel: res.OffersFullTime.level,
|
|
|
|
|
jobTitle: res.OffersFullTime.title,
|
|
|
|
|
location: res.location,
|
|
|
|
|
negotiationStrategy: res.negotiationStrategy || '',
|
|
|
|
|
otherComment: res.comments || '',
|
|
|
|
|
receivedMonth: formatDate(res.monthYearReceived),
|
|
|
|
|
stocks: res.OffersFullTime.stocks.value
|
|
|
|
|
? `${res.OffersFullTime.stocks.value} ${res.OffersFullTime.stocks.currency}`
|
|
|
|
|
: '',
|
|
|
|
|
totalCompensation: res.OffersFullTime.totalCompensation.value
|
|
|
|
|
? `${res.OffersFullTime.totalCompensation.value} ${res.OffersFullTime.totalCompensation.currency}`
|
|
|
|
|
: '',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return filteredOffer;
|
|
|
|
|
}
|
|
|
|
|
const filteredOffer: OfferEntity = {
|
|
|
|
|
companyName: res.company.name,
|
|
|
|
|
id: res.OffersIntern!.id,
|
|
|
|
|
jobTitle: res.OffersIntern!.title,
|
|
|
|
|
location: res.location,
|
|
|
|
|
monthlySalary: res.OffersIntern!.monthlySalary.value
|
|
|
|
|
? `${res.OffersIntern!.monthlySalary.value} ${
|
|
|
|
|
res.OffersIntern!.monthlySalary.currency
|
|
|
|
|
}`
|
|
|
|
|
: '',
|
|
|
|
|
negotiationStrategy: res.negotiationStrategy || '',
|
|
|
|
|
otherComment: res.comments || '',
|
|
|
|
|
receivedMonth: formatDate(res.monthYearReceived),
|
|
|
|
|
};
|
|
|
|
|
return filteredOffer;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
setOffers(filteredOffers ?? []);
|
|
|
|
|
|
|
|
|
|
if (data?.background) {
|
|
|
|
|
const filteredBackground: BackgroundCard = {
|
|
|
|
|
educations: [
|
|
|
|
|
{
|
|
|
|
|
endDate: data?.background.educations[0].endDate
|
|
|
|
|
? formatDate(data.background.educations[0].endDate)
|
|
|
|
|
: '-',
|
|
|
|
|
field: data.background.educations[0].field || '-',
|
|
|
|
|
school: data.background.educations[0].school || '-',
|
|
|
|
|
startDate: data.background.educations[0].startDate
|
|
|
|
|
? formatDate(data.background.educations[0].startDate)
|
|
|
|
|
: '-',
|
|
|
|
|
type: data.background.educations[0].type || '-',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
experiences: [
|
|
|
|
|
{
|
|
|
|
|
companyName:
|
|
|
|
|
data.background.experiences[0].company?.name ?? '-',
|
|
|
|
|
duration:
|
|
|
|
|
String(data.background.experiences[0].durationInMonths) ??
|
|
|
|
|
'-',
|
|
|
|
|
jobLevel: data.background.experiences[0].level ?? '',
|
|
|
|
|
jobTitle: data.background.experiences[0].title ?? '-',
|
|
|
|
|
monthlySalary: data.background.experiences[0].monthlySalary
|
|
|
|
|
?.value
|
|
|
|
|
? `${data.background.experiences[0].monthlySalary?.value} ${data.background.experiences[0].monthlySalary?.currency}`
|
|
|
|
|
: `-`,
|
|
|
|
|
totalCompensation: data.background.experiences[0]
|
|
|
|
|
.totalCompensation?.value
|
|
|
|
|
? `${data.background.experiences[0].totalCompensation?.value} ${data.background.experiences[0].totalCompensation?.currency}`
|
|
|
|
|
: ``,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
profileName: data.profileName,
|
|
|
|
|
specificYoes: data.background.specificYoes ?? [],
|
|
|
|
|
|
|
|
|
|
totalYoe: String(data.background.totalYoe) || '-',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setBackground(filteredBackground);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setIsEditable(data?.isEditable ?? false);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
function renderActionList() {
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-x-2">
|
|
|
|
@ -67,8 +185,8 @@ export default function OfferProfile() {
|
|
|
|
|
title="Are you sure you want to delete this offer profile?"
|
|
|
|
|
onClose={() => setIsDialogOpen(false)}>
|
|
|
|
|
<div>
|
|
|
|
|
All comments will gone. You will not be able to access or recover
|
|
|
|
|
it.
|
|
|
|
|
All comments will be gone. You will not be able to access or
|
|
|
|
|
recover it.
|
|
|
|
|
</div>
|
|
|
|
|
</Dialog>
|
|
|
|
|
)}
|
|
|
|
@ -84,20 +202,36 @@ export default function OfferProfile() {
|
|
|
|
|
</div>
|
|
|
|
|
<div className="w-full">
|
|
|
|
|
<div className="justify-left flex ">
|
|
|
|
|
<h2 className="flex w-4/5 text-2xl font-bold">anonymised-name</h2>
|
|
|
|
|
<div className="flex h-8 w-1/5 justify-end">
|
|
|
|
|
{renderActionList()}
|
|
|
|
|
</div>
|
|
|
|
|
<h2 className="flex w-4/5 text-2xl font-bold">
|
|
|
|
|
{background?.profileName ?? 'anonymous'}
|
|
|
|
|
</h2>
|
|
|
|
|
{isEditable && (
|
|
|
|
|
<div className="flex h-8 w-1/5 justify-end">
|
|
|
|
|
{isEditable && renderActionList()}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-row">
|
|
|
|
|
<BuildingOffice2Icon className="mr-2.5 h-5" />
|
|
|
|
|
<span className="mr-2 font-bold">Current:</span>
|
|
|
|
|
<span>Level 4 Google</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" />
|
|
|
|
|
<span className="mr-2 font-bold">YOE:</span>
|
|
|
|
|
<span>4</span>
|
|
|
|
|
<span className="mr-4">{background?.totalYoe}</span>
|
|
|
|
|
{background?.specificYoes &&
|
|
|
|
|
background?.specificYoes.length > 0 &&
|
|
|
|
|
background?.specificYoes.map(({ domain, yoe }) => (
|
|
|
|
|
<>
|
|
|
|
|
<span
|
|
|
|
|
key={domain}
|
|
|
|
|
className="mr-2">{`${domain} : ${yoe}`}</span>
|
|
|
|
|
<span>{background?.totalYoe}</span>
|
|
|
|
|
</>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
@ -131,41 +265,7 @@ export default function OfferProfile() {
|
|
|
|
|
if (selectedTab === 'offers') {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{[
|
|
|
|
|
{
|
|
|
|
|
base: undefined,
|
|
|
|
|
bonus: undefined,
|
|
|
|
|
companyName: 'Meta',
|
|
|
|
|
id: 1,
|
|
|
|
|
jobLevel: 'G5',
|
|
|
|
|
jobTitle: 'Software Engineer',
|
|
|
|
|
location: 'Singapore',
|
|
|
|
|
monthlySalary: undefined,
|
|
|
|
|
negotiationStrategy:
|
|
|
|
|
'Nostrud nulla aliqua deserunt commodo id aute.',
|
|
|
|
|
otherComment:
|
|
|
|
|
'Pariatur ut est voluptate incididunt consequat do veniam quis irure adipisicing. Deserunt laborum dolor quis voluptate enim.',
|
|
|
|
|
receivedMonth: 'Jun 2022',
|
|
|
|
|
stocks: undefined,
|
|
|
|
|
totalCompensation: undefined,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
companyName: 'Meta',
|
|
|
|
|
id: 2,
|
|
|
|
|
jobLevel: 'G5',
|
|
|
|
|
jobTitle: 'Software Engineer',
|
|
|
|
|
location: 'Singapore',
|
|
|
|
|
receivedMonth: 'Jun 2022',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
companyName: 'Meta',
|
|
|
|
|
id: 3,
|
|
|
|
|
jobLevel: 'G5',
|
|
|
|
|
jobTitle: 'Software Engineer',
|
|
|
|
|
location: 'Singapore',
|
|
|
|
|
receivedMonth: 'Jun 2022',
|
|
|
|
|
},
|
|
|
|
|
].map((offer) => (
|
|
|
|
|
{[...offers].map((offer) => (
|
|
|
|
|
<OfferCard key={offer.id} offer={offer} />
|
|
|
|
|
))}
|
|
|
|
|
</>
|
|
|
|
@ -174,37 +274,32 @@ export default function OfferProfile() {
|
|
|
|
|
if (selectedTab === 'background') {
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<div className="mx-8 my-4 flex flex-row">
|
|
|
|
|
<BriefcaseIcon className="mr-1 h-5" />
|
|
|
|
|
<span className="font-bold">Work Experience</span>
|
|
|
|
|
</div>
|
|
|
|
|
<OfferCard
|
|
|
|
|
offer={{
|
|
|
|
|
base: undefined,
|
|
|
|
|
bonus: undefined,
|
|
|
|
|
companyName: 'Prefer not to say',
|
|
|
|
|
jobLevel: 'G4',
|
|
|
|
|
jobTitle: 'N/A',
|
|
|
|
|
location: '',
|
|
|
|
|
monthlySalary: '1,400k',
|
|
|
|
|
receivedMonth: '',
|
|
|
|
|
stocks: undefined,
|
|
|
|
|
totalCompensation: undefined,
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<div className="mx-8 my-4 flex flex-row">
|
|
|
|
|
<AcademicCapIcon className="mr-1 h-5" />
|
|
|
|
|
<span className="font-bold">Education</span>
|
|
|
|
|
</div>
|
|
|
|
|
<EducationCard
|
|
|
|
|
education={{
|
|
|
|
|
backgroundType: EducationBackgroundType.Bachelor,
|
|
|
|
|
field: 'CS',
|
|
|
|
|
fromMonth: 'Aug 2019',
|
|
|
|
|
school: 'NUS',
|
|
|
|
|
toMonth: 'May 2021',
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
{background?.experiences && background?.experiences.length > 0 && (
|
|
|
|
|
<>
|
|
|
|
|
<div className="mx-8 my-4 flex flex-row">
|
|
|
|
|
<BriefcaseIcon className="mr-1 h-5" />
|
|
|
|
|
<span className="font-bold">Work Experience</span>
|
|
|
|
|
</div>
|
|
|
|
|
<OfferCard offer={background?.experiences[0]} />
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
{background?.educations && background?.educations.length > 0 && (
|
|
|
|
|
<>
|
|
|
|
|
<div className="mx-8 my-4 flex flex-row">
|
|
|
|
|
<AcademicCapIcon className="mr-1 h-5" />
|
|
|
|
|
<span className="font-bold">Education</span>
|
|
|
|
|
</div>
|
|
|
|
|
<EducationCard
|
|
|
|
|
education={{
|
|
|
|
|
endDate: background.educations[0].endDate,
|
|
|
|
|
field: background.educations[0].field,
|
|
|
|
|
school: background.educations[0].school,
|
|
|
|
|
startDate: background.educations[0].startDate,
|
|
|
|
|
type: EducationBackgroundType.Bachelor,
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
@ -215,14 +310,22 @@ export default function OfferProfile() {
|
|
|
|
|
return (
|
|
|
|
|
<div className="m-4">
|
|
|
|
|
<div className="flex-end flex justify-end space-x-4">
|
|
|
|
|
<Button
|
|
|
|
|
addonPosition="start"
|
|
|
|
|
icon={ClipboardDocumentIcon}
|
|
|
|
|
isLabelHidden={false}
|
|
|
|
|
label="Copy profile edit link"
|
|
|
|
|
size="sm"
|
|
|
|
|
variant="secondary"
|
|
|
|
|
/>
|
|
|
|
|
{isEditable && (
|
|
|
|
|
<Button
|
|
|
|
|
addonPosition="start"
|
|
|
|
|
icon={ClipboardDocumentIcon}
|
|
|
|
|
isLabelHidden={false}
|
|
|
|
|
label="Copy profile edit link"
|
|
|
|
|
size="sm"
|
|
|
|
|
variant="secondary"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
// TODO: Add notification
|
|
|
|
|
navigator.clipboard.writeText(
|
|
|
|
|
`${router.pathname}/${offerProfileId}?token=${token}`,
|
|
|
|
|
);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
<Button
|
|
|
|
|
addonPosition="start"
|
|
|
|
|
icon={ShareIcon}
|
|
|
|
@ -230,6 +333,12 @@ export default function OfferProfile() {
|
|
|
|
|
label="Copy public link"
|
|
|
|
|
size="sm"
|
|
|
|
|
variant="secondary"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
// TODO: Add notification
|
|
|
|
|
navigator.clipboard.writeText(
|
|
|
|
|
`${window.location.origin}/offers/profile/${offerProfileId}`,
|
|
|
|
|
);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<h2 className="mt-2 text-2xl font-bold">
|
|
|
|
@ -241,16 +350,19 @@ export default function OfferProfile() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="mb-4 flex flex h-screen w-screen items-center justify-center divide-x">
|
|
|
|
|
<div className="h-full w-2/3 divide-y">
|
|
|
|
|
<ProfileHeader />
|
|
|
|
|
<div className="h-4/5 w-full overflow-y-scroll pb-32">
|
|
|
|
|
<ProfileDetails />
|
|
|
|
|
<>
|
|
|
|
|
{detailsQuery.isError && ErrorPage}
|
|
|
|
|
<div className="mb-4 flex flex h-screen w-screen items-center justify-center divide-x">
|
|
|
|
|
<div className="h-full w-2/3 divide-y">
|
|
|
|
|
<ProfileHeader />
|
|
|
|
|
<div className="h-4/5 w-full overflow-y-scroll pb-32">
|
|
|
|
|
<ProfileDetails />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="h-full w-1/3 bg-white">
|
|
|
|
|
<ProfileComments />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="h-full w-1/3 bg-white">
|
|
|
|
|
<ProfileComments />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|