8.7 KiB
Frontend Components — Unit 6: Profile, Settings & CMS Placeholder
Component Hierarchy
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 viaAuthContext.refresh()
Sections:
- Profile info card: Name (text input), Email (text input), Role (read-only badge), Save button
- 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: booleanonClose: () => 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— viauseAvailabilityStatus(existing hook)POST /api/v1/Availability/admin/status— via newuseUpdateAvailabilitymutation; on success invalidatesuseAvailabilityStatusquery
Sections:
- Availability section (
AvailabilitySection): Current status badge + mode selector (radio or select) + optional message text area + Save button - PlaceholderCard — "Module Management" (locked, coming soon)
- PlaceholderCard — "System Configuration" (locked, coming soon)
- PlaceholderCard — "Branding / Theme" (locked, coming soon)
AvailabilitySection
Purpose: Embedded in SettingsPage. Shows current availability and provides controls to change it.
Props:
currentStatus: AvailabilityStatusonSave: (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: stringdescription?: 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:
ShieldOfforLock(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.saveSuccessprofile.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.saveSuccesssettings.availability.mode.available,settings.availability.mode.maintenance,settings.availability.mode.unavailablesettings.availability.reason,settings.placeholder.comingSoonsettings.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 / |