Reputation: 218
Context: I have a dashboard which is a route group, and I have a multiple roles, i.e. admin
, company
and student
. I have a sidebar in the Dashboard where the contents may change based on the type of user
you are, consider to be RBAC.
Problem: I want to render different sidebar contents based on the route I am visiting.
Project Tree:
.
└── src/
├── app/
│ └── (dashboard)/
│ ├── admin/
│ │ └── page.tsx
│ ├── company/
│ │ └── page.tsx
│ ├── student/
│ │ └── page.tsx
│ └── layout.tsx
└── components/
└── widgets/
└── dashboard/
├── sidebar.tsx
└── header.tsx
Layout.tsx
import { Sidebar } from "@/components/widgets/dashboard/Sidebar";
import { Header } from "@/components/widgets/dashboard/Header";
interface SidebarItem {
icon: React.ReactNode;
title: string;
href: string;
}
export default async function DashboardLayout({
children,
sidebarItems,
}: {
children: React.ReactNode;
admin: React.ReactNode;
sidebarItems: SidebarItem[];
}) {
return (
<div className="grid min-h-screen w-full md:grid-cols-[220px_1fr] lg:grid-cols-[280px_1fr]">
<Sidebar items={sidebarItems} />
<div className="flex flex-col">
<Header items={sidebarItems} />
<main className="flex flex-1 flex-col gap-4 p-4 lg:gap-6 lg:p-6">
{children}
</main>
</div>
</div>
);
}
Header.tsx
import Link from "next/link";
import {
CircleUser,
Menu,
Search,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
interface SidebarItem {
icon: React.ReactNode;
title: string;
href: string;
}
export function Header({ items = [] }: { items: SidebarItem[] }) {
return (
<header className="flex h-14 items-center gap-4 border-b bg-muted/40 px-4 lg:h-[60px] lg:px-6">
<Sheet>
<SheetTrigger asChild>
<Button variant="outline" size="icon" className="shrink-0 md:hidden">
<Menu className="h-5 w-5" />
<span className="sr-only">Toggle navigation menu</span>
</Button>
</SheetTrigger>
<SheetContent side="left" className="flex flex-col">
<nav className="grid gap-2 text-lg font-medium">
{items.map((item) => (
<Link
key={item.title}
href={item.href}
className="mx-[-0.65rem] flex items-center gap-4 rounded-xl px-3 py-2 text-muted-foreground hover:text-foreground"
>
{item.icon}
{item.title}
</Link>
))}
</nav>
<div className="mt-auto">
<Card>
<CardHeader>
<CardTitle>Upgrade to Pro</CardTitle>
<CardDescription>
Unlock all features and get unlimited access to our support
team.
</CardDescription>
</CardHeader>
<CardContent>
<Button size="sm" className="w-full">
Upgrade
</Button>
</CardContent>
</Card>
</div>
</SheetContent>
</Sheet>
<div className="w-full flex-1">
<form>
<div className="relative">
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="Search products..."
className="w-full appearance-none bg-background pl-8 shadow-none md:w-2/3 lg:w-1/3"
/>
</div>
</form>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary" size="icon" className="rounded-full">
<CircleUser className="h-5 w-5" />
<span className="sr-only">Toggle user menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuItem>Support</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>Logout</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</header>
);
}
Sidebar.tsx
import Link from "next/link";
import { Bell, Package2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
interface SidebarItem {
icon: React.ReactNode;
title: string;
href: string;
}
export function Sidebar({ items = [] }: { items: SidebarItem[] }) {
return (
<div className="hidden border-r bg-muted/40 md:block">
<div className="flex h-full max-h-screen flex-col gap-2">
<div className="flex h-14 items-center border-b px-4 lg:h-[60px] lg:px-6">
<Link href="/" className="flex items-center gap-2 font-semibold">
<Package2 className="h-6 w-6" />
<span className="">Acme Inc</span>
</Link>
<Button variant="outline" size="icon" className="ml-auto h-8 w-8">
<Bell className="h-4 w-4" />
<span className="sr-only">Toggle notifications</span>
</Button>
</div>
<div className="flex-1">
<nav className="grid items-start px-2 text-sm font-medium lg:px-4">
{items.map((item) => (
<Link
key={item.title}
href={item.href}
className="flex items-center gap-3 rounded-lg px-3 py-2 text-muted-foreground transition-all hover:text-primary"
>
{item.icon}
{item.title}
</Link>
))}
</nav>
</div>
<div className="mt-auto p-4">
<Card>
<CardHeader className="p-2 pt-0 md:p-4">
<CardTitle>Upgrade to Pro</CardTitle>
<CardDescription>
Unlock all features and get unlimited access to our support
team.
</CardDescription>
</CardHeader>
<CardContent className="p-2 pt-0 md:p-4 md:pt-0">
<Button size="sm" className="w-full">
Upgrade
</Button>
</CardContent>
</Card>
</div>
</div>
</div>
);
}
admin/page.tsx
import { Button } from "@/components/ui/button";
import {
Home,
LineChart,
Package,
ShoppingCart,
Users,
} from "lucide-react";
interface SidebarItem {
icon: React.ReactNode;
title: string;
href: string;
}
const sidebarItems: SidebarItem[] = [
{
icon: <Home className="h-4 w-4" />,
title: "Dashboard",
href: "#",
},
{
icon: <ShoppingCart className="h-4 w-4" />,
title: "Orders",
href: "#",
},
{
icon: <Package className="h-4 w-4" />,
title: "Products",
href: "#",
},
{
icon: <Users className="h-4 w-4" />,
title: "Customers",
href: "#",
},
{
icon: <LineChart className="h-4 w-4" />,
title: "Analytics",
href: "#",
},
];
export default async function AdminPage() {
return (
<>
<div className="flex items-center">
<h1 className="text-lg font-semibold md:text-2xl">Inventory</h1>
</div>
<div className="flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-sm">
<div className="flex flex-col items-center gap-1 text-center">
<h3 className="text-2xl font-bold tracking-tight">
You have no products
</h3>
<p className="text-sm text-muted-foreground">
You can start selling as soon as you add a product.
</p>
<Button className="mt-4">Add Product</Button>
</div>
</div>
</>
);
}
In this how can I pass the sidebarItems
so that it passes in the props defined in the DashboardLayout.tsx
?
Other information:
{
"name": "website2",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@auth/firebase-adapter": "^1.5.1",
"@heroicons/react": "^2.1.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-slot": "^1.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"firebase": "^10.9.0",
"firebase-admin": "^11.11.1",
"lucide-react": "^0.363.0",
"next": "14.1.4",
"next-auth": "^5.0.0-beta.16",
"react": "^18",
"react-dom": "^18",
"react-firebase-hooks": "^5.1.1",
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.1.4",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
}
Pass props to page.jsx child from root layout (next.js 13)
Upvotes: 0
Views: 619
Reputation: 1
If you want to send data from server component to server component, unfortunately you can’t do that for now in next.js, might be possible in future. For client components you have to make a global variable or state. For that you can use React built in context api or for big projects you can use redux toolkit.
Upvotes: 0
Reputation: 16
You'll have to create a separate layout for each role so that you can pass different sidebar items to each layout
Example:
admin/layout.tsx
export default function Layout({children} : {children: React.ReactNode}) {
return (<DashboardLayout sidebarItems={adminSidebarItems}>
{children}
</DashboardLayout>);
}
Or you can instead store the role of the signed-in user in a global state. I recommend zustand.
Upvotes: 0