first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
# profile-account-section — `<AccountSection>`
## Purpose
`profile-account-section.tsx` houses the delete-account tab renderer for `<ProfilePage>`. It is split out of `profile-sections.tsx` so each implementation file stays under the 300-line hard cap. The component renders an `<Alert variant="destructive">` framing a danger-zone block; clicking the initial delete button reveals a confirmation pane with a password input and two CTAs (`confirm-delete`, `cancel-delete`). The parent page owns the visibility state via `showDeleteConfirm` and the three callbacks (`onRequestDelete`, `onCancelDelete`, `onConfirmDelete`).
## Features
- Single-card destructive surface using the `react-ui` `<Alert variant="destructive">` primitive.
- Two-stage confirmation: initial "Delete Account" button → confirm pane with password re-entry + "Yes, Delete My Account" + "Cancel".
- The confirm button is disabled while `isLoading` is true OR the password field is empty.
- Spinning loader (`<Loader2>`) appears alongside the deleting copy while the call is in flight.
- Errors at the `currentPassword` field surface inline.
## Usage: Basic
```tsx
import { AccountSection } from "./profile-account-section.js";
<AccountSection
t={t}
formData={formData}
errors={errors}
isLoading={isLoading}
onChange={handleInputChange}
showDeleteConfirm={showDeleteConfirm}
onRequestDelete={() => setShowDeleteConfirm(true)}
onCancelDelete={handleCancelDelete}
onConfirmDelete={handleConfirmDelete}
/>;
```
## Usage: Advanced
Hooking an analytics callback into the delete flow is handled at the parent page level via `onDeleteAccount`. From this component's perspective, the `onConfirmDelete` callback is the only one that triggers a network call:
```tsx
async function handleConfirmDelete() {
if (!formData.currentPassword) {
setErrors({ currentPassword: t.errorPasswordRequiredForDelete });
return;
}
await deleteAccount(formData.currentPassword);
track("account.deleted");
navigate("/auth/login");
}
```
## API Reference
### `AccountSectionProps extends SectionProps`
| Field | Type | Description |
|---|---|---|
| `showDeleteConfirm` | `boolean` | When `true`, the confirm pane is rendered. |
| `onRequestDelete` | `() => void` | Invoked when the initial delete button is clicked. |
| `onCancelDelete` | `() => void` | Invoked when the cancel button is clicked. |
| `onConfirmDelete` | `() => Promise<void>` | Invoked when the confirm button is clicked. |
## Implementation Notes
- The component reuses `formData.currentPassword` instead of introducing a `deletePassword` field — the parent page resets that field on cancel.
- The placeholder copy on the delete-password input is intentionally the `errorPasswordRequiredForDelete` string (also used as the inline error). This matches upstream UX: the placeholder explains the requirement, the error reinforces it after a failed submit.
- The component does not render its own `<form>` — the confirm button is a regular `<Button>` whose `onClick` triggers the delete. That keeps the password input out of the `<form>` element used by `<PasswordSection>` and prevents accidental form-submit collisions.

View File

@@ -0,0 +1,76 @@
# profile-sections — `<ProfileSection>` + `<PasswordSection>`
## Purpose
`profile-sections.tsx` houses the two read/write tab renderers for `<ProfilePage>`: `<ProfileSection>` (name + email update form) and `<PasswordSection>` (current / new / confirm password form with independent visibility toggles). It is a pure-UI render layer — all state and side effects live on the parent page. The split keeps each implementation file under the 300-line hard cap mandated by `CLAUDE.md` while keeping the section markup co-located so a designer changing the profile form does not have to navigate through `<ProfilePage>` itself.
`<AccountSection>` lives in a sibling file (`profile-account-section.tsx`) because the delete-confirmation flow adds ~120 lines of conditional markup that would push this file over the cap.
## Features
- `<ProfileSection>` — name + email inputs, submit button, optional email-level error display.
- `<PasswordSection>` — three password rows (current / new / confirm) using a private `<PasswordRow>` factor; per-row visibility toggle button; submit button disabled until both `currentPassword` and `newPassword` are non-empty.
- `<PasswordRow>` — private helper that renders `<Label>` + `<Input>` + show/hide `<Button>` with `aria-label="Show password" | "Hide password"`.
- Strict typing: `SectionProps` is shared across all section renderers and is exported so `profile-account-section.tsx` can extend it.
## Usage: Basic
```tsx
import { ProfileSection } from "./profile-sections.js";
<ProfileSection
t={t}
formData={formData}
errors={errors}
isLoading={isLoading}
onChange={handleInputChange}
onSubmit={handleProfileUpdate}
/>;
```
## Usage: Advanced
`<PasswordSection>` takes both a `show` record and a `toggle(key)` setter so the three visibility toggles share a single store on the parent:
```tsx
const [show, setShow] = useState({ current: false, next: false, confirm: false });
const toggle = (k) => setShow((prev) => ({ ...prev, [k]: !prev[k] }));
<PasswordSection
{...sectionProps}
onSubmit={handlePasswordChange}
show={show}
toggle={toggle}
/>;
```
## API Reference
### `SectionProps`
| Field | Type | Description |
|---|---|---|
| `t` | `ProfilePageStrings` | Resolved translations table. |
| `formData` | `ProfilePageFormData` | Current form values. |
| `errors` | `Partial<ProfilePageFormData>` | Per-field error messages. |
| `isLoading` | `boolean` | Disables every input. |
| `onChange` | `(field) => ChangeHandler` | Per-field change-handler factory. |
### `ProfileSectionProps extends SectionProps`
| Field | Type | Description |
|---|---|---|
| `onSubmit` | `(e: FormEvent) => Promise<void>` | Form submit handler. |
### `PasswordSectionProps extends SectionProps`
| Field | Type | Description |
|---|---|---|
| `onSubmit` | `(e: FormEvent) => Promise<void>` | Form submit handler. |
| `show` | `{ current; next; confirm }` | Per-row visibility flags. |
| `toggle` | `(k) => void` | Flip a row's visibility. |
## Implementation Notes
- `PasswordRow` is intentionally NOT exported — it is an internal factor to keep the password section's markup under control. The `aria-label` is hardcoded in English; if a downstream locale wants Welsh / Chinese show/hide labels it should compose a custom password section.
- The submit-button disable condition on `<PasswordSection>` requires both `currentPassword` AND `newPassword` to be truthy — this matches upstream behaviour and keeps users from accidentally posting a change request with one field empty.

