[resumes][feat] Fetch resumes for browse tabs (#326)

* [resumes][fix] Change browse list item styling

* [resumes][feat] Add protected tabs router for browse page

* [resumes][feat] Fetch all, starred and my resumes in browse page

* [resumes][fix] Fix overflow y scrolling

* [resumes][fix] Use date-fns to format upload time text
This commit is contained in:
Su Yin
2022-10-08 20:57:04 +08:00
committed by GitHub
parent 827550a5fd
commit 2f50694016
6 changed files with 158 additions and 16 deletions

View File

@ -1,3 +1,4 @@
import { formatDistanceToNow } from 'date-fns';
import Link from 'next/link';
import type { UrlObject } from 'url';
import { ChevronRightIcon } from '@heroicons/react/20/solid';
@ -13,8 +14,8 @@ type Props = Readonly<{
export default function BrowseListItem({ href, resumeInfo }: Props) {
return (
<Link href={href}>
<div className="flex justify-between border-b border-slate-200 p-4">
<div>
<div className="grid grid-cols-8 border-b border-slate-200 p-4">
<div className="col-span-4">
{resumeInfo.title}
<div className="mt-2 flex items-center justify-start text-xs text-indigo-500">
{resumeInfo.role}
@ -33,11 +34,11 @@ export default function BrowseListItem({ href, resumeInfo }: Props) {
</div>
</div>
</div>
<div className="self-center text-sm text-slate-500">
{/* TODO: Replace hardcoded days ago with calculated days ago*/}
Uploaded 2 days ago by {resumeInfo.user}
<div className="col-span-3 self-center text-sm text-slate-500">
Uploaded {formatDistanceToNow(resumeInfo.createdAt)} ago by{' '}
{resumeInfo.user}
</div>
<ChevronRightIcon className="w-8" />
<ChevronRightIcon className="col-span-1 w-8 self-center justify-self-center" />
</div>
</Link>
);

View File

@ -1,3 +1,9 @@
export const BROWSE_TABS_VALUES = {
ALL: 'all',
MY: 'my',
STARRED: 'starred',
};
export const SORT_OPTIONS = [
{ current: true, href: '#', name: 'Latest' },
{ current: false, href: '#', name: 'Popular' },

View File

@ -1,5 +1,5 @@
import clsx from 'clsx';
import { Fragment, useState } from 'react';
import { Fragment, useEffect, useState } from 'react';
import { Disclosure, Menu, Transition } from '@headlessui/react';
import {
ChevronDownIcon,
@ -11,6 +11,7 @@ import { Tabs, TextInput } from '@tih/ui';
import BrowseListItem from '~/components/resumes/browse/BrowseListItem';
import {
BROWSE_TABS_VALUES,
EXPERIENCE,
LOCATION,
ROLES,
@ -19,6 +20,8 @@ import {
} from '~/components/resumes/browse/constants';
import FilterPill from '~/components/resumes/browse/FilterPill';
import type { Resume } from '~/types/resume';
const filters = [
{
id: 'roles',
@ -41,12 +44,47 @@ import ResumeReviewsTitle from '~/components/resumes/ResumeReviewsTitle';
import { trpc } from '~/utils/trpc';
export default function ResumeHomePage() {
const [tabsValue, setTabsValue] = useState('all');
const [tabsValue, setTabsValue] = useState(BROWSE_TABS_VALUES.ALL);
const [searchValue, setSearchValue] = useState('');
const resumesQuery = trpc.useQuery(['resumes.resume.list']);
const [resumes, setResumes] = useState(Array<Resume>());
const allResumesQuery = trpc.useQuery(['resumes.resume.all'], {
enabled: tabsValue === BROWSE_TABS_VALUES.ALL,
});
const starredResumesQuery = trpc.useQuery(['resumes.resume.browse.stars'], {
enabled: tabsValue === BROWSE_TABS_VALUES.STARRED,
});
const myResumesQuery = trpc.useQuery(['resumes.resume.browse.my'], {
enabled: tabsValue === BROWSE_TABS_VALUES.MY,
});
useEffect(() => {
switch (tabsValue) {
case BROWSE_TABS_VALUES.ALL: {
setResumes(allResumesQuery.data ?? Array<Resume>());
break;
}
case BROWSE_TABS_VALUES.STARRED: {
setResumes(starredResumesQuery.data ?? Array<Resume>());
break;
}
case BROWSE_TABS_VALUES.MY: {
setResumes(myResumesQuery.data ?? Array<Resume>());
break;
}
default: {
setResumes(Array<Resume>());
}
}
}, [
allResumesQuery.data,
starredResumesQuery.data,
myResumesQuery.data,
tabsValue,
]);
return (
<main className="h-full flex-1 overflow-y-auto">
<main className="h-[calc(100vh-4rem)] flex-1 overflow-y-scroll">
<div className="ml-4 py-4">
<ResumeReviewsTitle />
</div>
@ -64,15 +102,15 @@ export default function ResumeHomePage() {
tabs={[
{
label: 'All Resumes',
value: 'all',
value: BROWSE_TABS_VALUES.ALL,
},
{
label: 'Starred Resumes',
value: 'starred',
value: BROWSE_TABS_VALUES.STARRED,
},
{
label: 'My Resumes',
value: 'my',
value: BROWSE_TABS_VALUES.MY,
},
]}
value={tabsValue}
@ -223,12 +261,14 @@ export default function ResumeHomePage() {
</form>
</div>
</div>
{resumesQuery.isLoading ? (
{allResumesQuery.isLoading ||
starredResumesQuery.isLoading ||
myResumesQuery.isLoading ? (
<div>Loading...</div>
) : (
<div className="col-span-10 pr-8">
<ul role="list">
{resumesQuery.data?.map((resumeObj) => (
{resumes.map((resumeObj) => (
<li key={resumeObj.id}>
<BrowseListItem href="#" resumeInfo={resumeObj} />
</li>

View File

@ -4,6 +4,7 @@ import { createRouter } from './context';
import { protectedExampleRouter } from './protected-example-router';
import { resumesRouter } from './resumes';
import { resumesDetailsRouter } from './resumes-details-router';
import { resumesResumeProtectedTabsRouter } from './resumes-resume-protected-tabs-router';
import { resumesResumeUserRouter } from './resumes-resume-user-router';
import { resumeReviewsRouter } from './resumes-reviews-router';
import { resumesReviewsUserRouter } from './resumes-reviews-user-router';
@ -21,6 +22,7 @@ export const appRouter = createRouter()
.merge('resumes.resume.', resumesRouter)
.merge('resumes.details.', resumesDetailsRouter)
.merge('resumes.resume.user.', resumesResumeUserRouter)
.merge('resumes.resume.browse.', resumesResumeProtectedTabsRouter)
.merge('resumes.reviews.', resumeReviewsRouter)
.merge('resumes.reviews.user.', resumesReviewsUserRouter);

View File

@ -0,0 +1,93 @@
import { createProtectedRouter } from './context';
import type { Resume } from '~/types/resume';
export const resumesResumeProtectedTabsRouter = createProtectedRouter()
.query('stars', {
async resolve({ ctx }) {
const userId = ctx.session?.user?.id;
const resumeStarsData = await ctx.prisma.resumesStar.findMany({
include: {
resume: {
include: {
_count: {
select: {
comments: true,
stars: true,
},
},
},
},
user: {
select: {
name: true,
},
},
},
orderBy: {
createdAt: 'desc',
},
where: {
userId,
},
});
return resumeStarsData.map((rs) => {
const resume: Resume = {
additionalInfo: rs.resume.additionalInfo,
createdAt: rs.resume.createdAt,
experience: rs.resume.experience,
id: rs.id,
location: rs.resume.location,
numComments: rs.resume._count.comments,
numStars: rs.resume._count.stars,
role: rs.resume.role,
title: rs.resume.title,
url: rs.resume.url,
user: rs.user.name!,
};
return resume;
});
},
})
.query('my', {
async resolve({ ctx }) {
const userId = ctx.session?.user?.id;
const resumesData = await ctx.prisma.resumesResume.findMany({
include: {
_count: {
select: {
comments: true,
stars: true,
},
},
user: {
select: {
name: true,
},
},
},
orderBy: {
createdAt: 'desc',
},
where: {
userId,
},
});
return resumesData.map((r) => {
const resume: Resume = {
additionalInfo: r.additionalInfo,
createdAt: r.createdAt,
experience: r.experience,
id: r.id,
location: r.location,
numComments: r._count.comments,
numStars: r._count.stars,
role: r.role,
title: r.title,
url: r.url,
user: r.user.name!,
};
return resume;
});
},
});

View File

@ -2,7 +2,7 @@ import { createRouter } from './context';
import type { Resume } from '~/types/resume';
export const resumesRouter = createRouter().query('list', {
export const resumesRouter = createRouter().query('all', {
async resolve({ ctx }) {
const resumesData = await ctx.prisma.resumesResume.findMany({
include: {