diff --git a/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx b/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx index e7030b94a..19a4aebc2 100644 --- a/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx +++ b/apps/portal/src/app/features/certificates/pages/CertificatesPage.tsx @@ -7,6 +7,7 @@ import { Card, Divider, Group, + Loader, Modal, Paper, SimpleGrid, @@ -17,6 +18,7 @@ import { Title, rem, } from '@mantine/core'; +import { notifications } from '@mantine/notifications'; import { IconArrowRight, IconBook2, @@ -27,6 +29,8 @@ import { IconInfoCircle, IconShieldCheck, } from '@tabler/icons-react'; +import { authStorage } from '@ema-platform/auth'; +import { useAppSelector } from '../../../store/hooks'; // --------------------------------------------------------------------------- // Mock data @@ -65,18 +69,72 @@ const MOCK_CERTIFICATES = [ }, ]; -// blank PDF for demo -const BLANK_PDF = - 'data:application/pdf;base64,JVBERi0xLjQKJcfsj6IKMSAwIG9iago8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PgplbmRvYmoKMiAwIG9iago8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PgplbmRvYmoKMyAwIG9iago8PC9UeXBlL1BhZ2UvUGFyZW50IDIgMCBSL01lZGlhQm94WzAgMCA2MTIgNzkyXT4+CmVuZG9iagp4cmVmCjAgNAowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMDkgMDAwMDAgbiAKMDAwMDAwMDA1OCAwMDAwMCBuIAowMDAwMDAwMTE1IDAwMDAwIG4gCnRyYWlsZXIKPDwvU2l6ZSA0L1Jvb3QgMSAwIFI+PgpzdGFydHhyZWYKMjE3CiUlRU9G'; +const API_BASE = + (import.meta as { env?: Record }).env?.['VITE_BASE_API_URL'] ?? + 'http://localhost:3001/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/e04c4a7c-0feb-4af6-ab00-5ef6600ee2b4`, + { 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 user = useAppSelector((state) => state.auth.user); + const profileId = user?.id ?? ''; const [previewUrl, setPreviewUrl] = useState(null); const [previewTitle, setPreviewTitle] = useState(''); + const [loading, setLoading] = useState(false); - const openPreview = (title: string) => { - setPreviewTitle(title); - setPreviewUrl(BLANK_PDF); + const openPreview = async (profileId: string, title: string) => { + setLoading(true); + try { + const blob = await generateCertificate(profileId); + const url = URL.createObjectURL(blob); + setPreviewTitle(title); + setPreviewUrl(url); + } catch (err) { + notifications.show({ + color: 'red', + title: 'Error', + message: err instanceof Error ? err.message : 'Could not generate certificate', + }); + } finally { + setLoading(false); + } + }; + + const handleDownload = async (profileId: string, title: string) => { + try { + const blob = await generateCertificate(profileId); + downloadBlob(blob, `certificate-${Date.now()}.pdf`); + notifications.show({ + color: 'teal', + title: 'Downloaded', + message: 'Certificate PDF downloaded successfully', + }); + } catch (err) { + notifications.show({ + color: 'red', + title: 'Error', + message: err instanceof Error ? err.message : 'Could not download certificate', + }); + } }; return ( @@ -187,8 +245,8 @@ export function CertificatesPage() {
Expires{cert.expiry}
- - + + ))} @@ -201,12 +259,13 @@ export function CertificatesPage() { opened={!!previewUrl} onClose={() => setPreviewUrl(null)} title={{previewTitle}} - size="xl" + size="95vw" radius="lg" + fullScreen >