Files
source-gamepanel/apps/web/src/components/layout/sidebar.tsx
T
2026-02-21 16:59:21 +03:00

116 lines
3.7 KiB
TypeScript

import { Link, useLocation, useParams } from 'react-router';
import {
Server,
LayoutDashboard,
Network,
Settings,
Users,
Shield,
Gamepad2,
ScrollText,
ChevronLeft,
} from 'lucide-react';
import { cn } from '@source/ui';
import { Button } from '@/components/ui/button';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator';
import { useAuthStore } from '@/stores/auth';
interface NavItem {
label: string;
href: string;
icon: React.ComponentType<{ className?: string }>;
}
export function Sidebar() {
const location = useLocation();
const { orgId } = useParams();
const user = useAuthStore((s) => s.user);
const orgNav: NavItem[] = orgId
? [
{ label: 'Dashboard', href: `/org/${orgId}/dashboard`, icon: LayoutDashboard },
{ label: 'Servers', href: `/org/${orgId}/servers`, icon: Server },
{ label: 'Nodes', href: `/org/${orgId}/nodes`, icon: Network },
{ label: 'Members', href: `/org/${orgId}/settings/members`, icon: Users },
{ label: 'Settings', href: `/org/${orgId}/settings`, icon: Settings },
]
: [];
const adminNav: NavItem[] = user?.isSuperAdmin
? [
{ label: 'Users', href: '/admin/users', icon: Users },
{ label: 'Games', href: '/admin/games', icon: Gamepad2 },
{ label: 'Nodes', href: '/admin/nodes', icon: Network },
{ label: 'Audit Logs', href: '/admin/audit-logs', icon: ScrollText },
]
: [];
return (
<div className="flex h-full w-64 flex-col border-r bg-card">
<div className="flex h-14 items-center gap-2 border-b px-4">
<Shield className="h-6 w-6 text-primary" />
<span className="text-lg font-bold">GamePanel</span>
</div>
<ScrollArea className="flex-1 py-2">
{orgId && (
<div className="px-3 py-2">
<div className="mb-1 flex items-center gap-1 px-2">
<Link to="/" className="text-xs text-muted-foreground hover:text-foreground">
<ChevronLeft className="inline h-3 w-3" /> Organizations
</Link>
</div>
<NavSection items={orgNav} currentPath={location.pathname} />
</div>
)}
{!orgId && (
<div className="px-3 py-2">
<p className="mb-2 px-2 text-xs font-medium text-muted-foreground">ORGANIZATIONS</p>
<Link to="/">
<Button variant="ghost" className="w-full justify-start gap-2">
<LayoutDashboard className="h-4 w-4" />
All Organizations
</Button>
</Link>
</div>
)}
{adminNav.length > 0 && (
<>
<Separator className="mx-3 my-2" />
<div className="px-3 py-2">
<p className="mb-2 px-2 text-xs font-medium text-muted-foreground">ADMIN</p>
<NavSection items={adminNav} currentPath={location.pathname} />
</div>
</>
)}
</ScrollArea>
</div>
);
}
function NavSection({ items, currentPath }: { items: NavItem[]; currentPath: string }) {
return (
<nav className="flex flex-col gap-1">
{items.map((item) => {
const isActive =
currentPath === item.href || currentPath.startsWith(item.href + '/');
return (
<Link key={item.href} to={item.href}>
<Button
variant={isActive ? 'secondary' : 'ghost'}
className="w-full justify-start gap-2"
size="sm"
>
<item.icon className={cn('h-4 w-4', isActive && 'text-primary')} />
{item.label}
</Button>
</Link>
);
})}
</nav>
);
}