feat: manage generic page fallback copy via payload

This commit is contained in:
syntaxbullet
2026-06-22 13:00:02 +02:00
parent b4b4820e46
commit 9cf23552cc
9 changed files with 101 additions and 7 deletions

View File

@@ -0,0 +1,33 @@
import type { GlobalConfig } from 'payload'
import { defaultSiteSettings } from '@/spa/siteSettings'
export const SiteSettings: GlobalConfig = {
slug: 'site-settings',
access: {
read: () => true,
},
admin: {
group: 'Site Settings',
},
fields: [
{
name: 'genericPageKicker',
type: 'text',
defaultValue: defaultSiteSettings.genericPageKicker,
required: true,
},
{
name: 'genericPageTitle',
type: 'text',
defaultValue: defaultSiteSettings.genericPageTitle,
required: true,
},
{
name: 'genericPageDescription',
type: 'textarea',
defaultValue: defaultSiteSettings.genericPageDescription,
required: true,
},
],
}

View File

@@ -7,6 +7,7 @@ import { notFound } from 'next/navigation'
import NextApp from '@/spa/NextApp'
import { normalizeFooterData, normalizeHeaderData } from '@/spa/cmsNavigation'
import type { CmsRouteDoc } from '@/spa/cmsRoute'
import { normalizeSiteSettings, type SpaSiteSettingsData } from '@/spa/siteSettings'
export const dynamic = 'force-dynamic'
@@ -73,9 +74,10 @@ export default async function Page({ params }: Args) {
const pathname = `/${slug.join('/')}`.replace(/\/$/, '') || '/'
const payload = await getPayload({ config: configPromise })
const { isEnabled: draft } = await draftMode()
const [header, footer, route, preistraeger, events, posts] = await Promise.all([
const [header, footer, siteSettings, route, preistraeger, events, posts] = await Promise.all([
payload.findGlobal({ slug: 'header', depth: 1 }).catch(() => undefined),
payload.findGlobal({ slug: 'footer', depth: 1 }).catch(() => undefined),
payload.findGlobal({ slug: 'site-settings', depth: 0 }).catch(() => undefined),
findRouteDoc(payload, pathname, draft),
findList(payload, 'preistraeger', draft).catch(() => []),
findList(payload, 'events', draft).catch(() => []),
@@ -89,6 +91,7 @@ export default async function Page({ params }: Args) {
pathname={pathname}
header={normalizeHeaderData(header)}
footer={normalizeFooterData(footer)}
siteSettings={normalizeSiteSettings(siteSettings as Partial<SpaSiteSettingsData> | undefined)}
cmsRoute={{
collection: route.collection,
doc: route.doc as unknown as CmsRouteDoc,

View File

@@ -10,6 +10,7 @@ import { post1 } from './post-1'
import { post2 } from './post-2'
import { post3 } from './post-3'
import { defaultFooterData, defaultHeaderData } from '@/spa/cmsNavigation'
import { defaultSiteSettings } from '@/spa/siteSettings'
const collections: CollectionSlug[] = [
'categories',
@@ -21,7 +22,7 @@ const collections: CollectionSlug[] = [
'search',
]
const globals: GlobalSlug[] = ['header', 'footer']
const globals: GlobalSlug[] = ['header', 'footer', 'site-settings']
const categories = ['Technology', 'News', 'Finance', 'Design', 'Software', 'Engineering']
@@ -228,6 +229,10 @@ export const seed = async ({
slug: 'footer',
data: footerSeedData as never,
}),
payload.updateGlobal({
slug: 'site-settings',
data: defaultSiteSettings as never,
}),
])
payload.logger.info('Seeded database successfully!')

View File

@@ -116,10 +116,12 @@ export interface Config {
globals: {
header: Header;
footer: Footer;
'site-settings': SiteSetting;
};
globalsSelect: {
header: HeaderSelect<false> | HeaderSelect<true>;
footer: FooterSelect<false> | FooterSelect<true>;
'site-settings': SiteSettingsSelect<false> | SiteSettingsSelect<true>;
};
locale: null;
widgets: {
@@ -5886,6 +5888,18 @@ export interface Footer {
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "site-settings".
*/
export interface SiteSetting {
id: number;
genericPageKicker: string;
genericPageTitle: string;
genericPageDescription: string;
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "header_select".
@@ -6007,6 +6021,18 @@ export interface FooterSelect<T extends boolean = true> {
createdAt?: T;
globalType?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "site-settings_select".
*/
export interface SiteSettingsSelect<T extends boolean = true> {
genericPageKicker?: T;
genericPageTitle?: T;
genericPageDescription?: T;
updatedAt?: T;
createdAt?: T;
globalType?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "collections_widget".

View File

@@ -14,6 +14,7 @@ import { Users } from './collections/Users'
import { Footer } from './Footer/config'
import { Header } from './Header/config'
import { plugins } from './plugins'
import { SiteSettings } from './SiteSettings/config'
import { defaultLexical } from '@/fields/defaultLexical'
import { getServerSideURL } from './utilities/getURL'
@@ -66,7 +67,7 @@ export default buildConfig({
}),
collections: [Pages, Posts, Events, Preistraeger, Media, Categories, Users],
cors: [getServerSideURL()].filter(Boolean),
globals: [Header, Footer],
globals: [Header, Footer, SiteSettings],
plugins,
secret: process.env.PAYLOAD_SECRET,
sharp,

View File

@@ -6,6 +6,7 @@ import Layout from '@/spa/components/layout/Layout'
import ScrollToTop from '@/spa/components/layout/ScrollToTop'
import { defaultHeaderData, type SpaFooterData, type SpaHeaderData } from '@/spa/cmsNavigation'
import { CmsRouteProvider, type CmsRouteData } from '@/spa/cmsRoute'
import type { SpaSiteSettingsData } from '@/spa/siteSettings'
import About from '@/spa/pages/About'
import BlogDetail from '@/spa/pages/BlogDetail'
import Contact from '@/spa/pages/Contact'
@@ -58,11 +59,13 @@ export default function NextApp({
header,
footer,
cmsRoute,
siteSettings,
}: {
pathname: string
header?: SpaHeaderData
footer?: SpaFooterData
cmsRoute?: CmsRouteData
siteSettings?: SpaSiteSettingsData
}) {
const [loading, setLoading] = useState(true)
const headerData = header || defaultHeaderData
@@ -92,7 +95,7 @@ export default function NextApp({
return (
<>
<CmsRouteProvider value={cmsRoute}>
<CmsRouteProvider value={cmsRoute ? { ...cmsRoute, siteSettings } : undefined}>
<ScrollToTop />
<Layout header={header} footer={footer}>
{page}

View File

@@ -2,6 +2,8 @@
import React, { createContext, useContext } from 'react'
import type { SpaSiteSettingsData } from '@/spa/siteSettings'
export type CmsRouteCollection = 'pages' | 'posts' | 'events' | 'preistraeger'
export type CmsRouteDoc = {
@@ -21,6 +23,7 @@ export type CmsRouteData = {
collection: CmsRouteCollection
doc: CmsRouteDoc
lists?: Partial<Record<CmsRouteCollection, CmsRouteDoc[]>>
siteSettings?: SpaSiteSettingsData
}
const CmsRouteContext = createContext<CmsRouteData | undefined>(undefined)

View File

@@ -3,6 +3,7 @@
import React from 'react'
import { useCmsRoute } from '@/spa/cmsRoute'
import { defaultSiteSettings } from '@/spa/siteSettings'
const NAVY = '#111D55'
const GOLD = '#EFBF04'
@@ -13,14 +14,15 @@ 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 siteSettings = route?.siteSettings || defaultSiteSettings
const title = doc?.title || doc?.meta?.title || siteSettings.genericPageTitle
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'}
{route?.collection || siteSettings.genericPageKicker}
</div>
<h1 style={{ color: NAVY, fontFamily: FF, fontSize: 'clamp(2.25rem, 7vw, 5rem)', lineHeight: 1, fontWeight: 900, textTransform: 'uppercase', margin: 0 }}>
{title}
@@ -31,7 +33,7 @@ export default function CmsGenericPage() {
</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.
{siteSettings.genericPageDescription}
</p>
)}
</div>

18
src/spa/siteSettings.ts Normal file
View File

@@ -0,0 +1,18 @@
export type SpaSiteSettingsData = {
genericPageKicker: string
genericPageTitle: string
genericPageDescription: string
}
export const defaultSiteSettings: SpaSiteSettingsData = {
genericPageKicker: 'CMS',
genericPageTitle: 'Seite',
genericPageDescription: 'Diese Route wird aus Payload CMS geladen. Ergänze Inhalte im entsprechenden Collection-Dokument.',
}
export function normalizeSiteSettings(settings?: Partial<SpaSiteSettingsData> | null): SpaSiteSettingsData {
return {
...defaultSiteSettings,
...settings,
}
}