| 'use client' |
| import Link from 'next/link' |
| import { usePathname } from 'next/navigation' |
| import { cn } from '@/lib/utils' |
| import { LayoutDashboard, Trophy, Megaphone, BarChart3, Wallet, Bot, Zap, Shield, ChevronLeft, ChevronRight, Settings } from 'lucide-react' |
| import { useState, useEffect } from 'react' |
|
|
| const nav = [ |
| { label: 'Dashboard', href: '/', icon: LayoutDashboard }, |
| { label: 'Leaderboard', href: '/leaderboard', icon: Trophy }, |
| { label: 'Campaigns', href: '/campaigns', icon: Megaphone }, |
| { label: 'Analytics', href: '/analytics', icon: BarChart3 }, |
| { label: 'Wallets', href: '/wallets', icon: Wallet }, |
| { label: 'AI Agent', href: '/agent', icon: Bot }, |
| { label: 'Settings', href: '/settings', icon: Settings }, |
| ] |
|
|
| export function Sidebar() { |
| const path = usePathname() |
| const [col, setCol] = useState(false) |
| const [eventCount, setEventCount] = useState<number | null>(null) |
|
|
| useEffect(() => { |
| const fetchCount = async () => { |
| try { |
| const res = await fetch('/api/torque/events/recent?limit=1') |
| if (res.ok) { |
| const data = await res.json() |
| setEventCount(data.total ?? 0) |
| } |
| } catch {} |
| } |
| fetchCount() |
| const i = setInterval(fetchCount, 5000) |
| return () => clearInterval(i) |
| }, []) |
|
|
| return ( |
| <aside className={cn('hidden md:flex flex-col border-r border-hairline-dark bg-surface-card transition-all duration-300', col ? 'w-[68px]' : 'w-[240px]')}> |
| <div className="flex items-center h-16 px-4 border-b border-hairline-dark"> |
| <div className="flex items-center gap-2.5 overflow-hidden"> |
| <div className="w-8 h-8 rounded-lg bg-brand-yellow flex items-center justify-center flex-shrink-0"><Zap className="w-5 h-5 text-ink" /></div> |
| {!col && <span className="text-title-sm text-brand-yellow font-bold tracking-tight animate-slide-in-right">FlowState</span>} |
| </div> |
| </div> |
| <nav className="flex-1 py-4 px-2 space-y-1"> |
| {nav.map(item => { |
| const active = path === item.href || (item.href !== '/' && path.startsWith(item.href)) |
| return ( |
| <Link key={item.href} href={item.href} className={cn('flex items-center gap-3 px-3 py-2.5 rounded-lg text-nav transition-all duration-200', active ? 'bg-brand-yellow/10 text-brand-yellow' : 'text-muted hover:text-[#eaecef] hover:bg-surface-elevated', col && 'justify-center px-2')}> |
| <item.icon className={cn('w-5 h-5 flex-shrink-0', active && 'text-brand-yellow')} /> |
| {!col && <span>{item.label}</span>} |
| </Link> |
| ) |
| })} |
| </nav> |
| <div className="p-2 border-t border-hairline-dark"> |
| {!col && ( |
| <div className="mx-3 mb-3 p-3 rounded-xl bg-brand-yellow/5 border border-brand-yellow/20"> |
| <div className="flex items-center gap-2 mb-1"><Shield className="w-4 h-4 text-trading-up" /><span className="text-caption text-trading-up font-semibold">AI Agent Active</span></div> |
| <p className="text-caption text-muted"> |
| {eventCount === null |
| ? 'Connecting...' |
| : eventCount === 0 |
| ? 'No events this session' |
| : `${eventCount} event${eventCount === 1 ? '' : 's'} fired today`} |
| </p> |
| </div> |
| )} |
| <button onClick={() => setCol(!col)} className="flex items-center gap-3 px-3 py-2.5 rounded-lg text-nav text-muted hover:text-[#eaecef] hover:bg-surface-elevated w-full transition-colors"> |
| {col ? <ChevronRight className="w-5 h-5" /> : <ChevronLeft className="w-5 h-5" />} |
| {!col && <span>Collapse</span>} |
| </button> |
| </div> |
| </aside> |
| ) |
| } |
|
|