import { Clock3, LogIn, RefreshCw, Send } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { useParams, useSearchParams } from "react-router"; import { ActionToolbar, Button, Card, DescriptionItem, DescriptionList, DismissibleAlert, FormField, LoadingIndicator, PageScrollViewport, StatusBadge, WorkspaceFrame, type PlatformRouteContext } from "@govoplan/core-webui"; import { getApplicationStatusAccess, getAuthenticatedApplicationStatus, getPublicApplicationStatus, requestApplicationStatusEmailLink, type PortalApplicationStatus, type PortalApplicationStatusAccess } from "../../api/portal"; export default function PortalStatusPage({ settings }: PlatformRouteContext) { const { trackingId = "" } = useParams(); const [searchParams] = useSearchParams(); const token = searchParams.get("token") ?? ""; const [access, setAccess] = useState(null); const [status, setStatus] = useState(null); const [email, setEmail] = useState(""); const [loading, setLoading] = useState(true); const [sending, setSending] = useState(false); const [error, setError] = useState(""); const [notice, setNotice] = useState(""); const load = useCallback(async (signal?: AbortSignal) => { if (!trackingId) return; setLoading(true); setError(""); try { const nextAccess = await getApplicationStatusAccess(settings, trackingId, signal); setAccess(nextAccess); if (token) { setStatus(await getPublicApplicationStatus(settings, trackingId, token, signal)); return; } if (nextAccess.mode === "permanent_link") { setStatus(await getPublicApplicationStatus(settings, trackingId, undefined, signal)); return; } if (settings.accessToken || settings.apiKey) { try { setStatus(await getAuthenticatedApplicationStatus(settings, trackingId, signal)); return; } catch { setStatus(null); } } else { setStatus(null); } } catch (reason) { if ((reason as Error).name !== "AbortError") { setError(reason instanceof Error ? reason.message : "Application status could not be loaded."); } } finally { setLoading(false); } }, [settings, token, trackingId]); useEffect(() => { const controller = new AbortController(); void load(controller.signal); return () => controller.abort(); }, [load]); async function requestLink() { if (!email.trim()) return; setSending(true); setError(""); setNotice(""); try { const response = await requestApplicationStatusEmailLink(settings, trackingId, email.trim()); setNotice(response.message); } catch { setNotice("If the application and email address match, a new short-lived status link will be sent."); } finally { setSending(false); } } return (
Application status {error && {error}} {notice && {notice}} {loading && } {!loading && status && } {!loading && !status && access?.mode === "authenticated" &&

This application is configured for authenticated access only. Sign in with the account linked to the submission.

} {!loading && !status && access?.mode === "email_link" &&

Enter the email address linked to the application. The response is identical whether or not the details match.

{ event.preventDefault(); void requestLink(); }}> setEmail(event.target.value)} disabled={sending} required autoComplete="email" />
{access.token_ttl_seconds &&

}
}
); } function StatusProjection({ status }: { status: PortalApplicationStatus }) { return (
}> {status.tracking_id} {formatDate(status.updated_at)} {status.receipt_id && {status.receipt_id}} {status.timeline.length === 0 &&

No public status event is available yet.

}
    {status.timeline.map((item, index) =>
  1. )}
This page intentionally shows only the public lifecycle, update times, and receipt reference. Form values, evidence, internal notes, actors, and handoff details remain private.
); } function statusLabel(value: string): string { const labels: Record = { submitted: "Application received", validated: "Completeness checked", needs_review: "Under review", accepted: "Approved", rejected: "Decision issued", handed_off: "Further processing", archived: "Procedure closed" }; return labels[value] ?? value.replaceAll("_", " "); } function statusTone(value: string): string { if (value === "accepted" || value === "archived") return "active"; if (value === "rejected") return "warning"; return "pending"; } function formatDate(value: string): string { return new Intl.DateTimeFormat(undefined, { dateStyle: "medium", timeStyle: "short" }).format(new Date(value)); } function durationLabel(seconds: number): string { if (seconds % 3600 === 0) return `${seconds / 3600} hour${seconds === 3600 ? "" : "s"}`; return `${Math.round(seconds / 60)} minutes`; }