9.9 KiB
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-emailinto 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
onSubmitis NOT called
Scenario: happy path submit
- WHEN the user fills
alice@example.com+correcthorsebatterystapleand 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
onSubmitis NOT called
Scenario: username regex enforced
- WHEN username
"has space"is submitted - THEN the Zod regex error appears and
onSubmitis NOT called
Scenario: happy path submit
- WHEN every required field is valid and the form is submitted
- THEN
onSubmitreceives the full typed payload including the defaultsex: "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
onSubmitis NOT called
Scenario: success view rendered
- WHEN
isSuccessis true andemailSent="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
onSubmitis 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
onContinueToLoginis called once
Scenario: error CTA fires onTryAgain and respects isLoading
- WHEN
status="error"andisLoading=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
onRegisterAgainis 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"