Files
starr-philproperties/src/modules/client/pages/MyCertificates.jsx
T
kennethobsequio bac7168b1e push
pushy

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
2026-06-28 11:30:01 +08:00

134 lines
5.5 KiB
React

import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Award, Download, LockIcon, ArrowLeft, BadgeCheck } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Skeleton } from "@/components/ui/skeleton";
import { useProfile } from "@/contexts/ProfileProvider";
import { useDateFormat } from "@/hooks/useDateFormat";
import api from "@/utils/api.util";
import { toast } from "sonner";
const CertBadgeIcon = ({ className }) => (
<svg className={className} width="120" height="120" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Prism logo">
<defs>
<linearGradient id="prism-mc" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stopColor="#8EA2F6"/>
<stop offset="1" stopColor="#5061E6"/>
</linearGradient>
</defs>
<g transform="rotate(45 60 60)">
<rect x="24" y="24" width="72" height="72" rx="16" fill="url(#prism-mc)"/>
<rect x="46" y="46" width="28" height="28" rx="6" fill="#FFFFFF"/>
</g>
</svg>
);
const CertificateCard = ({ courseTitle, issuedAt, courseUuid }) => {
const [downloading, setDownloading] = useState(false);
const { fmtDate } = useDateFormat();
const issuedLabel = issuedAt ? fmtDate(issuedAt) : "—";
const handleDownload = async () => {
setDownloading(true);
try {
const res = await api.get(`/client/certificates/${courseUuid}`, { responseType: "blob" });
const disposition = res.headers["content-disposition"] ?? "";
const match = disposition.match(/filename="([^"]+)"/);
const filename = match ? match[1] : `certificate-${courseUuid}.pdf`;
const url = URL.createObjectURL(new Blob([res.data], { type: "application/pdf" }));
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
} catch {
toast.error("Could not download certificate.");
} finally {
setDownloading(false);
}
};
return (
<div className="rounded-2xl border bg-card p-6 flex flex-col items-center gap-4 shadow-sm">
<CertBadgeIcon className="w-[160px] h-[160px]" />
<p className="text-lg font-bold text-center leading-snug">Certificate of Completion</p>
<div className="w-full rounded-lg border px-3 py-2.5">
<p className="text-[10px] uppercase tracking-widest text-muted-foreground font-semibold">Course</p>
<p className="text-sm font-medium mt-1 line-clamp-2">{courseTitle}</p>
</div>
<div className="w-full flex items-end justify-between">
<div>
<p className="text-[10px] uppercase tracking-widest text-muted-foreground font-semibold">Issued</p>
<p className="text-sm font-medium">{issuedLabel}</p>
</div>
<Badge className="bg-green-100 text-green-700 border-green-400 dark:bg-green-900/40 dark:text-green-400 dark:border-green-700 gap-1">
<BadgeCheck className="size-3" /> Verified
</Badge>
</div>
<Button size="sm" variant="outline" className="w-full" onClick={handleDownload} disabled={downloading}>
<Download className="size-3.5" />
{downloading ? "Downloading…" : "Download PDF"}
</Button>
</div>
);
};
export default function MyCertificates() {
const navigate = useNavigate();
const { achievements, achievementsLoading, getAchievements } = useProfile();
useEffect(() => { getAchievements(); }, []);
const certAchievements = achievements.filter((a) => a.key.startsWith("course_completed_"));
return (
<section className="mt-17 bg-muted min-h-full">
<div className="p-6 lg:container lg:max-w-3xl lg:mx-auto space-y-5">
<div className="flex items-center gap-3">
<Button type="button" variant="ghost" size="icon" onClick={() => navigate(-1)}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div>
<div className="flex items-center gap-2">
<h1 className="text-xl font-semibold">My Certificates</h1>
<Badge variant="outline" className="gap-1 text-xs">
<LockIcon className="h-3 w-3" /> Only you
</Badge>
</div>
<p className="text-sm text-muted-foreground">Certificates earned from completed courses.</p>
</div>
</div>
{achievementsLoading ? (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{[...Array(2)].map((_, i) => (
<Skeleton key={i} className="h-80 rounded-2xl" />
))}
</div>
) : certAchievements.length === 0 ? (
<div className="flex flex-col items-center justify-center py-24 text-center">
<Award className="h-10 w-10 text-muted-foreground/40 mb-3" />
<p className="text-sm font-medium">No certificates yet</p>
<p className="text-xs text-muted-foreground">Pass a course assessment to earn your first certificate.</p>
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{certAchievements.map((cert) => (
<CertificateCard
key={cert.achievement_id}
courseTitle={cert.description}
issuedAt={cert.granted_at}
courseUuid={cert.metadata?.courseUuid ?? cert.key.replace("course_completed_", "")}
/>
))}
</div>
)}
</div>
</section>
);
}