feat: manage participation fallback labels via payload

This commit is contained in:
syntaxbullet
2026-06-22 12:30:34 +02:00
parent 6676610e5e
commit 53fc63b486
5 changed files with 32 additions and 9 deletions

View File

@@ -109,6 +109,7 @@ export const participationFields: Field[] = [
text('eyebrow', 'Eyebrow', participationContent.process.eyebrow), text('eyebrow', 'Eyebrow', participationContent.process.eyebrow),
textarea('heading', 'Heading', participationContent.process.heading), textarea('heading', 'Heading', participationContent.process.heading),
textarea('description', 'Intro paragraph', participationContent.process.description), textarea('description', 'Intro paragraph', participationContent.process.description),
text('stepPrefix', 'Step label prefix', participationContent.process.stepPrefix),
text('scrollHint', 'Desktop scroll hint', participationContent.process.scrollHint), text('scrollHint', 'Desktop scroll hint', participationContent.process.scrollHint),
text('progressLabel', 'Desktop progress label', participationContent.process.progressLabel), text('progressLabel', 'Desktop progress label', participationContent.process.progressLabel),
{ {
@@ -184,6 +185,12 @@ export const participationFields: Field[] = [
textarea('heading', 'Heading', participationContent.applicationWays.heading), textarea('heading', 'Heading', participationContent.applicationWays.heading),
textarea('description', 'Intro paragraph', participationContent.applicationWays.description), textarea('description', 'Intro paragraph', participationContent.applicationWays.description),
text('recommendedLabel', 'Featured card label', participationContent.applicationWays.recommendedLabel), text('recommendedLabel', 'Featured card label', participationContent.applicationWays.recommendedLabel),
ctaGroup(
'fallbackCta',
'Fallback CTA for path cards',
participationContent.applicationWays.fallbackCta.label,
participationContent.applicationWays.fallbackCta.url,
),
{ {
name: 'institutions', name: 'institutions',
label: 'Proposal institutions', label: 'Proposal institutions',
@@ -222,7 +229,7 @@ export const participationFields: Field[] = [
dbName: 'way_facts', dbName: 'way_facts',
fields: factFields, fields: factFields,
}, },
ctaGroup('cta', 'CTA', 'Zum Formular', '#bewerben'), ctaGroup('cta', 'CTA', participationContent.applicationWays.fallbackCta.label, participationContent.applicationWays.fallbackCta.url),
], ],
}, },
], ],

View File

@@ -732,6 +732,7 @@ export interface Page {
eyebrow?: string | null; eyebrow?: string | null;
heading?: string | null; heading?: string | null;
description?: string | null; description?: string | null;
stepPrefix?: string | null;
scrollHint?: string | null; scrollHint?: string | null;
progressLabel?: string | null; progressLabel?: string | null;
steps?: steps?:
@@ -872,6 +873,10 @@ export interface Page {
heading?: string | null; heading?: string | null;
description?: string | null; description?: string | null;
recommendedLabel?: string | null; recommendedLabel?: string | null;
fallbackCta?: {
label?: string | null;
url?: string | null;
};
institutions?: institutions?:
| { | {
text?: string | null; text?: string | null;
@@ -3711,6 +3716,7 @@ export interface PagesSelect<T extends boolean = true> {
eyebrow?: T; eyebrow?: T;
heading?: T; heading?: T;
description?: T; description?: T;
stepPrefix?: T;
scrollHint?: T; scrollHint?: T;
progressLabel?: T; progressLabel?: T;
steps?: steps?:
@@ -3796,6 +3802,12 @@ export interface PagesSelect<T extends boolean = true> {
heading?: T; heading?: T;
description?: T; description?: T;
recommendedLabel?: T; recommendedLabel?: T;
fallbackCta?:
| T
| {
label?: T;
url?: T;
};
institutions?: institutions?:
| T | T
| { | {

View File

@@ -38,6 +38,7 @@ export default function WegZurAuszeichnungSection({ content }: { content?: Proce
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const section = { ...participationContent.process, ...(content || {}) }; const section = { ...participationContent.process, ...(content || {}) };
const steps = fallbackArray(section.steps, participationContent.process.steps); const steps = fallbackArray(section.steps, participationContent.process.steps);
const stepPrefix = fallbackText(section.stepPrefix, participationContent.process.stepPrefix);
// ── MOBILE: stacked vertical list (no scroll-jack, no horizontal overflow) ── // ── MOBILE: stacked vertical list (no scroll-jack, no horizontal overflow) ──
if (isMobile) { if (isMobile) {
@@ -66,7 +67,7 @@ export default function WegZurAuszeichnungSection({ content }: { content?: Proce
</div> </div>
<div> <div>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: GOLD, letterSpacing: '0.18em', textTransform: 'uppercase' }}>Schritt {fallbackText(item.step, '')}</span> <span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: GOLD, letterSpacing: '0.18em', textTransform: 'uppercase' }}>{stepPrefix} {fallbackText(item.step, '')}</span>
<span style={{ width: 1, height: 11, background: 'rgba(255,255,255,0.2)' }} /> <span style={{ width: 1, height: 11, background: 'rgba(255,255,255,0.2)' }} />
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(255,255,255,0.62)', letterSpacing: '0.06em' }}>{fallbackText(item.date, '')}</span> <span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(255,255,255,0.62)', letterSpacing: '0.06em' }}>{fallbackText(item.date, '')}</span>
</div> </div>
@@ -160,7 +161,7 @@ export default function WegZurAuszeichnungSection({ content }: { content?: Proce
<div> <div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 24 }}> <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 24 }}>
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: GOLD, letterSpacing: '0.22em', textTransform: 'uppercase' }}> <span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: GOLD, letterSpacing: '0.22em', textTransform: 'uppercase' }}>
Schritt {fallbackText(item.step, '')} {stepPrefix} {fallbackText(item.step, '')}
</span> </span>
<span style={{ width: 1, height: 12, background: 'rgba(255,255,255,0.2)' }} /> <span style={{ width: 1, height: 12, background: 'rgba(255,255,255,0.2)' }} />
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(255,255,255,0.62)', letterSpacing: '0.08em' }}>{fallbackText(item.date, '')}</span> <span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(255,255,255,0.62)', letterSpacing: '0.08em' }}>{fallbackText(item.date, '')}</span>

