mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 12:43:12 +08:00
[resumes][feat] add isStarredByUser field to Resumes (#381)
This commit is contained in:
@ -1,17 +1,14 @@
|
|||||||
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
|
import formatDistanceToNow from 'date-fns/formatDistanceToNow';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useSession } from 'next-auth/react';
|
|
||||||
import type { UrlObject } from 'url';
|
import type { UrlObject } from 'url';
|
||||||
import { ChevronRightIcon } from '@heroicons/react/20/solid';
|
|
||||||
import {
|
import {
|
||||||
AcademicCapIcon,
|
AcademicCapIcon,
|
||||||
BriefcaseIcon,
|
BriefcaseIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
StarIcon as ColouredStarIcon,
|
StarIcon as ColouredStarIcon,
|
||||||
} from '@heroicons/react/20/solid';
|
} from '@heroicons/react/20/solid';
|
||||||
import { ChatBubbleLeftIcon, StarIcon } from '@heroicons/react/24/outline';
|
import { ChatBubbleLeftIcon, StarIcon } from '@heroicons/react/24/outline';
|
||||||
|
|
||||||
import { trpc } from '~/utils/trpc';
|
|
||||||
|
|
||||||
import type { Resume } from '~/types/resume';
|
import type { Resume } from '~/types/resume';
|
||||||
|
|
||||||
type Props = Readonly<{
|
type Props = Readonly<{
|
||||||
@ -19,16 +16,7 @@ type Props = Readonly<{
|
|||||||
resumeInfo: Resume;
|
resumeInfo: Resume;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export default function BrowseListItem({ href, resumeInfo }: Props) {
|
export default function ResumeListItem({ href, resumeInfo }: Props) {
|
||||||
const { data: sessionData } = useSession();
|
|
||||||
|
|
||||||
// Find out if user has starred this particular resume
|
|
||||||
const resumeId = resumeInfo.id;
|
|
||||||
const isStarredQuery = trpc.useQuery([
|
|
||||||
'resumes.resume.user.isResumeStarred',
|
|
||||||
{ resumeId },
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={href}>
|
<Link href={href}>
|
||||||
<div className="grid grid-cols-8 gap-4 border-b border-slate-200 p-4 hover:bg-slate-100">
|
<div className="grid grid-cols-8 gap-4 border-b border-slate-200 p-4 hover:bg-slate-100">
|
||||||
@ -56,7 +44,7 @@ export default function BrowseListItem({ href, resumeInfo }: Props) {
|
|||||||
{resumeInfo.numComments} comments
|
{resumeInfo.numComments} comments
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{isStarredQuery.data && sessionData?.user ? (
|
{resumeInfo.isStarredByUser ? (
|
||||||
<ColouredStarIcon className="w-4 text-yellow-400" />
|
<ColouredStarIcon className="w-4 text-yellow-400" />
|
||||||
) : (
|
) : (
|
||||||
<StarIcon className="w-4" />
|
<StarIcon className="w-4" />
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Spinner } from '@tih/ui';
|
import { Spinner } from '@tih/ui';
|
||||||
|
|
||||||
import ResumseListItem from './ResumeListItem';
|
import ResumeListItem from './ResumeListItem';
|
||||||
|
|
||||||
import type { Resume } from '~/types/resume';
|
import type { Resume } from '~/types/resume';
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ export default function ResumeListItems({ isLoading, resumes }: Props) {
|
|||||||
<ul role="list">
|
<ul role="list">
|
||||||
{resumes.map((resumeObj: Resume) => (
|
{resumes.map((resumeObj: Resume) => (
|
||||||
<li key={resumeObj.id}>
|
<li key={resumeObj.id}>
|
||||||
<ResumseListItem
|
<ResumeListItem
|
||||||
href={`/resumes/${resumeObj.id}`}
|
href={`/resumes/${resumeObj.id}`}
|
||||||
resumeInfo={resumeObj}
|
resumeInfo={resumeObj}
|
||||||
/>
|
/>
|
||||||
|
@ -49,7 +49,6 @@ export default function ResumeReviewPage() {
|
|||||||
utils.invalidateQueries(['resumes.resume.findOne']);
|
utils.invalidateQueries(['resumes.resume.findOne']);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const userIsOwner =
|
const userIsOwner =
|
||||||
session?.user?.id != null && session.user.id === detailsQuery.data?.userId;
|
session?.user?.id != null && session.user.id === detailsQuery.data?.userId;
|
||||||
|
|
||||||
@ -61,8 +60,6 @@ export default function ResumeReviewPage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Star button only rendered if resume exists
|
|
||||||
// Star button only clickable if user exists
|
|
||||||
if (detailsQuery.data?.stars.length) {
|
if (detailsQuery.data?.stars.length) {
|
||||||
unstarMutation.mutate({
|
unstarMutation.mutate({
|
||||||
resumeId: resumeId as string,
|
resumeId: resumeId as string,
|
||||||
|
@ -22,7 +22,7 @@ export const resumesCommentsUserRouter = createProtectedRouter().mutation(
|
|||||||
skills: z.string(),
|
skills: z.string(),
|
||||||
}),
|
}),
|
||||||
async resolve({ ctx, input }) {
|
async resolve({ ctx, input }) {
|
||||||
const userId = ctx.session?.user?.id;
|
const userId = ctx.session.user.id;
|
||||||
const { resumeId, education, experience, general, projects, skills } =
|
const { resumeId, education, experience, general, projects, skills } =
|
||||||
input;
|
input;
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import type { Resume } from '~/types/resume';
|
|||||||
export const resumesRouter = createRouter()
|
export const resumesRouter = createRouter()
|
||||||
.query('findAll', {
|
.query('findAll', {
|
||||||
async resolve({ ctx }) {
|
async resolve({ ctx }) {
|
||||||
|
const userId = ctx.session?.user?.id;
|
||||||
const resumesData = await ctx.prisma.resumesResume.findMany({
|
const resumesData = await ctx.prisma.resumesResume.findMany({
|
||||||
include: {
|
include: {
|
||||||
_count: {
|
_count: {
|
||||||
@ -15,6 +16,13 @@ export const resumesRouter = createRouter()
|
|||||||
stars: true,
|
stars: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
stars: {
|
||||||
|
where: {
|
||||||
|
OR: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
name: true,
|
name: true,
|
||||||
@ -31,6 +39,7 @@ export const resumesRouter = createRouter()
|
|||||||
createdAt: r.createdAt,
|
createdAt: r.createdAt,
|
||||||
experience: r.experience,
|
experience: r.experience,
|
||||||
id: r.id,
|
id: r.id,
|
||||||
|
isStarredByUser: r.stars.length > 0,
|
||||||
location: r.location,
|
location: r.location,
|
||||||
numComments: r._count.comments,
|
numComments: r._count.comments,
|
||||||
numStars: r._count.stars,
|
numStars: r._count.stars,
|
||||||
|
@ -17,7 +17,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
url: z.string(),
|
url: z.string(),
|
||||||
}),
|
}),
|
||||||
async resolve({ ctx, input }) {
|
async resolve({ ctx, input }) {
|
||||||
const userId = ctx.session?.user.id;
|
const userId = ctx.session.user.id;
|
||||||
|
|
||||||
return await ctx.prisma.resumesResume.upsert({
|
return await ctx.prisma.resumesResume.upsert({
|
||||||
create: {
|
create: {
|
||||||
@ -46,7 +46,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
})
|
})
|
||||||
.query('findUserStarred', {
|
.query('findUserStarred', {
|
||||||
async resolve({ ctx }) {
|
async resolve({ ctx }) {
|
||||||
const userId = ctx.session?.user?.id;
|
const userId = ctx.session.user.id;
|
||||||
const resumeStarsData = await ctx.prisma.resumesStar.findMany({
|
const resumeStarsData = await ctx.prisma.resumesStar.findMany({
|
||||||
include: {
|
include: {
|
||||||
resume: {
|
resume: {
|
||||||
@ -78,6 +78,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
createdAt: rs.resume.createdAt,
|
createdAt: rs.resume.createdAt,
|
||||||
experience: rs.resume.experience,
|
experience: rs.resume.experience,
|
||||||
id: rs.resume.id,
|
id: rs.resume.id,
|
||||||
|
isStarredByUser: true,
|
||||||
location: rs.resume.location,
|
location: rs.resume.location,
|
||||||
numComments: rs.resume._count.comments,
|
numComments: rs.resume._count.comments,
|
||||||
numStars: rs.resume._count.stars,
|
numStars: rs.resume._count.stars,
|
||||||
@ -92,7 +93,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
})
|
})
|
||||||
.query('findUserCreated', {
|
.query('findUserCreated', {
|
||||||
async resolve({ ctx }) {
|
async resolve({ ctx }) {
|
||||||
const userId = ctx.session?.user?.id;
|
const userId = ctx.session.user.id;
|
||||||
const resumesData = await ctx.prisma.resumesResume.findMany({
|
const resumesData = await ctx.prisma.resumesResume.findMany({
|
||||||
include: {
|
include: {
|
||||||
_count: {
|
_count: {
|
||||||
@ -101,6 +102,11 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
stars: true,
|
stars: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
stars: {
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
name: true,
|
name: true,
|
||||||
@ -120,6 +126,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
createdAt: r.createdAt,
|
createdAt: r.createdAt,
|
||||||
experience: r.experience,
|
experience: r.experience,
|
||||||
id: r.id,
|
id: r.id,
|
||||||
|
isStarredByUser: r.stars.length > 0,
|
||||||
location: r.location,
|
location: r.location,
|
||||||
numComments: r._count.comments,
|
numComments: r._count.comments,
|
||||||
numStars: r._count.stars,
|
numStars: r._count.stars,
|
||||||
@ -131,18 +138,4 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
|||||||
return resume;
|
return resume;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
})
|
|
||||||
.query('isResumeStarred', {
|
|
||||||
input: z.object({
|
|
||||||
resumeId: z.string(),
|
|
||||||
}),
|
|
||||||
async resolve({ ctx, input }) {
|
|
||||||
const userId = ctx.session?.user?.id;
|
|
||||||
const { resumeId } = input;
|
|
||||||
return await ctx.prisma.resumesStar.findUnique({
|
|
||||||
where: {
|
|
||||||
userId_resumeId: { resumeId, userId },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
1
apps/portal/src/types/resume.d.ts
vendored
1
apps/portal/src/types/resume.d.ts
vendored
@ -3,6 +3,7 @@ export type Resume = {
|
|||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
experience: string;
|
experience: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
isStarredByUser: boolean;
|
||||||
location: string;
|
location: string;
|
||||||
numComments: number;
|
numComments: number;
|
||||||
numStars: number;
|
numStars: number;
|
||||||
|
Reference in New Issue
Block a user