feat: manage winner detail chrome via payload
This commit is contained in:
@@ -3,8 +3,9 @@ import { Link, useParams, Navigate } from '@/spa/router';
|
||||
import { Trophy, ArrowLeft, MapPin, Building2, Globe, ChevronRight } from 'lucide-react';
|
||||
import { WINNERS } from '@/spa/data/winners';
|
||||
import { useCmsRoute } from '@/spa/cmsRoute';
|
||||
import { docImageUrl } from '@/spa/cmsMediaField';
|
||||
import { docImageUrl, mediaAlt, mediaUrl } from '@/spa/cmsMediaField';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
import { preistraegerDetailContent } from '@/spa/preistraegerDetailContent';
|
||||
import Image from '@/spa/components/ui/UnoptimizedImage'
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
@@ -15,6 +16,12 @@ const BORDER = '#D0D5DD';
|
||||
const GRAY = '#666666';
|
||||
const BG_ALT = '#E4E2E3';
|
||||
|
||||
type PreistraegerDetailCms = Partial<typeof preistraegerDetailContent> & {
|
||||
sideImage?: Partial<typeof preistraegerDetailContent.sideImage> & { image?: unknown }
|
||||
}
|
||||
|
||||
const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback;
|
||||
|
||||
export default function PreistraegerDetail() {
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const route = useCmsRoute();
|
||||
@@ -36,10 +43,22 @@ export default function PreistraegerDetail() {
|
||||
industry: String(cmsWinner.industry || 'Mittelstand'),
|
||||
website: String(cmsWinner.website || '#'),
|
||||
hasMedia: Boolean(cmsWinner.hasMedia),
|
||||
detailPage: (cmsWinner.detailPage || {}) as PreistraegerDetailCms,
|
||||
} : WINNERS.find(w => w.slug === slug);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
if (!winner) return <Navigate to="/preistraeger" replace />;
|
||||
const detail = {
|
||||
...preistraegerDetailContent,
|
||||
...((winner && 'detailPage' in winner ? winner.detailPage : {}) || {}),
|
||||
} as PreistraegerDetailCms;
|
||||
const breadcrumb = { ...preistraegerDetailContent.breadcrumb, ...(detail.breadcrumb || {}) };
|
||||
const sections = { ...preistraegerDetailContent.sections, ...(detail.sections || {}) };
|
||||
const sideImage = { ...preistraegerDetailContent.sideImage, ...(detail.sideImage || {}) };
|
||||
const facts = { ...preistraegerDetailContent.facts, ...(detail.facts || {}) };
|
||||
const websiteCta = { ...preistraegerDetailContent.websiteCta, ...(detail.websiteCta || {}) };
|
||||
const backLink = { ...preistraegerDetailContent.backLink, ...(detail.backLink || {}) };
|
||||
|
||||
if (!winner) return <Navigate to={preistraegerDetailContent.notFoundRedirect} replace />;
|
||||
|
||||
return (
|
||||
<div style={{ background: '#fff', minHeight: '100vh' }}>
|
||||
@@ -55,8 +74,8 @@ export default function PreistraegerDetail() {
|
||||
|
||||
{/* Breadcrumb */}
|
||||
<div style={{ background: '#fff', borderBottom: `1px solid ${BORDER}`, padding: isMobile ? '16px 24px' : '16px 80px', display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Link to="/preistraeger" style={{ fontFamily: FF, fontSize: 16, color: GOLD, textDecoration: 'none', fontWeight: 600 }}>
|
||||
Preisträger:innen
|
||||
<Link to={fallbackText(breadcrumb.url, preistraegerDetailContent.breadcrumb.url)} style={{ fontFamily: FF, fontSize: 16, color: GOLD, textDecoration: 'none', fontWeight: 600 }}>
|
||||
{fallbackText(breadcrumb.label, preistraegerDetailContent.breadcrumb.label)}
|
||||
</Link>
|
||||
<ChevronRight size={14} style={{ color: GRAY }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 16, color: GRAY }}>{winner.name}</span>
|
||||
@@ -92,17 +111,17 @@ export default function PreistraegerDetail() {
|
||||
{/* Left: Text */}
|
||||
<div>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 18, fontWeight: 800, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 20 }}>
|
||||
Über das Unternehmen
|
||||
{fallbackText(sections.companyHeading, preistraegerDetailContent.sections.companyHeading)}
|
||||
</h2>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: '#333', lineHeight: 1.8, marginBottom: 40 }}>
|
||||
{winner.longDesc}
|
||||
</p>
|
||||
|
||||
<h2 style={{ fontFamily: FF, fontSize: 18, fontWeight: 800, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 20 }}>
|
||||
Warum ausgezeichnet?
|
||||
{fallbackText(sections.juryHeading, preistraegerDetailContent.sections.juryHeading)}
|
||||
</h2>
|
||||
<p style={{ fontFamily: FB, fontSize: 18, color: '#333', lineHeight: 1.8, marginBottom: 40 }}>
|
||||
Die Jury des Bayerischen Mittelstandspreises überzeugte {winner.name} durch außergewöhnliche Leistungen im Bereich <strong>{winner.category}</strong>. Das Unternehmen steht exemplarisch für die Stärken des bayerischen Mittelstands: unternehmerischen Mut, wertebasierte Führung und nachhaltige Wirkung weit über den eigenen Betrieb hinaus.
|
||||
{fallbackText(sections.juryTextBeforeName, preistraegerDetailContent.sections.juryTextBeforeName)} {winner.name} {fallbackText(sections.juryTextAfterNameBeforeCategory, preistraegerDetailContent.sections.juryTextAfterNameBeforeCategory)} <strong>{winner.category}</strong>. {fallbackText(sections.juryTextAfterCategory, preistraegerDetailContent.sections.juryTextAfterCategory)}
|
||||
</p>
|
||||
|
||||
{/* Quote */}
|
||||
@@ -122,21 +141,21 @@ export default function PreistraegerDetail() {
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 32 }}>
|
||||
{/* Photo */}
|
||||
<div style={{ borderRadius: 0, overflow: 'hidden', aspectRatio: '4/3' }}>
|
||||
<Image unoptimized src="/gala-preistraeger.jpg" alt="Gala Preisverleihung" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', filter: 'brightness(0.95)' }} />
|
||||
<Image unoptimized src={mediaUrl(sideImage.image, `/${preistraegerDetailContent.sideImage.imageFilename}`)} alt={mediaAlt(sideImage.image, fallbackText(sideImage.imageAlt, preistraegerDetailContent.sideImage.imageAlt))} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', filter: 'brightness(0.95)' }} />
|
||||
</div>
|
||||
|
||||
{/* Kenndaten */}
|
||||
<div style={{ background: BG_ALT, borderLeft: `3px solid ${GOLD}`, padding: '20px 24px' }}>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, color: GRAY, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 20 }}>
|
||||
Auf einen Blick
|
||||
{fallbackText(facts.heading, preistraegerDetailContent.facts.heading)}
|
||||
</h3>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
{[
|
||||
{ label: 'Kategorie', value: winner.category, icon: <Trophy size={14} style={{ color: GOLD }} /> },
|
||||
{ label: 'Auszeichnung', value: winner.type, icon: <Trophy size={14} style={{ color: GOLD }} /> },
|
||||
{ label: 'Jahr', value: String(winner.year), icon: null },
|
||||
{ label: 'Standort', value: winner.location, icon: <MapPin size={14} style={{ color: GOLD }} /> },
|
||||
{ label: 'Branche', value: winner.industry, icon: <Building2 size={14} style={{ color: GOLD }} /> },
|
||||
{ label: fallbackText(facts.categoryLabel, preistraegerDetailContent.facts.categoryLabel), value: winner.category, icon: <Trophy size={14} style={{ color: GOLD }} /> },
|
||||
{ label: fallbackText(facts.awardLabel, preistraegerDetailContent.facts.awardLabel), value: winner.type, icon: <Trophy size={14} style={{ color: GOLD }} /> },
|
||||
{ label: fallbackText(facts.yearLabel, preistraegerDetailContent.facts.yearLabel), value: String(winner.year), icon: null },
|
||||
{ label: fallbackText(facts.locationLabel, preistraegerDetailContent.facts.locationLabel), value: winner.location, icon: <MapPin size={14} style={{ color: GOLD }} /> },
|
||||
{ label: fallbackText(facts.industryLabel, preistraegerDetailContent.facts.industryLabel), value: winner.industry, icon: <Building2 size={14} style={{ color: GOLD }} /> },
|
||||
].map(row => (
|
||||
<div key={row.label} style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 11, color: GRAY, textTransform: 'uppercase', letterSpacing: '0.08em' }}>{row.label}</span>
|
||||
@@ -162,7 +181,7 @@ export default function PreistraegerDetail() {
|
||||
onMouseEnter={e => { const el = e.currentTarget as HTMLElement; el.style.background = NAVY; el.style.color = '#fff'; }}
|
||||
onMouseLeave={e => { const el = e.currentTarget as HTMLElement; el.style.background = 'transparent'; el.style.color = NAVY; }}
|
||||
>
|
||||
<Globe size={16} /> Website besuchen ↗
|
||||
<Globe size={16} /> {fallbackText(websiteCta.label, preistraegerDetailContent.websiteCta.label)}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -170,12 +189,12 @@ export default function PreistraegerDetail() {
|
||||
{/* Back link */}
|
||||
<div style={{ padding: isMobile ? '32px 24px 48px' : '32px 80px 64px', borderTop: `1px solid ${BORDER}` }}>
|
||||
<Link
|
||||
to="/preistraeger"
|
||||
to={fallbackText(backLink.url, preistraegerDetailContent.backLink.url)}
|
||||
style={{ display: 'inline-flex', alignItems: 'center', gap: 8, fontFamily: FF, fontSize: 17, fontWeight: 700, color: '#101828', textDecoration: 'none', textTransform: 'uppercase', letterSpacing: '0.06em', transition: 'color 0.15s' }}
|
||||
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.color = GOLD)}
|
||||
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.color = NAVY)}
|
||||
>
|
||||
<ArrowLeft size={16} /> Alle Preisträger
|
||||
<ArrowLeft size={16} /> {fallbackText(backLink.label, preistraegerDetailContent.backLink.label)}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
34
src/spa/preistraegerDetailContent.ts
Normal file
34
src/spa/preistraegerDetailContent.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export const preistraegerDetailContent = {
|
||||
notFoundRedirect: '/preistraeger',
|
||||
breadcrumb: {
|
||||
url: '/preistraeger',
|
||||
label: 'Preisträger:innen',
|
||||
},
|
||||
sections: {
|
||||
companyHeading: 'Über das Unternehmen',
|
||||
juryHeading: 'Warum ausgezeichnet?',
|
||||
juryTextBeforeName: 'Die Jury des Bayerischen Mittelstandspreises überzeugte',
|
||||
juryTextAfterNameBeforeCategory: 'durch außergewöhnliche Leistungen im Bereich',
|
||||
juryTextAfterCategory:
|
||||
'Das Unternehmen steht exemplarisch für die Stärken des bayerischen Mittelstands: unternehmerischen Mut, wertebasierte Führung und nachhaltige Wirkung weit über den eigenen Betrieb hinaus.',
|
||||
},
|
||||
sideImage: {
|
||||
imageFilename: 'gala-preistraeger.jpg',
|
||||
imageAlt: 'Gala Preisverleihung',
|
||||
},
|
||||
facts: {
|
||||
heading: 'Auf einen Blick',
|
||||
categoryLabel: 'Kategorie',
|
||||
awardLabel: 'Auszeichnung',
|
||||
yearLabel: 'Jahr',
|
||||
locationLabel: 'Standort',
|
||||
industryLabel: 'Branche',
|
||||
},
|
||||
websiteCta: {
|
||||
label: 'Website besuchen ↗',
|
||||
},
|
||||
backLink: {
|
||||
url: '/preistraeger',
|
||||
label: 'Alle Preisträger',
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user