feat: manage winner detail chrome via payload
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
"preload:mitglied-werden-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-mitglied-werden-page-cms.ts",
|
||||
"preload:netzwerk-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-netzwerk-page-cms.ts",
|
||||
"preload:participation-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-participation-page-cms.ts",
|
||||
"preload:preistraeger-detail-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-preistraeger-detail-cms.ts",
|
||||
"preload:preistraeger-index-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-preistraeger-index-page-cms.ts",
|
||||
"preload:press-index-page-cms": "cross-env NODE_OPTIONS=--no-deprecation tsx src/scripts/preload-press-index-page-cms.ts",
|
||||
"reinstall": "cross-env NODE_OPTIONS=--no-deprecation rm -rf node_modules && rm pnpm-lock.yaml && pnpm --ignore-workspace install",
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { CollectionConfig } from 'payload'
|
||||
import { authenticated } from '@/access/authenticated'
|
||||
import { authenticatedOrPublished } from '@/access/authenticatedOrPublished'
|
||||
import { populatePublishedAt } from '@/hooks/populatePublishedAt'
|
||||
import { preistraegerDetailContent } from '@/spa/preistraegerDetailContent'
|
||||
import { generatePreviewPath } from '@/utilities/generatePreviewPath'
|
||||
import { slugField } from 'payload'
|
||||
|
||||
@@ -110,6 +111,94 @@ export const Preistraeger: CollectionConfig = {
|
||||
name: 'hasMedia',
|
||||
type: 'checkbox',
|
||||
},
|
||||
{
|
||||
name: 'detailPage',
|
||||
label: 'Detail page chrome',
|
||||
type: 'group',
|
||||
admin: {
|
||||
description:
|
||||
'Shared labels, explanatory copy, CTAs, and supporting media for the Preisträger detail route.',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'breadcrumb',
|
||||
label: 'Breadcrumb',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{ name: 'url', type: 'text', label: 'URL', defaultValue: preistraegerDetailContent.breadcrumb.url },
|
||||
{ name: 'label', type: 'text', label: 'Label', defaultValue: preistraegerDetailContent.breadcrumb.label },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'sections',
|
||||
label: 'Section copy',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{ name: 'companyHeading', type: 'text', defaultValue: preistraegerDetailContent.sections.companyHeading },
|
||||
{ name: 'juryHeading', type: 'text', defaultValue: preistraegerDetailContent.sections.juryHeading },
|
||||
{
|
||||
name: 'juryTextBeforeName',
|
||||
type: 'textarea',
|
||||
defaultValue: preistraegerDetailContent.sections.juryTextBeforeName,
|
||||
},
|
||||
{
|
||||
name: 'juryTextAfterNameBeforeCategory',
|
||||
type: 'textarea',
|
||||
defaultValue: preistraegerDetailContent.sections.juryTextAfterNameBeforeCategory,
|
||||
},
|
||||
{
|
||||
name: 'juryTextAfterCategory',
|
||||
type: 'textarea',
|
||||
defaultValue: preistraegerDetailContent.sections.juryTextAfterCategory,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'sideImage',
|
||||
label: 'Side image',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
admin: {
|
||||
description: `Current frontend image: /${preistraegerDetailContent.sideImage.imageFilename}`,
|
||||
},
|
||||
},
|
||||
{ name: 'imageAlt', type: 'text', defaultValue: preistraegerDetailContent.sideImage.imageAlt },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'facts',
|
||||
label: 'Facts box labels',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{ name: 'heading', type: 'text', defaultValue: preistraegerDetailContent.facts.heading },
|
||||
{ name: 'categoryLabel', type: 'text', defaultValue: preistraegerDetailContent.facts.categoryLabel },
|
||||
{ name: 'awardLabel', type: 'text', defaultValue: preistraegerDetailContent.facts.awardLabel },
|
||||
{ name: 'yearLabel', type: 'text', defaultValue: preistraegerDetailContent.facts.yearLabel },
|
||||
{ name: 'locationLabel', type: 'text', defaultValue: preistraegerDetailContent.facts.locationLabel },
|
||||
{ name: 'industryLabel', type: 'text', defaultValue: preistraegerDetailContent.facts.industryLabel },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'websiteCta',
|
||||
label: 'Website CTA',
|
||||
type: 'group',
|
||||
fields: [{ name: 'label', type: 'text', defaultValue: preistraegerDetailContent.websiteCta.label }],
|
||||
},
|
||||
{
|
||||
name: 'backLink',
|
||||
label: 'Back link',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{ name: 'url', type: 'text', label: 'URL', defaultValue: preistraegerDetailContent.backLink.url },
|
||||
{ name: 'label', type: 'text', label: 'Label', defaultValue: preistraegerDetailContent.backLink.label },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'spaPath',
|
||||
type: 'text',
|
||||
|
||||
@@ -2460,6 +2460,44 @@ export interface Preistraeger {
|
||||
industry?: string | null;
|
||||
website?: string | null;
|
||||
hasMedia?: boolean | null;
|
||||
/**
|
||||
* Shared labels, explanatory copy, CTAs, and supporting media for the Preisträger detail route.
|
||||
*/
|
||||
detailPage?: {
|
||||
breadcrumb?: {
|
||||
url?: string | null;
|
||||
label?: string | null;
|
||||
};
|
||||
sections?: {
|
||||
companyHeading?: string | null;
|
||||
juryHeading?: string | null;
|
||||
juryTextBeforeName?: string | null;
|
||||
juryTextAfterNameBeforeCategory?: string | null;
|
||||
juryTextAfterCategory?: string | null;
|
||||
};
|
||||
sideImage?: {
|
||||
/**
|
||||
* Current frontend image: /gala-preistraeger.jpg
|
||||
*/
|
||||
image?: (number | null) | Media;
|
||||
imageAlt?: string | null;
|
||||
};
|
||||
facts?: {
|
||||
heading?: string | null;
|
||||
categoryLabel?: string | null;
|
||||
awardLabel?: string | null;
|
||||
yearLabel?: string | null;
|
||||
locationLabel?: string | null;
|
||||
industryLabel?: string | null;
|
||||
};
|
||||
websiteCta?: {
|
||||
label?: string | null;
|
||||
};
|
||||
backLink?: {
|
||||
url?: string | null;
|
||||
label?: string | null;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Route in the legacy SPA used for previewing the current migrated Preisträger state.
|
||||
*/
|
||||
@@ -4451,6 +4489,52 @@ export interface PreistraegerSelect<T extends boolean = true> {
|
||||
industry?: T;
|
||||
website?: T;
|
||||
hasMedia?: T;
|
||||
detailPage?:
|
||||
| T
|
||||
| {
|
||||
breadcrumb?:
|
||||
| T
|
||||
| {
|
||||
url?: T;
|
||||
label?: T;
|
||||
};
|
||||
sections?:
|
||||
| T
|
||||
| {
|
||||
companyHeading?: T;
|
||||
juryHeading?: T;
|
||||
juryTextBeforeName?: T;
|
||||
juryTextAfterNameBeforeCategory?: T;
|
||||
juryTextAfterCategory?: T;
|
||||
};
|
||||
sideImage?:
|
||||
| T
|
||||
| {
|
||||
image?: T;
|
||||
imageAlt?: T;
|
||||
};
|
||||
facts?:
|
||||
| T
|
||||
| {
|
||||
heading?: T;
|
||||
categoryLabel?: T;
|
||||
awardLabel?: T;
|
||||
yearLabel?: T;
|
||||
locationLabel?: T;
|
||||
industryLabel?: T;
|
||||
};
|
||||
websiteCta?:
|
||||
| T
|
||||
| {
|
||||
label?: T;
|
||||
};
|
||||
backLink?:
|
||||
| T
|
||||
| {
|
||||
url?: T;
|
||||
label?: T;
|
||||
};
|
||||
};
|
||||
spaPath?: T;
|
||||
publishedAt?: T;
|
||||
meta?:
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getPayload } from 'payload'
|
||||
|
||||
import { articles } from '@/spa/data/blog'
|
||||
import { events } from '@/spa/data/events'
|
||||
import { preistraegerDetailContent } from '@/spa/preistraegerDetailContent'
|
||||
import { WINNERS } from '@/spa/data/winners'
|
||||
import { spaPages, type SpaPageRegistryEntry } from '@/spa/pageRegistry'
|
||||
|
||||
@@ -159,6 +160,7 @@ function eventData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
||||
|
||||
function preistraegerData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
||||
const winner = WINNERS.find((item) => item.slug === targetSlug(page))
|
||||
const { imageFilename: _sideImageFilename, ...sideImage } = preistraegerDetailContent.sideImage
|
||||
return {
|
||||
...baseData(page),
|
||||
image: mediaID(mediaMap, winner?.img),
|
||||
@@ -175,6 +177,13 @@ function preistraegerData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
||||
industry: winner?.industry,
|
||||
website: winner?.website,
|
||||
hasMedia: winner?.hasMedia,
|
||||
detailPage: {
|
||||
...preistraegerDetailContent,
|
||||
sideImage: {
|
||||
...sideImage,
|
||||
image: mediaID(mediaMap, `/${preistraegerDetailContent.sideImage.imageFilename}`),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +274,7 @@ async function upsertPage(payload: Payload, page: SpaPageRegistryEntry, mediaMap
|
||||
async function loadMediaMap(payload: Payload): Promise<MediaMap> {
|
||||
const sources = new Set([
|
||||
...WINNERS.map((winner) => winner.img),
|
||||
`/${preistraegerDetailContent.sideImage.imageFilename}`,
|
||||
...events.map((event) => event.img),
|
||||
...articles.map((article) => article.img),
|
||||
])
|
||||
|
||||
57
src/scripts/preload-preistraeger-detail-cms.ts
Normal file
57
src/scripts/preload-preistraeger-detail-cms.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import 'dotenv/config'
|
||||
|
||||
import config from '@payload-config'
|
||||
import { getPayload, type Payload } from 'payload'
|
||||
|
||||
import { preistraegerDetailContent } from '@/spa/preistraegerDetailContent'
|
||||
|
||||
const mediaByFilename = async (payload: Payload, filename: string) => {
|
||||
const result = await payload.find({
|
||||
collection: 'media',
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
where: { filename: { equals: filename } },
|
||||
})
|
||||
return result.docs[0]?.id
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const payload = await getPayload({ config })
|
||||
const sideImage = await mediaByFilename(payload, preistraegerDetailContent.sideImage.imageFilename)
|
||||
const { imageFilename: _sideImageFilename, ...sideImageContent } = preistraegerDetailContent.sideImage
|
||||
|
||||
const winners = await payload.find({
|
||||
collection: 'preistraeger',
|
||||
draft: true,
|
||||
overrideAccess: true,
|
||||
pagination: false,
|
||||
limit: 1000,
|
||||
})
|
||||
|
||||
await Promise.all(
|
||||
winners.docs.map((winner) =>
|
||||
payload.update({
|
||||
collection: 'preistraeger',
|
||||
id: winner.id,
|
||||
overrideAccess: true,
|
||||
context: { disableRevalidate: true },
|
||||
data: {
|
||||
detailPage: {
|
||||
...preistraegerDetailContent,
|
||||
sideImage: {
|
||||
...sideImageContent,
|
||||
image: sideImage,
|
||||
},
|
||||
},
|
||||
} as never,
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
payload.logger.info(`Preloaded Preisträger detail CMS fields for ${winners.docs.length} records`)
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -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