Alexis Bruneteau f825a2392c feat: Implement dark theme for frontend with toggle
Changes:
- Add next-themes dependency for theme management
- Create ThemeProvider wrapper for app root layout
- Set dark mode as default theme
- Create ThemeToggle component with Sun/Moon icons
- Add theme toggle to home page navigation
- Add theme toggle to dashboard header
- App now starts in dark mode with ability to switch to light mode

Styling uses existing Tailwind dark mode variables configured in
tailwind.config.ts and globals.css. All existing components automatically
support dark theme.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 16:35:44 +01:00

124 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Menu, LogOut, User as UserIcon } from "lucide-react"
import { useState } from "react"
import { useAuth } from "@/lib/auth-context"
import { ProtectedRoute } from "@/components/protected-route"
import { ThemeToggle } from "@/components/theme-toggle"
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
const router = useRouter()
const { user, logout } = useAuth()
const [sidebarOpen, setSidebarOpen] = useState(false)
const handleLogout = () => {
logout()
router.push("/auth/login")
}
const navItems = [
{ href: "/dashboard", label: "Tableau de Bord", icon: "📊" },
{ href: "/dashboard/votes/active", label: "Votes Actifs", icon: "🗳️" },
{ href: "/dashboard/votes/upcoming", label: "Votes à Venir", icon: "📅" },
{ href: "/dashboard/votes/history", label: "Historique", icon: "📜" },
{ href: "/dashboard/votes/archives", label: "Archives", icon: "🗂️" },
{ href: "/dashboard/blockchain", label: "Blockchain", icon: "⛓️" },
{ href: "/dashboard/profile", label: "Profil", icon: "👤" },
]
return (
<div className="min-h-screen bg-background flex">
{/* Sidebar */}
<aside
className={`fixed inset-y-0 left-0 z-40 w-64 bg-card border-r border-border transition-transform duration-300 ${
sidebarOpen ? "translate-x-0" : "-translate-x-full"
} lg:static lg:translate-x-0`}
>
<div className="flex flex-col h-full">
{/* Logo */}
<div className="flex items-center gap-2 p-6 border-b border-border">
<span className="text-2xl">🗳</span>
<span className="font-bold text-lg text-accent">E-Voting</span>
</div>
{/* Navigation */}
<nav className="flex-1 p-4 space-y-2">
{navItems.map((item) => (
<Link
key={item.href}
href={item.href}
className="flex items-center gap-3 px-4 py-3 rounded-lg hover:bg-muted transition-colors text-foreground hover:text-accent"
onClick={() => setSidebarOpen(false)}
>
<span className="text-xl">{item.icon}</span>
<span className="text-sm font-medium">{item.label}</span>
</Link>
))}
</nav>
{/* Footer */}
<div className="p-4 border-t border-border space-y-2">
<Link href="/dashboard/profile">
<Button variant="ghost" className="w-full justify-start gap-2">
<UserIcon className="w-4 h-4" />
Mon Profil
</Button>
</Link>
<Button
onClick={handleLogout}
variant="ghost"
className="w-full justify-start gap-2 text-destructive hover:text-destructive"
>
<LogOut className="w-4 h-4" />
Déconnexion
</Button>
</div>
</div>
</aside>
{/* Main Content */}
<div className="flex-1 flex flex-col">
{/* Top Bar */}
<header className="sticky top-0 z-30 border-b border-border bg-card/50 backdrop-blur-sm">
<div className="flex items-center justify-between px-6 py-4">
<button
onClick={() => setSidebarOpen(!sidebarOpen)}
className="lg:hidden p-2 hover:bg-muted rounded-lg"
>
<Menu className="w-5 h-5" />
</button>
<div className="ml-auto flex items-center gap-4">
{user && (
<span className="text-sm text-muted-foreground">
Bienvenue, {user.first_name} {user.last_name}
</span>
)}
<ThemeToggle />
</div>
</div>
</header>
{/* Content Area */}
<main className="flex-1 overflow-auto p-6 max-w-7xl w-full mx-auto">
<ProtectedRoute>{children}</ProtectedRoute>
</main>
</div>
{/* Mobile Overlay */}
{sidebarOpen && (
<div
className="fixed inset-0 bg-black/50 z-30 lg:hidden"
onClick={() => setSidebarOpen(false)}
/>
)}
</div>
)
}