# Units of Work — CMS Frontend ## Overview The CMS frontend is decomposed into **7 sequential units** (Unit 0–6). Each unit builds on the previous; dependencies flow strictly forward. All units together produce a single deployable SPA + updated backend API. ```mermaid graph LR U0["Unit 0\nBackend Prerequisites"] U1["Unit 1\nProject Scaffold"] U2["Unit 2\nAuth Pages"] U3["Unit 3\nLayout & Nav"] U4["Unit 4\nDashboard"] U5["Unit 5\nUser Management"] U6["Unit 6\nRemaining Pages"] U0 --> U1 --> U2 --> U3 --> U4 --> U5 --> U6 style U0 fill:#4CAF50,stroke:#2E7D32,color:#fff style U1 fill:#FFC107,stroke:#F57F17,color:#000 style U2 fill:#FF5722,stroke:#BF360C,color:#fff style U3 fill:#9C27B0,stroke:#4A148C,color:#fff style U4 fill:#2196F3,stroke:#0D47A1,color:#fff style U5 fill:#009688,stroke:#004D40,color:#fff style U6 fill:#607D8B,stroke:#263238,color:#fff ``` --- ## Unit 0 — Backend Prerequisites **Type**: Backend (.NET) — blocking prerequisite for all frontend units **Priority**: Must complete before Unit 1 ### Deliverables - `ServiceCollectionExtensions.cs` — add `AddCorsFrontendPolicy()` reading `Cors:AllowedOrigins[]` from config - `Program.cs` — add `app.UseCors("FrontendPolicy")` before `UseAuthentication` - `AuthController.cs` — update `Login`, `Refresh`, `Revoke` to use httpOnly cookie - `RefreshTokenRequest.cs` — **remove** (no backward compatibility needed; refresh now uses httpOnly cookie) - `TokenResponse.cs` — add `Name` property (display name from `UserName` or `Email`) - `appsettings.json` — add `Cors` section (production placeholder) - `appsettings.Development.json` — add `Cors:AllowedOrigins: ["http://localhost:5173"]` - Tests — update `AuthController` tests for cookie-based flow ### Key Decisions - Cookie name: `refreshToken`, Path: `/api/v1/auth`, HttpOnly, Secure, SameSite=Strict - CORS: `AllowCredentials()` required for cookie flow - `name` field in `TokenResponse` maps from `user.UserName ?? user.Email` --- ## Unit 1 — Project Scaffold & Infrastructure **Type**: Frontend — foundation for all subsequent units **Depends on**: Unit 0 (API must be reachable with CORS) ### Deliverables ``` frontend/ ├── src/ │ ├── api/ │ │ ├── client.ts # ApiClient: fetch + auth header + 401 interceptor │ │ └── types.ts # Shared TS interfaces (User, AuthResponse, etc.) │ ├── auth/ │ │ ├── AuthContext.tsx # AuthProvider: in-memory token + user state │ │ └── useAuth.ts # useAuth hook │ ├── components/ │ │ ├── layout/ │ │ │ └── ThemeProvider.tsx │ │ └── error/ │ │ └── ErrorBoundary.tsx │ ├── main.tsx # Entry: QueryClientProvider + AuthProvider + RouterProvider │ └── app.tsx # App root ├── .env.example # VITE_API_BASE_URL=http://localhost:5000 ├── .env # gitignored ├── vite.config.ts ├── tailwind.config.ts # Primary color #ac0000 ├── tsconfig.json └── package.json # pnpm workspace ``` ### Key Decisions - TanStack Router file-based routing initialized (empty route tree) - shadcn/ui configured with `#ac0000` primary - `ApiClient` created; `AuthContext` wired up; `QueryClient` configured --- ## Unit 2 — Authentication Pages **Type**: Frontend — public + session management **Depends on**: Unit 1 (ApiClient, AuthContext) ### Deliverables ``` frontend/src/ ├── auth/ │ ├── ProtectedRoute.tsx # Redirects unauthenticated → /login │ ├── RoleGuard.tsx # Redirects wrong role → /403 │ └── InitGuard.tsx # Redirects uninitialized → /setup ├── api/ │ └── useSetup.ts # useSetupStatus, useCreateOwner └── routes/ ├── __root.tsx # Root route: InitGuard + global ErrorBoundary ├── login.tsx # LoginPage ├── setup.tsx # SetupPage └── invite.complete.tsx # InviteCompletePage ``` ### Key Decisions - `react-hook-form` + `zod` for all forms; password schema enforces exact backend rules - `InitGuard` in `__root.tsx` blocks all routes until setup status confirmed - `ProtectedRoute` uses `beforeLoad` in TanStack Router --- ## Unit 3 — Layout & Navigation **Type**: Frontend — authenticated shell **Depends on**: Unit 2 (ProtectedRoute, RoleGuard, AuthContext) ### Deliverables ``` frontend/src/ ├── components/layout/ │ ├── AppLayout.tsx # Shell: Sidebar + │ └── Sidebar.tsx # Role-filtered nav, logout, theme toggle └── routes/ └── _authenticated.tsx # Authenticated layout route wrapping all protected pages ``` ### Key Decisions - `_authenticated.tsx` is a TanStack Router layout route — all child routes inherit `ProtectedRoute` - Sidebar filters nav items by `user.role` from `AuthContext` - Responsive: hamburger menu on mobile --- ## Unit 4 — Dashboard **Type**: Frontend — first authenticated page **Depends on**: Unit 3 (AppLayout, AuthContext) ### Deliverables ``` frontend/src/ ├── api/ │ └── useAvailability.ts # useAvailabilityStatus (staleTime: 30s) ├── components/shared/ │ └── AvailabilityStatusBadge.tsx └── routes/ └── index.tsx # DashboardPage: welcome + availability widget ``` --- ## Unit 5 — User Management **Type**: Frontend — Owner/Admin feature **Depends on**: Unit 3 (AppLayout, RoleGuard), Unit 4 (patterns established) ### Deliverables ``` frontend/src/ ├── api/ │ ├── useUsers.ts # useUsers, useInviteUser │ └── useInvitation.ts # useValidateInvitation, useCompleteSetup ├── components/users/ │ └── InviteUserDialog.tsx # Two-step: invite form → share link └── routes/ └── users.tsx # UsersPage: user table + invite dialog ``` --- ## Unit 6 — Remaining Pages & Documentation **Type**: Frontend + Documentation **Depends on**: Unit 3 (AppLayout, RoleGuard) ### Deliverables ``` frontend/src/routes/ ├── profile.tsx # ProfilePage: read-only user info ├── settings.tsx # SettingsPage: Owner-only, availability + placeholders ├── cms.tsx # CmsPage: Owner-only placeholder ├── 403.tsx # AccessDeniedPage └── $404.tsx # NotFoundPage ``` **Documentation**: - `README.md` — add **Frontend Development** section: - Prerequisites (Node.js, pnpm) - `pnpm install` in `frontend/` - `.env` setup (copy `.env.example`) - `pnpm dev` to start dev server - `pnpm build` for production build - Security headers note for production deployment