82 lines
2.9 KiB
Markdown
82 lines
2.9 KiB
Markdown
# Self-Service Profile Page Design
|
|
|
|
## Overview
|
|
|
|
Add a `/manage/profile` page where authenticated users can view and edit their OIDC profile fields. This completes the self-service user management story alongside the existing `/manage/credentials` page.
|
|
|
|
## Editable Fields
|
|
|
|
| Field | Input type | Validation |
|
|
|-------|-----------|------------|
|
|
| `given_name` | text | max 255 chars |
|
|
| `family_name` | text | max 255 chars |
|
|
| `preferred_username` | text | max 255 chars |
|
|
| `email` | email | HTML5 + server-side format check |
|
|
| `phone_number` | tel | optional, no strict format |
|
|
| `picture` | url | optional, valid URL check |
|
|
| `locale` | text | optional, max 20 chars |
|
|
|
|
Read-only: `username` (login identity, displayed but not editable).
|
|
|
|
## Architecture
|
|
|
|
### Routes
|
|
|
|
Add to `manage/routes.py`:
|
|
|
|
- `GET /manage/profile` — render profile form pre-filled with current user data
|
|
- `POST /manage/profile` — validate and update user, return HTMX partial response
|
|
|
|
Auth guard follows existing pattern: `get_session_user(request)` with redirect to `/login`.
|
|
|
|
### Templates
|
|
|
|
Create `manage/base.html` extending `base.html` with a nav bar containing links to Profile and Credentials. Both manage pages extend this instead of `base.html` directly.
|
|
|
|
```
|
|
templates/
|
|
base.html (unchanged)
|
|
manage/
|
|
base.html (NEW - adds nav)
|
|
credentials.html (CHANGED - extends manage/base.html)
|
|
profile.html (NEW - profile form)
|
|
```
|
|
|
|
### Navigation
|
|
|
|
The `manage/base.html` template adds a `<nav>` element with links:
|
|
- Profile (`/manage/profile`)
|
|
- Credentials (`/manage/credentials`)
|
|
|
|
This keeps the nav scoped to authenticated manage pages without modifying the public `base.html`.
|
|
|
|
### Form Behavior
|
|
|
|
- HTMX: `hx-post="/manage/profile"` with `hx-target="#profile-status"` and `hx-swap="innerHTML"`
|
|
- Success: `<div role="status">Profile updated</div>`
|
|
- Validation error: `<div role="alert">specific error message</div>`
|
|
- Pre-filled with current values on GET
|
|
- Empty optional fields show empty inputs (not "None")
|
|
|
|
### Validation
|
|
|
|
Server-side validation in the POST handler:
|
|
- `email`: if provided, basic format check (contains `@`)
|
|
- `picture`: if provided, basic URL format check (starts with `http://` or `https://`)
|
|
- String length limits enforced server-side
|
|
- HTML5 input attributes for client-side hints (`type="email"`, `type="url"`, `maxlength`)
|
|
|
|
### Data Flow
|
|
|
|
1. GET: `get_session_user()` → `user_repo.get_by_id(userid)` → render template with user fields
|
|
2. POST: `get_session_user()` → `user_repo.get_by_id(userid)` → validate form data → `user.model_copy(update={...})` → `user_repo.update(user)` → return status HTML
|
|
|
|
## E2E Tests
|
|
|
|
`tests/e2e/profile.spec.js`:
|
|
- Page renders with correct form fields
|
|
- Auth guard redirects unauthenticated users
|
|
- Successful profile update
|
|
- Validation errors (invalid email, etc.)
|
|
- Navigation between profile and credentials
|
|
- Fields pre-filled after update (persistence)
|