chore: eslint and prettier (#40)

This commit is contained in:
Jacob Fletcher
2023-05-15 11:23:29 -04:00
parent a12240b71e
commit a89df757bf
23 changed files with 1509 additions and 494 deletions

View File

@@ -0,0 +1,3 @@
module.exports = {
extends: ['@payloadcms'],
}

View File

@@ -0,0 +1,8 @@
module.exports = {
printWidth: 100,
parser: "typescript",
semi: false,
singleQuote: true,
trailingComma: "all",
arrowParens: "avoid",
};

View File

@@ -20,6 +20,7 @@
},
"devDependencies": {
"@types/express": "^4.17.9",
"@types/react": "^18.2.6",
"cross-env": "^7.0.3",
"nodemon": "^2.0.6",
"ts-node": "^9.1.1",

View File

@@ -1,5 +1,4 @@
// const payload = require('payload');
import { CollectionConfig } from 'payload/types';
import type { CollectionConfig } from 'payload/types'
export const Pages: CollectionConfig = {
slug: 'pages',
@@ -22,6 +21,6 @@ export const Pages: CollectionConfig = {
label: 'Form',
type: 'relationship',
relationTo: 'forms',
}
},
],
};
}

View File

@@ -1,4 +1,4 @@
import { CollectionConfig } from 'payload/types';
import type { CollectionConfig } from 'payload/types'
export const Users: CollectionConfig = {
slug: 'users',
@@ -13,4 +13,4 @@ export const Users: CollectionConfig = {
// Email added by default
// Add more fields as needed
],
};
}

View File

@@ -1,10 +1,12 @@
import { buildConfig } from 'payload/config';
import path from 'path';
import path from 'path'
import { buildConfig } from 'payload/config'
import type { Block } from 'payload/types'
// import formBuilderPlugin from '../../dist';
import formBuilderPlugin from '../../src';
import { Users } from './collections/Users';
import { Pages } from './collections/Pages';
import { Block } from 'payload/types';
// eslint-disable-next-line import/no-relative-packages
import formBuilderPlugin from '../../src'
import { Pages } from './collections/Pages'
import { Users } from './collections/Users'
const colorField: Block = {
slug: 'color',
@@ -16,8 +18,8 @@ const colorField: Block = {
{
name: 'value',
type: 'text',
}
]
},
],
}
export default buildConfig({
@@ -28,34 +30,29 @@ export default buildConfig({
},
admin: {
user: Users.slug,
webpack: (config) => {
webpack: config => {
const newConfig = {
...config,
resolve: {
...config.resolve,
alias: {
...config.resolve.alias,
react: path.join(__dirname, "../node_modules/react"),
"react-dom": path.join(__dirname, "../node_modules/react-dom"),
"payload": path.join(__dirname, "../node_modules/payload"),
react: path.join(__dirname, '../node_modules/react'),
'react-dom': path.join(__dirname, '../node_modules/react-dom'),
payload: path.join(__dirname, '../node_modules/payload'),
},
},
};
}
return newConfig;
return newConfig
},
},
collections: [
Users,
Pages
],
collections: [Users, Pages],
plugins: [
formBuilderPlugin({
// handlePayment: handleFormPayments,
// beforeEmail: prepareFormEmails,
redirectRelationships: [
'pages'
],
redirectRelationships: ['pages'],
formOverrides: {
// labels: {
// singular: 'Contact Form',
@@ -65,8 +62,8 @@ export default buildConfig({
{
name: 'name',
type: 'text',
}
]
},
],
},
fields: {
payment: true,
@@ -86,6 +83,6 @@ export default buildConfig({
}),
],
typescript: {
outputFile: path.resolve(__dirname, 'payload-types.ts')
outputFile: path.resolve(__dirname, 'payload-types.ts'),
},
});
})

View File

@@ -1,15 +1,15 @@
import { Payload } from 'payload';
import type { Payload } from 'payload'
export const seed = async (payload: Payload) => {
payload.logger.info('Seeding data...');
export const seed = async (payload: Payload): Promise<any> => {
payload.logger.info('Seeding data...')
await payload.create({
collection: 'users',
data: {
email: 'dev@payloadcms.com',
password: 'test',
}
});
},
})
const { id: formID } = await payload.create({
collection: 'forms',
@@ -33,8 +33,8 @@ export const seed = async (payload: Payload) => {
label: 'Email',
name: 'email',
required: true,
}
]
},
],
},
})
@@ -42,7 +42,7 @@ export const seed = async (payload: Payload) => {
collection: 'pages',
data: {
title: 'Contact',
form: formID
form: formID,
},
})
}

View File