View File

@@ -67,7 +67,7 @@ const Participation: React.FC = () => {
{/* ── HERO ─────────────────────────────────────────────────────────────── */} {/* ── HERO ─────────────────────────────────────────────────────────────── */}
<section style={{ position: 'relative', minHeight: '65vh', display: 'flex', alignItems: 'flex-end', overflow: 'hidden', background: NAVY }}> <section style={{ position: 'relative', minHeight: '65vh', display: 'flex', alignItems: 'flex-end', overflow: 'hidden', background: NAVY }}>
<Image unoptimized <Image unoptimized
src={mediaUrl(hero.backgroundImage, '/images/preisuebergabe.jpg')} src={mediaUrl(hero.backgroundImage, `/images/${participationContent.hero.backgroundImageFilename}`)}
alt={mediaAlt(hero.backgroundImage, fallbackText(hero.imageAlt, participationContent.hero.imageAlt))} alt={mediaAlt(hero.backgroundImage, fallbackText(hero.imageAlt, participationContent.hero.imageAlt))}
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
/> />
@@ -183,7 +183,7 @@ const Participation: React.FC = () => {
{/* Left full-bleed image with gradient blend into navy on the right (+ heading on mobile) */} {/* Left full-bleed image with gradient blend into navy on the right (+ heading on mobile) */}
<div style={{ position: 'relative', overflow: 'hidden', minHeight: isMobile ? 280 : undefined }}> <div style={{ position: 'relative', overflow: 'hidden', minHeight: isMobile ? 280 : undefined }}>
<Image unoptimized <Image unoptimized
src={mediaUrl(evaluation.image, '/images/buehne-gewinner.jpg')} src={mediaUrl(evaluation.image, `/images/${participationContent.evaluation.imageFilename}`)}
alt={mediaAlt(evaluation.image, fallbackText(evaluation.imageAlt, participationContent.evaluation.imageAlt))} alt={mediaAlt(evaluation.image, fallbackText(evaluation.imageAlt, participationContent.evaluation.imageAlt))}
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center', display: 'block' }} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center', display: 'block' }}
/> />
@@ -329,7 +329,7 @@ const Participation: React.FC = () => {
{!isMobile && ( {!isMobile && (
<div style={{ height: 220, position: 'relative', overflow: 'hidden', flexShrink: 0, zIndex: 1 }}> <div style={{ height: 220, position: 'relative', overflow: 'hidden', flexShrink: 0, zIndex: 1 }}>
<Image unoptimized src={mediaUrl(applicationForm.image, '/images/preistraeger-jubel.jpg')} alt={mediaAlt(applicationForm.image, fallbackText(applicationForm.imageAlt, participationContent.applicationForm.imageAlt))} style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 30%', filter: 'sepia(0.18) brightness(0.92)' }} /> <Image unoptimized src={mediaUrl(applicationForm.image, `/images/${participationContent.applicationForm.imageFilename}`)} alt={mediaAlt(applicationForm.image, fallbackText(applicationForm.imageAlt, participationContent.applicationForm.imageAlt))} style={{ width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center 30%', filter: 'sepia(0.18) brightness(0.92)' }} />
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to bottom, rgba(168,120,0,0.1) 0%, rgba(168,120,0,0.25) 50%, rgba(168,120,0,0.92) 88%, #A87800 100%)' }} /> <div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to bottom, rgba(168,120,0,0.1) 0%, rgba(168,120,0,0.25) 50%, rgba(168,120,0,0.92) 88%, #A87800 100%)' }} />
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: 'rgba(17,29,85,0.35)' }} /> <div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2, background: 'rgba(17,29,85,0.35)' }} />
<div style={{ position: 'absolute', bottom: 16, left: 40, display: 'flex', alignItems: 'center', gap: 8 }}> <div style={{ position: 'absolute', bottom: 16, left: 40, display: 'flex', alignItems: 'center', gap: 8 }}>
@@ -430,6 +430,7 @@ function BewerbungswegeSection({ content }: { content?: ApplicationWaysContent }
const section = { ...participationContent.applicationWays, ...(content || {}) }; const section = { ...participationContent.applicationWays, ...(content || {}) };
const institutions = fallbackArray(section.institutions, participationContent.applicationWays.institutions); const institutions = fallbackArray(section.institutions, participationContent.applicationWays.institutions);
const ways = fallbackArray(section.ways, participationContent.applicationWays.ways); const ways = fallbackArray(section.ways, participationContent.applicationWays.ways);
const fallbackCta = { ...participationContent.applicationWays.fallbackCta, ...(section.fallbackCta || {}) };
return ( return (
<section style={{ background: CREAM, position: 'relative', overflow: 'hidden', isolation: 'isolate' }}> <section style={{ background: CREAM, position: 'relative', overflow: 'hidden', isolation: 'isolate' }}>
<MunichSkylineBg /> <MunichSkylineBg />
@@ -488,7 +489,7 @@ function BewerbungswegeSection({ content }: { content?: ApplicationWaysContent }
</div> </div>
{/* CTA */} {/* CTA */}
<div style={{ padding: '18px 22px 22px' }}> <div style={{ padding: '18px 22px 22px' }}>
<a href={fallbackText(w.cta?.url, '#bewerben')} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, minHeight: 52, background: GOLD, color: '#101828', fontFamily: FF, fontSize: 14, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', textDecoration: 'none', borderRadius: 10 }}>{fallbackText(w.cta?.label, 'Zum Formular')} <ArrowRight size={14} /></a> <a href={fallbackText(w.cta?.url, fallbackCta.url)} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, minHeight: 52, background: GOLD, color: '#101828', fontFamily: FF, fontSize: 14, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', textDecoration: 'none', borderRadius: 10 }}>{fallbackText(w.cta?.label, fallbackCta.label)} <ArrowRight size={14} /></a>
</div> </div>
</div> </div>
); );
@@ -532,12 +533,12 @@ function BewerbungswegeSection({ content }: { content?: ApplicationWaysContent }
</div> </div>
<a <a
href={fallbackText(w.cta?.url, '#bewerben')} href={fallbackText(w.cta?.url, fallbackCta.url)}
style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#101828', background: GOLD, padding: '14px 24px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8, marginTop: 36, alignSelf: 'flex-start', transition: 'opacity 0.15s' }} style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: '#101828', background: GOLD, padding: '14px 24px', textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: 8, marginTop: 36, alignSelf: 'flex-start', transition: 'opacity 0.15s' }}
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.opacity = '0.85')} onMouseEnter={e => ((e.currentTarget as HTMLElement).style.opacity = '0.85')}
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.opacity = '1')} onMouseLeave={e => ((e.currentTarget as HTMLElement).style.opacity = '1')}
> >
{fallbackText(w.cta?.label, 'Zum Formular')} <ArrowRight size={13} /> {fallbackText(w.cta?.label, fallbackCta.label)} <ArrowRight size={13} />
</a> </a>
</div> </div>
); );

