'use client'; import React, { useState, useEffect, useLayoutEffect, useRef } from 'react'; import Link from 'next/link'; import { ChevronDown } from 'lucide-react'; import { usePathname } from 'next/navigation'; import { TopDealsMegaMenu, isTopDealsNavLabel, } from '@/components/TopDealsMegaMenu'; import { ProductsMegaMenu, isProductsNavLabel, } from '@/components/ProductsMegaMenu'; import { ShopMegaMenu, isShopNavLabel } from '@/components/ShopMegaMenu'; import { FaqMenu, isFaqNavLink } from '@/components/faq/FaqMenu'; import { getCategoryMenuItems, // CategorySidebar is kept in the file but the current mega sidebar is commented out below. } from '@/components/CategorySidebar'; import { catalogService } from '@/services/api/catalogService'; import { STOREFRONT_STICKY_NAV_GAP_PX, STOREFRONT_STICKY_SCROLL_Y, } from '@/layout/Header'; import { parseMetadata, resolveIsActive } from '@/lib/layoutConfig'; import { DEFAULT_NAV_LINKS } from '@/lib/homepageDefaults'; import { storefrontService, type AllMegaMenus, } from '@/services/api/storefrontService'; interface NavLink { label: string; url: string; hasDropdown: boolean; } function isContactNavLink(label: string, url?: string): boolean { const text = String(label || '').toLowerCase(); const path = String(url || '').split('?')[0]; return text.includes('contact') || /^\/contact\/?$/i.test(path); } /** Contact Us and FAQs stay in the top announcement bar, not the main nav. */ function withoutUtilityNavLinks(links: NavLink[]): NavLink[] { return links.filter( (l) => !isContactNavLink(l.label, l.url) && !isFaqNavLink(l.label, l.url) ); } type MegaKind = 'deals' | 'products' | 'shop' | 'faq' | null; /** Pick which mega panel to open when admin enables “dropdown on hover”. */ function resolveMegaKind(link: NavLink): MegaKind { const url = link.url || ''; if (isFaqNavLink(link.label, url)) return 'faq'; if (!link.hasDropdown) return null; if (isTopDealsNavLabel(link.label) || /[?&]tag=deals/i.test(url)) return 'deals'; if (isProductsNavLabel(link.label) || /[?&]featured=/i.test(url)) return 'products'; if (isShopNavLabel(link.label) || url.startsWith('/shop')) return 'shop'; // Checkbox label in admin: “Show categories dropdown on hover” return 'shop'; } function mapNavLinks(raw: unknown[]): NavLink[] { return raw .filter((l: any) => l && l.label && l.url) .map((l: any) => ({ label: String(l.label).trim(), url: String(l.url).trim() || '/', // Strict: only admin “Show categories dropdown on hover” hasDropdown: Boolean(l.hasDropdown), })); } function linksFromConfig(config: any): NavLink[] | null { const meta = parseMetadata(config); const raw = meta.links; if (Array.isArray(raw) && raw.length > 0) return mapNavLinks(raw); return null; } interface NavbarProps { config?: any; /** When false, hide configurable nav links (All Categories chrome stays). */ isActive?: boolean; } export const Navbar: React.FC = ({ config, isActive = true, }) => { const pathname = usePathname(); const [isSticky, setIsSticky] = useState(false); const [headerHeight, setHeaderHeight] = useState(0); const [navLinks, setNavLinks] = useState(() => mapNavLinks(DEFAULT_NAV_LINKS as unknown[]) ); const [megaOpen, setMegaOpen] = useState(null); const [categoriesOpen, setCategoriesOpen] = useState(false); const [categoryMenu, setCategoryMenu] = useState(() => getCategoryMenuItems([])); const [megaMenus, setMegaMenus] = useState({}); const closeTimer = useRef | null>(null); const categoriesRef = useRef(null); const openMega = (kind: MegaKind) => { if (!kind) return; if (closeTimer.current) clearTimeout(closeTimer.current); setCategoriesOpen(false); setMegaOpen(kind); }; const scheduleCloseMega = () => { if (closeTimer.current) clearTimeout(closeTimer.current); closeTimer.current = setTimeout(() => setMegaOpen(null), 120); }; const toggleCategories = () => { setMegaOpen(null); setCategoriesOpen((open) => !open); }; useEffect(() => { let cancelled = false; catalogService.getCategories().then((res) => { if (!cancelled) setCategoryMenu(getCategoryMenuItems(res || [])); }); return () => { cancelled = true; }; }, []); useEffect(() => { let cancelled = false; storefrontService.getMegaMenu().then((menus) => { if (!cancelled) setMegaMenus(menus || {}); }); return () => { cancelled = true; }; }, []); useEffect(() => { const defaults = mapNavLinks(DEFAULT_NAV_LINKS as unknown[]); // Section Hidden → keep a usable default nav (do not blank the bar) if (!isActive) { setNavLinks(defaults); setMegaOpen(null); return; } if (config) { // Prefer CMS links whenever the section is Visible const fromCms = linksFromConfig(config); setNavLinks(fromCms ?? defaults); return; } fetch('/api/v1/storefront/layout/home?region=nav_links', { cache: 'no-store', }) .then((r) => (r.ok ? r.json() : null)) .then((data: any) => { const rows = Array.isArray(data) ? data : data?.items || []; if (rows.length > 0) { const section = rows[0]; if (resolveIsActive(section) === false) { setNavLinks(defaults); return; } const fromCms = linksFromConfig(section); setNavLinks(fromCms ?? defaults); return; } setNavLinks(defaults); }) .catch(() => setNavLinks(defaults)); }, [config, isActive]); useEffect(() => { const syncSticky = () => { const next = window.scrollY > STOREFRONT_STICKY_SCROLL_Y; setIsSticky(next); if (next) { setMegaOpen(null); setCategoriesOpen(false); } }; syncSticky(); window.addEventListener('scroll', syncSticky, { passive: true }); return () => { window.removeEventListener('scroll', syncSticky); if (closeTimer.current) clearTimeout(closeTimer.current); }; }, []); useLayoutEffect(() => { const measure = () => { const header = document.querySelector( '[data-storefront-header]' ); setHeaderHeight(header?.offsetHeight || 0); }; measure(); window.addEventListener('resize', measure, { passive: true }); return () => window.removeEventListener('resize', measure); }, [isSticky]); useEffect(() => { setCategoriesOpen(false); setMegaOpen(null); }, [pathname]); useEffect(() => { if (!categoriesOpen) return; const onPointerDown = (event: MouseEvent) => { const root = categoriesRef.current; if (root && !root.contains(event.target as Node)) { setCategoriesOpen(false); } }; const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') setCategoriesOpen(false); }; document.addEventListener('mousedown', onPointerDown); document.addEventListener('keydown', onKeyDown); return () => { document.removeEventListener('mousedown', onPointerDown); document.removeEventListener('keydown', onKeyDown); }; }, [categoriesOpen]); return ( ); };