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

9.9 KiB
Raw Permalink Blame History

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"