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)}