feat: manage blog detail fallback media via payload
This commit is contained in:
@@ -138,6 +138,15 @@ export const Posts: CollectionConfig<'posts'> = {
|
||||
'Shared labels, category colors, meta copy, sidebar CTA, and related-article labels for the Presse detail route.',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'fallbackImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Fallback hero image',
|
||||
admin: {
|
||||
description: `Current frontend fallback image: /images/${blogDetailContent.fallbackImageFilename}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'notFound',
|
||||
label: 'Not found state',
|
||||
|
||||
@@ -2139,6 +2139,10 @@ export interface Post {
|
||||
* Shared labels, category colors, meta copy, sidebar CTA, and related-article labels for the Presse detail route.
|
||||
*/
|
||||
detailPage?: {
|
||||
/**
|
||||
* Current frontend fallback image: /images/gala-saal-overview.jpg
|
||||
*/
|
||||
fallbackImage?: (number | null) | Media;
|
||||
notFound?: {
|
||||
title?: string | null;
|
||||
backLabel?: string | null;
|
||||
@@ -4998,6 +5002,7 @@ export interface PostsSelect<T extends boolean = true> {
|
||||
detailPage?:
|
||||
| T
|
||||
| {
|
||||
fallbackImage?: T;
|
||||
notFound?:
|
||||
| T
|
||||
| {
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
import 'dotenv/config'
|
||||
|
||||
import config from '@payload-config'
|
||||
import { getPayload } from 'payload'
|
||||
import { getPayload, type Payload } from 'payload'
|
||||
|
||||
import { blogDetailContent } from '@/spa/blogDetailContent'
|
||||
|
||||
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 fallbackImage = await mediaByFilename(payload, blogDetailContent.fallbackImageFilename)
|
||||
const { fallbackImageFilename: _fallbackImageFilename, ...detailContent } = blogDetailContent
|
||||
|
||||
const posts = await payload.find({
|
||||
collection: 'posts',
|
||||
@@ -25,7 +37,8 @@ async function main() {
|
||||
context: { disableRevalidate: true },
|
||||
data: {
|
||||
detailPage: {
|
||||
...blogDetailContent,
|
||||
...detailContent,
|
||||
fallbackImage,
|
||||
metaCopy: blogDetailContent.meta,
|
||||
},
|
||||
} as never,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const blogDetailContent = {
|
||||
fallbackImageFilename: 'gala-saal-overview.jpg',
|
||||
notFound: {
|
||||
title: 'Artikel nicht gefunden',
|
||||
backLabel: 'Zurück zur Presse',
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ArrowLeft, Clock, User, Tag, ArrowRight } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { getArticleBySlug, articles, type BlogArticle, type BlogSection } from '@/spa/data/blog';
|
||||
import { useCmsRoute } from '@/spa/cmsRoute';
|
||||
import { docImageUrl } from '@/spa/cmsMediaField';
|
||||
import { docImageUrl, mediaUrl } from '@/spa/cmsMediaField';
|
||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||
import { blogDetailContent } from '@/spa/blogDetailContent';
|
||||
import Image from '@/spa/components/ui/UnoptimizedImage'
|
||||
@@ -17,6 +17,7 @@ const FF = '"IBM Plex Sans", sans-serif';
|
||||
const FB = '"Inter", sans-serif';
|
||||
|
||||
type BlogDetailCms = Omit<Partial<typeof blogDetailContent>, 'categoryColors'> & {
|
||||
fallbackImage?: unknown
|
||||
categoryColors?: unknown
|
||||
metaCopy?: Partial<typeof blogDetailContent.meta>
|
||||
}
|
||||
@@ -46,6 +47,8 @@ export default function BlogDetail() {
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const route = useCmsRoute();
|
||||
const cmsArticle = route?.collection === 'posts' ? route.doc : undefined;
|
||||
const cmsDetail = getDetailPage(cmsArticle?.detailPage);
|
||||
const articleFallbackImage = mediaUrl(cmsDetail.fallbackImage, `/images/${blogDetailContent.fallbackImageFilename}`);
|
||||
const article = (slug ? getArticleBySlug(slug) : undefined) || (cmsArticle ? {
|
||||
slug: String(cmsArticle.slug || slug),
|
||||
title: String(cmsArticle.title || 'Presse'),
|
||||
@@ -54,7 +57,7 @@ export default function BlogDetail() {
|
||||
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'),
|
||||
img: docImageUrl(cmsArticle, articleFallbackImage, '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 BlogSection[] : [{ body: String(cmsArticle.excerpt || cmsArticle.meta?.description || 'Dieser Pressebeitrag wird aus Payload CMS geladen.') }],
|
||||
tags: Array.isArray(cmsArticle.tags)
|
||||
@@ -69,7 +72,7 @@ export default function BlogDetail() {
|
||||
: [],
|
||||
detailPage: cmsArticle.detailPage,
|
||||
} as BlogArticle : undefined);
|
||||
const detail = getDetailPage(article && 'detailPage' in article ? article.detailPage : undefined);
|
||||
const detail = getDetailPage(article && 'detailPage' in article ? article.detailPage : cmsDetail);
|
||||
const notFound = detail.notFound || blogDetailContent.notFound;
|
||||
const heroBackLink = detail.heroBackLink || blogDetailContent.heroBackLink;
|
||||
const categoryColors = colorMap(detail.categoryColors);
|
||||
|
||||
Reference in New Issue
Block a user