@@ -1,17 +1,19 @@
import express from 'express';
import payload from 'payload';
import { seed } from './seed';
import dotenv from 'dotenv'
import express from 'express'
import payload from 'payload'
require('dotenv').config();
const app = express();
import { seed } from './seed'
dotenv.config()
const app = express()
// Redirect root to Admin panel
app.get('/', (_, res) => {
res.redirect('/admin');
});
res.redirect('/admin')
})
// Initialize Payload
const start = async () => {
const start = async (): Promise<any> => {
await payload.initAsync({
secret: process.env.PAYLOAD_SECRET,
mongoURL: process.env.MONGODB_URI,
@@ -22,10 +24,10 @@ const start = async () => {
})
if (process.env.PAYLOAD_SEED === 'true') {
await seed(payload);
await seed(payload)
}
app.listen(3000);
app.listen(3000)
}
start();
start()

View File

@@ -1861,6 +1861,15 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@^18.2.6":
version "18.2.6"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.6.tgz#5cd53ee0d30ffc193b159d3516c8c8ad2f19d571"
integrity sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.2"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"

View File

@@ -8,7 +8,9 @@
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint src",
"lint:fix": "eslint --fix --ext .ts,.tsx src"
},
"keywords": [
"payload",
@@ -29,9 +31,27 @@
},
"devDependencies": {
"@types/escape-html": "^1.0.1",
"@payloadcms/eslint-config": "^0.0.1",
"@types/express": "^4.17.9",
"@types/node": "18.11.3",
"@types/react": "18.0.21",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.51.0",
"copyfiles": "^2.4.1",
"cross-env": "^7.0.3",
"eslint": "^8.19.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-filenames": "^1.3.2",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"nodemon": "^2.0.6",
"payload": "^1.3.0",
"prettier": "^2.7.1",
"react": "^18.0.0",
"typescript": "^4.5.5"
"ts-node": "^9.1.1",
"typescript": "^4.8.4"
},
"files": [
"dist",

View File

@@ -1,24 +1,19 @@
import { PluginConfig } from "../../../types";
import type { PluginConfig } from '../../../types'
const createCharge = async (beforeChangeData: any, formConfig: PluginConfig) => {
const {
operation,
data
} = beforeChangeData;
const createCharge = async (beforeChangeData: any, formConfig: PluginConfig): Promise<any> => {
const { operation, data } = beforeChangeData
let dataWithPaymentDetails = data;
let dataWithPaymentDetails = data
if (operation === 'create') {
const {
handlePayment
} = formConfig || {};
const { handlePayment } = formConfig || {}
if (typeof handlePayment === 'function') {
dataWithPaymentDetails = await handlePayment(beforeChangeData);
dataWithPaymentDetails = await handlePayment(beforeChangeData)
}
}
return dataWithPaymentDetails;
};
return dataWithPaymentDetails
}
export default createCharge

View File

@@ -1,111 +1,93 @@
import { serialize } from '../../../utilities/serializeRichText';
import { Email, FormattedEmail, PluginConfig } from '../../../types';
import { replaceDoubleCurlys } from '../../../utilities/replaceDoubleCurlys';
import type { Email, FormattedEmail, PluginConfig } from '../../../types'
import { replaceDoubleCurlys } from '../../../utilities/replaceDoubleCurlys'
import { serialize } from '../../../utilities/serializeRichText'
const sendEmail = async (beforeChangeData: any, formConfig: PluginConfig) => {
const {
operation,
data
} = beforeChangeData;
const sendEmail = async (beforeChangeData: any, formConfig: PluginConfig): Promise<any> => {
const { operation, data } = beforeChangeData
if (operation === 'create') {
const {
data: {
id: formSubmissionID
},
req: {
payload,
locale
}
} = beforeChangeData;
data: { id: formSubmissionID },
req: { payload, locale },
} = beforeChangeData
const {
form: formID,
submissionData
} = data || {};
const { form: formID, submissionData } = data || {}
const {
beforeEmail,
formOverrides
} = formConfig || {};
const { beforeEmail, formOverrides } = formConfig || {}
try {
const form = await payload.findByID({
id: formID,
collection: formOverrides?.slug || 'forms',
locale
});
locale,
})
if (form) {
const {
emails,
} = form;
const { emails } = form
if (emails) {
const formattedEmails: FormattedEmail[] = emails.map((email: Email): FormattedEmail | null => {
const {
message,
subject,
emailTo,
cc: emailCC,
bcc: emailBCC,
emailFrom,
replyTo: emailReplyTo,
} = email;
const formattedEmails: FormattedEmail[] = emails.map(
(email: Email): FormattedEmail | null => {
const {
message,
subject,
emailTo,
cc: emailCC,
bcc: emailBCC,
emailFrom,
replyTo: emailReplyTo,
} = email
const to = replaceDoubleCurlys(emailTo, submissionData);
const cc = emailCC ? replaceDoubleCurlys(emailCC, submissionData) : '';
const bcc = emailBCC ? replaceDoubleCurlys(emailBCC, submissionData) : '';
const from = replaceDoubleCurlys(emailFrom, submissionData);
const replyTo = replaceDoubleCurlys(emailReplyTo || emailFrom, submissionData);
const to = replaceDoubleCurlys(emailTo, submissionData)
const cc = emailCC ? replaceDoubleCurlys(emailCC, submissionData) : ''
const bcc = emailBCC ? replaceDoubleCurlys(emailBCC, submissionData) : ''
const from = replaceDoubleCurlys(emailFrom, submissionData)
const replyTo = replaceDoubleCurlys(emailReplyTo || emailFrom, submissionData)
return ({
to,
from,
cc,
bcc,
replyTo,
subject: replaceDoubleCurlys(subject, submissionData),
html: `<div>${serialize(message, submissionData)}</div>`
});
});
return {
to,
from,
cc,
bcc,
replyTo,
subject: replaceDoubleCurlys(subject, submissionData),
html: `<div>${serialize(message, submissionData)}</div>`,
}
},
)
let emailsToSend = formattedEmails
if (typeof beforeEmail === 'function') {
emailsToSend = await beforeEmail(formattedEmails);
emailsToSend = await beforeEmail(formattedEmails)
}
const log = emailsToSend.map(({ html, ...rest }) => ({ ...rest }))
// const log = emailsToSend.map(({ html, ...rest }) => ({ ...rest }))
await Promise.all(
emailsToSend.map(async (email) => {
const { to } = email;
emailsToSend.map(async email => {
const { to } = email
try {
const emailPromise = await payload.sendEmail(email);
return emailPromise;
} catch (err: any) {
console.error(`Error while sending email to address: ${to}. Email not sent.`);
if (err?.response?.body?.errors) {
const error = err.response.body.errors?.[0];
console.log('%s: %s', error?.field, error?.message);
} else {
console.log(err);
}
const emailPromise = await payload.sendEmail(email)
return emailPromise
} catch (err: unknown) {
payload.logger.error({
err: `Error while sending email to address: ${to}. Email not sent: ${err}`,
})
}
})
);
}),
)
}
} else {
console.log('No emails to send.')
payload.logger.info({ msg: 'No emails to send.' })
}
} catch (err) {
console.error(`Error while sending one or more emails in form submission id: ${formSubmissionID}.`);
console.error(err);
} catch (err: unknown) {
const msg = `Error while sending one or more emails in form submission id: ${formSubmissionID}.`
payload.logger.error({ err: msg })
}
}
return data;
};
return data
}
export default sendEmail;
export default sendEmail

