feat(auth): expose individual auth page components as a public entry point
This commit is contained in:
@@ -63,6 +63,9 @@
|
|||||||
"./features/auth/client": {
|
"./features/auth/client": {
|
||||||
"import": "./dist/features/auth/AuthPage.client.js"
|
"import": "./dist/features/auth/AuthPage.client.js"
|
||||||
},
|
},
|
||||||
|
"./features/auth/pages": {
|
||||||
|
"import": "./dist/features/auth/pages/index.js"
|
||||||
|
},
|
||||||
"./features/admin": {
|
"./features/admin": {
|
||||||
"import": "./dist/features/admin/index.js"
|
"import": "./dist/features/admin/index.js"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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';
|
||||||
|
```
|
||||||
@@ -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 site’s 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 Zen’s 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 don’t 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.
|
|
||||||
@@ -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';
|
||||||
Reference in New Issue
Block a user