feat: manage home form copy via payload

This commit is contained in:
syntaxbullet
2026-06-22 12:13:59 +02:00
parent b4eb1eb7fd
commit c21f0d0c8f
5 changed files with 447 additions and 2 deletions

View File

@@ -33,6 +33,33 @@ const sectionAdmin = (description: string) => ({
initCollapsed: true,
})
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'): Field => ({
name: 'icon',
label: 'Icon',
type: 'select',
defaultValue,
options: iconOptions,
})
export const homeFields: Field[] = [
{
name: 'home',
@@ -236,9 +263,125 @@ export const homeFields: Field[] = [
text('footnoteStrong', 'Footnote highlighted text', homeContent.application.footnoteStrong),
],
},
{
name: 'form',
label: '09 · Embedded application form copy and logic labels',
type: 'group',
admin: sectionAdmin('Text, options, and eligibility thresholds used inside the home page multi-step application form.'),
fields: [
text('stepCompleteLabel', 'Completed step label', homeContent.form.stepCompleteLabel),
text('backLabel', 'Back button label', homeContent.form.backLabel),
text('nextLabel', 'Next button label', homeContent.form.nextLabel),
text('submitLabel', 'Submit button label', homeContent.form.submitLabel),
text('yesLabel', 'Yes option label', homeContent.form.yesLabel),
text('noLabel', 'No option label', homeContent.form.noLabel),
text('requiredSuffix', 'Required field suffix', homeContent.form.requiredSuffix),
{
name: 'validation',
label: 'Validation messages',
type: 'group',
fields: [
text('choose', 'Choose message', homeContent.form.validation.choose),
text('required', 'Required message', homeContent.form.validation.required),
text('invalidEmail', 'Invalid email message', homeContent.form.validation.invalidEmail),
],
},
{
name: 'typeStep',
label: 'Step 1 · Submission type',
type: 'group',
fields: [
text('eyebrow', 'Eyebrow', homeContent.form.typeStep.eyebrow),
text('heading', 'Heading', homeContent.form.typeStep.heading),
text('selfTitle', 'Self-application title', homeContent.form.typeStep.selfTitle),
textarea('selfDesc', 'Self-application description', homeContent.form.typeStep.selfDesc),
text('nominationTitle', 'Nomination title', homeContent.form.typeStep.nominationTitle),
textarea('nominationDesc', 'Nomination description', homeContent.form.typeStep.nominationDesc),
],
},
{
name: 'selfSteps',
label: 'Self-application step labels',
type: 'array',
dbName: 'home_self_steps',
defaultValue: homeContent.form.selfSteps,
fields: [text('label', 'Label'), iconField('trophy')],
},
{
name: 'nominationSteps',
label: 'Nomination step labels',
type: 'array',
dbName: 'home_nom_steps',
defaultValue: homeContent.form.nominationSteps,
fields: [text('label', 'Label'), iconField('trophy')],
},
{
name: 'employeeOptions',
label: 'Employee options',
type: 'array',
dbName: 'home_emp_opts',
defaultValue: homeContent.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', homeContent.form.eligibility.allowedEmployeeValues),
text('requiredBayernValue', 'Required Bavaria answer value', homeContent.form.eligibility.requiredBayernValue),
text('eyebrow', 'Eyebrow', homeContent.form.eligibility.eyebrow),
text('heading', 'Heading', homeContent.form.eligibility.heading),
text('employeesLabel', 'Employees field label', homeContent.form.eligibility.employeesLabel),
text('bayernLabel', 'Bavaria field label', homeContent.form.eligibility.bayernLabel),
text('ownerLabel', 'Owner field label', homeContent.form.eligibility.ownerLabel),
text('ineligibleHeading', 'Ineligible heading', homeContent.form.eligibility.ineligibleHeading),
textarea('ineligibleBody', 'Ineligible body', homeContent.form.eligibility.ineligibleBody),
text('ineligibleContactPrefix', 'Ineligible contact prefix', homeContent.form.eligibility.ineligibleContactPrefix),
text('ineligibleContactEmail', 'Ineligible contact email', homeContent.form.eligibility.ineligibleContactEmail),
],
},
{
name: 'selfContact',
label: 'Self-application contact step',
type: 'group',
fields: Object.entries(homeContent.form.selfContact).map(([name, value]) => text(name, name, value)),
},
{
name: 'selfSummary',
label: 'Self-application summary step',
type: 'group',
fields: Object.entries(homeContent.form.selfSummary).map(([name, value]) => text(name, name, value)),
},
{
name: 'nominationCompany',
label: 'Nomination company step',
type: 'group',
fields: Object.entries(homeContent.form.nominationCompany).map(([name, value]) => text(name, name, value)),
},
{
name: 'nominationContact',
label: 'Nomination contact step',
type: 'group',
fields: Object.entries(homeContent.form.nominationContact).map(([name, value]) => text(name, name, value)),
},
{
name: 'nominationSummary',
label: 'Nomination summary step',
type: 'group',
fields: Object.entries(homeContent.form.nominationSummary).map(([name, value]) => text(name, name, value)),
},
{
name: 'success',
label: 'Success state copy',
type: 'group',
fields: Object.entries(homeContent.form.success).map(([name, value]) => text(name, name, value)),
},
],
},
{
name: 'videoModal',
label: '09 · Video modal placeholder',
label: '10 · Video modal placeholder',
type: 'group',
admin: sectionAdmin('Copy shown when the hero video button is clicked.'),
fields: [text('title', 'Title', homeContent.videoModal.title), text('description', 'Description', homeContent.videoModal.description), text('closeLabel', 'Close button label', homeContent.videoModal.closeLabel)],

View File

@@ -414,6 +414,165 @@ export interface Page {
footnote?: string | null;
footnoteStrong?: string | null;
};
/**
* Text, options, and eligibility thresholds used inside the home page multi-step application form.
*/
form?: {
stepCompleteLabel?: string | null;
backLabel?: string | null;
nextLabel?: string | null;
submitLabel?: string | null;
yesLabel?: string | null;
noLabel?: string | null;
requiredSuffix?: string | null;
validation?: {
choose?: string | null;
required?: string | null;
invalidEmail?: string | null;
};
typeStep?: {
eyebrow?: string | null;
heading?: string | null;
selfTitle?: string | null;
selfDesc?: string | null;
nominationTitle?: string | null;
nominationDesc?: string | null;
};
selfSteps?:
| {
label?: string | null;
icon?:
| (
| 'alertCircle'
| 'arrowRight'
| 'building2'
| 'check'
| 'checkCircle2'
| 'fileText'
| 'mapPin'
| 'search'
| 'send'
| 'star'
| 'trendingUp'
| 'trophy'
| 'user'
| 'userCheck'
| 'userPlus'
| 'users'
)
| null;
id?: string | null;
}[]
| null;
nominationSteps?:
| {
label?: string | null;
icon?:
| (
| 'alertCircle'
| 'arrowRight'
| 'building2'
| 'check'
| 'checkCircle2'
| 'fileText'
| 'mapPin'
| 'search'
| 'send'
| 'star'
| 'trendingUp'
| 'trophy'
| 'user'
| 'userCheck'
| 'userPlus'
| 'users'
)
| null;
id?: string | null;
}[]
| null;
employeeOptions?:
| {
value?: string | null;
label?: string | null;
id?: string | null;
}[]
| null;
eligibility?: {
allowedEmployeeValues?: string | null;
requiredBayernValue?: string | null;
eyebrow?: string | null;
heading?: string | null;
employeesLabel?: string | null;
bayernLabel?: string | null;
ownerLabel?: string | null;
ineligibleHeading?: string | null;
ineligibleBody?: string | null;
ineligibleContactPrefix?: string | null;
ineligibleContactEmail?: string | null;
};
selfContact?: {
eyebrow?: string | null;
heading?: string | null;
companyLabel?: string | null;
companyPlaceholder?: string | null;
nameLabel?: string | null;
namePlaceholder?: string | null;
emailLabel?: string | null;
emailPlaceholder?: string | null;
phoneLabel?: string | null;
phonePlaceholder?: string | null;
footnote?: string | null;
};
selfSummary?: {
eyebrow?: string | null;
heading?: string | null;
companyLabel?: string | null;
employeesLabel?: string | null;
locationLabel?: string | null;
locationPrefix?: string | null;
contactLabel?: string | null;
};
nominationCompany?: {
eyebrow?: string | null;
heading?: string | null;
companyLabel?: string | null;
companyPlaceholder?: string | null;
industryLabel?: string | null;
industryPlaceholder?: string | null;
locationLabel?: string | null;
locationPlaceholder?: string | null;
};
nominationContact?: {
eyebrow?: string | null;
heading?: string | null;
nameLabel?: string | null;
namePlaceholder?: string | null;
emailLabel?: string | null;
emailPlaceholder?: string | null;
relationshipLabel?: string | null;
relationshipPlaceholder?: string | null;
footnote?: string | null;
};
nominationSummary?: {
eyebrow?: string | null;
heading?: string | null;
companyLabel?: string | null;
industryLabel?: string | null;
locationLabel?: string | null;
nominatedByLabel?: string | null;
emptyValue?: string | null;
};
success?: {
selfHeading?: string | null;
nominationHeading?: string | null;
selfPrefix?: string | null;
selfMiddle?: string | null;
selfSuffix?: string | null;
nominationPrefix?: string | null;
nominationMiddle?: string | null;
nominationSuffix?: string | null;
};
};
/**
* Copy shown when the hero video button is clicked.
*/
@@ -3232,6 +3391,144 @@ export interface PagesSelect<T extends boolean = true> {
footnote?: T;
footnoteStrong?: T;
};
form?:
| T
| {
stepCompleteLabel?: T;
backLabel?: T;
nextLabel?: T;
submitLabel?: T;
yesLabel?: T;
noLabel?: T;
requiredSuffix?: T;
validation?:
| T
| {
choose?: T;
required?: T;
invalidEmail?: T;
};
typeStep?:
| T
| {
eyebrow?: T;
heading?: T;
selfTitle?: T;
selfDesc?: T;
nominationTitle?: T;
nominationDesc?: T;
};
selfSteps?:
| T
| {
label?: T;
icon?: T;
id?: T;
};
nominationSteps?:
| T
| {
label?: T;
icon?: T;
id?: T;
};
employeeOptions?:
| T
| {
value?: T;
label?: T;
id?: T;
};
eligibility?:
| T
| {
allowedEmployeeValues?: T;
requiredBayernValue?: T;
eyebrow?: T;
heading?: T;
employeesLabel?: T;
bayernLabel?: T;
ownerLabel?: T;
ineligibleHeading?: T;
ineligibleBody?: T;
ineligibleContactPrefix?: T;
ineligibleContactEmail?: T;
};
selfContact?:
| T
| {
eyebrow?: T;
heading?: T;
companyLabel?: T;
companyPlaceholder?: T;
nameLabel?: T;
namePlaceholder?: T;
emailLabel?: T;
emailPlaceholder?: T;
phoneLabel?: T;
phonePlaceholder?: T;
footnote?: T;
};
selfSummary?:
| T
| {
eyebrow?: T;
heading?: T;
companyLabel?: T;
employeesLabel?: T;
locationLabel?: T;
locationPrefix?: T;
contactLabel?: T;
};
nominationCompany?:
| T
| {
eyebrow?: T;
heading?: T;
companyLabel?: T;
companyPlaceholder?: T;
industryLabel?: T;
industryPlaceholder?: T;
locationLabel?: T;
locationPlaceholder?: T;
};
nominationContact?:
| T
| {
eyebrow?: T;
heading?: T;
nameLabel?: T;
namePlaceholder?: T;
emailLabel?: T;
emailPlaceholder?: T;
relationshipLabel?: T;
relationshipPlaceholder?: T;
footnote?: T;
};
nominationSummary?:
| T
| {
eyebrow?: T;
heading?: T;
companyLabel?: T;
industryLabel?: T;
locationLabel?: T;
nominatedByLabel?: T;
emptyValue?: T;
};
success?:
| T
| {
selfHeading?: T;
nominationHeading?: T;
selfPrefix?: T;
selfMiddle?: T;
selfSuffix?: T;
nominationPrefix?: T;
nominationMiddle?: T;
nominationSuffix?: T;
};
};
videoModal?:
| T
| {

View File

@@ -89,6 +89,7 @@ async function main() {
...applicationContent,
awardImage,
},
form: homeContent.form,
videoModal: homeContent.videoModal,
},
} as never,

