mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 19:44:12 +00:00
feat: multi-tenant contact form refactoring
- Add forms + form-submissions to multiTenantPlugin with tenant scoping - Inject tenant field into forms via formOverrides - Reorder plugins: formBuilderPlugin before multiTenantPlugin (fixes warning) - Refactor ContactFormBlock: form relationship replaces hardcoded recipientEmail - Add setSubmissionTenant hook to auto-copy tenant from form to submission - Add tenant field (read-only) to FormSubmissionsOverrides - Migration: tenant_id on forms/form_submissions, form_id on contact block Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
130ab46ffb
commit
5e223cd7fb
7 changed files with 408 additions and 191 deletions
|
|
@ -7,6 +7,16 @@ export const ContactFormBlock: Block = {
|
|||
plural: 'Kontaktformulare',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'form',
|
||||
type: 'relationship',
|
||||
relationTo: 'forms',
|
||||
required: true,
|
||||
label: 'Formular',
|
||||
admin: {
|
||||
description: 'Wählen Sie ein Formular aus der Formulare-Sammlung',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'headline',
|
||||
type: 'text',
|
||||
|
|
@ -21,28 +31,44 @@ export const ContactFormBlock: Block = {
|
|||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'recipientEmail',
|
||||
type: 'email',
|
||||
defaultValue: 'info@porwoll.de',
|
||||
label: 'Empfänger E-Mail',
|
||||
name: 'successMessage',
|
||||
type: 'textarea',
|
||||
defaultValue: 'Vielen Dank! Wir melden uns schnellstmöglich.',
|
||||
label: 'Erfolgsmeldung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'showContactInfo',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Kontaktinformationen anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showPhone',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Telefon anzeigen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showContactInfo,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showAddress',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Adresse anzeigen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showContactInfo,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showSocials',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Social Media anzeigen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showContactInfo,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,17 @@ export const formSubmissionOverrides: Partial<CollectionConfig> = {
|
|||
plural: 'Formular-Einsendungen',
|
||||
},
|
||||
fields: [
|
||||
// Tenant (automatisch vom Formular übernommen via setSubmissionTenant Hook)
|
||||
{
|
||||
name: 'tenant',
|
||||
type: 'relationship',
|
||||
relationTo: 'tenants',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
readOnly: true,
|
||||
description: 'Wird automatisch vom Formular übernommen',
|
||||
},
|
||||
},
|
||||
// Status-Workflow
|
||||
{
|
||||
name: 'status',
|
||||
|
|
|
|||
39
src/hooks/setSubmissionTenant.ts
Normal file
39
src/hooks/setSubmissionTenant.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import type { CollectionBeforeChangeHook } from 'payload'
|
||||
|
||||
interface FormWithTenant {
|
||||
id: number | string
|
||||
tenant?: { id: number } | number | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook: Kopiert den Tenant vom übergeordneten Formular auf die Einsendung.
|
||||
* Wird als beforeChange auf form-submissions ausgeführt.
|
||||
*/
|
||||
export const setSubmissionTenant: CollectionBeforeChangeHook = async ({
|
||||
data,
|
||||
req,
|
||||
operation,
|
||||
}) => {
|
||||
// Nur bei neuen Einsendungen den Tenant setzen
|
||||
if (operation !== 'create') return data
|
||||
|
||||
const formId = data.form
|
||||
if (!formId) return data
|
||||
|
||||
try {
|
||||
const form = (await req.payload.findByID({
|
||||
collection: 'forms',
|
||||
id: formId,
|
||||
depth: 0,
|
||||
})) as unknown as FormWithTenant
|
||||
|
||||
if (form?.tenant) {
|
||||
const tenantId = typeof form.tenant === 'object' ? form.tenant.id : form.tenant
|
||||
return { ...data, tenant: tenantId }
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Forms] Error reading tenant from form:', error)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
81
src/migrations/20260217_120000_add_tenant_to_forms.ts
Normal file
81
src/migrations/20260217_120000_add_tenant_to_forms.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||
// 1. Add tenant_id to forms
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "forms"
|
||||
ADD COLUMN IF NOT EXISTS "tenant_id" integer REFERENCES tenants(id) ON DELETE SET NULL;
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "forms_tenant_idx" ON "forms" ("tenant_id");
|
||||
`)
|
||||
|
||||
// 2. Add tenant_id to form_submissions
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "form_submissions"
|
||||
ADD COLUMN IF NOT EXISTS "tenant_id" integer REFERENCES tenants(id) ON DELETE SET NULL;
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "form_submissions_tenant_idx" ON "form_submissions" ("tenant_id");
|
||||
`)
|
||||
|
||||
// 3. ContactFormBlock: add form_id relationship column (replaces recipientEmail)
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "pages_blocks_contact_form_block"
|
||||
ADD COLUMN IF NOT EXISTS "form_id" integer REFERENCES forms(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS "show_contact_info" boolean DEFAULT true;
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_contact_form_block_form_idx"
|
||||
ON "pages_blocks_contact_form_block" ("form_id");
|
||||
`)
|
||||
|
||||
// 4. ContactFormBlock locales: add successMessage
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "pages_blocks_contact_form_block_locales"
|
||||
ADD COLUMN IF NOT EXISTS "success_message" varchar;
|
||||
`)
|
||||
|
||||
// 5. pages_rels: add forms_id column for the block relationship
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "pages_rels"
|
||||
ADD COLUMN IF NOT EXISTS "forms_id" integer REFERENCES forms(id) ON DELETE CASCADE;
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "pages_rels_forms_idx" ON "pages_rels" ("forms_id");
|
||||
`)
|
||||
|
||||
// 6. Drop old recipientEmail column (data migrated: it was just a default 'info@porwoll.de')
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "pages_blocks_contact_form_block"
|
||||
DROP COLUMN IF EXISTS "recipient_email";
|
||||
`)
|
||||
}
|
||||
|
||||
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||
// Restore recipientEmail
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "pages_blocks_contact_form_block"
|
||||
ADD COLUMN IF NOT EXISTS "recipient_email" varchar DEFAULT 'info@porwoll.de';
|
||||
`)
|
||||
|
||||
// Remove new columns
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "pages_blocks_contact_form_block"
|
||||
DROP COLUMN IF EXISTS "form_id",
|
||||
DROP COLUMN IF EXISTS "show_contact_info";
|
||||
`)
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "pages_blocks_contact_form_block_locales"
|
||||
DROP COLUMN IF EXISTS "success_message";
|
||||
`)
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "pages_rels" DROP COLUMN IF EXISTS "forms_id";
|
||||
`)
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "forms" DROP COLUMN IF EXISTS "tenant_id";
|
||||
`)
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "form_submissions" DROP COLUMN IF EXISTS "tenant_id";
|
||||
`)
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@ import * as migration_20260116_100000_add_token_notification_fields from './2026
|
|||
import * as migration_20260116_120000_add_report_schedules from './20260116_120000_add_report_schedules';
|
||||
import * as migration_20260215_120000_add_monitoring_collections from './20260215_120000_add_monitoring_collections';
|
||||
import * as migration_20260216_150000_add_card_grid_icon_fields from './20260216_150000_add_card_grid_icon_fields';
|
||||
import * as migration_20260217_120000_add_tenant_to_forms from './20260217_120000_add_tenant_to_forms';
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
|
|
@ -234,4 +235,9 @@ export const migrations = [
|
|||
down: migration_20260216_150000_add_card_grid_icon_fields.down,
|
||||
name: '20260216_150000_add_card_grid_icon_fields'
|
||||
},
|
||||
{
|
||||
up: migration_20260217_120000_add_tenant_to_forms.up,
|
||||
down: migration_20260217_120000_add_tenant_to_forms.down,
|
||||
name: '20260217_120000_add_tenant_to_forms'
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -846,9 +846,14 @@ export interface Page {
|
|||
blockType: 'cta-block';
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* Wählen Sie ein Formular aus der Formulare-Sammlung
|
||||
*/
|
||||
form: number | Form;
|
||||
headline?: string | null;
|
||||
description?: string | null;
|
||||
recipientEmail?: string | null;
|
||||
successMessage?: string | null;
|
||||
showContactInfo?: boolean | null;
|
||||
showPhone?: boolean | null;
|
||||
showAddress?: boolean | null;
|
||||
showSocials?: boolean | null;
|
||||
|
|
@ -3076,6 +3081,168 @@ export interface Page {
|
|||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "forms".
|
||||
*/
|
||||
export interface Form {
|
||||
id: number;
|
||||
title: string;
|
||||
fields?:
|
||||
| (
|
||||
| {
|
||||
name: string;
|
||||
label?: string | null;
|
||||
width?: number | null;
|
||||
required?: boolean | null;
|
||||
defaultValue?: boolean | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'checkbox';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string | null;
|
||||
width?: number | null;
|
||||
required?: boolean | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'email';
|
||||
}
|
||||
| {
|
||||
message?: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: any;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'message';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string | null;
|
||||
width?: number | null;
|
||||
defaultValue?: number | null;
|
||||
required?: boolean | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'number';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string | null;
|
||||
width?: number | null;
|
||||
defaultValue?: string | null;
|
||||
placeholder?: string | null;
|
||||
options?:
|
||||
| {
|
||||
label: string;
|
||||
value: string;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
required?: boolean | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'select';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string | null;
|
||||
width?: number | null;
|
||||
defaultValue?: string | null;
|
||||
required?: boolean | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'text';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string | null;
|
||||
width?: number | null;
|
||||
defaultValue?: string | null;
|
||||
required?: boolean | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'textarea';
|
||||
}
|
||||
)[]
|
||||
| null;
|
||||
submitButtonLabel?: string | null;
|
||||
/**
|
||||
* Choose whether to display an on-page message or redirect to a different page after they submit the form.
|
||||
*/
|
||||
confirmationType?: ('message' | 'redirect') | null;
|
||||
confirmationMessage?: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: any;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
redirect?: {
|
||||
type?: ('reference' | 'custom') | null;
|
||||
reference?: {
|
||||
relationTo: 'pages';
|
||||
value: number | Page;
|
||||
} | null;
|
||||
url?: string | null;
|
||||
};
|
||||
/**
|
||||
* Send custom emails when the form submits. Use comma separated lists to send the same email to multiple recipients. To reference a value from this form, wrap that field's name with double curly brackets, i.e. {{firstName}}. You can use a wildcard {{*}} to output all data and {{*:table}} to format it as an HTML table in the email.
|
||||
*/
|
||||
emails?:
|
||||
| {
|
||||
emailTo?: string | null;
|
||||
cc?: string | null;
|
||||
bcc?: string | null;
|
||||
replyTo?: string | null;
|
||||
emailFrom?: string | null;
|
||||
subject: string;
|
||||
/**
|
||||
* Enter the message that should be sent in this email.
|
||||
*/
|
||||
message?: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: any;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
tenant: number | Tenant;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* Video-Bibliothek für YouTube/Vimeo Embeds und hochgeladene Videos
|
||||
*
|
||||
|
|
@ -4437,167 +4604,6 @@ export interface Job {
|
|||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "forms".
|
||||
*/
|
||||
export interface Form {
|
||||
id: number;
|
||||
title: string;
|
||||
fields?:
|
||||
| (
|
||||
| {
|
||||
name: string;
|
||||
label?: string | null;
|
||||
width?: number | null;
|
||||
required?: boolean | null;
|
||||
defaultValue?: boolean | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'checkbox';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string | null;
|
||||
width?: number | null;
|
||||
required?: boolean | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'email';
|
||||
}
|
||||
| {
|
||||
message?: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: any;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'message';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string | null;
|
||||
width?: number | null;
|
||||
defaultValue?: number | null;
|
||||
required?: boolean | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'number';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string | null;
|
||||
width?: number | null;
|
||||
defaultValue?: string | null;
|
||||
placeholder?: string | null;
|
||||
options?:
|
||||
| {
|
||||
label: string;
|
||||
value: string;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
required?: boolean | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'select';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string | null;
|
||||
width?: number | null;
|
||||
defaultValue?: string | null;
|
||||
required?: boolean | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'text';
|
||||
}
|
||||
| {
|
||||
name: string;
|
||||
label?: string | null;
|
||||
width?: number | null;
|
||||
defaultValue?: string | null;
|
||||
required?: boolean | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'textarea';
|
||||
}
|
||||
)[]
|
||||
| null;
|
||||
submitButtonLabel?: string | null;
|
||||
/**
|
||||
* Choose whether to display an on-page message or redirect to a different page after they submit the form.
|
||||
*/
|
||||
confirmationType?: ('message' | 'redirect') | null;
|
||||
confirmationMessage?: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: any;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
redirect?: {
|
||||
type?: ('reference' | 'custom') | null;
|
||||
reference?: {
|
||||
relationTo: 'pages';
|
||||
value: number | Page;
|
||||
} | null;
|
||||
url?: string | null;
|
||||
};
|
||||
/**
|
||||
* Send custom emails when the form submits. Use comma separated lists to send the same email to multiple recipients. To reference a value from this form, wrap that field's name with double curly brackets, i.e. {{firstName}}. You can use a wildcard {{*}} to output all data and {{*:table}} to format it as an HTML table in the email.
|
||||
*/
|
||||
emails?:
|
||||
| {
|
||||
emailTo?: string | null;
|
||||
cc?: string | null;
|
||||
bcc?: string | null;
|
||||
replyTo?: string | null;
|
||||
emailFrom?: string | null;
|
||||
subject: string;
|
||||
/**
|
||||
* Enter the message that should be sent in this email.
|
||||
*/
|
||||
message?: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: any;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* Produkte und Artikel
|
||||
*
|
||||
|
|
@ -7559,6 +7565,24 @@ export interface MonitoringSnapshot {
|
|||
| number
|
||||
| boolean
|
||||
| null;
|
||||
secrets?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
securityEvents?:
|
||||
| {
|
||||
[k: string]: unknown;
|
||||
}
|
||||
| unknown[]
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null;
|
||||
};
|
||||
/**
|
||||
* Performance-Metriken
|
||||
|
|
@ -7582,7 +7606,7 @@ export interface MonitoringSnapshot {
|
|||
export interface MonitoringLog {
|
||||
id: number;
|
||||
level: 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
||||
source: 'payload' | 'queue-worker' | 'cron' | 'email' | 'oauth' | 'sync';
|
||||
source: 'payload' | 'queue-worker' | 'cron' | 'email' | 'oauth' | 'sync' | 'security';
|
||||
message: string;
|
||||
/**
|
||||
* Strukturierte Metadaten
|
||||
|
|
@ -8597,9 +8621,11 @@ export interface PagesSelect<T extends boolean = true> {
|
|||
'contact-form-block'?:
|
||||
| T
|
||||
| {
|
||||
form?: T;
|
||||
headline?: T;
|
||||
description?: T;
|
||||
recipientEmail?: T;
|
||||
successMessage?: T;
|
||||
showContactInfo?: T;
|
||||
showPhone?: T;
|
||||
showAddress?: T;
|
||||
showSocials?: T;
|
||||
|
|
@ -12580,6 +12606,8 @@ export interface MonitoringSnapshotsSelect<T extends boolean = true> {
|
|||
metaOAuth?: T;
|
||||
youtubeOAuth?: T;
|
||||
cronJobs?: T;
|
||||
secrets?: T;
|
||||
securityEvents?: T;
|
||||
};
|
||||
performance?:
|
||||
| T
|
||||
|
|
@ -12855,6 +12883,7 @@ export interface FormsSelect<T extends boolean = true> {
|
|||
message?: T;
|
||||
id?: T;
|
||||
};
|
||||
tenant?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ import { SEOSettings } from './globals/SEOSettings'
|
|||
// Hooks
|
||||
import { sendFormNotification } from './hooks/sendFormNotification'
|
||||
import { formSubmissionBeforeChange } from './hooks/formSubmissionHooks'
|
||||
import { setSubmissionTenant } from './hooks/setSubmissionTenant'
|
||||
|
||||
// Form Submissions Overrides
|
||||
import { formSubmissionOverrides } from './collections/FormSubmissionsOverrides'
|
||||
|
|
@ -298,6 +299,50 @@ export default buildConfig({
|
|||
// Sharp für Bildoptimierung
|
||||
sharp,
|
||||
plugins: [
|
||||
// formBuilderPlugin MUSS vor multiTenantPlugin stehen, da es die forms/form-submissions
|
||||
// Collections erstellt, die multiTenantPlugin dann mit Tenant-Scoping erweitert.
|
||||
formBuilderPlugin({
|
||||
fields: {
|
||||
text: true,
|
||||
textarea: true,
|
||||
select: true,
|
||||
email: true,
|
||||
state: false,
|
||||
country: false,
|
||||
checkbox: true,
|
||||
number: true,
|
||||
message: true,
|
||||
payment: false,
|
||||
},
|
||||
// Fix für TypeScript Types Generation - das Plugin braucht explizite relationTo Angaben
|
||||
redirectRelationships: ['pages'],
|
||||
formOverrides: {
|
||||
admin: {
|
||||
group: 'Formulare',
|
||||
},
|
||||
labels: {
|
||||
singular: 'Formular',
|
||||
plural: 'Formulare',
|
||||
},
|
||||
fields: ({ defaultFields }) => [
|
||||
...defaultFields,
|
||||
{
|
||||
name: 'tenant',
|
||||
type: 'relationship',
|
||||
relationTo: 'tenants',
|
||||
required: true,
|
||||
admin: { position: 'sidebar' },
|
||||
},
|
||||
],
|
||||
},
|
||||
formSubmissionOverrides: {
|
||||
...(formSubmissionOverrides as Record<string, unknown>),
|
||||
hooks: {
|
||||
beforeChange: [setSubmissionTenant, formSubmissionBeforeChange],
|
||||
afterChange: [sendFormNotification],
|
||||
},
|
||||
} as Parameters<typeof formBuilderPlugin>[0]['formSubmissionOverrides'],
|
||||
}),
|
||||
multiTenantPlugin({
|
||||
tenantsSlug: 'tenants',
|
||||
collections: {
|
||||
|
|
@ -344,6 +389,9 @@ export default buildConfig({
|
|||
series: {},
|
||||
// Debug: Minimal test collection - DISABLED
|
||||
// 'test-minimal': {},
|
||||
// Form Builder Plugin Collections - customTenantField: true weil tenant via formOverrides injiziert wird
|
||||
forms: { customTenantField: true },
|
||||
'form-submissions': { customTenantField: true },
|
||||
// Consent Management Collections - customTenantField: true weil sie bereits ein tenant-Feld haben
|
||||
'cookie-configurations': { customTenantField: true },
|
||||
'cookie-inventory': { customTenantField: true },
|
||||
|
|
@ -386,29 +434,6 @@ export default buildConfig({
|
|||
generateLabel: (_, doc) => doc.title as string,
|
||||
generateURL: (docs) => docs.reduce((url, doc) => `${url}/${doc.slug}`, ''),
|
||||
}),
|
||||
formBuilderPlugin({
|
||||
fields: {
|
||||
text: true,
|
||||
textarea: true,
|
||||
select: true,
|
||||
email: true,
|
||||
state: false,
|
||||
country: false,
|
||||
checkbox: true,
|
||||
number: true,
|
||||
message: true,
|
||||
payment: false,
|
||||
},
|
||||
// Fix für TypeScript Types Generation - das Plugin braucht explizite relationTo Angaben
|
||||
redirectRelationships: ['pages'],
|
||||
formSubmissionOverrides: {
|
||||
...(formSubmissionOverrides as Record<string, unknown>),
|
||||
hooks: {
|
||||
beforeChange: [formSubmissionBeforeChange],
|
||||
afterChange: [sendFormNotification],
|
||||
},
|
||||
} as Parameters<typeof formBuilderPlugin>[0]['formSubmissionOverrides'],
|
||||
}),
|
||||
redirectsPlugin({
|
||||
collections: ['pages'],
|
||||
overrides: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue