feat: manage blog detail chrome via payload

This commit is contained in:
syntaxbullet
2026-06-22 11:54:18 +02:00
parent 43f85dfea2
commit aa839fcebc
7 changed files with 260 additions and 19 deletions

View File

@@ -6,6 +6,7 @@ import { getArticleBySlug, articles, type BlogArticle, type BlogSection } from '
import { useCmsRoute } from '@/spa/cmsRoute';
import { docImageUrl } from '@/spa/cmsMediaField';
import { useIsMobile } from '@/spa/hooks/useIsMobile';
import { blogDetailContent } from '@/spa/blogDetailContent';
import Image from '@/spa/components/ui/UnoptimizedImage'
const NAVY = '#111D55';
@@ -15,10 +16,29 @@ const INK = '#3A3A3A';
const FF = '"IBM Plex Sans", sans-serif';
const FB = '"Inter", sans-serif';
const CAT_COLORS: Record<string, string> = {
Innovation: '#2563EB',
Engagement: '#16A34A',
Wirtschaft: '#9333EA',
type BlogDetailCms = Omit<Partial<typeof blogDetailContent>, 'categoryColors'> & {
categoryColors?: unknown
metaCopy?: Partial<typeof blogDetailContent.meta>
}
const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback;
const colorMap = (value: unknown): Record<string, string> => {
if (!value || typeof value !== 'object') return blogDetailContent.categoryColors;
return { ...blogDetailContent.categoryColors, ...(value as Record<string, string>) };
};
const getDetailPage = (value: unknown): BlogDetailCms => {
const detail = (value && typeof value === 'object' ? value : {}) as BlogDetailCms;
return {
...blogDetailContent,
...detail,
notFound: { ...blogDetailContent.notFound, ...detail.notFound },
heroBackLink: { ...blogDetailContent.heroBackLink, ...detail.heroBackLink },
categoryColors: colorMap(detail.categoryColors),
metaCopy: { ...blogDetailContent.meta, ...(detail.metaCopy || detail.meta) },
sidebarCta: { ...blogDetailContent.sidebarCta, ...detail.sidebarCta },
related: { ...blogDetailContent.related, ...detail.related },
};
};
export default function BlogDetail() {
@@ -47,14 +67,22 @@ export default function BlogDetail() {
.map((item: { slug?: string }) => item.slug)
.filter((slug): slug is string => Boolean(slug))
: [],
detailPage: cmsArticle.detailPage,
} as BlogArticle : undefined);
const detail = getDetailPage(article && 'detailPage' in article ? article.detailPage : undefined);
const notFound = detail.notFound || blogDetailContent.notFound;
const heroBackLink = detail.heroBackLink || blogDetailContent.heroBackLink;
const categoryColors = colorMap(detail.categoryColors);
const metaCopy = detail.metaCopy || blogDetailContent.meta;
const sidebarCta = detail.sidebarCta || blogDetailContent.sidebarCta;
const relatedCopy = detail.related || blogDetailContent.related;
if (!article) {
return (
<div style={{ minHeight: '60vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 24, padding: '80px 24px', background: CREAM }}>
<h2 style={{ fontFamily: FF, fontSize: '2rem', fontWeight: 900, color: NAVY }}>Artikel nicht gefunden</h2>
<Link to="/presse" style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, color: NAVY, textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6 }}>
<ArrowLeft size={14} /> Zurück zur Presse
<h2 style={{ fontFamily: FF, fontSize: '2rem', fontWeight: 900, color: NAVY }}>{fallbackText(notFound.title, blogDetailContent.notFound.title)}</h2>
<Link to={fallbackText(notFound.backUrl, blogDetailContent.notFound.backUrl)} style={{ fontFamily: FF, fontSize: 16, fontWeight: 700, color: NAVY, textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 6 }}>
<ArrowLeft size={14} /> {fallbackText(notFound.backLabel, blogDetailContent.notFound.backLabel)}
</Link>
</div>
);
@@ -82,7 +110,7 @@ export default function BlogDetail() {
<div style={{ position: 'relative', zIndex: 2, padding: isMobile ? '0 24px 48px' : '0 80px 72px', maxWidth: 860 }}>
{/* Back link */}
<Link
to="/presse"
to={fallbackText(heroBackLink.url, blogDetailContent.heroBackLink.url)}
style={{
display: 'inline-flex', alignItems: 'center', gap: 8,
fontFamily: FF, fontSize: 11, fontWeight: 700,
@@ -93,7 +121,7 @@ export default function BlogDetail() {
onMouseEnter={e => (e.currentTarget.style.color = '#fff')}
onMouseLeave={e => (e.currentTarget.style.color = 'rgba(255,255,255,0.5)')}
>
<ArrowLeft size={13} /> Newsroom
<ArrowLeft size={13} /> {fallbackText(heroBackLink.label, blogDetailContent.heroBackLink.label)}
</Link>
{/* Category badge */}
@@ -103,7 +131,7 @@ export default function BlogDetail() {
padding: '4px 12px',
fontFamily: FF, fontSize: 9, fontWeight: 700,
textTransform: 'uppercase', letterSpacing: '0.22em',
background: CAT_COLORS[article.cat] ?? GOLD,
background: categoryColors[article.cat] ?? GOLD,
color: '#fff',
}}>
{article.cat}
@@ -133,7 +161,7 @@ export default function BlogDetail() {
<div style={{ display: 'flex', gap: 28, flexWrap: 'wrap' }}>
{[
{ icon: User, label: `${article.author} · ${article.authorRole}` },
{ icon: Clock, label: `${article.date} · ${article.readTime} Lesezeit` },
{ icon: Clock, label: `${article.date} · ${article.readTime} ${fallbackText(metaCopy.readTimeSuffix, blogDetailContent.meta.readTimeSuffix)}` },
].map(({ icon: Icon, label }, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, fontFamily: FB, fontSize: 16, color: 'rgba(255,255,255,0.5)' }}>
<Icon size={13} color={GOLD} /> {label}
@@ -225,13 +253,13 @@ export default function BlogDetail() {
<div style={{ background: '#fff', border: '1px solid rgba(3,9,58,0.1)', padding: '28px' }}>
<div style={{ width: 32, height: 2, background: GOLD, marginBottom: 16 }} />
<h4 style={{ fontFamily: FF, fontSize: 15, fontWeight: 900, color: NAVY, textTransform: 'uppercase', letterSpacing: '-0.01em', marginBottom: 12 }}>
Jetzt bewerben
{fallbackText(sidebarCta.heading, blogDetailContent.sidebarCta.heading)}
</h4>
<p style={{ fontFamily: FB, fontSize: 16, color: 'rgba(58,58,58,0.55)', lineHeight: 1.7, marginBottom: 20 }}>
Ist Ihr Unternehmen bereit für den Bayerischen Mittelstandspreis 2026?
{fallbackText(sidebarCta.text, blogDetailContent.sidebarCta.text)}
</p>
<Link
to="/teilnahme"
to={fallbackText(sidebarCta.url, blogDetailContent.sidebarCta.url)}
style={{
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
padding: '13px 20px',
@@ -244,7 +272,7 @@ export default function BlogDetail() {
onMouseEnter={e => (e.currentTarget.style.background = GOLD)}
onMouseLeave={e => (e.currentTarget.style.background = NAVY)}
>
Zur Bewerbung <ArrowRight size={12} />
{fallbackText(sidebarCta.label, blogDetailContent.sidebarCta.label)} <ArrowRight size={12} />
</Link>
</div>
</div>
@@ -259,14 +287,14 @@ export default function BlogDetail() {
<div style={{ display: 'flex', alignItems: 'baseline', gap: 20, marginBottom: 56 }}>
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.28em', textTransform: 'uppercase', color: 'rgba(255,255,255,0.35)' }}>
Weitere Artikel
{fallbackText(relatedCopy.heading, blogDetailContent.related.heading)}
</span>
<div style={{ flex: 1, height: 1, background: 'rgba(255,255,255,0.1)' }} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : `repeat(${related.length}, 1fr)`, gap: 0 }}>
{related.map((rel, idx) => (
<RelatedCard key={rel.slug} article={rel} bordered={isMobile ? false : idx > 0} isMobile={isMobile} />
<RelatedCard key={rel.slug} article={rel} bordered={isMobile ? false : idx > 0} isMobile={isMobile} readMoreLabel={fallbackText(relatedCopy.readMoreLabel, blogDetailContent.related.readMoreLabel)} />
))}
</div>
</div>
@@ -277,7 +305,7 @@ export default function BlogDetail() {
);
}
function RelatedCard({ article, bordered, isMobile }: { article: ReturnType<typeof getArticleBySlug> & object; bordered: boolean; isMobile: boolean }) {
function RelatedCard({ article, bordered, isMobile, readMoreLabel }: { article: ReturnType<typeof getArticleBySlug> & object; bordered: boolean; isMobile: boolean; readMoreLabel: string }) {
const [hovered, setHovered] = React.useState(false);
if (!article) return null;
return (
@@ -319,7 +347,7 @@ function RelatedCard({ article, bordered, isMobile }: { article: ReturnType<type
{article.excerpt}
</p>
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.12em', color: GOLD, display: 'inline-flex', alignItems: 'center', gap: 5 }}>
Weiterlesen <ArrowRight size={12} />
{readMoreLabel} <ArrowRight size={12} />
</span>
</Link>
);