Skip to content

Commit d74bbe6

Browse files
Zeff01claude
andcommitted
feat: improve codevs management and certificate system
- Add individual project tabs showing all 19 Codebility projects - Remove inactive members from main codevs view - Implement active/inactive tabs in both codevs and in-house views - Fix pagination visibility on last page - Create certificate preview page with point-based restrictions - Update certificate designs to match roadmap (Intern 100pts, Codev 200pts, Mentor 300pts) - Fix dark mode colors throughout the UI - Add progress tracking for certificate unlock (100 points required) - Make certificate icon always clickable with restrictions shown on preview page 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 94445e4 commit d74bbe6

10 files changed

Lines changed: 835 additions & 209 deletions

File tree

‎apps/codebility/app/home/(dashboard)/_components/DashboardCertificate.tsx‎

Lines changed: 25 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,38 @@
1-
import { useRef } from "react";
2-
import Image from "next/image";
1+
import { useRouter } from "next/navigation";
32
import { useUserStore } from "@/store/codev-store";
4-
import { BookDown } from "lucide-react";
5-
import certificateSvg from "@/public/assets/ssvgs/certificate.svg";
6-
7-
import Certificate, {
8-
CertificateProps,
9-
handleDownload,
10-
} from "./DashboardDownloadCertificate";
113
import { cn } from "@codevs/ui";
124
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@codevs/ui/tooltip";
135
import { CertificateIcon, CertificateOffIcon } from "./DashboardCertificateIcons";
146
import { Button } from "@codevs/ui/button";
157

16-
function hasLevelTwoBadge(level: Record<string, number> | undefined): boolean {
17-
const validLevels = Object.entries(level!).filter(
18-
([, levelValue]) => levelValue > 0,
19-
);
20-
21-
return validLevels.some(([, levelValue]) => levelValue >= 2);
22-
}
23-
248
const DashboardCertificate = () => {
9+
const router = useRouter();
2510
const { user, userLevel } = useUserStore();
2611

27-
// Check certificate type based on user
28-
let certificateType = "intern";
29-
// if (user?.role_id == 4) certificateType = "intern";
30-
if (user?.role_id == 10) {
31-
if (userLevel == 2) certificateType = "codev 2";
32-
else certificateType = "codev 1";
33-
}
34-
if (user?.role_id == 5) certificateType = "mentor";
35-
36-
const certRef = useRef<HTMLDivElement>(null);
37-
const fullName = `${user!.first_name} ${user!.last_name}`;
38-
39-
const certData: CertificateProps = (() => {
40-
switch (certificateType) {
41-
case "codev 1":
42-
return {
43-
title: "PROMOTION",
44-
name: fullName || "Codev",
45-
mainSentence: (
46-
<p className="text-sm">
47-
has been promoted to <strong>Codev – Level 1</strong> at
48-
<b> Codebility </b>
49-
</p>
50-
),
51-
description1: (
52-
<p>
53-
The recipient has earned this position by meeting the required
54-
technical standards, exhibiting reliability, and showing readiness
55-
to contribute in a full development role.
56-
</p>
57-
),
58-
};
59-
case "codev 2":
60-
return {
61-
title: "PROMOTION",
62-
name: fullName || "Codev",
63-
mainSentence: (
64-
<p className="text-sm">
65-
has been promoted to <strong>Codev – Level 2</strong> at
66-
<b> Codebility </b>
67-
</p>
68-
),
69-
description1: (
70-
<p>
71-
The recipient has achieved this advancement through consistent
72-
delivery on development tasks, increased technical proficiency,
73-
and active participation in team efforts.
74-
</p>
75-
),
76-
};
77-
case "mentor":
78-
return {
79-
title: "PROMOTION",
80-
name: fullName || "Mentor",
81-
mainSentence: (
82-
<p className="text-sm">
83-
has been promoted to <strong>Mentor</strong> at
84-
<b> Codebility </b>
85-
</p>
86-
),
87-
description1: (
88-
<p>
89-
Recognized for leadership, technical guidance, and contributions
90-
to the development of peers within the organization, reflecting a
91-
strong commitment to mentorship and team growth.
92-
</p>
93-
),
94-
};
95-
// Intern
96-
default:
97-
return {
98-
title: "COMPLETION",
99-
name: fullName || "Intern",
100-
mainSentence: (
101-
<p className="text-sm">
102-
has successfully completed the{" "}
103-
<strong>Internship Training Program</strong> at
104-
<b> Codebility </b>
105-
</p>
106-
),
107-
description1: (
108-
<p>
109-
The recipient has demonstrated a foundational understanding of
110-
software development practices, a commitment to learning, and the
111-
ability to collaborate effectively within a development team.
112-
</p>
113-
),
114-
};
115-
}
116-
})();
117-
118-
const isCertificateUnlocked = hasLevelTwoBadge(user?.level);
119-
12012
return (
121-
<>
122-
<div className="absolute left-4 top-4 z-10">
123-
<div
124-
style={{
125-
position: "absolute",
126-
left: "-9999px",
127-
top: 0,
128-
width: "1000px",
129-
height: "700px",
130-
overflow: "hidden",
131-
visibility: "hidden",
132-
pointerEvents: "none",
133-
}}
134-
aria-hidden="true"
135-
>
136-
<Certificate {...certData} ref={certRef} />
137-
</div>
138-
<Tooltip>
139-
<TooltipTrigger asChild>
140-
<Button
141-
size="icon"
142-
variant="ghost"
143-
className={cn(
144-
"rounded-full inline-flex items-center justify-center",
145-
"transition-colors duration-200",
146-
isCertificateUnlocked
147-
? "hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer"
148-
: "cursor-not-allowed hover:bg-gray-50 dark:hover:bg-gray-900"
149-
)}
150-
onClick={() => isCertificateUnlocked && handleDownload(certRef, certData.name)}
151-
>
152-
{isCertificateUnlocked ? (
153-
<CertificateIcon className="h-6 w-6 text-[#9747FF]" />
154-
) : (
155-
<CertificateOffIcon className="h-6 w-6 opacity-50" />
156-
)}
157-
</Button>
158-
</TooltipTrigger>
159-
<TooltipContent side="bottom" align="start">
160-
{isCertificateUnlocked
161-
? "Download your certificate"
162-
: "Reach level 2 to unlock the certificate"}
163-
</TooltipContent>
164-
</Tooltip>
165-
</div>
166-
</>
13+
<div className="absolute left-4 top-4 z-10">
14+
<Tooltip>
15+
<TooltipTrigger asChild>
16+
<Button
17+
size="icon"
18+
variant="ghost"
19+
className={cn(
20+
"rounded-full inline-flex items-center justify-center",
21+
"transition-colors duration-200",
22+
"hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer"
23+
)}
24+
onClick={() => {
25+
router.push("/home/certificate-preview");
26+
}}
27+
>
28+
<CertificateIcon className="h-6 w-6 text-[#9747FF]" />
29+
</Button>
30+
</TooltipTrigger>
31+
<TooltipContent side="bottom" align="start">
32+
View your certificate
33+
</TooltipContent>
34+
</Tooltip>
35+
</div>
16736
);
16837
};
16938

