diff --git a/package.json b/package.json index aff3bca..2cfc383 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "preload:impressum-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-impressum-page-cms.ts", "preload:participation-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-participation-page-cms.ts", "preload:preistraeger-index-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-preistraeger-index-page-cms.ts", + "preload:press-index-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-press-index-page-cms.ts", "reinstall": "cross-env NODE_OPTIONS=--no-deprecation rm -rf node_modules && rm pnpm-lock.yaml && pnpm --ignore-workspace install", "start": "cross-env NODE_OPTIONS=--no-deprecation next start", "test": "pnpm run test:int && pnpm run test:e2e", diff --git a/src/collections/Pages/index.ts b/src/collections/Pages/index.ts index 9cda35c..07582ab 100644 --- a/src/collections/Pages/index.ts +++ b/src/collections/Pages/index.ts @@ -15,6 +15,7 @@ import { homeFields } from './homeFields' import { impressumFields } from './impressumFields' import { participationFields } from './participationFields' import { preistraegerIndexFields } from './preistraegerIndexFields' +import { pressIndexFields } from './pressIndexFields' import { slugField } from 'payload' import { populatePublishedAt } from '../../hooks/populatePublishedAt' import { generatePreviewPath } from '../../utilities/generatePreviewPath' @@ -84,6 +85,14 @@ const isPreistraegerIndexPage = (_: unknown, siblingData?: { slug?: string; spaP return spaPath === '/preistraeger' || slug === 'preistraeger' || title === 'preisträger' || title === 'preistraeger' } +const isPressIndexPage = (_: unknown, siblingData?: { slug?: string; spaPath?: string; title?: string }) => { + const slug = siblingData?.slug + const spaPath = siblingData?.spaPath + const title = siblingData?.title?.toLowerCase() + + return spaPath === '/presse' || slug === 'presse' || slug === 'press' || title === 'presse' || title === 'press' +} + const isManagedSpaPage = (_: unknown, siblingData?: { slug?: string; spaPath?: string; title?: string }) => isHomePage(undefined, siblingData) || isContactPage(undefined, siblingData) || @@ -91,7 +100,8 @@ const isManagedSpaPage = (_: unknown, siblingData?: { slug?: string; spaPath?: s isAboutPage(undefined, siblingData) || isImpressumPage(undefined, siblingData) || isDatenschutzPage(undefined, siblingData) || - isPreistraegerIndexPage(undefined, siblingData) + isPreistraegerIndexPage(undefined, siblingData) || + isPressIndexPage(undefined, siblingData) export const Pages: CollectionConfig<'pages'> = { slug: 'pages', @@ -208,6 +218,13 @@ export const Pages: CollectionConfig<'pages'> = { fields: preistraegerIndexFields, label: 'Preisträger Index Page', }, + { + admin: { + condition: (data) => isPressIndexPage(undefined, data), + }, + fields: pressIndexFields, + label: 'Press Index Page', + }, { name: 'meta', label: 'SEO', diff --git a/src/collections/Pages/pressIndexFields.ts b/src/collections/Pages/pressIndexFields.ts new file mode 100644 index 0000000..da469d5 --- /dev/null +++ b/src/collections/Pages/pressIndexFields.ts @@ -0,0 +1,152 @@ +import type { Field } from 'payload' + +import { pressIndexContent } from '@/spa/pressIndexContent' + +const uploadField = (name: string, label: string, description?: string): Field => ({ + name, + type: 'upload', + relationTo: 'media', + label, + admin: description ? { description } : undefined, +}) + +const text = (name: string, label: string, defaultValue?: string): Field => ({ + name, + type: 'text', + label, + defaultValue, +}) + +const textarea = (name: string, label: string, defaultValue?: string): Field => ({ + name, + type: 'textarea', + label, + defaultValue, +}) + +const sectionAdmin = (description: string) => ({ + description, + initCollapsed: true, +}) + +const eventFields: Field[] = [ + text('title', 'Title'), + text('date', 'Date'), + text('location', 'Location'), + text('cat', 'Category'), + text('status', 'Status'), + text('img', 'Image URL or path'), + textarea('desc', 'Description'), + text('slug', 'Route'), +] + +const newsFields: Field[] = [ + text('title', 'Title'), + textarea('excerpt', 'Excerpt'), + text('cat', 'Category'), + text('img', 'Image URL or path'), + text('slug', 'Route'), +] + +export const pressIndexFields: Field[] = [ + { + name: 'pressIndex', + label: 'Press index page content', + type: 'group', + admin: { + description: + 'Edit the Presse listing page chrome. Event and news records are managed through Events and Presse collections; fallback cards remain editable here.', + }, + fields: [ + { + name: 'hero', + label: '01 · Hero', + type: 'group', + admin: sectionAdmin('Hero image, eyebrow, heading, and intro copy.'), + fields: [ + uploadField('image', 'Hero image', `Current frontend image: /images/${pressIndexContent.hero.imageFilename}`), + text('imageAlt', 'Image alt text', pressIndexContent.hero.imageAlt), + text('eyebrow', 'Eyebrow', pressIndexContent.hero.eyebrow), + textarea('heading', 'Heading', pressIndexContent.hero.heading), + textarea('description', 'Description', pressIndexContent.hero.description), + ], + }, + { + name: 'events', + label: '02 · Events section', + type: 'group', + admin: sectionAdmin('Section header, labels, and fallback event rows.'), + fields: [ + text('eyebrow', 'Eyebrow', pressIndexContent.events.eyebrow), + textarea('heading', 'Heading', pressIndexContent.events.heading), + textarea('description', 'Description', pressIndexContent.events.description), + text('detailLabel', 'Detail link label', pressIndexContent.events.detailLabel), + text('openStatusLabel', 'Open status label', pressIndexContent.events.openStatusLabel), + { + name: 'fallbackItems', + label: 'Fallback event rows', + type: 'array', + dbName: 'press_events_fb', + defaultValue: pressIndexContent.events.fallbackItems, + fields: eventFields, + }, + ], + }, + { + name: 'news', + label: '03 · Newsroom section', + type: 'group', + admin: sectionAdmin('Section header, read-more label, and fallback news cards.'), + fields: [ + text('eyebrow', 'Eyebrow', pressIndexContent.news.eyebrow), + textarea('heading', 'Heading', pressIndexContent.news.heading), + textarea('description', 'Description', pressIndexContent.news.description), + text('readMoreLabel', 'Read more label', pressIndexContent.news.readMoreLabel), + { + name: 'fallbackItems', + label: 'Fallback news cards', + type: 'array', + dbName: 'press_news_fb', + defaultValue: pressIndexContent.news.fallbackItems, + fields: newsFields, + }, + ], + }, + { + name: 'downloads', + label: '04 · Press material and contact', + type: 'group', + admin: sectionAdmin('Download button and press contact block.'), + fields: [ + text('eyebrow', 'Eyebrow', pressIndexContent.downloads.eyebrow), + textarea('heading', 'Heading', pressIndexContent.downloads.heading), + textarea('description', 'Description', pressIndexContent.downloads.description), + text('kitLabel', 'Kit label', pressIndexContent.downloads.kitLabel), + text('kitMeta', 'Kit metadata', pressIndexContent.downloads.kitMeta), + text('kitUrl', 'Kit URL', pressIndexContent.downloads.kitUrl), + text('kitDownloadName', 'Kit download filename', pressIndexContent.downloads.kitDownloadName), + text('contactEyebrow', 'Contact eyebrow', pressIndexContent.downloads.contactEyebrow), + text('contactName', 'Contact name', pressIndexContent.downloads.contactName), + textarea('contactLines', 'Contact lines', pressIndexContent.downloads.contactLines), + ], + }, + { + name: 'accreditation', + label: '05 · Accreditation form copy', + type: 'group', + admin: sectionAdmin('Labels and success state for the press accreditation form.'), + fields: [ + text('eyebrow', 'Eyebrow', pressIndexContent.accreditation.eyebrow), + textarea('heading', 'Heading', pressIndexContent.accreditation.heading), + textarea('description', 'Description', pressIndexContent.accreditation.description), + text('mediumLabel', 'Medium label', pressIndexContent.accreditation.mediumLabel), + text('nameLabel', 'Name label', pressIndexContent.accreditation.nameLabel), + text('emailLabel', 'Email label', pressIndexContent.accreditation.emailLabel), + text('submitLabel', 'Submit label', pressIndexContent.accreditation.submitLabel), + text('successHeading', 'Success heading', pressIndexContent.accreditation.successHeading), + textarea('successMessage', 'Success message', pressIndexContent.accreditation.successMessage), + ], + }, + ], + }, +] diff --git a/src/payload-types.ts b/src/payload-types.ts index 74a9bfd..b0a6eba 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -1242,6 +1242,95 @@ export interface Page { }; }; }; + /** + * Edit the Presse listing page chrome. Event and news records are managed through Events and Presse collections; fallback cards remain editable here. + */ + pressIndex?: { + /** + * Hero image, eyebrow, heading, and intro copy. + */ + hero?: { + /** + * Current frontend image: /images/gala-dinner.jpg + */ + image?: (number | null) | Media; + imageAlt?: string | null; + eyebrow?: string | null; + heading?: string | null; + description?: string | null; + }; + /** + * Section header, labels, and fallback event rows. + */ + events?: { + eyebrow?: string | null; + heading?: string | null; + description?: string | null; + detailLabel?: string | null; + openStatusLabel?: string | null; + fallbackItems?: + | { + title?: string | null; + date?: string | null; + location?: string | null; + cat?: string | null; + status?: string | null; + img?: string | null; + desc?: string | null; + slug?: string | null; + id?: string | null; + }[] + | null; + }; + /** + * Section header, read-more label, and fallback news cards. + */ + news?: { + eyebrow?: string | null; + heading?: string | null; + description?: string | null; + readMoreLabel?: string | null; + fallbackItems?: + | { + title?: string | null; + excerpt?: string | null; + cat?: string | null; + img?: string | null; + slug?: string | null; + id?: string | null; + }[] + | null; + }; + /** + * Download button and press contact block. + */ + downloads?: { + eyebrow?: string | null; + heading?: string | null; + description?: string | null; + kitLabel?: string | null; + kitMeta?: string | null; + kitUrl?: string | null; + kitDownloadName?: string | null; + contactEyebrow?: string | null; + contactName?: string | null; + contactLines?: string | null; + }; + /** + * Labels and success state for the press accreditation form. + */ + accreditation?: { + eyebrow?: string | null; + heading?: string | null; + description?: string | null; + mediumLabel?: string | null; + nameLabel?: string | null; + emailLabel?: string | null; + submitLabel?: string | null; + successHeading?: string | null; + successMessage?: string | null; + }; + }; meta?: { title?: string | null; /** @@ -3122,6 +3211,86 @@ export interface PagesSelect { }; }; }; + pressIndex?: + | T + | { + hero?: + | T + | { + image?: T; + imageAlt?: T; + eyebrow?: T; + heading?: T; + description?: T; + }; + events?: + | T + | { + eyebrow?: T; + heading?: T; + description?: T; + detailLabel?: T; + openStatusLabel?: T; + fallbackItems?: + | T + | { + title?: T; + date?: T; + location?: T; + cat?: T; + status?: T; + img?: T; + desc?: T; + slug?: T; + id?: T; + }; + }; + news?: + | T + | { + eyebrow?: T; + heading?: T; + description?: T; + readMoreLabel?: T; + fallbackItems?: + | T + | { + title?: T; + excerpt?: T; + cat?: T; + img?: T; + slug?: T; + id?: T; + }; + }; + downloads?: + | T + | { + eyebrow?: T; + heading?: T; + description?: T; + kitLabel?: T; + kitMeta?: T; + kitUrl?: T; + kitDownloadName?: T; + contactEyebrow?: T; + contactName?: T; + contactLines?: T; + }; + accreditation?: + | T + | { + eyebrow?: T; + heading?: T; + description?: T; + mediumLabel?: T; + nameLabel?: T; + emailLabel?: T; + submitLabel?: T; + successHeading?: T; + successMessage?: T; + }; + }; meta?: | T | { diff --git a/src/scripts/preload-press-index-page-cms.ts b/src/scripts/preload-press-index-page-cms.ts new file mode 100644 index 0000000..82b6966 --- /dev/null +++ b/src/scripts/preload-press-index-page-cms.ts @@ -0,0 +1,60 @@ +import 'dotenv/config' + +import config from '@payload-config' +import { getPayload, type Payload } from 'payload' + +import { pressIndexContent } from '@/spa/pressIndexContent' + +const mediaByFilename = async (payload: Payload, filename: string) => { + const result = await payload.find({ + collection: 'media', + limit: 1, + pagination: false, + where: { filename: { equals: filename } }, + }) + return result.docs[0]?.id +} + +async function main() { + const payload = await getPayload({ config }) + + const pageResult = await payload.find({ + collection: 'pages', + limit: 1, + pagination: false, + draft: true, + overrideAccess: true, + where: { + or: [{ spaPath: { equals: '/presse' } }, { slug: { equals: 'presse' } }, { slug: { equals: 'press' } }], + }, + }) + + const page = pageResult.docs[0] + if (!page) throw new Error('Press index page not found. Expected spaPath=/presse or slug=presse/press.') + + const heroImage = await mediaByFilename(payload, pressIndexContent.hero.imageFilename) + const { imageFilename: _heroFilename, ...heroContent } = pressIndexContent.hero + + await payload.update({ + collection: 'pages', + id: page.id, + overrideAccess: true, + context: { disableRevalidate: true }, + data: { + pressIndex: { + ...pressIndexContent, + hero: { + ...heroContent, + image: heroImage, + }, + }, + } as never, + }) + + payload.logger.info(`Preloaded Press index CMS fields for page ${page.id}`) +} + +main().catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/src/spa/pages/Press.tsx b/src/spa/pages/Press.tsx index ded9cbc..23ace13 100644 --- a/src/spa/pages/Press.tsx +++ b/src/spa/pages/Press.tsx @@ -3,9 +3,10 @@ import { Link } from '@/spa/router'; import { Calendar, MapPin, Download, ArrowRight, FileText } from 'lucide-react'; import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg'; import { useIsMobile } from '@/spa/hooks/useIsMobile'; -import { useCmsCollection } from '@/spa/cmsRoute'; -import { docImageUrl } from '@/spa/cmsMediaField'; +import { useCmsCollection, useCmsRoute, type CmsRouteDoc } from '@/spa/cmsRoute'; +import { docImageUrl, mediaAlt, mediaUrl } from '@/spa/cmsMediaField'; import Image from '@/spa/components/ui/UnoptimizedImage' +import { pressIndexContent } from '@/spa/pressIndexContent'; const NAVY = '#111D55'; const GOLD = '#EFBF04'; @@ -13,67 +14,52 @@ const CREAM = '#E4E2E3'; const FF = '"IBM Plex Sans", sans-serif'; const FB = '"Inter", sans-serif'; -const events = [ - { - title: 'Preisverleihung 2026', - date: '22. Okt 2026', - location: 'München, Residenz', - cat: 'Gala / Event', - status: 'In Planung', - img: '/images/buehne-moderatoren.jpg', - desc: 'Der glanzvolle Höhepunkt des Jahres. Die Verleihung der Preise in der prachtvollen Kulisse der Residenz München.', - slug: '/presse/events/preisverleihung-2026', - }, - { - title: 'Mittelstands-Gipfel', - date: '15. Juni 2026', - location: 'Nürnberg', - cat: 'Workshop', - status: 'Anmeldung offen', - img: '/images/networking-innenhof.jpg', - desc: 'Regionaler Austausch und Best-Practices für bayerische Unternehmen auf dem Weg zum Preis.', - slug: '/presse/events/mittelstands-gipfel-2026', - }, - { - title: 'Nominierten-Auswahl', - date: '04. Aug 2026', - location: 'Regensburg', - cat: 'Jury-Sitzung', - status: 'Intern', - img: '/images/saal-gedeckt.jpg', - desc: 'Das Gremium sichtet die Ergebnisse der Audits und legt die Nominierten für die Hauptpreise fest.', - slug: '/presse/events/nominierten-auswahl-2026', - }, -]; +type PressIndexCms = Partial & { + hero?: Partial & { image?: unknown } +} -const news = [ - { - title: 'Wie Bayerns KMU die KI nutzen', - excerpt: - 'Ein Deep-Dive in die Bewerbungsunterlagen 2024 zeigt: Der Mittelstand treibt die Digitalisierung aktiv voran.', - cat: 'Innovation', - img: '/images/gala-saal-overview.jpg', - slug: '/presse/blog/ki-nutzung-bayerischer-kmu', - }, - { - title: 'Start der Ehrenamts-Initiative', - excerpt: - 'Gemeinsam mit unseren Partnern fördern wir soziale Projekte mittelständischer Unternehmen.', - cat: 'Engagement', - img: '/images/networking-innenhof.jpg', - slug: '/presse/blog/ehrenamts-initiative-2026', - }, - { - title: 'Bayerische Wirtschaft wächst', - excerpt: - 'Neue Prognosen zeigen ein stabiles Wachstum für den Mittelstand – gute Aussichten für die Awards.', - cat: 'Wirtschaft', - img: '/images/gala-dinner.jpg', - slug: '/presse/blog/bayerische-wirtschaft-waechst', - }, -]; +type PressEventItem = (typeof pressIndexContent.events.fallbackItems)[number] +type PressNewsItem = (typeof pressIndexContent.news.fallbackItems)[number] -function NewsCard({ item, idx, isMobile }: { item: typeof news[0]; idx: number; isMobile: boolean }) { +const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback; +const fallbackArray = (value: unknown, fallback: T[]) => Array.isArray(value) && value.length ? value as T[] : fallback; + +const statusLabels: Record = { + geplant: 'In Planung', + 'anmeldung-offen': 'Anmeldung offen', + intern: 'Intern', + abgeschlossen: 'Abgeschlossen', +} + +const normalizeStatus = (status: unknown) => { + const value = fallbackText(status, 'Geplant') + return statusLabels[value] || value +} + +const eventFromCms = (event: CmsRouteDoc): PressEventItem => ({ + title: String(event.title || 'Event'), + date: String(event.date || 'Termin folgt'), + location: String(event.location || event.venue || 'Bayern'), + cat: String(event.category || 'Event'), + status: normalizeStatus(event.status), + img: docImageUrl(event, '/images/buehne-moderatoren.jpg'), + desc: String(event.description || event.meta?.description || ''), + slug: String(event.spaPath || `/presse/events/${event.slug}`), +}) + +const postFromCms = (post: CmsRouteDoc): PressNewsItem => ({ + title: String(post.title || 'Presse'), + excerpt: String(post.excerpt || post.meta?.description || ''), + cat: String(post.cat || 'Presse'), + img: docImageUrl(post, '/images/gala-saal-overview.jpg', 'heroImage'), + slug: String(post.spaPath || `/presse/blog/${post.slug}`), +}) + +function Lines({ text }: { text: string }) { + return <>{text.split('\n').map((line, i) => {i > 0 &&
}{line}
)} +} + +function NewsCard({ item, idx, isMobile, readMoreLabel, total }: { item: PressNewsItem; idx: number; isMobile: boolean; readMoreLabel: string; total: number }) { const [hovered, setHovered] = React.useState(false); return ( 0 ? '1px solid rgba(255,255,255,0.07)' : 'none'), - borderBottom: isMobile && idx < news.length - 1 ? '1px solid rgba(255,255,255,0.07)' : 'none', + borderBottom: isMobile && idx < total - 1 ? '1px solid rgba(255,255,255,0.07)' : 'none', textDecoration: 'none', }} > @@ -177,7 +163,7 @@ function NewsCard({ item, idx, isMobile }: { item: typeof news[0]; idx: number; gap: 5, }} > - Weiterlesen + {readMoreLabel} @@ -217,41 +203,23 @@ function DarkInput(props: React.InputHTMLAttributes) { const Press: React.FC = () => { const isMobile = useIsMobile(); + const cms = (useCmsRoute()?.doc?.pressIndex || {}) as PressIndexCms; + const hero = { ...pressIndexContent.hero, ...(cms.hero || {}) }; + const eventsSection = { ...pressIndexContent.events, ...(cms.events || {}) }; + const newsSection = { ...pressIndexContent.news, ...(cms.news || {}) }; + const downloads = { ...pressIndexContent.downloads, ...(cms.downloads || {}) }; + const accreditation = { ...pressIndexContent.accreditation, ...(cms.accreditation || {}) }; + const contactLines = fallbackText(downloads.contactLines, pressIndexContent.downloads.contactLines).split('\n'); const cmsEvents = useCmsCollection('events'); const cmsPosts = useCmsCollection('posts'); const combinedEvents = React.useMemo(() => { - const existing = new Set(events.map((event) => event.slug.split('/').pop())); - return [ - ...events, - ...cmsEvents - .filter((event) => event.slug && !existing.has(String(event.slug))) - .map((event) => ({ - title: String(event.title || 'Event'), - date: String(event.date || 'Termin folgt'), - location: String(event.location || event.venue || 'Bayern'), - cat: String(event.category || 'Event'), - status: String(event.status || 'Geplant'), - img: docImageUrl(event, '/images/buehne-moderatoren.jpg'), - desc: String(event.description || event.meta?.description || ''), - slug: String(event.spaPath || `/presse/events/${event.slug}`), - })), - ]; - }, [cmsEvents]); + if (cmsEvents.length) return cmsEvents.map(eventFromCms); + return fallbackArray(eventsSection.fallbackItems, pressIndexContent.events.fallbackItems); + }, [cmsEvents, eventsSection.fallbackItems]); const combinedNews = React.useMemo(() => { - const existing = new Set(news.map((item) => item.slug.split('/').pop())); - return [ - ...news, - ...cmsPosts - .filter((post) => post.slug && !existing.has(String(post.slug))) - .map((post) => ({ - title: String(post.title || 'Presse'), - excerpt: String(post.excerpt || post.meta?.description || ''), - cat: String(post.cat || 'Presse'), - img: docImageUrl(post, '/images/gala-saal-overview.jpg', 'heroImage'), - slug: String(post.spaPath || `/presse/blog/${post.slug}`), - })), - ]; - }, [cmsPosts]); + if (cmsPosts.length) return cmsPosts.map(postFromCms); + return fallbackArray(newsSection.fallbackItems, pressIndexContent.news.fallbackItems); + }, [cmsPosts, newsSection.fallbackItems]); const [hoveredEvent, setHoveredEvent] = useState(null); const [dlHovered, setDlHovered] = useState(false); const [submitHovered, setSubmitHovered] = useState(false); @@ -283,8 +251,8 @@ const Press: React.FC = () => { }} > Presse BMP
{ color: GOLD, }} > - Presse & Newsroom + {fallbackText(hero.eyebrow, pressIndexContent.hero.eyebrow)}

{ overflowWrap: 'anywhere', }} > - EVENTS &
BERICHTERSTATTUNG. +

{ margin: 0, }} > - Alle Termine, Pressemitteilungen und Medienmaterial rund um den Bayerischen Mittelstandspreis. + {fallbackText(hero.description, pressIndexContent.hero.description)}

@@ -380,7 +348,7 @@ const Press: React.FC = () => { marginBottom: 16, }} > - Termine 2026 + {fallbackText(eventsSection.eyebrow, pressIndexContent.events.eyebrow)}

{ margin: 0, }} > - EVENTS RUND UM DEN PREIS. +

@@ -406,7 +374,7 @@ const Press: React.FC = () => { margin: 0, }} > - Von der Jurysitzung bis zur festlichen Gala – alle Veranstaltungen auf einen Blick. + {fallbackText(eventsSection.description, pressIndexContent.events.description)}

@@ -429,7 +397,7 @@ const Press: React.FC = () => { {/* Left – image cell */}
{event.title} -
+
{event.status}
@@ -449,7 +417,7 @@ const Press: React.FC = () => {

{event.desc}

- Details + {fallbackText(eventsSection.detailLabel, pressIndexContent.events.detailLabel)}
@@ -481,7 +449,7 @@ const Press: React.FC = () => { marginBottom: 16, }} > - Newsroom + {fallbackText(newsSection.eyebrow, pressIndexContent.news.eyebrow)}

{ margin: 0, }} > - NACHRICHTEN & EINBLICKE. +

@@ -507,7 +475,7 @@ const Press: React.FC = () => { margin: 0, }} > - Berichte, Hintergründe und Einblicke rund um den bayerischen Mittelstand und den BMP. + {fallbackText(newsSection.description, pressIndexContent.news.description)}

@@ -515,7 +483,7 @@ const Press: React.FC = () => { {/* 3-column news grid */}
{combinedNews.map((item, idx) => ( - + ))}
@@ -547,7 +515,7 @@ const Press: React.FC = () => { marginBottom: 16, }} > - Pressekontakt & Material + {fallbackText(downloads.eyebrow, pressIndexContent.downloads.eyebrow)}

{ margin: '0 0 20px', }} > - PRESSE-MATERIAL & KONTAKT. +

