Spaces:
Running
Running
| "use client"; | |
| import Link from "next/link"; | |
| import { usePathname, useRouter } from "next/navigation"; | |
| import { cn } from "@/lib/utils"; | |
| import { motion } from "framer-motion"; | |
| import { | |
| LayoutDashboard, ArrowRightLeft, BarChart2, Wallet, Target, | |
| MessageSquare, Settings, LogOut, Zap, Shield, Sparkles, | |
| Activity, CreditCard, Sun, Moon, Globe, | |
| } from "lucide-react"; | |
| import { useAuthStore } from "@/lib/stores/authStore"; | |
| import { useThemeStore } from "@/lib/stores/themeStore"; | |
| import { useLanguageStore, LANGUAGES, Language } from "@/lib/stores/languageStore"; | |
| import { useState } from "react"; | |
| export function Sidebar() { | |
| const pathname = usePathname(); | |
| const router = useRouter(); | |
| const { logout } = useAuthStore(); | |
| const { theme, toggle } = useThemeStore(); | |
| const { language, setLanguage, t } = useLanguageStore(); | |
| const [showLang, setShowLang] = useState(false); | |
| const navigation = [ | |
| { key: "overview", href: "/", icon: LayoutDashboard }, | |
| { key: "transactions", href: "/transactions", icon: ArrowRightLeft }, | |
| { key: "payments", href: "/payments", icon: CreditCard, badge: "NEW" }, | |
| { key: "analytics", href: "/analytics", icon: BarChart2 }, | |
| { key: "simulator", href: "/simulator", icon: Zap }, | |
| { key: "loans", href: "/loans", icon: Wallet }, | |
| { key: "goals", href: "/goals", icon: Target }, | |
| { key: "ai_assistant", href: "/chat", icon: MessageSquare, badge: "AI" }, | |
| { key: "security", href: "/security", icon: Shield }, | |
| { key: "system_status", href: "/status", icon: Activity }, | |
| { key: "settings", href: "/settings", icon: Settings }, | |
| ]; | |
| const handleLogout = () => { logout(); router.replace("/login"); }; | |
| const isLight = theme === "light"; | |
| return ( | |
| <div | |
| className="flex h-full w-60 flex-col overflow-y-auto border-r text-sm backdrop-blur-2xl transition-colors duration-300" | |
| style={{ | |
| background: "var(--sidebar-bg)", | |
| borderColor: "var(--border)", | |
| color: "var(--fg)", | |
| }} | |
| > | |
| {/* ── Logo ─────────────────────────────────────────────────────────────── */} | |
| <div | |
| className="flex h-16 shrink-0 items-center gap-2.5 px-5 border-b" | |
| style={{ borderColor: "var(--border)" }} | |
| > | |
| <div className="flex h-8 w-8 items-center justify-center rounded-xl bg-gradient-to-br from-emerald-400 to-cyan-500 shadow-lg shadow-emerald-500/20"> | |
| <Sparkles className="h-4 w-4 text-white" /> | |
| </div> | |
| <div> | |
| <h1 className="text-base font-bold leading-tight" style={{ color: "var(--fg)" }}> | |
| BankBot | |
| </h1> | |
| <p className="text-[10px] text-emerald-500 leading-tight">AI Finance</p> | |
| </div> | |
| </div> | |
| {/* ── Nav ──────────────────────────────────────────────────────────────── */} | |
| <div className="flex flex-1 flex-col justify-between px-3 py-4"> | |
| <nav className="space-y-0.5"> | |
| {navigation.map((item) => { | |
| const isActive = pathname === item.href; | |
| return ( | |
| <Link | |
| key={item.key} | |
| href={item.href} | |
| className={cn( | |
| "group relative flex items-center rounded-xl px-3 py-2.5 font-medium transition-all duration-200", | |
| isActive | |
| ? "" | |
| : isLight | |
| ? "hover:bg-black/5" | |
| : "hover:bg-white/5" | |
| )} | |
| style={{ | |
| color: isActive | |
| ? "var(--fg)" | |
| : "var(--fg-subtle)", | |
| }} | |
| > | |
| {/* Active background pill */} | |
| {isActive && ( | |
| <motion.div | |
| layoutId="activeNav" | |
| className={cn( | |
| "absolute inset-0 rounded-xl border", | |
| isLight | |
| ? "bg-emerald-50 border-emerald-200/80" | |
| : "bg-white/10 border-white/10" | |
| )} | |
| transition={{ type: "spring", bounce: 0.2, duration: 0.4 }} | |
| /> | |
| )} | |
| {/* Active left accent */} | |
| {isActive && ( | |
| <div className="absolute left-0 top-1/2 -translate-y-1/2 h-5 w-0.5 rounded-full bg-emerald-500" /> | |
| )} | |
| <item.icon | |
| className={cn( | |
| "relative mr-3 h-4 w-4 flex-shrink-0 transition-colors", | |
| isActive | |
| ? "text-emerald-500" | |
| : isLight | |
| ? "text-slate-400 group-hover:text-slate-600" | |
| : "text-zinc-500 group-hover:text-zinc-300" | |
| )} | |
| /> | |
| <span className="relative flex-1">{t(item.key)}</span> | |
| {item.badge && ( | |
| <span | |
| className={cn( | |
| "relative ml-auto rounded-md px-1.5 py-0.5 text-[9px] font-bold uppercase tracking-wide border", | |
| item.badge === "AI" | |
| ? "bg-emerald-500/15 text-emerald-500 border-emerald-500/30" | |
| : "bg-blue-500/15 text-blue-500 border-blue-500/30" | |
| )} | |
| > | |
| {item.badge} | |
| </span> | |
| )} | |
| </Link> | |
| ); | |
| })} | |
| </nav> | |
| {/* ── Bottom controls ───────────────────────────────────────────────── */} | |
| <div | |
| className="space-y-1 pt-4 border-t" | |
| style={{ borderColor: "var(--border)" }} | |
| > | |
| {/* AI Shield status */} | |
| <div | |
| className={cn( | |
| "flex items-center gap-2.5 rounded-xl px-3 py-2.5 border", | |
| isLight | |
| ? "bg-emerald-50 border-emerald-200/80" | |
| : "bg-emerald-500/5 border-emerald-500/10" | |
| )} | |
| > | |
| <div className="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse flex-shrink-0" /> | |
| <div className="flex-1 min-w-0"> | |
| <p className="text-xs font-medium text-emerald-500 leading-tight"> | |
| {t("ai_shield")} | |
| </p> | |
| <p | |
| className="text-[10px] leading-tight truncate" | |
| style={{ color: "var(--fg-subtle)" }} | |
| > | |
| {t("all_normal")} | |
| </p> | |
| </div> | |
| </div> | |
| {/* Theme toggle */} | |
| <button | |
| onClick={toggle} | |
| className={cn( | |
| "flex w-full items-center rounded-xl px-3 py-2.5 font-medium transition-all duration-200", | |
| isLight ? "hover:bg-black/5" : "hover:bg-white/5" | |
| )} | |
| style={{ color: "var(--fg-muted)" }} | |
| > | |
| {isLight | |
| ? <Sun className="mr-3 h-4 w-4 text-amber-500" /> | |
| : <Moon className="mr-3 h-4 w-4 text-indigo-400" />} | |
| {isLight ? t("theme_light") : t("theme_dark")} | |
| </button> | |
| {/* Language switcher */} | |
| <div className="relative"> | |
| <button | |
| onClick={() => setShowLang(!showLang)} | |
| className={cn( | |
| "flex w-full items-center rounded-xl px-3 py-2.5 font-medium transition-all duration-200", | |
| isLight ? "hover:bg-black/5" : "hover:bg-white/5", | |
| showLang && (isLight ? "bg-black/5" : "bg-white/5") | |
| )} | |
| style={{ color: "var(--fg-muted)" }} | |
| > | |
| <Globe className="mr-3 h-4 w-4" /> | |
| <span className="flex-1 text-left">{LANGUAGES[language].native}</span> | |
| <span className="text-base leading-none">{LANGUAGES[language].flag}</span> | |
| </button> | |
| {showLang && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 4, scale: 0.97 }} | |
| animate={{ opacity: 1, y: 0, scale: 1 }} | |
| exit={{ opacity: 0, y: 4, scale: 0.97 }} | |
| transition={{ duration: 0.15 }} | |
| className="absolute bottom-full left-0 right-0 mb-1.5 rounded-xl border overflow-hidden shadow-2xl z-50" | |
| style={{ | |
| background: isLight ? "white" : "#18181b", | |
| borderColor: "var(--border-strong)", | |
| boxShadow: isLight | |
| ? "0 8px 32px rgba(15,23,42,0.12)" | |
| : "0 8px 32px rgba(0,0,0,0.5)", | |
| }} | |
| > | |
| {(Object.entries(LANGUAGES) as [Language, typeof LANGUAGES[Language]][]).map( | |
| ([code, info]) => ( | |
| <button | |
| key={code} | |
| onClick={() => { setLanguage(code); setShowLang(false); }} | |
| className={cn( | |
| "flex w-full items-center gap-3 px-3 py-2.5 text-sm transition-colors", | |
| language === code | |
| ? isLight | |
| ? "bg-emerald-50 text-emerald-700" | |
| : "bg-emerald-500/10 text-emerald-400" | |
| : isLight | |
| ? "text-slate-600 hover:bg-slate-50" | |
| : "text-zinc-300 hover:bg-white/5" | |
| )} | |
| > | |
| <span className="text-base leading-none">{info.flag}</span> | |
| <span className="flex-1 text-left font-medium">{info.native}</span> | |
| <span | |
| className="text-xs" | |
| style={{ color: "var(--fg-subtle)" }} | |
| > | |
| {info.label} | |
| </span> | |
| {language === code && ( | |
| <span className="ml-1 text-emerald-500 text-xs">✓</span> | |
| )} | |
| </button> | |
| ) | |
| )} | |
| </motion.div> | |
| )} | |
| </div> | |
| {/* Sign out */} | |
| <button | |
| onClick={handleLogout} | |
| className={cn( | |
| "flex w-full items-center rounded-xl px-3 py-2.5 font-medium transition-all duration-200", | |
| isLight | |
| ? "text-slate-500 hover:bg-red-50 hover:text-red-600" | |
| : "text-zinc-500 hover:bg-red-500/10 hover:text-red-400" | |
| )} | |
| > | |
| <LogOut className="mr-3 h-4 w-4 flex-shrink-0" /> | |
| {t("sign_out")} | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |