57 lines
1.6 KiB
TypeScript
57 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* The signed-in staff member's role and permissions, for the whole panel.
|
|
*
|
|
* Used to HIDE what someone can't do — nav entries, buttons, whole pages.
|
|
* Hiding is a courtesy, not the boundary: every /admin/* route on the BFF
|
|
* checks its own permission, so a hidden button that somehow fired would
|
|
* still be refused.
|
|
*/
|
|
|
|
import { createContext, useContext } from "react";
|
|
import type { StaffMe } from "@/lib/api";
|
|
|
|
const StaffContext = createContext<StaffMe | null>(null);
|
|
|
|
export const StaffProvider = StaffContext.Provider;
|
|
|
|
export function useStaff() {
|
|
const me = useContext(StaffContext);
|
|
const perms = new Set(me?.permissions ?? []);
|
|
return {
|
|
me,
|
|
can: (perm: string) => perms.has(perm),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Renders children only for staff holding `perm`; otherwise a short notice.
|
|
* Wraps whole pages, so landing on one by URL explains itself rather than
|
|
* showing a screen of 403 errors.
|
|
*/
|
|
export function RequirePermission({
|
|
perm,
|
|
children,
|
|
}: {
|
|
perm: string;
|
|
children: React.ReactNode;
|
|
}) {
|
|
const { can } = useStaff();
|
|
if (!can(perm)) {
|
|
return (
|
|
<div className="rounded-xl border border-border p-6 text-sm text-muted-foreground">
|
|
Your role doesn't include access to this section. A superadmin
|
|
can change that from the Staff page.
|
|
</div>
|
|
);
|
|
}
|
|
return <>{children}</>;
|
|
}
|
|
|
|
/** Renders children only when the signed-in role holds `perm`. */
|
|
export function Can({ perm, children }: { perm: string; children: React.ReactNode }) {
|
|
const { can } = useStaff();
|
|
return can(perm) ? <>{children}</> : null;
|
|
}
|