318 lines
10 KiB
TypeScript
318 lines
10 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import Image from 'next/image';
|
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
import { usePathname } from 'next/navigation';
|
|
import { useLanguage } from '@/context/LanguageContext';
|
|
import { useAuth } from '@/context/AuthContext';
|
|
import LanguageToggle from '@/components/LanguageToggle';
|
|
import Button from '@/components/ui/Button';
|
|
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline';
|
|
|
|
function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
|
|
const pathname = usePathname();
|
|
const isActive = pathname === href || (href !== '/' && pathname.startsWith(href));
|
|
|
|
return (
|
|
<Link
|
|
href={href}
|
|
className="font-medium transition-colors"
|
|
style={{ color: isActive ? '#FBB82B' : '#002F44' }}
|
|
>
|
|
{children}
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
function MobileNavLink({ href, children, onClick }: { href: string; children: React.ReactNode; onClick: () => void }) {
|
|
const pathname = usePathname();
|
|
const isActive = pathname === href || (href !== '/' && pathname.startsWith(href));
|
|
|
|
return (
|
|
<Link
|
|
href={href}
|
|
className="block px-6 py-3 text-lg font-medium transition-colors hover:bg-gray-50"
|
|
style={{ color: isActive ? '#FBB82B' : '#002F44' }}
|
|
onClick={onClick}
|
|
>
|
|
{children}
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
export default function Header() {
|
|
const { t } = useLanguage();
|
|
const { user, isAdmin, logout } = useAuth();
|
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
const touchStartX = useRef<number>(0);
|
|
const touchCurrentX = useRef<number>(0);
|
|
const isDragging = useRef<boolean>(false);
|
|
|
|
// Close menu on route change
|
|
const pathname = usePathname();
|
|
useEffect(() => {
|
|
setMobileMenuOpen(false);
|
|
}, [pathname]);
|
|
|
|
// Prevent body scroll when menu is open
|
|
useEffect(() => {
|
|
if (mobileMenuOpen) {
|
|
document.body.style.overflow = 'hidden';
|
|
} else {
|
|
document.body.style.overflow = '';
|
|
}
|
|
return () => {
|
|
document.body.style.overflow = '';
|
|
};
|
|
}, [mobileMenuOpen]);
|
|
|
|
// Handle swipe to close
|
|
const handleTouchStart = useCallback((e: React.TouchEvent) => {
|
|
touchStartX.current = e.touches[0].clientX;
|
|
touchCurrentX.current = e.touches[0].clientX;
|
|
isDragging.current = true;
|
|
}, []);
|
|
|
|
const handleTouchMove = useCallback((e: React.TouchEvent) => {
|
|
if (!isDragging.current) return;
|
|
touchCurrentX.current = e.touches[0].clientX;
|
|
|
|
const deltaX = touchCurrentX.current - touchStartX.current;
|
|
// Only allow dragging to the right (to close)
|
|
if (deltaX > 0 && menuRef.current) {
|
|
menuRef.current.style.transform = `translateX(${deltaX}px)`;
|
|
}
|
|
}, []);
|
|
|
|
const handleTouchEnd = useCallback(() => {
|
|
if (!isDragging.current) return;
|
|
isDragging.current = false;
|
|
|
|
const deltaX = touchCurrentX.current - touchStartX.current;
|
|
const threshold = 100; // Minimum swipe distance to close
|
|
|
|
if (menuRef.current) {
|
|
menuRef.current.style.transform = '';
|
|
if (deltaX > threshold) {
|
|
setMobileMenuOpen(false);
|
|
}
|
|
}
|
|
}, []);
|
|
|
|
const closeMenu = useCallback(() => {
|
|
setMobileMenuOpen(false);
|
|
}, []);
|
|
|
|
const navLinks = [
|
|
{ href: '/', label: t('nav.home') },
|
|
{ href: '/events', label: t('nav.events') },
|
|
{ href: '/community', label: t('nav.community') },
|
|
{ href: '/contact', label: t('nav.contact') },
|
|
];
|
|
|
|
return (
|
|
<header className="sticky top-0 z-50 bg-white shadow-sm">
|
|
<nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div className="flex items-center justify-between h-16">
|
|
{/* Logo */}
|
|
<Link href="/" className="flex items-center">
|
|
<Image
|
|
src="/images/logo-spanglish.png"
|
|
alt="Spanglish"
|
|
width={140}
|
|
height={40}
|
|
className="h-10 w-auto"
|
|
priority
|
|
/>
|
|
</Link>
|
|
|
|
{/* Desktop Navigation */}
|
|
<div className="hidden md:flex items-center gap-6">
|
|
{navLinks.map((link) => (
|
|
<NavLink key={link.href} href={link.href}>
|
|
{link.label}
|
|
</NavLink>
|
|
))}
|
|
</div>
|
|
|
|
{/* Right side actions */}
|
|
<div className="hidden md:flex items-center gap-4">
|
|
<LanguageToggle />
|
|
|
|
{user ? (
|
|
<div className="flex items-center gap-3">
|
|
<Link href="/dashboard">
|
|
<Button variant="ghost" size="sm">
|
|
{t('nav.dashboard')}
|
|
</Button>
|
|
</Link>
|
|
{isAdmin && (
|
|
<Link href="/admin">
|
|
<Button variant="ghost" size="sm">
|
|
{t('nav.admin')}
|
|
</Button>
|
|
</Link>
|
|
)}
|
|
<span className="text-sm text-gray-600">{user.name}</span>
|
|
<Button variant="outline" size="sm" onClick={logout}>
|
|
{t('nav.logout')}
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
<div className="flex items-center gap-2">
|
|
<Link href="/login">
|
|
<Button variant="ghost" size="sm">
|
|
{t('nav.login')}
|
|
</Button>
|
|
</Link>
|
|
<Link href="/events">
|
|
<Button size="sm">
|
|
{t('nav.joinEvent')}
|
|
</Button>
|
|
</Link>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Mobile menu button (hamburger) */}
|
|
<button
|
|
className="md:hidden p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
|
onClick={() => setMobileMenuOpen(true)}
|
|
aria-label="Open menu"
|
|
>
|
|
<Bars3Icon className="w-6 h-6" style={{ color: '#002F44' }} />
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
|
|
{/* Mobile Slide-in Menu */}
|
|
{/* Overlay */}
|
|
<div
|
|
className={`
|
|
fixed inset-0 bg-black/50 z-40 md:hidden
|
|
transition-opacity duration-300 ease-in-out
|
|
${mobileMenuOpen ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}
|
|
`}
|
|
onClick={closeMenu}
|
|
aria-hidden="true"
|
|
/>
|
|
|
|
{/* Slide-in Panel */}
|
|
<div
|
|
ref={menuRef}
|
|
className={`
|
|
fixed top-0 right-0 h-full w-[280px] max-w-[85vw] bg-white z-50 md:hidden
|
|
shadow-xl transform transition-transform duration-300 ease-in-out
|
|
${mobileMenuOpen ? 'translate-x-0' : 'translate-x-full'}
|
|
`}
|
|
onTouchStart={handleTouchStart}
|
|
onTouchMove={handleTouchMove}
|
|
onTouchEnd={handleTouchEnd}
|
|
>
|
|
{/* Menu Header */}
|
|
<div className="flex items-center justify-between p-4 border-b border-gray-100">
|
|
<Image
|
|
src="/images/logo-spanglish.png"
|
|
alt="Spanglish"
|
|
width={100}
|
|
height={28}
|
|
className="h-7 w-auto"
|
|
/>
|
|
<button
|
|
className="p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
|
onClick={closeMenu}
|
|
aria-label="Close menu"
|
|
>
|
|
<XMarkIcon className="w-6 h-6" style={{ color: '#002F44' }} />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Menu Content */}
|
|
<div className="flex flex-col h-[calc(100%-65px)] overflow-y-auto">
|
|
{/* Navigation Links */}
|
|
<nav className="py-4">
|
|
{navLinks.map((link) => (
|
|
<MobileNavLink
|
|
key={link.href}
|
|
href={link.href}
|
|
onClick={closeMenu}
|
|
>
|
|
{link.label}
|
|
</MobileNavLink>
|
|
))}
|
|
</nav>
|
|
|
|
{/* Divider */}
|
|
<div className="border-t border-gray-100 mx-6" />
|
|
|
|
{/* Language Toggle */}
|
|
<div className="px-6 py-4">
|
|
<LanguageToggle variant="buttons" />
|
|
</div>
|
|
|
|
{/* Divider */}
|
|
<div className="border-t border-gray-100 mx-6" />
|
|
|
|
{/* Auth Actions */}
|
|
<div className="px-6 py-4 flex flex-col gap-3 mt-auto">
|
|
{user ? (
|
|
<>
|
|
<div className="text-sm text-gray-500 mb-2 flex items-center gap-2">
|
|
<div className="w-8 h-8 rounded-full bg-[#002F44] flex items-center justify-center text-white text-sm font-medium">
|
|
{user.name?.charAt(0).toUpperCase()}
|
|
</div>
|
|
<span className="font-medium text-[#002F44]">{user.name}</span>
|
|
</div>
|
|
<Link href="/dashboard" onClick={closeMenu}>
|
|
<Button variant="outline" className="w-full justify-center">
|
|
{t('nav.dashboard')}
|
|
</Button>
|
|
</Link>
|
|
{isAdmin && (
|
|
<Link href="/admin" onClick={closeMenu}>
|
|
<Button variant="outline" className="w-full justify-center">
|
|
{t('nav.admin')}
|
|
</Button>
|
|
</Link>
|
|
)}
|
|
<Button
|
|
variant="secondary"
|
|
onClick={() => {
|
|
logout();
|
|
closeMenu();
|
|
}}
|
|
className="w-full justify-center"
|
|
>
|
|
{t('nav.logout')}
|
|
</Button>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Link href="/login" onClick={closeMenu}>
|
|
<Button variant="outline" className="w-full justify-center">
|
|
{t('nav.login')}
|
|
</Button>
|
|
</Link>
|
|
<Link href="/events" onClick={closeMenu}>
|
|
<Button className="w-full justify-center">
|
|
{t('nav.joinEvent')}
|
|
</Button>
|
|
</Link>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{/* Swipe hint */}
|
|
<div className="px-6 pb-6 pt-2">
|
|
<p className="text-xs text-gray-400 text-center">
|
|
Swipe right to close
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|