‎apps/codebility/app/home/(dashboard)/_components/DashboardDownloadCertificate.tsx‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,16 @@ export const handleDownload = async (
1212
) => {
1313
if (!certRef.current) return;
1414

15+
// Wait a bit to ensure images are loaded
16+
await new Promise(resolve => setTimeout(resolve, 500));
17+
1518
const canvas = await html2canvas(certRef.current, {
1619
useCORS: true,
1720
scale: 2,
1821
backgroundColor: null,
22+
logging: true, // Enable logging to debug
23+
allowTaint: true, // Allow tainted images
24+
foreignObjectRendering: true, // Better SVG/CSS rendering
1925
});
2026

2127
const imgData = canvas.toDataURL("image/png");
@@ -27,7 +33,7 @@ export const handleDownload = async (
2733
});
2834

2935
pdf.addImage(imgData, "PNG", 0, 0, canvas.width, canvas.height);
30-
pdf.save(`${name}.pdf`);
36+
pdf.save(`${name}-certificate.pdf`);
3137
};
3238

3339
export interface CertificateProps {
@@ -48,16 +54,19 @@ const Certificate = forwardRef<HTMLDivElement, CertificateProps>(
4854
return (
4955
<div
5056
ref={ref}
51-
className="relative h-[700px] w-[1000px] overflow-hidden border font-sans text-white shadow-md"
57+
className="relative h-[700px] w-[1000px] overflow-hidden font-sans text-white"
5258
style={{
5359
backgroundImage: `url(${background})`,
5460
backgroundSize: "cover",
5561
backgroundPosition: "center",
62+
backgroundColor: "transparent",
5663
}}
5764
>
5865
<div className="flex h-full w-full flex-col justify-between px-40 py-10 text-center">
5966
<div>
60-
<img className="mx-auto w-60" src={logo} alt="logo" />
67+
<div className="mx-auto w-60 h-[80px] relative">
68+
<img className="mx-auto w-60" src={logo} alt="logo" />
69+
</div>
6170
<h1 className="mt-3 leading-tight">
6271
<span className="my-5 text-5xl font-bold">CERTIFICATE</span>
6372
</h1>

0 commit comments

Comments
 (0)