diff --git a/app/Http/Controllers/Auth/PasswordResetLinkController.php b/app/Http/Controllers/Auth/PasswordResetLinkController.php index 9fcfe49..a42e810 100644 --- a/app/Http/Controllers/Auth/PasswordResetLinkController.php +++ b/app/Http/Controllers/Auth/PasswordResetLinkController.php @@ -36,6 +36,6 @@ class PasswordResetLinkController extends Controller $request->only('email') ); - return back()->with('status', __('A reset link will be sent if the account exists.')); + return back()->with('status', __('passwords.sent_if_exists')); } } diff --git a/lang/en/passwords.php b/lang/en/passwords.php new file mode 100644 index 0000000..0462488 --- /dev/null +++ b/lang/en/passwords.php @@ -0,0 +1,10 @@ + 'Your password has been reset.', + 'sent' => 'We have emailed your password reset link.', + 'sent_if_exists' => 'A reset link will be sent if the account exists.', + 'throttled' => 'Please wait before retrying.', + 'token' => 'This password reset token is invalid.', + 'user' => 'We can\'t find a user with that email address.', +]; diff --git a/lang/fr/passwords.php b/lang/fr/passwords.php new file mode 100644 index 0000000..0d9d42a --- /dev/null +++ b/lang/fr/passwords.php @@ -0,0 +1,10 @@ + 'Votre mot de passe a été réinitialisé.', + 'sent' => 'Nous vous avons envoyé le lien de réinitialisation par e-mail.', + 'sent_if_exists' => 'Un lien de réinitialisation sera envoyé si le compte existe.', + 'throttled' => 'Veuillez patienter avant de réessayer.', + 'token' => 'Ce jeton de réinitialisation est invalide.', + 'user' => 'Aucun utilisateur trouvé avec cette adresse e-mail.', +]; \ No newline at end of file diff --git a/resources/css/app.css b/resources/css/app.css index dc102d8..9d18d09 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -26,15 +26,24 @@ --color-card: var(--card); --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); --color-secondary: var(--secondary); --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); @@ -43,7 +52,15 @@ --color-chart-2: var(--chart-2); --color-chart-3: var(--chart-3); - + /* Sidebar */ + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); } :root { @@ -52,22 +69,40 @@ --card: #ffffff; --card-foreground: #0a0a0a; + --popover: #ffffff; + --popover-foreground: #0a0a0a; + --primary: #f5a623; --primary-foreground: #0a0a0a; --secondary: #f48fb1; --secondary-foreground: #0a0a0a; + --muted: #f5f5f5; + --muted-foreground: #737373; + --accent: #00473e; --accent-foreground: #ffffff; + --destructive: #dc2626; + --destructive-foreground: #ffffff; + --border: #e5e5e5; --input: #e5e5e5; --ring: #d4d4d4; - --chart-1: #f5a623; /* orange */ - --chart-2: #f48fb1; /* rose */ - --chart-3: #ffffff; /* blanc */ + --chart-1: #f5a623; + --chart-2: #f48fb1; + --chart-3: #ffffff; + + --sidebar: #ffffff; + --sidebar-foreground: #0a0a0a; + --sidebar-primary: #f5a623; + --sidebar-primary-foreground: #0a0a0a; + --sidebar-accent: #f5f5f5; + --sidebar-accent-foreground: #0a0a0a; + --sidebar-border: #e5e5e5; + --sidebar-ring: #d4d4d4; } .dark { @@ -76,15 +111,24 @@ --card: #171717; --card-foreground: #f9f9f9; - --primary: #007c6c; /* vert plus clair */ + --popover: #171717; + --popover-foreground: #f9f9f9; + + --primary: #007c6c; --primary-foreground: #0a0a0a; --secondary: #2c2c2c; --secondary-foreground: #f9f9f9; + --muted: #2c2c2c; + --muted-foreground: #a3a3a3; + --accent: #f48fb1; --accent-foreground: #171717; + --destructive: #ef4444; + --destructive-foreground: #ffffff; + --border: #2c2c2c; --input: #2c2c2c; --ring: #6f6f6f; @@ -92,6 +136,24 @@ --chart-1: #f48fb1; --chart-2: #ffb300; --chart-3: #f9f9f9; + + --sidebar: #171717; + --sidebar-foreground: #f9f9f9; + --sidebar-primary: #007c6c; + --sidebar-primary-foreground: #f9f9f9; + --sidebar-accent: #2c2c2c; + --sidebar-accent-foreground: #f9f9f9; + --sidebar-border: #2c2c2c; + --sidebar-ring: #6f6f6f; +} + +@layer utilities { + .nb-shadow { + @apply border-3 border-black shadow-[4px_4px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-2 transition delay-50 duration-200 ease-in-out; + } + .nb-shadow-static { + @apply border-3 border-black shadow-[4px_4px_0px_rgba(0,0,0,1)]; + } } @layer base { @@ -124,7 +186,7 @@ text-decoration-line: underline; } - button { + button:not([data-slot="button"]):not([data-slot="checkbox"]) { @apply bg-white border border-black shadow-sm text-black px-4 py-2 rounded-md hover:shadow-md transition; } } diff --git a/resources/js/components/app-header.tsx b/resources/js/components/app-header.tsx index 194efc3..2a267a3 100644 --- a/resources/js/components/app-header.tsx +++ b/resources/js/components/app-header.tsx @@ -13,54 +13,27 @@ import { NavigationMenuList, navigationMenuTriggerStyle, } from '@/components/ui/navigation-menu'; -import { - Sheet, - SheetContent, - SheetHeader, - SheetTitle, - SheetTrigger, -} from '@/components/ui/sheet'; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from '@/components/ui/tooltip'; import { UserMenuContent } from '@/components/user-menu-content'; +import { useAppearance } from '@/hooks/use-appearance'; import { useInitials } from '@/hooks/use-initials'; +import { useMobileNavigation } from '@/hooks/use-mobile-navigation'; import { cn } from '@/lib/utils'; -import { dashboard } from '@/routes'; +import { dashboard, logout } from '@/routes'; import { type BreadcrumbItem, type NavItem, type SharedData } from '@/types'; -import { Link, usePage } from '@inertiajs/react'; -import { BookOpen, Folder, LayoutGrid, Menu, Search } from 'lucide-react'; -import AppearanceToggleDropdown from './appearance-dropdown'; +import { Link, router, usePage } from '@inertiajs/react'; +import { LayoutGrid, LogOut, Menu, Moon, Settings, Sun, X } from 'lucide-react'; +import { useEffect, useState } from 'react'; import AppLogo from './app-logo'; import AppLogoIcon from './app-logo-icon'; const mainNavItems: NavItem[] = [ { - title: 'Dashboard', + title: 'Tableau de Bord', href: dashboard(), icon: LayoutGrid, }, ]; -const rightNavItems: NavItem[] = [ - { - title: 'Repository', - href: 'https://github.com/laravel/react-starter-kit', - icon: Folder, - }, - { - title: 'Documentation', - href: 'https://laravel.com/docs/starter-kits#react', - icon: BookOpen, - }, -]; - -const activeItemStyles = - 'text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100'; - interface AppHeaderProps { breadcrumbs?: BreadcrumbItem[]; } @@ -69,122 +42,68 @@ export function AppHeader({ breadcrumbs = [] }: AppHeaderProps) { const page = usePage(); const { auth } = page.props; const getInitials = useInitials(); + const cleanup = useMobileNavigation(); + const { appearance, updateAppearance } = useAppearance(); + const [isMenuOpen, setIsMenuOpen] = useState(false); + + const toggleAppearance = () => { + updateAppearance(appearance === 'dark' ? 'light' : 'dark'); + }; + + const closeMenu = () => setIsMenuOpen(false); + + const handleLogout = () => { + cleanup(); + router.flushAll(); + }; + + useEffect(() => { + return router.on('navigate', closeMenu); + }, []); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') closeMenu(); + }; + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, []); + + useEffect(() => { + document.body.style.overflow = isMenuOpen ? 'hidden' : ''; + return () => { document.body.style.overflow = ''; }; + }, [isMenuOpen]); + return ( <> -
+
- {/* Mobile Menu */} -
- - - - - - - Navigation Menu - - - - -
-
-
- {mainNavItems.map((item) => ( - - {item.icon && ( - - )} - {item.title} - - ))} -
-
- {rightNavItems.map((item) => ( - - {item.icon && ( - - )} - {item.title} - - ))} -
-
-
-
-
-
- - - + {/* Logo */} + + - {/* Desktop Navigation */} -
+ {/* Desktop nav */} +
- + {mainNavItems.map((item, index) => ( - + - {item.icon && ( - - )} + {item.icon && } {item.title} - {page.url === item.href && ( -
+ {page.url === (typeof item.href === 'string' ? item.href : item.href.url) && ( +
)} ))} @@ -192,66 +111,23 @@ export function AppHeader({ breadcrumbs = [] }: AppHeaderProps) {
-
-
- -
- {rightNavItems.map((item) => ( - - - - - - {item.title} - - {item.icon && ( - - )} - - - -

{item.title}

-
-
-
- ))} -
-
- + {/* Right actions */} +
+ {/* Theme toggle — desktop only */} + + + {/* Avatar dropdown — always visible */} -
+ + {/* Mobile menu */} + {isMenuOpen && ( + <> +