| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001 |
- import React, { useState, useEffect, useRef } from "react";
- import { useNavigate, useLocation, Link } from "react-router-dom";
- import logo from "../assets/img/getgo.svg";
- import { useAppDispatch, useAppSelector } from "../hooks/useRedux";
- import { useMutation } from "@tanstack/react-query";
- import {
- startLoading,
- startSmallLoading,
- stopLoading,
- stopSmallLoading,
- } from "../features/loading/loadingSlice";
- import { productApi } from "../apis/productApi";
- import { Area } from "../services/product/type";
- import { accountLogout } from "../features/account/accuntSlice";
- import { useSelector } from "react-redux";
- import { setAreas } from "../features/areas/areasSlice";
- import i18n from "../i18n";
- import { useTranslation } from "react-i18next";
- import { getWithExpiry, setWithExpiry } from "../logic/loigicUtils";
- const Header: React.FC = () => {
- const navigate = useNavigate();
- // const areas = useSelector((state: any) => state.areas.areas);
- const areas = getWithExpiry<Area[] | []>("areas");
- const { t } = useTranslation();
- const location = useLocation();
- const [isMenuOpen, setIsMenuOpen] = useState(false);
- const [isBuySimExpanded, setIsBuySimExpanded] = useState(false);
- const [isGuideExpanded, setIsGuideExpanded] = useState(false);
- const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
- const [isBuySimMegaVisible, setIsBuySimMegaVisible] = useState(false);
- const [isGuideMegaVisible, setIsGuideMegaVisible] = useState(false);
- const [isLangMenuOpen, setIsLangMenuOpen] = useState(false);
- const lang = localStorage.getItem("lang") || "en";
- const [selectedLang, setSelectedLang] = useState<"en" | "vi">(lang);
- const [activeDesktopTab, setActiveDesktopTab] = useState<
- "popular" | "region"
- >("popular");
- const [isScrolled, setIsScrolled] = useState(false);
- const [products, setProducts] = useState<Area[]>([]);
- const dispatch = useAppDispatch();
- const langMenuRef = useRef<HTMLDivElement>(null);
- const userMenuRef = useRef<HTMLDivElement>(null);
- const account = localStorage.getItem("accountInfo");
- const guideItems = [
- { label: t("whatEsim"), path: "/support" },
- { label: t("installationInstructions"), path: "/support" },
- { label: t("supportSupport"), path: "/support" },
- { label: t("orderSearch"), path: "/support" },
- ];
- const dropdownRef = useRef<HTMLDivElement>(null);
- const [areasList, setAreasList] = useState<Area[]>([]);
- const [searchQuery, setSearchQuery] = useState("");
- const [isSearchDropdownOpen, setIsSearchDropdownOpen] = useState(false);
- const languages = [
- { code: "en", label: "English", flag: "us" },
- { code: "vi", label: "Tiếng Việt", flag: "vn" },
- ];
- // load product by country/region or popularity
- const getProductMutation = useMutation({
- mutationFn: async () => {
- dispatch(startLoading({}));
- const res = await productApi.loadArea(
- activeDesktopTab === "popular"
- ? { isCountry: "-1", isPopular: "1" }
- : { isCountry: "0", isPopular: "-1" }
- );
- return res;
- },
- onSuccess: (data) => {
- dispatch(stopLoading());
- if (data && data.errorCode === "0") {
- setProducts(data.data);
- } else {
- console.error("Get area failed, no token received");
- }
- },
- onError: (error: any) => {
- dispatch(stopLoading());
- console.error("Get area error:", error.response.data);
- },
- });
- useEffect(() => {
- const handleScroll = () => {
- setIsScrolled(window.scrollY > 300);
- };
- window.addEventListener("scroll", handleScroll);
- return () => window.removeEventListener("scroll", handleScroll);
- }, []);
- const handleAreaClick = (c: { id: number }) => {
- navigate(`/product/${c.id}`, {
- state: {
- ...c,
- },
- });
- 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;
- useEffect(() => {
- getProductMutation.mutate();
- }, [activeDesktopTab]);
- useEffect(() => {
- if (!areas || areas.length === 0) getAreaMutation.mutate();
- else {
- setAreasList(areas);
- console.log("Areas loaded from store:", areas);
- }
- }, []);
- const getAreaMutation = useMutation({
- mutationFn: async () => {
- dispatch(startLoading({}));
- const res = await productApi.loadArea({
- isCountry: "-1",
- isPopular: "-1",
- });
- return res;
- },
- onSuccess: (data) => {
- dispatch(stopLoading());
- console.log("Get area response data:", data);
- if (data && data.errorCode === "0") {
- console.log("Get area successful");
- setWithExpiry("areas", JSON.stringify(data.data));
- setAreasList(data.data as Area[]);
- } else {
- console.error("Get area failed, no token received");
- }
- },
- onError: (error: any) => {
- dispatch(stopLoading());
- console.error("Get area error:", error.response.data);
- },
- });
- const handleSelect = (area: Area) => {
- console.log("Selected area:", area);
- setIsSearchDropdownOpen(false);
- navigate(`/product/${area.id}`, {
- state: {
- ...area,
- },
- });
- };
- const handleSearch = (query: string) => {
- setSearchQuery(query);
- if (query.trim() === "") {
- setAreasList(areas);
- } else {
- const filtered = areas.filter((area: Area) =>
- area.coverageArea.toLowerCase().includes(query.toLowerCase())
- );
- setAreasList(filtered);
- }
- };
- useEffect(() => {
- const handleClickOutside = (event: MouseEvent) => {
- if (
- dropdownRef.current &&
- !dropdownRef.current.contains(event.target as Node)
- ) {
- setIsSearchDropdownOpen(false);
- }
- };
- document.addEventListener("mousedown", handleClickOutside);
- return () => {
- document.removeEventListener("mousedown", handleClickOutside);
- };
- }, []);
- 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> */}
- <img src={logo} alt="Getgo Logo" className="w-35" />
- {/* <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
- ref={dropdownRef}
- className={`hidden lg:flex items-center transition-all duration-500 ${
- isScrolled
- ? "flex-1 max-w-md mx-8 opacity-100 overflow-visible"
- : "max-w-0 opacity-0 pointer-events-none overflow-hidden"
- }`}
- >
- <div className="relative w-full">
- <input
- type="text"
- value={searchQuery}
- onChange={(e) => {
- handleSearch(e.target.value);
- }}
- onFocus={() => setIsSearchDropdownOpen(true)}
- placeholder={t("searchCountry")}
- 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>
- {/* Dropdown Menu */}
- {isSearchDropdownOpen && (
- <div className="absolute top-full left-0 right-0 mt-2 bg-white rounded-2xl shadow-[0_10px_40px_-10px_rgba(0,0,0,0.1)] border border-slate-100 overflow-hidden animate-in fade-in zoom-in-95 duration-200">
- <div className="max-h-[300px] overflow-y-auto custom-scrollbar p-2">
- <h3 className="text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-2 px-3 pt-2">
- {t("mostPopular")}
- </h3>
- <div className="space-y-0.5">
- {areasList.length > 0 ? (
- areasList.map((p) => (
- <button
- key={p.id}
- onClick={() => handleSelect(p)}
- className="w-full flex items-center justify-between p-2 hover:bg-slate-50 rounded-xl transition-all group"
- >
- <div className="flex items-center space-x-3">
- <div className="w-8 h-8 rounded-full border border-slate-100 overflow-hidden shadow-sm shrink-0">
- <img
- src={`${p.iconUrl}`}
- alt={p.areaName1}
- className="w-full h-full object-cover scale-150"
- />
- </div>
- <span className="font-bold text-slate-700 text-sm group-hover:text-[#EE0434] transition-colors">
- {p.areaName1}
- </span>
- </div>
- <div className="text-right flex items-center space-x-1.5">
- <span className="text-xs text-slate-400 font-medium">
- {t("from")}:
- </span>
- <span className="text-sm font-black text-[#EE0434]">
- {p.minSellPrice.toLocaleString()} {p.curency}
- </span>
- </div>
- </button>
- ))
- ) : (
- <div className="text-center py-4 text-slate-400 text-sm font-medium">
- {t("noMatchesFound")}
- </div>
- )}
- </div>
- </div>
- </div>
- )}
- </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]"
- }`}
- >
- {t("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]"
- }`}
- >
- {t("buySim")}{" "}
- <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">
- {t("buySim")}
- </h3>
- <button
- onClick={() => {
- navigate("/buy-sim");
- setIsBuySimMegaVisible(false);
- }}
- className="text-[#EE0434] font-bold text-xl flex items-center group mb-8"
- >
- {t("viewAll")}{" "}
- <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"
- }`}
- >
- {t("mostPopular")}
- </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"
- }`}
- >
- {t("region")}
- </button>
- </div>
- <div className="grid grid-cols-4 gap-y-8 gap-x-4">
- {products.map((p) => (
- <div
- key={p.id}
- onClick={() => handleAreaClick(p)}
- 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={`${p.iconUrl}`}
- alt={p.areaName1}
- className="w-full h-full object-cover"
- />
- </div>
- <span className="text-[16px] font-bold text-slate-700 group-hover:text-[#EE0434] transition-colors">
- {p.areaName1}
- </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]"
- }`}
- >
- {t("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]"
- }`}
- >
- {t("news")}
- </Link>
- <Link
- to="/contact"
- className={`text-[17px] font-bold transition-colors ${
- isActive("/contact")
- ? "text-[#EE0434]"
- : "text-slate-700 hover:text-[#EE0434]"
- }`}
- >
- {t("contact")}
- </Link>
- </nav>
- {/* Icons */}
- <div className="flex items-center space-x-5">
- {/* Account Dropdown */}
- <div className="relative hidden lg:block" ref={userMenuRef}>
- <button
- onClick={() => setIsUserMenuOpen(!isUserMenuOpen)}
- className="p-2 text-slate-700 hover:text-[#EE0434] transition-colors rounded-full hover:bg-slate-50"
- title="Account"
- >
- <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>
- </button>
- {isUserMenuOpen && (
- <div className="absolute top-full right-0 mt-3 w-56 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 z-50">
- {account !== null && (
- <div className="flex flex-col py-2">
- <button
- onClick={() => {
- navigate("/order-history");
- setIsUserMenuOpen(false);
- }}
- className="flex items-center space-x-3 px-6 py-3.5 w-full text-left hover:bg-slate-50 text-slate-700 hover:text-[#EE0434] transition-colors"
- >
- <svg
- className="w-5 h-5 text-slate-400"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
- />
- </svg>
- <span className="font-bold text-sm">
- {t("orders")}
- </span>
- </button>
- <div className="h-px bg-slate-50 mx-4 my-1"></div>
- <button
- onClick={() => {
- setIsUserMenuOpen(false);
- dispatch(accountLogout());
- navigate("/login");
- }}
- className="flex items-center space-x-3 px-6 py-3.5 w-full text-left hover:bg-slate-50 text-slate-700 hover:text-[#EE0434] transition-colors"
- >
- <svg
- className="w-5 h-5 text-slate-400"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
- />
- </svg>
- <span className="font-bold text-sm">
- {t("logout")}
- </span>
- </button>
- </div>
- )}
- {account === null && (
- <div className="flex flex-col py-2">
- <button
- onClick={() => {
- setIsUserMenuOpen(false);
- dispatch(accountLogout());
- navigate("/login");
- }}
- className="flex items-center space-x-3 px-6 py-3.5 w-full text-left hover:bg-slate-50 text-slate-700 hover:text-[#EE0434] transition-colors"
- >
- <svg
- className="w-5 h-5 text-slate-400"
- fill="none"
- stroke="currentColor"
- viewBox="0 0 24 24"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- strokeWidth={2}
- d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"
- />
- </svg>
- <span className="font-bold text-sm">
- {t("login")}
- </span>
- </button>
- </div>
- )}
- </div>
- )}
- </div>
- {/* <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);
- i18n.changeLanguage(lang.code);
- localStorage.setItem("lang", lang.code);
- }}
- 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]">Get</span>
- <span className="text-[#333]">Go</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"
- }`}
- >
- {t("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>{t("buySim")}</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"
- >
- {t("viewAllDestinations")} →
- </button>
- {products.map((c) => (
- <button
- key={c.areaName1}
- onClick={() => handleAreaClick(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={`${c.iconUrl}`}
- alt={c.areaName1}
- className="w-10 h-10 rounded-full object-cover border-2 border-slate-50"
- />
- <span className="text-sm font-bold text-slate-700">
- {c.areaName1}
- </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>{t("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"
- }`}
- >
- {t("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"
- }`}
- >
- {t("contact")}
- </Link>
- {account !== null && (
- <button
- onClick={() => {
- navigate("/order-history");
- setIsMenuOpen(false);
- }}
- className={`w-full text-center py-5 px-6 rounded-3xl text-2xl font-black transition-all ${
- isActive("/order-history")
- ? "bg-red-50 text-[#EE0434]"
- : "text-slate-800 hover:bg-slate-50"
- }`}
- >
- {t("transactionHistory")}
- </button>
- )}
- <div className="w-full pt-4">
- <p className="text-center text-slate-400 font-bold text-xs uppercase tracking-widest mb-4">
- {t("selectLanguage")}
- </p>
- <div className="flex justify-center space-x-4">
- {languages.map((lang) => (
- <button
- key={lang.code}
- onClick={() => {
- setSelectedLang(lang.code as "en" | "vi");
- setIsLangMenuOpen(false);
- i18n.changeLanguage(lang.code);
- localStorage.setItem("lang", lang.code);
- }}
- 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">
- {account === null && (
- <Link
- to="/login"
- onClick={() => {
- setIsMenuOpen(false);
- dispatch(accountLogout());
- }}
- 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"
- >
- {t("login")} / {t("register")}
- </Link>
- )}
- {account !== null && (
- <Link
- to="/login"
- onClick={() => {
- setIsMenuOpen(false);
- dispatch(accountLogout());
- }}
- 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"
- >
- {t("logout")}
- </Link>
- )}
- </div>
- </div>
- </div>
- </>
- );
- };
- export default Header;
|