8.3 KiB
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-fieldconfirmPasswordrefinement. - 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 renderingverifying/success/expired/errorstates with appropriate icon, copy, CTA, andback-to-loginlink. - 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 1enGb.auth.<group>table so existingcyGb/zhCnpacks 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. PermissionGatecomponent — 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 withuseAuthaction wiring +useNavigateredirects. PermissionGatecomponent — 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,errorprop renders alert,isLoadingdisables submit,translationsprop overrides English defaults.src/components/register-form/register-form.test.tsx— same matrix + username regex,confirmPasswordmismatch, optional field flow, sex select changes.src/components/forgot-password-form/forgot-password-form.test.tsx— submit happy path, Zod email error,isSuccessview renders email +onResetbutton,erroralert,translationsoverride.src/components/reset-password-form/reset-password-form.test.tsx— submit happy path, short password Zod error,confirmPasswordmismatch,isSuccessview,hasToken=falseinvalid-token view withonNavigateToForgotPasswordCTA, password toggle,translationsoverride.src/components/email-verification-view/email-verification-view.test.tsx—verifying/success/error/expiredbranches, custommessageoverrides default copy,onTryAgainfires on error CTA,isLoadingdisables retry,translationsoverride.src/index.test.ts— extended to assert the 5 new symbols (LoginForm,RegisterForm,ForgotPasswordForm,ResetPasswordForm,EmailVerificationView) appear in the root barrel.
Migration impact
package.jsongains three new dependencies —@suaveplan/react-ui(workspace:^),react-hook-formand@hookform/resolvers(bothcatalog:forms). The five form subpath exports are appended to theexportsblock. Vitelib.entrygains five new entries; rollupexternalgains@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) andsrc/index.test.ts(asserts the new symbols are present). - No downstream consumers yet —
@suaveplan/react-auth-whitelabelis the only sibling and it ships independently in parallel. Verified bybunx turbo ls --filter=...@suaveplan/react-auth(empty list).