import type { ReactNode } from "react"; import { useAuth } from "./useAuth"; type Role = "admin" | "user" | "viewer"; interface Props { /** * Roles allowed to interact with the gated affordance. The user's * role must be in this list for children to render normally. */ allow: Role[]; children: ReactNode; /** * What to render instead when the user lacks the role AND we don't * want to show the disabled affordance at all (e.g. "contact your * admin" hint, or just nothing). */ fallback?: ReactNode; } /** * RoleGate wraps any write affordance (parse button, resubmit action, * "match selected", etc.) so it visibly disables itself when the * current user's role isn't in `allow`. * * Behavior matrix: * * - user is null → render nothing (the route guard * already redirects to /login; this * is belt-and-braces). * - user.role in `allow` → render children unchanged. * - user.role NOT in `allow` and * `fallback` is provided → render `fallback`. * - user.role NOT in `allow` and * no `fallback` → wrap children in a span carrying * `opacity-50` + `pointer-events-none` * + a `title` attribute that * explains why. We use the native * HTML `title` instead of a custom * Tooltip component to keep this * dependency-free and accessible * by default. */ export function RoleGate({ allow, children, fallback }: Props) { const { user } = useAuth(); if (!user) return null; if (allow.includes(user.role)) return <>{children}; if (fallback !== undefined) return <>{fallback}; return ( {children} ); }