mirror of
https://github.com/tiennm99/bsk.git
synced 2026-06-17 20:47:46 +00:00
9afd68a741
- app/[locale]/(app)/layout.tsx: Server gate — getServerSession()
redirects unauth users (defense in depth) and signs out + redirects
the authed-but-no-role edge case. Renders AppShell with user/role/locale.
- app/[locale]/(app)/admin/layout.tsx: second gate via requireRole;
non-admin → /[locale]/dashboard (not 404 — avoids confirming routes).
- app/[locale]/(app)/dashboard/page.tsx: placeholder showing email +
role badge.
- lib/auth/role-menu.ts: ROLE_MENU mapping per AppRole → MenuItem[]
with href + i18n labelKey + lucide icon.
- lib/auth/require-role.ts: server helper for the admin gate.
- components/app-shell/{app-shell,sidebar,sign-out-button,locale-switcher}.tsx:
Server-rendered shell + sidebar that reads ROLE_MENU[role]; client
locale switcher (native <select> fallback) and sign-out (<form action>
with useFormStatus pending UX).
- components/ui/{badge,separator}.tsx: shadcn primitives.
- messages/{vi,en}.json: nav.*, app.*, dashboard.* keys (parity).
Gating chain: proxy.ts redirects unauth → /sign-in for /dashboard +
/admin prefixes; (app) layout enforces session+role; (app)/admin layout
additionally enforces role === 'admin'.
31 lines
917 B
TypeScript
31 lines
917 B
TypeScript
/**
|
|
* AppShell — Server Component.
|
|
*
|
|
* Composes the full authenticated layout: fixed sidebar on the left,
|
|
* scrollable main content area on the right. Receives user info from the
|
|
* (app) layout which has already validated session + role.
|
|
*
|
|
* No client state here — sidebar is server-rendered, top-bar lives inside
|
|
* the sidebar's bottom section for Phase 1 simplicity.
|
|
*/
|
|
|
|
import type { ReactNode } from "react";
|
|
import type { AppRole } from "@/lib/db/roles";
|
|
import { Sidebar } from "@/components/app-shell/sidebar";
|
|
|
|
type AppShellProps = {
|
|
email: string;
|
|
role: AppRole;
|
|
locale: string;
|
|
children: ReactNode;
|
|
};
|
|
|
|
export function AppShell({ email, role, locale, children }: AppShellProps) {
|
|
return (
|
|
<div className="flex h-screen overflow-hidden">
|
|
<Sidebar email={email} role={role} locale={locale} />
|
|
<main className="flex-1 overflow-y-auto">{children}</main>
|
|
</div>
|
|
);
|
|
}
|