- Add Next.js frontend service (nextjs) with Dockerfile and source - Update docker-compose.yml: image names, Drupal 11.3.3, nextjs service - Add docker-compose.override.yml.disabled for dev hot-reload - Add install-headless-modules.sh for OAuth/JSON:API module setup - Add README.md with full setup and configuration guide - Update nginx/Dockerfile and nginx.conf.template for cms. subdomain - Update drupal/Dockerfile PHP-FPM build args - Gitignore **/.vscode/ to prevent IDE workspace files from being tracked
76 lines
2 KiB
TypeScript
76 lines
2 KiB
TypeScript
import type { DrupalMenuItem } from "next-drupal"
|
|
import { MainNavClient } from "./main-nav-client"
|
|
|
|
const drupalBaseUrl = process.env.NEXT_PUBLIC_DRUPAL_BASE_URL ?? ""
|
|
|
|
interface RawMenuItem {
|
|
id: string
|
|
parent: string
|
|
title: string
|
|
url: string
|
|
enabled?: boolean
|
|
weight?: string | number
|
|
}
|
|
|
|
function buildMenuTree(
|
|
items: RawMenuItem[],
|
|
parentId: string
|
|
): DrupalMenuItem[] {
|
|
return items
|
|
.filter((item) => (item.parent || "") === parentId && item.enabled !== false)
|
|
.sort((a, b) => Number(a.weight ?? 0) - Number(b.weight ?? 0))
|
|
.map((item) => {
|
|
const children = buildMenuTree(items, item.id)
|
|
return {
|
|
...item,
|
|
items: children.length ? children : undefined,
|
|
} as DrupalMenuItem
|
|
})
|
|
}
|
|
|
|
const FALLBACK_MENU: DrupalMenuItem[] = [
|
|
{
|
|
id: "home",
|
|
title: "Home",
|
|
url: "/",
|
|
enabled: true,
|
|
items: undefined,
|
|
} as DrupalMenuItem,
|
|
]
|
|
|
|
async function getMainMenu(): Promise<DrupalMenuItem[]> {
|
|
if (!drupalBaseUrl) return FALLBACK_MENU
|
|
|
|
try {
|
|
const url = `${drupalBaseUrl.replace(/\/$/, "")}/jsonapi/menu_items/main`
|
|
const controller = new AbortController()
|
|
const timeoutId = setTimeout(() => controller.abort(), 5000)
|
|
const res = await fetch(url, {
|
|
headers: { Accept: "application/vnd.api+json" },
|
|
next: { revalidate: 60 },
|
|
signal: controller.signal,
|
|
})
|
|
clearTimeout(timeoutId)
|
|
if (!res.ok) {
|
|
if (res.status !== 404) {
|
|
console.warn(`[MainNav] Menu fetch returned ${res.status}, using fallback nav.`)
|
|
}
|
|
return FALLBACK_MENU
|
|
}
|
|
|
|
const json = await res.json()
|
|
const items: RawMenuItem[] = json.data ?? []
|
|
|
|
return buildMenuTree(items, "")
|
|
} catch (error) {
|
|
if ((error as Error).name !== "AbortError") {
|
|
console.warn("[MainNav] CMS unreachable, using fallback nav:", (error as Error).message)
|
|
}
|
|
return FALLBACK_MENU
|
|
}
|
|
}
|
|
|
|
export async function MainNav() {
|
|
const menuItems = await getMainMenu()
|
|
return <MainNavClient menuItems={menuItems} />
|
|
}
|