amaniquery-frontend / src /components /admin-sidebar.tsx
Benaah's picture
Upload folder using huggingface_hub
6a059d3 verified
Raw
History Blame Contribute Delete
8.11 kB
"use client"
import { useState, useEffect } from "react"
import Link from "next/link"
import { usePathname, useRouter } from "next/navigation"
import { useAuth } from "@/lib/auth-context"
import {
Home,
MessageSquare,
Mic,
LogOut,
Menu,
X,
Settings,
BarChart3,
Users,
Shield,
FileText,
Activity,
Brain,
ArrowBigUp,
Database,
Sparkles,
Zap,
} from "lucide-react"
import { Button } from "./ui/button"
import { cn } from "@/lib/utils"
export function AdminSidebar() {
const [isOpen, setIsOpen] = useState(false)
const [isHovered, setIsHovered] = useState(false)
const pathname = usePathname()
const router = useRouter()
const { user, logout } = useAuth()
// Auto-hide on desktop, show on mobile toggle
const [isMobile, setIsMobile] = useState(false)
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth < 768)
}
checkMobile()
window.addEventListener("resize", checkMobile)
return () => window.removeEventListener("resize", checkMobile)
}, [])
const handleLogout = async () => {
await logout()
}
const adminNavItems = [
{ href: "/admin", icon: BarChart3, label: "Dashboard" },
{ href: "/admin/users", icon: Users, label: "Users" },
{ href: "/admin/knowledge", icon: Database, label: "Knowledge Bases" },
{ href: "/admin/models", icon: Sparkles, label: "AI Models" },
{ href: "/admin/rag", icon: Zap, label: "RAG Pipeline" },
{ href: "/admin/blog", icon: FileText, label: "Blog" },
{ href: "/admin/analytics", icon: Activity, label: "Analytics" },
{ href: "/admin/training", icon: ArrowBigUp, label: "Training" },
{ href: "/admin/agent-monitoring", icon: Brain, label: "Agent Monitoring" },
{ href: "/admin/settings", icon: Settings, label: "Settings" },
]
const generalNavItems = [
{ href: "/", icon: Home, label: "Home" },
{ href: "/chat", icon: MessageSquare, label: "Chat" },
{ href: "/voice", icon: Mic, label: "Voice" },
]
// Desktop: Show on hover at left edge
// Mobile: Toggle with hamburger button
const shouldShow = isMobile ? isOpen : isHovered
return (
<>
{/* Mobile Hamburger Button */}
{isMobile && (
<button
onClick={() => setIsOpen(!isOpen)}
className="fixed top-4 left-4 z-50 p-2 rounded-lg bg-background border shadow-lg md:hidden"
aria-label="Toggle menu"
>
{isOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</button>
)}
{/* Mobile Overlay */}
{isMobile && isOpen && (
<div
className="fixed inset-0 bg-black/50 z-40 md:hidden"
onClick={() => setIsOpen(false)}
/>
)}
{/* Sidebar */}
<aside
className={cn(
"fixed left-0 top-0 h-full bg-background border-r z-40 transition-all duration-300 ease-in-out overflow-y-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:'none'] [scrollbar-width:'none']",
isMobile
? cn(
"w-64",
isOpen ? "translate-x-0" : "-translate-x-full"
)
: cn(
"w-64",
shouldShow ? "translate-x-0" : "-translate-x-[calc(100%-20px)]"
)
)}
onMouseEnter={() => !isMobile && setIsHovered(true)}
onMouseLeave={() => !isMobile && setIsHovered(false)}
>
<div className="flex flex-col h-full p-4">
{/* Logo/Header */}
<div className="mb-6">
<div className="flex items-center gap-2">
<Shield className="w-6 h-6 text-primary" />
<h2 className="text-xl font-bold">AmaniQuery</h2>
</div>
<p className="text-xs text-muted-foreground mt-1">Admin Panel</p>
</div>
{/* Admin Navigation Section */}
<div className="mb-4">
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2 px-4">
Administration
</p>
<nav className="space-y-1">
{adminNavItems.map((item) => {
const Icon = item.icon
const isActive = pathname === item.href || (item.href !== "/admin" && pathname?.startsWith(item.href))
return (
<Link
key={item.href}
href={item.href}
onClick={() => isMobile && setIsOpen(false)}
className={cn(
"flex items-center gap-3 px-4 py-2.5 rounded-lg transition-colors text-sm",
isActive
? "bg-primary text-primary-foreground"
: "hover:bg-accent"
)}
>
<Icon className="w-4 h-4" />
<span>{item.label}</span>
</Link>
)
})}
</nav>
</div>
{/* General Navigation Section */}
<div className="mb-4">
<p className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-2 px-4">
General
</p>
<nav className="space-y-1">
{generalNavItems.map((item) => {
const Icon = item.icon
const isActive = pathname === item.href
return (
<Link
key={item.href}
href={item.href}
onClick={() => isMobile && setIsOpen(false)}
className={cn(
"flex items-center gap-3 px-4 py-2.5 rounded-lg transition-colors text-sm",
isActive
? "bg-primary text-primary-foreground"
: "hover:bg-accent"
)}
>
<Icon className="w-4 h-4" />
<span>{item.label}</span>
</Link>
)
})}
</nav>
</div>
{/* User Profile Section */}
<div className="border-t pt-4 mt-auto">
{user && (
<div className="mb-4">
<div className="flex items-center gap-3 px-4 py-2">
{user.profile_image_url ? (
<div className="relative w-10 h-10 rounded-full overflow-hidden flex-shrink-0">
<img
src={user.profile_image_url}
alt={user.name || "Profile"}
className="w-full h-full object-cover"
/>
</div>
) : (
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0">
<Shield className="w-5 h-5 text-primary" />
</div>
)}
<div className="flex-1 min-w-0">
<p className="font-medium truncate text-sm">{user.name || user.email}</p>
<p className="text-xs text-muted-foreground truncate">{user.email}</p>
<p className="text-xs text-primary font-medium mt-0.5">Admin</p>
</div>
</div>
</div>
)}
{/* Logout Button */}
<Button
variant="outline"
className="w-full justify-start"
onClick={handleLogout}
>
<LogOut className="w-4 h-4 mr-2" />
Logout
</Button>
</div>
</div>
</aside>
{/* Spacer for desktop (to prevent content overlap) */}
{!isMobile && (
<div className="w-[20px] fixed left-0 top-0 h-full z-30 pointer-events-none" />
)}
</>
)
}