import { useMemo } from "react"; /** * Reads the user's Acadenice permissions from the auth context. * * Why not a server call here: * The permissions are already resolved by R2.3a (`GET /api/v1/permissions/me`) * and cached in the application-level React Query cache. This hook reads from * that cache or falls back to parsing the `acadenice_permissions` claim from any * JS-readable cookie. It does NOT perform a new HTTP request — callers that need * fresh permissions should use the RBAC hook from the rbac feature. * * For the database-view inline editing use case we only need to know whether * the current user can write rows (`database.rows.write`). We resolve this from * the standard Acadenice permission `rows:write`. */ export interface UsePermissionsResult { /** True when the user has the `rows:write` permission. */ canWriteRows: boolean; /** True when the user has `admin:*` (covers all permissions). */ isAdmin: boolean; /** True when permissions have been resolved (not still loading). */ isResolved: boolean; } /** * Reads acadenice_permissions from any available JS-accessible source: * 1. The `__acadenice_perms` global injected by the app bootstrap (if present). * 2. A non-HttpOnly cookie `acadenicePerms` (serialised JSON array). * 3. The legacy jotai `authTokens` atom value decoded shallowly. * * Falls back to { canWriteRows: true, isAdmin: false } so that editing is * optimistically enabled — the server will reject the PATCH with 403 if the * user truly lacks the permission, and the UI rolls back (see useUpdateRow). */ export function usePermissions(): UsePermissionsResult { return useMemo(() => { // Try the window-level cache set by the RBAC hook (R2.3a) when the query resolves. // The cache key is `window.__acadenice_perms`. const fromGlobal = ( typeof window !== "undefined" && (window as unknown as Record)["__acadenice_perms"] ); if (Array.isArray(fromGlobal)) { return resolveFromPermissions(fromGlobal as string[]); } // Try the cookie fallback. const fromCookie = readPermissionsFromCookie(); if (fromCookie !== null) { return resolveFromPermissions(fromCookie); } // Optimistic default: allow writes (server is the guard). return { canWriteRows: true, isAdmin: false, isResolved: false }; }, []); } function resolveFromPermissions(permissions: string[]): UsePermissionsResult { const isAdmin = permissions.includes("admin:*"); const canWriteRows = isAdmin || permissions.includes("rows:write"); return { canWriteRows, isAdmin, isResolved: true }; } function readPermissionsFromCookie(): string[] | null { if (typeof document === "undefined") return null; try { const raw = document.cookie .split(";") .map((c) => c.trim()) .find((c) => c.startsWith("acadenicePerms=")); if (!raw) return null; const val = decodeURIComponent(raw.slice("acadenicePerms=".length)); const parsed = JSON.parse(val); return Array.isArray(parsed) ? (parsed as string[]) : null; } catch { return null; } }