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