From b906cbb2b151d4d969ce0bc955b488b619b4f585 Mon Sep 17 00:00:00 2001 From: CCS Admin Date: Mon, 16 Feb 2026 23:22:04 +0000 Subject: [PATCH] feat: implement CMS-driven navigation with dropdown submenus Bypass broken contracts client navigation filter (queries non-existent 'type' field) with direct fetch to /api/navigations. Transform CMS mainMenu structure (page/custom/submenu types) into NavItem format. Replace JS-state dropdown (AnimatePresence) with pure CSS group-hover to fix SSR hydration issues where both dropdowns opened simultaneously. Co-Authored-By: Claude Opus 4.6 --- src/app/layout.tsx | 43 +++++++++++++++++++++++++++++------ src/components/Navigation.tsx | 36 ++++++++++++++--------------- src/lib/api.ts | 12 ++++++++-- 3 files changed, 63 insertions(+), 28 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1b205c4..3cbf354 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -42,15 +42,44 @@ export default async function RootLayout({ getSocialLinks(), ]) - // Transform CMS navigation items into NavItem format + // Transform CMS navigation into NavItem format const nav = navigation as unknown as Record | null - const navItems = nav?.items - ? (nav.items as Array>).map( - (item) => ({ + const mainMenu = (nav?.mainMenu || nav?.items) as Array> | undefined + + function pageSlugToHref(page: Record | null): string { + if (!page?.slug) return '#' + return page.slug === 'startseite' ? '/' : `/${page.slug}` + } + + function buildSubmenuItems(items: Array>): { label: string; href: string }[] { + return items.map((sub) => { + const subPage = sub.page as Record | null + const subUrl = sub.url as string | null + const linkType = sub.linkType as string + return { + label: (sub.label as string) || '', + href: linkType === 'page' ? pageSlugToHref(subPage) : (subUrl || '#'), + } + }) + } + + const navItems = mainMenu?.length + ? mainMenu.map((item) => { + const type = item.type as string + const page = item.page as Record | null + const submenu = item.submenu as Array> | undefined + const children = submenu?.length ? buildSubmenuItems(submenu) : undefined + + let href = '#' + if (type === 'page') href = pageSlugToHref(page) + else if (type === 'custom') href = (item.url as string) || '#' + + return { label: (item.label as string) || '', - href: (item.link as string) || (item.url as string) || '#', - }) - ) + href, + children, + } + }) : [ { label: 'Home', href: '/' }, { label: 'Der Mensch', href: '/mensch' }, diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx index 2e445f6..ede34a6 100644 --- a/src/components/Navigation.tsx +++ b/src/components/Navigation.tsx @@ -22,7 +22,6 @@ interface NavigationProps { export function Navigation({ items, logo, transparent = false }: NavigationProps) { const [scrolled, setScrolled] = useState(false) const [mobileOpen, setMobileOpen] = useState(false) - const [activeDropdown, setActiveDropdown] = useState(null) const pathname = usePathname() useEffect(() => { @@ -75,10 +74,8 @@ export function Navigation({ items, logo, transparent = false }: NavigationProps
{items.map((item) => (
item.children && setActiveDropdown(item.href)} - onMouseLeave={() => setActiveDropdown(null)} + key={item.label} + className="group relative" > - {/* Dropdown */} - - {item.children && activeDropdown === item.href && ( - + {/* Dropdown — hidden by default, shown on group hover via CSS */} + {item.children && ( +
+
{item.children.map((child) => ( ))} - - )} - +
+
+ )}
))}
@@ -149,7 +147,7 @@ export function Navigation({ items, logo, transparent = false }: NavigationProps >
{items.map((item) => ( -
+
[] } + return data?.docs?.[0] || null } catch { return null }