2026-07-28 23:08:32 +00:00
|
|
|
import { NavLink, useLocation } from 'react-router-dom'
|
|
|
|
|
import { motion, AnimatePresence } from 'framer-motion'
|
|
|
|
|
import { useUIStore } from '@/stores/uiStore'
|
|
|
|
|
import {
|
|
|
|
|
LayoutDashboard,
|
|
|
|
|
Users,
|
|
|
|
|
TrendingUp,
|
|
|
|
|
Wallet,
|
|
|
|
|
Briefcase,
|
|
|
|
|
FileText,
|
|
|
|
|
Megaphone,
|
|
|
|
|
HeadphonesIcon,
|
|
|
|
|
Zap,
|
|
|
|
|
ChevronLeft,
|
|
|
|
|
ChevronRight,
|
2026-07-29 19:03:06 +00:00
|
|
|
Newspaper,
|
2026-08-12 07:50:12 +00:00
|
|
|
Bot,
|
2026-08-12 11:26:19 +00:00
|
|
|
Inbox,
|
|
|
|
|
Target,
|
|
|
|
|
MessageCircle,
|
2026-07-28 23:08:32 +00:00
|
|
|
} from 'lucide-react'
|
|
|
|
|
import { cn } from '@/lib/utils'
|
|
|
|
|
|
|
|
|
|
const navItems = [
|
2026-07-29 19:03:06 +00:00
|
|
|
{ path: '/', label: 'Briefing', icon: Newspaper },
|
|
|
|
|
{ path: '/dashboard', label: 'Dashboard', icon: LayoutDashboard },
|
2026-08-12 07:50:12 +00:00
|
|
|
{ path: '/agents', label: 'Agents', icon: Bot },
|
2026-08-12 11:26:19 +00:00
|
|
|
{ path: '/comm/inbox', label: 'Inbox', icon: Inbox },
|
|
|
|
|
{ path: '/comm/leads', label: 'Leads', icon: Target },
|
|
|
|
|
{ path: '/comm/contacts', label: 'Contacts', icon: MessageCircle },
|
2026-07-28 23:08:32 +00:00
|
|
|
{ path: '/crm', label: 'CRM', icon: Users },
|
|
|
|
|
{ path: '/sales', label: 'Sales', icon: TrendingUp },
|
|
|
|
|
{ path: '/finance', label: 'Finance', icon: Wallet },
|
|
|
|
|
{ path: '/hr', label: 'HR', icon: Briefcase },
|
|
|
|
|
{ path: '/legal', label: 'Legal', icon: FileText },
|
|
|
|
|
{ path: '/marketing', label: 'Marketing', icon: Megaphone },
|
|
|
|
|
{ path: '/support', label: 'Support', icon: HeadphonesIcon },
|
|
|
|
|
{ path: '/automation', label: 'Automation', icon: Zap },
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
export function Sidebar() {
|
|
|
|
|
const { sidebarOpen, toggleSidebar } = useUIStore()
|
|
|
|
|
const location = useLocation()
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
{/* Mobile overlay */}
|
|
|
|
|
<AnimatePresence>
|
|
|
|
|
{sidebarOpen && (
|
|
|
|
|
<motion.div
|
|
|
|
|
initial={{ opacity: 0 }}
|
|
|
|
|
animate={{ opacity: 1 }}
|
|
|
|
|
exit={{ opacity: 0 }}
|
|
|
|
|
transition={{ duration: 0.15 }}
|
|
|
|
|
className="fixed inset-0 bg-black/20 z-40 lg:hidden"
|
|
|
|
|
onClick={toggleSidebar}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</AnimatePresence>
|
|
|
|
|
|
|
|
|
|
<motion.aside
|
|
|
|
|
initial={false}
|
|
|
|
|
animate={{ width: sidebarOpen ? 240 : 72 }}
|
|
|
|
|
transition={{ duration: 0.2, ease: 'easeOut' }}
|
|
|
|
|
className={cn(
|
|
|
|
|
'fixed left-0 top-0 h-full bg-sidebar z-50 flex flex-col',
|
|
|
|
|
'border-r border-border/60',
|
|
|
|
|
sidebarOpen ? 'px-4' : 'px-3'
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{/* Logo */}
|
|
|
|
|
<div className="h-16 flex items-center justify-between shrink-0">
|
|
|
|
|
<div className="flex items-center gap-2.5 overflow-hidden">
|
|
|
|
|
<div className="w-8 h-8 rounded-lg bg-primary flex items-center justify-center shrink-0">
|
|
|
|
|
<svg width="16" height="16" viewBox="0 0 32 32" fill="none">
|
|
|
|
|
<path d="M10 22L16 10L22 22H10Z" stroke="white" strokeWidth="2.5" strokeLinejoin="round" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
<AnimatePresence>
|
|
|
|
|
{sidebarOpen && (
|
|
|
|
|
<motion.span
|
|
|
|
|
initial={{ opacity: 0, x: -8 }}
|
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
|
exit={{ opacity: 0, x: -8 }}
|
|
|
|
|
transition={{ duration: 0.15 }}
|
|
|
|
|
className="text-sm font-semibold text-text-primary whitespace-nowrap"
|
|
|
|
|
>
|
|
|
|
|
AMOS
|
|
|
|
|
</motion.span>
|
|
|
|
|
)}
|
|
|
|
|
</AnimatePresence>
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
onClick={toggleSidebar}
|
|
|
|
|
className="hidden lg:flex w-7 h-7 items-center justify-center rounded-lg hover:bg-bg text-text-secondary transition-colors"
|
|
|
|
|
>
|
|
|
|
|
{sidebarOpen ? <ChevronLeft size={14} /> : <ChevronRight size={14} />}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Navigation */}
|
|
|
|
|
<nav className="flex-1 py-4 space-y-0.5 overflow-y-auto">
|
|
|
|
|
{navItems.map((item) => {
|
|
|
|
|
const isActive = location.pathname === item.path ||
|
|
|
|
|
(item.path !== '/' && location.pathname.startsWith(item.path))
|
|
|
|
|
const Icon = item.icon
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<NavLink
|
|
|
|
|
key={item.path}
|
|
|
|
|
to={item.path}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
if (window.innerWidth < 1024) toggleSidebar()
|
|
|
|
|
}}
|
|
|
|
|
className={cn(
|
|
|
|
|
'flex items-center gap-3 h-10 rounded-xl px-3 transition-colors duration-150 relative',
|
|
|
|
|
'hover:bg-primary-light',
|
|
|
|
|
isActive && 'bg-primary-light text-primary',
|
|
|
|
|
!isActive && 'text-text-secondary'
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{isActive && (
|
|
|
|
|
<motion.div
|
|
|
|
|
layoutId="sidebar-active"
|
|
|
|
|
className="absolute left-0 top-1/2 -translate-y-1/2 w-0.5 h-5 bg-primary rounded-full"
|
|
|
|
|
transition={{ duration: 0.18, ease: 'easeOut' }}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
<Icon size={18} strokeWidth={isActive ? 2 : 1.5} />
|
|
|
|
|
<AnimatePresence>
|
|
|
|
|
{sidebarOpen && (
|
|
|
|
|
<motion.span
|
|
|
|
|
initial={{ opacity: 0, x: -8 }}
|
|
|
|
|
animate={{ opacity: 1, x: 0 }}
|
|
|
|
|
exit={{ opacity: 0, x: -8 }}
|
|
|
|
|
transition={{ duration: 0.15 }}
|
|
|
|
|
className={cn(
|
|
|
|
|
'text-sm font-medium whitespace-nowrap',
|
|
|
|
|
isActive ? 'text-primary' : 'text-text-secondary'
|
|
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{item.label}
|
|
|
|
|
</motion.span>
|
|
|
|
|
)}
|
|
|
|
|
</AnimatePresence>
|
|
|
|
|
</NavLink>
|
|
|
|
|
)
|
|
|
|
|
})}
|
|
|
|
|
</nav>
|
|
|
|
|
</motion.aside>
|
|
|
|
|
</>
|
|
|
|
|
)
|
|
|
|
|
}
|