[offers] tweak offer profiles UI

pull/492/head
Yangshun Tay 2 years ago
parent 8e08ff86f4
commit ea57743cfe

@ -1,5 +1,9 @@
import {
ArrowTrendingUpIcon,
BuildingOfficeIcon,
MapPinIcon,
} from '@heroicons/react/20/solid';
import { JobType } from '@prisma/client'; import { JobType } from '@prisma/client';
import { HorizontalDivider } from '@tih/ui';
import type { JobTitleType } from '~/components/shared/JobTitles'; import type { JobTitleType } from '~/components/shared/JobTitles';
import { getLabelForJobTitleType } 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'; import type { UserProfileOffer } from '~/types/offers';
type Props = Readonly<{ type Props = Readonly<{
disableTopDivider?: boolean;
offer: UserProfileOffer; offer: UserProfileOffer;
}>; }>;
export default function DashboardProfileCard({ export default function DashboardProfileCard({
disableTopDivider,
offer: { offer: {
company, company,
income, income,
@ -27,29 +29,53 @@ export default function DashboardProfileCard({
}, },
}: Props) { }: Props) {
return ( return (
<> <div className="px-4 py-4 sm:px-6">
{!disableTopDivider && <HorizontalDivider />}
<div className="flex items-end justify-between"> <div className="flex items-end justify-between">
<div className="col-span-1 row-span-3"> <div className="col-span-1 row-span-3">
<p className="font-bold"> <h4 className="font-medium">
{getLabelForJobTitleType(title as JobTitleType)} {getLabelForJobTitleType(title as JobTitleType)}
</p> </h4>
<p> <div className="mt-1 flex flex-col sm:mt-0 sm:flex-row sm:flex-wrap sm:space-x-4">
{location {company?.name && (
? `Company: ${company.name}, ${location}` <div className="mt-2 flex items-center text-sm text-gray-500">
: `Company: ${company.name}`} <BuildingOfficeIcon
</p> aria-hidden="true"
{level && <p>Level: {level}</p>} 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>
<div className="col-span-1 row-span-3"> <div className="col-span-1 row-span-3">
<p className="text-end">{formatDate(monthYearReceived)}</p> <p className="text-end text-lg font-medium leading-6 text-slate-900">
<p className="text-end text-xl">
{jobType === JobType.FULLTIME {jobType === JobType.FULLTIME
? `${convertMoneyToString(income)} / year` ? `${convertMoneyToString(income)} / year`
: `${convertMoneyToString(income)} / month`} : `${convertMoneyToString(income)} / month`}
</p> </p>
<p className="text-end text-sm text-slate-500">
{formatDate(monthYearReceived)}
</p>
</div> </div>
</div> </div>
</> </div>
); );
} }

@ -1,5 +1,6 @@
import { useRouter } from 'next/router'; 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 { Button, useToast } from '@tih/ui';
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics'; import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
@ -43,54 +44,52 @@ export default function DashboardProfileCard({
); );
function handleRemoveProfile() { function handleRemoveProfile() {
// TODO(offers): Confirm before removal.
removeSavedProfileMutation.mutate({ profileId: id }); removeSavedProfileMutation.mutate({ profileId: id });
} }
return ( 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 */} {/* 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="border-b border-slate-200 bg-white px-4 py-5 sm:px-6">
<div className="flex items-center gap-x-5"> <div className="-ml-4 -mt-4 flex flex-wrap items-center justify-between sm:flex-nowrap">
<div> <div className="ml-4 mt-4">
<ProfilePhotoHolder size="sm" /> <div className="flex items-center">
</div> <div className="flex-shrink-0">
<div className="col-span-10"> <ProfilePhotoHolder size="sm" />
<p className="text-xl font-bold">{profileName}</p> </div>
<div className="ml-4">
<div className="flex flex-row"> <h2 className="text-lg font-medium leading-6 text-slate-900">
<span>Created at {formatDate(createdAt)}</span> {profileName}
</h2>
<p className="text-sm text-slate-500">
<span>Created at {formatDate(createdAt)}</span>
</p>
</div>
</div> </div>
</div> </div>
</div> <div className="ml-4 mt-4 flex flex-shrink-0">
<Button
<div className="flex self-start"> disabled={removeSavedProfileMutation.isLoading}
<Button icon={BookmarkSlashIcon}
disabled={removeSavedProfileMutation.isLoading} isLabelHidden={true}
icon={XMarkIcon} label="Remove Profile"
isLabelHidden={true} size="md"
label="Remove Profile" variant="tertiary"
size="md" onClick={handleRemoveProfile}
variant="tertiary"
onClick={handleRemoveProfile}
/>
</div>
</div>
{/* Offers */}
<div>
{offers.map((offer: UserProfileOffer, index) =>
index === 0 ? (
<DashboardOfferCard
key={offer.id}
disableTopDivider={true}
offer={offer}
/> />
) : ( </div>
<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 <Button
disabled={removeSavedProfileMutation.isLoading} disabled={removeSavedProfileMutation.isLoading}
icon={ArrowRightIcon} icon={ArrowRightIcon}

@ -1,7 +1,8 @@
import { signIn, useSession } from 'next-auth/react'; import { signIn, useSession } from 'next-auth/react';
import { useState } from 'react'; import { useState } from 'react';
import { DocumentDuplicateIcon } from '@heroicons/react/20/solid'; 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 { Button, TextInput, useToast } from '@tih/ui';
import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics'; import { useGoogleAnalytics } from '~/components/global/GoogleAnalytics';
@ -126,7 +127,7 @@ export default function OffersProfileSave({
<div className="mt-6"> <div className="mt-6">
<Button <Button
disabled={isSavedQuery.isLoading || isSaved} disabled={isSavedQuery.isLoading || isSaved}
icon={isSaved ? CheckIcon : BookmarkSquareIcon} icon={isSaved ? BookmarkSolidIcon : BookmarkOutlineIcon}
isLoading={saveMutation.isLoading || isSavedQuery.isLoading} isLoading={saveMutation.isLoading || isSavedQuery.isLoading}
label={isSaved ? 'Added to account' : 'Add to your account'} label={isSaved ? 'Added to account' : 'Add to your account'}
size="sm" size="sm"

@ -264,7 +264,7 @@ export default function OffersSubmissionForm({
}, []); }, []);
return ( return (
<div ref={pageRef} className="w-full overflow-y-scroll"> <div ref={pageRef} className="w-full">
<div className="flex justify-center"> <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="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"> <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 ( return (
<div className="bh-white h-fit px-4 md:h-[calc(100vh-4.5rem)] md:overflow-y-auto"> <div className="bh-white h-fit px-4 lg:h-[calc(100vh-4.5rem)] lg:overflow-y-auto">
<div className="bg-white pt-4 md:sticky md:top-0"> <div className="bg-white pt-4 lg:sticky lg:top-0">
<div className="flex justify-end"> <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"> <div className="col-span-1 flex justify-end">
{isEditable && ( {isEditable && (
<Tooltip tooltipContent="Copy this link to edit your profile later"> <Tooltip tooltipContent="Copy this link to edit your profile later">
@ -210,7 +210,7 @@ export default function ProfileComments({
/> />
)} )}
</div> </div>
<div className="h-full w-full"> <div className="w-full">
{replies?.map((reply: Reply) => ( {replies?.map((reply: Reply) => (
<ExpandableCommentCard <ExpandableCommentCard
key={reply.id} key={reply.id}

@ -1,6 +1,6 @@
type ProfilePhotoHolderProps = { type ProfilePhotoHolderProps = Readonly<{
size?: 'lg' | 'sm'; size?: 'lg' | 'sm';
}; }>;
export default function ProfilePhotoHolder({ export default function ProfilePhotoHolder({
size = 'lg', size = 'lg',

@ -4,20 +4,21 @@ import React from 'react';
type Props = Readonly<{ type Props = Readonly<{
children: React.ReactNode; children: React.ReactNode;
className?: string; className?: string;
variant?: 'narrow' | 'normal'; variant?: 'md' | 'sm' | 'xs';
}>; }>;
export default function Container({ export default function Container({
children, children,
className, className,
variant = 'normal', variant = 'md',
}: Props) { }: Props) {
return ( return (
<div <div
className={clsx( className={clsx(
'mx-auto px-4 sm:px-6 lg:px-8', 'mx-auto px-4 sm:px-6 lg:px-8',
variant === 'normal' && 'max-w-7xl', variant === 'md' && 'max-w-7xl',
variant === 'narrow' && 'max-w-6xl', variant === 'sm' && 'max-w-5xl',
variant === 'xs' && 'max-w-3xl',
className, className,
)}> )}>
{children} {children}

@ -4,6 +4,7 @@ import { useState } from 'react';
import { Button, Spinner } from '@tih/ui'; import { Button, Spinner } from '@tih/ui';
import DashboardProfileCard from '~/components/offers/dashboard/DashboardProfileCard'; import DashboardProfileCard from '~/components/offers/dashboard/DashboardProfileCard';
import Container from '~/components/shared/Container';
import { trpc } from '~/utils/trpc'; import { trpc } from '~/utils/trpc';
@ -30,19 +31,21 @@ export default function ProfilesDashboard() {
if (status === 'loading' || userProfilesQuery.isLoading) { if (status === 'loading' || userProfilesQuery.isLoading) {
return ( return (
<div className="flex h-screen w-screen"> <div className="flex w-full">
<div className="m-auto mx-auto w-full justify-center"> <div className="m-auto mx-auto w-full justify-center">
<Spinner className="m-10" display="block" size="lg" /> <Spinner className="m-10" display="block" size="lg" />
</div> </div>
</div> </div>
); );
} }
if (status === 'unauthenticated') { if (status === 'unauthenticated') {
signIn(); signIn();
} }
if (userProfiles.length === 0) { if (userProfiles.length === 0) {
return ( 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="m-auto mx-auto w-full justify-center text-xl">
<div className="mb-8 flex w-full flex-row justify-center"> <div className="mb-8 flex w-full flex-row justify-center">
<h2>You have not saved any offer profiles yet.</h2> <h2>You have not saved any offer profiles yet.</h2>
@ -59,10 +62,11 @@ export default function ProfilesDashboard() {
</div> </div>
); );
} }
return ( return (
<> <Container variant="xs">
{userProfilesQuery.isLoading && ( {userProfilesQuery.isLoading && (
<div className="flex h-screen w-screen"> <div className="flex h-screen">
<div className="m-auto mx-auto w-full justify-center"> <div className="m-auto mx-auto w-full justify-center">
<Spinner className="m-10" display="block" size="lg" /> <Spinner className="m-10" display="block" size="lg" />
</div> </div>
@ -70,19 +74,17 @@ export default function ProfilesDashboard() {
)} )}
{!userProfilesQuery.isLoading && ( {!userProfilesQuery.isLoading && (
<div className="overflow-y-auto py-8"> <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 Your dashboard
</h1> </h1>
<p className="mx-auto w-3/4 text-start text-xl text-slate-900"> <p className="mt-4 text-xl leading-8 text-slate-500">
Save your offer profiles to dashboard to easily access and edit them Save your offer profiles to your dashboard to easily access and edit
later. them later.
</p> </p>
<div className="justfy-center mt-8 flex w-screen"> <div className="mt-8 flex justify-center">
<ul className="mx-auto w-3/4 space-y-3" role="list"> <ul className="w-full space-y-4" role="list">
{userProfiles?.map((profile) => ( {userProfiles?.map((profile) => (
<li <li key={profile.id}>
key={profile.id}
className="overflow-hidden bg-white px-4 py-4 shadow sm:rounded-md sm:px-6">
<DashboardProfileCard key={profile.id} profile={profile} /> <DashboardProfileCard key={profile.id} profile={profile} />
</li> </li>
))} ))}
@ -90,6 +92,6 @@ export default function ProfilesDashboard() {
</div> </div>
</div> </div>
)} )}
</> </Container>
); );
} }

@ -202,9 +202,9 @@ export default function OfferProfile() {
</div> </div>
)} )}
{!getProfileQuery.isLoading && !getProfileQuery.isError && ( {!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="w-full divide-x lg:flex">
<div className="col-span-1 flex h-full flex-col divide-y md:col-span-2 md:overflow-y-auto"> <div className="divide-y lg:w-2/3">
<div className="h-fit md:sticky md:top-0"> <div className="h-fit">
<ProfileHeader <ProfileHeader
background={background} background={background}
handleDelete={handleDelete} handleDelete={handleDelete}
@ -226,7 +226,9 @@ export default function OfferProfile() {
/> />
</div> </div>
</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 <ProfileComments
isDisabled={deleteMutation.isLoading} isDisabled={deleteMutation.isLoading}
isEditable={isEditable} isEditable={isEditable}

@ -78,7 +78,7 @@ export default function OffersSubmissionResult() {
</div> </div>
)} )}
{!getAnalysis.isLoading && ( {!getAnalysis.isLoading && (
<div ref={pageRef} className="w-full overflow-y-scroll"> <div ref={pageRef} className="w-full">
<div className="flex justify-center"> <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="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"> <div className="flex justify-center bg-slate-100 px-4 py-4 sm:px-6 lg:px-8">

Loading…
Cancel
Save