@mobilizehub/payload-plugin 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/collections/form-submissions/generateFormSubmissionsCollection.js +7 -0
- package/dist/collections/form-submissions/generateFormSubmissionsCollection.js.map +1 -1
- package/dist/collections/form-submissions/hooks/processFormSubmission.d.ts +11 -0
- package/dist/collections/form-submissions/hooks/processFormSubmission.js +153 -0
- package/dist/collections/form-submissions/hooks/processFormSubmission.js.map +1 -0
- package/dist/collections/form-submissions/hooks/sendAutoresponse.d.ts +16 -0
- package/dist/collections/form-submissions/hooks/sendAutoresponse.js +109 -0
- package/dist/collections/form-submissions/hooks/sendAutoresponse.js.map +1 -0
- package/dist/collections/pages/generatePagesCollection.js +11 -10
- package/dist/collections/pages/generatePagesCollection.js.map +1 -1
- package/dist/endpoints/formSubmissionHandler.d.ts +14 -0
- package/dist/endpoints/formSubmissionHandler.js +146 -0
- package/dist/endpoints/formSubmissionHandler.js.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/submit-form.d.ts +57 -0
- package/dist/react/submit-form.js +54 -0
- package/dist/react/submit-form.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { authenticated } from '../../access/authenticated.js';
|
|
2
|
+
import { createProcessFormSubmissionHook } from './hooks/processFormSubmission.js';
|
|
3
|
+
import { createSendAutoresponseHook } from './hooks/sendAutoresponse.js';
|
|
2
4
|
export const generateFormSubmissionsCollection = (formSubmissionsConfig)=>{
|
|
3
5
|
const defaultFields = [
|
|
4
6
|
{
|
|
@@ -53,7 +55,12 @@ export const generateFormSubmissionsCollection = (formSubmissionsConfig)=>{
|
|
|
53
55
|
hooks: {
|
|
54
56
|
...formSubmissionsConfig.formSubmissionsOverrides?.hooks || {},
|
|
55
57
|
afterChange: [
|
|
58
|
+
createSendAutoresponseHook(formSubmissionsConfig),
|
|
56
59
|
...formSubmissionsConfig.formSubmissionsOverrides?.hooks?.afterChange || []
|
|
60
|
+
],
|
|
61
|
+
beforeChange: [
|
|
62
|
+
createProcessFormSubmissionHook(formSubmissionsConfig),
|
|
63
|
+
...formSubmissionsConfig.formSubmissionsOverrides?.hooks?.beforeChange || []
|
|
57
64
|
]
|
|
58
65
|
}
|
|
59
66
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/collections/form-submissions/generateFormSubmissionsCollection.ts"],"sourcesContent":["import type { CollectionConfig, Field } from 'payload'\n\nimport type { MobilizehubPluginConfig } from '../../types/index.js'\n\nimport { authenticated } from '../../access/authenticated.js'\n\nexport const generateFormSubmissionsCollection = (\n formSubmissionsConfig: MobilizehubPluginConfig,\n) => {\n const defaultFields: Field[] = [\n {\n name: 'form',\n type: 'relationship',\n admin: {\n position: 'sidebar',\n readOnly: true,\n },\n relationTo: 'forms',\n required: true,\n },\n {\n name: 'contact',\n type: 'relationship',\n admin: {\n position: 'sidebar',\n readOnly: true,\n },\n relationTo: 'contacts',\n },\n {\n name: 'createdAt',\n type: 'date',\n admin: {\n position: 'sidebar',\n readOnly: true,\n },\n },\n {\n name: 'data',\n type: 'json',\n label: 'Data',\n },\n ]\n\n const config: CollectionConfig = {\n ...(formSubmissionsConfig.formSubmissionsOverrides || {}),\n slug: formSubmissionsConfig.formSubmissionsOverrides?.slug || 'formSubmissions',\n access: {\n create: () => false,\n read: authenticated,\n update: () => false,\n ...(formSubmissionsConfig.formSubmissionsOverrides?.access || {}),\n },\n admin: {\n ...(formSubmissionsConfig.formSubmissionsOverrides?.admin || {}),\n hidden: formSubmissionsConfig.formSubmissionsOverrides?.admin?.hidden || true,\n },\n fields: formSubmissionsConfig.formSubmissionsOverrides?.fields\n ? formSubmissionsConfig.formSubmissionsOverrides.fields({ defaultFields })\n : defaultFields,\n hooks: {\n ...(formSubmissionsConfig.formSubmissionsOverrides?.hooks || {}),\n afterChange: [...(formSubmissionsConfig.formSubmissionsOverrides?.hooks?.afterChange || [])],\n },\n }\n\n return config\n}\n"],"names":["authenticated","generateFormSubmissionsCollection","formSubmissionsConfig","defaultFields","name","type","admin","position","readOnly","relationTo","required","label","config","formSubmissionsOverrides","slug","access","create","read","update","hidden","fields","hooks","afterChange"],"mappings":"AAIA,SAASA,aAAa,QAAQ,gCAA+B;
|
|
1
|
+
{"version":3,"sources":["../../../src/collections/form-submissions/generateFormSubmissionsCollection.ts"],"sourcesContent":["import type { CollectionConfig, Field } from 'payload'\n\nimport type { MobilizehubPluginConfig } from '../../types/index.js'\n\nimport { authenticated } from '../../access/authenticated.js'\nimport { createProcessFormSubmissionHook } from './hooks/processFormSubmission.js'\nimport { createSendAutoresponseHook } from './hooks/sendAutoresponse.js'\n\nexport const generateFormSubmissionsCollection = (\n formSubmissionsConfig: MobilizehubPluginConfig,\n) => {\n const defaultFields: Field[] = [\n {\n name: 'form',\n type: 'relationship',\n admin: {\n position: 'sidebar',\n readOnly: true,\n },\n relationTo: 'forms',\n required: true,\n },\n {\n name: 'contact',\n type: 'relationship',\n admin: {\n position: 'sidebar',\n readOnly: true,\n },\n relationTo: 'contacts',\n },\n {\n name: 'createdAt',\n type: 'date',\n admin: {\n position: 'sidebar',\n readOnly: true,\n },\n },\n {\n name: 'data',\n type: 'json',\n label: 'Data',\n },\n ]\n\n const config: CollectionConfig = {\n ...(formSubmissionsConfig.formSubmissionsOverrides || {}),\n slug: formSubmissionsConfig.formSubmissionsOverrides?.slug || 'formSubmissions',\n access: {\n create: () => false,\n read: authenticated,\n update: () => false,\n ...(formSubmissionsConfig.formSubmissionsOverrides?.access || {}),\n },\n admin: {\n ...(formSubmissionsConfig.formSubmissionsOverrides?.admin || {}),\n hidden: formSubmissionsConfig.formSubmissionsOverrides?.admin?.hidden || true,\n },\n fields: formSubmissionsConfig.formSubmissionsOverrides?.fields\n ? formSubmissionsConfig.formSubmissionsOverrides.fields({ defaultFields })\n : defaultFields,\n hooks: {\n ...(formSubmissionsConfig.formSubmissionsOverrides?.hooks || {}),\n afterChange: [\n createSendAutoresponseHook(formSubmissionsConfig),\n ...(formSubmissionsConfig.formSubmissionsOverrides?.hooks?.afterChange || []),\n ],\n beforeChange: [\n createProcessFormSubmissionHook(formSubmissionsConfig),\n ...(formSubmissionsConfig.formSubmissionsOverrides?.hooks?.beforeChange || []),\n ],\n },\n }\n\n return config\n}\n"],"names":["authenticated","createProcessFormSubmissionHook","createSendAutoresponseHook","generateFormSubmissionsCollection","formSubmissionsConfig","defaultFields","name","type","admin","position","readOnly","relationTo","required","label","config","formSubmissionsOverrides","slug","access","create","read","update","hidden","fields","hooks","afterChange","beforeChange"],"mappings":"AAIA,SAASA,aAAa,QAAQ,gCAA+B;AAC7D,SAASC,+BAA+B,QAAQ,mCAAkC;AAClF,SAASC,0BAA0B,QAAQ,8BAA6B;AAExE,OAAO,MAAMC,oCAAoC,CAC/CC;IAEA,MAAMC,gBAAyB;QAC7B;YACEC,MAAM;YACNC,MAAM;YACNC,OAAO;gBACLC,UAAU;gBACVC,UAAU;YACZ;YACAC,YAAY;YACZC,UAAU;QACZ;QACA;YACEN,MAAM;YACNC,MAAM;YACNC,OAAO;gBACLC,UAAU;gBACVC,UAAU;YACZ;YACAC,YAAY;QACd;QACA;YACEL,MAAM;YACNC,MAAM;YACNC,OAAO;gBACLC,UAAU;gBACVC,UAAU;YACZ;QACF;QACA;YACEJ,MAAM;YACNC,MAAM;YACNM,OAAO;QACT;KACD;IAED,MAAMC,SAA2B;QAC/B,GAAIV,sBAAsBW,wBAAwB,IAAI,CAAC,CAAC;QACxDC,MAAMZ,sBAAsBW,wBAAwB,EAAEC,QAAQ;QAC9DC,QAAQ;YACNC,QAAQ,IAAM;YACdC,MAAMnB;YACNoB,QAAQ,IAAM;YACd,GAAIhB,sBAAsBW,wBAAwB,EAAEE,UAAU,CAAC,CAAC;QAClE;QACAT,OAAO;YACL,GAAIJ,sBAAsBW,wBAAwB,EAAEP,SAAS,CAAC,CAAC;YAC/Da,QAAQjB,sBAAsBW,wBAAwB,EAAEP,OAAOa,UAAU;QAC3E;QACAC,QAAQlB,sBAAsBW,wBAAwB,EAAEO,SACpDlB,sBAAsBW,wBAAwB,CAACO,MAAM,CAAC;YAAEjB;QAAc,KACtEA;QACJkB,OAAO;YACL,GAAInB,sBAAsBW,wBAAwB,EAAEQ,SAAS,CAAC,CAAC;YAC/DC,aAAa;gBACXtB,2BAA2BE;mBACvBA,sBAAsBW,wBAAwB,EAAEQ,OAAOC,eAAe,EAAE;aAC7E;YACDC,cAAc;gBACZxB,gCAAgCG;mBAC5BA,sBAAsBW,wBAAwB,EAAEQ,OAAOE,gBAAgB,EAAE;aAC9E;QACH;IACF;IAEA,OAAOX;AACT,EAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CollectionBeforeChangeHook } from 'payload';
|
|
2
|
+
import type { MobilizehubPluginConfig } from '../../../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates the form submission processing hook.
|
|
5
|
+
*
|
|
6
|
+
* This hook:
|
|
7
|
+
* 1. Creates or updates a contact based on submission data
|
|
8
|
+
* 2. Applies form tags to the contact
|
|
9
|
+
* 3. Links the submission to the contact by returning modified data
|
|
10
|
+
*/
|
|
11
|
+
export declare const createProcessFormSubmissionHook: (pluginConfig: MobilizehubPluginConfig) => CollectionBeforeChangeHook;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contact field mapping from form submission data to contact fields.
|
|
3
|
+
*/ const CONTACT_FIELD_MAP = [
|
|
4
|
+
'email',
|
|
5
|
+
'emailOptIn',
|
|
6
|
+
'firstName',
|
|
7
|
+
'lastName',
|
|
8
|
+
'mobileNumber',
|
|
9
|
+
'mobileOptIn',
|
|
10
|
+
'address',
|
|
11
|
+
'city',
|
|
12
|
+
'state',
|
|
13
|
+
'zip',
|
|
14
|
+
'country'
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Extracts contact-related fields from form submission data.
|
|
18
|
+
*/ function extractContactData(submissionData) {
|
|
19
|
+
const contactData = {};
|
|
20
|
+
for (const field of CONTACT_FIELD_MAP){
|
|
21
|
+
if (submissionData[field] !== undefined) {
|
|
22
|
+
contactData[field] = submissionData[field];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return contactData;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Creates the form submission processing hook.
|
|
29
|
+
*
|
|
30
|
+
* This hook:
|
|
31
|
+
* 1. Creates or updates a contact based on submission data
|
|
32
|
+
* 2. Applies form tags to the contact
|
|
33
|
+
* 3. Links the submission to the contact by returning modified data
|
|
34
|
+
*/ export const createProcessFormSubmissionHook = (pluginConfig)=>{
|
|
35
|
+
const contactsSlug = pluginConfig.contactsOverrides?.slug || 'contacts';
|
|
36
|
+
const formsSlug = pluginConfig.formsOverrides?.slug || 'forms';
|
|
37
|
+
return async ({ data, operation, req })=>{
|
|
38
|
+
// Only process on creation
|
|
39
|
+
if (operation !== 'create') {
|
|
40
|
+
return data;
|
|
41
|
+
}
|
|
42
|
+
const { payload } = req;
|
|
43
|
+
const logger = payload.logger;
|
|
44
|
+
// Parse submission data
|
|
45
|
+
const submissionData = data.data;
|
|
46
|
+
if (!submissionData) {
|
|
47
|
+
logger.warn('Form submission has no data');
|
|
48
|
+
return data;
|
|
49
|
+
}
|
|
50
|
+
// Email is required for contact creation
|
|
51
|
+
const email = submissionData.email;
|
|
52
|
+
if (!email || typeof email !== 'string') {
|
|
53
|
+
logger.info('Form submission has no email, skipping contact creation');
|
|
54
|
+
return data;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
// Extract contact fields from submission
|
|
58
|
+
const contactData = extractContactData(submissionData);
|
|
59
|
+
// Find or create contact
|
|
60
|
+
const existingContactResult = await payload.find({
|
|
61
|
+
collection: contactsSlug,
|
|
62
|
+
limit: 1,
|
|
63
|
+
where: {
|
|
64
|
+
email: {
|
|
65
|
+
equals: email
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const existingContact = existingContactResult.docs[0];
|
|
70
|
+
let contactId;
|
|
71
|
+
if (existingContact) {
|
|
72
|
+
// Update existing contact (merge data, don't overwrite with empty values)
|
|
73
|
+
const updateData = {};
|
|
74
|
+
for (const [key, value] of Object.entries(contactData)){
|
|
75
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
76
|
+
updateData[key] = value;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
await payload.update({
|
|
80
|
+
id: existingContact.id,
|
|
81
|
+
collection: contactsSlug,
|
|
82
|
+
data: updateData
|
|
83
|
+
});
|
|
84
|
+
contactId = existingContact.id;
|
|
85
|
+
logger.info(`Updated contact ${contactId} from form submission`);
|
|
86
|
+
} else {
|
|
87
|
+
// Create new contact
|
|
88
|
+
const newContact = await payload.create({
|
|
89
|
+
collection: contactsSlug,
|
|
90
|
+
data: contactData
|
|
91
|
+
});
|
|
92
|
+
contactId = newContact.id;
|
|
93
|
+
logger.info(`Created contact ${contactId} from form submission`);
|
|
94
|
+
}
|
|
95
|
+
const formId = data.form;
|
|
96
|
+
const formIdValue = typeof formId === 'object' ? formId.id : formId;
|
|
97
|
+
if (!formIdValue) {
|
|
98
|
+
logger.error('Form ID not found for form submission');
|
|
99
|
+
throw new Error('Form ID not found for form submission');
|
|
100
|
+
}
|
|
101
|
+
// Get form to check for tags
|
|
102
|
+
const form = await payload.findByID({
|
|
103
|
+
id: formIdValue,
|
|
104
|
+
collection: formsSlug,
|
|
105
|
+
depth: 0
|
|
106
|
+
});
|
|
107
|
+
// Apply form tags to contact
|
|
108
|
+
if (form && Array.isArray(form.tags) && form.tags.length > 0) {
|
|
109
|
+
// Get form tag IDs
|
|
110
|
+
const formTagIds = form.tags.map((t)=>typeof t === 'object' ? t.id : t);
|
|
111
|
+
// Fetch current contact to get up-to-date tags
|
|
112
|
+
const currentContact = await payload.findByID({
|
|
113
|
+
id: contactId,
|
|
114
|
+
collection: contactsSlug,
|
|
115
|
+
depth: 0
|
|
116
|
+
});
|
|
117
|
+
// Get existing tag IDs (normalize to IDs)
|
|
118
|
+
const existingTagIds = (currentContact.tags || []).map((t)=>typeof t === 'object' ? t.id : t);
|
|
119
|
+
// Find only new tags that don't already exist
|
|
120
|
+
const newTagIds = formTagIds.filter((tagId)=>!existingTagIds.includes(tagId));
|
|
121
|
+
if (newTagIds.length > 0) {
|
|
122
|
+
// Merge tags
|
|
123
|
+
const mergedTags = [
|
|
124
|
+
...existingTagIds,
|
|
125
|
+
...newTagIds
|
|
126
|
+
];
|
|
127
|
+
// Update contact with merged tags
|
|
128
|
+
await payload.update({
|
|
129
|
+
id: contactId,
|
|
130
|
+
collection: contactsSlug,
|
|
131
|
+
data: {
|
|
132
|
+
tags: mergedTags
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
logger.info(`Applied ${newTagIds.length} new tags to contact ${contactId}`);
|
|
136
|
+
} else {
|
|
137
|
+
logger.info(`Contact ${contactId} already has all form tags`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Return data with contact linked - no separate update needed
|
|
141
|
+
return {
|
|
142
|
+
...data,
|
|
143
|
+
contact: contactId
|
|
144
|
+
};
|
|
145
|
+
} catch (error) {
|
|
146
|
+
logger.error(error, 'Error processing form submission');
|
|
147
|
+
// Don't throw - we don't want to fail the submission
|
|
148
|
+
return data;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
//# sourceMappingURL=processFormSubmission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/collections/form-submissions/hooks/processFormSubmission.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\nimport type { MobilizehubPluginConfig } from '../../../types/index.js'\n\n/**\n * Contact field mapping from form submission data to contact fields.\n */\nconst CONTACT_FIELD_MAP = [\n 'email',\n 'emailOptIn',\n 'firstName',\n 'lastName',\n 'mobileNumber',\n 'mobileOptIn',\n 'address',\n 'city',\n 'state',\n 'zip',\n 'country',\n] as const\n\ntype ContactFieldName = (typeof CONTACT_FIELD_MAP)[number]\n\n/**\n * Extracts contact-related fields from form submission data.\n */\nfunction extractContactData(\n submissionData: Record<string, unknown>,\n): Partial<Record<ContactFieldName, unknown>> {\n const contactData: Partial<Record<ContactFieldName, unknown>> = {}\n\n for (const field of CONTACT_FIELD_MAP) {\n if (submissionData[field] !== undefined) {\n contactData[field] = submissionData[field]\n }\n }\n\n return contactData\n}\n\n/**\n * Creates the form submission processing hook.\n *\n * This hook:\n * 1. Creates or updates a contact based on submission data\n * 2. Applies form tags to the contact\n * 3. Links the submission to the contact by returning modified data\n */\nexport const createProcessFormSubmissionHook = (\n pluginConfig: MobilizehubPluginConfig,\n): CollectionBeforeChangeHook => {\n const contactsSlug = pluginConfig.contactsOverrides?.slug || 'contacts'\n const formsSlug = pluginConfig.formsOverrides?.slug || 'forms'\n\n return async ({ data, operation, req }) => {\n // Only process on creation\n if (operation !== 'create') {\n return data\n }\n\n const { payload } = req\n const logger = payload.logger\n\n // Parse submission data\n const submissionData = data.data as Record<string, unknown> | undefined\n\n if (!submissionData) {\n logger.warn('Form submission has no data')\n return data\n }\n\n // Email is required for contact creation\n const email = submissionData.email as string | undefined\n\n if (!email || typeof email !== 'string') {\n logger.info('Form submission has no email, skipping contact creation')\n return data\n }\n\n try {\n // Extract contact fields from submission\n const contactData = extractContactData(submissionData)\n\n // Find or create contact\n const existingContactResult = await payload.find({\n collection: contactsSlug,\n limit: 1,\n where: { email: { equals: email } },\n })\n\n const existingContact = existingContactResult.docs[0] as\n | { id: number | string; tags?: (number | string)[] }\n | undefined\n\n let contactId: number | string\n\n if (existingContact) {\n // Update existing contact (merge data, don't overwrite with empty values)\n const updateData: Record<string, unknown> = {}\n\n for (const [key, value] of Object.entries(contactData)) {\n if (value !== undefined && value !== null && value !== '') {\n updateData[key] = value\n }\n }\n\n await payload.update({\n id: existingContact.id,\n collection: contactsSlug,\n data: updateData,\n })\n\n contactId = existingContact.id\n logger.info(`Updated contact ${contactId} from form submission`)\n } else {\n // Create new contact\n const newContact = await payload.create({\n collection: contactsSlug,\n data: contactData,\n })\n\n contactId = newContact.id\n logger.info(`Created contact ${contactId} from form submission`)\n }\n\n const formId = data.form as { id: number | string } | number | string\n const formIdValue = typeof formId === 'object' ? formId.id : formId\n\n if (!formIdValue) {\n logger.error('Form ID not found for form submission')\n throw new Error('Form ID not found for form submission')\n }\n\n // Get form to check for tags\n const form = await payload.findByID({\n id: formIdValue,\n collection: formsSlug,\n depth: 0,\n })\n\n // Apply form tags to contact\n if (form && Array.isArray(form.tags) && form.tags.length > 0) {\n // Get form tag IDs\n const formTagIds = form.tags.map((t: { id: number | string } | number | string) =>\n typeof t === 'object' ? t.id : t,\n )\n\n // Fetch current contact to get up-to-date tags\n const currentContact = await payload.findByID({\n id: contactId,\n collection: contactsSlug,\n depth: 0,\n })\n\n // Get existing tag IDs (normalize to IDs)\n const existingTagIds = (\n (currentContact.tags as ({ id: number | string } | number | string)[]) || []\n ).map((t) => (typeof t === 'object' ? t.id : t))\n\n // Find only new tags that don't already exist\n const newTagIds = formTagIds.filter(\n (tagId: number | string) => !existingTagIds.includes(tagId),\n )\n\n if (newTagIds.length > 0) {\n // Merge tags\n const mergedTags = [...existingTagIds, ...newTagIds]\n\n // Update contact with merged tags\n await payload.update({\n id: contactId,\n collection: contactsSlug,\n data: { tags: mergedTags },\n })\n\n logger.info(`Applied ${newTagIds.length} new tags to contact ${contactId}`)\n } else {\n logger.info(`Contact ${contactId} already has all form tags`)\n }\n }\n\n // Return data with contact linked - no separate update needed\n return {\n ...data,\n contact: contactId,\n }\n } catch (error) {\n logger.error(error as Error, 'Error processing form submission')\n // Don't throw - we don't want to fail the submission\n return data\n }\n }\n}\n"],"names":["CONTACT_FIELD_MAP","extractContactData","submissionData","contactData","field","undefined","createProcessFormSubmissionHook","pluginConfig","contactsSlug","contactsOverrides","slug","formsSlug","formsOverrides","data","operation","req","payload","logger","warn","email","info","existingContactResult","find","collection","limit","where","equals","existingContact","docs","contactId","updateData","key","value","Object","entries","update","id","newContact","create","formId","form","formIdValue","error","Error","findByID","depth","Array","isArray","tags","length","formTagIds","map","t","currentContact","existingTagIds","newTagIds","filter","tagId","includes","mergedTags","contact"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CollectionAfterChangeHook } from 'payload';
|
|
2
|
+
import type { MobilizehubPluginConfig } from '../../../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates the autoresponse email hook for form submissions.
|
|
5
|
+
*
|
|
6
|
+
* This hook sends an automatic confirmation email to the form submitter
|
|
7
|
+
* when autoresponse is enabled on the form. It:
|
|
8
|
+
* 1. Checks if autoresponse is enabled on the form
|
|
9
|
+
* 2. Validates required autoresponse fields
|
|
10
|
+
* 3. Parses Lexical content to HTML
|
|
11
|
+
* 4. Renders through email template
|
|
12
|
+
* 5. Sends the email
|
|
13
|
+
*
|
|
14
|
+
* Errors are logged but never thrown to avoid failing the submission.
|
|
15
|
+
*/
|
|
16
|
+
export declare const createSendAutoresponseHook: (pluginConfig: MobilizehubPluginConfig) => CollectionAfterChangeHook;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { formatFromAddress } from '../../../utils/email.js';
|
|
2
|
+
import { parseLexicalContent } from '../../../utils/lexical.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates the autoresponse email hook for form submissions.
|
|
5
|
+
*
|
|
6
|
+
* This hook sends an automatic confirmation email to the form submitter
|
|
7
|
+
* when autoresponse is enabled on the form. It:
|
|
8
|
+
* 1. Checks if autoresponse is enabled on the form
|
|
9
|
+
* 2. Validates required autoresponse fields
|
|
10
|
+
* 3. Parses Lexical content to HTML
|
|
11
|
+
* 4. Renders through email template
|
|
12
|
+
* 5. Sends the email
|
|
13
|
+
*
|
|
14
|
+
* Errors are logged but never thrown to avoid failing the submission.
|
|
15
|
+
*/ export const createSendAutoresponseHook = (pluginConfig)=>{
|
|
16
|
+
const formsSlug = pluginConfig.formsOverrides?.slug || 'forms';
|
|
17
|
+
const contactsSlug = pluginConfig.contactsOverrides?.slug || 'contacts';
|
|
18
|
+
return async ({ doc, operation, req })=>{
|
|
19
|
+
// Only process on create
|
|
20
|
+
if (operation !== 'create') {
|
|
21
|
+
return doc;
|
|
22
|
+
}
|
|
23
|
+
const { payload } = req;
|
|
24
|
+
const logger = payload.logger;
|
|
25
|
+
try {
|
|
26
|
+
// Get form ID from submission
|
|
27
|
+
const formId = doc.form;
|
|
28
|
+
const formIdValue = typeof formId === 'object' ? formId.id : formId;
|
|
29
|
+
if (!formIdValue) {
|
|
30
|
+
return doc;
|
|
31
|
+
}
|
|
32
|
+
// Fetch form to get autoresponse configuration
|
|
33
|
+
const form = await payload.findByID({
|
|
34
|
+
id: formIdValue,
|
|
35
|
+
collection: formsSlug
|
|
36
|
+
});
|
|
37
|
+
if (!form) {
|
|
38
|
+
return doc;
|
|
39
|
+
}
|
|
40
|
+
// Check if autoresponse is enabled
|
|
41
|
+
const autoresponse = form.autoresponse;
|
|
42
|
+
if (!autoresponse?.enabled) {
|
|
43
|
+
return doc;
|
|
44
|
+
}
|
|
45
|
+
// Get contact email
|
|
46
|
+
const contactId = doc.contact;
|
|
47
|
+
if (!contactId) {
|
|
48
|
+
logger.warn('Form submission has no linked contact, skipping autoresponse');
|
|
49
|
+
return doc;
|
|
50
|
+
}
|
|
51
|
+
const contactIdValue = typeof contactId === 'object' ? contactId.id : contactId;
|
|
52
|
+
const contact = await payload.findByID({
|
|
53
|
+
id: contactIdValue,
|
|
54
|
+
collection: contactsSlug
|
|
55
|
+
});
|
|
56
|
+
const contactEmail = contact?.email;
|
|
57
|
+
if (!contactEmail) {
|
|
58
|
+
logger.warn(`Contact ${contactIdValue} has no email, skipping autoresponse`);
|
|
59
|
+
return doc;
|
|
60
|
+
}
|
|
61
|
+
// Validate required autoresponse fields
|
|
62
|
+
const { content, fromAddress, fromName, previewText, replyTo, subject } = autoresponse;
|
|
63
|
+
if (!subject) {
|
|
64
|
+
logger.warn(`Form ${formIdValue} autoresponse has no subject, skipping`);
|
|
65
|
+
return doc;
|
|
66
|
+
}
|
|
67
|
+
if (!content) {
|
|
68
|
+
logger.warn(`Form ${formIdValue} autoresponse has no content, skipping`);
|
|
69
|
+
return doc;
|
|
70
|
+
}
|
|
71
|
+
if (!fromName || !fromAddress) {
|
|
72
|
+
logger.warn(`Form ${formIdValue} autoresponse has no from address, skipping`);
|
|
73
|
+
return doc;
|
|
74
|
+
}
|
|
75
|
+
// Get email adapter
|
|
76
|
+
const { render, sendEmail } = pluginConfig.email(req);
|
|
77
|
+
// Parse Lexical content
|
|
78
|
+
const parsedContent = await parseLexicalContent(content, payload.config);
|
|
79
|
+
// Format from address
|
|
80
|
+
const formattedFromAddress = formatFromAddress(fromName, fromAddress);
|
|
81
|
+
// Render through email template
|
|
82
|
+
const html = render({
|
|
83
|
+
from: formattedFromAddress,
|
|
84
|
+
html: parsedContent.html,
|
|
85
|
+
markdown: parsedContent.markdown,
|
|
86
|
+
plainText: parsedContent.plainText,
|
|
87
|
+
previewText,
|
|
88
|
+
replyTo,
|
|
89
|
+
subject,
|
|
90
|
+
to: contactEmail,
|
|
91
|
+
token: ''
|
|
92
|
+
});
|
|
93
|
+
// Send the email
|
|
94
|
+
await sendEmail({
|
|
95
|
+
from: formattedFromAddress,
|
|
96
|
+
html,
|
|
97
|
+
subject,
|
|
98
|
+
to: contactEmail
|
|
99
|
+
});
|
|
100
|
+
logger.info(`Sent autoresponse to ${contactEmail} for form ${formIdValue}`);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
logger.error(error, 'Error sending autoresponse email');
|
|
103
|
+
// Don't throw - submission should still succeed
|
|
104
|
+
}
|
|
105
|
+
return doc;
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=sendAutoresponse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/collections/form-submissions/hooks/sendAutoresponse.ts"],"sourcesContent":["import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'\nimport type { CollectionAfterChangeHook } from 'payload'\n\nimport type { MobilizehubPluginConfig } from '../../../types/index.js'\n\nimport { formatFromAddress } from '../../../utils/email.js'\nimport { parseLexicalContent } from '../../../utils/lexical.js'\n\n/**\n * Autoresponse configuration from form.\n */\ntype AutoresponseConfig = {\n content?: SerializedEditorState\n enabled?: boolean\n fromAddress?: string\n fromName?: string\n previewText?: string\n replyTo?: string\n subject?: string\n}\n\n/**\n * Creates the autoresponse email hook for form submissions.\n *\n * This hook sends an automatic confirmation email to the form submitter\n * when autoresponse is enabled on the form. It:\n * 1. Checks if autoresponse is enabled on the form\n * 2. Validates required autoresponse fields\n * 3. Parses Lexical content to HTML\n * 4. Renders through email template\n * 5. Sends the email\n *\n * Errors are logged but never thrown to avoid failing the submission.\n */\nexport const createSendAutoresponseHook = (\n pluginConfig: MobilizehubPluginConfig,\n): CollectionAfterChangeHook => {\n const formsSlug = pluginConfig.formsOverrides?.slug || 'forms'\n const contactsSlug = pluginConfig.contactsOverrides?.slug || 'contacts'\n\n return async ({ doc, operation, req }) => {\n // Only process on create\n if (operation !== 'create') {\n return doc\n }\n\n const { payload } = req\n const logger = payload.logger\n\n try {\n // Get form ID from submission\n const formId = doc.form as { id: number | string } | number | string\n const formIdValue = typeof formId === 'object' ? formId.id : formId\n\n if (!formIdValue) {\n return doc\n }\n\n // Fetch form to get autoresponse configuration\n const form = await payload.findByID({\n id: formIdValue,\n collection: formsSlug,\n })\n\n if (!form) {\n return doc\n }\n\n // Check if autoresponse is enabled\n const autoresponse = form.autoresponse as AutoresponseConfig | undefined\n\n if (!autoresponse?.enabled) {\n return doc\n }\n\n // Get contact email\n const contactId = doc.contact as { id: number | string } | number | string | undefined\n\n if (!contactId) {\n logger.warn('Form submission has no linked contact, skipping autoresponse')\n return doc\n }\n\n const contactIdValue = typeof contactId === 'object' ? contactId.id : contactId\n\n const contact = await payload.findByID({\n id: contactIdValue,\n collection: contactsSlug,\n })\n\n const contactEmail = contact?.email as string | undefined\n\n if (!contactEmail) {\n logger.warn(`Contact ${contactIdValue} has no email, skipping autoresponse`)\n return doc\n }\n\n // Validate required autoresponse fields\n const { content, fromAddress, fromName, previewText, replyTo, subject } = autoresponse\n\n if (!subject) {\n logger.warn(`Form ${formIdValue} autoresponse has no subject, skipping`)\n return doc\n }\n\n if (!content) {\n logger.warn(`Form ${formIdValue} autoresponse has no content, skipping`)\n return doc\n }\n\n if (!fromName || !fromAddress) {\n logger.warn(`Form ${formIdValue} autoresponse has no from address, skipping`)\n return doc\n }\n\n // Get email adapter\n const { render, sendEmail } = pluginConfig.email(req)\n\n // Parse Lexical content\n const parsedContent = await parseLexicalContent(content, payload.config)\n\n // Format from address\n const formattedFromAddress = formatFromAddress(fromName, fromAddress)\n\n // Render through email template\n const html = render({\n from: formattedFromAddress,\n html: parsedContent.html,\n markdown: parsedContent.markdown,\n plainText: parsedContent.plainText,\n previewText,\n replyTo,\n subject,\n to: contactEmail,\n token: '', // No unsubscribe token for autoresponse\n })\n\n // Send the email\n await sendEmail({\n from: formattedFromAddress,\n html,\n subject,\n to: contactEmail,\n })\n\n logger.info(`Sent autoresponse to ${contactEmail} for form ${formIdValue}`)\n } catch (error) {\n logger.error(error as Error, 'Error sending autoresponse email')\n // Don't throw - submission should still succeed\n }\n\n return doc\n }\n}\n"],"names":["formatFromAddress","parseLexicalContent","createSendAutoresponseHook","pluginConfig","formsSlug","formsOverrides","slug","contactsSlug","contactsOverrides","doc","operation","req","payload","logger","formId","form","formIdValue","id","findByID","collection","autoresponse","enabled","contactId","contact","warn","contactIdValue","contactEmail","email","content","fromAddress","fromName","previewText","replyTo","subject","render","sendEmail","parsedContent","config","formattedFromAddress","html","from","markdown","plainText","to","token","info","error"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { createPublishedAtField } from '../../fields/publishedAt.js';
|
|
|
3
3
|
import { createSlugField } from '../../fields/slug.js';
|
|
4
4
|
import { createStatusField } from '../../fields/status.js';
|
|
5
5
|
export const generatePagesCollection = (pagesConfig)=>{
|
|
6
|
+
const { blocks: blocksOverride, ...pagesOverrides } = pagesConfig.pagesOverrides || {};
|
|
6
7
|
const defaultBlocks = [
|
|
7
8
|
{
|
|
8
9
|
slug: 'content',
|
|
@@ -16,7 +17,7 @@ export const generatePagesCollection = (pagesConfig)=>{
|
|
|
16
17
|
interfaceName: 'ContentBlock'
|
|
17
18
|
}
|
|
18
19
|
];
|
|
19
|
-
const blocks =
|
|
20
|
+
const blocks = blocksOverride ? blocksOverride({
|
|
20
21
|
defaultBlocks
|
|
21
22
|
}) : defaultBlocks;
|
|
22
23
|
const defaultFields = [
|
|
@@ -47,28 +48,28 @@ export const generatePagesCollection = (pagesConfig)=>{
|
|
|
47
48
|
}
|
|
48
49
|
];
|
|
49
50
|
const config = {
|
|
50
|
-
...
|
|
51
|
-
slug:
|
|
51
|
+
...pagesOverrides,
|
|
52
|
+
slug: pagesOverrides.slug || 'pages',
|
|
52
53
|
access: {
|
|
53
54
|
read: ()=>true,
|
|
54
|
-
...
|
|
55
|
+
...pagesOverrides.access || {}
|
|
55
56
|
},
|
|
56
57
|
admin: {
|
|
57
|
-
...
|
|
58
|
-
defaultColumns:
|
|
58
|
+
...pagesOverrides.admin || {},
|
|
59
|
+
defaultColumns: pagesOverrides.admin?.defaultColumns || [
|
|
59
60
|
'id',
|
|
60
61
|
'name',
|
|
61
62
|
'slug',
|
|
62
63
|
'status'
|
|
63
64
|
],
|
|
64
|
-
hidden:
|
|
65
|
-
useAsTitle:
|
|
65
|
+
hidden: pagesOverrides.admin?.hidden || false,
|
|
66
|
+
useAsTitle: pagesOverrides.admin?.useAsTitle || 'name'
|
|
66
67
|
},
|
|
67
|
-
fields:
|
|
68
|
+
fields: pagesOverrides.fields ? pagesOverrides.fields({
|
|
68
69
|
defaultFields
|
|
69
70
|
}) : defaultFields,
|
|
70
71
|
hooks: {
|
|
71
|
-
...
|
|
72
|
+
...pagesOverrides.hooks || {}
|
|
72
73
|
}
|
|
73
74
|
};
|
|
74
75
|
return config;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/collections/pages/generatePagesCollection.ts"],"sourcesContent":["import type { Block, CollectionConfig, Field } from 'payload'\n\nimport type { MobilizehubPluginConfig } from '../../types/index.js'\n\nimport { createNameField } from '../../fields/name.js'\nimport { createPublishedAtField } from '../../fields/publishedAt.js'\nimport { createSlugField } from '../../fields/slug.js'\nimport { createStatusField } from '../../fields/status.js'\n\nexport const generatePagesCollection = (pagesConfig: MobilizehubPluginConfig) => {\n const defaultBlocks: Block[] = [\n {\n slug: 'content',\n fields: [\n {\n name: 'richText',\n type: 'richText',\n label: false,\n },\n ],\n interfaceName: 'ContentBlock',\n },\n ]\n\n const blocks =
|
|
1
|
+
{"version":3,"sources":["../../../src/collections/pages/generatePagesCollection.ts"],"sourcesContent":["import type { Block, CollectionConfig, Field } from 'payload'\n\nimport type { MobilizehubPluginConfig } from '../../types/index.js'\n\nimport { createNameField } from '../../fields/name.js'\nimport { createPublishedAtField } from '../../fields/publishedAt.js'\nimport { createSlugField } from '../../fields/slug.js'\nimport { createStatusField } from '../../fields/status.js'\n\nexport const generatePagesCollection = (pagesConfig: MobilizehubPluginConfig) => {\n const { blocks: blocksOverride, ...pagesOverrides } = pagesConfig.pagesOverrides || {}\n\n const defaultBlocks: Block[] = [\n {\n slug: 'content',\n fields: [\n {\n name: 'richText',\n type: 'richText',\n label: false,\n },\n ],\n interfaceName: 'ContentBlock',\n },\n ]\n\n const blocks = blocksOverride ? blocksOverride({ defaultBlocks }) : defaultBlocks\n\n const defaultFields: Field[] = [\n createStatusField(),\n {\n type: 'tabs',\n tabs: [\n {\n fields: [createNameField(), createSlugField(), createPublishedAtField()],\n label: 'Settings',\n },\n {\n fields: [\n {\n name: 'blocks',\n type: 'blocks',\n blocks,\n label: 'Blocks',\n },\n ],\n label: 'Content',\n },\n ],\n },\n ]\n\n const config: CollectionConfig = {\n ...pagesOverrides,\n slug: pagesOverrides.slug || 'pages',\n access: {\n read: () => true,\n ...(pagesOverrides.access || {}),\n },\n admin: {\n ...(pagesOverrides.admin || {}),\n defaultColumns: pagesOverrides.admin?.defaultColumns || ['id', 'name', 'slug', 'status'],\n hidden: pagesOverrides.admin?.hidden || false,\n useAsTitle: pagesOverrides.admin?.useAsTitle || 'name',\n },\n fields: pagesOverrides.fields ? pagesOverrides.fields({ defaultFields }) : defaultFields,\n hooks: {\n ...(pagesOverrides.hooks || {}),\n },\n }\n\n return config\n}\n"],"names":["createNameField","createPublishedAtField","createSlugField","createStatusField","generatePagesCollection","pagesConfig","blocks","blocksOverride","pagesOverrides","defaultBlocks","slug","fields","name","type","label","interfaceName","defaultFields","tabs","config","access","read","admin","defaultColumns","hidden","useAsTitle","hooks"],"mappings":"AAIA,SAASA,eAAe,QAAQ,uBAAsB;AACtD,SAASC,sBAAsB,QAAQ,8BAA6B;AACpE,SAASC,eAAe,QAAQ,uBAAsB;AACtD,SAASC,iBAAiB,QAAQ,yBAAwB;AAE1D,OAAO,MAAMC,0BAA0B,CAACC;IACtC,MAAM,EAAEC,QAAQC,cAAc,EAAE,GAAGC,gBAAgB,GAAGH,YAAYG,cAAc,IAAI,CAAC;IAErF,MAAMC,gBAAyB;QAC7B;YACEC,MAAM;YACNC,QAAQ;gBACN;oBACEC,MAAM;oBACNC,MAAM;oBACNC,OAAO;gBACT;aACD;YACDC,eAAe;QACjB;KACD;IAED,MAAMT,SAASC,iBAAiBA,eAAe;QAAEE;IAAc,KAAKA;IAEpE,MAAMO,gBAAyB;QAC7Bb;QACA;YACEU,MAAM;YACNI,MAAM;gBACJ;oBACEN,QAAQ;wBAACX;wBAAmBE;wBAAmBD;qBAAyB;oBACxEa,OAAO;gBACT;gBACA;oBACEH,QAAQ;wBACN;4BACEC,MAAM;4BACNC,MAAM;4BACNP;4BACAQ,OAAO;wBACT;qBACD;oBACDA,OAAO;gBACT;aACD;QACH;KACD;IAED,MAAMI,SAA2B;QAC/B,GAAGV,cAAc;QACjBE,MAAMF,eAAeE,IAAI,IAAI;QAC7BS,QAAQ;YACNC,MAAM,IAAM;YACZ,GAAIZ,eAAeW,MAAM,IAAI,CAAC,CAAC;QACjC;QACAE,OAAO;YACL,GAAIb,eAAea,KAAK,IAAI,CAAC,CAAC;YAC9BC,gBAAgBd,eAAea,KAAK,EAAEC,kBAAkB;gBAAC;gBAAM;gBAAQ;gBAAQ;aAAS;YACxFC,QAAQf,eAAea,KAAK,EAAEE,UAAU;YACxCC,YAAYhB,eAAea,KAAK,EAAEG,cAAc;QAClD;QACAb,QAAQH,eAAeG,MAAM,GAAGH,eAAeG,MAAM,CAAC;YAAEK;QAAc,KAAKA;QAC3ES,OAAO;YACL,GAAIjB,eAAeiB,KAAK,IAAI,CAAC,CAAC;QAChC;IACF;IAEA,OAAOP;AACT,EAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PayloadHandler } from 'payload';
|
|
2
|
+
import type { MobilizehubPluginConfig } from '../types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates the public form submission endpoint handler.
|
|
5
|
+
*
|
|
6
|
+
* Accepts form submissions from frontend applications, validates the data,
|
|
7
|
+
* creates a form submission record, and returns the appropriate confirmation.
|
|
8
|
+
*
|
|
9
|
+
* This endpoint is public (no authentication required) but validates:
|
|
10
|
+
* - Form exists and is published
|
|
11
|
+
* - Required fields are present
|
|
12
|
+
* - Email format is valid (if provided)
|
|
13
|
+
*/
|
|
14
|
+
export declare const formSubmissionHandler: (pluginConfig: MobilizehubPluginConfig) => PayloadHandler;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import { ErrorCodes, errorResponse, successResponse } from '../utils/api-response.js';
|
|
3
|
+
/**
|
|
4
|
+
* Schema for form submission request body.
|
|
5
|
+
*/ const FormSubmissionBodySchema = z.object({
|
|
6
|
+
data: z.record(z.string(), z.unknown()),
|
|
7
|
+
formId: z.union([
|
|
8
|
+
z.string(),
|
|
9
|
+
z.number()
|
|
10
|
+
])
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Validates submission data against form field configuration.
|
|
14
|
+
*/ function validateSubmissionData(data, contactFields) {
|
|
15
|
+
const errors = [];
|
|
16
|
+
if (!contactFields || contactFields.length === 0) {
|
|
17
|
+
return {
|
|
18
|
+
errors: [],
|
|
19
|
+
valid: true
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
for (const field of contactFields){
|
|
23
|
+
if (field.required) {
|
|
24
|
+
const value = data[field.blockType];
|
|
25
|
+
if (value === undefined || value === null || value === '') {
|
|
26
|
+
errors.push(`${field.blockType} is required`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Validate email format if present
|
|
31
|
+
if (data.email && typeof data.email === 'string') {
|
|
32
|
+
const emailRegex = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/;
|
|
33
|
+
if (!emailRegex.test(data.email)) {
|
|
34
|
+
errors.push('Invalid email format');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
errors,
|
|
39
|
+
valid: errors.length === 0
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Fetches and validates a form exists and is published.
|
|
44
|
+
*/ async function getPublishedForm(payload, formId, collectionSlug) {
|
|
45
|
+
try {
|
|
46
|
+
const form = await payload.findByID({
|
|
47
|
+
id: formId,
|
|
48
|
+
collection: collectionSlug
|
|
49
|
+
});
|
|
50
|
+
if (!form) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
// Only allow submissions to published forms
|
|
54
|
+
if (form.status !== 'published') {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return form;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Builds the confirmation response based on form settings.
|
|
64
|
+
*/ function buildConfirmationResponse(form) {
|
|
65
|
+
if (form.confirmationType === 'redirect' && form.reference) {
|
|
66
|
+
let redirectUrl;
|
|
67
|
+
if (form.url) {
|
|
68
|
+
redirectUrl = form.url;
|
|
69
|
+
} else if (typeof form.reference.value === 'object' && form.reference.value.slug) {
|
|
70
|
+
redirectUrl = `/${form.reference.value.slug}`;
|
|
71
|
+
}
|
|
72
|
+
if (redirectUrl) {
|
|
73
|
+
return {
|
|
74
|
+
type: 'redirect',
|
|
75
|
+
redirect: redirectUrl
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
type: 'message',
|
|
81
|
+
message: form.confirmationMessage || 'Thank you for your submission.'
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Creates the public form submission endpoint handler.
|
|
86
|
+
*
|
|
87
|
+
* Accepts form submissions from frontend applications, validates the data,
|
|
88
|
+
* creates a form submission record, and returns the appropriate confirmation.
|
|
89
|
+
*
|
|
90
|
+
* This endpoint is public (no authentication required) but validates:
|
|
91
|
+
* - Form exists and is published
|
|
92
|
+
* - Required fields are present
|
|
93
|
+
* - Email format is valid (if provided)
|
|
94
|
+
*/ export const formSubmissionHandler = (pluginConfig)=>{
|
|
95
|
+
const formsSlug = pluginConfig.formsOverrides?.slug || 'forms';
|
|
96
|
+
const formSubmissionsSlug = pluginConfig.formSubmissionsOverrides?.slug || 'formSubmissions';
|
|
97
|
+
return async (req)=>{
|
|
98
|
+
const { payload } = req;
|
|
99
|
+
const logger = payload.logger;
|
|
100
|
+
if (!req.json) {
|
|
101
|
+
return errorResponse(ErrorCodes.BAD_REQUEST, 'No JSON body provided', 400);
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const body = await req.json();
|
|
105
|
+
// Validate request body structure
|
|
106
|
+
const parseResult = FormSubmissionBodySchema.safeParse(body);
|
|
107
|
+
if (!parseResult.success) {
|
|
108
|
+
const firstError = parseResult.error.issues[0]?.message || 'Invalid request body';
|
|
109
|
+
return errorResponse(ErrorCodes.VALIDATION_ERROR, firstError, 400);
|
|
110
|
+
}
|
|
111
|
+
const { data, formId } = parseResult.data;
|
|
112
|
+
// Fetch and validate form
|
|
113
|
+
const form = await getPublishedForm(payload, formId, formsSlug);
|
|
114
|
+
if (!form) {
|
|
115
|
+
return errorResponse(ErrorCodes.NOT_FOUND, 'Form not found or not published', 404);
|
|
116
|
+
}
|
|
117
|
+
// Validate submission data against form fields
|
|
118
|
+
const validation = validateSubmissionData(data, form.contactFields);
|
|
119
|
+
if (!validation.valid) {
|
|
120
|
+
return errorResponse(ErrorCodes.VALIDATION_ERROR, validation.errors.join(', '), 400);
|
|
121
|
+
}
|
|
122
|
+
// Create the form submission
|
|
123
|
+
// Note: The beforeChange hook will handle contact creation/update
|
|
124
|
+
const submission = await payload.create({
|
|
125
|
+
collection: formSubmissionsSlug,
|
|
126
|
+
data: {
|
|
127
|
+
data,
|
|
128
|
+
form: form.id
|
|
129
|
+
},
|
|
130
|
+
// Use internal context to bypass access control
|
|
131
|
+
overrideAccess: true
|
|
132
|
+
});
|
|
133
|
+
// Build confirmation response
|
|
134
|
+
const confirmation = buildConfirmationResponse(form);
|
|
135
|
+
return successResponse({
|
|
136
|
+
confirmation,
|
|
137
|
+
submissionId: submission.id
|
|
138
|
+
}, 201);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
logger.error(error, 'Error processing form submission');
|
|
141
|
+
return errorResponse(ErrorCodes.INTERNAL_ERROR, error instanceof Error ? error.message : 'Failed to process submission', 500);
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
//# sourceMappingURL=formSubmissionHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/endpoints/formSubmissionHandler.ts"],"sourcesContent":["import type { CollectionSlug, Payload, PayloadHandler } from 'payload'\n\nimport z from 'zod'\n\nimport type { MobilizehubPluginConfig } from '../types/index.js'\n\nimport { ErrorCodes, errorResponse, successResponse } from '../utils/api-response.js'\n\n/**\n * Schema for form submission request body.\n */\nconst FormSubmissionBodySchema = z.object({\n data: z.record(z.string(), z.unknown()),\n formId: z.union([z.string(), z.number()]),\n})\n\n/**\n * Form type for validation.\n */\ntype FormDocument = {\n confirmationMessage?: unknown\n confirmationType?: 'message' | 'redirect'\n contactFields?: Array<{\n blockType: string\n required?: boolean\n }>\n id: number | string\n reference?: {\n relationTo: 'forms' | 'pages'\n value: { slug?: string } | number | string\n }\n status?: 'draft' | 'published'\n url?: string\n}\n\n/**\n * Validates submission data against form field configuration.\n */\nfunction validateSubmissionData(\n data: Record<string, unknown>,\n contactFields: FormDocument['contactFields'],\n): { errors: string[]; valid: boolean } {\n const errors: string[] = []\n\n if (!contactFields || contactFields.length === 0) {\n return { errors: [], valid: true }\n }\n\n for (const field of contactFields) {\n if (field.required) {\n const value = data[field.blockType]\n if (value === undefined || value === null || value === '') {\n errors.push(`${field.blockType} is required`)\n }\n }\n }\n\n // Validate email format if present\n if (data.email && typeof data.email === 'string') {\n const emailRegex = /^[^\\s@]+@[^\\s@][^\\s.@]*\\.[^\\s@]+$/\n if (!emailRegex.test(data.email)) {\n errors.push('Invalid email format')\n }\n }\n\n return {\n errors,\n valid: errors.length === 0,\n }\n}\n\n/**\n * Fetches and validates a form exists and is published.\n */\nasync function getPublishedForm(\n payload: Payload,\n formId: number | string,\n collectionSlug: CollectionSlug,\n): Promise<FormDocument | null> {\n try {\n const form = await payload.findByID({\n id: formId,\n collection: collectionSlug,\n })\n\n if (!form) {\n return null\n }\n\n // Only allow submissions to published forms\n if ((form as FormDocument).status !== 'published') {\n return null\n }\n\n return form as FormDocument\n } catch {\n return null\n }\n}\n\n/**\n * Builds the confirmation response based on form settings.\n */\nfunction buildConfirmationResponse(form: FormDocument): {\n message?: unknown\n redirect?: string\n type: 'message' | 'redirect'\n} {\n if (form.confirmationType === 'redirect' && form.reference) {\n let redirectUrl: string | undefined\n\n if (form.url) {\n redirectUrl = form.url\n } else if (typeof form.reference.value === 'object' && form.reference.value.slug) {\n redirectUrl = `/${form.reference.value.slug}`\n }\n\n if (redirectUrl) {\n return { type: 'redirect', redirect: redirectUrl }\n }\n }\n\n return {\n type: 'message',\n message: form.confirmationMessage || 'Thank you for your submission.',\n }\n}\n\n/**\n * Creates the public form submission endpoint handler.\n *\n * Accepts form submissions from frontend applications, validates the data,\n * creates a form submission record, and returns the appropriate confirmation.\n *\n * This endpoint is public (no authentication required) but validates:\n * - Form exists and is published\n * - Required fields are present\n * - Email format is valid (if provided)\n */\nexport const formSubmissionHandler = (\n pluginConfig: MobilizehubPluginConfig,\n): PayloadHandler => {\n const formsSlug = pluginConfig.formsOverrides?.slug || 'forms'\n const formSubmissionsSlug = pluginConfig.formSubmissionsOverrides?.slug || 'formSubmissions'\n\n return async (req) => {\n const { payload } = req\n const logger = payload.logger\n\n if (!req.json) {\n return errorResponse(ErrorCodes.BAD_REQUEST, 'No JSON body provided', 400)\n }\n\n try {\n const body = await req.json()\n\n // Validate request body structure\n const parseResult = FormSubmissionBodySchema.safeParse(body)\n\n if (!parseResult.success) {\n const firstError = parseResult.error.issues[0]?.message || 'Invalid request body'\n return errorResponse(ErrorCodes.VALIDATION_ERROR, firstError, 400)\n }\n\n const { data, formId } = parseResult.data\n\n // Fetch and validate form\n const form = await getPublishedForm(payload, formId, formsSlug)\n\n if (!form) {\n return errorResponse(ErrorCodes.NOT_FOUND, 'Form not found or not published', 404)\n }\n\n // Validate submission data against form fields\n const validation = validateSubmissionData(data, form.contactFields)\n\n if (!validation.valid) {\n return errorResponse(ErrorCodes.VALIDATION_ERROR, validation.errors.join(', '), 400)\n }\n\n // Create the form submission\n // Note: The beforeChange hook will handle contact creation/update\n const submission = await payload.create({\n collection: formSubmissionsSlug,\n data: {\n data,\n form: form.id as number,\n },\n // Use internal context to bypass access control\n overrideAccess: true,\n })\n\n // Build confirmation response\n const confirmation = buildConfirmationResponse(form)\n\n return successResponse(\n {\n confirmation,\n submissionId: submission.id,\n },\n 201,\n )\n } catch (error) {\n logger.error(error as Error, 'Error processing form submission')\n return errorResponse(\n ErrorCodes.INTERNAL_ERROR,\n error instanceof Error ? error.message : 'Failed to process submission',\n 500,\n )\n }\n }\n}\n"],"names":["z","ErrorCodes","errorResponse","successResponse","FormSubmissionBodySchema","object","data","record","string","unknown","formId","union","number","validateSubmissionData","contactFields","errors","length","valid","field","required","value","blockType","undefined","push","email","emailRegex","test","getPublishedForm","payload","collectionSlug","form","findByID","id","collection","status","buildConfirmationResponse","confirmationType","reference","redirectUrl","url","slug","type","redirect","message","confirmationMessage","formSubmissionHandler","pluginConfig","formsSlug","formsOverrides","formSubmissionsSlug","formSubmissionsOverrides","req","logger","json","BAD_REQUEST","body","parseResult","safeParse","success","firstError","error","issues","VALIDATION_ERROR","NOT_FOUND","validation","join","submission","create","overrideAccess","confirmation","submissionId","INTERNAL_ERROR","Error"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { generatePagesCollection } from './collections/pages/generatePagesCollec
|
|
|
7
7
|
import { generateTagsCollection } from './collections/tags/generateTagsCollection.js';
|
|
8
8
|
import { generateUnsubscribeTokensCollection } from './collections/unsubscribe-tokens/generateUnsubscribeTokens.js';
|
|
9
9
|
import { emailWebhookHandler } from './endpoints/emailWebhookHandler.js';
|
|
10
|
+
import { formSubmissionHandler } from './endpoints/formSubmissionHandler.js';
|
|
10
11
|
import { sendBroadcastHandler } from './endpoints/sendBroadcastHandler.js';
|
|
11
12
|
import { sendTestEmailHandler } from './endpoints/sendTestBroadcastHandler.js';
|
|
12
13
|
import { unsubscribeHandler } from './endpoints/unsubscribeHandler.js';
|
|
@@ -44,6 +45,11 @@ export const mobilizehubPlugin = (pluginOptions)=>(config)=>{
|
|
|
44
45
|
handler: emailWebhookHandler(pluginOptions),
|
|
45
46
|
method: 'post',
|
|
46
47
|
path: '/webhooks/email'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
handler: formSubmissionHandler(pluginOptions),
|
|
51
|
+
method: 'post',
|
|
52
|
+
path: '/forms.createSubmission'
|
|
47
53
|
}
|
|
48
54
|
];
|
|
49
55
|
config.endpoints = [
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Config, Endpoint, TaskConfig } from 'payload'\n\nimport type { MobilizehubPluginConfig } from './types/index.js'\n\nimport { generateBroadcastsCollection } from './collections/broadcasts/generateBroadcastsCollection.js'\nimport { generateContactsCollection } from './collections/contacts/generateContactsCollection.js'\nimport { generateEmailsCollection } from './collections/emails/generateEmailsCollection.js'\nimport { generateFormSubmissionsCollection } from './collections/form-submissions/generateFormSubmissionsCollection.js'\nimport { generateFormsCollection } from './collections/forms/generateFormsCollection.js'\nimport { generatePagesCollection } from './collections/pages/generatePagesCollection.js'\nimport { generateTagsCollection } from './collections/tags/generateTagsCollection.js'\nimport { generateUnsubscribeTokensCollection } from './collections/unsubscribe-tokens/generateUnsubscribeTokens.js'\nimport { emailWebhookHandler } from './endpoints/emailWebhookHandler.js'\nimport { sendBroadcastHandler } from './endpoints/sendBroadcastHandler.js'\nimport { sendTestEmailHandler } from './endpoints/sendTestBroadcastHandler.js'\nimport { unsubscribeHandler } from './endpoints/unsubscribeHandler.js'\nimport { createSendBroadcastsTask } from './tasks/sendBroadcastsTask.js'\nimport { createSendEmailTask } from './tasks/sendEmailTask.js'\n\nexport * from './types/index.js'\n\nexport const mobilizehubPlugin =\n (pluginOptions: MobilizehubPluginConfig) =>\n (config: Config): Config => {\n if (!config.collections) {\n config.collections = []\n }\n\n config.collections.push(\n generateTagsCollection(pluginOptions),\n generateContactsCollection(pluginOptions),\n generateBroadcastsCollection(pluginOptions),\n generateEmailsCollection(pluginOptions),\n generatePagesCollection(pluginOptions),\n generateUnsubscribeTokensCollection(),\n generateFormSubmissionsCollection(pluginOptions),\n generateFormsCollection(pluginOptions),\n )\n\n if (pluginOptions.disabled) {\n return config\n }\n\n if (!config.endpoints) {\n config.endpoints = []\n }\n\n const endpoints: Endpoint[] = [\n {\n handler: sendBroadcastHandler(),\n method: 'post',\n path: '/send-broadcast',\n },\n {\n handler: sendTestEmailHandler(pluginOptions),\n method: 'post',\n path: '/send-test-email',\n },\n {\n handler: unsubscribeHandler(),\n method: 'post',\n path: '/unsubscribe',\n },\n {\n handler: emailWebhookHandler(pluginOptions),\n method: 'post',\n path: '/webhooks/email',\n },\n ]\n\n config.endpoints = [...config.endpoints, ...endpoints]\n\n if (!config.jobs) {\n config.jobs = {\n tasks: [],\n }\n }\n\n const tasks: TaskConfig[] = [\n createSendBroadcastsTask(pluginOptions),\n createSendEmailTask(pluginOptions),\n ]\n\n config.jobs.tasks = [...(config.jobs.tasks ?? []), ...tasks]\n\n const incomingOnInit = config.onInit\n\n config.onInit = async (payload) => {\n if (incomingOnInit) {\n await incomingOnInit(payload)\n }\n }\n\n return config\n }\n"],"names":["generateBroadcastsCollection","generateContactsCollection","generateEmailsCollection","generateFormSubmissionsCollection","generateFormsCollection","generatePagesCollection","generateTagsCollection","generateUnsubscribeTokensCollection","emailWebhookHandler","sendBroadcastHandler","sendTestEmailHandler","unsubscribeHandler","createSendBroadcastsTask","createSendEmailTask","mobilizehubPlugin","pluginOptions","config","collections","push","disabled","endpoints","handler","method","path","jobs","tasks","incomingOnInit","onInit","payload"],"mappings":"AAIA,SAASA,4BAA4B,QAAQ,2DAA0D;AACvG,SAASC,0BAA0B,QAAQ,uDAAsD;AACjG,SAASC,wBAAwB,QAAQ,mDAAkD;AAC3F,SAASC,iCAAiC,QAAQ,sEAAqE;AACvH,SAASC,uBAAuB,QAAQ,iDAAgD;AACxF,SAASC,uBAAuB,QAAQ,iDAAgD;AACxF,SAASC,sBAAsB,QAAQ,+CAA8C;AACrF,SAASC,mCAAmC,QAAQ,gEAA+D;AACnH,SAASC,mBAAmB,QAAQ,qCAAoC;AACxE,SAASC,oBAAoB,QAAQ,sCAAqC;AAC1E,SAASC,oBAAoB,QAAQ,0CAAyC;AAC9E,SAASC,kBAAkB,QAAQ,oCAAmC;AACtE,SAASC,wBAAwB,QAAQ,gCAA+B;AACxE,SAASC,mBAAmB,QAAQ,2BAA0B;AAE9D,cAAc,mBAAkB;AAEhC,OAAO,MAAMC,oBACX,CAACC,gBACD,CAACC;QACC,IAAI,CAACA,OAAOC,WAAW,EAAE;YACvBD,OAAOC,WAAW,GAAG,EAAE;QACzB;QAEAD,OAAOC,WAAW,CAACC,IAAI,
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Config, Endpoint, TaskConfig } from 'payload'\n\nimport type { MobilizehubPluginConfig } from './types/index.js'\n\nimport { generateBroadcastsCollection } from './collections/broadcasts/generateBroadcastsCollection.js'\nimport { generateContactsCollection } from './collections/contacts/generateContactsCollection.js'\nimport { generateEmailsCollection } from './collections/emails/generateEmailsCollection.js'\nimport { generateFormSubmissionsCollection } from './collections/form-submissions/generateFormSubmissionsCollection.js'\nimport { generateFormsCollection } from './collections/forms/generateFormsCollection.js'\nimport { generatePagesCollection } from './collections/pages/generatePagesCollection.js'\nimport { generateTagsCollection } from './collections/tags/generateTagsCollection.js'\nimport { generateUnsubscribeTokensCollection } from './collections/unsubscribe-tokens/generateUnsubscribeTokens.js'\nimport { emailWebhookHandler } from './endpoints/emailWebhookHandler.js'\nimport { formSubmissionHandler } from './endpoints/formSubmissionHandler.js'\nimport { sendBroadcastHandler } from './endpoints/sendBroadcastHandler.js'\nimport { sendTestEmailHandler } from './endpoints/sendTestBroadcastHandler.js'\nimport { unsubscribeHandler } from './endpoints/unsubscribeHandler.js'\nimport { createSendBroadcastsTask } from './tasks/sendBroadcastsTask.js'\nimport { createSendEmailTask } from './tasks/sendEmailTask.js'\n\nexport * from './types/index.js'\n\nexport const mobilizehubPlugin =\n (pluginOptions: MobilizehubPluginConfig) =>\n (config: Config): Config => {\n if (!config.collections) {\n config.collections = []\n }\n\n config.collections.push(\n generateTagsCollection(pluginOptions),\n generateContactsCollection(pluginOptions),\n generateBroadcastsCollection(pluginOptions),\n generateEmailsCollection(pluginOptions),\n generatePagesCollection(pluginOptions),\n generateUnsubscribeTokensCollection(),\n generateFormSubmissionsCollection(pluginOptions),\n generateFormsCollection(pluginOptions),\n )\n\n if (pluginOptions.disabled) {\n return config\n }\n\n if (!config.endpoints) {\n config.endpoints = []\n }\n\n const endpoints: Endpoint[] = [\n {\n handler: sendBroadcastHandler(),\n method: 'post',\n path: '/send-broadcast',\n },\n {\n handler: sendTestEmailHandler(pluginOptions),\n method: 'post',\n path: '/send-test-email',\n },\n {\n handler: unsubscribeHandler(),\n method: 'post',\n path: '/unsubscribe',\n },\n {\n handler: emailWebhookHandler(pluginOptions),\n method: 'post',\n path: '/webhooks/email',\n },\n {\n handler: formSubmissionHandler(pluginOptions),\n method: 'post',\n path: '/forms.createSubmission',\n },\n ]\n\n config.endpoints = [...config.endpoints, ...endpoints]\n\n if (!config.jobs) {\n config.jobs = {\n tasks: [],\n }\n }\n\n const tasks: TaskConfig[] = [\n createSendBroadcastsTask(pluginOptions),\n createSendEmailTask(pluginOptions),\n ]\n\n config.jobs.tasks = [...(config.jobs.tasks ?? []), ...tasks]\n\n const incomingOnInit = config.onInit\n\n config.onInit = async (payload) => {\n if (incomingOnInit) {\n await incomingOnInit(payload)\n }\n }\n\n return config\n }\n"],"names":["generateBroadcastsCollection","generateContactsCollection","generateEmailsCollection","generateFormSubmissionsCollection","generateFormsCollection","generatePagesCollection","generateTagsCollection","generateUnsubscribeTokensCollection","emailWebhookHandler","formSubmissionHandler","sendBroadcastHandler","sendTestEmailHandler","unsubscribeHandler","createSendBroadcastsTask","createSendEmailTask","mobilizehubPlugin","pluginOptions","config","collections","push","disabled","endpoints","handler","method","path","jobs","tasks","incomingOnInit","onInit","payload"],"mappings":"AAIA,SAASA,4BAA4B,QAAQ,2DAA0D;AACvG,SAASC,0BAA0B,QAAQ,uDAAsD;AACjG,SAASC,wBAAwB,QAAQ,mDAAkD;AAC3F,SAASC,iCAAiC,QAAQ,sEAAqE;AACvH,SAASC,uBAAuB,QAAQ,iDAAgD;AACxF,SAASC,uBAAuB,QAAQ,iDAAgD;AACxF,SAASC,sBAAsB,QAAQ,+CAA8C;AACrF,SAASC,mCAAmC,QAAQ,gEAA+D;AACnH,SAASC,mBAAmB,QAAQ,qCAAoC;AACxE,SAASC,qBAAqB,QAAQ,uCAAsC;AAC5E,SAASC,oBAAoB,QAAQ,sCAAqC;AAC1E,SAASC,oBAAoB,QAAQ,0CAAyC;AAC9E,SAASC,kBAAkB,QAAQ,oCAAmC;AACtE,SAASC,wBAAwB,QAAQ,gCAA+B;AACxE,SAASC,mBAAmB,QAAQ,2BAA0B;AAE9D,cAAc,mBAAkB;AAEhC,OAAO,MAAMC,oBACX,CAACC,gBACD,CAACC;QACC,IAAI,CAACA,OAAOC,WAAW,EAAE;YACvBD,OAAOC,WAAW,GAAG,EAAE;QACzB;QAEAD,OAAOC,WAAW,CAACC,IAAI,CACrBb,uBAAuBU,gBACvBf,2BAA2Be,gBAC3BhB,6BAA6BgB,gBAC7Bd,yBAAyBc,gBACzBX,wBAAwBW,gBACxBT,uCACAJ,kCAAkCa,gBAClCZ,wBAAwBY;QAG1B,IAAIA,cAAcI,QAAQ,EAAE;YAC1B,OAAOH;QACT;QAEA,IAAI,CAACA,OAAOI,SAAS,EAAE;YACrBJ,OAAOI,SAAS,GAAG,EAAE;QACvB;QAEA,MAAMA,YAAwB;YAC5B;gBACEC,SAASZ;gBACTa,QAAQ;gBACRC,MAAM;YACR;YACA;gBACEF,SAASX,qBAAqBK;gBAC9BO,QAAQ;gBACRC,MAAM;YACR;YACA;gBACEF,SAASV;gBACTW,QAAQ;gBACRC,MAAM;YACR;YACA;gBACEF,SAASd,oBAAoBQ;gBAC7BO,QAAQ;gBACRC,MAAM;YACR;YACA;gBACEF,SAASb,sBAAsBO;gBAC/BO,QAAQ;gBACRC,MAAM;YACR;SACD;QAEDP,OAAOI,SAAS,GAAG;eAAIJ,OAAOI,SAAS;eAAKA;SAAU;QAEtD,IAAI,CAACJ,OAAOQ,IAAI,EAAE;YAChBR,OAAOQ,IAAI,GAAG;gBACZC,OAAO,EAAE;YACX;QACF;QAEA,MAAMA,QAAsB;YAC1Bb,yBAAyBG;YACzBF,oBAAoBE;SACrB;QAEDC,OAAOQ,IAAI,CAACC,KAAK,GAAG;eAAKT,OAAOQ,IAAI,CAACC,KAAK,IAAI,EAAE;eAAMA;SAAM;QAE5D,MAAMC,iBAAiBV,OAAOW,MAAM;QAEpCX,OAAOW,MAAM,GAAG,OAAOC;YACrB,IAAIF,gBAAgB;gBAClB,MAAMA,eAAeE;YACvB;QACF;QAEA,OAAOZ;IACT,EAAC"}
|
package/dist/react/index.d.ts
CHANGED
package/dist/react/index.js
CHANGED
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/index.ts"],"sourcesContent":["export { confirmUnsubscribe } from './unsubscribe.js'\n"],"names":["confirmUnsubscribe"],"mappings":"AAAA,SAASA,kBAAkB,QAAQ,mBAAkB"}
|
|
1
|
+
{"version":3,"sources":["../../src/react/index.ts"],"sourcesContent":["export { submitForm } from './submit-form.js'\nexport { confirmUnsubscribe } from './unsubscribe.js'\n"],"names":["submitForm","confirmUnsubscribe"],"mappings":"AAAA,SAASA,UAAU,QAAQ,mBAAkB;AAC7C,SAASC,kBAAkB,QAAQ,mBAAkB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for handling form submission responses.
|
|
3
|
+
*/
|
|
4
|
+
type SubmitFormOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* Called when the form returns a message confirmation.
|
|
7
|
+
* @param message - The confirmation message (may be a RichText object)
|
|
8
|
+
*/
|
|
9
|
+
onMessage?: (message: unknown) => void;
|
|
10
|
+
/**
|
|
11
|
+
* Called when the form should redirect after submission.
|
|
12
|
+
* @param redirect - The URL to redirect to
|
|
13
|
+
*/
|
|
14
|
+
onRedirect?: (redirect: string) => void;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Submits a form to the backend API.
|
|
18
|
+
*
|
|
19
|
+
* @param args - Form submission arguments
|
|
20
|
+
* @param args.formId - The ID of the form to submit to
|
|
21
|
+
* @param args.data - The form data to submit
|
|
22
|
+
* @param args.opts - Optional callbacks for handling the response
|
|
23
|
+
* @returns The submission data including confirmation and submissionId
|
|
24
|
+
* @throws Error if the submission fails
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* const handleSubmit = async (formData: Record<string, unknown>) => {
|
|
29
|
+
* try {
|
|
30
|
+
* const result = await submitForm({
|
|
31
|
+
* formId: '1',
|
|
32
|
+
* data: formData,
|
|
33
|
+
* opts: {
|
|
34
|
+
* onRedirect: (url) => router.push(url),
|
|
35
|
+
* onMessage: (message) => setConfirmation(message),
|
|
36
|
+
* },
|
|
37
|
+
* })
|
|
38
|
+
* console.log('Submission ID:', result.submissionId)
|
|
39
|
+
* } catch (error) {
|
|
40
|
+
* console.error('Form submission failed:', error)
|
|
41
|
+
* }
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function submitForm(args: {
|
|
46
|
+
data: Record<string, unknown>;
|
|
47
|
+
formId: number | string;
|
|
48
|
+
opts?: SubmitFormOptions;
|
|
49
|
+
}): Promise<{
|
|
50
|
+
confirmation: {
|
|
51
|
+
message?: unknown;
|
|
52
|
+
redirect?: string;
|
|
53
|
+
type: "message" | "redirect";
|
|
54
|
+
};
|
|
55
|
+
submissionId: number | string;
|
|
56
|
+
} | undefined>;
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const apiUrl = '/api/forms.createSubmission';
|
|
2
|
+
/**
|
|
3
|
+
* Submits a form to the backend API.
|
|
4
|
+
*
|
|
5
|
+
* @param args - Form submission arguments
|
|
6
|
+
* @param args.formId - The ID of the form to submit to
|
|
7
|
+
* @param args.data - The form data to submit
|
|
8
|
+
* @param args.opts - Optional callbacks for handling the response
|
|
9
|
+
* @returns The submission data including confirmation and submissionId
|
|
10
|
+
* @throws Error if the submission fails
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const handleSubmit = async (formData: Record<string, unknown>) => {
|
|
15
|
+
* try {
|
|
16
|
+
* const result = await submitForm({
|
|
17
|
+
* formId: '1',
|
|
18
|
+
* data: formData,
|
|
19
|
+
* opts: {
|
|
20
|
+
* onRedirect: (url) => router.push(url),
|
|
21
|
+
* onMessage: (message) => setConfirmation(message),
|
|
22
|
+
* },
|
|
23
|
+
* })
|
|
24
|
+
* console.log('Submission ID:', result.submissionId)
|
|
25
|
+
* } catch (error) {
|
|
26
|
+
* console.error('Form submission failed:', error)
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/ export async function submitForm(args) {
|
|
31
|
+
const response = await fetch(apiUrl, {
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
data: args.data,
|
|
34
|
+
formId: args.formId
|
|
35
|
+
}),
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json'
|
|
38
|
+
},
|
|
39
|
+
method: 'POST'
|
|
40
|
+
});
|
|
41
|
+
const result = await response.json();
|
|
42
|
+
if (!result.success) {
|
|
43
|
+
throw new Error(result.error?.message || 'Submission failed');
|
|
44
|
+
}
|
|
45
|
+
if (result.data?.confirmation.type === 'redirect' && result.data.confirmation.redirect) {
|
|
46
|
+
args.opts?.onRedirect?.(result.data.confirmation.redirect);
|
|
47
|
+
}
|
|
48
|
+
if (result.data?.confirmation.type === 'message') {
|
|
49
|
+
args.opts?.onMessage?.(result.data.confirmation.message);
|
|
50
|
+
}
|
|
51
|
+
return result.data;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=submit-form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/submit-form.ts"],"sourcesContent":["const apiUrl = '/api/forms.createSubmission'\n\n/**\n * Response type from the form submission API.\n */\ntype FormSubmissionResponse = {\n data?: {\n confirmation: {\n message?: unknown\n redirect?: string\n type: 'message' | 'redirect'\n }\n submissionId: number | string\n }\n error?: {\n code: string\n message: string\n }\n success: boolean\n}\n\n/**\n * Options for handling form submission responses.\n */\ntype SubmitFormOptions = {\n /**\n * Called when the form returns a message confirmation.\n * @param message - The confirmation message (may be a RichText object)\n */\n onMessage?: (message: unknown) => void\n /**\n * Called when the form should redirect after submission.\n * @param redirect - The URL to redirect to\n */\n onRedirect?: (redirect: string) => void\n}\n\n/**\n * Submits a form to the backend API.\n *\n * @param args - Form submission arguments\n * @param args.formId - The ID of the form to submit to\n * @param args.data - The form data to submit\n * @param args.opts - Optional callbacks for handling the response\n * @returns The submission data including confirmation and submissionId\n * @throws Error if the submission fails\n *\n * @example\n * ```tsx\n * const handleSubmit = async (formData: Record<string, unknown>) => {\n * try {\n * const result = await submitForm({\n * formId: '1',\n * data: formData,\n * opts: {\n * onRedirect: (url) => router.push(url),\n * onMessage: (message) => setConfirmation(message),\n * },\n * })\n * console.log('Submission ID:', result.submissionId)\n * } catch (error) {\n * console.error('Form submission failed:', error)\n * }\n * }\n * ```\n */\nexport async function submitForm(args: {\n data: Record<string, unknown>\n formId: number | string\n opts?: SubmitFormOptions\n}) {\n const response = await fetch(apiUrl, {\n body: JSON.stringify({ data: args.data, formId: args.formId }),\n headers: {\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n })\n\n const result: FormSubmissionResponse = await response.json()\n\n if (!result.success) {\n throw new Error(result.error?.message || 'Submission failed')\n }\n\n if (result.data?.confirmation.type === 'redirect' && result.data.confirmation.redirect) {\n args.opts?.onRedirect?.(result.data.confirmation.redirect)\n }\n\n if (result.data?.confirmation.type === 'message') {\n args.opts?.onMessage?.(result.data.confirmation.message)\n }\n\n return result.data\n}\n"],"names":["apiUrl","submitForm","args","response","fetch","body","JSON","stringify","data","formId","headers","method","result","json","success","Error","error","message","confirmation","type","redirect","opts","onRedirect","onMessage"],"mappings":"AAAA,MAAMA,SAAS;AAqCf;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BC,GACD,OAAO,eAAeC,WAAWC,IAIhC;IACC,MAAMC,WAAW,MAAMC,MAAMJ,QAAQ;QACnCK,MAAMC,KAAKC,SAAS,CAAC;YAAEC,MAAMN,KAAKM,IAAI;YAAEC,QAAQP,KAAKO,MAAM;QAAC;QAC5DC,SAAS;YACP,gBAAgB;QAClB;QACAC,QAAQ;IACV;IAEA,MAAMC,SAAiC,MAAMT,SAASU,IAAI;IAE1D,IAAI,CAACD,OAAOE,OAAO,EAAE;QACnB,MAAM,IAAIC,MAAMH,OAAOI,KAAK,EAAEC,WAAW;IAC3C;IAEA,IAAIL,OAAOJ,IAAI,EAAEU,aAAaC,SAAS,cAAcP,OAAOJ,IAAI,CAACU,YAAY,CAACE,QAAQ,EAAE;QACtFlB,KAAKmB,IAAI,EAAEC,aAAaV,OAAOJ,IAAI,CAACU,YAAY,CAACE,QAAQ;IAC3D;IAEA,IAAIR,OAAOJ,IAAI,EAAEU,aAAaC,SAAS,WAAW;QAChDjB,KAAKmB,IAAI,EAAEE,YAAYX,OAAOJ,IAAI,CAACU,YAAY,CAACD,OAAO;IACzD;IAEA,OAAOL,OAAOJ,IAAI;AACpB"}
|