Adds profile and settings pages
This commit is contained in:
+279
@@ -0,0 +1,279 @@
|
||||
# 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<void>`
|
||||
- `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<void>`
|
||||
- `isSaving: boolean`
|
||||
|
||||
**Sub-component**: Renders `<AvailabilityStatusBadge>` (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 `/` |
|
||||
Reference in New Issue
Block a user