feat: manage membership page via payload

This commit is contained in:
syntaxbullet
2026-06-22 11:44:35 +02:00
parent c35e45499e
commit 5bd1977c63
8 changed files with 1506 additions and 464 deletions

View File

@@ -14,6 +14,7 @@ import { datenschutzFields } from './datenschutzFields'
import { formularUploadFields } from './formularUploadFields'
import { homeFields } from './homeFields'
import { impressumFields } from './impressumFields'
import { mitgliedWerdenFields } from './mitgliedWerdenFields'
import { netzwerkFields } from './netzwerkFields'
import { participationFields } from './participationFields'
import { preistraegerIndexFields } from './preistraegerIndexFields'
@@ -111,6 +112,14 @@ const isNetzwerkPage = (_: unknown, siblingData?: { slug?: string; spaPath?: str
return spaPath === '/netzwerk' || slug === 'netzwerk' || slug === 'network' || title === 'netzwerk' || title === 'network'
}
const isMitgliedWerdenPage = (_: unknown, siblingData?: { slug?: string; spaPath?: string; title?: string }) => {
const slug = siblingData?.slug
const spaPath = siblingData?.spaPath
const title = siblingData?.title?.toLowerCase()
return spaPath === '/mitglied-werden' || slug === 'mitglied-werden' || slug === 'membership' || title === 'mitglied werden'
}
const isManagedSpaPage = (_: unknown, siblingData?: { slug?: string; spaPath?: string; title?: string }) =>
isHomePage(undefined, siblingData) ||
isContactPage(undefined, siblingData) ||
@@ -121,7 +130,8 @@ const isManagedSpaPage = (_: unknown, siblingData?: { slug?: string; spaPath?: s
isPreistraegerIndexPage(undefined, siblingData) ||
isPressIndexPage(undefined, siblingData) ||
isFormularUploadPage(undefined, siblingData) ||
isNetzwerkPage(undefined, siblingData)
isNetzwerkPage(undefined, siblingData) ||
isMitgliedWerdenPage(undefined, siblingData)
export const Pages: CollectionConfig<'pages'> = {
slug: 'pages',
@@ -259,6 +269,13 @@ export const Pages: CollectionConfig<'pages'> = {
fields: netzwerkFields,
label: 'Network Page',
},
{
admin: {
condition: (data) => isMitgliedWerdenPage(undefined, data),
},
fields: mitgliedWerdenFields,
label: 'Membership Page',
},
{
name: 'meta',
label: 'SEO',

View File

@@ -0,0 +1,303 @@
import type { Field } from 'payload'
import { mitgliedWerdenContent } from '@/spa/mitgliedWerdenContent'
const text = (name: string, label: string, defaultValue?: string): Field => ({
name,
type: 'text',
label,
defaultValue,
})
const textarea = (name: string, label: string, defaultValue?: string): Field => ({
name,
type: 'textarea',
label,
defaultValue,
})
const numberField = (name: string, label: string, defaultValue?: number): Field => ({
name,
type: 'number',
label,
defaultValue,
})
const checkbox = (name: string, label: string, defaultValue?: boolean): Field => ({
name,
type: 'checkbox',
label,
defaultValue,
})
const uploadField = (name: string, label: string, description?: string): Field => ({
name,
type: 'upload',
relationTo: 'media',
label,
admin: description ? { description } : undefined,
})
const sectionAdmin = (description: string) => ({
description,
initCollapsed: true,
})
const statFields: Field[] = [text('value', 'Value'), text('label', 'Label')]
const factFields: Field[] = [text('num', 'Number'), text('label', 'Label'), textarea('body', 'Body')]
const faqFields: Field[] = [text('q', 'Question'), textarea('a', 'Answer')]
const testimonialFields: Field[] = [
textarea('quote', 'Quote'),
text('initials', 'Initials'),
text('name', 'Name'),
text('role', 'Role'),
]
export const mitgliedWerdenFields: Field[] = [
{
name: 'mitgliedWerden',
label: 'Membership page content',
type: 'group',
admin: {
description:
'Edit the Mitglied werden page sections, pricing model, application choices, FAQ, testimonials, and wizard copy.',
},
fields: [
{
name: 'header',
label: '00 · Header',
type: 'group',
admin: sectionAdmin('Small top bar brand and return link.'),
fields: [
text('brandLabel', 'Brand label', mitgliedWerdenContent.header.brandLabel),
text('backLabel', 'Back link label', mitgliedWerdenContent.header.backLabel),
text('backUrl', 'Back link URL', mitgliedWerdenContent.header.backUrl),
],
},
{
name: 'hero',
label: '01 · Hero',
type: 'group',
admin: sectionAdmin('Hero image, headline, description, and desktop stats.'),
fields: [
uploadField('image', 'Hero image', `Current frontend image: /images/${mitgliedWerdenContent.hero.imageFilename}`),
text('imageAlt', 'Image alt text', mitgliedWerdenContent.hero.imageAlt),
text('eyebrow', 'Eyebrow', mitgliedWerdenContent.hero.eyebrow),
textarea('heading', 'Heading', mitgliedWerdenContent.hero.heading),
textarea('description', 'Description', mitgliedWerdenContent.hero.description),
{
name: 'stats',
label: 'Stats',
type: 'array',
dbName: 'member_hero_stats',
defaultValue: mitgliedWerdenContent.hero.stats,
fields: statFields,
},
],
},
{
name: 'benefits',
label: '02 · Benefits',
type: 'group',
admin: sectionAdmin('Benefit section heading and cards. Icon choices map to Lucide icons in the SPA.'),
fields: [
text('eyebrow', 'Eyebrow', mitgliedWerdenContent.benefits.eyebrow),
textarea('heading', 'Heading', mitgliedWerdenContent.benefits.heading),
{
name: 'cards',
label: 'Cards',
type: 'array',
dbName: 'member_benefits',
defaultValue: mitgliedWerdenContent.benefits.cards,
fields: [
{
name: 'icon',
label: 'Icon',
type: 'select',
defaultValue: 'users',
options: [
{ label: 'Users', value: 'users' },
{ label: 'Star', value: 'star' },
{ label: 'Eye', value: 'eye' },
{ label: 'Heart', value: 'heart' },
],
},
text('title', 'Title'),
textarea('body', 'Body'),
],
},
],
},
{
name: 'about',
label: '03 · About',
type: 'group',
admin: sectionAdmin('Association explainer, descriptor, and fact rows.'),
fields: [
text('eyebrow', 'Eyebrow', mitgliedWerdenContent.about.eyebrow),
textarea('heading', 'Heading', mitgliedWerdenContent.about.heading),
{
name: 'paragraphs',
label: 'Paragraphs',
type: 'array',
dbName: 'member_about_copy',
defaultValue: mitgliedWerdenContent.about.paragraphs.map((text) => ({ text })),
fields: [textarea('text', 'Text')],
},
text('descriptor', 'Descriptor', mitgliedWerdenContent.about.descriptor),
{
name: 'facts',
label: 'Facts',
type: 'array',
dbName: 'member_about_facts',
defaultValue: mitgliedWerdenContent.about.facts,
fields: factFields,
},
],
},
{
name: 'pricing',
label: '04 · Pricing calculator',
type: 'group',
admin: sectionAdmin('Calculator labels, table copy, VAT rate, admission fee, and tier rows.'),
fields: [
text('eyebrow', 'Eyebrow', mitgliedWerdenContent.pricing.eyebrow),
textarea('heading', 'Heading', mitgliedWerdenContent.pricing.heading),
text('employeeLabel', 'Employee select label', mitgliedWerdenContent.pricing.employeeLabel),
text('selectPlaceholder', 'Select placeholder', mitgliedWerdenContent.pricing.selectPlaceholder),
text('optionSuffix', 'Option suffix', mitgliedWerdenContent.pricing.optionSuffix),
text('resultEyebrow', 'Result eyebrow', mitgliedWerdenContent.pricing.resultEyebrow),
text('annualNetLabel', 'Annual net label', mitgliedWerdenContent.pricing.annualNetLabel),
text('annualGrossLabel', 'Annual gross label', mitgliedWerdenContent.pricing.annualGrossLabel),
text('annualGrossShortLabel', 'Annual gross short label', mitgliedWerdenContent.pricing.annualGrossShortLabel),
text('admissionGrossLabel', 'Admission gross label', mitgliedWerdenContent.pricing.admissionGrossLabel),
text('totalYearOneLabel', 'Total year-one label', mitgliedWerdenContent.pricing.totalYearOneLabel),
textarea('footnote', 'Footnote', mitgliedWerdenContent.pricing.footnote),
text('ctaLabel', 'CTA label', mitgliedWerdenContent.pricing.ctaLabel),
text('ctaHref', 'CTA URL', mitgliedWerdenContent.pricing.ctaHref),
text('tableToggleLabel', 'Table toggle label', mitgliedWerdenContent.pricing.tableToggleLabel),
text('tableEmployeeHeader', 'Table employees header', mitgliedWerdenContent.pricing.tableEmployeeHeader),
text('tableNetHeader', 'Table net header', mitgliedWerdenContent.pricing.tableNetHeader),
text('tableGrossHeader', 'Table gross header', mitgliedWerdenContent.pricing.tableGrossHeader),
text('tableNotePrefix', 'Table note prefix', mitgliedWerdenContent.pricing.tableNotePrefix),
text('tableNoteSuffix', 'Table note suffix', mitgliedWerdenContent.pricing.tableNoteSuffix),
numberField('vatRate', 'VAT rate', mitgliedWerdenContent.pricing.vatRate),
numberField('admissionFeeNet', 'Admission fee net', mitgliedWerdenContent.pricing.admissionFeeNet),
{
name: 'tiers',
label: 'Tiers',
type: 'array',
dbName: 'member_pricing',
defaultValue: mitgliedWerdenContent.pricing.tiers,
fields: [numberField('max', 'Maximum employees'), text('label', 'Label'), numberField('annual', 'Annual net amount')],
},
],
},
{
name: 'applicationOptions',
label: '05 · Application options',
type: 'group',
admin: sectionAdmin('Online and PDF application strip.'),
fields: [
{
name: 'items',
label: 'Options',
type: 'array',
dbName: 'member_app_options',
defaultValue: mitgliedWerdenContent.applicationOptions.items,
fields: [
text('id', 'Stable ID'),
text('number', 'Number'),
text('eyebrow', 'Eyebrow'),
text('title', 'Title'),
textarea('description', 'Description'),
text('href', 'URL'),
uploadField('file', 'Download file'),
checkbox('download', 'Download link'),
],
},
],
},
{
name: 'formIntro',
label: '06 · Form intro',
type: 'group',
admin: sectionAdmin('Left-hand pitch copy and wizard image/caption.'),
fields: [
text('eyebrow', 'Eyebrow', mitgliedWerdenContent.formIntro.eyebrow),
textarea('heading', 'Heading', mitgliedWerdenContent.formIntro.heading),
textarea('description', 'Description', mitgliedWerdenContent.formIntro.description),
{
name: 'promises',
label: 'Promise rows',
type: 'array',
dbName: 'member_promises',
defaultValue: mitgliedWerdenContent.formIntro.promises,
fields: [text('num', 'Number'), text('label', 'Label'), text('desc', 'Description')],
},
uploadField('image', 'Wizard image', `Current frontend image: /images/${mitgliedWerdenContent.formIntro.imageFilename}`),
text('imageAlt', 'Image alt text', mitgliedWerdenContent.formIntro.imageAlt),
text('imageLabel', 'Image caption', mitgliedWerdenContent.formIntro.imageLabel),
],
},
{
name: 'wizard',
label: '07 · Wizard copy JSON',
type: 'json',
admin: {
description:
'Deep wizard labels, options, validation messages, legal text, consent copy, success copy, and duplicated pricing defaults. Keep the object shape aligned with src/spa/mitgliedWerdenContent.ts.',
},
defaultValue: mitgliedWerdenContent.wizard,
},
{
name: 'testimonials',
label: '08 · Testimonials',
type: 'group',
admin: sectionAdmin('Member quotes and attribution cards.'),
fields: [
text('eyebrow', 'Eyebrow', mitgliedWerdenContent.testimonials.eyebrow),
textarea('heading', 'Heading', mitgliedWerdenContent.testimonials.heading),
{
name: 'items',
label: 'Quotes',
type: 'array',
dbName: 'member_quotes',
defaultValue: mitgliedWerdenContent.testimonials.items,
fields: testimonialFields,
},
],
},
{
name: 'faq',
label: '09 · FAQ',
type: 'group',
admin: sectionAdmin('FAQ heading and question/answer rows.'),
fields: [
text('eyebrow', 'Eyebrow', mitgliedWerdenContent.faq.eyebrow),
textarea('heading', 'Heading', mitgliedWerdenContent.faq.heading),
{
name: 'items',
label: 'Items',
type: 'array',
dbName: 'member_faq',
defaultValue: mitgliedWerdenContent.faq.items,
fields: faqFields,
},
],
},
{
name: 'bottomCta',
label: '10 · Bottom CTA',
type: 'group',
admin: sectionAdmin('Final call-to-action band.'),
fields: [
text('eyebrow', 'Eyebrow', mitgliedWerdenContent.bottomCta.eyebrow),
textarea('heading', 'Heading', mitgliedWerdenContent.bottomCta.heading),
text('ctaLabel', 'CTA label', mitgliedWerdenContent.bottomCta.ctaLabel),
text('ctaHref', 'CTA URL', mitgliedWerdenContent.bottomCta.ctaHref),
],
},
],
},
]