{/* Gold divider */} @@ -577,13 +545,13 @@ const Press: React.FC = () => { marginBottom: 48, }} > - Laden Sie unser offizielles Material herunter oder kontaktieren Sie direkt unser Presseteam für individuelle Anfragen. + {fallbackText(downloads.description, pressIndexContent.downloads.description)}

{/* Download button */} setDlHovered(true)} onMouseLeave={() => setDlHovered(false)} style={{ @@ -622,10 +590,10 @@ const Press: React.FC = () => { marginBottom: 3, }} > - Download Presse-Kit + {fallbackText(downloads.kitLabel, pressIndexContent.downloads.kitLabel)}
- Print_BMP.zip – 1,5 MB + {fallbackText(downloads.kitMeta, pressIndexContent.downloads.kitMeta)}
@@ -643,7 +611,7 @@ const Press: React.FC = () => { marginBottom: 12, }} > - Pressekontakt + {fallbackText(downloads.contactEyebrow, pressIndexContent.downloads.contactEyebrow)}
{ marginBottom: 4, }} > - Tanja Meier + {fallbackText(downloads.contactName, pressIndexContent.downloads.contactName)}
{ overflowWrap: 'anywhere', }} > - presse@bmp-bayern.de -
- +49 89 123 456 99 + {contactLines.map((line, index) => ( + + {line} + {index < contactLines.length - 1 &&
} +
+ ))}
@@ -717,7 +688,7 @@ const Press: React.FC = () => { margin: '0 0 16px', }} > - Anfrage eingegangen + {fallbackText(accreditation.successHeading, pressIndexContent.accreditation.successHeading)}

