feat: connect header and footer to cms as globals

This commit is contained in:
syntaxbullet
2026-06-16 14:25:58 +02:00
parent 6b395e5735
commit cde2164773
13 changed files with 842 additions and 456 deletions

198
src/spa/cmsNavigation.ts Normal file
View File

@@ -0,0 +1,198 @@
import type { Media } from '@/payload-types'
export type SpaIconName = 'instagram' | 'linkedin' | 'facebook'
export type SpaLink = { label: string; path: string }
export type SpaSocialLink = { label: string; href: string; icon: SpaIconName }
export type SpaCtaLink = SpaLink & { variant?: 'primary' | 'secondary' | 'subtle' | 'upload' }
export type SpaMedia = { url?: string; alt?: string }
export type SpaHeaderNavItem = SpaLink & {
key: string
subLabel?: string
sub?: SpaLink[]
}
export type SpaHeaderData = {
logo?: SpaMedia
overlayKicker: string
overlayHeadline: string
overlayLogoAlt: string
mobileWatermark: string
menuLabel: string
openLabel: string
closeLabel: string
overviewLabel: string
navItems: SpaHeaderNavItem[]
secondaryLinks: SpaLink[]
socialLinks: SpaSocialLink[]
ctas: SpaCtaLink[]
languages: string[]
}
export type SpaFooterLinkColumn = { label: string; links: SpaLink[] }
export type SpaPartnerLogo = { image?: SpaMedia; alt: string }
export type SpaFooterData = {
logo?: SpaMedia
logoAlt: string
headlinePrefix: string
headlineAccent: string
headlineSuffix: string
sinceLabel: string
year: string
description: string
ctas: SpaCtaLink[]
linkColumns: SpaFooterLinkColumn[]
partnerLabel: string
partnerLogos: SpaPartnerLogo[]
copyright: string
socialLinks: SpaSocialLink[]
}
export const defaultSocialLinks: SpaSocialLink[] = [
{ href: 'https://instagram.com', icon: 'instagram', label: 'Instagram' },
{ href: 'https://linkedin.com', icon: 'linkedin', label: 'LinkedIn' },
{ href: 'https://facebook.com', icon: 'facebook', label: 'Facebook' },
]
export const defaultHeaderData: SpaHeaderData = {
logo: { url: '/bmp-logo.png', alt: 'Der Bayerische Mittelstandspreis 2026' },
overlayKicker: 'Bayerischer Mittelstandspreis',
overlayHeadline: 'EXZELLENZ HAT\nEINEN NAMEN.',
overlayLogoAlt: 'BMP Logo',
mobileWatermark: 'Exzellenz\nseit 2005',
menuLabel: 'Menü',
openLabel: 'Navigation öffnen',
closeLabel: 'Menü schließen',
overviewLabel: 'Überblick',
languages: ['DE', 'EN'],
socialLinks: defaultSocialLinks,
navItems: [
{
key: 'preistraeger', label: 'Preisträger:innen', path: '/preistraeger', subLabel: 'PREISTRÄGER:INNEN',
sub: [
{ label: 'Alle Preisträger:innen', path: '/preistraeger' },
{ label: 'Sieger & Aufsteiger 2025', path: '/preistraeger' },
],
},
{
key: 'teilnahme', label: 'Ihr Weg zum Preis', path: '/teilnahme', subLabel: 'IHR WEG ZUM PREIS',
sub: [
{ label: 'Teilnahmevoraussetzungen', path: '/teilnahme#voraussetzungen' },
{ label: 'Kriterien & Bewertung', path: '/teilnahme#kriterien' },
{ label: 'Fristen & Termine', path: '/teilnahme#fristen' },
{ label: 'Bewerbungsformular', path: '/teilnahme#bewerben' },
],
},
{
key: 'bmp', label: 'Der BMP', path: '/der-bmp', subLabel: 'DER BMP',
sub: [
{ label: 'Was ist der Preis?', path: '/der-bmp#was-ist' },
{ label: 'Geschichte & Werte', path: '/der-bmp#geschichte' },
{ label: 'Vorteile der Auszeichnung', path: '/der-bmp#vorteile' },
],
},
{
key: 'netzwerk', label: 'Netzwerk', path: '/netzwerk', subLabel: 'NETZWERK',
sub: [
{ label: 'Jury-Mitglieder', path: '/netzwerk#jury' },
{ label: 'Partner & Sponsoren', path: '/netzwerk#partner' },
{ label: 'Schirmherrschaft', path: '/netzwerk#schirmherrschaft' },
{ label: 'Mitglied werden', path: '/mitglied-werden' },
],
},
{
key: 'presse', label: 'Presse', path: '/presse', subLabel: 'PRESSE',
sub: [
{ label: 'Events & Gala', path: '/presse#events' },
{ label: 'Downloads & Impressionen', path: '/presse#downloads' },
],
},
],
secondaryLinks: [
{ label: 'Startseite', path: '/' },
{ label: 'Kontakt', path: '/kontakt' },
{ label: 'Impressum', path: '/impressum' },
{ label: 'Datenschutz', path: '/datenschutz' },
],
ctas: [
{ label: 'Jetzt bewerben', path: '/teilnahme#bewerben', variant: 'primary' },
{ label: 'Mitglied werden', path: '/mitglied-werden', variant: 'secondary' },
{ label: 'Formular hochladen', path: '/formular-hochladen', variant: 'upload' },
],
}
export const defaultFooterData: SpaFooterData = {
logo: { url: '/bmp-logo.png', alt: 'Bayerischer Mittelstandspreis' },
logoAlt: 'Bayerischer Mittelstandspreis',
headlinePrefix: 'Der Preis des',
headlineAccent: 'Bayerischen',
headlineSuffix: 'Mittelstands',
sinceLabel: 'Exzellenz seit 2005',
year: '2026',
description: 'Die renommierteste Auszeichnung für herausragende Unternehmen des bayerischen Mittelstands.',
ctas: [
{ label: 'Kostenlos bewerben', path: '/teilnahme#bewerben', variant: 'primary' },
{ label: 'Mitglied werden', path: '/mitglied-werden', variant: 'secondary' },
{ label: 'Sponsoring-Anfrage →', path: '/netzwerk#sponsoring', variant: 'subtle' },
],
linkColumns: [
{ label: 'BMP 2026', links: [
{ label: 'Startseite', path: '/' },
{ label: 'Preisträger', path: '/preistraeger' },
{ label: 'Teilnahme', path: '/teilnahme' },
] },
{ label: 'Engagement', links: [
{ label: 'Netzwerk & Jury', path: '/netzwerk' },
{ label: 'Presse', path: '/presse' },
{ label: 'Kontakt', path: '/kontakt' },
] },
{ label: 'Legal', links: [
{ label: 'Datenschutz', path: '/datenschutz' },
{ label: 'Impressum', path: '/impressum' },
] },
],
partnerLabel: 'Partner',
partnerLogos: [
{ alt: 'EWIF – Europäisches Wirtschaftsforum', image: { url: '/images/ewif-logo.png' } },
{ alt: 'Hochschule für angewandtes Management · Gipfeldialog Altaussee', image: { url: '/images/partner-logos.png' } },
],
copyright: '© 2026 Bayerischer Mittelstandspreis · Alle Rechte vorbehalten',
socialLinks: defaultSocialLinks,
}
const isMedia = (value: unknown): value is Media => Boolean(value && typeof value === 'object' && 'url' in value)
const mediaToSpa = (value: unknown, fallback?: SpaMedia): SpaMedia | undefined => {
if (isMedia(value)) return { url: value.url || fallback?.url, alt: value.alt || fallback?.alt }
return fallback
}
export function normalizeHeaderData(header: any): SpaHeaderData {
return {
...defaultHeaderData,
...header,
logo: mediaToSpa(header?.logo, defaultHeaderData.logo),
navItems: header?.navItems?.length ? header.navItems : defaultHeaderData.navItems,
secondaryLinks: header?.secondaryLinks?.length ? header.secondaryLinks : defaultHeaderData.secondaryLinks,
socialLinks: header?.socialLinks?.length ? header.socialLinks : defaultHeaderData.socialLinks,
ctas: header?.ctas?.length ? header.ctas : defaultHeaderData.ctas,
languages: header?.languages?.length ? header.languages.map((item: any) => item.label || item).filter(Boolean) : defaultHeaderData.languages,
}
}
export function normalizeFooterData(footer: any): SpaFooterData {
return {
...defaultFooterData,
...footer,
logo: mediaToSpa(footer?.logo, defaultFooterData.logo),
ctas: footer?.ctas?.length ? footer.ctas : defaultFooterData.ctas,
linkColumns: footer?.linkColumns?.length ? footer.linkColumns : defaultFooterData.linkColumns,
socialLinks: footer?.socialLinks?.length ? footer.socialLinks : defaultFooterData.socialLinks,
partnerLogos: footer?.partnerLogos?.length
? footer.partnerLogos.map((item: any, index: number) => ({
alt: item.alt || defaultFooterData.partnerLogos[index]?.alt || '',
image: mediaToSpa(item.image, defaultFooterData.partnerLogos[index]?.image),
}))
: defaultFooterData.partnerLogos,
}
}