mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 04:33:42 +08:00
[resumes][fix] add resolve resume functionality (#442)
* [resumes][fix] button getting cut off * [resumes][feat] add resolve functionality * [resumes][feat] replace student roles with internship * [resumes][feat] fetch isResolved field
This commit is contained in:
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "ResumesResume" ADD COLUMN "isResolved" BOOLEAN NOT NULL DEFAULT false;
|
@ -120,6 +120,7 @@ model ResumesResume {
|
||||
location String @db.Text
|
||||
url String
|
||||
additionalInfo String? @db.Text
|
||||
isResolved Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
AcademicCapIcon,
|
||||
BriefcaseIcon,
|
||||
CalendarIcon,
|
||||
CheckCircleIcon,
|
||||
InformationCircleIcon,
|
||||
MapPinIcon,
|
||||
PencilSquareIcon,
|
||||
@ -57,24 +58,33 @@ export default function ResumeReviewPage() {
|
||||
);
|
||||
const starMutation = trpc.useMutation('resumes.resume.star', {
|
||||
onSuccess() {
|
||||
utils.invalidateQueries(['resumes.resume.findOne']);
|
||||
utils.invalidateQueries(['resumes.resume.findAll']);
|
||||
utils.invalidateQueries(['resumes.resume.user.findUserStarred']);
|
||||
utils.invalidateQueries(['resumes.resume.user.findUserCreated']);
|
||||
invalidateResumeQueries();
|
||||
},
|
||||
});
|
||||
const unstarMutation = trpc.useMutation('resumes.resume.unstar', {
|
||||
onSuccess() {
|
||||
utils.invalidateQueries(['resumes.resume.findOne']);
|
||||
utils.invalidateQueries(['resumes.resume.findAll']);
|
||||
utils.invalidateQueries(['resumes.resume.user.findUserStarred']);
|
||||
utils.invalidateQueries(['resumes.resume.user.findUserCreated']);
|
||||
invalidateResumeQueries();
|
||||
},
|
||||
});
|
||||
const resolveMutation = trpc.useMutation('resumes.resume.user.resolve', {
|
||||
onSuccess() {
|
||||
invalidateResumeQueries();
|
||||
},
|
||||
});
|
||||
|
||||
const invalidateResumeQueries = () => {
|
||||
utils.invalidateQueries(['resumes.resume.findOne']);
|
||||
utils.invalidateQueries(['resumes.resume.findAll']);
|
||||
utils.invalidateQueries(['resumes.resume.user.findUserStarred']);
|
||||
utils.invalidateQueries(['resumes.resume.user.findUserCreated']);
|
||||
};
|
||||
|
||||
const userIsOwner =
|
||||
session?.user?.id !== undefined &&
|
||||
session.user.id === detailsQuery.data?.userId;
|
||||
|
||||
const isResumeResolved = detailsQuery.data?.isResolved;
|
||||
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
const [showCommentsForm, setShowCommentsForm] = useState(false);
|
||||
|
||||
@ -139,6 +149,13 @@ export default function ResumeReviewPage() {
|
||||
setIsEditMode(true);
|
||||
};
|
||||
|
||||
const onResolveButtonClick = () => {
|
||||
resolveMutation.mutate({
|
||||
id: resumeId as string,
|
||||
val: !isResumeResolved,
|
||||
});
|
||||
};
|
||||
|
||||
const renderReviewButton = () => {
|
||||
if (session === null) {
|
||||
return (
|
||||
@ -175,10 +192,7 @@ export default function ResumeReviewPage() {
|
||||
url: detailsQuery.data.url,
|
||||
}}
|
||||
onClose={() => {
|
||||
utils.invalidateQueries(['resumes.resume.findOne']);
|
||||
utils.invalidateQueries(['resumes.resume.findAll']);
|
||||
utils.invalidateQueries(['resumes.resume.user.findUserStarred']);
|
||||
utils.invalidateQueries(['resumes.resume.user.findUserCreated']);
|
||||
invalidateResumeQueries();
|
||||
setIsEditMode(false);
|
||||
}}
|
||||
/>
|
||||
@ -206,17 +220,36 @@ export default function ResumeReviewPage() {
|
||||
</h1>
|
||||
<div className="flex gap-3 xl:pr-4">
|
||||
{userIsOwner && (
|
||||
<Button
|
||||
addonPosition="start"
|
||||
className="h-10 shadow-md"
|
||||
icon={PencilSquareIcon}
|
||||
label="Edit"
|
||||
variant="tertiary"
|
||||
onClick={onEditButtonClick}
|
||||
/>
|
||||
<>
|
||||
<Button
|
||||
addonPosition="start"
|
||||
className="h-10 shadow-md"
|
||||
icon={PencilSquareIcon}
|
||||
label="Edit"
|
||||
variant="tertiary"
|
||||
onClick={onEditButtonClick}
|
||||
/>
|
||||
<button
|
||||
className="isolate inline-flex h-10 items-center space-x-4 rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-md hover:bg-slate-50 focus:ring-slate-600 disabled:hover:bg-white"
|
||||
disabled={resolveMutation.isLoading}
|
||||
type="button"
|
||||
onClick={onResolveButtonClick}>
|
||||
<div className="-ml-1 mr-2 h-5 w-5">
|
||||
{resolveMutation.isLoading ? (
|
||||
<Spinner className="mt-0.5" size="xs" />
|
||||
) : (
|
||||
<CheckCircleIcon
|
||||
aria-hidden="true"
|
||||
className={isResumeResolved ? '' : 'text-slate-400'}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{isResumeResolved ? 'Resolved' : 'Resolve'}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
className="isolate inline-flex h-10 items-center space-x-4 rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-md hover:bg-slate-50 disabled:hover:bg-white"
|
||||
className="isolate inline-flex h-10 items-center space-x-4 rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-md hover:bg-slate-50 focus:ring-slate-600 disabled:hover:bg-white"
|
||||
disabled={starMutation.isLoading || unstarMutation.isLoading}
|
||||
type="button"
|
||||
onClick={onStarButtonClick}>
|
||||
|
@ -542,7 +542,7 @@ export default function ResumeHomePage() {
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className="lg:hidden"
|
||||
className="whitespace-pre-wrap px-2 lg:hidden"
|
||||
label="Submit Resume"
|
||||
variant="primary"
|
||||
onClick={onSubmitResume}
|
||||
|
@ -96,6 +96,7 @@ export const resumesRouter = createRouter()
|
||||
createdAt: r.createdAt,
|
||||
experience: r.experience,
|
||||
id: r.id,
|
||||
isResolved: r.isResolved,
|
||||
isStarredByUser: r.stars.length > 0,
|
||||
location: r.location,
|
||||
numComments: r._count.comments,
|
||||
|
@ -44,6 +44,23 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
||||
});
|
||||
},
|
||||
})
|
||||
.mutation('resolve', {
|
||||
input: z.object({
|
||||
id: z.string(),
|
||||
val: z.boolean(),
|
||||
}),
|
||||
async resolve({ ctx, input }) {
|
||||
const resume = await ctx.prisma.resumesResume.update({
|
||||
data: {
|
||||
isResolved: input.val,
|
||||
},
|
||||
where: {
|
||||
id: input.id,
|
||||
},
|
||||
});
|
||||
return resume.isResolved;
|
||||
},
|
||||
})
|
||||
.query('findUserStarred', {
|
||||
input: z.object({
|
||||
experienceFilters: z.string().array(),
|
||||
@ -147,6 +164,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
||||
createdAt: rs.resume.createdAt,
|
||||
experience: rs.resume.experience,
|
||||
id: rs.resume.id,
|
||||
isResolved: rs.resume.isResolved,
|
||||
isStarredByUser: true,
|
||||
location: rs.resume.location,
|
||||
numComments: rs.resume._count.comments,
|
||||
@ -250,6 +268,7 @@ export const resumesResumeUserRouter = createProtectedRouter()
|
||||
createdAt: r.createdAt,
|
||||
experience: r.experience,
|
||||
id: r.id,
|
||||
isResolved: r.isResolved,
|
||||
isStarredByUser: r.stars.length > 0,
|
||||
location: r.location,
|
||||
numComments: r._count.comments,
|
||||
|
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;
|
||||
experience: string;
|
||||
id: string;
|
||||
isResolved: boolean;
|
||||
isStarredByUser: boolean;
|
||||
location: string;
|
||||
numComments: number;
|
||||
|
@ -14,12 +14,9 @@ export type RoleFilter =
|
||||
|
||||
export type ExperienceFilter =
|
||||
| 'Entry Level (0 - 2 years)'
|
||||
| 'Freshman'
|
||||
| 'Junior'
|
||||
| 'Internship'
|
||||
| 'Mid Level (3 - 5 years)'
|
||||
| 'Senior Level (5+ years)'
|
||||
| 'Senior'
|
||||
| 'Sophomore';
|
||||
| 'Senior Level (5+ years)';
|
||||
|
||||
export type LocationFilter = 'India' | 'Singapore' | 'United States';
|
||||
|
||||
@ -79,10 +76,7 @@ export const ROLES: Array<FilterOption<RoleFilter>> = [
|
||||
];
|
||||
|
||||
export const EXPERIENCES: Array<FilterOption<ExperienceFilter>> = [
|
||||
{ label: 'Freshman', value: 'Freshman' },
|
||||
{ label: 'Sophomore', value: 'Sophomore' },
|
||||
{ label: 'Junior', value: 'Junior' },
|
||||
{ label: 'Senior', value: 'Senior' },
|
||||
{ label: 'Internship', value: 'Internship' },
|
||||
{
|
||||
label: 'Entry Level (0 - 2 years)',
|
||||
value: 'Entry Level (0 - 2 years)',
|
||||
@ -133,7 +127,7 @@ export const SHORTCUTS: Array<Shortcut> = [
|
||||
},
|
||||
{
|
||||
filters: INITIAL_FILTER_STATE,
|
||||
name: 'GOATs',
|
||||
name: 'Top 10',
|
||||
sortOrder: 'popular',
|
||||
},
|
||||
{
|
||||
|
Reference in New Issue
Block a user