xpeditis2.0/apps/frontend/src/components/layout/LandingHeader.tsx
2026-08-13 12:26:46 +02:00

389 lines
15 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { Link } from '@/i18n/navigation';
import Image from 'next/image';
import { motion, AnimatePresence } from 'framer-motion';
import { ChevronDown, Info, BookOpen, LayoutDashboard, Code2, Menu, X } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useAuth } from '@/lib/context/auth-context';
import { usePathname } from '@/i18n/navigation';
import { LanguageSwitcher } from '@/components/LanguageSwitcher';
interface LandingHeaderProps {
transparentOnTop?: boolean;
activePage?: 'about' | 'contact' | 'careers' | 'blog' | 'press' | 'docs';
}
export function LandingHeader({ transparentOnTop = false, activePage }: LandingHeaderProps) {
const t = useTranslations();
const [isScrolled, setIsScrolled] = useState(false);
const [isCompanyMenuOpen, setIsCompanyMenuOpen] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const { user, isAuthenticated, loading } = useAuth();
const pathname = usePathname();
useEffect(() => {
setIsMobileMenuOpen(false);
}, [pathname]);
const companyMenuItems = [
{
href: '/about',
label: t('navigation.about'),
icon: Info,
description: t('header.company.aboutDescription'),
},
{
href: '/blog',
label: t('navigation.blog'),
icon: BookOpen,
description: t('header.company.blogDescription'),
},
];
const isCompanyMenuActive = activePage !== undefined && ['about', 'blog'].includes(activePage);
const getUserInitials = () => {
if (!user) return '';
const firstInitial = user.firstName?.charAt(0)?.toUpperCase() || '';
const lastInitial = user.lastName?.charAt(0)?.toUpperCase() || '';
return firstInitial + lastInitial || user.email?.charAt(0)?.toUpperCase() || '?';
};
const getFullName = () => {
if (!user) return '';
if (user.firstName && user.lastName) {
return `${user.firstName} ${user.lastName}`;
}
return user.email || '';
};
useEffect(() => {
if (!transparentOnTop) {
setIsScrolled(true);
return;
}
const handleScroll = () => {
setIsScrolled(window.scrollY > 50);
};
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [transparentOnTop]);
const isActiveLink = (href: string) => {
if (!activePage) return false;
return href === `/${activePage}`;
};
return (
<>
<motion.nav
initial={{ y: -100 }}
animate={{ y: 0 }}
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
isScrolled ? 'bg-brand-navy/95 backdrop-blur-md shadow-lg' : 'bg-transparent'
}`}
>
<div className="max-w-7xl mx-auto px-4 lg:px-8">
<div className="flex items-center justify-between h-16 md:h-20">
<Link href="/" className="flex items-center space-x-2">
<Image
src="/assets/logos/logo-white.png"
alt="Xpeditis"
width={60}
height={70}
priority
className="h-auto"
/>
</Link>
{/* Mobile hamburger button */}
<button
className="md:hidden p-2 text-white hover:text-brand-turquoise transition-colors"
onClick={() => setIsMobileMenuOpen(true)}
aria-label={t('header.mobileMenu.open')}
>
<Menu className="w-6 h-6" />
</button>
<div className="hidden md:flex items-center space-x-8">
<Link
href="/#features"
className="text-white hover:text-brand-turquoise transition-colors font-medium"
>
{t('navigation.features')}
</Link>
<Link
href="/#pricing"
className="text-white hover:text-brand-turquoise transition-colors font-medium"
>
{t('navigation.pricing')}
</Link>
{/* Menu Entreprise */}
<div
className="relative"
onMouseEnter={() => setIsCompanyMenuOpen(true)}
onMouseLeave={() => setIsCompanyMenuOpen(false)}
>
<button
className={`flex items-center space-x-1 transition-colors font-medium ${
isCompanyMenuActive
? 'text-brand-turquoise'
: 'text-white hover:text-brand-turquoise'
}`}
>
<span>{t('navigation.company')}</span>
<ChevronDown
className={`w-4 h-4 transition-transform ${isCompanyMenuOpen ? 'rotate-180' : ''}`}
/>
</button>
<AnimatePresence>
{isCompanyMenuOpen && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.2 }}
className="absolute top-full left-1/2 -translate-x-1/2 pt-4 z-50"
>
<div className="bg-white rounded-2xl shadow-2xl border border-border overflow-hidden min-w-[280px]">
<div className="p-2">
{companyMenuItems.map(item => {
const IconComponent = item.icon;
const isActive = isActiveLink(item.href);
return (
<Link
key={item.href}
href={item.href}
className={`flex items-center space-x-3 px-4 py-3 rounded-xl transition-colors group ${
isActive ? 'bg-brand-turquoise/10' : 'hover:bg-neutral-50'
}`}
>
<div
className={`w-10 h-10 rounded-lg flex items-center justify-center transition-colors ${
isActive
? 'bg-brand-turquoise/20'
: 'bg-brand-turquoise/10 group-hover:bg-brand-turquoise/20'
}`}
>
<IconComponent className="w-5 h-5 text-brand-turquoise" />
</div>
<div>
<div
className={`font-medium transition-colors ${
isActive
? 'text-brand-turquoise'
: 'text-brand-navy group-hover:text-brand-turquoise'
}`}
>
{item.label}
</div>
<div className="text-sm text-neutral-500">{item.description}</div>
</div>
</Link>
);
})}
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
<Link
href="/contact"
className={`transition-colors font-medium ${
activePage === 'contact'
? 'text-brand-turquoise'
: 'text-white hover:text-brand-turquoise'
}`}
>
{t('navigation.contact')}
</Link>
<Link
href="/docs/api"
className={`flex items-center gap-1.5 transition-colors font-medium ${
activePage === 'docs'
? 'text-brand-turquoise'
: 'text-white hover:text-brand-turquoise'
}`}
>
<Code2 className="w-4 h-4" />
{t('navigation.docsApi')}
</Link>
{/* Language switcher */}
<LanguageSwitcher variant="dark" />
{/* Affichage conditionnel: connecté vs non connecté */}
{loading ? (
<div className="w-8 h-8 rounded-full bg-white/20 animate-pulse" />
) : isAuthenticated && user ? (
<Link href="/dashboard" className="flex items-center space-x-3 group">
<div className="w-10 h-10 rounded-full bg-brand-turquoise flex items-center justify-center text-white font-semibold text-sm border-2 border-white/30 group-hover:border-white transition-colors">
{getUserInitials()}
</div>
<span className="text-white font-medium group-hover:text-brand-turquoise transition-colors max-w-[150px] truncate">
{getFullName()}
</span>
<LayoutDashboard className="w-5 h-5 text-white/70 group-hover:text-brand-turquoise transition-colors" />
</Link>
) : (
<>
<Link
href="/login"
className="text-white hover:text-brand-turquoise transition-colors font-medium"
>
{t('header.cta.signIn')}
</Link>
<Link
href="/register"
className="px-6 py-2.5 bg-brand-turquoise text-white rounded-lg hover:bg-brand-turquoise/90 transition-all hover:shadow-lg font-medium"
>
{t('header.cta.startFree')}
</Link>
</>
)}
</div>
</div>
</div>
</motion.nav>
{/* Mobile menu drawer */}
<AnimatePresence>
{isMobileMenuOpen && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-50 bg-black/60 md:hidden"
onClick={() => setIsMobileMenuOpen(false)}
/>
<motion.div
initial={{ x: '100%' }}
animate={{ x: 0 }}
exit={{ x: '100%' }}
transition={{ type: 'tween', duration: 0.25 }}
className="fixed top-0 right-0 bottom-0 z-50 w-[280px] bg-brand-navy flex flex-col md:hidden shadow-2xl"
>
<div className="flex items-center justify-between px-5 h-16 border-b border-white/10">
<Image
src="/assets/logos/logo-white.png"
alt="Xpeditis"
width={50}
height={60}
className="h-auto"
/>
<button
onClick={() => setIsMobileMenuOpen(false)}
className="p-2 text-white/70 hover:text-white transition-colors"
aria-label={t('header.mobileMenu.close')}
>
<X className="w-5 h-5" />
</button>
</div>
<nav className="flex-1 px-4 py-6 space-y-1 overflow-y-auto">
<Link
href="/#features"
className="flex items-center px-4 py-3 text-white/80 hover:text-white hover:bg-white/10 rounded-xl transition-colors font-medium"
>
{t('navigation.features')}
</Link>
<Link
href="/#pricing"
className="flex items-center px-4 py-3 text-white/80 hover:text-white hover:bg-white/10 rounded-xl transition-colors font-medium"
>
{t('navigation.pricing')}
</Link>
<div className="pt-2 pb-1 px-4 text-xs text-white/40 uppercase tracking-widest font-medium">
{t('navigation.company')}
</div>
{companyMenuItems.map(item => {
const IconComponent = item.icon;
return (
<Link
key={item.href}
href={item.href}
className="flex items-center space-x-3 px-4 py-3 text-white/80 hover:text-white hover:bg-white/10 rounded-xl transition-colors"
>
<IconComponent className="w-4 h-4 text-brand-turquoise" />
<span className="font-medium">{item.label}</span>
</Link>
);
})}
<Link
href="/contact"
className={`flex items-center px-4 py-3 rounded-xl transition-colors font-medium ${
activePage === 'contact'
? 'text-brand-turquoise bg-white/10'
: 'text-white/80 hover:text-white hover:bg-white/10'
}`}
>
{t('navigation.contact')}
</Link>
<Link
href="/docs/api"
className={`flex items-center space-x-2 px-4 py-3 rounded-xl transition-colors font-medium ${
activePage === 'docs'
? 'text-brand-turquoise bg-white/10'
: 'text-white/80 hover:text-white hover:bg-white/10'
}`}
>
<Code2 className="w-4 h-4" />
<span>{t('navigation.docsApi')}</span>
</Link>
<div className="pt-4 px-4">
<LanguageSwitcher variant="dark" />
</div>
</nav>
<div className="border-t border-white/10 p-4 space-y-3">
{loading ? (
<div className="h-10 rounded-xl bg-white/10 animate-pulse" />
) : isAuthenticated && user ? (
<Link
href="/dashboard"
className="flex items-center space-x-3 px-4 py-3 bg-brand-turquoise rounded-xl text-white font-medium"
>
<div className="w-8 h-8 rounded-full bg-white/20 flex items-center justify-center text-sm font-semibold flex-shrink-0">
{getUserInitials()}
</div>
<span className="flex-1 truncate">{getFullName()}</span>
<LayoutDashboard className="w-4 h-4 flex-shrink-0" />
</Link>
) : (
<>
<Link
href="/login"
className="flex items-center justify-center w-full px-4 py-3 border border-white/30 text-white rounded-xl hover:bg-white/10 transition-colors font-medium"
>
{t('header.cta.signIn')}
</Link>
<Link
href="/register"
className="flex items-center justify-center w-full px-4 py-3 bg-brand-turquoise text-white rounded-xl hover:bg-brand-turquoise/90 transition-colors font-medium"
>
{t('header.cta.startFree')}
</Link>
</>
)}
</div>
</motion.div>
</>
)}
</AnimatePresence>
</>
);
}