refactor(cms): remove obsolete page fields
This commit is contained in:
50
docs/payload-field-inventory.md
Normal file
50
docs/payload-field-inventory.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Payload field-to-consumer inventory
|
||||||
|
|
||||||
|
This inventory records the frontend or platform owner for every retained `Pages` field group and relevant Global. Nested scalar, upload, relationship, and array fields belong to the consumer named for their parent group unless an exception is listed.
|
||||||
|
|
||||||
|
## Shared `Pages` fields
|
||||||
|
|
||||||
|
| Field path | Consumer or contract |
|
||||||
|
| --- | --- |
|
||||||
|
| `title`, `slug`, `spaPath`, `publishedAt`, `_status` | Route lookup, preview URLs, publication access, sitemap generation, and generic-page headings in `src/app/(frontend)/[[...slug]]/page.tsx`, the sitemap routes, and `src/spa/pages/CmsGenericPage.tsx`. |
|
||||||
|
| `hero`, `layout` | Non-SPA pages through `src/heros/RenderHero.tsx`, `src/blocks/RenderBlocks.tsx`, and `src/spa/pages/CmsGenericPage.tsx`. |
|
||||||
|
| `meta.*` | `@payloadcms/plugin-seo`, route metadata generation, and generic-page title fallback. |
|
||||||
|
|
||||||
|
## Page-specific fields
|
||||||
|
|
||||||
|
| Page field path | Retained consumer |
|
||||||
|
| --- | --- |
|
||||||
|
| `home.hero.*` | `src/spa/pages/Home.tsx`; partner relationship records remain authoritative and the names array is presentation fallback only. |
|
||||||
|
| `home.winners.*` | `src/spa/components/HomeWinnersSection.tsx` and `src/spa/homeWinnerSelection.ts`; also reused by `/der-bmp`. |
|
||||||
|
| `home.quickCheck.*`, `home.intro.*`, `home.benefits.*` | `src/spa/pages/Home.tsx`. |
|
||||||
|
| `home.testimonials.*` | `src/spa/pages/Home.tsx` through `src/spa/components/TestimonialsSection.tsx`. |
|
||||||
|
| `home.videoModal.*` | Homepage video action and modal in `src/spa/pages/Home.tsx`. |
|
||||||
|
| `participation.hero.*`, `participation.eligibility.*`, `participation.applicationWays.*`, `participation.datesBanner.*` | `src/spa/pages/Participation.tsx`. |
|
||||||
|
| `participation.process.*` | `src/spa/pages/Participation.tsx` through `src/spa/components/WegZurAuszeichnungSection.tsx`. |
|
||||||
|
| `participation.awardsGrid.*` | `src/spa/pages/Participation.tsx` through `src/spa/components/AwardsGridSection.tsx`. |
|
||||||
|
| `about.hero.*`, `about.prize.*`, `about.mittelstand.*`, `about.history.*`, `about.goals.*`, `about.values.*`, `about.cta.*` | `src/spa/pages/About.tsx`. |
|
||||||
|
| `about.testimonials.*` | `src/spa/pages/About.tsx` through `src/spa/components/TestimonialsSection.tsx`. |
|
||||||
|
| `contact.*` | `src/spa/pages/Contact.tsx` and `src/spa/components/forms/KontaktForm.tsx`. |
|
||||||
|
| `datenschutz.*`, `impressum.*` | `src/spa/components/legal/LegalPage.tsx` via the respective route component. |
|
||||||
|
| `formUpload.*` | `src/spa/pages/FormularUpload.tsx`. |
|
||||||
|
| `mitgliedWerden.*` | `src/spa/pages/MitgliedWerden.tsx` and `src/spa/components/membership/MembershipWizard.tsx`. |
|
||||||
|
| `preistraegerIndex.*` | `src/spa/pages/Preistraeger.tsx`; winner records remain collection-owned. |
|
||||||
|
| `pressIndex.hero.*`, `pressIndex.events.*`, `pressIndex.news.*`, `pressIndex.downloads.*` | `src/spa/pages/Press.tsx`; event/article records remain collection-owned, while missing-field labels and images are presentation fallbacks only. |
|
||||||
|
| `netzwerk.hero.*`, `netzwerk.patronage.*`, `netzwerk.juryIntro.*`, `netzwerk.expertise.*`, `netzwerk.sponsoring.*` | `src/spa/pages/Netzwerk.tsx`. |
|
||||||
|
| `netzwerk.jury.{eyebrow,heading,description,chairBadge,members}` | Jury heading and authoritative rendered relationship selection in `src/spa/pages/Netzwerk.tsx`. |
|
||||||
|
| `netzwerk.partners.{eyebrow,heading,description,detailLabel,tiers,modal}` | Partner section, tier labels, and modal copy in `src/spa/pages/Netzwerk.tsx`; partner records remain collection-owned. |
|
||||||
|
| `netzwerk.sponsoringForm.*` | The two visible form steps, validation, success state, and footer copy in `src/spa/components/forms/SponsoringForm.tsx`. |
|
||||||
|
|
||||||
|
## Global ownership
|
||||||
|
|
||||||
|
| Global | Consumer or contract |
|
||||||
|
| --- | --- |
|
||||||
|
| `application-form` | Single source for the application section and form on both `/` and `/teilnahme`, hydrated by the frontend route and consumed by `ApplicationFormSection` / `BewerbungsForm`. |
|
||||||
|
| `application-phase` | Shared phase state and phase-specific site copy used by Home, shared layout CTAs, the application/newsletter form switch, and sponsoring submissions. |
|
||||||
|
| `newsletter-form` | Evaluation/completed newsletter variants shared by `/` and `/teilnahme`. |
|
||||||
|
| `header`, `footer` | Site-wide navigation, branding, links, social entries, and footer partner marks rendered by `src/spa/components/layout/Layout.tsx`. |
|
||||||
|
| `site-settings` | Shared skyline, Preisträger placeholder, and generic-page fallback copy. |
|
||||||
|
|
||||||
|
## Task 4 removals
|
||||||
|
|
||||||
|
The audit found no frontend, metadata, SEO, seed-only contract, or explicit Global purpose for these fields: `home.application`, `home.form`, `participation.applicationForm`, `participation.form`, the hidden participation CTA controls (`eligibility.secondaryCta`, `eligibility.nudgeCta`, `applicationWays.recommendedLabel`, `applicationWays.fallbackCta`, `applicationWays.ways[].featured`, and `applicationWays.ways[].cta`), `about.highlights`, the five `pressIndex.downloads.kit*` controls, `pressIndex.accreditation`, the old event/news CTA labels, `netzwerk.jury.note`, `netzwerk.partners.premiumLabel`, `netzwerk.partners.defaultLogoColor`, the four non-rendered `netzwerk.sponsoring` footnote/form-eyebrow controls, and the obsolete sponsoring package step (`packages`, `step1Eyebrow`, `step1Heading`, `requiredPackageError`). They are intentionally absent from the retained inventory.
|
||||||
@@ -123,52 +123,9 @@ export const aboutFields: Field[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'highlights',
|
|
||||||
label: '04 · Preisträger highlights',
|
|
||||||
type: 'group',
|
|
||||||
admin: {
|
|
||||||
...sectionAdmin('Legacy data retained for compatibility. The rendered section now reuses the homepage Preisträger card grid.'),
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
uploadField('fallbackImage', 'Fallback winner image', `Current frontend fallback image: /images/${aboutContent.highlights.fallbackImageFilename}`),
|
|
||||||
text('eyebrow', 'Eyebrow', aboutContent.highlights.eyebrow),
|
|
||||||
textarea('heading', 'Heading', aboutContent.highlights.heading),
|
|
||||||
ctaGroup('cta', 'CTA', aboutContent.highlights.cta.label, aboutContent.highlights.cta.url),
|
|
||||||
text('hoverLabel', 'Card hover label', aboutContent.highlights.hoverLabel),
|
|
||||||
{
|
|
||||||
name: 'fallbackWinner',
|
|
||||||
label: 'Fallback winner text',
|
|
||||||
type: 'group',
|
|
||||||
fields: [
|
|
||||||
text('name', 'Name fallback', aboutContent.highlights.fallbackWinner.name),
|
|
||||||
text('category', 'Category fallback', aboutContent.highlights.fallbackWinner.category),
|
|
||||||
text('awardType', 'Award type fallback', aboutContent.highlights.fallbackWinner.awardType),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'year',
|
|
||||||
label: 'Fallback winner year',
|
|
||||||
type: 'number',
|
|
||||||
defaultValue: aboutContent.highlights.year,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'selectedWinners',
|
|
||||||
label: 'Selected winners',
|
|
||||||
type: 'relationship',
|
|
||||||
relationTo: 'preistraeger',
|
|
||||||
hasMany: true,
|
|
||||||
maxRows: 8,
|
|
||||||
admin: {
|
|
||||||
description: 'Choose up to 8 Preisträger to show. If empty, the frontend falls back to winners from the selected year.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'testimonials',
|
name: 'testimonials',
|
||||||
label: '05 · Testimonials',
|
label: '04 · Testimonials',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
admin: sectionAdmin('Testimonial heading and slides. Upload images when available, or keep the migrated URL fallback.'),
|
admin: sectionAdmin('Testimonial heading and slides. Upload images when available, or keep the migrated URL fallback.'),
|
||||||
fields: [
|
fields: [
|
||||||
@@ -199,7 +156,7 @@ export const aboutFields: Field[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'history',
|
name: 'history',
|
||||||
label: '06 · Geschichte & Meilensteine',
|
label: '05 · Geschichte & Meilensteine',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
admin: sectionAdmin('Navy timeline section.'),
|
admin: sectionAdmin('Navy timeline section.'),
|
||||||
fields: [
|
fields: [
|
||||||
@@ -219,7 +176,7 @@ export const aboutFields: Field[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'goals',
|
name: 'goals',
|
||||||
label: '07 · Zielsetzung',
|
label: '06 · Zielsetzung',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
admin: sectionAdmin('Cream values list next to the history timeline.'),
|
admin: sectionAdmin('Cream values list next to the history timeline.'),
|
||||||
fields: [
|
fields: [
|
||||||
@@ -237,7 +194,7 @@ export const aboutFields: Field[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'values',
|
name: 'values',
|
||||||
label: '08 · Vorteile & Werte',
|
label: '07 · Vorteile & Werte',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
admin: sectionAdmin('Cream editorial rows before the final CTA.'),
|
admin: sectionAdmin('Cream editorial rows before the final CTA.'),
|
||||||
fields: [
|
fields: [
|
||||||
@@ -256,7 +213,7 @@ export const aboutFields: Field[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'cta',
|
name: 'cta',
|
||||||
label: '09 · Final CTA',
|
label: '08 · Final CTA',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
admin: sectionAdmin('Bottom navy CTA section.'),
|
admin: sectionAdmin('Bottom navy CTA section.'),
|
||||||
fields: [
|
fields: [
|
||||||
|
|||||||
@@ -33,33 +33,6 @@ const sectionAdmin = (description: string) => ({
|
|||||||
initCollapsed: true,
|
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[] = [
|
export const homeFields: Field[] = [
|
||||||
{
|
{
|
||||||
name: 'home',
|
name: 'home',
|
||||||
@@ -219,144 +192,9 @@ export const homeFields: Field[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'application',
|
|
||||||
label: '07 · Application form section',
|
|
||||||
type: 'group',
|
|
||||||
admin: { ...sectionAdmin('Legacy page-level form section. The frontend now uses Formulare > Bewerbungsformular.'), hidden: true },
|
|
||||||
fields: [
|
|
||||||
text('eyebrow', 'Eyebrow', homeContent.application.eyebrow),
|
|
||||||
textarea('heading', 'Heading', homeContent.application.heading),
|
|
||||||
textarea('body', 'Body copy', homeContent.application.body),
|
|
||||||
{ name: 'benefits', label: 'Benefit rows', type: 'array', defaultValue: homeContent.application.benefits, fields: [text('num', 'Number'), text('label', 'Label'), text('desc', 'Description')] },
|
|
||||||
uploadField('awardImage', 'Award image', `Current frontend image: /images/${homeContent.application.awardImageFilename}`),
|
|
||||||
text('awardImageAlt', 'Award image alt text', homeContent.application.awardImageAlt),
|
|
||||||
text('awardCaption', 'Award image caption', homeContent.application.awardCaption),
|
|
||||||
text('formEyebrow', 'Form eyebrow', homeContent.application.formEyebrow),
|
|
||||||
text('formTitle', 'Form title', homeContent.application.formTitle),
|
|
||||||
text('footnote', 'Footnote normal text', homeContent.application.footnote),
|
|
||||||
text('footnoteStrong', 'Footnote highlighted text', homeContent.application.footnoteStrong),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'form',
|
|
||||||
label: '08 · Embedded application form copy and logic labels',
|
|
||||||
type: 'group',
|
|
||||||
admin: { ...sectionAdmin('Legacy page-level form fields. The frontend now uses Formulare > Bewerbungsformular.'), hidden: true },
|
|
||||||
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',
|
name: 'videoModal',
|
||||||
label: '10 · Video modal',
|
label: '07 · Video modal',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
admin: sectionAdmin('Video and fallback copy shown when the hero video button is clicked.'),
|
admin: sectionAdmin('Video and fallback copy shown when the hero video button is clicked.'),
|
||||||
fields: [
|
fields: [
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ const personFields: Field[] = [
|
|||||||
const rowFields: Field[] = [text('num', 'Number'), text('label', 'Label'), textarea('body', 'Body')]
|
const rowFields: Field[] = [text('num', 'Number'), text('label', 'Label'), textarea('body', 'Body')]
|
||||||
|
|
||||||
const benefitFields: Field[] = [text('num', 'Number'), text('label', 'Label'), text('sub', 'Subline')]
|
const benefitFields: Field[] = [text('num', 'Number'), text('label', 'Label'), text('sub', 'Subline')]
|
||||||
const packageFields: Field[] = [text('value', 'Value'), text('title', 'Title'), textarea('desc', 'Description')]
|
|
||||||
const formStepFields: Field[] = [text('label', 'Label')]
|
const formStepFields: Field[] = [text('label', 'Label')]
|
||||||
|
|
||||||
export const netzwerkFields: Field[] = [
|
export const netzwerkFields: Field[] = [
|
||||||
@@ -134,13 +133,12 @@ export const netzwerkFields: Field[] = [
|
|||||||
name: 'jury',
|
name: 'jury',
|
||||||
label: '04 · Jury members',
|
label: '04 · Jury members',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
admin: sectionAdmin('Jury section heading, chair badge, members, and note.'),
|
admin: sectionAdmin('Jury section heading, chair badge, and rendered member selection.'),
|
||||||
fields: [
|
fields: [
|
||||||
text('eyebrow', 'Eyebrow', netzwerkContent.jury.eyebrow),
|
text('eyebrow', 'Eyebrow', netzwerkContent.jury.eyebrow),
|
||||||
textarea('heading', 'Heading', netzwerkContent.jury.heading),
|
textarea('heading', 'Heading', netzwerkContent.jury.heading),
|
||||||
textarea('description', 'Description', netzwerkContent.jury.description),
|
textarea('description', 'Description', netzwerkContent.jury.description),
|
||||||
text('chairBadge', 'Chair badge', netzwerkContent.jury.chairBadge),
|
text('chairBadge', 'Chair badge', netzwerkContent.jury.chairBadge),
|
||||||
textarea('note', 'Note', netzwerkContent.jury.note),
|
|
||||||
{
|
{
|
||||||
name: 'members',
|
name: 'members',
|
||||||
label: 'Members',
|
label: 'Members',
|
||||||
@@ -183,8 +181,6 @@ export const netzwerkFields: Field[] = [
|
|||||||
textarea('heading', 'Heading', netzwerkContent.partners.heading),
|
textarea('heading', 'Heading', netzwerkContent.partners.heading),
|
||||||
textarea('description', 'Description', netzwerkContent.partners.description),
|
textarea('description', 'Description', netzwerkContent.partners.description),
|
||||||
text('detailLabel', 'Detail label', netzwerkContent.partners.detailLabel),
|
text('detailLabel', 'Detail label', netzwerkContent.partners.detailLabel),
|
||||||
text('premiumLabel', 'Premium label', netzwerkContent.partners.premiumLabel),
|
|
||||||
text('defaultLogoColor', 'Default logo color', netzwerkContent.partners.defaultLogoColor),
|
|
||||||
{
|
{
|
||||||
name: 'tiers',
|
name: 'tiers',
|
||||||
label: 'Tiers',
|
label: 'Tiers',
|
||||||
@@ -214,7 +210,7 @@ export const netzwerkFields: Field[] = [
|
|||||||
name: 'sponsoring',
|
name: 'sponsoring',
|
||||||
label: '07 · Sponsoring section',
|
label: '07 · Sponsoring section',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
admin: sectionAdmin('Sponsoring pitch, image, benefits, form eyebrow, and membership footnote.'),
|
admin: sectionAdmin('Sponsoring pitch, image, benefits, and form eyebrow.'),
|
||||||
fields: [
|
fields: [
|
||||||
text('eyebrow', 'Eyebrow', netzwerkContent.sponsoring.eyebrow),
|
text('eyebrow', 'Eyebrow', netzwerkContent.sponsoring.eyebrow),
|
||||||
textarea('heading', 'Heading', netzwerkContent.sponsoring.heading),
|
textarea('heading', 'Heading', netzwerkContent.sponsoring.heading),
|
||||||
@@ -222,10 +218,6 @@ export const netzwerkFields: Field[] = [
|
|||||||
uploadField('image', 'Form panel image', `Current frontend image: /images/${netzwerkContent.sponsoring.imageFilename}`),
|
uploadField('image', 'Form panel image', `Current frontend image: /images/${netzwerkContent.sponsoring.imageFilename}`),
|
||||||
text('imageAlt', 'Image alt text', netzwerkContent.sponsoring.imageAlt),
|
text('imageAlt', 'Image alt text', netzwerkContent.sponsoring.imageAlt),
|
||||||
text('imageLabel', 'Image label', netzwerkContent.sponsoring.imageLabel),
|
text('imageLabel', 'Image label', netzwerkContent.sponsoring.imageLabel),
|
||||||
text('formEyebrow', 'Form eyebrow', netzwerkContent.sponsoring.formEyebrow),
|
|
||||||
text('footnotePrefix', 'Footnote prefix', netzwerkContent.sponsoring.footnotePrefix),
|
|
||||||
text('footnoteLabel', 'Footnote link label', netzwerkContent.sponsoring.footnoteLabel),
|
|
||||||
text('footnoteUrl', 'Footnote link URL', netzwerkContent.sponsoring.footnoteUrl),
|
|
||||||
{
|
{
|
||||||
name: 'benefits',
|
name: 'benefits',
|
||||||
label: 'Benefits',
|
label: 'Benefits',
|
||||||
@@ -240,7 +232,7 @@ export const netzwerkFields: Field[] = [
|
|||||||
name: 'sponsoringForm',
|
name: 'sponsoringForm',
|
||||||
label: '08 · Sponsoring form',
|
label: '08 · Sponsoring form',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
admin: sectionAdmin('Wizard labels, package options, validation messages, success copy, and footer note.'),
|
admin: sectionAdmin('Two-step wizard labels, field copy, validation messages, success copy, and footer note.'),
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
name: 'steps',
|
name: 'steps',
|
||||||
@@ -248,26 +240,18 @@ export const netzwerkFields: Field[] = [
|
|||||||
type: 'array',
|
type: 'array',
|
||||||
dbName: 'network_form_steps',
|
dbName: 'network_form_steps',
|
||||||
defaultValue: netzwerkContent.sponsoringForm.steps,
|
defaultValue: netzwerkContent.sponsoringForm.steps,
|
||||||
|
minRows: 2,
|
||||||
|
maxRows: 2,
|
||||||
fields: formStepFields,
|
fields: formStepFields,
|
||||||
},
|
},
|
||||||
{
|
text('step2Eyebrow', 'Step 1 eyebrow', netzwerkContent.sponsoringForm.step2Eyebrow),
|
||||||
name: 'packages',
|
text('step2Heading', 'Step 1 heading', netzwerkContent.sponsoringForm.step2Heading),
|
||||||
label: 'Package options',
|
|
||||||
type: 'array',
|
|
||||||
dbName: 'network_form_pkgs',
|
|
||||||
defaultValue: netzwerkContent.sponsoringForm.packages,
|
|
||||||
fields: packageFields,
|
|
||||||
},
|
|
||||||
text('step1Eyebrow', 'Step 1 eyebrow', netzwerkContent.sponsoringForm.step1Eyebrow),
|
|
||||||
text('step1Heading', 'Step 1 heading', netzwerkContent.sponsoringForm.step1Heading),
|
|
||||||
text('step2Eyebrow', 'Step 2 eyebrow', netzwerkContent.sponsoringForm.step2Eyebrow),
|
|
||||||
text('step2Heading', 'Step 2 heading', netzwerkContent.sponsoringForm.step2Heading),
|
|
||||||
text('companyLabel', 'Company label', netzwerkContent.sponsoringForm.companyLabel),
|
text('companyLabel', 'Company label', netzwerkContent.sponsoringForm.companyLabel),
|
||||||
text('companyPlaceholder', 'Company placeholder', netzwerkContent.sponsoringForm.companyPlaceholder),
|
text('companyPlaceholder', 'Company placeholder', netzwerkContent.sponsoringForm.companyPlaceholder),
|
||||||
text('industryLabel', 'Industry label', netzwerkContent.sponsoringForm.industryLabel),
|
text('industryLabel', 'Industry label', netzwerkContent.sponsoringForm.industryLabel),
|
||||||
text('industryPlaceholder', 'Industry placeholder', netzwerkContent.sponsoringForm.industryPlaceholder),
|
text('industryPlaceholder', 'Industry placeholder', netzwerkContent.sponsoringForm.industryPlaceholder),
|
||||||
text('step3Eyebrow', 'Step 3 eyebrow', netzwerkContent.sponsoringForm.step3Eyebrow),
|
text('step3Eyebrow', 'Step 2 eyebrow', netzwerkContent.sponsoringForm.step3Eyebrow),
|
||||||
text('step3Heading', 'Step 3 heading', netzwerkContent.sponsoringForm.step3Heading),
|
text('step3Heading', 'Step 2 heading', netzwerkContent.sponsoringForm.step3Heading),
|
||||||
text('contactLabel', 'Contact label', netzwerkContent.sponsoringForm.contactLabel),
|
text('contactLabel', 'Contact label', netzwerkContent.sponsoringForm.contactLabel),
|
||||||
text('contactPlaceholder', 'Contact placeholder', netzwerkContent.sponsoringForm.contactPlaceholder),
|
text('contactPlaceholder', 'Contact placeholder', netzwerkContent.sponsoringForm.contactPlaceholder),
|
||||||
text('emailLabel', 'Email label', netzwerkContent.sponsoringForm.emailLabel),
|
text('emailLabel', 'Email label', netzwerkContent.sponsoringForm.emailLabel),
|
||||||
@@ -275,7 +259,6 @@ export const netzwerkFields: Field[] = [
|
|||||||
text('backLabel', 'Back label', netzwerkContent.sponsoringForm.backLabel),
|
text('backLabel', 'Back label', netzwerkContent.sponsoringForm.backLabel),
|
||||||
text('nextLabel', 'Next label', netzwerkContent.sponsoringForm.nextLabel),
|
text('nextLabel', 'Next label', netzwerkContent.sponsoringForm.nextLabel),
|
||||||
text('submitLabel', 'Submit label', netzwerkContent.sponsoringForm.submitLabel),
|
text('submitLabel', 'Submit label', netzwerkContent.sponsoringForm.submitLabel),
|
||||||
text('requiredPackageError', 'Required package error', netzwerkContent.sponsoringForm.requiredPackageError),
|
|
||||||
text('requiredFieldError', 'Required field error', netzwerkContent.sponsoringForm.requiredFieldError),
|
text('requiredFieldError', 'Required field error', netzwerkContent.sponsoringForm.requiredFieldError),
|
||||||
text('invalidEmailError', 'Invalid email error', netzwerkContent.sponsoringForm.invalidEmailError),
|
text('invalidEmailError', 'Invalid email error', netzwerkContent.sponsoringForm.invalidEmailError),
|
||||||
text('doneLabel', 'Done step label', netzwerkContent.sponsoringForm.doneLabel),
|
text('doneLabel', 'Done step label', netzwerkContent.sponsoringForm.doneLabel),
|
||||||
|
|||||||
@@ -24,13 +24,6 @@ const textarea = (name: string, label: string, defaultValue?: string): Field =>
|
|||||||
defaultValue,
|
defaultValue,
|
||||||
})
|
})
|
||||||
|
|
||||||
const checkbox = (name: string, label: string, defaultValue = false): Field => ({
|
|
||||||
name,
|
|
||||||
type: 'checkbox',
|
|
||||||
label,
|
|
||||||
defaultValue,
|
|
||||||
})
|
|
||||||
|
|
||||||
const sectionAdmin = (description: string) => ({
|
const sectionAdmin = (description: string) => ({
|
||||||
description,
|
description,
|
||||||
initCollapsed: true,
|
initCollapsed: true,
|
||||||
@@ -133,7 +126,6 @@ export const participationFields: Field[] = [
|
|||||||
textarea('heading', 'Heading', participationContent.eligibility.heading),
|
textarea('heading', 'Heading', participationContent.eligibility.heading),
|
||||||
textarea('description', 'Intro paragraph', participationContent.eligibility.description),
|
textarea('description', 'Intro paragraph', participationContent.eligibility.description),
|
||||||
ctaGroup('primaryCta', 'Primary CTA', participationContent.eligibility.primaryCta.label, participationContent.eligibility.primaryCta.url),
|
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',
|
name: 'criteria',
|
||||||
label: 'Eligibility rows',
|
label: 'Eligibility rows',
|
||||||
@@ -151,7 +143,6 @@ export const participationFields: Field[] = [
|
|||||||
fields: [text('title', 'Title'), textarea('body', 'Body'), iconField('building2')],
|
fields: [text('title', 'Title'), textarea('body', 'Body'), iconField('building2')],
|
||||||
},
|
},
|
||||||
text('nudgeText', 'Bottom nudge text', participationContent.eligibility.nudgeText),
|
text('nudgeText', 'Bottom nudge text', participationContent.eligibility.nudgeText),
|
||||||
ctaGroup('nudgeCta', 'Bottom CTA', participationContent.eligibility.nudgeCta.label, participationContent.eligibility.nudgeCta.url),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -163,13 +154,6 @@ export const participationFields: Field[] = [
|
|||||||
text('eyebrow', 'Eyebrow', participationContent.applicationWays.eyebrow),
|
text('eyebrow', 'Eyebrow', participationContent.applicationWays.eyebrow),
|
||||||
textarea('heading', 'Heading', participationContent.applicationWays.heading),
|
textarea('heading', 'Heading', participationContent.applicationWays.heading),
|
||||||
textarea('description', 'Intro paragraph', participationContent.applicationWays.description),
|
textarea('description', 'Intro paragraph', participationContent.applicationWays.description),
|
||||||
text('recommendedLabel', 'Featured card label', participationContent.applicationWays.recommendedLabel),
|
|
||||||
ctaGroup(
|
|
||||||
'fallbackCta',
|
|
||||||
'Fallback CTA for path cards',
|
|
||||||
participationContent.applicationWays.fallbackCta.label,
|
|
||||||
participationContent.applicationWays.fallbackCta.url,
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
name: 'institutions',
|
name: 'institutions',
|
||||||
label: 'Proposal institutions',
|
label: 'Proposal institutions',
|
||||||
@@ -189,7 +173,6 @@ export const participationFields: Field[] = [
|
|||||||
textarea('title', 'Title'),
|
textarea('title', 'Title'),
|
||||||
textarea('desc', 'Description'),
|
textarea('desc', 'Description'),
|
||||||
iconField('send', 'ico'),
|
iconField('send', 'ico'),
|
||||||
checkbox('featured', 'Featured card'),
|
|
||||||
{
|
{
|
||||||
name: 'listType',
|
name: 'listType',
|
||||||
label: 'Detail list type',
|
label: 'Detail list type',
|
||||||
@@ -208,7 +191,6 @@ export const participationFields: Field[] = [
|
|||||||
dbName: 'way_facts',
|
dbName: 'way_facts',
|
||||||
fields: factFields,
|
fields: factFields,
|
||||||
},
|
},
|
||||||
ctaGroup('cta', 'CTA', participationContent.applicationWays.fallbackCta.label, participationContent.applicationWays.fallbackCta.url),
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -268,149 +250,6 @@ export const participationFields: Field[] = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'applicationForm',
|
|
||||||
label: '07 · Application form wrapper',
|
|
||||||
type: 'group',
|
|
||||||
admin: { ...sectionAdmin('Legacy page-level form section. The frontend now uses Formulare > Bewerbungsformular.'), hidden: true },
|
|
||||||
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),
|
|
||||||
text('formTitle', 'Form title', participationContent.applicationForm.formTitle),
|
|
||||||
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('Legacy page-level form fields. The frontend now uses Formulare > Bewerbungsformular.'), hidden: true },
|
|
||||||
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)),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ export const pressIndexFields: Field[] = [
|
|||||||
text('eyebrow', 'Eyebrow', pressIndexContent.events.eyebrow),
|
text('eyebrow', 'Eyebrow', pressIndexContent.events.eyebrow),
|
||||||
textarea('heading', 'Heading', pressIndexContent.events.heading),
|
textarea('heading', 'Heading', pressIndexContent.events.heading),
|
||||||
textarea('description', 'Description', pressIndexContent.events.description),
|
textarea('description', 'Description', pressIndexContent.events.description),
|
||||||
text('detailLabel', 'Detail link label', pressIndexContent.events.detailLabel),
|
|
||||||
text('defaultStatusLabel', 'Missing status fallback label', pressIndexContent.events.defaultStatusLabel),
|
text('defaultStatusLabel', 'Missing status fallback label', pressIndexContent.events.defaultStatusLabel),
|
||||||
text('openStatusLabel', 'Open status label', pressIndexContent.events.openStatusLabel),
|
text('openStatusLabel', 'Open status label', pressIndexContent.events.openStatusLabel),
|
||||||
text('missingTitleLabel', 'Missing event title label', pressIndexContent.events.missingTitleLabel),
|
text('missingTitleLabel', 'Missing event title label', pressIndexContent.events.missingTitleLabel),
|
||||||
@@ -92,7 +91,6 @@ export const pressIndexFields: Field[] = [
|
|||||||
text('eyebrow', 'Eyebrow', pressIndexContent.news.eyebrow),
|
text('eyebrow', 'Eyebrow', pressIndexContent.news.eyebrow),
|
||||||
textarea('heading', 'Heading', pressIndexContent.news.heading),
|
textarea('heading', 'Heading', pressIndexContent.news.heading),
|
||||||
textarea('description', 'Description', pressIndexContent.news.description),
|
textarea('description', 'Description', pressIndexContent.news.description),
|
||||||
text('readMoreLabel', 'Read more label', pressIndexContent.news.readMoreLabel),
|
|
||||||
text('missingTitleLabel', 'Missing news title label', pressIndexContent.news.missingTitleLabel),
|
text('missingTitleLabel', 'Missing news title label', pressIndexContent.news.missingTitleLabel),
|
||||||
text('missingCategoryLabel', 'Missing news category label', pressIndexContent.news.missingCategoryLabel),
|
text('missingCategoryLabel', 'Missing news category label', pressIndexContent.news.missingCategoryLabel),
|
||||||
uploadField('collectionFallbackImage', 'Missing news image fallback', `Current frontend image: /images/${pressIndexContent.news.collectionFallbackImageFilename}`),
|
uploadField('collectionFallbackImage', 'Missing news image fallback', `Current frontend image: /images/${pressIndexContent.news.collectionFallbackImageFilename}`),
|
||||||
@@ -102,38 +100,16 @@ export const pressIndexFields: Field[] = [
|
|||||||
name: 'downloads',
|
name: 'downloads',
|
||||||
label: '04 · Press material and contact',
|
label: '04 · Press material and contact',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
admin: sectionAdmin('Download button and press contact block.'),
|
admin: sectionAdmin('Press material introduction and contact block.'),
|
||||||
fields: [
|
fields: [
|
||||||
text('eyebrow', 'Eyebrow', pressIndexContent.downloads.eyebrow),
|
text('eyebrow', 'Eyebrow', pressIndexContent.downloads.eyebrow),
|
||||||
textarea('heading', 'Heading', pressIndexContent.downloads.heading),
|
textarea('heading', 'Heading', pressIndexContent.downloads.heading),
|
||||||
textarea('description', 'Description', pressIndexContent.downloads.description),
|
textarea('description', 'Description', pressIndexContent.downloads.description),
|
||||||
text('kitLabel', 'Kit label', pressIndexContent.downloads.kitLabel),
|
|
||||||
text('kitMeta', 'Kit metadata', pressIndexContent.downloads.kitMeta),
|
|
||||||
uploadField('kitFile', 'Press kit file', `Current frontend file: ${pressIndexContent.downloads.kitUrl}`),
|
|
||||||
text('kitUrl', 'Kit URL', pressIndexContent.downloads.kitUrl),
|
|
||||||
text('kitDownloadName', 'Kit download filename', pressIndexContent.downloads.kitDownloadName),
|
|
||||||
text('contactEyebrow', 'Contact eyebrow', pressIndexContent.downloads.contactEyebrow),
|
text('contactEyebrow', 'Contact eyebrow', pressIndexContent.downloads.contactEyebrow),
|
||||||
text('contactName', 'Contact name', pressIndexContent.downloads.contactName),
|
text('contactName', 'Contact name', pressIndexContent.downloads.contactName),
|
||||||
textarea('contactLines', 'Contact lines', pressIndexContent.downloads.contactLines),
|
textarea('contactLines', 'Contact lines', pressIndexContent.downloads.contactLines),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'accreditation',
|
|
||||||
label: '05 · Accreditation form copy',
|
|
||||||
type: 'group',
|
|
||||||
admin: sectionAdmin('Labels and success state for the press accreditation form.'),
|
|
||||||
fields: [
|
|
||||||
text('eyebrow', 'Eyebrow', pressIndexContent.accreditation.eyebrow),
|
|
||||||
textarea('heading', 'Heading', pressIndexContent.accreditation.heading),
|
|
||||||
textarea('description', 'Description', pressIndexContent.accreditation.description),
|
|
||||||
text('mediumLabel', 'Medium label', pressIndexContent.accreditation.mediumLabel),
|
|
||||||
text('nameLabel', 'Name label', pressIndexContent.accreditation.nameLabel),
|
|
||||||
text('emailLabel', 'Email label', pressIndexContent.accreditation.emailLabel),
|
|
||||||
text('submitLabel', 'Submit label', pressIndexContent.accreditation.submitLabel),
|
|
||||||
text('successHeading', 'Success heading', pressIndexContent.accreditation.successHeading),
|
|
||||||
textarea('successMessage', 'Success message', pressIndexContent.accreditation.successMessage),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
397
src/migrations/20260731_160000_focused_payload_cleanup.ts
Normal file
397
src/migrations/20260731_160000_focused_payload_cleanup.ts
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
import { type MigrateDownArgs, type MigrateUpArgs, sql } from '@payloadcms/db-sqlite'
|
||||||
|
|
||||||
|
import { homeContent } from '@/spa/homeContent'
|
||||||
|
|
||||||
|
type MigrationDB = MigrateUpArgs['db']
|
||||||
|
|
||||||
|
const ident = (value: string) => `\`${value.replace(/`/g, '``')}\``
|
||||||
|
const literal = (value: string) => `'${value.replace(/'/g, "''")}'`
|
||||||
|
const snake = (value: string) => value.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
|
||||||
|
|
||||||
|
const obsoleteChildTables = [
|
||||||
|
'pages_home_application_benefits',
|
||||||
|
'_pages_v_version_home_application_benefits',
|
||||||
|
'home_self_steps',
|
||||||
|
'_home_self_steps_v',
|
||||||
|
'home_nom_steps',
|
||||||
|
'_home_nom_steps_v',
|
||||||
|
'home_emp_opts',
|
||||||
|
'_home_emp_opts_v',
|
||||||
|
'form_facts',
|
||||||
|
'_form_facts_v',
|
||||||
|
'self_steps',
|
||||||
|
'_self_steps_v',
|
||||||
|
'nom_steps',
|
||||||
|
'_nom_steps_v',
|
||||||
|
'emp_opts',
|
||||||
|
'_emp_opts_v',
|
||||||
|
'network_form_pkgs',
|
||||||
|
'_network_form_pkgs_v',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const livePrefixes = [
|
||||||
|
'home_application_',
|
||||||
|
'home_form_',
|
||||||
|
'participation_application_form_',
|
||||||
|
'participation_form_',
|
||||||
|
'about_highlights_',
|
||||||
|
'press_index_downloads_kit_',
|
||||||
|
'press_index_accreditation_',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const versionPrefixes = livePrefixes.map((prefix) => `version_${prefix}`)
|
||||||
|
|
||||||
|
const standaloneLiveColumns = [
|
||||||
|
'netzwerk_jury_note',
|
||||||
|
'netzwerk_partners_premium_label',
|
||||||
|
'netzwerk_partners_default_logo_color',
|
||||||
|
'netzwerk_sponsoring_footnote_prefix',
|
||||||
|
'netzwerk_sponsoring_footnote_label',
|
||||||
|
'netzwerk_sponsoring_footnote_url',
|
||||||
|
'netzwerk_sponsoring_form_eyebrow',
|
||||||
|
'netzwerk_sponsoring_form_step1_eyebrow',
|
||||||
|
'netzwerk_sponsoring_form_step1_heading',
|
||||||
|
'netzwerk_sponsoring_form_required_package_error',
|
||||||
|
'participation_eligibility_secondary_cta_label',
|
||||||
|
'participation_eligibility_secondary_cta_url',
|
||||||
|
'participation_eligibility_nudge_cta_label',
|
||||||
|
'participation_eligibility_nudge_cta_url',
|
||||||
|
'participation_application_ways_recommended_label',
|
||||||
|
'participation_application_ways_fallback_cta_label',
|
||||||
|
'participation_application_ways_fallback_cta_url',
|
||||||
|
'press_index_events_detail_label',
|
||||||
|
'press_index_news_read_more_label',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const obsoleteApplicationWayColumns = ['featured', 'cta_label', 'cta_url'] as const
|
||||||
|
|
||||||
|
const uploadIndexes = [
|
||||||
|
'pages_home_application_home_application_award_image_idx',
|
||||||
|
'_pages_v_version_home_application_version_home_applicati_idx',
|
||||||
|
'pages_participation_application_form_participation_appli_idx',
|
||||||
|
'_pages_v_version_participation_application_form_version__idx',
|
||||||
|
'pages_about_highlights_about_highlights_fallback_image_idx',
|
||||||
|
'_pages_v_version_about_highlights_version_about_highligh_idx',
|
||||||
|
'pages_press_index_downloads_press_index_downloads_kit_fi_idx',
|
||||||
|
'_pages_v_version_press_index_downloads_version_press_ind_idx',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
function flattenScalars(value: Record<string, unknown>, prefix = ''): Array<[string, string]> {
|
||||||
|
return Object.entries(value).flatMap(([key, item]) => {
|
||||||
|
const path = prefix ? `${prefix}_${snake(key)}` : snake(key)
|
||||||
|
if (Array.isArray(item) || item === undefined || item === null) return []
|
||||||
|
if (typeof item === 'object') return flattenScalars(item as Record<string, unknown>, path)
|
||||||
|
return [[path, String(item)]]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const homeApplicationColumns = flattenScalars(homeContent.application as Record<string, unknown>)
|
||||||
|
.filter(([name]) => name !== 'award_image_filename')
|
||||||
|
.map(([name, value]) => [`home_application_${name}`, `text DEFAULT ${literal(value)}`] as const)
|
||||||
|
|
||||||
|
const sharedFormColumns = flattenScalars(homeContent.form as Record<string, unknown>)
|
||||||
|
|
||||||
|
const participationApplicationColumns = [
|
||||||
|
'image_alt',
|
||||||
|
'image_caption',
|
||||||
|
'eyebrow',
|
||||||
|
'heading',
|
||||||
|
'description',
|
||||||
|
'form_eyebrow',
|
||||||
|
'form_title',
|
||||||
|
'upload_cta_label',
|
||||||
|
'upload_cta_url',
|
||||||
|
'upload_prompt',
|
||||||
|
'upload_footer_cta_label',
|
||||||
|
'upload_footer_cta_url',
|
||||||
|
].map((suffix) => [`participation_application_form_${suffix}`, 'text'] as const)
|
||||||
|
|
||||||
|
const aboutHighlightColumns = [
|
||||||
|
'eyebrow',
|
||||||
|
'heading',
|
||||||
|
'cta_label',
|
||||||
|
'cta_url',
|
||||||
|
'hover_label',
|
||||||
|
'fallback_winner_name',
|
||||||
|
'fallback_winner_category',
|
||||||
|
'fallback_winner_award_type',
|
||||||
|
].map((suffix) => [`about_highlights_${suffix}`, 'text'] as const)
|
||||||
|
|
||||||
|
const pressColumns = [
|
||||||
|
'press_index_downloads_kit_label',
|
||||||
|
'press_index_downloads_kit_meta',
|
||||||
|
'press_index_downloads_kit_url',
|
||||||
|
'press_index_downloads_kit_download_name',
|
||||||
|
'press_index_accreditation_eyebrow',
|
||||||
|
'press_index_accreditation_heading',
|
||||||
|
'press_index_accreditation_description',
|
||||||
|
'press_index_accreditation_medium_label',
|
||||||
|
'press_index_accreditation_name_label',
|
||||||
|
'press_index_accreditation_email_label',
|
||||||
|
'press_index_accreditation_submit_label',
|
||||||
|
'press_index_accreditation_success_heading',
|
||||||
|
'press_index_accreditation_success_message',
|
||||||
|
].map((name) => [name, 'text'] as const)
|
||||||
|
|
||||||
|
const restoredLiveColumns = [
|
||||||
|
...homeApplicationColumns,
|
||||||
|
['home_application_award_caption', 'text'] as const,
|
||||||
|
['home_application_award_image_id', 'integer REFERENCES `media`(`id`) ON UPDATE no action ON DELETE set null'] as const,
|
||||||
|
...sharedFormColumns.map(([name, value]) => [`home_form_${name}`, `text DEFAULT ${literal(value)}`] as const),
|
||||||
|
...participationApplicationColumns,
|
||||||
|
['participation_application_form_image_id', 'integer REFERENCES `media`(`id`) ON UPDATE no action ON DELETE set null'] as const,
|
||||||
|
...sharedFormColumns.map(([name, value]) => [`participation_form_${name}`, `text DEFAULT ${literal(value)}`] as const),
|
||||||
|
...aboutHighlightColumns,
|
||||||
|
['about_highlights_fallback_image_id', 'integer REFERENCES `media`(`id`) ON UPDATE no action ON DELETE set null'] as const,
|
||||||
|
['about_highlights_year', 'numeric'] as const,
|
||||||
|
...pressColumns,
|
||||||
|
['press_index_downloads_kit_file_id', 'integer REFERENCES `media`(`id`) ON UPDATE no action ON DELETE set null'] as const,
|
||||||
|
...standaloneLiveColumns.map((name) => [name, 'text'] as const),
|
||||||
|
]
|
||||||
|
|
||||||
|
async function tableExists(db: MigrationDB, tableName: string) {
|
||||||
|
const rows = (await db.all(
|
||||||
|
sql.raw(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ${literal(tableName)}`),
|
||||||
|
)) as Array<{ name: string }>
|
||||||
|
return rows.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
async function columnNames(db: MigrationDB, tableName: string) {
|
||||||
|
if (!(await tableExists(db, tableName))) return []
|
||||||
|
const columns = (await db.all(sql.raw(`PRAGMA table_info(${ident(tableName)})`))) as Array<{ name: string }>
|
||||||
|
return columns.map((column) => column.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function columnExists(db: MigrationDB, tableName: string, columnName: string) {
|
||||||
|
return (await columnNames(db, tableName)).includes(columnName)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addColumn(db: MigrationDB, tableName: string, columnName: string, definition: string) {
|
||||||
|
if (await columnExists(db, tableName, columnName)) return
|
||||||
|
await db.run(sql.raw(`ALTER TABLE ${ident(tableName)} ADD ${ident(columnName)} ${definition};`))
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitDefinitions(value: string) {
|
||||||
|
const definitions: string[] = []
|
||||||
|
let current = ''
|
||||||
|
let depth = 0
|
||||||
|
let quote = ''
|
||||||
|
|
||||||
|
for (let index = 0; index < value.length; index += 1) {
|
||||||
|
const character = value[index]
|
||||||
|
const previous = value[index - 1]
|
||||||
|
|
||||||
|
if (quote) {
|
||||||
|
current += character
|
||||||
|
if (character === quote && previous !== '\\') quote = ''
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character === '`' || character === '"' || character === "'") {
|
||||||
|
quote = character
|
||||||
|
current += character
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character === '(') depth += 1
|
||||||
|
if (character === ')') depth -= 1
|
||||||
|
if (character === ',' && depth === 0) {
|
||||||
|
definitions.push(current.trim())
|
||||||
|
current = ''
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
current += character
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.trim()) definitions.push(current.trim())
|
||||||
|
return definitions
|
||||||
|
}
|
||||||
|
|
||||||
|
function definitionColumnName(definition: string) {
|
||||||
|
const match = definition.trim().match(/^(?:`([^`]+)`|"([^"]+)"|\[([^\]]+)\]|([^\s]+))/)
|
||||||
|
return match?.[1] || match?.[2] || match?.[3] || match?.[4] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function referencesColumn(definition: string, column: string) {
|
||||||
|
return [ident(column), `"${column}"`, `[${column}]`].some((value) => definition.includes(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rebuildTableWithoutColumns(
|
||||||
|
db: MigrationDB,
|
||||||
|
tableName: string,
|
||||||
|
removedColumns: readonly string[],
|
||||||
|
) {
|
||||||
|
if (removedColumns.length === 0) return
|
||||||
|
|
||||||
|
const schemaRows = (await db.all(
|
||||||
|
sql.raw(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ${literal(tableName)}`),
|
||||||
|
)) as Array<{ sql: string }>
|
||||||
|
const schema = schemaRows[0]?.sql
|
||||||
|
if (!schema) return
|
||||||
|
|
||||||
|
const open = schema.indexOf('(')
|
||||||
|
const close = schema.lastIndexOf(')')
|
||||||
|
if (open < 0 || close < open) throw new Error(`Could not parse schema for ${tableName}`)
|
||||||
|
|
||||||
|
const definitions = splitDefinitions(schema.slice(open + 1, close))
|
||||||
|
const keptDefinitions = definitions.filter((definition) => {
|
||||||
|
const columnName = definitionColumnName(definition)
|
||||||
|
const isConstraint = /^(CONSTRAINT|FOREIGN|PRIMARY|UNIQUE|CHECK)\b/i.test(columnName)
|
||||||
|
if (isConstraint) return !removedColumns.some((column) => referencesColumn(definition, column))
|
||||||
|
return !removedColumns.includes(columnName)
|
||||||
|
})
|
||||||
|
const keptColumns = (await columnNames(db, tableName)).filter((column) => !removedColumns.includes(column))
|
||||||
|
const indexRows = (await db.all(
|
||||||
|
sql.raw(`SELECT sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ${literal(tableName)} AND sql IS NOT NULL`),
|
||||||
|
)) as Array<{ sql: string }>
|
||||||
|
const keptIndexes = indexRows
|
||||||
|
.map((row) => row.sql)
|
||||||
|
.filter((indexSql) => !removedColumns.some((column) => referencesColumn(indexSql, column)))
|
||||||
|
const temporaryTable = `__task4_${tableName}`
|
||||||
|
const columnList = keptColumns.map(ident).join(', ')
|
||||||
|
|
||||||
|
await db.run(sql.raw(`DROP TABLE IF EXISTS ${ident(temporaryTable)};`))
|
||||||
|
await db.run(sql.raw(`CREATE TABLE ${ident(temporaryTable)} (${keptDefinitions.join(',\n')})${schema.slice(close + 1)};`))
|
||||||
|
await db.run(sql.raw(`INSERT INTO ${ident(temporaryTable)} (${columnList}) SELECT ${columnList} FROM ${ident(tableName)};`))
|
||||||
|
await db.run(sql.raw(`DROP TABLE ${ident(tableName)};`))
|
||||||
|
await db.run(sql.raw(`ALTER TABLE ${ident(temporaryTable)} RENAME TO ${ident(tableName)};`))
|
||||||
|
|
||||||
|
for (const indexSql of keptIndexes) await db.run(sql.raw(`${indexSql};`))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function trimSponsoringSteps(db: MigrationDB, tableName: string) {
|
||||||
|
if (!(await tableExists(db, tableName))) return
|
||||||
|
|
||||||
|
await db.run(sql.raw(`
|
||||||
|
DELETE FROM ${ident(tableName)}
|
||||||
|
WHERE ${ident('id')} IN (
|
||||||
|
SELECT ${ident('id')}
|
||||||
|
FROM (
|
||||||
|
SELECT ${ident('id')}, ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY ${ident('_parent_id')} ORDER BY ${ident('_order')} DESC
|
||||||
|
) AS row_number
|
||||||
|
FROM ${ident(tableName)}
|
||||||
|
)
|
||||||
|
WHERE row_number > 2
|
||||||
|
);
|
||||||
|
`))
|
||||||
|
|
||||||
|
await db.run(sql.raw(`
|
||||||
|
UPDATE ${ident(tableName)} AS current
|
||||||
|
SET ${ident('_order')} = (
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM ${ident(tableName)} AS preceding
|
||||||
|
WHERE preceding.${ident('_parent_id')} = current.${ident('_parent_id')}
|
||||||
|
AND preceding.${ident('_order')} <= current.${ident('_order')}
|
||||||
|
);
|
||||||
|
`))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createArrayTable(
|
||||||
|
db: MigrationDB,
|
||||||
|
tableName: string,
|
||||||
|
parentTable: 'pages' | '_pages_v',
|
||||||
|
versioned: boolean,
|
||||||
|
fields: string,
|
||||||
|
) {
|
||||||
|
if (await tableExists(db, tableName)) return
|
||||||
|
|
||||||
|
await db.run(sql.raw(`
|
||||||
|
CREATE TABLE ${ident(tableName)} (
|
||||||
|
${ident('_order')} integer NOT NULL,
|
||||||
|
${ident('_parent_id')} integer NOT NULL,
|
||||||
|
${ident('id')} ${versioned ? 'integer' : 'text'} PRIMARY KEY NOT NULL,
|
||||||
|
${fields},
|
||||||
|
${versioned ? `${ident('_uuid')} text,` : ''}
|
||||||
|
FOREIGN KEY (${ident('_parent_id')}) REFERENCES ${ident(parentTable)}(${ident('id')}) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
`))
|
||||||
|
await db.run(sql.raw(`CREATE INDEX IF NOT EXISTS ${ident(`${tableName}_order_idx`)} ON ${ident(tableName)} (${ident('_order')});`))
|
||||||
|
await db.run(sql.raw(`CREATE INDEX IF NOT EXISTS ${ident(`${tableName}_parent_id_idx`)} ON ${ident(tableName)} (${ident('_parent_id')});`))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restoreChildTables(db: MigrationDB) {
|
||||||
|
const textRows = `${ident('num')} text, ${ident('label')} text, ${ident('desc')} text`
|
||||||
|
const stepRows = `${ident('label')} text, ${ident('icon')} text DEFAULT 'trophy'`
|
||||||
|
const optionRows = `${ident('value')} text, ${ident('label')} text`
|
||||||
|
const packageRows = `${ident('value')} text, ${ident('title')} text, ${ident('desc')} text`
|
||||||
|
|
||||||
|
await createArrayTable(db, 'pages_home_application_benefits', 'pages', false, textRows)
|
||||||
|
await createArrayTable(db, '_pages_v_version_home_application_benefits', '_pages_v', true, textRows)
|
||||||
|
await createArrayTable(db, 'home_self_steps', 'pages', false, stepRows)
|
||||||
|
await createArrayTable(db, '_home_self_steps_v', '_pages_v', true, stepRows)
|
||||||
|
await createArrayTable(db, 'home_nom_steps', 'pages', false, stepRows)
|
||||||
|
await createArrayTable(db, '_home_nom_steps_v', '_pages_v', true, stepRows)
|
||||||
|
await createArrayTable(db, 'home_emp_opts', 'pages', false, optionRows)
|
||||||
|
await createArrayTable(db, '_home_emp_opts_v', '_pages_v', true, optionRows)
|
||||||
|
await createArrayTable(db, 'form_facts', 'pages', false, textRows)
|
||||||
|
await createArrayTable(db, '_form_facts_v', '_pages_v', true, textRows)
|
||||||
|
await createArrayTable(db, 'self_steps', 'pages', false, stepRows)
|
||||||
|
await createArrayTable(db, '_self_steps_v', '_pages_v', true, stepRows)
|
||||||
|
await createArrayTable(db, 'nom_steps', 'pages', false, stepRows)
|
||||||
|
await createArrayTable(db, '_nom_steps_v', '_pages_v', true, stepRows)
|
||||||
|
await createArrayTable(db, 'emp_opts', 'pages', false, optionRows)
|
||||||
|
await createArrayTable(db, '_emp_opts_v', '_pages_v', true, optionRows)
|
||||||
|
await createArrayTable(db, 'network_form_pkgs', 'pages', false, packageRows)
|
||||||
|
await createArrayTable(db, '_network_form_pkgs_v', '_pages_v', true, packageRows)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function up({ db, payload: _payload, req: _req }: MigrateUpArgs): Promise<void> {
|
||||||
|
await db.run(sql.raw('PRAGMA foreign_keys=OFF;'))
|
||||||
|
|
||||||
|
for (const indexName of uploadIndexes) {
|
||||||
|
await db.run(sql.raw(`DROP INDEX IF EXISTS ${ident(indexName)};`))
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.run(sql.raw("DELETE FROM `pages_rels` WHERE `path` = 'about.highlights.selectedWinners';"))
|
||||||
|
await db.run(sql.raw("DELETE FROM `_pages_v_rels` WHERE `path` = 'version.about.highlights.selectedWinners';"))
|
||||||
|
|
||||||
|
for (const tableName of obsoleteChildTables) {
|
||||||
|
await db.run(sql.raw(`DROP TABLE IF EXISTS ${ident(tableName)};`))
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageColumns = await columnNames(db, 'pages')
|
||||||
|
const removedPageColumns = pageColumns.filter(
|
||||||
|
(column) => standaloneLiveColumns.includes(column as (typeof standaloneLiveColumns)[number]) || livePrefixes.some((prefix) => column.startsWith(prefix)),
|
||||||
|
)
|
||||||
|
const versionColumns = await columnNames(db, '_pages_v')
|
||||||
|
const standaloneVersionColumns = standaloneLiveColumns.map((column) => `version_${column}`)
|
||||||
|
const removedVersionColumns = versionColumns.filter(
|
||||||
|
(column) => standaloneVersionColumns.includes(column) || versionPrefixes.some((prefix) => column.startsWith(prefix)),
|
||||||
|
)
|
||||||
|
|
||||||
|
await rebuildTableWithoutColumns(db, 'pages', removedPageColumns)
|
||||||
|
await rebuildTableWithoutColumns(db, '_pages_v', removedVersionColumns)
|
||||||
|
await rebuildTableWithoutColumns(db, 'app_ways', obsoleteApplicationWayColumns)
|
||||||
|
await rebuildTableWithoutColumns(db, '_app_ways_v', obsoleteApplicationWayColumns)
|
||||||
|
|
||||||
|
await trimSponsoringSteps(db, 'network_form_steps')
|
||||||
|
await trimSponsoringSteps(db, '_network_form_steps_v')
|
||||||
|
|
||||||
|
await db.run(sql.raw('PRAGMA foreign_keys=ON;'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down({ db, payload: _payload, req: _req }: MigrateDownArgs): Promise<void> {
|
||||||
|
for (const [column, definition] of restoredLiveColumns) {
|
||||||
|
await addColumn(db, 'pages', column, definition)
|
||||||
|
await addColumn(db, '_pages_v', `version_${column}`, definition)
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.run(sql.raw('CREATE INDEX IF NOT EXISTS `pages_home_application_home_application_award_image_idx` ON `pages` (`home_application_award_image_id`);'))
|
||||||
|
await db.run(sql.raw('CREATE INDEX IF NOT EXISTS `_pages_v_version_home_application_version_home_applicati_idx` ON `_pages_v` (`version_home_application_award_image_id`);'))
|
||||||
|
await db.run(sql.raw('CREATE INDEX IF NOT EXISTS `pages_participation_application_form_participation_appli_idx` ON `pages` (`participation_application_form_image_id`);'))
|
||||||
|
await db.run(sql.raw('CREATE INDEX IF NOT EXISTS `_pages_v_version_participation_application_form_version__idx` ON `_pages_v` (`version_participation_application_form_image_id`);'))
|
||||||
|
await db.run(sql.raw('CREATE INDEX IF NOT EXISTS `pages_about_highlights_about_highlights_fallback_image_idx` ON `pages` (`about_highlights_fallback_image_id`);'))
|
||||||
|
await db.run(sql.raw('CREATE INDEX IF NOT EXISTS `_pages_v_version_about_highlights_version_about_highligh_idx` ON `_pages_v` (`version_about_highlights_fallback_image_id`);'))
|
||||||
|
await db.run(sql.raw('CREATE INDEX IF NOT EXISTS `pages_press_index_downloads_press_index_downloads_kit_fi_idx` ON `pages` (`press_index_downloads_kit_file_id`);'))
|
||||||
|
await db.run(sql.raw('CREATE INDEX IF NOT EXISTS `_pages_v_version_press_index_downloads_version_press_ind_idx` ON `_pages_v` (`version_press_index_downloads_kit_file_id`);'))
|
||||||
|
|
||||||
|
await restoreChildTables(db)
|
||||||
|
|
||||||
|
await addColumn(db, 'app_ways', 'featured', 'integer DEFAULT false')
|
||||||
|
await addColumn(db, 'app_ways', 'cta_label', "text DEFAULT 'Zum Formular'")
|
||||||
|
await addColumn(db, 'app_ways', 'cta_url', "text DEFAULT '#bewerben'")
|
||||||
|
await addColumn(db, '_app_ways_v', 'featured', 'integer DEFAULT false')
|
||||||
|
await addColumn(db, '_app_ways_v', 'cta_label', "text DEFAULT 'Zum Formular'")
|
||||||
|
await addColumn(db, '_app_ways_v', 'cta_url', "text DEFAULT '#bewerben'")
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import * as migration_20260713_194500_contact_submissions from './20260713_19450
|
|||||||
import * as migration_20260713_213000_netzwerk_jury_selection from './20260713_213000_netzwerk_jury_selection';
|
import * as migration_20260713_213000_netzwerk_jury_selection from './20260713_213000_netzwerk_jury_selection';
|
||||||
import * as migration_20260713_220000_preistraeger_detail_cleanup from './20260713_220000_preistraeger_detail_cleanup';
|
import * as migration_20260713_220000_preistraeger_detail_cleanup from './20260713_220000_preistraeger_detail_cleanup';
|
||||||
import * as migration_20260731_143000_remove_press_record_fallbacks from './20260731_143000_remove_press_record_fallbacks';
|
import * as migration_20260731_143000_remove_press_record_fallbacks from './20260731_143000_remove_press_record_fallbacks';
|
||||||
|
import * as migration_20260731_160000_focused_payload_cleanup from './20260731_160000_focused_payload_cleanup';
|
||||||
|
|
||||||
export const migrations = [
|
export const migrations = [
|
||||||
{
|
{
|
||||||
@@ -108,4 +109,9 @@ export const migrations = [
|
|||||||
down: migration_20260731_143000_remove_press_record_fallbacks.down,
|
down: migration_20260731_143000_remove_press_record_fallbacks.down,
|
||||||
name: '20260731_143000_remove_press_record_fallbacks',
|
name: '20260731_143000_remove_press_record_fallbacks',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
up: migration_20260731_160000_focused_payload_cleanup.up,
|
||||||
|
down: migration_20260731_160000_focused_payload_cleanup.down,
|
||||||
|
name: '20260731_160000_focused_payload_cleanup',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -41,25 +41,15 @@ async function main() {
|
|||||||
const page = pageResult.docs[0]
|
const page = pageResult.docs[0]
|
||||||
if (!page) throw new Error('About page not found. Expected spaPath=/der-bmp or slug=der-bmp/about.')
|
if (!page) throw new Error('About page not found. Expected spaPath=/der-bmp or slug=der-bmp/about.')
|
||||||
|
|
||||||
const [heroImage, prizeImage, mittelstandImage, fallbackWinnerImage, winnerResult] = await Promise.all([
|
const [heroImage, prizeImage, mittelstandImage] = await Promise.all([
|
||||||
mediaByFilename(payload, aboutContent.hero.backgroundImageFilename),
|
mediaByFilename(payload, aboutContent.hero.backgroundImageFilename),
|
||||||
mediaByFilename(payload, aboutContent.prize.imageFilename),
|
mediaByFilename(payload, aboutContent.prize.imageFilename),
|
||||||
mediaByFilename(payload, aboutContent.mittelstand.imageFilename),
|
mediaByFilename(payload, aboutContent.mittelstand.imageFilename),
|
||||||
mediaByFilename(payload, aboutContent.highlights.fallbackImageFilename),
|
|
||||||
payload.find({
|
|
||||||
collection: 'preistraeger',
|
|
||||||
limit: 8,
|
|
||||||
pagination: false,
|
|
||||||
draft: true,
|
|
||||||
overrideAccess: true,
|
|
||||||
where: { year: { equals: aboutContent.highlights.year } },
|
|
||||||
}),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const { backgroundImageFilename: _heroFilename, ...heroContent } = aboutContent.hero
|
const { backgroundImageFilename: _heroFilename, ...heroContent } = aboutContent.hero
|
||||||
const { imageFilename: _prizeFilename, ...prizeContent } = aboutContent.prize
|
const { imageFilename: _prizeFilename, ...prizeContent } = aboutContent.prize
|
||||||
const { imageFilename: _mittelstandFilename, ...mittelstandContent } = aboutContent.mittelstand
|
const { imageFilename: _mittelstandFilename, ...mittelstandContent } = aboutContent.mittelstand
|
||||||
const { fallbackImageFilename: _highlightFallbackFilename, ...highlightsContent } = aboutContent.highlights
|
|
||||||
const testimonials = await testimonialsWithImages(payload)
|
const testimonials = await testimonialsWithImages(payload)
|
||||||
|
|
||||||
await payload.update({
|
await payload.update({
|
||||||
@@ -84,11 +74,6 @@ async function main() {
|
|||||||
image: mittelstandImage,
|
image: mittelstandImage,
|
||||||
body: aboutContent.mittelstand.body.map((text) => ({ text })),
|
body: aboutContent.mittelstand.body.map((text) => ({ text })),
|
||||||
},
|
},
|
||||||
highlights: {
|
|
||||||
...highlightsContent,
|
|
||||||
fallbackImage: fallbackWinnerImage,
|
|
||||||
selectedWinners: winnerResult.docs.map((winner) => winner.id),
|
|
||||||
},
|
|
||||||
testimonials,
|
testimonials,
|
||||||
},
|
},
|
||||||
} as never,
|
} as never,
|
||||||
|
|||||||
@@ -41,12 +41,11 @@ async function main() {
|
|||||||
const page = pageResult.docs[0]
|
const page = pageResult.docs[0]
|
||||||
if (!page) throw new Error('Home page not found. Expected spaPath=/ or slug=startseite/home.')
|
if (!page) throw new Error('Home page not found. Expected spaPath=/ or slug=startseite/home.')
|
||||||
|
|
||||||
const [heroImage, quickImage, introImage, benefitsImage, awardImage] = await Promise.all([
|
const [heroImage, quickImage, introImage, benefitsImage] = await Promise.all([
|
||||||
mediaByFilename(payload, homeContent.hero.backgroundImageFilename),
|
mediaByFilename(payload, homeContent.hero.backgroundImageFilename),
|
||||||
mediaByFilename(payload, homeContent.quickCheck.imageFilename),
|
mediaByFilename(payload, homeContent.quickCheck.imageFilename),
|
||||||
mediaByFilename(payload, homeContent.intro.imageFilename),
|
mediaByFilename(payload, homeContent.intro.imageFilename),
|
||||||
mediaByFilename(payload, homeContent.benefits.imageFilename),
|
mediaByFilename(payload, homeContent.benefits.imageFilename),
|
||||||
mediaByFilename(payload, homeContent.application.awardImageFilename),
|
|
||||||
])
|
])
|
||||||
const fallbackWinnerImage = await mediaByFilename(payload, homeContent.winners.fallbackImageFilename)
|
const fallbackWinnerImage = await mediaByFilename(payload, homeContent.winners.fallbackImageFilename)
|
||||||
const { backgroundImageFilename: _heroFilename, ...heroContent } = homeContent.hero
|
const { backgroundImageFilename: _heroFilename, ...heroContent } = homeContent.hero
|
||||||
@@ -54,7 +53,6 @@ async function main() {
|
|||||||
const { imageFilename: _introFilename, ...introContent } = homeContent.intro
|
const { imageFilename: _introFilename, ...introContent } = homeContent.intro
|
||||||
const { fallbackImageFilename: _winnerFallbackFilename, ...winnersContent } = homeContent.winners
|
const { fallbackImageFilename: _winnerFallbackFilename, ...winnersContent } = homeContent.winners
|
||||||
const { imageFilename: _benefitsFilename, ...benefitsContent } = homeContent.benefits
|
const { imageFilename: _benefitsFilename, ...benefitsContent } = homeContent.benefits
|
||||||
const { awardImageFilename: _awardFilename, ...applicationContent } = homeContent.application
|
|
||||||
const testimonials = await testimonialsWithImages(payload)
|
const testimonials = await testimonialsWithImages(payload)
|
||||||
|
|
||||||
const winners = await payload.find({
|
const winners = await payload.find({
|
||||||
@@ -94,11 +92,6 @@ async function main() {
|
|||||||
...benefitsContent,
|
...benefitsContent,
|
||||||
image: benefitsImage,
|
image: benefitsImage,
|
||||||
},
|
},
|
||||||
application: {
|
|
||||||
...applicationContent,
|
|
||||||
awardImage,
|
|
||||||
},
|
|
||||||
form: homeContent.form,
|
|
||||||
videoModal: homeContent.videoModal,
|
videoModal: homeContent.videoModal,
|
||||||
},
|
},
|
||||||
} as never,
|
} as never,
|
||||||
|
|||||||
@@ -23,13 +23,11 @@ async function main() {
|
|||||||
const page = pageResult.docs[0]
|
const page = pageResult.docs[0]
|
||||||
if (!page) throw new Error('Participation page not found. Expected spaPath=/teilnahme or slug=teilnahme/participation.')
|
if (!page) throw new Error('Participation page not found. Expected spaPath=/teilnahme or slug=teilnahme/participation.')
|
||||||
|
|
||||||
const [heroImage, formImage, ...awardCardImages] = await Promise.all([
|
const [heroImage, ...awardCardImages] = await Promise.all([
|
||||||
mediaByFilename(payload, participationContent.hero.backgroundImageFilename),
|
mediaByFilename(payload, participationContent.hero.backgroundImageFilename),
|
||||||
mediaByFilename(payload, participationContent.applicationForm.imageFilename),
|
|
||||||
...participationContent.awardsGrid.cards.map((card) => mediaByFilename(payload, card.imageFilename)),
|
...participationContent.awardsGrid.cards.map((card) => mediaByFilename(payload, card.imageFilename)),
|
||||||
])
|
])
|
||||||
const { backgroundImageFilename: _heroFilename, ...heroContent } = participationContent.hero
|
const { backgroundImageFilename: _heroFilename, ...heroContent } = participationContent.hero
|
||||||
const { imageFilename: _formFilename, ...applicationFormContent } = participationContent.applicationForm
|
|
||||||
const { cards: awardCards, ...awardsGridContent } = participationContent.awardsGrid
|
const { cards: awardCards, ...awardsGridContent } = participationContent.awardsGrid
|
||||||
|
|
||||||
await payload.update({
|
await payload.update({
|
||||||
@@ -54,11 +52,6 @@ async function main() {
|
|||||||
image: awardCardImages[index],
|
image: awardCardImages[index],
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
applicationForm: {
|
|
||||||
...applicationFormContent,
|
|
||||||
image: formImage,
|
|
||||||
},
|
|
||||||
form: participationContent.form,
|
|
||||||
},
|
},
|
||||||
} as never,
|
} as never,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,11 +23,10 @@ async function main() {
|
|||||||
const page = pageResult.docs[0]
|
const page = pageResult.docs[0]
|
||||||
if (!page) throw new Error('Press index page not found. Expected spaPath=/presse or slug=presse/press.')
|
if (!page) throw new Error('Press index page not found. Expected spaPath=/presse or slug=presse/press.')
|
||||||
|
|
||||||
const [heroImage, eventFallbackImage, newsFallbackImage, kitFile] = await Promise.all([
|
const [heroImage, eventFallbackImage, newsFallbackImage] = await Promise.all([
|
||||||
mediaByFilename(payload, pressIndexContent.hero.imageFilename),
|
mediaByFilename(payload, pressIndexContent.hero.imageFilename),
|
||||||
mediaByFilename(payload, pressIndexContent.events.collectionFallbackImageFilename),
|
mediaByFilename(payload, pressIndexContent.events.collectionFallbackImageFilename),
|
||||||
mediaByFilename(payload, pressIndexContent.news.collectionFallbackImageFilename),
|
mediaByFilename(payload, pressIndexContent.news.collectionFallbackImageFilename),
|
||||||
mediaByFilename(payload, pressIndexContent.downloads.kitDownloadName),
|
|
||||||
])
|
])
|
||||||
const { imageFilename: _heroFilename, ...heroContent } = pressIndexContent.hero
|
const { imageFilename: _heroFilename, ...heroContent } = pressIndexContent.hero
|
||||||
const { collectionFallbackImageFilename: _eventFallbackFilename, ...eventsContent } = pressIndexContent.events
|
const { collectionFallbackImageFilename: _eventFallbackFilename, ...eventsContent } = pressIndexContent.events
|
||||||
@@ -52,10 +51,7 @@ async function main() {
|
|||||||
...newsContent,
|
...newsContent,
|
||||||
collectionFallbackImage: newsFallbackImage,
|
collectionFallbackImage: newsFallbackImage,
|
||||||
},
|
},
|
||||||
downloads: {
|
downloads: pressIndexContent.downloads,
|
||||||
...pressIndexContent.downloads,
|
|
||||||
kitFile,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
} as never,
|
} as never,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -43,20 +43,6 @@ export const aboutContent = {
|
|||||||
'In einer globalisierten Welt sind es diese Unternehmen, die für Stabilität, Innovation und soziale Verantwortung stehen. Der BMP macht sie sichtbar.',
|
'In einer globalisierten Welt sind es diese Unternehmen, die für Stabilität, Innovation und soziale Verantwortung stehen. Der BMP macht sie sichtbar.',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
highlights: {
|
|
||||||
fallbackImageFilename: 'gala-saal-overview.jpg',
|
|
||||||
eyebrow: 'Preisträger-Highlights',
|
|
||||||
heading: 'AUSGEZEICHNETE 2025',
|
|
||||||
cta: { label: 'Alle Preisträger', url: '/preistraeger' },
|
|
||||||
hoverLabel: 'Detail →',
|
|
||||||
year: 2025,
|
|
||||||
fallbackWinner: {
|
|
||||||
name: 'Preisträger',
|
|
||||||
category: 'Preisträger',
|
|
||||||
awardType: 'Auszeichnung',
|
|
||||||
},
|
|
||||||
selectedWinners: [],
|
|
||||||
},
|
|
||||||
testimonials: testimonialsContent,
|
testimonials: testimonialsContent,
|
||||||
history: {
|
history: {
|
||||||
eyebrow: 'Geschichte & Meilensteine',
|
eyebrow: 'Geschichte & Meilensteine',
|
||||||
|
|||||||
@@ -147,8 +147,7 @@ export default function SponsoringForm({ theme = 'dark', content }: { theme?: 'd
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const applicationPhase = useApplicationPhase();
|
const applicationPhase = useApplicationPhase();
|
||||||
const copy = { ...netzwerkContent.sponsoringForm, ...(content || {}) };
|
const copy = { ...netzwerkContent.sponsoringForm, ...(content || {}) };
|
||||||
const configuredSteps = fallbackArray<StepOption>(copy.steps, netzwerkContent.sponsoringForm.steps);
|
const steps = fallbackArray<StepOption>(copy.steps, netzwerkContent.sponsoringForm.steps);
|
||||||
const steps = configuredSteps.slice(-2);
|
|
||||||
const [step, setStep] = useState(0);
|
const [step, setStep] = useState(0);
|
||||||
const [dir, setDir] = useState(1);
|
const [dir, setDir] = useState(1);
|
||||||
const [submitted, setSubmitted] = useState(false);
|
const [submitted, setSubmitted] = useState(false);
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ export const homeContent = {
|
|||||||
],
|
],
|
||||||
awardImageFilename: 'preisuebergabe.jpg',
|
awardImageFilename: 'preisuebergabe.jpg',
|
||||||
awardImageAlt: 'Preisverleihung Bayerischer Mittelstandspreis',
|
awardImageAlt: 'Preisverleihung Bayerischer Mittelstandspreis',
|
||||||
awardCaption: 'Preisverleihung 2025',
|
|
||||||
formEyebrow: 'Direktbewerbung 2026',
|
formEyebrow: 'Direktbewerbung 2026',
|
||||||
formTitle: 'Kostenlos bewerben',
|
formTitle: 'Kostenlos bewerben',
|
||||||
footnote: 'Kostenlos und unverbindlich –',
|
footnote: 'Kostenlos und unverbindlich –',
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ export const netzwerkContent = {
|
|||||||
description:
|
description:
|
||||||
'Unabhängige Expertinnen und Experten aus Wirtschaft, Wissenschaft und Verbänden, für einen vollständig unabhängigen Bewertungsprozess.',
|
'Unabhängige Expertinnen und Experten aus Wirtschaft, Wissenschaft und Verbänden, für einen vollständig unabhängigen Bewertungsprozess.',
|
||||||
chairBadge: 'Jury-Vorsitz',
|
chairBadge: 'Jury-Vorsitz',
|
||||||
note: 'Hinweis: Einzelne Persönlichkeiten engagieren sich sowohl in der Jury als auch als Partner des BMP – beide Rollen werden auf dieser Seite getrennt ausgewiesen.',
|
|
||||||
members: [
|
members: [
|
||||||
{
|
{
|
||||||
name: 'Prof. Dr. Klaus Bergmann',
|
name: 'Prof. Dr. Klaus Bergmann',
|
||||||
@@ -123,8 +122,6 @@ export const netzwerkContent = {
|
|||||||
heading: 'PARTNER & SPONSOREN',
|
heading: 'PARTNER & SPONSOREN',
|
||||||
description: 'Ein starkes Netzwerk aus Unternehmen und Medien begleitet den Bayerischen Mittelstandspreis. Klicken für Details.',
|
description: 'Ein starkes Netzwerk aus Unternehmen und Medien begleitet den Bayerischen Mittelstandspreis. Klicken für Details.',
|
||||||
detailLabel: 'Details',
|
detailLabel: 'Details',
|
||||||
premiumLabel: '',
|
|
||||||
defaultLogoColor: '#111D55',
|
|
||||||
tiers: [
|
tiers: [
|
||||||
{ tier: 1, label: 'Große Darstellung', note: '' },
|
{ tier: 1, label: 'Große Darstellung', note: '' },
|
||||||
{ tier: 2, label: 'Mittlere Darstellung', note: '' },
|
{ tier: 2, label: 'Mittlere Darstellung', note: '' },
|
||||||
@@ -287,10 +284,6 @@ export const netzwerkContent = {
|
|||||||
imageFilename: 'networking-innenhof.jpg',
|
imageFilename: 'networking-innenhof.jpg',
|
||||||
imageAlt: 'BMP Partnernetzwerk',
|
imageAlt: 'BMP Partnernetzwerk',
|
||||||
imageLabel: 'BMP Partnernetzwerk',
|
imageLabel: 'BMP Partnernetzwerk',
|
||||||
formEyebrow: 'Sponsoring 2026',
|
|
||||||
footnotePrefix: 'Oder:',
|
|
||||||
footnoteLabel: 'Vereinsmitglied werden',
|
|
||||||
footnoteUrl: '/mitglied-werden',
|
|
||||||
benefits: [
|
benefits: [
|
||||||
{ num: '01', label: 'Markenpräsenz', sub: 'Bühne, Drucksachen, digitale Kanäle' },
|
{ num: '01', label: 'Markenpräsenz', sub: 'Bühne, Drucksachen, digitale Kanäle' },
|
||||||
{ num: '02', label: 'Top-Entscheider', sub: 'Exklusives Netzwerk-Dinner für Partner' },
|
{ num: '02', label: 'Top-Entscheider', sub: 'Exklusives Netzwerk-Dinner für Partner' },
|
||||||
@@ -302,13 +295,6 @@ export const netzwerkContent = {
|
|||||||
{ label: 'Unternehmen' },
|
{ label: 'Unternehmen' },
|
||||||
{ label: 'Kontakt' },
|
{ label: 'Kontakt' },
|
||||||
],
|
],
|
||||||
packages: [
|
|
||||||
{ value: 'main', title: 'Haupt-Sponsoring', desc: 'Maximale Sichtbarkeit auf allen Kanälen – Bühne, Digital, Print.' },
|
|
||||||
{ value: 'category', title: 'Kategorie-Partner', desc: 'Namensgebung einer Auszeichnungskategorie – fokussiertes Branding.' },
|
|
||||||
{ value: 'event', title: 'Event-Partner', desc: 'Präsenz beim exklusiven Gala-Abend in München.' },
|
|
||||||
],
|
|
||||||
step1Eyebrow: 'Schritt 1 – Paket wählen',
|
|
||||||
step1Heading: 'Interesse an',
|
|
||||||
step2Eyebrow: 'Schritt 2 – Unternehmen',
|
step2Eyebrow: 'Schritt 2 – Unternehmen',
|
||||||
step2Heading: 'Ihr Unternehmen',
|
step2Heading: 'Ihr Unternehmen',
|
||||||
companyLabel: 'Unternehmen *',
|
companyLabel: 'Unternehmen *',
|
||||||
@@ -324,7 +310,6 @@ export const netzwerkContent = {
|
|||||||
backLabel: 'Zurück',
|
backLabel: 'Zurück',
|
||||||
nextLabel: 'Weiter',
|
nextLabel: 'Weiter',
|
||||||
submitLabel: 'Exposé anfordern',
|
submitLabel: 'Exposé anfordern',
|
||||||
requiredPackageError: 'Bitte wählen Sie ein Paket.',
|
|
||||||
requiredFieldError: 'Pflichtfeld',
|
requiredFieldError: 'Pflichtfeld',
|
||||||
invalidEmailError: 'Ungültige E-Mail',
|
invalidEmailError: 'Ungültige E-Mail',
|
||||||
doneLabel: '✓',
|
doneLabel: '✓',
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ type HomeHeroCms = {
|
|||||||
videoLabel?: string | null;
|
videoLabel?: string | null;
|
||||||
};
|
};
|
||||||
type HomeSectionCms = CmsRecord & {
|
type HomeSectionCms = CmsRecord & {
|
||||||
awardCaption?: string | null;
|
|
||||||
awardImage?: unknown;
|
awardImage?: unknown;
|
||||||
awardImageAlt?: string | null;
|
awardImageAlt?: string | null;
|
||||||
benefits?: unknown;
|
benefits?: unknown;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type PressIndexCms = Partial<typeof pressIndexContent> & {
|
|||||||
hero?: Partial<typeof pressIndexContent.hero> & { image?: unknown }
|
hero?: Partial<typeof pressIndexContent.hero> & { image?: unknown }
|
||||||
events?: Partial<typeof pressIndexContent.events> & { collectionFallbackImage?: unknown }
|
events?: Partial<typeof pressIndexContent.events> & { collectionFallbackImage?: unknown }
|
||||||
news?: Partial<typeof pressIndexContent.news> & { collectionFallbackImage?: unknown }
|
news?: Partial<typeof pressIndexContent.news> & { collectionFallbackImage?: unknown }
|
||||||
downloads?: Partial<typeof pressIndexContent.downloads> & { kitFile?: unknown }
|
downloads?: Partial<typeof pressIndexContent.downloads>
|
||||||
}
|
}
|
||||||
|
|
||||||
const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback;
|
const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback;
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ export const participationContent = {
|
|||||||
eyebrow: 'Berechtigung',
|
eyebrow: 'Berechtigung',
|
||||||
heading: 'WER KANN\nTEILNEHMEN?',
|
heading: 'WER KANN\nTEILNEHMEN?',
|
||||||
primaryCta: { label: 'Jetzt bewerben', url: '#bewerben' },
|
primaryCta: { label: 'Jetzt bewerben', url: '#bewerben' },
|
||||||
secondaryCta: { label: 'Mitglied werden', url: '/mitglied-werden' },
|
|
||||||
description:
|
description:
|
||||||
'Vier klar definierte Kriterien: Erfüllen Sie alle, bewerben Sie sich kostenlos und ohne bürokratischen Aufwand.',
|
'Vier klar definierte Kriterien: Erfüllen Sie alle, bewerben Sie sich kostenlos und ohne bürokratischen Aufwand.',
|
||||||
criteria: [
|
criteria: [
|
||||||
@@ -74,7 +73,6 @@ export const participationContent = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
nudgeText: 'Alle 4 Punkte erfüllt? Dann jetzt kostenlos bewerben.',
|
nudgeText: 'Alle 4 Punkte erfüllt? Dann jetzt kostenlos bewerben.',
|
||||||
nudgeCta: { label: 'Zum Formular', url: '#bewerben' },
|
|
||||||
},
|
},
|
||||||
applicationWays: {
|
applicationWays: {
|
||||||
eyebrow: 'Bewerbung 2026',
|
eyebrow: 'Bewerbung 2026',
|
||||||
@@ -90,15 +88,12 @@ export const participationContent = {
|
|||||||
'Mitglieder des Wirtschaftsbeirates Bayern',
|
'Mitglieder des Wirtschaftsbeirates Bayern',
|
||||||
'Ehemalige Preisträger des Bayerischen Mittelstandspreises',
|
'Ehemalige Preisträger des Bayerischen Mittelstandspreises',
|
||||||
].map((text) => ({ text })),
|
].map((text) => ({ text })),
|
||||||
fallbackCta: { label: 'Zum Formular', url: '#bewerben' },
|
|
||||||
ways: [
|
ways: [
|
||||||
{
|
{
|
||||||
label: 'Weg 01',
|
label: 'Weg 01',
|
||||||
title: 'Vorschlag\nunterbreiten',
|
title: 'Vorschlag\nunterbreiten',
|
||||||
desc: 'Durch ehemalige Preisträger, die HAM, Senatoren und den Preis unterstützende Institutionen.',
|
desc: 'Durch ehemalige Preisträger, die HAM, Senatoren und den Preis unterstützende Institutionen.',
|
||||||
icon: 'send',
|
icon: 'send',
|
||||||
featured: false,
|
|
||||||
cta: { label: 'Vorschlag unterbreiten', url: '#bewerben' },
|
|
||||||
listType: 'institutions',
|
listType: 'institutions',
|
||||||
facts: [],
|
facts: [],
|
||||||
},
|
},
|
||||||
@@ -107,8 +102,6 @@ export const participationContent = {
|
|||||||
title: 'Initiativbewerbung',
|
title: 'Initiativbewerbung',
|
||||||
desc: 'Für Unternehmer, die selbst die Initiative ergreifen.',
|
desc: 'Für Unternehmer, die selbst die Initiative ergreifen.',
|
||||||
icon: 'userCheck',
|
icon: 'userCheck',
|
||||||
featured: false,
|
|
||||||
cta: { label: 'Jetzt bewerben', url: '#bewerben' },
|
|
||||||
listType: 'facts',
|
listType: 'facts',
|
||||||
facts: [
|
facts: [
|
||||||
{ label: '0 EUR', desc: 'Teilnahmegebühr' },
|
{ label: '0 EUR', desc: 'Teilnahmegebühr' },
|
||||||
@@ -118,7 +111,6 @@ export const participationContent = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
recommendedLabel: 'Empfohlen',
|
|
||||||
},
|
},
|
||||||
datesBanner: {
|
datesBanner: {
|
||||||
heading: 'Wichtige Termine 2026',
|
heading: 'Wichtige Termine 2026',
|
||||||
@@ -135,25 +127,6 @@ export const participationContent = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
awardsGrid: participationAwardsGrid,
|
awardsGrid: participationAwardsGrid,
|
||||||
applicationForm: {
|
|
||||||
imageFilename: 'preistraeger-jubel.jpg',
|
|
||||||
imageAlt: 'Preisverleihung 2025',
|
|
||||||
imageCaption: 'Preisverleihung 2025',
|
|
||||||
eyebrow: 'Direktbewerbung',
|
|
||||||
heading: 'BEWERBEN ODER\nVORSCHLAGEN.',
|
|
||||||
description:
|
|
||||||
'Sie können sich selbst bewerben oder ein herausragendes Unternehmen vorschlagen. Firmenverbunde können sich ebenfalls bewerben. Die Teilnahme ist vollständig kostenfrei.',
|
|
||||||
facts: [
|
|
||||||
{ num: '01', label: '0 EUR', desc: 'Teilnahmegebühr' },
|
|
||||||
{ num: '02', label: 'Schnell', desc: 'Ca. 30–45 Minuten' },
|
|
||||||
{ num: '03', label: 'Mehrstufig', desc: 'Transparenter Prozess' },
|
|
||||||
],
|
|
||||||
formEyebrow: 'Bewerbung 2026',
|
|
||||||
formTitle: 'Kostenlos bewerben',
|
|
||||||
uploadCta: { label: 'PDF hochladen', url: '/formular-hochladen' },
|
|
||||||
uploadPrompt: 'Sie haben Ihre Unterlagen bereits als PDF vorbereitet? Laden Sie sie direkt hoch, statt das Formular auszufüllen.',
|
|
||||||
uploadFooterCta: { label: 'Formular hochladen →', url: '/formular-hochladen' },
|
|
||||||
},
|
|
||||||
form: {
|
form: {
|
||||||
stepCompleteLabel: '✓',
|
stepCompleteLabel: '✓',
|
||||||
backLabel: 'Zurück',
|
backLabel: 'Zurück',
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export const pressIndexContent = {
|
|||||||
eyebrow: 'Termine 2026',
|
eyebrow: 'Termine 2026',
|
||||||
heading: 'EVENTS RUND UM DEN PREIS.',
|
heading: 'EVENTS RUND UM DEN PREIS.',
|
||||||
description: 'Von der Jurysitzung bis zur festlichen Gala – alle Veranstaltungen auf einen Blick.',
|
description: 'Von der Jurysitzung bis zur festlichen Gala – alle Veranstaltungen auf einen Blick.',
|
||||||
detailLabel: 'Details',
|
|
||||||
defaultStatusLabel: 'Geplant',
|
defaultStatusLabel: 'Geplant',
|
||||||
openStatusLabel: 'Anmeldung offen',
|
openStatusLabel: 'Anmeldung offen',
|
||||||
missingTitleLabel: 'Event',
|
missingTitleLabel: 'Event',
|
||||||
@@ -29,7 +28,6 @@ export const pressIndexContent = {
|
|||||||
eyebrow: 'Newsroom',
|
eyebrow: 'Newsroom',
|
||||||
heading: 'NACHRICHTEN & EINBLICKE.',
|
heading: 'NACHRICHTEN & EINBLICKE.',
|
||||||
description: 'Berichte, Hintergründe und Einblicke rund um den bayerischen Mittelstand und den BMP.',
|
description: 'Berichte, Hintergründe und Einblicke rund um den bayerischen Mittelstand und den BMP.',
|
||||||
readMoreLabel: 'Weiterlesen',
|
|
||||||
missingTitleLabel: 'Presse',
|
missingTitleLabel: 'Presse',
|
||||||
missingCategoryLabel: 'Presse',
|
missingCategoryLabel: 'Presse',
|
||||||
collectionFallbackImageFilename: 'gala-saal-overview.jpg',
|
collectionFallbackImageFilename: 'gala-saal-overview.jpg',
|
||||||
@@ -38,23 +36,8 @@ export const pressIndexContent = {
|
|||||||
eyebrow: 'Pressekontakt & Material',
|
eyebrow: 'Pressekontakt & Material',
|
||||||
heading: 'PRESSE-MATERIAL & KONTAKT.',
|
heading: 'PRESSE-MATERIAL & KONTAKT.',
|
||||||
description: 'Laden Sie unser offizielles Material herunter oder kontaktieren Sie direkt unser Presseteam für individuelle Anfragen.',
|
description: 'Laden Sie unser offizielles Material herunter oder kontaktieren Sie direkt unser Presseteam für individuelle Anfragen.',
|
||||||
kitLabel: 'Download Presse-Kit',
|
|
||||||
kitMeta: 'Print_BMP.zip – 1,5 MB',
|
|
||||||
kitUrl: '/Print_BMP.zip',
|
|
||||||
kitDownloadName: 'Print_BMP.zip',
|
|
||||||
contactEyebrow: 'Pressekontakt',
|
contactEyebrow: 'Pressekontakt',
|
||||||
contactName: 'Tanja Meier',
|
contactName: 'Tanja Meier',
|
||||||
contactLines: 'presse@bmp-bayern.de\n+49 89 123 456 99',
|
contactLines: 'presse@bmp-bayern.de\n+49 89 123 456 99',
|
||||||
},
|
},
|
||||||
accreditation: {
|
|
||||||
eyebrow: 'Akkreditierung',
|
|
||||||
heading: 'AKKREDITIERUNG ANFRAGEN.',
|
|
||||||
description: 'Melden Sie sich für unsere Presse-Verteiler an oder fordern Sie eine Akkreditierung für die Gala-Verleihung an.',
|
|
||||||
mediumLabel: 'Medium / Redaktion *',
|
|
||||||
nameLabel: 'Ihr Name *',
|
|
||||||
emailLabel: 'E-Mail-Adresse *',
|
|
||||||
submitLabel: 'Akkreditierung anfragen',
|
|
||||||
successHeading: 'Anfrage eingegangen',
|
|
||||||
successMessage: 'Vielen Dank für Ihre Akkreditierungsanfrage. Unser Presseteam meldet sich zeitnah bei Ihnen.',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|||||||
133
tests/int/payload-field-inventory.int.spec.ts
Normal file
133
tests/int/payload-field-inventory.int.spec.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import type { Field } from 'payload'
|
||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { ApplicationForm } from '@/ApplicationForm/config'
|
||||||
|
import { ApplicationPhase } from '@/ApplicationPhase/config'
|
||||||
|
import { Footer } from '@/Footer/config'
|
||||||
|
import { Header } from '@/Header/config'
|
||||||
|
import { NewsletterForm } from '@/NewsletterForm/config'
|
||||||
|
import { SiteSettings } from '@/SiteSettings/config'
|
||||||
|
import { aboutFields } from '@/collections/Pages/aboutFields'
|
||||||
|
import { homeFields } from '@/collections/Pages/homeFields'
|
||||||
|
import { netzwerkFields } from '@/collections/Pages/netzwerkFields'
|
||||||
|
import { participationFields } from '@/collections/Pages/participationFields'
|
||||||
|
import { pressIndexFields } from '@/collections/Pages/pressIndexFields'
|
||||||
|
|
||||||
|
function fieldPaths(fields: Field[], prefix = ''): string[] {
|
||||||
|
return fields.flatMap((field) => {
|
||||||
|
if (!('name' in field) || typeof field.name !== 'string') {
|
||||||
|
return 'fields' in field && Array.isArray(field.fields) ? fieldPaths(field.fields, prefix) : []
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = prefix ? `${prefix}.${field.name}` : field.name
|
||||||
|
const children = 'fields' in field && Array.isArray(field.fields) ? fieldPaths(field.fields, path) : []
|
||||||
|
return [path, ...children]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function findField(fields: Field[], path: string): Field | undefined {
|
||||||
|
const [name, ...rest] = path.split('.')
|
||||||
|
const field = fields.find((candidate) => 'name' in candidate && candidate.name === name)
|
||||||
|
if (!field || rest.length === 0) return field
|
||||||
|
if (!('fields' in field) || !Array.isArray(field.fields)) return undefined
|
||||||
|
return findField(field.fields, rest.join('.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Payload field inventory', () => {
|
||||||
|
it('keeps obsolete page controls out of the schema', () => {
|
||||||
|
const paths = [
|
||||||
|
...fieldPaths(homeFields),
|
||||||
|
...fieldPaths(participationFields),
|
||||||
|
...fieldPaths(aboutFields),
|
||||||
|
...fieldPaths(pressIndexFields),
|
||||||
|
...fieldPaths(netzwerkFields),
|
||||||
|
]
|
||||||
|
|
||||||
|
expect(paths).not.toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
'home.application',
|
||||||
|
'home.form',
|
||||||
|
'participation.applicationForm',
|
||||||
|
'participation.form',
|
||||||
|
'participation.eligibility.secondaryCta',
|
||||||
|
'participation.eligibility.nudgeCta',
|
||||||
|
'participation.applicationWays.recommendedLabel',
|
||||||
|
'participation.applicationWays.fallbackCta',
|
||||||
|
'participation.applicationWays.ways.featured',
|
||||||
|
'participation.applicationWays.ways.cta',
|
||||||
|
'about.highlights',
|
||||||
|
'pressIndex.events.detailLabel',
|
||||||
|
'pressIndex.news.readMoreLabel',
|
||||||
|
'pressIndex.downloads.kitLabel',
|
||||||
|
'pressIndex.downloads.kitMeta',
|
||||||
|
'pressIndex.downloads.kitFile',
|
||||||
|
'pressIndex.downloads.kitUrl',
|
||||||
|
'pressIndex.downloads.kitDownloadName',
|
||||||
|
'pressIndex.accreditation',
|
||||||
|
'netzwerk.jury.note',
|
||||||
|
'netzwerk.partners.premiumLabel',
|
||||||
|
'netzwerk.partners.defaultLogoColor',
|
||||||
|
'netzwerk.sponsoring.footnotePrefix',
|
||||||
|
'netzwerk.sponsoring.footnoteLabel',
|
||||||
|
'netzwerk.sponsoring.footnoteUrl',
|
||||||
|
'netzwerk.sponsoring.formEyebrow',
|
||||||
|
'netzwerk.sponsoringForm.packages',
|
||||||
|
'netzwerk.sponsoringForm.step1Eyebrow',
|
||||||
|
'netzwerk.sponsoringForm.step1Heading',
|
||||||
|
'netzwerk.sponsoringForm.requiredPackageError',
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('retains rendered page controls and authoritative relationships', () => {
|
||||||
|
const paths = [
|
||||||
|
...fieldPaths(homeFields),
|
||||||
|
...fieldPaths(participationFields),
|
||||||
|
...fieldPaths(aboutFields),
|
||||||
|
...fieldPaths(pressIndexFields),
|
||||||
|
...fieldPaths(netzwerkFields),
|
||||||
|
]
|
||||||
|
|
||||||
|
expect(paths).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
'home.hero',
|
||||||
|
'home.winners.featured',
|
||||||
|
'home.videoModal.video',
|
||||||
|
'participation.process.steps',
|
||||||
|
'participation.awardsGrid.cards',
|
||||||
|
'about.testimonials.items',
|
||||||
|
'pressIndex.events.statusLabels',
|
||||||
|
'pressIndex.downloads.contactName',
|
||||||
|
'netzwerk.jury.members',
|
||||||
|
'netzwerk.partners.tiers',
|
||||||
|
'netzwerk.partners.modal.websiteLabel',
|
||||||
|
'netzwerk.sponsoringForm.steps',
|
||||||
|
'netzwerk.sponsoringForm.requiredFieldError',
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps shared multi-page and site-wide content in Globals', () => {
|
||||||
|
const globals = [ApplicationForm, ApplicationPhase, NewsletterForm, Header, Footer, SiteSettings]
|
||||||
|
const slugs = globals.map((global) => global.slug)
|
||||||
|
|
||||||
|
expect(slugs).toEqual([
|
||||||
|
'application-form',
|
||||||
|
'application-phase',
|
||||||
|
'newsletter-form',
|
||||||
|
'header',
|
||||||
|
'footer',
|
||||||
|
'site-settings',
|
||||||
|
])
|
||||||
|
expect(fieldPaths(ApplicationForm.fields)).toEqual(expect.arrayContaining(['section', 'form']))
|
||||||
|
expect(fieldPaths(ApplicationPhase.fields)).toEqual(expect.arrayContaining(['activePhase', 'phases']))
|
||||||
|
expect(fieldPaths(NewsletterForm.fields)).toEqual(expect.arrayContaining(['evaluation', 'completed']))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('exposes exactly two editable sponsoring step labels', () => {
|
||||||
|
expect(findField(netzwerkFields, 'netzwerk.sponsoringForm.steps')).toMatchObject({
|
||||||
|
minRows: 2,
|
||||||
|
maxRows: 2,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user