Bankbot / frontend /src /components /layout /Sidebar.tsx
mohsin-devs's picture
feat: remove Documents nav + add in-app Documentation page + BANKBOT_DOCUMENTATION.txt
88fa3b1
Raw
History Blame Contribute Delete
11.1 kB
"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>
);
}