veza/apps/web/src/components/layout/Header.tsx

233 lines
10 KiB
TypeScript
Raw Normal View History

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':
2025-12-13 02:34:34 +00:00
return <Sun className="h-4 w-4" />;
case 'dark':
2025-12-13 02:34:34 +00:00
return <Moon className="h-4 w-4" />;
default:
2025-12-13 02:34:34 +00:00
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
2025-12-13 02:34:34 +00:00
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"
>
aesthetic-improvements: replace secondary cyan hover states with steel - Button outline variant: hover:border-kodo-cyan/50 → hover:border-kodo-steel/50 - Header secondary nav: hover:text-kodo-cyan → hover:text-white, hover:bg-kodo-cyan/5 → hover:bg-white/5 - FileManagerView: hover:border-kodo-cyan/50 → hover:border-kodo-steel/50 (kept selected state cyan) - ProjectsManager: hover:border-kodo-cyan/50 → hover:border-kodo-steel/50, hover:text-kodo-cyan → hover:text-white - GroupDetailView: hover:border-kodo-cyan/30 → hover:border-kodo-steel/50 - AIToolsView: hover:border-kodo-cyan/50 → hover:border-kodo-steel/50 - CloudFileBrowser: hover:border-kodo-cyan/50 → hover:border-kodo-steel/50 (kept selected state cyan) - ProfileView: hover:border-kodo-cyan/50 → hover:border-kodo-steel/50 - CourseCard: hover:border-kodo-cyan/50 → hover:border-kodo-steel/50 - TwoFactorSetup: hover:border-kodo-cyan → hover:border-kodo-steel/50 - GearView: hover:text-kodo-cyan → hover:text-white, hover:border-kodo-cyan → hover:border-kodo-steel/50 - ChatInput: hover:text-kodo-cyan → hover:text-white (3 instances) - ChatMessage: hover:text-kodo-cyan → hover:text-white (2 instances) - ChatRoom: hover:text-kodo-cyan → hover:text-white - AddToPlaylistModal: hover:border-kodo-cyan → hover:border-kodo-steel/50, hover:text-kodo-cyan → hover:text-white - Preserved focus rings (cyan) and active/selected states (cyan) as per audit - Action 11.3.1.2 in progress (first batch of ~15 files)
2026-01-16 09:51:30 +00:00
<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>
);
}