fix: show a notice when the form screen fails to load
This commit is contained in:
parent
fe6c257ace
commit
b862f8215c
@ -25,7 +25,8 @@ async function parseResponse<T>(res: Response): Promise<T> {
|
||||
if (!res.ok) {
|
||||
let msg = res.statusText;
|
||||
try { const e = await res.json(); if (e.error) msg = e.error; } catch {}
|
||||
throw new Error(msg);
|
||||
// Status rides along so callers can special-case it (403 -> no permission).
|
||||
throw Object.assign(new Error(msg), { status: res.status });
|
||||
}
|
||||
if (res.status === 204) return undefined as T;
|
||||
return res.json() as Promise<T>;
|
||||
|
||||
@ -68,7 +68,7 @@ export default function FormModal({ activityId, instanceId, title, onClose, onSu
|
||||
const [succeeded, setSucceeded] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const { data, isPending } = useQuery({
|
||||
const { data, isPending, error: loadError } = useQuery({
|
||||
queryKey: ["form-screen", activityId, instanceId],
|
||||
queryFn: () => getFormScreen(activityId, instanceId) as Promise<any>,
|
||||
});
|
||||
@ -77,6 +77,15 @@ export default function FormModal({ activityId, instanceId, title, onClose, onSu
|
||||
const gridConfig: GridItem[] = data?.grid_config ?? [];
|
||||
const formTitle = data?.activity_name || title || "Form";
|
||||
|
||||
// A failed form-screen fetch used to fall through to an empty <form> with a
|
||||
// live Submit button. 403 is the common one — the activity's RBAC gate — and
|
||||
// its backend message ("not permitted") means nothing to the person reading it.
|
||||
const loadErrorMessage = loadError
|
||||
? (loadError as { status?: number }).status === 403
|
||||
? "You don't have permission to open this form. Ask an admin for access to this activity."
|
||||
: loadError.message || "This form could not be loaded."
|
||||
: null;
|
||||
|
||||
// /upload and /ocr-extract resolve the field's deployed config and RBAC from
|
||||
// these identifiers. The form screen echoes them; WORKFLOW_ID is the fallback
|
||||
// for older view-service builds that don't.
|
||||
@ -234,6 +243,17 @@ export default function FormModal({ activityId, instanceId, title, onClose, onSu
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : loadErrorMessage ? (
|
||||
<div
|
||||
role="alert"
|
||||
className="flex items-start gap-2 rounded-lg border border-destructive/30 bg-destructive/10 px-3 py-2.5 text-destructive"
|
||||
>
|
||||
<AlertCircle className="mt-0.5 size-4 shrink-0" aria-hidden />
|
||||
<div>
|
||||
<p className="text-sm font-medium">Form unavailable</p>
|
||||
<p className="mt-0.5 text-sm">{loadErrorMessage}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<form id="activity-form" onSubmit={handleSubmit} className="space-y-4">
|
||||
{rows.map((row, ri) => (
|
||||
@ -304,13 +324,16 @@ export default function FormModal({ activityId, instanceId, title, onClose, onSu
|
||||
assume the content keeps its own padding — this dialog is p-0. */}
|
||||
{!isPending && (
|
||||
<DialogFooter className="mx-0 mb-0 items-center border-t border-border px-5 py-3 sm:justify-between">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<span className="text-destructive">*</span> Required
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
{!loadErrorMessage && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<span className="text-destructive">*</span> Required
|
||||
</p>
|
||||
)}
|
||||
<div className="flex gap-2 sm:ml-auto">
|
||||
<Button type="button" variant="ghost" onClick={onClose} disabled={submitting}>
|
||||
Cancel
|
||||
{loadErrorMessage ? "Close" : "Cancel"}
|
||||
</Button>
|
||||
{!loadErrorMessage && (
|
||||
<Button type="submit" form="activity-form" disabled={submitting || succeeded}>
|
||||
{submitting ? (
|
||||
<>
|
||||
@ -329,6 +352,7 @@ export default function FormModal({ activityId, instanceId, title, onClose, onSu
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</DialogFooter>
|
||||
)}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user