diff --git a/package.json b/package.json index 0d7e9b0..c749490 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/features/auth/GUIDE-custom-login.md b/src/features/auth/GUIDE-custom-login.md new file mode 100644 index 0000000..3f54d10 --- /dev/null +++ b/src/features/auth/GUIDE-custom-login.md @@ -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 ( + + + + ); +} +``` + +**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 ( + 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'; +``` diff --git a/src/features/auth/README-custom-login.md b/src/features/auth/README-custom-login.md deleted file mode 100644 index 7c9ecb3..0000000 --- a/src/features/auth/README-custom-login.md +++ /dev/null @@ -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 ( - - - - ); -} -``` - -**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 ( - 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 ( - - - - ); -} -``` - -**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 ( - 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 ( - - - - ); -} -``` - -**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 ( - 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 ( - - - - ); -} -``` - -**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 ( - 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 ( - - - - ); -} -``` - -**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 ( - 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 ( - - - - ); -} -``` - -**Client:** `app/auth/logout/LogoutPageWrapper.js` - -```js -'use client'; - -import { LogoutPage } from '@zen/core/auth/components'; - -export function LogoutPageWrapper({ logoutAction, setSessionCookie }) { - return ( - - ); -} -``` - ---- - -## 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. diff --git a/src/features/auth/pages/index.js b/src/features/auth/pages/index.js new file mode 100644 index 0000000..cc9dde1 --- /dev/null +++ b/src/features/auth/pages/index.js @@ -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';