mirror of
https://github.com/KLogicHQ/nats-console.git
synced 2026-10-13 08:11:24 +00:00
106 lines
3.5 KiB
TypeScript
106 lines
3.5 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import Link from 'next/link';
|
|
import { usePathname } from 'next/navigation';
|
|
import {
|
|
LayoutDashboard,
|
|
Server,
|
|
Database,
|
|
Users,
|
|
BarChart3,
|
|
Bell,
|
|
Settings,
|
|
LogOut,
|
|
PanelTop,
|
|
FileSearch,
|
|
AlertTriangle,
|
|
} from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
import { useAuthStore } from '@/stores/auth';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Logo } from '@/components/logo';
|
|
|
|
const navigation = [
|
|
{ name: 'Overview', href: '/overview', icon: LayoutDashboard },
|
|
{ name: 'Clusters', href: '/clusters', icon: Server },
|
|
{ name: 'Streams', href: '/streams', icon: Database },
|
|
{ name: 'Consumers', href: '/consumers', icon: Users },
|
|
{ name: 'Dead Letter Queues', href: '/dlq', icon: AlertTriangle },
|
|
{ name: 'Analytics', href: '/analytics', icon: BarChart3 },
|
|
{ name: 'Dashboards', href: '/dashboards', icon: PanelTop },
|
|
{ name: 'Saved Queries', href: '/saved-queries', icon: FileSearch },
|
|
{ name: 'Alerts', href: '/alerts', icon: Bell },
|
|
{ name: 'Settings', href: '/settings', icon: Settings },
|
|
];
|
|
|
|
export function Sidebar() {
|
|
const pathname = usePathname();
|
|
const { logout, user } = useAuthStore();
|
|
|
|
// Optimistic navigation state for instant feedback
|
|
const [optimisticPath, setOptimisticPath] = useState<string | null>(null);
|
|
|
|
// Sync optimistic state with actual pathname when navigation completes
|
|
useEffect(() => {
|
|
setOptimisticPath(null);
|
|
}, [pathname]);
|
|
|
|
// Use optimistic path if set, otherwise use actual pathname
|
|
const activePath = optimisticPath || pathname;
|
|
|
|
return (
|
|
<div className="flex h-full w-52 flex-col border-r bg-sidebar">
|
|
{/* Logo */}
|
|
<div className="flex h-14 items-center border-b px-4">
|
|
<Logo href="/clusters" size="sm" />
|
|
</div>
|
|
|
|
{/* Navigation */}
|
|
<nav className="flex-1 space-y-1 p-3">
|
|
{navigation.map((item) => {
|
|
const isActive = activePath === item.href || (item.href !== '/overview' && activePath.startsWith(item.href));
|
|
return (
|
|
<Link
|
|
key={item.name}
|
|
href={item.href}
|
|
onClick={() => setOptimisticPath(item.href)}
|
|
className={cn(
|
|
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
|
|
isActive
|
|
? 'bg-primary text-primary-foreground'
|
|
: 'text-foreground/70 hover:bg-muted hover:text-foreground'
|
|
)}
|
|
>
|
|
<item.icon className="h-4 w-4" />
|
|
{item.name}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
{/* User section */}
|
|
<div className="border-t p-3">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<div className="h-8 w-8 rounded-full bg-primary/10 flex items-center justify-center">
|
|
<span className="text-xs font-medium">
|
|
{user?.firstName?.[0]}
|
|
{user?.lastName?.[0]}
|
|
</span>
|
|
</div>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-sm font-medium truncate">
|
|
{user?.firstName} {user?.lastName}
|
|
</p>
|
|
<p className="text-xs text-muted-foreground truncate">{user?.email}</p>
|
|
</div>
|
|
</div>
|
|
<Button variant="ghost" size="sm" className="w-full justify-start gap-2 h-8" onClick={() => logout()}>
|
|
<LogOut className="h-3.5 w-3.5" />
|
|
Sign out
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|