View File

@@ -1,30 +1,31 @@
import { CollectionConfig } from 'payload/types';
import { PluginConfig } from '../../types';
import sendEmail from './hooks/sendEmail';
import createCharge from './hooks/createCharge';
import type { CollectionConfig } from 'payload/types'
import type { PluginConfig } from '../../types'
import createCharge from './hooks/createCharge'
import sendEmail from './hooks/sendEmail'
// all settings can be overridden by the config
export const generateSubmissionCollection = (formConfig: PluginConfig): CollectionConfig => {
const newConfig: CollectionConfig = {
...formConfig?.formSubmissionOverrides || {},
...(formConfig?.formSubmissionOverrides || {}),
slug: formConfig?.formSubmissionOverrides?.slug || 'form-submissions',
access: {
create: () => true,
update: () => false,
read: ({ req: { user } }) => !!user, // logged-in users,
...formConfig?.formSubmissionOverrides?.access || {}
...(formConfig?.formSubmissionOverrides?.access || {}),
},
admin: {
...formConfig?.formSubmissionOverrides?.admin || {},
enableRichTextRelationship: false
...(formConfig?.formSubmissionOverrides?.admin || {}),
enableRichTextRelationship: false,
},
hooks: {
beforeChange: [
(data) => createCharge(data, formConfig),
(data) => sendEmail(data, formConfig),
...formConfig?.formSubmissionOverrides?.hooks?.beforeChange || []
data => createCharge(data, formConfig),
data => sendEmail(data, formConfig),
...(formConfig?.formSubmissionOverrides?.hooks?.beforeChange || []),
],
...formConfig?.formSubmissionOverrides?.hooks || {}
...(formConfig?.formSubmissionOverrides?.hooks || {}),
},
fields: [
{
@@ -33,14 +34,14 @@ export const generateSubmissionCollection = (formConfig: PluginConfig): Collecti
relationTo: formConfig?.formOverrides?.slug || 'forms',
required: true,
admin: {
readOnly: true
readOnly: true,
},
},
{
name: 'submissionData',
type: 'array',
admin: {
readOnly: true
readOnly: true,
},
fields: [
{
@@ -64,44 +65,44 @@ export const generateSubmissionCollection = (formConfig: PluginConfig): Collecti
// Instead, might need to do all validation in a `beforeValidate` collection hook.
if (typeof value !== 'undefined') {
return true;
return true
}
return 'This field is required.';
return 'This field is required.'
},
},
],
},
...formConfig?.formSubmissionOverrides?.fields || []
...(formConfig?.formSubmissionOverrides?.fields || []),
],
};
}
const paymentFieldConfig = formConfig?.fields?.payment;
const paymentFieldConfig = formConfig?.fields?.payment
if (paymentFieldConfig) {
newConfig.fields.push({
name: 'payment',
type: 'group',
admin: {
readOnly: true
readOnly: true,
},
fields: [
{
name: 'field',
label: 'Field',
type: 'text'
type: 'text',
},
{
name: 'status',
label: 'Status',
type: 'text'
type: 'text',
},
{
name: 'amount',
type: 'number',
admin: {
description: 'Amount in cents'
}
description: 'Amount in cents',
},
},
{
name: 'paymentProcessor',
@@ -115,23 +116,23 @@ export const generateSubmissionCollection = (formConfig: PluginConfig): Collecti
{
name: 'token',
label: 'token',
type: 'text'
type: 'text',
},
{
name: 'brand',
label: 'Brand',
type: 'text'
type: 'text',
},
{
name: 'number',
label: 'Number',
type: 'text'
}
]
}
]
type: 'text',
},
],
},
],
})
}
return newConfig;
return newConfig
}

