// Per-route capability guard. Wrap the page body — if the active // session doesn't hold the route's capability, render a 403 instead of // the page. Server-side authz is still the real gate; this is UX so a // deep link doesn't 500 inside a route loader that assumes access. import { useLocation } from "react-router" import { ShieldAlert } from "lucide-react" import { capabilityForPath, useCapabilities, type Capability, } from "~/lib/capabilities" import { Card, CardContent } from "~/components/ui/card" type RouteGuardProps = { children: React.ReactNode /** Override the capability derived from the current path. Useful for * nested routes where you want to check a specific cap. */ capability?: Capability } export function RouteGuard({ children, capability }: RouteGuardProps) { const caps = useCapabilities() const location = useLocation() const required = capability ?? capabilityForPath(location.pathname) // No mapping = route is intentionally unguarded (e.g. login flows // never reach AppShell anyway). if (!required) return <>{children} if (caps.has(required)) return <>{children} return } function Forbidden({ capability }: { capability: Capability }) { return (

You can't access this page

This view requires the {capability}{" "} capability on your active tenant. If you think you should have it, switch tenants from the avatar menu or ask an admin.

) }