Skip to content

Commit 9ea60ac

Browse files
committed
feat: implement resume duplication
1 parent f6f1b20 commit 9ea60ac

2 files changed

Lines changed: 88 additions & 66 deletions

File tree

src/app/app/dashboard/resumes/ResumeCardItem.tsx

Lines changed: 70 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import ResumeTemplateComponent from "@/components/templates";
2222
import { DEFAULT_TEMPLATES } from "@/config";
2323
import { cn } from "@/lib/utils";
2424
import { normalizeFontFamily } from "@/utils/fonts";
25+
import { Edit2, Copy, Trash2 } from "lucide-react";
2526

2627
interface ResumeCardItemProps {
2728
id: string;
@@ -31,6 +32,7 @@ interface ResumeCardItemProps {
3132
setActiveResume: (id: string) => void;
3233
router: any;
3334
deleteResume: (resume: any) => void;
35+
duplicateResume: (resume: any) => void;
3436
index: number;
3537
}
3638

@@ -42,10 +44,13 @@ export const ResumeCardItem = ({
4244
setActiveResume,
4345
router,
4446
deleteResume,
47+
duplicateResume,
4548
index,
4649
}: ResumeCardItemProps) => {
4750
const containerRef = React.useRef<HTMLDivElement>(null);
4851
const [scale, setScale] = React.useState(0.24);
52+
const [showDeleteDialog, setShowDeleteDialog] = React.useState(false);
53+
4954
const activeTemplate =
5055
DEFAULT_TEMPLATES.find((template) => template.id === resume.templateId) ??
5156
DEFAULT_TEMPLATES[0];
@@ -83,7 +88,14 @@ export const ResumeCardItem = ({
8388
"dark:hover:border-primary/40"
8489
)}
8590
>
86-
<CardContent className="p-0 flex-1 relative bg-gray-50 dark:bg-gray-900 overflow-hidden cursor-pointer">
91+
<CardContent
92+
className="p-0 flex-1 relative bg-gray-50 dark:bg-gray-900 overflow-hidden cursor-pointer"
93+
onClick={(e) => {
94+
e.stopPropagation();
95+
setActiveResume(id);
96+
router.push(`/app/workbench/${id}`);
97+
}}
98+
>
8799
<div className="absolute inset-0 pb-6 flex items-center justify-center pointer-events-none transition-transform duration-300 group-hover:scale-[1.02] overflow-hidden" ref={containerRef}>
88100
<div className="w-full h-full relative origin-top bg-white">
89101
<div
@@ -121,79 +133,71 @@ export const ResumeCardItem = ({
121133
</div>
122134
</div>
123135
</CardContent>
124-
<CardFooter className="pt-2 pb-2 px-2 bg-white dark:bg-gray-950 border-t border-gray-100 dark:border-gray-800 z-10">
125-
<div className="grid grid-cols-2 gap-2 w-full">
126-
<motion.div
127-
whileHover={{ scale: 1.05 }}
128-
whileTap={{ scale: 0.95 }}
129-
transition={{
130-
type: "spring",
131-
stiffness: 400,
132-
damping: 17,
136+
<CardFooter className="p-0 border-t border-gray-100 dark:border-gray-800 bg-gray-50/50 dark:bg-gray-900/30 overflow-hidden">
137+
<div className="flex w-full h-11 divide-x divide-gray-100 dark:divide-gray-800">
138+
<button
139+
onClick={(e) => {
140+
e.stopPropagation();
141+
setActiveResume(id);
142+
router.push(`/app/workbench/${id}`);
133143
}}
144+
className="flex-1 flex items-center justify-center gap-1.5 hover:bg-white dark:hover:bg-gray-800/80 transition-all duration-200 text-gray-700 dark:text-gray-200 hover:text-primary font-medium text-sm group"
134145
>
135-
<Button
136-
variant="outline"
137-
className="w-full text-sm hover:bg-gray-100 dark:border-primary/50 dark:hover:bg-primary/10"
138-
size="sm"
139-
onClick={(e) => {
140-
e.stopPropagation();
141-
setActiveResume(id);
142-
router.push(`/app/workbench/${id}`);
143-
}}
144-
>
145-
{t("common.edit")}
146-
</Button>
147-
</motion.div>
146+
<Edit2 className="w-3.5 h-3.5 group-hover:scale-110 transition-transform opacity-70 group-hover:opacity-100" />
147+
<span>{t("common.edit")}</span>
148+
</button>
149+
150+
<button
151+
onClick={(e) => {
152+
e.stopPropagation();
153+
duplicateResume(resume);
154+
}}
155+
className="flex-1 flex items-center justify-center gap-1.5 hover:bg-white dark:hover:bg-gray-800/80 transition-all duration-200 text-gray-700 dark:text-gray-200 hover:text-blue-600 dark:hover:text-blue-400 font-medium text-sm group"
156+
>
157+
<Copy className="w-3.5 h-3.5 group-hover:scale-110 transition-transform opacity-70 group-hover:opacity-100" />
158+
<span>{t("common.copy")}</span>
159+
</button>
148160

149-
<motion.div
150-
whileHover={{ scale: 1.05 }}
151-
whileTap={{ scale: 0.95 }}
152-
transition={{
153-
type: "spring",
154-
stiffness: 400,
155-
damping: 17,
161+
<button
162+
onClick={(e) => {
163+
e.stopPropagation();
164+
setShowDeleteDialog(true);
156165
}}
166+
className="flex-1 flex items-center justify-center gap-1.5 hover:bg-red-50 dark:hover:bg-red-950/40 transition-all duration-200 text-red-600 dark:text-red-400 font-medium text-sm group"
157167
>
158-
<AlertDialog>
159-
<AlertDialogTrigger asChild>
160-
<Button
161-
variant="outline"
162-
className="w-full text-sm text-red-600 hover:bg-red-50 hover:text-red-700 dark:text-red-500 dark:hover:bg-red-950/50 dark:hover:text-red-400"
163-
size="sm"
164-
onClick={(e) => {
165-
e.stopPropagation();
166-
}}
167-
>
168-
{t("common.delete")}
169-
</Button>
170-
</AlertDialogTrigger>
171-
<AlertDialogContent onClick={(e) => e.stopPropagation()}>
172-
<AlertDialogHeader>
173-
<AlertDialogTitle>{t("dashboard.resumes.deleteConfirmTitle")}</AlertDialogTitle>
174-
<AlertDialogDescription>
175-
{t("dashboard.resumes.deleteConfirmDescription")}
176-
</AlertDialogDescription>
177-
</AlertDialogHeader>
178-
<AlertDialogFooter>
179-
<AlertDialogCancel onClick={(e) => e.stopPropagation()}>{t("common.cancel")}</AlertDialogCancel>
180-
<AlertDialogAction
181-
className="bg-red-600 hover:bg-red-700 text-white focus:ring-red-600 border-none"
182-
onClick={(e) => {
183-
e.stopPropagation();
184-
deleteResume(resume);
185-
toast.success(t("common.deleteSuccess"));
186-
}}
187-
>
188-
{t("common.confirm")}
189-
</AlertDialogAction>
190-
</AlertDialogFooter>
191-
</AlertDialogContent>
192-
</AlertDialog>
193-
</motion.div>
168+
<Trash2 className="w-3.5 h-3.5 group-hover:scale-110 transition-transform opacity-80 group-hover:opacity-100" />
169+
<span>{t("common.delete")}</span>
170+
</button>
194171
</div>
195172
</CardFooter>
196173
</Card>
174+
175+
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
176+
<AlertDialogContent onClick={(e) => e.stopPropagation()}>
177+
<AlertDialogHeader>
178+
<AlertDialogTitle>{t("dashboard.resumes.deleteConfirmTitle")}</AlertDialogTitle>
179+
<AlertDialogDescription>
180+
{t("dashboard.resumes.deleteConfirmDescription")}
181+
</AlertDialogDescription>
182+
</AlertDialogHeader>
183+
<AlertDialogFooter>
184+
<AlertDialogCancel onClick={(e) => { e.stopPropagation(); setShowDeleteDialog(false); }}>
185+
{t("common.cancel")}
186+
</AlertDialogCancel>
187+
<AlertDialogAction
188+
className="bg-red-600 hover:bg-red-700 text-white focus:ring-red-600 border-none"
189+
onClick={(e) => {
190+
e.stopPropagation();
191+
deleteResume(resume);
192+
setShowDeleteDialog(false);
193+
toast.success(t("common.deleteSuccess"));
194+
}}
195+
>
196+
{t("common.confirm")}
197+
</AlertDialogAction>
198+
</AlertDialogFooter>
199+
</AlertDialogContent>
200+
</AlertDialog>
197201
</motion.div>
198202
);
199203
};

src/app/app/dashboard/resumes/ResumeWorkbench.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,23 @@ export const ResumeWorkbench = () => {
103103
router.push(`/app/workbench/${newId}`);
104104
};
105105

106+
const duplicateResume = async (resume: any) => {
107+
const { generateUUID } = await import("@/utils/uuid");
108+
const now = new Date().toISOString();
109+
110+
const { id, ...rest } = resume;
111+
const newResume = {
112+
...rest,
113+
id: generateUUID(),
114+
title: `${resume.title || t("dashboard.resumes.untitled")} - ${t("common.copy")}`,
115+
createdAt: now,
116+
updatedAt: now,
117+
};
118+
119+
const resumeId = addResume(newResume);
120+
toast.success(t("previewDock.copyResume.success"));
121+
};
122+
106123
const importResumeFromJson = async (file: File) => {
107124
const content = await file.text();
108125
const config = JSON.parse(content);
@@ -404,6 +421,7 @@ export const ResumeWorkbench = () => {
404421
setActiveResume={setActiveResume}
405422
router={router}
406423
deleteResume={deleteResume}
424+
duplicateResume={duplicateResume}
407425
index={index}
408426
/>
409427
))}

0 commit comments

Comments
 (0)