View File

@@ -1,48 +1,36 @@
import React, { useEffect, useState } from 'react';
import { Select, useForm } from 'payload/components/forms';
import { TextField } from 'payload/dist/fields/config/types';
import { SelectFieldOption } from '../../types';
import React, { useEffect, useState } from 'react'
import { Select, useForm } from 'payload/components/forms'
import { TextField } from 'payload/dist/fields/config/types'
export const DynamicFieldSelector: React.FC<TextField> = (props) => {
const {
fields,
getDataByPath
} = useForm();
import { SelectFieldOption } from '../../types'
const [options, setOptions] = useState<SelectFieldOption[]>([]);
export const DynamicFieldSelector: React.FC<TextField> = props => {
const { fields, getDataByPath } = useForm()
const [options, setOptions] = useState<SelectFieldOption[]>([])
useEffect(() => {
// @ts-ignore
const fields: any[] = getDataByPath('fields')
if (fields) {
const allNonPaymentFields = fields.map((block): SelectFieldOption | null => {
const {
name,
label,
blockType
} = block;
const allNonPaymentFields = fields
.map((block): SelectFieldOption | null => {
const { name, label, blockType } = block
if (blockType !== 'payment') {
return ({
label,
value: name
})
}
if (blockType !== 'payment') {
return {
label,
value: name,
}
}
return null
}).filter(Boolean) as SelectFieldOption[];
setOptions(allNonPaymentFields);
return null
})
.filter(Boolean) as SelectFieldOption[]
setOptions(allNonPaymentFields)
}
}, [
fields,
getDataByPath
]);
}, [fields, getDataByPath])
return (
<Select
{...props}
options={options}
/>
);
};
return <Select {...props} options={options} />
}

View File

