diff --git a/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx b/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx index b7fc93326..6ab70f6f7 100644 --- a/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx +++ b/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx @@ -30,13 +30,14 @@ import { IconShieldCheck, IconTrash, } from '@tabler/icons-react'; -import { authStorage, useCurrentProfile } from '@ema-platform/auth'; +import { useCurrentProfile } from '@ema-platform/auth'; import { extractErrorMessage, useApiQuery, useBypassPaymentMutation, useDiscardApplicationMutation, useGetLicenseTypesQuery, + useGetCertificateUrlMutation, useGetPaymentCapabilitiesQuery, type LicenseType, } from '@ema-platform/api'; @@ -56,6 +57,9 @@ import type { MyRegistration } from '../../exams/pages/ExamsPage'; interface CertificatesOverview { certificates: { id: string; + // The licence's own id — what `/licenses/:id/certificate` takes. Distinct + // from `id` above, which is the human-readable certificate number. + licenseId: string; type: string; issued: string; expiry: string; @@ -184,7 +188,6 @@ function validityLabel(type: LicenseType): string { export function CertificatesPage() { const navigate = useNavigate(); - const profileId = authStorage.getProfileId() ?? ''; const [previewUrl, setPreviewUrl] = useState(null); const [previewTitle, setPreviewTitle] = useState(''); const [loading, setLoading] = useState(false); @@ -194,6 +197,7 @@ export function CertificatesPage() { const { data: capabilities } = useGetPaymentCapabilitiesQuery(); const [bypassPayment, { isLoading: bypassing }] = useBypassPaymentMutation(); const [discardApplication, { isLoading: discarding }] = useDiscardApplicationMutation(); + const [getCertificateUrl] = useGetCertificateUrlMutation(); const [discardTarget, setDiscardTarget] = useState<{ id: string; applicationId: string } | null>(null); const { data, refetch } = useApiQuery({ @@ -307,38 +311,36 @@ export function CertificatesPage() { !hasVerifiedMedical && 'No verified medical certificate on file.', ].filter((r): r is string => Boolean(r)); - const openPreview = async (profileId: string, title: string) => { + // Same endpoint the main dashboard downloads from + // (DashboardPage's `downloadCertificate`): the PDF this returns is + // rendered from whatever design is published for the licence's type/rank + // in the Certificate Designer, not a generic profile-only certificate. + const openPreview = async (licenseId: string, title: string) => { setLoading(true); try { - const blob = await generateCertificate(profileId); - const url = URL.createObjectURL(blob); + const result = await getCertificateUrl(licenseId).unwrap(); setPreviewTitle(title); - setPreviewUrl(url); + setPreviewUrl(result.url); } catch (err) { notifications.show({ color: 'red', title: 'Error', - message: err instanceof Error ? err.message : 'Could not generate certificate', + message: extractErrorMessage(err) || 'Could not generate certificate', }); } finally { setLoading(false); } }; - const handleDownload = async (profileId: string, title: string) => { + const handleDownload = async (licenseId: string) => { try { - const blob = await generateCertificate(profileId); - downloadBlob(blob, `certificate-${Date.now()}.pdf`); - notifications.show({ - color: 'teal', - title: 'Downloaded', - message: 'Certificate PDF downloaded successfully', - }); + const result = await getCertificateUrl(licenseId).unwrap(); + window.open(result.url, '_blank', 'noopener'); } catch (err) { notifications.show({ color: 'red', title: 'Error', - message: err instanceof Error ? err.message : 'Could not download certificate', + message: extractErrorMessage(err) || 'Could not download certificate', }); } }; @@ -532,8 +534,8 @@ export function CertificatesPage() {
Expires{cert.expiry}
- - + + ))}