diff --git a/src/collections/ContactSubmissions.ts b/src/collections/ContactSubmissions.ts new file mode 100644 index 0000000..47cfe7b --- /dev/null +++ b/src/collections/ContactSubmissions.ts @@ -0,0 +1,125 @@ +import type { CollectionConfig, PayloadRequest } from 'payload' + +import { authenticated } from '@/access/authenticated' + +const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + +type ContactSubmissionBody = { + subject?: unknown + name?: unknown + email?: unknown + message?: unknown + website?: unknown +} + +const normalizedText = (value: unknown) => typeof value === 'string' ? value.trim() : '' + +async function submitContactForm(req: PayloadRequest) { + let body: ContactSubmissionBody + + try { + if (!req.json) throw new Error('Request body is unavailable.') + body = (await req.json()) as ContactSubmissionBody + } catch { + return Response.json({ success: false }, { status: 400 }) + } + + if (normalizedText(body.website)) return Response.json({ success: true }) + + const subject = normalizedText(body.subject) + const name = normalizedText(body.name) + const email = normalizedText(body.email).toLowerCase() + const message = normalizedText(body.message) + + if ( + !subject || subject.length > 120 || + !name || name.length > 200 || + !email || email.length > 254 || !emailPattern.test(email) || + !message || message.length > 10_000 + ) { + return Response.json({ success: false }, { status: 400 }) + } + + try { + await req.payload.create({ + collection: 'contact-submissions', + data: { + subject, + name, + email, + message, + submittedAt: new Date().toISOString(), + }, + }) + + return Response.json({ success: true }) + } catch (error) { + req.payload.logger.error({ err: error, msg: 'Contact submission could not be stored.' }) + return Response.json({ success: false }, { status: 500 }) + } +} + +export const ContactSubmissions: CollectionConfig<'contact-submissions'> = { + slug: 'contact-submissions', + labels: { + singular: 'Kontaktanfrage', + plural: 'Kontaktanfragen', + }, + access: { + create: authenticated, + delete: authenticated, + read: authenticated, + update: authenticated, + }, + admin: { + defaultColumns: ['submittedAt', 'subject', 'name', 'email'], + group: 'Formulare', + useAsTitle: 'email', + }, + endpoints: [ + { + path: '/submit', + method: 'post', + handler: submitContactForm, + }, + ], + fields: [ + { + name: 'subject', + label: 'Betreff', + type: 'text', + required: true, + }, + { + name: 'name', + label: 'Name', + type: 'text', + required: true, + }, + { + name: 'email', + label: 'E-Mail-Adresse', + type: 'email', + required: true, + index: true, + }, + { + name: 'message', + label: 'Nachricht', + type: 'textarea', + required: true, + }, + { + name: 'submittedAt', + label: 'Eingegangen am', + type: 'date', + required: true, + defaultValue: () => new Date().toISOString(), + index: true, + admin: { + date: { displayFormat: 'dd.MM.yyyy HH:mm' }, + readOnly: true, + }, + }, + ], +} diff --git a/src/collections/NewsletterSubscriptions.ts b/src/collections/NewsletterSubscriptions.ts index 4953378..6012956 100644 --- a/src/collections/NewsletterSubscriptions.ts +++ b/src/collections/NewsletterSubscriptions.ts @@ -4,8 +4,8 @@ import { authenticated } from '@/access/authenticated' import type { NewsletterPhase, NewsletterSource } from '@/spa/newsletterForm' const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ -const sources = new Set(['homepage', 'participation']) -const phases = new Set(['evaluation', 'completed']) +const sources = new Set(['homepage', 'participation', 'network']) +const phases = new Set(['open', 'evaluation', 'completed']) type SubscribeBody = { email?: unknown @@ -118,6 +118,7 @@ export const NewsletterSubscriptions: CollectionConfig<'newsletter-subscriptions options: [ { label: 'Startseite', value: 'homepage' }, { label: 'Teilnahme-Seite', value: 'participation' }, + { label: 'Netzwerk / Sponsoring', value: 'network' }, ], }, { @@ -126,6 +127,7 @@ export const NewsletterSubscriptions: CollectionConfig<'newsletter-subscriptions type: 'select', required: true, options: [ + { label: 'Bewerbungsphase offen', value: 'open' }, { label: 'Jury / Auswertung', value: 'evaluation' }, { label: 'Preisverleihung abgeschlossen', value: 'completed' }, ], diff --git a/src/migrations/20260713_194500_contact_submissions.ts b/src/migrations/20260713_194500_contact_submissions.ts new file mode 100644 index 0000000..3507a7e --- /dev/null +++ b/src/migrations/20260713_194500_contact_submissions.ts @@ -0,0 +1,118 @@ +import { type MigrateDownArgs, type MigrateUpArgs, sql } from '@payloadcms/db-sqlite' + +type MigrationDB = MigrateUpArgs['db'] + +const lockedRelationColumns = [ + 'id', + 'order', + 'parent_id', + 'path', + 'pages_id', + 'preistraeger_id', + 'jury_mitglieder_id', + 'partners_id', + 'posts_id', + 'events_id', + 'newsletter_subscriptions_id', + 'media_id', + 'users_id', + 'payload_folders_id', +] 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 = '${tableName.replace(/'/g, "''")}'`), + )) as Array<{ name: string }> + + return rows.length > 0 +} + +async function rebuildLockedDocumentRelations(db: MigrationDB, includeContactSubmissions: boolean) { + if (!(await tableExists(db, 'payload_locked_documents_rels'))) return + + const contactColumn = includeContactSubmissions ? ',\n `contact_submissions_id` integer' : '' + const contactForeignKey = includeContactSubmissions + ? ',\n FOREIGN KEY (`contact_submissions_id`) REFERENCES `contact_submissions`(`id`) ON UPDATE no action ON DELETE cascade' + : '' + + await db.run(sql.raw('DROP TABLE IF EXISTS `__new_payload_locked_documents_rels`;')) + await db.run(sql.raw(` + CREATE TABLE "__new_payload_locked_documents_rels" ( + "id" integer PRIMARY KEY NOT NULL, + "order" integer, + "parent_id" integer NOT NULL, + "path" text NOT NULL, + "pages_id" integer, + "preistraeger_id" integer, + "jury_mitglieder_id" integer, + "partners_id" integer, + "posts_id" integer, + "events_id" integer, + "newsletter_subscriptions_id" integer${contactColumn}, + "media_id" integer, + "users_id" integer, + "payload_folders_id" integer, + FOREIGN KEY ("parent_id") REFERENCES "payload_locked_documents"("id") ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ("pages_id") REFERENCES "pages"("id") ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ("preistraeger_id") REFERENCES "preistraeger"("id") ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ("jury_mitglieder_id") REFERENCES "jury_mitglieder"("id") ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ("partners_id") REFERENCES "partners"("id") ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ("posts_id") REFERENCES "posts"("id") ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ("events_id") REFERENCES "events"("id") ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ("newsletter_subscriptions_id") REFERENCES "newsletter_subscriptions"("id") ON UPDATE no action ON DELETE cascade${contactForeignKey}, + FOREIGN KEY ("media_id") REFERENCES "media"("id") ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ("users_id") REFERENCES "users"("id") ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ("payload_folders_id") REFERENCES "payload_folders"("id") ON UPDATE no action ON DELETE cascade + ); + `)) + + const columns = lockedRelationColumns.map((column) => `"${column}"`).join(', ') + await db.run(sql.raw(`INSERT INTO "__new_payload_locked_documents_rels" (${columns}) SELECT ${columns} FROM "payload_locked_documents_rels";`)) + await db.run(sql.raw('DROP TABLE `payload_locked_documents_rels`;')) + await db.run(sql.raw('ALTER TABLE `__new_payload_locked_documents_rels` RENAME TO `payload_locked_documents_rels`;')) + + for (const [suffix, column] of [ + ['order', 'order'], + ['parent', 'parent_id'], + ['path', 'path'], + ['pages_id', 'pages_id'], + ['preistraeger_id', 'preistraeger_id'], + ['jury_mitglieder_id', 'jury_mitglieder_id'], + ['partners_id', 'partners_id'], + ['posts_id', 'posts_id'], + ['events_id', 'events_id'], + ['newsletter_subscriptions_i', 'newsletter_subscriptions_id'], + ...(includeContactSubmissions ? [['contact_submissions_id', 'contact_submissions_id']] : []), + ['media_id', 'media_id'], + ['users_id', 'users_id'], + ['payload_folders_id', 'payload_folders_id'], + ] as string[][]) { + await db.run(sql.raw(`CREATE INDEX "payload_locked_documents_rels_${suffix}_idx" ON "payload_locked_documents_rels" ("${column}");`)) + } +} + +export async function up({ db, payload: _payload, req: _req }: MigrateUpArgs): Promise { + await db.run(sql.raw(` + CREATE TABLE IF NOT EXISTS "contact_submissions" ( + "id" integer PRIMARY KEY NOT NULL, + "subject" text NOT NULL, + "name" text NOT NULL, + "email" text NOT NULL, + "message" text NOT NULL, + "submitted_at" text NOT NULL, + "updated_at" text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL, + "created_at" text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL + ); + `)) + await db.run(sql.raw('CREATE INDEX IF NOT EXISTS `contact_submissions_email_idx` ON `contact_submissions` (`email`);')) + await db.run(sql.raw('CREATE INDEX IF NOT EXISTS `contact_submissions_submitted_at_idx` ON `contact_submissions` (`submitted_at`);')) + await db.run(sql.raw('CREATE INDEX IF NOT EXISTS `contact_submissions_updated_at_idx` ON `contact_submissions` (`updated_at`);')) + await db.run(sql.raw('CREATE INDEX IF NOT EXISTS `contact_submissions_created_at_idx` ON `contact_submissions` (`created_at`);')) + + await rebuildLockedDocumentRelations(db, true) +} + +export async function down({ db, payload: _payload, req: _req }: MigrateDownArgs): Promise { + await rebuildLockedDocumentRelations(db, false) + await db.run(sql.raw('DROP TABLE IF EXISTS `contact_submissions`;')) +} diff --git a/src/migrations/index.ts b/src/migrations/index.ts index 20072ee..e1c03ef 100644 --- a/src/migrations/index.ts +++ b/src/migrations/index.ts @@ -12,6 +12,7 @@ import * as migration_20260712_120000_partner_media_only from './20260712_120000 import * as migration_20260713_133000_newsletter_form_and_subscriptions from './20260713_133000_newsletter_form_and_subscriptions'; import * as migration_20260713_155500_shared_application_form from './20260713_155500_shared_application_form'; import * as migration_20260713_163000_participation_supporting_awards_copy from './20260713_163000_participation_supporting_awards_copy'; +import * as migration_20260713_194500_contact_submissions from './20260713_194500_contact_submissions'; export const migrations = [ { @@ -84,4 +85,9 @@ export const migrations = [ down: migration_20260713_163000_participation_supporting_awards_copy.down, name: '20260713_163000_participation_supporting_awards_copy', }, + { + up: migration_20260713_194500_contact_submissions.up, + down: migration_20260713_194500_contact_submissions.down, + name: '20260713_194500_contact_submissions', + }, ]; diff --git a/src/payload-types.ts b/src/payload-types.ts index 9374195..03b2959 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -73,6 +73,7 @@ export interface Config { partners: Partner; posts: Post; events: Event; + 'contact-submissions': ContactSubmission; 'newsletter-subscriptions': NewsletterSubscription; media: Media; users: User; @@ -95,6 +96,7 @@ export interface Config { partners: PartnersSelect | PartnersSelect; posts: PostsSelect | PostsSelect; events: EventsSelect | EventsSelect; + 'contact-submissions': ContactSubmissionsSelect | ContactSubmissionsSelect; 'newsletter-subscriptions': NewsletterSubscriptionsSelect | NewsletterSubscriptionsSelect; media: MediaSelect | MediaSelect; users: UsersSelect | UsersSelect; @@ -2814,6 +2816,20 @@ export interface Event { createdAt: string; _status?: ('draft' | 'published') | null; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "contact-submissions". + */ +export interface ContactSubmission { + id: number; + subject: string; + name: string; + email: string; + message: string; + submittedAt: string; + updatedAt: string; + createdAt: string; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "newsletter-subscriptions". @@ -2821,8 +2837,8 @@ export interface Event { export interface NewsletterSubscription { id: number; email: string; - source: 'homepage' | 'participation'; - phase: 'evaluation' | 'completed'; + source: 'homepage' | 'participation' | 'network'; + phase: 'open' | 'evaluation' | 'completed'; lastSubmittedAt: string; updatedAt: string; createdAt: string; @@ -2967,6 +2983,10 @@ export interface PayloadLockedDocument { relationTo: 'events'; value: number | Event; } | null) + | ({ + relationTo: 'contact-submissions'; + value: number | ContactSubmission; + } | null) | ({ relationTo: 'newsletter-subscriptions'; value: number | NewsletterSubscription; @@ -5120,6 +5140,19 @@ export interface EventsSelect { createdAt?: T; _status?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "contact-submissions_select". + */ +export interface ContactSubmissionsSelect { + subject?: T; + name?: T; + email?: T; + message?: T; + submittedAt?: T; + updatedAt?: T; + createdAt?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "newsletter-subscriptions_select". diff --git a/src/payload.config.ts b/src/payload.config.ts index 7257647..9bbccaf 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -5,6 +5,7 @@ import { buildConfig, PayloadRequest } from 'payload' import { fileURLToPath } from 'url' import { Events } from './collections/Events' +import { ContactSubmissions } from './collections/ContactSubmissions' import { JuryMitglieder } from './collections/JuryMitglieder' import { Media } from './collections/Media' import { NewsletterSubscriptions } from './collections/NewsletterSubscriptions' @@ -70,7 +71,7 @@ export default buildConfig({ url: process.env.DATABASE_URL || '', }, }), - collections: [Pages, Preistraeger, JuryMitglieder, Partners, Posts, Events, NewsletterSubscriptions, Media, Users], + collections: [Pages, Preistraeger, JuryMitglieder, Partners, Posts, Events, ContactSubmissions, NewsletterSubscriptions, Media, Users], cors: [getServerSideURL()].filter(Boolean), globals: [Header, Footer, SiteSettings, ApplicationPhase, ApplicationForm, NewsletterForm], plugins, diff --git a/src/spa/components/forms/KontaktForm.tsx b/src/spa/components/forms/KontaktForm.tsx index d812495..bc72258 100644 --- a/src/spa/components/forms/KontaktForm.tsx +++ b/src/spa/components/forms/KontaktForm.tsx @@ -5,6 +5,7 @@ import { useIsMobile } from '@/spa/hooks/useIsMobile'; import { contactFormContent } from '@/spa/contactFormContent'; const FF = '"IBM Plex Sans", sans-serif'; +const SUBMISSION_ERROR = 'Die Nachricht konnte nicht gespeichert werden. Bitte versuchen Sie es später erneut.'; const DARK = { border: 'rgba(255,255,255,0.12)', @@ -201,12 +202,16 @@ export default function KontaktForm({ theme = 'dark', content }: { theme?: 'dark const [step, setStep] = useState(0); const [dir, setDir] = useState(1); const [submitted, setSubmitted] = useState(false); + const [submitting, setSubmitting] = useState(false); + const [submissionError, setSubmissionError] = useState(''); + const [website, setWebsite] = useState(''); const [data, setData] = useState({ betreff: 'allgemein', name: '', email: '', nachricht: '' }); const [errors, setErrors] = useState>>({}); const set = (k: keyof FormData, v: string) => { setData(d => ({ ...d, [k]: v })); setErrors(e => { const n = { ...e }; delete n[k]; return n; }); + setSubmissionError(''); }; const validate = () => { @@ -223,7 +228,33 @@ export default function KontaktForm({ theme = 'dark', content }: { theme?: 'dark const next = () => { if (validate()) { setDir(1); setStep(s => s + 1); } }; const prev = () => { setDir(-1); setStep(s => s - 1); }; - const submit = () => { if (validate()) setSubmitted(true); }; + const submit = async () => { + if (!validate() || submitting) return; + + setSubmissionError(''); + setSubmitting(true); + + try { + const response = await fetch('/api/contact-submissions/submit', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + subject: copy.subjects.find((subject) => subject.value === data.betreff)?.label || data.betreff, + name: data.name, + email: data.email, + message: data.nachricht, + website, + }), + }); + + if (!response.ok) throw new Error(`Contact submission failed with ${response.status}`); + setSubmitted(true); + } catch { + setSubmissionError(SUBMISSION_ERROR); + } finally { + setSubmitting(false); + } + }; if (submitted) { return ( @@ -246,6 +277,19 @@ export default function KontaktForm({ theme = 'dark', content }: { theme?: 'dark return (
+ + {/* Progress */}
@@ -347,10 +391,10 @@ export default function KontaktForm({ theme = 'dark', content }: { theme?: 'dark {fallbackText(copy.navigation.next, contactFormContent.navigation.next)} ) : ( -
)} + {submissionError &&

{submissionError}

}
); } diff --git a/src/spa/components/forms/SponsoringForm.tsx b/src/spa/components/forms/SponsoringForm.tsx index 3eb6e4c..822b238 100644 --- a/src/spa/components/forms/SponsoringForm.tsx +++ b/src/spa/components/forms/SponsoringForm.tsx @@ -1,8 +1,10 @@ import React, { useState } from 'react'; import { AnimatePresence, motion } from 'framer-motion'; -import { ArrowRight, ArrowLeft, Check, Layers, Building2, Mail } from 'lucide-react'; +import { ArrowRight, ArrowLeft, Check, Building2, Mail } from 'lucide-react'; import { useIsMobile } from '@/spa/hooks/useIsMobile'; +import { useApplicationPhase } from '@/spa/cmsRoute'; import { netzwerkContent } from '@/spa/netzwerkContent'; +import { newsletterPhaseForApplicationPhase } from '@/spa/newsletterForm'; const FF = '"IBM Plex Sans", sans-serif'; @@ -87,23 +89,21 @@ const GOLD = { type C = typeof DARK; type FormData = { - paket: string; unternehmen: string; branche: string; kontakt: string; email: string; }; -const STEP_ICONS = [Layers, Building2, Mail]; +const STEP_ICONS = [Building2, Mail]; type SponsoringFormContent = Partial; -type PackageOption = (typeof netzwerkContent.sponsoringForm.packages)[number]; type StepOption = (typeof netzwerkContent.sponsoringForm.steps)[number]; const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback; const fallbackArray = (value: unknown, fallback: T[]) => Array.isArray(value) && value.length ? value as T[] : fallback; -const isPremiumPackage = (pkg: PackageOption) => - [pkg.value, pkg.title].some((value) => typeof value === 'string' && value.toLowerCase().includes('premium')); +const numberedStepLabel = (value: unknown, fallback: string, step: number) => + fallbackText(value, fallback).replace(/^Schritt\s+\d+/i, `Schritt ${step}`); const variants = { enter: (dir: number) => ({ x: dir > 0 ? 40 : -40, opacity: 0 }), @@ -142,51 +142,31 @@ function StyledInput({ c, ...props }: React.InputHTMLAttributes void }) { - return ( - - ); -} - export default function SponsoringForm({ theme = 'dark', content }: { theme?: 'dark' | 'gold'; content?: SponsoringFormContent }) { const c = theme === 'gold' ? GOLD : DARK; const isMobile = useIsMobile(); + const applicationPhase = useApplicationPhase(); const copy = { ...netzwerkContent.sponsoringForm, ...(content || {}) }; - const steps = fallbackArray(copy.steps, netzwerkContent.sponsoringForm.steps); - const packages = fallbackArray(copy.packages, netzwerkContent.sponsoringForm.packages).filter((pkg) => !isPremiumPackage(pkg)); + const configuredSteps = fallbackArray(copy.steps, netzwerkContent.sponsoringForm.steps); + const steps = configuredSteps.slice(-2); const [step, setStep] = useState(0); const [dir, setDir] = useState(1); const [submitted, setSubmitted] = useState(false); - const [data, setData] = useState({ paket: '', unternehmen: '', branche: '', kontakt: '', email: '' }); + const [submitting, setSubmitting] = useState(false); + const [submissionError, setSubmissionError] = useState(''); + const [data, setData] = useState({ unternehmen: '', branche: '', kontakt: '', email: '' }); const [errors, setErrors] = useState>>({}); const set = (k: keyof FormData, v: string) => { setData(d => ({ ...d, [k]: v })); setErrors(e => { const n = { ...e }; delete n[k]; return n; }); + setSubmissionError(''); }; const validate = () => { const e: typeof errors = {}; - if (step === 0 && !data.paket) e.paket = fallbackText(copy.requiredPackageError, netzwerkContent.sponsoringForm.requiredPackageError); - if (step === 1 && !data.unternehmen) e.unternehmen = fallbackText(copy.requiredFieldError, netzwerkContent.sponsoringForm.requiredFieldError); - if (step === 2) { + if (step === 0 && !data.unternehmen) e.unternehmen = fallbackText(copy.requiredFieldError, netzwerkContent.sponsoringForm.requiredFieldError); + if (step === 1) { if (!data.kontakt) e.kontakt = fallbackText(copy.requiredFieldError, netzwerkContent.sponsoringForm.requiredFieldError); if (!data.email) e.email = fallbackText(copy.requiredFieldError, netzwerkContent.sponsoringForm.requiredFieldError); else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) e.email = fallbackText(copy.invalidEmailError, netzwerkContent.sponsoringForm.invalidEmailError); @@ -197,7 +177,32 @@ export default function SponsoringForm({ theme = 'dark', content }: { theme?: 'd const next = () => { if (validate()) { setDir(1); setStep(s => s + 1); } }; const prev = () => { setDir(-1); setStep(s => s - 1); }; - const submit = () => { if (validate()) setSubmitted(true); }; + const submit = async () => { + if (!validate() || submitting) return; + + setSubmissionError(''); + setSubmitting(true); + + try { + const response = await fetch('/api/newsletter-subscriptions/subscribe', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + email: data.email.trim(), + phase: newsletterPhaseForApplicationPhase(applicationPhase?.activePhase), + source: 'network', + website: '', + }), + }); + + if (!response.ok) throw new Error(`Sponsoring request failed with ${response.status}`); + setSubmitted(true); + } catch { + setSubmissionError('Die Anfrage konnte nicht gespeichert werden. Bitte versuchen Sie es später erneut.'); + } finally { + setSubmitting(false); + } + }; const progress = (step / (steps.length - 1)) * 100; @@ -227,7 +232,7 @@ export default function SponsoringForm({ theme = 'dark', content }: { theme?: 'd {/* Step tabs */}
{steps.map((s, i) => { - const Icon = STEP_ICONS[i] || Layers; + const Icon = STEP_ICONS[i] || Building2; const done = i < step, active = i === step; return (
- {done ? fallbackText(copy.doneLabel, netzwerkContent.sponsoringForm.doneLabel) : fallbackText(s.label, netzwerkContent.sponsoringForm.steps[i]?.label || '')} + {done ? fallbackText(copy.doneLabel, netzwerkContent.sponsoringForm.doneLabel) : fallbackText(s.label, '')}
); })} @@ -254,24 +259,9 @@ export default function SponsoringForm({ theme = 'dark', content }: { theme?: 'd transition={{ duration: 0.22, ease: 'easeInOut' }}> {step === 0 && ( -
-
-

{fallbackText(copy.step1Eyebrow, netzwerkContent.sponsoringForm.step1Eyebrow)}

-

{fallbackText(copy.step1Heading, netzwerkContent.sponsoringForm.step1Heading)}

-
-
- {packages.map(pkg => ( - set('paket', pkg.value)} /> - ))} -
- {errors.paket &&

{errors.paket}

} -
- )} - - {step === 1 && (
-

{fallbackText(copy.step2Eyebrow, netzwerkContent.sponsoringForm.step2Eyebrow)}

+

{numberedStepLabel(copy.step2Eyebrow, netzwerkContent.sponsoringForm.step2Eyebrow, 1)}

{fallbackText(copy.step2Heading, netzwerkContent.sponsoringForm.step2Heading)}

@@ -283,10 +273,10 @@ export default function SponsoringForm({ theme = 'dark', content }: { theme?: 'd
)} - {step === 2 && ( + {step === 1 && (
-

{fallbackText(copy.step3Eyebrow, netzwerkContent.sponsoringForm.step3Eyebrow)}

+

{numberedStepLabel(copy.step3Eyebrow, netzwerkContent.sponsoringForm.step3Eyebrow, 2)}

{fallbackText(copy.step3Heading, netzwerkContent.sponsoringForm.step3Heading)}

@@ -331,9 +321,9 @@ export default function SponsoringForm({ theme = 'dark', content }: { theme?: 'd {fallbackText(copy.nextLabel, netzwerkContent.sponsoringForm.nextLabel)} ) : ( -