mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 20:52:00 +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 { HorizontalDivider, Pagination, Select, Tabs } from '@tih/ui';
|
||||
|
||||
@ -40,7 +40,6 @@ type Pagination = {
|
||||
const NUMBER_OF_OFFERS_IN_PAGE = 10;
|
||||
|
||||
export default function OffersTable({ jobTitleFilter }: OffersTableProps) {
|
||||
const router = useRouter();
|
||||
const [currency, setCurrency] = useState('SGD'); // TODO
|
||||
const [selectedTab, setSelectedTab] = useState(YOE_CATEGORY.ENTRY);
|
||||
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) => {
|
||||
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">{date}</td>
|
||||
<td className="space-x-4 py-4 px-6">
|
||||
<a
|
||||
{/* <a
|
||||
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
|
||||
onClick={() => handleClickViewProfile(profileId)}>
|
||||
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
|
||||
className="font-medium text-indigo-600 hover:underline dark:text-indigo-500"
|
||||
href="#">
|
||||
@ -244,7 +245,6 @@ export default function OffersTable({ jobTitleFilter }: OffersTableProps) {
|
||||
{`of `}
|
||||
<span className="font-semibold text-gray-900 dark:text-white">
|
||||
{pagination.totalItems}
|
||||
{/* {pagination.numOfPages * NUMBER_OF_OFFERS_IN_PAGE} */}
|
||||
</span>
|
||||
</span>
|
||||
<Pagination
|
||||
|
@ -6,22 +6,7 @@ import {
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { HorizontalDivider } from '@tih/ui';
|
||||
|
||||
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;
|
||||
};
|
||||
import type { OfferEntity } from '~/components/offers/types';
|
||||
|
||||
type Props = Readonly<{
|
||||
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 */
|
||||
import type { OfferEntity } from '~/components/offers/profile/OfferCard';
|
||||
import type { MonthYear } from '~/components/shared/MonthYearPicker';
|
||||
|
||||
/*
|
||||
@ -120,6 +119,23 @@ type EducationDisplay = {
|
||||
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 = {
|
||||
educations: Array<EducationDisplay>;
|
||||
experiences: Array<OfferEntity>;
|
||||
|
@ -1,28 +1,16 @@
|
||||
import Error from 'next/error';
|
||||
import { useRouter } from 'next/router';
|
||||
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 type { OfferEntity } from '~/components/offers/profile/OfferCard';
|
||||
import OfferCard from '~/components/offers/profile/OfferCard';
|
||||
import ProfilePhotoHolder from '~/components/offers/profile/ProfilePhotoHolder';
|
||||
import ProfileHeader from '~/components/offers/profile/ProfileHeader';
|
||||
import type { OfferEntity } from '~/components/offers/types';
|
||||
import type { BackgroundCard } from '~/components/offers/types';
|
||||
import { EducationBackgroundType } from '~/components/offers/types';
|
||||
|
||||
import { formatDate } from '~/utils/offers/time';
|
||||
import { trpc } from '~/utils/trpc';
|
||||
|
||||
import ProfileComments from '../../../components/offers/profile/ProfileComments';
|
||||
import ProfileDetails from '../../../components/offers/profile/ProfileDetails';
|
||||
export default function OfferProfile() {
|
||||
const ErrorPage = (
|
||||
<Error statusCode={404} title="Requested profile does not exist." />
|
||||
@ -32,10 +20,10 @@ export default function OfferProfile() {
|
||||
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(
|
||||
const [selectedTab, setSelectedTab] = useState('offers');
|
||||
|
||||
const getProfileQuery = trpc.useQuery(
|
||||
[
|
||||
'offers.profile.listOne',
|
||||
{ profileId: offerProfileId as string, token: token as string },
|
||||
@ -43,54 +31,64 @@ export default function OfferProfile() {
|
||||
{
|
||||
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}`
|
||||
: '',
|
||||
};
|
||||
if (!data) {
|
||||
router.push('/offers');
|
||||
}
|
||||
if (!data?.isEditable && token !== '') {
|
||||
router.push(`/offers/profile/${offerProfileId}`);
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
setIsEditable(data?.isEditable ?? false);
|
||||
|
||||
setOffers(filteredOffers ?? []);
|
||||
const filteredOffers: Array<OfferEntity> = data
|
||||
? 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 = {
|
||||
const transformedBackground = {
|
||||
educations: [
|
||||
{
|
||||
endDate: data?.background.educations[0].endDate
|
||||
@ -104,7 +102,6 @@ export default function OfferProfile() {
|
||||
type: data.background.educations[0].type || '-',
|
||||
},
|
||||
],
|
||||
|
||||
experiences: [
|
||||
{
|
||||
companyName:
|
||||
@ -126,243 +123,75 @@ export default function OfferProfile() {
|
||||
],
|
||||
profileName: data.profileName,
|
||||
specificYoes: data.background.specificYoes ?? [],
|
||||
|
||||
totalYoe: String(data.background.totalYoe) || '-',
|
||||
};
|
||||
|
||||
setBackground(filteredBackground);
|
||||
setBackground(transformedBackground);
|
||||
}
|
||||
|
||||
setIsEditable(data?.isEditable ?? false);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
function renderActionList() {
|
||||
return (
|
||||
<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>
|
||||
const trpcContext = trpc.useContext();
|
||||
const deleteMutation = trpc.useMutation(['offers.profile.delete']);
|
||||
|
||||
<div className="absolute left-8 bottom-1 content-center">
|
||||
<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>
|
||||
function handleDelete() {
|
||||
if (isEditable) {
|
||||
deleteMutation.mutate({
|
||||
id: offerProfileId as string,
|
||||
// TODO: token: token as string,
|
||||
});
|
||||
trpcContext.invalidateQueries(['offers.profile.listOne']);
|
||||
router.push('/offers');
|
||||
}
|
||||
}
|
||||
|
||||
function handleCopyEditLink() {
|
||||
// TODO: Add notification
|
||||
navigator.clipboard.writeText(
|
||||
`${window.location.origin}/offers/profile/${offerProfileId}?token=${token}`,
|
||||
);
|
||||
}
|
||||
|
||||
function ProfileDetails() {
|
||||
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
|
||||
navigator.clipboard.writeText(
|
||||
`${router.pathname}/${offerProfileId}?token=${token}`,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
addonPosition="start"
|
||||
icon={ShareIcon}
|
||||
isLabelHidden={false}
|
||||
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">
|
||||
Discussions feature coming soon
|
||||
</h2>
|
||||
{/* <TextArea label="Comment" placeholder="Type your comment here" /> */}
|
||||
</div>
|
||||
function handleCopyPublicLink() {
|
||||
navigator.clipboard.writeText(
|
||||
`${window.location.origin}/offers/profile/${offerProfileId}`,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{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 />
|
||||
{getProfileQuery.isError && ErrorPage}
|
||||
{!getProfileQuery.isError && (
|
||||
<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
|
||||
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">
|
||||
<ProfileDetails
|
||||
background={background}
|
||||
isLoading={getProfileQuery.isLoading}
|
||||
offers={offers}
|
||||
selectedTab={selectedTab}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-full w-1/3 bg-white">
|
||||
<ProfileComments
|
||||
handleCopyEditLink={handleCopyEditLink}
|
||||
handleCopyPublicLink={handleCopyPublicLink}
|
||||
isDisabled={deleteMutation.isLoading}
|
||||
isEditable={isEditable}
|
||||
isLoading={getProfileQuery.isLoading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-full w-1/3 bg-white">
|
||||
<ProfileComments />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user