// components/RegistrationDocuments.tsx // Client-side panel for managing a registration's `documents` field: // - Lists current files with download links // - Provides an upload form for the 5 post-wizard document types: // Cor9.1 (name reserved letter), Cor9.4, Director Acceptance, Resolution, // CIPC Registration, MOI // // All uploads go to /api/register/[id]/upload, which renames the file // server-side per the type and appends it to the reg's `documents` array. "use client"; import { useEffect, useState } from "react"; type DocType = | "cipc_reservation" | "cipc_approval" | "director_acceptance" | "resolution" | "cipc_registration" | "moi"; const TYPE_LABELS: Record = { cipc_reservation: "Cor9.1 — Name Reservation letter", cipc_approval: "Cor9.4 — Name Approval letter", director_acceptance: "Director Acceptance letter", resolution: "Resolution to start company", cipc_registration: "CIPC Registration document", moi: "MOI from CIPC", }; const TYPE_PREFIX: Record = { cipc_reservation: "Cor9.1", cipc_approval: "Cor9.4", director_acceptance: "Director Acceptance", resolution: "Resolution", cipc_registration: "CIPC Registration", moi: "MOI", }; interface PBFile { name: string; originalName?: string; } function extractFiles(documents: unknown): PBFile[] { if (!documents) return []; if (Array.isArray(documents)) { return documents .map((d) => { if (typeof d === "string") return { name: d }; if (d && typeof d === "object" && "name" in (d as Record)) { return { name: String((d as Record).name) }; } return null; }) .filter((x): x is PBFile => !!x); } return []; } export default function RegistrationDocuments({ registrationId, initialDocuments, cipcReservedName, }: { registrationId: string; initialDocuments: unknown; cipcReservedName?: string; }) { const [docs, setDocs] = useState(extractFiles(initialDocuments)); const [type, setType] = useState("cipc_reservation"); const [file, setFile] = useState(null); const [busy, setBusy] = useState(false); const [msg, setMsg] = useState<{ kind: "ok" | "err"; text: string } | null>(null); // Refresh the file list every time the component remounts useEffect(() => { setDocs(extractFiles(initialDocuments)); }, [initialDocuments]); async function handleUpload(e: React.FormEvent) { e.preventDefault(); if (!file) { setMsg({ kind: "err", text: "Pick a file first." }); return; } setBusy(true); setMsg(null); try { const fd = new FormData(); fd.append("type", type); if (cipcReservedName) fd.append("reserved_name", cipcReservedName); fd.append("files", file); const res = await fetch(`/api/register/${registrationId}/upload`, { method: "POST", body: fd, }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error || `HTTP ${res.status}`); // Append the new file to local state (server didn't return per-file names // in a stable shape, so we just re-fetch by triggering a page refresh). setFile(null); setMsg({ kind: "ok", text: `Uploaded ${data.filesUploaded || 1} file(s). Reloading…` }); setTimeout(() => window.location.reload(), 800); } catch (err: unknown) { setMsg({ kind: "err", text: err instanceof Error ? err.message : "Upload failed" }); } finally { setBusy(false); } } return (

Documents

{docs.length === 0 ? (

No documents uploaded yet.

) : ( )}

Upload document

{cipcReservedName && (

Will save as: {TYPE_PREFIX[type]} - {cipcReservedName}.<ext>

)}
setFile(e.target.files?.[0] || null)} className="block w-full text-sm text-fg file:mr-3 file:py-1.5 file:px-3 file:rounded file:border file:border-border file:bg-surface-warm file:text-fg file:text-xs file:font-semibold file:cursor-pointer hover:file:bg-accent/10" />
{msg && ( {msg.text} )}
); }