Files
internify-fe/src/Pages/internify/intern/components/CertificateAvailabe.tsx
T
2026-07-18 00:54:36 +07:00

194 lines
7.7 KiB
TypeScript

// CertificateAvailable.tsx
import { useState, useEffect } from "react";
import { Download, Link, Printer } from "lucide-react";
import type { ProjectDetail } from "../../../../types/project.types";
import type { Certificate } from "../../../../types/certificate.types";
import { downloadCertificatePdf, downloadCertificateImage } from "../../../utils/Certificates";
import { generateCertificate } from "../../../utils/SertificateGenerator";
import { resolveFileUrl } from "../../../utils/resolveFileFromUrl";
import { customToast } from "../../../utils/showToast";
interface CertificateAvailableProps {
project: ProjectDetail;
certificate: Certificate;
templateUrl: string;
}
function formatDateIndo(date: string): string {
return new Intl.DateTimeFormat("id-ID", {
day: "numeric",
month: "long",
year: "numeric",
}).format(new Date(date));
}
function formatDateRange(startDate: string, endDate: string): string {
return `${formatDateIndo(startDate)} - ${formatDateIndo(endDate)}`;
}
export default function CertificateAvailable({ project, certificate, templateUrl }: CertificateAvailableProps) {
const [downloadingImg, setDownloadingImg] = useState(false);
const [downloadingPdf, setDownloadingPdf] = useState(false);
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [loadingPreview, setLoadingPreview] = useState(true);
const duration = formatDateRange(project?.start_date, project?.end_date)
useEffect(() => {
if (!templateUrl) return;
const verifyUrl = `${window.location.origin}/verify-certificate/${certificate.uuid}`;
generateCertificate(
resolveFileUrl(templateUrl),
certificate.user.full_name,
certificate.project.project_name,
certificate.certificate_no,
duration,
verifyUrl,
)
.then((blob) => {
const url = URL.createObjectURL(blob);
setPreviewUrl(url);
})
.finally(() => setLoadingPreview(false));
return () => {
if (previewUrl) URL.revokeObjectURL(previewUrl);
};
}, [templateUrl, certificate.user.full_name]);
const handleDownloadImage = async () => {
setDownloadingImg(true);
try {
await downloadCertificateImage(certificate, templateUrl, "png");
} catch {
customToast.error("Gagal mengunduh sertifikat!", "Terjadi kendala saat mengunduh sertifikat!");
} finally {
setDownloadingImg(false);
}
};
const handleDownloadPdf = async () => {
setDownloadingPdf(true);
try {
await downloadCertificatePdf(certificate, templateUrl);
} catch {
customToast.error("Gagal mengunduh sertifikat!", "Terjadi kendala saat mengunduh sertifikat!");
} finally {
setDownloadingPdf(false);
}
};
const handleShareLinkedIn = () => {
const verifyUrl = `${window.location.origin}/verify-certificate/${certificate.uuid}`;
const params = new URLSearchParams({
startTask: "CERTIFICATION_NAME",
name: certificate.project.project_name,
organizationName: "Internify",
certUrl: verifyUrl,
certId: certificate.certificate_no,
});
window.open(
`https://www.linkedin.com/profile/add?${params.toString()}`,
"_blank",
"width=600,height=600,noopener,noreferrer"
);
};
return (
<div className="flex flex-col gap-4">
{/* Certificate Card */}
<div className="flex rounded-xl p-5 mb-1 border bg-box-primary border-box-border shadow-md items-center justify-center">
{loadingPreview ? (
<div className="w-[550px] h-[350px] flex items-center justify-center text-gray-400 text-sm">
Memuat sertifikat...
</div>
) : previewUrl ? (
<img
src={previewUrl}
alt="Certificate"
className="rounded-xl object-contain border-2"
/>
) : (
<div className="w-[550px] h-[350px] flex items-center justify-center text-gray-400 text-sm">
Gagal memuat sertifikat.
</div>
)}
</div>
{/* Program Summary */}
<div className="grid grid-cols-2 gap-5">
<div className="bg-white rounded-2xl border border-card-outline shadow-sm p-5">
<div className="flex items-center gap-2 mb-4">
<h3 className="font-semibold text-gray-800 text-sm">Ringkasan Program</h3>
</div>
<div className="space-y-3">
{[
{ label: "Nama Program", value: project?.project_name ?? "-" },
{ label: "Durasi", value: duration },
{ label: "No. Sertifikat", value: certificate.certificate_no ?? "-" },
].map((row) => (
<div
key={row.label}
className="flex justify-between items-center text-sm border-b border-card-outline pb-2 last:border-0"
>
<span className="text-font">{row.label}</span>
<span className="text-font-shade font-bold">{row.value}</span>
</div>
))}
</div>
</div>
<div className="bg-white rounded-2xl border border-card-outline shadow-sm p-5">
<div className="flex flex-col gap-3">
{/* Unduh Sertifikat */}
<button
onClick={handleDownloadPdf}
disabled={downloadingPdf}
className="flex items-center justify-center gap-2 w-full bg-red-700 hover:bg-red-800 active:scale-95 transition-all text-white font-semibold py-2 rounded-xl text-sm"
>
<Download className="w-4 h-4" />
{downloadingPdf ? "Memproses" : "Unduh Sertifikat"}
</button>
{/* Bagikan ke LinkedIn */}
<button
onClick={handleShareLinkedIn}
className="flex items-center justify-center gap-2 w-full bg-[#0A66C2] hover:bg-[#004182] active:scale-95 transition-all text-white font-semibold py-2 rounded-xl text-sm"
>
{/* LinkedIn icon */}
<svg className="w-4 h-4 fill-white" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
</svg>
Tambah ke Profil LinkedIn
</button>
{/* Cetak & Salin Link */}
<div className="grid grid-cols-2 gap-3">
<button
onClick={handleDownloadImage}
disabled={downloadingImg}
className="flex flex-col items-center justify-center gap-1.5 border border-card-outline hover:bg-gray-50 active:scale-95 transition-all text-red-700 font-medium py-3.5 rounded-xl text-sm">
<Printer className="w-5 h-5" />
{downloadingImg ? "Memproses" : "Cetak"}
</button>
<button
// onClick={handleCopyLink}
className="flex flex-col items-center justify-center gap-1.5 border border-card-outline hover:bg-gray-50 active:scale-95 transition-all text-red-700 font-medium py-3.5 rounded-xl text-sm"
onClick={() => {
navigator.clipboard.writeText(`${window.location.origin}/verify-certificate/${certificate.uuid}`);
customToast.success("Link berhasil disalin!");
}}
>
<Link className="w-5 h-5" />
Salin Link
</button>
</div>
</div>
</div>
</div>
</div>
);
}