210 lines
10 KiB
TypeScript
210 lines
10 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import Link from 'next/link';
|
|
import { useRouter } from 'next/router';
|
|
import {
|
|
LayoutDashboard,
|
|
MessageSquare,
|
|
Flame,
|
|
Users,
|
|
Network,
|
|
Send,
|
|
Key,
|
|
Settings,
|
|
LogOut,
|
|
Sparkles,
|
|
Zap,
|
|
ChevronLeft,
|
|
ChevronRight,
|
|
Layers,
|
|
Plug2,
|
|
ArrowLeftFromLine,
|
|
BrainCircuit,
|
|
} from 'lucide-react';
|
|
import { useAuth } from '../context/AuthContext';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { useUIStore } from '../store/uiStore';
|
|
|
|
// Simple Tooltip component specifically for the collapsed sidebar
|
|
const SidebarTooltip = ({ text, children }: { text: string, children: React.ReactNode }) => {
|
|
const [isVisible, setIsVisible] = useState(false);
|
|
return (
|
|
<div className="relative flex items-center" onMouseEnter={() => setIsVisible(true)} onMouseLeave={() => setIsVisible(false)}>
|
|
{children}
|
|
<AnimatePresence>
|
|
{isVisible && (
|
|
<motion.div
|
|
initial={{ opacity: 0, x: -10 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
exit={{ opacity: 0, x: -10 }}
|
|
className="absolute left-full ml-4 px-3 py-1.5 bg-brand-500 text-white text-xs font-semibold rounded-lg shadow-xl z-[100] whitespace-nowrap pointer-events-none"
|
|
>
|
|
{text}
|
|
<div className="absolute top-1/2 -left-1 -translate-y-1/2 border-y-4 border-y-transparent border-r-4 border-r-brand-500" />
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default function Sidebar() {
|
|
const router = useRouter();
|
|
const { user, logout } = useAuth();
|
|
const { isSidebarCollapsed, toggleSidebar } = useUIStore();
|
|
const [mounted, setMounted] = useState(false);
|
|
|
|
// Wait for mount to avoid hydration mismatch
|
|
useEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
|
|
const menuItems = [
|
|
{ path: '/dashboard', label: 'Dashboard', icon: LayoutDashboard },
|
|
{ path: '/inbox', label: 'Inbox', icon: MessageSquare },
|
|
{ path: '/sessions', label: 'Sessões', icon: Users },
|
|
{ path: '/warmer', label: 'WhatsApp Warmer', icon: Flame },
|
|
{ path: '/phonebook', label: 'Agenda', icon: Users },
|
|
{ path: '/flow-builder', label: 'Fluxos', icon: Network },
|
|
{ path: '/sending', label: 'Disparos', icon: Send },
|
|
{ path: '/sectors', label: 'Setores & Equipes', icon: Layers },
|
|
{ path: '/api-access', label: 'Acesso à API', icon: Key },
|
|
{ path: '/secretaria', label: 'Secretária IA', icon: BrainCircuit },
|
|
];
|
|
|
|
const adminItems = [
|
|
{ path: '/admin/dashboard', label: 'Admin Dashboard', icon: LayoutDashboard },
|
|
{ path: '/admin/plans', label: 'Plan Management', icon: Sparkles },
|
|
{ path: '/admin/users', label: 'Users', icon: Users },
|
|
{ path: '/admin/plugins', label: 'Plugins', icon: Plug2 },
|
|
{ path: '/admin/settings', label: 'Global Settings', icon: Settings },
|
|
{ path: '/baileys', label: 'Baileys', icon: Zap },
|
|
];
|
|
|
|
const navItems = user?.role === 'admin' && router.pathname.startsWith('/admin')
|
|
? adminItems
|
|
: menuItems;
|
|
|
|
if (!mounted) return <aside className="w-64 bg-black" />;
|
|
|
|
return (
|
|
<motion.aside
|
|
animate={{ width: isSidebarCollapsed ? 80 : 256 }}
|
|
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
|
|
className={`bg-black border-r border-white/5 h-screen flex-col font-sans shrink-0 z-50 overflow-y-auto relative hidden min-[401px]:flex max-[400px]:hidden transition-all`}
|
|
>
|
|
{/* Ambient */}
|
|
<div className="absolute top-0 left-0 w-full h-[500px] bg-brand-500/5 blur-[100px] pointer-events-none" />
|
|
|
|
{/* Application Logo */}
|
|
<div className={`h-16 flex items-center px-6 border-b border-white/5 relative z-10 shrink-0 ${isSidebarCollapsed ? 'justify-center px-0' : ''}`}>
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 rounded bg-gradient-to-br from-brand-500 to-brand-700 flex items-center justify-center shadow-lg shadow-brand-500/20 shrink-0">
|
|
<Sparkles className="text-white w-5 h-5 transition-transform duration-300 group-hover:rotate-12" />
|
|
</div>
|
|
{!isSidebarCollapsed && (
|
|
<motion.span
|
|
initial={{ opacity: 0, x: -10 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
className="text-xl font-black text-white tracking-tight"
|
|
>
|
|
new<span className="text-brand-400">whats</span>
|
|
</motion.span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Nav Menu */}
|
|
<nav className="flex-1 px-4 py-6 space-y-2 relative z-10 overflow-y-auto custom-scrollbar">
|
|
{navItems.map((item) => {
|
|
const isActive = router.pathname.startsWith(item.path);
|
|
const Icon = item.icon;
|
|
|
|
const content = (
|
|
<Link key={item.path} href={item.path}>
|
|
<div className={`relative flex items-center gap-3 px-3 py-2.5 rounded-xl cursor-pointer transition-all group overflow-hidden
|
|
${isActive ? 'text-white font-semibold' : 'text-slate-400 hover:text-white hover:bg-white/[0.05]'}
|
|
${isSidebarCollapsed ? 'justify-center p-2.5 mx-auto' : ''}`}
|
|
>
|
|
{isActive && (
|
|
<motion.div
|
|
layoutId="sidebar-active"
|
|
className="absolute inset-0 bg-brand-500/10 border border-brand-500/20 rounded-xl"
|
|
initial={false}
|
|
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
|
|
/>
|
|
)}
|
|
{isActive && (
|
|
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-6 bg-brand-500 rounded-r-full shadow-[0_0_10px_#3b82f6]" />
|
|
)}
|
|
|
|
<Icon size={20} className={`relative z-10 shrink-0 ${isActive ? 'text-brand-400' : 'group-hover:scale-110 transition-transform'}`} />
|
|
|
|
{!isSidebarCollapsed && (
|
|
<motion.span
|
|
initial={{ opacity: 0, x: -10 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
className="relative z-10 text-sm whitespace-nowrap"
|
|
>
|
|
{item.label}
|
|
</motion.span>
|
|
)}
|
|
</div>
|
|
</Link>
|
|
);
|
|
|
|
return isSidebarCollapsed ? (
|
|
<SidebarTooltip key={item.path} text={item.label}>
|
|
{content}
|
|
</SidebarTooltip>
|
|
) : (
|
|
content
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
{/* Bottom Actions & Toggle */}
|
|
<div className={`p-4 border-t border-white/5 relative z-10 shrink-0 ${isSidebarCollapsed ? 'items-center flex flex-col' : ''}`}>
|
|
|
|
{user?.role === 'admin' && router.pathname.startsWith('/admin') && (
|
|
<Link href="/dashboard">
|
|
<div className={`flex items-center gap-3 px-3 py-2 mb-1 text-sm rounded-xl cursor-pointer transition-all font-semibold group
|
|
bg-white/[0.04] border border-white/8 hover:bg-brand-500/10 hover:border-brand-500/30 text-slate-400 hover:text-brand-300
|
|
${isSidebarCollapsed ? 'justify-center w-full' : ''}`}>
|
|
<ArrowLeftFromLine size={17} className="shrink-0 transition-transform group-hover:-translate-x-0.5" />
|
|
{!isSidebarCollapsed && <span>Sair do Admin</span>}
|
|
</div>
|
|
</Link>
|
|
)}
|
|
|
|
{user?.role === 'admin' && !router.pathname.startsWith('/admin') && (
|
|
<Link href="/admin/dashboard">
|
|
<div className={`flex items-center gap-3 px-3 py-2 text-sm text-yellow-400 hover:bg-white/5 rounded-xl cursor-pointer transition-colors font-semibold ${isSidebarCollapsed ? 'justify-center w-full' : ''}`}>
|
|
<Settings size={18} className="shrink-0" />
|
|
{!isSidebarCollapsed && <span>Admin Panel</span>}
|
|
</div>
|
|
</Link>
|
|
)}
|
|
|
|
<div
|
|
onClick={logout}
|
|
className={`flex items-center gap-3 px-3 py-2 text-sm text-slate-400 hover:text-red-400 hover:bg-white/5 rounded-xl cursor-pointer transition-colors mb-2 ${isSidebarCollapsed ? 'justify-center w-full' : ''}`}
|
|
>
|
|
<LogOut size={18} className="shrink-0" />
|
|
{!isSidebarCollapsed && <span>Log Out</span>}
|
|
</div>
|
|
|
|
{/* Intelligent Toggle Button */}
|
|
<button
|
|
onClick={toggleSidebar}
|
|
className={`w-full flex items-center bg-white/5 hover:bg-white/10 text-slate-300 hover:text-white rounded-xl py-2 transition-all group ${isSidebarCollapsed ? 'justify-center px-0' : 'px-3 justify-between'}`}
|
|
title={isSidebarCollapsed ? 'Expandir' : 'Recolher'}
|
|
>
|
|
{!isSidebarCollapsed && <span className="text-xs font-bold uppercase tracking-widest opacity-40">Layout</span>}
|
|
{isSidebarCollapsed ? <ChevronRight size={18} /> : <ChevronLeft size={18} />}
|
|
</button>
|
|
</div>
|
|
</motion.aside>
|
|
);
|
|
}
|
|
|