first commit
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
# `<EmailVerificationPage>` — routing-aware verification page
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`<EmailVerificationPage>` is the routing-aware adapter that wraps the Wave 2 `<EmailVerificationView>` for the `?token=…`-bearing email-verification link sent by the server. It extracts the token from the URL search params, calls `useAuth().verifyEmail(token)` exactly once on mount, and renders one of four states via the underlying view component: `verifying` (default while the call is in flight), `success` (after a resolved call), `expired` (after a rejection whose message contains "expired" or "invalid"), or `error` (everything else).
|
||||
|
||||
Use this as the `Route element` for `/auth/verify-email`. The page never re-implements the four state cards — those live in the Wave 2 view.
|
||||
|
||||
## Features
|
||||
|
||||
- `useAuthGuard({ requireGuest: true })` so authenticated visitors are redirected away.
|
||||
- Single in-flight `verifyEmail(token)` call on mount; React's `useEffect` dep array is `[token, verifyEmail]` so re-renders don't fire spurious calls.
|
||||
- Error classifier: rejections whose `message` contains `expired` or `invalid` (case-insensitive) map to the `expired` state; all other failures map to `error`.
|
||||
- Three navigation callbacks wired into the view: `onContinueToLogin` → `/auth/login`, `onRegisterAgain` → `/auth/register`, `onTryAgain` reloads the page.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```tsx
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { EmailVerificationPage } from "@suaveplan/react-auth";
|
||||
|
||||
export function AuthRoutes() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
path="/auth/verify-email"
|
||||
element={<EmailVerificationPage />}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
When sending the verification email, embed the token in the URL:
|
||||
|
||||
```ts
|
||||
const link = new URL("/auth/verify-email", site.origin);
|
||||
link.searchParams.set("token", verificationToken);
|
||||
await mailer.send({ to: user.email, subject, body: link.toString() });
|
||||
```
|
||||
|
||||
If you want a different post-verification destination, wrap the page in a custom layout that hooks into `useAuth()` to redirect on `user.emailVerified === true`.
|
||||
|
||||
## API Reference
|
||||
|
||||
`<EmailVerificationPage>` accepts no props.
|
||||
|
||||
### `EmailVerificationPageState`
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `status` | `EmailVerificationStatus` | One of `"verifying" \| "success" \| "error" \| "expired"`. |
|
||||
| `message` | `string` | Human-readable message displayed by the view. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The reload-style `onTryAgain` handler intentionally short-circuits on SSR (`typeof window === "undefined"`). The page is browser-only in practice, but the guard keeps the code defensive.
|
||||
- The `verifyEmail` mount-time call is fired even if the token is malformed; the server's response carries the failure mode (404, expired, malformed) which the classifier maps to `expired` / `error`.
|
||||
- The page intentionally does NOT renavigate on success — the user clicks the visible CTA to continue to login, mirroring the upstream UX where some users may want to copy state from the verification screen first.
|
||||
@@ -0,0 +1,70 @@
|
||||
# `<ForgotPasswordPage>` — routing-aware reset-link page
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`<ForgotPasswordPage>` is the routing-aware adapter that wraps the Wave 2 `<ForgotPasswordForm>` with the navigation and auth-context wiring needed for a "forgot your password?" route. It drives the form's `isSuccess` / `emailSent` props from local state on a successful `useAuth().forgotPassword(email)` call, and clears that state when the form's `onReset` callback fires.
|
||||
|
||||
Use this as the `Route element` for `/auth/forgot-password`. The page never navigates on success — it transitions the same form node to its built-in success view, mirroring the upstream UX.
|
||||
|
||||
## Features
|
||||
|
||||
- Calls `useAuth().forgotPassword(email)` on submit.
|
||||
- Tracks local `isSuccess` / `emailSent` state so the form renders the success card after a resolved call.
|
||||
- `onReset` clears local state and returns the form to its entry view, so the user can send another link if needed.
|
||||
- `forgotPassword` rejections are swallowed; errors surface via `useAuth().error`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```tsx
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { ForgotPasswordPage } from "@suaveplan/react-auth";
|
||||
|
||||
export function AuthRoutes() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
path="/auth/forgot-password"
|
||||
element={<ForgotPasswordPage />}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
To override the success-view copy or wire analytics into the submission, mount `<ForgotPasswordForm>` directly with custom handlers — for example to capture an analytics event:
|
||||
|
||||
```tsx
|
||||
import { useAuth, ForgotPasswordForm } from "@suaveplan/react-auth";
|
||||
|
||||
function CustomForgotPassword() {
|
||||
const { forgotPassword, error } = useAuth();
|
||||
const [success, setSuccess] = useState(false);
|
||||
return (
|
||||
<ForgotPasswordForm
|
||||
onSubmit={async ({ email }) => {
|
||||
track("password.forgot.submit", { email });
|
||||
await forgotPassword(email);
|
||||
setSuccess(true);
|
||||
}}
|
||||
isSuccess={success}
|
||||
emailSent="(redacted)"
|
||||
error={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
`<ForgotPasswordPage>` accepts no props.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The page does NOT guard against authenticated visitors — a logged-in user reaching the forgot-password route is a legitimate flow when they want to rotate their password from a known-good session.
|
||||
- The `emailSent` state is the literal value the user typed, not a redacted echo — the form decides how to render it.
|
||||
67
docs/ui/react/react-auth/src/pages/login/login.md
Normal file
67
docs/ui/react/react-auth/src/pages/login/login.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# `<LoginPage>` — routing-aware login page
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`<LoginPage>` is the routing-aware adapter that wraps the Wave 2 `<LoginForm>` with the navigation and auth-context wiring needed to drop into a `react-router-dom` v7 application. It applies a guest-only auth guard, calls `useAuth().signIn` on submit, redirects to `?returnTo` (or `/dashboard` as fallback) on success, and surfaces errors through the form's destructive alert via the `useAuth().error` field.
|
||||
|
||||
Use this component for the sign-in route of a shipping application. For a customisable lower-level UI consider mounting `<LoginForm>` directly and wiring submission and navigation manually.
|
||||
|
||||
## Features
|
||||
|
||||
- `useAuthGuard({ requireGuest: true })` — already-authenticated visitors are redirected to `/`.
|
||||
- `?returnTo` query param honoured for post-login navigation; defaults to `/dashboard`.
|
||||
- `signIn` rejections are swallowed so the page never throws into a router error boundary.
|
||||
- Reads `useAuth().isLoading` to disable the submit button during the in-flight `signIn`.
|
||||
- Reads `useAuth().error` to render the destructive alert in the form.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```tsx
|
||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||
import { AuthProvider, LoginPage } from "@suaveplan/react-auth";
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<AuthProvider config={{ baseURL: "https://auth.example.com" }}>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/auth/login" element={<LoginPage />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</AuthProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
When a protected route renders `<AuthGuard requireAuth fallback={<Navigate to={`/auth/login?returnTo=${path}`} />}>`, the user returns to the original path after sign-in:
|
||||
|
||||
```tsx
|
||||
import { Navigate, useLocation } from "react-router-dom";
|
||||
|
||||
function Protected({ children }) {
|
||||
const { isAuthenticated } = useAuth();
|
||||
const loc = useLocation();
|
||||
if (!isAuthenticated) {
|
||||
return <Navigate to={`/auth/login?returnTo=${loc.pathname}`} replace />;
|
||||
}
|
||||
return children;
|
||||
}
|
||||
```
|
||||
|
||||
The `LoginPage` reads the `returnTo` search param via `useSearchParams` and routes the user back after a successful sign-in.
|
||||
|
||||
## API Reference
|
||||
|
||||
`<LoginPage>` accepts no props. It reads context via `useAuth()` and `useSearchParams()`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The page mounts the Wave 2 `<LoginForm>` and never re-implements form-level concerns (Zod validation, react-hook-form wiring, password show/hide). Those remain in the form layer.
|
||||
- The submit handler is `async` and catches synchronously; nothing propagates to a React error boundary on a failed `signIn`.
|
||||
- The guard is a side-effect of `useAuthGuard` — the page still renders the form for one frame while the guard's `useEffect` schedules the redirect.
|
||||
@@ -0,0 +1,64 @@
|
||||
# profile-account-section — `<AccountSection>`
|
||||
|
||||
## Purpose
|
||||
|
||||
`profile-account-section.tsx` houses the delete-account tab renderer for `<ProfilePage>`. It is split out of `profile-sections.tsx` so each implementation file stays under the 300-line hard cap. The component renders an `<Alert variant="destructive">` framing a danger-zone block; clicking the initial delete button reveals a confirmation pane with a password input and two CTAs (`confirm-delete`, `cancel-delete`). The parent page owns the visibility state via `showDeleteConfirm` and the three callbacks (`onRequestDelete`, `onCancelDelete`, `onConfirmDelete`).
|
||||
|
||||
## Features
|
||||
|
||||
- Single-card destructive surface using the `react-ui` `<Alert variant="destructive">` primitive.
|
||||
- Two-stage confirmation: initial "Delete Account" button → confirm pane with password re-entry + "Yes, Delete My Account" + "Cancel".
|
||||
- The confirm button is disabled while `isLoading` is true OR the password field is empty.
|
||||
- Spinning loader (`<Loader2>`) appears alongside the deleting copy while the call is in flight.
|
||||
- Errors at the `currentPassword` field surface inline.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```tsx
|
||||
import { AccountSection } from "./profile-account-section.js";
|
||||
|
||||
<AccountSection
|
||||
t={t}
|
||||
formData={formData}
|
||||
errors={errors}
|
||||
isLoading={isLoading}
|
||||
onChange={handleInputChange}
|
||||
showDeleteConfirm={showDeleteConfirm}
|
||||
onRequestDelete={() => setShowDeleteConfirm(true)}
|
||||
onCancelDelete={handleCancelDelete}
|
||||
onConfirmDelete={handleConfirmDelete}
|
||||
/>;
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Hooking an analytics callback into the delete flow is handled at the parent page level via `onDeleteAccount`. From this component's perspective, the `onConfirmDelete` callback is the only one that triggers a network call:
|
||||
|
||||
```tsx
|
||||
async function handleConfirmDelete() {
|
||||
if (!formData.currentPassword) {
|
||||
setErrors({ currentPassword: t.errorPasswordRequiredForDelete });
|
||||
return;
|
||||
}
|
||||
await deleteAccount(formData.currentPassword);
|
||||
track("account.deleted");
|
||||
navigate("/auth/login");
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `AccountSectionProps extends SectionProps`
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `showDeleteConfirm` | `boolean` | When `true`, the confirm pane is rendered. |
|
||||
| `onRequestDelete` | `() => void` | Invoked when the initial delete button is clicked. |
|
||||
| `onCancelDelete` | `() => void` | Invoked when the cancel button is clicked. |
|
||||
| `onConfirmDelete` | `() => Promise<void>` | Invoked when the confirm button is clicked. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The component reuses `formData.currentPassword` instead of introducing a `deletePassword` field — the parent page resets that field on cancel.
|
||||
- The placeholder copy on the delete-password input is intentionally the `errorPasswordRequiredForDelete` string (also used as the inline error). This matches upstream UX: the placeholder explains the requirement, the error reinforces it after a failed submit.
|
||||
- The component does not render its own `<form>` — the confirm button is a regular `<Button>` whose `onClick` triggers the delete. That keeps the password input out of the `<form>` element used by `<PasswordSection>` and prevents accidental form-submit collisions.
|
||||
@@ -0,0 +1,76 @@
|
||||
# profile-sections — `<ProfileSection>` + `<PasswordSection>`
|
||||
|
||||
## Purpose
|
||||
|
||||
`profile-sections.tsx` houses the two read/write tab renderers for `<ProfilePage>`: `<ProfileSection>` (name + email update form) and `<PasswordSection>` (current / new / confirm password form with independent visibility toggles). It is a pure-UI render layer — all state and side effects live on the parent page. The split keeps each implementation file under the 300-line hard cap mandated by `CLAUDE.md` while keeping the section markup co-located so a designer changing the profile form does not have to navigate through `<ProfilePage>` itself.
|
||||
|
||||
`<AccountSection>` lives in a sibling file (`profile-account-section.tsx`) because the delete-confirmation flow adds ~120 lines of conditional markup that would push this file over the cap.
|
||||
|
||||
## Features
|
||||
|
||||
- `<ProfileSection>` — name + email inputs, submit button, optional email-level error display.
|
||||
- `<PasswordSection>` — three password rows (current / new / confirm) using a private `<PasswordRow>` factor; per-row visibility toggle button; submit button disabled until both `currentPassword` and `newPassword` are non-empty.
|
||||
- `<PasswordRow>` — private helper that renders `<Label>` + `<Input>` + show/hide `<Button>` with `aria-label="Show password" | "Hide password"`.
|
||||
- Strict typing: `SectionProps` is shared across all section renderers and is exported so `profile-account-section.tsx` can extend it.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```tsx
|
||||
import { ProfileSection } from "./profile-sections.js";
|
||||
|
||||
<ProfileSection
|
||||
t={t}
|
||||
formData={formData}
|
||||
errors={errors}
|
||||
isLoading={isLoading}
|
||||
onChange={handleInputChange}
|
||||
onSubmit={handleProfileUpdate}
|
||||
/>;
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
`<PasswordSection>` takes both a `show` record and a `toggle(key)` setter so the three visibility toggles share a single store on the parent:
|
||||
|
||||
```tsx
|
||||
const [show, setShow] = useState({ current: false, next: false, confirm: false });
|
||||
const toggle = (k) => setShow((prev) => ({ ...prev, [k]: !prev[k] }));
|
||||
|
||||
<PasswordSection
|
||||
{...sectionProps}
|
||||
onSubmit={handlePasswordChange}
|
||||
show={show}
|
||||
toggle={toggle}
|
||||
/>;
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `SectionProps`
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `t` | `ProfilePageStrings` | Resolved translations table. |
|
||||
| `formData` | `ProfilePageFormData` | Current form values. |
|
||||
| `errors` | `Partial<ProfilePageFormData>` | Per-field error messages. |
|
||||
| `isLoading` | `boolean` | Disables every input. |
|
||||
| `onChange` | `(field) => ChangeHandler` | Per-field change-handler factory. |
|
||||
|
||||
### `ProfileSectionProps extends SectionProps`
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `onSubmit` | `(e: FormEvent) => Promise<void>` | Form submit handler. |
|
||||
|
||||
### `PasswordSectionProps extends SectionProps`
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `onSubmit` | `(e: FormEvent) => Promise<void>` | Form submit handler. |
|
||||
| `show` | `{ current; next; confirm }` | Per-row visibility flags. |
|
||||
| `toggle` | `(k) => void` | Flip a row's visibility. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- `PasswordRow` is intentionally NOT exported — it is an internal factor to keep the password section's markup under control. The `aria-label` is hardcoded in English; if a downstream locale wants Welsh / Chinese show/hide labels it should compose a custom password section.
|
||||
- The submit-button disable condition on `<PasswordSection>` requires both `currentPassword` AND `newPassword` to be truthy — this matches upstream behaviour and keeps users from accidentally posting a change request with one field empty.
|
||||
85
docs/ui/react/react-auth/src/pages/profile/profile.md
Normal file
85
docs/ui/react/react-auth/src/pages/profile/profile.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# `<ProfilePage>` — three-tab profile-settings page
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`<ProfilePage>` is the routing-aware profile-settings surface for an authenticated user. It renders three tabs — Profile (name + email), Password (current / new / confirm), and Account (danger zone / delete account) — over the Wave 1 `useAuth()` action surface (`updateProfile`, `changePassword`, `deleteAccount`). It applies `useAuthGuard({ requireAuth: true })` so unauthenticated visitors are redirected to `/auth/login`, hydrates the profile form from `useAuth().user`, and exposes optional `onSuccess` and `onDeleteAccount` callbacks for downstream analytics. All copy flows through a `translations?: Partial<AuthProfilePageStrings>` prop defaulting to `enGb.auth.profilePage`.
|
||||
|
||||
The implementation is split into three files to honour the 300-line hard cap: `profile.tsx` (shell + state + actions), `profile-sections.tsx` (Profile and Password tab renderers), and `profile-account-section.tsx` (Account tab with delete-account confirmation flow). All three sit behind the same package entry point.
|
||||
|
||||
## Features
|
||||
|
||||
- Three tabs via `@suaveplan/react-ui` `<Tabs>`: profile / password / account.
|
||||
- Auth-guard: unauthenticated visitors redirected to `/auth/login`.
|
||||
- Hydrates name + email from `useAuth().user` on mount.
|
||||
- Calls `updateProfile`, `changePassword`, `deleteAccount` from the matching tab.
|
||||
- Local validation on password change: required, ≥8 chars, must match.
|
||||
- Delete-account confirmation flow with a password re-entry input.
|
||||
- Three independent password-visibility toggles (current / new / confirm).
|
||||
- `successMessage` and `error.message` alerts surfaced inline.
|
||||
- Locale overrides via `translations`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```tsx
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { ProfilePage } from "@suaveplan/react-auth";
|
||||
|
||||
export function ProfileRoute() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/profile" element={<ProfilePage />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Hook into analytics:
|
||||
|
||||
```tsx
|
||||
import { ProfilePage } from "@suaveplan/react-auth";
|
||||
|
||||
export function MyProfile() {
|
||||
return (
|
||||
<ProfilePage
|
||||
onSuccess={() => track("profile.update.success")}
|
||||
onDeleteAccount={() => track("account.deleted")}
|
||||
translations={{
|
||||
title: "My Account",
|
||||
subtitle: "Keep your details up to date.",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `ProfilePageProps`
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `onSuccess` | `() => void` | Invoked after profile update or password change succeeds. |
|
||||
| `onDeleteAccount` | `() => void` | Invoked after `deleteAccount` resolves, before navigation. |
|
||||
| `translations` | `Partial<AuthProfilePageStrings>` | Locale overrides. |
|
||||
|
||||
### `ProfilePageFormData`
|
||||
|
||||
`{ name, email, currentPassword, newPassword, confirmPassword }` — five strings.
|
||||
|
||||
### `ProfilePageSection`
|
||||
|
||||
`{ id, title, active, icon }` — descriptor consumed by the `<TabsList>` render loop. `id` is one of `"profile" | "password" | "account"`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The export is `<ProfilePage>`; the upstream `Profile` alias is preserved as a named re-export for back-compat.
|
||||
- The page does NOT navigate after `updateProfile` or `changePassword` succeed — it stays on the same route and surfaces a success alert above the tab bar.
|
||||
- The delete-account flow re-uses the `currentPassword` form field rather than a dedicated input, mirroring the upstream UX.
|
||||
- `clearMessages()` is invoked on every input change so success / error banners disappear when the user starts editing again.
|
||||
- `onValueChange` on `<Tabs>` clears messages so banners from one tab do not bleed across tab changes.
|
||||
68
docs/ui/react/react-auth/src/pages/register/register.md
Normal file
68
docs/ui/react/react-auth/src/pages/register/register.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# `<RegisterPage>` — routing-aware sign-up page
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`<RegisterPage>` is the routing-aware adapter that wraps the Wave 2 `<RegisterForm>` with the navigation and auth-context wiring needed for a sign-up route. It applies a guest-only auth guard, strips `confirmPassword` from the form payload before invoking `useAuth().signUp`, and navigates to `/dashboard` on success.
|
||||
|
||||
Use this component as the `Route element` for the `/auth/register` path. If a downstream application needs a richer flow (multi-step wizard, marketing copy, A/B testing) it can compose `<RegisterForm>` directly instead.
|
||||
|
||||
## Features
|
||||
|
||||
- `useAuthGuard({ requireGuest: true })` — already-authenticated visitors are redirected to `/`.
|
||||
- Strips the client-side-only `confirmPassword` field before calling `signUp`. The `RegisterData` payload that reaches the auth client matches the server contract.
|
||||
- `signUp` rejections are swallowed; errors surface via `useAuth().error` into the form's alert.
|
||||
- Navigates to `/dashboard` on success.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```tsx
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { RegisterPage } from "@suaveplan/react-auth";
|
||||
|
||||
export function AuthRoutes() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/auth/register" element={<RegisterPage />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Combine with a custom landing redirect after sign-up. Because `<RegisterPage>` always navigates to `/dashboard`, an app that wants a different post-sign-up route can intercept via a `useAuth().onAuthStateChange` callback registered on the `<AuthProvider>`:
|
||||
|
||||
```tsx
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { AuthProvider } from "@suaveplan/react-auth";
|
||||
|
||||
function App() {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<AuthProvider
|
||||
config={{ baseURL: "/auth" }}
|
||||
onAuthStateChange={(state) => {
|
||||
if (state.isAuthenticated && state.user?.emailVerified === false) {
|
||||
navigate("/auth/verify-email");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RegisterPage />
|
||||
</AuthProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
`<RegisterPage>` accepts no props. It reads context via `useAuth()` and navigates via `useNavigate()`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The page never inspects the underlying form's Zod schema; it relies on the form to deliver a validated `RegisterFormData` payload.
|
||||
- The destructured `confirmPassword` discard uses a `void` statement to keep Biome's `noUnusedVariables` rule satisfied without resorting to `// biome-ignore`.
|
||||
- The `signUp` action wraps better-auth; failure modes (`USER_EXISTS`, `WEAK_PASSWORD`, network) propagate via the context `error` field.
|
||||
@@ -0,0 +1,56 @@
|
||||
# `<ResetPasswordPage>` — routing-aware password-reset page
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`<ResetPasswordPage>` is the routing-aware adapter that wraps the Wave 2 `<ResetPasswordForm>` for the `?token=…`-bearing reset link emailed by `forgotPassword`. It extracts the token from the URL search params, hands it to `useAuth().resetPassword(token, password)` on submit, and drives the form between three states: entry (token present), success (after resolution), and invalid-token (token absent).
|
||||
|
||||
Use this as the `Route element` for `/auth/reset-password`. The component never re-implements the form-level password rules — both `password.min(8)` and the cross-field `confirmPassword` refinement live in the form.
|
||||
|
||||
## Features
|
||||
|
||||
- `useAuthGuard({ requireGuest: true })` so authenticated visitors are redirected away.
|
||||
- `?token` extracted via `useSearchParams`; `hasToken={!!token}` drives the form's branch.
|
||||
- `useAuth().resetPassword(token, password)` invoked on submit when a token is present.
|
||||
- Local `isSuccess` flips after a resolved call so the form renders the "password reset!" card.
|
||||
- Two navigation callbacks: `onNavigateToLogin` (`/auth/login`) and `onNavigateToForgotPassword` (`/auth/forgot-password`).
|
||||
- `resetPassword` rejections are swallowed; errors surface via `useAuth().error`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```tsx
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { ResetPasswordPage } from "@suaveplan/react-auth";
|
||||
|
||||
export function AuthRoutes() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/auth/reset-password" element={<ResetPasswordPage />} />
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
When emailing the reset link, embed the token directly:
|
||||
|
||||
```ts
|
||||
const link = new URL("/auth/reset-password", site.origin);
|
||||
link.searchParams.set("token", resetToken);
|
||||
await mailer.send({ to, subject, body: link.toString() });
|
||||
```
|
||||
|
||||
The page extracts the token via `useSearchParams().get("token")` and renders the invalid-token branch if the user lands on the route without a token (e.g. they typed the URL by hand). Both branches share the same component instance so React preserves form state across the entry/success transitions, but throws state away on the entry/invalid transition (which only happens on page reload anyway).
|
||||
|
||||
## API Reference
|
||||
|
||||
`<ResetPasswordPage>` accepts no props. It reads context via `useAuth()` and routing via `useSearchParams` / `useNavigate`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The `if (!token) return;` guard inside `handleSubmit` is defence in depth — the form already renders the invalid-token view in that case and never invokes `onSubmit`.
|
||||
- The page does not memoise the navigation callbacks; `<ResetPasswordForm>` re-renders cheaply and React's `useCallback` is not warranted here.
|
||||
Reference in New Issue
Block a user