import { useState } from 'react'; import { FileWarning, Loader2 } from 'lucide-react'; import { useFormSubmissionsService } from '../../../services/FormSubmissionsService.tsx'; import { Button } from '../../../components/ui/button.tsx'; import { relayServerFailure } from '../../../utils/fetchFailureMessage'; import { SCAN_HOLD_WORDS, scanHoldOf } from '../../../utils/uploadScanHold'; // - the files a stranger attached to a public form, and the decision to open one. // // QUARANTINED IS NOT A STORAGE DETAIL, IT IS THE WHOLE DESIGN. These arrived from an anonymous, // unauthenticated visitor on the public internet; each opens only once its virus scan clears it, and nothing in the clinical // record references them until a human at the practice triages the submission. What the quarantine gates // is that NOTHING TOUCHES THEM UNTIL SOMEBODY DECIDES TO LOOK - so this list holds no links, no previews // and no thumbnails, and a URL is asked for only when a named person clicks. // // AND THAT IS WHY THERE IS NO IMAGE PREVIEW HERE, which is the obvious thing to want. Rendering the file // IS opening it: an fetches and decodes an unscanned stranger's bytes with nobody having chosen to. // // ZERO WordPress: Tailwind + lucide only. interface Props { submissionId: string; attachmentKeys?: string[]; attachmentNames?: Record; } /** * A key is `public-form-quarantine///.` - server-chosen, so the only part worth * showing a receptionist is what KIND of file it is. * * The uuid is deliberately not shown: it names nothing a human recognises, and putting it on screen * invites somebody to paste it somewhere. */ const describeKey = (key: string, index: number): string => { const extension = key.slice(key.lastIndexOf('.') + 1).toUpperCase(); const known = /^[A-Z]{3,4}$/.test(extension) ? extension : 'File'; return `${known} attachment ${index + 1}`; }; const QuarantinedAttachments: React.FC = ({ submissionId, attachmentKeys, attachmentNames }) => { const { openSubmissionAttachment } = useFormSubmissionsService(); const [busyKey, setBusyKey] = useState(null); const [error, setError] = useState(null); // Nothing at all when nothing was attached - not an empty state. Most submissions have no files, and // a permanent "No attachments" row on every one of them is furniture. if (!attachmentKeys?.length) return null; const open = async (key: string) => { setBusyKey(key); setError(null); try { const { url } = await openSubmissionAttachment(submissionId, key); // `noopener` is not optional on a URL we did not author the contents of: without it the // opened document gets a handle on this window through `window.opener` and can navigate it. window.open(url, '_blank', 'noopener,noreferrer'); } catch (openError) { // A 404 here means EITHER the file is gone OR it is not on this submission, and the server // makes those indistinguishable on purpose. So the copy says neither. const hold = scanHoldOf(openError); setError(hold ? SCAN_HOLD_WORDS[hold] : relayServerFailure(openError, 'That file could not be opened.')); } finally { setBusyKey(null); } }; return (
Attachments
{/* 🚨 THE WARNING IS THE POINT OF THIS BLOCK, not decoration. The person about to click is being handed a file by a stranger, and everything else on this screen looks like ordinary practice data. */}

Sent by someone who filled in your public form. Each file opens only after a virus check — open one only if you were expecting it.

    {attachmentKeys.map((key, index) => (
  • {/* The sender's name for it when the server kept one; naming it opens nothing. */} {attachmentNames?.[key] ?? describeKey(key, index)}
  • ))}
{error &&

{error}

}
); }; export default QuarantinedAttachments;