diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx
index f75f6a0..f6f9af2 100755
--- a/src/app/dashboard/layout.tsx
+++ b/src/app/dashboard/layout.tsx
@@ -3,19 +3,20 @@ import { SteamCMDProvider } from '@/lib/context/SteamCMDContext';
import { ServerCreationPopupProvider } from '@/lib/context/ServerCreationPopupContext';
import { ServerCreationPopupContainer } from '@/components/server/ServerCreationPopupContainer';
import { requireAuth } from '@/lib/auth/server';
+import { AppShell } from '@/components/ui/AppShell';
+import { hasPermission } from '@/lib/schemas';
-export default async function DashboardLayout({
- children
-}: Readonly<{
- children: React.ReactNode;
-}>) {
+export default async function DashboardLayout({ children }: Readonly<{ children: React.ReactNode }>) {
const session = await requireAuth();
+ const showUsers = hasPermission(session.user!, 'membership.view');
return (
- {children}
-
+
+ {children}
+
+
diff --git a/src/components/ui/AppShell.tsx b/src/components/ui/AppShell.tsx
new file mode 100644
index 0000000..7c21026
--- /dev/null
+++ b/src/components/ui/AppShell.tsx
@@ -0,0 +1,19 @@
+// NOTE: intentionally no 'use client' — must remain a Server Component
+import { Sidebar } from './Sidebar';
+
+interface AppShellProps {
+ children: React.ReactNode;
+ showUsers: boolean;
+}
+
+export function AppShell({ children, showUsers }: AppShellProps) {
+ return (
+
+
+ {/* Offset for sidebar (desktop: left margin, mobile: bottom padding) */}
+
+ {children}
+
+
+ );
+}
diff --git a/src/components/ui/Sidebar.tsx b/src/components/ui/Sidebar.tsx
new file mode 100644
index 0000000..195b03e
--- /dev/null
+++ b/src/components/ui/Sidebar.tsx
@@ -0,0 +1,94 @@
+'use client';
+
+import Link from 'next/link';
+import { usePathname } from 'next/navigation';
+import { cn } from '@/lib/utils';
+
+interface SidebarProps {
+ showUsers: boolean; // true if user has membership.view permission
+}
+
+function ServerIcon({ active }: { active: boolean }) {
+ return (
+
+ );
+}
+
+function UsersIcon({ active }: { active: boolean }) {
+ return (
+
+ );
+}
+
+export function Sidebar({ showUsers }: SidebarProps) {
+ const pathname = usePathname();
+ const isServers = !pathname.startsWith('/dashboard/membership');
+ const isUsers = pathname.startsWith('/dashboard/membership');
+
+ const navItems = [
+ { href: '/dashboard', label: 'Servers', active: isServers, icon: },
+ ...(showUsers ? [{ href: '/dashboard/membership', label: 'Users', active: isUsers, icon: }] : []),
+ ];
+
+ return (
+ <>
+ {/* Desktop icon sidebar */}
+
+
+ {/* Mobile bottom tab bar */}
+
+ >
+ );
+}