| 'use client' |
|
|
| import Link from 'next/link' |
| import { usePathname } from 'next/navigation' |
| import { |
| LayoutDashboard, Plug, Wrench, Link2, Key, BarChart3, Settings, |
| LogOut, ExternalLink, Play, ChevronRight |
| } from 'lucide-react' |
| import { cn } from '@/lib/utils' |
|
|
| const mainNav = [ |
| { href: '/', icon: LayoutDashboard, label: 'Dashboard' }, |
| { href: '/apps', icon: Plug, label: 'Apps' }, |
| { href: '/connections', icon: Link2, label: 'Connections' }, |
| { href: '/tools', icon: Wrench, label: 'Tools' }, |
| { href: '/playground', icon: Play, label: 'Playground' }, |
| ] |
|
|
| const configNav = [ |
| { href: '/api-keys', icon: Key, label: 'API Keys' }, |
| { href: '/analytics', icon: BarChart3, label: 'Analytics' }, |
| { href: '/settings', icon: Settings, label: 'Settings' }, |
| ] |
|
|
| export function Sidebar({ integrationCount = 0, connectionCount = 0 }: { integrationCount?: number; connectionCount?: number }) { |
| const pathname = usePathname() |
|
|
| return ( |
| <aside className="w-60 bg-bg-secondary border-r border-border flex flex-col shrink-0 h-screen sticky top-0"> |
| <div className="px-5 py-4 border-b border-border"> |
| <Link href="/" className="flex items-center gap-3 group"> |
| <div className="w-8 h-8 bg-gradient-to-br from-accent-cyan to-accent-blue rounded-lg flex items-center justify-center font-bold text-bg-primary text-sm group-hover:scale-105 transition-transform"> |
| C |
| </div> |
| <div> |
| <div className="text-base font-semibold tracking-tight">Compost</div> |
| <div className="text-[10px] text-text-dim font-medium">Platform v1.0</div> |
| </div> |
| </Link> |
| </div> |
| |
| <nav className="flex-1 px-3 py-4 space-y-1 overflow-y-auto"> |
| <div className="px-3 mb-2 text-[10px] font-semibold uppercase tracking-widest text-text-dim">Navigation</div> |
| {mainNav.map((item) => { |
| const isActive = pathname === item.href || (item.href !== '/' && pathname.startsWith(item.href)) |
| return ( |
| <Link key={item.href} href={item.href} |
| className={cn( |
| 'flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-all duration-150 group', |
| isActive |
| ? 'bg-accent-cyan/10 text-accent-cyanLight font-medium' |
| : 'text-text-secondary hover:bg-bg-hover hover:text-text-primary' |
| )} |
| > |
| <item.icon className={cn('w-4 h-4 shrink-0', isActive ? 'text-accent-cyanLight' : 'text-text-muted group-hover:text-text-primary')} /> |
| <span className="truncate">{item.label}</span> |
| {item.href === '/apps' && integrationCount > 0 && ( |
| <span className="ml-auto text-[10px] bg-bg-tertiary border border-border text-text-muted px-1.5 py-0.5 rounded-md font-medium">{integrationCount}</span> |
| )} |
| {item.href === '/connections' && connectionCount > 0 && ( |
| <span className="ml-auto text-[10px] bg-accent-green/10 border border-accent-green/20 text-accent-green px-1.5 py-0.5 rounded-md font-medium">{connectionCount}</span> |
| )} |
| </Link> |
| ) |
| })} |
| |
| <div className="pt-6 pb-1"> |
| <div className="px-3 mb-2 text-[10px] font-semibold uppercase tracking-widest text-text-dim">Configuration</div> |
| {configNav.map((item) => { |
| const isActive = pathname === item.href |
| return ( |
| <Link key={item.href} href={item.href} |
| className={cn( |
| 'flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition-all duration-150 group', |
| isActive |
| ? 'bg-accent-cyan/10 text-accent-cyanLight font-medium' |
| : 'text-text-secondary hover:bg-bg-hover hover:text-text-primary' |
| )} |
| > |
| <item.icon className={cn('w-4 h-4 shrink-0', isActive ? 'text-accent-cyanLight' : 'text-text-muted group-hover:text-text-primary')} /> |
| <span className="truncate">{item.label}</span> |
| </Link> |
| ) |
| })} |
| </div> |
| |
| <div className="pt-6"> |
| <a href="/api/docs" target="_blank" rel="noopener noreferrer" |
| className="flex items-center gap-3 px-3 py-2 rounded-lg text-sm text-text-secondary hover:bg-bg-hover hover:text-text-primary transition-colors group" |
| > |
| <ExternalLink className="w-4 h-4 shrink-0 text-text-muted group-hover:text-text-primary" /> |
| <span>API Docs</span> |
| <ChevronRight className="w-3 h-3 ml-auto text-text-dim" /> |
| </a> |
| </div> |
| </nav> |
| |
| <div className="px-3 py-4 border-t border-border"> |
| <div className="flex items-center justify-between px-1"> |
| <div className="flex items-center gap-2.5 min-w-0"> |
| <div className="w-7 h-7 bg-gradient-to-br from-accent-cyan/20 to-accent-blue/20 rounded-full flex items-center justify-center text-xs font-semibold shrink-0 border border-border"> |
| {typeof window !== 'undefined' ? (localStorage.getItem('email')?.[0]?.toUpperCase() || 'U') : 'U'} |
| </div> |
| <div className="min-w-0"> |
| <div className="text-xs text-text-secondary truncate"> |
| {typeof window !== 'undefined' ? localStorage.getItem('email') || 'User' : 'User'} |
| </div> |
| <div className="text-[10px] text-text-dim">Online</div> |
| </div> |
| </div> |
| <button onClick={() => { localStorage.removeItem('token'); localStorage.removeItem('email'); window.location.href = '/login' }} |
| className="p-1.5 text-text-muted hover:text-accent-red hover:bg-accent-red/10 rounded-lg transition-colors" |
| title="Sign out" |
| > |
| <LogOut className="w-3.5 h-3.5" /> |
| </button> |
| </div> |
| </div> |
| </aside> |
| ) |
| } |
|
|