359 lines
12 KiB
TypeScript
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;
|