import { Save } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Button, Dialog, DialogForm, DialogSection, DismissibleAlert, FormField, FormGrid, LoadingIndicator, StatusBadge, ToggleSwitch, type PlatformRouteContext } from "@govoplan/core-webui"; import { listFormIntakeDefinitions, listFormStatusAccessPolicies, saveFormStatusAccessPolicy, type FormDefinition, type FormStatusAccessMode, type FormStatusAccessPolicy } from "../../api/formsRuntime"; type StatusAccessPoliciesDialogProps = { open: boolean; settings: PlatformRouteContext["settings"]; onClose: () => void; }; export default function StatusAccessPoliciesDialog({ open, settings, onClose }: StatusAccessPoliciesDialogProps) { const [definitions, setDefinitions] = useState([]); const [policies, setPolicies] = useState([]); const [definitionKey, setDefinitionKey] = useState(""); const [mode, setMode] = useState("authenticated"); const [emailFieldKey, setEmailFieldKey] = useState(""); const [tokenMinutes, setTokenMinutes] = useState(60); const [requestLimit, setRequestLimit] = useState(5); const [enabled, setEnabled] = useState(true); const [loading, setLoading] = useState(false); const [busyKey, setBusyKey] = useState(""); const [error, setError] = useState(""); const [notice, setNotice] = useState(""); const load = useCallback(async (signal?: AbortSignal) => { setLoading(true); setError(""); try { const [definitionResult, policyResult] = await Promise.all([ listFormIntakeDefinitions(settings, signal), listFormStatusAccessPolicies(settings, signal) ]); setDefinitions(definitionResult.definitions); setPolicies(policyResult.policies); setDefinitionKey((current) => current || referenceKey(definitionResult.definitions[0])); } finally { setLoading(false); } }, [settings]); useEffect(() => { if (!open) return undefined; const controller = new AbortController(); void load(controller.signal).catch((reason) => { if ((reason as Error).name !== "AbortError") { setError(reason instanceof Error ? reason.message : "Status access policies could not be loaded."); } }); return () => controller.abort(); }, [load, open]); const definition = useMemo( () => definitions.find((item) => referenceKey(item) === definitionKey), [definitionKey, definitions] ); const policy = useMemo( () => policies.find((item) => referenceKey(item) === definitionKey), [definitionKey, policies] ); useEffect(() => { if (!definitionKey) return; if (policy) { setMode(policy.mode); setEmailFieldKey(policy.email_field_key ?? ""); setTokenMinutes(Math.max(5, Math.round(policy.token_ttl_seconds / 60))); setRequestLimit(policy.request_limit_per_hour); setEnabled(policy.enabled); return; } setMode("authenticated"); setEmailFieldKey(""); setTokenMinutes(60); setRequestLimit(5); setEnabled(true); }, [definitionKey, policy]); async function save() { if (!definition || (mode === "email_link" && !emailFieldKey)) return; setBusyKey(definitionKey); setError(""); setNotice(""); try { await saveFormStatusAccessPolicy(settings, { definitionRef: definition.reference, mode, enabled, emailFieldKey, tokenTtlSeconds: tokenMinutes * 60, requestLimitPerHour: requestLimit, expectedRevision: policy?.revision }); await load(); setNotice("The applicant status access policy was saved."); } catch (reason) { setError(reason instanceof Error ? reason.message : "The status access policy could not be saved."); } finally { setBusyKey(""); } } async function toggle(item: FormStatusAccessPolicy, nextEnabled: boolean) { const exactDefinition = definitions.find((candidate) => referenceKey(candidate) === referenceKey(item)); if (!exactDefinition) return; setBusyKey(item.policy_id); setError(""); setNotice(""); try { await saveFormStatusAccessPolicy(settings, { definitionRef: exactDefinition.reference, mode: item.mode, enabled: nextEnabled, emailFieldKey: item.email_field_key ?? undefined, tokenTtlSeconds: item.token_ttl_seconds, requestLimitPerHour: item.request_limit_per_hour, expectedRevision: item.revision }); await load(); setNotice(nextEnabled ? "Applicant status access was enabled for future submissions and existing grants." : "Applicant status access and all existing grants for this exact Form revision were suspended." ); } catch (reason) { setError(reason instanceof Error ? reason.message : "The status access policy could not be changed."); } finally { setBusyKey(""); } } return ( Close}> {error && {error}} {notice && {notice}} {loading && } {!loading && definitions.length === 0 && Publish a Form revision before configuring applicant status. } {!loading && definitions.length > 0 && { event.preventDefault(); void save(); }}> {mode === "email_link" && } {mode === "email_link" && setTokenMinutes(Number(event.target.value))} disabled={Boolean(busyKey)} /> } {mode === "email_link" && setRequestLimit(Number(event.target.value))} disabled={Boolean(busyKey)} /> }
{policies.length === 0 &&

No applicant status policy has been configured.

}
{policies.map((item) =>
{policyTitle(item)} Revision {item.definition_ref.version} · {modeLabel(item.mode)} void toggle(item, value)} />
)}
}
); } function AccessConsequence({ mode }: { mode: FormStatusAccessMode }) { if (mode === "authenticated") { return Only the bound applicant account can view status. Assisted intake needs an explicit account: party reference for this profile.; } if (mode === "email_link") { return A matching identifier and email request creates a new expiring secret and revokes the previous one. Notifications and Mail must be configured for delivery.; } return The link does not expire or require sign-in. Anyone holding it can see the bounded status timeline until the policy is suspended.; } function referenceKey(value?: FormDefinition | FormStatusAccessPolicy): string { if (!value) return ""; const reference = "definition_ref" in value ? value.definition_ref : value.reference; return `${reference.object_id}:${reference.version ?? ""}`; } function policyTitle(policy: FormStatusAccessPolicy): string { const title = policy.metadata.definition_title; return typeof title === "string" && title.trim() ? title : policy.definition_ref.label ?? policy.definition_ref.object_id; } function modeLabel(mode: FormStatusAccessMode): string { if (mode === "authenticated") return "Authenticated applicant"; if (mode === "email_link") return "Short-lived email link"; return "Permanent public link"; }