# Component Definitions — CMS Frontend ## Backend Components (Unit 0) ### CorsPolicy - **Purpose**: Configure Cross-Origin Resource Sharing for the frontend SPA - **Responsibilities**: - Allow the configured frontend origin(s) from `appsettings.json → AllowedOrigins` - Allow credentials (`AllowCredentials()`) so the browser sends the httpOnly cookie - Allow required HTTP methods and headers - **Location**: `src/SlpModularCms.Api/Extensions/ServiceCollectionExtensions.cs` (extended) - **Configuration**: `appsettings.json → Cors:AllowedOrigins[]`, `appsettings.Development.json` (dev origins), `appsettings.local.json` (developer override) ### AuthController (updated) - **Purpose**: Authentication HTTP endpoints — updated to set/clear httpOnly cookie - **Responsibilities**: - `Login`: Call `IAuthService.AuthenticateAsync()`, set `refreshToken` httpOnly cookie, return access token + user info only in body - `Refresh`: Read `refreshToken` from cookie (not request body), call `IAuthService.RefreshTokenAsync()`, set new cookie, return new access token - `Revoke`: Call `IAuthService.RevokeTokenAsync()` with cookie value, clear the cookie - **Location**: `src/SlpModularCms.Modules.Identity/Controllers/AuthController.cs` ### AuthService (updated) - **Purpose**: Core authentication business logic — updated for cookie-based refresh flow - **Responsibilities**: - `RefreshTokenAsync(accessToken, refreshToken)` — signature unchanged; cookie reading is the controller's responsibility - No other changes to service layer --- ## Frontend Components (Units 1–6) ### Infrastructure Layer #### ApiClient - **Purpose**: Centralised HTTP client — all API calls go through this module - **Responsibilities**: - Base URL from `import.meta.env.VITE_API_BASE_URL` - Attach `Authorization: Bearer {accessToken}` header on authenticated requests - Intercept 401 responses — trigger token refresh via `AuthContext.refresh()`, retry original request once - Expose typed request functions used by TanStack Query - Send requests with `credentials: 'include'` so the browser includes the httpOnly cookie - **Location**: `frontend/src/api/client.ts` #### AuthContext - **Purpose**: React Context providing authentication state to the entire app - **Responsibilities**: - Store in-memory `accessToken` (string | null) — never persisted to localStorage - Store authenticated user: `{ id, email, name, role, isActive }` - Expose `login(email, password)`, `logout()`, `refresh()` methods - On app mount: call `/auth/refresh` via httpOnly cookie to restore session - Provide `isAuthenticated`, `isLoading`, `user`, `role` derived state - **Location**: `frontend/src/auth/AuthContext.tsx` + `frontend/src/auth/useAuth.ts` #### RouterConfig - **Purpose**: TanStack Router file-based route tree with integrated guards - **Responsibilities**: - File-based route structure under `frontend/src/routes/` - Root route checks `InitGuard` (setup status) before rendering any child - Authenticated routes wrapped with `ProtectedRoute` check - Role-specific routes wrapped with `RoleGuard` check - Redirect to `/login` with `?redirect=` param on auth failure - **Location**: `frontend/src/routes/` (file-based), `frontend/src/routeTree.gen.ts` (generated) --- ### Auth & Guard Components #### ProtectedRoute - **Purpose**: Redirect unauthenticated users to `/login` - **Responsibilities**: - Read `isAuthenticated` from `AuthContext` - If not authenticated: redirect to `/login?redirect={currentPath}` - If loading (session restore in progress): show loading spinner — no flash of content - If authenticated: render child route - **Location**: `frontend/src/auth/ProtectedRoute.tsx` #### RoleGuard - **Purpose**: Restrict routes to specific roles - **Responsibilities**: - Accept `allowedRoles: string[]` prop - Read `user.role` from `AuthContext` - If role not in `allowedRoles`: redirect to `/403` - If role allowed: render child route - **Location**: `frontend/src/auth/RoleGuard.tsx` #### InitGuard - **Purpose**: Redirect to `/setup` if the system is not initialized - **Responsibilities**: - On first app render: call `GET /setup/status` - If `initialized: false`: redirect all routes to `/setup` - If `initialized: true`: allow normal routing - Show loading state during check - Handle API failure with error page + retry - **Location**: `frontend/src/auth/InitGuard.tsx` --- ### Layout Components #### AppLayout - **Purpose**: Authenticated shell layout wrapping all protected pages - **Responsibilities**: - Render `Sidebar` on the left + `` (page content) on the right - Responsive: full sidebar on desktop, collapsed/hamburger on mobile - **Location**: `frontend/src/components/layout/AppLayout.tsx` #### Sidebar - **Purpose**: Navigation sidebar with role-filtered links - **Responsibilities**: - Read `user.role` from `AuthContext` to filter navigation items - Navigation items: Dashboard (all), User Management (Owner/Admin), System Settings (Owner), CMS (Owner), Profile (all) - Logout button: calls `AuthContext.logout()` - Theme toggle: calls `ThemeProvider.toggle()` - Highlight active route - Collapsible on mobile - **Location**: `frontend/src/components/layout/Sidebar.tsx` #### ThemeProvider - **Purpose**: Dark/light theme management - **Responsibilities**: - Wrap app with `next-themes` provider - Persist theme preference in `localStorage` under key `cms-theme` - Default to system preference - Expose `useTheme()` hook via next-themes - **Location**: `frontend/src/components/layout/ThemeProvider.tsx` --- ### Page Components #### LoginPage (`/login`) - **Purpose**: Unauthenticated login form - **Responsibilities**: Email + password form, react-hook-form + zod validation, calls `AuthContext.login()` - **Location**: `frontend/src/routes/login.tsx` #### SetupPage (`/setup`) - **Purpose**: First-time system initialization form - **Responsibilities**: Email + password form (owner creation), calls `POST /setup/owner`, redirects to `/login` on success - **Location**: `frontend/src/routes/setup.tsx` #### InviteCompletePage (`/invite/complete`) - **Purpose**: Invitation token completion form - **Responsibilities**: Validate token via `GET /users/validate-invitation`, show display name + password form, call `POST /users/complete-setup` - **Location**: `frontend/src/routes/invite.complete.tsx` #### DashboardPage (`/`) - **Purpose**: Authenticated home page - **Responsibilities**: Welcome widget (user name + role), availability status widget (TanStack Query) - **Location**: `frontend/src/routes/index.tsx` #### UsersPage (`/users`) - **Purpose**: User management page (Owner/Admin only) - **Responsibilities**: User list (TanStack Query), InviteUserDialog (invite form + share link step) - **Location**: `frontend/src/routes/users.tsx` #### ProfilePage (`/profile`) - **Purpose**: Read-only profile page - **Responsibilities**: Show `user.name`, `user.email`, `user.role` from `AuthContext` - **Location**: `frontend/src/routes/profile.tsx` #### SettingsPage (`/settings`) - **Purpose**: System settings page (Owner only) - **Responsibilities**: Show availability status (TanStack Query), show initialized state, placeholder sections - **Location**: `frontend/src/routes/settings.tsx` #### CmsPage (`/cms`) - **Purpose**: CMS management placeholder (Owner only) - **Responsibilities**: Work-in-progress placeholder with description of future multi-CMS management - **Location**: `frontend/src/routes/cms.tsx` #### NotFoundPage (`/404` or catch-all) - **Purpose**: 404 error page - **Location**: `frontend/src/routes/$404.tsx` #### AccessDeniedPage (`/403`) - **Purpose**: 403 forbidden page shown when role guard fails - **Location**: `frontend/src/routes/403.tsx` --- ### Shared UI Components #### InviteUserDialog - **Purpose**: Modal dialog for inviting a new user - **Responsibilities**: Two-step: (1) invite form with email + role, (2) share invite link with copy button - **Location**: `frontend/src/components/users/InviteUserDialog.tsx` #### AvailabilityStatusBadge - **Purpose**: Colour-coded availability status indicator - **Responsibilities**: Display Available (green) / Maintenance (yellow) / Unavailable (red) with reason - **Location**: `frontend/src/components/shared/AvailabilityStatusBadge.tsx` #### ErrorBoundary (Global + Per-Page) - **Purpose**: Catch unexpected React errors - **Responsibilities**: Global boundary at app root; per-page boundaries for isolated page failures; show user-friendly fallback UI - **Location**: `frontend/src/components/error/ErrorBoundary.tsx`