feat: manage participation page via payload

This commit is contained in:
syntaxbullet
2026-06-22 10:40:10 +02:00
parent ae98269b03
commit 2e9ad5cdf8
9 changed files with 1886 additions and 360 deletions

View File

@@ -0,0 +1,400 @@
import type { Field } from 'payload'
import { participationContent } from '@/spa/participationContent'
const uploadField = (name: string, label: string, description?: string): Field => ({
name,
type: 'upload',
relationTo: 'media',
label,
admin: description ? { description } : undefined,
})
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 checkbox = (name: string, label: string, defaultValue = false): Field => ({
name,
type: 'checkbox',
label,
defaultValue,
})
const sectionAdmin = (description: string) => ({
description,
initCollapsed: true,
})
const linkGroup = (labelDefault: string, urlDefault: string): Field[] => [
text('label', 'Label', labelDefault),
text('url', 'URL', urlDefault),
]
const iconOptions = [
{ label: 'Alert circle', value: 'alertCircle' },
{ label: 'Arrow right', value: 'arrowRight' },
{ label: 'Building', value: 'building2' },
{ label: 'Check', value: 'check' },
{ label: 'Check circle', value: 'checkCircle2' },
{ label: 'File text', value: 'fileText' },
{ label: 'Map pin', value: 'mapPin' },
{ label: 'Search', value: 'search' },
{ label: 'Send', value: 'send' },
{ label: 'Star', value: 'star' },
{ label: 'Trending up', value: 'trendingUp' },
{ label: 'Trophy', value: 'trophy' },
{ label: 'User', value: 'user' },
{ label: 'User check', value: 'userCheck' },
{ label: 'User plus', value: 'userPlus' },
{ label: 'Users', value: 'users' },
]
const iconField = (defaultValue = 'star', dbName?: string): Field => ({
name: 'icon',
label: 'Icon',
type: 'select',
dbName,
defaultValue,
options: iconOptions,
})
const factFields: Field[] = [text('label', 'Label'), textarea('desc', 'Description')]
const ctaGroup = (name: string, label: string, labelDefault: string, urlDefault: string): Field => ({
name,
label,
type: 'group',
fields: linkGroup(labelDefault, urlDefault),
})
export const participationFields: Field[] = [
{
name: 'participation',
label: 'Participation page layout sections',
type: 'group',
admin: {
description:
'Edit the participation page in the same order it appears on the frontend. The generic Hero and Content tabs are hidden for this SPA-managed route.',
},
fields: [
{
name: 'hero',
label: '01 · Hero',
type: 'group',
admin: sectionAdmin('Top image, eyebrow, headline, and intro copy.'),
fields: [
uploadField('backgroundImage', 'Background image', `Current frontend image: /images/${participationContent.hero.backgroundImageFilename}`),
text('imageAlt', 'Image alt text', participationContent.hero.imageAlt),
text('eyebrow', 'Eyebrow', participationContent.hero.eyebrow),
textarea('heading', 'Headline', participationContent.hero.heading),
textarea('description', 'Intro paragraph', participationContent.hero.description),
],
},
{
name: 'process',
label: '02 · Weg zur Auszeichnung timeline',
type: 'group',
admin: sectionAdmin('Timeline section rendered directly after the hero.'),
fields: [
text('eyebrow', 'Eyebrow', participationContent.process.eyebrow),
textarea('heading', 'Heading', participationContent.process.heading),
textarea('description', 'Intro paragraph', participationContent.process.description),
text('scrollHint', 'Desktop scroll hint', participationContent.process.scrollHint),
text('progressLabel', 'Desktop progress label', participationContent.process.progressLabel),
{
name: 'steps',
label: 'Process steps',
type: 'array',
dbName: 'proc_steps',
defaultValue: participationContent.process.steps,
fields: [text('step', 'Step number'), text('title', 'Title'), textarea('desc', 'Description'), text('date', 'Date'), iconField('fileText')],
},
],
},
{
name: 'eligibility',
label: '03 · Eligibility section',
type: 'group',
admin: sectionAdmin('Cream eligibility section with criteria rows, notes, and CTAs.'),
fields: [
text('eyebrow', 'Eyebrow', participationContent.eligibility.eyebrow),
textarea('heading', 'Heading', participationContent.eligibility.heading),
textarea('description', 'Intro paragraph', participationContent.eligibility.description),
ctaGroup('primaryCta', 'Primary CTA', participationContent.eligibility.primaryCta.label, participationContent.eligibility.primaryCta.url),
ctaGroup('secondaryCta', 'Secondary CTA', participationContent.eligibility.secondaryCta.label, participationContent.eligibility.secondaryCta.url),
{
name: 'criteria',
label: 'Eligibility rows',
type: 'array',
dbName: 'elig_crit',
defaultValue: participationContent.eligibility.criteria,
fields: [text('num', 'Number'), text('title', 'Title'), textarea('body', 'Body'), iconField('mapPin')],
},
{
name: 'notes',
label: 'Additional notes',
type: 'array',
dbName: 'elig_notes',
defaultValue: participationContent.eligibility.notes,
fields: [text('title', 'Title'), textarea('body', 'Body'), iconField('building2')],
},
text('nudgeText', 'Bottom nudge text', participationContent.eligibility.nudgeText),
ctaGroup('nudgeCta', 'Bottom CTA', participationContent.eligibility.nudgeCta.label, participationContent.eligibility.nudgeCta.url),
],
},
{
name: 'evaluation',
label: '04 · Criteria / evaluation section',
type: 'group',
admin: sectionAdmin('Navy evaluation section with image, link, and criteria grid.'),
fields: [
uploadField('image', 'Image', `Current frontend image: /images/${participationContent.evaluation.imageFilename}`),
text('imageAlt', 'Image alt text', participationContent.evaluation.imageAlt),
text('eyebrow', 'Eyebrow', participationContent.evaluation.eyebrow),
textarea('heading', 'Heading', participationContent.evaluation.heading),
textarea('body', 'Body copy', participationContent.evaluation.body),
ctaGroup('link', 'External criteria link', participationContent.evaluation.link.label, participationContent.evaluation.link.url),
{
name: 'criteria',
label: 'Evaluation criteria',
type: 'array',
dbName: 'eval_crit',
defaultValue: participationContent.evaluation.criteria,
fields: factFields,
},
],
},
{
name: 'applicationWays',
label: '05 · Application paths',
type: 'group',
admin: sectionAdmin('Cream section explaining proposal, self-application, and special award paths.'),
fields: [
text('eyebrow', 'Eyebrow', participationContent.applicationWays.eyebrow),
textarea('heading', 'Heading', participationContent.applicationWays.heading),
textarea('description', 'Intro paragraph', participationContent.applicationWays.description),
text('recommendedLabel', 'Featured card label', participationContent.applicationWays.recommendedLabel),
{
name: 'institutions',
label: 'Proposal institutions',
type: 'array',
dbName: 'app_inst',
defaultValue: participationContent.applicationWays.institutions,
fields: [textarea('text', 'Institution')],
},
{
name: 'ways',
label: 'Application path cards',
type: 'array',
dbName: 'app_ways',
defaultValue: participationContent.applicationWays.ways,
fields: [
text('label', 'Path label'),
textarea('title', 'Title'),
textarea('desc', 'Description'),
iconField('send', 'ico'),
checkbox('featured', 'Featured card'),
{
name: 'listType',
label: 'Detail list type',
type: 'select',
dbName: 'lt',
defaultValue: 'facts',
options: [
{ label: 'Use proposal institutions list', value: 'institutions' },
{ label: 'Use facts below', value: 'facts' },
],
},
{
name: 'facts',
label: 'Fact rows',
type: 'array',
dbName: 'way_facts',
fields: factFields,
},
ctaGroup('cta', 'CTA', 'Zum Formular', '#bewerben'),
],
},
],
},
{
name: 'datesBanner',
label: '06 · Dates banner',
type: 'group',
admin: sectionAdmin('Gold important-dates banner. Mobile and desktop labels differ slightly in the current design.'),
fields: [
text('heading', 'Heading', participationContent.datesBanner.heading),
textarea('description', 'Desktop description', participationContent.datesBanner.description),
{
name: 'mobileItems',
label: 'Mobile date items',
type: 'array',
dbName: 'date_mob',
defaultValue: participationContent.datesBanner.mobileItems,
fields: [text('date', 'Date'), text('label', 'Label')],
},
{
name: 'desktopItems',
label: 'Desktop date items',
type: 'array',
dbName: 'date_desk',
defaultValue: participationContent.datesBanner.desktopItems,
fields: [text('date', 'Date'), text('label', 'Label')],
},
],
},
{
name: 'applicationForm',
label: '07 · Application form wrapper',
type: 'group',
admin: sectionAdmin('Navy/gold section containing the application form and PDF upload links.'),
fields: [
uploadField('image', 'Form image', `Current frontend image: /images/${participationContent.applicationForm.imageFilename}`),
text('imageAlt', 'Image alt text', participationContent.applicationForm.imageAlt),
text('imageCaption', 'Image caption', participationContent.applicationForm.imageCaption),
text('eyebrow', 'Eyebrow', participationContent.applicationForm.eyebrow),
textarea('heading', 'Heading', participationContent.applicationForm.heading),
textarea('description', 'Body copy', participationContent.applicationForm.description),
{
name: 'facts',
label: 'Fact rows',
type: 'array',
dbName: 'form_facts',
defaultValue: participationContent.applicationForm.facts,
fields: [text('num', 'Number'), text('label', 'Label'), textarea('desc', 'Description')],
},
text('formEyebrow', 'Form eyebrow', participationContent.applicationForm.formEyebrow),
ctaGroup('uploadCta', 'Top upload CTA', participationContent.applicationForm.uploadCta.label, participationContent.applicationForm.uploadCta.url),
textarea('uploadPrompt', 'Bottom upload prompt', participationContent.applicationForm.uploadPrompt),
ctaGroup('uploadFooterCta', 'Bottom upload CTA', participationContent.applicationForm.uploadFooterCta.label, participationContent.applicationForm.uploadFooterCta.url),
],
},
{
name: 'form',
label: '08 · Application form copy and logic labels',
type: 'group',
admin: sectionAdmin('Text, options, and eligibility thresholds used inside the multi-step application form.'),
fields: [
text('stepCompleteLabel', 'Completed step label', participationContent.form.stepCompleteLabel),
text('backLabel', 'Back button label', participationContent.form.backLabel),
text('nextLabel', 'Next button label', participationContent.form.nextLabel),
text('submitLabel', 'Submit button label', participationContent.form.submitLabel),
text('yesLabel', 'Yes option label', participationContent.form.yesLabel),
text('noLabel', 'No option label', participationContent.form.noLabel),
text('requiredSuffix', 'Required field suffix', participationContent.form.requiredSuffix),
{
name: 'validation',
label: 'Validation messages',
type: 'group',
fields: [
text('choose', 'Choose message', participationContent.form.validation.choose),
text('required', 'Required message', participationContent.form.validation.required),
text('invalidEmail', 'Invalid email message', participationContent.form.validation.invalidEmail),
],
},
{
name: 'typeStep',
label: 'Step 1 · Submission type',
type: 'group',
fields: [
text('eyebrow', 'Eyebrow', participationContent.form.typeStep.eyebrow),
text('heading', 'Heading', participationContent.form.typeStep.heading),
text('selfTitle', 'Self-application title', participationContent.form.typeStep.selfTitle),
textarea('selfDesc', 'Self-application description', participationContent.form.typeStep.selfDesc),
text('nominationTitle', 'Nomination title', participationContent.form.typeStep.nominationTitle),
textarea('nominationDesc', 'Nomination description', participationContent.form.typeStep.nominationDesc),
],
},
{
name: 'selfSteps',
label: 'Self-application step labels',
type: 'array',
dbName: 'self_steps',
defaultValue: participationContent.form.selfSteps,
fields: [text('label', 'Label'), iconField('trophy')],
},
{
name: 'nominationSteps',
label: 'Nomination step labels',
type: 'array',
dbName: 'nom_steps',
defaultValue: participationContent.form.nominationSteps,
fields: [text('label', 'Label'), iconField('trophy')],
},
{
name: 'employeeOptions',
label: 'Employee options',
type: 'array',
dbName: 'emp_opts',
defaultValue: participationContent.form.employeeOptions,
fields: [text('value', 'Value'), text('label', 'Label')],
},
{
name: 'eligibility',
label: 'Self-application eligibility step',
type: 'group',
fields: [
text('allowedEmployeeValues', 'Allowed employee option values, comma-separated', participationContent.form.eligibility.allowedEmployeeValues),
text('requiredBayernValue', 'Required Bavaria answer value', participationContent.form.eligibility.requiredBayernValue),
text('eyebrow', 'Eyebrow', participationContent.form.eligibility.eyebrow),
text('heading', 'Heading', participationContent.form.eligibility.heading),
text('employeesLabel', 'Employees field label', participationContent.form.eligibility.employeesLabel),
text('bayernLabel', 'Bavaria field label', participationContent.form.eligibility.bayernLabel),
text('ownerLabel', 'Owner field label', participationContent.form.eligibility.ownerLabel),
text('ineligibleHeading', 'Ineligible heading', participationContent.form.eligibility.ineligibleHeading),
textarea('ineligibleBody', 'Ineligible body', participationContent.form.eligibility.ineligibleBody),
text('ineligibleContactPrefix', 'Ineligible contact prefix', participationContent.form.eligibility.ineligibleContactPrefix),
text('ineligibleContactEmail', 'Ineligible contact email', participationContent.form.eligibility.ineligibleContactEmail),
],
},
{
name: 'selfContact',
label: 'Self-application contact step',
type: 'group',
fields: Object.entries(participationContent.form.selfContact).map(([name, value]) => text(name, name, value)),
},
{
name: 'selfSummary',
label: 'Self-application summary step',
type: 'group',
fields: Object.entries(participationContent.form.selfSummary).map(([name, value]) => text(name, name, value)),
},
{
name: 'nominationCompany',
label: 'Nomination company step',
type: 'group',
fields: Object.entries(participationContent.form.nominationCompany).map(([name, value]) => text(name, name, value)),
},
{
name: 'nominationContact',
label: 'Nomination contact step',
type: 'group',
fields: Object.entries(participationContent.form.nominationContact).map(([name, value]) => text(name, name, value)),
},
{
name: 'nominationSummary',
label: 'Nomination summary step',
type: 'group',
fields: Object.entries(participationContent.form.nominationSummary).map(([name, value]) => text(name, name, value)),
},
{
name: 'success',
label: 'Success state copy',
type: 'group',
fields: Object.entries(participationContent.form.success).map(([name, value]) => text(name, name, value)),
},
],
},
],
},
]