import { FC, ReactElement, useState, useEffect, useRef } from 'react'; import { useParams, useNavigate } from 'react-router'; import { Button } from '@imphnen-frontend-service/ui/atoms'; import { decodeCertificateId } from '../../../utils/certificate'; import { useCertificatePublicData, useAuthStore, } from '@imphnen-frontend-service/service'; import QRCode from 'qrcode'; import html2canvas from 'html2canvas'; interface DecodedCert { teamId: string; submissionId: string; userId: string; } const CertificatePage: FC = (): ReactElement => { const { certId } = useParams<{ certId: string }>(); const navigate = useNavigate(); const { session } = useAuthStore(); const [decodedInfo, setDecodedInfo] = useState(null); const [error, setError] = useState(null); const teamNameRef = useRef(null); const userNameRef = useRef(null); const [teamNameFontSize, setTeamNameFontSize] = useState('2.25rem'); const [userNameFontSize, setUserNameFontSize] = useState('2.25rem'); const [qrCodeUrl, setQrCodeUrl] = useState(''); const certificateRef = useRef(null); const [isGenerating, setIsGenerating] = useState(false); const [certificateImage, setCertificateImage] = useState(''); const [showTemplate, setShowTemplate] = useState(true); useEffect(() => { if (certId) { decodeCertificateId(certId) .then(setDecodedInfo) .catch(() => { setError('Invalid certificate ID'); }); } }, [certId]); // Fetch certificate data using the new endpoint const { data: certificateData, isLoading: isLoadingCertificate } = useCertificatePublicData(decodedInfo?.userId || '', !!decodedInfo?.userId); // Generate QR Code useEffect(() => { if (certId) { // Use encodeURIComponent to properly encode the certId for the URL const encodedCertId = encodeURIComponent(certId); const certificateUrl = `${window.location.origin}/certificate/${encodedCertId}`; QRCode.toDataURL(certificateUrl, { width: 200, margin: 1, color: { dark: '#000000', light: '#ffffff', }, }) .then(setQrCodeUrl) .catch((err) => console.error('QR Code generation failed:', err)); } }, [certId]); const certificate = certificateData?.data; const team = certificate?.team; const submission = certificate?.submission; const certificateUser = certificate?.user; const isLoading = (!decodedInfo && !error) || isLoadingCertificate; // Certificate name from the user data const certificateName = certificateUser?.fullname; // Check if current user is viewing their own certificate (team member) const isTeamMember = session?.user?.id === decodedInfo?.userId; // Dynamic font sizing: shrink by 2px if height exceeds 80px useEffect(() => { const adjustFontSize = ( element: HTMLElement | null, maxHeight: number, startSize: number, setter: (size: string) => void ) => { if (!element) return; let currentSize = startSize; element.style.fontSize = `${currentSize}px`; while (element.offsetHeight > maxHeight && currentSize > 1) { currentSize -= 2; element.style.fontSize = `${currentSize}px`; } setter(`${currentSize}px`); }; const timer = setTimeout(() => { adjustFontSize(teamNameRef.current, 80, 20, setTeamNameFontSize); adjustFontSize(userNameRef.current, 80, 36, setUserNameFontSize); }, 0); return () => clearTimeout(timer); }, [team?.name, certificateName]); // Generate certificate canvas screenshot useEffect(() => { const generateCertificate = async () => { if (!certificateRef.current || !team || !submission || !qrCodeUrl) return; setIsGenerating(true); try { // Wait longer for fonts and images to load properly await new Promise((resolve) => setTimeout(resolve, 1500)); const canvas = await html2canvas(certificateRef.current, { scale: 4, useCORS: true, backgroundColor: '#ffffff', logging: false, width: 1000, height: (1000 * 2480) / 3508, allowTaint: true, imageTimeout: 0, removeContainer: true, }); const imageUrl = canvas.toDataURL('image/png', 1.0); setCertificateImage(imageUrl); setShowTemplate(false); } catch (error) { console.error('Failed to generate certificate:', error); } finally { setIsGenerating(false); } }; generateCertificate(); }, [team, submission, qrCodeUrl]); // Download certificate const handleDownloadCertificate = () => { if (!certificateImage) return; const link = document.createElement('a'); link.href = certificateImage; link.download = `certificate-${team?.name || 'hackathon'}.png`; link.click(); }; // Print certificate const handlePrintCertificate = () => { if (!certificateImage) return; const printWindow = window.open('', '_blank'); if (printWindow) { printWindow.document.write(` Certificate - ${team?.name} `); printWindow.document.close(); printWindow.onload = () => { printWindow.print(); }; } }; if (error || !certId) { return (

Invalid Certificate

{error || 'The certificate ID is invalid or malformed.'}

); } if (isLoading) { return (
Loading certificate...
); } if (!certificateUser) { return (
📄

Certificate Not Found

The user associated with this certificate could not be found.

); } return (
{/* Print Styles */} {/* Header */}

Certificate

{team?.name}

{team && isTeamMember && ( )}
{/* Certificate Content */}
{/* Hidden Template for Canvas Generation */}
{/* Team Name - positioned in middle between "Diberikan Kepada" and "Telah Berpartisipasi" */}

{team?.name}

{/* User Name - positioned below team name */}

{certificateName || 'N/A'}

{/* QR Code - positioned in the white box area */}
{qrCodeUrl && ( Certificate QR Code )}
{/* Display Certificate Image */}
{isGenerating && (
Generating certificate...
)} {certificateImage && !isGenerating && ( Certificate )} {/* Actions */} {isTeamMember && (
{team && ( )}
)}
{/* Info Box */}

Certificate Information

This certificate is a digital record of your hackathon participation and project submission. You can print or save this page as a PDF for your records.

); }; export default CertificatePage;