diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 30c807f..2a05155 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,6 +4,7 @@ import "./globals.css"; import { PluginProvider } from "@/lib/plugins/client"; import { getEnabledPluginKeys, syncPluginsFromRegistry } from "@/lib/plugins/server"; import { SeasonBanner } from "@/components/SeasonBanner"; +import { SiteHeaderGuard } from "@/components/SiteHeaderGuard"; import { LocaleProvider } from "@/lib/i18n/client"; import { dict, getLocale } from "@/lib/i18n/server"; @@ -102,6 +103,7 @@ export default async function RootLayout({ + {children} diff --git a/src/components/SignOutButton.tsx b/src/components/SignOutButton.tsx new file mode 100644 index 0000000..9837157 --- /dev/null +++ b/src/components/SignOutButton.tsx @@ -0,0 +1,19 @@ +import { signOut } from "@/auth"; + +export function SignOutButton() { + return ( +
{ + "use server"; + await signOut({ redirectTo: "/" }); + }} + > + +
+ ); +} diff --git a/src/components/SiteHeader.tsx b/src/components/SiteHeader.tsx new file mode 100644 index 0000000..5e88e57 --- /dev/null +++ b/src/components/SiteHeader.tsx @@ -0,0 +1,79 @@ +/** + * Header global affiché sur toutes les pages PUBLIQUES (hors /admin qui a son + * propre shell). Charge la session côté serveur pour adapter les liens. + */ + +import Link from "next/link"; + +import { auth } from "@/auth"; +import { UserRole } from "@/generated/prisma/enums"; + +import { SignOutButton } from "./SignOutButton"; + +export async function SiteHeader() { + const session = await auth(); + const u = session?.user; + const isAdmin = u?.role === UserRole.ADMIN; + const isOwner = u?.role === UserRole.OWNER || isAdmin; + + return ( +
+
+ + + K + + Karbé + + + + +
+ {u ? ( + <> + + Mes réservations + + {isOwner ? ( + + Espace hôte + + ) : null} + {isAdmin ? ( + + Admin + + ) : null} + + {u.name || u.email} + + + + ) : ( + <> + + Connexion + + + Créer un compte + + + )} +
+
+
+ ); +} diff --git a/src/components/SiteHeaderGuard.tsx b/src/components/SiteHeaderGuard.tsx new file mode 100644 index 0000000..6a1eddb --- /dev/null +++ b/src/components/SiteHeaderGuard.tsx @@ -0,0 +1,26 @@ +/** + * N'affiche le SiteHeader QUE sur les pages publiques. + * Sur /admin, le shell admin a déjà sa propre TopBar + Sidebar. + * Sur /connexion et /inscription, on garde la page nue. + */ + +import { headers } from "next/headers"; + +import { SiteHeader } from "./SiteHeader"; + +export async function SiteHeaderGuard() { + const h = await headers(); + // Next.js 16 expose le pathname via le header x-pathname si on l'a posé, + // sinon on retombe sur next-url ou referer. On utilise une heuristique simple : + // pathname depuis x-invoke-path (Next internal) ou x-next-url-path-prefix. + const pathname = + h.get("x-pathname") ?? + h.get("x-invoke-path") ?? + h.get("next-url") ?? + ""; + + if (pathname.startsWith("/admin")) return null; + if (pathname === "/connexion" || pathname === "/inscription") return null; + + return ; +} diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..db39867 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,23 @@ +/** + * Middleware Karbé. + * + * Pose `x-pathname` sur tous les requests pour que les server components puissent + * lire le path courant via `headers()` (utile pour SiteHeaderGuard qui décide + * de rendre ou non le header global selon /admin vs reste). + */ + +import { NextResponse } from "next/server"; +import type { NextRequest } from "next/server"; + +export function middleware(request: NextRequest) { + const response = NextResponse.next(); + response.headers.set("x-pathname", request.nextUrl.pathname); + return response; +} + +export const config = { + // Exclut les assets statiques + API auth (qu'on ne veut pas modifier). + matcher: [ + "/((?!_next/static|_next/image|favicon.ico|api/auth|api/health|api/metrics).*)", + ], +};