chore: adds README
This commit is contained in:
BIN
packages/plugin-form-builder/.DS_Store
vendored
Normal file
BIN
packages/plugin-form-builder/.DS_Store
vendored
Normal file
Binary file not shown.
217
packages/plugin-form-builder/README.md
Normal file
217
packages/plugin-form-builder/README.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# Payload Form Builder Plugin
|
||||
|
||||
[](https://www.npmjs.com/package/payload-plugin-form-builder)
|
||||
|
||||
A plugin for [Payload CMS](https://github.com/payloadcms/payload) to easily allow your admin editors to build and manage forms from the admin panel.
|
||||
|
||||
Core features:
|
||||
- Creates a `forms` collection where you can:
|
||||
- Build dynamic forms with any number of fields
|
||||
- Add payment fields that can handle dynamic prices
|
||||
- Build completely custom and dynamic emails
|
||||
- Creates a `formSubmissions` collection that:
|
||||
- Validates and saves the form data submitted by your frontend
|
||||
- Sends emails (if applicable)
|
||||
- Handles payment processing (if applicable)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
yarn add payload-plugin-form-builder
|
||||
# OR
|
||||
npm i payload-plugin-form-builder
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
In the `plugins` array of your [Payload config](https://payloadcms.com/docs/configuration/overview), call the plugin with [options](#options):
|
||||
|
||||
```js
|
||||
import { buildConfig } from 'payload/config';
|
||||
import formBuilder from 'payload-plugin-form-builder';
|
||||
|
||||
const config = buildConfig({
|
||||
collections: [
|
||||
{
|
||||
slug: 'pages',
|
||||
fields: []
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
formBuilder()
|
||||
]
|
||||
});
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
- `fields`
|
||||
An object of field types to allow your admin editors to build forms with. Pass either a boolean value or a partial [Payload Block](https://payloadcms.com/docs/fields/blocks#block-configs) to override default settings. See [Fields](#fields) for more details.
|
||||
|
||||
```
|
||||
fields: {
|
||||
text: true,
|
||||
select: true,
|
||||
email: true,
|
||||
state: true,
|
||||
country: true,
|
||||
checkbox: true,
|
||||
number: true,
|
||||
message: true,
|
||||
payment: false
|
||||
}
|
||||
```
|
||||
|
||||
You can also provide your own custom field definitions by passing a new [Payload Block](https://payloadcms.com/docs/fields/blocks#block-configs) object into `fields`.
|
||||
|
||||
|
||||
- `redirectRelationships`
|
||||
|
||||
An array of collection slugs that, when enabled, are populated as options in form redirect fields.
|
||||
|
||||
```
|
||||
redirectRelationships: ['pages']
|
||||
```
|
||||
|
||||
- `handlePayment`
|
||||
|
||||
A [beforeChange]([beforeChange](https://payloadcms.com/docs/hooks/globals#beforechange)) hook that is called upon form submissions. You can integrate into any third-party payment processing API here. There is a `getPaymentTotal` function that will calculate the total cost after all conditions have been applied.
|
||||
|
||||
```
|
||||
import { getPaymentTotal } from 'payload-plugin-form-builder';
|
||||
...
|
||||
handlePayment: async (beforeChangeData) => {
|
||||
// asynchronously process payments here
|
||||
const price = getPaymentTotal({
|
||||
...paymentField, // you can get this from somewhere in the beforeChangeData
|
||||
fieldValues: {}, // an object of kay:value pairs for every field in the form
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- `beforeEmail`
|
||||
|
||||
A [beforeChange]([beforeChange](https://payloadcms.com/docs/hooks/globals#beforechange)) hook that is called just after emails are prepared, but before they are sent. This is a great place to inject your own HTML template to add custom styles.
|
||||
|
||||
```
|
||||
beforeEmails: (emailsToSend) => {
|
||||
// modify the emails in any way before they are sent
|
||||
return emails.map((email) => ({
|
||||
...email,
|
||||
html: email.html // transform the html in any way you'd like (maybe wrap it in an html template?)
|
||||
}))
|
||||
};
|
||||
```
|
||||
|
||||
- `formOverrides`
|
||||
|
||||
Override anything on the form collection by sending a [Payload Collection Config](https://payloadcms.com/docs/configuration/collections).
|
||||
|
||||
```
|
||||
formSubmissionOverrides: {
|
||||
slug: 'contact-forms'
|
||||
}
|
||||
```
|
||||
|
||||
- `formSubmissionOverrides`
|
||||
By default, this plugin relies on [Payload access control](https://payloadcms.com/docs/access-control/collections) to restrict the `update` and `read` operations. This is because anyone should be able to create a form submission, even from a public-facing website - but no one should be able to update a submission one it has been created, or read a submission unless they have permission.
|
||||
|
||||
You can verride access control and anything else on the form submission collection by sending a [Payload Collection Config](https://payloadcms.com/docs/configuration/collections).
|
||||
|
||||
```
|
||||
formSubmissionOverrides: {
|
||||
slug: 'leads'
|
||||
}
|
||||
```
|
||||
|
||||
## Fields
|
||||
|
||||
Each field is a [Payload Block](https://payloadcms.com/docs/fields/blocks) with the following fields:
|
||||
|
||||
- Text
|
||||
- `name`: string
|
||||
- `label`: string
|
||||
- `defaultValue`: string
|
||||
- `width`: string
|
||||
- `required`: checkbox
|
||||
- Select
|
||||
- `name`: string
|
||||
- `label`: string
|
||||
- `defaultValue`: string
|
||||
- `width`: string
|
||||
- `options`: array
|
||||
- `required`: checkbox
|
||||
- Email
|
||||
- `name`: string
|
||||
- `label`: string
|
||||
- `defaultValue`: string
|
||||
- `width`: string
|
||||
- `required`: checkbox
|
||||
- State
|
||||
- `name`: string
|
||||
- `label`: string
|
||||
- `defaultValue`: string
|
||||
- `width`: string
|
||||
- `required`: checkbox
|
||||
- Country
|
||||
- `name`: string
|
||||
- `label`: string
|
||||
- `defaultValue`: string
|
||||
- `width`: string
|
||||
- `required`: checkbox
|
||||
- Checkbox
|
||||
- `name`: string
|
||||
- `label`: string
|
||||
- `defaultValue`: checkbox
|
||||
- `width`: string
|
||||
- `required`: checkbox
|
||||
- Number
|
||||
- `name`: string
|
||||
- `label`: string
|
||||
- `defaultValue`: number
|
||||
- `width`: string
|
||||
- `required`: checkbox
|
||||
- Message
|
||||
- `message`: richText
|
||||
- Payment
|
||||
- `name`: string
|
||||
- `label`: string
|
||||
- `defaultValue`: number
|
||||
- `width`: string
|
||||
- `required`: checkbox
|
||||
- `priceConditions`: array
|
||||
- `fieldToUse`: relationship, dynamically populated based on the fields in your form
|
||||
- `condition`: string - `equals`, `notEquals` | `hasValue`
|
||||
- `valueForOperator`: string - only if `condition` is `equals` or `notEquals`
|
||||
- `operator`: string - `add`, `subtract`, `multiply`, `divide`
|
||||
- `valueType`: string - `static`, `valueOfField`
|
||||
- `value`: string - only if `valueType` is `static`
|
||||
|
||||
## Email
|
||||
|
||||
This plugin relies on the [email configuration](https://payloadcms.com/docs/email/overview) defined in your `payload.init()`. It will read from your config and attempt to send your emails using the credentials provided.
|
||||
|
||||
## TypeScript
|
||||
|
||||
All types can be directly imported:
|
||||
```js
|
||||
import {
|
||||
FormConfig,
|
||||
Form,
|
||||
FormSubmission,
|
||||
FieldsConfig,
|
||||
BeforePayment,
|
||||
HandlePayment
|
||||
} from 'payload-plugin-form-builder/dist/types';
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
BIN
packages/plugin-form-builder/images/.DS_Store
vendored
Normal file
BIN
packages/plugin-form-builder/images/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
packages/plugin-form-builder/images/screenshot-1.jpg
Normal file
BIN
packages/plugin-form-builder/images/screenshot-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
BIN
packages/plugin-form-builder/images/screenshot-2.jpg
Normal file
BIN
packages/plugin-form-builder/images/screenshot-2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
packages/plugin-form-builder/images/screenshot-3.jpg
Normal file
BIN
packages/plugin-form-builder/images/screenshot-3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
BIN
packages/plugin-form-builder/images/screenshot-4.jpg
Normal file
BIN
packages/plugin-form-builder/images/screenshot-4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
BIN
packages/plugin-form-builder/images/screenshot-5.jpg
Normal file
BIN
packages/plugin-form-builder/images/screenshot-5.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
BIN
packages/plugin-form-builder/images/screenshot-6.jpg
Normal file
BIN
packages/plugin-form-builder/images/screenshot-6.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -1,96 +0,0 @@
|
||||
# Form Builder
|
||||
|
||||
This plugin adds full, headless, form-building functionality to Payload CMS.
|
||||
|
||||
### Example Installation
|
||||
|
||||
```js
|
||||
import { buildConfig } from 'payload/config';
|
||||
import payloadFormBuilder from '@payloadcms/plugin-form-builder';
|
||||
|
||||
export default buildConfig({
|
||||
serverURL: 'https://localhost:3000',
|
||||
plugins: [
|
||||
payloadFormBuilder({
|
||||
fields: [
|
||||
'text',
|
||||
'select',
|
||||
'checkbox',
|
||||
|
||||
// Custom field example
|
||||
{
|
||||
block: {
|
||||
slug: 'CustomField',
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
validate: (value) => {
|
||||
if (value === 'what we want it to be') return true;
|
||||
|
||||
// Otherwise, this field data is invalid.
|
||||
return 'This field is incorrect'.
|
||||
}
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
This plugin injects two new collections:
|
||||
|
||||
### Forms
|
||||
|
||||
Forms provide a form-building mechanism where your admins can self-build forms to suit their use cases. The Forms collection provides a `blocks` field type that contains a variety of built-in fields, including:
|
||||
|
||||
- Typical text field
|
||||
- Select field that offers specific options
|
||||
- Email that validates an incoming email address
|
||||
- Checkbox
|
||||
- State (US-based list of states)
|
||||
- Country (Common country list)
|
||||
|
||||
### Form Submissions
|
||||
|
||||
Form Submissions validate incoming form submissions and track them over time right in Payload.
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description |
|
||||
| -------------------- | ----------- |
|
||||
| **`fields`** | An array of field slugs or custom field definitions which define the fields you allow your admin editors to build forms with. [More](#specifying-field-types) |
|
||||
| **`formsOverrides`** | Provide any overrides that you need to the `Forms` collection config. They will be merged in to the injected `Forms` collection. |
|
||||
| **`formSubmissionsOverrides`** | Provide any overrides that you need to the `Form Submissions` collection config. They will be merged in to the injected `Form Submissions` collection. |
|
||||
|
||||
*\* An asterisk denotes that a property is required.*
|
||||
|
||||
#### Specifying field types
|
||||
|
||||
You can specify which fields you want to use, as well as inject your own custom fields.
|
||||
|
||||
##### Custom fields
|
||||
|
||||
You can also provide your own custom field definitions. A custom field is simply a [Payload block definition](https://payloadcms.com/docs/fields/blocks#block-configs) - nothing more. To define a custom field, pass an object into the `fields` option with the following properties:
|
||||
|
||||
| Property | Description |
|
||||
| ---------------- | ----------- |
|
||||
| **`block`** * | A Payload block config that defines the fields that will be presented to your admin user if they choose to use this field. |
|
||||
| **`validate`** | An optional validation function to use when data for this field is received. Return either `true` or a `string` equal to an appropriate error message. |
|
||||
|
||||
*\* An asterisk denotes that a property is required.*
|
||||
|
||||
## Default Access Control
|
||||
|
||||
By default, this plugin relies on the default Payload access control function for all collection access control, except the `Form Submissions` `create` and `update` operations. This is because anyone should be able to create a form submission, even from a public-facing website - but no one should be able to update a submission one it has been created.
|
||||
|
||||
If this is not suitable for your use case, you can override access control of both `Forms` and `Form Submissions` by passing in overrides for either collection config.
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Block, Field } from 'payload/types';
|
||||
import { FieldConfig, TextField } from '../../types';
|
||||
import { FieldConfig, PaymentFieldConfig, TextField } from '../../types';
|
||||
import { DynamicFieldSelector } from './DynamicFieldSelector';
|
||||
import { DynamicPriceSelector } from './DynamicPriceSelector';
|
||||
|
||||
@@ -340,7 +340,7 @@ const Checkbox: Block = {
|
||||
],
|
||||
};
|
||||
|
||||
const Payment = (fieldConfig: FieldConfig): Block => {
|
||||
const Payment = (fieldConfig: PaymentFieldConfig): Block => {
|
||||
|
||||
let paymentProcessorField = null;
|
||||
if (fieldConfig?.paymentProcessor) {
|
||||
@@ -364,13 +364,13 @@ const Payment = (fieldConfig: FieldConfig): Block => {
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
...label,
|
||||
...name,
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
},
|
||||
{
|
||||
...width,
|
||||
...label,
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
@@ -380,116 +380,125 @@ const Payment = (fieldConfig: FieldConfig): Block => {
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
paymentProcessorField,
|
||||
{
|
||||
...width,
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'basePrice',
|
||||
type: 'number',
|
||||
label: 'Base Price',
|
||||
admin: {
|
||||
width: '50%',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
paymentProcessorField,
|
||||
{
|
||||
name: 'priceConditions',
|
||||
labels: {
|
||||
singular: 'Price Condition',
|
||||
plural: 'Price Conditions',
|
||||
},
|
||||
type: 'array',
|
||||
label: 'Price Conditions',
|
||||
fields: [
|
||||
{
|
||||
name: 'fieldToUse',
|
||||
type: 'text',
|
||||
admin: {
|
||||
components: {
|
||||
Field: DynamicFieldSelector,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'priceConditions',
|
||||
labels: {
|
||||
singular: 'Price Condition',
|
||||
plural: 'Price Conditions',
|
||||
},
|
||||
type: 'array',
|
||||
label: 'Price Conditions',
|
||||
fields: [
|
||||
name: 'condition',
|
||||
label: 'Condition',
|
||||
type: 'select',
|
||||
defaultValue: 'hasValue',
|
||||
options: [
|
||||
{
|
||||
name: 'fieldToUse',
|
||||
type: 'text',
|
||||
admin: {
|
||||
components: {
|
||||
Field: DynamicFieldSelector,
|
||||
},
|
||||
},
|
||||
value: 'hasValue',
|
||||
label: 'Has Any Value'
|
||||
},
|
||||
{
|
||||
name: 'condition',
|
||||
label: 'Condition',
|
||||
type: 'select',
|
||||
defaultValue: 'hasValue',
|
||||
options: [
|
||||
{
|
||||
value: 'hasValue',
|
||||
label: 'Has Any Value'
|
||||
},
|
||||
{
|
||||
value: 'equals',
|
||||
label: 'Equals'
|
||||
},
|
||||
{
|
||||
value: 'notEquals',
|
||||
label: 'Does Not Equal'
|
||||
}
|
||||
]
|
||||
value: 'equals',
|
||||
label: 'Equals'
|
||||
},
|
||||
{
|
||||
name: 'valueForCondition',
|
||||
label: 'Value',
|
||||
type: 'text',
|
||||
admin: {
|
||||
condition: (_: any, { condition }: any) => condition === 'equals' || condition === 'notEquals'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'operator',
|
||||
type: 'select',
|
||||
defaultValue: 'add',
|
||||
options: [
|
||||
{
|
||||
value: 'add',
|
||||
label: 'Add'
|
||||
},
|
||||
{
|
||||
value: 'subtract',
|
||||
label: 'Subtract'
|
||||
},
|
||||
{
|
||||
value: 'multiply',
|
||||
label: 'Multiply'
|
||||
},
|
||||
{
|
||||
value: 'divide',
|
||||
label: 'Divide'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'valueType',
|
||||
label: 'Value Type',
|
||||
type: 'radio',
|
||||
admin: {
|
||||
width: '100%',
|
||||
},
|
||||
defaultValue: 'static',
|
||||
options: [
|
||||
{
|
||||
label: 'Static Value',
|
||||
value: 'static'
|
||||
},
|
||||
{
|
||||
label: 'Value Of Field',
|
||||
value: 'valueOfField'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'valueForOperator',
|
||||
label: 'Value',
|
||||
type: 'text',
|
||||
admin: {
|
||||
components: {
|
||||
Field: DynamicPriceSelector,
|
||||
},
|
||||
},
|
||||
},
|
||||
value: 'notEquals',
|
||||
label: 'Does Not Equal'
|
||||
}
|
||||
]
|
||||
},
|
||||
].filter(Boolean) as Field[],
|
||||
{
|
||||
name: 'valueForCondition',
|
||||
label: 'Value',
|
||||
type: 'text',
|
||||
admin: {
|
||||
condition: (_: any, { condition }: any) => condition === 'equals' || condition === 'notEquals'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'operator',
|
||||
type: 'select',
|
||||
defaultValue: 'add',
|
||||
options: [
|
||||
{
|
||||
value: 'add',
|
||||
label: 'Add'
|
||||
},
|
||||
{
|
||||
value: 'subtract',
|
||||
label: 'Subtract'
|
||||
},
|
||||
{
|
||||
value: 'multiply',
|
||||
label: 'Multiply'
|
||||
},
|
||||
{
|
||||
value: 'divide',
|
||||
label: 'Divide'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'valueType',
|
||||
label: 'Value Type',
|
||||
type: 'radio',
|
||||
admin: {
|
||||
width: '100%',
|
||||
},
|
||||
defaultValue: 'static',
|
||||
options: [
|
||||
{
|
||||
label: 'Static Value',
|
||||
value: 'static'
|
||||
},
|
||||
{
|
||||
label: 'Value Of Field',
|
||||
value: 'valueOfField'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'valueForOperator',
|
||||
label: 'Value',
|
||||
type: 'text',
|
||||
admin: {
|
||||
components: {
|
||||
Field: DynamicPriceSelector,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
},
|
||||
required,
|
||||
]
|
||||
].filter(Boolean) as Field[]
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import { generateSubmissionCollection } from './collections/FormSubmissions';
|
||||
import { FormConfig } from './types';
|
||||
// import path from 'path';
|
||||
|
||||
export { getPaymentTotal } from './utilities/getPaymentTotal'
|
||||
|
||||
const FormBuilder = (incomingFormConfig: FormConfig) => (config: Config): Config => {
|
||||
const formConfig: FormConfig = {
|
||||
...incomingFormConfig,
|
||||
|
||||
@@ -15,11 +15,12 @@ export type FieldValues = {
|
||||
[key: string]: string | number | boolean | null | undefined
|
||||
}
|
||||
|
||||
export type FieldConfig = {
|
||||
[key: string]: Partial<Field>
|
||||
export type PaymentFieldConfig = Partial<Field> & {
|
||||
paymentProcessor: Partial<SelectField>
|
||||
}
|
||||
|
||||
export type FieldConfig = Partial<Field> | PaymentFieldConfig;
|
||||
|
||||
export type FieldsConfig = {
|
||||
select?: boolean | FieldConfig
|
||||
text?: boolean | FieldConfig
|
||||
|
||||
Reference in New Issue
Block a user