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

9.0 KiB
Raw Blame History

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.

// 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:

// 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:

'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:

// 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:

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:

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

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:

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:

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:

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 for the full setup (API details, page example, linking users to clients, and security).