feat: add preistraeger placeholder image and related database migrations

- Introduced a new SVG placeholder image for preistraeger without an image.
- Created migration scripts to add download zip and gallery features to the preistraeger table.
- Added site settings for preistraeger placeholder image in the database.
- Implemented a utility function to retrieve the preistraeger placeholder image URL in the SPA.
This commit is contained in:
syntaxbullet
2026-07-07 12:53:00 +02:00
parent 2842caa936
commit 4167730fc2
15 changed files with 10903 additions and 25 deletions

View File

@@ -11,6 +11,7 @@ import { WINNERS, type Winner } from '@/spa/data/winners'
import { useIsMobile } from '@/spa/hooks/useIsMobile'
import { Link } from '@/spa/router'
import { aboutContent } from '@/spa/aboutContent'
import { usePreistraegerPlaceholderImage } from '@/spa/preistraegerPlaceholder'
const FF = '"IBM Plex Sans", sans-serif'
const FB = '"Inter", sans-serif'
@@ -83,7 +84,7 @@ function HighlightedText({ text, highlight }: { text: string; highlight: string
const normalizeWinner = (
doc: CmsRouteDoc | undefined,
fallbackImage: string,
placeholderImage: string,
fallbackWinner: typeof aboutContent.highlights.fallbackWinner,
): Winner | undefined => {
if (!doc) return undefined
@@ -94,7 +95,7 @@ const normalizeWinner = (
category: String(doc.category || fallbackWinner.category),
year: Number(doc.year || aboutContent.highlights.year),
type: String(doc.awardType || fallbackWinner.awardType),
img: mediaUrl(doc.image, fallbackImage),
img: mediaUrl(doc.image, placeholderImage),
shortDesc: String(doc.shortDesc || doc.description || doc.meta?.description || ''),
longDesc: String(doc.longDesc || doc.description || doc.meta?.description || ''),
quote: String(doc.quote || ''),
@@ -110,16 +111,17 @@ const normalizeWinner = (
const winnerFromRelationship = (
entry: unknown,
allDocs: CmsRouteDoc[],
fallbackImage: string,
placeholderImage: string,
fallbackWinner: typeof aboutContent.highlights.fallbackWinner,
) => {
if (entry && typeof entry === 'object') return normalizeWinner(entry as CmsRouteDoc, fallbackImage, fallbackWinner)
if (entry && typeof entry === 'object') return normalizeWinner(entry as CmsRouteDoc, placeholderImage, fallbackWinner)
const doc = allDocs.find((item) => String(item.id) === String(entry))
return normalizeWinner(doc, fallbackImage, fallbackWinner)
return normalizeWinner(doc, placeholderImage, fallbackWinner)
}
const About: React.FC = () => {
const isMobile = useIsMobile()
const placeholderImage = usePreistraegerPlaceholderImage()
const cms = (useCmsRoute()?.doc?.about || {}) as AboutCms
const cmsWinners = useCmsCollection('preistraeger')
const cmsPages = useCmsCollection('pages')
@@ -135,10 +137,6 @@ const About: React.FC = () => {
const participationPage = cmsPages.find(isParticipationPage)
const participationAwardsGrid = (participationPage?.participation as { awardsGrid?: AwardsGridContent } | undefined)?.awardsGrid
const showSecondaryCta = !HIDE_MEMBERSHIP_ABOUT_CTA || !isMembershipCta(cta.secondaryCta)
const highlightFallbackImage = mediaUrl(
highlights.fallbackImage,
`/images/${aboutContent.highlights.fallbackImageFilename}`,
)
const highlightFallbackWinner = useMemo(
() => ({ ...aboutContent.highlights.fallbackWinner, ...(highlights.fallbackWinner || {}) }),
[highlights],
@@ -146,7 +144,7 @@ const About: React.FC = () => {
const highlightWinners = useMemo(() => {
const selected = fallbackArray<unknown>(highlights.selectedWinners, [])
.map((entry) => winnerFromRelationship(entry, cmsWinners, highlightFallbackImage, highlightFallbackWinner))
.map((entry) => winnerFromRelationship(entry, cmsWinners, placeholderImage, highlightFallbackWinner))
.filter((winner): winner is Winner => Boolean(winner))
if (selected.length) return selected.slice(0, 8)
@@ -155,12 +153,12 @@ const About: React.FC = () => {
Boolean(winner) && winner?.year === Number(highlights.year || aboutContent.highlights.year)
const cmsByYear = cmsWinners
.map((winner) => normalizeWinner(winner, highlightFallbackImage, highlightFallbackWinner))
.map((winner) => normalizeWinner(winner, placeholderImage, highlightFallbackWinner))
.filter(isWinnerFromYear)
.slice(0, 8)
return cmsByYear.length ? cmsByYear : FALLBACK_HIGHLIGHTS
}, [cmsWinners, highlightFallbackImage, highlightFallbackWinner, highlights])
}, [cmsWinners, highlightFallbackWinner, highlights, placeholderImage])
return (
<div className="animate-fade-in">