mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-08-02 19:46:40 +08:00
[offers] tweak offer profiles UI
This commit is contained in:
@ -1,5 +1,9 @@
|
||||
import {
|
||||
ArrowTrendingUpIcon,
|
||||
BuildingOfficeIcon,
|
||||
MapPinIcon,
|
||||
} from '@heroicons/react/20/solid';
|
||||
import { JobType } from '@prisma/client';
|
||||
import { HorizontalDivider } from '@tih/ui';
|
||||
|
||||
import type { JobTitleType } from '~/components/shared/JobTitles';
|
||||
import { getLabelForJobTitleType } from '~/components/shared/JobTitles';
|
||||
@ -10,12 +14,10 @@ import { formatDate } from '~/utils/offers/time';
|
||||
import type { UserProfileOffer } from '~/types/offers';
|
||||
|
||||
type Props = Readonly<{
|
||||
disableTopDivider?: boolean;
|
||||
offer: UserProfileOffer;
|
||||
}>;
|
||||
|
||||
export default function DashboardProfileCard({
|
||||
disableTopDivider,
|
||||
offer: {
|
||||
company,
|
||||
income,
|
||||
@ -27,29 +29,53 @@ export default function DashboardProfileCard({
|
||||
},
|
||||
}: Props) {
|
||||
return (
|
||||
<>
|
||||
{!disableTopDivider && <HorizontalDivider />}
|
||||
<div className="px-4 py-4 sm:px-6">
|
||||
<div className="flex items-end justify-between">
|
||||
<div className="col-span-1 row-span-3">
|
||||
<p className="font-bold">
|
||||
<h4 className="font-medium">
|
||||
{getLabelForJobTitleType(title as JobTitleType)}
|
||||
</p>
|
||||
<p>
|
||||
{location
|
||||
? `Company: ${company.name}, ${location}`
|
||||
: `Company: ${company.name}`}
|
||||
</p>
|
||||
{level && <p>Level: {level}</p>}
|
||||
</h4>
|
||||
<div className="mt-1 flex flex-col sm:mt-0 sm:flex-row sm:flex-wrap sm:space-x-4">
|
||||
{company?.name && (
|
||||
<div className="mt-2 flex items-center text-sm text-gray-500">
|
||||
<BuildingOfficeIcon
|
||||
aria-hidden="true"
|
||||
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
|
||||
/>
|
||||
{company.name}
|
||||
</div>
|
||||
)}
|
||||
{location && (
|
||||
<div className="mt-2 flex items-center text-sm text-gray-500">
|
||||
<MapPinIcon
|
||||
aria-hidden="true"
|
||||
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
|
||||
/>
|
||||
{location}
|
||||
</div>
|
||||
)}
|
||||
{level && (
|
||||
<div className="mt-2 flex items-center text-sm text-gray-500">
|
||||
<ArrowTrendingUpIcon
|
||||
aria-hidden="true"
|
||||
className="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400"
|
||||
/>
|
||||
{level}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 row-span-3">
|
||||
<p className="text-end">{formatDate(monthYearReceived)}</p>
|
||||
<p className="text-end text-xl">
|
||||
<p className="text-end text-lg font-medium leading-6 text-slate-900">
|
||||
{jobType === JobType.FULLTIME
|
||||
? `${convertMoneyToString(income)} / year`
|
||||
: `${convertMoneyToString(income)} / month`}
|
||||
</p>
|
||||
<p className="text-end text-sm text-slate-500">
|
||||
{formatDate(monthYearReceived)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import { ArrowRightIcon, XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import { BookmarkSlashIcon } from '@heroicons/react/20/solid';
|
||||
import { ArrowRightIcon } from '@heroicons/react/24/outline';
|
||||
import { Button, useToast } from '@tih/ui';
|
||||
|
||||
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
|
||||
@ -43,54 +44,52 @@ export default function DashboardProfileCard({
|
||||
);
|
||||
|
||||
function handleRemoveProfile() {
|
||||
// TODO(offers): Confirm before removal.
|
||||
removeSavedProfileMutation.mutate({ profileId: id });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4 bg-white px-4 pt-5 sm:px-4">
|
||||
<div className="overflow-hidden bg-white sm:rounded-lg sm:shadow">
|
||||
{/* Header */}
|
||||
<div className="-ml-4 -mt-2 flex flex-wrap items-center justify-between border-b border-gray-300 pb-4 sm:flex-nowrap">
|
||||
<div className="flex items-center gap-x-5">
|
||||
<div>
|
||||
<ProfilePhotoHolder size="sm" />
|
||||
</div>
|
||||
<div className="col-span-10">
|
||||
<p className="text-xl font-bold">{profileName}</p>
|
||||
|
||||
<div className="flex flex-row">
|
||||
<span>Created at {formatDate(createdAt)}</span>
|
||||
<div className="border-b border-slate-200 bg-white px-4 py-5 sm:px-6">
|
||||
<div className="-ml-4 -mt-4 flex flex-wrap items-center justify-between sm:flex-nowrap">
|
||||
<div className="ml-4 mt-4">
|
||||
<div className="flex items-center">
|
||||
<div className="flex-shrink-0">
|
||||
<ProfilePhotoHolder size="sm" />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<h2 className="text-lg font-medium leading-6 text-slate-900">
|
||||
{profileName}
|
||||
</h2>
|
||||
<p className="text-sm text-slate-500">
|
||||
<span>Created at {formatDate(createdAt)}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex self-start">
|
||||
<Button
|
||||
disabled={removeSavedProfileMutation.isLoading}
|
||||
icon={XMarkIcon}
|
||||
isLabelHidden={true}
|
||||
label="Remove Profile"
|
||||
size="md"
|
||||
variant="tertiary"
|
||||
onClick={handleRemoveProfile}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Offers */}
|
||||
<div>
|
||||
{offers.map((offer: UserProfileOffer, index) =>
|
||||
index === 0 ? (
|
||||
<DashboardOfferCard
|
||||
key={offer.id}
|
||||
disableTopDivider={true}
|
||||
offer={offer}
|
||||
<div className="ml-4 mt-4 flex flex-shrink-0">
|
||||
<Button
|
||||
disabled={removeSavedProfileMutation.isLoading}
|
||||
icon={BookmarkSlashIcon}
|
||||
isLabelHidden={true}
|
||||
label="Remove Profile"
|
||||
size="md"
|
||||
variant="tertiary"
|
||||
onClick={handleRemoveProfile}
|
||||
/>
|
||||
) : (
|
||||
<DashboardOfferCard key={offer.id} offer={offer} />
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end pt-1">
|
||||
{/* List of Offers */}
|
||||
<ul className="divide-y divide-slate-200" role="list">
|
||||
{offers.map((offer: UserProfileOffer) => (
|
||||
<li key={offer.id}>
|
||||
<DashboardOfferCard offer={offer} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex justify-end border-t border-slate-200 px-4 py-5 sm:px-6">
|
||||
<Button
|
||||
disabled={removeSavedProfileMutation.isLoading}
|
||||
icon={ArrowRightIcon}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { signIn, useSession } from 'next-auth/react';
|
||||
import { useState } from 'react';
|
||||
import { DocumentDuplicateIcon } from '@heroicons/react/20/solid';
|
||||
import { BookmarkSquareIcon, CheckIcon } from '@heroicons/react/24/outline';
|
||||
import { BookmarkIcon as BookmarkOutlineIcon } from '@heroicons/react/24/outline';
|
||||
import { BookmarkIcon as BookmarkSolidIcon } from '@heroicons/react/24/solid';
|
||||
import { Button, TextInput, useToast } from '@tih/ui';
|
||||
|
||||
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
|
||||
@ -126,7 +127,7 @@ export default function OffersProfileSave({
|
||||
<div className="mt-6">
|
||||
<Button
|
||||
disabled={isSavedQuery.isLoading || isSaved}
|
||||
icon={isSaved ? CheckIcon : BookmarkSquareIcon}
|
||||
icon={isSaved ? BookmarkSolidIcon : BookmarkOutlineIcon}
|
||||
isLoading={saveMutation.isLoading || isSavedQuery.isLoading}
|
||||
label={isSaved ? 'Added to account' : 'Add to your account'}
|
||||
size="sm"
|
||||
|
@ -264,7 +264,7 @@ export default function OffersSubmissionForm({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div ref={pageRef} className="w-full overflow-y-scroll">
|
||||
<div ref={pageRef} className="w-full">
|
||||
<div className="flex justify-center">
|
||||
<div className="block w-full max-w-screen-md overflow-hidden rounded-lg sm:shadow-lg md:my-10">
|
||||
<div className="flex justify-center bg-slate-100 px-4 py-4 sm:px-6 lg:px-8">
|
||||
|
@ -110,10 +110,10 @@ export default function ProfileComments({
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="bh-white h-fit px-4 md:h-[calc(100vh-4.5rem)] md:overflow-y-auto">
|
||||
<div className="bg-white pt-4 md:sticky md:top-0">
|
||||
<div className="bh-white h-fit px-4 lg:h-[calc(100vh-4.5rem)] lg:overflow-y-auto">
|
||||
<div className="bg-white pt-4 lg:sticky lg:top-0">
|
||||
<div className="flex justify-end">
|
||||
<div className="grid w-fit space-y-2 md:grid-cols-1 lg:grid-cols-2 lg:space-y-0 lg:space-x-4">
|
||||
<div className="grid w-fit space-y-2 lg:grid-cols-1 lg:grid-cols-2 lg:space-y-0 lg:space-x-4">
|
||||
<div className="col-span-1 flex justify-end">
|
||||
{isEditable && (
|
||||
<Tooltip tooltipContent="Copy this link to edit your profile later">
|
||||
@ -210,7 +210,7 @@ export default function ProfileComments({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-full w-full">
|
||||
<div className="w-full">
|
||||
{replies?.map((reply: Reply) => (
|
||||
<ExpandableCommentCard
|
||||
key={reply.id}
|
||||
|
@ -1,6 +1,6 @@
|
||||
type ProfilePhotoHolderProps = {
|
||||
type ProfilePhotoHolderProps = Readonly<{
|
||||
size?: 'lg' | 'sm';
|
||||
};
|
||||
}>;
|
||||
|
||||
export default function ProfilePhotoHolder({
|
||||
size = 'lg',
|
||||
|
@ -4,20 +4,21 @@ import React from 'react';
|
||||
type Props = Readonly<{
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
variant?: 'narrow' | 'normal';
|
||||
variant?: 'md' | 'sm' | 'xs';
|
||||
}>;
|
||||
|
||||
export default function Container({
|
||||
children,
|
||||
className,
|
||||
variant = 'normal',
|
||||
variant = 'md',
|
||||
}: Props) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'mx-auto px-4 sm:px-6 lg:px-8',
|
||||
variant === 'normal' && 'max-w-7xl',
|
||||
variant === 'narrow' && 'max-w-6xl',
|
||||
variant === 'md' && 'max-w-7xl',
|
||||
variant === 'sm' && 'max-w-5xl',
|
||||
variant === 'xs' && 'max-w-3xl',
|
||||
className,
|
||||
)}>
|
||||
{children}
|
||||
|
@ -4,6 +4,7 @@ import { useState } from 'react';
|
||||
import { Button, Spinner } from '@tih/ui';
|
||||
|
||||
import DashboardProfileCard from '~/components/offers/dashboard/DashboardProfileCard';
|
||||
import Container from '~/components/shared/Container';
|
||||
|
||||
import { trpc } from '~/utils/trpc';
|
||||
|
||||
@ -30,19 +31,21 @@ export default function ProfilesDashboard() {
|
||||
|
||||
if (status === 'loading' || userProfilesQuery.isLoading) {
|
||||
return (
|
||||
<div className="flex h-screen w-screen">
|
||||
<div className="flex w-full">
|
||||
<div className="m-auto mx-auto w-full justify-center">
|
||||
<Spinner className="m-10" display="block" size="lg" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
signIn();
|
||||
}
|
||||
|
||||
if (userProfiles.length === 0) {
|
||||
return (
|
||||
<div className="flex h-screen w-screen">
|
||||
<div className="flex w-full">
|
||||
<div className="m-auto mx-auto w-full justify-center text-xl">
|
||||
<div className="mb-8 flex w-full flex-row justify-center">
|
||||
<h2>You have not saved any offer profiles yet.</h2>
|
||||
@ -59,10 +62,11 @@ export default function ProfilesDashboard() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container variant="xs">
|
||||
{userProfilesQuery.isLoading && (
|
||||
<div className="flex h-screen w-screen">
|
||||
<div className="flex h-screen">
|
||||
<div className="m-auto mx-auto w-full justify-center">
|
||||
<Spinner className="m-10" display="block" size="lg" />
|
||||
</div>
|
||||
@ -70,19 +74,17 @@ export default function ProfilesDashboard() {
|
||||
)}
|
||||
{!userProfilesQuery.isLoading && (
|
||||
<div className="overflow-y-auto py-8">
|
||||
<h1 className="mx-auto mb-4 w-3/4 text-start text-4xl font-bold text-slate-900">
|
||||
<h1 className="mx-auto mb-4 text-start text-4xl font-bold text-slate-900">
|
||||
Your dashboard
|
||||
</h1>
|
||||
<p className="mx-auto w-3/4 text-start text-xl text-slate-900">
|
||||
Save your offer profiles to dashboard to easily access and edit them
|
||||
later.
|
||||
<p className="mt-4 text-xl leading-8 text-slate-500">
|
||||
Save your offer profiles to your dashboard to easily access and edit
|
||||
them later.
|
||||
</p>
|
||||
<div className="justfy-center mt-8 flex w-screen">
|
||||
<ul className="mx-auto w-3/4 space-y-3" role="list">
|
||||
<div className="mt-8 flex justify-center">
|
||||
<ul className="w-full space-y-4" role="list">
|
||||
{userProfiles?.map((profile) => (
|
||||
<li
|
||||
key={profile.id}
|
||||
className="overflow-hidden bg-white px-4 py-4 shadow sm:rounded-md sm:px-6">
|
||||
<li key={profile.id}>
|
||||
<DashboardProfileCard key={profile.id} profile={profile} />
|
||||
</li>
|
||||
))}
|
||||
@ -90,6 +92,6 @@ export default function ProfilesDashboard() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
@ -202,9 +202,9 @@ export default function OfferProfile() {
|
||||
</div>
|
||||
)}
|
||||
{!getProfileQuery.isLoading && !getProfileQuery.isError && (
|
||||
<div className="h-fuill flex grid w-full grid-cols-1 items-center justify-center divide-x overflow-y-auto md:grid-cols-3">
|
||||
<div className="col-span-1 flex h-full flex-col divide-y md:col-span-2 md:overflow-y-auto">
|
||||
<div className="h-fit md:sticky md:top-0">
|
||||
<div className="w-full divide-x lg:flex">
|
||||
<div className="divide-y lg:w-2/3">
|
||||
<div className="h-fit">
|
||||
<ProfileHeader
|
||||
background={background}
|
||||
handleDelete={handleDelete}
|
||||
@ -226,7 +226,9 @@ export default function OfferProfile() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-1 h-full bg-white">
|
||||
<div
|
||||
className="bg-white lg:fixed lg:right-0 lg:bottom-0 lg:w-1/3"
|
||||
style={{ top: 64 }}>
|
||||
<ProfileComments
|
||||
isDisabled={deleteMutation.isLoading}
|
||||
isEditable={isEditable}
|
||||
|
@ -78,7 +78,7 @@ export default function OffersSubmissionResult() {
|
||||
</div>
|
||||
)}
|
||||
{!getAnalysis.isLoading && (
|
||||
<div ref={pageRef} className="w-full overflow-y-scroll">
|
||||
<div ref={pageRef} className="w-full">
|
||||
<div className="flex justify-center">
|
||||
<div className="block w-full max-w-screen-md overflow-hidden rounded-lg sm:shadow-lg md:my-10">
|
||||
<div className="flex justify-center bg-slate-100 px-4 py-4 sm:px-6 lg:px-8">
|
||||
|
Reference in New Issue
Block a user