@@ -1,33 +1,34 @@
import { Block, Field } from 'payload/types';
import { FieldConfig, PaymentFieldConfig, TextField } from '../../types';
import { DynamicFieldSelector } from './DynamicFieldSelector';
import { DynamicPriceSelector } from './DynamicPriceSelector';
import type { Block, Field } from 'payload/types'
import type { FieldConfig, PaymentFieldConfig } from '../../types'
import { DynamicFieldSelector } from './DynamicFieldSelector'
import { DynamicPriceSelector } from './DynamicPriceSelector'
const name: Field = {
name: 'name',
label: 'Name (lowercase, no special characters)',
type: 'text',
required: true,
};
}
const label: Field = {
name: 'label',
label: 'Label',
type: 'text',
localized: true,
};
}
const required: Field = {
name: 'required',
label: 'Required',
type: 'checkbox',
};
}
const width: Field = {
name: 'width',
label: 'Field Width (percentage)',
type: 'number',
};
}
const Select: Block = {
slug: 'select',
@@ -110,7 +111,7 @@ const Select: Block = {
},
required,
],
};
}
const Text: Block = {
slug: 'text',
@@ -158,7 +159,7 @@ const Text: Block = {
},
required,
],
};
}
const TextArea: Block = {
slug: 'textarea',
@@ -206,7 +207,7 @@ const TextArea: Block = {
},
required,
],
};
}
const Number: Block = {
slug: 'number',
@@ -253,7 +254,7 @@ const Number: Block = {
},
required,
],
};
}
const Email: Block = {
slug: 'email',
@@ -282,7 +283,7 @@ const Email: Block = {
width,
required,
],
};
}
const State: Block = {
slug: 'state',
@@ -311,7 +312,7 @@ const State: Block = {
width,
required,
],
};
}
const Country: Block = {
slug: 'country',
@@ -340,7 +341,7 @@ const Country: Block = {
width,
required,
],
};
}
const Checkbox: Block = {
slug: 'checkbox',
@@ -389,11 +390,10 @@ const Checkbox: Block = {
type: 'checkbox',
},
],
};
}
const Payment = (fieldConfig: PaymentFieldConfig): Block => {
let paymentProcessorField = null;
let paymentProcessorField = null
if (fieldConfig?.paymentProcessor) {
paymentProcessorField = {
type: 'select',
@@ -474,25 +474,26 @@ const Payment = (fieldConfig: PaymentFieldConfig): Block => {
options: [
{
value: 'hasValue',
label: 'Has Any Value'
label: 'Has Any Value',
},
{
value: 'equals',
label: 'Equals'
label: 'Equals',
},
{
value: 'notEquals',
label: 'Does Not Equal'
}
]
label: 'Does Not Equal',
},
],
},
{
name: 'valueForCondition',
label: 'Value',
type: 'text',
admin: {
condition: (_: any, { condition }: any) => condition === 'equals' || condition === 'notEquals'
}
condition: (_: any, { condition }: any) =>
condition === 'equals' || condition === 'notEquals',
},
},
{
name: 'operator',
@@ -501,21 +502,21 @@ const Payment = (fieldConfig: PaymentFieldConfig): Block => {
options: [
{
value: 'add',
label: 'Add'
label: 'Add',
},
{
value: 'subtract',
label: 'Subtract'
label: 'Subtract',
},
{
value: 'multiply',
label: 'Multiply'
label: 'Multiply',
},
{
value: 'divide',
label: 'Divide'
}
]
label: 'Divide',
},
],
},
{
name: 'valueType',
@@ -528,13 +529,13 @@ const Payment = (fieldConfig: PaymentFieldConfig): Block => {
options: [
{
label: 'Static Value',
value: 'static'
value: 'static',
},
{
label: 'Value Of Field',
value: 'valueOfField'
}
]
value: 'valueOfField',
},
],
},
{
name: 'valueForOperator',
@@ -546,14 +547,14 @@ const Payment = (fieldConfig: PaymentFieldConfig): Block => {
},
},
},
]
],
},
required,
].filter(Boolean) as Field[]
].filter(Boolean) as Field[],
}
return fields
};
}
const Message: Block = {
slug: 'message',
@@ -568,8 +569,9 @@ const Message: Block = {
localized: true,
},
],
};
}
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
export default {
select: Select,
checkbox: Checkbox,
@@ -580,7 +582,7 @@ export default {
number: Number,
country: Country,
state: State,
payment: Payment
payment: Payment,
} as {
[key: string]: Block | ((fieldConfig?: boolean | FieldConfig) => Block)
};
}

View File

@@ -1,11 +1,11 @@
import { Block, CollectionConfig, Field } from 'payload/types';
import merge from 'deepmerge';
import { FieldConfig, PluginConfig } from '../../types';
import fields from './fields';
import merge from 'deepmerge'
import type { Block, CollectionConfig, Field } from 'payload/types'
import type { FieldConfig, PluginConfig } from '../../types'
import fields from './fields'
// all settings can be overridden by the config
export const generateFormCollection = (formConfig: PluginConfig): CollectionConfig => {
const redirect: Field = {
name: 'redirect',
type: 'group',
@@ -21,7 +21,7 @@ export const generateFormCollection = (formConfig: PluginConfig): CollectionConf
required: true,
},
],
};
}
if (formConfig.redirectRelationships) {
redirect.fields.unshift({
@@ -34,7 +34,7 @@ export const generateFormCollection = (formConfig: PluginConfig): CollectionConf
admin: {
condition: (_, siblingData) => siblingData?.type === 'reference',
},
});
})
redirect.fields.unshift({
name: 'type',
@@ -53,26 +53,26 @@ export const generateFormCollection = (formConfig: PluginConfig): CollectionConf
admin: {
layout: 'horizontal',
},
});
})
if (redirect.fields[2].type !== 'row') redirect.fields[2].label = 'Custom URL';
if (redirect.fields[2].type !== 'row') redirect.fields[2].label = 'Custom URL'
redirect.fields[2].admin = {
condition: (_, siblingData) => siblingData?.type === 'custom',
};
}
}
const config: CollectionConfig = {
...formConfig?.formOverrides || {},
...(formConfig?.formOverrides || {}),
slug: formConfig?.formOverrides?.slug || 'forms',
admin: {
useAsTitle: 'title',
enableRichTextRelationship: false,
...formConfig?.formOverrides?.admin || {},
...(formConfig?.formOverrides?.admin || {}),
},
access: {
read: () => true,
...formConfig?.formOverrides?.access || {}
...(formConfig?.formOverrides?.access || {}),
},
fields: [
{
@@ -83,30 +83,32 @@ export const generateFormCollection = (formConfig: PluginConfig): CollectionConf
{
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) {
let block = fields[fieldKey];
blocks: Object.entries(formConfig?.fields || {})
.map(([fieldKey, fieldConfig]) => {
// let the config enable/disable fields with either boolean values or objects
if (fieldConfig !== false) {
let block = fields[fieldKey]
if (block === undefined && typeof fieldConfig === 'object') {
return fieldConfig
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
}
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[],
return null
})
.filter(Boolean) as Block[],
},
{
name: 'submitButtonLabel',
@@ -117,7 +119,8 @@ export const generateFormCollection = (formConfig: PluginConfig): CollectionConf
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.',
description:
'Choose whether to display an on-page message or redirect to a different page after they submit the form.',
layout: 'horizontal',
},
options: [
@@ -146,7 +149,8 @@ export const generateFormCollection = (formConfig: PluginConfig): CollectionConf
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}}.',
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: [
{
@@ -158,7 +162,7 @@ export const generateFormCollection = (formConfig: PluginConfig): CollectionConf
label: 'Email To',
admin: {
width: '100%',
placeholder: '"Email Sender" <sender@email.com>'
placeholder: '"Email Sender" <sender@email.com>',
},
},
{
@@ -206,7 +210,7 @@ export const generateFormCollection = (formConfig: PluginConfig): CollectionConf
type: 'text',
name: 'subject',
label: 'Subject',
defaultValue: 'You\'ve received a new message.',
defaultValue: "You've received a new message.",
required: true,
localized: true,
},
@@ -221,9 +225,9 @@ export const generateFormCollection = (formConfig: PluginConfig): CollectionConf
},
],
},
...formConfig?.formOverrides?.fields || []
...(formConfig?.formOverrides?.fields || []),
],
}
return config;
};
return config
}

