@nextlyhq/plugin-form-builder 0.0.2-alpha.60 → 0.0.2-alpha.62
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/admin/index.d.ts +18 -2
- package/dist/admin/index.js +1796 -1306
- package/dist/admin/index.js.map +1 -1
- package/dist/{chunk-XCICDELK.js → chunk-H5X3FKOZ.js} +131 -1
- package/dist/chunk-H5X3FKOZ.js.map +1 -0
- package/dist/index.d.ts +3 -18
- package/dist/index.js +601 -166
- package/dist/index.js.map +1 -1
- package/dist/{types-DuEFdLNU.d.ts → types-hLfLe81K.d.ts} +122 -75
- package/package.json +11 -11
- package/dist/chunk-XCICDELK.js.map +0 -1
package/dist/admin/index.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_FORM_SETTINGS,
|
|
3
|
+
buildFieldReferenceMap,
|
|
4
|
+
documentReachability,
|
|
5
|
+
enforcedValidationRules,
|
|
3
6
|
evaluateConditions,
|
|
4
7
|
formatExportValue,
|
|
5
8
|
isKnownFormField,
|
|
6
|
-
normalizeFormSettings
|
|
7
|
-
|
|
9
|
+
normalizeFormSettings,
|
|
10
|
+
parseFieldRef,
|
|
11
|
+
parseRedirectReference
|
|
12
|
+
} from "../chunk-H5X3FKOZ.js";
|
|
8
13
|
|
|
9
14
|
// src/admin/index.ts
|
|
10
15
|
import {
|
|
@@ -872,23 +877,24 @@ function SubmissionsView({ collectionSlug }) {
|
|
|
872
877
|
// src/admin/FormBuilderView.tsx
|
|
873
878
|
import {
|
|
874
879
|
Button as Button8,
|
|
880
|
+
Card as Card3,
|
|
875
881
|
FieldShell as FieldShell2,
|
|
876
882
|
FormActions,
|
|
877
|
-
|
|
878
|
-
|
|
883
|
+
Input as Input7,
|
|
884
|
+
Textarea as Textarea4,
|
|
879
885
|
toast,
|
|
880
886
|
Tabs as Tabs4,
|
|
881
887
|
TabsList as TabsList4,
|
|
882
888
|
TabsTrigger as TabsTrigger4,
|
|
883
|
-
Select as
|
|
884
|
-
SelectTrigger as
|
|
885
|
-
SelectValue as
|
|
886
|
-
SelectContent as
|
|
887
|
-
SelectItem as
|
|
889
|
+
Select as Select9,
|
|
890
|
+
SelectTrigger as SelectTrigger9,
|
|
891
|
+
SelectValue as SelectValue9,
|
|
892
|
+
SelectContent as SelectContent9,
|
|
893
|
+
SelectItem as SelectItem9
|
|
888
894
|
} from "@nextlyhq/ui";
|
|
889
895
|
import { useQueryClient } from "@tanstack/react-query";
|
|
890
896
|
import { FORM_FIELD_TYPE_CATALOG as FORM_FIELD_TYPE_CATALOG3 } from "nextly/field-catalog";
|
|
891
|
-
import { useCallback as useCallback7, useEffect as
|
|
897
|
+
import { useCallback as useCallback7, useEffect as useEffect5, useRef as useRef2, useState as useState10 } from "react";
|
|
892
898
|
|
|
893
899
|
// src/admin/components/builder/FieldCards.tsx
|
|
894
900
|
import {
|
|
@@ -922,52 +928,6 @@ import * as Lucide from "lucide-react";
|
|
|
922
928
|
import { FORM_FIELD_TYPE_CATALOG as FORM_FIELD_TYPE_CATALOG2 } from "nextly/field-catalog";
|
|
923
929
|
import { useCallback as useCallback5, useMemo as useMemo5, useState as useState6 } from "react";
|
|
924
930
|
|
|
925
|
-
// src/utils/field-references.ts
|
|
926
|
-
function referencesInTemplate(template, fieldName) {
|
|
927
|
-
const pattern = new RegExp(
|
|
928
|
-
`\\{\\{\\s*${fieldName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\}\\}`
|
|
929
|
-
);
|
|
930
|
-
return pattern.test(template);
|
|
931
|
-
}
|
|
932
|
-
function findFieldReferences(fieldName, fields, notifications) {
|
|
933
|
-
const references = [];
|
|
934
|
-
for (const field of fields) {
|
|
935
|
-
if (field.name === fieldName) continue;
|
|
936
|
-
if (!field.conditionalLogic?.enabled) continue;
|
|
937
|
-
const conditions = field.conditionalLogic.conditions ?? [];
|
|
938
|
-
if (conditions.some((condition) => condition.field === fieldName)) {
|
|
939
|
-
references.push({ kind: "condition", label: field.label || field.name });
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
for (const notification of notifications) {
|
|
943
|
-
if (!notification.enabled) continue;
|
|
944
|
-
const templates = [
|
|
945
|
-
notification.to,
|
|
946
|
-
...notification.cc ?? [],
|
|
947
|
-
...notification.bcc ?? [],
|
|
948
|
-
notification.replyTo
|
|
949
|
-
].filter((value) => typeof value === "string");
|
|
950
|
-
const referencedByTemplate = templates.some(
|
|
951
|
-
(template) => referencesInTemplate(template, fieldName)
|
|
952
|
-
);
|
|
953
|
-
const referencedByCondition = notification.condition?.field === fieldName;
|
|
954
|
-
if (referencedByTemplate || referencedByCondition) {
|
|
955
|
-
references.push({
|
|
956
|
-
kind: "notification",
|
|
957
|
-
label: notification.name || "Notification"
|
|
958
|
-
});
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
return references;
|
|
962
|
-
}
|
|
963
|
-
function buildFieldReferenceMap(fields, notifications) {
|
|
964
|
-
const map = /* @__PURE__ */ new Map();
|
|
965
|
-
for (const field of fields) {
|
|
966
|
-
map.set(field.name, findFieldReferences(field.name, fields, notifications));
|
|
967
|
-
}
|
|
968
|
-
return map;
|
|
969
|
-
}
|
|
970
|
-
|
|
971
931
|
// src/admin/context/FormBuilderContext.tsx
|
|
972
932
|
import { FORM_FIELD_TYPE_CATALOG } from "nextly/field-catalog";
|
|
973
933
|
import {
|
|
@@ -1074,7 +1034,8 @@ function FormBuilderProvider({
|
|
|
1074
1034
|
name: initialData?.name || "",
|
|
1075
1035
|
slug: initialData?.slug || "",
|
|
1076
1036
|
description: initialData?.description,
|
|
1077
|
-
status: initialData?.status || "draft"
|
|
1037
|
+
status: initialData?.status || "draft",
|
|
1038
|
+
closedMessage: initialData?.closedMessage
|
|
1078
1039
|
});
|
|
1079
1040
|
const [settings, setSettings] = useState3(
|
|
1080
1041
|
() => normalizeFormSettings(initialData?.settings)
|
|
@@ -1341,8 +1302,9 @@ function AddFieldDialog({
|
|
|
1341
1302
|
|
|
1342
1303
|
// src/admin/components/builder/FieldEditor.tsx
|
|
1343
1304
|
import {
|
|
1305
|
+
drawsAnyValidationRule,
|
|
1344
1306
|
FieldOptionsEditor,
|
|
1345
|
-
|
|
1307
|
+
ValidationRulesEditor,
|
|
1346
1308
|
withOptionIds
|
|
1347
1309
|
} from "@nextlyhq/plugin-sdk/admin";
|
|
1348
1310
|
import {
|
|
@@ -1359,6 +1321,7 @@ import {
|
|
|
1359
1321
|
SelectTrigger as SelectTrigger4,
|
|
1360
1322
|
SelectValue as SelectValue4
|
|
1361
1323
|
} from "@nextlyhq/ui";
|
|
1324
|
+
import { validationRulesForFieldType } from "nextly/field-catalog";
|
|
1362
1325
|
import { useState as useState5, useCallback as useCallback4, useMemo as useMemo4 } from "react";
|
|
1363
1326
|
|
|
1364
1327
|
// src/admin/components/builder/ConditionalLogicEditor.tsx
|
|
@@ -1592,7 +1555,11 @@ function ConditionalLogicEditor({
|
|
|
1592
1555
|
}
|
|
1593
1556
|
) : (
|
|
1594
1557
|
// Full-strength warning border so this notice boundary is perceivable over its tinted fill.
|
|
1595
|
-
|
|
1558
|
+
// The 600 shade rather than the base token: the base measures 4.37:1
|
|
1559
|
+
// once its own 10% fill composites over the page container, short of
|
|
1560
|
+
// the 4.5:1 text needs. The 600 shade holds 5.13:1 at its worst
|
|
1561
|
+
// surface in either mode.
|
|
1562
|
+
/* @__PURE__ */ jsx5("p", { className: "mt-4 text-center text-xs text-warning-600 font-medium bg-warning/10 p-2 rounded-none border border-warning", children: "Add more fields to the form to create conditions." })
|
|
1596
1563
|
)
|
|
1597
1564
|
] })
|
|
1598
1565
|
] });
|
|
@@ -1982,100 +1949,49 @@ function ValidationTab({
|
|
|
1982
1949
|
field,
|
|
1983
1950
|
onUpdate
|
|
1984
1951
|
}) {
|
|
1985
|
-
const validation = useMemo4(
|
|
1986
|
-
|
|
1987
|
-
|
|
1952
|
+
const validation = useMemo4(
|
|
1953
|
+
() => field.validation ?? {},
|
|
1954
|
+
[field.validation]
|
|
1955
|
+
);
|
|
1956
|
+
const allowed = useMemo4(() => {
|
|
1957
|
+
const enforced = enforcedValidationRules(field.type);
|
|
1958
|
+
return validationRulesForFieldType(field.type).filter(
|
|
1959
|
+
(rule) => enforced.includes(rule)
|
|
1960
|
+
);
|
|
1961
|
+
}, [field.type]);
|
|
1962
|
+
const applyRules = useCallback4(
|
|
1963
|
+
(next) => {
|
|
1964
|
+
const { message, ...rules } = next;
|
|
1988
1965
|
onUpdate({
|
|
1989
|
-
validation: {
|
|
1966
|
+
validation: {
|
|
1967
|
+
...validation,
|
|
1968
|
+
...rules,
|
|
1969
|
+
// This surface's own key for the rule core calls `message`.
|
|
1970
|
+
...message === void 0 ? {} : { errorMessage: message }
|
|
1971
|
+
}
|
|
1990
1972
|
});
|
|
1991
1973
|
},
|
|
1992
1974
|
[validation, onUpdate]
|
|
1993
1975
|
);
|
|
1994
1976
|
return /* @__PURE__ */ jsxs5("div", { className: "space-y-6 pt-2", children: [
|
|
1995
|
-
/* @__PURE__ */
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
}
|
|
2014
|
-
)
|
|
2015
|
-
] }),
|
|
2016
|
-
(field.type === "text" || field.type === "textarea") && /* @__PURE__ */ jsxs5("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2017
|
-
/* @__PURE__ */ jsx6(
|
|
2018
|
-
ValidationNumberField,
|
|
2019
|
-
{
|
|
2020
|
-
label: "Min Length",
|
|
2021
|
-
description: "Minimum characters required.",
|
|
2022
|
-
counts: true,
|
|
2023
|
-
value: field.validation?.minLength,
|
|
2024
|
-
onChange: (n) => updateValidation("minLength", n)
|
|
2025
|
-
}
|
|
2026
|
-
),
|
|
2027
|
-
/* @__PURE__ */ jsx6(
|
|
2028
|
-
ValidationNumberField,
|
|
2029
|
-
{
|
|
2030
|
-
label: "Max Length",
|
|
2031
|
-
description: "Maximum characters allowed.",
|
|
2032
|
-
counts: true,
|
|
2033
|
-
value: field.validation?.maxLength,
|
|
2034
|
-
onChange: (n) => updateValidation("maxLength", n)
|
|
2035
|
-
}
|
|
2036
|
-
)
|
|
2037
|
-
] }),
|
|
2038
|
-
field.type === "number" && /* @__PURE__ */ jsxs5("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2039
|
-
/* @__PURE__ */ jsx6(
|
|
2040
|
-
ValidationNumberField,
|
|
2041
|
-
{
|
|
2042
|
-
label: "Min Value",
|
|
2043
|
-
description: "Minimum numerical value.",
|
|
2044
|
-
value: field.validation?.min,
|
|
2045
|
-
onChange: (n) => updateValidation("min", n)
|
|
2046
|
-
}
|
|
2047
|
-
),
|
|
2048
|
-
/* @__PURE__ */ jsx6(
|
|
2049
|
-
ValidationNumberField,
|
|
2050
|
-
{
|
|
2051
|
-
label: "Max Value",
|
|
2052
|
-
description: "Maximum numerical value.",
|
|
2053
|
-
value: field.validation?.max,
|
|
2054
|
-
onChange: (n) => updateValidation("max", n)
|
|
2055
|
-
}
|
|
2056
|
-
)
|
|
2057
|
-
] }),
|
|
2058
|
-
(field.type === "text" || field.type === "email" || field.type === "phone" || field.type === "url") && /* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
|
|
2059
|
-
/* @__PURE__ */ jsx6(
|
|
2060
|
-
FormLabelWithTooltip2,
|
|
2061
|
-
{
|
|
2062
|
-
label: "Pattern (Regex)",
|
|
2063
|
-
htmlFor: "val-pattern",
|
|
2064
|
-
description: "Regular expression for advanced validation."
|
|
2065
|
-
}
|
|
2066
|
-
),
|
|
2067
|
-
/* @__PURE__ */ jsx6(
|
|
2068
|
-
Input2,
|
|
2069
|
-
{
|
|
2070
|
-
id: "val-pattern",
|
|
2071
|
-
type: "text",
|
|
2072
|
-
value: field.validation?.pattern || "",
|
|
2073
|
-
onChange: (e) => updateValidation("pattern", e.target.value),
|
|
2074
|
-
placeholder: "^[A-Za-z]+$",
|
|
2075
|
-
className: "bg-transparent"
|
|
2076
|
-
}
|
|
2077
|
-
)
|
|
2078
|
-
] }),
|
|
1977
|
+
/* @__PURE__ */ jsx6(
|
|
1978
|
+
ValidationRulesEditor,
|
|
1979
|
+
{
|
|
1980
|
+
allowed,
|
|
1981
|
+
value: {
|
|
1982
|
+
minLength: validation.minLength,
|
|
1983
|
+
maxLength: validation.maxLength,
|
|
1984
|
+
// No row bounds here: this surface neither stores nor enforces them,
|
|
1985
|
+
// and `allowed` above never admits them, so passing them would be a
|
|
1986
|
+
// value the editor could not draw and the runtime could not read.
|
|
1987
|
+
min: validation.min,
|
|
1988
|
+
max: validation.max,
|
|
1989
|
+
pattern: validation.pattern,
|
|
1990
|
+
message: validation.errorMessage
|
|
1991
|
+
},
|
|
1992
|
+
onChange: applyRules
|
|
1993
|
+
}
|
|
1994
|
+
),
|
|
2079
1995
|
field.type === "file" && /* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
|
|
2080
1996
|
/* @__PURE__ */ jsx6(
|
|
2081
1997
|
FormLabelWithTooltip2,
|
|
@@ -2100,7 +2016,7 @@ function ValidationTab({
|
|
|
2100
2016
|
}
|
|
2101
2017
|
)
|
|
2102
2018
|
] }),
|
|
2103
|
-
(
|
|
2019
|
+
!drawsAnyValidationRule(allowed) && field.type !== "file" && // Semantic border token so this info-box boundary is visible at the 3:1 UI minimum.
|
|
2104
2020
|
/* @__PURE__ */ jsx6("div", { className: "p-3 bg-muted rounded-none text-xs text-muted-foreground text-center border border-dashed border-border", children: "No additional validation options for this field type." })
|
|
2105
2021
|
] });
|
|
2106
2022
|
}
|
|
@@ -2485,6 +2401,7 @@ function FieldCards({
|
|
|
2485
2401
|
// src/admin/components/builder/FormNotificationsTab.tsx
|
|
2486
2402
|
import {
|
|
2487
2403
|
Badge as Badge3,
|
|
2404
|
+
Card,
|
|
2488
2405
|
Button as Button6,
|
|
2489
2406
|
DropdownMenu as DropdownMenu3,
|
|
2490
2407
|
DropdownMenuContent as DropdownMenuContent3,
|
|
@@ -2500,11 +2417,6 @@ import {
|
|
|
2500
2417
|
SelectItem as SelectItem5,
|
|
2501
2418
|
SelectTrigger as SelectTrigger5,
|
|
2502
2419
|
SelectValue as SelectValue5,
|
|
2503
|
-
Sheet as Sheet2,
|
|
2504
|
-
SheetContent as SheetContent2,
|
|
2505
|
-
SheetDescription as SheetDescription2,
|
|
2506
|
-
SheetHeader as SheetHeader2,
|
|
2507
|
-
SheetTitle as SheetTitle2,
|
|
2508
2420
|
Switch as Switch2
|
|
2509
2421
|
} from "@nextlyhq/ui";
|
|
2510
2422
|
import {
|
|
@@ -2519,7 +2431,51 @@ import {
|
|
|
2519
2431
|
X
|
|
2520
2432
|
} from "lucide-react";
|
|
2521
2433
|
import { useCallback as useCallback6, useEffect as useEffect3, useMemo as useMemo6, useState as useState7 } from "react";
|
|
2522
|
-
|
|
2434
|
+
|
|
2435
|
+
// src/admin/components/builder/notification-addresses.ts
|
|
2436
|
+
var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2437
|
+
function isValidEmail(value) {
|
|
2438
|
+
return EMAIL_PATTERN.test(value.trim());
|
|
2439
|
+
}
|
|
2440
|
+
var MESSAGE = "Enter a valid email address.";
|
|
2441
|
+
function addressError(field, value) {
|
|
2442
|
+
if (field === "replyTo" && parseFieldRef(value) !== null) return void 0;
|
|
2443
|
+
if (!value?.trim()) return void 0;
|
|
2444
|
+
return isValidEmail(value) ? void 0 : MESSAGE;
|
|
2445
|
+
}
|
|
2446
|
+
function addressErrorsIn(notification) {
|
|
2447
|
+
const errors = {};
|
|
2448
|
+
const sender = addressError("senderEmail", notification.senderEmail);
|
|
2449
|
+
if (sender) errors.senderEmail = sender;
|
|
2450
|
+
if (notification.recipientType === "static") {
|
|
2451
|
+
const to = addressError("to", notification.to);
|
|
2452
|
+
if (to) errors.to = to;
|
|
2453
|
+
}
|
|
2454
|
+
const replyTo = addressError("replyTo", notification.replyTo);
|
|
2455
|
+
if (replyTo) errors.replyTo = replyTo;
|
|
2456
|
+
return errors;
|
|
2457
|
+
}
|
|
2458
|
+
function notificationsWithBadAddresses(notifications) {
|
|
2459
|
+
return notifications.filter((n) => Object.keys(addressErrorsIn(n)).length > 0).map((n) => n.name.trim() || "Untitled notification");
|
|
2460
|
+
}
|
|
2461
|
+
function badAddressMessage(notifications) {
|
|
2462
|
+
const names = notificationsWithBadAddresses(notifications);
|
|
2463
|
+
if (names.length === 0) return null;
|
|
2464
|
+
return names.length === 1 ? `${names[0]} has an invalid email address` : `${names.length} notifications have invalid email addresses`;
|
|
2465
|
+
}
|
|
2466
|
+
function notificationsBlockingSave(notifications) {
|
|
2467
|
+
const unnamed = unnamedNotificationCount(notifications);
|
|
2468
|
+
if (unnamed > 0) {
|
|
2469
|
+
return unnamed === 1 ? "A notification needs a name" : `${unnamed} notifications need a name`;
|
|
2470
|
+
}
|
|
2471
|
+
return badAddressMessage(notifications);
|
|
2472
|
+
}
|
|
2473
|
+
function unnamedNotificationCount(notifications) {
|
|
2474
|
+
return notifications.filter((n) => !n.name.trim()).length;
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
// src/admin/components/builder/FormNotificationsTab.tsx
|
|
2478
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2523
2479
|
async function fetchProviders() {
|
|
2524
2480
|
try {
|
|
2525
2481
|
const res = await fetch("/admin/api/email-providers?pageSize=100&page=1", {
|
|
@@ -2544,15 +2500,9 @@ async function fetchTemplates() {
|
|
|
2544
2500
|
return [];
|
|
2545
2501
|
}
|
|
2546
2502
|
}
|
|
2547
|
-
var FIELD_REF_PATTERN = /^\{\{(\w+)\}\}$/;
|
|
2548
2503
|
function toFieldRef(fieldName) {
|
|
2549
2504
|
return `{{${fieldName}}}`;
|
|
2550
2505
|
}
|
|
2551
|
-
function parseFieldRef(value) {
|
|
2552
|
-
if (!value) return null;
|
|
2553
|
-
const match = value.match(FIELD_REF_PATTERN);
|
|
2554
|
-
return match ? match[1] : null;
|
|
2555
|
-
}
|
|
2556
2506
|
function buildEmailFieldOptions(fields, currentRef) {
|
|
2557
2507
|
const emailFields = fields.filter((field) => field.type === "email");
|
|
2558
2508
|
if (currentRef && !emailFields.some((field) => field.name === currentRef)) {
|
|
@@ -2561,10 +2511,6 @@ function buildEmailFieldOptions(fields, currentRef) {
|
|
|
2561
2511
|
}
|
|
2562
2512
|
return emailFields;
|
|
2563
2513
|
}
|
|
2564
|
-
var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
2565
|
-
function isValidEmail(value) {
|
|
2566
|
-
return EMAIL_PATTERN.test(value.trim());
|
|
2567
|
-
}
|
|
2568
2514
|
var COMPARISON_LABELS = {
|
|
2569
2515
|
equals: "Equals",
|
|
2570
2516
|
notEquals: "Does not equal",
|
|
@@ -2600,10 +2546,13 @@ function NotificationCard({
|
|
|
2600
2546
|
notification,
|
|
2601
2547
|
providerName,
|
|
2602
2548
|
fields,
|
|
2603
|
-
|
|
2549
|
+
expanded,
|
|
2550
|
+
editorId,
|
|
2551
|
+
onToggle,
|
|
2604
2552
|
onDuplicate,
|
|
2605
2553
|
onDelete,
|
|
2606
|
-
onToggleEnabled
|
|
2554
|
+
onToggleEnabled,
|
|
2555
|
+
children
|
|
2607
2556
|
}) {
|
|
2608
2557
|
const recipient = describeRecipient(notification, fields);
|
|
2609
2558
|
const ccCount = (notification.cc?.length ?? 0) + (notification.bcc?.length ?? 0);
|
|
@@ -2617,120 +2566,140 @@ function NotificationCard({
|
|
|
2617
2566
|
if (hasStaleReference) {
|
|
2618
2567
|
warnings.push("References a deleted field \u2014 edit to repair");
|
|
2619
2568
|
}
|
|
2620
|
-
return
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
children:
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2569
|
+
return (
|
|
2570
|
+
// `Card` rather than its classes written out again: the primitive owns the
|
|
2571
|
+
// surface colour, the radius, the foreground and the transition, and a
|
|
2572
|
+
// second spelling of them drifts the moment the shared surface changes.
|
|
2573
|
+
//
|
|
2574
|
+
// `overflow-hidden` because an expanded editor paints a full-width tinted
|
|
2575
|
+
// child; without clipping it squares off the corners the card just rounded.
|
|
2576
|
+
/* @__PURE__ */ jsxs7(Card, { className: "overflow-hidden p-0", children: [
|
|
2577
|
+
/* @__PURE__ */ jsxs7(
|
|
2578
|
+
"div",
|
|
2579
|
+
{
|
|
2580
|
+
className: `flex items-center gap-2 px-3 py-2.5 ${notification.enabled ? "" : "opacity-60"}`,
|
|
2581
|
+
children: [
|
|
2582
|
+
/* @__PURE__ */ jsx8("span", { className: "flex h-7 w-7 shrink-0 items-center justify-center bg-primary/5 text-primary", children: /* @__PURE__ */ jsx8(Mail, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }),
|
|
2583
|
+
/* @__PURE__ */ jsxs7(
|
|
2584
|
+
"button",
|
|
2585
|
+
{
|
|
2586
|
+
type: "button",
|
|
2587
|
+
onClick: onToggle,
|
|
2588
|
+
"aria-expanded": expanded,
|
|
2589
|
+
"aria-controls": editorId,
|
|
2590
|
+
className: "flex min-w-0 flex-1 items-center gap-3 rounded-none py-1 text-left cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary",
|
|
2591
|
+
"aria-label": `Edit notification ${notification.name}`,
|
|
2592
|
+
children: [
|
|
2593
|
+
/* @__PURE__ */ jsxs7("span", { className: "min-w-0 flex-1", children: [
|
|
2594
|
+
/* @__PURE__ */ jsxs7("span", { className: "flex items-center gap-2", children: [
|
|
2595
|
+
/* @__PURE__ */ jsx8("span", { className: "truncate text-sm font-medium text-foreground", children: notification.name }),
|
|
2596
|
+
notification.condition && /* @__PURE__ */ jsxs7(
|
|
2597
|
+
Badge3,
|
|
2598
|
+
{
|
|
2599
|
+
variant: "outline",
|
|
2600
|
+
className: "shrink-0 gap-1 rounded-none border-border px-1.5 py-0 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground",
|
|
2601
|
+
children: [
|
|
2602
|
+
/* @__PURE__ */ jsx8(Filter, { className: "h-2.5 w-2.5", "aria-hidden": "true" }),
|
|
2603
|
+
"Conditional"
|
|
2604
|
+
]
|
|
2605
|
+
}
|
|
2606
|
+
)
|
|
2607
|
+
] }),
|
|
2608
|
+
/* @__PURE__ */ jsxs7("span", { className: "block truncate text-xs text-muted-foreground", children: [
|
|
2609
|
+
/* @__PURE__ */ jsx8("span", { className: recipient.missing ? "text-destructive" : "", children: recipient.text }),
|
|
2610
|
+
ccCount > 0 && /* @__PURE__ */ jsxs7("span", { children: [
|
|
2611
|
+
/* @__PURE__ */ jsx8("span", { className: "mx-1.5 text-muted-foreground", children: "\xB7" }),
|
|
2612
|
+
"+",
|
|
2613
|
+
ccCount,
|
|
2614
|
+
" cc/bcc"
|
|
2615
|
+
] }),
|
|
2616
|
+
/* @__PURE__ */ jsx8("span", { className: "mx-1.5 text-muted-foreground", children: "\xB7" }),
|
|
2617
|
+
providerName
|
|
2618
|
+
] })
|
|
2619
|
+
] }),
|
|
2620
|
+
warnings.length > 0 && /* @__PURE__ */ jsx8("span", { className: "flex shrink-0 flex-col items-end gap-0.5", children: warnings.map((warning) => /* @__PURE__ */ jsxs7(
|
|
2621
|
+
"span",
|
|
2639
2622
|
{
|
|
2640
|
-
|
|
2641
|
-
className: "shrink-0 gap-1 rounded-none border-border px-1.5 py-0 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground",
|
|
2623
|
+
className: "flex items-center gap-1 text-xs font-medium text-destructive",
|
|
2642
2624
|
children: [
|
|
2643
|
-
/* @__PURE__ */ jsx8(
|
|
2644
|
-
|
|
2625
|
+
/* @__PURE__ */ jsx8(TriangleAlert2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
2626
|
+
warning
|
|
2645
2627
|
]
|
|
2646
|
-
}
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2628
|
+
},
|
|
2629
|
+
warning
|
|
2630
|
+
)) })
|
|
2631
|
+
]
|
|
2632
|
+
}
|
|
2633
|
+
),
|
|
2634
|
+
/* @__PURE__ */ jsx8(
|
|
2635
|
+
Switch2,
|
|
2636
|
+
{
|
|
2637
|
+
checked: notification.enabled,
|
|
2638
|
+
onCheckedChange: onToggleEnabled,
|
|
2639
|
+
"aria-label": `${notification.enabled ? "Disable" : "Enable"} notification ${notification.name}`
|
|
2640
|
+
}
|
|
2641
|
+
),
|
|
2642
|
+
/* @__PURE__ */ jsxs7(DropdownMenu3, { children: [
|
|
2643
|
+
/* @__PURE__ */ jsx8(DropdownMenuTrigger3, { asChild: true, children: /* @__PURE__ */ jsx8(
|
|
2644
|
+
Button6,
|
|
2645
|
+
{
|
|
2646
|
+
type: "button",
|
|
2647
|
+
variant: "ghost",
|
|
2648
|
+
size: "icon",
|
|
2649
|
+
className: "h-8 w-8 shrink-0 rounded-none text-muted-foreground hover:text-foreground",
|
|
2650
|
+
"aria-label": `Notification actions for ${notification.name}`,
|
|
2651
|
+
children: /* @__PURE__ */ jsx8(MoreHorizontal2, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
2652
|
+
}
|
|
2653
|
+
) }),
|
|
2654
|
+
/* @__PURE__ */ jsxs7(
|
|
2655
|
+
DropdownMenuContent3,
|
|
2663
2656
|
{
|
|
2664
|
-
|
|
2657
|
+
align: "end",
|
|
2658
|
+
className: "w-56 shadow-none border-border",
|
|
2665
2659
|
children: [
|
|
2666
|
-
/* @__PURE__ */
|
|
2667
|
-
|
|
2660
|
+
/* @__PURE__ */ jsxs7(
|
|
2661
|
+
DropdownMenuItem3,
|
|
2662
|
+
{
|
|
2663
|
+
onClick: onToggle,
|
|
2664
|
+
className: "gap-2 cursor-pointer",
|
|
2665
|
+
children: [
|
|
2666
|
+
/* @__PURE__ */ jsx8(Pencil2, { className: "h-4 w-4 text-muted-foreground" }),
|
|
2667
|
+
expanded ? "Collapse" : "Edit"
|
|
2668
|
+
]
|
|
2669
|
+
}
|
|
2670
|
+
),
|
|
2671
|
+
/* @__PURE__ */ jsxs7(
|
|
2672
|
+
DropdownMenuItem3,
|
|
2673
|
+
{
|
|
2674
|
+
onClick: onDuplicate,
|
|
2675
|
+
className: "gap-2 cursor-pointer",
|
|
2676
|
+
children: [
|
|
2677
|
+
/* @__PURE__ */ jsx8(Copy2, { className: "h-4 w-4 text-muted-foreground" }),
|
|
2678
|
+
"Duplicate"
|
|
2679
|
+
]
|
|
2680
|
+
}
|
|
2681
|
+
),
|
|
2682
|
+
/* @__PURE__ */ jsx8(DropdownMenuSeparator2, {}),
|
|
2683
|
+
/* @__PURE__ */ jsxs7(
|
|
2684
|
+
DropdownMenuItem3,
|
|
2685
|
+
{
|
|
2686
|
+
onClick: onDelete,
|
|
2687
|
+
className: "gap-2 cursor-pointer text-destructive focus:text-destructive",
|
|
2688
|
+
children: [
|
|
2689
|
+
/* @__PURE__ */ jsx8(Trash22, { className: "h-4 w-4" }),
|
|
2690
|
+
"Delete"
|
|
2691
|
+
]
|
|
2692
|
+
}
|
|
2693
|
+
)
|
|
2668
2694
|
]
|
|
2669
|
-
}
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
{
|
|
2678
|
-
checked: notification.enabled,
|
|
2679
|
-
onCheckedChange: onToggleEnabled,
|
|
2680
|
-
"aria-label": `${notification.enabled ? "Disable" : "Enable"} notification ${notification.name}`
|
|
2681
|
-
}
|
|
2682
|
-
),
|
|
2683
|
-
/* @__PURE__ */ jsxs7(DropdownMenu3, { children: [
|
|
2684
|
-
/* @__PURE__ */ jsx8(DropdownMenuTrigger3, { asChild: true, children: /* @__PURE__ */ jsx8(
|
|
2685
|
-
Button6,
|
|
2686
|
-
{
|
|
2687
|
-
type: "button",
|
|
2688
|
-
variant: "ghost",
|
|
2689
|
-
size: "icon",
|
|
2690
|
-
className: "h-8 w-8 shrink-0 rounded-none text-muted-foreground hover:text-foreground",
|
|
2691
|
-
"aria-label": `Notification actions for ${notification.name}`,
|
|
2692
|
-
children: /* @__PURE__ */ jsx8(MoreHorizontal2, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
2693
|
-
}
|
|
2694
|
-
) }),
|
|
2695
|
-
/* @__PURE__ */ jsxs7(
|
|
2696
|
-
DropdownMenuContent3,
|
|
2697
|
-
{
|
|
2698
|
-
align: "end",
|
|
2699
|
-
className: "w-56 shadow-none border-border",
|
|
2700
|
-
children: [
|
|
2701
|
-
/* @__PURE__ */ jsxs7(DropdownMenuItem3, { onClick: onEdit, className: "gap-2 cursor-pointer", children: [
|
|
2702
|
-
/* @__PURE__ */ jsx8(Pencil2, { className: "h-4 w-4 text-muted-foreground" }),
|
|
2703
|
-
"Edit"
|
|
2704
|
-
] }),
|
|
2705
|
-
/* @__PURE__ */ jsxs7(
|
|
2706
|
-
DropdownMenuItem3,
|
|
2707
|
-
{
|
|
2708
|
-
onClick: onDuplicate,
|
|
2709
|
-
className: "gap-2 cursor-pointer",
|
|
2710
|
-
children: [
|
|
2711
|
-
/* @__PURE__ */ jsx8(Copy2, { className: "h-4 w-4 text-muted-foreground" }),
|
|
2712
|
-
"Duplicate"
|
|
2713
|
-
]
|
|
2714
|
-
}
|
|
2715
|
-
),
|
|
2716
|
-
/* @__PURE__ */ jsx8(DropdownMenuSeparator2, {}),
|
|
2717
|
-
/* @__PURE__ */ jsxs7(
|
|
2718
|
-
DropdownMenuItem3,
|
|
2719
|
-
{
|
|
2720
|
-
onClick: onDelete,
|
|
2721
|
-
className: "gap-2 cursor-pointer text-destructive focus:text-destructive",
|
|
2722
|
-
children: [
|
|
2723
|
-
/* @__PURE__ */ jsx8(Trash22, { className: "h-4 w-4" }),
|
|
2724
|
-
"Delete"
|
|
2725
|
-
]
|
|
2726
|
-
}
|
|
2727
|
-
)
|
|
2728
|
-
]
|
|
2729
|
-
}
|
|
2730
|
-
)
|
|
2731
|
-
] })
|
|
2732
|
-
] })
|
|
2733
|
-
}
|
|
2695
|
+
}
|
|
2696
|
+
)
|
|
2697
|
+
] })
|
|
2698
|
+
]
|
|
2699
|
+
}
|
|
2700
|
+
),
|
|
2701
|
+
expanded && children
|
|
2702
|
+
] })
|
|
2734
2703
|
);
|
|
2735
2704
|
}
|
|
2736
2705
|
function AddressChipList({
|
|
@@ -2812,596 +2781,579 @@ function AddressChipList({
|
|
|
2812
2781
|
error && /* @__PURE__ */ jsx8("p", { id: `${id}-error`, className: "text-xs text-destructive", children: error })
|
|
2813
2782
|
] });
|
|
2814
2783
|
}
|
|
2815
|
-
function
|
|
2816
|
-
|
|
2817
|
-
return parseFieldRef(replyTo) ? "field" : "custom";
|
|
2818
|
-
}
|
|
2819
|
-
function NotificationSheet({
|
|
2820
|
-
initial,
|
|
2821
|
-
isEditing,
|
|
2822
|
-
providers,
|
|
2823
|
-
templates,
|
|
2784
|
+
function RecipientFields({
|
|
2785
|
+
form,
|
|
2824
2786
|
fields,
|
|
2825
2787
|
defaults,
|
|
2826
|
-
|
|
2827
|
-
|
|
2788
|
+
update,
|
|
2789
|
+
onRecipientTypeChange,
|
|
2790
|
+
addressErrors,
|
|
2791
|
+
validateAddress,
|
|
2792
|
+
replyToMode,
|
|
2793
|
+
setReplyToMode
|
|
2828
2794
|
}) {
|
|
2829
|
-
const [form, setForm] = useState7(initial);
|
|
2830
|
-
const [replyToMode, setReplyToMode] = useState7(
|
|
2831
|
-
initialReplyToMode(initial.replyTo)
|
|
2832
|
-
);
|
|
2833
|
-
const [addressErrors, setAddressErrors] = useState7({});
|
|
2834
|
-
const update = useCallback6(
|
|
2835
|
-
(key, value) => {
|
|
2836
|
-
setForm((prev) => ({ ...prev, [key]: value }));
|
|
2837
|
-
setAddressErrors(
|
|
2838
|
-
(prev) => key in prev ? { ...prev, [key]: void 0 } : prev
|
|
2839
|
-
);
|
|
2840
|
-
},
|
|
2841
|
-
[]
|
|
2842
|
-
);
|
|
2843
|
-
const handleSave = useCallback6(() => {
|
|
2844
|
-
const errors = {};
|
|
2845
|
-
const invalid = (value) => Boolean(value?.trim()) && !isValidEmail(value);
|
|
2846
|
-
if (invalid(form.senderEmail)) {
|
|
2847
|
-
errors.senderEmail = "Enter a valid email address.";
|
|
2848
|
-
}
|
|
2849
|
-
if (form.recipientType === "static" && invalid(form.to)) {
|
|
2850
|
-
errors.to = "Enter a valid email address.";
|
|
2851
|
-
}
|
|
2852
|
-
if (!parseFieldRef(form.replyTo) && invalid(form.replyTo)) {
|
|
2853
|
-
errors.replyTo = "Enter a valid email address.";
|
|
2854
|
-
}
|
|
2855
|
-
if (Object.values(errors).some(Boolean)) {
|
|
2856
|
-
setAddressErrors(errors);
|
|
2857
|
-
return;
|
|
2858
|
-
}
|
|
2859
|
-
onSave(form);
|
|
2860
|
-
}, [form, onSave]);
|
|
2861
|
-
const defaultProvider = providers.find((p) => p.isDefault);
|
|
2862
|
-
const defaultProviderLabel = defaultProvider ? `System default (${defaultProvider.name})` : "System default";
|
|
2863
2795
|
const toRef = parseFieldRef(form.to);
|
|
2864
|
-
const toFieldOptions = buildEmailFieldOptions(
|
|
2865
|
-
fields,
|
|
2866
|
-
form.recipientType === "field" ? toRef : null
|
|
2867
|
-
);
|
|
2796
|
+
const toFieldOptions = buildEmailFieldOptions(fields, toRef);
|
|
2868
2797
|
const replyToRef = parseFieldRef(form.replyTo);
|
|
2869
2798
|
const replyToFieldOptions = buildEmailFieldOptions(fields, replyToRef);
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
Sheet2,
|
|
2875
|
-
{
|
|
2876
|
-
open: true,
|
|
2877
|
-
onOpenChange: (open) => {
|
|
2878
|
-
if (!open) onClose();
|
|
2879
|
-
},
|
|
2880
|
-
children: /* @__PURE__ */ jsxs7(
|
|
2881
|
-
SheetContent2,
|
|
2799
|
+
return /* @__PURE__ */ jsx8(Fragment3, { children: /* @__PURE__ */ jsxs7("div", { className: "space-y-4 pt-4 border-t border-border", children: [
|
|
2800
|
+
/* @__PURE__ */ jsxs7(Grid, { cols: 2, responsive: true, children: [
|
|
2801
|
+
/* @__PURE__ */ jsx8(FieldShell, { label: "Send to", htmlFor: "notification-recipient-type", children: ({ id, describedBy, invalid }) => /* @__PURE__ */ jsxs7(
|
|
2802
|
+
Select5,
|
|
2882
2803
|
{
|
|
2883
|
-
|
|
2884
|
-
|
|
2804
|
+
value: form.recipientType,
|
|
2805
|
+
onValueChange: (value) => {
|
|
2806
|
+
onRecipientTypeChange(value);
|
|
2807
|
+
},
|
|
2885
2808
|
children: [
|
|
2886
|
-
/* @__PURE__ */
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
{
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
/* @__PURE__ */ jsx8(
|
|
2911
|
-
SelectTrigger5,
|
|
2912
|
-
{
|
|
2913
|
-
id,
|
|
2914
|
-
"aria-describedby": describedBy,
|
|
2915
|
-
"aria-invalid": invalid,
|
|
2916
|
-
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
2917
|
-
children: /* @__PURE__ */ jsx8(SelectValue5, {})
|
|
2918
|
-
}
|
|
2919
|
-
),
|
|
2920
|
-
/* @__PURE__ */ jsxs7(SelectContent5, { children: [
|
|
2921
|
-
/* @__PURE__ */ jsx8(SelectItem5, { value: "__default", children: defaultProviderLabel }),
|
|
2922
|
-
providers.map((p) => /* @__PURE__ */ jsxs7(SelectItem5, { value: p.id, children: [
|
|
2923
|
-
p.name,
|
|
2924
|
-
p.isDefault ? " (Default)" : ""
|
|
2925
|
-
] }, p.id))
|
|
2926
|
-
] })
|
|
2927
|
-
]
|
|
2928
|
-
}
|
|
2929
|
-
) }),
|
|
2930
|
-
/* @__PURE__ */ jsxs7("div", { className: "space-y-1.5", children: [
|
|
2931
|
-
/* @__PURE__ */ jsx8(
|
|
2932
|
-
FieldShell,
|
|
2933
|
-
{
|
|
2934
|
-
label: "Email template",
|
|
2935
|
-
htmlFor: "notification-template",
|
|
2936
|
-
children: ({ id, describedBy, invalid }) => /* @__PURE__ */ jsxs7(
|
|
2937
|
-
Select5,
|
|
2938
|
-
{
|
|
2939
|
-
value: form.templateSlug ?? "__none",
|
|
2940
|
-
onValueChange: (value) => update(
|
|
2941
|
-
"templateSlug",
|
|
2942
|
-
value === "__none" ? void 0 : value
|
|
2943
|
-
),
|
|
2944
|
-
children: [
|
|
2945
|
-
/* @__PURE__ */ jsx8(
|
|
2946
|
-
SelectTrigger5,
|
|
2947
|
-
{
|
|
2948
|
-
id,
|
|
2949
|
-
"aria-describedby": describedBy,
|
|
2950
|
-
"aria-invalid": invalid,
|
|
2951
|
-
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
2952
|
-
children: /* @__PURE__ */ jsx8(SelectValue5, {})
|
|
2953
|
-
}
|
|
2954
|
-
),
|
|
2955
|
-
/* @__PURE__ */ jsxs7(SelectContent5, { children: [
|
|
2956
|
-
/* @__PURE__ */ jsx8(SelectItem5, { value: "__none", children: "Select a template" }),
|
|
2957
|
-
templates.filter((t) => t.isActive && t.kind !== "layout").map((t) => /* @__PURE__ */ jsx8(SelectItem5, { value: t.slug, children: t.name }, t.id))
|
|
2958
|
-
] })
|
|
2959
|
-
]
|
|
2960
|
-
}
|
|
2961
|
-
)
|
|
2962
|
-
}
|
|
2963
|
-
),
|
|
2964
|
-
!form.templateSlug && /* @__PURE__ */ jsxs7("p", { className: "flex items-center gap-1 text-xs text-destructive", children: [
|
|
2965
|
-
/* @__PURE__ */ jsx8(TriangleAlert2, { className: "h-3 w-3", "aria-hidden": "true" }),
|
|
2966
|
-
"Required \u2014 a notification without a template is never sent."
|
|
2967
|
-
] })
|
|
2968
|
-
] })
|
|
2969
|
-
] }),
|
|
2809
|
+
/* @__PURE__ */ jsx8(
|
|
2810
|
+
SelectTrigger5,
|
|
2811
|
+
{
|
|
2812
|
+
id,
|
|
2813
|
+
"aria-describedby": describedBy,
|
|
2814
|
+
"aria-invalid": invalid,
|
|
2815
|
+
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
2816
|
+
children: /* @__PURE__ */ jsx8(SelectValue5, {})
|
|
2817
|
+
}
|
|
2818
|
+
),
|
|
2819
|
+
/* @__PURE__ */ jsxs7(SelectContent5, { children: [
|
|
2820
|
+
/* @__PURE__ */ jsx8(SelectItem5, { value: "static", children: "A specific address" }),
|
|
2821
|
+
/* @__PURE__ */ jsx8(SelectItem5, { value: "field", children: "The visitor (email field)" })
|
|
2822
|
+
] })
|
|
2823
|
+
]
|
|
2824
|
+
}
|
|
2825
|
+
) }),
|
|
2826
|
+
form.recipientType === "field" ? /* @__PURE__ */ jsxs7("div", { className: "space-y-1.5", children: [
|
|
2827
|
+
/* @__PURE__ */ jsx8(FieldShell, { label: "Visitor email field", htmlFor: "notification-to", children: ({ id, describedBy, invalid }) => /* @__PURE__ */ jsxs7(
|
|
2828
|
+
Select5,
|
|
2829
|
+
{
|
|
2830
|
+
value: toRef ?? "__none",
|
|
2831
|
+
onValueChange: (value) => update("to", value === "__none" ? "" : toFieldRef(value)),
|
|
2832
|
+
children: [
|
|
2970
2833
|
/* @__PURE__ */ jsx8(
|
|
2971
|
-
|
|
2834
|
+
SelectTrigger5,
|
|
2972
2835
|
{
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
children: /* @__PURE__ */ jsx8(
|
|
2979
|
-
Input3,
|
|
2980
|
-
{
|
|
2981
|
-
type: "email",
|
|
2982
|
-
value: form.senderEmail ?? "",
|
|
2983
|
-
onChange: (e) => update("senderEmail", e.target.value || void 0),
|
|
2984
|
-
placeholder: senderPlaceholder
|
|
2985
|
-
}
|
|
2986
|
-
)
|
|
2836
|
+
id,
|
|
2837
|
+
"aria-describedby": describedBy,
|
|
2838
|
+
"aria-invalid": invalid,
|
|
2839
|
+
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
2840
|
+
children: /* @__PURE__ */ jsx8(SelectValue5, {})
|
|
2987
2841
|
}
|
|
2988
2842
|
),
|
|
2989
|
-
/* @__PURE__ */ jsxs7(
|
|
2990
|
-
/* @__PURE__ */
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
width: "half",
|
|
3061
|
-
error: addressErrors.to,
|
|
3062
|
-
children: /* @__PURE__ */ jsx8(
|
|
3063
|
-
Input3,
|
|
3064
|
-
{
|
|
3065
|
-
type: "email",
|
|
3066
|
-
value: form.to,
|
|
3067
|
-
onChange: (e) => update("to", e.target.value),
|
|
3068
|
-
placeholder: defaults?.defaultToEmail || "admin@example.com"
|
|
3069
|
-
}
|
|
3070
|
-
)
|
|
3071
|
-
}
|
|
3072
|
-
)
|
|
3073
|
-
] }),
|
|
3074
|
-
/* @__PURE__ */ jsxs7(Grid, { cols: 2, responsive: true, children: [
|
|
3075
|
-
/* @__PURE__ */ jsx8(FieldShell, { label: "Reply-To", htmlFor: "notification-replyto-mode", children: ({ id, describedBy, invalid }) => /* @__PURE__ */ jsxs7(
|
|
3076
|
-
Select5,
|
|
3077
|
-
{
|
|
3078
|
-
value: replyToMode,
|
|
3079
|
-
onValueChange: (value) => {
|
|
3080
|
-
const mode = value;
|
|
3081
|
-
setReplyToMode(mode);
|
|
3082
|
-
update("replyTo", void 0);
|
|
3083
|
-
},
|
|
3084
|
-
children: [
|
|
3085
|
-
/* @__PURE__ */ jsx8(
|
|
3086
|
-
SelectTrigger5,
|
|
3087
|
-
{
|
|
3088
|
-
id,
|
|
3089
|
-
"aria-describedby": describedBy,
|
|
3090
|
-
"aria-invalid": invalid,
|
|
3091
|
-
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
3092
|
-
children: /* @__PURE__ */ jsx8(SelectValue5, {})
|
|
3093
|
-
}
|
|
3094
|
-
),
|
|
3095
|
-
/* @__PURE__ */ jsxs7(SelectContent5, { children: [
|
|
3096
|
-
/* @__PURE__ */ jsx8(SelectItem5, { value: "none", children: "None" }),
|
|
3097
|
-
/* @__PURE__ */ jsx8(SelectItem5, { value: "field", children: "The visitor (email field)" }),
|
|
3098
|
-
/* @__PURE__ */ jsx8(SelectItem5, { value: "custom", children: "A custom address" })
|
|
3099
|
-
] })
|
|
3100
|
-
]
|
|
3101
|
-
}
|
|
3102
|
-
) }),
|
|
3103
|
-
replyToMode === "field" && /* @__PURE__ */ jsx8(
|
|
3104
|
-
FieldShell,
|
|
3105
|
-
{
|
|
3106
|
-
label: "Visitor email field",
|
|
3107
|
-
htmlFor: "notification-replyto",
|
|
3108
|
-
children: ({ id, describedBy, invalid }) => /* @__PURE__ */ jsxs7(
|
|
3109
|
-
Select5,
|
|
3110
|
-
{
|
|
3111
|
-
value: replyToRef ?? "__none",
|
|
3112
|
-
onValueChange: (value) => update(
|
|
3113
|
-
"replyTo",
|
|
3114
|
-
value === "__none" ? void 0 : toFieldRef(value)
|
|
3115
|
-
),
|
|
3116
|
-
children: [
|
|
3117
|
-
/* @__PURE__ */ jsx8(
|
|
3118
|
-
SelectTrigger5,
|
|
3119
|
-
{
|
|
3120
|
-
id,
|
|
3121
|
-
"aria-describedby": describedBy,
|
|
3122
|
-
"aria-invalid": invalid,
|
|
3123
|
-
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
3124
|
-
children: /* @__PURE__ */ jsx8(SelectValue5, {})
|
|
3125
|
-
}
|
|
3126
|
-
),
|
|
3127
|
-
/* @__PURE__ */ jsxs7(SelectContent5, { children: [
|
|
3128
|
-
/* @__PURE__ */ jsx8(SelectItem5, { value: "__none", children: "Select a field" }),
|
|
3129
|
-
replyToFieldOptions.map((f) => /* @__PURE__ */ jsx8(SelectItem5, { value: f.name, children: f.label }, f.name))
|
|
3130
|
-
] })
|
|
3131
|
-
]
|
|
3132
|
-
}
|
|
3133
|
-
)
|
|
3134
|
-
}
|
|
3135
|
-
),
|
|
3136
|
-
replyToMode === "custom" && /* @__PURE__ */ jsx8(
|
|
3137
|
-
FieldShell,
|
|
3138
|
-
{
|
|
3139
|
-
label: "Reply-To address",
|
|
3140
|
-
htmlFor: "notification-replyto",
|
|
3141
|
-
width: "half",
|
|
3142
|
-
error: addressErrors.replyTo,
|
|
3143
|
-
children: /* @__PURE__ */ jsx8(
|
|
3144
|
-
Input3,
|
|
3145
|
-
{
|
|
3146
|
-
type: "email",
|
|
3147
|
-
value: form.replyTo ?? "",
|
|
3148
|
-
onChange: (e) => update("replyTo", e.target.value || void 0),
|
|
3149
|
-
placeholder: "replies@example.com"
|
|
3150
|
-
}
|
|
3151
|
-
)
|
|
3152
|
-
}
|
|
3153
|
-
)
|
|
3154
|
-
] }),
|
|
3155
|
-
replyToMode === "field" && /* @__PURE__ */ jsx8("p", { className: "text-xs text-muted-foreground -mt-2", children: "Replying to this email answers the person who submitted the form." }),
|
|
2843
|
+
/* @__PURE__ */ jsxs7(SelectContent5, { children: [
|
|
2844
|
+
/* @__PURE__ */ jsx8(SelectItem5, { value: "__none", children: "Select a field" }),
|
|
2845
|
+
toFieldOptions.map((f) => /* @__PURE__ */ jsx8(SelectItem5, { value: f.name, children: f.label }, f.name))
|
|
2846
|
+
] })
|
|
2847
|
+
]
|
|
2848
|
+
}
|
|
2849
|
+
) }),
|
|
2850
|
+
toFieldOptions.length === 0 && /* @__PURE__ */ jsx8("p", { className: "text-xs text-muted-foreground", children: "Add an email field to the form first." })
|
|
2851
|
+
] }) : /* @__PURE__ */ jsx8(
|
|
2852
|
+
FieldShell,
|
|
2853
|
+
{
|
|
2854
|
+
label: "Recipient address",
|
|
2855
|
+
htmlFor: "notification-to",
|
|
2856
|
+
width: "half",
|
|
2857
|
+
error: addressErrors.to,
|
|
2858
|
+
children: /* @__PURE__ */ jsx8(
|
|
2859
|
+
Input3,
|
|
2860
|
+
{
|
|
2861
|
+
type: "email",
|
|
2862
|
+
value: form.to,
|
|
2863
|
+
onChange: (e) => update("to", e.target.value),
|
|
2864
|
+
onBlur: (e) => validateAddress("to", e.target.value),
|
|
2865
|
+
placeholder: defaults?.defaultToEmail || "admin@example.com"
|
|
2866
|
+
}
|
|
2867
|
+
)
|
|
2868
|
+
}
|
|
2869
|
+
)
|
|
2870
|
+
] }),
|
|
2871
|
+
/* @__PURE__ */ jsxs7(Grid, { cols: 2, responsive: true, children: [
|
|
2872
|
+
/* @__PURE__ */ jsx8(FieldShell, { label: "Reply-To", htmlFor: "notification-replyto-mode", children: ({ id, describedBy, invalid }) => /* @__PURE__ */ jsxs7(
|
|
2873
|
+
Select5,
|
|
2874
|
+
{
|
|
2875
|
+
value: replyToMode,
|
|
2876
|
+
onValueChange: (value) => {
|
|
2877
|
+
const mode = value;
|
|
2878
|
+
setReplyToMode(mode);
|
|
2879
|
+
update("replyTo", void 0);
|
|
2880
|
+
},
|
|
2881
|
+
children: [
|
|
2882
|
+
/* @__PURE__ */ jsx8(
|
|
2883
|
+
SelectTrigger5,
|
|
2884
|
+
{
|
|
2885
|
+
id,
|
|
2886
|
+
"aria-describedby": describedBy,
|
|
2887
|
+
"aria-invalid": invalid,
|
|
2888
|
+
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
2889
|
+
children: /* @__PURE__ */ jsx8(SelectValue5, {})
|
|
2890
|
+
}
|
|
2891
|
+
),
|
|
2892
|
+
/* @__PURE__ */ jsxs7(SelectContent5, { children: [
|
|
2893
|
+
/* @__PURE__ */ jsx8(SelectItem5, { value: "none", children: "None" }),
|
|
2894
|
+
/* @__PURE__ */ jsx8(SelectItem5, { value: "field", children: "The visitor (email field)" }),
|
|
2895
|
+
/* @__PURE__ */ jsx8(SelectItem5, { value: "custom", children: "A custom address" })
|
|
2896
|
+
] })
|
|
2897
|
+
]
|
|
2898
|
+
}
|
|
2899
|
+
) }),
|
|
2900
|
+
replyToMode === "field" && /* @__PURE__ */ jsx8(
|
|
2901
|
+
FieldShell,
|
|
2902
|
+
{
|
|
2903
|
+
label: "Visitor email field",
|
|
2904
|
+
htmlFor: "notification-replyto",
|
|
2905
|
+
children: ({ id, describedBy, invalid }) => /* @__PURE__ */ jsxs7(
|
|
2906
|
+
Select5,
|
|
2907
|
+
{
|
|
2908
|
+
value: replyToRef ?? "__none",
|
|
2909
|
+
onValueChange: (value) => update(
|
|
2910
|
+
"replyTo",
|
|
2911
|
+
value === "__none" ? void 0 : toFieldRef(value)
|
|
2912
|
+
),
|
|
2913
|
+
children: [
|
|
3156
2914
|
/* @__PURE__ */ jsx8(
|
|
3157
|
-
|
|
2915
|
+
SelectTrigger5,
|
|
3158
2916
|
{
|
|
3159
|
-
id
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
2917
|
+
id,
|
|
2918
|
+
"aria-describedby": describedBy,
|
|
2919
|
+
"aria-invalid": invalid,
|
|
2920
|
+
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
2921
|
+
children: /* @__PURE__ */ jsx8(SelectValue5, {})
|
|
3164
2922
|
}
|
|
3165
2923
|
),
|
|
2924
|
+
/* @__PURE__ */ jsxs7(SelectContent5, { children: [
|
|
2925
|
+
/* @__PURE__ */ jsx8(SelectItem5, { value: "__none", children: "Select a field" }),
|
|
2926
|
+
replyToFieldOptions.map((f) => /* @__PURE__ */ jsx8(SelectItem5, { value: f.name, children: f.label }, f.name))
|
|
2927
|
+
] })
|
|
2928
|
+
]
|
|
2929
|
+
}
|
|
2930
|
+
)
|
|
2931
|
+
}
|
|
2932
|
+
),
|
|
2933
|
+
replyToMode === "custom" && /* @__PURE__ */ jsx8(
|
|
2934
|
+
FieldShell,
|
|
2935
|
+
{
|
|
2936
|
+
label: "Reply-To address",
|
|
2937
|
+
htmlFor: "notification-replyto",
|
|
2938
|
+
width: "half",
|
|
2939
|
+
error: addressErrors.replyTo,
|
|
2940
|
+
children: /* @__PURE__ */ jsx8(
|
|
2941
|
+
Input3,
|
|
2942
|
+
{
|
|
2943
|
+
type: "email",
|
|
2944
|
+
value: form.replyTo ?? "",
|
|
2945
|
+
onBlur: (e) => validateAddress("replyTo", e.target.value),
|
|
2946
|
+
onChange: (e) => update("replyTo", e.target.value || void 0),
|
|
2947
|
+
placeholder: "replies@example.com"
|
|
2948
|
+
}
|
|
2949
|
+
)
|
|
2950
|
+
}
|
|
2951
|
+
)
|
|
2952
|
+
] }),
|
|
2953
|
+
replyToMode === "field" && /* @__PURE__ */ jsx8("p", { className: "text-xs text-muted-foreground -mt-2", children: "Replying to this email answers the person who submitted the form." }),
|
|
2954
|
+
/* @__PURE__ */ jsx8(
|
|
2955
|
+
AddressChipList,
|
|
2956
|
+
{
|
|
2957
|
+
id: "notification-cc",
|
|
2958
|
+
label: "CC (optional)",
|
|
2959
|
+
addresses: form.cc,
|
|
2960
|
+
placeholder: "cc@example.com",
|
|
2961
|
+
onChange: (cc) => update("cc", cc)
|
|
2962
|
+
}
|
|
2963
|
+
),
|
|
2964
|
+
/* @__PURE__ */ jsx8(
|
|
2965
|
+
AddressChipList,
|
|
2966
|
+
{
|
|
2967
|
+
id: "notification-bcc",
|
|
2968
|
+
label: "BCC (optional)",
|
|
2969
|
+
addresses: form.bcc,
|
|
2970
|
+
placeholder: "bcc@example.com",
|
|
2971
|
+
onChange: (bcc) => update("bcc", bcc)
|
|
2972
|
+
}
|
|
2973
|
+
)
|
|
2974
|
+
] }) });
|
|
2975
|
+
}
|
|
2976
|
+
function SendConditionField({
|
|
2977
|
+
condition,
|
|
2978
|
+
fields,
|
|
2979
|
+
update
|
|
2980
|
+
}) {
|
|
2981
|
+
return /* @__PURE__ */ jsx8(Fragment3, { children: /* @__PURE__ */ jsxs7("div", { className: "space-y-3 pt-4 border-t border-border", children: [
|
|
2982
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between", children: [
|
|
2983
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
2984
|
+
/* @__PURE__ */ jsx8("p", { className: "text-sm font-medium text-foreground", children: "Send condition" }),
|
|
2985
|
+
/* @__PURE__ */ jsx8("p", { className: "text-xs text-muted-foreground", children: "Only send this notification when a submitted value matches." })
|
|
2986
|
+
] }),
|
|
2987
|
+
!condition && /* @__PURE__ */ jsxs7(
|
|
2988
|
+
Button6,
|
|
2989
|
+
{
|
|
2990
|
+
type: "button",
|
|
2991
|
+
variant: "outline",
|
|
2992
|
+
size: "sm",
|
|
2993
|
+
onClick: () => update("condition", {
|
|
2994
|
+
field: fields[0]?.name ?? "",
|
|
2995
|
+
comparison: "equals",
|
|
2996
|
+
value: ""
|
|
2997
|
+
}),
|
|
2998
|
+
disabled: fields.length === 0,
|
|
2999
|
+
children: [
|
|
3000
|
+
/* @__PURE__ */ jsx8(Plus2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
3001
|
+
"Add condition"
|
|
3002
|
+
]
|
|
3003
|
+
}
|
|
3004
|
+
)
|
|
3005
|
+
] }),
|
|
3006
|
+
condition && /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-end gap-2 border border-border bg-muted/40 p-3", children: [
|
|
3007
|
+
/* @__PURE__ */ jsx8(
|
|
3008
|
+
FieldShell,
|
|
3009
|
+
{
|
|
3010
|
+
label: "Field",
|
|
3011
|
+
htmlFor: "notification-condition-field",
|
|
3012
|
+
className: "min-w-36 flex-1",
|
|
3013
|
+
children: ({ id, describedBy, invalid }) => /* @__PURE__ */ jsxs7(
|
|
3014
|
+
Select5,
|
|
3015
|
+
{
|
|
3016
|
+
value: condition.field || "__none",
|
|
3017
|
+
onValueChange: (value) => update("condition", {
|
|
3018
|
+
...condition,
|
|
3019
|
+
field: value === "__none" ? "" : value
|
|
3020
|
+
}),
|
|
3021
|
+
children: [
|
|
3166
3022
|
/* @__PURE__ */ jsx8(
|
|
3167
|
-
|
|
3023
|
+
SelectTrigger5,
|
|
3168
3024
|
{
|
|
3169
|
-
id
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3025
|
+
id,
|
|
3026
|
+
"aria-describedby": describedBy,
|
|
3027
|
+
"aria-invalid": invalid,
|
|
3028
|
+
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
3029
|
+
children: /* @__PURE__ */ jsx8(SelectValue5, {})
|
|
3174
3030
|
}
|
|
3175
|
-
)
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
/* @__PURE__ */ jsxs7("div", { children: [
|
|
3180
|
-
/* @__PURE__ */ jsx8("p", { className: "text-sm font-medium text-foreground", children: "Send condition" }),
|
|
3181
|
-
/* @__PURE__ */ jsx8("p", { className: "text-xs text-muted-foreground", children: "Only send this notification when a submitted value matches." })
|
|
3182
|
-
] }),
|
|
3183
|
-
!condition && /* @__PURE__ */ jsxs7(
|
|
3184
|
-
Button6,
|
|
3185
|
-
{
|
|
3186
|
-
type: "button",
|
|
3187
|
-
variant: "outline",
|
|
3188
|
-
size: "sm",
|
|
3189
|
-
onClick: () => update("condition", {
|
|
3190
|
-
field: fields[0]?.name ?? "",
|
|
3191
|
-
comparison: "equals",
|
|
3192
|
-
value: ""
|
|
3193
|
-
}),
|
|
3194
|
-
disabled: fields.length === 0,
|
|
3195
|
-
children: [
|
|
3196
|
-
/* @__PURE__ */ jsx8(Plus2, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
3197
|
-
"Add condition"
|
|
3198
|
-
]
|
|
3199
|
-
}
|
|
3200
|
-
)
|
|
3201
|
-
] }),
|
|
3202
|
-
condition && /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-end gap-2 border border-border bg-muted/40 p-3", children: [
|
|
3203
|
-
/* @__PURE__ */ jsx8(
|
|
3204
|
-
FieldShell,
|
|
3205
|
-
{
|
|
3206
|
-
label: "Field",
|
|
3207
|
-
htmlFor: "notification-condition-field",
|
|
3208
|
-
className: "min-w-36 flex-1",
|
|
3209
|
-
children: ({ id, describedBy, invalid }) => /* @__PURE__ */ jsxs7(
|
|
3210
|
-
Select5,
|
|
3211
|
-
{
|
|
3212
|
-
value: condition.field || "__none",
|
|
3213
|
-
onValueChange: (value) => update("condition", {
|
|
3214
|
-
...condition,
|
|
3215
|
-
field: value === "__none" ? "" : value
|
|
3216
|
-
}),
|
|
3217
|
-
children: [
|
|
3218
|
-
/* @__PURE__ */ jsx8(
|
|
3219
|
-
SelectTrigger5,
|
|
3220
|
-
{
|
|
3221
|
-
id,
|
|
3222
|
-
"aria-describedby": describedBy,
|
|
3223
|
-
"aria-invalid": invalid,
|
|
3224
|
-
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
3225
|
-
children: /* @__PURE__ */ jsx8(SelectValue5, {})
|
|
3226
|
-
}
|
|
3227
|
-
),
|
|
3228
|
-
/* @__PURE__ */ jsxs7(SelectContent5, { children: [
|
|
3229
|
-
/* @__PURE__ */ jsx8(SelectItem5, { value: "__none", children: "Select a field" }),
|
|
3230
|
-
fields.map((f) => /* @__PURE__ */ jsx8(SelectItem5, { value: f.name, children: f.label }, f.name))
|
|
3231
|
-
] })
|
|
3232
|
-
]
|
|
3233
|
-
}
|
|
3234
|
-
)
|
|
3235
|
-
}
|
|
3236
|
-
),
|
|
3237
|
-
/* @__PURE__ */ jsx8(
|
|
3238
|
-
FieldShell,
|
|
3239
|
-
{
|
|
3240
|
-
label: "Comparison",
|
|
3241
|
-
htmlFor: "notification-condition-comparison",
|
|
3242
|
-
className: "min-w-36 flex-1",
|
|
3243
|
-
children: ({ id, describedBy, invalid }) => /* @__PURE__ */ jsxs7(
|
|
3244
|
-
Select5,
|
|
3245
|
-
{
|
|
3246
|
-
value: condition.comparison,
|
|
3247
|
-
onValueChange: (value) => update("condition", {
|
|
3248
|
-
...condition,
|
|
3249
|
-
comparison: value
|
|
3250
|
-
}),
|
|
3251
|
-
children: [
|
|
3252
|
-
/* @__PURE__ */ jsx8(
|
|
3253
|
-
SelectTrigger5,
|
|
3254
|
-
{
|
|
3255
|
-
id,
|
|
3256
|
-
"aria-describedby": describedBy,
|
|
3257
|
-
"aria-invalid": invalid,
|
|
3258
|
-
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
3259
|
-
children: /* @__PURE__ */ jsx8(SelectValue5, {})
|
|
3260
|
-
}
|
|
3261
|
-
),
|
|
3262
|
-
/* @__PURE__ */ jsx8(SelectContent5, { children: Object.entries(COMPARISON_LABELS).map(
|
|
3263
|
-
([value, label]) => /* @__PURE__ */ jsx8(SelectItem5, { value, children: label }, value)
|
|
3264
|
-
) })
|
|
3265
|
-
]
|
|
3266
|
-
}
|
|
3267
|
-
)
|
|
3268
|
-
}
|
|
3269
|
-
),
|
|
3270
|
-
!VALUELESS_COMPARISONS.has(condition.comparison) && /* @__PURE__ */ jsx8(
|
|
3271
|
-
FieldShell,
|
|
3272
|
-
{
|
|
3273
|
-
label: "Value",
|
|
3274
|
-
htmlFor: "notification-condition-value",
|
|
3275
|
-
className: "min-w-36 flex-1",
|
|
3276
|
-
children: /* @__PURE__ */ jsx8(
|
|
3277
|
-
Input3,
|
|
3278
|
-
{
|
|
3279
|
-
type: "text",
|
|
3280
|
-
value: typeof condition.value === "string" || typeof condition.value === "number" ? String(condition.value) : "",
|
|
3281
|
-
onChange: (e) => update("condition", {
|
|
3282
|
-
...condition,
|
|
3283
|
-
value: e.target.value
|
|
3284
|
-
})
|
|
3285
|
-
}
|
|
3286
|
-
)
|
|
3287
|
-
}
|
|
3288
|
-
),
|
|
3289
|
-
/* @__PURE__ */ jsx8(
|
|
3290
|
-
Button6,
|
|
3291
|
-
{
|
|
3292
|
-
type: "button",
|
|
3293
|
-
variant: "ghost",
|
|
3294
|
-
size: "icon",
|
|
3295
|
-
className: "h-9 w-9 rounded-none text-muted-foreground hover:text-destructive",
|
|
3296
|
-
onClick: () => update("condition", void 0),
|
|
3297
|
-
"aria-label": "Remove send condition",
|
|
3298
|
-
children: /* @__PURE__ */ jsx8(Trash22, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
3299
|
-
}
|
|
3300
|
-
)
|
|
3031
|
+
),
|
|
3032
|
+
/* @__PURE__ */ jsxs7(SelectContent5, { children: [
|
|
3033
|
+
/* @__PURE__ */ jsx8(SelectItem5, { value: "__none", children: "Select a field" }),
|
|
3034
|
+
fields.map((f) => /* @__PURE__ */ jsx8(SelectItem5, { value: f.name, children: f.label }, f.name))
|
|
3301
3035
|
] })
|
|
3302
|
-
]
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3036
|
+
]
|
|
3037
|
+
}
|
|
3038
|
+
)
|
|
3039
|
+
}
|
|
3040
|
+
),
|
|
3041
|
+
/* @__PURE__ */ jsx8(
|
|
3042
|
+
FieldShell,
|
|
3043
|
+
{
|
|
3044
|
+
label: "Comparison",
|
|
3045
|
+
htmlFor: "notification-condition-comparison",
|
|
3046
|
+
className: "min-w-36 flex-1",
|
|
3047
|
+
children: ({ id, describedBy, invalid }) => /* @__PURE__ */ jsxs7(
|
|
3048
|
+
Select5,
|
|
3049
|
+
{
|
|
3050
|
+
value: condition.comparison,
|
|
3051
|
+
onValueChange: (value) => update("condition", {
|
|
3052
|
+
...condition,
|
|
3053
|
+
comparison: value
|
|
3054
|
+
}),
|
|
3055
|
+
children: [
|
|
3308
3056
|
/* @__PURE__ */ jsx8(
|
|
3309
|
-
|
|
3057
|
+
SelectTrigger5,
|
|
3310
3058
|
{
|
|
3311
|
-
id
|
|
3312
|
-
|
|
3313
|
-
|
|
3059
|
+
id,
|
|
3060
|
+
"aria-describedby": describedBy,
|
|
3061
|
+
"aria-invalid": invalid,
|
|
3062
|
+
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
3063
|
+
children: /* @__PURE__ */ jsx8(SelectValue5, {})
|
|
3314
3064
|
}
|
|
3315
|
-
)
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3065
|
+
),
|
|
3066
|
+
/* @__PURE__ */ jsx8(SelectContent5, { children: Object.entries(COMPARISON_LABELS).map(([value, label]) => /* @__PURE__ */ jsx8(SelectItem5, { value, children: label }, value)) })
|
|
3067
|
+
]
|
|
3068
|
+
}
|
|
3069
|
+
)
|
|
3070
|
+
}
|
|
3071
|
+
),
|
|
3072
|
+
!VALUELESS_COMPARISONS.has(condition.comparison) && /* @__PURE__ */ jsx8(
|
|
3073
|
+
FieldShell,
|
|
3074
|
+
{
|
|
3075
|
+
label: "Value",
|
|
3076
|
+
htmlFor: "notification-condition-value",
|
|
3077
|
+
className: "min-w-36 flex-1",
|
|
3078
|
+
children: /* @__PURE__ */ jsx8(
|
|
3079
|
+
Input3,
|
|
3080
|
+
{
|
|
3081
|
+
type: "text",
|
|
3082
|
+
value: typeof condition.value === "string" || typeof condition.value === "number" ? String(condition.value) : "",
|
|
3083
|
+
onChange: (e) => update("condition", {
|
|
3084
|
+
...condition,
|
|
3085
|
+
value: e.target.value
|
|
3086
|
+
})
|
|
3087
|
+
}
|
|
3088
|
+
)
|
|
3089
|
+
}
|
|
3090
|
+
),
|
|
3091
|
+
/* @__PURE__ */ jsx8(
|
|
3092
|
+
Button6,
|
|
3093
|
+
{
|
|
3094
|
+
type: "button",
|
|
3095
|
+
variant: "ghost",
|
|
3096
|
+
size: "icon",
|
|
3097
|
+
className: "h-9 w-9 rounded-none text-muted-foreground hover:text-destructive",
|
|
3098
|
+
onClick: () => update("condition", void 0),
|
|
3099
|
+
"aria-label": "Remove send condition",
|
|
3100
|
+
children: /* @__PURE__ */ jsx8(Trash22, { className: "h-4 w-4", "aria-hidden": "true" })
|
|
3331
3101
|
}
|
|
3332
3102
|
)
|
|
3333
|
-
}
|
|
3334
|
-
);
|
|
3103
|
+
] })
|
|
3104
|
+
] }) });
|
|
3335
3105
|
}
|
|
3336
|
-
function
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
const
|
|
3346
|
-
const
|
|
3347
|
-
const
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
setSheetState({ open: true, editing: null });
|
|
3358
|
-
}, []);
|
|
3359
|
-
const openEditSheet = useCallback6((notification) => {
|
|
3360
|
-
setSheetState({ open: true, editing: notification });
|
|
3361
|
-
}, []);
|
|
3362
|
-
const closeSheet = useCallback6(() => {
|
|
3363
|
-
setSheetState({ open: false, editing: null });
|
|
3364
|
-
}, []);
|
|
3365
|
-
const handleSave = useCallback6(
|
|
3366
|
-
(notification) => {
|
|
3367
|
-
if (sheetState.editing) {
|
|
3368
|
-
updateNotification(notification.id, notification);
|
|
3369
|
-
} else {
|
|
3370
|
-
addNotification(notification);
|
|
3371
|
-
}
|
|
3372
|
-
closeSheet();
|
|
3373
|
-
},
|
|
3374
|
-
[sheetState.editing, addNotification, updateNotification, closeSheet]
|
|
3375
|
-
);
|
|
3376
|
-
const getProviderName = useCallback6(
|
|
3377
|
-
(providerId) => {
|
|
3378
|
-
if (!providerId) {
|
|
3379
|
-
const def = providers.find((p) => p.isDefault);
|
|
3380
|
-
return def ? `${def.name} (default)` : "System default";
|
|
3106
|
+
function NotificationIdentityFields({
|
|
3107
|
+
form,
|
|
3108
|
+
providers,
|
|
3109
|
+
defaults,
|
|
3110
|
+
templates,
|
|
3111
|
+
update,
|
|
3112
|
+
addressErrors,
|
|
3113
|
+
validateAddress
|
|
3114
|
+
}) {
|
|
3115
|
+
const defaultProvider = providers.find((p) => p.isDefault);
|
|
3116
|
+
const defaultProviderLabel = defaultProvider ? `System default (${defaultProvider.name})` : "System default";
|
|
3117
|
+
const senderPlaceholder = defaults?.defaultFrom || "Provider default";
|
|
3118
|
+
const senderHelp = defaults?.defaultFrom ? `Leave blank to send from ${defaults.defaultFrom} (the configured default).` : "Leave blank to use the template or provider default address.";
|
|
3119
|
+
return /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
3120
|
+
/* @__PURE__ */ jsx8(FieldShell, { label: "Name", htmlFor: "notification-name", width: "half", children: /* @__PURE__ */ jsx8(
|
|
3121
|
+
Input3,
|
|
3122
|
+
{
|
|
3123
|
+
type: "text",
|
|
3124
|
+
value: form.name,
|
|
3125
|
+
onChange: (e) => update("name", e.target.value),
|
|
3126
|
+
placeholder: "e.g. Admin notification"
|
|
3381
3127
|
}
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3128
|
+
) }),
|
|
3129
|
+
/* @__PURE__ */ jsxs7(Grid, { cols: 2, responsive: true, children: [
|
|
3130
|
+
/* @__PURE__ */ jsx8(FieldShell, { label: "Email provider", htmlFor: "notification-provider", children: ({ id, describedBy, invalid }) => /* @__PURE__ */ jsxs7(
|
|
3131
|
+
Select5,
|
|
3132
|
+
{
|
|
3133
|
+
value: form.providerId ?? "__default",
|
|
3134
|
+
onValueChange: (value) => update("providerId", value === "__default" ? void 0 : value),
|
|
3135
|
+
children: [
|
|
3136
|
+
/* @__PURE__ */ jsx8(
|
|
3137
|
+
SelectTrigger5,
|
|
3138
|
+
{
|
|
3139
|
+
id,
|
|
3140
|
+
"aria-describedby": describedBy,
|
|
3141
|
+
"aria-invalid": invalid,
|
|
3142
|
+
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
3143
|
+
children: /* @__PURE__ */ jsx8(SelectValue5, {})
|
|
3144
|
+
}
|
|
3145
|
+
),
|
|
3146
|
+
/* @__PURE__ */ jsxs7(SelectContent5, { children: [
|
|
3147
|
+
/* @__PURE__ */ jsx8(SelectItem5, { value: "__default", children: defaultProviderLabel }),
|
|
3148
|
+
providers.map((p) => /* @__PURE__ */ jsxs7(SelectItem5, { value: p.id, children: [
|
|
3149
|
+
p.name,
|
|
3150
|
+
p.isDefault ? " (Default)" : ""
|
|
3151
|
+
] }, p.id))
|
|
3152
|
+
] })
|
|
3153
|
+
]
|
|
3154
|
+
}
|
|
3155
|
+
) }),
|
|
3156
|
+
/* @__PURE__ */ jsxs7("div", { className: "space-y-1.5", children: [
|
|
3157
|
+
/* @__PURE__ */ jsx8(FieldShell, { label: "Email template", htmlFor: "notification-template", children: ({ id, describedBy, invalid }) => /* @__PURE__ */ jsxs7(
|
|
3158
|
+
Select5,
|
|
3159
|
+
{
|
|
3160
|
+
value: form.templateSlug ?? "__none",
|
|
3161
|
+
onValueChange: (value) => update("templateSlug", value === "__none" ? void 0 : value),
|
|
3162
|
+
children: [
|
|
3163
|
+
/* @__PURE__ */ jsx8(
|
|
3164
|
+
SelectTrigger5,
|
|
3165
|
+
{
|
|
3166
|
+
id,
|
|
3167
|
+
"aria-describedby": describedBy,
|
|
3168
|
+
"aria-invalid": invalid,
|
|
3169
|
+
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
3170
|
+
children: /* @__PURE__ */ jsx8(SelectValue5, {})
|
|
3171
|
+
}
|
|
3172
|
+
),
|
|
3173
|
+
/* @__PURE__ */ jsxs7(SelectContent5, { children: [
|
|
3174
|
+
/* @__PURE__ */ jsx8(SelectItem5, { value: "__none", children: "Select a template" }),
|
|
3175
|
+
templates.filter((t) => t.isActive && t.kind !== "layout").map((t) => /* @__PURE__ */ jsx8(SelectItem5, { value: t.slug, children: t.name }, t.id))
|
|
3176
|
+
] })
|
|
3177
|
+
]
|
|
3178
|
+
}
|
|
3179
|
+
) }),
|
|
3180
|
+
!form.templateSlug && /* @__PURE__ */ jsxs7("p", { className: "flex items-center gap-1 text-xs text-destructive", children: [
|
|
3181
|
+
/* @__PURE__ */ jsx8(TriangleAlert2, { className: "h-3 w-3", "aria-hidden": "true" }),
|
|
3182
|
+
"Required \u2014 a notification without a template is never sent."
|
|
3183
|
+
] })
|
|
3184
|
+
] })
|
|
3185
|
+
] }),
|
|
3186
|
+
/* @__PURE__ */ jsx8(
|
|
3187
|
+
FieldShell,
|
|
3188
|
+
{
|
|
3189
|
+
label: "Sender email",
|
|
3190
|
+
htmlFor: "notification-sender",
|
|
3191
|
+
width: "half",
|
|
3192
|
+
description: addressErrors.senderEmail ? void 0 : senderHelp,
|
|
3193
|
+
error: addressErrors.senderEmail,
|
|
3194
|
+
children: /* @__PURE__ */ jsx8(
|
|
3195
|
+
Input3,
|
|
3196
|
+
{
|
|
3197
|
+
type: "email",
|
|
3198
|
+
value: form.senderEmail ?? "",
|
|
3199
|
+
onChange: (e) => update("senderEmail", e.target.value || void 0),
|
|
3200
|
+
onBlur: (e) => validateAddress("senderEmail", e.target.value),
|
|
3201
|
+
placeholder: senderPlaceholder
|
|
3202
|
+
}
|
|
3203
|
+
)
|
|
3204
|
+
}
|
|
3205
|
+
)
|
|
3206
|
+
] });
|
|
3207
|
+
}
|
|
3208
|
+
function initialReplyToMode(replyTo) {
|
|
3209
|
+
if (!replyTo) return "none";
|
|
3210
|
+
return parseFieldRef(replyTo) ? "field" : "custom";
|
|
3211
|
+
}
|
|
3212
|
+
function NotificationEditor({
|
|
3213
|
+
notification,
|
|
3214
|
+
editorId,
|
|
3215
|
+
providers,
|
|
3216
|
+
templates,
|
|
3217
|
+
fields,
|
|
3218
|
+
defaults,
|
|
3219
|
+
onChange
|
|
3220
|
+
}) {
|
|
3221
|
+
const form = notification;
|
|
3222
|
+
const [replyToMode, setReplyToMode] = useState7(
|
|
3223
|
+
initialReplyToMode(notification.replyTo)
|
|
3224
|
+
);
|
|
3225
|
+
const [addressErrors, setAddressErrors] = useState7(
|
|
3226
|
+
() => addressErrorsIn(notification)
|
|
3227
|
+
);
|
|
3228
|
+
const update = useCallback6(
|
|
3229
|
+
(key, value) => {
|
|
3230
|
+
onChange({ ...notification, [key]: value });
|
|
3231
|
+
setAddressErrors(
|
|
3232
|
+
(prev) => key in prev ? { ...prev, [key]: void 0 } : prev
|
|
3233
|
+
);
|
|
3234
|
+
},
|
|
3235
|
+
[notification, onChange]
|
|
3236
|
+
);
|
|
3237
|
+
const changeRecipientType = useCallback6(
|
|
3238
|
+
(kind) => {
|
|
3239
|
+
onChange({ ...notification, recipientType: kind, to: "" });
|
|
3240
|
+
},
|
|
3241
|
+
[notification, onChange]
|
|
3242
|
+
);
|
|
3243
|
+
const validateAddress = useCallback6(
|
|
3244
|
+
(key, value) => {
|
|
3245
|
+
setAddressErrors((prev) => ({ ...prev, [key]: addressError(key, value) }));
|
|
3246
|
+
},
|
|
3247
|
+
[]
|
|
3248
|
+
);
|
|
3249
|
+
const condition = form.condition;
|
|
3250
|
+
return /* @__PURE__ */ jsx8("div", { id: editorId, className: "border-t border-border bg-muted/20 px-4 py-5", children: /* @__PURE__ */ jsxs7("div", { className: "space-y-6", children: [
|
|
3251
|
+
/* @__PURE__ */ jsx8(
|
|
3252
|
+
NotificationIdentityFields,
|
|
3253
|
+
{
|
|
3254
|
+
form,
|
|
3255
|
+
providers,
|
|
3256
|
+
defaults,
|
|
3257
|
+
templates,
|
|
3258
|
+
update,
|
|
3259
|
+
addressErrors,
|
|
3260
|
+
validateAddress
|
|
3261
|
+
}
|
|
3262
|
+
),
|
|
3263
|
+
/* @__PURE__ */ jsx8(
|
|
3264
|
+
RecipientFields,
|
|
3265
|
+
{
|
|
3266
|
+
form,
|
|
3267
|
+
fields,
|
|
3268
|
+
defaults,
|
|
3269
|
+
update,
|
|
3270
|
+
onRecipientTypeChange: changeRecipientType,
|
|
3271
|
+
addressErrors,
|
|
3272
|
+
validateAddress,
|
|
3273
|
+
replyToMode,
|
|
3274
|
+
setReplyToMode
|
|
3275
|
+
}
|
|
3276
|
+
),
|
|
3277
|
+
/* @__PURE__ */ jsx8(
|
|
3278
|
+
SendConditionField,
|
|
3279
|
+
{
|
|
3280
|
+
condition,
|
|
3281
|
+
fields,
|
|
3282
|
+
update
|
|
3283
|
+
}
|
|
3284
|
+
),
|
|
3285
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between pt-4 border-t border-border", children: [
|
|
3286
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
3287
|
+
/* @__PURE__ */ jsx8(Label2, { htmlFor: "notification-enabled", children: "Enabled" }),
|
|
3288
|
+
/* @__PURE__ */ jsx8("p", { className: "text-xs text-muted-foreground", children: "Turn off to keep the rule without sending." })
|
|
3289
|
+
] }),
|
|
3290
|
+
/* @__PURE__ */ jsx8(
|
|
3291
|
+
Switch2,
|
|
3292
|
+
{
|
|
3293
|
+
id: "notification-enabled",
|
|
3294
|
+
checked: form.enabled,
|
|
3295
|
+
onCheckedChange: (checked) => update("enabled", checked)
|
|
3296
|
+
}
|
|
3297
|
+
)
|
|
3298
|
+
] })
|
|
3299
|
+
] }) });
|
|
3300
|
+
}
|
|
3301
|
+
function FormNotificationsTab({ defaults }) {
|
|
3302
|
+
const {
|
|
3303
|
+
notifications,
|
|
3304
|
+
fields,
|
|
3305
|
+
addNotification,
|
|
3306
|
+
duplicateNotification,
|
|
3307
|
+
updateNotification,
|
|
3308
|
+
deleteNotification
|
|
3309
|
+
} = useFormBuilder();
|
|
3310
|
+
const [providers, setProviders] = useState7([]);
|
|
3311
|
+
const [templates, setTemplates] = useState7([]);
|
|
3312
|
+
const [expandedId, setExpandedId] = useState7(null);
|
|
3313
|
+
useEffect3(() => {
|
|
3314
|
+
void fetchProviders().then(setProviders);
|
|
3315
|
+
void fetchTemplates().then(setTemplates);
|
|
3316
|
+
}, []);
|
|
3317
|
+
const fieldList = useMemo6(
|
|
3318
|
+
() => fields.map((f) => ({ name: f.name, label: f.label, type: f.type })),
|
|
3319
|
+
[fields]
|
|
3320
|
+
);
|
|
3321
|
+
const addAndOpen = useCallback6(() => {
|
|
3322
|
+
const created = createNotification();
|
|
3323
|
+
addNotification(created);
|
|
3324
|
+
setExpandedId(created.id);
|
|
3325
|
+
}, [addNotification]);
|
|
3326
|
+
const toggleExpanded = useCallback6((id) => {
|
|
3327
|
+
setExpandedId((current) => current === id ? null : id);
|
|
3328
|
+
}, []);
|
|
3329
|
+
const getProviderName = useCallback6(
|
|
3330
|
+
(providerId) => {
|
|
3331
|
+
if (!providerId) {
|
|
3332
|
+
const def = providers.find((p) => p.isDefault);
|
|
3333
|
+
return def ? `${def.name} (default)` : "System default";
|
|
3334
|
+
}
|
|
3335
|
+
return providers.find((p) => p.id === providerId)?.name ?? "Unknown";
|
|
3336
|
+
},
|
|
3337
|
+
[providers]
|
|
3338
|
+
);
|
|
3339
|
+
return (
|
|
3340
|
+
// The measure belongs to the page's shell, not to this tab.
|
|
3341
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
3342
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between mb-8 pb-4 border-b border-border", children: [
|
|
3343
|
+
/* @__PURE__ */ jsxs7("div", { children: [
|
|
3344
|
+
/* @__PURE__ */ jsx8("h3", { className: "text-xl font-semibold text-foreground", children: "Notifications" }),
|
|
3345
|
+
/* @__PURE__ */ jsx8("p", { className: "text-sm text-muted-foreground mt-1", children: "Emails sent when someone submits this form." })
|
|
3346
|
+
] }),
|
|
3347
|
+
/* @__PURE__ */ jsxs7(Button6, { type: "button", onClick: addAndOpen, children: [
|
|
3348
|
+
/* @__PURE__ */ jsx8(Plus2, { className: "h-4 w-4", "aria-hidden": "true" }),
|
|
3349
|
+
"Add notification"
|
|
3350
|
+
] })
|
|
3351
|
+
] }),
|
|
3400
3352
|
notifications.length === 0 ? /* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-center justify-center p-12 mt-6 border-2 border-dashed border-border rounded-none bg-muted", children: [
|
|
3401
3353
|
/* @__PURE__ */ jsx8("div", { className: "flex items-center justify-center w-12 h-12 rounded-none border border-border bg-background mb-4 text-primary", children: /* @__PURE__ */ jsx8(Mail, { className: "h-6 w-6", "aria-hidden": "true" }) }),
|
|
3402
3354
|
/* @__PURE__ */ jsx8("p", { className: "text-base font-medium text-foreground mb-1", children: "No notifications yet" }),
|
|
3403
3355
|
/* @__PURE__ */ jsx8("p", { className: "text-sm text-muted-foreground mb-6 text-center max-w-sm", children: "Add a notification to email someone when this form is submitted." }),
|
|
3404
|
-
/* @__PURE__ */ jsxs7(Button6, { type: "button", onClick:
|
|
3356
|
+
/* @__PURE__ */ jsxs7(Button6, { type: "button", onClick: addAndOpen, children: [
|
|
3405
3357
|
/* @__PURE__ */ jsx8(Plus2, { className: "h-4 w-4", "aria-hidden": "true" }),
|
|
3406
3358
|
"Add notification"
|
|
3407
3359
|
] })
|
|
@@ -3411,52 +3363,771 @@ function FormNotificationsTab({ defaults }) {
|
|
|
3411
3363
|
notification,
|
|
3412
3364
|
providerName: getProviderName(notification.providerId),
|
|
3413
3365
|
fields: fieldList,
|
|
3414
|
-
|
|
3366
|
+
expanded: expandedId === notification.id,
|
|
3367
|
+
editorId: `notification-editor-${notification.id}`,
|
|
3368
|
+
onToggle: () => toggleExpanded(notification.id),
|
|
3415
3369
|
onDuplicate: () => duplicateNotification(notification.id),
|
|
3416
3370
|
onDelete: () => deleteNotification(notification.id),
|
|
3417
|
-
onToggleEnabled: (enabled) => updateNotification(notification.id, { enabled })
|
|
3371
|
+
onToggleEnabled: (enabled) => updateNotification(notification.id, { enabled }),
|
|
3372
|
+
children: /* @__PURE__ */ jsx8(
|
|
3373
|
+
NotificationEditor,
|
|
3374
|
+
{
|
|
3375
|
+
notification,
|
|
3376
|
+
editorId: `notification-editor-${notification.id}`,
|
|
3377
|
+
providers,
|
|
3378
|
+
templates,
|
|
3379
|
+
fields: fieldList,
|
|
3380
|
+
defaults,
|
|
3381
|
+
onChange: (next) => updateNotification(notification.id, next)
|
|
3382
|
+
}
|
|
3383
|
+
)
|
|
3418
3384
|
},
|
|
3419
3385
|
notification.id
|
|
3420
|
-
)) })
|
|
3421
|
-
|
|
3422
|
-
|
|
3386
|
+
)) })
|
|
3387
|
+
] })
|
|
3388
|
+
);
|
|
3389
|
+
}
|
|
3390
|
+
|
|
3391
|
+
// src/admin/components/builder/FormPreview.tsx
|
|
3392
|
+
import {
|
|
3393
|
+
Badge as Badge4,
|
|
3394
|
+
Button as Button7,
|
|
3395
|
+
Checkbox as Checkbox3,
|
|
3396
|
+
Input as Input6,
|
|
3397
|
+
Card as Card2,
|
|
3398
|
+
Label as Label4,
|
|
3399
|
+
RadioGroup as RadioGroup2,
|
|
3400
|
+
RadioGroupItem as RadioGroupItem2,
|
|
3401
|
+
Select as Select8,
|
|
3402
|
+
SelectContent as SelectContent8,
|
|
3403
|
+
SelectItem as SelectItem8,
|
|
3404
|
+
SelectTrigger as SelectTrigger8,
|
|
3405
|
+
SelectValue as SelectValue8,
|
|
3406
|
+
Tabs as Tabs3,
|
|
3407
|
+
TabsList as TabsList3,
|
|
3408
|
+
TabsTrigger as TabsTrigger3,
|
|
3409
|
+
Textarea as Textarea3
|
|
3410
|
+
} from "@nextlyhq/ui";
|
|
3411
|
+
import { Eye, Monitor, Paperclip, RotateCcw, Smartphone } from "lucide-react";
|
|
3412
|
+
import { useMemo as useMemo7, useState as useState9 } from "react";
|
|
3413
|
+
|
|
3414
|
+
// src/admin/components/builder/FormSettingsTab.tsx
|
|
3415
|
+
import {
|
|
3416
|
+
FormSection,
|
|
3417
|
+
Input as Input5,
|
|
3418
|
+
Label as Label3,
|
|
3419
|
+
RadioGroup,
|
|
3420
|
+
RadioGroupItem,
|
|
3421
|
+
Select as Select7,
|
|
3422
|
+
SelectContent as SelectContent7,
|
|
3423
|
+
SelectItem as SelectItem7,
|
|
3424
|
+
SelectTrigger as SelectTrigger7,
|
|
3425
|
+
SelectValue as SelectValue7,
|
|
3426
|
+
Switch as Switch3,
|
|
3427
|
+
Textarea as Textarea2
|
|
3428
|
+
} from "@nextlyhq/ui";
|
|
3429
|
+
|
|
3430
|
+
// src/admin/components/builder/RedirectPagePicker.tsx
|
|
3431
|
+
import {
|
|
3432
|
+
Input as Input4,
|
|
3433
|
+
Select as Select6,
|
|
3434
|
+
SelectContent as SelectContent6,
|
|
3435
|
+
SelectGroup,
|
|
3436
|
+
SelectItem as SelectItem6,
|
|
3437
|
+
SelectLabel,
|
|
3438
|
+
SelectTrigger as SelectTrigger6,
|
|
3439
|
+
SelectValue as SelectValue6
|
|
3440
|
+
} from "@nextlyhq/ui";
|
|
3441
|
+
import { encodeSelectParam } from "nextly/query";
|
|
3442
|
+
import { useEffect as useEffect4, useState as useState8 } from "react";
|
|
3443
|
+
import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
3444
|
+
var PAGE_SIZE2 = 50;
|
|
3445
|
+
var FALLBACK_TITLE_FIELDS = ["title", "name", "label", "slug"];
|
|
3446
|
+
function labelFieldsFor(useAsTitle) {
|
|
3447
|
+
const configured = useAsTitle?.trim();
|
|
3448
|
+
if (!configured) return ["id", ...FALLBACK_TITLE_FIELDS];
|
|
3449
|
+
return [
|
|
3450
|
+
"id",
|
|
3451
|
+
configured,
|
|
3452
|
+
...FALLBACK_TITLE_FIELDS.filter((field) => field !== configured)
|
|
3453
|
+
];
|
|
3454
|
+
}
|
|
3455
|
+
function selectFieldsFor(useAsTitle) {
|
|
3456
|
+
return [...labelFieldsFor(useAsTitle), "status", "firstPublishedAt"];
|
|
3457
|
+
}
|
|
3458
|
+
function selectParam(fields) {
|
|
3459
|
+
return encodeURIComponent(encodeSelectParam(fields));
|
|
3460
|
+
}
|
|
3461
|
+
function selectionKey(value, collections) {
|
|
3462
|
+
const reference = parseRedirectReference(value, collections);
|
|
3463
|
+
return reference ? `${reference.collection}:${reference.id}` : void 0;
|
|
3464
|
+
}
|
|
3465
|
+
function documentLabel(row, fields = labelFieldsFor()) {
|
|
3466
|
+
for (const field of fields.filter((name) => name !== "id")) {
|
|
3467
|
+
const candidate = row[field];
|
|
3468
|
+
const text = typeof candidate === "string" ? candidate : typeof candidate === "number" && Number.isFinite(candidate) ? String(candidate) : typeof candidate === "boolean" ? String(candidate) : void 0;
|
|
3469
|
+
if (text !== void 0 && text.trim()) return text;
|
|
3470
|
+
}
|
|
3471
|
+
return typeof row.id === "string" ? row.id : "Untitled";
|
|
3472
|
+
}
|
|
3473
|
+
async function fetchChoices(collection, query, page, meta) {
|
|
3474
|
+
const search = query ? `&search=${encodeURIComponent(query)}` : "";
|
|
3475
|
+
try {
|
|
3476
|
+
const response = await fetch(
|
|
3477
|
+
`/admin/api/collections/${collection}/entries?limit=${PAGE_SIZE2}&page=${page}&status=all&depth=0&select=${selectParam(selectFieldsFor(meta.titleField))}${search}`,
|
|
3478
|
+
{ credentials: "include" }
|
|
3479
|
+
);
|
|
3480
|
+
if (!response.ok) return null;
|
|
3481
|
+
const body = await response.json();
|
|
3482
|
+
return {
|
|
3483
|
+
rows: (body.items ?? []).filter((row) => typeof row.id === "string").map((row) => ({
|
|
3484
|
+
collection,
|
|
3485
|
+
id: row.id,
|
|
3486
|
+
label: documentLabel(row, labelFieldsFor(meta.titleField)),
|
|
3487
|
+
reachability: documentReachability(
|
|
3488
|
+
row,
|
|
3489
|
+
meta.localized
|
|
3490
|
+
)
|
|
3491
|
+
})),
|
|
3492
|
+
// Reported rather than assumed, so the control can offer the rest
|
|
3493
|
+
// instead of truncating where the author cannot see it.
|
|
3494
|
+
hasMore: page < (body.meta?.totalPages ?? 1)
|
|
3495
|
+
};
|
|
3496
|
+
} catch {
|
|
3497
|
+
return null;
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
function mergeChoices(existing, incoming) {
|
|
3501
|
+
const seen = new Set(existing.map((c) => `${c.collection}:${c.id}`));
|
|
3502
|
+
const merged = [...existing];
|
|
3503
|
+
for (const choice of incoming) {
|
|
3504
|
+
const key = `${choice.collection}:${choice.id}`;
|
|
3505
|
+
if (seen.has(key)) continue;
|
|
3506
|
+
seen.add(key);
|
|
3507
|
+
merged.push(choice);
|
|
3508
|
+
}
|
|
3509
|
+
return merged;
|
|
3510
|
+
}
|
|
3511
|
+
function renderChoice(choice) {
|
|
3512
|
+
return /* @__PURE__ */ jsx9(
|
|
3513
|
+
SelectItem6,
|
|
3514
|
+
{
|
|
3515
|
+
value: `${choice.collection}:${choice.id}`,
|
|
3516
|
+
children: /* @__PURE__ */ jsxs8("span", { className: "inline-flex items-center gap-2", children: [
|
|
3517
|
+
/* @__PURE__ */ jsx9("span", { children: choice.label }),
|
|
3518
|
+
choice.reachability === "unreachable" && /* @__PURE__ */ jsx9("span", { className: "shrink-0 rounded-sm border px-1 py-px text-[10px] uppercase tracking-wide text-muted-foreground", children: "Draft" })
|
|
3519
|
+
] })
|
|
3520
|
+
},
|
|
3521
|
+
`${choice.collection}:${choice.id}`
|
|
3522
|
+
);
|
|
3523
|
+
}
|
|
3524
|
+
function metadataCollections(collections, value) {
|
|
3525
|
+
const stored = selectionKey(value, collections)?.split(":")[0];
|
|
3526
|
+
const wanted = stored && !collections.includes(stored) ? [...collections, stored] : collections;
|
|
3527
|
+
return wanted.join(",");
|
|
3528
|
+
}
|
|
3529
|
+
async function fetchCollectionMeta(collection) {
|
|
3530
|
+
try {
|
|
3531
|
+
const response = await fetch(`/admin/api/collections/${collection}`, {
|
|
3532
|
+
credentials: "include"
|
|
3533
|
+
});
|
|
3534
|
+
if (!response.ok) return { titleField: void 0, localized: void 0 };
|
|
3535
|
+
const body = await response.json();
|
|
3536
|
+
const configured = body.admin?.useAsTitle;
|
|
3537
|
+
return {
|
|
3538
|
+
titleField: typeof configured === "string" && configured.trim() ? configured.trim() : void 0,
|
|
3539
|
+
localized: typeof body.localized === "boolean" ? body.localized : void 0
|
|
3540
|
+
};
|
|
3541
|
+
} catch {
|
|
3542
|
+
return { titleField: void 0, localized: void 0 };
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
function useTitleFields(key) {
|
|
3546
|
+
const [fields, setFields] = useState8(
|
|
3547
|
+
null
|
|
3548
|
+
);
|
|
3549
|
+
useEffect4(() => {
|
|
3550
|
+
let cancelled = false;
|
|
3551
|
+
const wanted = key ? key.split(",") : [];
|
|
3552
|
+
setFields(null);
|
|
3553
|
+
void Promise.all(
|
|
3554
|
+
wanted.map(async (collection) => {
|
|
3555
|
+
return [collection, await fetchCollectionMeta(collection)];
|
|
3556
|
+
})
|
|
3557
|
+
).then((pairs) => {
|
|
3558
|
+
if (!cancelled) setFields(Object.fromEntries(pairs));
|
|
3559
|
+
});
|
|
3560
|
+
return () => {
|
|
3561
|
+
cancelled = true;
|
|
3562
|
+
};
|
|
3563
|
+
}, [key]);
|
|
3564
|
+
return fields;
|
|
3565
|
+
}
|
|
3566
|
+
function useChoices(key, applied, titleFields) {
|
|
3567
|
+
const [choices, setChoices] = useState8(null);
|
|
3568
|
+
const [failed, setFailed] = useState8([]);
|
|
3569
|
+
const [hasMore, setHasMore] = useState8(false);
|
|
3570
|
+
const [loading, setLoading] = useState8(false);
|
|
3571
|
+
const [pages, setPages] = useState8({});
|
|
3572
|
+
useEffect4(() => {
|
|
3573
|
+
setPages((current) => Object.keys(current).length === 0 ? current : {});
|
|
3574
|
+
}, [key, applied]);
|
|
3575
|
+
useEffect4(() => {
|
|
3576
|
+
if (titleFields === null) return;
|
|
3577
|
+
let cancelled = false;
|
|
3578
|
+
const wanted = key ? key.split(",") : [];
|
|
3579
|
+
setLoading(true);
|
|
3580
|
+
void Promise.all(
|
|
3581
|
+
wanted.map(
|
|
3582
|
+
(collection) => fetchChoices(
|
|
3583
|
+
collection,
|
|
3584
|
+
applied,
|
|
3585
|
+
pages[collection] ?? 1,
|
|
3586
|
+
titleFields[collection] ?? {
|
|
3587
|
+
titleField: void 0,
|
|
3588
|
+
localized: void 0
|
|
3589
|
+
}
|
|
3590
|
+
)
|
|
3591
|
+
)
|
|
3592
|
+
).then((results) => {
|
|
3593
|
+
if (cancelled) return;
|
|
3594
|
+
setLoading(false);
|
|
3595
|
+
setFailed(wanted.filter((_, index) => results[index] === null));
|
|
3596
|
+
const loaded = results.filter(
|
|
3597
|
+
(result) => result !== null
|
|
3598
|
+
);
|
|
3599
|
+
setHasMore(loaded.some((result) => result.hasMore));
|
|
3600
|
+
const rows = loaded.flatMap((result) => result.rows);
|
|
3601
|
+
const first = wanted.every((collection) => (pages[collection] ?? 1) === 1);
|
|
3602
|
+
setChoices((current) => mergeChoices(first ? [] : current ?? [], rows));
|
|
3603
|
+
});
|
|
3604
|
+
return () => {
|
|
3605
|
+
cancelled = true;
|
|
3606
|
+
};
|
|
3607
|
+
}, [key, applied, pages, titleFields]);
|
|
3608
|
+
return {
|
|
3609
|
+
choices,
|
|
3610
|
+
setChoices,
|
|
3611
|
+
failed,
|
|
3612
|
+
hasMore,
|
|
3613
|
+
loading,
|
|
3614
|
+
/**
|
|
3615
|
+
* Advances only the collections that have more to give, and RETRIES the
|
|
3616
|
+
* ones whose last page failed rather than stepping over it.
|
|
3617
|
+
*
|
|
3618
|
+
* Guarded on the in-flight flag: two clicks before the first resolves
|
|
3619
|
+
* either batch into a double increment or cancel the pending effect, and a
|
|
3620
|
+
* skipped page is then absent from a list that reports having loaded it.
|
|
3621
|
+
*/
|
|
3622
|
+
loadMore: () => {
|
|
3623
|
+
if (loading) return;
|
|
3624
|
+
setPages((current) => {
|
|
3625
|
+
const next = { ...current };
|
|
3626
|
+
for (const collection of key ? key.split(",") : []) {
|
|
3627
|
+
if (failed.includes(collection)) continue;
|
|
3628
|
+
next[collection] = (current[collection] ?? 1) + 1;
|
|
3629
|
+
}
|
|
3630
|
+
return next;
|
|
3631
|
+
});
|
|
3632
|
+
},
|
|
3633
|
+
/** Ask the failed collections again, without advancing anyone. */
|
|
3634
|
+
retryFailed: () => {
|
|
3635
|
+
if (!loading) setPages((current) => ({ ...current }));
|
|
3636
|
+
}
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3639
|
+
function useSelectedChoice(selected, choices, setChoices, titleFields) {
|
|
3640
|
+
const [unreadable, setUnreadable] = useState8(false);
|
|
3641
|
+
useEffect4(() => {
|
|
3642
|
+
if (!selected || choices === null) return;
|
|
3643
|
+
const separator = selected.indexOf(":");
|
|
3644
|
+
const collection = selected.slice(0, separator);
|
|
3645
|
+
const id = selected.slice(separator + 1);
|
|
3646
|
+
if (!id || choices.some(
|
|
3647
|
+
(choice) => choice.id === id && choice.collection === collection
|
|
3648
|
+
)) {
|
|
3649
|
+
setUnreadable(false);
|
|
3650
|
+
return;
|
|
3651
|
+
}
|
|
3652
|
+
let cancelled = false;
|
|
3653
|
+
void (async () => {
|
|
3654
|
+
try {
|
|
3655
|
+
const response = await fetch(
|
|
3656
|
+
`/admin/api/collections/${collection}/entries/${id}?depth=0&status=all`,
|
|
3657
|
+
{ credentials: "include" }
|
|
3658
|
+
);
|
|
3659
|
+
if (!response.ok) {
|
|
3660
|
+
if (!cancelled) setUnreadable(true);
|
|
3661
|
+
return;
|
|
3662
|
+
}
|
|
3663
|
+
const row = await response.json();
|
|
3664
|
+
if (cancelled) return;
|
|
3665
|
+
if (!row || typeof row.id !== "string") {
|
|
3666
|
+
setUnreadable(true);
|
|
3667
|
+
return;
|
|
3668
|
+
}
|
|
3669
|
+
setUnreadable(false);
|
|
3670
|
+
setChoices((current) => [
|
|
3671
|
+
{
|
|
3672
|
+
collection,
|
|
3673
|
+
id: row.id,
|
|
3674
|
+
// The same fields the listing labels by. Labelling the recovered
|
|
3675
|
+
// document differently would show one document under two names
|
|
3676
|
+
// depending on whether the listing happened to reach it.
|
|
3677
|
+
label: documentLabel(
|
|
3678
|
+
row,
|
|
3679
|
+
labelFieldsFor(titleFields?.[collection]?.titleField)
|
|
3680
|
+
),
|
|
3681
|
+
reachability: documentReachability(
|
|
3682
|
+
row,
|
|
3683
|
+
titleFields?.[collection]?.localized
|
|
3684
|
+
)
|
|
3685
|
+
},
|
|
3686
|
+
...current ?? []
|
|
3687
|
+
]);
|
|
3688
|
+
} catch {
|
|
3689
|
+
if (!cancelled) setUnreadable(true);
|
|
3690
|
+
}
|
|
3691
|
+
})();
|
|
3692
|
+
return () => {
|
|
3693
|
+
cancelled = true;
|
|
3694
|
+
};
|
|
3695
|
+
}, [selected, choices, setChoices, titleFields]);
|
|
3696
|
+
return unreadable;
|
|
3697
|
+
}
|
|
3698
|
+
function groupChoices(choices, collections) {
|
|
3699
|
+
const present = [...new Set(choices.map((choice) => choice.collection))];
|
|
3700
|
+
const order = [
|
|
3701
|
+
...collections.filter((name) => present.includes(name)),
|
|
3702
|
+
...present.filter((name) => !collections.includes(name))
|
|
3703
|
+
];
|
|
3704
|
+
return order.map((collection) => {
|
|
3705
|
+
const removed = !collections.includes(collection);
|
|
3706
|
+
return {
|
|
3707
|
+
collection,
|
|
3708
|
+
// Named as unavailable rather than hidden: the value is still stored and
|
|
3709
|
+
// the server refuses a save into a collection with no pattern, so an
|
|
3710
|
+
// author needs to see what is set in order to change it.
|
|
3711
|
+
label: removed ? `${collection} \u2014 no longer configured` : collection,
|
|
3712
|
+
removed,
|
|
3713
|
+
choices: choices.filter((choice) => choice.collection === collection)
|
|
3714
|
+
};
|
|
3715
|
+
});
|
|
3716
|
+
}
|
|
3717
|
+
function Options({
|
|
3718
|
+
choices,
|
|
3719
|
+
collections
|
|
3720
|
+
}) {
|
|
3721
|
+
const groups = groupChoices(choices, collections);
|
|
3722
|
+
if (groups.length <= 1 && !groups[0]?.removed) {
|
|
3723
|
+
return /* @__PURE__ */ jsx9(Fragment4, { children: choices.map(renderChoice) });
|
|
3724
|
+
}
|
|
3725
|
+
return /* @__PURE__ */ jsx9(Fragment4, { children: groups.map((group) => /* @__PURE__ */ jsxs8(SelectGroup, { children: [
|
|
3726
|
+
/* @__PURE__ */ jsx9(SelectLabel, { children: group.label }),
|
|
3727
|
+
group.choices.map(renderChoice)
|
|
3728
|
+
] }, group.collection)) });
|
|
3729
|
+
}
|
|
3730
|
+
function Empty({
|
|
3731
|
+
applied,
|
|
3732
|
+
collections
|
|
3733
|
+
}) {
|
|
3734
|
+
return /* @__PURE__ */ jsx9("p", { className: "text-[12px] text-muted-foreground", children: applied ? `No documents match \u201C${applied}\u201D.` : collections.length === 0 ? "No collection is configured as a redirect target." : `No documents to redirect to yet. Create one in ${collections.join(" or ")} first.` });
|
|
3735
|
+
}
|
|
3736
|
+
function PickerNotices({
|
|
3737
|
+
failed,
|
|
3738
|
+
loading,
|
|
3739
|
+
onRetry,
|
|
3740
|
+
unidentifiable,
|
|
3741
|
+
unreadableSelection
|
|
3742
|
+
}) {
|
|
3743
|
+
return /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
3744
|
+
failed.length > 0 && /* @__PURE__ */ jsxs8("p", { className: "text-[12px] text-destructive", children: [
|
|
3745
|
+
"Could not read ",
|
|
3746
|
+
failed.join(" or "),
|
|
3747
|
+
". You may not have permission to list ",
|
|
3748
|
+
failed.length > 1 ? "them" : "it",
|
|
3749
|
+
", or the request failed.",
|
|
3750
|
+
" ",
|
|
3751
|
+
/* @__PURE__ */ jsx9(
|
|
3752
|
+
"button",
|
|
3423
3753
|
{
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3754
|
+
type: "button",
|
|
3755
|
+
onClick: onRetry,
|
|
3756
|
+
disabled: loading,
|
|
3757
|
+
className: "underline underline-offset-2 disabled:no-underline",
|
|
3758
|
+
children: "Try again"
|
|
3759
|
+
}
|
|
3760
|
+
)
|
|
3761
|
+
] }),
|
|
3762
|
+
unidentifiable && /* @__PURE__ */ jsx9("p", { className: "text-[12px] text-muted-foreground", children: "A page is saved on this form, but it cannot be identified without a configured redirect collection. Choosing a different confirmation replaces it." }),
|
|
3763
|
+
unreadableSelection && /* @__PURE__ */ jsx9("p", { className: "text-[12px] text-destructive", children: "This form has a page selected that could not be read. Leave it as it is unless you mean to change it \u2014 saving a new choice replaces the stored one." })
|
|
3764
|
+
] });
|
|
3765
|
+
}
|
|
3766
|
+
function RedirectPagePicker({
|
|
3767
|
+
collections,
|
|
3768
|
+
value,
|
|
3769
|
+
onChange
|
|
3770
|
+
}) {
|
|
3771
|
+
const [query, setQuery] = useState8("");
|
|
3772
|
+
const [applied, setApplied] = useState8("");
|
|
3773
|
+
useEffect4(() => {
|
|
3774
|
+
const timer = setTimeout(() => setApplied(query), 250);
|
|
3775
|
+
return () => clearTimeout(timer);
|
|
3776
|
+
}, [query]);
|
|
3777
|
+
const titleFields = useTitleFields(metadataCollections(collections, value));
|
|
3778
|
+
const {
|
|
3779
|
+
choices,
|
|
3780
|
+
setChoices,
|
|
3781
|
+
failed,
|
|
3782
|
+
hasMore,
|
|
3783
|
+
loading,
|
|
3784
|
+
loadMore,
|
|
3785
|
+
retryFailed
|
|
3786
|
+
} = useChoices(collections.join(","), applied, titleFields);
|
|
3787
|
+
const selected = selectionKey(value, collections);
|
|
3788
|
+
const unidentifiable = value != null && selected === void 0;
|
|
3789
|
+
const selectionUnreadable = useSelectedChoice(
|
|
3790
|
+
selected,
|
|
3791
|
+
choices,
|
|
3792
|
+
setChoices,
|
|
3793
|
+
titleFields
|
|
3794
|
+
);
|
|
3795
|
+
if (choices === null) {
|
|
3796
|
+
return /* @__PURE__ */ jsx9("p", { className: "text-[12px] text-muted-foreground", children: "Loading pages\u2026" });
|
|
3797
|
+
}
|
|
3798
|
+
return /* @__PURE__ */ jsxs8("div", { className: "space-y-2", children: [
|
|
3799
|
+
collections.length > 0 && /* @__PURE__ */ jsx9(
|
|
3800
|
+
Input4,
|
|
3801
|
+
{
|
|
3802
|
+
"aria-label": "Search pages",
|
|
3803
|
+
value: query,
|
|
3804
|
+
onChange: (event) => setQuery(event.target.value),
|
|
3805
|
+
placeholder: "Search\u2026"
|
|
3806
|
+
}
|
|
3807
|
+
),
|
|
3808
|
+
/* @__PURE__ */ jsx9(
|
|
3809
|
+
PickerNotices,
|
|
3810
|
+
{
|
|
3811
|
+
failed,
|
|
3812
|
+
loading,
|
|
3813
|
+
onRetry: retryFailed,
|
|
3814
|
+
unidentifiable,
|
|
3815
|
+
unreadableSelection: selectionUnreadable
|
|
3816
|
+
}
|
|
3817
|
+
),
|
|
3818
|
+
choices.length === 0 ? failed.length < collections.length && /* @__PURE__ */ jsx9(
|
|
3819
|
+
Empty,
|
|
3820
|
+
{
|
|
3821
|
+
applied,
|
|
3822
|
+
collections: collections.filter((name) => !failed.includes(name))
|
|
3823
|
+
}
|
|
3824
|
+
) : /* @__PURE__ */ jsxs8(
|
|
3825
|
+
Select6,
|
|
3826
|
+
{
|
|
3827
|
+
value: selected ?? "",
|
|
3828
|
+
onValueChange: (next) => {
|
|
3829
|
+
const separator = next.indexOf(":");
|
|
3830
|
+
if (separator < 0) return onChange(void 0);
|
|
3831
|
+
onChange({
|
|
3832
|
+
relationTo: next.slice(0, separator),
|
|
3833
|
+
value: next.slice(separator + 1)
|
|
3834
|
+
});
|
|
3835
|
+
},
|
|
3836
|
+
children: [
|
|
3837
|
+
/* @__PURE__ */ jsx9(SelectTrigger6, { "aria-label": "Redirect page", className: "w-full", children: /* @__PURE__ */ jsx9(SelectValue6, { placeholder: "Choose a page" }) }),
|
|
3838
|
+
/* @__PURE__ */ jsx9(SelectContent6, { children: /* @__PURE__ */ jsx9(Options, { choices, collections }) })
|
|
3839
|
+
]
|
|
3840
|
+
}
|
|
3841
|
+
),
|
|
3842
|
+
hasMore && /* @__PURE__ */ jsx9(
|
|
3843
|
+
"button",
|
|
3844
|
+
{
|
|
3845
|
+
type: "button",
|
|
3846
|
+
onClick: loadMore,
|
|
3847
|
+
disabled: loading,
|
|
3848
|
+
className: "text-[12px] text-muted-foreground underline underline-offset-2 hover:text-foreground disabled:no-underline disabled:opacity-60",
|
|
3849
|
+
children: loading ? "Loading\u2026" : "Load more"
|
|
3850
|
+
}
|
|
3851
|
+
)
|
|
3852
|
+
] });
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3855
|
+
// src/admin/components/builder/FormSettingsTab.tsx
|
|
3856
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3857
|
+
function SettingRow({
|
|
3858
|
+
label,
|
|
3859
|
+
description,
|
|
3860
|
+
htmlFor,
|
|
3861
|
+
children
|
|
3862
|
+
}) {
|
|
3863
|
+
return /* @__PURE__ */ jsxs9("div", { className: "flex flex-col sm:flex-row sm:items-center justify-between gap-6", children: [
|
|
3864
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-1", children: [
|
|
3865
|
+
/* @__PURE__ */ jsx10(
|
|
3866
|
+
Label3,
|
|
3867
|
+
{
|
|
3868
|
+
htmlFor,
|
|
3869
|
+
className: "text-[13px] font-semibold text-foreground tracking-tight",
|
|
3870
|
+
children: label
|
|
3871
|
+
}
|
|
3872
|
+
),
|
|
3873
|
+
description && /* @__PURE__ */ jsx10("p", { className: "text-[12px] text-muted-foreground leading-relaxed", children: description })
|
|
3874
|
+
] }),
|
|
3875
|
+
/* @__PURE__ */ jsx10("div", { className: "shrink-0 flex items-center justify-end min-w-25", children })
|
|
3876
|
+
] });
|
|
3877
|
+
}
|
|
3878
|
+
function InheritToggle({
|
|
3879
|
+
id,
|
|
3880
|
+
value,
|
|
3881
|
+
inherited,
|
|
3882
|
+
onChange
|
|
3883
|
+
}) {
|
|
3884
|
+
const inheritedLabel = inherited === void 0 ? "Inherit" : `Inherit (${inherited ? "on" : "off"})`;
|
|
3885
|
+
return /* @__PURE__ */ jsxs9(
|
|
3886
|
+
Select7,
|
|
3887
|
+
{
|
|
3888
|
+
value: value === void 0 ? "inherit" : value ? "on" : "off",
|
|
3889
|
+
onValueChange: (selected) => onChange(selected === "inherit" ? void 0 : selected === "on"),
|
|
3890
|
+
children: [
|
|
3891
|
+
/* @__PURE__ */ jsx10(
|
|
3892
|
+
SelectTrigger7,
|
|
3893
|
+
{
|
|
3894
|
+
id,
|
|
3895
|
+
className: "w-40 bg-transparent border-input dark:bg-muted/50",
|
|
3896
|
+
children: /* @__PURE__ */ jsx10(SelectValue7, {})
|
|
3897
|
+
}
|
|
3898
|
+
),
|
|
3899
|
+
/* @__PURE__ */ jsxs9(SelectContent7, { children: [
|
|
3900
|
+
/* @__PURE__ */ jsx10(SelectItem7, { value: "inherit", children: inheritedLabel }),
|
|
3901
|
+
/* @__PURE__ */ jsx10(SelectItem7, { value: "on", children: "On" }),
|
|
3902
|
+
/* @__PURE__ */ jsx10(SelectItem7, { value: "off", children: "Off" })
|
|
3903
|
+
] })
|
|
3904
|
+
]
|
|
3905
|
+
}
|
|
3906
|
+
);
|
|
3907
|
+
}
|
|
3908
|
+
function hasStoredRedirectPage(settings) {
|
|
3909
|
+
const value = settings.redirectPage;
|
|
3910
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
3911
|
+
return parseRedirectReference(value, []) !== null;
|
|
3912
|
+
}
|
|
3913
|
+
function redirectOptionState(input) {
|
|
3914
|
+
const configured = input.collections !== null && input.collections.length > 0;
|
|
3915
|
+
if (configured) return "ready";
|
|
3916
|
+
if (input.hasStoredPage) return "stored-only";
|
|
3917
|
+
return input.configFailed ? "unknown" : "hidden";
|
|
3918
|
+
}
|
|
3919
|
+
function PageRedirectOption({
|
|
3920
|
+
redirectCollections,
|
|
3921
|
+
configFailed,
|
|
3922
|
+
settings,
|
|
3923
|
+
updateSettings
|
|
3924
|
+
}) {
|
|
3925
|
+
const state = redirectOptionState({
|
|
3926
|
+
hasStoredPage: hasStoredRedirectPage(settings),
|
|
3927
|
+
collections: redirectCollections,
|
|
3928
|
+
configFailed
|
|
3929
|
+
});
|
|
3930
|
+
if (state === "hidden") return null;
|
|
3931
|
+
return /* @__PURE__ */ jsxs9("div", { className: "flex items-start gap-3", children: [
|
|
3932
|
+
/* @__PURE__ */ jsx10(
|
|
3933
|
+
RadioGroupItem,
|
|
3934
|
+
{
|
|
3935
|
+
value: "relationship",
|
|
3936
|
+
id: "settings-confirm-page",
|
|
3937
|
+
className: "mt-0.5",
|
|
3938
|
+
disabled: state !== "ready"
|
|
3939
|
+
}
|
|
3940
|
+
),
|
|
3941
|
+
/* @__PURE__ */ jsxs9("div", { className: "w-full space-y-2", children: [
|
|
3942
|
+
/* @__PURE__ */ jsx10(Label3, { htmlFor: "settings-confirm-page", children: "Redirect to a page" }),
|
|
3943
|
+
state !== "ready" && /* @__PURE__ */ jsx10("p", { className: "text-[12px] text-muted-foreground", children: configFailed ? "The redirect configuration could not be loaded, so the pages you can choose from are not listed. Reload to try again." : "No collection is configured as a redirect target right now. The page below is still saved, and stays until you choose a different confirmation." }),
|
|
3944
|
+
state === "stored-only" || state === "ready" ? settings.confirmationType === "relationship" && /* @__PURE__ */ jsx10(
|
|
3945
|
+
RedirectPagePicker,
|
|
3946
|
+
{
|
|
3947
|
+
collections: redirectCollections ?? [],
|
|
3948
|
+
value: settings.redirectPage,
|
|
3949
|
+
onChange: (next) => updateSettings({ redirectPage: next })
|
|
3950
|
+
}
|
|
3951
|
+
) : null
|
|
3952
|
+
] })
|
|
3953
|
+
] });
|
|
3954
|
+
}
|
|
3955
|
+
function FormSettingsTab({
|
|
3956
|
+
spamDefaults,
|
|
3957
|
+
redirectCollections,
|
|
3958
|
+
redirectConfigFailed
|
|
3959
|
+
}) {
|
|
3960
|
+
const { settings, updateSettings } = useFormBuilder();
|
|
3961
|
+
return (
|
|
3962
|
+
// The measure belongs to the page's shell, not to this tab.
|
|
3963
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-10", children: [
|
|
3964
|
+
/* @__PURE__ */ jsxs9(FormSection, { label: "Submission", children: [
|
|
3965
|
+
/* @__PURE__ */ jsx10(
|
|
3966
|
+
SettingRow,
|
|
3967
|
+
{
|
|
3968
|
+
label: "Submit button text",
|
|
3969
|
+
description: "Label on the form's primary action button",
|
|
3970
|
+
htmlFor: "settings-submit-text",
|
|
3971
|
+
children: /* @__PURE__ */ jsx10(
|
|
3972
|
+
Input5,
|
|
3973
|
+
{
|
|
3974
|
+
id: "settings-submit-text",
|
|
3975
|
+
type: "text",
|
|
3976
|
+
className: "w-56",
|
|
3977
|
+
value: settings.submitButtonText ?? "",
|
|
3978
|
+
onChange: (e) => updateSettings({ submitButtonText: e.target.value })
|
|
3979
|
+
}
|
|
3980
|
+
)
|
|
3981
|
+
}
|
|
3982
|
+
),
|
|
3983
|
+
/* @__PURE__ */ jsx10(
|
|
3984
|
+
SettingRow,
|
|
3985
|
+
{
|
|
3986
|
+
label: "Allow multiple submissions",
|
|
3987
|
+
description: "When off, the same visitor (by IP) can submit this form only once. Best-effort: IP-based, so shared networks count as one visitor.",
|
|
3988
|
+
htmlFor: "settings-multiple",
|
|
3989
|
+
children: /* @__PURE__ */ jsx10(
|
|
3990
|
+
Switch3,
|
|
3991
|
+
{
|
|
3992
|
+
id: "settings-multiple",
|
|
3993
|
+
checked: settings.allowMultipleSubmissions ?? true,
|
|
3994
|
+
onCheckedChange: (checked) => updateSettings({ allowMultipleSubmissions: checked })
|
|
3995
|
+
}
|
|
3996
|
+
)
|
|
3997
|
+
}
|
|
3998
|
+
)
|
|
3999
|
+
] }),
|
|
4000
|
+
/* @__PURE__ */ jsx10(FormSection, { label: "After submission", children: /* @__PURE__ */ jsxs9(
|
|
4001
|
+
RadioGroup,
|
|
4002
|
+
{
|
|
4003
|
+
value: settings.confirmationType ?? "message",
|
|
4004
|
+
onValueChange: (value) => updateSettings({
|
|
4005
|
+
confirmationType: value
|
|
4006
|
+
}),
|
|
4007
|
+
className: "flex flex-col gap-3",
|
|
4008
|
+
children: [
|
|
4009
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-start gap-3", children: [
|
|
4010
|
+
/* @__PURE__ */ jsx10(
|
|
4011
|
+
RadioGroupItem,
|
|
4012
|
+
{
|
|
4013
|
+
value: "message",
|
|
4014
|
+
id: "settings-confirm-message",
|
|
4015
|
+
className: "mt-0.5"
|
|
4016
|
+
}
|
|
4017
|
+
),
|
|
4018
|
+
/* @__PURE__ */ jsxs9("div", { className: "w-full space-y-2", children: [
|
|
4019
|
+
/* @__PURE__ */ jsx10(Label3, { htmlFor: "settings-confirm-message", children: "Show a message" }),
|
|
4020
|
+
(settings.confirmationType ?? "message") === "message" && /* @__PURE__ */ jsx10(
|
|
4021
|
+
Textarea2,
|
|
4022
|
+
{
|
|
4023
|
+
"aria-label": "Success message",
|
|
4024
|
+
value: settings.successMessage ?? "",
|
|
4025
|
+
onChange: (e) => updateSettings({ successMessage: e.target.value }),
|
|
4026
|
+
rows: 3,
|
|
4027
|
+
placeholder: "Thank you for your submission!"
|
|
4028
|
+
}
|
|
4029
|
+
)
|
|
4030
|
+
] })
|
|
4031
|
+
] }),
|
|
4032
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-start gap-3", children: [
|
|
4033
|
+
/* @__PURE__ */ jsx10(
|
|
4034
|
+
RadioGroupItem,
|
|
4035
|
+
{
|
|
4036
|
+
value: "redirect",
|
|
4037
|
+
id: "settings-confirm-redirect",
|
|
4038
|
+
className: "mt-0.5"
|
|
4039
|
+
}
|
|
4040
|
+
),
|
|
4041
|
+
/* @__PURE__ */ jsxs9("div", { className: "w-full space-y-2", children: [
|
|
4042
|
+
/* @__PURE__ */ jsx10(Label3, { htmlFor: "settings-confirm-redirect", children: "Redirect to a URL" }),
|
|
4043
|
+
settings.confirmationType === "redirect" && /* @__PURE__ */ jsx10(
|
|
4044
|
+
Input5,
|
|
4045
|
+
{
|
|
4046
|
+
"aria-label": "Redirect URL",
|
|
4047
|
+
type: "url",
|
|
4048
|
+
value: settings.redirectUrl ?? "",
|
|
4049
|
+
onChange: (e) => updateSettings({
|
|
4050
|
+
redirectUrl: e.target.value || void 0
|
|
4051
|
+
}),
|
|
4052
|
+
placeholder: "https://example.com/thanks"
|
|
4053
|
+
}
|
|
4054
|
+
)
|
|
4055
|
+
] })
|
|
4056
|
+
] }),
|
|
4057
|
+
/* @__PURE__ */ jsx10(
|
|
4058
|
+
PageRedirectOption,
|
|
4059
|
+
{
|
|
4060
|
+
redirectCollections,
|
|
4061
|
+
configFailed: redirectConfigFailed,
|
|
4062
|
+
settings,
|
|
4063
|
+
updateSettings
|
|
4064
|
+
}
|
|
4065
|
+
)
|
|
4066
|
+
]
|
|
3432
4067
|
}
|
|
3433
|
-
)
|
|
4068
|
+
) }),
|
|
4069
|
+
/* @__PURE__ */ jsxs9(FormSection, { label: "Spam protection", children: [
|
|
4070
|
+
/* @__PURE__ */ jsx10(
|
|
4071
|
+
SettingRow,
|
|
4072
|
+
{
|
|
4073
|
+
label: "Honeypot",
|
|
4074
|
+
description: "Invisible trap field for bots. Inherits the plugin default unless overridden here.",
|
|
4075
|
+
htmlFor: "settings-honeypot",
|
|
4076
|
+
children: /* @__PURE__ */ jsx10(
|
|
4077
|
+
InheritToggle,
|
|
4078
|
+
{
|
|
4079
|
+
id: "settings-honeypot",
|
|
4080
|
+
value: settings.honeypotEnabled,
|
|
4081
|
+
inherited: spamDefaults?.honeypot,
|
|
4082
|
+
onChange: (honeypotEnabled) => updateSettings({ honeypotEnabled })
|
|
4083
|
+
}
|
|
4084
|
+
)
|
|
4085
|
+
}
|
|
4086
|
+
),
|
|
4087
|
+
/* @__PURE__ */ jsx10(
|
|
4088
|
+
SettingRow,
|
|
4089
|
+
{
|
|
4090
|
+
label: "reCAPTCHA",
|
|
4091
|
+
description: "Challenge-based bot check. Inherits the plugin default unless overridden here.",
|
|
4092
|
+
htmlFor: "settings-captcha",
|
|
4093
|
+
children: /* @__PURE__ */ jsx10(
|
|
4094
|
+
InheritToggle,
|
|
4095
|
+
{
|
|
4096
|
+
id: "settings-captcha",
|
|
4097
|
+
value: settings.captchaEnabled,
|
|
4098
|
+
inherited: spamDefaults?.recaptchaEnabled,
|
|
4099
|
+
onChange: (captchaEnabled) => updateSettings({ captchaEnabled })
|
|
4100
|
+
}
|
|
4101
|
+
)
|
|
4102
|
+
}
|
|
4103
|
+
),
|
|
4104
|
+
settings.captchaEnabled === true && /* @__PURE__ */ jsx10(
|
|
4105
|
+
SettingRow,
|
|
4106
|
+
{
|
|
4107
|
+
label: "reCAPTCHA site key",
|
|
4108
|
+
description: "The client-facing site key for this form",
|
|
4109
|
+
htmlFor: "settings-captcha-key",
|
|
4110
|
+
children: /* @__PURE__ */ jsx10(
|
|
4111
|
+
Input5,
|
|
4112
|
+
{
|
|
4113
|
+
id: "settings-captcha-key",
|
|
4114
|
+
type: "text",
|
|
4115
|
+
className: "w-full font-mono sm:w-72",
|
|
4116
|
+
value: settings.captchaSiteKey ?? "",
|
|
4117
|
+
onChange: (e) => updateSettings({
|
|
4118
|
+
captchaSiteKey: e.target.value || void 0
|
|
4119
|
+
})
|
|
4120
|
+
}
|
|
4121
|
+
)
|
|
4122
|
+
}
|
|
4123
|
+
)
|
|
4124
|
+
] })
|
|
3434
4125
|
] })
|
|
3435
4126
|
);
|
|
3436
4127
|
}
|
|
3437
4128
|
|
|
3438
4129
|
// src/admin/components/builder/FormPreview.tsx
|
|
3439
|
-
import {
|
|
3440
|
-
Badge as Badge4,
|
|
3441
|
-
Button as Button7,
|
|
3442
|
-
Checkbox as Checkbox3,
|
|
3443
|
-
Input as Input4,
|
|
3444
|
-
Label as Label3,
|
|
3445
|
-
RadioGroup,
|
|
3446
|
-
RadioGroupItem,
|
|
3447
|
-
Select as Select6,
|
|
3448
|
-
SelectContent as SelectContent6,
|
|
3449
|
-
SelectItem as SelectItem6,
|
|
3450
|
-
SelectTrigger as SelectTrigger6,
|
|
3451
|
-
SelectValue as SelectValue6,
|
|
3452
|
-
Tabs as Tabs3,
|
|
3453
|
-
TabsList as TabsList3,
|
|
3454
|
-
TabsTrigger as TabsTrigger3,
|
|
3455
|
-
Textarea as Textarea2
|
|
3456
|
-
} from "@nextlyhq/ui";
|
|
3457
|
-
import { Eye, Monitor, Paperclip, RotateCcw, Smartphone } from "lucide-react";
|
|
3458
|
-
import { useMemo as useMemo7, useState as useState8 } from "react";
|
|
3459
|
-
import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
4130
|
+
import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3460
4131
|
function PreviewFieldInput({
|
|
3461
4132
|
field,
|
|
3462
4133
|
value,
|
|
@@ -3465,8 +4136,8 @@ function PreviewFieldInput({
|
|
|
3465
4136
|
const id = `preview-${field.name}`;
|
|
3466
4137
|
switch (field.type) {
|
|
3467
4138
|
case "textarea":
|
|
3468
|
-
return /* @__PURE__ */
|
|
3469
|
-
|
|
4139
|
+
return /* @__PURE__ */ jsx11(
|
|
4140
|
+
Textarea3,
|
|
3470
4141
|
{
|
|
3471
4142
|
id,
|
|
3472
4143
|
value: typeof value === "string" ? value : "",
|
|
@@ -3476,7 +4147,7 @@ function PreviewFieldInput({
|
|
|
3476
4147
|
}
|
|
3477
4148
|
);
|
|
3478
4149
|
case "checkbox":
|
|
3479
|
-
return /* @__PURE__ */
|
|
4150
|
+
return /* @__PURE__ */ jsx11(
|
|
3480
4151
|
Checkbox3,
|
|
3481
4152
|
{
|
|
3482
4153
|
id,
|
|
@@ -3486,49 +4157,49 @@ function PreviewFieldInput({
|
|
|
3486
4157
|
);
|
|
3487
4158
|
case "select": {
|
|
3488
4159
|
const options = "options" in field ? field.options ?? [] : [];
|
|
3489
|
-
return /* @__PURE__ */
|
|
3490
|
-
|
|
4160
|
+
return /* @__PURE__ */ jsxs10(
|
|
4161
|
+
Select8,
|
|
3491
4162
|
{
|
|
3492
4163
|
value: typeof value === "string" ? value : "",
|
|
3493
4164
|
onValueChange: onChange,
|
|
3494
4165
|
children: [
|
|
3495
|
-
/* @__PURE__ */
|
|
3496
|
-
|
|
4166
|
+
/* @__PURE__ */ jsx11(
|
|
4167
|
+
SelectTrigger8,
|
|
3497
4168
|
{
|
|
3498
4169
|
id,
|
|
3499
4170
|
className: "w-full bg-transparent border-input dark:bg-muted/50",
|
|
3500
|
-
children: /* @__PURE__ */
|
|
4171
|
+
children: /* @__PURE__ */ jsx11(SelectValue8, { placeholder: field.placeholder ?? "Select\u2026" })
|
|
3501
4172
|
}
|
|
3502
4173
|
),
|
|
3503
|
-
/* @__PURE__ */
|
|
4174
|
+
/* @__PURE__ */ jsx11(SelectContent8, { children: options.map((option) => /* @__PURE__ */ jsx11(SelectItem8, { value: option.value, children: option.label }, option.value)) })
|
|
3504
4175
|
]
|
|
3505
4176
|
}
|
|
3506
4177
|
);
|
|
3507
4178
|
}
|
|
3508
4179
|
case "radio": {
|
|
3509
4180
|
const options = "options" in field ? field.options ?? [] : [];
|
|
3510
|
-
return /* @__PURE__ */
|
|
3511
|
-
|
|
4181
|
+
return /* @__PURE__ */ jsx11(
|
|
4182
|
+
RadioGroup2,
|
|
3512
4183
|
{
|
|
3513
4184
|
value: typeof value === "string" ? value : "",
|
|
3514
4185
|
onValueChange: onChange,
|
|
3515
4186
|
className: "flex flex-col gap-2",
|
|
3516
|
-
children: options.map((option) => /* @__PURE__ */
|
|
3517
|
-
/* @__PURE__ */
|
|
3518
|
-
|
|
4187
|
+
children: options.map((option) => /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
|
|
4188
|
+
/* @__PURE__ */ jsx11(
|
|
4189
|
+
RadioGroupItem2,
|
|
3519
4190
|
{
|
|
3520
4191
|
value: option.value,
|
|
3521
4192
|
id: `${id}-${option.value}`
|
|
3522
4193
|
}
|
|
3523
4194
|
),
|
|
3524
|
-
/* @__PURE__ */
|
|
4195
|
+
/* @__PURE__ */ jsx11(Label4, { htmlFor: `${id}-${option.value}`, className: "font-normal", children: option.label })
|
|
3525
4196
|
] }, option.value))
|
|
3526
4197
|
}
|
|
3527
4198
|
);
|
|
3528
4199
|
}
|
|
3529
4200
|
case "number":
|
|
3530
|
-
return /* @__PURE__ */
|
|
3531
|
-
|
|
4201
|
+
return /* @__PURE__ */ jsx11(
|
|
4202
|
+
Input6,
|
|
3532
4203
|
{
|
|
3533
4204
|
id,
|
|
3534
4205
|
type: "number",
|
|
@@ -3539,8 +4210,8 @@ function PreviewFieldInput({
|
|
|
3539
4210
|
);
|
|
3540
4211
|
case "date":
|
|
3541
4212
|
case "time":
|
|
3542
|
-
return /* @__PURE__ */
|
|
3543
|
-
|
|
4213
|
+
return /* @__PURE__ */ jsx11(
|
|
4214
|
+
Input6,
|
|
3544
4215
|
{
|
|
3545
4216
|
id,
|
|
3546
4217
|
type: field.type,
|
|
@@ -3549,13 +4220,13 @@ function PreviewFieldInput({
|
|
|
3549
4220
|
}
|
|
3550
4221
|
);
|
|
3551
4222
|
case "file":
|
|
3552
|
-
return /* @__PURE__ */
|
|
3553
|
-
/* @__PURE__ */
|
|
4223
|
+
return /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 border border-dashed border-border bg-muted/40 px-3 py-2 text-sm text-muted-foreground", children: [
|
|
4224
|
+
/* @__PURE__ */ jsx11(Paperclip, { className: "h-4 w-4", "aria-hidden": "true" }),
|
|
3554
4225
|
"File upload (disabled in preview)"
|
|
3555
4226
|
] });
|
|
3556
4227
|
default:
|
|
3557
|
-
return /* @__PURE__ */
|
|
3558
|
-
|
|
4228
|
+
return /* @__PURE__ */ jsx11(
|
|
4229
|
+
Input6,
|
|
3559
4230
|
{
|
|
3560
4231
|
id,
|
|
3561
4232
|
type: field.type === "email" ? "email" : field.type === "url" ? "url" : field.type === "phone" ? "tel" : "text",
|
|
@@ -3567,25 +4238,43 @@ function PreviewFieldInput({
|
|
|
3567
4238
|
}
|
|
3568
4239
|
}
|
|
3569
4240
|
function PluginFieldPreview({ field }) {
|
|
3570
|
-
return /* @__PURE__ */
|
|
3571
|
-
/* @__PURE__ */
|
|
4241
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-1.5", children: [
|
|
4242
|
+
/* @__PURE__ */ jsxs10(Label4, { htmlFor: `preview-${field.name}`, children: [
|
|
3572
4243
|
field.label || field.name,
|
|
3573
|
-
field.required && /* @__PURE__ */
|
|
4244
|
+
field.required && /* @__PURE__ */ jsx11("span", { className: "ml-1 text-destructive", children: "*" })
|
|
3574
4245
|
] }),
|
|
3575
|
-
/* @__PURE__ */
|
|
4246
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 border border-dashed border-border bg-muted/40 px-3 py-2 text-sm text-muted-foreground", children: [
|
|
3576
4247
|
"Custom field (",
|
|
3577
4248
|
field.type,
|
|
3578
4249
|
") \u2014 renders from its plugin component in the live form."
|
|
3579
4250
|
] }),
|
|
3580
|
-
field.helpText && /* @__PURE__ */
|
|
4251
|
+
field.helpText && /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: field.helpText })
|
|
3581
4252
|
] });
|
|
3582
4253
|
}
|
|
4254
|
+
function Confirmation({
|
|
4255
|
+
settings
|
|
4256
|
+
}) {
|
|
4257
|
+
const panel = "border border-border bg-muted/40 p-4 text-sm text-foreground";
|
|
4258
|
+
if (settings.confirmationType === "redirect") {
|
|
4259
|
+
return /* @__PURE__ */ jsxs10("p", { className: panel, children: [
|
|
4260
|
+
"The visitor would now be redirected to",
|
|
4261
|
+
" ",
|
|
4262
|
+
/* @__PURE__ */ jsx11("span", { className: "font-mono", children: settings.redirectUrl || "(no redirect URL set)" }),
|
|
4263
|
+
"."
|
|
4264
|
+
] });
|
|
4265
|
+
}
|
|
4266
|
+
if (settings.confirmationType === "relationship") {
|
|
4267
|
+
const named = hasStoredRedirectPage(settings);
|
|
4268
|
+
return /* @__PURE__ */ jsx11("p", { className: panel, children: named ? "The visitor would now be redirected to the linked page. Its URL is built when the form is submitted, from the pattern the site configured for that collection." : "No page is selected yet, so this form has no destination. Choose one under Settings, or pick a different confirmation." });
|
|
4269
|
+
}
|
|
4270
|
+
return /* @__PURE__ */ jsx11("p", { className: panel, children: settings.successMessage || "Thank you for your submission!" });
|
|
4271
|
+
}
|
|
3583
4272
|
function FormPreview({ fields, formData }) {
|
|
3584
4273
|
const { settings } = useFormBuilder();
|
|
3585
|
-
const [values, setValues] =
|
|
3586
|
-
const [device, setDevice] =
|
|
3587
|
-
const [confirmed, setConfirmed] =
|
|
3588
|
-
const [missingRequired, setMissingRequired] =
|
|
4274
|
+
const [values, setValues] = useState9({});
|
|
4275
|
+
const [device, setDevice] = useState9("desktop");
|
|
4276
|
+
const [confirmed, setConfirmed] = useState9(false);
|
|
4277
|
+
const [missingRequired, setMissingRequired] = useState9(
|
|
3589
4278
|
/* @__PURE__ */ new Set()
|
|
3590
4279
|
);
|
|
3591
4280
|
const visibleFields = useMemo7(() => {
|
|
@@ -3621,362 +4310,121 @@ function FormPreview({ fields, formData }) {
|
|
|
3621
4310
|
setMissingRequired(missing);
|
|
3622
4311
|
if (missing.size === 0) setConfirmed(true);
|
|
3623
4312
|
};
|
|
3624
|
-
return /* @__PURE__ */ jsxs8("div", { className: "space-y-4", children: [
|
|
3625
|
-
/* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [
|
|
3626
|
-
/* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
3627
|
-
/* @__PURE__ */ jsx9(Eye, { className: "h-4 w-4", "aria-hidden": "true" }),
|
|
3628
|
-
"Preview \u2014 a simulation of this form. Nothing here submits anywhere."
|
|
3629
|
-
] }),
|
|
3630
|
-
/* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
|
|
3631
|
-
/* @__PURE__ */ jsx9(
|
|
3632
|
-
Tabs3,
|
|
3633
|
-
{
|
|
3634
|
-
value: device,
|
|
3635
|
-
onValueChange: (value) => setDevice(value),
|
|
3636
|
-
children: /* @__PURE__ */ jsxs8(TabsList3, { children: [
|
|
3637
|
-
/* @__PURE__ */ jsxs8(TabsTrigger3, { value: "desktop", className: "gap-1.5", children: [
|
|
3638
|
-
/* @__PURE__ */ jsx9(Monitor, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
3639
|
-
"Desktop"
|
|
3640
|
-
] }),
|
|
3641
|
-
/* @__PURE__ */ jsxs8(TabsTrigger3, { value: "mobile", className: "gap-1.5", children: [
|
|
3642
|
-
/* @__PURE__ */ jsx9(Smartphone, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
3643
|
-
"Mobile"
|
|
3644
|
-
] })
|
|
3645
|
-
] })
|
|
3646
|
-
}
|
|
3647
|
-
),
|
|
3648
|
-
/* @__PURE__ */ jsxs8(Button7, { type: "button", variant: "outline", size: "sm", onClick: reset, children: [
|
|
3649
|
-
/* @__PURE__ */ jsx9(RotateCcw, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
3650
|
-
"Reset"
|
|
3651
|
-
] })
|
|
3652
|
-
] })
|
|
3653
|
-
] }),
|
|
3654
|
-
/* @__PURE__ */ jsx9("div", { className: "flex justify-center border border-border bg-muted/30 p-6", children: /* @__PURE__ */ jsxs8(
|
|
3655
|
-
"div",
|
|
3656
|
-
{
|
|
3657
|
-
className: `w-full space-y-5 border border-border bg-background p-6 ${device === "mobile" ? "max-w-95" : "max-w-2xl"}`,
|
|
3658
|
-
children: [
|
|
3659
|
-
/* @__PURE__ */ jsxs8("div", { children: [
|
|
3660
|
-
/* @__PURE__ */ jsx9("h3", { className: "text-lg font-semibold text-foreground", children: formData?.name || "Untitled form" }),
|
|
3661
|
-
formData?.description && /* @__PURE__ */ jsx9("p", { className: "mt-1 text-sm text-muted-foreground", children: formData.description })
|
|
3662
|
-
] }),
|
|
3663
|
-
confirmed ? /* @__PURE__ */ jsxs8("div", { className: "space-y-4", children: [
|
|
3664
|
-
settings.confirmationType === "redirect" ? /* @__PURE__ */ jsxs8("p", { className: "border border-border bg-muted/40 p-4 text-sm text-foreground", children: [
|
|
3665
|
-
"The visitor would now be redirected to",
|
|
3666
|
-
" ",
|
|
3667
|
-
/* @__PURE__ */ jsx9("span", { className: "font-mono", children: settings.redirectUrl || "(no redirect URL set)" }),
|
|
3668
|
-
"."
|
|
3669
|
-
] }) : /* @__PURE__ */ jsx9("p", { className: "border border-border bg-muted/40 p-4 text-sm text-foreground", children: settings.successMessage || "Thank you for your submission!" }),
|
|
3670
|
-
/* @__PURE__ */ jsx9(Button7, { type: "button", variant: "outline", onClick: reset, children: "Fill again" })
|
|
3671
|
-
] }) : /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
3672
|
-
visibleFields.length === 0 && /* @__PURE__ */ jsx9("p", { className: "text-sm text-muted-foreground", children: "No visible fields \u2014 add fields in the Builder tab." }),
|
|
3673
|
-
visibleFields.map(
|
|
3674
|
-
(field) => !isKnownFormField(field) ? /* @__PURE__ */ jsx9(PluginFieldPreview, { field }, field.name) : /* @__PURE__ */ jsxs8("div", { className: "space-y-1.5", children: [
|
|
3675
|
-
field.type !== "checkbox" && /* @__PURE__ */ jsxs8(Label3, { htmlFor: `preview-${field.name}`, children: [
|
|
3676
|
-
field.label || field.name,
|
|
3677
|
-
field.required && /* @__PURE__ */ jsx9("span", { className: "ml-1 text-destructive", children: "*" })
|
|
3678
|
-
] }),
|
|
3679
|
-
field.type === "checkbox" ? /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
|
|
3680
|
-
/* @__PURE__ */ jsx9(
|
|
3681
|
-
PreviewFieldInput,
|
|
3682
|
-
{
|
|
3683
|
-
field,
|
|
3684
|
-
value: values[field.name],
|
|
3685
|
-
onChange: (value) => setValues((prev) => ({
|
|
3686
|
-
...prev,
|
|
3687
|
-
[field.name]: value
|
|
3688
|
-
}))
|
|
3689
|
-
}
|
|
3690
|
-
),
|
|
3691
|
-
/* @__PURE__ */ jsxs8(Label3, { htmlFor: `preview-${field.name}`, children: [
|
|
3692
|
-
field.label || field.name,
|
|
3693
|
-
field.required && /* @__PURE__ */ jsx9("span", { className: "ml-1 text-destructive", children: "*" })
|
|
3694
|
-
] })
|
|
3695
|
-
] }) : /* @__PURE__ */ jsx9(
|
|
3696
|
-
PreviewFieldInput,
|
|
3697
|
-
{
|
|
3698
|
-
field,
|
|
3699
|
-
value: values[field.name],
|
|
3700
|
-
onChange: (value) => setValues((prev) => ({ ...prev, [field.name]: value }))
|
|
3701
|
-
}
|
|
3702
|
-
),
|
|
3703
|
-
field.helpText && /* @__PURE__ */ jsx9("p", { className: "text-xs text-muted-foreground", children: field.helpText }),
|
|
3704
|
-
missingRequired.has(field.name) && /* @__PURE__ */ jsx9("p", { className: "text-xs text-destructive", children: "This field is required." })
|
|
3705
|
-
] }, field.name)
|
|
3706
|
-
),
|
|
3707
|
-
/* @__PURE__ */ jsx9(
|
|
3708
|
-
Button7,
|
|
3709
|
-
{
|
|
3710
|
-
type: "button",
|
|
3711
|
-
onClick: simulateSubmit,
|
|
3712
|
-
disabled: visibleFields.length === 0,
|
|
3713
|
-
children: settings.submitButtonText || "Submit"
|
|
3714
|
-
}
|
|
3715
|
-
)
|
|
3716
|
-
] }),
|
|
3717
|
-
hiddenCount > 0 && !confirmed && /* @__PURE__ */ jsxs8("p", { className: "text-xs text-muted-foreground", children: [
|
|
3718
|
-
/* @__PURE__ */ jsx9(
|
|
3719
|
-
Badge4,
|
|
3720
|
-
{
|
|
3721
|
-
variant: "outline",
|
|
3722
|
-
className: "mr-1.5 rounded-none border-border px-1 py-0 text-[10px]",
|
|
3723
|
-
children: hiddenCount
|
|
3724
|
-
}
|
|
3725
|
-
),
|
|
3726
|
-
"hidden ",
|
|
3727
|
-
hiddenCount === 1 ? "field" : "fields",
|
|
3728
|
-
" will be submitted invisibly."
|
|
3729
|
-
] })
|
|
3730
|
-
]
|
|
3731
|
-
}
|
|
3732
|
-
) })
|
|
3733
|
-
] });
|
|
3734
|
-
}
|
|
3735
|
-
|
|
3736
|
-
// src/admin/components/builder/FormSettingsTab.tsx
|
|
3737
|
-
import {
|
|
3738
|
-
Input as Input5,
|
|
3739
|
-
Label as Label4,
|
|
3740
|
-
RadioGroup as RadioGroup2,
|
|
3741
|
-
RadioGroupItem as RadioGroupItem2,
|
|
3742
|
-
Select as Select7,
|
|
3743
|
-
SelectContent as SelectContent7,
|
|
3744
|
-
SelectItem as SelectItem7,
|
|
3745
|
-
SelectTrigger as SelectTrigger7,
|
|
3746
|
-
SelectValue as SelectValue7,
|
|
3747
|
-
Switch as Switch3,
|
|
3748
|
-
Textarea as Textarea3
|
|
3749
|
-
} from "@nextlyhq/ui";
|
|
3750
|
-
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3751
|
-
function SettingRow({
|
|
3752
|
-
label,
|
|
3753
|
-
description,
|
|
3754
|
-
htmlFor,
|
|
3755
|
-
children
|
|
3756
|
-
}) {
|
|
3757
|
-
return /* @__PURE__ */ jsxs9("div", { className: "flex flex-col sm:flex-row sm:items-center justify-between gap-6 py-2", children: [
|
|
3758
|
-
/* @__PURE__ */ jsxs9("div", { className: "space-y-1", children: [
|
|
3759
|
-
/* @__PURE__ */ jsx10(
|
|
3760
|
-
Label4,
|
|
3761
|
-
{
|
|
3762
|
-
htmlFor,
|
|
3763
|
-
className: "text-[13px] font-semibold text-foreground tracking-tight",
|
|
3764
|
-
children: label
|
|
3765
|
-
}
|
|
3766
|
-
),
|
|
3767
|
-
description && /* @__PURE__ */ jsx10("p", { className: "text-[12px] text-muted-foreground leading-relaxed", children: description })
|
|
3768
|
-
] }),
|
|
3769
|
-
/* @__PURE__ */ jsx10("div", { className: "shrink-0 flex items-center justify-end min-w-25", children })
|
|
3770
|
-
] });
|
|
3771
|
-
}
|
|
3772
|
-
function SectionHeading({ children }) {
|
|
3773
|
-
return /* @__PURE__ */ jsx10("div", { className: "border-b border-border pb-3", children: /* @__PURE__ */ jsx10("h3", { className: "text-[16px] font-bold text-foreground tracking-tight", children }) });
|
|
3774
|
-
}
|
|
3775
|
-
function InheritToggle({
|
|
3776
|
-
id,
|
|
3777
|
-
value,
|
|
3778
|
-
inherited,
|
|
3779
|
-
onChange
|
|
3780
|
-
}) {
|
|
3781
|
-
const inheritedLabel = inherited === void 0 ? "Inherit" : `Inherit (${inherited ? "on" : "off"})`;
|
|
3782
|
-
return /* @__PURE__ */ jsxs9(
|
|
3783
|
-
Select7,
|
|
3784
|
-
{
|
|
3785
|
-
value: value === void 0 ? "inherit" : value ? "on" : "off",
|
|
3786
|
-
onValueChange: (selected) => onChange(selected === "inherit" ? void 0 : selected === "on"),
|
|
3787
|
-
children: [
|
|
3788
|
-
/* @__PURE__ */ jsx10(
|
|
3789
|
-
SelectTrigger7,
|
|
3790
|
-
{
|
|
3791
|
-
id,
|
|
3792
|
-
className: "w-40 bg-transparent border-input dark:bg-muted/50",
|
|
3793
|
-
children: /* @__PURE__ */ jsx10(SelectValue7, {})
|
|
3794
|
-
}
|
|
3795
|
-
),
|
|
3796
|
-
/* @__PURE__ */ jsxs9(SelectContent7, { children: [
|
|
3797
|
-
/* @__PURE__ */ jsx10(SelectItem7, { value: "inherit", children: inheritedLabel }),
|
|
3798
|
-
/* @__PURE__ */ jsx10(SelectItem7, { value: "on", children: "On" }),
|
|
3799
|
-
/* @__PURE__ */ jsx10(SelectItem7, { value: "off", children: "Off" })
|
|
3800
|
-
] })
|
|
3801
|
-
]
|
|
3802
|
-
}
|
|
3803
|
-
);
|
|
3804
|
-
}
|
|
3805
|
-
function FormSettingsTab({ spamDefaults }) {
|
|
3806
|
-
const { settings, updateSettings } = useFormBuilder();
|
|
3807
4313
|
return (
|
|
3808
|
-
//
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
4314
|
+
// One surface for the whole tab. The chrome and the stage are the same
|
|
4315
|
+
// panel seen twice, and leaving the chrome unbacked put it on the page's
|
|
4316
|
+
// grey while the stage below it had a frame — reading as a control strip
|
|
4317
|
+
// that had lost its box.
|
|
4318
|
+
/* @__PURE__ */ jsxs10(Card2, { className: "overflow-hidden p-0", children: [
|
|
4319
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-3 border-b border-border px-6 py-4 sm:flex-row sm:items-center sm:justify-between", children: [
|
|
4320
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
4321
|
+
/* @__PURE__ */ jsx11(Eye, { className: "h-4 w-4", "aria-hidden": "true" }),
|
|
4322
|
+
"Preview \u2014 a simulation of this form. Nothing here submits anywhere."
|
|
4323
|
+
] }),
|
|
4324
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [
|
|
4325
|
+
/* @__PURE__ */ jsx11(
|
|
4326
|
+
Tabs3,
|
|
3815
4327
|
{
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
className: "w-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
)
|
|
4328
|
+
value: device,
|
|
4329
|
+
onValueChange: (value) => setDevice(value),
|
|
4330
|
+
children: /* @__PURE__ */ jsxs10(TabsList3, { children: [
|
|
4331
|
+
/* @__PURE__ */ jsxs10(TabsTrigger3, { value: "desktop", className: "gap-1.5", children: [
|
|
4332
|
+
/* @__PURE__ */ jsx11(Monitor, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
4333
|
+
"Desktop"
|
|
4334
|
+
] }),
|
|
4335
|
+
/* @__PURE__ */ jsxs10(TabsTrigger3, { value: "mobile", className: "gap-1.5", children: [
|
|
4336
|
+
/* @__PURE__ */ jsx11(Smartphone, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
4337
|
+
"Mobile"
|
|
4338
|
+
] })
|
|
4339
|
+
] })
|
|
3829
4340
|
}
|
|
3830
4341
|
),
|
|
3831
|
-
/* @__PURE__ */
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
description: "When off, the same visitor (by IP) can submit this form only once. Best-effort: IP-based, so shared networks count as one visitor.",
|
|
3836
|
-
htmlFor: "settings-multiple",
|
|
3837
|
-
children: /* @__PURE__ */ jsx10(
|
|
3838
|
-
Switch3,
|
|
3839
|
-
{
|
|
3840
|
-
id: "settings-multiple",
|
|
3841
|
-
checked: settings.allowMultipleSubmissions ?? true,
|
|
3842
|
-
onCheckedChange: (checked) => updateSettings({ allowMultipleSubmissions: checked })
|
|
3843
|
-
}
|
|
3844
|
-
)
|
|
3845
|
-
}
|
|
3846
|
-
)
|
|
4342
|
+
/* @__PURE__ */ jsxs10(Button7, { type: "button", variant: "outline", size: "sm", onClick: reset, children: [
|
|
4343
|
+
/* @__PURE__ */ jsx11(RotateCcw, { className: "h-3.5 w-3.5", "aria-hidden": "true" }),
|
|
4344
|
+
"Reset"
|
|
4345
|
+
] })
|
|
3847
4346
|
] })
|
|
3848
4347
|
] }),
|
|
3849
|
-
/* @__PURE__ */
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
}),
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
/* @__PURE__ */ jsx10(Label4, { htmlFor: "settings-confirm-message", children: "Show a message" }),
|
|
3873
|
-
(settings.confirmationType ?? "message") === "message" && /* @__PURE__ */ jsx10(
|
|
3874
|
-
Textarea3,
|
|
4348
|
+
/* @__PURE__ */ jsx11("div", { className: "flex justify-center bg-muted/30 p-6", children: /* @__PURE__ */ jsxs10(
|
|
4349
|
+
"div",
|
|
4350
|
+
{
|
|
4351
|
+
className: `w-full space-y-5 border border-border bg-background p-6 ${device === "mobile" ? "max-w-95" : "max-w-2xl"}`,
|
|
4352
|
+
children: [
|
|
4353
|
+
/* @__PURE__ */ jsxs10("div", { children: [
|
|
4354
|
+
/* @__PURE__ */ jsx11("h3", { className: "text-lg font-semibold text-foreground", children: formData?.name || "Untitled form" }),
|
|
4355
|
+
formData?.description && /* @__PURE__ */ jsx11("p", { className: "mt-1 text-sm text-muted-foreground", children: formData.description })
|
|
4356
|
+
] }),
|
|
4357
|
+
confirmed ? /* @__PURE__ */ jsxs10("div", { className: "space-y-4", children: [
|
|
4358
|
+
/* @__PURE__ */ jsx11(Confirmation, { settings }),
|
|
4359
|
+
/* @__PURE__ */ jsx11(Button7, { type: "button", variant: "outline", onClick: reset, children: "Fill again" })
|
|
4360
|
+
] }) : /* @__PURE__ */ jsxs10(Fragment5, { children: [
|
|
4361
|
+
visibleFields.length === 0 && /* @__PURE__ */ jsx11("p", { className: "text-sm text-muted-foreground", children: "No visible fields \u2014 add fields in the Builder tab." }),
|
|
4362
|
+
visibleFields.map(
|
|
4363
|
+
(field) => !isKnownFormField(field) ? /* @__PURE__ */ jsx11(PluginFieldPreview, { field }, field.name) : /* @__PURE__ */ jsxs10("div", { className: "space-y-1.5", children: [
|
|
4364
|
+
field.type !== "checkbox" && /* @__PURE__ */ jsxs10(Label4, { htmlFor: `preview-${field.name}`, children: [
|
|
4365
|
+
field.label || field.name,
|
|
4366
|
+
field.required && /* @__PURE__ */ jsx11("span", { className: "ml-1 text-destructive", children: "*" })
|
|
4367
|
+
] }),
|
|
4368
|
+
field.type === "checkbox" ? /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
|
|
4369
|
+
/* @__PURE__ */ jsx11(
|
|
4370
|
+
PreviewFieldInput,
|
|
3875
4371
|
{
|
|
3876
|
-
|
|
3877
|
-
value:
|
|
3878
|
-
onChange: (
|
|
3879
|
-
|
|
3880
|
-
|
|
4372
|
+
field,
|
|
4373
|
+
value: values[field.name],
|
|
4374
|
+
onChange: (value) => setValues((prev) => ({
|
|
4375
|
+
...prev,
|
|
4376
|
+
[field.name]: value
|
|
4377
|
+
}))
|
|
3881
4378
|
}
|
|
3882
|
-
)
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
4379
|
+
),
|
|
4380
|
+
/* @__PURE__ */ jsxs10(Label4, { htmlFor: `preview-${field.name}`, children: [
|
|
4381
|
+
field.label || field.name,
|
|
4382
|
+
field.required && /* @__PURE__ */ jsx11("span", { className: "ml-1 text-destructive", children: "*" })
|
|
4383
|
+
] })
|
|
4384
|
+
] }) : /* @__PURE__ */ jsx11(
|
|
4385
|
+
PreviewFieldInput,
|
|
3888
4386
|
{
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
4387
|
+
field,
|
|
4388
|
+
value: values[field.name],
|
|
4389
|
+
onChange: (value) => setValues((prev) => ({ ...prev, [field.name]: value }))
|
|
3892
4390
|
}
|
|
3893
4391
|
),
|
|
3894
|
-
/* @__PURE__ */
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
type: "url",
|
|
3901
|
-
value: settings.redirectUrl ?? "",
|
|
3902
|
-
onChange: (e) => updateSettings({
|
|
3903
|
-
redirectUrl: e.target.value || void 0
|
|
3904
|
-
}),
|
|
3905
|
-
placeholder: "https://example.com/thanks"
|
|
3906
|
-
}
|
|
3907
|
-
)
|
|
3908
|
-
] })
|
|
3909
|
-
] })
|
|
3910
|
-
]
|
|
3911
|
-
}
|
|
3912
|
-
)
|
|
3913
|
-
] })
|
|
3914
|
-
] }),
|
|
3915
|
-
/* @__PURE__ */ jsxs9("section", { children: [
|
|
3916
|
-
/* @__PURE__ */ jsx10(SectionHeading, { children: "Spam protection" }),
|
|
3917
|
-
/* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-1 pt-2", children: [
|
|
3918
|
-
/* @__PURE__ */ jsx10(
|
|
3919
|
-
SettingRow,
|
|
3920
|
-
{
|
|
3921
|
-
label: "Honeypot",
|
|
3922
|
-
description: "Invisible trap field for bots. Inherits the plugin default unless overridden here.",
|
|
3923
|
-
htmlFor: "settings-honeypot",
|
|
3924
|
-
children: /* @__PURE__ */ jsx10(
|
|
3925
|
-
InheritToggle,
|
|
3926
|
-
{
|
|
3927
|
-
id: "settings-honeypot",
|
|
3928
|
-
value: settings.honeypotEnabled,
|
|
3929
|
-
inherited: spamDefaults?.honeypot,
|
|
3930
|
-
onChange: (honeypotEnabled) => updateSettings({ honeypotEnabled })
|
|
3931
|
-
}
|
|
3932
|
-
)
|
|
3933
|
-
}
|
|
3934
|
-
),
|
|
3935
|
-
/* @__PURE__ */ jsx10(
|
|
3936
|
-
SettingRow,
|
|
3937
|
-
{
|
|
3938
|
-
label: "reCAPTCHA",
|
|
3939
|
-
description: "Challenge-based bot check. Inherits the plugin default unless overridden here.",
|
|
3940
|
-
htmlFor: "settings-captcha",
|
|
3941
|
-
children: /* @__PURE__ */ jsx10(
|
|
3942
|
-
InheritToggle,
|
|
4392
|
+
field.helpText && /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: field.helpText }),
|
|
4393
|
+
missingRequired.has(field.name) && /* @__PURE__ */ jsx11("p", { className: "text-xs text-destructive", children: "This field is required." })
|
|
4394
|
+
] }, field.name)
|
|
4395
|
+
),
|
|
4396
|
+
/* @__PURE__ */ jsx11(
|
|
4397
|
+
Button7,
|
|
3943
4398
|
{
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
4399
|
+
type: "button",
|
|
4400
|
+
onClick: simulateSubmit,
|
|
4401
|
+
disabled: visibleFields.length === 0,
|
|
4402
|
+
children: settings.submitButtonText || "Submit"
|
|
3948
4403
|
}
|
|
3949
4404
|
)
|
|
3950
|
-
}
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
{
|
|
3955
|
-
label: "reCAPTCHA site key",
|
|
3956
|
-
description: "The client-facing site key for this form",
|
|
3957
|
-
htmlFor: "settings-captcha-key",
|
|
3958
|
-
children: /* @__PURE__ */ jsx10(
|
|
3959
|
-
Input5,
|
|
4405
|
+
] }),
|
|
4406
|
+
hiddenCount > 0 && !confirmed && /* @__PURE__ */ jsxs10("p", { className: "text-xs text-muted-foreground", children: [
|
|
4407
|
+
/* @__PURE__ */ jsx11(
|
|
4408
|
+
Badge4,
|
|
3960
4409
|
{
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
value: settings.captchaSiteKey ?? "",
|
|
3965
|
-
onChange: (e) => updateSettings({
|
|
3966
|
-
captchaSiteKey: e.target.value || void 0
|
|
3967
|
-
})
|
|
4410
|
+
variant: "outline",
|
|
4411
|
+
className: "mr-1.5 rounded-none border-border px-1 py-0 text-[10px]",
|
|
4412
|
+
children: hiddenCount
|
|
3968
4413
|
}
|
|
3969
|
-
)
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
4414
|
+
),
|
|
4415
|
+
"hidden ",
|
|
4416
|
+
hiddenCount === 1 ? "field" : "fields",
|
|
4417
|
+
" will be submitted invisibly."
|
|
4418
|
+
] })
|
|
4419
|
+
]
|
|
4420
|
+
}
|
|
4421
|
+
) })
|
|
3974
4422
|
] })
|
|
3975
4423
|
);
|
|
3976
4424
|
}
|
|
3977
4425
|
|
|
3978
4426
|
// src/admin/FormBuilderView.tsx
|
|
3979
|
-
import { Fragment as
|
|
4427
|
+
import { Fragment as Fragment6, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3980
4428
|
function slugify(text) {
|
|
3981
4429
|
return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3982
4430
|
}
|
|
@@ -4003,15 +4451,17 @@ function FormBuilderViewInner({
|
|
|
4003
4451
|
markAsSaved
|
|
4004
4452
|
} = useFormBuilder();
|
|
4005
4453
|
const queryClient = useQueryClient();
|
|
4006
|
-
const [isSaving, setIsSaving] =
|
|
4007
|
-
const [saveError, setSaveError] =
|
|
4008
|
-
const [enabledTypes, setEnabledTypes] =
|
|
4009
|
-
const [disabledFieldTypes, setDisabledFieldTypes] =
|
|
4454
|
+
const [isSaving, setIsSaving] = useState10(false);
|
|
4455
|
+
const [saveError, setSaveError] = useState10(null);
|
|
4456
|
+
const [enabledTypes, setEnabledTypes] = useState10(null);
|
|
4457
|
+
const [disabledFieldTypes, setDisabledFieldTypes] = useState10(
|
|
4010
4458
|
() => /* @__PURE__ */ new Set()
|
|
4011
4459
|
);
|
|
4012
|
-
const [notificationDefaults, setNotificationDefaults] =
|
|
4013
|
-
const [spamDefaults, setSpamDefaults] =
|
|
4014
|
-
|
|
4460
|
+
const [notificationDefaults, setNotificationDefaults] = useState10(null);
|
|
4461
|
+
const [spamDefaults, setSpamDefaults] = useState10(null);
|
|
4462
|
+
const [redirectCollections, setRedirectCollections] = useState10(null);
|
|
4463
|
+
const [redirectConfigFailed, setRedirectConfigFailed] = useState10(false);
|
|
4464
|
+
useEffect5(() => {
|
|
4015
4465
|
let cancelled = false;
|
|
4016
4466
|
const allTypes = FORM_FIELD_TYPE_CATALOG3.map((entry) => entry.type);
|
|
4017
4467
|
void fetch(
|
|
@@ -4030,19 +4480,23 @@ function FormBuilderViewInner({
|
|
|
4030
4480
|
);
|
|
4031
4481
|
setNotificationDefaults(config?.notifications ?? {});
|
|
4032
4482
|
setSpamDefaults(config?.spamProtection ?? {});
|
|
4483
|
+
setRedirectConfigFailed(config === null);
|
|
4484
|
+
setRedirectCollections(config?.redirectCollections ?? []);
|
|
4033
4485
|
}
|
|
4034
4486
|
).catch(() => {
|
|
4035
4487
|
if (cancelled) return;
|
|
4036
4488
|
setEnabledTypes(allTypes);
|
|
4037
4489
|
setNotificationDefaults({});
|
|
4038
4490
|
setSpamDefaults({});
|
|
4491
|
+
setRedirectConfigFailed(true);
|
|
4492
|
+
setRedirectCollections([]);
|
|
4039
4493
|
});
|
|
4040
4494
|
return () => {
|
|
4041
4495
|
cancelled = true;
|
|
4042
4496
|
};
|
|
4043
4497
|
}, []);
|
|
4044
4498
|
const seededRef = useRef2(false);
|
|
4045
|
-
|
|
4499
|
+
useEffect5(() => {
|
|
4046
4500
|
if (!isCreating || notificationDefaults === null || seededRef.current) {
|
|
4047
4501
|
return;
|
|
4048
4502
|
}
|
|
@@ -4071,6 +4525,11 @@ function FormBuilderViewInner({
|
|
|
4071
4525
|
toast.error("Please add at least one field to the form");
|
|
4072
4526
|
return;
|
|
4073
4527
|
}
|
|
4528
|
+
const blocked = notificationsBlockingSave(notifications);
|
|
4529
|
+
if (blocked) {
|
|
4530
|
+
toast.error(blocked);
|
|
4531
|
+
return;
|
|
4532
|
+
}
|
|
4074
4533
|
const { id: _id, ...formDataWithoutId } = formData;
|
|
4075
4534
|
const saveData = {
|
|
4076
4535
|
...formDataWithoutId,
|
|
@@ -4093,9 +4552,11 @@ function FormBuilderViewInner({
|
|
|
4093
4552
|
body: JSON.stringify(saveData)
|
|
4094
4553
|
});
|
|
4095
4554
|
if (!response.ok) {
|
|
4096
|
-
const err = await response.json().catch(() => ({}));
|
|
4097
4555
|
throw new Error(
|
|
4098
|
-
|
|
4556
|
+
saveErrorMessage(
|
|
4557
|
+
await response.json().catch(() => ({})),
|
|
4558
|
+
response.statusText
|
|
4559
|
+
)
|
|
4099
4560
|
);
|
|
4100
4561
|
}
|
|
4101
4562
|
const result = await response.json();
|
|
@@ -4165,103 +4626,119 @@ function FormBuilderViewInner({
|
|
|
4165
4626
|
count: notifications.length > 0 ? notifications.length : null
|
|
4166
4627
|
}
|
|
4167
4628
|
];
|
|
4168
|
-
return /* @__PURE__ */
|
|
4169
|
-
/* @__PURE__ */
|
|
4170
|
-
/* @__PURE__ */
|
|
4171
|
-
/* @__PURE__ */
|
|
4629
|
+
return /* @__PURE__ */ jsxs11(Fragment6, { children: [
|
|
4630
|
+
/* @__PURE__ */ jsxs11("div", { className: "mb-6", children: [
|
|
4631
|
+
/* @__PURE__ */ jsx12("h1", { className: "text-2xl font-semibold text-foreground tracking-tight", children: isCreating ? "Create Form" : "Edit Form" }),
|
|
4632
|
+
/* @__PURE__ */ jsx12("p", { className: "text-sm text-muted-foreground mt-1", children: isCreating ? "Design a new form with drag-and-drop fields." : "Modify your form fields and settings." })
|
|
4172
4633
|
] }),
|
|
4173
|
-
/* @__PURE__ */
|
|
4174
|
-
/* @__PURE__ */
|
|
4175
|
-
/* @__PURE__ */
|
|
4634
|
+
/* @__PURE__ */ jsxs11(Card3, { className: "mb-6 px-6 pt-5 pb-6", children: [
|
|
4635
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap gap-4", children: [
|
|
4636
|
+
/* @__PURE__ */ jsx12(
|
|
4176
4637
|
FieldShell2,
|
|
4177
4638
|
{
|
|
4178
4639
|
label: "Form Name",
|
|
4179
4640
|
htmlFor: "form-name",
|
|
4180
4641
|
className: "flex-1 min-w-[200px]",
|
|
4181
|
-
children: /* @__PURE__ */
|
|
4182
|
-
|
|
4642
|
+
children: /* @__PURE__ */ jsx12(
|
|
4643
|
+
Input7,
|
|
4183
4644
|
{
|
|
4184
4645
|
type: "text",
|
|
4185
4646
|
value: formData.name || "",
|
|
4186
4647
|
onChange: (e) => handleNameChange(e.target.value),
|
|
4187
|
-
placeholder: "e.g., Contact Form"
|
|
4188
|
-
className: "bg-transparent"
|
|
4648
|
+
placeholder: "e.g., Contact Form"
|
|
4189
4649
|
}
|
|
4190
4650
|
)
|
|
4191
4651
|
}
|
|
4192
4652
|
),
|
|
4193
|
-
/* @__PURE__ */
|
|
4653
|
+
/* @__PURE__ */ jsx12(
|
|
4194
4654
|
FieldShell2,
|
|
4195
4655
|
{
|
|
4196
4656
|
label: "Slug",
|
|
4197
4657
|
htmlFor: "form-slug",
|
|
4198
4658
|
className: "flex-1 min-w-[160px]",
|
|
4199
|
-
children: /* @__PURE__ */
|
|
4200
|
-
|
|
4659
|
+
children: /* @__PURE__ */ jsx12(
|
|
4660
|
+
Input7,
|
|
4201
4661
|
{
|
|
4202
4662
|
type: "text",
|
|
4203
4663
|
value: formData.slug || "",
|
|
4204
4664
|
onChange: (e) => updateFormData({ slug: e.target.value }),
|
|
4205
4665
|
placeholder: "e.g., contact-form",
|
|
4206
|
-
className: "
|
|
4666
|
+
className: "placeholder:text-muted-foreground"
|
|
4207
4667
|
}
|
|
4208
4668
|
)
|
|
4209
4669
|
}
|
|
4210
4670
|
),
|
|
4211
|
-
/* @__PURE__ */
|
|
4212
|
-
|
|
4671
|
+
/* @__PURE__ */ jsx12("div", { className: "w-36", children: /* @__PURE__ */ jsx12(FieldShell2, { label: "Status", htmlFor: "form-status", children: ({ id, describedBy, invalid }) => /* @__PURE__ */ jsxs11(
|
|
4672
|
+
Select9,
|
|
4213
4673
|
{
|
|
4214
4674
|
value: formData.status || "draft",
|
|
4215
4675
|
onValueChange: (value) => updateFormData({
|
|
4216
4676
|
status: value
|
|
4217
4677
|
}),
|
|
4218
4678
|
children: [
|
|
4219
|
-
/* @__PURE__ */
|
|
4220
|
-
|
|
4679
|
+
/* @__PURE__ */ jsx12(
|
|
4680
|
+
SelectTrigger9,
|
|
4221
4681
|
{
|
|
4222
4682
|
id,
|
|
4223
4683
|
"aria-describedby": describedBy,
|
|
4224
4684
|
"aria-invalid": invalid,
|
|
4225
|
-
|
|
4226
|
-
children: /* @__PURE__ */ jsx11(SelectValue8, { placeholder: "Status" })
|
|
4685
|
+
children: /* @__PURE__ */ jsx12(SelectValue9, { placeholder: "Status" })
|
|
4227
4686
|
}
|
|
4228
4687
|
),
|
|
4229
|
-
/* @__PURE__ */
|
|
4230
|
-
/* @__PURE__ */
|
|
4231
|
-
/* @__PURE__ */
|
|
4232
|
-
/* @__PURE__ */
|
|
4688
|
+
/* @__PURE__ */ jsxs11(SelectContent9, { children: [
|
|
4689
|
+
/* @__PURE__ */ jsx12(SelectItem9, { value: "draft", children: "Draft" }),
|
|
4690
|
+
/* @__PURE__ */ jsx12(SelectItem9, { value: "published", children: "Published" }),
|
|
4691
|
+
/* @__PURE__ */ jsx12(SelectItem9, { value: "closed", children: "Closed" })
|
|
4233
4692
|
] })
|
|
4234
4693
|
]
|
|
4235
4694
|
}
|
|
4236
4695
|
) }) })
|
|
4237
4696
|
] }),
|
|
4238
|
-
/* @__PURE__ */
|
|
4239
|
-
|
|
4697
|
+
formData.status === "closed" && /* @__PURE__ */ jsx12("div", { className: "mt-4", children: /* @__PURE__ */ jsx12(
|
|
4698
|
+
FieldShell2,
|
|
4240
4699
|
{
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4700
|
+
label: "Message for visitors",
|
|
4701
|
+
htmlFor: "form-closed-message",
|
|
4702
|
+
width: "full",
|
|
4703
|
+
description: "Shown to anyone who reaches this form now that it is closed. Left empty, they are told only that it is not accepting submissions.",
|
|
4704
|
+
children: /* @__PURE__ */ jsx12(
|
|
4705
|
+
Textarea4,
|
|
4245
4706
|
{
|
|
4246
|
-
value:
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
{
|
|
4253
|
-
className: `inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 text-[10px] font-semibold rounded-sm ml-2 transition-colors ${activeTab === tab.value ? "bg-primary/5 text-primary" : "bg-primary/5 text-muted-foreground"}`,
|
|
4254
|
-
children: tab.count
|
|
4255
|
-
}
|
|
4256
|
-
)
|
|
4257
|
-
]
|
|
4258
|
-
},
|
|
4259
|
-
tab.value
|
|
4260
|
-
)) })
|
|
4707
|
+
value: formData.closedMessage ?? "",
|
|
4708
|
+
onChange: (e) => updateFormData({ closedMessage: e.target.value }),
|
|
4709
|
+
placeholder: "e.g., Applications closed on 31 March. Thank you to everyone who applied.",
|
|
4710
|
+
rows: 3
|
|
4711
|
+
}
|
|
4712
|
+
)
|
|
4261
4713
|
}
|
|
4262
|
-
)
|
|
4714
|
+
) })
|
|
4263
4715
|
] }),
|
|
4264
|
-
|
|
4716
|
+
/* @__PURE__ */ jsx12("div", { className: "border-b border-border mb-6", children: /* @__PURE__ */ jsx12(
|
|
4717
|
+
Tabs4,
|
|
4718
|
+
{
|
|
4719
|
+
value: activeTab,
|
|
4720
|
+
onValueChange: (v) => setActiveTab(v),
|
|
4721
|
+
children: /* @__PURE__ */ jsx12(TabsList4, { scrollable: true, className: "bg-transparent justify-start gap-0", children: mainTabs.map((tab) => /* @__PURE__ */ jsxs11(
|
|
4722
|
+
TabsTrigger4,
|
|
4723
|
+
{
|
|
4724
|
+
value: tab.value,
|
|
4725
|
+
className: "shrink-0 whitespace-nowrap",
|
|
4726
|
+
children: [
|
|
4727
|
+
tab.label,
|
|
4728
|
+
tab.count !== null && /* @__PURE__ */ jsx12(
|
|
4729
|
+
"span",
|
|
4730
|
+
{
|
|
4731
|
+
className: `inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 text-[10px] font-semibold rounded-sm ml-2 transition-colors ${activeTab === tab.value ? "bg-primary/5 text-primary" : "bg-primary/5 text-muted-foreground"}`,
|
|
4732
|
+
children: tab.count
|
|
4733
|
+
}
|
|
4734
|
+
)
|
|
4735
|
+
]
|
|
4736
|
+
},
|
|
4737
|
+
tab.value
|
|
4738
|
+
)) })
|
|
4739
|
+
}
|
|
4740
|
+
) }),
|
|
4741
|
+
activeTab === "builder" && /* @__PURE__ */ jsx12(
|
|
4265
4742
|
FieldCards,
|
|
4266
4743
|
{
|
|
4267
4744
|
enabledTypes,
|
|
@@ -4269,13 +4746,20 @@ function FormBuilderViewInner({
|
|
|
4269
4746
|
onAddField: handleAddField
|
|
4270
4747
|
}
|
|
4271
4748
|
),
|
|
4272
|
-
activeTab === "preview" && /* @__PURE__ */
|
|
4273
|
-
activeTab === "settings" && /* @__PURE__ */
|
|
4274
|
-
|
|
4749
|
+
activeTab === "preview" && /* @__PURE__ */ jsx12(FormPreview, { fields, formData }),
|
|
4750
|
+
activeTab === "settings" && /* @__PURE__ */ jsx12(
|
|
4751
|
+
FormSettingsTab,
|
|
4752
|
+
{
|
|
4753
|
+
spamDefaults,
|
|
4754
|
+
redirectCollections,
|
|
4755
|
+
redirectConfigFailed
|
|
4756
|
+
}
|
|
4757
|
+
),
|
|
4758
|
+
activeTab === "notifications" && /* @__PURE__ */ jsx12(FormNotificationsTab, { defaults: notificationDefaults }),
|
|
4275
4759
|
saveError && // Full-strength destructive border so the error box boundary is perceivable over its tinted fill.
|
|
4276
|
-
/* @__PURE__ */
|
|
4277
|
-
/* @__PURE__ */
|
|
4278
|
-
/* @__PURE__ */
|
|
4760
|
+
/* @__PURE__ */ jsx12("div", { className: "mt-6 p-3 text-sm text-destructive bg-destructive/10 border border-destructive rounded-none", children: saveError }),
|
|
4761
|
+
/* @__PURE__ */ jsxs11(FormActions, { dirty: isDirty, children: [
|
|
4762
|
+
/* @__PURE__ */ jsx12(
|
|
4279
4763
|
Button8,
|
|
4280
4764
|
{
|
|
4281
4765
|
type: "button",
|
|
@@ -4286,7 +4770,7 @@ function FormBuilderViewInner({
|
|
|
4286
4770
|
children: "Cancel"
|
|
4287
4771
|
}
|
|
4288
4772
|
),
|
|
4289
|
-
/* @__PURE__ */
|
|
4773
|
+
/* @__PURE__ */ jsx12(
|
|
4290
4774
|
Button8,
|
|
4291
4775
|
{
|
|
4292
4776
|
type: "button",
|
|
@@ -4296,15 +4780,15 @@ function FormBuilderViewInner({
|
|
|
4296
4780
|
},
|
|
4297
4781
|
disabled: isSaving,
|
|
4298
4782
|
className: "flex items-center gap-1.5",
|
|
4299
|
-
children: isSaving ? /* @__PURE__ */
|
|
4300
|
-
/* @__PURE__ */
|
|
4783
|
+
children: isSaving ? /* @__PURE__ */ jsxs11(Fragment6, { children: [
|
|
4784
|
+
/* @__PURE__ */ jsxs11(
|
|
4301
4785
|
"svg",
|
|
4302
4786
|
{
|
|
4303
4787
|
className: "animate-spin h-3.5 w-3.5",
|
|
4304
4788
|
viewBox: "0 0 24 24",
|
|
4305
4789
|
fill: "none",
|
|
4306
4790
|
children: [
|
|
4307
|
-
/* @__PURE__ */
|
|
4791
|
+
/* @__PURE__ */ jsx12(
|
|
4308
4792
|
"circle",
|
|
4309
4793
|
{
|
|
4310
4794
|
className: "opacity-25",
|
|
@@ -4315,7 +4799,7 @@ function FormBuilderViewInner({
|
|
|
4315
4799
|
strokeWidth: "4"
|
|
4316
4800
|
}
|
|
4317
4801
|
),
|
|
4318
|
-
/* @__PURE__ */
|
|
4802
|
+
/* @__PURE__ */ jsx12(
|
|
4319
4803
|
"path",
|
|
4320
4804
|
{
|
|
4321
4805
|
className: "opacity-75",
|
|
@@ -4333,6 +4817,11 @@ function FormBuilderViewInner({
|
|
|
4333
4817
|
] })
|
|
4334
4818
|
] });
|
|
4335
4819
|
}
|
|
4820
|
+
function saveErrorMessage(body, statusText) {
|
|
4821
|
+
const error = body?.error;
|
|
4822
|
+
const fieldIssues = (error?.data?.errors ?? []).map((issue) => issue.message).filter((message) => Boolean(message));
|
|
4823
|
+
return fieldIssues.join(" ") || error?.message || `Failed to save form: ${statusText}`;
|
|
4824
|
+
}
|
|
4336
4825
|
function FormBuilderView({
|
|
4337
4826
|
id,
|
|
4338
4827
|
entryId,
|
|
@@ -4348,7 +4837,7 @@ function FormBuilderView({
|
|
|
4348
4837
|
const resolvedCollectionSlug = collectionSlug || collection || "forms";
|
|
4349
4838
|
const resolvedIsCreating = isCreating ?? !resolvedEntryId;
|
|
4350
4839
|
const providerKey = resolvedEntryId || "new";
|
|
4351
|
-
return /* @__PURE__ */
|
|
4840
|
+
return /* @__PURE__ */ jsx12(
|
|
4352
4841
|
FormBuilderProvider,
|
|
4353
4842
|
{
|
|
4354
4843
|
initialData: {
|
|
@@ -4357,11 +4846,12 @@ function FormBuilderView({
|
|
|
4357
4846
|
slug: initialData?.slug,
|
|
4358
4847
|
description: initialData?.description,
|
|
4359
4848
|
status: initialData?.status,
|
|
4849
|
+
closedMessage: initialData?.closedMessage,
|
|
4360
4850
|
fields: initialData?.fields,
|
|
4361
4851
|
settings: initialData?.settings,
|
|
4362
4852
|
notifications: initialData?.notifications
|
|
4363
4853
|
},
|
|
4364
|
-
children: /* @__PURE__ */
|
|
4854
|
+
children: /* @__PURE__ */ jsx12(
|
|
4365
4855
|
FormBuilderViewInner,
|
|
4366
4856
|
{
|
|
4367
4857
|
isCreating: resolvedIsCreating,
|