# Add @suaveplan/react-auth — pages + PermissionGate (Wave 3) **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) and Wave 2 shipped the five pure-UI form components. Downstream apps still cannot drop the package into a `react-router-dom` app without re-implementing the routing-aware adapter layer on every page. Wave 3 closes the migration by shipping the six page components (`LoginPage`, `RegisterPage`, `ForgotPasswordPage`, `ResetPasswordPage`, `EmailVerificationPage`, `ProfilePage`) that wire Wave 2 forms to the Wave 1 `useAuth()` actions plus `useNavigate` / `useSearchParams` redirects, and the `PermissionGate` component that conditionally renders children against the Wave 1 `usePermission` hook. After this wave the public surface of `@suaveplan/react-auth` reaches full parity with the upstream `../suaveplan/packages/react/react-auth/` package and the migration is complete. ## What changes - ADDED: `LoginPage` — wraps ``, calls `useAuth().signIn`, redirects to `?returnTo` or `/dashboard` on success, applies `useAuthGuard({ requireGuest: true })`. - ADDED: `RegisterPage` — wraps ``, strips `confirmPassword`, calls `useAuth().signUp`, redirects to `/dashboard` on success, applies `useAuthGuard({ requireGuest: true })`. - ADDED: `ForgotPasswordPage` — wraps ``, calls `useAuth().forgotPassword(email)`, manages local success state + reset. - ADDED: `ResetPasswordPage` — wraps ``, extracts `?token` from search params, calls `useAuth().resetPassword(token, password)`, manages success state, navigates to `/auth/login` and `/auth/forgot-password`, applies `useAuthGuard({ requireGuest: true })`. - ADDED: `EmailVerificationPage` — wraps ``, extracts `?token`, auto-runs `useAuth().verifyEmail(token)` on mount, classifies errors into `expired` vs `error`, applies `useAuthGuard({ requireGuest: true })`. - ADDED: `ProfilePage` — three-tab profile surface (profile / password / account) calling `useAuth().updateProfile`, `changePassword`, `deleteAccount`; applies `useAuthGuard({ requireAuth: true })`; uses `react-ui` Tabs/Card primitives + a new `profilePage` translation group. - ADDED: `PermissionGate` — renders children when `usePermission()` reports the configured bitmask permissions (any-of by default, all-of when `requireAll`); otherwise renders a fallback (default `null`). - ADDED: `AuthProfilePageStrings` interface + `profilePage` group on every locale pack (`enGb` / `cyGb` / `zhCn`) — purely additive, the existing Wave 1 `profile` group is left untouched. - ADDED: Subpath exports under `./pages/` for every page and `./components/permission-gate` for the gate, mirroring the Wave 1 + Wave 2 per-symbol convention. ## Out of scope - MFA / passkey / magic-link flows — enterprise audit §4 enhancements, never part of upstream. - `RoleGuard` companion to `PermissionGate` — enterprise audit §4 enhancement. - Device-management / audit-trail pages — enterprise audit §4 enhancements. ## Source mapping This change derives from `../suaveplan/enterprise/react-auth.md` and `../suaveplan/packages/react/react-auth/src/`. | Requirement | Source lines | Notes | |---|---|---| | `LoginPage` shape | `pages/login/login.tsx:1-42` | Verbatim semantics; uses Wave 2 ``. | | `RegisterPage` shape | `pages/register/register.tsx:1-41` | Verbatim semantics; strips `confirmPassword` before calling `signUp`. | | `ForgotPasswordPage` shape | `pages/forgot-password/forgot-password.tsx:1-43` | Verbatim semantics. | | `ResetPasswordPage` shape | `pages/reset-password/reset-password.tsx:1-61` | Verbatim semantics; `?token` from search params. | | `EmailVerificationPage` shape | `pages/email-verification/email-verification.tsx:1-96` | Verbatim semantics; upstream's `useTranslation` swapped for Wave 2 `translations` prop pattern (consistent with Wave 1 i18n decision). The dual `EmailVerification` legacy fork (lines 99-254) is NOT ported — it duplicated the `EmailVerificationView` which already shipped in Wave 2. | | `ProfilePage` shape | `pages/profile/profile.tsx:1-638` | Three-tab profile / password / account surface. Upstream's `useTranslation` swapped for a `translations` prop defaulting to `enGb.auth.profilePage`. The implementation is split into `profile-page.tsx` (shell) + `profile-page-sections.tsx` (three section renderers) to honour the 300-line hard cap (§3). | | `PermissionGate` shape | `components/permission-gate/permission-gate.tsx:1-28` | Verbatim semantics. | ### Requirements with no source line | Requirement | Justification | |---|---| | `translations?: Partial<...>` prop on `EmailVerificationPage` and `ProfilePage` | Genesis-specific — Wave 1 dropped `@suaveplan/react-translate`; Wave 2 established the prop-injection precedent. Wave 3 follows. | | New `profilePage` translation group + `AuthProfilePageStrings` interface | Required because the upstream profile page used 20+ string keys not in Wave 1's slim `profile` group; rather than mutate the Wave 1 group (which Wave 2 also reads), Wave 3 adds a sibling `profilePage` group. | | 100% statement / branch / function / line coverage | Genesis-wide policy. | | Sibling `.md` per implementation file | Genesis rule §4. | | Sibling `.stories.tsx` per page + permission-gate | CLAUDE.md: "Every React component needs `.stories.tsx`." | | Sibling `.test.tsx` per page + permission-gate | Genesis-wide — upstream's pages had no co-located unit tests at this layer either. | ## Verification approach Every page and `PermissionGate` ships a sibling `.test.tsx` that uses `@suaveplan/testing/runner` + `@testing-library/react` with real `react-hook-form`, real Zod, real ``, and a real `` wrapped around an injectable `AuthClient` stub. No `vi.mock`. The `wrapForm` helper from Wave 2 is reused / extended to mount the auth context. - `src/pages/login/login.test.tsx` — guard redirect when already authenticated, happy-path navigate to `returnTo`, happy-path default to `/dashboard`, signIn failure swallowed by the page. - `src/pages/register/register.test.tsx` — guard redirect, happy-path strips `confirmPassword`, navigate to `/dashboard`, signUp failure swallowed. - `src/pages/forgot-password/forgot-password.test.tsx` — happy-path submit renders success view with email, `onReset` clears, error surfaces. - `src/pages/reset-password/reset-password.test.tsx` — missing-token branch shows invalid view, happy-path renders success, login navigation CTA, forgot navigation CTA. - `src/pages/email-verification/email-verification.test.tsx` — missing-token sets `error` state, success sets `success` state, expired-token error classifies as `expired`, `onContinueToLogin` / `onRegisterAgain` / `onTryAgain` navigate correctly. - `src/pages/profile/profile.test.tsx` — guard redirect for unauthenticated, three tab switches, profile update success, password change validation (required, length, mismatch), delete-account confirmation flow. - `src/components/permission-gate/permission-gate.test.tsx` — renders children when permitted, renders fallback when denied, `requireAll` AND semantics, unauthenticated denies, numeric permission accepted. - `src/index.test.ts` — extended to assert the 7 new symbols (`LoginPage`, `RegisterPage`, `ForgotPasswordPage`, `ResetPasswordPage`, `EmailVerificationPage`, `ProfilePage`, `PermissionGate`) appear in the root barrel. ## Migration impact - `package.json` gains 7 new subpath exports under `./pages/` and `./components/permission-gate`. No new runtime dependencies — every dep is already present from Wave 1 or Wave 2 (`react-router-dom` is a peer + dev dep on Wave 1). - `vite.config.ts` `lib.entry` gains 7 new entries; `rollupOptions.external` already lists every consumed package. - No Wave 1 or Wave 2 file is modified except `src/index.ts` (named re-exports for the 7 new symbols added — strictly additive), `src/components/index.ts` (one new entry for `PermissionGate`), `src/index.test.ts` (asserts the new symbols are present), and the three locale-pack files which gain the new `profilePage` group as a strictly additive sibling under `auth`. - No downstream consumers yet — `@suaveplan/react-auth-whitelabel` is the only sibling and it ships independently. Verified by `bunx turbo ls --filter=...@suaveplan/react-auth` (empty list).