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: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: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: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: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",
|
"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",
|
"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 { authenticated } from '@/access/authenticated'
|
||||||
import { authenticatedOrPublished } from '@/access/authenticatedOrPublished'
|
import { authenticatedOrPublished } from '@/access/authenticatedOrPublished'
|
||||||
import { populatePublishedAt } from '@/hooks/populatePublishedAt'
|
import { populatePublishedAt } from '@/hooks/populatePublishedAt'
|
||||||
|
import { preistraegerDetailContent } from '@/spa/preistraegerDetailContent'
|
||||||
import { generatePreviewPath } from '@/utilities/generatePreviewPath'
|
import { generatePreviewPath } from '@/utilities/generatePreviewPath'
|
||||||
import { slugField } from 'payload'
|
import { slugField } from 'payload'
|
||||||
|
|
||||||
@@ -110,6 +111,94 @@ export const Preistraeger: CollectionConfig = {
|
|||||||
name: 'hasMedia',
|
name: 'hasMedia',
|
||||||
type: 'checkbox',
|
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',
|
name: 'spaPath',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
|
|||||||
@@ -2460,6 +2460,44 @@ export interface Preistraeger {
|
|||||||
industry?: string | null;
|
industry?: string | null;
|
||||||
website?: string | null;
|
website?: string | null;
|
||||||
hasMedia?: boolean | 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.
|
* 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;
|
industry?: T;
|
||||||
website?: T;
|
website?: T;
|
||||||
hasMedia?: 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;
|
spaPath?: T;
|
||||||
publishedAt?: T;
|
publishedAt?: T;
|
||||||
meta?:
|
meta?:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { getPayload } from 'payload'
|
|||||||
|
|
||||||
import { articles } from '@/spa/data/blog'
|
import { articles } from '@/spa/data/blog'
|
||||||
import { events } from '@/spa/data/events'
|
import { events } from '@/spa/data/events'
|
||||||
|
import { preistraegerDetailContent } from '@/spa/preistraegerDetailContent'
|
||||||
import { WINNERS } from '@/spa/data/winners'
|
import { WINNERS } from '@/spa/data/winners'
|
||||||
import { spaPages, type SpaPageRegistryEntry } from '@/spa/pageRegistry'
|
import { spaPages, type SpaPageRegistryEntry } from '@/spa/pageRegistry'
|
||||||
|
|
||||||
@@ -159,6 +160,7 @@ function eventData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
|||||||
|
|
||||||
function preistraegerData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
function preistraegerData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
||||||
const winner = WINNERS.find((item) => item.slug === targetSlug(page))
|
const winner = WINNERS.find((item) => item.slug === targetSlug(page))
|
||||||
|
const { imageFilename: _sideImageFilename, ...sideImage } = preistraegerDetailContent.sideImage
|
||||||
return {
|
return {
|
||||||
...baseData(page),
|
...baseData(page),
|
||||||
image: mediaID(mediaMap, winner?.img),
|
image: mediaID(mediaMap, winner?.img),
|
||||||
@@ -175,6 +177,13 @@ function preistraegerData(page: SpaPageRegistryEntry, mediaMap: MediaMap) {
|
|||||||
industry: winner?.industry,
|
industry: winner?.industry,
|
||||||
website: winner?.website,
|
website: winner?.website,
|
||||||
hasMedia: winner?.hasMedia,
|
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> {
|
async function loadMediaMap(payload: Payload): Promise<MediaMap> {
|
||||||
const sources = new Set([
|
const sources = new Set([
|
||||||
...WINNERS.map((winner) => winner.img),
|
...WINNERS.map((winner) => winner.img),
|
||||||
|
`/${preistraegerDetailContent.sideImage.imageFilename}`,
|
||||||
...events.map((event) => event.img),
|
...events.map((event) => event.img),
|
||||||
...articles.map((article) => article.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 { Trophy, ArrowLeft, MapPin, Building2, Globe, ChevronRight } from 'lucide-react';
|
||||||
import { WINNERS } from '@/spa/data/winners';
|
import { WINNERS } from '@/spa/data/winners';
|
||||||
import { useCmsRoute } from '@/spa/cmsRoute';
|
import { useCmsRoute } from '@/spa/cmsRoute';
|
||||||
import { docImageUrl } from '@/spa/cmsMediaField';
|
import { docImageUrl, mediaAlt, mediaUrl } from '@/spa/cmsMediaField';
|
||||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||||
|
import { preistraegerDetailContent } from '@/spa/preistraegerDetailContent';
|
||||||
import Image from '@/spa/components/ui/UnoptimizedImage'
|
import Image from '@/spa/components/ui/UnoptimizedImage'
|
||||||
|
|
||||||
const FF = '"IBM Plex Sans", sans-serif';
|
const FF = '"IBM Plex Sans", sans-serif';
|
||||||
@@ -15,6 +16,12 @@ const BORDER = '#D0D5DD';
|
|||||||
const GRAY = '#666666';
|
const GRAY = '#666666';
|
||||||
const BG_ALT = '#E4E2E3';
|
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() {
|
export default function PreistraegerDetail() {
|
||||||
const { slug } = useParams<{ slug: string }>();
|
const { slug } = useParams<{ slug: string }>();
|
||||||
const route = useCmsRoute();
|
const route = useCmsRoute();
|
||||||
@@ -36,10 +43,22 @@ export default function PreistraegerDetail() {
|
|||||||
industry: String(cmsWinner.industry || 'Mittelstand'),
|
industry: String(cmsWinner.industry || 'Mittelstand'),
|
||||||
website: String(cmsWinner.website || '#'),
|
website: String(cmsWinner.website || '#'),
|
||||||
hasMedia: Boolean(cmsWinner.hasMedia),
|
hasMedia: Boolean(cmsWinner.hasMedia),
|
||||||
|
detailPage: (cmsWinner.detailPage || {}) as PreistraegerDetailCms,
|
||||||
} : WINNERS.find(w => w.slug === slug);
|
} : WINNERS.find(w => w.slug === slug);
|
||||||
const isMobile = useIsMobile();
|
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 (
|
return (
|
||||||
<div style={{ background: '#fff', minHeight: '100vh' }}>
|
<div style={{ background: '#fff', minHeight: '100vh' }}>
|
||||||
@@ -55,8 +74,8 @@ export default function PreistraegerDetail() {
|
|||||||
|
|
||||||
{/* Breadcrumb */}
|
{/* Breadcrumb */}
|
||||||
<div style={{ background: '#fff', borderBottom: `1px solid ${BORDER}`, padding: isMobile ? '16px 24px' : '16px 80px', display: 'flex', alignItems: 'center', gap: 8 }}>
|
<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 }}>
|
<Link to={fallbackText(breadcrumb.url, preistraegerDetailContent.breadcrumb.url)} style={{ fontFamily: FF, fontSize: 16, color: GOLD, textDecoration: 'none', fontWeight: 600 }}>
|
||||||
Preisträger:innen
|
{fallbackText(breadcrumb.label, preistraegerDetailContent.breadcrumb.label)}
|
||||||
</Link>
|
</Link>
|
||||||
<ChevronRight size={14} style={{ color: GRAY }} />
|
<ChevronRight size={14} style={{ color: GRAY }} />
|
||||||
<span style={{ fontFamily: FF, fontSize: 16, color: GRAY }}>{winner.name}</span>
|
<span style={{ fontFamily: FF, fontSize: 16, color: GRAY }}>{winner.name}</span>
|
||||||
@@ -92,17 +111,17 @@ export default function PreistraegerDetail() {
|
|||||||
{/* Left: Text */}
|
{/* Left: Text */}
|
||||||
<div>
|
<div>
|
||||||
<h2 style={{ fontFamily: FF, fontSize: 18, fontWeight: 800, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 20 }}>
|
<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>
|
</h2>
|
||||||
<p style={{ fontFamily: FB, fontSize: 18, color: '#333', lineHeight: 1.8, marginBottom: 40 }}>
|
<p style={{ fontFamily: FB, fontSize: 18, color: '#333', lineHeight: 1.8, marginBottom: 40 }}>
|
||||||
{winner.longDesc}
|
{winner.longDesc}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 style={{ fontFamily: FF, fontSize: 18, fontWeight: 800, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.04em', marginBottom: 20 }}>
|
<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>
|
</h2>
|
||||||
<p style={{ fontFamily: FB, fontSize: 18, color: '#333', lineHeight: 1.8, marginBottom: 40 }}>
|
<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>
|
</p>
|
||||||
|
|
||||||
{/* Quote */}
|
{/* Quote */}
|
||||||
@@ -122,21 +141,21 @@ export default function PreistraegerDetail() {
|
|||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 32 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 32 }}>
|
||||||
{/* Photo */}
|
{/* Photo */}
|
||||||
<div style={{ borderRadius: 0, overflow: 'hidden', aspectRatio: '4/3' }}>
|
<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>
|
</div>
|
||||||
|
|
||||||
{/* Kenndaten */}
|
{/* Kenndaten */}
|
||||||
<div style={{ background: BG_ALT, borderLeft: `3px solid ${GOLD}`, padding: '20px 24px' }}>
|
<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 }}>
|
<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>
|
</h3>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||||
{[
|
{[
|
||||||
{ label: 'Kategorie', value: winner.category, icon: <Trophy size={14} style={{ color: GOLD }} /> },
|
{ label: fallbackText(facts.categoryLabel, preistraegerDetailContent.facts.categoryLabel), value: winner.category, icon: <Trophy size={14} style={{ color: GOLD }} /> },
|
||||||
{ label: 'Auszeichnung', value: winner.type, 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: 'Jahr', value: String(winner.year), icon: null },
|
{ label: fallbackText(facts.yearLabel, preistraegerDetailContent.facts.yearLabel), value: String(winner.year), icon: null },
|
||||||
{ label: 'Standort', value: winner.location, icon: <MapPin size={14} style={{ color: GOLD }} /> },
|
{ label: fallbackText(facts.locationLabel, preistraegerDetailContent.facts.locationLabel), 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.industryLabel, preistraegerDetailContent.facts.industryLabel), value: winner.industry, icon: <Building2 size={14} style={{ color: GOLD }} /> },
|
||||||
].map(row => (
|
].map(row => (
|
||||||
<div key={row.label} style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
<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>
|
<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'; }}
|
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; }}
|
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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -170,12 +189,12 @@ export default function PreistraegerDetail() {
|
|||||||
{/* Back link */}
|
{/* Back link */}
|
||||||
<div style={{ padding: isMobile ? '32px 24px 48px' : '32px 80px 64px', borderTop: `1px solid ${BORDER}` }}>
|
<div style={{ padding: isMobile ? '32px 24px 48px' : '32px 80px 64px', borderTop: `1px solid ${BORDER}` }}>
|
||||||
<Link
|
<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' }}
|
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)}
|
onMouseEnter={e => ((e.currentTarget as HTMLElement).style.color = GOLD)}
|
||||||
onMouseLeave={e => ((e.currentTarget as HTMLElement).style.color = NAVY)}
|
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>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</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