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