@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
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
var React = require('react');
|
|
5
5
|
var forms = require('@page-speed/forms');
|
|
6
|
-
var inputs = require('@page-speed/forms/inputs');
|
|
7
6
|
var clsx = require('clsx');
|
|
8
7
|
var tailwindMerge = require('tailwind-merge');
|
|
9
8
|
var classVarianceAuthority = require('class-variance-authority');
|
|
10
9
|
var jsxRuntime = require('react/jsx-runtime');
|
|
11
10
|
var img = require('@page-speed/img');
|
|
11
|
+
var inputs = require('@page-speed/forms/inputs');
|
|
12
12
|
var LabelPrimitive = require('@radix-ui/react-label');
|
|
13
13
|
var integration = require('@page-speed/forms/integration');
|
|
14
14
|
|
|
@@ -501,6 +501,389 @@ function Label({
|
|
|
501
501
|
}
|
|
502
502
|
);
|
|
503
503
|
}
|
|
504
|
+
function DynamicFormField({
|
|
505
|
+
field,
|
|
506
|
+
className,
|
|
507
|
+
uploadProgress = {},
|
|
508
|
+
onFileUpload,
|
|
509
|
+
onFileRemove,
|
|
510
|
+
isUploading = false
|
|
511
|
+
}) {
|
|
512
|
+
const fieldId = `field-${field.name}`;
|
|
513
|
+
return /* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: field.name, children: ({ field: formField, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("space-y-2", className), children: [
|
|
514
|
+
field.type !== "checkbox" && /* @__PURE__ */ jsxRuntime.jsxs(Label, { htmlFor: fieldId, children: [
|
|
515
|
+
field.label,
|
|
516
|
+
field.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-destructive ml-1", children: "*" })
|
|
517
|
+
] }),
|
|
518
|
+
(field.type === "text" || field.type === "email" || field.type === "tel" || field.type === "search" || field.type === "password" || field.type === "url") && /* @__PURE__ */ jsxRuntime.jsx(
|
|
519
|
+
inputs.TextInput,
|
|
520
|
+
{
|
|
521
|
+
...formField,
|
|
522
|
+
id: fieldId,
|
|
523
|
+
type: field.type,
|
|
524
|
+
placeholder: field.placeholder,
|
|
525
|
+
error: meta.touched && !!meta.error,
|
|
526
|
+
disabled: field.disabled,
|
|
527
|
+
"aria-label": field.label
|
|
528
|
+
}
|
|
529
|
+
),
|
|
530
|
+
field.type === "number" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
531
|
+
inputs.TextInput,
|
|
532
|
+
{
|
|
533
|
+
...formField,
|
|
534
|
+
id: fieldId,
|
|
535
|
+
type: "text",
|
|
536
|
+
placeholder: field.placeholder,
|
|
537
|
+
error: meta.touched && !!meta.error,
|
|
538
|
+
disabled: field.disabled,
|
|
539
|
+
"aria-label": field.label
|
|
540
|
+
}
|
|
541
|
+
),
|
|
542
|
+
field.type === "textarea" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
543
|
+
inputs.TextArea,
|
|
544
|
+
{
|
|
545
|
+
...formField,
|
|
546
|
+
id: fieldId,
|
|
547
|
+
placeholder: field.placeholder,
|
|
548
|
+
rows: field.rows || 4,
|
|
549
|
+
error: meta.touched && !!meta.error,
|
|
550
|
+
disabled: field.disabled,
|
|
551
|
+
"aria-label": field.label
|
|
552
|
+
}
|
|
553
|
+
),
|
|
554
|
+
field.type === "select" && field.options && /* @__PURE__ */ jsxRuntime.jsx(
|
|
555
|
+
inputs.Select,
|
|
556
|
+
{
|
|
557
|
+
...formField,
|
|
558
|
+
id: fieldId,
|
|
559
|
+
options: field.options,
|
|
560
|
+
placeholder: field.placeholder || `Select ${field.label.toLowerCase()}`,
|
|
561
|
+
error: meta.touched && !!meta.error,
|
|
562
|
+
disabled: field.disabled,
|
|
563
|
+
"aria-label": field.label
|
|
564
|
+
}
|
|
565
|
+
),
|
|
566
|
+
field.type === "multi-select" && field.options && /* @__PURE__ */ jsxRuntime.jsx(
|
|
567
|
+
inputs.Select,
|
|
568
|
+
{
|
|
569
|
+
...formField,
|
|
570
|
+
id: fieldId,
|
|
571
|
+
options: field.options,
|
|
572
|
+
placeholder: field.placeholder || `Select ${field.label.toLowerCase()}`,
|
|
573
|
+
error: meta.touched && !!meta.error,
|
|
574
|
+
disabled: field.disabled,
|
|
575
|
+
"aria-label": field.label,
|
|
576
|
+
multiple: true
|
|
577
|
+
}
|
|
578
|
+
),
|
|
579
|
+
field.type === "radio" && field.options && /* @__PURE__ */ jsxRuntime.jsx(
|
|
580
|
+
inputs.Radio,
|
|
581
|
+
{
|
|
582
|
+
...formField,
|
|
583
|
+
id: fieldId,
|
|
584
|
+
options: field.options,
|
|
585
|
+
disabled: field.disabled,
|
|
586
|
+
layout: field.layout || "stacked",
|
|
587
|
+
error: meta.touched && !!meta.error,
|
|
588
|
+
"aria-label": field.label
|
|
589
|
+
}
|
|
590
|
+
),
|
|
591
|
+
field.type === "checkbox" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start space-x-2", children: [
|
|
592
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
593
|
+
inputs.Checkbox,
|
|
594
|
+
{
|
|
595
|
+
...formField,
|
|
596
|
+
id: fieldId,
|
|
597
|
+
value: formField.value === true || formField.value === "true",
|
|
598
|
+
onChange: (checked) => formField.onChange(checked),
|
|
599
|
+
disabled: field.disabled,
|
|
600
|
+
error: meta.touched && !!meta.error,
|
|
601
|
+
"aria-label": field.label
|
|
602
|
+
}
|
|
603
|
+
),
|
|
604
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
605
|
+
Label,
|
|
606
|
+
{
|
|
607
|
+
htmlFor: fieldId,
|
|
608
|
+
className: "font-normal cursor-pointer leading-relaxed",
|
|
609
|
+
children: [
|
|
610
|
+
field.label,
|
|
611
|
+
field.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-destructive ml-1", children: "*" })
|
|
612
|
+
]
|
|
613
|
+
}
|
|
614
|
+
)
|
|
615
|
+
] }),
|
|
616
|
+
field.type === "checkbox-group" && field.options && /* @__PURE__ */ jsxRuntime.jsx(
|
|
617
|
+
inputs.CheckboxGroup,
|
|
618
|
+
{
|
|
619
|
+
...formField,
|
|
620
|
+
id: fieldId,
|
|
621
|
+
options: field.options,
|
|
622
|
+
disabled: field.disabled,
|
|
623
|
+
layout: field.layout || "stacked",
|
|
624
|
+
error: meta.touched && !!meta.error,
|
|
625
|
+
"aria-label": field.label
|
|
626
|
+
}
|
|
627
|
+
),
|
|
628
|
+
(field.type === "date-picker" || field.type === "date") && /* @__PURE__ */ jsxRuntime.jsx(
|
|
629
|
+
inputs.DatePicker,
|
|
630
|
+
{
|
|
631
|
+
...formField,
|
|
632
|
+
id: fieldId,
|
|
633
|
+
placeholder: field.placeholder,
|
|
634
|
+
error: meta.touched && !!meta.error,
|
|
635
|
+
disabled: field.disabled,
|
|
636
|
+
"aria-label": field.label
|
|
637
|
+
}
|
|
638
|
+
),
|
|
639
|
+
field.type === "date-range" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
640
|
+
inputs.DateRangePicker,
|
|
641
|
+
{
|
|
642
|
+
...formField,
|
|
643
|
+
id: fieldId,
|
|
644
|
+
error: meta.touched && !!meta.error,
|
|
645
|
+
disabled: field.disabled,
|
|
646
|
+
"aria-label": field.label
|
|
647
|
+
}
|
|
648
|
+
),
|
|
649
|
+
field.type === "time" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
650
|
+
inputs.TimePicker,
|
|
651
|
+
{
|
|
652
|
+
...formField,
|
|
653
|
+
id: fieldId,
|
|
654
|
+
placeholder: field.placeholder,
|
|
655
|
+
error: meta.touched && !!meta.error,
|
|
656
|
+
disabled: field.disabled,
|
|
657
|
+
"aria-label": field.label
|
|
658
|
+
}
|
|
659
|
+
),
|
|
660
|
+
field.type === "file" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
661
|
+
inputs.FileInput,
|
|
662
|
+
{
|
|
663
|
+
...formField,
|
|
664
|
+
id: fieldId,
|
|
665
|
+
accept: field.accept,
|
|
666
|
+
maxSize: field.maxSize || 5 * 1024 * 1024,
|
|
667
|
+
maxFiles: field.maxFiles || 1,
|
|
668
|
+
multiple: field.multiple || false,
|
|
669
|
+
placeholder: field.placeholder || "Choose file(s)...",
|
|
670
|
+
error: meta.touched && !!meta.error,
|
|
671
|
+
disabled: field.disabled || isUploading,
|
|
672
|
+
showProgress: true,
|
|
673
|
+
uploadProgress,
|
|
674
|
+
onChange: (files) => {
|
|
675
|
+
formField.onChange(files);
|
|
676
|
+
if (files.length > 0 && onFileUpload) {
|
|
677
|
+
onFileUpload(files);
|
|
678
|
+
}
|
|
679
|
+
},
|
|
680
|
+
onFileRemove,
|
|
681
|
+
"aria-label": field.label
|
|
682
|
+
}
|
|
683
|
+
),
|
|
684
|
+
field.type === "rich-text" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
685
|
+
inputs.RichTextEditor,
|
|
686
|
+
{
|
|
687
|
+
...formField,
|
|
688
|
+
id: fieldId,
|
|
689
|
+
placeholder: field.placeholder,
|
|
690
|
+
error: meta.touched && !!meta.error,
|
|
691
|
+
disabled: field.disabled,
|
|
692
|
+
"aria-label": field.label
|
|
693
|
+
}
|
|
694
|
+
),
|
|
695
|
+
meta.touched && meta.error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive", children: meta.error })
|
|
696
|
+
] }) });
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// lib/form-field-types.ts
|
|
700
|
+
function generateInitialValues(fields) {
|
|
701
|
+
return fields.reduce(
|
|
702
|
+
(acc, field) => {
|
|
703
|
+
if (field.type === "checkbox") {
|
|
704
|
+
acc[field.name] = false;
|
|
705
|
+
} else if (field.type === "checkbox-group" || field.type === "multi-select") {
|
|
706
|
+
acc[field.name] = [];
|
|
707
|
+
} else if (field.type === "file") {
|
|
708
|
+
acc[field.name] = [];
|
|
709
|
+
} else if (field.type === "date-range") {
|
|
710
|
+
acc[field.name] = { start: null, end: null };
|
|
711
|
+
} else {
|
|
712
|
+
acc[field.name] = "";
|
|
713
|
+
}
|
|
714
|
+
return acc;
|
|
715
|
+
},
|
|
716
|
+
{}
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
function generateValidationSchema(fields) {
|
|
720
|
+
return fields.reduce(
|
|
721
|
+
(acc, field) => {
|
|
722
|
+
acc[field.name] = (value, allValues) => {
|
|
723
|
+
if (field.required) {
|
|
724
|
+
if (!value || typeof value === "string" && !value.trim()) {
|
|
725
|
+
return `${field.label} is required`;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
if (field.type === "email" && value) {
|
|
729
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
730
|
+
return "Please enter a valid email address";
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (field.type === "url" && value) {
|
|
734
|
+
try {
|
|
735
|
+
new URL(value);
|
|
736
|
+
} catch {
|
|
737
|
+
return "Please enter a valid URL";
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (field.validator) {
|
|
741
|
+
return field.validator(value, allValues);
|
|
742
|
+
}
|
|
743
|
+
return void 0;
|
|
744
|
+
};
|
|
745
|
+
return acc;
|
|
746
|
+
},
|
|
747
|
+
{}
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
function getColumnSpanClass(span) {
|
|
751
|
+
if (!span || span === 12) return "col-span-12";
|
|
752
|
+
return `col-span-12 sm:col-span-${Math.min(span, 12)}`;
|
|
753
|
+
}
|
|
754
|
+
function useFileUpload(options) {
|
|
755
|
+
const [uploadTokens, setUploadTokens] = React.useState([]);
|
|
756
|
+
const [uploadProgress, setUploadProgress] = React.useState({});
|
|
757
|
+
const [isUploading, setIsUploading] = React.useState(false);
|
|
758
|
+
const endpoint = options?.endpoint || "https://api.dashtrack.com/contacts/_/contact_form_uploads";
|
|
759
|
+
const uploadFiles = React.useCallback(
|
|
760
|
+
async (files) => {
|
|
761
|
+
if (files.length === 0) return;
|
|
762
|
+
setIsUploading(true);
|
|
763
|
+
try {
|
|
764
|
+
const tokens = [];
|
|
765
|
+
for (const file of files) {
|
|
766
|
+
const formData = new FormData();
|
|
767
|
+
formData.append("contact_form_upload[file_upload]", file);
|
|
768
|
+
formData.append("contact_form_upload[title]", file.name);
|
|
769
|
+
formData.append("contact_form_upload[file_name]", file.name);
|
|
770
|
+
formData.append("contact_form_upload[file_size]", String(file.size));
|
|
771
|
+
const response = await fetch(endpoint, {
|
|
772
|
+
method: "POST",
|
|
773
|
+
body: formData
|
|
774
|
+
});
|
|
775
|
+
if (!response.ok) {
|
|
776
|
+
throw new Error(`Upload failed: ${response.statusText}`);
|
|
777
|
+
}
|
|
778
|
+
const data = await response.json();
|
|
779
|
+
if (data.contact_form_upload?.token) {
|
|
780
|
+
tokens.push(`upload_${data.contact_form_upload.token}`);
|
|
781
|
+
}
|
|
782
|
+
setUploadProgress((prev) => ({
|
|
783
|
+
...prev,
|
|
784
|
+
[file.name]: 100
|
|
785
|
+
}));
|
|
786
|
+
}
|
|
787
|
+
setUploadTokens(tokens);
|
|
788
|
+
} catch (error) {
|
|
789
|
+
console.error("File upload error:", error);
|
|
790
|
+
options?.onError?.(error);
|
|
791
|
+
} finally {
|
|
792
|
+
setIsUploading(false);
|
|
793
|
+
}
|
|
794
|
+
},
|
|
795
|
+
[endpoint, options]
|
|
796
|
+
);
|
|
797
|
+
const removeFile = React.useCallback((file, index) => {
|
|
798
|
+
setUploadTokens((prev) => prev.filter((_, i) => i !== index));
|
|
799
|
+
setUploadProgress((prev) => {
|
|
800
|
+
const newProgress = { ...prev };
|
|
801
|
+
delete newProgress[file.name];
|
|
802
|
+
return newProgress;
|
|
803
|
+
});
|
|
804
|
+
}, []);
|
|
805
|
+
const resetUpload = React.useCallback(() => {
|
|
806
|
+
setUploadTokens([]);
|
|
807
|
+
setUploadProgress({});
|
|
808
|
+
}, []);
|
|
809
|
+
return {
|
|
810
|
+
uploadTokens,
|
|
811
|
+
uploadProgress,
|
|
812
|
+
isUploading,
|
|
813
|
+
uploadFiles,
|
|
814
|
+
removeFile,
|
|
815
|
+
resetUpload
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
function useContactForm(options) {
|
|
819
|
+
const {
|
|
820
|
+
formFields,
|
|
821
|
+
formConfig,
|
|
822
|
+
onSubmit,
|
|
823
|
+
onSuccess,
|
|
824
|
+
onError,
|
|
825
|
+
resetOnSuccess = true,
|
|
826
|
+
uploadTokens = []
|
|
827
|
+
} = options;
|
|
828
|
+
const [isSubmitted, setIsSubmitted] = React.useState(false);
|
|
829
|
+
const [submissionError, setSubmissionError] = React.useState(null);
|
|
830
|
+
const form = forms.useForm({
|
|
831
|
+
initialValues: React.useMemo(
|
|
832
|
+
() => generateInitialValues(formFields),
|
|
833
|
+
[formFields]
|
|
834
|
+
),
|
|
835
|
+
validationSchema: React.useMemo(
|
|
836
|
+
() => generateValidationSchema(formFields),
|
|
837
|
+
[formFields]
|
|
838
|
+
),
|
|
839
|
+
onSubmit: async (values, helpers) => {
|
|
840
|
+
setSubmissionError(null);
|
|
841
|
+
const shouldAutoSubmit = Boolean(formConfig?.endpoint);
|
|
842
|
+
if (!shouldAutoSubmit && !onSubmit) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
try {
|
|
846
|
+
let result;
|
|
847
|
+
const submissionValues = {
|
|
848
|
+
...values,
|
|
849
|
+
...uploadTokens.length > 0 && {
|
|
850
|
+
contact_form_upload_tokens: uploadTokens
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
if (shouldAutoSubmit) {
|
|
854
|
+
result = await submitPageSpeedForm(submissionValues, formConfig);
|
|
855
|
+
}
|
|
856
|
+
if (onSubmit) {
|
|
857
|
+
await onSubmit(submissionValues);
|
|
858
|
+
}
|
|
859
|
+
if (shouldAutoSubmit || onSubmit) {
|
|
860
|
+
setIsSubmitted(true);
|
|
861
|
+
if (resetOnSuccess) {
|
|
862
|
+
helpers.resetForm();
|
|
863
|
+
}
|
|
864
|
+
onSuccess?.(result);
|
|
865
|
+
setTimeout(() => setIsSubmitted(false), 5e3);
|
|
866
|
+
}
|
|
867
|
+
} catch (error) {
|
|
868
|
+
if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
|
|
869
|
+
helpers.setErrors(error.formErrors);
|
|
870
|
+
}
|
|
871
|
+
const errorMessage = error instanceof Error ? error.message : "Form submission failed";
|
|
872
|
+
setSubmissionError(errorMessage);
|
|
873
|
+
onError?.(error);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
});
|
|
877
|
+
const formMethod = formConfig?.method?.toLowerCase() === "get" ? "get" : "post";
|
|
878
|
+
return {
|
|
879
|
+
form,
|
|
880
|
+
isSubmitted,
|
|
881
|
+
submissionError,
|
|
882
|
+
formMethod
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// lib/forms.ts
|
|
504
887
|
var PageSpeedFormSubmissionError = class extends Error {
|
|
505
888
|
constructor(message, options = {}) {
|
|
506
889
|
super(message);
|
|
@@ -882,19 +1265,67 @@ function PatternBackground({
|
|
|
882
1265
|
}
|
|
883
1266
|
);
|
|
884
1267
|
}
|
|
1268
|
+
var DEFAULT_FORM_FIELDS = [
|
|
1269
|
+
{
|
|
1270
|
+
name: "first_name",
|
|
1271
|
+
type: "text",
|
|
1272
|
+
label: "First Name",
|
|
1273
|
+
placeholder: "First name",
|
|
1274
|
+
required: true,
|
|
1275
|
+
columnSpan: 6
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
name: "last_name",
|
|
1279
|
+
type: "text",
|
|
1280
|
+
label: "Last Name",
|
|
1281
|
+
placeholder: "Last name",
|
|
1282
|
+
required: true,
|
|
1283
|
+
columnSpan: 6
|
|
1284
|
+
},
|
|
1285
|
+
{
|
|
1286
|
+
name: "email",
|
|
1287
|
+
type: "email",
|
|
1288
|
+
label: "Email",
|
|
1289
|
+
placeholder: "your@email.com",
|
|
1290
|
+
required: true,
|
|
1291
|
+
columnSpan: 12
|
|
1292
|
+
},
|
|
1293
|
+
{
|
|
1294
|
+
name: "phone",
|
|
1295
|
+
type: "tel",
|
|
1296
|
+
label: "Phone",
|
|
1297
|
+
placeholder: "+1 (555) 000-0000",
|
|
1298
|
+
required: true,
|
|
1299
|
+
columnSpan: 12
|
|
1300
|
+
},
|
|
1301
|
+
{
|
|
1302
|
+
name: "message",
|
|
1303
|
+
type: "textarea",
|
|
1304
|
+
label: "Message",
|
|
1305
|
+
placeholder: "Your message...",
|
|
1306
|
+
required: true,
|
|
1307
|
+
rows: 4,
|
|
1308
|
+
columnSpan: 12
|
|
1309
|
+
}
|
|
1310
|
+
];
|
|
885
1311
|
function ContactPhotography({
|
|
886
1312
|
heading,
|
|
887
1313
|
description,
|
|
888
|
-
buttonText,
|
|
1314
|
+
buttonText = "Submit",
|
|
889
1315
|
buttonIcon,
|
|
890
1316
|
actions,
|
|
891
1317
|
actionsSlot,
|
|
1318
|
+
formFields = DEFAULT_FORM_FIELDS,
|
|
1319
|
+
successMessage = "Thank you! Your message has been sent successfully.",
|
|
1320
|
+
errorMessage = "There was an error sending your message. Please try again.",
|
|
892
1321
|
className,
|
|
893
1322
|
headingClassName,
|
|
894
1323
|
descriptionClassName,
|
|
895
1324
|
contentClassName,
|
|
896
1325
|
formClassName,
|
|
897
1326
|
submitClassName,
|
|
1327
|
+
successMessageClassName,
|
|
1328
|
+
errorMessageClassName,
|
|
898
1329
|
background,
|
|
899
1330
|
pattern,
|
|
900
1331
|
patternOpacity,
|
|
@@ -908,55 +1339,26 @@ function ContactPhotography({
|
|
|
908
1339
|
onSuccess,
|
|
909
1340
|
onError
|
|
910
1341
|
}) {
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1342
|
+
const {
|
|
1343
|
+
uploadTokens,
|
|
1344
|
+
uploadProgress,
|
|
1345
|
+
isUploading,
|
|
1346
|
+
uploadFiles,
|
|
1347
|
+
removeFile,
|
|
1348
|
+
resetUpload
|
|
1349
|
+
} = useFileUpload({ onError });
|
|
1350
|
+
const { form, isSubmitted, submissionError, formMethod } = useContactForm({
|
|
1351
|
+
formFields,
|
|
1352
|
+
formConfig,
|
|
1353
|
+
onSubmit,
|
|
1354
|
+
onSuccess: (data) => {
|
|
1355
|
+
resetUpload();
|
|
1356
|
+
onSuccess?.(data);
|
|
918
1357
|
},
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
email: (value) => {
|
|
923
|
-
if (!value) return "Email is required";
|
|
924
|
-
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
|
|
925
|
-
return "Please enter a valid email address";
|
|
926
|
-
return void 0;
|
|
927
|
-
},
|
|
928
|
-
phone: (value) => !value ? "Phone is required" : void 0,
|
|
929
|
-
message: (value) => !value ? "Message is required" : void 0
|
|
930
|
-
},
|
|
931
|
-
onSubmit: async (values, helpers) => {
|
|
932
|
-
const shouldAutoSubmit = Boolean(formConfig?.endpoint);
|
|
933
|
-
if (!shouldAutoSubmit && !onSubmit) {
|
|
934
|
-
return;
|
|
935
|
-
}
|
|
936
|
-
try {
|
|
937
|
-
let result;
|
|
938
|
-
if (shouldAutoSubmit) {
|
|
939
|
-
result = await submitPageSpeedForm(values, formConfig);
|
|
940
|
-
}
|
|
941
|
-
if (onSubmit) {
|
|
942
|
-
await onSubmit(values);
|
|
943
|
-
}
|
|
944
|
-
if (shouldAutoSubmit || onSubmit) {
|
|
945
|
-
if (formConfig?.resetOnSuccess !== false) {
|
|
946
|
-
helpers.resetForm();
|
|
947
|
-
}
|
|
948
|
-
onSuccess?.(result);
|
|
949
|
-
}
|
|
950
|
-
} catch (error) {
|
|
951
|
-
if (error instanceof PageSpeedFormSubmissionError && error.formErrors) {
|
|
952
|
-
helpers.setErrors(error.formErrors);
|
|
953
|
-
}
|
|
954
|
-
onError?.(error);
|
|
955
|
-
throw error;
|
|
956
|
-
}
|
|
957
|
-
}
|
|
1358
|
+
onError,
|
|
1359
|
+
resetOnSuccess: formConfig?.resetOnSuccess !== false,
|
|
1360
|
+
uploadTokens
|
|
958
1361
|
});
|
|
959
|
-
const formMethod = formConfig?.method?.toLowerCase() === "get" ? "get" : "post";
|
|
960
1362
|
const actionsContent = React.useMemo(() => {
|
|
961
1363
|
if (actionsSlot) return actionsSlot;
|
|
962
1364
|
if (actions && actions.length > 0) {
|
|
@@ -1035,6 +1437,26 @@ function ContactPhotography({
|
|
|
1035
1437
|
children: description
|
|
1036
1438
|
}
|
|
1037
1439
|
) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: descriptionClassName, children: description })),
|
|
1440
|
+
isSubmitted && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1441
|
+
"div",
|
|
1442
|
+
{
|
|
1443
|
+
className: cn(
|
|
1444
|
+
"p-4 bg-primary/10 border border-primary rounded-md",
|
|
1445
|
+
successMessageClassName
|
|
1446
|
+
),
|
|
1447
|
+
children: typeof successMessage === "string" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-primary-foreground/90 text-center", children: successMessage }) : successMessage
|
|
1448
|
+
}
|
|
1449
|
+
),
|
|
1450
|
+
submissionError && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1451
|
+
"div",
|
|
1452
|
+
{
|
|
1453
|
+
className: cn(
|
|
1454
|
+
"p-4 bg-destructive/10 border border-destructive rounded-md",
|
|
1455
|
+
errorMessageClassName
|
|
1456
|
+
),
|
|
1457
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-destructive text-center", children: submissionError })
|
|
1458
|
+
}
|
|
1459
|
+
),
|
|
1038
1460
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1039
1461
|
forms.Form,
|
|
1040
1462
|
{
|
|
@@ -1043,76 +1465,23 @@ function ContactPhotography({
|
|
|
1043
1465
|
method: formMethod,
|
|
1044
1466
|
className: cn("space-y-4", formClassName),
|
|
1045
1467
|
children: [
|
|
1046
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
...field,
|
|
1053
|
-
id: "first-name",
|
|
1054
|
-
placeholder: "John",
|
|
1055
|
-
error: meta.touched && !!meta.error,
|
|
1056
|
-
"aria-label": "First Name"
|
|
1057
|
-
}
|
|
1058
|
-
)
|
|
1059
|
-
] }) }),
|
|
1060
|
-
/* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "last_name", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
1061
|
-
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "last-name", children: "Last Name" }),
|
|
1062
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1063
|
-
inputs.TextInput,
|
|
1468
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-12 gap-4", children: formFields.map((field) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1469
|
+
"div",
|
|
1470
|
+
{
|
|
1471
|
+
className: getColumnSpanClass(field.columnSpan),
|
|
1472
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1473
|
+
DynamicFormField,
|
|
1064
1474
|
{
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1475
|
+
field,
|
|
1476
|
+
uploadProgress,
|
|
1477
|
+
onFileUpload: uploadFiles,
|
|
1478
|
+
onFileRemove: removeFile,
|
|
1479
|
+
isUploading
|
|
1070
1480
|
}
|
|
1071
1481
|
)
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "email", children: "Email" }),
|
|
1076
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1077
|
-
inputs.TextInput,
|
|
1078
|
-
{
|
|
1079
|
-
...field,
|
|
1080
|
-
id: "email",
|
|
1081
|
-
type: "email",
|
|
1082
|
-
placeholder: "john@example.com",
|
|
1083
|
-
error: meta.touched && !!meta.error,
|
|
1084
|
-
"aria-label": "Email"
|
|
1085
|
-
}
|
|
1086
|
-
)
|
|
1087
|
-
] }) }),
|
|
1088
|
-
/* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "phone", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
1089
|
-
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "phone", children: "Phone" }),
|
|
1090
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1091
|
-
inputs.TextInput,
|
|
1092
|
-
{
|
|
1093
|
-
...field,
|
|
1094
|
-
id: "phone",
|
|
1095
|
-
type: "tel",
|
|
1096
|
-
placeholder: "+1 (555) 000-0000",
|
|
1097
|
-
error: meta.touched && !!meta.error,
|
|
1098
|
-
"aria-label": "Phone"
|
|
1099
|
-
}
|
|
1100
|
-
)
|
|
1101
|
-
] }) }),
|
|
1102
|
-
/* @__PURE__ */ jsxRuntime.jsx(forms.Field, { name: "message", children: ({ field, meta }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
1103
|
-
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "message", children: "Message" }),
|
|
1104
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1105
|
-
inputs.TextArea,
|
|
1106
|
-
{
|
|
1107
|
-
...field,
|
|
1108
|
-
id: "message",
|
|
1109
|
-
placeholder: "Your message...",
|
|
1110
|
-
rows: 4,
|
|
1111
|
-
error: meta.touched && !!meta.error,
|
|
1112
|
-
"aria-label": "Message"
|
|
1113
|
-
}
|
|
1114
|
-
)
|
|
1115
|
-
] }) }),
|
|
1482
|
+
},
|
|
1483
|
+
field.name
|
|
1484
|
+
)) }),
|
|
1116
1485
|
actionsSlot || actions && actions.length > 0 ? actionsContent : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1117
1486
|
Pressable,
|
|
1118
1487
|
{
|