@page-speed/forms 0.5.1 → 0.5.3
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 +29 -0
- package/dist/chunk-232KNGJI.js +207 -0
- package/dist/chunk-232KNGJI.js.map +1 -0
- package/dist/chunk-24RPM43T.js +373 -0
- package/dist/chunk-24RPM43T.js.map +1 -0
- package/dist/chunk-27JUYRDE.cjs +173 -0
- package/dist/chunk-27JUYRDE.cjs.map +1 -0
- package/dist/chunk-5NT5T5XY.js +4136 -0
- package/dist/chunk-5NT5T5XY.js.map +1 -0
- package/dist/chunk-AVAKC6R7.cjs +236 -0
- package/dist/chunk-AVAKC6R7.cjs.map +1 -0
- package/dist/chunk-DKLLPKZN.cjs +238 -0
- package/dist/chunk-DKLLPKZN.cjs.map +1 -0
- package/dist/chunk-EX6CRLKG.cjs +397 -0
- package/dist/chunk-EX6CRLKG.cjs.map +1 -0
- package/dist/chunk-H6NNFV64.js +127 -0
- package/dist/chunk-H6NNFV64.js.map +1 -0
- package/dist/chunk-JBEWTBFH.js +217 -0
- package/dist/chunk-JBEWTBFH.js.map +1 -0
- package/dist/chunk-JBEZLX3H.cjs +138 -0
- package/dist/chunk-JBEZLX3H.cjs.map +1 -0
- package/dist/chunk-VLGZG2VP.js +150 -0
- package/dist/chunk-VLGZG2VP.js.map +1 -0
- package/dist/chunk-ZYFTT6DB.cjs +4169 -0
- package/dist/chunk-ZYFTT6DB.cjs.map +1 -0
- package/dist/core.cjs +23 -722
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +3 -3
- package/dist/core.d.ts +3 -3
- package/dist/core.js +3 -705
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +43 -727
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -705
- package/dist/index.js.map +1 -1
- package/dist/inputs.cjs +44 -4359
- package/dist/inputs.cjs.map +1 -1
- package/dist/inputs.d.cts +1 -1
- package/dist/inputs.d.ts +1 -1
- package/dist/inputs.js +2 -4337
- package/dist/inputs.js.map +1 -1
- package/dist/integration.cjs +574 -9
- package/dist/integration.cjs.map +1 -1
- package/dist/integration.d.cts +298 -1
- package/dist/integration.d.ts +298 -1
- package/dist/integration.js +566 -9
- package/dist/integration.js.map +1 -1
- package/dist/{types-DuX3q6A4.d.cts → types-CnOCn7b3.d.cts} +51 -1
- package/dist/{types-DuX3q6A4.d.ts → types-CnOCn7b3.d.ts} +51 -1
- package/dist/validation-rules.cjs +75 -231
- package/dist/validation-rules.cjs.map +1 -1
- package/dist/validation-rules.d.cts +1 -1
- package/dist/validation-rules.d.ts +1 -1
- package/dist/validation-rules.js +1 -215
- package/dist/validation-rules.js.map +1 -1
- package/dist/validation-utils.cjs +43 -133
- package/dist/validation-utils.cjs.map +1 -1
- package/dist/validation-utils.d.cts +1 -1
- package/dist/validation-utils.d.ts +1 -1
- package/dist/validation-utils.js +1 -125
- package/dist/validation-utils.js.map +1 -1
- package/dist/validation-valibot.d.cts +1 -1
- package/dist/validation-valibot.d.ts +1 -1
- package/dist/validation.cjs +115 -364
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.d.cts +1 -1
- package/dist/validation.d.ts +1 -1
- package/dist/validation.js +2 -339
- package/dist/validation.js.map +1 -1
- package/package.json +1 -1
package/dist/integration.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useForm, Field } from './chunk-24RPM43T.js';
|
|
2
|
+
import { TextInput, TextArea, Select, MultiSelect, Radio, Checkbox, CheckboxGroup, DatePicker, DateRangePicker, TimePicker, FileInput } from './chunk-5NT5T5XY.js';
|
|
3
|
+
import { cn } from './chunk-232KNGJI.js';
|
|
4
|
+
import * as React2 from 'react';
|
|
5
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
2
6
|
|
|
3
7
|
// src/integration/ContactFormSerializer.ts
|
|
4
8
|
var STANDARD_FIELDS = [
|
|
@@ -139,7 +143,7 @@ function deserializeErrors(railsErrors) {
|
|
|
139
143
|
}
|
|
140
144
|
return formErrors;
|
|
141
145
|
}
|
|
142
|
-
var BlockErrorBoundary = class extends
|
|
146
|
+
var BlockErrorBoundary = class extends React2.Component {
|
|
143
147
|
constructor(props) {
|
|
144
148
|
super(props);
|
|
145
149
|
this.state = { error: null };
|
|
@@ -159,16 +163,16 @@ var BlockErrorBoundary = class extends React.Component {
|
|
|
159
163
|
if (this.props.fallback) {
|
|
160
164
|
return this.props.fallback(this.state.error, this.props.block);
|
|
161
165
|
}
|
|
162
|
-
return /* @__PURE__ */
|
|
166
|
+
return /* @__PURE__ */ React2.createElement(
|
|
163
167
|
"div",
|
|
164
168
|
{
|
|
165
169
|
className: "block-error border border-destructive bg-destructive p-4 rounded text-destructive-foreground",
|
|
166
170
|
"data-block-id": this.props.block._id,
|
|
167
171
|
"data-block-type": this.props.block._type
|
|
168
172
|
},
|
|
169
|
-
/* @__PURE__ */
|
|
170
|
-
/* @__PURE__ */
|
|
171
|
-
/* @__PURE__ */
|
|
173
|
+
/* @__PURE__ */ React2.createElement("p", { className: "font-semibold" }, "Block Render Error"),
|
|
174
|
+
/* @__PURE__ */ React2.createElement("p", { className: "text-sm" }, "Block: ", this.props.block._name || this.props.block._id, " (", this.props.block._type, ")"),
|
|
175
|
+
/* @__PURE__ */ React2.createElement("p", { className: "text-sm mt-1" }, this.state.error.message)
|
|
172
176
|
);
|
|
173
177
|
}
|
|
174
178
|
return this.props.children;
|
|
@@ -199,9 +203,9 @@ function createBlockAdapter(Component2, options = {}) {
|
|
|
199
203
|
...dataAttrs
|
|
200
204
|
};
|
|
201
205
|
const renderedChildren = renderChildren ? renderChildren(block._id) : children;
|
|
202
|
-
const element = /* @__PURE__ */
|
|
206
|
+
const element = /* @__PURE__ */ React2.createElement(Component2, { ...componentProps }, renderedChildren);
|
|
203
207
|
if (withErrorBoundary) {
|
|
204
|
-
return /* @__PURE__ */
|
|
208
|
+
return /* @__PURE__ */ React2.createElement(BlockErrorBoundary, { block, fallback: errorFallback }, element);
|
|
205
209
|
}
|
|
206
210
|
return element;
|
|
207
211
|
};
|
|
@@ -226,6 +230,559 @@ function createBlockAdapters(components, options = {}) {
|
|
|
226
230
|
return adapted;
|
|
227
231
|
}
|
|
228
232
|
|
|
229
|
-
|
|
233
|
+
// src/integration/form-submit.ts
|
|
234
|
+
var PageSpeedFormSubmissionError = class extends Error {
|
|
235
|
+
constructor(message, options = {}) {
|
|
236
|
+
super(message);
|
|
237
|
+
this.name = "PageSpeedFormSubmissionError";
|
|
238
|
+
this.formErrors = options.formErrors;
|
|
239
|
+
this.status = options.status;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
243
|
+
function isValidEmail(value) {
|
|
244
|
+
return EMAIL_REGEX.test(value);
|
|
245
|
+
}
|
|
246
|
+
function buildUrlWithParams(endpoint, values) {
|
|
247
|
+
const base = typeof window === "undefined" ? "http://localhost" : window.location.origin;
|
|
248
|
+
const url = new URL(endpoint, base);
|
|
249
|
+
Object.entries(values).forEach(([key, value]) => {
|
|
250
|
+
if (value === void 0 || value === null) return;
|
|
251
|
+
if (Array.isArray(value)) {
|
|
252
|
+
value.forEach((item) => {
|
|
253
|
+
if (item !== void 0 && item !== null) {
|
|
254
|
+
url.searchParams.append(key, String(item));
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
url.searchParams.set(key, String(value));
|
|
260
|
+
});
|
|
261
|
+
return url.toString();
|
|
262
|
+
}
|
|
263
|
+
function normalizeMethod(method) {
|
|
264
|
+
return (method || "post").toUpperCase();
|
|
265
|
+
}
|
|
266
|
+
function resolveFormat(config) {
|
|
267
|
+
if (config?.format) return config.format;
|
|
268
|
+
return config?.apiKey ? "rails" : "json";
|
|
269
|
+
}
|
|
270
|
+
function mergeValues(values, config) {
|
|
271
|
+
return {
|
|
272
|
+
...config?.values ?? {},
|
|
273
|
+
...values
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
async function submitPageSpeedForm(values, config) {
|
|
277
|
+
if (!config?.endpoint) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
const payload = mergeValues(values, config);
|
|
281
|
+
const method = normalizeMethod(config.method);
|
|
282
|
+
const format = resolveFormat(config);
|
|
283
|
+
const headers = { ...config.headers ?? {} };
|
|
284
|
+
if (format === "rails") {
|
|
285
|
+
if (!config.apiKey) {
|
|
286
|
+
throw new PageSpeedFormSubmissionError(
|
|
287
|
+
"Missing apiKey for Rails form submission."
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
const railsConfig = {
|
|
291
|
+
apiKey: config.apiKey,
|
|
292
|
+
contactCategoryToken: config.contactCategoryToken,
|
|
293
|
+
locationId: config.locationId,
|
|
294
|
+
websiteId: config.websiteId,
|
|
295
|
+
websiteFormAssignmentId: config.websiteFormAssignmentId,
|
|
296
|
+
visitorIpAddress: config.visitorIpAddress
|
|
297
|
+
};
|
|
298
|
+
const serialized = serializeForRails(payload, railsConfig);
|
|
299
|
+
if (serialized.contact.contact_form_upload_tokens) {
|
|
300
|
+
serialized.contact.contact_form_upload_tokens = serialized.contact.contact_form_upload_tokens.map((token) => token.replace(/^upload_/, ""));
|
|
301
|
+
}
|
|
302
|
+
headers["Content-Type"] ?? (headers["Content-Type"] = "application/json");
|
|
303
|
+
const response2 = await fetch(config.endpoint, {
|
|
304
|
+
method,
|
|
305
|
+
headers,
|
|
306
|
+
body: JSON.stringify(serialized)
|
|
307
|
+
});
|
|
308
|
+
const data2 = await response2.json().catch(() => null);
|
|
309
|
+
if (!response2.ok || data2 && data2.errors) {
|
|
310
|
+
const errorResponse = {
|
|
311
|
+
errors: data2?.errors ?? { base: ["Form submission failed"] },
|
|
312
|
+
status: data2?.status ?? response2.status
|
|
313
|
+
};
|
|
314
|
+
const formErrors = deserializeErrors(errorResponse);
|
|
315
|
+
throw new PageSpeedFormSubmissionError("Form submission failed.", {
|
|
316
|
+
formErrors,
|
|
317
|
+
status: errorResponse.status
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return data2;
|
|
321
|
+
}
|
|
322
|
+
if (method === "GET") {
|
|
323
|
+
const url = buildUrlWithParams(config.endpoint, payload);
|
|
324
|
+
const response2 = await fetch(url, { method, headers });
|
|
325
|
+
const data2 = await response2.json().catch(() => null);
|
|
326
|
+
if (!response2.ok) {
|
|
327
|
+
throw new PageSpeedFormSubmissionError(
|
|
328
|
+
data2?.message || "Form submission failed.",
|
|
329
|
+
{ status: response2.status }
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
return data2;
|
|
333
|
+
}
|
|
334
|
+
headers["Content-Type"] ?? (headers["Content-Type"] = "application/json");
|
|
335
|
+
const response = await fetch(config.endpoint, {
|
|
336
|
+
method,
|
|
337
|
+
headers,
|
|
338
|
+
body: JSON.stringify(payload)
|
|
339
|
+
});
|
|
340
|
+
const data = await response.json().catch(() => null);
|
|
341
|
+
if (!response.ok) {
|
|
342
|
+
throw new PageSpeedFormSubmissionError(
|
|
343
|
+
data?.message || "Form submission failed.",
|
|
344
|
+
{ status: response.status }
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
return data;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// src/integration/form-field-types.ts
|
|
351
|
+
function generateInitialValues(fields) {
|
|
352
|
+
return fields.reduce(
|
|
353
|
+
(acc, field) => {
|
|
354
|
+
if (field.type === "checkbox") {
|
|
355
|
+
acc[field.name] = false;
|
|
356
|
+
} else if (field.type === "checkbox-group" || field.type === "multi-select") {
|
|
357
|
+
acc[field.name] = [];
|
|
358
|
+
} else if (field.type === "file") {
|
|
359
|
+
acc[field.name] = [];
|
|
360
|
+
} else if (field.type === "date-range") {
|
|
361
|
+
acc[field.name] = { start: null, end: null };
|
|
362
|
+
} else {
|
|
363
|
+
acc[field.name] = "";
|
|
364
|
+
}
|
|
365
|
+
return acc;
|
|
366
|
+
},
|
|
367
|
+
{}
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
function generateValidationSchema(fields) {
|
|
371
|
+
return fields.reduce(
|
|
372
|
+
(acc, field) => {
|
|
373
|
+
acc[field.name] = (value, allValues) => {
|
|
374
|
+
if (field.required) {
|
|
375
|
+
if (!value || typeof value === "string" && !value.trim()) {
|
|
376
|
+
return `${field.label} is required`;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (field.type === "email" && value) {
|
|
380
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
381
|
+
return "Please enter a valid email address";
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (field.type === "url" && value) {
|
|
385
|
+
try {
|
|
386
|
+
new URL(value);
|
|
387
|
+
} catch {
|
|
388
|
+
return "Please enter a valid URL";
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (field.validator) {
|
|
392
|
+
return field.validator(value, allValues);
|
|
393
|
+
}
|
|
394
|
+
return void 0;
|
|
395
|
+
};
|
|
396
|
+
return acc;
|
|
397
|
+
},
|
|
398
|
+
{}
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
var columnSpanClasses = {
|
|
402
|
+
1: "col-span-12 md:col-span-1",
|
|
403
|
+
2: "col-span-12 md:col-span-2",
|
|
404
|
+
3: "col-span-12 md:col-span-3",
|
|
405
|
+
4: "col-span-12 md:col-span-4",
|
|
406
|
+
5: "col-span-12 md:col-span-5",
|
|
407
|
+
6: "col-span-12 md:col-span-6",
|
|
408
|
+
7: "col-span-12 md:col-span-7",
|
|
409
|
+
8: "col-span-12 md:col-span-8",
|
|
410
|
+
9: "col-span-12 md:col-span-9",
|
|
411
|
+
10: "col-span-12 md:col-span-10",
|
|
412
|
+
11: "col-span-12 md:col-span-11",
|
|
413
|
+
12: "col-span-12"
|
|
414
|
+
};
|
|
415
|
+
function getColumnSpanClass(span) {
|
|
416
|
+
if (!span || span === 12) return "col-span-12";
|
|
417
|
+
const clamped = Math.max(1, Math.min(span, 12));
|
|
418
|
+
return columnSpanClasses[clamped] || "col-span-12";
|
|
419
|
+
}
|
|
420
|
+
var DEFAULT_UPLOAD_ENDPOINT = "https://api.dashtrack.com/contacts/_/contact_form_uploads";
|
|
421
|
+
function useFileUpload(options) {
|
|
422
|
+
const [uploadTokens, setUploadTokens] = useState([]);
|
|
423
|
+
const [uploadProgress, setUploadProgress] = useState({});
|
|
424
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
425
|
+
const endpoint = options?.endpoint || DEFAULT_UPLOAD_ENDPOINT;
|
|
426
|
+
const uploadFiles = useCallback(
|
|
427
|
+
async (files) => {
|
|
428
|
+
if (files.length === 0) return;
|
|
429
|
+
setIsUploading(true);
|
|
430
|
+
try {
|
|
431
|
+
const tokens = [];
|
|
432
|
+
for (const file of files) {
|
|
433
|
+
const formData = new FormData();
|
|
434
|
+
formData.append("contact_form_upload[file_upload]", file);
|
|
435
|
+
formData.append("contact_form_upload[title]", file.name);
|
|
436
|
+
formData.append("contact_form_upload[file_name]", file.name);
|
|
437
|
+
formData.append("contact_form_upload[file_size]", String(file.size));
|
|
438
|
+
const response = await fetch(endpoint, {
|
|
439
|
+
method: "POST",
|
|
440
|
+
body: formData
|
|
441
|
+
});
|
|
442
|
+
if (!response.ok) {
|
|
443
|
+
throw new Error(`Upload failed: ${response.statusText}`);
|
|
444
|
+
}
|
|
445
|
+
const data = await response.json();
|
|
446
|
+
if (data.contact_form_upload?.token) {
|
|
447
|
+
tokens.push(`upload_${data.contact_form_upload.token}`);
|
|
448
|
+
}
|
|
449
|
+
setUploadProgress((prev) => ({
|
|
450
|
+
...prev,
|
|
451
|
+
[file.name]: 100
|
|
452
|
+
}));
|
|
453
|
+
}
|
|
454
|
+
setUploadTokens(tokens);
|
|
455
|
+
} catch (error) {
|
|
456
|
+
options?.onError?.(error);
|
|
457
|
+
} finally {
|
|
458
|
+
setIsUploading(false);
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
[endpoint, options]
|
|
462
|
+
);
|
|
463
|
+
const removeFile = useCallback((file, index) => {
|
|
464
|
+
setUploadTokens((prev) => prev.filter((_, i) => i !== index));
|
|
465
|
+
setUploadProgress((prev) => {
|
|
466
|
+
const next = { ...prev };
|
|
467
|
+
delete next[file.name];
|
|
468
|
+
return next;
|
|
469
|
+
});
|
|
470
|
+
}, []);
|
|
471
|
+
const resetUpload = useCallback(() => {
|
|
472
|
+
setUploadTokens([]);
|
|
473
|
+
setUploadProgress({});
|
|
474
|
+
}, []);
|
|
475
|
+
return {
|
|
476
|
+
uploadTokens,
|
|
477
|
+
uploadProgress,
|
|
478
|
+
isUploading,
|
|
479
|
+
uploadFiles,
|
|
480
|
+
removeFile,
|
|
481
|
+
resetUpload
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
function resolveRedirect(redirectUrl) {
|
|
485
|
+
const trimmed = redirectUrl.trim();
|
|
486
|
+
if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
|
|
487
|
+
return { destination: trimmed, internalHref: trimmed };
|
|
488
|
+
}
|
|
489
|
+
if (typeof window === "undefined") {
|
|
490
|
+
return { destination: trimmed };
|
|
491
|
+
}
|
|
492
|
+
try {
|
|
493
|
+
const url = new URL(trimmed, window.location.href);
|
|
494
|
+
if (url.origin === window.location.origin) {
|
|
495
|
+
return {
|
|
496
|
+
destination: url.toString(),
|
|
497
|
+
internalHref: `${url.pathname}${url.search}${url.hash}`
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
return { destination: url.toString() };
|
|
501
|
+
} catch {
|
|
502
|
+
return { destination: trimmed };
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function useContactForm(options) {
|
|
506
|
+
const {
|
|
507
|
+
formFields,
|
|
508
|
+
formConfig,
|
|
509
|
+
onSubmit,
|
|
510
|
+
onSuccess,
|
|
511
|
+
onError,
|
|
512
|
+
resetOnSuccess = true,
|
|
513
|
+
uploadTokens = [],
|
|
514
|
+
navigate
|
|
515
|
+
} = options;
|
|
516
|
+
const [submissionError, setSubmissionError] = useState(null);
|
|
517
|
+
const submissionConfig = formConfig?.submissionConfig;
|
|
518
|
+
const redirectUrl = submissionConfig?.redirectUrl;
|
|
519
|
+
const resetSubmissionState = useCallback(() => {
|
|
520
|
+
setSubmissionError(null);
|
|
521
|
+
}, []);
|
|
522
|
+
const performRedirect = useCallback(() => {
|
|
523
|
+
if (!redirectUrl || typeof window === "undefined") {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const { destination, internalHref } = resolveRedirect(redirectUrl);
|
|
527
|
+
const attemptInternalNavigation = () => {
|
|
528
|
+
if (!internalHref) return false;
|
|
529
|
+
if (navigate) {
|
|
530
|
+
return navigate(internalHref) !== false;
|
|
531
|
+
}
|
|
532
|
+
const handler = window.__opensiteNavigationHandler;
|
|
533
|
+
if (typeof handler === "function") {
|
|
534
|
+
try {
|
|
535
|
+
return handler(internalHref, void 0) !== false;
|
|
536
|
+
} catch {
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return false;
|
|
541
|
+
};
|
|
542
|
+
window.setTimeout(() => {
|
|
543
|
+
if (attemptInternalNavigation()) return;
|
|
544
|
+
window.location.assign(destination);
|
|
545
|
+
}, 150);
|
|
546
|
+
}, [navigate, redirectUrl]);
|
|
547
|
+
const form = useForm({
|
|
548
|
+
initialValues: useMemo(
|
|
549
|
+
() => generateInitialValues(formFields),
|
|
550
|
+
[formFields]
|
|
551
|
+
),
|
|
552
|
+
validationSchema: useMemo(
|
|
553
|
+
() => generateValidationSchema(formFields),
|
|
554
|
+
[formFields]
|
|
555
|
+
),
|
|
556
|
+
onSubmit: async (values, helpers) => {
|
|
557
|
+
resetSubmissionState();
|
|
558
|
+
const shouldAutoSubmit = Boolean(formConfig?.endpoint);
|
|
559
|
+
if (!shouldAutoSubmit && !onSubmit) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
try {
|
|
563
|
+
let result;
|
|
564
|
+
const submissionValues = {
|
|
565
|
+
...values,
|
|
566
|
+
...uploadTokens.length > 0 && {
|
|
567
|
+
contact_form_upload_tokens: uploadTokens
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
if (shouldAutoSubmit) {
|
|
571
|
+
result = await submitPageSpeedForm(submissionValues, formConfig);
|
|
572
|
+
}
|
|
573
|
+
if (onSubmit) {
|
|
574
|
+
await onSubmit(submissionValues);
|
|
575
|
+
}
|
|
576
|
+
if (shouldAutoSubmit || onSubmit) {
|
|
577
|
+
try {
|
|
578
|
+
await submissionConfig?.handleFormSubmission?.({
|
|
579
|
+
formData: submissionValues,
|
|
580
|
+
responseData: result
|
|
581
|
+
});
|
|
582
|
+
} catch {
|
|
583
|
+
}
|
|
584
|
+
if (resetOnSuccess) {
|
|
585
|
+
helpers.resetForm();
|
|
586
|
+
}
|
|
587
|
+
onSuccess?.(result);
|
|
588
|
+
if (submissionConfig?.behavior === "redirect" && submissionConfig.redirectUrl) {
|
|
589
|
+
performRedirect();
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
} catch (error) {
|
|
593
|
+
if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
|
|
594
|
+
helpers.setErrors(error.formErrors);
|
|
595
|
+
}
|
|
596
|
+
const errorMessage = error instanceof Error ? error.message : "Form submission failed";
|
|
597
|
+
setSubmissionError(errorMessage);
|
|
598
|
+
onError?.(error);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
const formMethod = formConfig?.method?.toLowerCase() === "get" ? "get" : "post";
|
|
603
|
+
return {
|
|
604
|
+
form,
|
|
605
|
+
isSubmitted: form.status === "success",
|
|
606
|
+
submissionError,
|
|
607
|
+
formMethod,
|
|
608
|
+
resetSubmissionState
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function DynamicFormField({
|
|
612
|
+
field,
|
|
613
|
+
className,
|
|
614
|
+
uploadProgress = {},
|
|
615
|
+
onFileUpload,
|
|
616
|
+
onFileRemove,
|
|
617
|
+
isUploading = false
|
|
618
|
+
}) {
|
|
619
|
+
const fieldId = field.name;
|
|
620
|
+
const usesGroupLegend = field.type === "radio" || field.type === "checkbox-group";
|
|
621
|
+
const usesInlineCheckboxLabel = field.type === "checkbox";
|
|
622
|
+
const shouldRenderFieldLabel = !usesGroupLegend && !usesInlineCheckboxLabel;
|
|
623
|
+
return /* @__PURE__ */ React2.createElement(
|
|
624
|
+
Field,
|
|
625
|
+
{
|
|
626
|
+
name: field.name,
|
|
627
|
+
label: shouldRenderFieldLabel ? field.label : void 0,
|
|
628
|
+
description: shouldRenderFieldLabel ? field.description : void 0,
|
|
629
|
+
required: field.required,
|
|
630
|
+
className: cn("space-y-2", className)
|
|
631
|
+
},
|
|
632
|
+
({ field: formField, meta }) => /* @__PURE__ */ React2.createElement("div", null, (field.type === "text" || field.type === "email" || field.type === "tel" || field.type === "search" || field.type === "password" || field.type === "url") && /* @__PURE__ */ React2.createElement(
|
|
633
|
+
TextInput,
|
|
634
|
+
{
|
|
635
|
+
...formField,
|
|
636
|
+
id: fieldId,
|
|
637
|
+
type: field.type,
|
|
638
|
+
placeholder: field.placeholder,
|
|
639
|
+
error: meta.touched && !!meta.error,
|
|
640
|
+
disabled: field.disabled,
|
|
641
|
+
"aria-label": field.label
|
|
642
|
+
}
|
|
643
|
+
), field.type === "number" && /* @__PURE__ */ React2.createElement(
|
|
644
|
+
TextInput,
|
|
645
|
+
{
|
|
646
|
+
...formField,
|
|
647
|
+
id: fieldId,
|
|
648
|
+
type: "text",
|
|
649
|
+
placeholder: field.placeholder,
|
|
650
|
+
error: meta.touched && !!meta.error,
|
|
651
|
+
disabled: field.disabled,
|
|
652
|
+
"aria-label": field.label
|
|
653
|
+
}
|
|
654
|
+
), field.type === "textarea" && /* @__PURE__ */ React2.createElement(
|
|
655
|
+
TextArea,
|
|
656
|
+
{
|
|
657
|
+
...formField,
|
|
658
|
+
id: fieldId,
|
|
659
|
+
placeholder: field.placeholder,
|
|
660
|
+
rows: field.rows || 4,
|
|
661
|
+
error: meta.touched && !!meta.error,
|
|
662
|
+
disabled: field.disabled,
|
|
663
|
+
"aria-label": field.label
|
|
664
|
+
}
|
|
665
|
+
), field.type === "select" && field.options && /* @__PURE__ */ React2.createElement(
|
|
666
|
+
Select,
|
|
667
|
+
{
|
|
668
|
+
...formField,
|
|
669
|
+
id: fieldId,
|
|
670
|
+
options: field.options,
|
|
671
|
+
placeholder: field.placeholder || `Select ${field.label.toLowerCase()}`,
|
|
672
|
+
error: meta.touched && !!meta.error,
|
|
673
|
+
disabled: field.disabled,
|
|
674
|
+
"aria-label": field.label
|
|
675
|
+
}
|
|
676
|
+
), field.type === "multi-select" && field.options && /* @__PURE__ */ React2.createElement(
|
|
677
|
+
MultiSelect,
|
|
678
|
+
{
|
|
679
|
+
...formField,
|
|
680
|
+
id: fieldId,
|
|
681
|
+
options: field.options,
|
|
682
|
+
placeholder: field.placeholder || `Select ${field.label.toLowerCase()}`,
|
|
683
|
+
error: meta.touched && !!meta.error,
|
|
684
|
+
disabled: field.disabled,
|
|
685
|
+
"aria-label": field.label
|
|
686
|
+
}
|
|
687
|
+
), field.type === "radio" && field.options && /* @__PURE__ */ React2.createElement(
|
|
688
|
+
Radio,
|
|
689
|
+
{
|
|
690
|
+
...formField,
|
|
691
|
+
id: fieldId,
|
|
692
|
+
options: field.options,
|
|
693
|
+
label: field.label,
|
|
694
|
+
description: field.description,
|
|
695
|
+
required: field.required,
|
|
696
|
+
disabled: field.disabled,
|
|
697
|
+
layout: field.layout || "stacked",
|
|
698
|
+
error: meta.touched && !!meta.error,
|
|
699
|
+
"aria-label": field.label
|
|
700
|
+
}
|
|
701
|
+
), field.type === "checkbox" && /* @__PURE__ */ React2.createElement(
|
|
702
|
+
Checkbox,
|
|
703
|
+
{
|
|
704
|
+
...formField,
|
|
705
|
+
id: fieldId,
|
|
706
|
+
value: formField.value === true || formField.value === "true",
|
|
707
|
+
onChange: (checked) => formField.onChange(checked),
|
|
708
|
+
label: field.label,
|
|
709
|
+
description: field.description,
|
|
710
|
+
disabled: field.disabled,
|
|
711
|
+
required: field.required,
|
|
712
|
+
error: meta.touched && !!meta.error,
|
|
713
|
+
"aria-label": field.label
|
|
714
|
+
}
|
|
715
|
+
), field.type === "checkbox-group" && field.options && /* @__PURE__ */ React2.createElement(
|
|
716
|
+
CheckboxGroup,
|
|
717
|
+
{
|
|
718
|
+
...formField,
|
|
719
|
+
id: fieldId,
|
|
720
|
+
options: field.options,
|
|
721
|
+
label: field.label,
|
|
722
|
+
description: field.description,
|
|
723
|
+
required: field.required,
|
|
724
|
+
disabled: field.disabled,
|
|
725
|
+
layout: field.layout || "stacked",
|
|
726
|
+
error: meta.touched && !!meta.error,
|
|
727
|
+
"aria-label": field.label
|
|
728
|
+
}
|
|
729
|
+
), (field.type === "date-picker" || field.type === "date") && /* @__PURE__ */ React2.createElement(
|
|
730
|
+
DatePicker,
|
|
731
|
+
{
|
|
732
|
+
...formField,
|
|
733
|
+
id: fieldId,
|
|
734
|
+
placeholder: field.placeholder,
|
|
735
|
+
error: meta.touched && !!meta.error,
|
|
736
|
+
disabled: field.disabled,
|
|
737
|
+
"aria-label": field.label
|
|
738
|
+
}
|
|
739
|
+
), field.type === "date-range" && /* @__PURE__ */ React2.createElement(
|
|
740
|
+
DateRangePicker,
|
|
741
|
+
{
|
|
742
|
+
...formField,
|
|
743
|
+
id: fieldId,
|
|
744
|
+
placeholder: field.placeholder,
|
|
745
|
+
error: meta.touched && !!meta.error,
|
|
746
|
+
disabled: field.disabled,
|
|
747
|
+
"aria-label": field.label
|
|
748
|
+
}
|
|
749
|
+
), field.type === "time" && /* @__PURE__ */ React2.createElement(
|
|
750
|
+
TimePicker,
|
|
751
|
+
{
|
|
752
|
+
...formField,
|
|
753
|
+
id: fieldId,
|
|
754
|
+
placeholder: field.placeholder,
|
|
755
|
+
error: meta.touched && !!meta.error,
|
|
756
|
+
disabled: field.disabled,
|
|
757
|
+
"aria-label": field.label
|
|
758
|
+
}
|
|
759
|
+
), field.type === "file" && /* @__PURE__ */ React2.createElement(
|
|
760
|
+
FileInput,
|
|
761
|
+
{
|
|
762
|
+
...formField,
|
|
763
|
+
id: fieldId,
|
|
764
|
+
accept: field.accept,
|
|
765
|
+
maxSize: field.maxSize || 5 * 1024 * 1024,
|
|
766
|
+
maxFiles: field.maxFiles || 1,
|
|
767
|
+
multiple: field.multiple || false,
|
|
768
|
+
placeholder: field.placeholder || "Choose file(s)...",
|
|
769
|
+
error: meta.touched && !!meta.error,
|
|
770
|
+
disabled: field.disabled || isUploading,
|
|
771
|
+
showProgress: true,
|
|
772
|
+
uploadProgress,
|
|
773
|
+
onChange: (files) => {
|
|
774
|
+
formField.onChange(files);
|
|
775
|
+
if (files.length > 0 && onFileUpload) {
|
|
776
|
+
onFileUpload(files);
|
|
777
|
+
}
|
|
778
|
+
},
|
|
779
|
+
onFileRemove,
|
|
780
|
+
"aria-label": field.label
|
|
781
|
+
}
|
|
782
|
+
))
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
export { DynamicFormField, PageSpeedFormSubmissionError, createBlockAdapter, createBlockAdapters, deserializeErrors, generateInitialValues, generateValidationSchema, getColumnSpanClass, isValidEmail, serializeForRails, standardInputTransformer, submitPageSpeedForm, useContactForm, useFileUpload };
|
|
230
787
|
//# sourceMappingURL=integration.js.map
|
|
231
788
|
//# sourceMappingURL=integration.js.map
|