import { type AttachmentHandle, downloadAndDecryptAttachment } from '@chat-app/shared/chat'; import { useEffect, useState } from 'react'; import { supabase } from '../lib/supabase'; import { AlertIcon, SpinnerIcon } from './icons'; interface Props { handle: AttachmentHandle; } // PDF preview rendered via the browser's built-in PDF viewer (Chromium / // Safari both ship one). Embedding via with a fallback link keeps // the implementation tiny — no pdf.js dependency. export function AttachmentPdf({ handle }: Props) { const [blobUrl, setBlobUrl] = useState(null); const [error, setError] = useState(null); const [expanded, setExpanded] = useState(false); useEffect(() => { let cancelled = false; let url: string | null = null; setError(null); setBlobUrl(null); downloadAndDecryptAttachment({ client: supabase, handle }) .then((blob) => { if (cancelled) return; // Force the application/pdf type so the browser plugin engages. const typed = new Blob([blob], { type: 'application/pdf' }); url = URL.createObjectURL(typed); setBlobUrl(url); }) .catch((err: unknown) => { if (!cancelled) { setError(err instanceof Error ? err.message : 'download failed'); } }); return () => { cancelled = true; if (url) URL.revokeObjectURL(url); }; }, [handle.id, handle.storagePath, handle.keyB64, handle.nonceB64]); if (error) { return (
{error}
); } if (!blobUrl) { return (
); } return (
PDF · {formatSize(handle.sizeBytes)}
Download
{expanded && (

Vorschau nicht verfügbar — bitte herunterladen.

)}
); } function PdfGlyph() { return ( ); } function formatSize(bytes: number): string { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(0) + ' KB'; return (bytes / 1024 / 1024).toFixed(1) + ' MB'; }