Adds profile and settings pages
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
# Code Generation Plan — Unit 6: Profile, Settings & CMS Placeholder
|
||||
|
||||
**Status**: 🚧 In Progress
|
||||
|
||||
## Unit Context
|
||||
|
||||
**Unit**: Unit 6 — Profile, Settings & CMS Placeholder
|
||||
**Type**: Frontend (React/TypeScript) + Backend (.NET) — brownfield (modifying existing stubs)
|
||||
**Workspace root**: `K:\Development\Projects\SlpModularCms`
|
||||
**Stories covered**: US-15, US-16, US-17, US-20
|
||||
|
||||
## Key Observations from Code Scan
|
||||
|
||||
**Existing files to MODIFY** (not create):
|
||||
- `frontend/src/pages/ProfilePage.tsx` — currently a "Coming soon" stub
|
||||
- `frontend/src/pages/SettingsPage.tsx` — currently a "Coming soon" stub
|
||||
- `frontend/src/pages/CmsPage.tsx` — currently a "Coming soon" stub
|
||||
- `frontend/src/api/useAvailability.ts` — has `useAvailabilityStatus` read query; needs `useUpdateAvailability` mutation added
|
||||
- `frontend/src/router.tsx` — has all routes except 403 and 404; needs catch-all added
|
||||
- `frontend/src/mocks/users/handlers.ts` — needs `PUT /api/v1/Users/me` mock added
|
||||
- `frontend/src/mocks/auth/handlers.ts` — needs `POST /api/v1/Auth/change-password` mock added
|
||||
- `frontend/src/i18n/locales/en/translation.json` — needs profile, settings, error page keys
|
||||
- `frontend/src/i18n/locales/nl/translation.json` — needs Dutch translations
|
||||
- `src/SlpModularCms.Core/Identity/Models/IdentityRequests.cs` — needs `UpdateProfileRequest` + `ChangePasswordRequest`
|
||||
- `src/SlpModularCms.Core/Identity/Services/IAuthService.cs` — needs `ChangePassword` method
|
||||
- `src/SlpModularCms.Core/Identity/Services/AuthService.cs` — needs `ChangePassword` implementation
|
||||
- `src/SlpModularCms.Modules.Identity/Controllers/AuthController.cs` — needs `POST /change-password` endpoint
|
||||
- `src/SlpModularCms.Modules.Identity/Controllers/UsersController.cs` — needs `PUT /me` endpoint
|
||||
|
||||
**New files to CREATE**:
|
||||
- `frontend/src/api/useProfile.ts` — `useUpdateProfile` mutation + `useChangePassword` mutation
|
||||
- `frontend/src/pages/AccessDeniedPage.tsx` — 403 page
|
||||
- `frontend/src/pages/NotFoundPage.tsx` — 404 page
|
||||
- `frontend/src/pages/ProfilePage.test.tsx`
|
||||
- `frontend/src/pages/SettingsPage.test.tsx`
|
||||
- `frontend/src/pages/CmsPage.test.tsx`
|
||||
- `frontend/src/pages/AccessDeniedPage.test.tsx`
|
||||
- `frontend/src/pages/NotFoundPage.test.tsx`
|
||||
|
||||
---
|
||||
|
||||
## Stories
|
||||
|
||||
- [x] US-15: View own profile — ProfilePage with name/email edit + change password
|
||||
- [x] US-16: View availability status in settings — SettingsPage with availability controls
|
||||
- [x] US-17: Access denied to System Settings for non-Owners — 403 page + RoleGuard (already wired in router)
|
||||
- [x] US-20: View CMS management placeholder — CmsPage enhanced placeholder
|
||||
|
||||
---
|
||||
|
||||
## Generation Steps
|
||||
|
||||
### Backend
|
||||
|
||||
- [x] **Step 1**: Modify `src/SlpModularCms.Core/Identity/Models/IdentityRequests.cs`
|
||||
- Add `record UpdateProfileRequest(string Name, string Email)`
|
||||
- Add `record ChangePasswordRequest(string CurrentPassword, string NewPassword)`
|
||||
|
||||
- [x] **Step 2**: Modify `src/SlpModularCms.Core/Identity/Services/IAuthService.cs`
|
||||
- Add `Task ChangePasswordAsync(Guid userId, string currentPassword, string newPassword)`
|
||||
|
||||
- [x] **Step 3**: Modify `src/SlpModularCms.Core/Identity/Services/AuthService.cs`
|
||||
- Implement `ChangePasswordAsync` — uses `UserManager.ChangePasswordAsync`; throws `ValidationException` if current password is wrong
|
||||
|
||||
- [x] **Step 4**: Modify `src/SlpModularCms.Modules.Identity/Controllers/AuthController.cs`
|
||||
- Add `POST /change-password` endpoint — `[Authorize]` (any authenticated user), reads userId from JWT claims, calls `IAuthService.ChangePasswordAsync`
|
||||
- Returns 200 OK on success, 400 with error detail on failure
|
||||
|
||||
- [x] **Step 5**: Modify `src/SlpModularCms.Modules.Identity/Controllers/UsersController.cs`
|
||||
- Add `PUT /me` endpoint — `[Authorize]` (any authenticated user), reads userId from JWT claims
|
||||
- Updates `UserName` (display name) and `Email` on the `ApplicationUser` via `UserManager`
|
||||
- Returns `UserDto` with updated data on 200 OK; returns 400 if email already taken
|
||||
|
||||
### Frontend — API layer
|
||||
|
||||
- [x] **Step 6**: Create `frontend/src/api/useProfile.ts`
|
||||
- `useUpdateProfile()` — `useMutation` calling `PUT /api/v1/Users/me` with `{ name, email }`; on success calls `auth.refresh()` from `useAuth()`
|
||||
- `useChangePassword()` — `useMutation` calling `POST /api/v1/Auth/change-password` with `{ currentPassword, newPassword }`
|
||||
|
||||
- [x] **Step 7**: Modify `frontend/src/api/useAvailability.ts`
|
||||
- Add `useUpdateAvailability()` — `useMutation` calling `POST /api/v1/Availability/admin/status` with `{ newStatus, reason }`; on success calls `queryClient.invalidateQueries({ queryKey: ['availability', 'status'] })`
|
||||
|
||||
- [x] **Step 8**: Add MSW mock handlers
|
||||
- In `frontend/src/mocks/users/handlers.ts`: add `http.put('/api/v1/Users/me', ...)` returning updated UserDto
|
||||
- In `frontend/src/mocks/auth/handlers.ts`: add `http.post('/api/v1/Auth/change-password', ...)` returning 200 OK (and a 400 error handler variant for wrong-password tests)
|
||||
|
||||
### Frontend — i18n keys
|
||||
|
||||
- [x] **Step 9**: Modify `frontend/src/i18n/locales/en/translation.json` — add:
|
||||
```json
|
||||
"profile": {
|
||||
"title": "My Profile",
|
||||
"name": "Full name",
|
||||
"email": "Email address",
|
||||
"role": "Role",
|
||||
"save": "Save changes",
|
||||
"saving": "Saving…",
|
||||
"saveSuccess": "Profile updated successfully",
|
||||
"changePassword": "Change password",
|
||||
"changePassword": {
|
||||
"title": "Change Password",
|
||||
"current": "Current password",
|
||||
"new": "New password",
|
||||
"confirm": "Confirm new password",
|
||||
"submit": "Change password",
|
||||
"submitting": "Changing…",
|
||||
"success": "Password changed successfully",
|
||||
"errorMismatch": "Passwords do not match",
|
||||
"errorCurrent": "Current password is incorrect"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "System Settings",
|
||||
"availability": {
|
||||
"title": "System Availability",
|
||||
"mode": "Availability mode",
|
||||
"reason": "Message (optional)",
|
||||
"save": "Update availability",
|
||||
"saving": "Updating…",
|
||||
"saveSuccess": "Availability updated",
|
||||
"modes": {
|
||||
"Available": "Available",
|
||||
"Maintenance": "Maintenance",
|
||||
"Unavailable": "Unavailable"
|
||||
}
|
||||
},
|
||||
"modules": { "title": "Module Management", "comingSoon": "Coming soon" },
|
||||
"systemConfig": { "title": "System Configuration", "comingSoon": "Coming soon" },
|
||||
"branding": { "title": "Branding / Theme", "comingSoon": "Coming soon" }
|
||||
},
|
||||
"error": {
|
||||
"403": {
|
||||
"title": "Access Denied",
|
||||
"message": "You don't have permission to view this page."
|
||||
},
|
||||
"404": {
|
||||
"title": "Page Not Found",
|
||||
"message": "The page you're looking for doesn't exist."
|
||||
},
|
||||
"backToDashboard": "Back to Dashboard"
|
||||
}
|
||||
```
|
||||
|
||||
- [x] **Step 10**: Modify `frontend/src/i18n/locales/nl/translation.json` — add Dutch translations for all keys added in Step 9
|
||||
|
||||
### Frontend — pages
|
||||
|
||||
- [x] **Step 11**: Modify `frontend/src/pages/ProfilePage.tsx` — full implementation:
|
||||
- Use `useAuth()` for current user data (name, email, role)
|
||||
- Use `useUpdateProfile()` + `react-hook-form` + `zod` for name/email form
|
||||
- Render Role as read-only badge
|
||||
- Include "Change Password" button that opens `ChangePasswordDialog`
|
||||
- `ChangePasswordDialog` (inline in file or as separate component in same directory): uses `useChangePassword()` with own `react-hook-form` + `zod` validation
|
||||
- All interactive elements get `data-testid` attributes
|
||||
|
||||
- [x] **Step 12**: Modify `frontend/src/pages/SettingsPage.tsx` — full implementation:
|
||||
- Use `useAvailabilityStatus()` to fetch current status
|
||||
- Use `useUpdateAvailability()` for saving changes
|
||||
- Mode selector (radio group or select) + optional reason text area
|
||||
- Render `AvailabilityStatusBadge` showing current status
|
||||
- Three `PlaceholderCard` sections: Module Management, System Configuration, Branding/Theme
|
||||
- All interactive elements get `data-testid` attributes
|
||||
|
||||
- [x] **Step 13**: Modify `frontend/src/pages/CmsPage.tsx` — enhanced placeholder:
|
||||
- Icon (`LayoutGrid` from lucide-react) + heading "Content Management System" + description text
|
||||
- Use i18n keys; add `data-testid="cms-placeholder"`
|
||||
|
||||
- [x] **Step 14**: Create `frontend/src/pages/AccessDeniedPage.tsx`
|
||||
- Icon (`ShieldOff` from lucide-react) + heading from `t('error.403.title')` + message + "Back to Dashboard" button
|
||||
- `data-testid` on heading, message, and button
|
||||
|
||||
- [x] **Step 15**: Create `frontend/src/pages/NotFoundPage.tsx`
|
||||
- Icon (`FileQuestion` from lucide-react) + heading from `t('error.404.title')` + message + "Back to Dashboard" button
|
||||
- `data-testid` on heading, message, and button
|
||||
|
||||
### Frontend — router
|
||||
|
||||
- [x] **Step 16**: Modify `frontend/src/router.tsx`
|
||||
- Add `accessDeniedRoute` at path `/403` (under rootRoute, not authenticatedRoute — accessible without auth)
|
||||
- Add `notFoundRoute` as catch-all `$` path (under rootRoute)
|
||||
- Add both routes to `routeTree`
|
||||
|
||||
### Frontend — tests
|
||||
|
||||
- [x] **Step 17**: Create `frontend/src/pages/ProfilePage.test.tsx`
|
||||
- Test: renders name, email, role from AuthContext
|
||||
- Test: editing name and email and clicking Save triggers `PUT /api/v1/Users/me`
|
||||
- Test: validation errors shown for empty name or invalid email
|
||||
- Test: "Change Password" button opens dialog
|
||||
- Test: change password form validates (mismatch, empty fields)
|
||||
- Test: successful password change closes dialog
|
||||
|
||||
- [x] **Step 18**: Create `frontend/src/pages/SettingsPage.test.tsx`
|
||||
- Test: renders current availability status (mocked via MSW)
|
||||
- Test: changing mode and clicking save triggers `POST /api/v1/Availability/admin/status`
|
||||
- Test: placeholder sections render with correct headings
|
||||
|
||||
- [x] **Step 19**: Create `frontend/src/pages/CmsPage.test.tsx`
|
||||
- Test: renders heading and placeholder description
|
||||
|
||||
- [x] **Step 20**: Create `frontend/src/pages/AccessDeniedPage.test.tsx`
|
||||
- Test: renders "Access Denied" heading and message
|
||||
- Test: "Back to Dashboard" button navigates to `/dashboard`
|
||||
|
||||
- [x] **Step 21**: Create `frontend/src/pages/NotFoundPage.test.tsx`
|
||||
- Test: renders "Page Not Found" heading and message
|
||||
- Test: "Back to Dashboard" button navigates to `/dashboard`
|
||||
|
||||
### Verification
|
||||
|
||||
- [x] **Step 22**: Backend migration check
|
||||
- Review all new/modified entity classes and `ApplicationDbContext` to determine if EF Core migrations are needed
|
||||
- Unit 6 backend changes: `PUT /Users/me` updates `ApplicationUser.UserName` and `Email` (existing columns in `AspNetUsers`); `POST /Auth/change-password` updates the password hash (existing column) — **no schema changes expected**
|
||||
- Verify by checking that no new `DbSet<>` properties or `[Column]`/`HasColumnName` changes were introduced in Steps 1–5
|
||||
- If a schema change is found: run `dotnet ef migrations add <MigrationName> --project src/SlpModularCms.Core --startup-project src/SlpModularCms.Api` and include the generated migration files
|
||||
- If no schema change: document explicitly in the code summary (Step 25) that no migration was needed
|
||||
|
||||
- [x] **Step 23**: i18n completeness check
|
||||
- For every `t('...')` call added in Steps 11–16 (ProfilePage, SettingsPage, CmsPage, AccessDeniedPage, NotFoundPage, router), verify the key exists in both `en/translation.json` and `nl/translation.json`
|
||||
- For every key added in Steps 9–10 to `en/translation.json` and `nl/translation.json`, verify it is actually used via a `t('...')` call in at least one component — remove unused keys
|
||||
- Fix any missing or orphaned keys before proceeding
|
||||
|
||||
### Documentation
|
||||
|
||||
- [x] **Step 24**: Update `README.md` — add "Frontend Development" section:
|
||||
- Prerequisites: Node.js ≥20, pnpm
|
||||
- Installation: `pnpm install` in `frontend/`
|
||||
- Environment: copy `frontend/.env.example` to `frontend/.env`
|
||||
- Dev server: `pnpm dev` in `frontend/` (runs on http://localhost:5173)
|
||||
- Build: `pnpm build` in `frontend/`
|
||||
- Tests: `pnpm test` in `frontend/`
|
||||
- Security note: production deployment should add security headers (CSP, HSTS)
|
||||
|
||||
### Code summary
|
||||
|
||||
- [x] **Step 25**: Create `aidlc-docs/features/cms-frontend/construction/unit-6/code/code-generation-summary.md`
|
||||
- List all modified and created files with their purpose
|
||||
- Include migration check outcome (Step 22)
|
||||
- Include i18n check outcome (Step 23)
|
||||
|
||||
---
|
||||
|
||||
## Total: 25 steps
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
# Unit 6 Functional Design — Clarification Questions
|
||||
|
||||
I detected gaps in your responses that need clarification before I can complete the functional design.
|
||||
|
||||
## Gap: Backend endpoints missing for profile editing and password change
|
||||
|
||||
Your answers to Q1 and Q2 indicate the ProfilePage should:
|
||||
- Allow editing **Name** and **Email** (Q2: C)
|
||||
- Show a **Change Password** button (Q1: C, note "only Role is read-only")
|
||||
|
||||
However, the current backend API has **no endpoints** for these operations:
|
||||
|
||||
| Required operation | Needed endpoint | Currently exists? |
|
||||
|---|---|---|
|
||||
| Update name / email | `PUT /api/v1/Users/me` or `PATCH /api/v1/Users/{id}` | ❌ No |
|
||||
| Change password | `POST /api/v1/Auth/change-password` or similar | ❌ No |
|
||||
| Read own profile | `GET /api/v1/Users/me` (optional, or use AuthContext) | ❌ No |
|
||||
|
||||
These are additional backend changes not originally scoped for Unit 6. I need your decisions on each.
|
||||
|
||||
---
|
||||
|
||||
## Clarification Question 1: Backend endpoint for profile update (name + email)
|
||||
|
||||
Editing name and email requires a new backend endpoint. How should this be handled?
|
||||
|
||||
A) Add `PUT /api/v1/Users/me` to the backend in this unit — accepts `{ "name": string, "email": string }` and updates the current user's profile (recommended — required for the edit feature to function)
|
||||
B) Add a backend endpoint but only allow editing name — email changes are too sensitive (require email verification flow which is out of scope)
|
||||
C) Make the edit fields visible in the UI but non-functional for now (placeholder edits — form renders but the save button is disabled or shows "coming soon")
|
||||
D) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
---
|
||||
|
||||
## Clarification Question 2: Change Password — functional or placeholder?
|
||||
|
||||
The "Change Password" button requires a new backend endpoint. How should this be handled in this unit?
|
||||
|
||||
A) Implement Change Password fully: add `POST /api/v1/Auth/change-password` to the backend — accepts `{ "currentPassword": string, "newPassword": string }` and changes the password (requires full backend + frontend implementation)
|
||||
B) Show a "Change Password" button that opens a dialog, but the submit is a placeholder — shows "This feature is coming soon" (frontend only, no backend work)
|
||||
C) Show a "Change Password" link/button but leave it disabled with a tooltip "Coming soon"
|
||||
D) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
---
|
||||
|
||||
## Clarification Question 3: Profile data source after editing
|
||||
|
||||
When the user edits their name/email and saves, the AuthContext currently holds the logged-in user's data (set at login and refreshed on token refresh). After a successful profile update, how should the frontend reflect the change?
|
||||
|
||||
A) Call `auth/refresh` after a successful profile update — the refresh response returns updated user data, which updates AuthContext automatically (recommended — reuses existing infrastructure)
|
||||
B) Update AuthContext directly in the frontend after a successful save (optimistic update — no extra API call)
|
||||
C) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
@@ -0,0 +1,154 @@
|
||||
# Functional Design Plan — Unit 6: Profile, Settings & CMS Placeholder
|
||||
|
||||
**Status**: 🚧 In Progress
|
||||
|
||||
## Unit Context
|
||||
|
||||
**Unit**: Unit 6 — Profile, Settings & CMS Placeholder
|
||||
**Type**: Frontend (React/TypeScript) + Documentation
|
||||
**Depends on**: Unit 3 (AppLayout, RoleGuard), Unit 4 (useAvailabilityStatus, AvailabilityStatusBadge)
|
||||
**Stories Covered**:
|
||||
- US-15: View own profile
|
||||
- US-16: View availability status in settings
|
||||
- US-17: Access denied to System Settings for non-Owners
|
||||
- US-20: View CMS management placeholder
|
||||
|
||||
**Key Deliverables** (from unit-of-work.md):
|
||||
```
|
||||
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
|
||||
|
||||
README.md # Frontend Development section added
|
||||
```
|
||||
|
||||
**Key Observations**:
|
||||
- These are the final frontend pages; all shared infrastructure (ApiClient, AuthContext, AppLayout, RoleGuard) is already in place
|
||||
- `useAvailabilityStatus` from Unit 4 can be reused directly in SettingsPage
|
||||
- RoleGuard (from Unit 2) is already implemented; `/settings` and `/cms` need Owner-only guards
|
||||
- The 403 and 404 pages can be pure presentational components (no data fetching)
|
||||
- Routes `403.tsx` and `$404.tsx` need to be registered in the TanStack Router route tree
|
||||
|
||||
---
|
||||
|
||||
## Functional Design Steps
|
||||
|
||||
- [x] Step 1: Analyze unit context and existing code patterns
|
||||
- [x] Step 2: Generate questions, collect answers
|
||||
- [x] Step 3: Create `domain-entities.md`
|
||||
- [x] Step 4: Create `business-rules.md`
|
||||
- [x] Step 5: Create `business-logic-model.md`
|
||||
- [x] Step 6: Create `frontend-components.md`
|
||||
- [x] Step 7: Present completion message and await approval
|
||||
|
||||
---
|
||||
|
||||
## Questions
|
||||
|
||||
Please fill in the letter after each `[Answer]:` tag.
|
||||
|
||||
---
|
||||
|
||||
### Question 1: ProfilePage — displayed fields
|
||||
|
||||
The ProfilePage (US-15) should show the logged-in user's own information. Which fields should be displayed?
|
||||
|
||||
A) Name, Email, Role — three read-only fields sourced from AuthContext (no API call needed)
|
||||
B) Name, Email, Role, Member Since (created date) — four fields; requires a `GET /api/v1/Users/{id}` or `/me` endpoint to fetch the date
|
||||
C) Name, Email, Role, and a "Change Password" button (button can be a placeholder/not yet functional)
|
||||
D) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: C, but only Role is read-only
|
||||
|
||||
---
|
||||
|
||||
### Question 2: ProfilePage — editability
|
||||
|
||||
Should the ProfilePage support editing any of the displayed information, or is it strictly read-only for now?
|
||||
|
||||
A) Strictly read-only — no edit capability in this unit (recommended — keeps scope minimal; edits can be a future feature)
|
||||
B) Allow editing the display name only — inline edit with save
|
||||
C) Allow editing email and/or name — full form
|
||||
D) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: C
|
||||
|
||||
---
|
||||
|
||||
### Question 3: SettingsPage — availability section interaction
|
||||
|
||||
The SettingsPage (US-16) includes an availability section. Should it allow changing the availability mode, or just display the current status?
|
||||
|
||||
A) Display + change: show current mode with controls to switch between Online / Maintenance / Offline, and optionally set a custom message (recommended — Settings is the management location; Dashboard is read-only view)
|
||||
B) Display only: show current availability status, link/note that says "Manage via API or a future admin tool"
|
||||
C) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
---
|
||||
|
||||
### Question 4: SettingsPage — placeholder sections
|
||||
|
||||
Beyond the availability section, what placeholder sections should SettingsPage include to indicate planned future features?
|
||||
|
||||
A) One generic placeholder: "More settings coming soon"
|
||||
B) Two placeholders: "Module Management" and "System Configuration" — with "Coming soon" labels
|
||||
C) Three placeholders: "Module Management", "System Configuration", and "Branding / Theme" — styled as locked cards
|
||||
D) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: C
|
||||
|
||||
---
|
||||
|
||||
### Question 5: CmsPage — placeholder content
|
||||
|
||||
The CmsPage (US-20) is an Owner-only placeholder. What should it display?
|
||||
|
||||
A) A simple "CMS coming soon" message with an icon and brief description of what will be here (recommended — clear intent without overdesigning)
|
||||
B) A styled empty state card with a title "Content Management System" and a description of planned capabilities
|
||||
C) Just a heading: "Content Management" with "Under construction" text
|
||||
D) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
---
|
||||
|
||||
### Question 6: 403 AccessDeniedPage — navigation options
|
||||
|
||||
When a user hits a route they don't have permission for (e.g., a non-Owner accessing `/settings`), what should the 403 page offer?
|
||||
|
||||
A) Heading "Access Denied" + message explaining the page requires a higher role + "Back to Dashboard" button (recommended — clear and actionable)
|
||||
B) Heading + message + "Go Back" (browser back) + "Back to Dashboard" buttons
|
||||
C) Heading + message only — no navigation buttons (user uses browser back)
|
||||
D) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
---
|
||||
|
||||
### Question 7: 404 NotFoundPage — navigation options
|
||||
|
||||
When a user navigates to a route that doesn't exist, what should the 404 page offer?
|
||||
|
||||
A) Heading "Page Not Found" + brief message + "Back to Dashboard" button (recommended)
|
||||
B) Heading + message + "Go Back" (browser back) + "Back to Dashboard" buttons
|
||||
C) Heading + message only
|
||||
D) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: A
|
||||
|
||||
---
|
||||
|
||||
### Question 8: Unit test scope
|
||||
|
||||
Which parts of Unit 6 should have unit tests?
|
||||
|
||||
A) ProfilePage (renders correct user fields from AuthContext) + SettingsPage (availability display/interaction) + 403 page (renders message + navigation) + 404 page (renders message + navigation)
|
||||
B) SettingsPage only — the other pages are too simple to warrant tests
|
||||
C) All pages including CmsPage — full test coverage for the final unit
|
||||
D) Other (please describe after [Answer]: tag below)
|
||||
|
||||
[Answer]: C
|
||||
@@ -0,0 +1,81 @@
|
||||
# Unit 6 Code Generation Summary
|
||||
|
||||
## Overview
|
||||
|
||||
Unit 6 implements Profile, Settings, CMS placeholder, and error pages (403/404) for the CMS Frontend.
|
||||
|
||||
## Migration Check
|
||||
|
||||
**No EF Core migration required.** All backend changes in Steps 1–5 operate on existing columns in the `AspNetUsers` table managed by ASP.NET Identity (`UserName`, `NormalizedUserName`, `Email`, `NormalizedEmail`, `PasswordHash`). No new `DbSet<>` entries or schema columns were introduced.
|
||||
|
||||
---
|
||||
|
||||
## Files Created or Modified
|
||||
|
||||
### Backend — Core
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/SlpModularCms.Core/Identity/Models/IdentityRequests.cs` | Added `UpdateProfileRequest` and `ChangePasswordRequest` records |
|
||||
| `src/SlpModularCms.Core/Identity/Services/IAuthService.cs` | Added `ChangePasswordAsync` method signature |
|
||||
| `src/SlpModularCms.Core/Identity/Services/AuthService.cs` | Implemented `ChangePasswordAsync` using `UserManager.ChangePasswordAsync` |
|
||||
|
||||
### Backend — Modules.Identity
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/SlpModularCms.Modules.Identity/Controllers/AuthController.cs` | Added `POST /api/v1/Auth/change-password` endpoint |
|
||||
| `src/SlpModularCms.Modules.Identity/Controllers/UsersController.cs` | Added `PUT /api/v1/Users/me` endpoint for profile self-update |
|
||||
|
||||
### Frontend — API hooks
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `frontend/src/api/useProfile.ts` | **Created** — `useUpdateProfile()` and `useChangePassword()` hooks |
|
||||
| `frontend/src/api/useAvailability.ts` | Extended with `useUpdateAvailability()` mutation |
|
||||
|
||||
### Frontend — MSW mocks
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `frontend/src/mocks/users/handlers.ts` | Added `PUT /api/v1/Users/me` mock handler |
|
||||
| `frontend/src/mocks/auth/handlers.ts` | Added `POST /api/v1/Auth/change-password` mock handler |
|
||||
|
||||
### Frontend — i18n
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `frontend/src/i18n/locales/en/translation.json` | Added `profile`, `settings`, `cms`, `error` key groups |
|
||||
| `frontend/src/i18n/locales/nl/translation.json` | Added Dutch translations for all new key groups |
|
||||
|
||||
### Frontend — Pages
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `frontend/src/pages/ProfilePage.tsx` | **Replaced stub** — full implementation with ProfileInfoForm, RoleDisplay badge, ChangePasswordDialog |
|
||||
| `frontend/src/pages/SettingsPage.tsx` | **Replaced stub** — full implementation with availability selector + 3 placeholder cards |
|
||||
| `frontend/src/pages/CmsPage.tsx` | **Replaced stub** — enhanced placeholder with LayoutGrid icon |
|
||||
| `frontend/src/pages/AccessDeniedPage.tsx` | **Created** — 403 page with ShieldOff icon and back button |
|
||||
| `frontend/src/pages/NotFoundPage.tsx` | **Created** — 404 catch-all page with FileQuestion icon and back button |
|
||||
|
||||
### Frontend — Router
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `frontend/src/router.tsx` | Added `/403` (`accessDeniedRoute`) and `$` catch-all (`notFoundRoute`) routes |
|
||||
|
||||
### Frontend — Tests
|
||||
|
||||
| File | Coverage |
|
||||
|------|----------|
|
||||
| `frontend/src/pages/ProfilePage.test.tsx` | Renders, pre-fill, save disabled when pristine, save enabled after edit, save success, email validation, dialog open, wrong password error, auth redirect |
|
||||
| `frontend/src/pages/SettingsPage.test.tsx` | Renders, status badge, mode buttons, mode selection, save success, save error, placeholder sections, auth redirect |
|
||||
| `frontend/src/pages/CmsPage.test.tsx` | Renders title and placeholder, auth redirect |
|
||||
| `frontend/src/pages/AccessDeniedPage.test.tsx` | Renders title, message, back button, navigation |
|
||||
| `frontend/src/pages/NotFoundPage.test.tsx` | Renders for unknown routes, back button, navigation |
|
||||
|
||||
---
|
||||
|
||||
## i18n Completeness
|
||||
|
||||
All `t('...')` calls in the new pages and hooks have corresponding keys in both `en/translation.json` and `nl/translation.json`. No orphaned keys.
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
# Business Logic Model — Unit 6: Profile, Settings & CMS Placeholder
|
||||
|
||||
## 1. Profile Update Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
box rgba(33,150,243,0.15) Frontend
|
||||
participant U as User
|
||||
participant PF as ProfilePage
|
||||
participant AC as AuthContext
|
||||
end
|
||||
box rgba(244,67,54,0.15) Backend
|
||||
participant API as PUT /Users/me
|
||||
participant REF as POST /auth/refresh
|
||||
end
|
||||
|
||||
U->>PF: Edit name or email, click Save
|
||||
PF->>PF: Validate form (name required, email valid)
|
||||
alt Validation fails
|
||||
PF-->>U: Show inline field errors
|
||||
else Validation passes
|
||||
PF->>API: PUT /api/v1/Users/me with name and email
|
||||
alt API error
|
||||
API-->>PF: 400 or 409 (e.g. email taken)
|
||||
PF-->>U: Show error message
|
||||
else Success
|
||||
API-->>PF: 200 Updated user data
|
||||
PF->>REF: POST /api/v1/Auth/refresh
|
||||
REF-->>AC: New access token plus updated user object
|
||||
AC-->>PF: AuthContext updated
|
||||
PF-->>U: Show success toast, form reset to saved values
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Text alternative: User edits name/email on ProfilePage → frontend validates → PUT /Users/me → on success calls /auth/refresh to sync AuthContext → success toast shown.
|
||||
|
||||
---
|
||||
|
||||
## 2. Change Password Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
box rgba(33,150,243,0.15) Frontend
|
||||
participant U as User
|
||||
participant PF as ProfilePage
|
||||
participant DL as ChangePasswordDialog
|
||||
end
|
||||
box rgba(244,67,54,0.15) Backend
|
||||
participant API as POST /auth/change-password
|
||||
end
|
||||
|
||||
U->>PF: Click Change Password button
|
||||
PF->>DL: Open dialog
|
||||
U->>DL: Enter currentPassword, newPassword, confirmPassword
|
||||
DL->>DL: Validate (newPassword matches confirm, meets policy)
|
||||
alt Validation fails
|
||||
DL-->>U: Show inline errors
|
||||
else Validation passes
|
||||
DL->>API: POST /api/v1/Auth/change-password
|
||||
alt Wrong current password or policy violation
|
||||
API-->>DL: 400 with error detail
|
||||
DL-->>U: Show error message
|
||||
else Success
|
||||
API-->>DL: 200 OK
|
||||
DL-->>U: Close dialog, show success toast on ProfilePage
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Text alternative: User opens Change Password dialog → validates fields → POST /auth/change-password → success closes dialog and shows toast.
|
||||
|
||||
---
|
||||
|
||||
## 3. Settings — Availability Update Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
box rgba(33,150,243,0.15) Frontend
|
||||
participant U as User
|
||||
participant SP as SettingsPage
|
||||
participant QC as QueryClient
|
||||
end
|
||||
box rgba(244,67,54,0.15) Backend
|
||||
participant GET as GET /availability/status
|
||||
participant PUT as POST /availability/admin/status
|
||||
end
|
||||
|
||||
SP->>GET: Fetch current availability on mount
|
||||
GET-->>SP: status, message, checkedAt
|
||||
SP-->>U: Display current mode and message
|
||||
U->>SP: Select new mode and optional message, click Save
|
||||
SP->>PUT: POST /api/v1/Availability/admin/status
|
||||
alt Success
|
||||
PUT-->>SP: 200 OK
|
||||
SP->>QC: Invalidate availability query cache
|
||||
QC->>GET: Re-fetch status
|
||||
GET-->>SP: Updated status
|
||||
SP-->>U: Show success toast, updated badge
|
||||
else Error
|
||||
PUT-->>SP: 400 or 403
|
||||
SP-->>U: Show error message
|
||||
end
|
||||
```
|
||||
|
||||
Text alternative: SettingsPage fetches availability on mount. User selects new mode and saves → POST to admin status endpoint → on success invalidate cache to re-fetch updated status.
|
||||
|
||||
---
|
||||
|
||||
## 4. Route Guard Flow (403 / 404)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Nav["Navigation event"]
|
||||
ProtectedRoute{"ProtectedRoute check\n(authenticated?)"}
|
||||
RoleGuard{"RoleGuard check\n(role allowed?)"}
|
||||
RouteMatch{"Route exists?"}
|
||||
Page["Render Page"]
|
||||
P403["Render 403 AccessDeniedPage"]
|
||||
P404["Render 404 NotFoundPage"]
|
||||
Login["Redirect to /login"]
|
||||
|
||||
Nav --> ProtectedRoute
|
||||
ProtectedRoute -->|No| Login
|
||||
ProtectedRoute -->|Yes| RouteMatch
|
||||
RouteMatch -->|No| P404
|
||||
RouteMatch -->|Yes, has RoleGuard| RoleGuard
|
||||
RouteMatch -->|Yes, no RoleGuard| Page
|
||||
RoleGuard -->|Allowed| Page
|
||||
RoleGuard -->|Denied| P403
|
||||
|
||||
classDef guard fill:#FF9800,stroke:#e65100,color:#000
|
||||
classDef page fill:#2196F3,stroke:#0d47a1,color:#000
|
||||
classDef error fill:#F44336,stroke:#b71c1c,color:#000
|
||||
classDef start fill:#9C27B0,stroke:#4a148c,color:#000
|
||||
|
||||
class ProtectedRoute guard
|
||||
class RoleGuard guard
|
||||
class RouteMatch guard
|
||||
class Page page
|
||||
class P403 error
|
||||
class P404 error
|
||||
class Login error
|
||||
class Nav start
|
||||
```
|
||||
|
||||
Text alternative: Navigation → ProtectedRoute (unauthenticated → /login) → route match (unknown → 404) → RoleGuard (denied → 403, allowed → page renders).
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
# Business Rules — Unit 6: Profile, Settings & CMS Placeholder
|
||||
|
||||
## Access Control Rules
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Request["Incoming Route Request"]
|
||||
IsAuth{"Authenticated?"}
|
||||
Route{"Which route?"}
|
||||
IsOwner{"Role = Owner?"}
|
||||
AccessDenied["403 AccessDeniedPage"]
|
||||
NotFound["404 NotFoundPage"]
|
||||
Profile["ProfilePage"]
|
||||
Settings["SettingsPage"]
|
||||
Cms["CmsPage"]
|
||||
Login["Redirect to /login"]
|
||||
|
||||
Request --> IsAuth
|
||||
IsAuth -->|No| Login
|
||||
IsAuth -->|Yes| Route
|
||||
Route -->|/profile| Profile
|
||||
Route -->|/settings| IsOwner
|
||||
Route -->|/cms| IsOwner
|
||||
Route -->|unknown path| NotFound
|
||||
IsOwner -->|Yes| Settings
|
||||
IsOwner -->|Yes| Cms
|
||||
IsOwner -->|No| AccessDenied
|
||||
|
||||
classDef guard fill:#FF9800,stroke:#e65100,color:#000
|
||||
classDef page fill:#2196F3,stroke:#0d47a1,color:#000
|
||||
classDef error fill:#F44336,stroke:#b71c1c,color:#000
|
||||
classDef start fill:#9C27B0,stroke:#4a148c,color:#000
|
||||
|
||||
class IsAuth guard
|
||||
class IsOwner guard
|
||||
class Route guard
|
||||
class Profile page
|
||||
class Settings page
|
||||
class Cms page
|
||||
class AccessDenied error
|
||||
class NotFound error
|
||||
class Login error
|
||||
class Request start
|
||||
```
|
||||
|
||||
Text alternative: All routes require authentication (redirect to /login if not). /settings and /cms additionally require Owner role; non-Owners are shown 403. Unknown paths show 404.
|
||||
|
||||
---
|
||||
|
||||
## BR-01: Profile Access
|
||||
- **Rule**: Any authenticated user can access `/profile`
|
||||
- **Implementation**: ProfilePage is a child of `_authenticated.tsx` (inherits ProtectedRoute); no additional RoleGuard
|
||||
- **Data**: Profile data is read from AuthContext (no extra API call for display)
|
||||
|
||||
## BR-02: Profile — Name and Email are Editable
|
||||
- **Rule**: The logged-in user may update their own `name` and `email`
|
||||
- **Validation** (frontend, mirrors backend):
|
||||
- `name`: required, non-empty
|
||||
- `email`: required, valid email format
|
||||
- **Endpoint**: `PUT /api/v1/Users/me`
|
||||
- **After save**: Call `POST /api/v1/Auth/refresh` to synchronize AuthContext with updated values
|
||||
|
||||
## BR-03: Profile — Role is Read-Only
|
||||
- **Rule**: A user cannot change their own role from the profile page
|
||||
- **Display**: Role shown as a static badge; no edit controls rendered
|
||||
|
||||
## BR-04: Change Password
|
||||
- **Rule**: The logged-in user may change their own password via a dialog on ProfilePage
|
||||
- **Validation** (frontend, mirrors backend password policy):
|
||||
- `currentPassword`: required, non-empty
|
||||
- `newPassword`: required, min 8 chars, at least 1 uppercase, 1 lowercase, 1 digit, 1 special character
|
||||
- `confirmPassword` (UI-only field): must match `newPassword`
|
||||
- **Endpoint**: `POST /api/v1/Auth/change-password`
|
||||
- **On success**: Close dialog, show success toast; no AuthContext update needed (password change does not affect access token)
|
||||
|
||||
## BR-05: Settings — Owner Only
|
||||
- **Rule**: Only users with role `Owner` may access `/settings`
|
||||
- **Implementation**: `RoleGuard` with `allowedRoles={["Owner"]}` wraps SettingsPage
|
||||
- **On violation**: Redirect to `/403`
|
||||
|
||||
## BR-06: Settings — Availability Management
|
||||
- **Rule**: Owner may change the system availability status from SettingsPage
|
||||
- **Allowed modes**: `Available`, `Maintenance`, `Unavailable`
|
||||
- **Endpoint**: `POST /api/v1/Availability/admin/status` (OwnerOnly — already enforced by backend)
|
||||
- **Message field**: Optional free-text reason displayed to end-users
|
||||
- **On save**: Invalidate `useAvailabilityStatus` query cache to reflect new status immediately
|
||||
|
||||
## BR-07: CMS Page — Owner Only
|
||||
- **Rule**: Only users with role `Owner` may access `/cms`
|
||||
- **Implementation**: `RoleGuard` with `allowedRoles={["Owner"]}` wraps CmsPage
|
||||
- **On violation**: Redirect to `/403`
|
||||
- **Content**: Placeholder only — no functional CMS features in this unit
|
||||
|
||||
## BR-08: 403 Access Denied Page
|
||||
- **Rule**: Rendered when `RoleGuard` rejects a route request
|
||||
- **Content**: Heading "Access Denied" + explanatory message + "Back to Dashboard" button (navigates to `/`)
|
||||
- **No authentication required**: 403 is a public route (unauthenticated users hitting protected routes are redirected to `/login` by ProtectedRoute first)
|
||||
|
||||
## BR-09: 404 Not Found Page
|
||||
- **Rule**: Rendered when TanStack Router cannot match any registered route
|
||||
- **Content**: Heading "Page Not Found" + brief message + "Back to Dashboard" button (navigates to `/`)
|
||||
- **Implementation**: TanStack Router catch-all route (`$404.tsx`)
|
||||
|
||||
## BR-10: Backend — New Endpoints Required
|
||||
The following new backend endpoints must be added as part of Unit 6:
|
||||
|
||||
| Endpoint | Method | Policy | Purpose |
|
||||
|----------|--------|--------|---------|
|
||||
| `/api/v1/Users/me` | PUT | Authenticated | Update own name and email |
|
||||
| `/api/v1/Auth/change-password` | POST | Authenticated | Change own password |
|
||||
|
||||
Both endpoints operate on the currently authenticated user (identified via JWT claims).
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
# Domain Entities — Unit 6: Profile, Settings & CMS Placeholder
|
||||
|
||||
## Overview
|
||||
|
||||
Unit 6 introduces profile editing and password management. It reuses the existing `AvailabilityStatus` entity from Unit 4 for the Settings page, and introduces new request/response shapes for profile and password operations.
|
||||
|
||||
---
|
||||
|
||||
## Entity Diagram
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
AuthUser["AuthUser"]
|
||||
UpdateProfileRequest["UpdateProfileRequest"]
|
||||
UpdateProfileResponse["UpdateProfileResponse"]
|
||||
ChangePasswordRequest["ChangePasswordRequest"]
|
||||
AvailabilityStatus["AvailabilityStatus"]
|
||||
UpdateAvailabilityRequest["UpdateAvailabilityRequest"]
|
||||
|
||||
AuthUser -->|"provides data for"| UpdateProfileRequest
|
||||
UpdateProfileRequest -->|"produces"| UpdateProfileResponse
|
||||
UpdateProfileResponse -->|"refreshed into"| AuthUser
|
||||
AvailabilityStatus -->|"changed by"| UpdateAvailabilityRequest
|
||||
|
||||
classDef user fill:#2196F3,stroke:#0d47a1,color:#000
|
||||
classDef request fill:#FF9800,stroke:#e65100,color:#000
|
||||
classDef response fill:#4CAF50,stroke:#2e7d32,color:#000
|
||||
classDef availability fill:#9C27B0,stroke:#4a148c,color:#000
|
||||
|
||||
class AuthUser user
|
||||
class UpdateProfileRequest,ChangePasswordRequest,UpdateAvailabilityRequest request
|
||||
class UpdateProfileResponse response
|
||||
class AvailabilityStatus availability
|
||||
```
|
||||
|
||||
Text alternative: AuthUser provides data for UpdateProfileRequest; saving produces UpdateProfileResponse which refreshes AuthContext. AvailabilityStatus is changed via UpdateAvailabilityRequest. ChangePasswordRequest is standalone.
|
||||
|
||||
---
|
||||
|
||||
## Entity Descriptions
|
||||
|
||||
### AuthUser
|
||||
Represents the currently logged-in user. Sourced from `AuthContext` (populated at login and after token refresh). Displayed on ProfilePage; updated indirectly via token refresh after a profile save.
|
||||
|
||||
| Field | Type | Editable | Source |
|
||||
|-------|------|----------|--------|
|
||||
| id | string (GUID) | No | AuthContext |
|
||||
| email | string | Yes (via PUT /Users/me) | AuthContext |
|
||||
| name | string | Yes (via PUT /Users/me) | AuthContext |
|
||||
| role | string | No (read-only) | AuthContext |
|
||||
| isActive | boolean | No | AuthContext |
|
||||
|
||||
---
|
||||
|
||||
### UpdateProfileRequest
|
||||
Sent to `PUT /api/v1/Users/me` when the user saves their profile edits.
|
||||
|
||||
| Field | Type | Required | Validation |
|
||||
|-------|------|----------|------------|
|
||||
| name | string | Yes | Non-empty, max 100 chars |
|
||||
| email | string | Yes | Valid email format |
|
||||
|
||||
---
|
||||
|
||||
### UpdateProfileResponse
|
||||
Response from `PUT /api/v1/Users/me`. Contains the updated user data. After receiving this, the frontend calls `POST /api/v1/Auth/refresh` to sync AuthContext with the new values.
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| id | string | GUID |
|
||||
| email | string | Updated value |
|
||||
| name | string | Updated value |
|
||||
| role | string | Unchanged |
|
||||
| isActive | boolean | Unchanged |
|
||||
|
||||
---
|
||||
|
||||
### ChangePasswordRequest
|
||||
Sent to `POST /api/v1/Auth/change-password`. Requires current password for verification.
|
||||
|
||||
| Field | Type | Required | Validation |
|
||||
|-------|------|----------|------------|
|
||||
| currentPassword | string | Yes | Must match current stored password |
|
||||
| newPassword | string | Yes | Must satisfy backend password policy (≥8 chars, upper, lower, digit, special char) |
|
||||
|
||||
---
|
||||
|
||||
### AvailabilityStatus (reused from Unit 4)
|
||||
Retrieved via `GET /api/v1/Availability/status`. Displayed on SettingsPage with controls to change it.
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| status | string | "Available" \| "Maintenance" \| "Unavailable" |
|
||||
| message | string | Optional custom message |
|
||||
| checkedAt | datetime | Timestamp of last check |
|
||||
|
||||
---
|
||||
|
||||
### UpdateAvailabilityRequest
|
||||
Sent to `POST /api/v1/Availability/admin/status` (OwnerOnly policy — already exists in backend).
|
||||
|
||||
| Field | Type | Required | Allowed values |
|
||||
|-------|------|----------|----------------|
|
||||
| newStatus | string | Yes | "Available" \| "Maintenance" \| "Unavailable" |
|
||||
| reason | string | No | Free-form message shown to users |
|
||||
+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