| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- import React, { useState, useEffect, useRef } from 'react';
- import { useNavigate, useLocation, Link } from 'react-router-dom';
- const Header: React.FC = () => {
- const navigate = useNavigate();
- const location = useLocation();
- const [isMenuOpen, setIsMenuOpen] = useState(false);
- const [isBuySimExpanded, setIsBuySimExpanded] = useState(false);
- const [isGuideExpanded, setIsGuideExpanded] = useState(false);
- const [isBuySimMegaVisible, setIsBuySimMegaVisible] = useState(false);
- const [isGuideMegaVisible, setIsGuideMegaVisible] = useState(false);
- const [isLangMenuOpen, setIsLangMenuOpen] = useState(false);
- const [selectedLang, setSelectedLang] = useState<'en' | 'vi'>('en');
- const [activeDesktopTab, setActiveDesktopTab] = useState<'popular' | 'region'>('popular');
- const [isScrolled, setIsScrolled] = useState(false);
-
- const langMenuRef = useRef<HTMLDivElement>(null);
- const countries = [
- { name: 'China', flag: 'cn' },
- { name: 'Hong Kong', flag: 'hk' },
- { name: 'Japan', flag: 'jp' },
- { name: 'Singapore', flag: 'sg' },
- { name: 'South Korea', flag: 'kr' },
- { name: 'Taiwan', flag: 'tw' },
- { name: 'Thailand', flag: 'th' },
- { name: 'United States', flag: 'us' },
- ];
- const regionList = [
- "Americas", "Asia", "Asia 11 countries", "Asialink 7 countries",
- "Australia & New Zealand", "EU 33 countries", "EU 40 countries", "Eurolink",
- "Europe", "Europe 33 countries", "Hong Kong & Macau", "Middle East and Africa",
- "North America & Canada", "North America A", "Oceania", "Singapore & Malaysia"
- ];
- const guideItems = [
- { label: "What is eSIM", path: "/support" },
- { label: "Installation instructions", path: "/support" },
- { label: "Support", path: "/support" },
- { label: "Order Tracking Search", path: "/support" }
- ];
- const languages = [
- { code: 'en', label: 'English', flag: 'us' },
- { code: 'vi', label: 'Tiếng Việt', flag: 'vn' }
- ];
- useEffect(() => {
- const handleScroll = () => {
- setIsScrolled(window.scrollY > 300);
- };
- window.addEventListener('scroll', handleScroll);
- return () => window.removeEventListener('scroll', handleScroll);
- }, []);
- const handleCountryClick = (c: { name: string; flag: string }) => {
- navigate(`/product/${c.name.toLowerCase()}`, { state: { country: c.name, flag: c.flag } });
- setIsBuySimMegaVisible(false);
- setIsMenuOpen(false);
- };
- const handleRegionClick = (region: string) => {
- navigate(`/product/${region.toLowerCase()}`, { state: { country: region, flag: 'un' } });
- setIsBuySimMegaVisible(false);
- setIsMenuOpen(false);
- };
- useEffect(() => {
- const handleClickOutside = (event: MouseEvent) => {
- if (langMenuRef.current && !langMenuRef.current.contains(event.target as Node)) {
- setIsLangMenuOpen(false);
- }
- };
- document.addEventListener('mousedown', handleClickOutside);
- return () => document.removeEventListener('mousedown', handleClickOutside);
- }, []);
- useEffect(() => {
- const handleResize = () => {
- if (window.innerWidth >= 1024) setIsMenuOpen(false);
- };
- window.addEventListener('resize', handleResize);
- return () => window.removeEventListener('resize', handleResize);
- }, []);
- const currentLangObj = languages.find(l => l.code === selectedLang) || languages[0];
- const isActive = (path: string) => location.pathname === path;
- return (
- <>
- <header className="sticky top-0 z-[60] w-full bg-white border-b border-slate-100 shadow-sm transition-all duration-300">
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
- <div className="flex justify-between items-center h-20">
- {/* Logo */}
- <Link to="/" className="flex-shrink-0 flex items-center">
- <div className="flex items-center space-x-1">
- <svg className="w-8 h-8 text-[#EE0434]" viewBox="0 0 24 24" fill="currentColor">
- <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
- </svg>
- <span className="text-2xl font-bold tracking-tighter">
- <span className="text-[#EE0434]">Infi</span>
- <span className="text-[#333]">Gate</span>
- </span>
- </div>
- </Link>
- {/* Desktop Search on Scroll */}
- <div className={`hidden lg:flex items-center transition-all duration-500 overflow-hidden ${isScrolled ? 'flex-1 max-w-md mx-8 opacity-100' : 'max-w-0 opacity-0 pointer-events-none'}`}>
- <div className="relative w-full">
- <input
- type="text"
- placeholder="Search country..."
- className="w-full bg-slate-50 border border-slate-200 rounded-full py-2.5 px-6 pl-12 text-sm focus:outline-none focus:ring-2 focus:ring-red-100 focus:border-[#EE0434] transition-all"
- />
- <svg className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/></svg>
- </div>
- </div>
- {/* Desktop Nav */}
- <nav className={`hidden lg:flex items-center h-full transition-all duration-300 ${isScrolled ? 'space-x-4' : 'space-x-8'}`}>
- <Link to="/" className={`text-[17px] font-bold ${isActive('/') ? 'text-[#EE0434]' : 'text-slate-700 hover:text-[#EE0434]'}`}>Home</Link>
-
- <div
- className="relative h-full flex items-center"
- onMouseEnter={() => setIsBuySimMegaVisible(true)}
- onMouseLeave={() => setIsBuySimMegaVisible(false)}
- >
- <Link
- to="/buy-sim"
- className={`flex items-center text-[17px] font-bold transition-colors ${isActive('/buy-sim') ? 'text-[#EE0434]' : 'text-slate-700 hover:text-[#EE0434]'}`}
- >
- Buy SIM <svg className={`ml-1 w-4 h-4 transition-transform ${isBuySimMegaVisible ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
- </Link>
- {isBuySimMegaVisible && (
- <div className="absolute top-full left-1/2 -translate-x-1/2 w-[950px] bg-white rounded-[32px] shadow-[0_30px_60px_-15px_rgba(0,0,0,0.15)] border border-slate-100 mt-0 overflow-hidden flex animate-in fade-in slide-in-from-top-2 duration-300">
- <div className="w-[280px] bg-red-50 p-10 flex flex-col">
- <h3 className="text-4xl font-black text-slate-900 mb-4">Buy SIM</h3>
- <button
- onClick={() => {
- navigate('/buy-sim');
- setIsBuySimMegaVisible(false);
- }}
- className="text-[#EE0434] font-bold text-xl flex items-center group mb-8"
- >
- View all <svg className="ml-2 w-5 h-5 transition-transform group-hover:translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M9 5l7 7-7 7" /></svg>
- </button>
- </div>
- <div className="flex-1 p-10">
- <div className="flex space-x-4 mb-10">
- <button
- onClick={() => setActiveDesktopTab('popular')}
- className={`px-8 py-3 rounded-full text-base font-bold transition-all ${activeDesktopTab === 'popular' ? 'bg-[#EE0434] text-white shadow-lg shadow-red-100' : 'bg-slate-50 text-slate-900 hover:bg-slate-100'}`}
- >
- Most Popular
- </button>
- <button
- onClick={() => setActiveDesktopTab('region')}
- className={`px-8 py-3 rounded-full text-base font-bold transition-all ${activeDesktopTab === 'region' ? 'bg-[#EE0434] text-white shadow-lg shadow-red-100' : 'bg-slate-50 text-slate-900 hover:bg-slate-100'}`}
- >
- Region
- </button>
- </div>
- {activeDesktopTab === 'popular' ? (
- <div className="grid grid-cols-4 gap-y-8 gap-x-4">
- {countries.map((c) => (
- <div key={c.name} onClick={() => handleCountryClick(c)} className="flex items-center space-x-3 group cursor-pointer hover:bg-slate-50 p-2 rounded-xl transition-colors">
- <div className="w-7 h-7 rounded-full overflow-hidden border border-slate-200 shadow-sm shrink-0">
- <img src={`https://flagcdn.com/w40/${c.flag}.png`} alt={c.name} className="w-full h-full object-cover" />
- </div>
- <span className="text-[16px] font-bold text-slate-700 group-hover:text-[#EE0434] transition-colors">{c.name}</span>
- </div>
- ))}
- </div>
- ) : (
- <div className="grid grid-cols-4 gap-y-6 gap-x-2">
- {regionList.map((region) => (
- <div
- key={region}
- onClick={() => handleRegionClick(region)}
- className="flex items-center space-x-3 group cursor-pointer hover:bg-slate-50 p-2 rounded-xl transition-colors min-w-0"
- >
- <div className="w-7 h-7 rounded-full bg-red-50 flex items-center justify-center shrink-0 border border-red-100/50">
- <svg className="w-4 h-4 text-red-300" fill="currentColor" viewBox="0 0 24 24">
- <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7.01-3.55-7.5-7.5H7v-1H3.5c.49-3.95 3.55-7.01 7.5-7.5v3.5h1V3.5c3.95.49 7.01 3.55 7.5 7.5H17v1h3.5c-.49 3.95-3.55-7.01-7.5 7.5v-3.5h-1v3.5z"/>
- </svg>
- </div>
- <span className="text-[16px] font-bold text-slate-700 group-hover:text-[#EE0434] transition-colors truncate">
- {region}
- </span>
- </div>
- ))}
- </div>
- )}
- </div>
- </div>
- )}
- </div>
- <div
- className="relative h-full flex items-center"
- onMouseEnter={() => setIsGuideMegaVisible(true)}
- onMouseLeave={() => setIsGuideMegaVisible(false)}
- >
- <Link
- to="/support"
- className={`flex items-center text-[17px] font-bold transition-colors ${isActive('/support') ? 'text-[#EE0434]' : 'text-slate-700 hover:text-[#EE0434]'}`}
- >
- Guide <svg className={`ml-1 w-4 h-4 transition-transform ${isGuideMegaVisible ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
- </Link>
- {isGuideMegaVisible && (
- <div className="absolute top-full left-0 w-[600px] bg-white rounded-[32px] shadow-2xl border border-slate-100 mt-0 overflow-hidden flex animate-in fade-in slide-in-from-top-2 duration-300">
- <div className="flex-1 py-10 px-6 flex flex-col">
- {guideItems.map((item, idx) => (
- <button
- key={item.label}
- onClick={() => {
- navigate(item.path);
- setIsGuideMegaVisible(false);
- }}
- className={`w-full text-left px-6 py-4 rounded-2xl text-base font-medium transition-all text-slate-600 hover:bg-slate-50 hover:text-[#EE0434]`}
- >
- {item.label}
- </button>
- ))}
- </div>
- </div>
- )}
- </div>
- <Link
- to="/news"
- className={`text-[17px] font-bold transition-colors ${isActive('/news') ? 'text-[#EE0434]' : 'text-slate-700 hover:text-[#EE0434]'}`}
- >
- News
- </Link>
-
- <Link
- to="/contact"
- className={`text-[17px] font-bold transition-colors ${isActive('/contact') ? 'text-[#EE0434]' : 'text-slate-700 hover:text-[#EE0434]'}`}
- >
- Contact
- </Link>
- </nav>
- {/* Icons */}
- <div className="flex items-center space-x-5">
- <Link
- to="/login"
- className="p-2 text-slate-700 hover:text-[#EE0434] transition-colors"
- >
- <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
- </Link>
- <button className="hidden sm:flex p-2 text-slate-700 hover:text-[#EE0434] relative">
- <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
- <span className="absolute top-1 right-1 w-4 h-4 bg-[#EE0434] text-white text-[10px] flex items-center justify-center rounded-full font-black">0</span>
- </button>
- <div className="relative" ref={langMenuRef}>
- <button
- onClick={() => setIsLangMenuOpen(!isLangMenuOpen)}
- className="bg-white p-1 rounded-2xl border border-slate-100 shadow-sm hover:shadow-md transition-all flex items-center justify-center"
- >
- <div className="w-8 h-8 rounded-full overflow-hidden border border-slate-50">
- <img
- src={`https://flagcdn.com/w80/${currentLangObj.flag}.png`}
- alt={currentLangObj.label}
- className="w-full h-full object-cover"
- />
- </div>
- </button>
- {isLangMenuOpen && (
- <div className="absolute top-full right-0 mt-3 w-48 bg-white rounded-[24px] shadow-[0_20px_40px_rgba(0,0,0,0.1)] border border-slate-50 overflow-hidden animate-in fade-in slide-in-from-top-2 duration-200">
- <div className="flex flex-col">
- {languages.map((lang) => (
- <button
- key={lang.code}
- onClick={() => {
- setSelectedLang(lang.code as 'en' | 'vi');
- setIsLangMenuOpen(false);
- }}
- className={`flex items-center space-x-3 px-5 py-4 w-full text-left transition-colors ${
- selectedLang === lang.code ? 'bg-slate-50' : 'hover:bg-slate-50/50'
- }`}
- >
- <div className="w-7 h-7 rounded-full overflow-hidden border border-slate-100 shadow-sm">
- <img
- src={`https://flagcdn.com/w80/${lang.flag}.png`}
- alt={lang.label}
- className="w-full h-full object-cover"
- />
- </div>
- <span className={`text-[15px] font-bold ${selectedLang === lang.code ? 'text-slate-900' : 'text-slate-500'}`}>
- {lang.label}
- </span>
- </button>
- ))}
- </div>
- </div>
- )}
- </div>
- <button onClick={() => setIsMenuOpen(true)} className="lg:hidden p-2 text-slate-900">
- <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M4 6h16M4 12h16m-7 6h7" /></svg>
- </button>
- </div>
- </div>
- </div>
- </header>
- {/* Full-Screen Mobile Menu with Slide-Right Transition */}
- <div className={`fixed inset-0 z-[100] lg:hidden transition-all duration-500 ease-in-out ${isMenuOpen ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}`}>
- <div className={`absolute inset-0 bg-white flex flex-col transform transition-transform duration-500 ease-out ${isMenuOpen ? 'translate-x-0' : '-translate-x-full'}`}>
- {/* Mobile Menu Header */}
- <div className="flex justify-between items-center p-6 border-b border-slate-50">
- <Link to="/" onClick={() => setIsMenuOpen(false)} className="flex items-center space-x-1">
- <svg className="w-7 h-7 text-[#EE0434]" viewBox="0 0 24 24" fill="currentColor">
- <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
- </svg>
- <span className="text-xl font-black tracking-tighter">
- <span className="text-[#EE0434]">Infi</span>
- <span className="text-[#333]">Gate</span>
- </span>
- </Link>
- <button onClick={() => setIsMenuOpen(false)} className="p-2 text-slate-400 hover:text-[#EE0434] transition-colors rounded-full hover:bg-slate-100">
- <svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg>
- </button>
- </div>
- <div className="flex-1 overflow-y-auto px-6 py-8 space-y-6">
- <div className="flex flex-col items-center space-y-4">
- <Link
- to="/"
- onClick={() => setIsMenuOpen(false)}
- className={`w-full text-center py-5 px-6 rounded-3xl text-2xl font-black transition-all ${isActive('/') ? 'bg-red-50 text-[#EE0434]' : 'text-slate-800 hover:bg-slate-50'}`}
- >
- Home
- </Link>
-
- <div className="w-full">
- <button
- onClick={() => setIsBuySimExpanded(!isBuySimExpanded)}
- className={`w-full flex items-center justify-center space-x-3 py-5 px-6 rounded-3xl text-2xl font-black transition-all ${isActive('/buy-sim') ? 'bg-red-50 text-[#EE0434]' : 'text-slate-800 hover:bg-slate-50'}`}
- >
- <span>Buy SIM</span>
- <svg className={`w-6 h-6 transition-transform duration-300 ${isBuySimExpanded ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M19 9l-7 7-7-7" /></svg>
- </button>
- <div className={`overflow-hidden transition-all duration-300 ease-in-out ${isBuySimExpanded ? 'max-h-[1000px] opacity-100 mt-4' : 'max-h-0 opacity-0'}`}>
- <div className="grid grid-cols-2 gap-3 px-2">
- <button
- onClick={() => { navigate('/buy-sim'); setIsMenuOpen(false); }}
- className="col-span-2 text-center py-4 bg-slate-50 rounded-2xl text-[#EE0434] font-black text-sm uppercase tracking-wider shadow-sm"
- >
- View All Destinations →
- </button>
- {countries.map(c => (
- <button
- key={c.name}
- onClick={() => handleCountryClick(c)}
- className="flex flex-col items-center justify-center space-y-2 py-5 rounded-2xl bg-white border border-slate-100 shadow-sm active:bg-slate-50"
- >
- <img src={`https://flagcdn.com/w80/${c.flag}.png`} alt={c.name} className="w-10 h-10 rounded-full object-cover border-2 border-slate-50" />
- <span className="text-sm font-bold text-slate-700">{c.name}</span>
- </button>
- ))}
- </div>
- </div>
- </div>
- <div className="w-full">
- <button
- onClick={() => setIsGuideExpanded(!isGuideExpanded)}
- className={`w-full flex items-center justify-center space-x-3 py-5 px-6 rounded-3xl text-2xl font-black transition-all ${isActive('/support') ? 'bg-red-50 text-[#EE0434]' : 'text-slate-800 hover:bg-slate-50'}`}
- >
- <span>Guide</span>
- <svg className={`w-6 h-6 transition-transform duration-300 ${isGuideExpanded ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M19 9l-7 7-7-7" /></svg>
- </button>
- <div className={`overflow-hidden transition-all duration-300 ease-in-out ${isGuideExpanded ? 'max-h-[400px] opacity-100 mt-4' : 'max-h-0 opacity-0'}`}>
- <div className="flex flex-col space-y-2 px-2">
- {guideItems.map(item => (
- <button
- key={item.label}
- onClick={() => { navigate(item.path); setIsMenuOpen(false); }}
- className="w-full text-center py-4 rounded-2xl bg-slate-50 text-slate-600 font-bold hover:text-[#EE0434] active:bg-red-50"
- >
- {item.label}
- </button>
- ))}
- </div>
- </div>
- </div>
- <Link
- to="/news"
- onClick={() => setIsMenuOpen(false)}
- className={`w-full text-center py-5 px-6 rounded-3xl text-2xl font-black transition-all ${isActive('/news') ? 'bg-red-50 text-[#EE0434]' : 'text-slate-800 hover:bg-slate-50'}`}
- >
- News
- </Link>
-
- <Link
- to="/contact"
- onClick={() => setIsMenuOpen(false)}
- className={`w-full text-center py-5 px-6 rounded-3xl text-2xl font-black transition-all ${isActive('/contact') ? 'bg-red-50 text-[#EE0434]' : 'text-slate-800 hover:bg-slate-50'}`}
- >
- Contact
- </Link>
- <div className="w-full pt-4">
- <p className="text-center text-slate-400 font-bold text-xs uppercase tracking-widest mb-4">Select Language</p>
- <div className="flex justify-center space-x-4">
- {languages.map(lang => (
- <button
- key={lang.code}
- onClick={() => setSelectedLang(lang.code as 'en' | 'vi')}
- className={`flex items-center space-x-2 px-4 py-2 rounded-2xl transition-all border ${selectedLang === lang.code ? 'bg-red-50 border-[#EE0434] text-[#EE0434]' : 'bg-white border-slate-100 text-slate-500'}`}
- >
- <img src={`https://flagcdn.com/w40/${lang.flag}.png`} alt={lang.label} className="w-6 h-6 rounded-full object-cover border border-slate-100" />
- <span className="font-bold">{lang.label}</span>
- </button>
- ))}
- </div>
- </div>
- </div>
- </div>
- <div className="p-8 border-t border-slate-50 bg-slate-50/50">
- <Link
- to="/login"
- onClick={() => setIsMenuOpen(false)}
- className="w-full bg-gradient-to-r from-[#E21c34] to-[#500B28] text-white py-5 rounded-[40px] font-black text-2xl shadow-xl active:scale-[0.98] transition-all flex justify-center"
- >
- Login / Register
- </Link>
- </div>
- </div>
- </div>
- </>
- );
- };
- export default Header;
|