@orion-studios/payload-studio 0.6.0-beta.8 → 0.7.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/README.md +6 -1
- package/bin/init.js +329 -0
- package/dist/admin/client.js +2106 -585
- package/dist/admin/client.mjs +2161 -653
- package/dist/admin/index.d.mts +2 -2
- package/dist/admin/index.d.ts +2 -2
- package/dist/admin/index.js +201 -37
- package/dist/admin/index.mjs +2 -2
- package/dist/admin-app/client.js +14 -5
- package/dist/admin-app/client.mjs +1 -1
- package/dist/admin-app/index.d.mts +2 -2
- package/dist/admin-app/index.d.ts +2 -2
- package/dist/admin-app/styles.css +361 -41
- package/dist/admin.css +98 -2
- package/dist/blocks/index.mjs +1 -2
- package/dist/builder-v2/client.d.mts +18 -0
- package/dist/builder-v2/client.d.ts +18 -0
- package/dist/builder-v2/client.js +4287 -0
- package/dist/builder-v2/client.mjs +4162 -0
- package/dist/builder-v2/index.d.mts +249 -0
- package/dist/builder-v2/index.d.ts +249 -0
- package/dist/builder-v2/index.js +836 -0
- package/dist/builder-v2/index.mjs +786 -0
- package/dist/builder-v2/styles.css +2705 -0
- package/dist/{chunk-PF3EBZXF.mjs → chunk-7ZMXZRBP.mjs} +39 -3
- package/dist/{chunk-XKUTZ7IU.mjs → chunk-DTHBPJXU.mjs} +246 -25
- package/dist/{chunk-JQAHXYAM.mjs → chunk-KMYMSR7W.mjs} +163 -3
- package/dist/{chunk-5FNTVRCR.mjs → chunk-MMJNHBOF.mjs} +204 -40
- package/dist/{chunk-KPIX7OSV.mjs → chunk-NF37A575.mjs} +14 -5
- package/dist/{chunk-OTHERBGX.mjs → chunk-OF6BATTO.mjs} +57 -112
- package/dist/chunk-VA545CHJ.mjs +202 -0
- package/dist/forms/index.d.mts +25 -0
- package/dist/forms/index.d.ts +25 -0
- package/dist/forms/index.js +237 -0
- package/dist/forms/index.mjs +25 -0
- package/dist/forms/server.d.mts +35 -0
- package/dist/forms/server.d.ts +35 -0
- package/dist/forms/server.js +442 -0
- package/dist/forms/server.mjs +228 -0
- package/dist/{index-Crx_MtPw.d.ts → index-0xXWuOuz.d.ts} +38 -6
- package/dist/{index-Cv-6qnrw.d.mts → index-BU82akSL.d.mts} +38 -6
- package/dist/{index-DyMmaRfI.d.mts → index-BbTcimgH.d.mts} +5 -2
- package/dist/{index-D8BNfUJb.d.mts → index-Cen-9wcc.d.mts} +17 -5
- package/dist/{index-52HdVLQq.d.ts → index-DAdN56fM.d.ts} +1 -1
- package/dist/{index-QPDAedIX.d.ts → index-DV-nW43e.d.ts} +5 -2
- package/dist/{index-DEQC3Dwj.d.mts → index-G_uTNffQ.d.mts} +1 -1
- package/dist/{index-DD_E2UfJ.d.ts → index-gxcMPpm-.d.ts} +17 -5
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +533 -172
- package/dist/index.mjs +10 -11
- package/dist/nextjs/index.d.mts +1 -1
- package/dist/nextjs/index.d.ts +1 -1
- package/dist/nextjs/index.js +95 -114
- package/dist/nextjs/index.mjs +2 -2
- package/dist/{sitePreviewTypes-BkHCWxNW.d.mts → sitePreviewTypes-BrJwGzJj.d.mts} +1 -1
- package/dist/{sitePreviewTypes-BkHCWxNW.d.ts → sitePreviewTypes-BrJwGzJj.d.ts} +1 -1
- package/dist/studio-pages/builder.css +66 -5
- package/dist/studio-pages/client.js +618 -73
- package/dist/studio-pages/client.mjs +641 -96
- package/dist/studio-pages/index.d.mts +1 -1
- package/dist/studio-pages/index.d.ts +1 -1
- package/dist/studio-pages/index.js +1893 -24
- package/dist/studio-pages/index.mjs +11 -3
- package/package.json +38 -3
- package/dist/chunk-OQSEJXC4.mjs +0 -166
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/forms/server.ts
|
|
21
|
+
var server_exports = {};
|
|
22
|
+
__export(server_exports, {
|
|
23
|
+
createFormSubmissionHandler: () => createFormSubmissionHandler,
|
|
24
|
+
createMemoryRateLimitStore: () => createMemoryRateLimitStore,
|
|
25
|
+
formatPhoneUS: () => formatPhoneUS,
|
|
26
|
+
inferFieldType: () => inferFieldType,
|
|
27
|
+
normalizeEmail: () => normalizeEmail,
|
|
28
|
+
normalizeFieldValue: () => normalizeFieldValue,
|
|
29
|
+
normalizePhone: () => normalizePhone,
|
|
30
|
+
normalizeUrl: () => normalizeUrl,
|
|
31
|
+
validateEmail: () => validateEmail,
|
|
32
|
+
validatePhoneUS: () => validatePhoneUS,
|
|
33
|
+
validateRequired: () => validateRequired,
|
|
34
|
+
validateUrl: () => validateUrl
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(server_exports);
|
|
37
|
+
|
|
38
|
+
// src/forms/validation.ts
|
|
39
|
+
var MAJOR_EMAIL_PROVIDERS = [
|
|
40
|
+
"gmail.com",
|
|
41
|
+
"yahoo.com",
|
|
42
|
+
"outlook.com",
|
|
43
|
+
"hotmail.com",
|
|
44
|
+
"icloud.com",
|
|
45
|
+
"live.com",
|
|
46
|
+
"protonmail.com"
|
|
47
|
+
];
|
|
48
|
+
var KNOWN_GOOD_DOMAINS = /* @__PURE__ */ new Set([
|
|
49
|
+
...MAJOR_EMAIL_PROVIDERS,
|
|
50
|
+
"aol.com",
|
|
51
|
+
"gmx.com",
|
|
52
|
+
"googlemail.com",
|
|
53
|
+
"hey.com",
|
|
54
|
+
"mac.com",
|
|
55
|
+
"mail.com",
|
|
56
|
+
"me.com",
|
|
57
|
+
"msn.com",
|
|
58
|
+
"pm.me",
|
|
59
|
+
"proton.me",
|
|
60
|
+
"ymail.com",
|
|
61
|
+
"zoho.com"
|
|
62
|
+
]);
|
|
63
|
+
var EXPLICIT_TYPO_MAP = {
|
|
64
|
+
"gamil.com": "gmail.com",
|
|
65
|
+
"gmial.com": "gmail.com",
|
|
66
|
+
"gmai.com": "gmail.com",
|
|
67
|
+
"gmaill.com": "gmail.com",
|
|
68
|
+
"gmail.co": "gmail.com",
|
|
69
|
+
"gmail.con": "gmail.com",
|
|
70
|
+
"gmail.cmo": "gmail.com",
|
|
71
|
+
"hotmial.com": "hotmail.com",
|
|
72
|
+
"hotmall.com": "hotmail.com",
|
|
73
|
+
"hotmail.co": "hotmail.com",
|
|
74
|
+
"hotmail.con": "hotmail.com",
|
|
75
|
+
"iclod.com": "icloud.com",
|
|
76
|
+
"icloud.co": "icloud.com",
|
|
77
|
+
"icoud.com": "icloud.com",
|
|
78
|
+
"outlok.com": "outlook.com",
|
|
79
|
+
"outloook.com": "outlook.com",
|
|
80
|
+
"outlook.co": "outlook.com",
|
|
81
|
+
"outlook.con": "outlook.com",
|
|
82
|
+
"protonmail.co": "protonmail.com",
|
|
83
|
+
"yaho.com": "yahoo.com",
|
|
84
|
+
"yahooo.com": "yahoo.com",
|
|
85
|
+
"yahoo.co": "yahoo.com",
|
|
86
|
+
"yahoo.con": "yahoo.com"
|
|
87
|
+
};
|
|
88
|
+
var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
89
|
+
function levenshtein(a, b) {
|
|
90
|
+
if (a === b) return 0;
|
|
91
|
+
const rows = a.length + 1;
|
|
92
|
+
const cols = b.length + 1;
|
|
93
|
+
const dist = Array.from({ length: cols }, (_, j) => j);
|
|
94
|
+
for (let i = 1; i < rows; i += 1) {
|
|
95
|
+
let prevDiagonal = dist[0];
|
|
96
|
+
dist[0] = i;
|
|
97
|
+
for (let j = 1; j < cols; j += 1) {
|
|
98
|
+
const temp = dist[j];
|
|
99
|
+
dist[j] = Math.min(
|
|
100
|
+
dist[j] + 1,
|
|
101
|
+
dist[j - 1] + 1,
|
|
102
|
+
prevDiagonal + (a[i - 1] === b[j - 1] ? 0 : 1)
|
|
103
|
+
);
|
|
104
|
+
prevDiagonal = temp;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return dist[cols - 1];
|
|
108
|
+
}
|
|
109
|
+
function normalizeEmail(value) {
|
|
110
|
+
return value.trim().toLowerCase();
|
|
111
|
+
}
|
|
112
|
+
function validateEmail(value, options = {}) {
|
|
113
|
+
const normalized = normalizeEmail(value);
|
|
114
|
+
if (normalized.length === 0) {
|
|
115
|
+
return { valid: false, normalized, message: "Email is required." };
|
|
116
|
+
}
|
|
117
|
+
if (!EMAIL_PATTERN.test(normalized)) {
|
|
118
|
+
return { valid: false, normalized, message: "Enter a valid email address." };
|
|
119
|
+
}
|
|
120
|
+
const [localPart, domain] = normalized.split("@");
|
|
121
|
+
const knownGood = options.knownGoodDomains ? /* @__PURE__ */ new Set([...KNOWN_GOOD_DOMAINS, ...options.knownGoodDomains.map((entry) => entry.toLowerCase())]) : KNOWN_GOOD_DOMAINS;
|
|
122
|
+
if (knownGood.has(domain)) {
|
|
123
|
+
return { valid: true, normalized };
|
|
124
|
+
}
|
|
125
|
+
const explicitCorrection = EXPLICIT_TYPO_MAP[domain];
|
|
126
|
+
if (explicitCorrection) {
|
|
127
|
+
const suggestion = `${localPart}@${explicitCorrection}`;
|
|
128
|
+
return {
|
|
129
|
+
valid: false,
|
|
130
|
+
normalized,
|
|
131
|
+
message: `Did you mean ${suggestion}?`,
|
|
132
|
+
suggestion
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
for (const provider of MAJOR_EMAIL_PROVIDERS) {
|
|
136
|
+
if (levenshtein(domain, provider) === 1) {
|
|
137
|
+
const suggestion = `${localPart}@${provider}`;
|
|
138
|
+
return {
|
|
139
|
+
valid: false,
|
|
140
|
+
normalized,
|
|
141
|
+
message: `Did you mean ${suggestion}?`,
|
|
142
|
+
suggestion
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { valid: true, normalized };
|
|
147
|
+
}
|
|
148
|
+
function normalizePhone(value) {
|
|
149
|
+
const digits = value.replace(/\D+/g, "");
|
|
150
|
+
if (digits.length === 11 && digits.startsWith("1")) {
|
|
151
|
+
return digits.slice(1);
|
|
152
|
+
}
|
|
153
|
+
return digits;
|
|
154
|
+
}
|
|
155
|
+
function formatPhoneUS(value) {
|
|
156
|
+
const digits = normalizePhone(value).slice(0, 10);
|
|
157
|
+
if (digits.length === 0) return "";
|
|
158
|
+
if (digits.length < 4) return `(${digits}`;
|
|
159
|
+
if (digits.length < 7) return `(${digits.slice(0, 3)}) ${digits.slice(3)}`;
|
|
160
|
+
return `(${digits.slice(0, 3)}) ${digits.slice(3, 6)}-${digits.slice(6)}`;
|
|
161
|
+
}
|
|
162
|
+
function validatePhoneUS(value) {
|
|
163
|
+
const digits = normalizePhone(value);
|
|
164
|
+
if (digits.length === 0) {
|
|
165
|
+
return { valid: false, normalized: "", message: "Phone number is required." };
|
|
166
|
+
}
|
|
167
|
+
if (digits.length !== 10) {
|
|
168
|
+
return { valid: false, normalized: digits, message: "Enter a 10-digit phone number." };
|
|
169
|
+
}
|
|
170
|
+
return { valid: true, normalized: digits };
|
|
171
|
+
}
|
|
172
|
+
function normalizeUrl(value) {
|
|
173
|
+
const trimmed = value.trim();
|
|
174
|
+
if (trimmed.length === 0) return "";
|
|
175
|
+
if (/^https?:\/\//i.test(trimmed)) return trimmed;
|
|
176
|
+
return `https://${trimmed}`;
|
|
177
|
+
}
|
|
178
|
+
function validateUrl(value) {
|
|
179
|
+
const normalized = normalizeUrl(value);
|
|
180
|
+
if (normalized.length === 0) {
|
|
181
|
+
return { valid: false, normalized, message: "URL is required." };
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const parsed = new URL(normalized);
|
|
185
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
186
|
+
return { valid: false, normalized, message: "Enter a valid web address." };
|
|
187
|
+
}
|
|
188
|
+
if (!parsed.hostname.includes(".")) {
|
|
189
|
+
return { valid: false, normalized, message: "Enter a valid web address." };
|
|
190
|
+
}
|
|
191
|
+
return { valid: true, normalized };
|
|
192
|
+
} catch {
|
|
193
|
+
return { valid: false, normalized, message: "Enter a valid web address." };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function validateRequired(value, label = "This field") {
|
|
197
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
198
|
+
const present = typeof value === "string" ? normalized.length > 0 : Array.isArray(value) ? value.length > 0 : value !== null && value !== void 0 && value !== false;
|
|
199
|
+
if (!present) {
|
|
200
|
+
return { valid: false, normalized, message: `${label} is required.` };
|
|
201
|
+
}
|
|
202
|
+
return { valid: true, normalized: typeof value === "string" ? normalized : String(value) };
|
|
203
|
+
}
|
|
204
|
+
function normalizeFieldValue(value, fieldType) {
|
|
205
|
+
switch (fieldType) {
|
|
206
|
+
case "email":
|
|
207
|
+
return normalizeEmail(value);
|
|
208
|
+
case "phone":
|
|
209
|
+
return normalizePhone(value);
|
|
210
|
+
case "url":
|
|
211
|
+
return normalizeUrl(value);
|
|
212
|
+
default:
|
|
213
|
+
return value.trim();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function inferFieldType(field) {
|
|
217
|
+
const type = typeof field.type === "string" ? field.type.toLowerCase() : "";
|
|
218
|
+
if (type === "email" || type === "phone" || type === "tel" || type === "url") {
|
|
219
|
+
return type === "tel" ? "phone" : type;
|
|
220
|
+
}
|
|
221
|
+
const name = typeof field.name === "string" ? field.name.toLowerCase() : "";
|
|
222
|
+
if (name.includes("email")) return "email";
|
|
223
|
+
if (name.includes("phone") || name.includes("mobile")) return "phone";
|
|
224
|
+
if (name.includes("website") || name.endsWith("url")) return "url";
|
|
225
|
+
return "text";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/forms/submissionHandler.ts
|
|
229
|
+
function createMemoryRateLimitStore(options) {
|
|
230
|
+
const max = options?.max ?? 5;
|
|
231
|
+
const windowMs = options?.windowMs ?? 6e4;
|
|
232
|
+
const hits = /* @__PURE__ */ new Map();
|
|
233
|
+
return {
|
|
234
|
+
isLimited(key, now) {
|
|
235
|
+
const windowStart = now - windowMs;
|
|
236
|
+
const entries = (hits.get(key) || []).filter((timestamp) => timestamp > windowStart);
|
|
237
|
+
entries.push(now);
|
|
238
|
+
hits.set(key, entries);
|
|
239
|
+
if (hits.size > 1e4) {
|
|
240
|
+
for (const [entryKey, timestamps] of hits) {
|
|
241
|
+
if (timestamps.every((timestamp) => timestamp <= windowStart)) {
|
|
242
|
+
hits.delete(entryKey);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return entries.length > max;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
var json = (body, status) => new Response(JSON.stringify(body), {
|
|
251
|
+
status,
|
|
252
|
+
headers: { "content-type": "application/json" }
|
|
253
|
+
});
|
|
254
|
+
var getClientKey = (request) => {
|
|
255
|
+
const forwarded = request.headers.get("x-forwarded-for");
|
|
256
|
+
if (forwarded) {
|
|
257
|
+
return forwarded.split(",")[0].trim();
|
|
258
|
+
}
|
|
259
|
+
return request.headers.get("x-real-ip") || "unknown";
|
|
260
|
+
};
|
|
261
|
+
var isOriginAllowed = (request, allowedOrigins) => {
|
|
262
|
+
const origin = request.headers.get("origin");
|
|
263
|
+
if (!origin) {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
if (allowedOrigins && allowedOrigins.length > 0) {
|
|
267
|
+
return allowedOrigins.includes(origin);
|
|
268
|
+
}
|
|
269
|
+
const host = request.headers.get("x-forwarded-host") || request.headers.get("host");
|
|
270
|
+
if (!host) return false;
|
|
271
|
+
try {
|
|
272
|
+
return new URL(origin).host === host;
|
|
273
|
+
} catch {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
var collectFormFieldDefinitions = (formDoc) => {
|
|
278
|
+
const steps = Array.isArray(formDoc.steps) ? formDoc.steps : [];
|
|
279
|
+
const definitions = [];
|
|
280
|
+
for (const step of steps) {
|
|
281
|
+
if (!step || typeof step !== "object") continue;
|
|
282
|
+
const fields = step.fields;
|
|
283
|
+
for (const field of Array.isArray(fields) ? fields : []) {
|
|
284
|
+
if (!field || typeof field !== "object") continue;
|
|
285
|
+
const record = field;
|
|
286
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
287
|
+
if (!name) continue;
|
|
288
|
+
definitions.push({
|
|
289
|
+
name,
|
|
290
|
+
type: inferFieldType(record),
|
|
291
|
+
required: record.required === true,
|
|
292
|
+
label: typeof record.label === "string" && record.label.length > 0 ? record.label : name
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return definitions;
|
|
297
|
+
};
|
|
298
|
+
function createFormSubmissionHandler(options) {
|
|
299
|
+
const {
|
|
300
|
+
getPayloadClient,
|
|
301
|
+
formsCollectionSlug = "forms",
|
|
302
|
+
submissionsCollectionSlug = "form-submissions",
|
|
303
|
+
allowedOrigins,
|
|
304
|
+
honeypotFieldName = "website_url_confirm",
|
|
305
|
+
minFillTimeMs = 3e3,
|
|
306
|
+
knownGoodEmailDomains,
|
|
307
|
+
onSubmission
|
|
308
|
+
} = options;
|
|
309
|
+
const rateLimitStore = options.rateLimitStore === null ? null : options.rateLimitStore || createMemoryRateLimitStore();
|
|
310
|
+
return async function POST(request, context) {
|
|
311
|
+
if (!isOriginAllowed(request, allowedOrigins)) {
|
|
312
|
+
return json({ error: "Origin not allowed." }, 403);
|
|
313
|
+
}
|
|
314
|
+
const now = Date.now();
|
|
315
|
+
if (rateLimitStore && rateLimitStore.isLimited(getClientKey(request), now)) {
|
|
316
|
+
return json({ error: "Too many requests. Please try again shortly." }, 429);
|
|
317
|
+
}
|
|
318
|
+
const params = await context.params;
|
|
319
|
+
const slug = typeof params?.slug === "string" ? params.slug : "";
|
|
320
|
+
if (!slug) {
|
|
321
|
+
return json({ error: "Unknown form." }, 404);
|
|
322
|
+
}
|
|
323
|
+
let body;
|
|
324
|
+
try {
|
|
325
|
+
const parsed = await request.json();
|
|
326
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
327
|
+
return json({ error: "Invalid submission body." }, 400);
|
|
328
|
+
}
|
|
329
|
+
body = parsed;
|
|
330
|
+
} catch {
|
|
331
|
+
return json({ error: "Invalid submission body." }, 400);
|
|
332
|
+
}
|
|
333
|
+
const data = body.data && typeof body.data === "object" && !Array.isArray(body.data) ? body.data : body;
|
|
334
|
+
const honeypotValue = data[honeypotFieldName];
|
|
335
|
+
if (typeof honeypotValue === "string" && honeypotValue.trim().length > 0) {
|
|
336
|
+
return json({ success: true }, 200);
|
|
337
|
+
}
|
|
338
|
+
if (minFillTimeMs > 0) {
|
|
339
|
+
const renderedAt = Number(data._renderedAt ?? body._renderedAt);
|
|
340
|
+
if (Number.isFinite(renderedAt) && renderedAt > 0 && now - renderedAt < minFillTimeMs) {
|
|
341
|
+
return json({ success: true }, 200);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const payload = await getPayloadClient();
|
|
345
|
+
const formResult = await payload.find({
|
|
346
|
+
collection: formsCollectionSlug,
|
|
347
|
+
depth: 0,
|
|
348
|
+
limit: 1,
|
|
349
|
+
overrideAccess: false,
|
|
350
|
+
where: { slug: { equals: slug } }
|
|
351
|
+
});
|
|
352
|
+
const formDoc = formResult.docs[0];
|
|
353
|
+
if (!formDoc) {
|
|
354
|
+
return json({ error: "Unknown form." }, 404);
|
|
355
|
+
}
|
|
356
|
+
const definitions = collectFormFieldDefinitions(formDoc);
|
|
357
|
+
const normalizedData = {};
|
|
358
|
+
const errors = {};
|
|
359
|
+
const knownNames = new Set(definitions.map((definition) => definition.name));
|
|
360
|
+
for (const definition of definitions) {
|
|
361
|
+
const rawValue = data[definition.name];
|
|
362
|
+
const isString = typeof rawValue === "string";
|
|
363
|
+
const normalized = isString ? normalizeFieldValue(rawValue, definition.type) : "";
|
|
364
|
+
const missing = rawValue === void 0 || rawValue === null || isString && normalized.length === 0 || Array.isArray(rawValue) && rawValue.length === 0;
|
|
365
|
+
if (definition.required && missing) {
|
|
366
|
+
errors[definition.name] = `${definition.label} is required.`;
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (normalized.length > 0) {
|
|
370
|
+
if (definition.type === "email") {
|
|
371
|
+
const result = validateEmail(normalized, { knownGoodDomains: knownGoodEmailDomains });
|
|
372
|
+
if (!result.valid) {
|
|
373
|
+
errors[definition.name] = result.message || "Enter a valid email address.";
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
normalizedData[definition.name] = result.normalized;
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
if (definition.type === "phone") {
|
|
380
|
+
const result = validatePhoneUS(normalized);
|
|
381
|
+
if (!result.valid) {
|
|
382
|
+
errors[definition.name] = result.message || "Enter a valid phone number.";
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
normalizedData[definition.name] = result.normalized;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (definition.type === "url") {
|
|
389
|
+
const result = validateUrl(normalized);
|
|
390
|
+
if (!result.valid) {
|
|
391
|
+
errors[definition.name] = result.message || "Enter a valid web address.";
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
normalizedData[definition.name] = result.normalized;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (rawValue !== void 0 && rawValue !== null) {
|
|
399
|
+
normalizedData[definition.name] = typeof rawValue === "string" ? normalized : rawValue;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
for (const [key, value] of Object.entries(data)) {
|
|
403
|
+
if (knownNames.has(key) || key === honeypotFieldName || key.startsWith("_")) continue;
|
|
404
|
+
normalizedData[key] = typeof value === "string" ? value.trim() : value;
|
|
405
|
+
}
|
|
406
|
+
if (Object.keys(errors).length > 0) {
|
|
407
|
+
return json({ error: "Validation failed.", fieldErrors: errors }, 422);
|
|
408
|
+
}
|
|
409
|
+
const submission = await payload.create({
|
|
410
|
+
collection: submissionsCollectionSlug,
|
|
411
|
+
data: {
|
|
412
|
+
data: normalizedData,
|
|
413
|
+
form: formDoc.id,
|
|
414
|
+
submittedAt: new Date(now).toISOString()
|
|
415
|
+
},
|
|
416
|
+
overrideAccess: true
|
|
417
|
+
});
|
|
418
|
+
if (onSubmission) {
|
|
419
|
+
try {
|
|
420
|
+
await onSubmission({ formDoc, normalizedData, submissionID: submission.id });
|
|
421
|
+
} catch (error) {
|
|
422
|
+
payload.logger?.error?.({ err: error, msg: "Form submission onSubmission hook failed" });
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return json({ success: true, id: submission.id }, 200);
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
429
|
+
0 && (module.exports = {
|
|
430
|
+
createFormSubmissionHandler,
|
|
431
|
+
createMemoryRateLimitStore,
|
|
432
|
+
formatPhoneUS,
|
|
433
|
+
inferFieldType,
|
|
434
|
+
normalizeEmail,
|
|
435
|
+
normalizeFieldValue,
|
|
436
|
+
normalizePhone,
|
|
437
|
+
normalizeUrl,
|
|
438
|
+
validateEmail,
|
|
439
|
+
validatePhoneUS,
|
|
440
|
+
validateRequired,
|
|
441
|
+
validateUrl
|
|
442
|
+
});
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatPhoneUS,
|
|
3
|
+
inferFieldType,
|
|
4
|
+
normalizeEmail,
|
|
5
|
+
normalizeFieldValue,
|
|
6
|
+
normalizePhone,
|
|
7
|
+
normalizeUrl,
|
|
8
|
+
validateEmail,
|
|
9
|
+
validatePhoneUS,
|
|
10
|
+
validateRequired,
|
|
11
|
+
validateUrl
|
|
12
|
+
} from "../chunk-VA545CHJ.mjs";
|
|
13
|
+
import "../chunk-6BWS3CLP.mjs";
|
|
14
|
+
|
|
15
|
+
// src/forms/submissionHandler.ts
|
|
16
|
+
function createMemoryRateLimitStore(options) {
|
|
17
|
+
const max = options?.max ?? 5;
|
|
18
|
+
const windowMs = options?.windowMs ?? 6e4;
|
|
19
|
+
const hits = /* @__PURE__ */ new Map();
|
|
20
|
+
return {
|
|
21
|
+
isLimited(key, now) {
|
|
22
|
+
const windowStart = now - windowMs;
|
|
23
|
+
const entries = (hits.get(key) || []).filter((timestamp) => timestamp > windowStart);
|
|
24
|
+
entries.push(now);
|
|
25
|
+
hits.set(key, entries);
|
|
26
|
+
if (hits.size > 1e4) {
|
|
27
|
+
for (const [entryKey, timestamps] of hits) {
|
|
28
|
+
if (timestamps.every((timestamp) => timestamp <= windowStart)) {
|
|
29
|
+
hits.delete(entryKey);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return entries.length > max;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
var json = (body, status) => new Response(JSON.stringify(body), {
|
|
38
|
+
status,
|
|
39
|
+
headers: { "content-type": "application/json" }
|
|
40
|
+
});
|
|
41
|
+
var getClientKey = (request) => {
|
|
42
|
+
const forwarded = request.headers.get("x-forwarded-for");
|
|
43
|
+
if (forwarded) {
|
|
44
|
+
return forwarded.split(",")[0].trim();
|
|
45
|
+
}
|
|
46
|
+
return request.headers.get("x-real-ip") || "unknown";
|
|
47
|
+
};
|
|
48
|
+
var isOriginAllowed = (request, allowedOrigins) => {
|
|
49
|
+
const origin = request.headers.get("origin");
|
|
50
|
+
if (!origin) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (allowedOrigins && allowedOrigins.length > 0) {
|
|
54
|
+
return allowedOrigins.includes(origin);
|
|
55
|
+
}
|
|
56
|
+
const host = request.headers.get("x-forwarded-host") || request.headers.get("host");
|
|
57
|
+
if (!host) return false;
|
|
58
|
+
try {
|
|
59
|
+
return new URL(origin).host === host;
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var collectFormFieldDefinitions = (formDoc) => {
|
|
65
|
+
const steps = Array.isArray(formDoc.steps) ? formDoc.steps : [];
|
|
66
|
+
const definitions = [];
|
|
67
|
+
for (const step of steps) {
|
|
68
|
+
if (!step || typeof step !== "object") continue;
|
|
69
|
+
const fields = step.fields;
|
|
70
|
+
for (const field of Array.isArray(fields) ? fields : []) {
|
|
71
|
+
if (!field || typeof field !== "object") continue;
|
|
72
|
+
const record = field;
|
|
73
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
74
|
+
if (!name) continue;
|
|
75
|
+
definitions.push({
|
|
76
|
+
name,
|
|
77
|
+
type: inferFieldType(record),
|
|
78
|
+
required: record.required === true,
|
|
79
|
+
label: typeof record.label === "string" && record.label.length > 0 ? record.label : name
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return definitions;
|
|
84
|
+
};
|
|
85
|
+
function createFormSubmissionHandler(options) {
|
|
86
|
+
const {
|
|
87
|
+
getPayloadClient,
|
|
88
|
+
formsCollectionSlug = "forms",
|
|
89
|
+
submissionsCollectionSlug = "form-submissions",
|
|
90
|
+
allowedOrigins,
|
|
91
|
+
honeypotFieldName = "website_url_confirm",
|
|
92
|
+
minFillTimeMs = 3e3,
|
|
93
|
+
knownGoodEmailDomains,
|
|
94
|
+
onSubmission
|
|
95
|
+
} = options;
|
|
96
|
+
const rateLimitStore = options.rateLimitStore === null ? null : options.rateLimitStore || createMemoryRateLimitStore();
|
|
97
|
+
return async function POST(request, context) {
|
|
98
|
+
if (!isOriginAllowed(request, allowedOrigins)) {
|
|
99
|
+
return json({ error: "Origin not allowed." }, 403);
|
|
100
|
+
}
|
|
101
|
+
const now = Date.now();
|
|
102
|
+
if (rateLimitStore && rateLimitStore.isLimited(getClientKey(request), now)) {
|
|
103
|
+
return json({ error: "Too many requests. Please try again shortly." }, 429);
|
|
104
|
+
}
|
|
105
|
+
const params = await context.params;
|
|
106
|
+
const slug = typeof params?.slug === "string" ? params.slug : "";
|
|
107
|
+
if (!slug) {
|
|
108
|
+
return json({ error: "Unknown form." }, 404);
|
|
109
|
+
}
|
|
110
|
+
let body;
|
|
111
|
+
try {
|
|
112
|
+
const parsed = await request.json();
|
|
113
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
114
|
+
return json({ error: "Invalid submission body." }, 400);
|
|
115
|
+
}
|
|
116
|
+
body = parsed;
|
|
117
|
+
} catch {
|
|
118
|
+
return json({ error: "Invalid submission body." }, 400);
|
|
119
|
+
}
|
|
120
|
+
const data = body.data && typeof body.data === "object" && !Array.isArray(body.data) ? body.data : body;
|
|
121
|
+
const honeypotValue = data[honeypotFieldName];
|
|
122
|
+
if (typeof honeypotValue === "string" && honeypotValue.trim().length > 0) {
|
|
123
|
+
return json({ success: true }, 200);
|
|
124
|
+
}
|
|
125
|
+
if (minFillTimeMs > 0) {
|
|
126
|
+
const renderedAt = Number(data._renderedAt ?? body._renderedAt);
|
|
127
|
+
if (Number.isFinite(renderedAt) && renderedAt > 0 && now - renderedAt < minFillTimeMs) {
|
|
128
|
+
return json({ success: true }, 200);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const payload = await getPayloadClient();
|
|
132
|
+
const formResult = await payload.find({
|
|
133
|
+
collection: formsCollectionSlug,
|
|
134
|
+
depth: 0,
|
|
135
|
+
limit: 1,
|
|
136
|
+
overrideAccess: false,
|
|
137
|
+
where: { slug: { equals: slug } }
|
|
138
|
+
});
|
|
139
|
+
const formDoc = formResult.docs[0];
|
|
140
|
+
if (!formDoc) {
|
|
141
|
+
return json({ error: "Unknown form." }, 404);
|
|
142
|
+
}
|
|
143
|
+
const definitions = collectFormFieldDefinitions(formDoc);
|
|
144
|
+
const normalizedData = {};
|
|
145
|
+
const errors = {};
|
|
146
|
+
const knownNames = new Set(definitions.map((definition) => definition.name));
|
|
147
|
+
for (const definition of definitions) {
|
|
148
|
+
const rawValue = data[definition.name];
|
|
149
|
+
const isString = typeof rawValue === "string";
|
|
150
|
+
const normalized = isString ? normalizeFieldValue(rawValue, definition.type) : "";
|
|
151
|
+
const missing = rawValue === void 0 || rawValue === null || isString && normalized.length === 0 || Array.isArray(rawValue) && rawValue.length === 0;
|
|
152
|
+
if (definition.required && missing) {
|
|
153
|
+
errors[definition.name] = `${definition.label} is required.`;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (normalized.length > 0) {
|
|
157
|
+
if (definition.type === "email") {
|
|
158
|
+
const result = validateEmail(normalized, { knownGoodDomains: knownGoodEmailDomains });
|
|
159
|
+
if (!result.valid) {
|
|
160
|
+
errors[definition.name] = result.message || "Enter a valid email address.";
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
normalizedData[definition.name] = result.normalized;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (definition.type === "phone") {
|
|
167
|
+
const result = validatePhoneUS(normalized);
|
|
168
|
+
if (!result.valid) {
|
|
169
|
+
errors[definition.name] = result.message || "Enter a valid phone number.";
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
normalizedData[definition.name] = result.normalized;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (definition.type === "url") {
|
|
176
|
+
const result = validateUrl(normalized);
|
|
177
|
+
if (!result.valid) {
|
|
178
|
+
errors[definition.name] = result.message || "Enter a valid web address.";
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
normalizedData[definition.name] = result.normalized;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (rawValue !== void 0 && rawValue !== null) {
|
|
186
|
+
normalizedData[definition.name] = typeof rawValue === "string" ? normalized : rawValue;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
for (const [key, value] of Object.entries(data)) {
|
|
190
|
+
if (knownNames.has(key) || key === honeypotFieldName || key.startsWith("_")) continue;
|
|
191
|
+
normalizedData[key] = typeof value === "string" ? value.trim() : value;
|
|
192
|
+
}
|
|
193
|
+
if (Object.keys(errors).length > 0) {
|
|
194
|
+
return json({ error: "Validation failed.", fieldErrors: errors }, 422);
|
|
195
|
+
}
|
|
196
|
+
const submission = await payload.create({
|
|
197
|
+
collection: submissionsCollectionSlug,
|
|
198
|
+
data: {
|
|
199
|
+
data: normalizedData,
|
|
200
|
+
form: formDoc.id,
|
|
201
|
+
submittedAt: new Date(now).toISOString()
|
|
202
|
+
},
|
|
203
|
+
overrideAccess: true
|
|
204
|
+
});
|
|
205
|
+
if (onSubmission) {
|
|
206
|
+
try {
|
|
207
|
+
await onSubmission({ formDoc, normalizedData, submissionID: submission.id });
|
|
208
|
+
} catch (error) {
|
|
209
|
+
payload.logger?.error?.({ err: error, msg: "Form submission onSubmission hook failed" });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return json({ success: true, id: submission.id }, 200);
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
export {
|
|
216
|
+
createFormSubmissionHandler,
|
|
217
|
+
createMemoryRateLimitStore,
|
|
218
|
+
formatPhoneUS,
|
|
219
|
+
inferFieldType,
|
|
220
|
+
normalizeEmail,
|
|
221
|
+
normalizeFieldValue,
|
|
222
|
+
normalizePhone,
|
|
223
|
+
normalizeUrl,
|
|
224
|
+
validateEmail,
|
|
225
|
+
validatePhoneUS,
|
|
226
|
+
validateRequired,
|
|
227
|
+
validateUrl
|
|
228
|
+
};
|