diff --git a/src/collections/Pages/aboutFields.ts b/src/collections/Pages/aboutFields.ts index d5284f0..2d86ddf 100644 --- a/src/collections/Pages/aboutFields.ts +++ b/src/collections/Pages/aboutFields.ts @@ -129,6 +129,7 @@ export const aboutFields: Field[] = [ type: 'group', admin: sectionAdmin('Header copy and editor-selected winners for the photo grid.'), fields: [ + uploadField('fallbackImage', 'Fallback winner image', `Current frontend fallback image: /images/${aboutContent.highlights.fallbackImageFilename}`), text('eyebrow', 'Eyebrow', aboutContent.highlights.eyebrow), textarea('heading', 'Heading', aboutContent.highlights.heading), ctaGroup('cta', 'CTA', aboutContent.highlights.cta.label, aboutContent.highlights.cta.url), diff --git a/src/payload-types.ts b/src/payload-types.ts index 2ff87b6..3941f10 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -1219,6 +1219,10 @@ export interface Page { * Header copy and editor-selected winners for the photo grid. */ highlights?: { + /** + * Current frontend fallback image: /images/gala-saal-overview.jpg + */ + fallbackImage?: (number | null) | Media; eyebrow?: string | null; heading?: string | null; cta?: { @@ -4101,6 +4105,7 @@ export interface PagesSelect { highlights?: | T | { + fallbackImage?: T; eyebrow?: T; heading?: T; cta?: diff --git a/src/scripts/preload-about-page-cms.ts b/src/scripts/preload-about-page-cms.ts index 7f2a3f9..638bca6 100644 --- a/src/scripts/preload-about-page-cms.ts +++ b/src/scripts/preload-about-page-cms.ts @@ -32,10 +32,11 @@ async function main() { const page = pageResult.docs[0] if (!page) throw new Error('About page not found. Expected spaPath=/der-bmp or slug=der-bmp/about.') - const [heroImage, prizeImage, mittelstandImage, winnerResult] = await Promise.all([ + const [heroImage, prizeImage, mittelstandImage, fallbackWinnerImage, winnerResult] = await Promise.all([ mediaByFilename(payload, aboutContent.hero.backgroundImageFilename), mediaByFilename(payload, aboutContent.prize.imageFilename), mediaByFilename(payload, aboutContent.mittelstand.imageFilename), + mediaByFilename(payload, aboutContent.highlights.fallbackImageFilename), payload.find({ collection: 'preistraeger', limit: 8, @@ -49,6 +50,7 @@ async function main() { const { backgroundImageFilename: _heroFilename, ...heroContent } = aboutContent.hero const { imageFilename: _prizeFilename, ...prizeContent } = aboutContent.prize const { imageFilename: _mittelstandFilename, ...mittelstandContent } = aboutContent.mittelstand + const { fallbackImageFilename: _highlightFallbackFilename, ...highlightsContent } = aboutContent.highlights await payload.update({ collection: 'pages', @@ -73,7 +75,8 @@ async function main() { body: aboutContent.mittelstand.body.map((text) => ({ text })), }, highlights: { - ...aboutContent.highlights, + ...highlightsContent, + fallbackImage: fallbackWinnerImage, selectedWinners: winnerResult.docs.map((winner) => winner.id), }, }, diff --git a/src/spa/aboutContent.ts b/src/spa/aboutContent.ts index a95be47..94fee6e 100644 --- a/src/spa/aboutContent.ts +++ b/src/spa/aboutContent.ts @@ -44,6 +44,7 @@ export const aboutContent = { ], }, highlights: { + fallbackImageFilename: 'gala-saal-overview.jpg', eyebrow: 'Preisträger-Highlights', heading: 'AUSGEZEICHNETE 2025', cta: { label: 'Alle Preisträger', url: '/preistraeger' }, diff --git a/src/spa/pages/About.tsx b/src/spa/pages/About.tsx index 1672207..5bb443b 100644 --- a/src/spa/pages/About.tsx +++ b/src/spa/pages/About.tsx @@ -21,7 +21,7 @@ type AboutCms = Partial & { hero?: Partial & { backgroundImage?: unknown } prize?: Partial & { image?: unknown } mittelstand?: Partial & { image?: unknown } - highlights?: Partial & { selectedWinners?: unknown[] | null } + highlights?: Partial & { fallbackImage?: unknown; selectedWinners?: unknown[] | null } } type TextRow = { text?: string | null } @@ -63,7 +63,7 @@ function HighlightedText({ text, highlight }: { text: string; highlight: string ) } -const normalizeWinner = (doc: CmsRouteDoc | undefined): Winner | undefined => { +const normalizeWinner = (doc: CmsRouteDoc | undefined, fallbackImage: string): Winner | undefined => { if (!doc) return undefined return { id: String(doc.id), @@ -72,7 +72,7 @@ const normalizeWinner = (doc: CmsRouteDoc | undefined): Winner | undefined => { category: String(doc.category || 'Preisträger'), year: Number(doc.year || aboutContent.highlights.year), type: String(doc.awardType || 'Auszeichnung'), - img: mediaUrl(doc.image, '/images/gala-saal-overview.jpg'), + img: mediaUrl(doc.image, fallbackImage), shortDesc: String(doc.shortDesc || doc.description || doc.meta?.description || ''), longDesc: String(doc.longDesc || doc.description || doc.meta?.description || ''), quote: String(doc.quote || ''), @@ -85,10 +85,10 @@ const normalizeWinner = (doc: CmsRouteDoc | undefined): Winner | undefined => { } } -const winnerFromRelationship = (entry: unknown, allDocs: CmsRouteDoc[]) => { - if (entry && typeof entry === 'object') return normalizeWinner(entry as CmsRouteDoc) +const winnerFromRelationship = (entry: unknown, allDocs: CmsRouteDoc[], fallbackImage: string) => { + if (entry && typeof entry === 'object') return normalizeWinner(entry as CmsRouteDoc, fallbackImage) const doc = allDocs.find((item) => String(item.id) === String(entry)) - return normalizeWinner(doc) + return normalizeWinner(doc, fallbackImage) } const About: React.FC = () => { @@ -98,16 +98,20 @@ const About: React.FC = () => { const hero = { ...aboutContent.hero, ...(cms.hero || {}) } const prize = { ...aboutContent.prize, ...(cms.prize || {}) } const mittelstand = { ...aboutContent.mittelstand, ...(cms.mittelstand || {}) } - const highlights = { ...aboutContent.highlights, ...(cms.highlights || {}) } + const highlights = useMemo(() => ({ ...aboutContent.highlights, ...(cms.highlights || {}) }), [cms.highlights]) const testimonials = { ...aboutContent.testimonials, ...(cms.testimonials || {}) } const history = { ...aboutContent.history, ...(cms.history || {}) } const goals = { ...aboutContent.goals, ...(cms.goals || {}) } const values = { ...aboutContent.values, ...(cms.values || {}) } const cta = { ...aboutContent.cta, ...(cms.cta || {}) } + const highlightFallbackImage = mediaUrl( + highlights.fallbackImage, + `/images/${aboutContent.highlights.fallbackImageFilename}`, + ) const highlightWinners = useMemo(() => { const selected = fallbackArray(highlights.selectedWinners, []) - .map((entry) => winnerFromRelationship(entry, cmsWinners)) + .map((entry) => winnerFromRelationship(entry, cmsWinners, highlightFallbackImage)) .filter((winner): winner is Winner => Boolean(winner)) if (selected.length) return selected.slice(0, 8) @@ -116,12 +120,12 @@ const About: React.FC = () => { Boolean(winner) && winner?.year === Number(highlights.year || aboutContent.highlights.year) const cmsByYear = cmsWinners - .map(normalizeWinner) + .map((winner) => normalizeWinner(winner, highlightFallbackImage)) .filter(isWinnerFromYear) .slice(0, 8) return cmsByYear.length ? cmsByYear : FALLBACK_HIGHLIGHTS - }, [cmsWinners, highlights.selectedWinners, highlights.year]) + }, [cmsWinners, highlightFallbackImage, highlights]) return (