52 lines
2.0 KiB
TypeScript
52 lines
2.0 KiB
TypeScript
import type { ReactNode } from 'react';
|
||
import { Lock } from 'lucide-react';
|
||
|
||
/** True for a backend RBAC denial — so we can show a human line, not the raw
|
||
* "user N does not have permission for activity <uuid>". */
|
||
export function isPermissionError(msg?: string | null): boolean {
|
||
return !!msg && /permission|not authoriz|unauthoriz|forbidden|\b403\b/i.test(msg);
|
||
}
|
||
|
||
interface SchemaGuardProps {
|
||
loading: boolean;
|
||
error: string | null;
|
||
/** Verb phrase for the denial copy, e.g. "collect documents for this lead". */
|
||
action: string;
|
||
/** True when the schema loaded but yielded no usable fields. */
|
||
empty?: boolean;
|
||
children: ReactNode;
|
||
}
|
||
|
||
/** Gate any activity body on its form-screens fetch. Until the schema resolves
|
||
* cleanly we never render the form: an RBAC denial (or any load error) shows a
|
||
* human-readable card instead of a live-looking form that 403s only on submit.
|
||
* Bespoke bodies and the generic <ActivityForm> share this so the guard can't
|
||
* drift out of sync between them. */
|
||
export function SchemaGuard({ loading, error, action, empty, children }: SchemaGuardProps) {
|
||
if (loading) {
|
||
return <div className="py-6 text-sm text-faint">Loading form…</div>;
|
||
}
|
||
if (error || empty) {
|
||
if (isPermissionError(error)) {
|
||
return (
|
||
<div className="flex items-start gap-2.5 rounded-md border border-amber-200 bg-amber-50 px-4 py-3.5 text-sm text-amber-800">
|
||
<Lock size={16} className="mt-0.5 shrink-0 text-amber-600" />
|
||
<div>
|
||
<div className="font-semibold">You don’t have permission for this action</div>
|
||
<div className="mt-0.5 text-xs text-amber-700">
|
||
Your role can’t {action}. Ask an admin to grant your role access to this activity, or
|
||
sign in as a role that can.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
return (
|
||
<div className="py-4 text-sm text-amber-700">
|
||
{error ? `Couldn’t load the form: ${error}` : 'This activity has no input fields.'}
|
||
</div>
|
||
);
|
||
}
|
||
return <>{children}</>;
|
||
}
|