@opensite/ui 1.7.5 → 1.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contact-dark.cjs +503 -124
- package/dist/contact-dark.d.cts +28 -9
- package/dist/contact-dark.d.ts +28 -9
- package/dist/contact-dark.js +505 -126
- package/dist/contact-faq.cjs +482 -104
- package/dist/contact-faq.d.cts +27 -17
- package/dist/contact-faq.d.ts +27 -17
- package/dist/contact-faq.js +484 -106
- package/dist/contact-photography.cjs +485 -116
- package/dist/contact-photography.d.cts +22 -9
- package/dist/contact-photography.d.ts +22 -9
- package/dist/contact-photography.js +487 -118
- package/dist/form-field-types-BYdJNOsW.d.cts +92 -0
- package/dist/form-field-types-BYdJNOsW.d.ts +92 -0
- package/dist/registry.cjs +2397 -2037
- package/dist/registry.js +1141 -781
- package/package.json +6 -1
package/dist/contact-faq.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import React__default, { useMemo } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
import { TextInput, TextArea } from '@page-speed/forms/inputs';
|
|
3
|
+
import React__default, { useMemo, useState, useCallback } from 'react';
|
|
4
|
+
import { Form, useForm, Field } from '@page-speed/forms';
|
|
6
5
|
import { clsx } from 'clsx';
|
|
7
6
|
import { twMerge } from 'tailwind-merge';
|
|
8
7
|
import { cva } from 'class-variance-authority';
|
|
9
8
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
9
|
+
import { TextInput, TextArea, Select, Radio, Checkbox, CheckboxGroup, DatePicker, DateRangePicker, TimePicker, FileInput, RichTextEditor } from '@page-speed/forms/inputs';
|
|
10
10
|
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
11
11
|
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
|
12
12
|
import { serializeForRails, deserializeErrors } from '@page-speed/forms/integration';
|
|
@@ -472,6 +472,200 @@ function Label({
|
|
|
472
472
|
}
|
|
473
473
|
);
|
|
474
474
|
}
|
|
475
|
+
function DynamicFormField({
|
|
476
|
+
field,
|
|
477
|
+
className,
|
|
478
|
+
uploadProgress = {},
|
|
479
|
+
onFileUpload,
|
|
480
|
+
onFileRemove,
|
|
481
|
+
isUploading = false
|
|
482
|
+
}) {
|
|
483
|
+
const fieldId = `field-${field.name}`;
|
|
484
|
+
return /* @__PURE__ */ jsx(Field, { name: field.name, children: ({ field: formField, meta }) => /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", className), children: [
|
|
485
|
+
field.type !== "checkbox" && /* @__PURE__ */ jsxs(Label, { htmlFor: fieldId, children: [
|
|
486
|
+
field.label,
|
|
487
|
+
field.required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
|
|
488
|
+
] }),
|
|
489
|
+
(field.type === "text" || field.type === "email" || field.type === "tel" || field.type === "search" || field.type === "password" || field.type === "url") && /* @__PURE__ */ jsx(
|
|
490
|
+
TextInput,
|
|
491
|
+
{
|
|
492
|
+
...formField,
|
|
493
|
+
id: fieldId,
|
|
494
|
+
type: field.type,
|
|
495
|
+
placeholder: field.placeholder,
|
|
496
|
+
error: meta.touched && !!meta.error,
|
|
497
|
+
disabled: field.disabled,
|
|
498
|
+
"aria-label": field.label
|
|
499
|
+
}
|
|
500
|
+
),
|
|
501
|
+
field.type === "number" && /* @__PURE__ */ jsx(
|
|
502
|
+
TextInput,
|
|
503
|
+
{
|
|
504
|
+
...formField,
|
|
505
|
+
id: fieldId,
|
|
506
|
+
type: "text",
|
|
507
|
+
placeholder: field.placeholder,
|
|
508
|
+
error: meta.touched && !!meta.error,
|
|
509
|
+
disabled: field.disabled,
|
|
510
|
+
"aria-label": field.label
|
|
511
|
+
}
|
|
512
|
+
),
|
|
513
|
+
field.type === "textarea" && /* @__PURE__ */ jsx(
|
|
514
|
+
TextArea,
|
|
515
|
+
{
|
|
516
|
+
...formField,
|
|
517
|
+
id: fieldId,
|
|
518
|
+
placeholder: field.placeholder,
|
|
519
|
+
rows: field.rows || 4,
|
|
520
|
+
error: meta.touched && !!meta.error,
|
|
521
|
+
disabled: field.disabled,
|
|
522
|
+
"aria-label": field.label
|
|
523
|
+
}
|
|
524
|
+
),
|
|
525
|
+
field.type === "select" && field.options && /* @__PURE__ */ jsx(
|
|
526
|
+
Select,
|
|
527
|
+
{
|
|
528
|
+
...formField,
|
|
529
|
+
id: fieldId,
|
|
530
|
+
options: field.options,
|
|
531
|
+
placeholder: field.placeholder || `Select ${field.label.toLowerCase()}`,
|
|
532
|
+
error: meta.touched && !!meta.error,
|
|
533
|
+
disabled: field.disabled,
|
|
534
|
+
"aria-label": field.label
|
|
535
|
+
}
|
|
536
|
+
),
|
|
537
|
+
field.type === "multi-select" && field.options && /* @__PURE__ */ jsx(
|
|
538
|
+
Select,
|
|
539
|
+
{
|
|
540
|
+
...formField,
|
|
541
|
+
id: fieldId,
|
|
542
|
+
options: field.options,
|
|
543
|
+
placeholder: field.placeholder || `Select ${field.label.toLowerCase()}`,
|
|
544
|
+
error: meta.touched && !!meta.error,
|
|
545
|
+
disabled: field.disabled,
|
|
546
|
+
"aria-label": field.label,
|
|
547
|
+
multiple: true
|
|
548
|
+
}
|
|
549
|
+
),
|
|
550
|
+
field.type === "radio" && field.options && /* @__PURE__ */ jsx(
|
|
551
|
+
Radio,
|
|
552
|
+
{
|
|
553
|
+
...formField,
|
|
554
|
+
id: fieldId,
|
|
555
|
+
options: field.options,
|
|
556
|
+
disabled: field.disabled,
|
|
557
|
+
layout: field.layout || "stacked",
|
|
558
|
+
error: meta.touched && !!meta.error,
|
|
559
|
+
"aria-label": field.label
|
|
560
|
+
}
|
|
561
|
+
),
|
|
562
|
+
field.type === "checkbox" && /* @__PURE__ */ jsxs("div", { className: "flex items-start space-x-2", children: [
|
|
563
|
+
/* @__PURE__ */ jsx(
|
|
564
|
+
Checkbox,
|
|
565
|
+
{
|
|
566
|
+
...formField,
|
|
567
|
+
id: fieldId,
|
|
568
|
+
value: formField.value === true || formField.value === "true",
|
|
569
|
+
onChange: (checked) => formField.onChange(checked),
|
|
570
|
+
disabled: field.disabled,
|
|
571
|
+
error: meta.touched && !!meta.error,
|
|
572
|
+
"aria-label": field.label
|
|
573
|
+
}
|
|
574
|
+
),
|
|
575
|
+
/* @__PURE__ */ jsxs(
|
|
576
|
+
Label,
|
|
577
|
+
{
|
|
578
|
+
htmlFor: fieldId,
|
|
579
|
+
className: "font-normal cursor-pointer leading-relaxed",
|
|
580
|
+
children: [
|
|
581
|
+
field.label,
|
|
582
|
+
field.required && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
|
|
583
|
+
]
|
|
584
|
+
}
|
|
585
|
+
)
|
|
586
|
+
] }),
|
|
587
|
+
field.type === "checkbox-group" && field.options && /* @__PURE__ */ jsx(
|
|
588
|
+
CheckboxGroup,
|
|
589
|
+
{
|
|
590
|
+
...formField,
|
|
591
|
+
id: fieldId,
|
|
592
|
+
options: field.options,
|
|
593
|
+
disabled: field.disabled,
|
|
594
|
+
layout: field.layout || "stacked",
|
|
595
|
+
error: meta.touched && !!meta.error,
|
|
596
|
+
"aria-label": field.label
|
|
597
|
+
}
|
|
598
|
+
),
|
|
599
|
+
(field.type === "date-picker" || field.type === "date") && /* @__PURE__ */ jsx(
|
|
600
|
+
DatePicker,
|
|
601
|
+
{
|
|
602
|
+
...formField,
|
|
603
|
+
id: fieldId,
|
|
604
|
+
placeholder: field.placeholder,
|
|
605
|
+
error: meta.touched && !!meta.error,
|
|
606
|
+
disabled: field.disabled,
|
|
607
|
+
"aria-label": field.label
|
|
608
|
+
}
|
|
609
|
+
),
|
|
610
|
+
field.type === "date-range" && /* @__PURE__ */ jsx(
|
|
611
|
+
DateRangePicker,
|
|
612
|
+
{
|
|
613
|
+
...formField,
|
|
614
|
+
id: fieldId,
|
|
615
|
+
error: meta.touched && !!meta.error,
|
|
616
|
+
disabled: field.disabled,
|
|
617
|
+
"aria-label": field.label
|
|
618
|
+
}
|
|
619
|
+
),
|
|
620
|
+
field.type === "time" && /* @__PURE__ */ jsx(
|
|
621
|
+
TimePicker,
|
|
622
|
+
{
|
|
623
|
+
...formField,
|
|
624
|
+
id: fieldId,
|
|
625
|
+
placeholder: field.placeholder,
|
|
626
|
+
error: meta.touched && !!meta.error,
|
|
627
|
+
disabled: field.disabled,
|
|
628
|
+
"aria-label": field.label
|
|
629
|
+
}
|
|
630
|
+
),
|
|
631
|
+
field.type === "file" && /* @__PURE__ */ jsx(
|
|
632
|
+
FileInput,
|
|
633
|
+
{
|
|
634
|
+
...formField,
|
|
635
|
+
id: fieldId,
|
|
636
|
+
accept: field.accept,
|
|
637
|
+
maxSize: field.maxSize || 5 * 1024 * 1024,
|
|
638
|
+
maxFiles: field.maxFiles || 1,
|
|
639
|
+
multiple: field.multiple || false,
|
|
640
|
+
placeholder: field.placeholder || "Choose file(s)...",
|
|
641
|
+
error: meta.touched && !!meta.error,
|
|
642
|
+
disabled: field.disabled || isUploading,
|
|
643
|
+
showProgress: true,
|
|
644
|
+
uploadProgress,
|
|
645
|
+
onChange: (files) => {
|
|
646
|
+
formField.onChange(files);
|
|
647
|
+
if (files.length > 0 && onFileUpload) {
|
|
648
|
+
onFileUpload(files);
|
|
649
|
+
}
|
|
650
|
+
},
|
|
651
|
+
onFileRemove,
|
|
652
|
+
"aria-label": field.label
|
|
653
|
+
}
|
|
654
|
+
),
|
|
655
|
+
field.type === "rich-text" && /* @__PURE__ */ jsx(
|
|
656
|
+
RichTextEditor,
|
|
657
|
+
{
|
|
658
|
+
...formField,
|
|
659
|
+
id: fieldId,
|
|
660
|
+
placeholder: field.placeholder,
|
|
661
|
+
error: meta.touched && !!meta.error,
|
|
662
|
+
disabled: field.disabled,
|
|
663
|
+
"aria-label": field.label
|
|
664
|
+
}
|
|
665
|
+
),
|
|
666
|
+
meta.touched && meta.error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: meta.error })
|
|
667
|
+
] }) });
|
|
668
|
+
}
|
|
475
669
|
var svgCache = /* @__PURE__ */ new Map();
|
|
476
670
|
function DynamicIcon({
|
|
477
671
|
name,
|
|
@@ -637,6 +831,197 @@ function AccordionContent({
|
|
|
637
831
|
}
|
|
638
832
|
);
|
|
639
833
|
}
|
|
834
|
+
function useFileUpload(options) {
|
|
835
|
+
const [uploadTokens, setUploadTokens] = useState([]);
|
|
836
|
+
const [uploadProgress, setUploadProgress] = useState({});
|
|
837
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
838
|
+
const endpoint = options?.endpoint || "https://api.dashtrack.com/contacts/_/contact_form_uploads";
|
|
839
|
+
const uploadFiles = useCallback(
|
|
840
|
+
async (files) => {
|
|
841
|
+
if (files.length === 0) return;
|
|
842
|
+
setIsUploading(true);
|
|
843
|
+
try {
|
|
844
|
+
const tokens = [];
|
|
845
|
+
for (const file of files) {
|
|
846
|
+
const formData = new FormData();
|
|
847
|
+
formData.append("contact_form_upload[file_upload]", file);
|
|
848
|
+
formData.append("contact_form_upload[title]", file.name);
|
|
849
|
+
formData.append("contact_form_upload[file_name]", file.name);
|
|
850
|
+
formData.append("contact_form_upload[file_size]", String(file.size));
|
|
851
|
+
const response = await fetch(endpoint, {
|
|
852
|
+
method: "POST",
|
|
853
|
+
body: formData
|
|
854
|
+
});
|
|
855
|
+
if (!response.ok) {
|
|
856
|
+
throw new Error(`Upload failed: ${response.statusText}`);
|
|
857
|
+
}
|
|
858
|
+
const data = await response.json();
|
|
859
|
+
if (data.contact_form_upload?.token) {
|
|
860
|
+
tokens.push(`upload_${data.contact_form_upload.token}`);
|
|
861
|
+
}
|
|
862
|
+
setUploadProgress((prev) => ({
|
|
863
|
+
...prev,
|
|
864
|
+
[file.name]: 100
|
|
865
|
+
}));
|
|
866
|
+
}
|
|
867
|
+
setUploadTokens(tokens);
|
|
868
|
+
} catch (error) {
|
|
869
|
+
console.error("File upload error:", error);
|
|
870
|
+
options?.onError?.(error);
|
|
871
|
+
} finally {
|
|
872
|
+
setIsUploading(false);
|
|
873
|
+
}
|
|
874
|
+
},
|
|
875
|
+
[endpoint, options]
|
|
876
|
+
);
|
|
877
|
+
const removeFile = useCallback((file, index) => {
|
|
878
|
+
setUploadTokens((prev) => prev.filter((_, i) => i !== index));
|
|
879
|
+
setUploadProgress((prev) => {
|
|
880
|
+
const newProgress = { ...prev };
|
|
881
|
+
delete newProgress[file.name];
|
|
882
|
+
return newProgress;
|
|
883
|
+
});
|
|
884
|
+
}, []);
|
|
885
|
+
const resetUpload = useCallback(() => {
|
|
886
|
+
setUploadTokens([]);
|
|
887
|
+
setUploadProgress({});
|
|
888
|
+
}, []);
|
|
889
|
+
return {
|
|
890
|
+
uploadTokens,
|
|
891
|
+
uploadProgress,
|
|
892
|
+
isUploading,
|
|
893
|
+
uploadFiles,
|
|
894
|
+
removeFile,
|
|
895
|
+
resetUpload
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// lib/form-field-types.ts
|
|
900
|
+
function generateInitialValues(fields) {
|
|
901
|
+
return fields.reduce(
|
|
902
|
+
(acc, field) => {
|
|
903
|
+
if (field.type === "checkbox") {
|
|
904
|
+
acc[field.name] = false;
|
|
905
|
+
} else if (field.type === "checkbox-group" || field.type === "multi-select") {
|
|
906
|
+
acc[field.name] = [];
|
|
907
|
+
} else if (field.type === "file") {
|
|
908
|
+
acc[field.name] = [];
|
|
909
|
+
} else if (field.type === "date-range") {
|
|
910
|
+
acc[field.name] = { start: null, end: null };
|
|
911
|
+
} else {
|
|
912
|
+
acc[field.name] = "";
|
|
913
|
+
}
|
|
914
|
+
return acc;
|
|
915
|
+
},
|
|
916
|
+
{}
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
function generateValidationSchema(fields) {
|
|
920
|
+
return fields.reduce(
|
|
921
|
+
(acc, field) => {
|
|
922
|
+
acc[field.name] = (value, allValues) => {
|
|
923
|
+
if (field.required) {
|
|
924
|
+
if (!value || typeof value === "string" && !value.trim()) {
|
|
925
|
+
return `${field.label} is required`;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
if (field.type === "email" && value) {
|
|
929
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
930
|
+
return "Please enter a valid email address";
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
if (field.type === "url" && value) {
|
|
934
|
+
try {
|
|
935
|
+
new URL(value);
|
|
936
|
+
} catch {
|
|
937
|
+
return "Please enter a valid URL";
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (field.validator) {
|
|
941
|
+
return field.validator(value, allValues);
|
|
942
|
+
}
|
|
943
|
+
return void 0;
|
|
944
|
+
};
|
|
945
|
+
return acc;
|
|
946
|
+
},
|
|
947
|
+
{}
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
function getColumnSpanClass(span) {
|
|
951
|
+
if (!span || span === 12) return "col-span-12";
|
|
952
|
+
return `col-span-12 sm:col-span-${Math.min(span, 12)}`;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// lib/forms/use-contact-form.ts
|
|
956
|
+
function useContactForm(options) {
|
|
957
|
+
const {
|
|
958
|
+
formFields,
|
|
959
|
+
formConfig,
|
|
960
|
+
onSubmit,
|
|
961
|
+
onSuccess,
|
|
962
|
+
onError,
|
|
963
|
+
resetOnSuccess = true,
|
|
964
|
+
uploadTokens = []
|
|
965
|
+
} = options;
|
|
966
|
+
const [isSubmitted, setIsSubmitted] = useState(false);
|
|
967
|
+
const [submissionError, setSubmissionError] = useState(null);
|
|
968
|
+
const form = useForm({
|
|
969
|
+
initialValues: useMemo(
|
|
970
|
+
() => generateInitialValues(formFields),
|
|
971
|
+
[formFields]
|
|
972
|
+
),
|
|
973
|
+
validationSchema: useMemo(
|
|
974
|
+
() => generateValidationSchema(formFields),
|
|
975
|
+
[formFields]
|
|
976
|
+
),
|
|
977
|
+
onSubmit: async (values, helpers) => {
|
|
978
|
+
setSubmissionError(null);
|
|
979
|
+
const shouldAutoSubmit = Boolean(formConfig?.endpoint);
|
|
980
|
+
if (!shouldAutoSubmit && !onSubmit) {
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
try {
|
|
984
|
+
let result;
|
|
985
|
+
const submissionValues = {
|
|
986
|
+
...values,
|
|
987
|
+
...uploadTokens.length > 0 && {
|
|
988
|
+
contact_form_upload_tokens: uploadTokens
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
if (shouldAutoSubmit) {
|
|
992
|
+
result = await submitPageSpeedForm(submissionValues, formConfig);
|
|
993
|
+
}
|
|
994
|
+
if (onSubmit) {
|
|
995
|
+
await onSubmit(submissionValues);
|
|
996
|
+
}
|
|
997
|
+
if (shouldAutoSubmit || onSubmit) {
|
|
998
|
+
setIsSubmitted(true);
|
|
999
|
+
if (resetOnSuccess) {
|
|
1000
|
+
helpers.resetForm();
|
|
1001
|
+
}
|
|
1002
|
+
onSuccess?.(result);
|
|
1003
|
+
setTimeout(() => setIsSubmitted(false), 5e3);
|
|
1004
|
+
}
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
|
|
1007
|
+
helpers.setErrors(error.formErrors);
|
|
1008
|
+
}
|
|
1009
|
+
const errorMessage = error instanceof Error ? error.message : "Form submission failed";
|
|
1010
|
+
setSubmissionError(errorMessage);
|
|
1011
|
+
onError?.(error);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
const formMethod = formConfig?.method?.toLowerCase() === "get" ? "get" : "post";
|
|
1016
|
+
return {
|
|
1017
|
+
form,
|
|
1018
|
+
isSubmitted,
|
|
1019
|
+
submissionError,
|
|
1020
|
+
formMethod
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// lib/forms.ts
|
|
640
1025
|
var PageSpeedFormSubmissionError = class extends Error {
|
|
641
1026
|
constructor(message, options = {}) {
|
|
642
1027
|
super(message);
|
|
@@ -1127,18 +1512,57 @@ var Section = React__default.forwardRef(
|
|
|
1127
1512
|
}
|
|
1128
1513
|
);
|
|
1129
1514
|
Section.displayName = "Section";
|
|
1515
|
+
var DEFAULT_FORM_FIELDS = [
|
|
1516
|
+
{
|
|
1517
|
+
name: "name",
|
|
1518
|
+
type: "text",
|
|
1519
|
+
label: "Name",
|
|
1520
|
+
placeholder: "Full Name",
|
|
1521
|
+
required: true,
|
|
1522
|
+
columnSpan: 6
|
|
1523
|
+
},
|
|
1524
|
+
{
|
|
1525
|
+
name: "email",
|
|
1526
|
+
type: "email",
|
|
1527
|
+
label: "Email",
|
|
1528
|
+
placeholder: "your@email.com",
|
|
1529
|
+
required: true,
|
|
1530
|
+
columnSpan: 6
|
|
1531
|
+
},
|
|
1532
|
+
{
|
|
1533
|
+
name: "subject",
|
|
1534
|
+
type: "text",
|
|
1535
|
+
label: "Subject",
|
|
1536
|
+
placeholder: "What is this regarding?",
|
|
1537
|
+
required: true,
|
|
1538
|
+
columnSpan: 12
|
|
1539
|
+
},
|
|
1540
|
+
{
|
|
1541
|
+
name: "message",
|
|
1542
|
+
type: "textarea",
|
|
1543
|
+
label: "Message",
|
|
1544
|
+
placeholder: "Your message...",
|
|
1545
|
+
required: true,
|
|
1546
|
+
rows: 4,
|
|
1547
|
+
columnSpan: 12
|
|
1548
|
+
}
|
|
1549
|
+
];
|
|
1130
1550
|
function ContactFaq({
|
|
1131
1551
|
heading,
|
|
1132
1552
|
description,
|
|
1133
1553
|
formHeading,
|
|
1134
|
-
buttonText,
|
|
1554
|
+
buttonText = "Submit",
|
|
1135
1555
|
buttonIcon,
|
|
1136
1556
|
actions,
|
|
1137
1557
|
actionsSlot,
|
|
1138
1558
|
items,
|
|
1139
1559
|
itemsSlot,
|
|
1140
1560
|
faqHeading,
|
|
1561
|
+
formFields = DEFAULT_FORM_FIELDS,
|
|
1562
|
+
successMessage = "Thank you! Your message has been sent successfully.",
|
|
1563
|
+
errorMessage = "There was an error sending your message. Please try again.",
|
|
1141
1564
|
className,
|
|
1565
|
+
containerClassName = "px-6 sm:px-6 md:px-8 lg:px-8",
|
|
1142
1566
|
headerClassName,
|
|
1143
1567
|
headingClassName,
|
|
1144
1568
|
descriptionClassName,
|
|
@@ -1154,9 +1578,10 @@ function ContactFaq({
|
|
|
1154
1578
|
accordionTriggerClassName,
|
|
1155
1579
|
accordionContentClassName,
|
|
1156
1580
|
gridClassName,
|
|
1581
|
+
successMessageClassName,
|
|
1582
|
+
errorMessageClassName,
|
|
1157
1583
|
background,
|
|
1158
1584
|
spacing = "py-8 md:py-32",
|
|
1159
|
-
containerClassName = "px-6 sm:px-6 md:px-8 lg:px-8",
|
|
1160
1585
|
pattern,
|
|
1161
1586
|
patternOpacity,
|
|
1162
1587
|
formConfig,
|
|
@@ -1164,54 +1589,27 @@ function ContactFaq({
|
|
|
1164
1589
|
onSuccess,
|
|
1165
1590
|
onError
|
|
1166
1591
|
}) {
|
|
1167
|
-
const
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1592
|
+
const {
|
|
1593
|
+
uploadTokens,
|
|
1594
|
+
uploadProgress,
|
|
1595
|
+
isUploading,
|
|
1596
|
+
uploadFiles,
|
|
1597
|
+
removeFile,
|
|
1598
|
+
resetUpload
|
|
1599
|
+
} = useFileUpload({ onError });
|
|
1600
|
+
const { form, isSubmitted, submissionError, formMethod } = useContactForm({
|
|
1601
|
+
formFields,
|
|
1602
|
+
formConfig,
|
|
1603
|
+
onSubmit,
|
|
1604
|
+
onSuccess: (data) => {
|
|
1605
|
+
resetUpload();
|
|
1606
|
+
onSuccess?.(data);
|
|
1173
1607
|
},
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
if (!value) return "Email is required";
|
|
1178
|
-
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
|
|
1179
|
-
return "Please enter a valid email address";
|
|
1180
|
-
return void 0;
|
|
1181
|
-
},
|
|
1182
|
-
subject: (value) => !value ? "Subject is required" : void 0,
|
|
1183
|
-
message: (value) => !value ? "Message is required" : void 0
|
|
1184
|
-
},
|
|
1185
|
-
onSubmit: async (values, helpers) => {
|
|
1186
|
-
const shouldAutoSubmit = Boolean(formConfig?.endpoint);
|
|
1187
|
-
if (!shouldAutoSubmit && !onSubmit) {
|
|
1188
|
-
return;
|
|
1189
|
-
}
|
|
1190
|
-
try {
|
|
1191
|
-
let result;
|
|
1192
|
-
if (shouldAutoSubmit) {
|
|
1193
|
-
result = await submitPageSpeedForm(values, formConfig);
|
|
1194
|
-
}
|
|
1195
|
-
if (onSubmit) {
|
|
1196
|
-
await onSubmit(values);
|
|
1197
|
-
}
|
|
1198
|
-
if (shouldAutoSubmit || onSubmit) {
|
|
1199
|
-
if (formConfig?.resetOnSuccess !== false) {
|
|
1200
|
-
helpers.resetForm();
|
|
1201
|
-
}
|
|
1202
|
-
onSuccess?.(result);
|
|
1203
|
-
}
|
|
1204
|
-
} catch (error) {
|
|
1205
|
-
if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
|
|
1206
|
-
helpers.setErrors(error.formErrors);
|
|
1207
|
-
}
|
|
1208
|
-
onError?.(error);
|
|
1209
|
-
throw error;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1608
|
+
onError,
|
|
1609
|
+
resetOnSuccess: formConfig?.resetOnSuccess !== false,
|
|
1610
|
+
uploadTokens
|
|
1212
1611
|
});
|
|
1213
|
-
const
|
|
1214
|
-
const actionsContent = React.useMemo(() => {
|
|
1612
|
+
const actionsContent = useMemo(() => {
|
|
1215
1613
|
if (actionsSlot) return actionsSlot;
|
|
1216
1614
|
if (actions && actions.length > 0) {
|
|
1217
1615
|
return actions.map((action, index) => {
|
|
@@ -1272,8 +1670,7 @@ function ContactFaq({
|
|
|
1272
1670
|
accordionClassName,
|
|
1273
1671
|
accordionItemClassName,
|
|
1274
1672
|
accordionTriggerClassName,
|
|
1275
|
-
accordionContentClassName
|
|
1276
|
-
background
|
|
1673
|
+
accordionContentClassName
|
|
1277
1674
|
]);
|
|
1278
1675
|
return /* @__PURE__ */ jsx(
|
|
1279
1676
|
Section,
|
|
@@ -1336,6 +1733,26 @@ function ContactFaq({
|
|
|
1336
1733
|
children: formHeading
|
|
1337
1734
|
}
|
|
1338
1735
|
) : /* @__PURE__ */ jsx("div", { className: formHeadingClassName, children: formHeading })),
|
|
1736
|
+
isSubmitted && /* @__PURE__ */ jsx(
|
|
1737
|
+
"div",
|
|
1738
|
+
{
|
|
1739
|
+
className: cn(
|
|
1740
|
+
"mb-6 p-4 bg-primary/10 border border-primary rounded-md",
|
|
1741
|
+
successMessageClassName
|
|
1742
|
+
),
|
|
1743
|
+
children: typeof successMessage === "string" ? /* @__PURE__ */ jsx("p", { className: "text-sm text-primary-foreground/90 text-center", children: successMessage }) : successMessage
|
|
1744
|
+
}
|
|
1745
|
+
),
|
|
1746
|
+
submissionError && /* @__PURE__ */ jsx(
|
|
1747
|
+
"div",
|
|
1748
|
+
{
|
|
1749
|
+
className: cn(
|
|
1750
|
+
"mb-6 p-4 bg-destructive/10 border border-destructive rounded-md",
|
|
1751
|
+
errorMessageClassName
|
|
1752
|
+
),
|
|
1753
|
+
children: typeof errorMessage === "string" ? /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive text-center", children: submissionError }) : errorMessage
|
|
1754
|
+
}
|
|
1755
|
+
),
|
|
1339
1756
|
/* @__PURE__ */ jsxs(
|
|
1340
1757
|
Form,
|
|
1341
1758
|
{
|
|
@@ -1344,62 +1761,23 @@ function ContactFaq({
|
|
|
1344
1761
|
method: formMethod,
|
|
1345
1762
|
className: cn("space-y-4", formClassName),
|
|
1346
1763
|
children: [
|
|
1347
|
-
/* @__PURE__ */
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
...field,
|
|
1354
|
-
id: "name",
|
|
1355
|
-
placeholder: "John Doe",
|
|
1356
|
-
error: meta.touched && !!meta.error,
|
|
1357
|
-
"aria-label": "Name"
|
|
1358
|
-
}
|
|
1359
|
-
)
|
|
1360
|
-
] }) }),
|
|
1361
|
-
/* @__PURE__ */ jsx(Field, { name: "email", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1362
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "email", children: "Email" }),
|
|
1363
|
-
/* @__PURE__ */ jsx(
|
|
1364
|
-
TextInput,
|
|
1764
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-12 gap-4", children: formFields.map((field) => /* @__PURE__ */ jsx(
|
|
1765
|
+
"div",
|
|
1766
|
+
{
|
|
1767
|
+
className: getColumnSpanClass(field.columnSpan),
|
|
1768
|
+
children: /* @__PURE__ */ jsx(
|
|
1769
|
+
DynamicFormField,
|
|
1365
1770
|
{
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
"aria-label": "Email"
|
|
1771
|
+
field,
|
|
1772
|
+
uploadProgress,
|
|
1773
|
+
onFileUpload: uploadFiles,
|
|
1774
|
+
onFileRemove: removeFile,
|
|
1775
|
+
isUploading
|
|
1372
1776
|
}
|
|
1373
1777
|
)
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "subject", children: "Subject" }),
|
|
1378
|
-
/* @__PURE__ */ jsx(
|
|
1379
|
-
TextInput,
|
|
1380
|
-
{
|
|
1381
|
-
...field,
|
|
1382
|
-
id: "subject",
|
|
1383
|
-
placeholder: "What is this regarding?",
|
|
1384
|
-
error: meta.touched && !!meta.error,
|
|
1385
|
-
"aria-label": "Subject"
|
|
1386
|
-
}
|
|
1387
|
-
)
|
|
1388
|
-
] }) }),
|
|
1389
|
-
/* @__PURE__ */ jsx(Field, { name: "message", children: ({ field, meta }) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1390
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "message", children: "Message" }),
|
|
1391
|
-
/* @__PURE__ */ jsx(
|
|
1392
|
-
TextArea,
|
|
1393
|
-
{
|
|
1394
|
-
...field,
|
|
1395
|
-
id: "message",
|
|
1396
|
-
placeholder: "Your question...",
|
|
1397
|
-
rows: 4,
|
|
1398
|
-
error: meta.touched && !!meta.error,
|
|
1399
|
-
"aria-label": "Message"
|
|
1400
|
-
}
|
|
1401
|
-
)
|
|
1402
|
-
] }) }),
|
|
1778
|
+
},
|
|
1779
|
+
field.name
|
|
1780
|
+
)) }),
|
|
1403
1781
|
actionsSlot || actions && actions.length > 0 ? actionsContent : /* @__PURE__ */ jsxs(
|
|
1404
1782
|
Pressable,
|
|
1405
1783
|
{
|