mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-29 13:13:54 +08:00
[offers][feat] integrate profile delete API and set loading status (#367)
This commit is contained in:
@ -1,4 +1,4 @@
|
|||||||
import { useRouter } from 'next/router';
|
import Link from 'next/link';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { HorizontalDivider, Pagination, Select, Tabs } from '@tih/ui';
|
import { HorizontalDivider, Pagination, Select, Tabs } from '@tih/ui';
|
||||||
|
|
||||||
@ -40,7 +40,6 @@ type Pagination = {
|
|||||||
const NUMBER_OF_OFFERS_IN_PAGE = 10;
|
const NUMBER_OF_OFFERS_IN_PAGE = 10;
|
||||||
|
|
||||||
export default function OffersTable({ jobTitleFilter }: OffersTableProps) {
|
export default function OffersTable({ jobTitleFilter }: OffersTableProps) {
|
||||||
const router = useRouter();
|
|
||||||
const [currency, setCurrency] = useState('SGD'); // TODO
|
const [currency, setCurrency] = useState('SGD'); // TODO
|
||||||
const [selectedTab, setSelectedTab] = useState(YOE_CATEGORY.ENTRY);
|
const [selectedTab, setSelectedTab] = useState(YOE_CATEGORY.ENTRY);
|
||||||
const [pagination, setPagination] = useState<Pagination>({
|
const [pagination, setPagination] = useState<Pagination>({
|
||||||
@ -180,10 +179,6 @@ export default function OffersTable({ jobTitleFilter }: OffersTableProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClickViewProfile = (profileId: string) => {
|
|
||||||
router.push(`/offers/profile/${profileId}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePageChange = (currPage: number) => {
|
const handlePageChange = (currPage: number) => {
|
||||||
setPagination({ ...pagination, currentPage: currPage });
|
setPagination({ ...pagination, currentPage: currPage });
|
||||||
};
|
};
|
||||||
@ -211,11 +206,17 @@ export default function OffersTable({ jobTitleFilter }: OffersTableProps) {
|
|||||||
<td className="py-4 px-6">{salary}</td>
|
<td className="py-4 px-6">{salary}</td>
|
||||||
<td className="py-4 px-6">{date}</td>
|
<td className="py-4 px-6">{date}</td>
|
||||||
<td className="space-x-4 py-4 px-6">
|
<td className="space-x-4 py-4 px-6">
|
||||||
<a
|
{/* <a
|
||||||
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
|
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
|
||||||
onClick={() => handleClickViewProfile(profileId)}>
|
onClick={() => handleClickViewProfile(profileId)}>
|
||||||
View Profile
|
View Profile
|
||||||
</a>
|
</a> */}
|
||||||
|
|
||||||
|
<Link
|
||||||
|
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
|
||||||
|
href={`/offers/profile/${profileId}`}>
|
||||||
|
View Profile
|
||||||
|
</Link>
|
||||||
{/* <a
|
{/* <a
|
||||||
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
|
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
|
||||||
href="#">
|
href="#">
|
||||||
@ -244,7 +245,6 @@ export default function OffersTable({ jobTitleFilter }: OffersTableProps) {
|
|||||||
{`of `}
|
{`of `}
|
||||||
<span className="font-semibold text-gray-900 dark:text-white">
|
<span className="font-semibold text-gray-900 dark:text-white">
|
||||||
{pagination.totalItems}
|
{pagination.totalItems}
|
||||||
{/* {pagination.numOfPages * NUMBER_OF_OFFERS_IN_PAGE} */}
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<Pagination
|
<Pagination
|
||||||
|
@ -6,22 +6,7 @@ import {
|
|||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import { HorizontalDivider } from '@tih/ui';
|
import { HorizontalDivider } from '@tih/ui';
|
||||||
|
|
||||||
export type OfferEntity = {
|
import type { OfferEntity } from '~/components/offers/types';
|
||||||
base?: string;
|
|
||||||
bonus?: string;
|
|
||||||
companyName: string;
|
|
||||||
duration?: string;
|
|
||||||
id?: string;
|
|
||||||
jobLevel?: string;
|
|
||||||
jobTitle: string;
|
|
||||||
location?: string;
|
|
||||||
monthlySalary?: string;
|
|
||||||
negotiationStrategy?: string;
|
|
||||||
otherComment?: string;
|
|
||||||
receivedMonth?: string;
|
|
||||||
stocks?: string;
|
|
||||||
totalCompensation?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = Readonly<{
|
type Props = Readonly<{
|
||||||
offer: OfferEntity;
|
offer: OfferEntity;
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
import { ClipboardDocumentIcon, ShareIcon } from '@heroicons/react/24/outline';
|
||||||
|
import { Button, Spinner } from '@tih/ui';
|
||||||
|
|
||||||
|
type ProfileHeaderProps = Readonly<{
|
||||||
|
handleCopyEditLink: () => void;
|
||||||
|
handleCopyPublicLink: () => void;
|
||||||
|
isDisabled: boolean;
|
||||||
|
isEditable: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function ProfileComments({
|
||||||
|
handleCopyEditLink,
|
||||||
|
handleCopyPublicLink,
|
||||||
|
isDisabled,
|
||||||
|
isEditable,
|
||||||
|
isLoading,
|
||||||
|
}: ProfileHeaderProps) {
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="col-span-10 pt-4">
|
||||||
|
<Spinner display="block" size="lg" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="m-4">
|
||||||
|
<div className="flex-end flex justify-end space-x-4">
|
||||||
|
{isEditable && (
|
||||||
|
<Button
|
||||||
|
addonPosition="start"
|
||||||
|
disabled={isDisabled}
|
||||||
|
icon={ClipboardDocumentIcon}
|
||||||
|
isLabelHidden={false}
|
||||||
|
label="Copy profile edit link"
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={handleCopyEditLink}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
addonPosition="start"
|
||||||
|
disabled={isDisabled}
|
||||||
|
icon={ShareIcon}
|
||||||
|
isLabelHidden={false}
|
||||||
|
label="Copy public link"
|
||||||
|
size="sm"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={handleCopyPublicLink}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h2 className="mt-2 text-2xl font-bold">
|
||||||
|
Discussions feature coming soon
|
||||||
|
</h2>
|
||||||
|
{/* <TextArea label="Comment" placeholder="Type your comment here" /> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
79
apps/portal/src/components/offers/profile/ProfileDetails.tsx
Normal file
79
apps/portal/src/components/offers/profile/ProfileDetails.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { AcademicCapIcon, BriefcaseIcon } from '@heroicons/react/24/outline';
|
||||||
|
import { Spinner } from '@tih/ui';
|
||||||
|
|
||||||
|
import EducationCard from '~/components/offers/profile/EducationCard';
|
||||||
|
import OfferCard from '~/components/offers/profile/OfferCard';
|
||||||
|
import type { BackgroundCard, OfferEntity } from '~/components/offers/types';
|
||||||
|
import { EducationBackgroundType } from '~/components/offers/types';
|
||||||
|
|
||||||
|
type ProfileHeaderProps = Readonly<{
|
||||||
|
background?: BackgroundCard;
|
||||||
|
isLoading: boolean;
|
||||||
|
offers: Array<OfferEntity>;
|
||||||
|
selectedTab: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function ProfileDetails({
|
||||||
|
background,
|
||||||
|
isLoading,
|
||||||
|
offers,
|
||||||
|
selectedTab,
|
||||||
|
}: ProfileHeaderProps) {
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="col-span-10 pt-4">
|
||||||
|
<Spinner display="block" size="lg" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (selectedTab === 'offers') {
|
||||||
|
if (offers && offers.length !== 0) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{[...offers].map((offer) => (
|
||||||
|
<OfferCard key={offer.id} offer={offer} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="mx-8 my-4 flex flex-row">
|
||||||
|
<BriefcaseIcon className="mr-1 h-5" />
|
||||||
|
<span className="font-bold">No offer is attached.</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (selectedTab === 'background') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{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,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return <div>Detail page for {selectedTab}</div>;
|
||||||
|
}
|
167
apps/portal/src/components/offers/profile/ProfileHeader.tsx
Normal file
167
apps/portal/src/components/offers/profile/ProfileHeader.tsx
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import {
|
||||||
|
BookmarkSquareIcon,
|
||||||
|
BuildingOffice2Icon,
|
||||||
|
CalendarDaysIcon,
|
||||||
|
PencilSquareIcon,
|
||||||
|
TrashIcon,
|
||||||
|
} from '@heroicons/react/24/outline';
|
||||||
|
import { Button, Dialog, Spinner, Tabs } from '@tih/ui';
|
||||||
|
|
||||||
|
import type { BackgroundCard } from '~/components/offers/types';
|
||||||
|
|
||||||
|
import ProfilePhotoHolder from './ProfilePhotoHolder';
|
||||||
|
|
||||||
|
type ProfileHeaderProps = Readonly<{
|
||||||
|
background?: BackgroundCard;
|
||||||
|
handleDelete: () => void;
|
||||||
|
isEditable: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
selectedTab: string;
|
||||||
|
setSelectedTab: (tab: string) => void;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export default function ProfileHeader({
|
||||||
|
background,
|
||||||
|
handleDelete,
|
||||||
|
isEditable,
|
||||||
|
isLoading,
|
||||||
|
selectedTab,
|
||||||
|
setSelectedTab,
|
||||||
|
}: ProfileHeaderProps) {
|
||||||
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
function renderActionList() {
|
||||||
|
return (
|
||||||
|
<div className="space-x-2">
|
||||||
|
<Button
|
||||||
|
disabled={isLoading}
|
||||||
|
icon={BookmarkSquareIcon}
|
||||||
|
isLabelHidden={true}
|
||||||
|
label="Save to user account"
|
||||||
|
size="md"
|
||||||
|
variant="tertiary"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
disabled={isLoading}
|
||||||
|
icon={PencilSquareIcon}
|
||||||
|
isLabelHidden={true}
|
||||||
|
label="Edit"
|
||||||
|
size="md"
|
||||||
|
variant="tertiary"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
disabled={isLoading}
|
||||||
|
icon={TrashIcon}
|
||||||
|
isLabelHidden={true}
|
||||||
|
label="Delete"
|
||||||
|
size="md"
|
||||||
|
variant="tertiary"
|
||||||
|
onClick={() => setIsDialogOpen(true)}
|
||||||
|
/>
|
||||||
|
{isDialogOpen && (
|
||||||
|
<Dialog
|
||||||
|
isShown={isDialogOpen}
|
||||||
|
primaryButton={
|
||||||
|
<Button
|
||||||
|
display="block"
|
||||||
|
label="Delete"
|
||||||
|
variant="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setIsDialogOpen(false);
|
||||||
|
handleDelete();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
secondaryButton={
|
||||||
|
<Button
|
||||||
|
display="block"
|
||||||
|
label="Cancel"
|
||||||
|
variant="tertiary"
|
||||||
|
onClick={() => setIsDialogOpen(false)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
title="Are you sure you want to delete this offer profile?"
|
||||||
|
onClose={() => setIsDialogOpen(false)}>
|
||||||
|
<div>
|
||||||
|
All comments will be gone. You will not be able to access or
|
||||||
|
recover it.
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="col-span-10 pt-4">
|
||||||
|
<Spinner display="block" size="lg" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="h-40 bg-white p-4">
|
||||||
|
<div className="justify-left flex h-1/2">
|
||||||
|
<div className="mx-4 mt-2">
|
||||||
|
<ProfilePhotoHolder />
|
||||||
|
</div>
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="justify-left flex flex-1">
|
||||||
|
<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">
|
||||||
|
{renderActionList()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<CalendarDaysIcon className="mr-2.5 h-5" />
|
||||||
|
<span className="mr-2 font-bold">YOE:</span>
|
||||||
|
<span className="mr-4">{background?.totalYoe}</span>
|
||||||
|
{background?.specificYoes &&
|
||||||
|
background?.specificYoes.length > 0 &&
|
||||||
|
background?.specificYoes.map(({ domain, yoe }) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={domain}
|
||||||
|
className="mr-4">{`${domain}: ${yoe}`}</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-8">
|
||||||
|
<Tabs
|
||||||
|
label="Profile Detail Navigation"
|
||||||
|
tabs={[
|
||||||
|
{
|
||||||
|
label: 'Offers',
|
||||||
|
value: 'offers',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Background',
|
||||||
|
value: 'background',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Offer Engine Analysis',
|
||||||
|
value: 'offerEngineAnalysis',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
value={selectedTab}
|
||||||
|
onChange={(value) => setSelectedTab(value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable no-shadow */
|
/* eslint-disable no-shadow */
|
||||||
import type { OfferEntity } from '~/components/offers/profile/OfferCard';
|
|
||||||
import type { MonthYear } from '~/components/shared/MonthYearPicker';
|
import type { MonthYear } from '~/components/shared/MonthYearPicker';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -120,6 +119,23 @@ type EducationDisplay = {
|
|||||||
type: string;
|
type: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type OfferEntity = {
|
||||||
|
base?: string;
|
||||||
|
bonus?: string;
|
||||||
|
companyName: string;
|
||||||
|
duration?: string;
|
||||||
|
id?: string;
|
||||||
|
jobLevel?: string;
|
||||||
|
jobTitle: string;
|
||||||
|
location?: string;
|
||||||
|
monthlySalary?: string;
|
||||||
|
negotiationStrategy?: string;
|
||||||
|
otherComment?: string;
|
||||||
|
receivedMonth?: string;
|
||||||
|
stocks?: string;
|
||||||
|
totalCompensation?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type BackgroundCard = {
|
export type BackgroundCard = {
|
||||||
educations: Array<EducationDisplay>;
|
educations: Array<EducationDisplay>;
|
||||||
experiences: Array<OfferEntity>;
|
experiences: Array<OfferEntity>;
|
||||||
|
@ -1,28 +1,16 @@
|
|||||||
import Error from 'next/error';
|
import Error from 'next/error';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import {
|
|
||||||
AcademicCapIcon,
|
|
||||||
BookmarkSquareIcon,
|
|
||||||
BriefcaseIcon,
|
|
||||||
BuildingOffice2Icon,
|
|
||||||
CalendarDaysIcon,
|
|
||||||
ClipboardDocumentIcon,
|
|
||||||
PencilSquareIcon,
|
|
||||||
ShareIcon,
|
|
||||||
TrashIcon,
|
|
||||||
} from '@heroicons/react/24/outline';
|
|
||||||
import { Button, Dialog, Tabs } from '@tih/ui';
|
|
||||||
|
|
||||||
import EducationCard from '~/components/offers/profile/EducationCard';
|
import ProfileHeader from '~/components/offers/profile/ProfileHeader';
|
||||||
import type { OfferEntity } from '~/components/offers/profile/OfferCard';
|
import type { OfferEntity } from '~/components/offers/types';
|
||||||
import OfferCard from '~/components/offers/profile/OfferCard';
|
|
||||||
import ProfilePhotoHolder from '~/components/offers/profile/ProfilePhotoHolder';
|
|
||||||
import type { BackgroundCard } from '~/components/offers/types';
|
import type { BackgroundCard } from '~/components/offers/types';
|
||||||
import { EducationBackgroundType } from '~/components/offers/types';
|
|
||||||
|
|
||||||
import { formatDate } from '~/utils/offers/time';
|
import { formatDate } from '~/utils/offers/time';
|
||||||
import { trpc } from '~/utils/trpc';
|
import { trpc } from '~/utils/trpc';
|
||||||
|
|
||||||
|
import ProfileComments from '../../../components/offers/profile/ProfileComments';
|
||||||
|
import ProfileDetails from '../../../components/offers/profile/ProfileDetails';
|
||||||
export default function OfferProfile() {
|
export default function OfferProfile() {
|
||||||
const ErrorPage = (
|
const ErrorPage = (
|
||||||
<Error statusCode={404} title="Requested profile does not exist." />
|
<Error statusCode={404} title="Requested profile does not exist." />
|
||||||
@ -32,10 +20,10 @@ export default function OfferProfile() {
|
|||||||
const [isEditable, setIsEditable] = useState(false);
|
const [isEditable, setIsEditable] = useState(false);
|
||||||
const [background, setBackground] = useState<BackgroundCard>();
|
const [background, setBackground] = useState<BackgroundCard>();
|
||||||
const [offers, setOffers] = useState<Array<OfferEntity>>([]);
|
const [offers, setOffers] = useState<Array<OfferEntity>>([]);
|
||||||
const [selectedTab, setSelectedTab] = useState('offers');
|
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
||||||
|
|
||||||
const detailsQuery = trpc.useQuery(
|
const [selectedTab, setSelectedTab] = useState('offers');
|
||||||
|
|
||||||
|
const getProfileQuery = trpc.useQuery(
|
||||||
[
|
[
|
||||||
'offers.profile.listOne',
|
'offers.profile.listOne',
|
||||||
{ profileId: offerProfileId as string, token: token as string },
|
{ profileId: offerProfileId as string, token: token as string },
|
||||||
@ -43,7 +31,17 @@ export default function OfferProfile() {
|
|||||||
{
|
{
|
||||||
enabled: typeof offerProfileId === 'string',
|
enabled: typeof offerProfileId === 'string',
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
const filteredOffers: Array<OfferEntity> = data!.offers.map((res) => {
|
if (!data) {
|
||||||
|
router.push('/offers');
|
||||||
|
}
|
||||||
|
if (!data?.isEditable && token !== '') {
|
||||||
|
router.push(`/offers/profile/${offerProfileId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsEditable(data?.isEditable ?? false);
|
||||||
|
|
||||||
|
const filteredOffers: Array<OfferEntity> = data
|
||||||
|
? data?.offers.map((res) => {
|
||||||
if (res.OffersFullTime) {
|
if (res.OffersFullTime) {
|
||||||
const filteredOffer: OfferEntity = {
|
const filteredOffer: OfferEntity = {
|
||||||
base: res.OffersFullTime.baseSalary.value
|
base: res.OffersFullTime.baseSalary.value
|
||||||
@ -85,12 +83,12 @@ export default function OfferProfile() {
|
|||||||
receivedMonth: formatDate(res.monthYearReceived),
|
receivedMonth: formatDate(res.monthYearReceived),
|
||||||
};
|
};
|
||||||
return filteredOffer;
|
return filteredOffer;
|
||||||
});
|
})
|
||||||
|
: [];
|
||||||
setOffers(filteredOffers ?? []);
|
setOffers(filteredOffers);
|
||||||
|
|
||||||
if (data?.background) {
|
if (data?.background) {
|
||||||
const filteredBackground: BackgroundCard = {
|
const transformedBackground = {
|
||||||
educations: [
|
educations: [
|
||||||
{
|
{
|
||||||
endDate: data?.background.educations[0].endDate
|
endDate: data?.background.educations[0].endDate
|
||||||
@ -104,7 +102,6 @@ export default function OfferProfile() {
|
|||||||
type: data.background.educations[0].type || '-',
|
type: data.background.educations[0].type || '-',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
experiences: [
|
experiences: [
|
||||||
{
|
{
|
||||||
companyName:
|
companyName:
|
||||||
@ -126,243 +123,75 @@ export default function OfferProfile() {
|
|||||||
],
|
],
|
||||||
profileName: data.profileName,
|
profileName: data.profileName,
|
||||||
specificYoes: data.background.specificYoes ?? [],
|
specificYoes: data.background.specificYoes ?? [],
|
||||||
|
|
||||||
totalYoe: String(data.background.totalYoe) || '-',
|
totalYoe: String(data.background.totalYoe) || '-',
|
||||||
};
|
};
|
||||||
|
setBackground(transformedBackground);
|
||||||
setBackground(filteredBackground);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsEditable(data?.isEditable ?? false);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
function renderActionList() {
|
const trpcContext = trpc.useContext();
|
||||||
return (
|
const deleteMutation = trpc.useMutation(['offers.profile.delete']);
|
||||||
<div className="space-x-2">
|
|
||||||
<Button
|
|
||||||
icon={BookmarkSquareIcon}
|
|
||||||
isLabelHidden={true}
|
|
||||||
label="Save to user account"
|
|
||||||
size="md"
|
|
||||||
variant="tertiary"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
icon={PencilSquareIcon}
|
|
||||||
isLabelHidden={true}
|
|
||||||
label="Edit"
|
|
||||||
size="md"
|
|
||||||
variant="tertiary"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
icon={TrashIcon}
|
|
||||||
isLabelHidden={true}
|
|
||||||
label="Delete"
|
|
||||||
size="md"
|
|
||||||
variant="tertiary"
|
|
||||||
onClick={() => setIsDialogOpen(true)}
|
|
||||||
/>
|
|
||||||
{isDialogOpen && (
|
|
||||||
<Dialog
|
|
||||||
isShown={isDialogOpen}
|
|
||||||
primaryButton={
|
|
||||||
<Button
|
|
||||||
display="block"
|
|
||||||
label="Delete"
|
|
||||||
variant="primary"
|
|
||||||
onClick={() => setIsDialogOpen(false)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
secondaryButton={
|
|
||||||
<Button
|
|
||||||
display="block"
|
|
||||||
label="Cancel"
|
|
||||||
variant="tertiary"
|
|
||||||
onClick={() => setIsDialogOpen(false)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
title="Are you sure you want to delete this offer profile?"
|
|
||||||
onClose={() => setIsDialogOpen(false)}>
|
|
||||||
<div>
|
|
||||||
All comments will be gone. You will not be able to access or
|
|
||||||
recover it.
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function ProfileHeader() {
|
|
||||||
return (
|
|
||||||
<div className="relative h-40 bg-white p-4">
|
|
||||||
<div className="justify-left flex h-1/2">
|
|
||||||
<div className="mx-4 mt-2">
|
|
||||||
<ProfilePhotoHolder />
|
|
||||||
</div>
|
|
||||||
<div className="w-full">
|
|
||||||
<div className="justify-left flex ">
|
|
||||||
<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>{`${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 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>
|
|
||||||
|
|
||||||
<div className="absolute left-8 bottom-1 content-center">
|
function handleDelete() {
|
||||||
<Tabs
|
if (isEditable) {
|
||||||
label="Profile Detail Navigation"
|
deleteMutation.mutate({
|
||||||
tabs={[
|
id: offerProfileId as string,
|
||||||
{
|
// TODO: token: token as string,
|
||||||
label: 'Offers',
|
});
|
||||||
value: 'offers',
|
trpcContext.invalidateQueries(['offers.profile.listOne']);
|
||||||
},
|
router.push('/offers');
|
||||||
{
|
}
|
||||||
label: 'Background',
|
|
||||||
value: 'background',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Offer Engine Analysis',
|
|
||||||
value: 'offerEngineAnalysis',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
value={selectedTab}
|
|
||||||
onChange={(value) => setSelectedTab(value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProfileDetails() {
|
function handleCopyEditLink() {
|
||||||
if (selectedTab === 'offers') {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{[...offers].map((offer) => (
|
|
||||||
<OfferCard key={offer.id} offer={offer} />
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (selectedTab === 'background') {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{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,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return <div>Detail page for {selectedTab}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ProfileComments() {
|
|
||||||
return (
|
|
||||||
<div className="m-4">
|
|
||||||
<div className="flex-end flex justify-end space-x-4">
|
|
||||||
{isEditable && (
|
|
||||||
<Button
|
|
||||||
addonPosition="start"
|
|
||||||
icon={ClipboardDocumentIcon}
|
|
||||||
isLabelHidden={false}
|
|
||||||
label="Copy profile edit link"
|
|
||||||
size="sm"
|
|
||||||
variant="secondary"
|
|
||||||
onClick={() => {
|
|
||||||
// TODO: Add notification
|
// TODO: Add notification
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
`${router.pathname}/${offerProfileId}?token=${token}`,
|
`${window.location.origin}/offers/profile/${offerProfileId}?token=${token}`,
|
||||||
);
|
);
|
||||||
}}
|
}
|
||||||
/>
|
|
||||||
)}
|
function handleCopyPublicLink() {
|
||||||
<Button
|
|
||||||
addonPosition="start"
|
|
||||||
icon={ShareIcon}
|
|
||||||
isLabelHidden={false}
|
|
||||||
label="Copy public link"
|
|
||||||
size="sm"
|
|
||||||
variant="secondary"
|
|
||||||
onClick={() => {
|
|
||||||
// TODO: Add notification
|
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
`${window.location.origin}/offers/profile/${offerProfileId}`,
|
`${window.location.origin}/offers/profile/${offerProfileId}`,
|
||||||
);
|
);
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<h2 className="mt-2 text-2xl font-bold">
|
|
||||||
Discussions feature coming soon
|
|
||||||
</h2>
|
|
||||||
{/* <TextArea label="Comment" placeholder="Type your comment here" /> */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{detailsQuery.isError && ErrorPage}
|
{getProfileQuery.isError && ErrorPage}
|
||||||
|
{!getProfileQuery.isError && (
|
||||||
<div className="mb-4 flex flex h-screen w-screen items-center justify-center divide-x">
|
<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">
|
<div className="h-full w-2/3 divide-y">
|
||||||
<ProfileHeader />
|
<ProfileHeader
|
||||||
|
background={background}
|
||||||
|
handleDelete={handleDelete}
|
||||||
|
isEditable={isEditable}
|
||||||
|
isLoading={getProfileQuery.isLoading}
|
||||||
|
selectedTab={selectedTab}
|
||||||
|
setSelectedTab={setSelectedTab}
|
||||||
|
/>
|
||||||
<div className="h-4/5 w-full overflow-y-scroll pb-32">
|
<div className="h-4/5 w-full overflow-y-scroll pb-32">
|
||||||
<ProfileDetails />
|
<ProfileDetails
|
||||||
|
background={background}
|
||||||
|
isLoading={getProfileQuery.isLoading}
|
||||||
|
offers={offers}
|
||||||
|
selectedTab={selectedTab}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full w-1/3 bg-white">
|
<div className="h-full w-1/3 bg-white">
|
||||||
<ProfileComments />
|
<ProfileComments
|
||||||
|
handleCopyEditLink={handleCopyEditLink}
|
||||||
|
handleCopyPublicLink={handleCopyPublicLink}
|
||||||
|
isDisabled={deleteMutation.isLoading}
|
||||||
|
isEditable={isEditable}
|
||||||
|
isLoading={getProfileQuery.isLoading}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user