diff --git a/docs/Permission-Based-UI-Rendering.md b/docs/Permission-Based-UI-Rendering.md new file mode 100644 index 0000000..d7db8f5 --- /dev/null +++ b/docs/Permission-Based-UI-Rendering.md @@ -0,0 +1,661 @@ +# Permission-Based UI Rendering + +## Overview + +CommDesk uses a permission-based UI rendering system to decide which pages, buttons, actions, and management controls should be visible to a user. + +This implementation combines: + +- `TanStack Query` for fetching and caching permissions +- `Redux Toolkit` for storing permission state globally +- custom permission hooks for clean access checks +- reusable boundary and gate components for page-level and inline authorization + +The goal is simple: + +- show only the UI a user is allowed to use +- keep permission logic centralized +- make feature code easy to read and easy to extend +- avoid duplicated permission-check code across screens + +This document explains why we use this system, how it works, and how to use it correctly in production code. + +--- + +## Why We Use Permission-Based UI Rendering + +### 1. Better security posture at the UI layer + +The UI should not invite users to click actions they cannot perform. + +Examples: + +- hide `Create Event` if the user cannot create events +- hide `Delete Member` if the user cannot delete members +- hide `Send Email` if the user does not have contact email access + +Important: + +UI authorization is not a replacement for backend authorization. The backend must still validate permissions for every protected API action. + +### 2. Better user experience + +Without permission-aware rendering: + +- users see buttons that fail later +- users get confusing error messages after clicking +- layout becomes noisy and misleading + +With permission-aware rendering: + +- the interface feels focused +- users only see relevant actions +- restricted areas can provide clear guidance instead of dead ends + +### 3. Better maintainability + +If every component checks permissions differently, the codebase becomes fragile. + +This system keeps permission logic in one place so we can: + +- update permission behavior globally +- reuse hooks and boundary components +- keep feature components readable + +--- + +## High-Level Architecture + +The permission system lives in `src/permissions/`. + +### Main files + +- `src/permissions/constants.ts` + Contains permission keys such as `event:create` and `member:view`. + +- `src/permissions/permission.service.ts` + Fetches permissions for the current user and defines query keys. + +- `src/permissions/PermissionBootstrap.tsx` + Runs once near app startup, loads permissions through React Query, and syncs them into Redux. + +- `src/store/permissionsSlice.ts` + Stores granted permissions plus loading, error, and sync metadata. + +- `src/permissions/useAuthorization.tsx` + Exposes reusable hooks for permission checks: + `useAuthorization`, `usePermissionMap`, and `PermissionGate`. + +- `src/permissions/PermissionBoundary.tsx` + Handles page-level permission states: + loading, allowed, unauthorized. + +- `src/permissions/PermissionLoading.tsx` + Provides a clean loading state while access is being validated. + +- `src/permissions/AccessDenied.tsx` + Provides a reusable unauthorized state. + +- `src/permissions/selectors.ts` + Central Redux selectors for permission state. + +- `src/permissions/utils.ts` + Shared helpers for normalizing permission input and evaluating access. + +--- + +## Data Flow + +### Step 1: App starts + +`PermissionBootstrap` mounts near the app root in `src/main.tsx`. + +### Step 2: Permissions are fetched + +The bootstrap component uses `TanStack Query` to fetch permissions for the current user role. + +### Step 3: Permissions are cached + +React Query caches the result and prevents unnecessary refetching during the cache window. + +### Step 4: Permissions are stored globally + +Once fetched, permissions are written into Redux so any component can read them quickly without re-implementing fetch logic. + +### Step 5: Components consume permissions + +Components use: + +- `PermissionBoundary` for full-page access control +- `PermissionGate` for showing or hiding small UI sections +- `usePermissionMap` for components with multiple action buttons +- `useAuthorization` for lower-level custom logic + +--- + +## Permission Constants + +The system uses named constants instead of hard-coded strings. + +Example: + +```ts +export const Event_Permissions = { + CREATE_EVENT: "event:create", + UPDATE_EVENT: "event:update", + DELETE_EVENT: "event:delete", + VIEW_EVENT: "event:view", + PUBLISH_EVENT: "event:publish", + JOIN_EVENT: "event:join", + LEAVE_EVENT: "event:leave", +} as const; +``` + +Why this matters: + +- avoids typos +- improves autocomplete +- makes refactoring safer +- keeps permission usage consistent across modules + +--- + +## When to Use Each API + +### `PermissionBoundary` + +Use for page-level or section-level protection when you need: + +- a loading state +- an unauthorized fallback +- a protected content area + +Example: + +```tsx +} + unauthorizedFallback={ + + } +> + + +``` + +Use this when an entire page or major content area depends on access. + +### `PermissionGate` + +Use for small inline UI blocks such as: + +- buttons +- cards +- menu items +- action groups + +Example: + +```tsx + + } +{canDelete && } +``` + +--- + +## Production Guidelines + +### 1. Never hard-code permission strings inside feature components + +Do this: + +```tsx +permission={Event_Permissions.CREATE_EVENT} +``` + +Not this: + +```tsx +permission="event:create" +``` + +### 2. Prefer boundaries for pages + +If an entire page depends on permission, use `PermissionBoundary` instead of manual `if` chains. + +This keeps page code cleaner and more consistent. + +### 3. Prefer `usePermissionMap` for action-heavy components + +This is more maintainable than calling `useAuthorization` many times in the same component. + +### 4. Keep unauthorized UI honest + +Avoid showing: + +- fake action icons +- disabled-looking controls that still work +- menus with no valid action + +If a user cannot perform an action, either: + +- hide it +- or present a clear read-only state + +### 5. Handle loading intentionally + +Do not render unauthorized fallbacks before permission loading completes. + +That creates a flash of wrong content. + +Use: + +- `PermissionBoundary` with `loadingFallback` +- or `PermissionGate` with `loadingFallback` +- or `useAuthorization` with `isLoading` + +### 6. Keep backend and frontend authorization aligned + +Frontend permissions improve UX. +Backend permissions enforce security. + +Both are required. + +--- + +## Why Redux and React Query Together + +This is a common question. + +### Why not only React Query? + +React Query is excellent for: + +- fetching +- caching +- server-state freshness + +But UI code often wants lightweight global reads without repeating query usage everywhere. + +### Why not only Redux? + +Redux is excellent for: + +- global synchronous access +- predictable state transitions +- selectors + +But it is not the best tool alone for server data fetching and caching. + +### Why combine them? + +We use: + +- React Query for the async fetch lifecycle and caching +- Redux for simple app-wide access to resolved permission state + +This gives us: + +- centralized fetching +- consistent cached results +- simple UI access anywhere in the app + +--- + +## Current Role Mapping + +At the moment, permissions are resolved from a role-to-permission map in `permission.service.ts`. + +This works well for local development and UI scaffolding. + +Example: + +- `Admin` gets all permissions +- `Member` gets a limited set + +In a production backend integration, this file should fetch permissions from a real API instead of using mock role mapping. + +--- + +## How to Add a New Permission + +### Step 1: Add the constant + +Add it to the correct permission group in `src/permissions/constants.ts`. + +Example: + +```ts +export const Project_Permissions = { + VIEW_PROJECT: "project:view", + CREATE_PROJECT: "project:create", + ARCHIVE_PROJECT: "project:archive", +} as const; +``` + +### Step 2: Add it to the permission source + +Update the role mapping or backend response logic in `permission.service.ts`. + +### Step 3: Use it in the UI + +Example: + +```tsx + +