import type { Metadata } from "next"; import { Inter, Playfair_Display, Caveat, Lora } from "next/font/google"; import "./globals.css"; import { Navbar } from "./components/Navbar"; import { CartFlyProvider } from "./components/CartFly"; import { CartSync } from "./components/CartSync"; import { KlaroConsentManager } from "./components/KlaroConsentManager"; import { getProducts, getSeoSettings, getCompanySettings, getWishlistEnabled, getSearchEnabled, getTrackingCodes } from "./lib/payload"; import { buildOrganizationSchema } from "./lib/structuredData"; const inter = Inter({ variable: "--font-inter", subsets: ["latin"], weight: ["400", "500", "600", "700"], }); const playfair = Playfair_Display({ variable: "--font-playfair", subsets: ["latin"], weight: ["600", "700", "800"], }); const caveat = Caveat({ variable: "--font-caveat", subsets: ["latin"], weight: ["700"], }); const lora = Lora({ variable: "--font-lora", subsets: ["latin"], weight: ["400", "600"], }); // Backend-driven since 2026-07-24 (CompanySettings' "SEO" tab) — the // literal strings below are only the fallback getSeoSettings() returns if // that field is empty or unreachable, kept identical to what used to be // hardcoded here so nothing changes until an admin actually fills in the // new fields. export async function generateMetadata(): Promise { const seo = await getSeoSettings(); return { metadataBase: new URL("https://einfach-produktiv.mk360.de"), title: { default: seo.defaultTitle ?? "einfach produktiv.", template: seo.titleTemplate ?? "%s | einfach produktiv.", }, description: seo.defaultDescription ?? undefined, openGraph: { siteName: "einfach produktiv.", locale: "de_DE", type: "website", images: seo.defaultOgImage ? [{ url: seo.defaultOgImage }] : undefined, }, twitter: { card: "summary_large_image", images: seo.defaultOgImage ? [seo.defaultOgImage] : undefined, }, }; } export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { // Same 60s-ISR-cached call every other page already makes — reused here // just to know whether Navbar's "Shop" link should behave as an anchor // to the homepage spotlight instead of a real /shop navigation (see // Navbar.tsx/ProductSpotlight.tsx). const [products, seller, wishlistEnabled, searchEnabled, trackingCodes] = await Promise.all([ getProducts(), getCompanySettings(), getWishlistEnabled(), getSearchEnabled(), getTrackingCodes(), ]); const singleActiveProduct = products.filter((p) => p.active).length === 1; // Organization JSON-LD on every page — one canonical node (@id) that // Product/Article schemas elsewhere link back to via `{ "@id": ... }` // instead of repeating the full seller object per page (see // structuredData.ts's own comment). getCompanySettings() is already the // established pattern for a public page needing seller data server-side // (see /impressum) — only non-sensitive fields (name/address/email/ // vatID) ever make it into the rendered schema, never iban/bic. const organizationSchema = buildOrganizationSchema(seller); return (