Ajout du mode clair/sombre (dark mode)
- Installation de next-themes - Création du composant ThemeToggle avec icônes soleil/lune - Intégration du ThemeProvider dans le layout - Ajout du toggle dans la navigation mobile et le header admin - Adaptation des couleurs pour le dark mode (tropical chic) - Mise à jour des composants UI (Card, Button) pour le dark mode - Adaptation des composants principaux (Layout, WifiCard, etc.)
This commit is contained in:
9
components/ThemeProvider.tsx
Normal file
9
components/ThemeProvider.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||
import { type ThemeProviderProps } from "next-themes/dist/types";
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||
}
|
||||
|
||||
45
components/ThemeToggle.tsx
Normal file
45
components/ThemeToggle.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-9 w-9 rounded-full"
|
||||
aria-label="Changer de thème"
|
||||
>
|
||||
<Sun className="h-5 w-5" />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||
className="h-10 w-10 rounded-full hover:bg-secondary dark:hover:bg-gray-800"
|
||||
aria-label="Changer de thème"
|
||||
>
|
||||
{theme === "dark" ? (
|
||||
<Sun className="h-5 w-5 text-primary dark:text-yellow-400" />
|
||||
) : (
|
||||
<Moon className="h-5 w-5 text-primary dark:text-blue-300" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ export default function WifiCard() {
|
||||
const showPasswordFallback = error && error.includes("sélectionner manuellement");
|
||||
|
||||
return (
|
||||
<Card className="bg-white">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Wifi className="h-6 w-6 text-primary" />
|
||||
@ -80,22 +80,22 @@ export default function WifiCard() {
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-600 mb-1">Nom du réseau</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mb-1">Nom du réseau</p>
|
||||
<p className="text-lg font-semibold text-primary">{wifiName || "Chargement..."}</p>
|
||||
</div>
|
||||
|
||||
{showPasswordFallback && wifiPassword && (
|
||||
<div className="bg-yellow-50 border border-yellow-200 rounded-xl p-3">
|
||||
<p className="text-sm text-yellow-800 font-mono select-all">
|
||||
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-xl p-3">
|
||||
<p className="text-sm text-yellow-800 dark:text-yellow-300 font-mono select-all">
|
||||
{wifiPassword}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && !showPasswordFallback && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-xl p-3 flex items-start gap-2">
|
||||
<AlertCircle className="h-5 w-5 text-red-600 flex-shrink-0 mt-0.5" />
|
||||
<p className="text-sm text-red-800">{error}</p>
|
||||
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-xl p-3 flex items-start gap-2">
|
||||
<AlertCircle className="h-5 w-5 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5" />
|
||||
<p className="text-sm text-red-800 dark:text-red-300">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import { LogOut } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||
|
||||
export default function AdminLayout({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter();
|
||||
@ -13,14 +14,17 @@ export default function AdminLayout({ children }: { children: React.ReactNode })
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<header className="bg-white border-b border-gray-200 shadow-sm">
|
||||
<div className="min-h-screen bg-background dark:bg-background-dark">
|
||||
<header className="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-800 shadow-sm">
|
||||
<div className="max-w-4xl mx-auto px-4 py-4 flex items-center justify-between">
|
||||
<h1 className="text-xl font-bold text-primary">Administration</h1>
|
||||
<Button variant="outline" size="sm" onClick={handleLogout}>
|
||||
<LogOut className="h-4 w-4 mr-2" />
|
||||
Déconnexion
|
||||
</Button>
|
||||
<h1 className="text-xl font-bold text-primary dark:text-primary">Administration</h1>
|
||||
<div className="flex items-center gap-2">
|
||||
<ThemeToggle />
|
||||
<Button variant="outline" size="sm" onClick={handleLogout} className="dark:border-gray-700 dark:text-gray-300">
|
||||
<LogOut className="h-4 w-4 mr-2" />
|
||||
Déconnexion
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main className="max-w-4xl mx-auto px-4 py-6">{children}</main>
|
||||
|
||||
@ -2,7 +2,7 @@ import TabNavigation from "./TabNavigation";
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background pb-16">
|
||||
<div className="min-h-screen bg-background dark:bg-background-dark pb-16">
|
||||
{children}
|
||||
<TabNavigation />
|
||||
</div>
|
||||
|
||||
@ -4,6 +4,7 @@ import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Home, MapPin, Info, Waves } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ThemeToggle } from "@/components/ThemeToggle";
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
@ -32,7 +33,7 @@ export default function TabNavigation() {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<nav className="fixed bottom-0 left-0 right-0 z-50 bg-white border-t border-gray-200 shadow-lg">
|
||||
<nav className="fixed bottom-0 left-0 right-0 z-50 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-800 shadow-lg">
|
||||
<div className="flex items-center justify-around h-16 px-2">
|
||||
{tabs.map((tab) => {
|
||||
const Icon = tab.icon;
|
||||
@ -44,8 +45,8 @@ export default function TabNavigation() {
|
||||
className={cn(
|
||||
"flex flex-col items-center justify-center gap-1 flex-1 h-full rounded-xl transition-colors",
|
||||
isActive
|
||||
? "text-primary bg-secondary"
|
||||
: "text-gray-500 hover:text-primary hover:bg-gray-50"
|
||||
? "text-primary bg-secondary dark:bg-primary/20"
|
||||
: "text-gray-500 dark:text-gray-400 hover:text-primary dark:hover:text-primary hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
)}
|
||||
>
|
||||
<Icon className="h-6 w-6" />
|
||||
@ -53,6 +54,9 @@ export default function TabNavigation() {
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
<div className="flex items-center justify-center h-full px-2">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
|
||||
@ -7,10 +7,10 @@ const buttonVariants = cva(
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
outline: "border-2 border-primary text-primary hover:bg-primary hover:text-white",
|
||||
ghost: "hover:bg-secondary hover:text-secondary-foreground",
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90 dark:bg-primary dark:hover:bg-primary/80",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 dark:bg-primary/20 dark:text-primary dark:hover:bg-primary/30",
|
||||
outline: "border-2 border-primary text-primary hover:bg-primary hover:text-white dark:border-primary dark:text-primary dark:hover:bg-primary dark:hover:text-white",
|
||||
ghost: "hover:bg-secondary hover:text-secondary-foreground dark:hover:bg-gray-800 dark:hover:text-gray-200",
|
||||
},
|
||||
size: {
|
||||
default: "h-12 px-6 py-3",
|
||||
|
||||
@ -8,7 +8,7 @@ const Card = React.forwardRef<
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-2xl border border-gray-200 bg-white shadow-sm",
|
||||
"rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@ -46,7 +46,7 @@ const CardDescription = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={cn("text-sm text-gray-600", className)}
|
||||
className={cn("text-sm text-gray-600 dark:text-gray-400", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
Reference in New Issue
Block a user