Files
GNX-WEB/frontEnd/components/shared/layout/header/Header.tsx
Iliyan Angelov 0b1cabcfaf updates
2025-11-24 16:47:37 +02:00

359 lines
12 KiB
TypeScript

"use client";
import { useState, useEffect, useMemo, useRef } from "react";
import { usePathname } from "next/navigation";
import Link from "next/link";
import Image from "next/image";
import OffcanvasMenu from "./OffcanvasMenu";
import { useNavigationServices } from "@/lib/hooks/useServices";
const Header = () => {
const [isOffcanvasOpen, setIsOffcanvasOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
const [isActive, setIsActive] = useState(true);
const [openDropdown, setOpenDropdown] = useState<number | null>(null);
const [isMobile, setIsMobile] = useState(false);
const dropdownTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// Fetch services from API
const { services: apiServices, loading: servicesLoading, error: servicesError } = useNavigationServices();
// Create dynamic navigation data - only use API data, no hardcoded fallback
const navigationData = useMemo(() => {
const baseNavigation = [
{
id: 1,
title: "Home",
path: "/",
submenu: null,
},
{
id: 2,
title: "About Us",
path: "/about-us",
submenu: null,
},
{
id: 3,
title: "Services",
path: "/services",
submenu: apiServices.length > 0
? apiServices.map(service => ({
id: service.id + 1000,
title: service.title,
path: `/services/${service.slug}`,
display_order: service.display_order,
}))
: null,
},
{
id: 4,
title: "Case Studies",
path: "/case-study",
submenu: null,
},
{
id: 5,
title: "Insights",
path: "/insights",
submenu: null,
},
{
id: 6,
title: "Career",
path: "/career",
submenu: null,
},
{
id: 7,
title: "Support Center",
path: "/support-center",
submenu: null,
},
{
id: 8,
title: "Contact Us",
path: "/contact-us",
submenu: null,
},
];
return baseNavigation;
}, [apiServices]);
// Static header data
const headerData = {
title: "EnterpriseSoft Solutions",
logoUrl: "/images/logo.png",
logoLightUrl: "/images/logo-light.png",
navigationType: "both",
headerClass: "tp-header",
scrolledClass: "navbar-active",
buttonText: "Support Center",
buttonUrl: "/support-center",
buttonClass: "btn btn-primary d-none d-sm-flex",
isActive: true,
displayOrder: 1,
metaData: JSON.stringify({
mobileBreakpoint: 992,
scrollThreshold: 50,
hideOnMobile: false,
mobileFirst: true,
hamburgerMenu: true
})
};
const handleClick = () => {
setTimeout(() => {
setIsOffcanvasOpen(false);
}, 900);
setIsActive(false);
};
const handleDropdownToggle = (index: number) => {
setOpenDropdown(openDropdown === index ? null : index);
};
const handleDropdownEnter = (index: number) => {
if (dropdownTimeoutRef.current) {
clearTimeout(dropdownTimeoutRef.current);
dropdownTimeoutRef.current = null;
}
if (!isMobile) {
setOpenDropdown(index);
}
};
const handleDropdownLeave = (e: React.MouseEvent) => {
if (!isMobile) {
// Check if we're moving to the dropdown menu itself
const relatedTarget = e.relatedTarget as HTMLElement;
const currentTarget = e.currentTarget as HTMLElement;
// If moving to a child element (dropdown menu), don't close
if (relatedTarget && currentTarget.contains(relatedTarget)) {
return;
}
if (dropdownTimeoutRef.current) {
clearTimeout(dropdownTimeoutRef.current);
}
dropdownTimeoutRef.current = setTimeout(() => {
setOpenDropdown(null);
}, 300); // Increased delay to allow for scrolling
}
};
useEffect(() => {
const handleScroll = () => {
const scrollPosition = window.scrollY;
if (scrollPosition > 50) {
setScrolled(true);
} else {
setScrolled(false);
}
};
window.addEventListener("scroll", handleScroll);
handleScroll();
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, [scrolled]);
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth < 992);
setTimeout(() => {
setIsOffcanvasOpen(false);
}, 900);
setIsActive(false);
};
handleResize(); // Check on mount
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
if (dropdownTimeoutRef.current) {
clearTimeout(dropdownTimeoutRef.current);
}
};
}, []);
// Use static data
let logoSrc: string = headerData.logoUrl;
let headerClass = headerData.headerClass;
let buttonText = headerData.buttonText;
let buttonUrl = headerData.buttonUrl;
let buttonClass = headerData.buttonClass;
const pathname = usePathname();
// Override logo based on pathname if needed (maintain existing behavior)
if (
pathname === "/career" ||
pathname === "/" ||
pathname === "/index" ||
pathname === "/services" ||
pathname === "/service-single"
) {
logoSrc = headerData.logoLightUrl;
}
const handleOffCanvas = () => {
setIsOffcanvasOpen(true);
setIsActive(true);
};
return (
<>
<header className={headerClass}>
<div className={"primary-navbar" + (scrolled ? " navbar-active" : " ")}>
<div className="container">
<div className="row">
<div className="col-12">
<nav className="navbar p-0">
<div className="navbar__logo">
<Link href="/" aria-label="go to home" className="logo-img">
<Image
src={logoSrc}
alt="Logo"
width={160}
height={120}
priority
className="logo-image"
/>
</Link>
</div>
{/* Desktop Navigation Menu */}
<div className="navbar__menu d-none d-lg-flex">
<ul>
{navigationData.map((item) =>
item.title === "Support Center" ? null : item.submenu ? (
<li
className="navbar__item navbar__item--has-children"
key={item.id}
onMouseEnter={() => handleDropdownEnter(item.id)}
onMouseLeave={(e) => handleDropdownLeave(e)}
>
<button
aria-label="dropdown menu"
className={
"navbar__dropdown-label" +
(openDropdown === item.id
? " navbar__item-active"
: " ")
}
onClick={() => isMobile && handleDropdownToggle(item.id)}
>
{item.title}
{item.title === "Services" && servicesLoading && (
<span className="loading-indicator"></span>
)}
</button>
<ul
className={`navbar__sub-menu ${openDropdown === item.id ? 'show' : ''}`}
onMouseEnter={() => {
if (dropdownTimeoutRef.current) {
clearTimeout(dropdownTimeoutRef.current);
dropdownTimeoutRef.current = null;
}
if (!isMobile) {
setOpenDropdown(item.id);
}
}}
onMouseLeave={(e) => handleDropdownLeave(e)}
onWheel={(e) => {
// Prevent dropdown from closing when scrolling
e.stopPropagation();
if (dropdownTimeoutRef.current) {
clearTimeout(dropdownTimeoutRef.current);
dropdownTimeoutRef.current = null;
}
}}
>
{item.title === "Services" && servicesLoading ? (
<li>
<span className="text-muted">Loading services...</span>
</li>
) : item.title === "Services" && (servicesError || !item.submenu || item.submenu.length === 0) ? (
<li>
<span className="text-muted">No data available</span>
</li>
) : item.submenu ? (
item.submenu.map((subItem, subIndex) => (
<li key={subIndex}>
<Link
href={subItem.path || "#"}
className={
pathname === subItem.path
? " active-current-sub"
: " "
}
>
{subItem.title}
</Link>
</li>
))
) : null}
</ul>
</li>
) : (
<li
className="navbar__item"
key={item.id}
>
<Link
href={item.path || "#"}
className={
pathname === item.path ? " active-current-link" : " "
}
>
{item.title}
</Link>
</li>
)
)}
</ul>
</div>
<div className="navbar__options">
<Link href={buttonUrl} className={buttonClass}>
{buttonText}
</Link>
<button
className="open-offcanvas-nav d-lg-none"
aria-label="toggle mobile menu"
title="open offcanvas menu"
onClick={handleOffCanvas}
>
<span className="icon-bar top-bar"></span>
<span className="icon-bar middle-bar"></span>
<span className="icon-bar bottom-bar"></span>
</button>
</div>
</nav>
</div>
</div>
</div>
</div>
</header>
<OffcanvasMenu
isOffcanvasOpen={isOffcanvasOpen}
handleClick={handleClick}
isActive={isActive}
navigationData={navigationData}
servicesLoading={servicesLoading}
servicesError={servicesError}
/>
</>
);
};
export default Header;