View File

@@ -1,3 +1,5 @@
import { participationContent } from '@/spa/participationContent'
export const homeContent = {
hero: {
backgroundImageFilename: 'gala-saal-overview.jpg',
@@ -209,6 +211,7 @@ export const homeContent = {
footnote: 'Kostenlos und unverbindlich ',
footnoteStrong: 'keine Teilnahmegebühr',
},
form: participationContent.form,
videoModal: {
title: 'Video-Player Platzhalter',
description: 'Hier würde das Image-Video des BMP geladen werden.',

View File

@@ -116,6 +116,7 @@ const Home: React.FC = () => {
const winnersSection = home.winners || {};
const benefits = home.benefits || {};
const application = home.application || {};
const form = home.form || {};
const videoModal = home.videoModal || {};
const cmsPreistraeger = useCmsCollection('preistraeger');
const selectedWinners = fallbackArray(winnersSection.featured, []);
@@ -551,7 +552,7 @@ const Home: React.FC = () => {
<div style={{ padding: isMobile ? '32px 24px' : '24px 48px 32px 40px', flex: isMobile ? 'none' : 1, height: isMobile ? '82svh' : undefined, display: 'flex', flexDirection: 'column', minHeight: 0, position: 'relative', zIndex: 1 }}>
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(17,29,85,0.5)', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 8 }}>{fallbackText(application.formEyebrow, homeContent.application.formEyebrow)}</span>
<h3 style={{ fontFamily: FF, fontSize: 'clamp(1.2rem, 1.8vw, 1.6rem)', fontWeight: 900, color: '#111D55', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.1, marginBottom: 24 }}>{fallbackText(application.formTitle, homeContent.application.formTitle)}</h3>
<BewerbungsForm theme="gold" />
<BewerbungsForm theme="gold" content={form} />
</div>
</div>