View File

@@ -1,51 +1,54 @@
import { Config } from 'payload/config';
import { generateFormCollection } from './collections/Forms';
import { generateSubmissionCollection } from './collections/FormSubmissions';
import { PluginConfig } from './types';
import type { Config } from 'payload/config'
import { generateFormCollection } from './collections/Forms'
import { generateSubmissionCollection } from './collections/FormSubmissions'
import type { PluginConfig } from './types'
// import path from 'path';
export { getPaymentTotal } from './utilities/getPaymentTotal'
const FormBuilder = (incomingFormConfig: PluginConfig) => (config: Config): Config => {
const formConfig: PluginConfig = {
...incomingFormConfig,
fields: {
text: true,
textarea: true,
select: true,
email: true,
state: true,
country: true,
number: true,
checkbox: true,
message: true,
payment: false,
...incomingFormConfig.fields,
},
};
const FormBuilder =
(incomingFormConfig: PluginConfig) =>
(config: Config): Config => {
const formConfig: PluginConfig = {
...incomingFormConfig,
fields: {
text: true,
textarea: true,
select: true,
email: true,
state: true,
country: true,
number: true,
checkbox: true,
message: true,
payment: false,
...incomingFormConfig.fields,
},
}
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),
generateSubmissionCollection(formConfig),
],
};
};
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),
generateSubmissionCollection(formConfig),
],
}
}
export default FormBuilder;
export default FormBuilder

View File

@@ -1 +1 @@
export default {};
export default {}

View File

