'use client'; import { FunctionComponent } from 'react'; import { useTranslations } from 'next-intl'; import { Box, Skeleton, Stack, Typography } from '@mui/material'; import type { VerificationDocument } from '@/services/verification/types'; import AppButton from '../common/AppButton'; import AppIcon from '../common/AppIcon'; export interface DocumentViewerProps { document: VerificationDocument; /** Refetches the parent case — there is no per-document re-sign route, so a reopen re-fetches the whole * case to get a freshly-signed `document.url`. */ onReload: () => void; /** True while the parent case is (re)loading — drives the skeleton. */ reloading: boolean; } /** Human-readable file size. */ function formatSize(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } /** * A verification-document viewer. `document.url` is the **short-lived signed GET URL the case detail * already carries** — there is no separate per-document re-sign route (b6 gap), so "reload" refetches the * whole case (`onReload`) rather than calling a document-specific endpoint. Handles the loading (parent * case fetching) and loaded states (inline image preview for images, otherwise an "open in a new tab" * affordance). PII → only the signed URL is ever surfaced, never a public asset (phase §5). * @component DocumentViewer */ const DocumentViewer: FunctionComponent = ({ document, onReload, reloading }) => { const t = useTranslations('admin'); const isImage = document.contentType.startsWith('image/'); return ( {t('doc_file_meta', { name: document.originalFileName ?? `#${document.id}`, size: formatSize(document.fileSizeBytes) })} {t('doc_reload')} {reloading ? ( ) : document.url ? ( isImage ? ( // Signed, short-lived, cross-host URL (not a static asset) — a plain via Box, not next/image. ) : ( {t('doc_open_new')} ) ) : null} ); }; export default DocumentViewer;