# Architecture **Analysis Date:** 2026-05-07 ## Pattern Overview **Overall:** Next.js 15 App Router with client-centric admin dashboard, role-based access control (RBAC) via localStorage permission matrix, and layered API abstraction. **Key Characteristics:** - Client-side rendering-first pattern with permission checks in layout/shell components - Token-based authentication with Axios interceptors for transparent token injection - Role-based module access controlled by permission matrix in `lib/permissions.ts` - Form handling via React Hook Form + Zod for validation - Shadcn-style UI components copied into `components/ui/` (not npm packages) ## Layers **Page Layer (Presentation/Routing):** - Purpose: App Router page components that mount client-side shells and load data - Location: `app/[module]/page.tsx` (e.g., `app/outfits/page.tsx`, `app/login/page.tsx`) - Contains: "use client" page entries, form pages, list views - Depends on: DashboardShell, custom feature components, API clients - Used by: Next.js App Router, browser navigation **Component Layer (UI & Business Logic):** - Purpose: Reusable business components (modals, dialogs, tables, feature-specific UI) - Location: `components/` (feature folders) and `components/ui/` (primitives) - Contains: DashboardShell, Sidebar, feature dialogs (AddOutfitDialog, DeleteConfirmationDialog), stat cards - Depends on: UI primitives, API clients, React Hook Form, icons (lucide-react) - Used by: Page components **API Client Layer (HTTP Communication):** - Purpose: Axios-based API communication with interceptors for auth, error handling - Location: `lib/api/` (e.g., `lib/api/client.ts`, `lib/api/auth.ts`, `lib/api/outfits.ts`) - Contains: API singleton instance, interceptors, module-specific request/response adapters - Depends on: Axios, localStorage (for token retrieval), environment variables - Used by: Page components and feature components **Permission/Auth Layer (Access Control):** - Purpose: Role-based module visibility and path-level permission checks - Location: `lib/permissions.ts` (RBAC matrix), `middleware.ts` (route protection) - Contains: PERMISSION_MATRIX (role → module[]), utility functions (hasPermission, getUserRole, getModuleFromPath) - Depends on: localStorage.user_role, localStorage.is_superuser - Used by: DashboardShell, Sidebar, middleware **Utility Layer:** - Purpose: Type definitions, helpers, environment detection - Location: `lib/utils.ts`, `lib/api/types.ts`, `lib/api/adapters.ts` - Contains: Tailwind utilities (cn()), type mappings, mock data - Depends on: Nothing (standalone) - Used by: All layers ## Data Flow **Authentication Flow (Login → Authenticated State):** 1. User fills login form on `app/login/page.tsx` 2. Form submission calls `emailLogin(email, password)` from `lib/api/auth.ts` 3. Axios POSTs to `{NEXT_PUBLIC_API_BASE_URL}/v1/admin/login/` 4. Response contains `token`, `role`, `is_superuser` 5. `saveAuthToken(token, isSuperUser, role)` stores to localStorage and Cookie 6. Page redirects to `/` (dashboard) 7. Middleware checks Cookie `auth_token`; on subsequent page loads, Axios request interceptor injects token from localStorage **Protected Page Access Flow:** 1. User navigates to protected route (e.g., `/outfits`) 2. Middleware checks cookie; if missing, redirects to `/login` 3. Page mounts, DashboardShell component renders 4. DashboardShell calls `hasPathPermission(pathname)` at mount 5. Function checks localStorage.user_role against PERMISSION_MATRIX 6. If denied, renders access-denied UI; if allowed, renders children 7. Sidebar calls `hasPermission(module)` for each menu item (mounted only) to filter visible items 8. Sidebar also displays current role from localStorage.user_role **Data Fetching Flow (Page → Component → API → Backend):** 1. Page component (e.g., `app/outfits/page.tsx`) mounts as "use client" 2. Page renders feature components (e.g., OutfitsList, OutfitsTable) 3. Feature component calls `getOutfits(params)` from `lib/api/outfits.ts` 4. API function calls `apiClient.get('/card/category/clothing/?...')` 5. Request interceptor injects token: `Authorization: Bearer {token}` 6. Backend receives request with token in header 7. Response returns `{ success, code, data, message }` 8. API adapter function maps backend response to frontend type (e.g., `mapBackendOutfit`) 9. Component stores result in React state or updates UI 10. Response interceptor handles 401 (token expired) → clears localStorage → redirects to `/login` **State Management:** - **Token/Auth state:** Persisted in `localStorage.auth_token`, `localStorage.user_role`, `localStorage.is_superuser`; synced to Cookie `auth_token` - **Component state:** React useState (shallow, page-scoped) - **Form state:** React Hook Form (form-scoped) - **No global state manager** (Redux/Zustand not used) **Authentication Token Lifecycle:** 1. Stored in localStorage + Cookie (7-day expiry) after login 2. On every API request, request interceptor reads from localStorage 3. If 401 response, token deleted from localStorage, user redirected to `/login` 4. Cookie-based fallback allows middleware to check auth on route entry ## Client/Server Component Split **Server Components:** - `app/layout.tsx` — Root layout with metadata only; does NOT render protected content - Most pages are **"use client"** due to permission checks and hooks **Client Components ("use client"):** - All pages under `app/[module]/` — Need React hooks for permission checks, form handling - `components/dashboard-shell.tsx` — Runs permission checks via `hasPathPermission()` - `components/sidebar.tsx` — Uses `usePathname()`, `useRouter()`, `useState()` for role-based filtering - Feature components — Use useState, API calls, form handling ## Key Abstractions **DashboardShell:** - Purpose: Layout wrapper that enforces route-level permissions - Location: `components/dashboard-shell.tsx` - Pattern: Client wrapper checking `hasPathPermission(pathname)` on mount; renders either access-denied UI or children - Used by: All page components (wraps page content) **Sidebar:** - Purpose: Dynamic navigation menu filtered by user role - Location: `components/sidebar.tsx` - Pattern: Client component reading `localStorage.user_role` after mount; filters menu items via `hasPermission(module)` - Menu structure: Three sections (AI Admin, Content Admin, System Admin) with conditional rendering - Icon mapping: MenuItem interface + lucide-react icons **API Client Singleton:** - Purpose: Centralized HTTP client with reusable interceptors - Location: `lib/api/client.ts` exports `apiClient` (Axios instance) - Pattern: Single instance created at module load; request/response interceptors for auth/error handling - Interceptors: Request injects token; response handles 401 by clearing token + redirecting **Permission Matrix:** - Purpose: Role → Module[] mapping - Location: `lib/permissions.ts` PERMISSION_MATRIX constant - Pattern: Record with 5 roles × 13 modules - Functions: getUserRole() → RoleName; hasPermission(module) → boolean; getModuleFromPath(pathname) → module **API Adapter Pattern:** - Purpose: Map backend response schema to frontend type - Example: `mapBackendOutfit()` in `lib/api/outfits.ts` - Pattern: Backend returns `{ id, name, image_url, rarity_display, ... }`; adapter extracts and renames to `{ id, name, imageUrl, rarity, ... }` - Why: Frontend type contracts differ from backend schema ## Entry Points **Browser Entry:** - Location: `app/layout.tsx` (Root layout) - Triggers: Initial page load or navigation - Responsibilities: Renders HTML shell; child routes rendered by App Router **Auth Entry:** - Location: `app/login/page.tsx` - Triggers: Unauthenticated users accessing protected routes (redirected by middleware) - Responsibilities: Render login form, call `emailLogin()`, save auth token, redirect to dashboard **Dashboard Entry:** - Location: `app/page.tsx` - Triggers: Authenticated users navigating to `/` or post-login redirect - Responsibilities: Check auth status, render stats/charts, provide links to submodules **Protected Route Entry (Example):** - Location: `app/outfits/page.tsx` - Triggers: User navigates to `/outfits` - Responsibilities: Wrap content in DashboardShell (permission check), render OutfitsList component, fetch data ## Error Handling **Strategy:** Try-catch at API level; Axios interceptor handles 401; UI displays error toast or inline messages. **Patterns:** - **API Request Errors:** Caught in try-catch, logged, thrown to caller (page/component) - **401 Unauthorized:** Interceptor clears token, redirects to `/login` - **Form Validation Errors:** React Hook Form + Zod pre-submission; validation errors displayed inline - **Permission Denied:** DashboardShell renders access-denied UI (no error thrown) - **Mock Data Fallback:** `lib/api/client.ts` exports mock users/roles; can be used for offline testing ## Cross-Cutting Concerns **Logging:** - Verbose Axios logging in request/response interceptors (console.log with emoji prefixes) - Error logging in catch blocks **Validation:** - React Hook Form + Zod for form fields - Backend response type-checked against ApiResponse interface **Authentication:** - localStorage.auth_token + Axios Bearer header injection - Middleware checks Cookie auth_token for route protection - 401 response triggers token cleanup + redirect **Internationalization:** - Chinese UI text hardcoded in components - No i18n library used --- *Architecture analysis: 2026-05-07*