mirror of
https://github.com/complexcaresolutions/frontend.blogwoman.de.git
synced 2026-03-17 16:14:00 +00:00
Complete type migration removing all 33+ local interfaces and as-unknown-as casts. All components now use contracts types directly with type-safe relationship resolution via payload-helpers.ts. Key changes: - New payload-helpers.ts: resolveRelation, getMediaUrl, getMediaAlt, socialLinksToMap - types.ts: thin re-export layer from contracts (backward-compatible aliases) - api.ts: direct contracts types, no bridge casts, typed getSeoSettings - All 17 block components: correct CMS field names (headline, subline, cta group, etc.) - All route files: page.seo.metaTitle (not page.meta.title), getMediaUrl for unions - structuredData.ts: proper types for all schema generators - Footer: social links from separate collection via socialLinksToMap() - Header/Footer: resolveMedia for logo Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
import Image from 'next/image'
|
|
import type { Metadata } from 'next'
|
|
import { notFound } from 'next/navigation'
|
|
import { getPost, getSeoSettings, getSiteSettings } from '@/lib/api'
|
|
import { formatDate } from '@/lib/utils'
|
|
import { getMediaUrl, getMediaAlt } from '@/lib/payload-helpers'
|
|
import { RichTextRenderer } from '@/components/blocks'
|
|
import { generateBlogPostingSchema, generateBreadcrumbSchema } from '@/lib/structuredData'
|
|
|
|
interface NewsPostPageProps {
|
|
params: Promise<{ slug: string }>
|
|
}
|
|
|
|
export async function generateMetadata({ params }: NewsPostPageProps): Promise<Metadata> {
|
|
const { slug } = await params
|
|
const [post, seoSettings] = await Promise.all([
|
|
getPost(slug),
|
|
getSeoSettings(),
|
|
])
|
|
|
|
if (!post) return {}
|
|
|
|
const titleSuffix = seoSettings?.metaDefaults?.titleSuffix || ''
|
|
const titleBase = post.seo?.metaTitle || post.title
|
|
const title = titleSuffix ? `${titleBase} ${titleSuffix}` : titleBase
|
|
const description =
|
|
post.seo?.metaDescription || post.excerpt || seoSettings?.metaDefaults?.defaultDescription
|
|
const image =
|
|
getMediaUrl(post.seo?.ogImage) ||
|
|
getMediaUrl(post.featuredImage) ||
|
|
getMediaUrl(seoSettings?.metaDefaults?.defaultOgImage)
|
|
|
|
return {
|
|
title,
|
|
description,
|
|
openGraph: {
|
|
title,
|
|
description: description || undefined,
|
|
images: image ? [{ url: image }] : undefined,
|
|
type: 'article',
|
|
},
|
|
twitter: {
|
|
card: 'summary_large_image',
|
|
title,
|
|
description: description || undefined,
|
|
images: image ? [image] : undefined,
|
|
},
|
|
}
|
|
}
|
|
|
|
export default async function NewsPostPage({ params }: NewsPostPageProps) {
|
|
const { slug } = await params
|
|
const [post, settings] = await Promise.all([
|
|
getPost(slug),
|
|
getSiteSettings(),
|
|
])
|
|
|
|
if (!post) {
|
|
notFound()
|
|
}
|
|
|
|
const imageUrl = getMediaUrl(post.featuredImage)
|
|
const blogSchema = generateBlogPostingSchema(post, settings)
|
|
const breadcrumbSchema = generateBreadcrumbSchema([
|
|
{ name: 'Startseite', url: '/' },
|
|
{ name: 'News', url: '/news' },
|
|
{ name: post.title, url: `/news/${post.slug}` },
|
|
])
|
|
|
|
return (
|
|
<article className="py-12 md:py-16">
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{
|
|
__html: JSON.stringify([blogSchema, breadcrumbSchema]),
|
|
}}
|
|
/>
|
|
|
|
<div className="container max-w-3xl">
|
|
<header className="mb-10 text-center">
|
|
<p className="text-sm text-warm-gray-dark mb-3">
|
|
{post.publishedAt ? formatDate(post.publishedAt) : formatDate(post.createdAt)}
|
|
</p>
|
|
<h1 className="mb-6">{post.title}</h1>
|
|
{post.excerpt && (
|
|
<p className="text-lg text-espresso/80">{post.excerpt}</p>
|
|
)}
|
|
</header>
|
|
|
|
{imageUrl && (
|
|
<div className="relative aspect-[16/9] rounded-2xl overflow-hidden bg-warm-gray mb-10">
|
|
<Image
|
|
src={imageUrl}
|
|
alt={getMediaAlt(post.featuredImage, post.title)}
|
|
fill
|
|
className="object-cover"
|
|
priority
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{post.content && <RichTextRenderer content={post.content} />}
|
|
</div>
|
|
</article>
|
|
)
|
|
}
|