fix(portal-shell): wrap sidebar in client component to respect RSC boundary
P1-1 regression introduced by layout.tsx RSC refactor: navigation.ts
exports `icon: LucideIcon` (function refs) which cannot cross the RSC
boundary from a Server Component to a Client Component.
Fix:
- Introduce ShellSidebar (Client Component) that owns the navigation
filtering + icon refs entirely on the client side.
- layout.tsx (RSC) now only passes serializable strings (`role` and
`permsBitmap`) to ShellSidebar; no function references cross the
boundary.
Error before fix:
Error: Functions cannot be passed directly to Client Components
unless you explicitly expose it by marking it with "use server".
{$$typeof: ..., render: function LayoutDashboard}
Refs: apps/portal-shell/ARCHITECTURE.md §7.2 AppFrame, §10 P1-1,
§11.7 red line #5 (fail-closed identity).
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import type { Role } from "@edu/shared-ts/contracts";
|
||||
|
||||
import { AppSidebar, type NavItem } from "./app-sidebar";
|
||||
import { getNavigationItemsForRole } from "@/shared/lib/navigation";
|
||||
import { batchCheckRoutePermission } from "@/shared/lib/route-permissions";
|
||||
|
||||
/**
|
||||
* ShellSidebar - portal-shell 专用侧边栏容器(Client Component)
|
||||
*
|
||||
* 职责:在 Client 侧按 role + permsBitmap 过滤导航项,渲染 AppSidebar。
|
||||
*
|
||||
* 为何是 Client Component:
|
||||
* - navigation.ts 的 `icon` 字段是 lucide-react 组件(函数),
|
||||
* 不能从 RSC 直接传给 Client Component(Next.js RSC 边界限制)。
|
||||
* - 将过滤逻辑放到 Client 侧,icon 函数引用不出 Client 边界。
|
||||
*
|
||||
* 关联:ARCHITECTURE.md §7.2 AppFrame / §10 P1-1
|
||||
*/
|
||||
export interface ShellSidebarProps {
|
||||
role: Role;
|
||||
permsBitmap: string;
|
||||
}
|
||||
|
||||
export function ShellSidebar({
|
||||
role,
|
||||
permsBitmap,
|
||||
}: ShellSidebarProps): React.ReactNode {
|
||||
const roleItems = getNavigationItemsForRole(role);
|
||||
// PREFIX 路由以 "/" 结尾,需同时检查 href 和 href+"/"
|
||||
const pathsToCheck = roleItems.flatMap((i) => [i.href, `${i.href}/`]);
|
||||
const permMap = batchCheckRoutePermission(pathsToCheck, permsBitmap, role);
|
||||
const visibleItems: NavItem[] = roleItems
|
||||
.filter((i) => permMap[i.href] === true || permMap[`${i.href}/`] === true)
|
||||
.map((i) => ({ title: i.label, href: i.href, icon: i.icon }));
|
||||
|
||||
// layout 已按权限过滤,AppSidebar 内部无需再次过滤
|
||||
return <AppSidebar items={visibleItems} hasPermission={() => true} />;
|
||||
}
|
||||
Reference in New Issue
Block a user