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

149 lines
9.9 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.
<!-- markdownlint-disable MD024 -->
# 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"`
<!-- markdownlint-enable MD024 -->