diff --git a/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx b/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx index 360f5ef2d..f96b3eec5 100644 --- a/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx +++ b/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx @@ -30,12 +30,13 @@ 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, + useGetCertificateUrlMutation, useGetPaymentCapabilitiesQuery, } from '@ema-platform/api'; import { @@ -54,6 +55,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; @@ -141,31 +145,8 @@ function formatDate(value: string | null | undefined): string { }); } -import { BASE_API_URL as API_BASE } from '@ema-platform/api'; - -async function generateCertificate(profileId: string): Promise { - const token = authStorage.getToken(); - if (!token) throw new Error('No auth token found'); - const res = await fetch( - `${API_BASE}/profiles/generate-seafarer-certificate/${profileId}`, - { headers: { Authorization: `Bearer ${token}` } }, - ); - if (!res.ok) throw new Error(`Failed to generate certificate (${res.status})`); - return res.blob(); -} - -function downloadBlob(blob: Blob, filename: string) { - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = filename; - a.click(); - URL.revokeObjectURL(url); -} - export function CertificatesPage() { const navigate = useNavigate(); - const profileId = authStorage.getProfileId() ?? ''; const [previewUrl, setPreviewUrl] = useState(null); const [previewTitle, setPreviewTitle] = useState(''); const [loading, setLoading] = useState(false); @@ -175,6 +156,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({ @@ -261,38 +243,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', }); } }; @@ -489,8 +469,8 @@ export function CertificatesPage() {
Expires{cert.expiry}
- - + + ))}