224 lines
7.0 KiB
TypeScript
224 lines
7.0 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import Link from "next/link";
|
|
import { usePathname, useRouter } from "next/navigation";
|
|
import { signOut } from "next-auth/react";
|
|
import { cn } from "@/lib/utils";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
LayoutDashboard,
|
|
Wallet,
|
|
Tags,
|
|
BarChart3,
|
|
Upload,
|
|
ChevronLeft,
|
|
ChevronRight,
|
|
Settings,
|
|
Wand2,
|
|
LogOut,
|
|
} from "lucide-react";
|
|
import { toast } from "sonner";
|
|
import { Sheet, SheetContent } from "@/components/ui/sheet";
|
|
import { useIsMobile } from "@/hooks/use-mobile";
|
|
|
|
const navItems = [
|
|
{ href: "/", label: "Tableau de bord", icon: LayoutDashboard },
|
|
{ href: "/accounts", label: "Comptes", icon: Wallet },
|
|
{ href: "/transactions", label: "Transactions", icon: Upload },
|
|
{ href: "/categories", label: "Catégories", icon: Tags },
|
|
{ href: "/rules", label: "Règles", icon: Wand2 },
|
|
{ href: "/statistics", label: "Statistiques", icon: BarChart3 },
|
|
];
|
|
|
|
interface SidebarContentProps {
|
|
collapsed?: boolean;
|
|
onNavigate?: () => void;
|
|
}
|
|
|
|
function SidebarContent({
|
|
collapsed = false,
|
|
onNavigate,
|
|
showHeader = false,
|
|
}: SidebarContentProps & { showHeader?: boolean }) {
|
|
const pathname = usePathname();
|
|
const router = useRouter();
|
|
|
|
const handleSignOut = async () => {
|
|
try {
|
|
await signOut({ redirect: false });
|
|
toast.success("Déconnexion réussie");
|
|
router.push("/login");
|
|
router.refresh();
|
|
} catch (error) {
|
|
console.error("Error signing out:", error);
|
|
toast.error("Erreur lors de la déconnexion");
|
|
}
|
|
};
|
|
|
|
const handleLinkClick = () => {
|
|
onNavigate?.();
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{showHeader && (
|
|
<div className="flex items-center justify-between p-5 border-b border-border/50">
|
|
{!collapsed && (
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary to-primary/80 flex items-center justify-center shadow-lg shadow-primary/20">
|
|
<Wallet className="w-5 h-5 text-primary-foreground" />
|
|
</div>
|
|
<span className="font-bold text-lg text-foreground tracking-tight">
|
|
FinTrack
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<nav className={cn(
|
|
"flex-1 space-y-2",
|
|
collapsed ? "p-2" : "p-4"
|
|
)}>
|
|
{navItems.map((item) => {
|
|
const isActive = pathname === item.href;
|
|
return (
|
|
<Link key={item.href} href={item.href} onClick={handleLinkClick}>
|
|
<Button
|
|
variant={isActive ? "secondary" : "ghost"}
|
|
className={cn(
|
|
"w-full justify-start gap-4 h-12 rounded-2xl transition-all duration-300",
|
|
"hover:bg-muted/70 hover:scale-[1.02] hover:shadow-md",
|
|
isActive &&
|
|
"bg-gradient-to-r from-primary/15 via-primary/10 to-primary/5 border-2 border-primary/30 shadow-lg shadow-primary/10 backdrop-blur-sm",
|
|
collapsed && "justify-center px-2 w-12 mx-auto",
|
|
)}
|
|
>
|
|
<item.icon
|
|
className={cn(
|
|
"w-5 h-5 shrink-0 transition-all duration-300",
|
|
isActive && "text-primary scale-110",
|
|
)}
|
|
/>
|
|
{!collapsed && (
|
|
<span
|
|
className={cn(
|
|
"font-semibold text-sm",
|
|
isActive && "text-primary font-bold",
|
|
)}
|
|
>
|
|
{item.label}
|
|
</span>
|
|
)}
|
|
</Button>
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
<div className={cn(
|
|
"border-t border-border/30 space-y-2",
|
|
collapsed ? "p-2" : "p-4"
|
|
)}>
|
|
<Link href="/settings" onClick={handleLinkClick}>
|
|
<Button
|
|
variant="ghost"
|
|
className={cn(
|
|
"w-full justify-start gap-4 h-12 rounded-2xl transition-all duration-300 hover:bg-muted/70 hover:scale-[1.02] hover:shadow-md",
|
|
collapsed && "justify-center px-2 w-12 mx-auto",
|
|
)}
|
|
>
|
|
<Settings className="w-5 h-5 shrink-0" />
|
|
{!collapsed && (
|
|
<span className="font-semibold text-sm">Paramètres</span>
|
|
)}
|
|
</Button>
|
|
</Link>
|
|
<Button
|
|
variant="ghost"
|
|
onClick={handleSignOut}
|
|
className={cn(
|
|
"w-full justify-start gap-4 h-12 rounded-2xl transition-all duration-300",
|
|
"text-destructive hover:text-destructive hover:bg-destructive/15 hover:scale-[1.02] hover:shadow-md",
|
|
collapsed && "justify-center px-2 w-12 mx-auto",
|
|
)}
|
|
>
|
|
<LogOut className="w-5 h-5 shrink-0" />
|
|
{!collapsed && (
|
|
<span className="font-semibold text-sm">Déconnexion</span>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
interface SidebarProps {
|
|
open?: boolean;
|
|
onOpenChange?: (open: boolean) => void;
|
|
}
|
|
|
|
export function Sidebar({ open, onOpenChange }: SidebarProps) {
|
|
const [collapsed, setCollapsed] = useState(false);
|
|
const isMobile = useIsMobile();
|
|
|
|
if (isMobile) {
|
|
return (
|
|
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
<SheetContent side="left" className="w-64 p-0">
|
|
<div className="flex flex-col h-full">
|
|
<SidebarContent
|
|
showHeader
|
|
onNavigate={() => onOpenChange?.(false)}
|
|
/>
|
|
</div>
|
|
</SheetContent>
|
|
</Sheet>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<aside
|
|
className={cn(
|
|
"hidden md:flex flex-col h-screen glass border-r border-border transition-all duration-300",
|
|
"backdrop-blur-xl",
|
|
collapsed ? "w-16" : "w-64",
|
|
)}
|
|
>
|
|
<div className={cn(
|
|
"flex items-center border-b border-border/30 transition-all duration-300",
|
|
collapsed ? "justify-center p-4" : "justify-between p-6"
|
|
)}>
|
|
{!collapsed && (
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-primary via-primary/90 to-primary/80 flex items-center justify-center shadow-xl shadow-primary/30 backdrop-blur-sm">
|
|
<Wallet className="w-6 h-6 text-primary-foreground" />
|
|
</div>
|
|
<span className="font-black text-xl text-foreground tracking-tight">
|
|
FinTrack
|
|
</span>
|
|
</div>
|
|
)}
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={() => setCollapsed(!collapsed)}
|
|
className={cn(
|
|
"hover:bg-muted/60 rounded-xl transition-all duration-300 hover:scale-110",
|
|
collapsed ? "" : "ml-auto"
|
|
)}
|
|
>
|
|
{collapsed ? (
|
|
<ChevronRight className="w-5 h-5" />
|
|
) : (
|
|
<ChevronLeft className="w-5 h-5" />
|
|
)}
|
|
</Button>
|
|
</div>
|
|
|
|
<SidebarContent collapsed={collapsed} showHeader={false} />
|
|
</aside>
|
|
);
|
|
}
|