feat(plugin-form-builder)!: update form builder plugin field overrides to use a function instead (#6497)
## Description
Changes the `fields` override for form builder plugin to use a function
instead so that we can actually override existing fields which currently
will not work.
```ts
//before
fields: [
{
name: 'custom',
type: 'text',
}
]
// current
fields: ({ defaultFields }) => {
return [
...defaultFields,
{
name: 'custom',
type: 'text',
},
]
}
```
## Type of change
- [x] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { CollectionConfig } from 'payload/types'
|
||||
import type { CollectionConfig, Field } from 'payload/types'
|
||||
|
||||
import type { FormBuilderPluginConfig } from '../../types.js'
|
||||
|
||||
@@ -11,6 +11,74 @@ export const generateSubmissionCollection = (
|
||||
): CollectionConfig => {
|
||||
const formSlug = formConfig?.formOverrides?.slug || 'forms'
|
||||
|
||||
const defaultFields: Field[] = [
|
||||
{
|
||||
name: 'form',
|
||||
type: 'relationship',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
relationTo: formSlug,
|
||||
required: true,
|
||||
validate: async (value, { req: { payload }, req }) => {
|
||||
/* Don't run in the client side */
|
||||
if (!payload) return true
|
||||
|
||||
if (payload) {
|
||||
let _existingForm
|
||||
|
||||
try {
|
||||
_existingForm = await payload.findByID({
|
||||
id: value,
|
||||
collection: formSlug,
|
||||
req,
|
||||
})
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
return 'Cannot create this submission because this form does not exist.'
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'submissionData',
|
||||
type: 'array',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'field',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: 'text',
|
||||
required: true,
|
||||
validate: (value: unknown) => {
|
||||
// TODO:
|
||||
// create a validation function that dynamically
|
||||
// relies on the field type and its options as configured.
|
||||
|
||||
// How to access sibling data from this field?
|
||||
// Need the `name` of the field in order to validate it.
|
||||
|
||||
// Might not be possible to use this validation function.
|
||||
// Instead, might need to do all validation in a `beforeValidate` collection hook.
|
||||
|
||||
if (typeof value !== 'undefined') {
|
||||
return true
|
||||
}
|
||||
|
||||
return 'This field is required.'
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const newConfig: CollectionConfig = {
|
||||
...(formConfig?.formSubmissionOverrides || {}),
|
||||
slug: formConfig?.formSubmissionOverrides?.slug || 'form-submissions',
|
||||
@@ -24,74 +92,11 @@ export const generateSubmissionCollection = (
|
||||
...(formConfig?.formSubmissionOverrides?.admin || {}),
|
||||
enableRichTextRelationship: false,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'form',
|
||||
type: 'relationship',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
relationTo: formSlug,
|
||||
required: true,
|
||||
validate: async (value, { req: { payload }, req }) => {
|
||||
/* Don't run in the client side */
|
||||
if (!payload) return true
|
||||
|
||||
if (payload) {
|
||||
let _existingForm
|
||||
|
||||
try {
|
||||
_existingForm = await payload.findByID({
|
||||
id: value,
|
||||
collection: formSlug,
|
||||
req,
|
||||
})
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
return 'Cannot create this submission because this form does not exist.'
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'submissionData',
|
||||
type: 'array',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'field',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: 'text',
|
||||
required: true,
|
||||
validate: (value: unknown) => {
|
||||
// TODO:
|
||||
// create a validation function that dynamically
|
||||
// relies on the field type and its options as configured.
|
||||
|
||||
// How to access sibling data from this field?
|
||||
// Need the `name` of the field in order to validate it.
|
||||
|
||||
// Might not be possible to use this validation function.
|
||||
// Instead, might need to do all validation in a `beforeValidate` collection hook.
|
||||
|
||||
if (typeof value !== 'undefined') {
|
||||
return true
|
||||
}
|
||||
|
||||
return 'This field is required.'
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
...(formConfig?.formSubmissionOverrides?.fields || []),
|
||||
],
|
||||
fields:
|
||||
formConfig?.formSubmissionOverrides?.fields &&
|
||||
typeof formConfig?.formSubmissionOverrides?.fields === 'function'
|
||||
? formConfig?.formSubmissionOverrides?.fields({ defaultFields })
|
||||
: defaultFields,
|
||||
hooks: {
|
||||
...(formConfig?.formSubmissionOverrides?.hooks || {}),
|
||||
beforeChange: [
|
||||
|
||||
@@ -64,6 +64,159 @@ export const generateFormCollection = (formConfig: FormBuilderPluginConfig): Col
|
||||
}
|
||||
}
|
||||
|
||||
const defaultFields: Field[] = [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'fields',
|
||||
type: 'blocks',
|
||||
blocks: Object.entries(formConfig?.fields || {})
|
||||
.map(([fieldKey, fieldConfig]) => {
|
||||
// let the config enable/disable fields with either boolean values or objects
|
||||
if (fieldConfig !== false) {
|
||||
const block = fields[fieldKey]
|
||||
|
||||
if (block === undefined && typeof fieldConfig === 'object') {
|
||||
return fieldConfig
|
||||
}
|
||||
|
||||
if (typeof block === 'object' && typeof fieldConfig === 'object') {
|
||||
return merge<FieldConfig>(block, fieldConfig, {
|
||||
arrayMerge: (_, sourceArray) => sourceArray,
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof block === 'function') {
|
||||
return block(fieldConfig)
|
||||
}
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
.filter(Boolean) as Block[],
|
||||
},
|
||||
{
|
||||
name: 'submitButtonLabel',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'confirmationType',
|
||||
type: 'radio',
|
||||
admin: {
|
||||
description:
|
||||
'Choose whether to display an on-page message or redirect to a different page after they submit the form.',
|
||||
layout: 'horizontal',
|
||||
},
|
||||
defaultValue: 'message',
|
||||
options: [
|
||||
{
|
||||
label: 'Message',
|
||||
value: 'message',
|
||||
},
|
||||
{
|
||||
label: 'Redirect',
|
||||
value: 'redirect',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'confirmationMessage',
|
||||
type: 'richText',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.confirmationType === 'message',
|
||||
},
|
||||
localized: true,
|
||||
required: true,
|
||||
},
|
||||
redirect,
|
||||
{
|
||||
name: 'emails',
|
||||
type: 'array',
|
||||
admin: {
|
||||
description:
|
||||
"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}}.",
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'emailTo',
|
||||
type: 'text',
|
||||
admin: {
|
||||
placeholder: '"Email Sender" <sender@email.com>',
|
||||
width: '100%',
|
||||
},
|
||||
label: 'Email To',
|
||||
},
|
||||
{
|
||||
name: 'cc',
|
||||
type: 'text',
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
label: 'CC',
|
||||
},
|
||||
{
|
||||
name: 'bcc',
|
||||
type: 'text',
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
label: 'BCC',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'replyTo',
|
||||
type: 'text',
|
||||
admin: {
|
||||
placeholder: '"Reply To" <reply-to@email.com>',
|
||||
width: '50%',
|
||||
},
|
||||
label: 'Reply To',
|
||||
},
|
||||
{
|
||||
name: 'emailFrom',
|
||||
type: 'text',
|
||||
admin: {
|
||||
placeholder: '"Email From" <email-from@email.com>',
|
||||
width: '50%',
|
||||
},
|
||||
label: 'Email From',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'subject',
|
||||
type: 'text',
|
||||
defaultValue: "You've received a new message.",
|
||||
label: 'Subject',
|
||||
localized: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
type: 'richText',
|
||||
admin: {
|
||||
description: 'Enter the message that should be sent in this email.',
|
||||
},
|
||||
label: 'Message',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const config: CollectionConfig = {
|
||||
...(formConfig?.formOverrides || {}),
|
||||
slug: formConfig?.formOverrides?.slug || 'forms',
|
||||
@@ -76,159 +229,10 @@ export const generateFormCollection = (formConfig: FormBuilderPluginConfig): Col
|
||||
useAsTitle: 'title',
|
||||
...(formConfig?.formOverrides?.admin || {}),
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'fields',
|
||||
type: 'blocks',
|
||||
blocks: Object.entries(formConfig?.fields || {})
|
||||
.map(([fieldKey, fieldConfig]) => {
|
||||
// let the config enable/disable fields with either boolean values or objects
|
||||
if (fieldConfig !== false) {
|
||||
const block = fields[fieldKey]
|
||||
|
||||
if (block === undefined && typeof fieldConfig === 'object') {
|
||||
return fieldConfig
|
||||
}
|
||||
|
||||
if (typeof block === 'object' && typeof fieldConfig === 'object') {
|
||||
return merge<FieldConfig>(block, fieldConfig, {
|
||||
arrayMerge: (_, sourceArray) => sourceArray,
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof block === 'function') {
|
||||
return block(fieldConfig)
|
||||
}
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
.filter(Boolean) as Block[],
|
||||
},
|
||||
{
|
||||
name: 'submitButtonLabel',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'confirmationType',
|
||||
type: 'radio',
|
||||
admin: {
|
||||
description:
|
||||
'Choose whether to display an on-page message or redirect to a different page after they submit the form.',
|
||||
layout: 'horizontal',
|
||||
},
|
||||
defaultValue: 'message',
|
||||
options: [
|
||||
{
|
||||
label: 'Message',
|
||||
value: 'message',
|
||||
},
|
||||
{
|
||||
label: 'Redirect',
|
||||
value: 'redirect',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'confirmationMessage',
|
||||
type: 'richText',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.confirmationType === 'message',
|
||||
},
|
||||
localized: true,
|
||||
required: true,
|
||||
},
|
||||
redirect,
|
||||
{
|
||||
name: 'emails',
|
||||
type: 'array',
|
||||
admin: {
|
||||
description:
|
||||
"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}}.",
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'emailTo',
|
||||
type: 'text',
|
||||
admin: {
|
||||
placeholder: '"Email Sender" <sender@email.com>',
|
||||
width: '100%',
|
||||
},
|
||||
label: 'Email To',
|
||||
},
|
||||
{
|
||||
name: 'cc',
|
||||
type: 'text',
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
label: 'CC',
|
||||
},
|
||||
{
|
||||
name: 'bcc',
|
||||
type: 'text',
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
label: 'BCC',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'replyTo',
|
||||
type: 'text',
|
||||
admin: {
|
||||
placeholder: '"Reply To" <reply-to@email.com>',
|
||||
width: '50%',
|
||||
},
|
||||
label: 'Reply To',
|
||||
},
|
||||
{
|
||||
name: 'emailFrom',
|
||||
type: 'text',
|
||||
admin: {
|
||||
placeholder: '"Email From" <email-from@email.com>',
|
||||
width: '50%',
|
||||
},
|
||||
label: 'Email From',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'subject',
|
||||
type: 'text',
|
||||
defaultValue: "You've received a new message.",
|
||||
label: 'Subject',
|
||||
localized: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
type: 'richText',
|
||||
admin: {
|
||||
description: 'Enter the message that should be sent in this email.',
|
||||
},
|
||||
label: 'Message',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
...(formConfig?.formOverrides?.fields || []),
|
||||
],
|
||||
fields:
|
||||
formConfig?.formOverrides.fields && typeof formConfig?.formOverrides.fields === 'function'
|
||||
? formConfig?.formOverrides.fields({ defaultFields })
|
||||
: defaultFields,
|
||||
}
|
||||
|
||||
return config
|
||||
|
||||
@@ -30,20 +30,6 @@ export const formBuilderPlugin =
|
||||
|
||||
return {
|
||||
...config,
|
||||
// admin: {
|
||||
// ...config.admin,
|
||||
// webpack: (webpackConfig) => ({
|
||||
// ...webpackConfig,
|
||||
// resolve: {
|
||||
// ...webpackConfig.resolve,
|
||||
// alias: {
|
||||
// ...webpackConfig.resolve.alias,
|
||||
// [path.resolve(__dirname, 'collections/FormSubmissions/hooks/sendEmail.ts')]: path.resolve(__dirname, 'mocks/serverModule.js'),
|
||||
// [path.resolve(__dirname, 'collections/FormSubmissions/hooks/createCharge.ts')]: path.resolve(__dirname, 'mocks/serverModule.js'),
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
// },
|
||||
collections: [
|
||||
...(config?.collections || []),
|
||||
generateFormCollection(formConfig),
|
||||
|
||||
@@ -39,12 +39,13 @@ export interface FieldsConfig {
|
||||
|
||||
export type BeforeEmail = (emails: FormattedEmail[]) => FormattedEmail[] | Promise<FormattedEmail[]>
|
||||
export type HandlePayment = (data: any) => void
|
||||
export type FieldsOverride = (args: { defaultFields: Field[] }) => Field[]
|
||||
|
||||
export type FormBuilderPluginConfig = {
|
||||
beforeEmail?: BeforeEmail
|
||||
fields?: FieldsConfig
|
||||
formOverrides?: Partial<CollectionConfig>
|
||||
formSubmissionOverrides?: Partial<CollectionConfig>
|
||||
formOverrides?: Partial<Omit<CollectionConfig, 'fields'>> & { fields: FieldsOverride }
|
||||
formSubmissionOverrides?: Partial<Omit<CollectionConfig, 'fields'>> & { fields: FieldsOverride }
|
||||
handlePayment?: HandlePayment
|
||||
redirectRelationships?: string[]
|
||||
}
|
||||
|
||||
@@ -73,12 +73,26 @@ export default buildConfigWithDefaults({
|
||||
// singular: 'Contact Form',
|
||||
// plural: 'Contact Forms'
|
||||
// },
|
||||
fields: [
|
||||
{
|
||||
name: 'custom',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
fields: ({ defaultFields }) => {
|
||||
return [
|
||||
...defaultFields,
|
||||
{
|
||||
name: 'custom',
|
||||
type: 'text',
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
formSubmissionOverrides: {
|
||||
fields: ({ defaultFields }) => {
|
||||
return [
|
||||
...defaultFields,
|
||||
{
|
||||
name: 'custom',
|
||||
type: 'text',
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
redirectRelationships: ['pages'],
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user