# 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<RoleName, PermissionModule[]> 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*
