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() ? (
{isPasswordExpiry() ? (
  • ) : (
  • Dashboard
  • {isGrantedAny(["VIEW_APPLICATION", "MAINTAIN_APPLICATION"]) ? (
  • Application
  • ) : null} {isGrantedAny(["VIEW_PROOF", "MAINTAIN_PROOF"]) ? (
  • Proof
  • ) : null} {/* ===== Payment dropdown (admin) ===== */} {isGrantedAny([ "MAINTAIN_PROOF", "MAINTAIN_PAYMENT", "MAINTAIN_RECON", "VIEW_DEMANDNOTE", "MAINTAIN_DEMANDNOTE", ]) ? (
    • {isGranted("MAINTAIN_PROOF") ? (
    • Export for GDN
    • ) : null} {isGranted("MAINTAIN_PAYMENT") ? (
    • Mark Payment
    • ) : null} {isGranted("MAINTAIN_PAYMENT") ? (
    • Online Payment Record
    • ) : null} {isGranted("MAINTAIN_RECON") ? (
    • GFMIS Generate XML
    • ) : null} {isGranted("MAINTAIN_DEMANDNOTE") ? (
    • Create Demand Note
    • ) : null} {isGrantedAny(["VIEW_DEMANDNOTE", "MAINTAIN_DEMANDNOTE"]) ? (
    • Demand Note
    • ) : null} {isGranted("MAINTAIN_RECON") ? (
    • Recon Report
    • ) : null}
  • ) : null} {/* ===== Client dropdown (admin) ===== */} {isGrantedAny([ "VIEW_USER", "MAINTAIN_USER", "VIEW_ORG", "MAINTAIN_ORG", "VIEW_GROUP", "MAINTAIN_GROUP", "VIEW_GLD_USER", "VIEW_IND_USER", "VIEW_ORG_USER", "MAINTAIN_GLD_USER", "MAINTAIN_IND_USER", "MAINTAIN_ORG_USER", ]) ? (
    • {isGrantedAny(["VIEW_USER", "MAINTAIN_USER"]) ? ( <>
    • Users (GLD)
    • Users (Individual)
    • Users (Organisation)
    • ) : ( <> {isGrantedAny(["VIEW_GLD_USER", "MAINTAIN_GLD_USER"]) ? (
    • Users (GLD)
    • ) : null} {isGrantedAny(["VIEW_IND_USER", "MAINTAIN_IND_USER"]) ? (
    • Users (Individual)
    • ) : null} {isGrantedAny(["VIEW_ORG_USER", "MAINTAIN_ORG_USER"]) ? (
    • Users (Organisation)
    • ) : null} )} {isGrantedAny(["VIEW_ORG", "MAINTAIN_ORG"]) ? (
    • Organisation
    • ) : null} {isGrantedAny(["VIEW_GROUP", "MAINTAIN_GROUP"]) ? (
    • User Group
    • ) : null}
  • ) : null} {/* ===== Report dropdown (admin) ===== */}
    • Summary of Gazette Notice
    • Gazette Notice Full List
  • {/* ===== Settings dropdown (admin) ===== */}
    • My Profile
    • {isGranted("VIEW_GAZETTE_ISSUE", "MAINTAIN_GAZETTE_ISSUE") ? ( <>
    • Holiday Settings
    • Gazette Issues
    • ) : null} {isGranted("MAINTAIN_ANNOUNCEMENT") ? (
    • Announcement
    • ) : null} {isGranted("MAINTAIN_EMAIL") ? (
    • Email Template
    • ) : null} {isGranted("MAINTAIN_DR") ? (
    • DR Import
    • ) : null} {isGranted("MAINTAIN_SETTING") ? ( <>
    • System Settings
    • Audit Log
    • ) : null}
  • Logout
  • )}
    ) : ( // ===== Non-GLD login content (your original logic, only dropdown triggers changed to ) : ( )} {/* ===== User Setting dropdown (non-admin) ===== */}
  • )}
  • ); // ============================= // Logged-out top nav content // ============================= const logoutContent = (
  • {!isAfterAboutSwitchDate() ? ( ) : ( )}
  • {sysSetting?.allowRegistration ? (
  • ) : null}
    ); const drawer = isUserLoggedIn() ? ( PNSPS
    ) : ( PNSPS ); const container = window !== undefined ? () => window().document.body : undefined; // ============================= // Render (your layout kept) // ============================= return isUserLoggedIn() ? ( {isGLDLoggedIn() ? ( PNSPS RESTRICTED ) : ( )} {isGLDLoggedIn() ? ( RESTRICTED ) : ( )} {drawer} ) : ( {drawer} ); } Header.propTypes = { window: PropTypes.func, }; export default Header;