diff --git a/website/app.js b/website/app.js index 68b16f25..897e1169 100644 --- a/website/app.js +++ b/website/app.js @@ -101,6 +101,7 @@ function createAposConfig() { 'container-widget': {}, 'leadership-team-widget': {}, 'table-widget': {}, + 'form-field-standardizer': {}, }, }; } diff --git a/website/modules/@apostrophecms/form-widget/index.js b/website/modules/@apostrophecms/form-widget/index.js index 1007cead..2a371331 100644 --- a/website/modules/@apostrophecms/form-widget/index.js +++ b/website/modules/@apostrophecms/form-widget/index.js @@ -1,5 +1,3 @@ -const headingToolbar = require('../../../lib/headingToolbar'); - module.exports = { options: { fields: { @@ -10,9 +8,7 @@ module.exports = { options: { max: 1, widgets: { - '@apostrophecms/rich-text': { - ...headingToolbar, - }, + '@apostrophecms/rich-text': {}, }, }, }, diff --git a/website/modules/@apostrophecms/form/index.js b/website/modules/@apostrophecms/form/index.js index 9d6d47e7..7b284fab 100644 --- a/website/modules/@apostrophecms/form/index.js +++ b/website/modules/@apostrophecms/form/index.js @@ -4,6 +4,9 @@ const GoogleSheetsErrorHandler = require('./lib/GoogleSheetsErrorHandler'); const { formatForSpreadsheet } = require('./lib/formatForSpreadsheet'); const { getSheetsAuthConfig } = require('./lib/getSheetsAuthConfig'); +const VALIDATION_INSTRUCTIONS = + 'For proper validation, place the name, email, and phone number fields at the beginning of the form, in this exact order. Use a text input for each. Add all other fields afterward.'; + const parseFormData = (req) => { const rawData = req?.body?.data; if (!rawData) { @@ -24,6 +27,26 @@ const validateSubmissionSuccess = (result) => { }; module.exports = { + improve: '@apostrophecms/form', + fields: { + add: { + instructions: { + label: 'Instructions', + type: 'string', + required: false, + textarea: true, + def: VALIDATION_INSTRUCTIONS, + readOnly: true, + }, + }, + group: { + basics: { + label: 'Form', + fields: ['title', 'instructions', 'contents'], + }, + }, + }, + options: { label: 'Form', }, diff --git a/website/modules/asset/ui/src/scss/_form.scss b/website/modules/asset/ui/src/scss/_form.scss index 612d0534..4ff532fd 100644 --- a/website/modules/asset/ui/src/scss/_form.scss +++ b/website/modules/asset/ui/src/scss/_form.scss @@ -124,3 +124,9 @@ label { color: $gray-300; } } + +.apos-field--instructions { + .apos-input[disabled] { + background-color: $gray-100; + } +} diff --git a/website/modules/form-field-standardizer/index.js b/website/modules/form-field-standardizer/index.js new file mode 100644 index 00000000..7dada3f2 --- /dev/null +++ b/website/modules/form-field-standardizer/index.js @@ -0,0 +1,19 @@ +const { standardizeFieldNames } = require('../../utils/standardizeFieldNames'); + +module.exports = { + improve: '@apostrophecms/form', + + options: { + alias: 'formFieldStandardizer', + }, + + handlers(self) { + return { + '@apostrophecms/form:beforeSave': { + standardizeFieldNames(req, doc) { + standardizeFieldNames(doc); + }, + }, + }; + }, +}; diff --git a/website/utils/standardizeFieldNames.js b/website/utils/standardizeFieldNames.js new file mode 100644 index 00000000..c1259ec5 --- /dev/null +++ b/website/utils/standardizeFieldNames.js @@ -0,0 +1,24 @@ +const { + STANDARD_FORM_FIELD_NAMES, +} = require('../modules/@apostrophecms/shared-constants/ui/src/index'); + +const standardizeFieldNames = (doc) => { + if (!doc) return; + const fieldNames = Object.values(STANDARD_FORM_FIELD_NAMES); + + const items = doc.contents?.items; + if (!Array.isArray(items) || items.length === 0) return; + + const limit = Math.min(fieldNames.length, items.length); + + for (let i = 0; i < limit; i += 1) { + const field = items[i]; + const desiredName = fieldNames[i]; + + if (field?.fieldName && field.fieldName !== desiredName) { + field.fieldName = desiredName; + } + } +}; + +module.exports = { standardizeFieldNames }; diff --git a/website/utils/standardizeFieldNames.test.js b/website/utils/standardizeFieldNames.test.js new file mode 100644 index 00000000..c7589d5a --- /dev/null +++ b/website/utils/standardizeFieldNames.test.js @@ -0,0 +1,109 @@ +const { standardizeFieldNames } = require('./standardizeFieldNames'); +const { + STANDARD_FORM_FIELD_NAMES, +} = require('../modules/@apostrophecms/shared-constants/ui/src/index'); + +describe('standardizeFieldNames', () => { + let fieldNames = Object.values(STANDARD_FORM_FIELD_NAMES); + + beforeEach(() => { + fieldNames = Object.values(STANDARD_FORM_FIELD_NAMES); + }); + + const createTestDoc = (items) => ({ + contents: { + items, + }, + }); + + const expectFieldNames = (items, expectedNames) => { + items.forEach((item, index) => { + expect(item.fieldName).toBe(expectedNames[index]); + }); + }; + + it('should standardize field names according to the standard list', () => { + const doc = createTestDoc([ + { fieldName: 'name' }, + { fieldName: 'email' }, + { fieldName: 'phone' }, + ]); + + standardizeFieldNames(doc); + + expectFieldNames(doc.contents.items, fieldNames); + }); + + it('should handle fewer items than standard names', () => { + const doc = createTestDoc([{ fieldName: 'name' }, { fieldName: 'email' }]); + + standardizeFieldNames(doc); + + expectFieldNames(doc.contents.items, fieldNames); + expect(doc.contents.items.length).toBe(2); + }); + + it('should handle more items than standard names', () => { + const doc = createTestDoc([ + { fieldName: 'name' }, + { fieldName: 'email' }, + { fieldName: 'phone' }, + { fieldName: 'extra' }, + ]); + + standardizeFieldNames(doc); + + expectFieldNames(doc.contents.items.slice(0, 3), fieldNames); + expect(doc.contents.items[3].fieldName).toBe('extra'); + }); + + it('should handle empty items array', () => { + const doc = createTestDoc([]); + + standardizeFieldNames(doc); + + expect(doc.contents.items).toEqual([]); + }); + + it('should handle missing contents', () => { + const doc = {}; + + standardizeFieldNames(doc); + + expect(doc).toEqual({}); + }); + + it('should handle null doc', () => { + const doc = null; + + standardizeFieldNames(doc); + + expect(doc).toBeNull(); + }); + + it('should not modify field names that already match standard names', () => { + const doc = createTestDoc([ + { fieldName: fieldNames[0] }, + { fieldName: fieldNames[1] }, + { fieldName: fieldNames[2] }, + ]); + + standardizeFieldNames(doc); + + expectFieldNames(doc.contents.items, fieldNames); + }); + + it('should handle items without fieldName property', () => { + const doc = createTestDoc([ + {}, + { fieldName: 'email' }, + { otherProp: 'value' }, + ]); + + standardizeFieldNames(doc); + + expect(doc.contents.items[0]).toEqual({}); + expect(doc.contents.items[1].fieldName).toBe(fieldNames[1]); + expect(doc.contents.items[2]).toEqual({ otherProp: 'value' }); + }); +});