{ margin: 0, }} > - Vielen Dank für Ihre Akkreditierungsanfrage. Unser Presseteam meldet sich zeitnah bei Ihnen. + {fallbackText(accreditation.successMessage, pressIndexContent.accreditation.successMessage)}

) : ( @@ -744,7 +715,7 @@ const Press: React.FC = () => { marginBottom: 20, }} > - Akkreditierung + {fallbackText(accreditation.eyebrow, pressIndexContent.accreditation.eyebrow)}

{ margin: '0 0 16px', }} > - AKKREDITIERUNG ANFRAGEN. +

{/* Gold divider */} @@ -773,7 +744,7 @@ const Press: React.FC = () => { marginBottom: 36, }} > - Melden Sie sich für unsere Presse-Verteiler an oder fordern Sie eine Akkreditierung für die Gala-Verleihung an. + {fallbackText(accreditation.description, pressIndexContent.accreditation.description)}

{/* Form fields */} @@ -789,7 +760,7 @@ const Press: React.FC = () => { color: 'rgba(255,255,255,0.35)', }} > - Medium / Redaktion * + {fallbackText(accreditation.mediumLabel, pressIndexContent.accreditation.mediumLabel)} { color: 'rgba(255,255,255,0.35)', }} > - Ihr Name * + {fallbackText(accreditation.nameLabel, pressIndexContent.accreditation.nameLabel)} { color: 'rgba(255,255,255,0.35)', }} > - E-Mail-Adresse * + {fallbackText(accreditation.emailLabel, pressIndexContent.accreditation.emailLabel)} { transition: 'background 0.15s, box-shadow 0.2s', }} > - Akkreditierung anfragen + {fallbackText(accreditation.submitLabel, pressIndexContent.accreditation.submitLabel)} diff --git a/src/spa/pressIndexContent.ts b/src/spa/pressIndexContent.ts new file mode 100644 index 0000000..a06aa02 --- /dev/null +++ b/src/spa/pressIndexContent.ts @@ -0,0 +1,100 @@ +export const pressIndexContent = { + hero: { + imageFilename: 'gala-dinner.jpg', + imageAlt: 'Presse BMP', + eyebrow: 'Presse & Newsroom', + heading: 'EVENTS &\nBERICHTERSTATTUNG.', + description: 'Alle Termine, Pressemitteilungen und Medienmaterial rund um den Bayerischen Mittelstandspreis.', + }, + events: { + eyebrow: 'Termine 2026', + heading: 'EVENTS RUND UM DEN PREIS.', + description: 'Von der Jurysitzung bis zur festlichen Gala – alle Veranstaltungen auf einen Blick.', + detailLabel: 'Details', + openStatusLabel: 'Anmeldung offen', + fallbackItems: [ + { + title: 'Preisverleihung 2026', + date: '22. Okt 2026', + location: 'München, Residenz', + cat: 'Gala / Event', + status: 'In Planung', + img: '/images/buehne-moderatoren.jpg', + desc: 'Der glanzvolle Höhepunkt des Jahres. Die Verleihung der Preise in der prachtvollen Kulisse der Residenz München.', + slug: '/presse/events/preisverleihung-2026', + }, + { + title: 'Mittelstands-Gipfel', + date: '15. Juni 2026', + location: 'Nürnberg', + cat: 'Workshop', + status: 'Anmeldung offen', + img: '/images/networking-innenhof.jpg', + desc: 'Regionaler Austausch und Best-Practices für bayerische Unternehmen auf dem Weg zum Preis.', + slug: '/presse/events/mittelstands-gipfel-2026', + }, + { + title: 'Nominierten-Auswahl', + date: '04. Aug 2026', + location: 'Regensburg', + cat: 'Jury-Sitzung', + status: 'Intern', + img: '/images/saal-gedeckt.jpg', + desc: 'Das Gremium sichtet die Ergebnisse der Audits und legt die Nominierten für die Hauptpreise fest.', + slug: '/presse/events/nominierten-auswahl-2026', + }, + ], + }, + news: { + eyebrow: 'Newsroom', + heading: 'NACHRICHTEN & EINBLICKE.', + description: 'Berichte, Hintergründe und Einblicke rund um den bayerischen Mittelstand und den BMP.', + readMoreLabel: 'Weiterlesen', + fallbackItems: [ + { + title: 'Wie Bayerns KMU die KI nutzen', + excerpt: 'Ein Deep-Dive in die Bewerbungsunterlagen 2024 zeigt: Der Mittelstand treibt die Digitalisierung aktiv voran.', + cat: 'Innovation', + img: '/images/gala-saal-overview.jpg', + slug: '/presse/blog/ki-nutzung-bayerischer-kmu', + }, + { + title: 'Start der Ehrenamts-Initiative', + excerpt: 'Gemeinsam mit unseren Partnern fördern wir soziale Projekte mittelständischer Unternehmen.', + cat: 'Engagement', + img: '/images/networking-innenhof.jpg', + slug: '/presse/blog/ehrenamts-initiative-2026', + }, + { + title: 'Bayerische Wirtschaft wächst', + excerpt: 'Neue Prognosen zeigen ein stabiles Wachstum für den Mittelstand – gute Aussichten für die Awards.', + cat: 'Wirtschaft', + img: '/images/gala-dinner.jpg', + slug: '/presse/blog/bayerische-wirtschaft-waechst', + }, + ], + }, + downloads: { + eyebrow: 'Pressekontakt & Material', + heading: 'PRESSE-MATERIAL & KONTAKT.', + description: 'Laden Sie unser offizielles Material herunter oder kontaktieren Sie direkt unser Presseteam für individuelle Anfragen.', + kitLabel: 'Download Presse-Kit', + kitMeta: 'Print_BMP.zip – 1,5 MB', + kitUrl: '/Print_BMP.zip', + kitDownloadName: 'Print_BMP.zip', + contactEyebrow: 'Pressekontakt', + contactName: 'Tanja Meier', + contactLines: 'presse@bmp-bayern.de\n+49 89 123 456 99', + }, + accreditation: { + eyebrow: 'Akkreditierung', + heading: 'AKKREDITIERUNG ANFRAGEN.', + description: 'Melden Sie sich für unsere Presse-Verteiler an oder fordern Sie eine Akkreditierung für die Gala-Verleihung an.', + mediumLabel: 'Medium / Redaktion *', + nameLabel: 'Ihr Name *', + emailLabel: 'E-Mail-Adresse *', + submitLabel: 'Akkreditierung anfragen', + successHeading: 'Anfrage eingegangen', + successMessage: 'Vielen Dank für Ihre Akkreditierungsanfrage. Unser Presseteam meldet sich zeitnah bei Ihnen.', + }, +}