Files
2026-05-27 23:29:53 +01:00

75 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Add @suaveplan/react-auth — forms (Wave 2)
**Package**: `@suaveplan/react-auth`
**Status**: PROPOSED
**Type**: feature
- **Spec delta**: ./specs/react-auth/spec.md
## Why
Wave 1 shipped the `@suaveplan/react-auth` core (client, provider, context, hooks, guard, translations data) but explicitly deferred the form components to Wave 2. Downstream apps need shipping login/register/forgot-password/reset-password/email-verification surfaces to wire up the Better Auth flow. Wave 2 layers the five pure-UI form components on top of Wave 1's core, consuming the existing `enGb`/`cyGb`/`zhCn` translation packs (which Wave 1 sized for this exact purpose) and `@suaveplan/react-ui` primitives. No Wave 1 surface is modified — these forms are purely additive. The upstream enterprise audit (`../suaveplan/enterprise/react-auth.md` §2.32, §3.4, §5.5) called out the absence of `.test.tsx` coverage for these five components as the dominant test gap in the package; Wave 2 closes that gap by shipping 100% covered, accessible, schema-validated forms.
## What changes
- ADDED: `LoginForm` — pure UI form with email/password/remember-me, password show/hide, react-hook-form + Zod, "forgot password?" + "sign up" links.
- ADDED: `RegisterForm` — pure UI form with email/username/password+confirm/optional name/dob/sex/gender/company, password show/hide on both fields, react-hook-form + Zod with cross-field `confirmPassword` refinement.
- ADDED: `ForgotPasswordForm` — pure UI form with email field, success view (with "send another" + "back to login"), react-hook-form + Zod.
- ADDED: `ResetPasswordForm` — pure UI form with new password + confirm, success view, invalid-token view, react-hook-form + Zod with cross-field refinement.
- ADDED: `EmailVerificationView` — pure UI status view rendering `verifying` / `success` / `expired` / `error` states with appropriate icon, copy, CTA, and `back-to-login` link.
- ADDED: Form-scoped subpath exports under `./components/<form>` for every form module, mirroring Wave 1's per-component subpath convention.
- ADDED: New translation keys consumed by the forms — every form accepts an optional `translations?: Partial<AuthLoginStrings | …>` prop defaulting to the Wave 1 `enGb.auth.<group>` table so existing `cyGb` / `zhCn` packs work end-to-end with zero new strings (the Wave 1 packs were sized to cover Wave 2 form copy).
## Out of scope
- Page components (`Login`, `Register`, `ForgotPassword`, `ResetPassword`, `EmailVerify`, `Profile`) — deferred to Wave 3.
- `PermissionGate` component — deferred to Wave 3.
- MFA / passkey / magic-link flows — enterprise audit §4 enhancements, not in any current wave.
### Excluded from this migration
- **Page components** (`pages/login`, `pages/register`, `pages/forgot-password`, `pages/reset-password`, `pages/email-verification`, `pages/profile`) — Deferred to Wave 3 — scope-bounded migration to keep ship velocity at 100/100/100/100 quality. Pages wrap the Wave 2 forms with `useAuth` action wiring + `useNavigate` redirects.
- **`PermissionGate` component** — Deferred to Wave 3 — scope-bounded migration; ships next to the Wave 3 pages because the page surfaces are its primary consumer.
- **MFA / passkey / magic-link surfaces** (`MfaChallengePage`, `MfaEnrolmentPage`, `PasskeyRegistrationPage`, `PasskeyLoginPage`) — Enterprise audit §4 enhancements, never part of upstream — out of scope for every current wave.
## Source mapping
This change derives from `../suaveplan/enterprise/react-auth.md` (§1, §2.25, §2.30–32, §3.4, §5.5) and `../suaveplan/packages/react/react-auth/src/components/`.
| Requirement | Source lines | Notes |
|---|---|---|
| `LoginForm` shape | `components/login-form/login-form.tsx:1-210` | Verbatim semantics; `resolver: zodResolver(...) as any` cast removed via typed resolver narrowing — no `any` survives §2. |
| `RegisterForm` shape | `components/register-form/register-form.tsx:1-366` | Verbatim semantics; same `any` removal; split into `register-form.tsx` + `register-form-fields.tsx` to stay under the 300-line hard cap (§3). |
| `ForgotPasswordForm` shape | `components/forgot-password-form/forgot-password-form.tsx:1-190` | Verbatim semantics; same `any` removal. |
| `ResetPasswordForm` shape | `components/reset-password-form/reset-password-form.tsx:1-277` | Verbatim semantics; same `any` removal. |
| `EmailVerificationView` shape | `components/email-verification-view/email-verification-view.tsx:1-220` | Reworded: upstream consumed `useTranslation` from `@suaveplan/react-translate`; Wave 1 settled this package as no-react-translate (`enterprise/react-auth.md §5.5` migration impact note in Wave 1 proposal). Wave 2 follows that precedent — the view accepts a `translations?: Partial<AuthEmailVerificationStrings>` prop defaulting to `enGb.auth.emailVerification`. Callers wanting `cyGb` / `zhCn` pass that pack's slice. |
| 5 sibling `.test.tsx` files | `enterprise/react-auth.md §3.4 / §5.5` | Upstream gap — these tests do not exist in upstream and are added in this wave per the audit. |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| Zod-validated `<FormName>Props` boundary on every form | Genesis rule §0a — Zod boundary on every public option bundle. Upstream had `Props` interfaces but no runtime validation. |
| `react-hook-form` typed resolver, no `as any` cast | Genesis rule §2 — `any` is banned. Upstream's `resolver: zodResolver(schema) as any` is replaced with a typed wrapper. |
| 100% statement / branch / function / line coverage | Genesis-wide policy, not package-specific. |
| Sibling `<form>.md` per implementation file | Genesis rule §4. |
| Sibling `<form>.stories.tsx` per component | CLAUDE.md: "Every React component needs `.stories.tsx`." Upstream ships stories for pages / guards but not for these forms. |
| `translations?: Partial<...>` prop on every form, defaulting to `enGb.auth.<group>` | Genesis-specific — Wave 1 dropped `@suaveplan/react-translate` and parked translations as static packs. Wave 2 wires those packs in via prop injection to preserve the three-locale surface without re-introducing the dropped dep. |
## Verification approach
Every form gets a sibling `.test.tsx` that uses `@suaveplan/testing/runner` and `@testing-library/react` with real `react-hook-form` + real Zod. No mocks. Each test file covers:
- `src/components/login-form/login-form.test.tsx` — submit happy path, Zod validation errors (empty email, short password, bad email), password show/hide toggle, `error` prop renders alert, `isLoading` disables submit, `translations` prop overrides English defaults.
- `src/components/register-form/register-form.test.tsx` — same matrix + username regex, `confirmPassword` mismatch, optional field flow, sex select changes.
- `src/components/forgot-password-form/forgot-password-form.test.tsx` — submit happy path, Zod email error, `isSuccess` view renders email + `onReset` button, `error` alert, `translations` override.
- `src/components/reset-password-form/reset-password-form.test.tsx` — submit happy path, short password Zod error, `confirmPassword` mismatch, `isSuccess` view, `hasToken=false` invalid-token view with `onNavigateToForgotPassword` CTA, password toggle, `translations` override.
- `src/components/email-verification-view/email-verification-view.test.tsx` — `verifying` / `success` / `error` / `expired` branches, custom `message` overrides default copy, `onTryAgain` fires on error CTA, `isLoading` disables retry, `translations` override.
- `src/index.test.ts` — extended to assert the 5 new symbols (`LoginForm`, `RegisterForm`, `ForgotPasswordForm`, `ResetPasswordForm`, `EmailVerificationView`) appear in the root barrel.
## Migration impact
- `package.json` gains three new dependencies — `@suaveplan/react-ui` (`workspace:^`), `react-hook-form` and `@hookform/resolvers` (both `catalog:forms`). The five form subpath exports are appended to the `exports` block. Vite `lib.entry` gains five new entries; rollup `external` gains `@suaveplan/react-ui`, `react-hook-form`, `@hookform/resolvers`.
- No Wave 1 file is modified except `src/index.ts` (named re-exports for the 5 new components added — strictly additive) and `src/index.test.ts` (asserts the new symbols are present).
- No downstream consumers yet — `@suaveplan/react-auth-whitelabel` is the only sibling and it ships independently in parallel. Verified by `bunx turbo ls --filter=...@suaveplan/react-auth` (empty list).