feat(auth): expose individual auth page components as a public entry point

This commit is contained in:
2026-04-23 19:55:35 -04:00
parent c901e81c83
commit 995edae513
4 changed files with 111 additions and 347 deletions
+3
View File
@@ -63,6 +63,9 @@
"./features/auth/client": {
"import": "./dist/features/auth/AuthPage.client.js"
},
"./features/auth/pages": {
"import": "./dist/features/auth/pages/index.js"
},
"./features/admin": {
"import": "./dist/features/admin/index.js"
},
+102
View File
@@ -0,0 +1,102 @@
# Pages d'authentification personnalisées
Pour utiliser sa propre mise en page, on crée des routes Next.js qui enveloppent les composants Zen dans un layout.
Chaque page suit le même patron : un **composant serveur** qui charge la session et passe les actions, et un **wrapper client** qui gère la navigation.
- Composants : `@zen/core/features/auth/pages`
- Actions : `@zen/core/features/auth/actions`
---
## Composants disponibles
| Page | Composant | Props principales |
|-----------------------|------------------------|-------------------|
| Connexion | `LoginPage` | `onSubmit`, `onSetSessionCookie`, `onNavigate`, `redirectAfterLogin`, `currentUser` |
| Inscription | `RegisterPage` | `onSubmit`, `onNavigate`, `currentUser` |
| Mot de passe oublié | `ForgotPasswordPage` | `onSubmit`, `onNavigate`, `currentUser` |
| Réinitialisation | `ResetPasswordPage` | `onSubmit`, `onNavigate`, `email`, `token` |
| Confirmation courriel | `ConfirmEmailPage` | `onSubmit`, `onNavigate`, `email`, `token` |
| Déconnexion | `LogoutPage` | `onLogout`, `onSetSessionCookie` |
`onNavigate` reçoit une valeur parmi : `'login' | 'register' | 'forgot' | 'reset'`.
---
## Exemple : page de connexion
**Serveur**`app/auth/login/page.js`
```js
import { getSession, loginAction, setSessionCookie } from '@zen/core/features/auth/actions';
import { LoginPageWrapper } from './LoginPageWrapper';
export default async function LoginRoute() {
const session = await getSession();
return (
<MonLayout>
<LoginPageWrapper
loginAction={loginAction}
setSessionCookie={setSessionCookie}
currentUser={session?.user ?? null}
/>
</MonLayout>
);
}
```
**Client**`app/auth/login/LoginPageWrapper.js`
```js
'use client';
import { useRouter } from 'next/navigation';
import { LoginPage } from '@zen/core/features/auth/pages';
export function LoginPageWrapper({ loginAction, setSessionCookie, currentUser }) {
const router = useRouter();
return (
<LoginPage
onSubmit={loginAction}
onSetSessionCookie={setSessionCookie}
onNavigate={(page) => router.push(`/auth/${page}`)}
redirectAfterLogin="/"
currentUser={currentUser}
/>
);
}
```
Le même patron s'applique à toutes les autres pages. Les différences :
- **RegisterPage** : passer `registerAction` comme `onSubmit`
- **ForgotPasswordPage** : passer `forgotPasswordAction` comme `onSubmit`
- **ResetPasswordPage** / **ConfirmEmailPage** : lire `email` et `token` dans `searchParams` et les passer en props
- **LogoutPage** : utiliser `onLogout` au lieu de `onSubmit`, pas de `onNavigate` requis
---
## Protection de route
```js
import { getSession } from '@zen/core/features/auth/actions';
import { redirect } from 'next/navigation';
export default async function PageProtégée() {
const session = await getSession();
if (!session?.user) redirect('/auth/login');
// ...
}
```
---
## Page par défaut (sans personnalisation)
Si une mise en page personnalisée n'est pas nécessaire, on garde simplement :
```js
// app/auth/[...auth]/page.js
export { default } from '@zen/core/features/auth/server';
```
-347
View File
@@ -1,347 +0,0 @@
# Custom auth pages
This guide explains how to build your own auth pages (login, register, forgot password, reset password, confirm email, logout) so they match your sites layout and style. For a basic site you can keep using the [default auth page](#default-auth-page).
## Overview
You can use a **custom page for every auth flow**:
| Page | Component | Server action(s) |
|-----------------|-----------------------|-------------------------------------|
| Login | `LoginPage` | `loginAction`, `setSessionCookie` |
| Register | `RegisterPage` | `registerAction` |
| Forgot password | `ForgotPasswordPage` | `forgotPasswordAction` |
| Reset password | `ResetPasswordPage` | `resetPasswordAction` |
| Confirm email | `ConfirmEmailPage` | `verifyEmailAction` |
| Logout | `LogoutPage` | `logoutAction`, `setSessionCookie` |
- **Components**: from `@zen/core/auth/components`
- **Actions**: from `@zen/core/auth/actions`
Create your own routes (e.g. `/login`, `/register`, `/auth/forgot`) and wrap Zens components in your layout. Each page follows the same pattern: a **server component** that loads data and passes actions, and a **client wrapper** that handles navigation and renders the Zen component.
---
## Route structure
Choose a URL scheme and use it consistently. Two common options:
**Option A All under `/auth/*` (like the default)**
`/auth/login`, `/auth/register`, `/auth/forgot`, `/auth/reset`, `/auth/confirm`, `/auth/logout`
**Option B Top-level routes**
`/login`, `/register`, `/forgot`, `/reset`, `/confirm`, `/logout`
The `onNavigate` callback receives one of: `'login' | 'register' | 'forgot' | 'reset'`. Map each to your chosen path, e.g. `router.push(\`/auth/${page}\`)` or `router.push(\`/${page}\`)`.
Reset and confirm pages need `email` and `token` from the URL (e.g. `/auth/reset?email=...&token=...`). Your server page can read `searchParams` and pass them to the component.
---
## Component reference (props)
Use this when wiring each custom page.
| Component | Props |
|-----------------------|--------|
| **LoginPage** | `onSubmit` (loginAction), `onSetSessionCookie`, `onNavigate`, `redirectAfterLogin`, `currentUser` |
| **RegisterPage** | `onSubmit` (registerAction), `onNavigate`, `currentUser` |
| **ForgotPasswordPage**| `onSubmit` (forgotPasswordAction), `onNavigate`, `currentUser` |
| **ResetPasswordPage** | `onSubmit` (resetPasswordAction), `onNavigate`, `email`, `token` (from URL) |
| **ConfirmEmailPage** | `onSubmit` (verifyEmailAction), `onNavigate`, `email`, `token` (from URL) |
| **LogoutPage** | `onLogout` (logoutAction), `onSetSessionCookie` (optional) |
---
## 1. Login
**Server:** `app/login/page.js` (or `app/auth/login/page.js`)
```js
import { getSession, loginAction, setSessionCookie } from '@zen/core/auth/actions';
import { LoginPageWrapper } from './LoginPageWrapper';
export default async function LoginRoute() {
const session = await getSession();
return (
<YourSiteLayout>
<LoginPageWrapper
loginAction={loginAction}
setSessionCookie={setSessionCookie}
currentUser={session?.user ?? null}
/>
</YourSiteLayout>
);
}
```
**Client:** `app/login/LoginPageWrapper.js`
```js
'use client';
import { useRouter } from 'next/navigation';
import { LoginPage } from '@zen/core/auth/components';
export function LoginPageWrapper({ loginAction, setSessionCookie, currentUser }) {
const router = useRouter();
return (
<LoginPage
onSubmit={loginAction}
onSetSessionCookie={setSessionCookie}
onNavigate={(page) => router.push(`/auth/${page}`)}
redirectAfterLogin="/"
currentUser={currentUser}
/>
);
}
```
---
## 2. Register
**Server:** `app/register/page.js`
```js
import { getSession, registerAction } from '@zen/core/auth/actions';
import { RegisterPageWrapper } from './RegisterPageWrapper';
export default async function RegisterRoute() {
const session = await getSession();
return (
<YourSiteLayout>
<RegisterPageWrapper
registerAction={registerAction}
currentUser={session?.user ?? null}
/>
</YourSiteLayout>
);
}
```
**Client:** `app/register/RegisterPageWrapper.js`
```js
'use client';
import { useRouter } from 'next/navigation';
import { RegisterPage } from '@zen/core/auth/components';
export function RegisterPageWrapper({ registerAction, currentUser }) {
const router = useRouter();
return (
<RegisterPage
onSubmit={registerAction}
onNavigate={(page) => router.push(`/auth/${page}`)}
currentUser={currentUser}
/>
);
}
```
---
## 3. Forgot password
**Server:** `app/forgot/page.js`
```js
import { getSession, forgotPasswordAction } from '@zen/core/auth/actions';
import { ForgotPasswordPageWrapper } from './ForgotPasswordPageWrapper';
export default async function ForgotRoute() {
const session = await getSession();
return (
<YourSiteLayout>
<ForgotPasswordPageWrapper
forgotPasswordAction={forgotPasswordAction}
currentUser={session?.user ?? null}
/>
</YourSiteLayout>
);
}
```
**Client:** `app/forgot/ForgotPasswordPageWrapper.js`
```js
'use client';
import { useRouter } from 'next/navigation';
import { ForgotPasswordPage } from '@zen/core/auth/components';
export function ForgotPasswordPageWrapper({ forgotPasswordAction, currentUser }) {
const router = useRouter();
return (
<ForgotPasswordPage
onSubmit={forgotPasswordAction}
onNavigate={(page) => router.push(`/auth/${page}`)}
currentUser={currentUser}
/>
);
}
```
---
## 4. Reset password
Requires `email` and `token` from the reset link (e.g. `/auth/reset?email=...&token=...`). Read `searchParams` in the server component and pass them to the client.
**Server:** `app/auth/reset/page.js` (or `app/reset/page.js` with dynamic segment if needed)
```js
import { resetPasswordAction } from '@zen/core/auth/actions';
import { ResetPasswordPageWrapper } from './ResetPasswordPageWrapper';
export default async function ResetRoute({ searchParams }) {
const params = typeof searchParams?.then === 'function' ? await searchParams : searchParams ?? {};
const email = params.email ?? '';
const token = params.token ?? '';
return (
<YourSiteLayout>
<ResetPasswordPageWrapper
resetPasswordAction={resetPasswordAction}
email={email}
token={token}
/>
</YourSiteLayout>
);
}
```
**Client:** `app/auth/reset/ResetPasswordPageWrapper.js`
```js
'use client';
import { useRouter } from 'next/navigation';
import { ResetPasswordPage } from '@zen/core/auth/components';
export function ResetPasswordPageWrapper({ resetPasswordAction, email, token }) {
const router = useRouter();
return (
<ResetPasswordPage
onSubmit={resetPasswordAction}
onNavigate={(page) => router.push(`/auth/${page}`)}
email={email}
token={token}
/>
);
}
```
---
## 5. Confirm email
Requires `email` and `token` from the verification link (e.g. `/auth/confirm?email=...&token=...`).
**Server:** `app/auth/confirm/page.js`
```js
import { verifyEmailAction } from '@zen/core/auth/actions';
import { ConfirmEmailPageWrapper } from './ConfirmEmailPageWrapper';
export default async function ConfirmRoute({ searchParams }) {
const params = typeof searchParams?.then === 'function' ? await searchParams : searchParams ?? {};
const email = params.email ?? '';
const token = params.token ?? '';
return (
<YourSiteLayout>
<ConfirmEmailPageWrapper
verifyEmailAction={verifyEmailAction}
email={email}
token={token}
/>
</YourSiteLayout>
);
}
```
**Client:** `app/auth/confirm/ConfirmEmailPageWrapper.js`
```js
'use client';
import { useRouter } from 'next/navigation';
import { ConfirmEmailPage } from '@zen/core/auth/components';
export function ConfirmEmailPageWrapper({ verifyEmailAction, email, token }) {
const router = useRouter();
return (
<ConfirmEmailPage
onSubmit={verifyEmailAction}
onNavigate={(page) => router.push(`/auth/${page}`)}
email={email}
token={token}
/>
);
}
```
---
## 6. Logout
**Server:** `app/auth/logout/page.js`
```js
import { logoutAction, setSessionCookie } from '@zen/core/auth/actions';
import { LogoutPageWrapper } from './LogoutPageWrapper';
export default function LogoutRoute() {
return (
<YourSiteLayout>
<LogoutPageWrapper
logoutAction={logoutAction}
setSessionCookie={setSessionCookie}
/>
</YourSiteLayout>
);
}
```
**Client:** `app/auth/logout/LogoutPageWrapper.js`
```js
'use client';
import { LogoutPage } from '@zen/core/auth/components';
export function LogoutPageWrapper({ logoutAction, setSessionCookie }) {
return (
<LogoutPage
onLogout={logoutAction}
onSetSessionCookie={setSessionCookie}
/>
);
}
```
---
## Protecting routes
Use `protect()` from `@zen/core/auth` and set `redirectTo` to your custom login path:
```js
import { protect } from '@zen/core/auth';
export const middleware = protect({ redirectTo: '/login' });
```
So unauthenticated users are sent to your custom login page.
---
## Default auth page
If you dont need a custom layout, keep using the built-in auth UI. In `app/auth/[...auth]/page.js`:
```js
export { default } from '@zen/core/auth/page';
```
This serves login, register, forgot, reset, confirm, and logout under `/auth/*` with the default styling.
+6
View File
@@ -0,0 +1,6 @@
export { default as LoginPage } from './LoginPage.client.js';
export { default as RegisterPage } from './RegisterPage.client.js';
export { default as ForgotPasswordPage } from './ForgotPasswordPage.client.js';
export { default as ResetPasswordPage } from './ResetPasswordPage.client.js';
export { default as ConfirmEmailPage } from './ConfirmEmailPage.client.js';
export { default as LogoutPage } from './LogoutPage.client.js';