| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- "use client";
- import React from "react";
- import { useAuth } from "@/components/auth/authContext";
- import { getBranches } from "@/lib/frontend/apiClient";
- import {
- decideBranchUi,
- BRANCH_UI_DECISION,
- } from "@/lib/frontend/rbac/branchUiDecision";
- import ForbiddenView from "@/components/system/ForbiddenView";
- import NotFoundView from "@/components/system/NotFoundView";
- const BRANCH_LIST_STATE = Object.freeze({
- IDLE: "idle",
- LOADING: "loading",
- READY: "ready",
- ERROR: "error",
- });
- /**
- * BranchGuard
- *
- * UX improvement:
- * - We do NOT block rendering while admin/dev branch list is loading.
- * - Existence validation is applied once the list is READY.
- * - While LOADING/ERROR we fail open to avoid "solo spinner" screens.
- *
- * Security note:
- * - Backend RBAC remains authoritative.
- * - Branch users are enforced immediately (no existence check needed).
- */
- export default function BranchGuard({ branch, children }) {
- const { status, user } = useAuth();
- const isAuthenticated = status === "authenticated" && user;
- const needsExistenceCheck =
- isAuthenticated && (user.role === "admin" || user.role === "dev");
- const [branchList, setBranchList] = React.useState({
- status: BRANCH_LIST_STATE.IDLE,
- branches: null,
- });
- React.useEffect(() => {
- if (!needsExistenceCheck) return;
- let cancelled = false;
- setBranchList({ status: BRANCH_LIST_STATE.LOADING, branches: null });
- (async () => {
- try {
- const res = await getBranches();
- if (cancelled) return;
- const branches = Array.isArray(res?.branches) ? res.branches : [];
- setBranchList({ status: BRANCH_LIST_STATE.READY, branches });
- } catch (err) {
- if (cancelled) return;
- // Fail open: do not block navigation if validation fails.
- console.error("[BranchGuard] getBranches failed:", err);
- setBranchList({ status: BRANCH_LIST_STATE.ERROR, branches: null });
- }
- })();
- return () => {
- cancelled = true;
- };
- }, [needsExistenceCheck, user?.userId]);
- if (!isAuthenticated) return children;
- // Only apply existence validation when the list is READY.
- const allowedBranches =
- branchList.status === BRANCH_LIST_STATE.READY ? branchList.branches : null;
- const decision = decideBranchUi({
- user,
- branch,
- allowedBranches,
- });
- if (decision === BRANCH_UI_DECISION.FORBIDDEN) {
- return <ForbiddenView attemptedBranch={branch} />;
- }
- if (decision === BRANCH_UI_DECISION.NOT_FOUND) {
- return <NotFoundView />;
- }
- return children;
- }
|