- Ajouter margin-left au contenu principal pour compenser la sidebar fixe - Margin dynamique basé sur l'état ouvert/fermé de la sidebar - Augmenter z-index du dropdown de recherche à z-[110] pour être au-dessus du header (z-100) - Ajouter z-index au wrapper de recherche dans Header - Le contenu principal ne se superpose plus avec la sidebar - Le dropdown de recherche s'affiche correctement au-dessus de tous les éléments
232 lines
10 KiB
TypeScript
232 lines
10 KiB
TypeScript
import { useState } from 'react';
|
|
import { Link, useNavigate } from 'react-router-dom';
|
|
import { useAuthStore } from '@/features/auth/store/authStore';
|
|
import { useUser } from '@/features/auth/hooks/useUser';
|
|
import { useUIStore } from '@/stores/ui';
|
|
import { useTranslation } from '@/hooks/useTranslation';
|
|
import { EmailVerificationBadge } from '@/features/auth/components/EmailVerificationBadge';
|
|
import { NotificationMenu } from '@/components/notifications/NotificationMenu';
|
|
import { GlobalSearchBar } from '@/components/search/GlobalSearchBar';
|
|
import { RateLimitIndicator } from '@/components/RateLimitIndicator';
|
|
import { Button } from '@/components/ui/button';
|
|
import { FocusTrap } from '@/components/ui/focus-trap';
|
|
import { Tooltip } from '@/components/ui/tooltip';
|
|
import { cn } from '@/lib/utils';
|
|
import {
|
|
User,
|
|
Settings,
|
|
LogOut,
|
|
Moon,
|
|
Sun,
|
|
Monitor,
|
|
Search,
|
|
Cpu,
|
|
Menu,
|
|
} from 'lucide-react';
|
|
import type { BaseComponentProps } from '../types';
|
|
|
|
/**
|
|
* Props for Header component
|
|
* FE-TYPE-013: Fully typed component props
|
|
*/
|
|
export interface HeaderProps extends BaseComponentProps {
|
|
// No additional props needed - uses global stores
|
|
}
|
|
|
|
export function Header(_props: HeaderProps) {
|
|
const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
|
|
const { logout } = useAuthStore();
|
|
const { data: user } = useUser();
|
|
const { theme, setTheme, sidebarOpen, setSidebarOpen } = useUIStore();
|
|
const { t } = useTranslation();
|
|
const navigate = useNavigate();
|
|
|
|
const handleLogout = async () => {
|
|
await logout();
|
|
navigate('/login');
|
|
};
|
|
|
|
const toggleTheme = () => {
|
|
const newTheme =
|
|
theme === 'light' ? 'dark' : theme === 'dark' ? 'system' : 'light';
|
|
setTheme(newTheme);
|
|
};
|
|
|
|
const getThemeIcon = () => {
|
|
switch (theme) {
|
|
case 'light':
|
|
return <Sun className="h-4 w-4" />;
|
|
case 'dark':
|
|
return <Moon className="h-4 w-4" />;
|
|
default:
|
|
return <Monitor className="h-4 w-4" />;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<header
|
|
className="fixed top-0 left-0 right-0 h-16 px-6 mt-4 pointer-events-none"
|
|
style={{ zIndex: 100 }}
|
|
>
|
|
<div className="max-w-[1700px] mx-auto w-full h-full glass-hud rounded-2xl border-white/10 flex items-center justify-between px-6 pointer-events-auto hud-corner group">
|
|
<div className="flex items-center gap-6">
|
|
{/* Bouton toggle sidebar pour mobile */}
|
|
<button
|
|
onClick={() => setSidebarOpen(!sidebarOpen)}
|
|
className="lg:hidden p-2 rounded-lg hover:bg-white/5 transition-colors text-kodo-secondary hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kodo-cyan focus-visible:ring-offset-2 focus-visible:ring-offset-kodo-void"
|
|
aria-label={sidebarOpen ? 'Close sidebar' : 'Open sidebar'}
|
|
>
|
|
<Menu className="w-5 h-5" />
|
|
</button>
|
|
|
|
<Link
|
|
to="/dashboard"
|
|
className="flex items-center gap-4 active:scale-95 transition-transform focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kodo-steel focus-visible:ring-offset-2 focus-visible:ring-offset-kodo-void rounded"
|
|
>
|
|
<div className="w-10 h-10 bg-kodo-steel/20 rounded-xl flex items-center justify-center border border-kodo-steel/30 shadow-2xl animate-scan">
|
|
<Cpu className="w-6 h-6 text-kodo-steel animate-pulse-glow" />
|
|
</div>
|
|
<div className="hidden sm:block">
|
|
<h1 className="text-3xl font-display font-bold text-white tracking-widest uppercase leading-tight">
|
|
Veza<span className="text-kodo-steel">OS</span>
|
|
</h1>
|
|
<div className="text-[9px] font-mono text-kodo-steel/50 tracking-[0.2em] uppercase -mt-0.5">
|
|
Core Network v2.4
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
|
|
{/* HUD System Status */}
|
|
<div className="hidden xl:flex items-center gap-6 border-l border-white/10 pl-8 h-8">
|
|
<div className="flex flex-col">
|
|
<span className="text-hud">Uplink Status</span>
|
|
<div className="flex items-center gap-1.5">
|
|
<span className="w-1.5 h-1.5 rounded-full bg-kodo-lime animate-pulse" />
|
|
<span className="text-[11px] font-mono text-white opacity-90 uppercase tracking-tighter">
|
|
Connected
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col">
|
|
<span className="text-hud">Node ID</span>
|
|
<span className="text-[11px] font-mono text-kodo-cyan opacity-90 uppercase truncate max-w-[80px]">
|
|
VZ-{user?.id?.slice(0, 4)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Global Search integrated */}
|
|
<div className="flex-1 max-w-lg mx-8 relative hidden md:block z-[105]">
|
|
{/* FIX: L'icône Search est déjà dans le composant Search, pas besoin de la dupliquer */}
|
|
{/* FIX: z-index élevé pour que le dropdown de recherche soit au-dessus de tout */}
|
|
<GlobalSearchBar className="w-full" />
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4">
|
|
{/* Quick Stats Overlay - Styled specifically */}
|
|
<div className="hidden lg:flex items-center gap-1 mr-4 bg-kodo-cyan/5 border border-kodo-steel/10 rounded-full px-4 py-1.5">
|
|
<div className="w-1.5 h-1.5 rounded-full bg-kodo-lime" />
|
|
<span className="text-[10px] font-mono text-kodo-steel uppercase font-bold tracking-tight">
|
|
Active_Stream_OK
|
|
</span>
|
|
</div>
|
|
|
|
<NotificationMenu />
|
|
|
|
<RateLimitIndicator />
|
|
|
|
<Tooltip content={t('common.changeTheme')}>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={toggleTheme}
|
|
className="hover:bg-white/5 hover:text-white transition-all rounded-xl border border-transparent hover:border-white/5"
|
|
>
|
|
{getThemeIcon()}
|
|
</Button>
|
|
</Tooltip>
|
|
|
|
<div className="w-px h-6 bg-white/10 mx-1" />
|
|
|
|
{/* User Profile HUD */}
|
|
<div className="relative">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => setIsUserMenuOpen(!isUserMenuOpen)}
|
|
className={cn(
|
|
'p-1 pr-3 rounded-xl gap-2 transition-all border border-transparent',
|
|
isUserMenuOpen
|
|
? 'bg-kodo-cyan/10 border-kodo-cyan/30 text-kodo-cyan'
|
|
: 'hover:bg-white/5',
|
|
)}
|
|
>
|
|
<div className="w-8 h-8 rounded-lg bg-kodo-graphite border border-white/10 flex items-center justify-center overflow-hidden">
|
|
<User className="w-5 h-5 text-kodo-secondary" />
|
|
</div>
|
|
<div className="flex flex-col items-start hidden sm:flex">
|
|
<span className="text-xs font-bold text-white leading-none">
|
|
{user?.username}
|
|
</span>
|
|
<span className="text-[9px] font-mono text-kodo-secondary uppercase tracking-tighter">
|
|
Lvl 1 Operative
|
|
</span>
|
|
</div>
|
|
</Button>
|
|
|
|
{isUserMenuOpen && (
|
|
<FocusTrap
|
|
active={isUserMenuOpen}
|
|
onEscape={() => setIsUserMenuOpen(false)}
|
|
>
|
|
<div className="absolute right-0 mt-3 w-56 glass-hud rounded-2xl border-white/10 shadow-2xl z-50 p-2 animate-scaleIn hud-corner">
|
|
<div className="px-4 py-4 border-b border-white/5 mb-2">
|
|
<div className="text-xs font-mono text-kodo-steel opacity-50 uppercase tracking-widest mb-1">
|
|
User_Registry
|
|
</div>
|
|
<div className="text-sm font-bold text-white truncate">
|
|
{user?.email}
|
|
</div>
|
|
{user && !user.is_verified && (
|
|
<div className="mt-2">
|
|
<EmailVerificationBadge verified={false} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-1">
|
|
<Link
|
|
to="/profile"
|
|
onClick={() => setIsUserMenuOpen(false)}
|
|
className="flex items-center gap-4 px-4 py-2 text-sm text-kodo-secondary hover:text-white hover:bg-white/5 rounded-xl transition-all group focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kodo-steel focus-visible:ring-offset-2 focus-visible:ring-offset-kodo-void"
|
|
>
|
|
<User className="w-4 h-4" />
|
|
<span>System.Profile</span>
|
|
</Link>
|
|
<Link
|
|
to="/settings"
|
|
onClick={() => setIsUserMenuOpen(false)}
|
|
className="flex items-center gap-4 px-4 py-2 text-sm text-kodo-secondary hover:text-white hover:bg-white/5 rounded-xl transition-all group focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-kodo-steel focus-visible:ring-offset-2 focus-visible:ring-offset-kodo-void"
|
|
>
|
|
<Settings className="w-4 h-4 group-hover:rotate-45 transition-transform" />
|
|
<span>Global.Settings</span>
|
|
</Link>
|
|
<div className="h-px bg-white/5 my-2 mx-2" />
|
|
<button
|
|
onClick={handleLogout}
|
|
className="flex items-center gap-4 w-full px-4 py-2 text-sm text-kodo-red/80 hover:text-kodo-red hover:bg-kodo-red/10 rounded-xl transition-all group cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-red-400 focus-visible:ring-offset-2 focus-visible:ring-offset-kodo-void"
|
|
>
|
|
<LogOut className="w-4 h-4" />
|
|
<span>Terminate.Session</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</FocusTrap>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|