View File

@@ -0,0 +1,85 @@
# `<ProfilePage>` — three-tab profile-settings page
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`<ProfilePage>` is the routing-aware profile-settings surface for an authenticated user. It renders three tabs — Profile (name + email), Password (current / new / confirm), and Account (danger zone / delete account) — over the Wave 1 `useAuth()` action surface (`updateProfile`, `changePassword`, `deleteAccount`). It applies `useAuthGuard({ requireAuth: true })` so unauthenticated visitors are redirected to `/auth/login`, hydrates the profile form from `useAuth().user`, and exposes optional `onSuccess` and `onDeleteAccount` callbacks for downstream analytics. All copy flows through a `translations?: Partial<AuthProfilePageStrings>` prop defaulting to `enGb.auth.profilePage`.
The implementation is split into three files to honour the 300-line hard cap: `profile.tsx` (shell + state + actions), `profile-sections.tsx` (Profile and Password tab renderers), and `profile-account-section.tsx` (Account tab with delete-account confirmation flow). All three sit behind the same package entry point.
## Features
- Three tabs via `@suaveplan/react-ui` `<Tabs>`: profile / password / account.
- Auth-guard: unauthenticated visitors redirected to `/auth/login`.
- Hydrates name + email from `useAuth().user` on mount.
- Calls `updateProfile`, `changePassword`, `deleteAccount` from the matching tab.
- Local validation on password change: required, ≥8 chars, must match.
- Delete-account confirmation flow with a password re-entry input.
- Three independent password-visibility toggles (current / new / confirm).
- `successMessage` and `error.message` alerts surfaced inline.
- Locale overrides via `translations`.
## Usage: Basic
```tsx
import { Route, Routes } from "react-router-dom";
import { ProfilePage } from "@suaveplan/react-auth";
export function ProfileRoute() {
return (
<Routes>
<Route path="/profile" element={<ProfilePage />} />
</Routes>
);
}
```
## Usage: Advanced
Hook into analytics:
```tsx
import { ProfilePage } from "@suaveplan/react-auth";
export function MyProfile() {
return (
<ProfilePage
onSuccess={() => track("profile.update.success")}
onDeleteAccount={() => track("account.deleted")}
translations={{
title: "My Account",
subtitle: "Keep your details up to date.",
}}
/>
);
}
```
## API Reference
### `ProfilePageProps`
| Field | Type | Description |
|---|---|---|
| `onSuccess` | `() => void` | Invoked after profile update or password change succeeds. |
| `onDeleteAccount` | `() => void` | Invoked after `deleteAccount` resolves, before navigation. |
| `translations` | `Partial<AuthProfilePageStrings>` | Locale overrides. |
### `ProfilePageFormData`
`{ name, email, currentPassword, newPassword, confirmPassword }` — five strings.
### `ProfilePageSection`
`{ id, title, active, icon }` — descriptor consumed by the `<TabsList>` render loop. `id` is one of `"profile" | "password" | "account"`.
## Implementation Notes
- The export is `<ProfilePage>`; the upstream `Profile` alias is preserved as a named re-export for back-compat.
- The page does NOT navigate after `updateProfile` or `changePassword` succeed — it stays on the same route and surfaces a success alert above the tab bar.
- The delete-account flow re-uses the `currentPassword` form field rather than a dedicated input, mirroring the upstream UX.
- `clearMessages()` is invoked on every input change so success / error banners disappear when the user starts editing again.
- `onValueChange` on `<Tabs>` clears messages so banners from one tab do not bleed across tab changes.