# Capability Spec Delta: react-auth (add-react-auth-forms) **Package**: `@suaveplan/react-auth` **Status**: PROPOSED > Every requirement carries a `**Verified by:**` line authored at draft time. ## ADDED Requirements ### Requirement: LoginForm renders a Zod-validated sign-in form with email, password, and remember-me The package SHALL expose a `LoginForm` component that renders a card-layout sign-in form with email + password + remember-me fields. The form SHALL validate via `react-hook-form` + `zodResolver` against an internal `loginSchema` requiring email to be a non-empty valid email and password to be at least 8 characters. Submit SHALL invoke the caller-supplied `onSubmit({ email, password, rememberMe })`. The component SHALL render a destructive `Alert` when `error` is non-null, disable the submit button when `isLoading` or `formState.isSubmitting` is true, and accept a `translations` prop overriding the default `enGb.auth.login` strings. #### Scenario: invalid email shows Zod error on blur - **WHEN** the user types `not-an-email` into the email field and blurs out - **THEN** the text "Invalid email address" appears #### Scenario: short password rejected - **WHEN** the user submits with password `"abc"` - **THEN** the text "Password must be at least 8 characters" appears and `onSubmit` is NOT called #### Scenario: happy path submit - **WHEN** the user fills `alice@example.com` + `correcthorsebatterystaple` and clicks Sign in - **THEN** `onSubmit({ email: "alice@example.com", password: "correcthorsebatterystaple", rememberMe: true })` is called #### Scenario: error prop renders alert - **WHEN** `error={new Error("Invalid credentials")}` is passed - **THEN** an alert containing "Invalid credentials" is in the document #### Scenario: translations prop overrides defaults - **WHEN** `translations={{ submit: "Connexion" }}` is passed - **THEN** the submit button displays "Connexion" **Verified by:** `src/components/login-form/login-form.test.tsx::"shows Zod error when email is invalid"` AND `src/components/login-form/login-form.test.tsx::"shows Zod error when password is shorter than 8 characters"` AND `src/components/login-form/login-form.test.tsx::"calls onSubmit with the typed payload on happy path"` AND `src/components/login-form/login-form.test.tsx::"renders the error prop in a destructive Alert"` AND `src/components/login-form/login-form.test.tsx::"overrides the submit copy when translations prop is supplied"` --- ### Requirement: RegisterForm renders a Zod-validated sign-up form with cross-field password match The package SHALL expose a `RegisterForm` component rendering email, username, password, confirm-password, optional first-name, surname, date-of-birth, sex (4-option select), gender, company fields. The Zod schema SHALL enforce email validity, username `^[a-zA-Z0-9_]+$` with length 3–30, password ≥ 8 chars, and a `confirmPassword` cross-field refinement matching `password`. Submit SHALL fire only when validation passes. The component SHALL accept `error`, `isLoading`, and `translations` props mirroring `LoginForm` semantics. #### Scenario: confirmPassword mismatch blocks submit - **WHEN** password `"longenough1"` and confirmPassword `"longenough2"` are submitted - **THEN** "Passwords don't match" appears under the confirm field and `onSubmit` is NOT called #### Scenario: username regex enforced - **WHEN** username `"has space"` is submitted - **THEN** the Zod regex error appears and `onSubmit` is NOT called #### Scenario: happy path submit - **WHEN** every required field is valid and the form is submitted - **THEN** `onSubmit` receives the full typed payload including the default `sex: "prefer-not-to-say"` **Verified by:** `src/components/register-form/register-form.test.tsx::"blocks submit when passwords do not match"` AND `src/components/register-form/register-form.test.tsx::"rejects usernames that do not match the regex"` AND `src/components/register-form/register-form.test.tsx::"calls onSubmit with the typed payload on happy path"` AND `src/components/register-form/register-form.test.tsx::"renders the error prop in a destructive Alert"` --- ### Requirement: ForgotPasswordForm renders an email-only Zod-validated form with success view The package SHALL expose a `ForgotPasswordForm` component rendering an email-only form. When `isSuccess` is true the component SHALL switch to a confirmation card displaying `emailSent`, a "send another" button wired to `onReset`, and a "back to login" link. Otherwise it SHALL render the email form with Zod validation, a destructive alert when `error` is non-null, and a submit button disabled while `isLoading` or `isSubmitting`. A `translations` prop SHALL override `enGb.auth.forgotPassword` defaults. #### Scenario: invalid email blocked - **WHEN** the user submits "not-email" - **THEN** "Invalid email address" appears and `onSubmit` is NOT called #### Scenario: success view rendered - **WHEN** `isSuccess` is true and `emailSent="alice@example.com"` - **THEN** the document contains "alice@example.com" and a button whose click calls `onReset` **Verified by:** `src/components/forgot-password-form/forgot-password-form.test.tsx::"blocks submit when email is invalid"` AND `src/components/forgot-password-form/forgot-password-form.test.tsx::"calls onSubmit with the typed payload on happy path"` AND `src/components/forgot-password-form/forgot-password-form.test.tsx::"renders the success view with emailSent and a working onReset button"` AND `src/components/forgot-password-form/forgot-password-form.test.tsx::"renders the error prop in a destructive Alert"` --- ### Requirement: ResetPasswordForm renders new+confirm password with success and invalid-token views The package SHALL expose a `ResetPasswordForm` component supporting three view modes: form (default), success (`isSuccess=true`), and invalid token (`hasToken=false`). The schema SHALL require both `password` and `confirmPassword` ≥ 8 chars with a cross-field match refinement. The success view SHALL render a "sign in" CTA wired to `onNavigateToLogin`; the invalid-token view SHALL render a "request new link" CTA wired to `onNavigateToForgotPassword`. The form view SHALL accept `error`, `isLoading`, and `translations` props. #### Scenario: password mismatch blocks submit - **WHEN** password `"longenough1"` and confirm `"longenough2"` are submitted - **THEN** "Passwords don't match" appears and `onSubmit` is NOT called #### Scenario: success view renders CTA - **WHEN** `isSuccess=true` - **THEN** clicking the Sign in CTA calls `onNavigateToLogin` #### Scenario: invalid token view renders CTA - **WHEN** `hasToken=false` - **THEN** clicking the Request new reset link CTA calls `onNavigateToForgotPassword` **Verified by:** `src/components/reset-password-form/reset-password-form.test.tsx::"blocks submit when passwords do not match"` AND `src/components/reset-password-form/reset-password-form.test.tsx::"calls onSubmit with the typed payload on happy path"` AND `src/components/reset-password-form/reset-password-form.test.tsx::"renders the success view with a working onNavigateToLogin CTA"` AND `src/components/reset-password-form/reset-password-form.test.tsx::"renders the invalid-token view with a working onNavigateToForgotPassword CTA"` --- ### Requirement: EmailVerificationView renders four status modes with caller-supplied translations The package SHALL expose an `EmailVerificationView` component accepting `status: "verifying" | "success" | "expired" | "error"`. Each status SHALL render a Card with an icon, title, description, and (where applicable) a CTA wired to one of `onContinueToLogin` / `onRegisterAgain` / `onTryAgain`. The `error` status SHALL render a destructive Alert; the `error` retry CTA SHALL be disabled while `isLoading`. A `translations` prop SHALL override `enGb.auth.emailVerification` defaults; an optional `message` prop SHALL override the default status description. #### Scenario: verifying state renders spinner and copy - **WHEN** `status="verifying"` - **THEN** a `role="status"` spinner is present and the default English "Verifying email..." copy is visible #### Scenario: success CTA fires onContinueToLogin - **WHEN** `status="success"` and the CTA is clicked - **THEN** `onContinueToLogin` is called once #### Scenario: error CTA fires onTryAgain and respects isLoading - **WHEN** `status="error"` and `isLoading=false` - **THEN** the retry CTA is enabled and clicking it calls `onTryAgain` #### Scenario: expired CTA fires onRegisterAgain - **WHEN** `status="expired"` and the CTA is clicked - **THEN** `onRegisterAgain` is called once **Verified by:** `src/components/email-verification-view/email-verification-view.test.tsx::"renders the verifying status with a spinner"` AND `src/components/email-verification-view/email-verification-view.test.tsx::"fires onContinueToLogin from the success CTA"` AND `src/components/email-verification-view/email-verification-view.test.tsx::"fires onTryAgain from the error CTA"` AND `src/components/email-verification-view/email-verification-view.test.tsx::"fires onRegisterAgain from the expired CTA"` AND `src/components/email-verification-view/email-verification-view.test.tsx::"overrides the default copy when translations prop is supplied"` --- ### Requirement: Form components are reachable from the root barrel The `@suaveplan/react-auth` root entry SHALL re-export the 5 Wave 2 form components as named exports (`LoginForm`, `RegisterForm`, `ForgotPasswordForm`, `ResetPasswordForm`, `EmailVerificationView`) with no `export *`. Each form SHALL also be reachable via a dedicated subpath export (`./components/login-form`, etc.) for tree-shaking. #### Scenario: root barrel surface - **WHEN** the root-barrel test enumerates `Object.keys(import("@suaveplan/react-auth"))` - **THEN** every Wave 2 form symbol is present alongside the Wave 1 surface **Verified by:** `src/index.test.ts::"the root barrel exports exactly the documented public surface"`