chore: migrate pages and media to be cms controlled.
This commit is contained in:
@@ -2,7 +2,9 @@ import React from 'react';
|
||||
import { useParams, Link } from '@/spa/router';
|
||||
import { ArrowLeft, Clock, User, Tag, ArrowRight } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { getArticleBySlug, articles } from '@/spa/data/blog';
|
||||
import { getArticleBySlug, articles, type BlogArticle } from '@/spa/data/blog';
|
||||
import { useCmsRoute } from '@/spa/cmsRoute';
|
||||
import { docImageUrl } from '@/spa/cmsMediaField';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const NAVY = '#111D55';
|
||||
@@ -21,7 +23,22 @@ const CAT_COLORS: Record<string, string> = {
|
||||
export default function BlogDetail() {
|
||||
const isMobile = useIsMobile();
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const article = slug ? getArticleBySlug(slug) : undefined;
|
||||
const route = useCmsRoute();
|
||||
const cmsArticle = route?.collection === 'posts' ? route.doc : undefined;
|
||||
const article = (slug ? getArticleBySlug(slug) : undefined) || (cmsArticle ? {
|
||||
slug: String(cmsArticle.slug || slug),
|
||||
title: String(cmsArticle.title || 'Presse'),
|
||||
cat: String(cmsArticle.cat || 'Presse'),
|
||||
date: String(cmsArticle.date || 'Aktuell'),
|
||||
author: String(cmsArticle.authorName || 'BMP Redaktion'),
|
||||
authorRole: String(cmsArticle.authorRole || 'Presse'),
|
||||
readTime: String(cmsArticle.readTime || '1 min'),
|
||||
img: docImageUrl(cmsArticle, '/images/gala-saal-overview.jpg', 'heroImage'),
|
||||
excerpt: String(cmsArticle.excerpt || cmsArticle.meta?.description || 'Dieser Pressebeitrag wird aus Payload CMS geladen.'),
|
||||
sections: Array.isArray(cmsArticle.sections) && cmsArticle.sections.length > 0 ? cmsArticle.sections as any : [{ body: String(cmsArticle.excerpt || cmsArticle.meta?.description || 'Dieser Pressebeitrag wird aus Payload CMS geladen.') }],
|
||||
tags: Array.isArray(cmsArticle.tags) ? cmsArticle.tags.map((item: any) => item.tag).filter(Boolean) : ['Presse'],
|
||||
relatedSlugs: Array.isArray(cmsArticle.relatedSlugs) ? cmsArticle.relatedSlugs.map((item: any) => item.slug).filter(Boolean) : [],
|
||||
} as BlogArticle : undefined);
|
||||
|
||||
if (!article) {
|
||||
return (
|
||||
|
||||
40
src/spa/pages/CmsGenericPage.tsx
Normal file
40
src/spa/pages/CmsGenericPage.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import { useCmsRoute } from '@/spa/cmsRoute'
|
||||
|
||||
const NAVY = '#111D55'
|
||||
const GOLD = '#EFBF04'
|
||||
const CREAM = '#EFE5E3'
|
||||
const FF = '"IBM Plex Sans", sans-serif'
|
||||
const FB = '"Inter", sans-serif'
|
||||
|
||||
export default function CmsGenericPage() {
|
||||
const route = useCmsRoute()
|
||||
const doc = route?.doc
|
||||
const title = doc?.title || doc?.meta?.title || 'Seite'
|
||||
const description = doc?.description || doc?.meta?.description
|
||||
|
||||
return (
|
||||
<main style={{ minHeight: '70vh', background: CREAM, padding: '140px 24px 80px' }}>
|
||||
<div style={{ maxWidth: 960, margin: '0 auto' }}>
|
||||
<div style={{ color: GOLD, fontFamily: FF, fontWeight: 800, fontSize: 12, letterSpacing: '0.18em', textTransform: 'uppercase', marginBottom: 20 }}>
|
||||
{route?.collection || 'CMS'}
|
||||
</div>
|
||||
<h1 style={{ color: NAVY, fontFamily: FF, fontSize: 'clamp(2.25rem, 7vw, 5rem)', lineHeight: 1, fontWeight: 900, textTransform: 'uppercase', margin: 0 }}>
|
||||
{title}
|
||||
</h1>
|
||||
{description ? (
|
||||
<p style={{ color: 'rgba(58,58,58,0.78)', fontFamily: FB, fontSize: 22, lineHeight: 1.6, marginTop: 28, maxWidth: 760 }}>
|
||||
{description}
|
||||
</p>
|
||||
) : (
|
||||
<p style={{ color: 'rgba(58,58,58,0.65)', fontFamily: FB, fontSize: 18, lineHeight: 1.7, marginTop: 28, maxWidth: 760 }}>
|
||||
Diese Route wird aus Payload CMS geladen. Ergänze Inhalte im entsprechenden Collection-Dokument.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,9 @@ import React, { useState } from 'react'
|
||||
import { useParams, Link } from '@/spa/router'
|
||||
import { motion } from 'framer-motion'
|
||||
import { Calendar, MapPin, Clock, Bell, BellOff, ArrowLeft, ChevronRight, Users, Tag } from 'lucide-react'
|
||||
import { getEventBySlug, EventUpdate, UpdateType } from '@/spa/data/events'
|
||||
import { getEventBySlug, EventUpdate, UpdateType, type BmpEvent } from '@/spa/data/events'
|
||||
import { useCmsRoute } from '@/spa/cmsRoute'
|
||||
import { docImageUrl } from '@/spa/cmsMediaField'
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile'
|
||||
|
||||
const NAVY = '#111D55'
|
||||
@@ -75,7 +77,24 @@ function UpdateCard({ update, index }: { update: EventUpdate; index: number }) {
|
||||
// ── Main Component ────────────────────────────────────────────────────────────
|
||||
export default function EventDetail() {
|
||||
const { slug } = useParams<{ slug: string }>()
|
||||
const event = getEventBySlug(slug ?? '')
|
||||
const route = useCmsRoute()
|
||||
const cmsEvent = route?.collection === 'events' ? route.doc : undefined
|
||||
const event = getEventBySlug(slug ?? '') || (cmsEvent ? {
|
||||
slug: String(cmsEvent.slug || slug),
|
||||
title: String(cmsEvent.title || 'Event'),
|
||||
subtitle: String(cmsEvent.subtitle || cmsEvent.description || cmsEvent.meta?.description || ''),
|
||||
date: String(cmsEvent.date || 'Termin folgt'),
|
||||
time: String(cmsEvent.time || 'Uhrzeit folgt'),
|
||||
location: String(cmsEvent.location || 'Bayern'),
|
||||
venue: String(cmsEvent.venue || 'Ort folgt'),
|
||||
category: String(cmsEvent.category || 'Event'),
|
||||
status: (cmsEvent.status as any) || 'geplant',
|
||||
img: docImageUrl(cmsEvent, '/images/buehne-moderatoren.jpg'),
|
||||
description: String(cmsEvent.description || cmsEvent.meta?.description || ''),
|
||||
longDescription: String(cmsEvent.longDescription || cmsEvent.description || cmsEvent.meta?.description || 'Dieses Event wird aus Payload CMS geladen.'),
|
||||
eckdaten: Array.isArray(cmsEvent.eckdaten) ? cmsEvent.eckdaten as any : [],
|
||||
updates: Array.isArray(cmsEvent.updates) ? cmsEvent.updates as any : [],
|
||||
} as BmpEvent : undefined)
|
||||
const [subscribed, setSubscribed] = useState(false)
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import React, { useState, useMemo } from 'react';
|
||||
import { Link } from '@/spa/router';
|
||||
import { Trophy, Search, ChevronDown, ChevronRight, X, ArrowRight } from 'lucide-react';
|
||||
import { WINNERS, CATEGORIES, YEARS } from '@/spa/data/winners';
|
||||
import { useCmsCollection } from '@/spa/cmsRoute';
|
||||
import { docImageUrl } from '@/spa/cmsMediaField';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
@@ -17,14 +19,41 @@ export default function Preistraeger() {
|
||||
const [filterCategory, setFilterCategory] = useState('');
|
||||
const [search, setSearch] = useState('');
|
||||
const [mediaOnly, setMediaOnly] = useState(false);
|
||||
const cmsPreistraeger = useCmsCollection('preistraeger');
|
||||
const winners = useMemo(() => {
|
||||
const existingSlugs = new Set(WINNERS.map((winner) => winner.slug));
|
||||
return [
|
||||
...WINNERS,
|
||||
...cmsPreistraeger
|
||||
.filter((doc) => doc.slug && !existingSlugs.has(String(doc.slug)))
|
||||
.map((doc) => ({
|
||||
id: String(doc.id),
|
||||
slug: String(doc.slug),
|
||||
name: String(doc.title || 'Preisträger'),
|
||||
category: String(doc.category || 'Preisträger'),
|
||||
year: Number(doc.year || new Date().getFullYear()),
|
||||
type: String(doc.awardType || 'Auszeichnung'),
|
||||
img: docImageUrl(doc, '/images/gala-saal-overview.jpg'),
|
||||
shortDesc: String(doc.shortDesc || doc.description || doc.meta?.description || ''),
|
||||
longDesc: String(doc.longDesc || doc.description || doc.meta?.description || ''),
|
||||
quote: String(doc.quote || ''),
|
||||
quotePerson: String(doc.quotePerson || ''),
|
||||
quoteRole: String(doc.quoteRole || ''),
|
||||
location: String(doc.location || 'Bayern'),
|
||||
industry: String(doc.industry || 'Mittelstand'),
|
||||
website: String(doc.website || '#'),
|
||||
hasMedia: Boolean(doc.hasMedia),
|
||||
})),
|
||||
];
|
||||
}, [cmsPreistraeger]);
|
||||
|
||||
const filtered = useMemo(() => WINNERS.filter(w => {
|
||||
const filtered = useMemo(() => winners.filter(w => {
|
||||
if (w.year !== activeYear) return false;
|
||||
if (filterCategory && w.category !== filterCategory) return false;
|
||||
if (search && !w.name.toLowerCase().includes(search.toLowerCase())) return false;
|
||||
if (mediaOnly && !w.hasMedia) return false;
|
||||
return true;
|
||||
}), [activeYear, filterCategory, search, mediaOnly]);
|
||||
}), [activeYear, filterCategory, search, mediaOnly, winners]);
|
||||
|
||||
const reset = () => { setFilterCategory(''); setSearch(''); setMediaOnly(false); };
|
||||
const hasFilter = !!(filterCategory || search || mediaOnly);
|
||||
|
||||
@@ -2,6 +2,8 @@ import React from 'react';
|
||||
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 { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
@@ -14,7 +16,26 @@ const BG_ALT = '#E4E2E3';
|
||||
|
||||
export default function PreistraegerDetail() {
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const winner = WINNERS.find(w => w.slug === slug);
|
||||
const route = useCmsRoute();
|
||||
const cmsWinner = route?.collection === 'preistraeger' ? route.doc : undefined;
|
||||
const winner = WINNERS.find(w => w.slug === slug) || (cmsWinner ? {
|
||||
id: String(cmsWinner.id),
|
||||
slug: String(cmsWinner.slug || slug),
|
||||
name: String(cmsWinner.title || 'Preisträger'),
|
||||
category: String(cmsWinner.category || 'Preisträger'),
|
||||
year: Number(cmsWinner.year || new Date().getFullYear()),
|
||||
type: String(cmsWinner.awardType || 'Auszeichnung'),
|
||||
img: docImageUrl(cmsWinner, '/images/gala-saal-overview.jpg'),
|
||||
shortDesc: String(cmsWinner.shortDesc || cmsWinner.description || cmsWinner.meta?.description || ''),
|
||||
longDesc: String(cmsWinner.longDesc || cmsWinner.description || cmsWinner.meta?.description || 'Dieser Preisträger wird aus Payload CMS geladen.'),
|
||||
quote: String(cmsWinner.quote || 'Diese Erfolgsgeschichte wird aktuell in Payload CMS gepflegt.'),
|
||||
quotePerson: String(cmsWinner.quotePerson || cmsWinner.title || 'BMP'),
|
||||
quoteRole: String(cmsWinner.quoteRole || 'Preisträger'),
|
||||
location: String(cmsWinner.location || 'Bayern'),
|
||||
industry: String(cmsWinner.industry || 'Mittelstand'),
|
||||
website: String(cmsWinner.website || '#'),
|
||||
hasMedia: Boolean(cmsWinner.hasMedia),
|
||||
} : undefined);
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
if (!winner) return <Navigate to="/preistraeger" replace />;
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Link } from '@/spa/router';
|
||||
import { Calendar, MapPin, Download, ArrowRight, FileText } from 'lucide-react';
|
||||
import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
import { useCmsCollection } from '@/spa/cmsRoute';
|
||||
import { docImageUrl } from '@/spa/cmsMediaField';
|
||||
|
||||
const NAVY = '#111D55';
|
||||
const GOLD = '#EFBF04';
|
||||
@@ -183,6 +185,41 @@ function NewsCard({ item, idx, isMobile }: { item: typeof news[0]; idx: number;
|
||||
|
||||
const Press: React.FC = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const cmsEvents = useCmsCollection('events');
|
||||
const cmsPosts = useCmsCollection('posts');
|
||||
const combinedEvents = React.useMemo(() => {
|
||||
const existing = new Set(events.map((event) => event.slug.split('/').pop()));
|
||||
return [
|
||||
...events,
|
||||
...cmsEvents
|
||||
.filter((event) => event.slug && !existing.has(String(event.slug)))
|
||||
.map((event) => ({
|
||||
title: String(event.title || 'Event'),
|
||||
date: String(event.date || 'Termin folgt'),
|
||||
location: String(event.location || event.venue || 'Bayern'),
|
||||
cat: String(event.category || 'Event'),
|
||||
status: String(event.status || 'Geplant'),
|
||||
img: docImageUrl(event, '/images/buehne-moderatoren.jpg'),
|
||||
desc: String(event.description || event.meta?.description || ''),
|
||||
slug: String(event.spaPath || `/presse/events/${event.slug}`),
|
||||
})),
|
||||
];
|
||||
}, [cmsEvents]);
|
||||
const combinedNews = React.useMemo(() => {
|
||||
const existing = new Set(news.map((item) => item.slug.split('/').pop()));
|
||||
return [
|
||||
...news,
|
||||
...cmsPosts
|
||||
.filter((post) => post.slug && !existing.has(String(post.slug)))
|
||||
.map((post) => ({
|
||||
title: String(post.title || 'Presse'),
|
||||
excerpt: String(post.excerpt || post.meta?.description || ''),
|
||||
cat: String(post.cat || 'Presse'),
|
||||
img: docImageUrl(post, '/images/gala-saal-overview.jpg', 'heroImage'),
|
||||
slug: String(post.spaPath || `/presse/blog/${post.slug}`),
|
||||
})),
|
||||
];
|
||||
}, [cmsPosts]);
|
||||
const [hoveredEvent, setHoveredEvent] = useState<number | null>(null);
|
||||
const [dlHovered, setDlHovered] = useState(false);
|
||||
const [submitHovered, setSubmitHovered] = useState(false);
|
||||
@@ -375,7 +412,7 @@ const Press: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* Event rows */}
|
||||
{events.map((event, idx) => (
|
||||
{combinedEvents.map((event, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
onMouseEnter={() => setHoveredEvent(idx)}
|
||||
@@ -477,7 +514,7 @@ const Press: React.FC = () => {
|
||||
|
||||
{/* 3-column news grid */}
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(3, 1fr)' }}>
|
||||
{news.map((item, idx) => (
|
||||
{combinedNews.map((item, idx) => (
|
||||
<NewsCard key={idx} item={item} idx={idx} isMobile={isMobile} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user