diff --git a/src/app/(marketing)/contact/page.tsx b/src/app/(marketing)/contact/page.tsx index 0161544..ee26513 100644 --- a/src/app/(marketing)/contact/page.tsx +++ b/src/app/(marketing)/contact/page.tsx @@ -1,7 +1,7 @@ import type { Metadata } from "next"; -import Link from "next/link"; import { getTranslations } from "next-intl/server"; -import { LanguageSwitcher } from "@/components/LanguageSwitcher"; +import { SiteHeader } from "@/components/SiteHeader"; +import { SiteFooter } from "@/components/SiteFooter"; import { ContactForm } from "@/components/contact/ContactForm"; export const metadata: Metadata = { @@ -12,27 +12,11 @@ export const metadata: Metadata = { }; export default async function ContactPage() { - const t = await getTranslations(); const c = await getTranslations("contact"); return ( <> - - - - {t("nav.brand")} - - - - - {t("nav.openMap")} → - - - - + @@ -47,27 +31,7 @@ export default async function ContactPage() { - + > ); } diff --git a/src/app/(marketing)/destination/[slug]/page.tsx b/src/app/(marketing)/destination/[slug]/page.tsx index 245317e..400c51a 100644 --- a/src/app/(marketing)/destination/[slug]/page.tsx +++ b/src/app/(marketing)/destination/[slug]/page.tsx @@ -2,8 +2,9 @@ import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; import { getLocale, getTranslations } from "next-intl/server"; -import { LanguageSwitcher } from "@/components/LanguageSwitcher"; import { JsonLd } from "@/components/seo/JsonLd"; +import { SiteHeader } from "@/components/SiteHeader"; +import { SiteFooter } from "@/components/SiteFooter"; import { getCountryName } from "@/lib/data"; import { allDestinationSlugs, @@ -118,7 +119,6 @@ export default async function DestinationPage({ // German slugs force German rendering (see generateMetadata). const locale = entry.locale === "de" ? "de" : await getLocale(); const t = await getTranslations({ locale, namespace: "destination" }); - const nav = await getTranslations({ locale, namespace: "nav" }); const tip = await getTranslations({ locale, namespace: "tooltip" }); const alts = localizedSlugsForSlug(slug); const hrefByLocale = alts ? switcherHrefs(alts) : undefined; @@ -147,26 +147,12 @@ export default async function DestinationPage({ return ( <> - - - - {nav("brand")} - - - - - {nav("openMap")} → - - - - + @@ -251,6 +237,8 @@ export default async function DestinationPage({ + + > ); } diff --git a/src/app/(marketing)/page.tsx b/src/app/(marketing)/page.tsx index 8def4f1..7839865 100644 --- a/src/app/(marketing)/page.tsx +++ b/src/app/(marketing)/page.tsx @@ -1,9 +1,10 @@ import type { Metadata } from "next"; import Link from "next/link"; import { getTranslations } from "next-intl/server"; -import { LanguageSwitcher } from "@/components/LanguageSwitcher"; import { JsonLd } from "@/components/seo/JsonLd"; import { HeroDestinationSearch } from "@/components/HeroDestinationSearch"; +import { SiteHeader } from "@/components/SiteHeader"; +import { SiteFooter } from "@/components/SiteFooter"; const webAppJsonLd = { "@context": "https://schema.org", @@ -115,41 +116,7 @@ export default async function MarketingPage() { return ( <> - {/* Navigation */} - - - - {t("nav.brand")} - - - - {t("nav.howItWorks")} - - - {t("nav.data")} - - - {t("nav.openSource")} - - - - {t("nav.openMap")} → - - - - + {/* Hero */} @@ -368,36 +335,7 @@ export default async function MarketingPage() { - {/* Footer */} - + > ); } diff --git a/src/app/(marketing)/privacy/page.tsx b/src/app/(marketing)/privacy/page.tsx index 4d4b6a5..c6e7b78 100644 --- a/src/app/(marketing)/privacy/page.tsx +++ b/src/app/(marketing)/privacy/page.tsx @@ -1,7 +1,7 @@ import type { Metadata } from "next"; -import Link from "next/link"; import { getTranslations } from "next-intl/server"; -import { LanguageSwitcher } from "@/components/LanguageSwitcher"; +import { SiteHeader } from "@/components/SiteHeader"; +import { SiteFooter } from "@/components/SiteFooter"; export const metadata: Metadata = { title: "Privacy Policy — PackedPlaces.com", @@ -11,27 +11,11 @@ export const metadata: Metadata = { }; export default async function PrivacyPage() { - const t = await getTranslations(); const p = await getTranslations("privacy"); return ( <> - - - - {t("nav.brand")} - - - - - {t("nav.openMap")} → - - - - + @@ -129,27 +113,7 @@ export default async function PrivacyPage() { - + > ); } diff --git a/src/components/SiteFooter.tsx b/src/components/SiteFooter.tsx new file mode 100644 index 0000000..d590ceb --- /dev/null +++ b/src/components/SiteFooter.tsx @@ -0,0 +1,46 @@ +import Link from "next/link"; +import { getTranslations } from "next-intl/server"; + +interface Props { + /** Render the footer in a specific locale (e.g. a forced-German destination slug). */ + locale?: string; +} + +/** Shared site footer used across the landing page and content pages. */ +export async function SiteFooter({ locale }: Props) { + const t = locale + ? await getTranslations({ locale, namespace: "footer" }) + : await getTranslations("footer"); + + return ( + + ); +} diff --git a/src/components/SiteHeader.tsx b/src/components/SiteHeader.tsx new file mode 100644 index 0000000..0c787e5 --- /dev/null +++ b/src/components/SiteHeader.tsx @@ -0,0 +1,67 @@ +import Link from "next/link"; +import { getTranslations } from "next-intl/server"; +import { LanguageSwitcher } from "@/components/LanguageSwitcher"; + +interface Props { + /** Render the header in a specific locale (e.g. a forced-German destination slug). */ + locale?: string; + /** Selected locale for the switcher, when it differs from the request locale. */ + currentLocale?: string; + /** Per-locale hrefs so the switcher can jump to a localized URL. */ + hrefByLocale?: Record; + /** Target for the "open map" button; defaults to the generic map. */ + mapHref?: string; +} + +/** Shared site header used across the landing page and content pages. */ +export async function SiteHeader({ + locale, + currentLocale, + hrefByLocale, + mapHref = "/map", +}: Props) { + const t = locale + ? await getTranslations({ locale, namespace: "nav" }) + : await getTranslations("nav"); + + return ( + + + + {t("brand")} + + + + {t("howItWorks")} + + + {t("data")} + + + {t("openSource")} + + + + {t("openMap")} → + + + + + ); +} diff --git a/src/components/features/FeaturePageShell.tsx b/src/components/features/FeaturePageShell.tsx index 765a8e6..427d9c7 100644 --- a/src/components/features/FeaturePageShell.tsx +++ b/src/components/features/FeaturePageShell.tsx @@ -1,6 +1,7 @@ import Link from "next/link"; import { getTranslations } from "next-intl/server"; -import { LanguageSwitcher } from "@/components/LanguageSwitcher"; +import { SiteHeader } from "@/components/SiteHeader"; +import { SiteFooter } from "@/components/SiteFooter"; const FEATURE_SLUGS = [ { slug: "crowdedness", key: "crowdedness" }, @@ -18,7 +19,6 @@ interface Props { } export async function FeaturePageShell({ slug, children }: Props) { - const t = await getTranslations(); const nav = await getTranslations("featurePages.nav"); const shell = await getTranslations("featurePages.shell"); const feature = await getTranslations(`featurePages.${FEATURE_SLUGS.find((f) => f.slug === slug)?.key ?? slug}`); @@ -27,22 +27,7 @@ export async function FeaturePageShell({ slug, children }: Props) { return ( <> - - - - {t("nav.brand")} - - - - - {t("nav.openMap")} → - - - - + @@ -102,27 +87,7 @@ export async function FeaturePageShell({ slug, children }: Props) { - + > ); }