Files
core/src/features/auth/README-dashboard.md
T
hykocx 81172bda94 chore: rename package from @hykocx/zen to @zen/core
Update all references across source files, documentation, and
configuration to reflect the new package scope and name. This includes
updating `.npmrc` registry config, install instructions, module
examples, and all import path comments throughout the codebase.
2026-04-12 15:09:26 -04:00

275 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Client dashboard and user features
This guide explains how to build a **client dashboard** in your Next.js app using Zen auth: protect routes, show the current user (name, avatar), add an account section to edit profile and avatar, and redirect to login when the user is not connected.
## What is available
| Need | Solution |
|------|----------|
| Require login on a page | `protect()` in a **server component** redirects to login if not authenticated |
| Get current user on server | `getSession()` from `@zen/core/auth/actions` |
| Check auth without redirect | `checkAuth()` from `@zen/core/auth` |
| Require a role | `requireRole(['admin', 'manager'])` from `@zen/core/auth` |
| Show user in client (header/nav) | `UserMenu` or `UserAvatar` + `useCurrentUser` from `@zen/core/auth/components` |
| Edit account (name + avatar) | `AccountSection` from `@zen/core/auth/components` |
| Call user API from client | `GET /zen/api/users/me`, `PUT /zen/api/users/profile`, `POST/DELETE /zen/api/users/profile/picture` (with `credentials: 'include'`) |
All user APIs are **session-based**: the session cookie is read on the server. No token in client code. Avatar and profile updates are scoped to the current user; the API validates the session on every request.
---
## 1. Protect a dashboard page (redirect if not logged in)
Use `protect()` in a **server component**. If there is no valid session, the user is redirected to the login page.
```js
// app/dashboard/page.js (Server Component)
import { protect } from '@zen/core/auth';
import { DashboardClient } from './DashboardClient';
export default async function DashboardPage() {
const session = await protect({ redirectTo: '/auth/login' });
return (
<div>
<h1>Dashboard</h1>
<DashboardClient initialUser={session.user} />
</div>
);
}
```
- `redirectTo`: where to send the user if not authenticated (default: `'/auth/login'`).
- `protect()` returns the **session** (with `session.user`: `id`, `email`, `name`, `role`, `image`, etc.).
---
## 2. Display the current user in the layout (name, avatar)
**Option A Server: pass user into a client component**
In your layout or header (server component), get the session and pass `user` to a client component that shows avatar and name:
```js
// app/layout.js or app/dashboard/layout.js
import { getSession } from '@zen/core/auth/actions';
import { UserMenu } from '@zen/core/auth/components';
export default async function Layout({ children }) {
const session = await getSession();
return (
<div>
<header>
{session?.user ? (
<UserMenu user={session.user} accountHref="/dashboard/account" logoutHref="/auth/logout" />
) : (
<a href="/auth/login">Log in</a>
)}
</header>
{children}
</div>
);
}
```
**Option B Client only: fetch user with `useCurrentUser`**
If you prefer not to pass user from the server, use the hook in a client component. It calls `GET /zen/api/users/me` with the session cookie:
```js
'use client';
import { UserMenu } from '@zen/core/auth/components';
export function Header() {
return (
<UserMenu
accountHref="/dashboard/account"
logoutHref="/auth/logout"
/>
);
}
```
`UserMenu` with no `user` prop will call `useCurrentUser()` itself and show a loading state until the request finishes. If the user is not logged in, it renders nothing (you can show a “Log in” link elsewhere).
**Components:**
- **`UserMenu`** Avatar + name + dropdown with “My account” and “Log out”. Props: `user` (optional), `accountHref`, `logoutHref`, `className`.
- **`UserAvatar`** Only the avatar (image or initials). Props: `user`, `size` (`'sm' | 'md' | 'lg'`), `className`.
- **`useCurrentUser()`** Returns `{ user, loading, error, refetch }`. Use when you need the current user in a client component without receiving it from the server.
---
## 3. Account page (edit profile and avatar)
Use **`AccountSection`** on a page that is already protected (e.g. `/dashboard/account`). It shows:
- Profile picture (upload / remove)
- Full name (editable)
- Email (read-only)
- Optional “Account created” date
**Server page:**
```js
// app/dashboard/account/page.js
import { protect } from '@zen/core/auth';
import { AccountSection } from '@zen/core/auth/components';
export default async function AccountPage() {
const session = await protect({ redirectTo: '/auth/login' });
return (
<div>
<h1>My account</h1>
<AccountSection initialUser={session.user} />
</div>
);
}
```
- **`initialUser`** Optional. If you pass `session.user`, the section uses it immediately and does not need an extra API call on load.
- **`onUpdate`** Optional callback after profile or avatar update; you can use it to refresh parent state or revalidate.
`AccountSection` uses:
- `PUT /zen/api/users/profile` for name
- `POST /zen/api/users/profile/picture` for upload
- `DELETE /zen/api/users/profile/picture` for remove
All with `credentials: 'include'` (session cookie). Ensure your app uses **ToastProvider** (from `@zen/core/toast`) if you want toasts.
---
## 4. Check if the user is connected (without redirect)
Use **`checkAuth()`** in a server component when you only need to know whether someone is logged in:
```js
import { checkAuth } from '@zen/core/auth';
export default async function Page() {
const session = await checkAuth();
return session ? <div>Hello, {session.user.name}</div> : <div>Please log in</div>;
}
```
Use **`requireRole()`** when a page is only for certain roles:
```js
import { requireRole } from '@zen/core/auth';
export default async function ManagerPage() {
const session = await requireRole(['admin', 'manager'], {
redirectTo: '/auth/login',
forbiddenRedirect: '/dashboard',
});
return <div>Manager content</div>;
}
```
---
## 5. Security summary
- **Session cookie**: HttpOnly, validated on the server for every protected API call.
- **User APIs**:
- `GET /zen/api/users/me` current user only.
- `PUT /zen/api/users/profile` update only the authenticated users name.
- Profile picture upload/delete scoped to the current user; storage path includes `users/{userId}/...`.
- **Storage**: User files under `users/{userId}/...` are only served if the request session matches that `userId` (or admin).
- **Protection**: Use `protect()` or `requireRole()` in server components so unauthenticated or unauthorized users never see sensitive dashboard content.
---
## 6. Minimal dashboard example
```text
app/
layout.js # Root layout with ToastProvider if you use it
auth/
[...auth]/page.js # Zen default auth page (login, register, logout, etc.)
dashboard/
layout.js # Optional: layout that shows UserMenu and requires login
page.js # Protected dashboard home
account/
page.js # Protected account page with AccountSection
```
**dashboard/layout.js:**
```js
import { protect } from '@zen/core/auth';
import { UserMenu } from '@zen/core/auth/components';
import Link from 'next/link';
export default async function DashboardLayout({ children }) {
const session = await protect({ redirectTo: '/auth/login' });
return (
<div>
<header className="flex justify-between items-center p-4 border-b">
<Link href="/dashboard">Dashboard</Link>
<UserMenu
user={session.user}
accountHref="/dashboard/account"
logoutHref="/auth/logout"
/>
</header>
<main className="p-4">{children}</main>
</div>
);
}
```
**dashboard/page.js:**
```js
import { protect } from '@zen/core/auth';
export default async function DashboardPage() {
const session = await protect({ redirectTo: '/auth/login' });
return (
<div>
<h1>Welcome, {session.user.name}</h1>
<p><a href="/dashboard/account">Edit my account</a></p>
</div>
);
}
```
**dashboard/account/page.js:**
```js
import { protect } from '@zen/core/auth';
import { AccountSection } from '@zen/core/auth/components';
export default async function AccountPage() {
const session = await protect({ redirectTo: '/auth/login' });
return (
<div>
<h1>My account</h1>
<AccountSection initialUser={session.user} />
</div>
);
}
```
This gives you a protected dashboard, user display in the header, and a dedicated account page to modify profile and avatar, with redirect to login when the user is not connected.
---
## 7. Facturation (invoices) section
If you use the **Invoice** module and want logged-in users to see their own invoices in the dashboard:
- **Userclient link**: In the admin, link a user to a client (User edit → Client). Only invoices for that client are shown.
- **API**: `GET /zen/api/invoices/me` (session required) returns the current users linked client and that clients invoices.
- **Component**: Use `ClientInvoicesSection` from `@zen/core/invoice/dashboard` on a protected page (e.g. `/dashboard/invoices`).
See the [Invoice module dashboard guide](../../modules/invoice/README-dashboard.md) for the full setup (API details, page example, linking users to clients, and security).