# Frontend Components — Unit 6: Profile, Settings & CMS Placeholder ## Component Hierarchy ```mermaid graph TD Auth["_authenticated.tsx\n(layout route)"] Profile["profile.tsx\nProfilePage"] PIF["ProfileInfoForm\n(name + email edit)"] CPD["ChangePasswordDialog\n(dialog overlay)"] RB["RoleDisplay\n(read-only badge)"] Settings["settings.tsx\nSettingsPage\n[RoleGuard: Owner]"] AS["AvailabilitySection\n(mode selector + message)"] ASBI["AvailabilityStatusBadge\n(reused from Unit 4)"] PM1["PlaceholderCard\nModule Management"] PM2["PlaceholderCard\nSystem Configuration"] PM3["PlaceholderCard\nBranding / Theme"] Cms["cms.tsx\nCmsPage\n[RoleGuard: Owner]"] P403["403.tsx\nAccessDeniedPage"] P404["$404.tsx\nNotFoundPage"] Auth --> Profile Auth --> Settings Auth --> Cms Auth --> P403 Auth --> P404 Profile --> PIF Profile --> CPD Profile --> RB Settings --> AS AS --> ASBI Settings --> PM1 Settings --> PM2 Settings --> PM3 classDef layout fill:#4CAF50,stroke:#2e7d32,color:#000 classDef page fill:#2196F3,stroke:#0d47a1,color:#000 classDef component fill:#9C27B0,stroke:#4a148c,color:#000 classDef reused fill:#FF9800,stroke:#e65100,color:#000 classDef error fill:#F44336,stroke:#b71c1c,color:#000 class Auth layout class Profile page class Settings page class Cms page class PIF component class CPD component class RB component class AS component class PM1 component class PM2 component class PM3 component class ASBI reused class P403 error class P404 error ``` Text alternative: _authenticated layout route contains Profile (with ProfileInfoForm, ChangePasswordDialog, RoleDisplay), Settings (Owner-only: AvailabilitySection with badge + 3 PlaceholderCards), CmsPage (Owner-only), 403, and 404. --- ## Component Specifications ### `profile.tsx` — ProfilePage **Purpose**: Displays and allows editing of the logged-in user's name and email. Shows role as read-only. Provides Change Password access. **State**: - Form state managed by `react-hook-form` - `isSaving` — boolean (PUT /Users/me in flight) - `isPasswordDialogOpen` — boolean **Props**: None (reads from `useAuth()`) **API integrations**: - `PUT /api/v1/Users/me` — save profile edits (new hook: `useUpdateProfile`) - `POST /api/v1/Auth/refresh` — called after successful profile save via `AuthContext.refresh()` **Sections**: 1. **Profile info card**: Name (text input), Email (text input), Role (read-only badge), Save button 2. **Security card**: "Change Password" button → opens `ChangePasswordDialog` **Validation** (via `zod`): ``` name: z.string().min(1, "Name is required") email: z.string().email("Invalid email address") ``` --- ### `ProfileInfoForm` **Purpose**: Form fields for name and email editing. Embedded in ProfilePage. **Props**: - `defaultValues: { name: string; email: string }` - `onSave: (data: UpdateProfileRequest) => Promise` - `isSaving: boolean` --- ### `ChangePasswordDialog` **Purpose**: Modal dialog for changing password. Opened from ProfilePage. **Props**: - `open: boolean` - `onClose: () => void` **State** (internal, via `react-hook-form`): - `currentPassword`, `newPassword`, `confirmPassword` **API integration**: `POST /api/v1/Auth/change-password` (new hook: `useChangePassword`) **Validation** (via `zod`): ``` currentPassword: z.string().min(1, "Required") newPassword: z.string() .min(8, "At least 8 characters") .regex(/[A-Z]/, "At least 1 uppercase letter") .regex(/[a-z]/, "At least 1 lowercase letter") .regex(/[0-9]/, "At least 1 digit") .regex(/[^a-zA-Z0-9]/, "At least 1 special character") confirmPassword: z.string() // .refine: confirmPassword === newPassword ``` **On success**: Close dialog, emit success toast. --- ### `RoleDisplay` **Purpose**: Read-only badge showing the user's current role. Not editable. **Props**: - `role: string` --- ### `settings.tsx` — SettingsPage **Purpose**: Owner-only management page. Contains availability controls and placeholder sections for future settings. **State**: - Availability data from `useAvailabilityStatus()` (reused from Unit 4) - `selectedMode` — controlled select: `"Available" | "Maintenance" | "Unavailable"` - `reason` — string (optional message input) - `isSaving` — boolean **Props**: None **API integrations**: - `GET /api/v1/Availability/status` — via `useAvailabilityStatus` (existing hook) - `POST /api/v1/Availability/admin/status` — via new `useUpdateAvailability` mutation; on success invalidates `useAvailabilityStatus` query **Sections**: 1. **Availability section** (`AvailabilitySection`): Current status badge + mode selector (radio or select) + optional message text area + Save button 2. **PlaceholderCard** — "Module Management" (locked, coming soon) 3. **PlaceholderCard** — "System Configuration" (locked, coming soon) 4. **PlaceholderCard** — "Branding / Theme" (locked, coming soon) --- ### `AvailabilitySection` **Purpose**: Embedded in SettingsPage. Shows current availability and provides controls to change it. **Props**: - `currentStatus: AvailabilityStatus` - `onSave: (request: UpdateAvailabilityRequest) => Promise` - `isSaving: boolean` **Sub-component**: Renders `` (reused from Unit 4, imported from `components/shared/`) --- ### `PlaceholderCard` **Purpose**: Reusable locked card for future settings sections. **Props**: - `title: string` - `description?: string` **Visual**: Dimmed card with lock icon and "Coming soon" label. --- ### `cms.tsx` — CmsPage **Purpose**: Owner-only placeholder for the future CMS feature. **Props**: None **Content**: Icon (e.g. `LayoutGrid` from lucide-react) + heading "Content Management System" + brief description: "This is where you will manage your CMS content. This feature is coming soon." **State**: None (pure presentational) --- ### `403.tsx` — AccessDeniedPage **Purpose**: Shown when RoleGuard rejects access to a route. **Props**: None **Content**: - Icon: `ShieldOff` or `Lock` (lucide-react) - Heading: "Access Denied" - Message: "You don't have permission to view this page." - Button: "Back to Dashboard" → navigates to `/` **State**: None (pure presentational) --- ### `$404.tsx` — NotFoundPage **Purpose**: TanStack Router catch-all for unknown routes. **Props**: None **Content**: - Icon: `FileQuestion` (lucide-react) - Heading: "Page Not Found" - Message: "The page you're looking for doesn't exist." - Button: "Back to Dashboard" → navigates to `/` **State**: None (pure presentational) --- ## New API Hooks | Hook | File | Endpoint | Method | |------|------|----------|--------| | `useUpdateProfile` | `src/api/useProfile.ts` | `PUT /api/v1/Users/me` | useMutation | | `useChangePassword` | `src/api/useProfile.ts` | `POST /api/v1/Auth/change-password` | useMutation | | `useUpdateAvailability` | `src/api/useAvailability.ts` (extend existing) | `POST /api/v1/Availability/admin/status` | useMutation | ## New Backend Endpoints | Endpoint | Method | Controller | Policy | |----------|--------|------------|--------| | `/api/v1/Users/me` | PUT | UsersController | Authenticated | | `/api/v1/Auth/change-password` | POST | AuthController | Authenticated | ## i18n Keys Required **Profile page**: - `profile.title`, `profile.name`, `profile.email`, `profile.role`, `profile.save`, `profile.saving`, `profile.saveSuccess` - `profile.changePassword`, `profile.changePassword.current`, `profile.changePassword.new`, `profile.changePassword.confirm`, `profile.changePassword.success` **Settings page**: - `settings.title`, `settings.availability.title`, `settings.availability.save`, `settings.availability.saveSuccess` - `settings.availability.mode.available`, `settings.availability.mode.maintenance`, `settings.availability.mode.unavailable` - `settings.availability.reason`, `settings.placeholder.comingSoon` - `settings.modules.title`, `settings.systemConfig.title`, `settings.branding.title` **Error pages**: - `error.403.title`, `error.403.message`, `error.404.title`, `error.404.message`, `error.backToDashboard` ## Unit Test Scope (Q8: C — all pages) | Component | Test focus | |-----------|------------| | ProfilePage | Renders AuthContext user data; name/email inputs; save triggers PUT; password dialog opens | | ChangePasswordDialog | Validation (mismatch, policy); success flow; error handling | | SettingsPage | Fetches and displays availability; mode change + save triggers POST; placeholder sections render | | CmsPage | Renders heading and description | | AccessDeniedPage (403) | Renders heading, message, Back to Dashboard navigates to `/` | | NotFoundPage (404) | Renders heading, message, Back to Dashboard navigates to `/` |