From 9cf23552cc5a210be213ab8accefed18abe72bd0 Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Mon, 22 Jun 2026 13:00:02 +0200 Subject: [PATCH] feat: manage generic page fallback copy via payload --- src/SiteSettings/config.ts | 33 +++++++++++++++++++++++++ src/app/(frontend)/[[...slug]]/page.tsx | 5 +++- src/endpoints/seed/index.ts | 7 +++++- src/payload-types.ts | 26 +++++++++++++++++++ src/payload.config.ts | 3 ++- src/spa/NextApp.tsx | 5 +++- src/spa/cmsRoute.tsx | 3 +++ src/spa/pages/CmsGenericPage.tsx | 8 +++--- src/spa/siteSettings.ts | 18 ++++++++++++++ 9 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 src/SiteSettings/config.ts create mode 100644 src/spa/siteSettings.ts diff --git a/src/SiteSettings/config.ts b/src/SiteSettings/config.ts new file mode 100644 index 0000000..c66288f --- /dev/null +++ b/src/SiteSettings/config.ts @@ -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, + }, + ], +} diff --git a/src/app/(frontend)/[[...slug]]/page.tsx b/src/app/(frontend)/[[...slug]]/page.tsx index a7e2f68..b573701 100644 --- a/src/app/(frontend)/[[...slug]]/page.tsx +++ b/src/app/(frontend)/[[...slug]]/page.tsx @@ -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 | undefined)} cmsRoute={{ collection: route.collection, doc: route.doc as unknown as CmsRouteDoc, diff --git a/src/endpoints/seed/index.ts b/src/endpoints/seed/index.ts index d90dea4..5be8511 100644 --- a/src/endpoints/seed/index.ts +++ b/src/endpoints/seed/index.ts @@ -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!') diff --git a/src/payload-types.ts b/src/payload-types.ts index 687eb78..ccacc78 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -116,10 +116,12 @@ export interface Config { globals: { header: Header; footer: Footer; + 'site-settings': SiteSetting; }; globalsSelect: { header: HeaderSelect | HeaderSelect; footer: FooterSelect | FooterSelect; + 'site-settings': SiteSettingsSelect | SiteSettingsSelect; }; 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 { createdAt?: T; globalType?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "site-settings_select". + */ +export interface SiteSettingsSelect { + 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". diff --git a/src/payload.config.ts b/src/payload.config.ts index 4cd93e0..169744a 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -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, diff --git a/src/spa/NextApp.tsx b/src/spa/NextApp.tsx index 9334d29..2c3ee79 100644 --- a/src/spa/NextApp.tsx +++ b/src/spa/NextApp.tsx @@ -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 ( <> - + {page} diff --git a/src/spa/cmsRoute.tsx b/src/spa/cmsRoute.tsx index f1c3279..89ecfb7 100644 --- a/src/spa/cmsRoute.tsx +++ b/src/spa/cmsRoute.tsx @@ -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> + siteSettings?: SpaSiteSettingsData } const CmsRouteContext = createContext(undefined) diff --git a/src/spa/pages/CmsGenericPage.tsx b/src/spa/pages/CmsGenericPage.tsx index 8de7664..043a3dc 100644 --- a/src/spa/pages/CmsGenericPage.tsx +++ b/src/spa/pages/CmsGenericPage.tsx @@ -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 (
- {route?.collection || 'CMS'} + {route?.collection || siteSettings.genericPageKicker}

{title} @@ -31,7 +33,7 @@ export default function CmsGenericPage() {

) : (

- Diese Route wird aus Payload CMS geladen. Ergänze Inhalte im entsprechenden Collection-Dokument. + {siteSettings.genericPageDescription}

)}

diff --git a/src/spa/siteSettings.ts b/src/spa/siteSettings.ts new file mode 100644 index 0000000..0ee94a2 --- /dev/null +++ b/src/spa/siteSettings.ts @@ -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 | null): SpaSiteSettingsData { + return { + ...defaultSiteSettings, + ...settings, + } +}