import PropTypes from "prop-types"; import React, { useState, useContext, useEffect, useRef } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate, Link } from "react-router-dom"; import { SysContext } from "components/SysSettingProvider"; import { checkIsOnlyOnlinePayment } from "../../../utils/Utils"; // material-ui import { AppBar, Typography, Box, Stack, Toolbar, Divider, IconButton, Drawer, Grid, } from "@mui/material"; import MenuIcon from "@mui/icons-material/Menu"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; // project import import Logo from "components/Logo"; import AdminLogo from "components/AdminLogo"; import MobileLogo from "components/MobileLogo"; import "assets/style/navbarStyles.css"; import { isUserLoggedIn, isGLDLoggedIn, isPrimaryLoggedIn, isCreditorLoggedIn, isINDLoggedIn, isPasswordExpiry, haveOrgPaymentRecord, haveOrgDnRecord, isORGLoggedIn, checkSysEnv, } from "utils/Utils"; import { handleLogoutFunction } from "auth/index"; import { isGranted, isGrantedAny } from "auth/utils"; import LocaleSelector from "./HeaderContent/LocaleSelector"; import { FormattedMessage, useIntl } from "react-intl"; const drawerWidth = 300; const isAfterAboutSwitchDate = checkIsOnlyOnlinePayment; const getAboutExternalUrlByLocale = () => { const locale = (localStorage.getItem("locale") || "").toLowerCase(); if (locale === "zh-hk") return "https://www.gld.gov.hk/zh-hk/our-services/printing/advertising-gov-gazette/"; if (locale === "zh-cn") return "https://www.gld.gov.hk/zh-cn/our-services/printing/advertising-gov-gazette/"; return "https://www.gld.gov.hk/en/our-services/printing/advertising-gov-gazette/"; }; /** * Accessible dropdown trigger: * - button is always focusable * - opens on focus (keyboard tab) via CSS :focus-within + optional state * - supports Enter/Space/ArrowDown to jump to first submenu item * - supports Escape to close + return focus */ function Header(props) { const { sysSetting } = useContext(SysContext); const { window } = props; /** Subscribe to auth changes (LOGIN/LOGOUT/cross-tab storage); Header reads localStorage and must re-render. */ useSelector((state) => state.authRevision); const [mobileOpen, setMobileOpen] = useState(false); const dispatch = useDispatch(); const navigate = useNavigate(); const intl = useIntl(); // which dropdown is open (optional but useful for aria-expanded + closing) const [openMenu, setOpenMenu] = useState({ payment: false, client: false, report: false, settings: false, paymentHistory: false, userSetting: false, }); const rootNavRef = useRef(null); const handleDrawerToggle = () => setMobileOpen((prev) => !prev); const handleLogout = async () => { await dispatch(handleLogoutFunction()); await navigate("/login"); }; const openKey = (key) => setOpenMenu((p) => ({ ...p, [key]: true })); const closeKey = (key) => setOpenMenu((p) => ({ ...p, [key]: false })); const closeAll = () => setOpenMenu({ payment: false, client: false, report: false, settings: false, paymentHistory: false, userSetting: false, }); // close dropdowns on outside click / focus useEffect(() => { const onDocMouseDown = (e) => { if (!rootNavRef.current) return; if (!rootNavRef.current.contains(e.target)) closeAll(); }; document.addEventListener("mousedown", onDocMouseDown, true); return () => document.removeEventListener("mousedown", onDocMouseDown, true); }, []); const onMenuKeyDown = (key) => (e) => { if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") { e.preventDefault(); openKey(key); requestAnimationFrame(() => { const first = document.querySelector( `[data-submenu="${key}"] a, [data-submenu="${key}"] button` ); first?.focus?.(); }); } else if (e.key === "Escape") { e.preventDefault(); closeKey(key); e.currentTarget?.focus?.(); } else if (e.key === "ArrowUp") { // optional: close on ArrowUp when on trigger closeKey(key); } }; const onTriggerBlur = (key) => (e) => { // close only when focus leaves the whole LI (trigger + submenu) const li = e.currentTarget.closest("li"); if (li && li.contains(e.relatedTarget)) return; closeKey(key); }; // ============================= // Desktop top nav content (login) // ============================= const loginContent = isGLDLoggedIn() ? (