75 lines
8.3 KiB
Markdown
75 lines
8.3 KiB
Markdown
# 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).
|