View File

@@ -11,6 +11,7 @@ export const participationContent = {
heading: 'IHR WEG ZUM\nPREIS.', heading: 'IHR WEG ZUM\nPREIS.',
description: description:
'Von der Einreichung bis zur Gala vier klar definierte Schritte auf dem Weg zur höchsten Auszeichnung des bayerischen Mittelstands.', 'Von der Einreichung bis zur Gala vier klar definierte Schritte auf dem Weg zur höchsten Auszeichnung des bayerischen Mittelstands.',
stepPrefix: 'Schritt',
scrollHint: 'Scrollen zum Erkunden', scrollHint: 'Scrollen zum Erkunden',
progressLabel: '01 / 04', progressLabel: '01 / 04',
steps: [ steps: [
@@ -105,6 +106,7 @@ export const participationContent = {
'Mitglieder des Wirtschaftsbeirates Bayern', 'Mitglieder des Wirtschaftsbeirates Bayern',
'Ehemalige Preisträger des Bayerischen Mittelstandspreises', 'Ehemalige Preisträger des Bayerischen Mittelstandspreises',
].map((text) => ({ text })), ].map((text) => ({ text })),
fallbackCta: { label: 'Zum Formular', url: '#bewerben' },
ways: [ ways: [
{ {
label: 'Weg 01', label: 'Weg 01',