@@ -1,17 +1,19 @@
import { Block, CollectionConfig, Field } from 'payload/types';
import type { Block, CollectionConfig, Field } from 'payload/types'
export type BlockConfig = {
export interface BlockConfig {
block: Block
validate?: (value: unknown) => boolean | string
}
export function isValidBlockConfig(blockConfig: BlockConfig | string): blockConfig is BlockConfig {
return typeof blockConfig !== 'string'
&& typeof blockConfig?.block?.slug === 'string'
&& Array.isArray(blockConfig?.block?.fields);
return (
typeof blockConfig !== 'string' &&
typeof blockConfig?.block?.slug === 'string' &&
Array.isArray(blockConfig?.block?.fields)
)
}
export type FieldValues = {
export interface FieldValues {
[key: string]: string | number | boolean | null | undefined
}
@@ -19,9 +21,9 @@ export type PaymentFieldConfig = Partial<Field> & {
paymentProcessor: Partial<SelectField>
}
export type FieldConfig = Partial<Field> | PaymentFieldConfig;
export type FieldConfig = Partial<Field> | PaymentFieldConfig
export type FieldsConfig = {
export interface FieldsConfig {
select?: boolean | FieldConfig
text?: boolean | FieldConfig
textarea?: boolean | FieldConfig
@@ -35,10 +37,10 @@ export type FieldsConfig = {
[key: string]: boolean | FieldConfig | undefined
}
export type BeforeEmail = (emails: FormattedEmail[]) => FormattedEmail[] | Promise<FormattedEmail[]>;
export type HandlePayment = (data: any) => void;
export type BeforeEmail = (emails: FormattedEmail[]) => FormattedEmail[] | Promise<FormattedEmail[]>
export type HandlePayment = (data: any) => void
export type PluginConfig = {
export interface PluginConfig {
fields?: FieldsConfig
formSubmissionOverrides?: Partial<CollectionConfig>
formOverrides?: Partial<CollectionConfig>
@@ -47,7 +49,7 @@ export type PluginConfig = {
redirectRelationships?: string[]
}
export type TextField = {
export interface TextField {
blockType: 'text'
blockName?: string
width?: string
@@ -57,7 +59,7 @@ export type TextField = {
required?: boolean
}
export type TextAreaField = {
export interface TextAreaField {
blockType: 'textarea'
blockName?: string
width?: string
@@ -67,12 +69,12 @@ export type TextAreaField = {
required?: boolean
}
export type SelectFieldOption = {
export interface SelectFieldOption {
label: string
value: string
}
export type SelectField = {
export interface SelectField {
blockType: 'select'
blockName?: string
width?: string
@@ -83,7 +85,7 @@ export type SelectField = {
options: SelectFieldOption[]
}
export type PriceCondition = {
export interface PriceCondition {
fieldToUse: string
condition: 'equals' | 'notEquals' | 'hasValue'
valueForCondition: string
@@ -92,7 +94,7 @@ export type PriceCondition = {
valueForOperator: string | number // TODO: make this a number, see ./collections/Forms/DynamicPriceSelector.tsx
}
export type PaymentField = {
export interface PaymentField {
blockType: 'payment'
blockName?: string
width?: string
@@ -100,12 +102,12 @@ export type PaymentField = {
label?: string
defaultValue?: string
required?: boolean
paymentProcessor: string,
paymentProcessor: string
basePrice: number
priceConditions: PriceCondition[]
}
export type EmailField = {
export interface EmailField {
blockType: 'email'
blockName?: string
width?: string
@@ -115,7 +117,7 @@ export type EmailField = {
required?: boolean
}
export type StateField = {
export interface StateField {
blockType: 'state'
blockName?: string
width?: string
@@ -125,7 +127,7 @@ export type StateField = {
required?: boolean
}
export type CountryField = {
export interface CountryField {
blockType: 'country'
blockName?: string
width?: string
@@ -135,7 +137,7 @@ export type CountryField = {
required?: boolean
}
export type CheckboxField = {
export interface CheckboxField {
blockType: 'checkbox'
blockName?: string
width?: string
@@ -145,15 +147,24 @@ export type CheckboxField = {
required?: boolean
}
export type MessageField = {
export interface MessageField {
blockType: 'message'
blockName?: string
message: unknown
}
export type FormFieldBlock = TextField | TextAreaField | SelectField | EmailField | StateField | CountryField | CheckboxField | MessageField | PaymentField
export type FormFieldBlock =
| TextField
| TextAreaField
| SelectField
| EmailField
| StateField
| CountryField
| CheckboxField
| MessageField
| PaymentField
export type Email = {
export interface Email {
emailTo: string
emailFrom: string
cc?: string
@@ -163,7 +174,7 @@ export type Email = {
message?: any // TODO: configure rich text type
}
export type FormattedEmail = {
export interface FormattedEmail {
to: string
cc?: string
bcc?: string
@@ -173,7 +184,7 @@ export type FormattedEmail = {
replyTo: string
}
export type Redirect = {
export interface Redirect {
type: 'reference' | 'custom'
reference?: {
relationTo: string
@@ -182,7 +193,7 @@ export type Redirect = {
url: string
}
export type Form = {
export interface Form {
id: string
title: string
fields: FormFieldBlock[]
@@ -193,12 +204,12 @@ export type Form = {
emails: Email[]
}
export type SubmissionValue = {
export interface SubmissionValue {
field: string
value: unknown
}
export type FormSubmission = {
export interface FormSubmission {
form: string | Form
submissionData: SubmissionValue[]
}

View File

@@ -1,61 +1,53 @@
import { FieldValues, PaymentField, PriceCondition } from "../types";
import type { FieldValues, PaymentField, PriceCondition } from '../types'
export const getPaymentTotal = (args: Partial<PaymentField> & {
fieldValues: FieldValues
}): number => {
const {
basePrice = 0,
priceConditions,
fieldValues
} = args;
export const getPaymentTotal = (
args: Partial<PaymentField> & {
fieldValues: FieldValues
},
): number => {
const { basePrice = 0, priceConditions, fieldValues } = args
let total = basePrice;
let total = basePrice
if (Array.isArray(priceConditions) && priceConditions.length > 0) {
priceConditions.forEach((priceCondition: PriceCondition) => {
const {
condition,
valueForCondition,
fieldToUse,
operator,
valueType,
valueForOperator
} = priceCondition;
const { condition, valueForCondition, fieldToUse, operator, valueType, valueForOperator } =
priceCondition
const valueOfField = fieldValues?.[fieldToUse];
const valueOfField = fieldValues?.[fieldToUse]
if (valueOfField) {
if (
condition === 'hasValue'
|| condition === 'equals' && valueOfField === valueForCondition
|| condition === 'notEquals' && valueOfField !== valueForCondition
condition === 'hasValue' ||
(condition === 'equals' && valueOfField === valueForCondition) ||
(condition === 'notEquals' && valueOfField !== valueForCondition)
) {
const valueToUse = Number(valueType === 'valueOfField' ? valueOfField : valueForOperator);
const valueToUse = Number(valueType === 'valueOfField' ? valueOfField : valueForOperator)
switch (operator) {
case 'add': {
total += valueToUse;
break;
total += valueToUse
break
}
case 'subtract': {
total -= valueToUse;
break;
total -= valueToUse
break
}
case 'multiply': {
total *= valueToUse;
break;
total *= valueToUse
break
}
case 'divide': {
total /= valueToUse;
break;
total /= valueToUse
break
}
default: {
break;
break
}
}
}
}
});
})
}
return total;
return total
}

View File

@@ -1,18 +1,18 @@
type EmailVariable = {
interface EmailVariable {
field: string
value: string
}
type EmailVariables = EmailVariable[];
type EmailVariables = EmailVariable[]
export const replaceDoubleCurlys = (str: string, variables?: EmailVariables): string => {
const regex = /{{(.+?)}}/g;
const regex = /{{(.+?)}}/g
if (str && variables) {
return str.replace(regex, (_, variable) => {
const foundVariable = variables.find(({ field: fieldName }) => variable === fieldName);
if (foundVariable) return foundVariable.value;
return variable;
const foundVariable = variables.find(({ field: fieldName }) => variable === fieldName)
if (foundVariable) return foundVariable.value
return variable
})
}
return str;
return str
}

View File

@@ -1,8 +1,9 @@
import escapeHTML from 'escape-html';
import { Text } from 'slate';
import { replaceDoubleCurlys } from './replaceDoubleCurlys';
import escapeHTML from 'escape-html'
import { Text } from 'slate'
type Node = {
import { replaceDoubleCurlys } from './replaceDoubleCurlys'
interface Node {
bold?: boolean
code?: boolean
italic?: boolean
@@ -11,96 +12,100 @@ type Node = {
children?: Node[]
}
export const serialize = (children?: Node[], submissionData?: any): string | undefined => children?.map((node: Node, i) => {
if (Text.isText(node)) {
let text = `<span>${escapeHTML(replaceDoubleCurlys(node.text, submissionData))}</span>`;
export const serialize = (children?: Node[], submissionData?: any): string | undefined =>
children
?.map((node: Node) => {
if (Text.isText(node)) {
let text = `<span>${escapeHTML(replaceDoubleCurlys(node.text, submissionData))}</span>`
if (node.bold) {
text = (`
if (node.bold) {
text = `
<strong>
${text}
</strong>
`);
}
`
}
if (node.code) {
text = (`
if (node.code) {
text = `
<code>
${text}
</code>
`);
}
`
}
if (node.italic) {
text = (`
if (node.italic) {
text = `
<em >
${text}
</em>
`);
}
`
}
return text;
}
return text
}
if (!node) {
return null;
}
if (!node) {
return null
}
switch (node.type) {
case 'h1':
return (`
switch (node.type) {
case 'h1':
return `
<h1>
${serialize(node.children, submissionData)}
</h1>
`);
case 'h6':
return (`
`
case 'h6':
return `
<h6>
${serialize(node.children, submissionData)}
</h6>
`);
case 'quote':
return (`
`
case 'quote':
return `
<blockquote>
${serialize(node.children, submissionData)}
</blockquote>
`);
case 'ul':
return (`
`
case 'ul':
return `
<ul>
${serialize(node.children, submissionData)}
</ul>
`);
case 'ol':
return (`
`
case 'ol':
return `
<ol>
${serialize(node.children, submissionData)}
</ol>
`);
case 'li':
return (`
`
case 'li':
return `
<li>
${serialize(node.children, submissionData)}
</li>
`);
case 'indent':
return (`
`
case 'indent':
return `
<p style="padding-left: 20px">
${serialize(node.children, submissionData)}
</p>
`);
case 'link':
return (`
`
case 'link':
return `
<a href={${escapeHTML(node.url)}}>
${serialize(node.children, submissionData)}
</a>
`);
`
default:
return (`
default:
return `
<p>
${serialize(node.children, submissionData)}
</p>
`);
}
}).filter(Boolean).join('');
`
}
})
.filter(Boolean)
.join('')

File diff suppressed because it is too large Load Diff