@forumone/throughline-forms 0.0.1 → 0.2.1
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/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +132 -40
- package/dist/destinations.d.ts +26 -0
- package/dist/destinations.d.ts.map +1 -0
- package/dist/destinations.js +36 -0
- package/dist/destinations.js.map +1 -0
- package/dist/functions/_shared.d.ts +13 -0
- package/dist/functions/_shared.d.ts.map +1 -0
- package/dist/functions/_shared.js +44 -0
- package/dist/functions/_shared.js.map +1 -0
- package/dist/functions/email-destination.d.ts +25 -0
- package/dist/functions/email-destination.d.ts.map +1 -0
- package/dist/functions/email-destination.js +75 -0
- package/dist/functions/email-destination.js.map +1 -0
- package/dist/functions/fan-out.d.ts +24 -0
- package/dist/functions/fan-out.d.ts.map +1 -0
- package/dist/functions/fan-out.js +71 -0
- package/dist/functions/fan-out.js.map +1 -0
- package/dist/functions/index.d.ts +9 -0
- package/dist/functions/index.d.ts.map +1 -0
- package/dist/functions/index.js +5 -0
- package/dist/functions/index.js.map +1 -0
- package/dist/functions/submitter-confirmation.d.ts +25 -0
- package/dist/functions/submitter-confirmation.d.ts.map +1 -0
- package/dist/functions/submitter-confirmation.js +76 -0
- package/dist/functions/submitter-confirmation.js.map +1 -0
- package/dist/functions/webhook-destination.d.ts +20 -0
- package/dist/functions/webhook-destination.d.ts.map +1 -0
- package/dist/functions/webhook-destination.js +74 -0
- package/dist/functions/webhook-destination.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/options.d.ts +74 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +61 -0
- package/dist/options.js.map +1 -0
- package/dist/plugin.d.ts +19 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +180 -0
- package/dist/plugin.js.map +1 -0
- package/dist/policy-fields.d.ts +18 -0
- package/dist/policy-fields.d.ts.map +1 -0
- package/dist/policy-fields.js +115 -0
- package/dist/policy-fields.js.map +1 -0
- package/dist/submit/endpoint.d.ts +16 -0
- package/dist/submit/endpoint.d.ts.map +1 -0
- package/dist/submit/endpoint.js +116 -0
- package/dist/submit/endpoint.js.map +1 -0
- package/dist/submit/ip.d.ts +20 -0
- package/dist/submit/ip.d.ts.map +1 -0
- package/dist/submit/ip.js +48 -0
- package/dist/submit/ip.js.map +1 -0
- package/dist/submit/rate-limit.d.ts +23 -0
- package/dist/submit/rate-limit.d.ts.map +1 -0
- package/dist/submit/rate-limit.js +27 -0
- package/dist/submit/rate-limit.js.map +1 -0
- package/dist/submit/spam.d.ts +10 -0
- package/dist/submit/spam.d.ts.map +1 -0
- package/dist/submit/spam.js +16 -0
- package/dist/submit/spam.js.map +1 -0
- package/dist/templates/FormSubmission.d.ts +21 -0
- package/dist/templates/FormSubmission.d.ts.map +1 -0
- package/dist/templates/FormSubmission.js +32 -0
- package/dist/templates/FormSubmission.js.map +1 -0
- package/dist/templates/SubmitterConfirmation.d.ts +18 -0
- package/dist/templates/SubmitterConfirmation.d.ts.map +1 -0
- package/dist/templates/SubmitterConfirmation.js +14 -0
- package/dist/templates/SubmitterConfirmation.js.map +1 -0
- package/dist/templates/_layout.d.ts +14 -0
- package/dist/templates/_layout.d.ts.map +1 -0
- package/dist/templates/_layout.js +34 -0
- package/dist/templates/_layout.js.map +1 -0
- package/dist/templates/index.d.ts +7 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +4 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/tools/_field-schema.d.ts +68 -0
- package/dist/tools/_field-schema.d.ts.map +1 -0
- package/dist/tools/_field-schema.js +62 -0
- package/dist/tools/_field-schema.js.map +1 -0
- package/dist/tools/access.d.ts +7 -0
- package/dist/tools/access.d.ts.map +1 -0
- package/dist/tools/access.js +16 -0
- package/dist/tools/access.js.map +1 -0
- package/dist/tools/create-form.d.ts +138 -0
- package/dist/tools/create-form.d.ts.map +1 -0
- package/dist/tools/create-form.js +96 -0
- package/dist/tools/create-form.js.map +1 -0
- package/dist/tools/get-form-submissions.d.ts +27 -0
- package/dist/tools/get-form-submissions.d.ts.map +1 -0
- package/dist/tools/get-form-submissions.js +47 -0
- package/dist/tools/get-form-submissions.js.map +1 -0
- package/dist/tools/index.d.ts +14 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/list-allowed-destinations.d.ts +10 -0
- package/dist/tools/list-allowed-destinations.d.ts.map +1 -0
- package/dist/tools/list-allowed-destinations.js +14 -0
- package/dist/tools/list-allowed-destinations.js.map +1 -0
- package/dist/tools/update-form-destinations.d.ts +45 -0
- package/dist/tools/update-form-destinations.d.ts.map +1 -0
- package/dist/tools/update-form-destinations.js +75 -0
- package/dist/tools/update-form-destinations.js.map +1 -0
- package/dist/tools/update-form-fields.d.ts +101 -0
- package/dist/tools/update-form-fields.d.ts.map +1 -0
- package/dist/tools/update-form-fields.js +65 -0
- package/dist/tools/update-form-fields.js.map +1 -0
- package/dist/tools/validate-form.d.ts +127 -0
- package/dist/tools/validate-form.d.ts.map +1 -0
- package/dist/tools/validate-form.js +50 -0
- package/dist/tools/validate-form.js.map +1 -0
- package/package.json +71 -7
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-fields.d.ts","sourceRoot":"","sources":["../src/policy-fields.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAEpC,MAAM,WAAW,mBAAmB;IAClC,0BAA0B,EAAE,MAAM,EAAE,CAAA;IACpC,oBAAoB,EAAE,MAAM,CAAA;IAC5B,uBAAuB,EAAE,OAAO,CAAA;IAChC,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,KAAK,EAAE,EACnB,OAAO,EAAE,mBAAmB,GAC3B,KAAK,EAAE,CAkHT"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Augments the Form Builder collection's fields with the Throughline policy
|
|
3
|
+
* group: privacy notice, consent toggle and label, spam protection (honeypot
|
|
4
|
+
* + per-form rate limit), allowlist-bound destinations, and an optional
|
|
5
|
+
* submitter-confirmation block.
|
|
6
|
+
*
|
|
7
|
+
* Returns a *new* array; never mutates the input. Form Builder's
|
|
8
|
+
* `formOverrides.fields` callback hands us the default field list.
|
|
9
|
+
*/
|
|
10
|
+
export function addFormPolicyFields(baseFields, options) {
|
|
11
|
+
const destinationOptions = options.availableDestinationLabels.map((label) => ({
|
|
12
|
+
label,
|
|
13
|
+
value: label,
|
|
14
|
+
}));
|
|
15
|
+
return [
|
|
16
|
+
...baseFields,
|
|
17
|
+
{
|
|
18
|
+
name: 'policy',
|
|
19
|
+
type: 'group',
|
|
20
|
+
admin: {
|
|
21
|
+
description: 'Privacy notice, consent, spam protection, destinations, and submitter confirmation. Edited in the admin only — Claude cannot modify these directly except through the forms MCP tools.',
|
|
22
|
+
},
|
|
23
|
+
fields: [
|
|
24
|
+
{
|
|
25
|
+
name: 'privacyNoticeText',
|
|
26
|
+
type: 'textarea',
|
|
27
|
+
required: true,
|
|
28
|
+
defaultValue: options.defaultPrivacyNotice,
|
|
29
|
+
admin: {
|
|
30
|
+
description: 'Required. Shown above the submit button on rendered forms. Legal review recommended.',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'requiresExplicitConsent',
|
|
35
|
+
type: 'checkbox',
|
|
36
|
+
defaultValue: options.requireConsentByDefault,
|
|
37
|
+
admin: {
|
|
38
|
+
description: 'When true, the form renders a required consent checkbox above submit. Server-side enforcement: submissions without `consent: true` are rejected at the endpoint.',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'consentLabel',
|
|
43
|
+
type: 'text',
|
|
44
|
+
defaultValue: 'I agree to the processing of my data as described above.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'spamProtection',
|
|
48
|
+
type: 'group',
|
|
49
|
+
fields: [
|
|
50
|
+
{
|
|
51
|
+
name: 'honeypot',
|
|
52
|
+
type: 'checkbox',
|
|
53
|
+
defaultValue: true,
|
|
54
|
+
admin: {
|
|
55
|
+
description: 'When enabled, the rendered form includes a visually hidden field. Bots that fill every input get silently dropped.',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'rateLimit',
|
|
60
|
+
type: 'number',
|
|
61
|
+
defaultValue: options.defaultRateLimit,
|
|
62
|
+
admin: {
|
|
63
|
+
description: 'Maximum submissions per submitter IP-hash per hour. Falls back to the plugin-wide default.',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'destinations',
|
|
70
|
+
type: 'array',
|
|
71
|
+
minRows: 1,
|
|
72
|
+
admin: {
|
|
73
|
+
description: 'Where submissions are routed. Each entry must reference a label from the plugin\'s allowedDestinations.',
|
|
74
|
+
},
|
|
75
|
+
fields: [
|
|
76
|
+
{
|
|
77
|
+
name: 'label',
|
|
78
|
+
type: 'select',
|
|
79
|
+
required: true,
|
|
80
|
+
options: destinationOptions,
|
|
81
|
+
admin: {
|
|
82
|
+
description: 'Pre-approved destination from the allowlist. Adding a new option requires editing plugin config + redeploying.',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{ name: 'enabled', type: 'checkbox', defaultValue: true },
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'submitterConfirmation',
|
|
90
|
+
type: 'group',
|
|
91
|
+
admin: {
|
|
92
|
+
description: 'Auto-reply to the submitter. When enabled, set emailFieldName to the form field that captures the submitter\'s email address.',
|
|
93
|
+
},
|
|
94
|
+
fields: [
|
|
95
|
+
{ name: 'enabled', type: 'checkbox', defaultValue: false },
|
|
96
|
+
{
|
|
97
|
+
name: 'emailFieldName',
|
|
98
|
+
type: 'text',
|
|
99
|
+
admin: {
|
|
100
|
+
description: 'Name of the field on the form that holds the submitter\'s email. Required when enabled.',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'subject',
|
|
105
|
+
type: 'text',
|
|
106
|
+
defaultValue: 'Thank you for your submission',
|
|
107
|
+
},
|
|
108
|
+
{ name: 'body', type: 'textarea' },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=policy-fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-fields.js","sourceRoot":"","sources":["../src/policy-fields.ts"],"names":[],"mappings":"AASA;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,UAAmB,EACnB,OAA4B;IAE5B,MAAM,kBAAkB,GAAG,OAAO,CAAC,0BAA0B,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5E,KAAK;QACL,KAAK,EAAE,KAAK;KACb,CAAC,CAAC,CAAA;IAEH,OAAO;QACL,GAAG,UAAU;QACb;YACE,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,OAAO;YACb,KAAK,EAAE;gBACL,WAAW,EACT,wLAAwL;aAC3L;YACD,MAAM,EAAE;gBACN;oBACE,IAAI,EAAE,mBAAmB;oBACzB,IAAI,EAAE,UAAU;oBAChB,QAAQ,EAAE,IAAI;oBACd,YAAY,EAAE,OAAO,CAAC,oBAAoB;oBAC1C,KAAK,EAAE;wBACL,WAAW,EACT,sFAAsF;qBACzF;iBACF;gBACD;oBACE,IAAI,EAAE,yBAAyB;oBAC/B,IAAI,EAAE,UAAU;oBAChB,YAAY,EAAE,OAAO,CAAC,uBAAuB;oBAC7C,KAAK,EAAE;wBACL,WAAW,EACT,kKAAkK;qBACrK;iBACF;gBACD;oBACE,IAAI,EAAE,cAAc;oBACpB,IAAI,EAAE,MAAM;oBACZ,YAAY,EAAE,0DAA0D;iBACzE;gBACD;oBACE,IAAI,EAAE,gBAAgB;oBACtB,IAAI,EAAE,OAAO;oBACb,MAAM,EAAE;wBACN;4BACE,IAAI,EAAE,UAAU;4BAChB,IAAI,EAAE,UAAU;4BAChB,YAAY,EAAE,IAAI;4BAClB,KAAK,EAAE;gCACL,WAAW,EACT,oHAAoH;6BACvH;yBACF;wBACD;4BACE,IAAI,EAAE,WAAW;4BACjB,IAAI,EAAE,QAAQ;4BACd,YAAY,EAAE,OAAO,CAAC,gBAAgB;4BACtC,KAAK,EAAE;gCACL,WAAW,EACT,4FAA4F;6BAC/F;yBACF;qBACF;iBACF;gBACD;oBACE,IAAI,EAAE,cAAc;oBACpB,IAAI,EAAE,OAAO;oBACb,OAAO,EAAE,CAAC;oBACV,KAAK,EAAE;wBACL,WAAW,EACT,yGAAyG;qBAC5G;oBACD,MAAM,EAAE;wBACN;4BACE,IAAI,EAAE,OAAO;4BACb,IAAI,EAAE,QAAQ;4BACd,QAAQ,EAAE,IAAI;4BACd,OAAO,EAAE,kBAAkB;4BAC3B,KAAK,EAAE;gCACL,WAAW,EACT,gHAAgH;6BACnH;yBACF;wBACD,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,IAAI,EAAE;qBAC1D;iBACF;gBACD;oBACE,IAAI,EAAE,uBAAuB;oBAC7B,IAAI,EAAE,OAAO;oBACb,KAAK,EAAE;wBACL,WAAW,EACT,+HAA+H;qBAClI;oBACD,MAAM,EAAE;wBACN,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,EAAE;wBAC1D;4BACE,IAAI,EAAE,gBAAgB;4BACtB,IAAI,EAAE,MAAM;4BACZ,KAAK,EAAE;gCACL,WAAW,EACT,yFAAyF;6BAC5F;yBACF;wBACD;4BACE,IAAI,EAAE,SAAS;4BACf,IAAI,EAAE,MAAM;4BACZ,YAAY,EAAE,+BAA+B;yBAC9C;wBACD,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE;qBACnC;iBACF;aACF;SACF;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Endpoint, PayloadRequest } from 'payload';
|
|
2
|
+
import type { ResolvedFormsConfig } from '../options.js';
|
|
3
|
+
/**
|
|
4
|
+
* Builds the public submit endpoint. Performs (in order):
|
|
5
|
+
* 1. Honeypot check — silently 200 if filled (so bots don't learn the trick).
|
|
6
|
+
* 2. Form lookup — 404 if missing.
|
|
7
|
+
* 3. Consent enforcement — server-side; client bypass doesn't work.
|
|
8
|
+
* 4. Rate limit per (form, IP-hash) per hour.
|
|
9
|
+
* 5. Persist a sanitized submission row.
|
|
10
|
+
* 6. Fire `form/submission.received` for the fan-out worker.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createSubmitEndpoint(resolved: ResolvedFormsConfig): Endpoint;
|
|
13
|
+
export type SubmitRequest = PayloadRequest & {
|
|
14
|
+
json?: () => Promise<unknown>;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=endpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../../src/submit/endpoint.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAA2B,cAAc,EAAE,MAAM,SAAS,CAAA;AAChF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAaxD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,QAAQ,CAqF5E;AA8BD,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAA"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { checkHoneypot } from './spam.js';
|
|
2
|
+
import { extractClientIp, hashIp } from './ip.js';
|
|
3
|
+
import { checkRateLimit } from './rate-limit.js';
|
|
4
|
+
/**
|
|
5
|
+
* Builds the public submit endpoint. Performs (in order):
|
|
6
|
+
* 1. Honeypot check — silently 200 if filled (so bots don't learn the trick).
|
|
7
|
+
* 2. Form lookup — 404 if missing.
|
|
8
|
+
* 3. Consent enforcement — server-side; client bypass doesn't work.
|
|
9
|
+
* 4. Rate limit per (form, IP-hash) per hour.
|
|
10
|
+
* 5. Persist a sanitized submission row.
|
|
11
|
+
* 6. Fire `form/submission.received` for the fan-out worker.
|
|
12
|
+
*/
|
|
13
|
+
export function createSubmitEndpoint(resolved) {
|
|
14
|
+
const submitPath = `${resolved.routePrefix}/submit`;
|
|
15
|
+
const handler = async (req) => {
|
|
16
|
+
let body;
|
|
17
|
+
try {
|
|
18
|
+
body = (await req.json?.());
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return jsonResponse({ error: 'Invalid JSON body.' }, 400);
|
|
22
|
+
}
|
|
23
|
+
const { formId, data, consent, _hp } = body;
|
|
24
|
+
if (typeof formId !== 'string' || formId.length === 0) {
|
|
25
|
+
return jsonResponse({ error: 'formId is required.' }, 400);
|
|
26
|
+
}
|
|
27
|
+
// Honeypot: silently accept so bots don't pivot when they detect a 4xx.
|
|
28
|
+
if (!checkHoneypot(_hp)) {
|
|
29
|
+
return jsonResponse({ ok: true }, 200);
|
|
30
|
+
}
|
|
31
|
+
const payload = req.payload;
|
|
32
|
+
let form = null;
|
|
33
|
+
try {
|
|
34
|
+
form = (await payload.findByID({
|
|
35
|
+
collection: resolved.formsCollectionSlug,
|
|
36
|
+
id: formId,
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
form = null;
|
|
41
|
+
}
|
|
42
|
+
if (!form)
|
|
43
|
+
return jsonResponse({ error: 'Form not found.' }, 404);
|
|
44
|
+
const policy = (form['policy'] ?? {});
|
|
45
|
+
const requiresConsent = policy['requiresExplicitConsent'] === true;
|
|
46
|
+
if (requiresConsent && consent !== true) {
|
|
47
|
+
return jsonResponse({ error: 'Consent is required.' }, 400);
|
|
48
|
+
}
|
|
49
|
+
const headers = req.headers;
|
|
50
|
+
const ip = extractClientIp(headers);
|
|
51
|
+
const ipHash = await hashIp(ip, resolved.ipHashSecret);
|
|
52
|
+
const formRateLimit = readNumber((policy['spamProtection'] ?? {})['rateLimit']);
|
|
53
|
+
const limit = formRateLimit ?? resolved.rateLimit;
|
|
54
|
+
const rate = await checkRateLimit({
|
|
55
|
+
payload,
|
|
56
|
+
formId,
|
|
57
|
+
ipHash,
|
|
58
|
+
limit,
|
|
59
|
+
collectionSlug: resolved.submissionsCollectionSlug,
|
|
60
|
+
});
|
|
61
|
+
if (!rate.ok) {
|
|
62
|
+
return jsonResponse({ error: 'Too many submissions. Try again later.' }, 429);
|
|
63
|
+
}
|
|
64
|
+
const submission = await payload.create({
|
|
65
|
+
collection: resolved.submissionsCollectionSlug,
|
|
66
|
+
data: {
|
|
67
|
+
form: formId,
|
|
68
|
+
submissionData: serializeSubmissionData(data ?? {}),
|
|
69
|
+
consentGivenAt: requiresConsent ? new Date().toISOString() : null,
|
|
70
|
+
ipHash,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
await resolved.options.inngest.send({
|
|
74
|
+
name: 'form/submission.received',
|
|
75
|
+
data: {
|
|
76
|
+
formId,
|
|
77
|
+
submissionId: String(submission.id),
|
|
78
|
+
formTitle: typeof form['title'] === 'string' ? form['title'] : '(untitled)',
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
return jsonResponse({ ok: true, submissionId: String(submission.id) });
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
path: submitPath,
|
|
85
|
+
method: 'post',
|
|
86
|
+
handler,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function jsonResponse(body, status = 200) {
|
|
90
|
+
return new Response(JSON.stringify(body), {
|
|
91
|
+
status,
|
|
92
|
+
headers: { 'content-type': 'application/json' },
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function readNumber(value) {
|
|
96
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
|
|
97
|
+
return null;
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
/** Form Builder stores submission data as `[{ field, value }]` rows. */
|
|
101
|
+
function serializeSubmissionData(data) {
|
|
102
|
+
return Object.entries(data).map(([field, value]) => ({
|
|
103
|
+
field,
|
|
104
|
+
value: stringify(value),
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
function stringify(value) {
|
|
108
|
+
if (value === null || value === undefined)
|
|
109
|
+
return '';
|
|
110
|
+
if (typeof value === 'string')
|
|
111
|
+
return value;
|
|
112
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
113
|
+
return String(value);
|
|
114
|
+
return JSON.stringify(value);
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=endpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../../src/submit/endpoint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hashes a submitter IP with HMAC-SHA256 and the deployment's secret. The
|
|
3
|
+
* raw IP is never persisted; the hash is what rate-limit and audit need.
|
|
4
|
+
* Using a per-deployment secret means two deployments produce different
|
|
5
|
+
* hashes for the same IP — an attacker who exfiltrated the hash table from
|
|
6
|
+
* one site cannot pivot to another.
|
|
7
|
+
*
|
|
8
|
+
* Implemented via Web Crypto so the same code runs in Node 20+, edge
|
|
9
|
+
* runtimes, and browser test environments.
|
|
10
|
+
*/
|
|
11
|
+
export declare function hashIp(ip: string, secret: string): Promise<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Best-effort client-IP extraction. Honors the standard proxy headers
|
|
14
|
+
* Vercel/Cloudflare/most reverse proxies set; falls back to '0.0.0.0' so
|
|
15
|
+
* upstream code can still hash without branching. Two requests with no
|
|
16
|
+
* headers will share a hash and rate-limit bucket — that is the intended
|
|
17
|
+
* "no-IP" fail-safe.
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractClientIp(headers: Headers): string;
|
|
20
|
+
//# sourceMappingURL=ip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip.d.ts","sourceRoot":"","sources":["../../src/submit/ip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAsB,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAWxE;AAaD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAWxD"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hashes a submitter IP with HMAC-SHA256 and the deployment's secret. The
|
|
3
|
+
* raw IP is never persisted; the hash is what rate-limit and audit need.
|
|
4
|
+
* Using a per-deployment secret means two deployments produce different
|
|
5
|
+
* hashes for the same IP — an attacker who exfiltrated the hash table from
|
|
6
|
+
* one site cannot pivot to another.
|
|
7
|
+
*
|
|
8
|
+
* Implemented via Web Crypto so the same code runs in Node 20+, edge
|
|
9
|
+
* runtimes, and browser test environments.
|
|
10
|
+
*/
|
|
11
|
+
export async function hashIp(ip, secret) {
|
|
12
|
+
const encoder = new TextEncoder();
|
|
13
|
+
const key = await crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
14
|
+
const sig = await crypto.subtle.sign('HMAC', key, encoder.encode(ip));
|
|
15
|
+
return bytesToHex(new Uint8Array(sig));
|
|
16
|
+
}
|
|
17
|
+
const HEX_CHARS = '0123456789abcdef';
|
|
18
|
+
function bytesToHex(bytes) {
|
|
19
|
+
let out = '';
|
|
20
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
21
|
+
const byte = bytes[i];
|
|
22
|
+
out += HEX_CHARS[byte >> 4] + HEX_CHARS[byte & 0xf];
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Best-effort client-IP extraction. Honors the standard proxy headers
|
|
28
|
+
* Vercel/Cloudflare/most reverse proxies set; falls back to '0.0.0.0' so
|
|
29
|
+
* upstream code can still hash without branching. Two requests with no
|
|
30
|
+
* headers will share a hash and rate-limit bucket — that is the intended
|
|
31
|
+
* "no-IP" fail-safe.
|
|
32
|
+
*/
|
|
33
|
+
export function extractClientIp(headers) {
|
|
34
|
+
const forwarded = headers.get('x-forwarded-for');
|
|
35
|
+
if (forwarded) {
|
|
36
|
+
const first = forwarded.split(',')[0];
|
|
37
|
+
if (first)
|
|
38
|
+
return first.trim();
|
|
39
|
+
}
|
|
40
|
+
const real = headers.get('x-real-ip');
|
|
41
|
+
if (real)
|
|
42
|
+
return real;
|
|
43
|
+
const cf = headers.get('cf-connecting-ip');
|
|
44
|
+
if (cf)
|
|
45
|
+
return cf;
|
|
46
|
+
return '0.0.0.0';
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=ip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip.js","sourceRoot":"","sources":["../../src/submit/ip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,EAAU,EAAE,MAAc;IACrD,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAA;IACjC,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CACvC,KAAK,EACL,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EACtB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,CAAC,CACT,CAAA;IACD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;IACrE,OAAO,UAAU,CAAC,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;AACxC,CAAC;AAED,MAAM,SAAS,GAAG,kBAAkB,CAAA;AAEpC,SAAS,UAAU,CAAC,KAAiB;IACnC,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAA;QACtB,GAAG,IAAI,SAAS,CAAC,IAAI,IAAI,CAAC,CAAE,GAAG,SAAS,CAAC,IAAI,GAAG,GAAG,CAAE,CAAA;IACvD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;IAChD,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QACrC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC,IAAI,EAAE,CAAA;IAChC,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IACrC,IAAI,IAAI;QAAE,OAAO,IAAI,CAAA;IACrB,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;IAC1C,IAAI,EAAE;QAAE,OAAO,EAAE,CAAA;IACjB,OAAO,SAAS,CAAA;AAClB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Payload } from 'payload';
|
|
2
|
+
export interface RateLimitArgs {
|
|
3
|
+
payload: Payload;
|
|
4
|
+
formId: string;
|
|
5
|
+
ipHash: string;
|
|
6
|
+
limit: number;
|
|
7
|
+
/** Override the form-submissions slug; defaults to 'form-submissions'. */
|
|
8
|
+
collectionSlug?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface RateLimitResult {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
remaining: number;
|
|
13
|
+
total: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Postgres-backed rate limiter. Counts form-submissions for a given
|
|
17
|
+
* (form, ipHash) tuple in the past hour. Sufficient for Phase 1 traffic;
|
|
18
|
+
* deployments with high-volume forms should swap in a Redis-backed
|
|
19
|
+
* limiter via plugin options later (the public submit endpoint already
|
|
20
|
+
* abstracts this behind a single function call).
|
|
21
|
+
*/
|
|
22
|
+
export declare function checkRateLimit(args: RateLimitArgs): Promise<RateLimitResult>;
|
|
23
|
+
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/submit/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAEtC,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,0EAA0E;IAC1E,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,CAmBlF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Postgres-backed rate limiter. Counts form-submissions for a given
|
|
3
|
+
* (form, ipHash) tuple in the past hour. Sufficient for Phase 1 traffic;
|
|
4
|
+
* deployments with high-volume forms should swap in a Redis-backed
|
|
5
|
+
* limiter via plugin options later (the public submit endpoint already
|
|
6
|
+
* abstracts this behind a single function call).
|
|
7
|
+
*/
|
|
8
|
+
export async function checkRateLimit(args) {
|
|
9
|
+
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
|
10
|
+
const result = await args.payload.find({
|
|
11
|
+
collection: args.collectionSlug ?? 'form-submissions',
|
|
12
|
+
where: {
|
|
13
|
+
and: [
|
|
14
|
+
{ form: { equals: args.formId } },
|
|
15
|
+
{ ipHash: { equals: args.ipHash } },
|
|
16
|
+
{ createdAt: { greater_than_equal: oneHourAgo } },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
limit: args.limit + 1,
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
ok: result.totalDocs < args.limit,
|
|
23
|
+
remaining: Math.max(0, args.limit - result.totalDocs),
|
|
24
|
+
total: result.totalDocs,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/submit/rate-limit.ts"],"names":[],"mappings":"AAiBA;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAmB;IACtD,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;IACtE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;QACrC,UAAU,EAAE,IAAI,CAAC,cAAc,IAAI,kBAAkB;QACrD,KAAK,EAAE;YACL,GAAG,EAAE;gBACH,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE;gBACjC,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE;gBACnC,EAAE,SAAS,EAAE,EAAE,kBAAkB,EAAE,UAAU,EAAE,EAAE;aAClD;SACF;QACD,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC;KACtB,CAAC,CAAA;IAEF,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK;QACjC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC;QACrD,KAAK,EAAE,MAAM,CAAC,SAAS;KACxB,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true when the honeypot field is empty (i.e. the submitter is
|
|
3
|
+
* probably a real human; bots fill every input). Designed for use with a
|
|
4
|
+
* visually hidden honeypot input — accessible CSS like
|
|
5
|
+
* `position: absolute; left: -10000px` rather than `display: none` so
|
|
6
|
+
* screen readers still see it but humans don't fill it. `display: none`
|
|
7
|
+
* tells naive crawlers "skip this field"; offscreen positioning does not.
|
|
8
|
+
*/
|
|
9
|
+
export declare function checkHoneypot(value: unknown): boolean;
|
|
10
|
+
//# sourceMappingURL=spam.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spam.d.ts","sourceRoot":"","sources":["../../src/submit/spam.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAIrD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true when the honeypot field is empty (i.e. the submitter is
|
|
3
|
+
* probably a real human; bots fill every input). Designed for use with a
|
|
4
|
+
* visually hidden honeypot input — accessible CSS like
|
|
5
|
+
* `position: absolute; left: -10000px` rather than `display: none` so
|
|
6
|
+
* screen readers still see it but humans don't fill it. `display: none`
|
|
7
|
+
* tells naive crawlers "skip this field"; offscreen positioning does not.
|
|
8
|
+
*/
|
|
9
|
+
export function checkHoneypot(value) {
|
|
10
|
+
if (value === undefined || value === null)
|
|
11
|
+
return true;
|
|
12
|
+
if (typeof value !== 'string')
|
|
13
|
+
return false;
|
|
14
|
+
return value.trim() === '';
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=spam.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spam.js","sourceRoot":"","sources":["../../src/submit/spam.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IACtD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAC3C,OAAO,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAA;AAC5B,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface FormSubmissionField {
|
|
2
|
+
field: string;
|
|
3
|
+
label?: string;
|
|
4
|
+
value: string;
|
|
5
|
+
}
|
|
6
|
+
export interface FormSubmissionEmailProps {
|
|
7
|
+
formTitle: string;
|
|
8
|
+
brandName: string;
|
|
9
|
+
receivedAt: string;
|
|
10
|
+
fields: FormSubmissionField[];
|
|
11
|
+
/** Optional admin URL where the submission can be viewed in Payload. */
|
|
12
|
+
adminUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Admin-facing notification sent to email destinations. Lays out submitted
|
|
16
|
+
* fields in a labeled list rather than a table — tables have more cross-
|
|
17
|
+
* client compatibility hazards than they're worth for a transactional
|
|
18
|
+
* notification.
|
|
19
|
+
*/
|
|
20
|
+
export declare function FormSubmissionEmail(props: FormSubmissionEmailProps): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
//# sourceMappingURL=FormSubmission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormSubmission.d.ts","sourceRoot":"","sources":["../../src/templates/FormSubmission.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,mBAAmB,EAAE,CAAA;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,2CA0DlE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Heading, Section, Text } from '@react-email/components';
|
|
3
|
+
import { FormsLayout } from './_layout.js';
|
|
4
|
+
/**
|
|
5
|
+
* Admin-facing notification sent to email destinations. Lays out submitted
|
|
6
|
+
* fields in a labeled list rather than a table — tables have more cross-
|
|
7
|
+
* client compatibility hazards than they're worth for a transactional
|
|
8
|
+
* notification.
|
|
9
|
+
*/
|
|
10
|
+
export function FormSubmissionEmail(props) {
|
|
11
|
+
const labelStyle = {
|
|
12
|
+
color: '#52525B',
|
|
13
|
+
fontSize: 12,
|
|
14
|
+
fontWeight: 600,
|
|
15
|
+
margin: '16px 0 4px',
|
|
16
|
+
textTransform: 'uppercase',
|
|
17
|
+
letterSpacing: '0.05em',
|
|
18
|
+
};
|
|
19
|
+
const valueStyle = {
|
|
20
|
+
color: '#18181B',
|
|
21
|
+
fontSize: 14,
|
|
22
|
+
margin: 0,
|
|
23
|
+
whiteSpace: 'pre-wrap',
|
|
24
|
+
};
|
|
25
|
+
const linkStyle = {
|
|
26
|
+
color: '#2563EB',
|
|
27
|
+
fontSize: 14,
|
|
28
|
+
textDecoration: 'underline',
|
|
29
|
+
};
|
|
30
|
+
return (_jsxs(FormsLayout, { preview: `New submission for ${props.formTitle}`, brandName: props.brandName, children: [_jsxs(Heading, { style: { color: '#18181B', fontSize: 22, margin: 0 }, children: ["New submission for ", props.formTitle] }), _jsxs(Text, { style: { color: '#52525B', fontSize: 14, marginTop: 8 }, children: ["Received ", props.receivedAt, "."] }), _jsx(Section, { children: props.fields.length === 0 ? (_jsx(Text, { style: { color: '#52525B', fontSize: 14, marginTop: 16 }, children: "(No data submitted.)" })) : (props.fields.map((field) => (_jsxs(Section, { children: [_jsx(Text, { style: labelStyle, children: field.label ?? field.field }), _jsx(Text, { style: valueStyle, children: field.value || '(empty)' })] }, field.field)))) }), props.adminUrl ? (_jsx(Section, { style: { marginTop: 24 }, children: _jsx("a", { href: props.adminUrl, style: linkStyle, children: "View in admin" }) })) : null] }));
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=FormSubmission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormSubmission.js","sourceRoot":"","sources":["../../src/templates/FormSubmission.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AAEhE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAiB1C;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAA+B;IACjE,MAAM,UAAU,GAAkB;QAChC,KAAK,EAAE,SAAS;QAChB,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,GAAG;QACf,MAAM,EAAE,YAAY;QACpB,aAAa,EAAE,WAAW;QAC1B,aAAa,EAAE,QAAQ;KACxB,CAAA;IACD,MAAM,UAAU,GAAkB;QAChC,KAAK,EAAE,SAAS;QAChB,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,CAAC;QACT,UAAU,EAAE,UAAU;KACvB,CAAA;IACD,MAAM,SAAS,GAAkB;QAC/B,KAAK,EAAE,SAAS;QAChB,QAAQ,EAAE,EAAE;QACZ,cAAc,EAAE,WAAW;KAC5B,CAAA;IAED,OAAO,CACL,MAAC,WAAW,IACV,OAAO,EAAE,sBAAsB,KAAK,CAAC,SAAS,EAAE,EAChD,SAAS,EAAE,KAAK,CAAC,SAAS,aAE1B,MAAC,OAAO,IAAC,KAAK,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,oCACvC,KAAK,CAAC,SAAS,IAC3B,EAEV,MAAC,IAAI,IAAC,KAAK,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,0BACjD,KAAK,CAAC,UAAU,SACrB,EAEP,KAAC,OAAO,cACL,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAC3B,KAAC,IAAI,IAAC,KAAK,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,qCAEvD,CACR,CAAC,CAAC,CAAC,CACF,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAC1B,MAAC,OAAO,eACN,KAAC,IAAI,IAAC,KAAK,EAAE,UAAU,YAAG,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,GAAQ,EAC5D,KAAC,IAAI,IAAC,KAAK,EAAE,UAAU,YAAG,KAAK,CAAC,KAAK,IAAI,SAAS,GAAQ,KAF9C,KAAK,CAAC,KAAK,CAGf,CACX,CAAC,CACH,GACO,EAET,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAChB,KAAC,OAAO,IAAC,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,YAC/B,YAAG,IAAI,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,SAAS,8BAErC,GACI,CACX,CAAC,CAAC,CAAC,IAAI,IACI,CACf,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface SubmitterConfirmationEmailProps {
|
|
2
|
+
/** Free-form recipient name (or `"there"` if unknown). */
|
|
3
|
+
recipientName: string;
|
|
4
|
+
brandName: string;
|
|
5
|
+
/** Per-form subject line; the configured fallback is "Thank you for your submission". */
|
|
6
|
+
subject: string;
|
|
7
|
+
/** Per-form body. Multi-paragraph; rendered as plain prose preserving line breaks. */
|
|
8
|
+
body: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Submitter-facing auto-reply. Intentionally minimal: a greeting, the
|
|
12
|
+
* configured body, and a brand-named footer. The `body` is operator-
|
|
13
|
+
* authored copy from the form admin; this template treats it as trusted
|
|
14
|
+
* (admin-edited) content but renders it as plaintext — `body` strings
|
|
15
|
+
* containing HTML render literally rather than as markup.
|
|
16
|
+
*/
|
|
17
|
+
export declare function SubmitterConfirmationEmail(props: SubmitterConfirmationEmailProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
//# sourceMappingURL=SubmitterConfirmation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubmitterConfirmation.d.ts","sourceRoot":"","sources":["../../src/templates/SubmitterConfirmation.tsx"],"names":[],"mappings":"AAGA,MAAM,WAAW,+BAA+B;IAC9C,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,yFAAyF;IACzF,OAAO,EAAE,MAAM,CAAA;IACf,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,+BAA+B,2CAqBhF"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Heading, Text } from '@react-email/components';
|
|
3
|
+
import { FormsLayout } from './_layout.js';
|
|
4
|
+
/**
|
|
5
|
+
* Submitter-facing auto-reply. Intentionally minimal: a greeting, the
|
|
6
|
+
* configured body, and a brand-named footer. The `body` is operator-
|
|
7
|
+
* authored copy from the form admin; this template treats it as trusted
|
|
8
|
+
* (admin-edited) content but renders it as plaintext — `body` strings
|
|
9
|
+
* containing HTML render literally rather than as markup.
|
|
10
|
+
*/
|
|
11
|
+
export function SubmitterConfirmationEmail(props) {
|
|
12
|
+
return (_jsxs(FormsLayout, { preview: props.subject, brandName: props.brandName, children: [_jsx(Heading, { style: { color: '#18181B', fontSize: 22, margin: 0 }, children: props.subject }), _jsxs(Text, { style: { color: '#18181B', fontSize: 16, marginTop: 16 }, children: ["Hi ", props.recipientName, ","] }), (props.body || '').split(/\n{2,}/).map((paragraph, idx) => (_jsx(Text, { style: { color: '#18181B', fontSize: 14, lineHeight: 1.5, whiteSpace: 'pre-wrap' }, children: paragraph }, idx)))] }));
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=SubmitterConfirmation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubmitterConfirmation.js","sourceRoot":"","sources":["../../src/templates/SubmitterConfirmation.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAY1C;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CAAC,KAAsC;IAC/E,OAAO,CACL,MAAC,WAAW,IAAC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,aAC7D,KAAC,OAAO,IAAC,KAAK,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,YAC1D,KAAK,CAAC,OAAO,GACN,EAEV,MAAC,IAAI,IAAC,KAAK,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,oBACxD,KAAK,CAAC,aAAa,SAClB,EAEN,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,GAAG,EAAE,EAAE,CAAC,CAC1D,KAAC,IAAI,IAEH,KAAK,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,YAEjF,SAAS,IAHL,GAAG,CAIH,CACR,CAAC,IACU,CACf,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export interface FormsLayoutProps {
|
|
3
|
+
preview: string;
|
|
4
|
+
brandName: string;
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Lightweight chrome shared across the two forms-package templates. Modeled
|
|
9
|
+
* on the email package's EmailLayout but uses neutral defaults rather than
|
|
10
|
+
* pulling the full token surface — these emails are short, transactional,
|
|
11
|
+
* and benefit from minimal branding.
|
|
12
|
+
*/
|
|
13
|
+
export declare function FormsLayout({ preview, brandName, children }: FormsLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=_layout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_layout.d.ts","sourceRoot":"","sources":["../../src/templates/_layout.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAErD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,gBAAgB,2CA2C7E"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Body, Container, Head, Hr, Html, Preview, Section, Text } from '@react-email/components';
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight chrome shared across the two forms-package templates. Modeled
|
|
5
|
+
* on the email package's EmailLayout but uses neutral defaults rather than
|
|
6
|
+
* pulling the full token surface — these emails are short, transactional,
|
|
7
|
+
* and benefit from minimal branding.
|
|
8
|
+
*/
|
|
9
|
+
export function FormsLayout({ preview, brandName, children }) {
|
|
10
|
+
const bodyStyle = {
|
|
11
|
+
backgroundColor: '#F5F5F5',
|
|
12
|
+
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, sans-serif',
|
|
13
|
+
margin: 0,
|
|
14
|
+
padding: '24px 0',
|
|
15
|
+
};
|
|
16
|
+
const containerStyle = {
|
|
17
|
+
backgroundColor: '#FFFFFF',
|
|
18
|
+
maxWidth: 600,
|
|
19
|
+
margin: '0 auto',
|
|
20
|
+
padding: 32,
|
|
21
|
+
borderRadius: 8,
|
|
22
|
+
};
|
|
23
|
+
const headerStyle = {
|
|
24
|
+
color: '#18181B',
|
|
25
|
+
fontWeight: 700,
|
|
26
|
+
fontSize: 18,
|
|
27
|
+
margin: 0,
|
|
28
|
+
};
|
|
29
|
+
const dividerStyle = { borderColor: '#E4E4E7', margin: '24px 0' };
|
|
30
|
+
const footerDividerStyle = { borderColor: '#E4E4E7', margin: '32px 0 16px' };
|
|
31
|
+
const footerStyle = { color: '#52525B', fontSize: 12, margin: 0 };
|
|
32
|
+
return (_jsxs(Html, { lang: "en", children: [_jsx(Head, {}), _jsx(Preview, { children: preview }), _jsx(Body, { style: bodyStyle, children: _jsxs(Container, { style: containerStyle, children: [_jsx(Section, { children: _jsx(Text, { style: headerStyle, children: brandName }) }), _jsx(Hr, { style: dividerStyle }), children, _jsx(Hr, { style: footerDividerStyle }), _jsxs(Text, { style: footerStyle, children: ["This is an automated message from ", brandName, ". Replies to this address are not monitored."] })] }) })] }));
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=_layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_layout.js","sourceRoot":"","sources":["../../src/templates/_layout.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAA;AASjG;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAoB;IAC5E,MAAM,SAAS,GAAkB;QAC/B,eAAe,EAAE,SAAS;QAC1B,UAAU,EAAE,0DAA0D;QACtE,MAAM,EAAE,CAAC;QACT,OAAO,EAAE,QAAQ;KAClB,CAAA;IACD,MAAM,cAAc,GAAkB;QACpC,eAAe,EAAE,SAAS;QAC1B,QAAQ,EAAE,GAAG;QACb,MAAM,EAAE,QAAQ;QAChB,OAAO,EAAE,EAAE;QACX,YAAY,EAAE,CAAC;KAChB,CAAA;IACD,MAAM,WAAW,GAAkB;QACjC,KAAK,EAAE,SAAS;QAChB,UAAU,EAAE,GAAG;QACf,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,CAAC;KACV,CAAA;IACD,MAAM,YAAY,GAAkB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAA;IAChF,MAAM,kBAAkB,GAAkB,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,CAAA;IAC3F,MAAM,WAAW,GAAkB,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAA;IAEhF,OAAO,CACL,MAAC,IAAI,IAAC,IAAI,EAAC,IAAI,aACb,KAAC,IAAI,KAAG,EACR,KAAC,OAAO,cAAE,OAAO,GAAW,EAC5B,KAAC,IAAI,IAAC,KAAK,EAAE,SAAS,YACpB,MAAC,SAAS,IAAC,KAAK,EAAE,cAAc,aAC9B,KAAC,OAAO,cACN,KAAC,IAAI,IAAC,KAAK,EAAE,WAAW,YAAG,SAAS,GAAQ,GACpC,EACV,KAAC,EAAE,IAAC,KAAK,EAAE,YAAY,GAAI,EAC1B,QAAQ,EACT,KAAC,EAAE,IAAC,KAAK,EAAE,kBAAkB,GAAI,EACjC,MAAC,IAAI,IAAC,KAAK,EAAE,WAAW,mDACa,SAAS,oDACvC,IACG,GACP,IACF,CACR,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { FormsLayout } from './_layout.js';
|
|
2
|
+
export type { FormsLayoutProps } from './_layout.js';
|
|
3
|
+
export { FormSubmissionEmail } from './FormSubmission.js';
|
|
4
|
+
export type { FormSubmissionEmailProps, FormSubmissionField } from './FormSubmission.js';
|
|
5
|
+
export { SubmitterConfirmationEmail } from './SubmitterConfirmation.js';
|
|
6
|
+
export type { SubmitterConfirmationEmailProps } from './SubmitterConfirmation.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAEpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AACzD,YAAY,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAExF,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAA;AACvE,YAAY,EAAE,+BAA+B,EAAE,MAAM,4BAA4B,CAAA"}
|