Files
tiennm99 9afd68a741 feat(phase-1): role-gated app shell + dashboard placeholder
- 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'.
2026-05-25 17:53:44 +07:00

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>
);
}