@nextlyhq/plugin-form-builder 0.0.1
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/LICENSE +22 -0
- package/README.md +122 -0
- package/dist/CheckboxFieldEditor-K4T6QX52.js +121 -0
- package/dist/CheckboxFieldEditor-K4T6QX52.js.map +1 -0
- package/dist/DateFieldEditor-JFJFAVOH.js +187 -0
- package/dist/DateFieldEditor-JFJFAVOH.js.map +1 -0
- package/dist/EmailFieldEditor-MMOXXL7P.js +138 -0
- package/dist/EmailFieldEditor-MMOXXL7P.js.map +1 -0
- package/dist/NumberFieldEditor-UISAYR6Z.js +175 -0
- package/dist/NumberFieldEditor-UISAYR6Z.js.map +1 -0
- package/dist/RadioFieldEditor-6UQFH4WT.js +165 -0
- package/dist/RadioFieldEditor-6UQFH4WT.js.map +1 -0
- package/dist/SelectFieldEditor-PSO4KHV4.js +213 -0
- package/dist/SelectFieldEditor-PSO4KHV4.js.map +1 -0
- package/dist/TextFieldEditor-5EU64EA5.js +187 -0
- package/dist/TextFieldEditor-5EU64EA5.js.map +1 -0
- package/dist/TextareaFieldEditor-773G3LNM.js +190 -0
- package/dist/TextareaFieldEditor-773G3LNM.js.map +1 -0
- package/dist/admin/index.css +934 -0
- package/dist/admin/index.css.map +1 -0
- package/dist/admin/index.d.ts +547 -0
- package/dist/admin/index.js +4030 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/chunk-ABNZFTQK.js +586 -0
- package/dist/chunk-ABNZFTQK.js.map +1 -0
- package/dist/chunk-XTCEPJ7B.js +256 -0
- package/dist/chunk-XTCEPJ7B.js.map +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.js +9 -0
- package/dist/components/index.js.map +1 -0
- package/dist/index-C0kBvYjm.d.ts +1012 -0
- package/dist/index.d.ts +1663 -0
- package/dist/index.js +2470 -0
- package/dist/index.js.map +1 -0
- package/dist/styles/form-builder.css +1150 -0
- package/dist/styles/submissions-filter.css +75 -0
- package/package.json +96 -0
|
@@ -0,0 +1,4030 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SubmissionDetail,
|
|
3
|
+
SubmissionList
|
|
4
|
+
} from "../chunk-ABNZFTQK.js";
|
|
5
|
+
|
|
6
|
+
// src/admin/index.ts
|
|
7
|
+
import {
|
|
8
|
+
registerComponents,
|
|
9
|
+
registerKnownPlugin
|
|
10
|
+
} from "@nextlyhq/admin/lib/component-registry";
|
|
11
|
+
|
|
12
|
+
// src/admin/components/SubmissionsFilter.tsx
|
|
13
|
+
import { useRouter, useSearchParams } from "next/navigation";
|
|
14
|
+
import { useCallback, useEffect, useState } from "react";
|
|
15
|
+
import "@nextlyhq/plugin-form-builder/styles/submissions-filter.css";
|
|
16
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
17
|
+
function SubmissionsFilter({
|
|
18
|
+
collectionSlug
|
|
19
|
+
}) {
|
|
20
|
+
const router = useRouter();
|
|
21
|
+
const searchParams = useSearchParams();
|
|
22
|
+
const [forms, setForms] = useState([]);
|
|
23
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
24
|
+
const [selectedFormId, setSelectedFormId] = useState("");
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const whereParam = searchParams.get("where");
|
|
27
|
+
if (whereParam) {
|
|
28
|
+
try {
|
|
29
|
+
const where = JSON.parse(whereParam);
|
|
30
|
+
if (where.form?.equals) {
|
|
31
|
+
setSelectedFormId(where.form.equals);
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}, [searchParams]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
async function fetchForms() {
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch("/admin/api/collections/forms/entries");
|
|
41
|
+
if (response.ok) {
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
setForms(data.items ?? []);
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error("Failed to fetch forms:", error);
|
|
47
|
+
} finally {
|
|
48
|
+
setIsLoading(false);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
void fetchForms();
|
|
52
|
+
}, []);
|
|
53
|
+
const handleFormChange = useCallback(
|
|
54
|
+
(formId) => {
|
|
55
|
+
setSelectedFormId(formId);
|
|
56
|
+
const params = new URLSearchParams(searchParams.toString());
|
|
57
|
+
if (formId) {
|
|
58
|
+
const where = JSON.stringify({ form: { equals: formId } });
|
|
59
|
+
params.set("where", where);
|
|
60
|
+
} else {
|
|
61
|
+
params.delete("where");
|
|
62
|
+
}
|
|
63
|
+
params.delete("page");
|
|
64
|
+
router.push(`/admin/collections/${collectionSlug}?${params.toString()}`);
|
|
65
|
+
},
|
|
66
|
+
[router, searchParams, collectionSlug]
|
|
67
|
+
);
|
|
68
|
+
if (collectionSlug !== "form-submissions") {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return /* @__PURE__ */ jsx("div", { className: "submissions-filter", children: /* @__PURE__ */ jsxs("div", { className: "submissions-filter-container", children: [
|
|
72
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "form-filter", className: "submissions-filter-label", children: "Filter by Form:" }),
|
|
73
|
+
/* @__PURE__ */ jsxs(
|
|
74
|
+
"select",
|
|
75
|
+
{
|
|
76
|
+
id: "form-filter",
|
|
77
|
+
value: selectedFormId,
|
|
78
|
+
onChange: (e) => handleFormChange(e.target.value),
|
|
79
|
+
className: "submissions-filter-select",
|
|
80
|
+
disabled: isLoading,
|
|
81
|
+
children: [
|
|
82
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "All Forms" }),
|
|
83
|
+
forms.map((form) => /* @__PURE__ */ jsx("option", { value: form.id, children: form.name }, form.id))
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
selectedFormId && /* @__PURE__ */ jsx(
|
|
88
|
+
"button",
|
|
89
|
+
{
|
|
90
|
+
type: "button",
|
|
91
|
+
onClick: () => handleFormChange(""),
|
|
92
|
+
className: "submissions-filter-clear",
|
|
93
|
+
title: "Clear filter",
|
|
94
|
+
children: "Clear"
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
] }) });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/admin/FormBuilderView.tsx
|
|
101
|
+
import {
|
|
102
|
+
DndContext,
|
|
103
|
+
closestCenter,
|
|
104
|
+
KeyboardSensor,
|
|
105
|
+
PointerSensor,
|
|
106
|
+
useSensor,
|
|
107
|
+
useSensors
|
|
108
|
+
} from "@dnd-kit/core";
|
|
109
|
+
import {
|
|
110
|
+
SortableContext,
|
|
111
|
+
sortableKeyboardCoordinates,
|
|
112
|
+
verticalListSortingStrategy
|
|
113
|
+
} from "@dnd-kit/sortable";
|
|
114
|
+
import {
|
|
115
|
+
Button as Button5,
|
|
116
|
+
Input as Input6,
|
|
117
|
+
toast,
|
|
118
|
+
Tabs as Tabs3,
|
|
119
|
+
TabsList as TabsList3,
|
|
120
|
+
TabsTrigger as TabsTrigger3,
|
|
121
|
+
Select as Select3,
|
|
122
|
+
SelectTrigger as SelectTrigger3,
|
|
123
|
+
SelectValue as SelectValue3,
|
|
124
|
+
SelectContent as SelectContent3,
|
|
125
|
+
SelectItem as SelectItem3
|
|
126
|
+
} from "@nextlyhq/ui";
|
|
127
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
128
|
+
import { useCallback as useCallback6, useState as useState6 } from "react";
|
|
129
|
+
|
|
130
|
+
// src/admin/components/builder/FieldEditor.tsx
|
|
131
|
+
import { FormLabelWithTooltip as FormLabelWithTooltip2 } from "@nextlyhq/admin";
|
|
132
|
+
import {
|
|
133
|
+
Input as Input2,
|
|
134
|
+
Button as Button2,
|
|
135
|
+
Checkbox as Checkbox2,
|
|
136
|
+
Tabs,
|
|
137
|
+
TabsContent,
|
|
138
|
+
TabsList,
|
|
139
|
+
TabsTrigger,
|
|
140
|
+
Select as Select2,
|
|
141
|
+
SelectContent as SelectContent2,
|
|
142
|
+
SelectItem as SelectItem2,
|
|
143
|
+
SelectTrigger as SelectTrigger2,
|
|
144
|
+
SelectValue as SelectValue2
|
|
145
|
+
} from "@nextlyhq/ui";
|
|
146
|
+
import { useState as useState2, useCallback as useCallback3, useMemo as useMemo2 } from "react";
|
|
147
|
+
|
|
148
|
+
// src/admin/components/builder/ConditionalLogicEditor.tsx
|
|
149
|
+
import { FormLabelWithTooltip } from "@nextlyhq/admin";
|
|
150
|
+
import {
|
|
151
|
+
Input,
|
|
152
|
+
Button,
|
|
153
|
+
Checkbox,
|
|
154
|
+
Select,
|
|
155
|
+
SelectContent,
|
|
156
|
+
SelectItem,
|
|
157
|
+
SelectTrigger,
|
|
158
|
+
SelectValue
|
|
159
|
+
} from "@nextlyhq/ui";
|
|
160
|
+
import { useCallback as useCallback2, useMemo } from "react";
|
|
161
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
162
|
+
var COMPARISON_OPERATORS = [
|
|
163
|
+
{ value: "equals", label: "Equals" },
|
|
164
|
+
{ value: "notEquals", label: "Does not equal" },
|
|
165
|
+
{ value: "contains", label: "Contains" },
|
|
166
|
+
{ value: "isEmpty", label: "Is empty" },
|
|
167
|
+
{ value: "isNotEmpty", label: "Is not empty" },
|
|
168
|
+
{ value: "greaterThan", label: "Greater than" },
|
|
169
|
+
{ value: "lessThan", label: "Less than" }
|
|
170
|
+
];
|
|
171
|
+
function conditionValueToString(value) {
|
|
172
|
+
if (value === null || value === void 0) return "";
|
|
173
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
174
|
+
return String(value);
|
|
175
|
+
}
|
|
176
|
+
function ConditionalLogicEditor({
|
|
177
|
+
field,
|
|
178
|
+
allFields,
|
|
179
|
+
onUpdate
|
|
180
|
+
}) {
|
|
181
|
+
const logic = useMemo(
|
|
182
|
+
() => field.conditionalLogic || {
|
|
183
|
+
enabled: false,
|
|
184
|
+
action: "show",
|
|
185
|
+
operator: "AND",
|
|
186
|
+
conditions: []
|
|
187
|
+
},
|
|
188
|
+
[field.conditionalLogic]
|
|
189
|
+
);
|
|
190
|
+
const availableFields = allFields.filter((f) => f.name !== field.name);
|
|
191
|
+
const updateLogic = useCallback2(
|
|
192
|
+
(updates) => {
|
|
193
|
+
onUpdate({
|
|
194
|
+
conditionalLogic: { ...logic, ...updates }
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
[logic, onUpdate]
|
|
198
|
+
);
|
|
199
|
+
const handleToggleEnabled = useCallback2(() => {
|
|
200
|
+
updateLogic({ enabled: !logic.enabled });
|
|
201
|
+
}, [logic.enabled, updateLogic]);
|
|
202
|
+
const handleAddCondition = useCallback2(() => {
|
|
203
|
+
const firstField = availableFields[0];
|
|
204
|
+
if (!firstField) return;
|
|
205
|
+
const newCondition = {
|
|
206
|
+
field: firstField.name,
|
|
207
|
+
comparison: "equals",
|
|
208
|
+
value: ""
|
|
209
|
+
};
|
|
210
|
+
updateLogic({
|
|
211
|
+
conditions: [...logic.conditions, newCondition]
|
|
212
|
+
});
|
|
213
|
+
}, [availableFields, logic.conditions, updateLogic]);
|
|
214
|
+
const handleUpdateCondition = useCallback2(
|
|
215
|
+
(index, updates) => {
|
|
216
|
+
const newConditions = logic.conditions.map(
|
|
217
|
+
(c, i) => i === index ? { ...c, ...updates } : c
|
|
218
|
+
);
|
|
219
|
+
updateLogic({ conditions: newConditions });
|
|
220
|
+
},
|
|
221
|
+
[logic.conditions, updateLogic]
|
|
222
|
+
);
|
|
223
|
+
const handleRemoveCondition = useCallback2(
|
|
224
|
+
(index) => {
|
|
225
|
+
const newConditions = logic.conditions.filter((_, i) => i !== index);
|
|
226
|
+
updateLogic({ conditions: newConditions });
|
|
227
|
+
},
|
|
228
|
+
[logic.conditions, updateLogic]
|
|
229
|
+
);
|
|
230
|
+
const needsValue = (comparison) => {
|
|
231
|
+
return comparison !== "isEmpty" && comparison !== "isNotEmpty";
|
|
232
|
+
};
|
|
233
|
+
return /* @__PURE__ */ jsxs2("div", { className: "space-y-6 pt-2", children: [
|
|
234
|
+
/* @__PURE__ */ jsx2("div", { className: "space-y-2", children: /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3", children: [
|
|
235
|
+
/* @__PURE__ */ jsx2(
|
|
236
|
+
Checkbox,
|
|
237
|
+
{
|
|
238
|
+
id: "logic-enabled",
|
|
239
|
+
checked: logic.enabled,
|
|
240
|
+
onCheckedChange: handleToggleEnabled
|
|
241
|
+
}
|
|
242
|
+
),
|
|
243
|
+
/* @__PURE__ */ jsx2(
|
|
244
|
+
FormLabelWithTooltip,
|
|
245
|
+
{
|
|
246
|
+
label: "Enable conditional logic",
|
|
247
|
+
htmlFor: "logic-enabled",
|
|
248
|
+
description: "Show or hide this field based on other field values."
|
|
249
|
+
}
|
|
250
|
+
)
|
|
251
|
+
] }) }),
|
|
252
|
+
logic.enabled && /* @__PURE__ */ jsxs2("div", { className: "space-y-4 pt-4 border-t border-primary/5", children: [
|
|
253
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-center gap-2 text-sm text-muted-foreground", children: [
|
|
254
|
+
/* @__PURE__ */ jsxs2(
|
|
255
|
+
Select,
|
|
256
|
+
{
|
|
257
|
+
value: logic.action,
|
|
258
|
+
onValueChange: (value) => updateLogic({ action: value }),
|
|
259
|
+
children: [
|
|
260
|
+
/* @__PURE__ */ jsx2(SelectTrigger, { className: "w-[100px] h-9 bg-transparent border-input dark:bg-slate-900/50", children: /* @__PURE__ */ jsx2(SelectValue, { placeholder: "Show/Hide" }) }),
|
|
261
|
+
/* @__PURE__ */ jsxs2(SelectContent, { children: [
|
|
262
|
+
/* @__PURE__ */ jsx2(SelectItem, { value: "show", children: "Show" }),
|
|
263
|
+
/* @__PURE__ */ jsx2(SelectItem, { value: "hide", children: "Hide" })
|
|
264
|
+
] })
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
),
|
|
268
|
+
/* @__PURE__ */ jsx2("span", { children: "this field when" }),
|
|
269
|
+
/* @__PURE__ */ jsxs2(
|
|
270
|
+
Select,
|
|
271
|
+
{
|
|
272
|
+
value: logic.operator,
|
|
273
|
+
onValueChange: (value) => updateLogic({ operator: value }),
|
|
274
|
+
children: [
|
|
275
|
+
/* @__PURE__ */ jsx2(SelectTrigger, { className: "w-[80px] h-9 bg-transparent border-input dark:bg-slate-900/50", children: /* @__PURE__ */ jsx2(SelectValue, { placeholder: "All/Any" }) }),
|
|
276
|
+
/* @__PURE__ */ jsxs2(SelectContent, { children: [
|
|
277
|
+
/* @__PURE__ */ jsx2(SelectItem, { value: "AND", children: "All" }),
|
|
278
|
+
/* @__PURE__ */ jsx2(SelectItem, { value: "OR", children: "Any" })
|
|
279
|
+
] })
|
|
280
|
+
]
|
|
281
|
+
}
|
|
282
|
+
),
|
|
283
|
+
/* @__PURE__ */ jsx2("span", { children: "of the following conditions are met:" })
|
|
284
|
+
] }),
|
|
285
|
+
/* @__PURE__ */ jsx2("div", { className: "space-y-3 pt-4", children: logic.conditions.length === 0 ? /* @__PURE__ */ jsx2("div", { className: "p-4 bg-primary/5/30 rounded-none border border-dashed border-primary/5 text-center text-xs text-muted-foreground", children: "No conditions defined. Add a condition to get started." }) : logic.conditions.map((condition, index) => /* @__PURE__ */ jsxs2(
|
|
286
|
+
"div",
|
|
287
|
+
{
|
|
288
|
+
className: "flex flex-col gap-2 p-3 rounded-none bg-primary/5/20 border border-primary/5 relative group",
|
|
289
|
+
children: [
|
|
290
|
+
/* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-1 gap-2", children: [
|
|
291
|
+
/* @__PURE__ */ jsxs2(
|
|
292
|
+
Select,
|
|
293
|
+
{
|
|
294
|
+
value: condition.field,
|
|
295
|
+
onValueChange: (value) => handleUpdateCondition(index, { field: value }),
|
|
296
|
+
children: [
|
|
297
|
+
/* @__PURE__ */ jsx2(SelectTrigger, { className: "w-full bg-transparent border-input dark:bg-slate-900/50", children: /* @__PURE__ */ jsx2(SelectValue, { placeholder: "Select field" }) }),
|
|
298
|
+
/* @__PURE__ */ jsx2(SelectContent, { children: availableFields.map((f) => /* @__PURE__ */ jsx2(SelectItem, { value: f.name, children: f.label || f.name }, f.name)) })
|
|
299
|
+
]
|
|
300
|
+
}
|
|
301
|
+
),
|
|
302
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
|
|
303
|
+
/* @__PURE__ */ jsxs2(
|
|
304
|
+
Select,
|
|
305
|
+
{
|
|
306
|
+
value: condition.comparison,
|
|
307
|
+
onValueChange: (value) => handleUpdateCondition(index, {
|
|
308
|
+
comparison: value
|
|
309
|
+
}),
|
|
310
|
+
children: [
|
|
311
|
+
/* @__PURE__ */ jsx2(SelectTrigger, { className: "flex-1 h-9 bg-transparent border-input dark:bg-slate-900/50", children: /* @__PURE__ */ jsx2(SelectValue, { placeholder: "Compare operator" }) }),
|
|
312
|
+
/* @__PURE__ */ jsx2(SelectContent, { children: COMPARISON_OPERATORS.map((op) => /* @__PURE__ */ jsx2(SelectItem, { value: op.value, children: op.label }, op.value)) })
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
),
|
|
316
|
+
needsValue(condition.comparison) && /* @__PURE__ */ jsx2(
|
|
317
|
+
Input,
|
|
318
|
+
{
|
|
319
|
+
type: "text",
|
|
320
|
+
value: conditionValueToString(condition.value),
|
|
321
|
+
onChange: (e) => handleUpdateCondition(index, {
|
|
322
|
+
value: e.target.value
|
|
323
|
+
}),
|
|
324
|
+
placeholder: "Value",
|
|
325
|
+
className: "flex-1 h-9 bg-transparent"
|
|
326
|
+
}
|
|
327
|
+
)
|
|
328
|
+
] })
|
|
329
|
+
] }),
|
|
330
|
+
/* @__PURE__ */ jsx2(
|
|
331
|
+
Button,
|
|
332
|
+
{
|
|
333
|
+
type: "button",
|
|
334
|
+
variant: "ghost",
|
|
335
|
+
size: "icon-sm",
|
|
336
|
+
onClick: () => handleRemoveCondition(index),
|
|
337
|
+
className: "absolute -top-2 -right-2 h-6 w-6 rounded-full bg-background border border-primary/5 opacity-0 group-hover:opacity-100 transition-opacity",
|
|
338
|
+
title: "Remove condition",
|
|
339
|
+
children: /* @__PURE__ */ jsx2("span", { className: "text-xs", children: "\xD7" })
|
|
340
|
+
}
|
|
341
|
+
)
|
|
342
|
+
]
|
|
343
|
+
},
|
|
344
|
+
index
|
|
345
|
+
)) }),
|
|
346
|
+
availableFields.length > 0 ? /* @__PURE__ */ jsx2(
|
|
347
|
+
Button,
|
|
348
|
+
{
|
|
349
|
+
type: "button",
|
|
350
|
+
variant: "outline",
|
|
351
|
+
size: "sm",
|
|
352
|
+
onClick: handleAddCondition,
|
|
353
|
+
className: "w-full mt-4 border-dashed",
|
|
354
|
+
children: "+ Add Condition"
|
|
355
|
+
}
|
|
356
|
+
) : /* @__PURE__ */ jsx2("p", { className: "mt-4 text-center text-xs text-amber-500 font-medium bg-amber-500/10 p-2 rounded-none border border-amber-500/20", children: "Add more fields to the form to create conditions." })
|
|
357
|
+
] })
|
|
358
|
+
] });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/admin/components/builder/FieldEditor.tsx
|
|
362
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
363
|
+
function GeneralTab({
|
|
364
|
+
field,
|
|
365
|
+
allFields,
|
|
366
|
+
onUpdate
|
|
367
|
+
}) {
|
|
368
|
+
const hasConditionalReferences = useMemo2(() => {
|
|
369
|
+
return allFields.some((f) => {
|
|
370
|
+
if (f.name === field.name || !f.conditionalLogic?.enabled) return false;
|
|
371
|
+
return f.conditionalLogic.conditions.some((c) => c.field === field.name);
|
|
372
|
+
});
|
|
373
|
+
}, [allFields, field.name]);
|
|
374
|
+
return /* @__PURE__ */ jsxs3("div", { className: "space-y-6 py-4", children: [
|
|
375
|
+
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
376
|
+
/* @__PURE__ */ jsx3(
|
|
377
|
+
FormLabelWithTooltip2,
|
|
378
|
+
{
|
|
379
|
+
label: "Field Name (ID)",
|
|
380
|
+
htmlFor: "field-name",
|
|
381
|
+
description: "Used as the key in submission data. Should be unique and contain no spaces."
|
|
382
|
+
}
|
|
383
|
+
),
|
|
384
|
+
/* @__PURE__ */ jsx3(
|
|
385
|
+
Input2,
|
|
386
|
+
{
|
|
387
|
+
id: "field-name",
|
|
388
|
+
type: "text",
|
|
389
|
+
value: field.name,
|
|
390
|
+
onChange: (e) => onUpdate({ name: e.target.value }),
|
|
391
|
+
pattern: "^[a-zA-Z][a-zA-Z0-9_]*$",
|
|
392
|
+
className: "bg-transparent"
|
|
393
|
+
}
|
|
394
|
+
),
|
|
395
|
+
hasConditionalReferences && /* @__PURE__ */ jsx3("p", { className: "text-[11px] text-amber-500 font-medium", children: "\u26A0\uFE0F Changing this may break conditional logic referencing this field" })
|
|
396
|
+
] }),
|
|
397
|
+
field.type !== "hidden" && /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
398
|
+
/* @__PURE__ */ jsx3(
|
|
399
|
+
FormLabelWithTooltip2,
|
|
400
|
+
{
|
|
401
|
+
label: "Label",
|
|
402
|
+
htmlFor: "field-label",
|
|
403
|
+
description: "The text displayed above the field in the form."
|
|
404
|
+
}
|
|
405
|
+
),
|
|
406
|
+
/* @__PURE__ */ jsx3(
|
|
407
|
+
Input2,
|
|
408
|
+
{
|
|
409
|
+
id: "field-label",
|
|
410
|
+
type: "text",
|
|
411
|
+
value: field.label,
|
|
412
|
+
onChange: (e) => onUpdate({ label: e.target.value }),
|
|
413
|
+
className: "bg-transparent"
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
] }),
|
|
417
|
+
(field.type === "text" || field.type === "email" || field.type === "phone" || field.type === "url" || field.type === "number" || field.type === "textarea") && /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
418
|
+
/* @__PURE__ */ jsx3(
|
|
419
|
+
FormLabelWithTooltip2,
|
|
420
|
+
{
|
|
421
|
+
label: "Placeholder",
|
|
422
|
+
htmlFor: "field-placeholder",
|
|
423
|
+
description: "The faint text shown inside the field when empty."
|
|
424
|
+
}
|
|
425
|
+
),
|
|
426
|
+
/* @__PURE__ */ jsx3(
|
|
427
|
+
Input2,
|
|
428
|
+
{
|
|
429
|
+
id: "field-placeholder",
|
|
430
|
+
type: "text",
|
|
431
|
+
value: field.placeholder || "",
|
|
432
|
+
onChange: (e) => onUpdate({ placeholder: e.target.value }),
|
|
433
|
+
className: "bg-transparent"
|
|
434
|
+
}
|
|
435
|
+
)
|
|
436
|
+
] }),
|
|
437
|
+
field.type !== "hidden" && /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
438
|
+
/* @__PURE__ */ jsx3(
|
|
439
|
+
FormLabelWithTooltip2,
|
|
440
|
+
{
|
|
441
|
+
label: "Help Text",
|
|
442
|
+
htmlFor: "field-help",
|
|
443
|
+
description: "Additional instructions displayed below the field."
|
|
444
|
+
}
|
|
445
|
+
),
|
|
446
|
+
/* @__PURE__ */ jsx3(
|
|
447
|
+
Input2,
|
|
448
|
+
{
|
|
449
|
+
id: "field-help",
|
|
450
|
+
type: "text",
|
|
451
|
+
value: field.helpText || "",
|
|
452
|
+
onChange: (e) => onUpdate({ helpText: e.target.value }),
|
|
453
|
+
className: "bg-transparent"
|
|
454
|
+
}
|
|
455
|
+
)
|
|
456
|
+
] }),
|
|
457
|
+
field.type !== "hidden" && field.type !== "checkbox" && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 pt-1", children: [
|
|
458
|
+
/* @__PURE__ */ jsx3(
|
|
459
|
+
Checkbox2,
|
|
460
|
+
{
|
|
461
|
+
id: "field-required",
|
|
462
|
+
checked: field.required || false,
|
|
463
|
+
onCheckedChange: (checked) => onUpdate({ required: checked === true })
|
|
464
|
+
}
|
|
465
|
+
),
|
|
466
|
+
/* @__PURE__ */ jsx3(
|
|
467
|
+
FormLabelWithTooltip2,
|
|
468
|
+
{
|
|
469
|
+
label: "Required field",
|
|
470
|
+
htmlFor: "field-required",
|
|
471
|
+
description: "Forces the user to fill this field before submitting."
|
|
472
|
+
}
|
|
473
|
+
)
|
|
474
|
+
] }),
|
|
475
|
+
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
476
|
+
/* @__PURE__ */ jsx3(
|
|
477
|
+
FormLabelWithTooltip2,
|
|
478
|
+
{
|
|
479
|
+
label: "Width",
|
|
480
|
+
htmlFor: "field-width",
|
|
481
|
+
description: "How much space this field takes in a horizontal row."
|
|
482
|
+
}
|
|
483
|
+
),
|
|
484
|
+
/* @__PURE__ */ jsxs3(
|
|
485
|
+
Select2,
|
|
486
|
+
{
|
|
487
|
+
value: field.admin?.width || "100%",
|
|
488
|
+
onValueChange: (value) => onUpdate({
|
|
489
|
+
admin: {
|
|
490
|
+
...field.admin,
|
|
491
|
+
width: value
|
|
492
|
+
}
|
|
493
|
+
}),
|
|
494
|
+
children: [
|
|
495
|
+
/* @__PURE__ */ jsx3(SelectTrigger2, { className: "w-full bg-transparent border-input dark:bg-slate-900/50", children: /* @__PURE__ */ jsx3(SelectValue2, { placeholder: "Select width" }) }),
|
|
496
|
+
/* @__PURE__ */ jsxs3(SelectContent2, { children: [
|
|
497
|
+
/* @__PURE__ */ jsx3(SelectItem2, { value: "100%", children: "Full Width (100%)" }),
|
|
498
|
+
/* @__PURE__ */ jsx3(SelectItem2, { value: "75%", children: "Three Quarters (75%)" }),
|
|
499
|
+
/* @__PURE__ */ jsx3(SelectItem2, { value: "66%", children: "Two Thirds (66%)" }),
|
|
500
|
+
/* @__PURE__ */ jsx3(SelectItem2, { value: "50%", children: "Half (50%)" }),
|
|
501
|
+
/* @__PURE__ */ jsx3(SelectItem2, { value: "33%", children: "One Third (33%)" }),
|
|
502
|
+
/* @__PURE__ */ jsx3(SelectItem2, { value: "25%", children: "One Quarter (25%)" })
|
|
503
|
+
] })
|
|
504
|
+
]
|
|
505
|
+
}
|
|
506
|
+
)
|
|
507
|
+
] }),
|
|
508
|
+
/* @__PURE__ */ jsx3(TypeSpecificOptions, { field, onUpdate })
|
|
509
|
+
] });
|
|
510
|
+
}
|
|
511
|
+
function TypeSpecificOptions({
|
|
512
|
+
field,
|
|
513
|
+
onUpdate
|
|
514
|
+
}) {
|
|
515
|
+
switch (field.type) {
|
|
516
|
+
case "select":
|
|
517
|
+
case "radio":
|
|
518
|
+
return /* @__PURE__ */ jsx3(OptionsEditor, { field, onUpdate });
|
|
519
|
+
case "textarea": {
|
|
520
|
+
const textareaField = field;
|
|
521
|
+
return /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
522
|
+
/* @__PURE__ */ jsx3(
|
|
523
|
+
FormLabelWithTooltip2,
|
|
524
|
+
{
|
|
525
|
+
label: "Rows",
|
|
526
|
+
htmlFor: "field-rows",
|
|
527
|
+
description: "Height of the textarea in text lines."
|
|
528
|
+
}
|
|
529
|
+
),
|
|
530
|
+
/* @__PURE__ */ jsx3(
|
|
531
|
+
Input2,
|
|
532
|
+
{
|
|
533
|
+
id: "field-rows",
|
|
534
|
+
type: "number",
|
|
535
|
+
min: 2,
|
|
536
|
+
max: 20,
|
|
537
|
+
value: textareaField.rows || 4,
|
|
538
|
+
onChange: (e) => onUpdate({ rows: parseInt(e.target.value) || 4 }),
|
|
539
|
+
className: "w-24 bg-transparent"
|
|
540
|
+
}
|
|
541
|
+
)
|
|
542
|
+
] });
|
|
543
|
+
}
|
|
544
|
+
case "file": {
|
|
545
|
+
const fileField = field;
|
|
546
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
547
|
+
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
548
|
+
/* @__PURE__ */ jsx3(
|
|
549
|
+
FormLabelWithTooltip2,
|
|
550
|
+
{
|
|
551
|
+
label: "Accepted File Types",
|
|
552
|
+
htmlFor: "field-accept",
|
|
553
|
+
description: "Comma-separated MIME types (e.g., image/*, application/pdf)."
|
|
554
|
+
}
|
|
555
|
+
),
|
|
556
|
+
/* @__PURE__ */ jsx3(
|
|
557
|
+
Input2,
|
|
558
|
+
{
|
|
559
|
+
id: "field-accept",
|
|
560
|
+
type: "text",
|
|
561
|
+
value: fileField.accept || "",
|
|
562
|
+
onChange: (e) => onUpdate({ accept: e.target.value }),
|
|
563
|
+
placeholder: "image/*,application/pdf",
|
|
564
|
+
className: "bg-transparent"
|
|
565
|
+
}
|
|
566
|
+
)
|
|
567
|
+
] }),
|
|
568
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 pt-1", children: [
|
|
569
|
+
/* @__PURE__ */ jsx3(
|
|
570
|
+
Checkbox2,
|
|
571
|
+
{
|
|
572
|
+
id: "field-multiple",
|
|
573
|
+
checked: fileField.multiple || false,
|
|
574
|
+
onCheckedChange: (checked) => onUpdate({ multiple: checked === true })
|
|
575
|
+
}
|
|
576
|
+
),
|
|
577
|
+
/* @__PURE__ */ jsx3(
|
|
578
|
+
FormLabelWithTooltip2,
|
|
579
|
+
{
|
|
580
|
+
label: "Allow multiple files",
|
|
581
|
+
htmlFor: "field-multiple",
|
|
582
|
+
description: "Users can upload more than one file at once."
|
|
583
|
+
}
|
|
584
|
+
)
|
|
585
|
+
] }),
|
|
586
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 pt-1", children: [
|
|
587
|
+
/* @__PURE__ */ jsx3(
|
|
588
|
+
Checkbox2,
|
|
589
|
+
{
|
|
590
|
+
id: "field-attach-to-email",
|
|
591
|
+
checked: fileField.attachToEmail || false,
|
|
592
|
+
onCheckedChange: (checked) => onUpdate({ attachToEmail: checked === true })
|
|
593
|
+
}
|
|
594
|
+
),
|
|
595
|
+
/* @__PURE__ */ jsx3(
|
|
596
|
+
FormLabelWithTooltip2,
|
|
597
|
+
{
|
|
598
|
+
label: "Attach to notification emails",
|
|
599
|
+
htmlFor: "field-attach-to-email",
|
|
600
|
+
description: "Uploaded files will be attached to all notification emails for this form."
|
|
601
|
+
}
|
|
602
|
+
)
|
|
603
|
+
] })
|
|
604
|
+
] });
|
|
605
|
+
}
|
|
606
|
+
case "hidden": {
|
|
607
|
+
const hiddenField = field;
|
|
608
|
+
return /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
609
|
+
/* @__PURE__ */ jsx3(
|
|
610
|
+
FormLabelWithTooltip2,
|
|
611
|
+
{
|
|
612
|
+
label: "Default Value",
|
|
613
|
+
htmlFor: "field-default",
|
|
614
|
+
description: "This value will be submitted silently with the form."
|
|
615
|
+
}
|
|
616
|
+
),
|
|
617
|
+
/* @__PURE__ */ jsx3(
|
|
618
|
+
Input2,
|
|
619
|
+
{
|
|
620
|
+
id: "field-default",
|
|
621
|
+
type: "text",
|
|
622
|
+
value: hiddenField.defaultValue || "",
|
|
623
|
+
onChange: (e) => onUpdate({ defaultValue: e.target.value }),
|
|
624
|
+
className: "bg-transparent"
|
|
625
|
+
}
|
|
626
|
+
)
|
|
627
|
+
] });
|
|
628
|
+
}
|
|
629
|
+
case "date": {
|
|
630
|
+
const dateField = field;
|
|
631
|
+
return /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
632
|
+
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
633
|
+
/* @__PURE__ */ jsx3(
|
|
634
|
+
FormLabelWithTooltip2,
|
|
635
|
+
{
|
|
636
|
+
label: "Minimum Date",
|
|
637
|
+
htmlFor: "min-date",
|
|
638
|
+
description: "Lower bound for date selection."
|
|
639
|
+
}
|
|
640
|
+
),
|
|
641
|
+
/* @__PURE__ */ jsx3(
|
|
642
|
+
Input2,
|
|
643
|
+
{
|
|
644
|
+
id: "min-date",
|
|
645
|
+
type: "date",
|
|
646
|
+
value: dateField.min || "",
|
|
647
|
+
onChange: (e) => onUpdate({ min: e.target.value }),
|
|
648
|
+
className: "bg-transparent"
|
|
649
|
+
}
|
|
650
|
+
)
|
|
651
|
+
] }),
|
|
652
|
+
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
653
|
+
/* @__PURE__ */ jsx3(
|
|
654
|
+
FormLabelWithTooltip2,
|
|
655
|
+
{
|
|
656
|
+
label: "Maximum Date",
|
|
657
|
+
htmlFor: "max-date",
|
|
658
|
+
description: "Upper bound for date selection."
|
|
659
|
+
}
|
|
660
|
+
),
|
|
661
|
+
/* @__PURE__ */ jsx3(
|
|
662
|
+
Input2,
|
|
663
|
+
{
|
|
664
|
+
id: "max-date",
|
|
665
|
+
type: "date",
|
|
666
|
+
value: dateField.max || "",
|
|
667
|
+
onChange: (e) => onUpdate({ max: e.target.value }),
|
|
668
|
+
className: "bg-transparent"
|
|
669
|
+
}
|
|
670
|
+
)
|
|
671
|
+
] })
|
|
672
|
+
] });
|
|
673
|
+
}
|
|
674
|
+
case "number": {
|
|
675
|
+
const numField = field;
|
|
676
|
+
return /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
677
|
+
/* @__PURE__ */ jsx3(
|
|
678
|
+
FormLabelWithTooltip2,
|
|
679
|
+
{
|
|
680
|
+
label: "Step",
|
|
681
|
+
htmlFor: "field-step",
|
|
682
|
+
description: "Increment/decrement step (e.g., 0.01 for decimals)."
|
|
683
|
+
}
|
|
684
|
+
),
|
|
685
|
+
/* @__PURE__ */ jsx3(
|
|
686
|
+
Input2,
|
|
687
|
+
{
|
|
688
|
+
id: "field-step",
|
|
689
|
+
type: "number",
|
|
690
|
+
value: numField.validation?.step || "",
|
|
691
|
+
onChange: (e) => onUpdate({
|
|
692
|
+
validation: {
|
|
693
|
+
...numField.validation,
|
|
694
|
+
step: parseFloat(e.target.value) || void 0
|
|
695
|
+
}
|
|
696
|
+
}),
|
|
697
|
+
placeholder: "1",
|
|
698
|
+
className: "w-24 bg-transparent"
|
|
699
|
+
}
|
|
700
|
+
)
|
|
701
|
+
] });
|
|
702
|
+
}
|
|
703
|
+
default:
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function OptionsEditor({
|
|
708
|
+
field,
|
|
709
|
+
onUpdate
|
|
710
|
+
}) {
|
|
711
|
+
const optionsField = field;
|
|
712
|
+
const options = useMemo2(
|
|
713
|
+
() => optionsField.options || [],
|
|
714
|
+
[optionsField.options]
|
|
715
|
+
);
|
|
716
|
+
const addOption = useCallback3(() => {
|
|
717
|
+
const newOptions = [
|
|
718
|
+
...options,
|
|
719
|
+
{
|
|
720
|
+
label: `Option ${options.length + 1}`,
|
|
721
|
+
value: `option_${options.length + 1}`
|
|
722
|
+
}
|
|
723
|
+
];
|
|
724
|
+
onUpdate({ options: newOptions });
|
|
725
|
+
}, [options, onUpdate]);
|
|
726
|
+
const updateOption = useCallback3(
|
|
727
|
+
(index, key, value) => {
|
|
728
|
+
const newOptions = options.map(
|
|
729
|
+
(opt, i) => i === index ? { ...opt, [key]: value } : opt
|
|
730
|
+
);
|
|
731
|
+
onUpdate({ options: newOptions });
|
|
732
|
+
},
|
|
733
|
+
[options, onUpdate]
|
|
734
|
+
);
|
|
735
|
+
const removeOption = useCallback3(
|
|
736
|
+
(index) => {
|
|
737
|
+
const newOptions = options.filter((_, i) => i !== index);
|
|
738
|
+
onUpdate({ options: newOptions });
|
|
739
|
+
},
|
|
740
|
+
[options, onUpdate]
|
|
741
|
+
);
|
|
742
|
+
return /* @__PURE__ */ jsxs3("div", { className: "space-y-3 pt-6", children: [
|
|
743
|
+
/* @__PURE__ */ jsx3(
|
|
744
|
+
FormLabelWithTooltip2,
|
|
745
|
+
{
|
|
746
|
+
label: "Options",
|
|
747
|
+
description: "List of choices available for this field."
|
|
748
|
+
}
|
|
749
|
+
),
|
|
750
|
+
options.length === 0 ? /* @__PURE__ */ jsx3("div", { className: "p-4 bg-primary/5/30 rounded-none border border-dashed border-primary/5 text-center text-xs text-muted-foreground", children: "No options defined. Add an option to get started." }) : /* @__PURE__ */ jsx3("div", { className: "space-y-2", children: options.map((opt, index) => /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 group", children: [
|
|
751
|
+
/* @__PURE__ */ jsx3(
|
|
752
|
+
Input2,
|
|
753
|
+
{
|
|
754
|
+
type: "text",
|
|
755
|
+
value: opt.label,
|
|
756
|
+
onChange: (e) => updateOption(index, "label", e.target.value),
|
|
757
|
+
placeholder: "Label",
|
|
758
|
+
className: "flex-1 h-9 bg-transparent"
|
|
759
|
+
}
|
|
760
|
+
),
|
|
761
|
+
/* @__PURE__ */ jsx3(
|
|
762
|
+
Input2,
|
|
763
|
+
{
|
|
764
|
+
type: "text",
|
|
765
|
+
value: opt.value,
|
|
766
|
+
onChange: (e) => updateOption(index, "value", e.target.value),
|
|
767
|
+
placeholder: "Value",
|
|
768
|
+
className: "flex-1 h-9 bg-transparent"
|
|
769
|
+
}
|
|
770
|
+
),
|
|
771
|
+
/* @__PURE__ */ jsx3(
|
|
772
|
+
Button2,
|
|
773
|
+
{
|
|
774
|
+
type: "button",
|
|
775
|
+
variant: "ghost",
|
|
776
|
+
size: "icon-sm",
|
|
777
|
+
onClick: () => removeOption(index),
|
|
778
|
+
className: "h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10",
|
|
779
|
+
title: "Remove option",
|
|
780
|
+
children: "\xD7"
|
|
781
|
+
}
|
|
782
|
+
)
|
|
783
|
+
] }, index)) }),
|
|
784
|
+
/* @__PURE__ */ jsx3(
|
|
785
|
+
Button2,
|
|
786
|
+
{
|
|
787
|
+
type: "button",
|
|
788
|
+
variant: "outline",
|
|
789
|
+
size: "sm",
|
|
790
|
+
onClick: addOption,
|
|
791
|
+
className: "w-full border-dashed text-xs h-8",
|
|
792
|
+
children: "+ Add Option"
|
|
793
|
+
}
|
|
794
|
+
)
|
|
795
|
+
] });
|
|
796
|
+
}
|
|
797
|
+
function ValidationTab({
|
|
798
|
+
field,
|
|
799
|
+
onUpdate
|
|
800
|
+
}) {
|
|
801
|
+
const validation = useMemo2(() => field.validation || {}, [field.validation]);
|
|
802
|
+
const updateValidation = useCallback3(
|
|
803
|
+
(key, value) => {
|
|
804
|
+
onUpdate({
|
|
805
|
+
validation: { ...validation, [key]: value }
|
|
806
|
+
});
|
|
807
|
+
},
|
|
808
|
+
[validation, onUpdate]
|
|
809
|
+
);
|
|
810
|
+
return /* @__PURE__ */ jsxs3("div", { className: "space-y-6 pt-2", children: [
|
|
811
|
+
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
812
|
+
/* @__PURE__ */ jsx3(
|
|
813
|
+
FormLabelWithTooltip2,
|
|
814
|
+
{
|
|
815
|
+
label: "Custom Error Message",
|
|
816
|
+
htmlFor: "error-message",
|
|
817
|
+
description: "Override the default browser validation message."
|
|
818
|
+
}
|
|
819
|
+
),
|
|
820
|
+
/* @__PURE__ */ jsx3(
|
|
821
|
+
Input2,
|
|
822
|
+
{
|
|
823
|
+
id: "error-message",
|
|
824
|
+
type: "text",
|
|
825
|
+
value: validation.errorMessage || "",
|
|
826
|
+
onChange: (e) => updateValidation("errorMessage", e.target.value),
|
|
827
|
+
placeholder: "This field is required",
|
|
828
|
+
className: "bg-transparent"
|
|
829
|
+
}
|
|
830
|
+
)
|
|
831
|
+
] }),
|
|
832
|
+
(field.type === "text" || field.type === "textarea") && /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
833
|
+
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
834
|
+
/* @__PURE__ */ jsx3(
|
|
835
|
+
FormLabelWithTooltip2,
|
|
836
|
+
{
|
|
837
|
+
label: "Min Length",
|
|
838
|
+
htmlFor: "min-len",
|
|
839
|
+
description: "Minimum characters required."
|
|
840
|
+
}
|
|
841
|
+
),
|
|
842
|
+
/* @__PURE__ */ jsx3(
|
|
843
|
+
Input2,
|
|
844
|
+
{
|
|
845
|
+
id: "min-len",
|
|
846
|
+
type: "number",
|
|
847
|
+
min: 0,
|
|
848
|
+
value: field.validation?.minLength ?? "",
|
|
849
|
+
onChange: (e) => updateValidation(
|
|
850
|
+
"minLength",
|
|
851
|
+
e.target.value ? parseInt(e.target.value) : void 0
|
|
852
|
+
),
|
|
853
|
+
className: "bg-transparent"
|
|
854
|
+
}
|
|
855
|
+
)
|
|
856
|
+
] }),
|
|
857
|
+
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
858
|
+
/* @__PURE__ */ jsx3(
|
|
859
|
+
FormLabelWithTooltip2,
|
|
860
|
+
{
|
|
861
|
+
label: "Max Length",
|
|
862
|
+
htmlFor: "max-len",
|
|
863
|
+
description: "Maximum characters allowed."
|
|
864
|
+
}
|
|
865
|
+
),
|
|
866
|
+
/* @__PURE__ */ jsx3(
|
|
867
|
+
Input2,
|
|
868
|
+
{
|
|
869
|
+
id: "max-len",
|
|
870
|
+
type: "number",
|
|
871
|
+
min: 0,
|
|
872
|
+
value: field.validation?.maxLength ?? "",
|
|
873
|
+
onChange: (e) => updateValidation(
|
|
874
|
+
"maxLength",
|
|
875
|
+
e.target.value ? parseInt(e.target.value) : void 0
|
|
876
|
+
),
|
|
877
|
+
className: "bg-transparent"
|
|
878
|
+
}
|
|
879
|
+
)
|
|
880
|
+
] })
|
|
881
|
+
] }),
|
|
882
|
+
field.type === "number" && /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
883
|
+
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
884
|
+
/* @__PURE__ */ jsx3(
|
|
885
|
+
FormLabelWithTooltip2,
|
|
886
|
+
{
|
|
887
|
+
label: "Min Value",
|
|
888
|
+
htmlFor: "min-val",
|
|
889
|
+
description: "Minimum numerical value."
|
|
890
|
+
}
|
|
891
|
+
),
|
|
892
|
+
/* @__PURE__ */ jsx3(
|
|
893
|
+
Input2,
|
|
894
|
+
{
|
|
895
|
+
id: "min-val",
|
|
896
|
+
type: "number",
|
|
897
|
+
value: field.validation?.min ?? "",
|
|
898
|
+
onChange: (e) => updateValidation(
|
|
899
|
+
"min",
|
|
900
|
+
e.target.value ? parseFloat(e.target.value) : void 0
|
|
901
|
+
),
|
|
902
|
+
className: "bg-transparent"
|
|
903
|
+
}
|
|
904
|
+
)
|
|
905
|
+
] }),
|
|
906
|
+
/* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
907
|
+
/* @__PURE__ */ jsx3(
|
|
908
|
+
FormLabelWithTooltip2,
|
|
909
|
+
{
|
|
910
|
+
label: "Max Value",
|
|
911
|
+
htmlFor: "max-val",
|
|
912
|
+
description: "Maximum numerical value."
|
|
913
|
+
}
|
|
914
|
+
),
|
|
915
|
+
/* @__PURE__ */ jsx3(
|
|
916
|
+
Input2,
|
|
917
|
+
{
|
|
918
|
+
id: "max-val",
|
|
919
|
+
type: "number",
|
|
920
|
+
value: field.validation?.max ?? "",
|
|
921
|
+
onChange: (e) => updateValidation(
|
|
922
|
+
"max",
|
|
923
|
+
e.target.value ? parseFloat(e.target.value) : void 0
|
|
924
|
+
),
|
|
925
|
+
className: "bg-transparent"
|
|
926
|
+
}
|
|
927
|
+
)
|
|
928
|
+
] })
|
|
929
|
+
] }),
|
|
930
|
+
(field.type === "text" || field.type === "email" || field.type === "phone" || field.type === "url") && /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
931
|
+
/* @__PURE__ */ jsx3(
|
|
932
|
+
FormLabelWithTooltip2,
|
|
933
|
+
{
|
|
934
|
+
label: "Pattern (Regex)",
|
|
935
|
+
htmlFor: "val-pattern",
|
|
936
|
+
description: "Regular expression for advanced validation."
|
|
937
|
+
}
|
|
938
|
+
),
|
|
939
|
+
/* @__PURE__ */ jsx3(
|
|
940
|
+
Input2,
|
|
941
|
+
{
|
|
942
|
+
id: "val-pattern",
|
|
943
|
+
type: "text",
|
|
944
|
+
value: field.validation?.pattern || "",
|
|
945
|
+
onChange: (e) => updateValidation("pattern", e.target.value),
|
|
946
|
+
placeholder: "^[A-Za-z]+$",
|
|
947
|
+
className: "bg-transparent"
|
|
948
|
+
}
|
|
949
|
+
)
|
|
950
|
+
] }),
|
|
951
|
+
field.type === "file" && /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
952
|
+
/* @__PURE__ */ jsx3(
|
|
953
|
+
FormLabelWithTooltip2,
|
|
954
|
+
{
|
|
955
|
+
label: "Max File Size (bytes)",
|
|
956
|
+
htmlFor: "max-file-size",
|
|
957
|
+
description: "Maximum allowed size in bytes (10MB \u2248 10485760)."
|
|
958
|
+
}
|
|
959
|
+
),
|
|
960
|
+
/* @__PURE__ */ jsx3(
|
|
961
|
+
Input2,
|
|
962
|
+
{
|
|
963
|
+
id: "max-file-size",
|
|
964
|
+
type: "number",
|
|
965
|
+
min: 0,
|
|
966
|
+
value: field.maxFileSize ?? "",
|
|
967
|
+
onChange: (e) => onUpdate({
|
|
968
|
+
maxFileSize: e.target.value ? parseInt(e.target.value) : void 0
|
|
969
|
+
}),
|
|
970
|
+
placeholder: "10485760",
|
|
971
|
+
className: "bg-transparent"
|
|
972
|
+
}
|
|
973
|
+
)
|
|
974
|
+
] }),
|
|
975
|
+
(field.type === "checkbox" || field.type === "hidden") && /* @__PURE__ */ jsx3("div", { className: "p-3 bg-primary/5/30 rounded-none text-xs text-muted-foreground text-center border border-dashed border-primary/5", children: "No additional validation options for this field type." })
|
|
976
|
+
] });
|
|
977
|
+
}
|
|
978
|
+
function FieldEditor({
|
|
979
|
+
field,
|
|
980
|
+
allFields,
|
|
981
|
+
onUpdate,
|
|
982
|
+
onDelete,
|
|
983
|
+
onDuplicate
|
|
984
|
+
}) {
|
|
985
|
+
const [activeTab, setActiveTab] = useState2("general");
|
|
986
|
+
const getTypeLabel = (type) => {
|
|
987
|
+
const labels = {
|
|
988
|
+
text: "Text",
|
|
989
|
+
email: "Email",
|
|
990
|
+
number: "Number",
|
|
991
|
+
phone: "Phone",
|
|
992
|
+
url: "URL",
|
|
993
|
+
textarea: "Textarea",
|
|
994
|
+
select: "Select",
|
|
995
|
+
checkbox: "Checkbox",
|
|
996
|
+
radio: "Radio",
|
|
997
|
+
file: "File",
|
|
998
|
+
date: "Date",
|
|
999
|
+
time: "Time",
|
|
1000
|
+
hidden: "Hidden"
|
|
1001
|
+
};
|
|
1002
|
+
return labels[type] || type;
|
|
1003
|
+
};
|
|
1004
|
+
return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col h-full", children: [
|
|
1005
|
+
/* @__PURE__ */ jsx3("div", { className: "p-4 border-b border-primary/5", children: /* @__PURE__ */ jsxs3("h3", { className: "text-sm font-semibold text-foreground flex items-center gap-2", children: [
|
|
1006
|
+
"Field Properties",
|
|
1007
|
+
/* @__PURE__ */ jsx3("span", { className: "text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded-full bg-primary/5 text-primary font-bold dark:bg-primary/20 dark:text-primary-foreground/90", children: getTypeLabel(field.type) })
|
|
1008
|
+
] }) }),
|
|
1009
|
+
/* @__PURE__ */ jsxs3(
|
|
1010
|
+
Tabs,
|
|
1011
|
+
{
|
|
1012
|
+
value: activeTab,
|
|
1013
|
+
onValueChange: setActiveTab,
|
|
1014
|
+
className: "flex-1 flex flex-col",
|
|
1015
|
+
children: [
|
|
1016
|
+
/* @__PURE__ */ jsx3("div", { className: "border-b bg-primary/5/10 border-primary/5", children: /* @__PURE__ */ jsxs3(TabsList, { className: "w-full justify-start gap-0", children: [
|
|
1017
|
+
/* @__PURE__ */ jsx3(
|
|
1018
|
+
TabsTrigger,
|
|
1019
|
+
{
|
|
1020
|
+
value: "general",
|
|
1021
|
+
className: "w-full rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-background/50 transition-all",
|
|
1022
|
+
children: "General"
|
|
1023
|
+
}
|
|
1024
|
+
),
|
|
1025
|
+
/* @__PURE__ */ jsx3(
|
|
1026
|
+
TabsTrigger,
|
|
1027
|
+
{
|
|
1028
|
+
value: "validation",
|
|
1029
|
+
className: "w-full rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-background/50 transition-all",
|
|
1030
|
+
children: "Validation"
|
|
1031
|
+
}
|
|
1032
|
+
),
|
|
1033
|
+
/* @__PURE__ */ jsx3(
|
|
1034
|
+
TabsTrigger,
|
|
1035
|
+
{
|
|
1036
|
+
value: "conditional",
|
|
1037
|
+
className: "w-full rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-background/50 transition-all",
|
|
1038
|
+
children: "Conditional"
|
|
1039
|
+
}
|
|
1040
|
+
)
|
|
1041
|
+
] }) }),
|
|
1042
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex-1 overflow-y-auto p-4 pb-20", children: [
|
|
1043
|
+
/* @__PURE__ */ jsx3(TabsContent, { value: "general", className: "mt-0", children: /* @__PURE__ */ jsx3(
|
|
1044
|
+
GeneralTab,
|
|
1045
|
+
{
|
|
1046
|
+
field,
|
|
1047
|
+
allFields,
|
|
1048
|
+
onUpdate
|
|
1049
|
+
}
|
|
1050
|
+
) }),
|
|
1051
|
+
/* @__PURE__ */ jsx3(TabsContent, { value: "validation", className: "mt-0", children: /* @__PURE__ */ jsx3(ValidationTab, { field, onUpdate }) }),
|
|
1052
|
+
/* @__PURE__ */ jsx3(TabsContent, { value: "conditional", className: "mt-0", children: /* @__PURE__ */ jsx3(
|
|
1053
|
+
ConditionalLogicEditor,
|
|
1054
|
+
{
|
|
1055
|
+
field,
|
|
1056
|
+
allFields,
|
|
1057
|
+
onUpdate
|
|
1058
|
+
}
|
|
1059
|
+
) })
|
|
1060
|
+
] })
|
|
1061
|
+
]
|
|
1062
|
+
}
|
|
1063
|
+
),
|
|
1064
|
+
/* @__PURE__ */ jsxs3("div", { className: "p-4 bg-background border-t border-primary/5 flex gap-2 shrink-0", children: [
|
|
1065
|
+
onDuplicate && /* @__PURE__ */ jsx3(
|
|
1066
|
+
Button2,
|
|
1067
|
+
{
|
|
1068
|
+
type: "button",
|
|
1069
|
+
variant: "outline",
|
|
1070
|
+
className: "flex-1 h-9",
|
|
1071
|
+
onClick: onDuplicate,
|
|
1072
|
+
children: "Duplicate"
|
|
1073
|
+
}
|
|
1074
|
+
),
|
|
1075
|
+
/* @__PURE__ */ jsx3(
|
|
1076
|
+
Button2,
|
|
1077
|
+
{
|
|
1078
|
+
type: "button",
|
|
1079
|
+
variant: "destructive",
|
|
1080
|
+
className: "flex-1 h-9",
|
|
1081
|
+
onClick: onDelete,
|
|
1082
|
+
children: "Delete Field"
|
|
1083
|
+
}
|
|
1084
|
+
)
|
|
1085
|
+
] })
|
|
1086
|
+
] });
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// src/admin/components/builder/FieldLibrary.tsx
|
|
1090
|
+
import { useDraggable } from "@dnd-kit/core";
|
|
1091
|
+
import { useMemo as useMemo4, useState as useState4 } from "react";
|
|
1092
|
+
|
|
1093
|
+
// src/admin/context/FormBuilderContext.tsx
|
|
1094
|
+
import {
|
|
1095
|
+
createContext,
|
|
1096
|
+
useContext,
|
|
1097
|
+
useCallback as useCallback4,
|
|
1098
|
+
useMemo as useMemo3,
|
|
1099
|
+
useState as useState3
|
|
1100
|
+
} from "react";
|
|
1101
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1102
|
+
var DEFAULT_SETTINGS = {
|
|
1103
|
+
submitButtonText: "Submit",
|
|
1104
|
+
showResetButton: false,
|
|
1105
|
+
resetButtonText: "Reset",
|
|
1106
|
+
confirmationMessage: "Thank you for your submission!",
|
|
1107
|
+
redirectUrl: void 0,
|
|
1108
|
+
honeypotEnabled: true,
|
|
1109
|
+
captchaEnabled: false,
|
|
1110
|
+
storeSubmissions: true,
|
|
1111
|
+
submissionLimit: void 0
|
|
1112
|
+
};
|
|
1113
|
+
function createNotification() {
|
|
1114
|
+
const id = `notif_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 6)}`;
|
|
1115
|
+
return {
|
|
1116
|
+
id,
|
|
1117
|
+
name: "Email Notification",
|
|
1118
|
+
enabled: true,
|
|
1119
|
+
recipientType: "static",
|
|
1120
|
+
to: "",
|
|
1121
|
+
cc: [],
|
|
1122
|
+
bcc: []
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
var FormBuilderContext = createContext(null);
|
|
1126
|
+
function generateFieldName(type) {
|
|
1127
|
+
const timestamp = Date.now().toString(36);
|
|
1128
|
+
const random = Math.random().toString(36).substring(2, 6);
|
|
1129
|
+
return `${type}_${timestamp}_${random}`;
|
|
1130
|
+
}
|
|
1131
|
+
function generateFieldLabel(type) {
|
|
1132
|
+
const labels = {
|
|
1133
|
+
text: "Text Field",
|
|
1134
|
+
email: "Email Address",
|
|
1135
|
+
number: "Number",
|
|
1136
|
+
phone: "Phone Number",
|
|
1137
|
+
url: "Website URL",
|
|
1138
|
+
textarea: "Text Area",
|
|
1139
|
+
select: "Dropdown",
|
|
1140
|
+
checkbox: "Checkbox",
|
|
1141
|
+
radio: "Radio Buttons",
|
|
1142
|
+
file: "File Upload",
|
|
1143
|
+
date: "Date",
|
|
1144
|
+
time: "Time",
|
|
1145
|
+
hidden: "Hidden Field"
|
|
1146
|
+
};
|
|
1147
|
+
return labels[type] || "New Field";
|
|
1148
|
+
}
|
|
1149
|
+
function createFieldFromType(type) {
|
|
1150
|
+
const name = generateFieldName(type);
|
|
1151
|
+
const label = generateFieldLabel(type);
|
|
1152
|
+
const baseField = {
|
|
1153
|
+
name,
|
|
1154
|
+
label,
|
|
1155
|
+
required: false
|
|
1156
|
+
};
|
|
1157
|
+
switch (type) {
|
|
1158
|
+
case "text":
|
|
1159
|
+
return { ...baseField, type: "text" };
|
|
1160
|
+
case "email":
|
|
1161
|
+
return { ...baseField, type: "email", label: "Email Address" };
|
|
1162
|
+
case "number":
|
|
1163
|
+
return { ...baseField, type: "number" };
|
|
1164
|
+
case "phone":
|
|
1165
|
+
return { ...baseField, type: "phone", label: "Phone Number" };
|
|
1166
|
+
case "url":
|
|
1167
|
+
return { ...baseField, type: "url", label: "Website" };
|
|
1168
|
+
case "textarea":
|
|
1169
|
+
return { ...baseField, type: "textarea", rows: 4 };
|
|
1170
|
+
case "select":
|
|
1171
|
+
return {
|
|
1172
|
+
...baseField,
|
|
1173
|
+
type: "select",
|
|
1174
|
+
options: [
|
|
1175
|
+
{ label: "Option 1", value: "option_1" },
|
|
1176
|
+
{ label: "Option 2", value: "option_2" }
|
|
1177
|
+
]
|
|
1178
|
+
};
|
|
1179
|
+
case "checkbox":
|
|
1180
|
+
return { ...baseField, type: "checkbox" };
|
|
1181
|
+
case "radio":
|
|
1182
|
+
return {
|
|
1183
|
+
...baseField,
|
|
1184
|
+
type: "radio",
|
|
1185
|
+
options: [
|
|
1186
|
+
{ label: "Option 1", value: "option_1" },
|
|
1187
|
+
{ label: "Option 2", value: "option_2" }
|
|
1188
|
+
]
|
|
1189
|
+
};
|
|
1190
|
+
case "file":
|
|
1191
|
+
return { ...baseField, type: "file" };
|
|
1192
|
+
case "date":
|
|
1193
|
+
return { ...baseField, type: "date" };
|
|
1194
|
+
case "time":
|
|
1195
|
+
return { ...baseField, type: "time" };
|
|
1196
|
+
case "hidden":
|
|
1197
|
+
return { ...baseField, type: "hidden", label: "Hidden Field" };
|
|
1198
|
+
default:
|
|
1199
|
+
return { ...baseField, type: "text" };
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
function FormBuilderProvider({
|
|
1203
|
+
initialData,
|
|
1204
|
+
children
|
|
1205
|
+
}) {
|
|
1206
|
+
const [fields, setFieldsState] = useState3(
|
|
1207
|
+
initialData?.fields || []
|
|
1208
|
+
);
|
|
1209
|
+
const [selectedFieldId, setSelectedFieldId] = useState3(null);
|
|
1210
|
+
const [activeTab, setActiveTab] = useState3("builder");
|
|
1211
|
+
const [isDirty, setIsDirty] = useState3(false);
|
|
1212
|
+
const [formData, setFormData] = useState3({
|
|
1213
|
+
id: initialData?.id,
|
|
1214
|
+
name: initialData?.name || "",
|
|
1215
|
+
slug: initialData?.slug || "",
|
|
1216
|
+
description: initialData?.description,
|
|
1217
|
+
status: initialData?.status || "draft"
|
|
1218
|
+
});
|
|
1219
|
+
const [settings, setSettings] = useState3({
|
|
1220
|
+
...DEFAULT_SETTINGS,
|
|
1221
|
+
...initialData?.settings
|
|
1222
|
+
});
|
|
1223
|
+
const [notifications, setNotifications] = useState3(
|
|
1224
|
+
initialData?.notifications || []
|
|
1225
|
+
);
|
|
1226
|
+
const setFields = useCallback4((newFields) => {
|
|
1227
|
+
setFieldsState(newFields);
|
|
1228
|
+
setIsDirty(true);
|
|
1229
|
+
}, []);
|
|
1230
|
+
const addField = useCallback4((field, index) => {
|
|
1231
|
+
setFieldsState((prev) => {
|
|
1232
|
+
if (index !== void 0 && index >= 0 && index <= prev.length) {
|
|
1233
|
+
const newFields = [...prev];
|
|
1234
|
+
newFields.splice(index, 0, field);
|
|
1235
|
+
return newFields;
|
|
1236
|
+
}
|
|
1237
|
+
return [...prev, field];
|
|
1238
|
+
});
|
|
1239
|
+
setSelectedFieldId(field.name);
|
|
1240
|
+
setIsDirty(true);
|
|
1241
|
+
}, []);
|
|
1242
|
+
const updateField = useCallback4(
|
|
1243
|
+
(fieldName, updates) => {
|
|
1244
|
+
setFieldsState(
|
|
1245
|
+
(prev) => prev.map(
|
|
1246
|
+
(f) => f.name === fieldName ? { ...f, ...updates } : f
|
|
1247
|
+
)
|
|
1248
|
+
);
|
|
1249
|
+
if (updates.name && updates.name !== fieldName) {
|
|
1250
|
+
setSelectedFieldId(updates.name);
|
|
1251
|
+
}
|
|
1252
|
+
setIsDirty(true);
|
|
1253
|
+
},
|
|
1254
|
+
[]
|
|
1255
|
+
);
|
|
1256
|
+
const deleteField = useCallback4(
|
|
1257
|
+
(fieldName) => {
|
|
1258
|
+
setFieldsState((prev) => prev.filter((f) => f.name !== fieldName));
|
|
1259
|
+
if (selectedFieldId === fieldName) {
|
|
1260
|
+
setSelectedFieldId(null);
|
|
1261
|
+
}
|
|
1262
|
+
setIsDirty(true);
|
|
1263
|
+
},
|
|
1264
|
+
[selectedFieldId]
|
|
1265
|
+
);
|
|
1266
|
+
const moveField = useCallback4((fromIndex, toIndex) => {
|
|
1267
|
+
setFieldsState((prev) => {
|
|
1268
|
+
if (fromIndex < 0 || fromIndex >= prev.length || toIndex < 0 || toIndex >= prev.length) {
|
|
1269
|
+
return prev;
|
|
1270
|
+
}
|
|
1271
|
+
const newFields = [...prev];
|
|
1272
|
+
const [removed] = newFields.splice(fromIndex, 1);
|
|
1273
|
+
newFields.splice(toIndex, 0, removed);
|
|
1274
|
+
return newFields;
|
|
1275
|
+
});
|
|
1276
|
+
setIsDirty(true);
|
|
1277
|
+
}, []);
|
|
1278
|
+
const duplicateField = useCallback4((fieldName) => {
|
|
1279
|
+
let newFieldName = null;
|
|
1280
|
+
setFieldsState((prev) => {
|
|
1281
|
+
const fieldIndex = prev.findIndex((f) => f.name === fieldName);
|
|
1282
|
+
if (fieldIndex === -1) return prev;
|
|
1283
|
+
const field = prev[fieldIndex];
|
|
1284
|
+
newFieldName = generateFieldName(field.type);
|
|
1285
|
+
const newField = {
|
|
1286
|
+
...field,
|
|
1287
|
+
name: newFieldName,
|
|
1288
|
+
label: `${field.label} (Copy)`
|
|
1289
|
+
};
|
|
1290
|
+
const newFields = [...prev];
|
|
1291
|
+
newFields.splice(fieldIndex + 1, 0, newField);
|
|
1292
|
+
return newFields;
|
|
1293
|
+
});
|
|
1294
|
+
if (newFieldName) {
|
|
1295
|
+
setSelectedFieldId(newFieldName);
|
|
1296
|
+
}
|
|
1297
|
+
setIsDirty(true);
|
|
1298
|
+
}, []);
|
|
1299
|
+
const selectField = useCallback4((fieldName) => {
|
|
1300
|
+
setSelectedFieldId(fieldName);
|
|
1301
|
+
}, []);
|
|
1302
|
+
const updateFormData = useCallback4(
|
|
1303
|
+
(updates) => {
|
|
1304
|
+
setFormData((prev) => ({ ...prev, ...updates }));
|
|
1305
|
+
setIsDirty(true);
|
|
1306
|
+
},
|
|
1307
|
+
[]
|
|
1308
|
+
);
|
|
1309
|
+
const updateSettings = useCallback4((updates) => {
|
|
1310
|
+
setSettings((prev) => ({ ...prev, ...updates }));
|
|
1311
|
+
setIsDirty(true);
|
|
1312
|
+
}, []);
|
|
1313
|
+
const addNotification = useCallback4((notification) => {
|
|
1314
|
+
setNotifications((prev) => [...prev, notification]);
|
|
1315
|
+
setIsDirty(true);
|
|
1316
|
+
}, []);
|
|
1317
|
+
const updateNotification = useCallback4(
|
|
1318
|
+
(id, updates) => {
|
|
1319
|
+
setNotifications(
|
|
1320
|
+
(prev) => prev.map((n) => n.id === id ? { ...n, ...updates } : n)
|
|
1321
|
+
);
|
|
1322
|
+
setIsDirty(true);
|
|
1323
|
+
},
|
|
1324
|
+
[]
|
|
1325
|
+
);
|
|
1326
|
+
const deleteNotification = useCallback4((id) => {
|
|
1327
|
+
setNotifications((prev) => prev.filter((n) => n.id !== id));
|
|
1328
|
+
setIsDirty(true);
|
|
1329
|
+
}, []);
|
|
1330
|
+
const markAsSaved = useCallback4(() => {
|
|
1331
|
+
setIsDirty(false);
|
|
1332
|
+
}, []);
|
|
1333
|
+
const value = useMemo3(
|
|
1334
|
+
() => ({
|
|
1335
|
+
// State
|
|
1336
|
+
fields,
|
|
1337
|
+
selectedFieldId,
|
|
1338
|
+
activeTab,
|
|
1339
|
+
isDirty,
|
|
1340
|
+
formData,
|
|
1341
|
+
settings,
|
|
1342
|
+
notifications,
|
|
1343
|
+
// Actions
|
|
1344
|
+
setFields,
|
|
1345
|
+
addField,
|
|
1346
|
+
updateField,
|
|
1347
|
+
deleteField,
|
|
1348
|
+
moveField,
|
|
1349
|
+
duplicateField,
|
|
1350
|
+
selectField,
|
|
1351
|
+
setActiveTab,
|
|
1352
|
+
updateFormData,
|
|
1353
|
+
updateSettings,
|
|
1354
|
+
addNotification,
|
|
1355
|
+
updateNotification,
|
|
1356
|
+
deleteNotification,
|
|
1357
|
+
markAsSaved
|
|
1358
|
+
}),
|
|
1359
|
+
[
|
|
1360
|
+
fields,
|
|
1361
|
+
selectedFieldId,
|
|
1362
|
+
activeTab,
|
|
1363
|
+
isDirty,
|
|
1364
|
+
formData,
|
|
1365
|
+
settings,
|
|
1366
|
+
notifications,
|
|
1367
|
+
setFields,
|
|
1368
|
+
addField,
|
|
1369
|
+
updateField,
|
|
1370
|
+
deleteField,
|
|
1371
|
+
moveField,
|
|
1372
|
+
duplicateField,
|
|
1373
|
+
selectField,
|
|
1374
|
+
updateFormData,
|
|
1375
|
+
updateSettings,
|
|
1376
|
+
addNotification,
|
|
1377
|
+
updateNotification,
|
|
1378
|
+
deleteNotification,
|
|
1379
|
+
markAsSaved
|
|
1380
|
+
]
|
|
1381
|
+
);
|
|
1382
|
+
return /* @__PURE__ */ jsx4(FormBuilderContext.Provider, { value, children });
|
|
1383
|
+
}
|
|
1384
|
+
function useFormBuilder() {
|
|
1385
|
+
const context = useContext(FormBuilderContext);
|
|
1386
|
+
if (!context) {
|
|
1387
|
+
throw new Error("useFormBuilder must be used within a FormBuilderProvider");
|
|
1388
|
+
}
|
|
1389
|
+
return context;
|
|
1390
|
+
}
|
|
1391
|
+
function useOptionalFormBuilder() {
|
|
1392
|
+
return useContext(FormBuilderContext);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// src/admin/components/builder/FieldLibrary.tsx
|
|
1396
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1397
|
+
var FIELD_TYPES = [
|
|
1398
|
+
// -------------------------------------------------------------------------
|
|
1399
|
+
// Basic
|
|
1400
|
+
// -------------------------------------------------------------------------
|
|
1401
|
+
{
|
|
1402
|
+
type: "text",
|
|
1403
|
+
label: "Text",
|
|
1404
|
+
description: "Single line text input",
|
|
1405
|
+
iconPath: "M4 7h16M4 12h16M4 17h10",
|
|
1406
|
+
category: "basic"
|
|
1407
|
+
},
|
|
1408
|
+
{
|
|
1409
|
+
type: "email",
|
|
1410
|
+
label: "Email",
|
|
1411
|
+
description: "Email address with validation",
|
|
1412
|
+
iconPath: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z",
|
|
1413
|
+
category: "basic"
|
|
1414
|
+
},
|
|
1415
|
+
{
|
|
1416
|
+
type: "number",
|
|
1417
|
+
label: "Number",
|
|
1418
|
+
description: "Numeric input",
|
|
1419
|
+
iconPath: "M7 20l4-16m2 16l4-16M6 9h14M4 15h14",
|
|
1420
|
+
category: "basic"
|
|
1421
|
+
},
|
|
1422
|
+
{
|
|
1423
|
+
type: "phone",
|
|
1424
|
+
label: "Phone",
|
|
1425
|
+
description: "Phone number input",
|
|
1426
|
+
iconPath: "M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498A1 1 0 0121 15.72V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z",
|
|
1427
|
+
category: "basic"
|
|
1428
|
+
},
|
|
1429
|
+
{
|
|
1430
|
+
type: "url",
|
|
1431
|
+
label: "URL",
|
|
1432
|
+
description: "Website URL input",
|
|
1433
|
+
iconPath: "M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1",
|
|
1434
|
+
category: "basic"
|
|
1435
|
+
},
|
|
1436
|
+
{
|
|
1437
|
+
type: "textarea",
|
|
1438
|
+
label: "Textarea",
|
|
1439
|
+
description: "Multi-line text input",
|
|
1440
|
+
iconPath: "M4 6h16M4 12h16M4 18h7",
|
|
1441
|
+
category: "basic"
|
|
1442
|
+
},
|
|
1443
|
+
{
|
|
1444
|
+
type: "select",
|
|
1445
|
+
label: "Dropdown",
|
|
1446
|
+
description: "Dropdown select menu",
|
|
1447
|
+
iconPath: "M8 9l4-4 4 4m0 6l-4 4-4-4",
|
|
1448
|
+
category: "basic"
|
|
1449
|
+
},
|
|
1450
|
+
{
|
|
1451
|
+
type: "checkbox",
|
|
1452
|
+
label: "Checkbox",
|
|
1453
|
+
description: "Single checkbox",
|
|
1454
|
+
iconPath: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z",
|
|
1455
|
+
category: "basic"
|
|
1456
|
+
},
|
|
1457
|
+
{
|
|
1458
|
+
type: "radio",
|
|
1459
|
+
label: "Radio",
|
|
1460
|
+
description: "Radio button group",
|
|
1461
|
+
iconPath: "M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-6a4 4 0 100-8 4 4 0 000 8z",
|
|
1462
|
+
category: "basic"
|
|
1463
|
+
},
|
|
1464
|
+
// -------------------------------------------------------------------------
|
|
1465
|
+
// Advanced
|
|
1466
|
+
// -------------------------------------------------------------------------
|
|
1467
|
+
{
|
|
1468
|
+
type: "file",
|
|
1469
|
+
label: "File Upload",
|
|
1470
|
+
description: "File attachment",
|
|
1471
|
+
iconPath: "M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13",
|
|
1472
|
+
category: "advanced"
|
|
1473
|
+
},
|
|
1474
|
+
{
|
|
1475
|
+
type: "date",
|
|
1476
|
+
label: "Date",
|
|
1477
|
+
description: "Date picker",
|
|
1478
|
+
iconPath: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z",
|
|
1479
|
+
category: "advanced"
|
|
1480
|
+
},
|
|
1481
|
+
{
|
|
1482
|
+
type: "time",
|
|
1483
|
+
label: "Time",
|
|
1484
|
+
description: "Time picker",
|
|
1485
|
+
iconPath: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z",
|
|
1486
|
+
category: "advanced"
|
|
1487
|
+
},
|
|
1488
|
+
{
|
|
1489
|
+
type: "hidden",
|
|
1490
|
+
label: "Hidden",
|
|
1491
|
+
description: "Hidden field with static value",
|
|
1492
|
+
iconPath: "M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21",
|
|
1493
|
+
category: "advanced"
|
|
1494
|
+
}
|
|
1495
|
+
];
|
|
1496
|
+
var CATEGORIES = [
|
|
1497
|
+
{ key: "basic", label: "Basic" },
|
|
1498
|
+
{ key: "advanced", label: "Advanced" }
|
|
1499
|
+
];
|
|
1500
|
+
function DraggableFieldItem({
|
|
1501
|
+
type,
|
|
1502
|
+
label,
|
|
1503
|
+
description,
|
|
1504
|
+
iconPath,
|
|
1505
|
+
onAdd
|
|
1506
|
+
}) {
|
|
1507
|
+
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
|
1508
|
+
id: `library-${type}`,
|
|
1509
|
+
data: {
|
|
1510
|
+
isLibraryItem: true,
|
|
1511
|
+
fieldType: type
|
|
1512
|
+
}
|
|
1513
|
+
});
|
|
1514
|
+
return /* @__PURE__ */ jsxs4(
|
|
1515
|
+
"button",
|
|
1516
|
+
{
|
|
1517
|
+
ref: setNodeRef,
|
|
1518
|
+
type: "button",
|
|
1519
|
+
onClick: () => onAdd(type),
|
|
1520
|
+
title: description,
|
|
1521
|
+
className: `flex items-center gap-3 w-full px-2 py-2 rounded-none text-left group hover:bg-primary/5/50 transition-all duration-150 cursor-grab active:cursor-grabbing active:scale-[0.98] ${isDragging ? "opacity-50 bg-primary/5" : ""}`,
|
|
1522
|
+
...attributes,
|
|
1523
|
+
...listeners,
|
|
1524
|
+
children: [
|
|
1525
|
+
/* @__PURE__ */ jsx5(
|
|
1526
|
+
"div",
|
|
1527
|
+
{
|
|
1528
|
+
className: "shrink-0 flex items-center justify-center w-9 h-9 bg-primary/5 text-primary dark:bg-primary/20 dark:text-primary-foreground/80",
|
|
1529
|
+
style: {
|
|
1530
|
+
borderRadius: "6px",
|
|
1531
|
+
border: "1px solid hsl(var(--primary) / 0.25)"
|
|
1532
|
+
},
|
|
1533
|
+
children: /* @__PURE__ */ jsx5(
|
|
1534
|
+
"svg",
|
|
1535
|
+
{
|
|
1536
|
+
className: "h-4 w-4",
|
|
1537
|
+
viewBox: "0 0 24 24",
|
|
1538
|
+
fill: "none",
|
|
1539
|
+
stroke: "currentColor",
|
|
1540
|
+
strokeWidth: "1.75",
|
|
1541
|
+
strokeLinecap: "round",
|
|
1542
|
+
strokeLinejoin: "round",
|
|
1543
|
+
children: /* @__PURE__ */ jsx5("path", { d: iconPath })
|
|
1544
|
+
}
|
|
1545
|
+
)
|
|
1546
|
+
}
|
|
1547
|
+
),
|
|
1548
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex flex-col min-w-0", children: [
|
|
1549
|
+
/* @__PURE__ */ jsx5("span", { className: "text-[13px] font-semibold text-foreground leading-snug", children: label }),
|
|
1550
|
+
/* @__PURE__ */ jsx5("span", { className: "text-[11px] text-muted-foreground/80 leading-snug truncate", children: description })
|
|
1551
|
+
] })
|
|
1552
|
+
]
|
|
1553
|
+
}
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
function CategorySection({
|
|
1557
|
+
name,
|
|
1558
|
+
types,
|
|
1559
|
+
isOpen,
|
|
1560
|
+
onToggle,
|
|
1561
|
+
onAdd
|
|
1562
|
+
}) {
|
|
1563
|
+
return /* @__PURE__ */ jsxs4("div", { children: [
|
|
1564
|
+
/* @__PURE__ */ jsxs4(
|
|
1565
|
+
"button",
|
|
1566
|
+
{
|
|
1567
|
+
type: "button",
|
|
1568
|
+
onClick: onToggle,
|
|
1569
|
+
className: "flex items-center gap-1.5 w-full py-2 px-1 text-left select-none cursor-pointer",
|
|
1570
|
+
children: [
|
|
1571
|
+
/* @__PURE__ */ jsx5(
|
|
1572
|
+
"svg",
|
|
1573
|
+
{
|
|
1574
|
+
className: `h-3 w-3 transition-transform duration-200 shrink-0 ${isOpen ? "rotate-0" : "-rotate-90"}`,
|
|
1575
|
+
viewBox: "0 0 24 24",
|
|
1576
|
+
fill: "none",
|
|
1577
|
+
stroke: "#9ca3af",
|
|
1578
|
+
strokeWidth: "2",
|
|
1579
|
+
children: /* @__PURE__ */ jsx5("polyline", { points: "6 9 12 15 18 9" })
|
|
1580
|
+
}
|
|
1581
|
+
),
|
|
1582
|
+
/* @__PURE__ */ jsx5(
|
|
1583
|
+
"span",
|
|
1584
|
+
{
|
|
1585
|
+
className: "text-[13px] font-semibold tracking-widest uppercase",
|
|
1586
|
+
style: { color: "#9ca3af" },
|
|
1587
|
+
children: name
|
|
1588
|
+
}
|
|
1589
|
+
),
|
|
1590
|
+
/* @__PURE__ */ jsx5(
|
|
1591
|
+
"span",
|
|
1592
|
+
{
|
|
1593
|
+
className: "ml-auto text-[10px] font-medium leading-none",
|
|
1594
|
+
style: {
|
|
1595
|
+
background: "#f3f4f6",
|
|
1596
|
+
color: "#9ca3af",
|
|
1597
|
+
padding: "2px 6px",
|
|
1598
|
+
borderRadius: "4px"
|
|
1599
|
+
},
|
|
1600
|
+
children: types.length
|
|
1601
|
+
}
|
|
1602
|
+
)
|
|
1603
|
+
]
|
|
1604
|
+
}
|
|
1605
|
+
),
|
|
1606
|
+
isOpen && /* @__PURE__ */ jsx5(
|
|
1607
|
+
"div",
|
|
1608
|
+
{
|
|
1609
|
+
className: "mb-1 space-y-0.5",
|
|
1610
|
+
style: {
|
|
1611
|
+
marginLeft: "10px",
|
|
1612
|
+
paddingLeft: "10px",
|
|
1613
|
+
borderLeft: "1px solid #e5e7eb"
|
|
1614
|
+
},
|
|
1615
|
+
children: types.map((field) => /* @__PURE__ */ jsx5(DraggableFieldItem, { ...field, onAdd }, field.type))
|
|
1616
|
+
}
|
|
1617
|
+
)
|
|
1618
|
+
] });
|
|
1619
|
+
}
|
|
1620
|
+
function FieldLibrary() {
|
|
1621
|
+
const { addField } = useFormBuilder();
|
|
1622
|
+
const [searchQuery, setSearchQuery] = useState4("");
|
|
1623
|
+
const [openSection, setOpenSection] = useState4("Basic");
|
|
1624
|
+
const handleAdd = (type) => {
|
|
1625
|
+
const newField = createFieldFromType(type);
|
|
1626
|
+
addField(newField);
|
|
1627
|
+
};
|
|
1628
|
+
const toggleSection = (name) => {
|
|
1629
|
+
setOpenSection((prev) => prev === name ? "" : name);
|
|
1630
|
+
};
|
|
1631
|
+
const isSearching = searchQuery.trim().length > 0;
|
|
1632
|
+
const filteredCategories = useMemo4(() => {
|
|
1633
|
+
const byCategory = {
|
|
1634
|
+
basic: [],
|
|
1635
|
+
advanced: []
|
|
1636
|
+
};
|
|
1637
|
+
for (const field of FIELD_TYPES) {
|
|
1638
|
+
byCategory[field.category].push(field);
|
|
1639
|
+
}
|
|
1640
|
+
return CATEGORIES.map((cat) => ({
|
|
1641
|
+
...cat,
|
|
1642
|
+
types: byCategory[cat.key].filter((f) => {
|
|
1643
|
+
if (!isSearching) return true;
|
|
1644
|
+
const q = searchQuery.toLowerCase();
|
|
1645
|
+
return f.label.toLowerCase().includes(q) || f.type.toLowerCase().includes(q) || f.description.toLowerCase().includes(q);
|
|
1646
|
+
})
|
|
1647
|
+
})).filter((c) => c.types.length > 0);
|
|
1648
|
+
}, [searchQuery, isSearching]);
|
|
1649
|
+
const activeFieldCount = filteredCategories.reduce(
|
|
1650
|
+
(acc, cat) => acc + cat.types.length,
|
|
1651
|
+
0
|
|
1652
|
+
);
|
|
1653
|
+
return /* @__PURE__ */ jsxs4("div", { className: "h-full flex flex-col bg-background", children: [
|
|
1654
|
+
/* @__PURE__ */ jsx5("div", { className: "px-3 py-3 border-b border-primary/5", children: /* @__PURE__ */ jsxs4("div", { className: "relative", children: [
|
|
1655
|
+
/* @__PURE__ */ jsxs4(
|
|
1656
|
+
"svg",
|
|
1657
|
+
{
|
|
1658
|
+
className: "absolute left-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground/50",
|
|
1659
|
+
viewBox: "0 0 24 24",
|
|
1660
|
+
fill: "none",
|
|
1661
|
+
stroke: "currentColor",
|
|
1662
|
+
strokeWidth: "2",
|
|
1663
|
+
children: [
|
|
1664
|
+
/* @__PURE__ */ jsx5("circle", { cx: "11", cy: "11", r: "8" }),
|
|
1665
|
+
/* @__PURE__ */ jsx5("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
|
|
1666
|
+
]
|
|
1667
|
+
}
|
|
1668
|
+
),
|
|
1669
|
+
/* @__PURE__ */ jsx5(
|
|
1670
|
+
"input",
|
|
1671
|
+
{
|
|
1672
|
+
type: "search",
|
|
1673
|
+
placeholder: `Search ${activeFieldCount} field types...`,
|
|
1674
|
+
value: searchQuery,
|
|
1675
|
+
onChange: (e) => setSearchQuery(e.target.value),
|
|
1676
|
+
className: "text-[13px] pl-9 flex h-9 w-full rounded-none border border-input bg-primary/5/20 px-3 py-1 text-sm shadow-none transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
1677
|
+
style: { borderRadius: "6px", height: "38px" }
|
|
1678
|
+
}
|
|
1679
|
+
)
|
|
1680
|
+
] }) }),
|
|
1681
|
+
/* @__PURE__ */ jsxs4(
|
|
1682
|
+
"div",
|
|
1683
|
+
{
|
|
1684
|
+
className: "flex-1 overflow-y-auto px-3 py-3 space-y-3",
|
|
1685
|
+
style: {
|
|
1686
|
+
scrollbarWidth: "none",
|
|
1687
|
+
msOverflowStyle: "none"
|
|
1688
|
+
},
|
|
1689
|
+
children: [
|
|
1690
|
+
filteredCategories.map((category) => /* @__PURE__ */ jsx5(
|
|
1691
|
+
CategorySection,
|
|
1692
|
+
{
|
|
1693
|
+
name: category.label,
|
|
1694
|
+
types: category.types,
|
|
1695
|
+
isOpen: isSearching ? true : openSection === category.label,
|
|
1696
|
+
onToggle: () => toggleSection(category.label),
|
|
1697
|
+
onAdd: handleAdd
|
|
1698
|
+
},
|
|
1699
|
+
category.key
|
|
1700
|
+
)),
|
|
1701
|
+
filteredCategories.length === 0 && /* @__PURE__ */ jsxs4("div", { className: "text-center py-12", children: [
|
|
1702
|
+
/* @__PURE__ */ jsxs4(
|
|
1703
|
+
"svg",
|
|
1704
|
+
{
|
|
1705
|
+
className: "h-7 w-7 text-muted-foreground/20 mx-auto mb-2",
|
|
1706
|
+
viewBox: "0 0 24 24",
|
|
1707
|
+
fill: "none",
|
|
1708
|
+
stroke: "currentColor",
|
|
1709
|
+
strokeWidth: "2",
|
|
1710
|
+
children: [
|
|
1711
|
+
/* @__PURE__ */ jsx5("circle", { cx: "11", cy: "11", r: "8" }),
|
|
1712
|
+
/* @__PURE__ */ jsx5("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
|
|
1713
|
+
]
|
|
1714
|
+
}
|
|
1715
|
+
),
|
|
1716
|
+
/* @__PURE__ */ jsx5("p", { className: "text-sm text-muted-foreground", children: "No fields found" })
|
|
1717
|
+
] })
|
|
1718
|
+
]
|
|
1719
|
+
}
|
|
1720
|
+
),
|
|
1721
|
+
/* @__PURE__ */ jsx5("div", { className: "px-3 py-2.5 border-t border-primary/5", children: /* @__PURE__ */ jsx5("p", { className: "text-[11px] text-muted-foreground/50 text-center", children: "Drag fields to the canvas to add them" }) })
|
|
1722
|
+
] });
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
// src/admin/components/builder/FormCanvas.tsx
|
|
1726
|
+
import { useDroppable } from "@dnd-kit/core";
|
|
1727
|
+
import { useSortable } from "@dnd-kit/sortable";
|
|
1728
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
1729
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1730
|
+
var FIELD_ICON_PATHS = {
|
|
1731
|
+
text: "M4 7h16M4 12h16M4 17h10",
|
|
1732
|
+
email: "M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z",
|
|
1733
|
+
number: "M7 20l4-16m2 16l4-16M6 9h14M4 15h14",
|
|
1734
|
+
phone: "M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498A1 1 0 0121 15.72V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z",
|
|
1735
|
+
url: "M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1",
|
|
1736
|
+
textarea: "M4 6h16M4 12h16M4 18h7",
|
|
1737
|
+
select: "M8 9l4-4 4 4m0 6l-4 4-4-4",
|
|
1738
|
+
checkbox: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z",
|
|
1739
|
+
radio: "M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-6a4 4 0 100-8 4 4 0 000 8z",
|
|
1740
|
+
file: "M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13",
|
|
1741
|
+
date: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z",
|
|
1742
|
+
time: "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z",
|
|
1743
|
+
hidden: "M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
|
|
1744
|
+
};
|
|
1745
|
+
var DEFAULT_ICON_PATH = "M4 7h16M4 12h16M4 17h10";
|
|
1746
|
+
var FIELD_TYPE_LABELS = {
|
|
1747
|
+
text: "Text",
|
|
1748
|
+
email: "Email",
|
|
1749
|
+
number: "Number",
|
|
1750
|
+
phone: "Phone",
|
|
1751
|
+
url: "URL",
|
|
1752
|
+
textarea: "Textarea",
|
|
1753
|
+
select: "Dropdown",
|
|
1754
|
+
checkbox: "Checkbox",
|
|
1755
|
+
radio: "Radio",
|
|
1756
|
+
file: "File Upload",
|
|
1757
|
+
date: "Date",
|
|
1758
|
+
time: "Time",
|
|
1759
|
+
hidden: "Hidden"
|
|
1760
|
+
};
|
|
1761
|
+
function SortableFieldItem({
|
|
1762
|
+
field,
|
|
1763
|
+
isSelected,
|
|
1764
|
+
onSelect,
|
|
1765
|
+
onDelete
|
|
1766
|
+
}) {
|
|
1767
|
+
const {
|
|
1768
|
+
attributes,
|
|
1769
|
+
listeners,
|
|
1770
|
+
setNodeRef,
|
|
1771
|
+
transform,
|
|
1772
|
+
transition,
|
|
1773
|
+
isDragging
|
|
1774
|
+
} = useSortable({ id: field.name });
|
|
1775
|
+
const style = {
|
|
1776
|
+
transform: CSS.Transform.toString(transform),
|
|
1777
|
+
transition
|
|
1778
|
+
};
|
|
1779
|
+
const iconPath = FIELD_ICON_PATHS[field.type] || DEFAULT_ICON_PATH;
|
|
1780
|
+
const typeLabel = FIELD_TYPE_LABELS[field.type] || field.type;
|
|
1781
|
+
return /* @__PURE__ */ jsxs5(
|
|
1782
|
+
"div",
|
|
1783
|
+
{
|
|
1784
|
+
ref: setNodeRef,
|
|
1785
|
+
style,
|
|
1786
|
+
onClick: onSelect,
|
|
1787
|
+
role: "button",
|
|
1788
|
+
tabIndex: 0,
|
|
1789
|
+
onKeyDown: (e) => {
|
|
1790
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1791
|
+
e.preventDefault();
|
|
1792
|
+
onSelect();
|
|
1793
|
+
}
|
|
1794
|
+
},
|
|
1795
|
+
className: `flex items-center gap-3 py-3 px-4 border border-solid rounded-none cursor-pointer transition-all duration-150 group outline-none focus:outline-none focus:ring-0 ${isSelected ? "bg-transparent dark:bg-transparent shadow-none ring-0" : "border-primary/5 dark:border-primary/5 dark:bg-transparent dark:hover:bg-transparent"} ${isDragging ? "opacity-50" : ""}`,
|
|
1796
|
+
children: [
|
|
1797
|
+
/* @__PURE__ */ jsx6(
|
|
1798
|
+
"button",
|
|
1799
|
+
{
|
|
1800
|
+
type: "button",
|
|
1801
|
+
className: "p-1 shrink-0 cursor-grab active:cursor-grabbing text-muted-foreground/30 hover:text-muted-foreground transition-colors",
|
|
1802
|
+
"aria-label": `Drag to reorder ${field.label || field.name}`,
|
|
1803
|
+
onClick: (e) => e.stopPropagation(),
|
|
1804
|
+
...attributes,
|
|
1805
|
+
...listeners,
|
|
1806
|
+
children: /* @__PURE__ */ jsxs5(
|
|
1807
|
+
"svg",
|
|
1808
|
+
{
|
|
1809
|
+
className: "h-4 w-4",
|
|
1810
|
+
viewBox: "0 0 24 24",
|
|
1811
|
+
fill: "none",
|
|
1812
|
+
stroke: "currentColor",
|
|
1813
|
+
strokeWidth: "2",
|
|
1814
|
+
children: [
|
|
1815
|
+
/* @__PURE__ */ jsx6("circle", { cx: "9", cy: "5", r: "1", fill: "currentColor" }),
|
|
1816
|
+
/* @__PURE__ */ jsx6("circle", { cx: "9", cy: "12", r: "1", fill: "currentColor" }),
|
|
1817
|
+
/* @__PURE__ */ jsx6("circle", { cx: "9", cy: "19", r: "1", fill: "currentColor" }),
|
|
1818
|
+
/* @__PURE__ */ jsx6("circle", { cx: "15", cy: "5", r: "1", fill: "currentColor" }),
|
|
1819
|
+
/* @__PURE__ */ jsx6("circle", { cx: "15", cy: "12", r: "1", fill: "currentColor" }),
|
|
1820
|
+
/* @__PURE__ */ jsx6("circle", { cx: "15", cy: "19", r: "1", fill: "currentColor" })
|
|
1821
|
+
]
|
|
1822
|
+
}
|
|
1823
|
+
)
|
|
1824
|
+
}
|
|
1825
|
+
),
|
|
1826
|
+
/* @__PURE__ */ jsx6(
|
|
1827
|
+
"div",
|
|
1828
|
+
{
|
|
1829
|
+
className: "shrink-0 flex items-center justify-center w-9 h-9 bg-primary/5 text-primary dark:bg-primary/20 dark:text-white mr-1",
|
|
1830
|
+
style: {
|
|
1831
|
+
borderRadius: "6px",
|
|
1832
|
+
border: "1px solid hsl(var(--primary) / 0.25)"
|
|
1833
|
+
},
|
|
1834
|
+
children: /* @__PURE__ */ jsx6(
|
|
1835
|
+
"svg",
|
|
1836
|
+
{
|
|
1837
|
+
className: "h-4 w-4",
|
|
1838
|
+
viewBox: "0 0 24 24",
|
|
1839
|
+
fill: "none",
|
|
1840
|
+
stroke: "currentColor",
|
|
1841
|
+
strokeWidth: "1.75",
|
|
1842
|
+
strokeLinecap: "round",
|
|
1843
|
+
strokeLinejoin: "round",
|
|
1844
|
+
children: /* @__PURE__ */ jsx6("path", { d: iconPath })
|
|
1845
|
+
}
|
|
1846
|
+
)
|
|
1847
|
+
}
|
|
1848
|
+
),
|
|
1849
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex-1 flex items-center gap-2 min-w-0 flex-wrap", children: [
|
|
1850
|
+
/* @__PURE__ */ jsx6("span", { className: "text-sm font-medium text-foreground truncate", children: field.label || field.name || "Unnamed Field" }),
|
|
1851
|
+
/* @__PURE__ */ jsx6("span", { className: "text-muted-foreground/40 text-xs shrink-0", children: "\u2022" }),
|
|
1852
|
+
/* @__PURE__ */ jsx6("span", { className: "text-[10px] font-medium shrink-0 px-2 py-0 leading-5 rounded-full border border-primary/5 bg-primary/5 text-muted-foreground capitalize", children: typeLabel }),
|
|
1853
|
+
field.required && /* @__PURE__ */ jsx6("span", { className: "text-[10px] px-2 py-0 leading-5 bg-red-50 text-red-600 dark:bg-red-500/20 dark:text-red-400 font-normal rounded-full border border-red-200 dark:border-red-500/30 shrink-0", children: "Required" })
|
|
1854
|
+
] }),
|
|
1855
|
+
/* @__PURE__ */ jsx6(
|
|
1856
|
+
"button",
|
|
1857
|
+
{
|
|
1858
|
+
type: "button",
|
|
1859
|
+
onClick: (e) => {
|
|
1860
|
+
e.stopPropagation();
|
|
1861
|
+
onDelete();
|
|
1862
|
+
},
|
|
1863
|
+
title: `Delete ${field.label || field.name}`,
|
|
1864
|
+
"aria-label": `Delete ${field.label || field.name}`,
|
|
1865
|
+
className: "shrink-0 flex items-center justify-center w-7 h-7 rounded-none text-muted-foreground/40 hover:text-destructive hover:bg-destructive/10 transition-all duration-150 opacity-0 group-hover:opacity-100",
|
|
1866
|
+
children: /* @__PURE__ */ jsxs5(
|
|
1867
|
+
"svg",
|
|
1868
|
+
{
|
|
1869
|
+
className: "h-3.5 w-3.5",
|
|
1870
|
+
viewBox: "0 0 24 24",
|
|
1871
|
+
fill: "none",
|
|
1872
|
+
stroke: "currentColor",
|
|
1873
|
+
strokeWidth: "2",
|
|
1874
|
+
children: [
|
|
1875
|
+
/* @__PURE__ */ jsx6("polyline", { points: "3 6 5 6 21 6" }),
|
|
1876
|
+
/* @__PURE__ */ jsx6("path", { d: "M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a1 1 0 011-1h4a1 1 0 011 1v2" })
|
|
1877
|
+
]
|
|
1878
|
+
}
|
|
1879
|
+
)
|
|
1880
|
+
}
|
|
1881
|
+
)
|
|
1882
|
+
]
|
|
1883
|
+
}
|
|
1884
|
+
);
|
|
1885
|
+
}
|
|
1886
|
+
function FormCanvas({
|
|
1887
|
+
fields,
|
|
1888
|
+
selectedFieldId,
|
|
1889
|
+
onFieldSelect,
|
|
1890
|
+
onFieldDelete
|
|
1891
|
+
}) {
|
|
1892
|
+
const { setNodeRef, isOver } = useDroppable({
|
|
1893
|
+
id: "canvas-drop-zone"
|
|
1894
|
+
});
|
|
1895
|
+
if (fields.length === 0) {
|
|
1896
|
+
return /* @__PURE__ */ jsx6(
|
|
1897
|
+
"div",
|
|
1898
|
+
{
|
|
1899
|
+
ref: setNodeRef,
|
|
1900
|
+
id: "canvas-drop-zone",
|
|
1901
|
+
className: `min-h-[320px] flex flex-col items-center justify-center border-2 border-dashed rounded-none transition-all duration-200 ${isOver ? "border-primary bg-primary/5" : "border-primary/5 bg-primary/5/20"}`,
|
|
1902
|
+
children: /* @__PURE__ */ jsxs5("div", { className: "text-center px-6 py-12", children: [
|
|
1903
|
+
/* @__PURE__ */ jsx6(
|
|
1904
|
+
"div",
|
|
1905
|
+
{
|
|
1906
|
+
className: `mx-auto mb-4 flex items-center justify-center w-12 h-12 rounded-none border transition-colors ${isOver ? "border-primary/30 bg-primary/5 text-primary" : "border-primary/5 bg-primary/5 text-muted-foreground/40"}`,
|
|
1907
|
+
children: /* @__PURE__ */ jsxs5(
|
|
1908
|
+
"svg",
|
|
1909
|
+
{
|
|
1910
|
+
className: "h-5 w-5",
|
|
1911
|
+
viewBox: "0 0 24 24",
|
|
1912
|
+
fill: "none",
|
|
1913
|
+
stroke: "currentColor",
|
|
1914
|
+
strokeWidth: "1.5",
|
|
1915
|
+
children: [
|
|
1916
|
+
/* @__PURE__ */ jsx6("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
|
|
1917
|
+
/* @__PURE__ */ jsx6("polyline", { points: "19 12 12 19 5 12" })
|
|
1918
|
+
]
|
|
1919
|
+
}
|
|
1920
|
+
)
|
|
1921
|
+
}
|
|
1922
|
+
),
|
|
1923
|
+
/* @__PURE__ */ jsx6(
|
|
1924
|
+
"p",
|
|
1925
|
+
{
|
|
1926
|
+
className: `text-sm font-medium mb-1 ${isOver ? "text-primary" : "text-foreground"}`,
|
|
1927
|
+
children: isOver ? "Release to add field" : "No fields yet"
|
|
1928
|
+
}
|
|
1929
|
+
),
|
|
1930
|
+
/* @__PURE__ */ jsx6("p", { className: "text-xs text-muted-foreground", children: isOver ? "Drop the field here" : "Drag a field from the panel on the right to add it here" })
|
|
1931
|
+
] })
|
|
1932
|
+
}
|
|
1933
|
+
);
|
|
1934
|
+
}
|
|
1935
|
+
return /* @__PURE__ */ jsxs5(
|
|
1936
|
+
"div",
|
|
1937
|
+
{
|
|
1938
|
+
ref: setNodeRef,
|
|
1939
|
+
className: `min-h-[120px] transition-all duration-200 ${isOver ? "bg-primary/5 rounded-none" : ""}`,
|
|
1940
|
+
children: [
|
|
1941
|
+
/* @__PURE__ */ jsx6("div", { className: "space-y-2", children: fields.map((field) => /* @__PURE__ */ jsx6(
|
|
1942
|
+
SortableFieldItem,
|
|
1943
|
+
{
|
|
1944
|
+
field,
|
|
1945
|
+
isSelected: selectedFieldId === field.name,
|
|
1946
|
+
onSelect: () => onFieldSelect(field.name),
|
|
1947
|
+
onDelete: () => onFieldDelete(field.name)
|
|
1948
|
+
},
|
|
1949
|
+
field.name
|
|
1950
|
+
)) }),
|
|
1951
|
+
isOver && /* @__PURE__ */ jsx6("div", { className: "mt-2 flex items-center justify-center py-3 rounded-none border-2 border-dashed border-primary bg-primary/5", children: /* @__PURE__ */ jsx6("p", { className: "text-xs font-medium text-primary", children: "Release to add field here" }) })
|
|
1952
|
+
]
|
|
1953
|
+
}
|
|
1954
|
+
);
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
// src/admin/components/builder/FormNotificationsTab.tsx
|
|
1958
|
+
import { Button as Button3, Input as Input3 } from "@nextlyhq/ui";
|
|
1959
|
+
import { useCallback as useCallback5, useEffect as useEffect2, useRef, useState as useState5 } from "react";
|
|
1960
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1961
|
+
async function fetchProviders() {
|
|
1962
|
+
try {
|
|
1963
|
+
const res = await fetch("/admin/api/email-providers?pageSize=100&page=1", {
|
|
1964
|
+
credentials: "include"
|
|
1965
|
+
});
|
|
1966
|
+
if (!res.ok) return [];
|
|
1967
|
+
const json = await res.json();
|
|
1968
|
+
return json.providers ?? [];
|
|
1969
|
+
} catch {
|
|
1970
|
+
return [];
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
async function fetchTemplates() {
|
|
1974
|
+
try {
|
|
1975
|
+
const res = await fetch("/admin/api/email-templates", {
|
|
1976
|
+
credentials: "include"
|
|
1977
|
+
});
|
|
1978
|
+
if (!res.ok) return [];
|
|
1979
|
+
const json = await res.json();
|
|
1980
|
+
return json.templates ?? [];
|
|
1981
|
+
} catch {
|
|
1982
|
+
return [];
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
function IntegrationCard({
|
|
1986
|
+
notification,
|
|
1987
|
+
providerName,
|
|
1988
|
+
onEdit,
|
|
1989
|
+
onDelete
|
|
1990
|
+
}) {
|
|
1991
|
+
return /* @__PURE__ */ jsxs6(
|
|
1992
|
+
"div",
|
|
1993
|
+
{
|
|
1994
|
+
className: `form-integration-card ${!notification.enabled ? "form-integration-card--disabled" : ""}`,
|
|
1995
|
+
children: [
|
|
1996
|
+
/* @__PURE__ */ jsx7("div", { className: "form-integration-card-left", children: /* @__PURE__ */ jsxs6("div", { className: "form-integration-card-info", children: [
|
|
1997
|
+
/* @__PURE__ */ jsx7("span", { className: "form-integration-card-name", children: notification.name }),
|
|
1998
|
+
/* @__PURE__ */ jsxs6("span", { className: "form-integration-provider-badge", children: [
|
|
1999
|
+
"Provider: ",
|
|
2000
|
+
providerName
|
|
2001
|
+
] })
|
|
2002
|
+
] }) }),
|
|
2003
|
+
/* @__PURE__ */ jsxs6("div", { className: "form-integration-card-actions", children: [
|
|
2004
|
+
/* @__PURE__ */ jsx7(
|
|
2005
|
+
"button",
|
|
2006
|
+
{
|
|
2007
|
+
type: "button",
|
|
2008
|
+
className: "form-integration-btn form-integration-btn--edit",
|
|
2009
|
+
onClick: onEdit,
|
|
2010
|
+
children: "Edit"
|
|
2011
|
+
}
|
|
2012
|
+
),
|
|
2013
|
+
/* @__PURE__ */ jsx7(
|
|
2014
|
+
"button",
|
|
2015
|
+
{
|
|
2016
|
+
type: "button",
|
|
2017
|
+
className: "form-integration-btn form-integration-btn--delete",
|
|
2018
|
+
onClick: onDelete,
|
|
2019
|
+
children: "Delete"
|
|
2020
|
+
}
|
|
2021
|
+
)
|
|
2022
|
+
] })
|
|
2023
|
+
]
|
|
2024
|
+
}
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
2027
|
+
function IntegrationModal({
|
|
2028
|
+
initial,
|
|
2029
|
+
providers,
|
|
2030
|
+
templates,
|
|
2031
|
+
fields,
|
|
2032
|
+
isEditing,
|
|
2033
|
+
onSave,
|
|
2034
|
+
onCancel
|
|
2035
|
+
}) {
|
|
2036
|
+
const [form, setForm] = useState5(initial);
|
|
2037
|
+
const [newCc, setNewCc] = useState5("");
|
|
2038
|
+
const [newBcc, setNewBcc] = useState5("");
|
|
2039
|
+
const overlayRef = useRef(null);
|
|
2040
|
+
const update = useCallback5(
|
|
2041
|
+
(key, value) => {
|
|
2042
|
+
setForm((prev) => ({ ...prev, [key]: value }));
|
|
2043
|
+
},
|
|
2044
|
+
[]
|
|
2045
|
+
);
|
|
2046
|
+
const addCc = useCallback5(() => {
|
|
2047
|
+
const email = newCc.trim();
|
|
2048
|
+
if (!email) return;
|
|
2049
|
+
setForm((prev) => ({ ...prev, cc: [...prev.cc, email] }));
|
|
2050
|
+
setNewCc("");
|
|
2051
|
+
}, [newCc]);
|
|
2052
|
+
const removeCc = useCallback5((index) => {
|
|
2053
|
+
setForm((prev) => ({ ...prev, cc: prev.cc.filter((_, i) => i !== index) }));
|
|
2054
|
+
}, []);
|
|
2055
|
+
const addBcc = useCallback5(() => {
|
|
2056
|
+
const email = newBcc.trim();
|
|
2057
|
+
if (!email) return;
|
|
2058
|
+
setForm((prev) => ({ ...prev, bcc: [...prev.bcc, email] }));
|
|
2059
|
+
setNewBcc("");
|
|
2060
|
+
}, [newBcc]);
|
|
2061
|
+
const removeBcc = useCallback5((index) => {
|
|
2062
|
+
setForm((prev) => ({ ...prev, bcc: prev.bcc.filter((_, i) => i !== index) }));
|
|
2063
|
+
}, []);
|
|
2064
|
+
const handleSubmit = useCallback5(() => {
|
|
2065
|
+
onSave(form);
|
|
2066
|
+
}, [form, onSave]);
|
|
2067
|
+
const handleOverlayClick = useCallback5(
|
|
2068
|
+
(e) => {
|
|
2069
|
+
if (e.target === overlayRef.current) {
|
|
2070
|
+
onCancel();
|
|
2071
|
+
}
|
|
2072
|
+
},
|
|
2073
|
+
[onCancel]
|
|
2074
|
+
);
|
|
2075
|
+
const defaultProvider = providers.find((p) => p.isDefault);
|
|
2076
|
+
const defaultLabel = defaultProvider ? `System Default (${defaultProvider.name})` : "System Default";
|
|
2077
|
+
return /* @__PURE__ */ jsx7(
|
|
2078
|
+
"div",
|
|
2079
|
+
{
|
|
2080
|
+
className: "fixed inset-0 z-50 bg-background/80 backdrop-blur-sm flex items-center justify-center p-4 sm:p-6",
|
|
2081
|
+
ref: overlayRef,
|
|
2082
|
+
onClick: handleOverlayClick,
|
|
2083
|
+
children: /* @__PURE__ */ jsxs6("div", { className: "bg-background rounded-none border border-primary/5 shadow-md w-full max-w-lg overflow-hidden flex flex-col max-h-[90vh]", children: [
|
|
2084
|
+
/* @__PURE__ */ jsxs6("div", { className: "px-6 py-5 border-b border-primary/5 flex items-center justify-between sticky top-0 bg-background z-10", children: [
|
|
2085
|
+
/* @__PURE__ */ jsx7("h3", { className: "text-lg font-semibold text-foreground", children: isEditing ? "Edit Email Integration" : "Add Email Integration" }),
|
|
2086
|
+
/* @__PURE__ */ jsx7(
|
|
2087
|
+
"button",
|
|
2088
|
+
{
|
|
2089
|
+
onClick: onCancel,
|
|
2090
|
+
className: "text-muted-foreground hover:text-foreground",
|
|
2091
|
+
children: /* @__PURE__ */ jsxs6(
|
|
2092
|
+
"svg",
|
|
2093
|
+
{
|
|
2094
|
+
width: "20",
|
|
2095
|
+
height: "20",
|
|
2096
|
+
viewBox: "0 0 24 24",
|
|
2097
|
+
fill: "none",
|
|
2098
|
+
stroke: "currentColor",
|
|
2099
|
+
strokeWidth: "2",
|
|
2100
|
+
strokeLinecap: "round",
|
|
2101
|
+
strokeLinejoin: "round",
|
|
2102
|
+
children: [
|
|
2103
|
+
/* @__PURE__ */ jsx7("path", { d: "M18 6 6 18" }),
|
|
2104
|
+
/* @__PURE__ */ jsx7("path", { d: "m6 6 12 12" })
|
|
2105
|
+
]
|
|
2106
|
+
}
|
|
2107
|
+
)
|
|
2108
|
+
}
|
|
2109
|
+
)
|
|
2110
|
+
] }),
|
|
2111
|
+
/* @__PURE__ */ jsxs6("div", { className: "p-6 overflow-y-auto space-y-6", children: [
|
|
2112
|
+
/* @__PURE__ */ jsxs6("div", { className: "space-y-1.5", children: [
|
|
2113
|
+
/* @__PURE__ */ jsx7("label", { className: "text-sm font-medium text-foreground", children: "Integration Name" }),
|
|
2114
|
+
/* @__PURE__ */ jsx7(
|
|
2115
|
+
Input3,
|
|
2116
|
+
{
|
|
2117
|
+
type: "text",
|
|
2118
|
+
value: form.name,
|
|
2119
|
+
onChange: (e) => update("name", e.target.value),
|
|
2120
|
+
placeholder: "e.g. Admin Notification"
|
|
2121
|
+
}
|
|
2122
|
+
)
|
|
2123
|
+
] }),
|
|
2124
|
+
/* @__PURE__ */ jsxs6("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [
|
|
2125
|
+
/* @__PURE__ */ jsxs6("div", { className: "space-y-1.5", children: [
|
|
2126
|
+
/* @__PURE__ */ jsx7("label", { className: "text-sm font-medium text-foreground", children: "Email Provider" }),
|
|
2127
|
+
/* @__PURE__ */ jsxs6(
|
|
2128
|
+
"select",
|
|
2129
|
+
{
|
|
2130
|
+
className: "flex h-9 w-full rounded-none border border-input bg-transparent px-3 py-1 text-sm shadow-none focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
2131
|
+
value: form.providerId ?? "",
|
|
2132
|
+
onChange: (e) => update("providerId", e.target.value || void 0),
|
|
2133
|
+
children: [
|
|
2134
|
+
/* @__PURE__ */ jsx7("option", { value: "", children: defaultLabel }),
|
|
2135
|
+
providers.map((p) => /* @__PURE__ */ jsxs6("option", { value: p.id, children: [
|
|
2136
|
+
p.name,
|
|
2137
|
+
p.isDefault ? " (Default)" : ""
|
|
2138
|
+
] }, p.id))
|
|
2139
|
+
]
|
|
2140
|
+
}
|
|
2141
|
+
)
|
|
2142
|
+
] }),
|
|
2143
|
+
/* @__PURE__ */ jsxs6("div", { className: "space-y-1.5", children: [
|
|
2144
|
+
/* @__PURE__ */ jsx7("label", { className: "text-sm font-medium text-foreground", children: "Email Template" }),
|
|
2145
|
+
/* @__PURE__ */ jsxs6(
|
|
2146
|
+
"select",
|
|
2147
|
+
{
|
|
2148
|
+
className: "flex h-9 w-full rounded-none border border-input bg-transparent px-3 py-1 text-sm shadow-none focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
2149
|
+
value: form.templateSlug ?? "",
|
|
2150
|
+
onChange: (e) => update("templateSlug", e.target.value || void 0),
|
|
2151
|
+
children: [
|
|
2152
|
+
/* @__PURE__ */ jsx7("option", { value: "", children: "Select an email template" }),
|
|
2153
|
+
templates.filter((t) => t.isActive).map((t) => /* @__PURE__ */ jsx7("option", { value: t.slug, children: t.name }, t.id))
|
|
2154
|
+
]
|
|
2155
|
+
}
|
|
2156
|
+
)
|
|
2157
|
+
] })
|
|
2158
|
+
] }),
|
|
2159
|
+
/* @__PURE__ */ jsxs6("div", { className: "space-y-1.5", children: [
|
|
2160
|
+
/* @__PURE__ */ jsx7("label", { className: "text-sm font-medium text-foreground", children: "Sender Email" }),
|
|
2161
|
+
/* @__PURE__ */ jsx7(
|
|
2162
|
+
Input3,
|
|
2163
|
+
{
|
|
2164
|
+
type: "email",
|
|
2165
|
+
value: form.senderEmail ?? "",
|
|
2166
|
+
onChange: (e) => update("senderEmail", e.target.value || void 0),
|
|
2167
|
+
placeholder: "noreply@example.com"
|
|
2168
|
+
}
|
|
2169
|
+
),
|
|
2170
|
+
/* @__PURE__ */ jsx7("p", { className: "text-xs text-muted-foreground", children: "Leave blank to use the provider's default configured address." })
|
|
2171
|
+
] }),
|
|
2172
|
+
/* @__PURE__ */ jsxs6("div", { className: "space-y-4 pt-4 border-t border-primary/5", children: [
|
|
2173
|
+
/* @__PURE__ */ jsxs6("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-4", children: [
|
|
2174
|
+
/* @__PURE__ */ jsxs6("div", { className: "space-y-1.5", children: [
|
|
2175
|
+
/* @__PURE__ */ jsx7("label", { className: "text-sm font-medium text-foreground", children: "Recipient Type" }),
|
|
2176
|
+
/* @__PURE__ */ jsxs6(
|
|
2177
|
+
"select",
|
|
2178
|
+
{
|
|
2179
|
+
className: "flex h-9 w-full rounded-none border border-input bg-transparent px-3 py-1 text-sm shadow-none focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
2180
|
+
value: form.recipientType,
|
|
2181
|
+
onChange: (e) => update(
|
|
2182
|
+
"recipientType",
|
|
2183
|
+
e.target.value
|
|
2184
|
+
),
|
|
2185
|
+
children: [
|
|
2186
|
+
/* @__PURE__ */ jsx7("option", { value: "static", children: "Static Email Address" }),
|
|
2187
|
+
/* @__PURE__ */ jsx7("option", { value: "field", children: "Email from Form Field" })
|
|
2188
|
+
]
|
|
2189
|
+
}
|
|
2190
|
+
)
|
|
2191
|
+
] }),
|
|
2192
|
+
/* @__PURE__ */ jsxs6("div", { className: "space-y-1.5", children: [
|
|
2193
|
+
/* @__PURE__ */ jsx7("label", { className: "text-sm font-medium text-foreground", children: form.recipientType === "field" ? "Target Email Field" : "Recipient Email" }),
|
|
2194
|
+
form.recipientType === "field" ? /* @__PURE__ */ jsxs6(
|
|
2195
|
+
"select",
|
|
2196
|
+
{
|
|
2197
|
+
className: "flex h-9 w-full rounded-none border border-input bg-transparent px-3 py-1 text-sm shadow-none focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
2198
|
+
value: form.to,
|
|
2199
|
+
onChange: (e) => update("to", e.target.value),
|
|
2200
|
+
children: [
|
|
2201
|
+
/* @__PURE__ */ jsx7("option", { value: "", children: "Select a field" }),
|
|
2202
|
+
fields.map((f) => /* @__PURE__ */ jsxs6("option", { value: `{{${f.name}}}`, children: [
|
|
2203
|
+
f.label,
|
|
2204
|
+
" (",
|
|
2205
|
+
`{{${f.name}}}`,
|
|
2206
|
+
")"
|
|
2207
|
+
] }, f.name))
|
|
2208
|
+
]
|
|
2209
|
+
}
|
|
2210
|
+
) : /* @__PURE__ */ jsx7(
|
|
2211
|
+
Input3,
|
|
2212
|
+
{
|
|
2213
|
+
type: "email",
|
|
2214
|
+
value: form.to,
|
|
2215
|
+
onChange: (e) => update("to", e.target.value),
|
|
2216
|
+
placeholder: "admin@example.com"
|
|
2217
|
+
}
|
|
2218
|
+
)
|
|
2219
|
+
] })
|
|
2220
|
+
] }),
|
|
2221
|
+
/* @__PURE__ */ jsxs6("div", { className: "space-y-1.5", children: [
|
|
2222
|
+
/* @__PURE__ */ jsx7("label", { className: "text-sm font-medium text-foreground", children: "CC Recipients (Optional)" }),
|
|
2223
|
+
form.cc.length > 0 && /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-2 mb-2", children: form.cc.map((email, i) => /* @__PURE__ */ jsxs6(
|
|
2224
|
+
"div",
|
|
2225
|
+
{
|
|
2226
|
+
className: "inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-primary/5 text-xs font-medium border border-primary/5",
|
|
2227
|
+
children: [
|
|
2228
|
+
/* @__PURE__ */ jsx7("span", { children: email }),
|
|
2229
|
+
/* @__PURE__ */ jsx7(
|
|
2230
|
+
"button",
|
|
2231
|
+
{
|
|
2232
|
+
type: "button",
|
|
2233
|
+
className: "text-muted-foreground hover:text-foreground hover:bg-background rounded-full p-0.5",
|
|
2234
|
+
onClick: () => removeCc(i),
|
|
2235
|
+
children: /* @__PURE__ */ jsxs6(
|
|
2236
|
+
"svg",
|
|
2237
|
+
{
|
|
2238
|
+
width: "12",
|
|
2239
|
+
height: "12",
|
|
2240
|
+
viewBox: "0 0 24 24",
|
|
2241
|
+
fill: "none",
|
|
2242
|
+
stroke: "currentColor",
|
|
2243
|
+
strokeWidth: "2",
|
|
2244
|
+
strokeLinecap: "round",
|
|
2245
|
+
strokeLinejoin: "round",
|
|
2246
|
+
children: [
|
|
2247
|
+
/* @__PURE__ */ jsx7("path", { d: "M18 6 6 18" }),
|
|
2248
|
+
/* @__PURE__ */ jsx7("path", { d: "m6 6 12 12" })
|
|
2249
|
+
]
|
|
2250
|
+
}
|
|
2251
|
+
)
|
|
2252
|
+
}
|
|
2253
|
+
)
|
|
2254
|
+
]
|
|
2255
|
+
},
|
|
2256
|
+
i
|
|
2257
|
+
)) }),
|
|
2258
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
|
|
2259
|
+
/* @__PURE__ */ jsx7(
|
|
2260
|
+
Input3,
|
|
2261
|
+
{
|
|
2262
|
+
type: "email",
|
|
2263
|
+
value: newCc,
|
|
2264
|
+
onChange: (e) => setNewCc(e.target.value),
|
|
2265
|
+
onKeyDown: (e) => {
|
|
2266
|
+
if (e.key === "Enter") {
|
|
2267
|
+
e.preventDefault();
|
|
2268
|
+
addCc();
|
|
2269
|
+
}
|
|
2270
|
+
},
|
|
2271
|
+
placeholder: "cc@example.com"
|
|
2272
|
+
}
|
|
2273
|
+
),
|
|
2274
|
+
/* @__PURE__ */ jsx7(
|
|
2275
|
+
Button3,
|
|
2276
|
+
{
|
|
2277
|
+
type: "button",
|
|
2278
|
+
variant: "outline",
|
|
2279
|
+
onClick: addCc,
|
|
2280
|
+
className: "shrink-0 px-3",
|
|
2281
|
+
children: "Add CC"
|
|
2282
|
+
}
|
|
2283
|
+
)
|
|
2284
|
+
] })
|
|
2285
|
+
] }),
|
|
2286
|
+
/* @__PURE__ */ jsxs6("div", { className: "space-y-1.5", children: [
|
|
2287
|
+
/* @__PURE__ */ jsx7("label", { className: "text-sm font-medium text-foreground", children: "BCC Recipients (Optional)" }),
|
|
2288
|
+
form.bcc.length > 0 && /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-2 mb-2", children: form.bcc.map((email, i) => /* @__PURE__ */ jsxs6(
|
|
2289
|
+
"div",
|
|
2290
|
+
{
|
|
2291
|
+
className: "inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-primary/5 text-xs font-medium border border-primary/5",
|
|
2292
|
+
children: [
|
|
2293
|
+
/* @__PURE__ */ jsx7("span", { children: email }),
|
|
2294
|
+
/* @__PURE__ */ jsx7(
|
|
2295
|
+
"button",
|
|
2296
|
+
{
|
|
2297
|
+
type: "button",
|
|
2298
|
+
className: "text-muted-foreground hover:text-foreground hover:bg-background rounded-full p-0.5",
|
|
2299
|
+
onClick: () => removeBcc(i),
|
|
2300
|
+
children: /* @__PURE__ */ jsxs6(
|
|
2301
|
+
"svg",
|
|
2302
|
+
{
|
|
2303
|
+
width: "12",
|
|
2304
|
+
height: "12",
|
|
2305
|
+
viewBox: "0 0 24 24",
|
|
2306
|
+
fill: "none",
|
|
2307
|
+
stroke: "currentColor",
|
|
2308
|
+
strokeWidth: "2",
|
|
2309
|
+
strokeLinecap: "round",
|
|
2310
|
+
strokeLinejoin: "round",
|
|
2311
|
+
children: [
|
|
2312
|
+
/* @__PURE__ */ jsx7("path", { d: "M18 6 6 18" }),
|
|
2313
|
+
/* @__PURE__ */ jsx7("path", { d: "m6 6 12 12" })
|
|
2314
|
+
]
|
|
2315
|
+
}
|
|
2316
|
+
)
|
|
2317
|
+
}
|
|
2318
|
+
)
|
|
2319
|
+
]
|
|
2320
|
+
},
|
|
2321
|
+
i
|
|
2322
|
+
)) }),
|
|
2323
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
|
|
2324
|
+
/* @__PURE__ */ jsx7(
|
|
2325
|
+
Input3,
|
|
2326
|
+
{
|
|
2327
|
+
type: "email",
|
|
2328
|
+
value: newBcc,
|
|
2329
|
+
onChange: (e) => setNewBcc(e.target.value),
|
|
2330
|
+
onKeyDown: (e) => {
|
|
2331
|
+
if (e.key === "Enter") {
|
|
2332
|
+
e.preventDefault();
|
|
2333
|
+
addBcc();
|
|
2334
|
+
}
|
|
2335
|
+
},
|
|
2336
|
+
placeholder: "bcc@example.com"
|
|
2337
|
+
}
|
|
2338
|
+
),
|
|
2339
|
+
/* @__PURE__ */ jsx7(
|
|
2340
|
+
Button3,
|
|
2341
|
+
{
|
|
2342
|
+
type: "button",
|
|
2343
|
+
variant: "outline",
|
|
2344
|
+
onClick: addBcc,
|
|
2345
|
+
className: "shrink-0 px-3",
|
|
2346
|
+
children: "Add BCC"
|
|
2347
|
+
}
|
|
2348
|
+
)
|
|
2349
|
+
] })
|
|
2350
|
+
] })
|
|
2351
|
+
] }),
|
|
2352
|
+
/* @__PURE__ */ jsxs6("div", { className: "pt-4 border-t border-primary/5 flex items-center justify-between", children: [
|
|
2353
|
+
/* @__PURE__ */ jsxs6("div", { children: [
|
|
2354
|
+
/* @__PURE__ */ jsx7("label", { className: "text-sm font-medium text-foreground", children: "Enable Integration" }),
|
|
2355
|
+
/* @__PURE__ */ jsx7("p", { className: "text-xs text-muted-foreground", children: "Toggle to turn on/off without removing." })
|
|
2356
|
+
] }),
|
|
2357
|
+
/* @__PURE__ */ jsxs6("label", { className: "relative inline-flex items-center cursor-pointer", children: [
|
|
2358
|
+
/* @__PURE__ */ jsx7(
|
|
2359
|
+
"input",
|
|
2360
|
+
{
|
|
2361
|
+
type: "checkbox",
|
|
2362
|
+
className: "sr-only peer",
|
|
2363
|
+
checked: form.enabled,
|
|
2364
|
+
onChange: (e) => update("enabled", e.target.checked)
|
|
2365
|
+
}
|
|
2366
|
+
),
|
|
2367
|
+
/* @__PURE__ */ jsx7("div", { className: "w-9 h-5 bg-primary/5 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-primary/20 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-primary/5 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-primary border border-primary/5" })
|
|
2368
|
+
] })
|
|
2369
|
+
] })
|
|
2370
|
+
] }),
|
|
2371
|
+
/* @__PURE__ */ jsxs6("div", { className: "px-6 py-4 border-t border-primary/5 bg-primary/5/20 flex items-center justify-end gap-3 rounded-b-xl", children: [
|
|
2372
|
+
/* @__PURE__ */ jsx7(Button3, { type: "button", variant: "outline", onClick: onCancel, children: "Cancel" }),
|
|
2373
|
+
/* @__PURE__ */ jsx7(Button3, { type: "button", onClick: handleSubmit, children: isEditing ? "Save Changes" : "Add Integration" })
|
|
2374
|
+
] })
|
|
2375
|
+
] })
|
|
2376
|
+
}
|
|
2377
|
+
);
|
|
2378
|
+
}
|
|
2379
|
+
function FormNotificationsTab() {
|
|
2380
|
+
const {
|
|
2381
|
+
notifications,
|
|
2382
|
+
fields,
|
|
2383
|
+
addNotification,
|
|
2384
|
+
updateNotification,
|
|
2385
|
+
deleteNotification
|
|
2386
|
+
} = useFormBuilder();
|
|
2387
|
+
const [providers, setProviders] = useState5([]);
|
|
2388
|
+
const [templates, setTemplates] = useState5([]);
|
|
2389
|
+
const [modalState, setModalState] = useState5({ open: false, editing: null });
|
|
2390
|
+
useEffect2(() => {
|
|
2391
|
+
void fetchProviders().then(setProviders);
|
|
2392
|
+
void fetchTemplates().then(setTemplates);
|
|
2393
|
+
}, []);
|
|
2394
|
+
const fieldList = fields.map((f) => ({ name: f.name, label: f.label }));
|
|
2395
|
+
const openAddModal = useCallback5(() => {
|
|
2396
|
+
setModalState({ open: true, editing: null });
|
|
2397
|
+
}, []);
|
|
2398
|
+
const openEditModal = useCallback5((notification) => {
|
|
2399
|
+
setModalState({ open: true, editing: notification });
|
|
2400
|
+
}, []);
|
|
2401
|
+
const closeModal = useCallback5(() => {
|
|
2402
|
+
setModalState({ open: false, editing: null });
|
|
2403
|
+
}, []);
|
|
2404
|
+
const handleSave = useCallback5(
|
|
2405
|
+
(notification) => {
|
|
2406
|
+
if (modalState.editing) {
|
|
2407
|
+
updateNotification(notification.id, notification);
|
|
2408
|
+
} else {
|
|
2409
|
+
addNotification(notification);
|
|
2410
|
+
}
|
|
2411
|
+
closeModal();
|
|
2412
|
+
},
|
|
2413
|
+
[modalState.editing, addNotification, updateNotification, closeModal]
|
|
2414
|
+
);
|
|
2415
|
+
const getProviderName = useCallback5(
|
|
2416
|
+
(providerId) => {
|
|
2417
|
+
if (!providerId) {
|
|
2418
|
+
const def = providers.find((p) => p.isDefault);
|
|
2419
|
+
return def ? `${def.name} (Default)` : "System Default";
|
|
2420
|
+
}
|
|
2421
|
+
return providers.find((p) => p.id === providerId)?.name ?? "Unknown";
|
|
2422
|
+
},
|
|
2423
|
+
[providers]
|
|
2424
|
+
);
|
|
2425
|
+
const initialNotification = modalState.editing ?? createNotification();
|
|
2426
|
+
return /* @__PURE__ */ jsxs6("div", { className: "form-notifications-tab", children: [
|
|
2427
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between mb-8 pb-4 border-b border-primary/5", children: [
|
|
2428
|
+
/* @__PURE__ */ jsxs6("div", { children: [
|
|
2429
|
+
/* @__PURE__ */ jsx7("h3", { className: "text-xl font-semibold text-foreground", children: "Email Integrations" }),
|
|
2430
|
+
/* @__PURE__ */ jsx7("p", { className: "text-sm text-muted-foreground mt-1", children: "Configure email notifications sent when someone submits this form." })
|
|
2431
|
+
] }),
|
|
2432
|
+
/* @__PURE__ */ jsx7(Button3, { type: "button", onClick: openAddModal, children: "+ Add Integration" })
|
|
2433
|
+
] }),
|
|
2434
|
+
notifications.length === 0 ? /* @__PURE__ */ jsxs6("div", { className: "flex flex-col items-center justify-center p-12 mt-6 border-2 border-dashed border-primary/5 rounded-none bg-primary/5/20", children: [
|
|
2435
|
+
/* @__PURE__ */ jsx7("div", { className: "flex items-center justify-center w-12 h-12 rounded-none border border-primary/5 bg-background mb-4 text-primary", children: /* @__PURE__ */ jsxs6(
|
|
2436
|
+
"svg",
|
|
2437
|
+
{
|
|
2438
|
+
className: "h-6 w-6",
|
|
2439
|
+
viewBox: "0 0 24 24",
|
|
2440
|
+
fill: "none",
|
|
2441
|
+
stroke: "currentColor",
|
|
2442
|
+
strokeWidth: "2",
|
|
2443
|
+
strokeLinecap: "round",
|
|
2444
|
+
strokeLinejoin: "round",
|
|
2445
|
+
children: [
|
|
2446
|
+
/* @__PURE__ */ jsx7("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
|
|
2447
|
+
/* @__PURE__ */ jsx7("path", { d: "Mm22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" })
|
|
2448
|
+
]
|
|
2449
|
+
}
|
|
2450
|
+
) }),
|
|
2451
|
+
/* @__PURE__ */ jsx7("p", { className: "text-base font-medium text-foreground mb-1", children: "No email integrations yet" }),
|
|
2452
|
+
/* @__PURE__ */ jsx7("p", { className: "text-sm text-muted-foreground mb-6 text-center max-w-sm", children: "Add an email integration to notify recipients when someone submits this form." }),
|
|
2453
|
+
/* @__PURE__ */ jsx7(Button3, { type: "button", onClick: openAddModal, children: "+ Add Integration" })
|
|
2454
|
+
] }) : /* @__PURE__ */ jsx7("div", { className: "form-notifications-list", children: notifications.map((notification) => /* @__PURE__ */ jsx7(
|
|
2455
|
+
IntegrationCard,
|
|
2456
|
+
{
|
|
2457
|
+
notification,
|
|
2458
|
+
providerName: getProviderName(notification.providerId),
|
|
2459
|
+
onEdit: () => openEditModal(notification),
|
|
2460
|
+
onDelete: () => deleteNotification(notification.id)
|
|
2461
|
+
},
|
|
2462
|
+
notification.id
|
|
2463
|
+
)) }),
|
|
2464
|
+
modalState.open && /* @__PURE__ */ jsx7(
|
|
2465
|
+
IntegrationModal,
|
|
2466
|
+
{
|
|
2467
|
+
initial: initialNotification,
|
|
2468
|
+
providers,
|
|
2469
|
+
templates,
|
|
2470
|
+
fields: fieldList,
|
|
2471
|
+
isEditing: !!modalState.editing,
|
|
2472
|
+
onSave: handleSave,
|
|
2473
|
+
onCancel: closeModal
|
|
2474
|
+
}
|
|
2475
|
+
)
|
|
2476
|
+
] });
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
// src/admin/components/builder/FormPreview.tsx
|
|
2480
|
+
import { Input as Input4, Button as Button4 } from "@nextlyhq/ui";
|
|
2481
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2482
|
+
function FormPreview({ fields, formData }) {
|
|
2483
|
+
if (fields.length === 0) {
|
|
2484
|
+
return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-center justify-center p-12 bg-primary/5/20 rounded-none border-2 border-dashed border-primary/5 text-center", children: [
|
|
2485
|
+
/* @__PURE__ */ jsx8("div", { className: "text-4xl mb-3", children: "\u{1F4DD}" }),
|
|
2486
|
+
/* @__PURE__ */ jsx8("p", { className: "text-sm font-medium text-muted-foreground", children: "Add some fields to preview your form" })
|
|
2487
|
+
] });
|
|
2488
|
+
}
|
|
2489
|
+
return /* @__PURE__ */ jsxs7("div", { className: "bg-background max-w-2xl mt-8", children: [
|
|
2490
|
+
/* @__PURE__ */ jsxs7("div", { className: "mb-8", children: [
|
|
2491
|
+
/* @__PURE__ */ jsx8("h2", { className: "text-2xl font-bold text-foreground", children: formData?.name || "Untitled Form" }),
|
|
2492
|
+
formData?.description && /* @__PURE__ */ jsx8("p", { className: "text-sm text-muted-foreground mt-2", children: formData.description })
|
|
2493
|
+
] }),
|
|
2494
|
+
/* @__PURE__ */ jsx8("div", { className: "space-y-6", children: fields.map((field) => /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1.5", children: [
|
|
2495
|
+
/* @__PURE__ */ jsxs7("label", { className: "text-sm font-medium text-foreground", children: [
|
|
2496
|
+
field.label,
|
|
2497
|
+
field.required && /* @__PURE__ */ jsx8("span", { className: "text-destructive ml-1", children: "*" })
|
|
2498
|
+
] }),
|
|
2499
|
+
renderFieldPreview(field),
|
|
2500
|
+
field.helpText && /* @__PURE__ */ jsx8("p", { className: "text-xs text-muted-foreground mt-1", children: field.helpText })
|
|
2501
|
+
] }, field.name)) }),
|
|
2502
|
+
/* @__PURE__ */ jsxs7("div", { className: "mt-8 pt-6 border-t border-primary/5 flex items-center gap-4", children: [
|
|
2503
|
+
/* @__PURE__ */ jsx8(Button4, { type: "button", disabled: true, children: "Submit" }),
|
|
2504
|
+
/* @__PURE__ */ jsx8("p", { className: "text-xs text-muted-foreground", children: "This is a preview. Submissions are disabled." })
|
|
2505
|
+
] })
|
|
2506
|
+
] });
|
|
2507
|
+
}
|
|
2508
|
+
function renderFieldPreview(field) {
|
|
2509
|
+
switch (field.type) {
|
|
2510
|
+
case "text":
|
|
2511
|
+
case "email":
|
|
2512
|
+
case "phone":
|
|
2513
|
+
case "url":
|
|
2514
|
+
return /* @__PURE__ */ jsx8(
|
|
2515
|
+
Input4,
|
|
2516
|
+
{
|
|
2517
|
+
type: field.type === "email" ? "email" : "text",
|
|
2518
|
+
placeholder: field.placeholder || `Enter ${field.label.toLowerCase()}`,
|
|
2519
|
+
disabled: true
|
|
2520
|
+
}
|
|
2521
|
+
);
|
|
2522
|
+
case "number":
|
|
2523
|
+
return /* @__PURE__ */ jsx8(
|
|
2524
|
+
Input4,
|
|
2525
|
+
{
|
|
2526
|
+
type: "number",
|
|
2527
|
+
placeholder: field.placeholder || "Enter a number",
|
|
2528
|
+
disabled: true
|
|
2529
|
+
}
|
|
2530
|
+
);
|
|
2531
|
+
case "textarea":
|
|
2532
|
+
return /* @__PURE__ */ jsx8(
|
|
2533
|
+
"textarea",
|
|
2534
|
+
{
|
|
2535
|
+
className: "flex w-full rounded-none border border-input bg-transparent px-3 py-2 text-sm shadow-none placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
2536
|
+
placeholder: field.placeholder || `Enter ${field.label.toLowerCase()}`,
|
|
2537
|
+
rows: field.rows || 4,
|
|
2538
|
+
disabled: true
|
|
2539
|
+
}
|
|
2540
|
+
);
|
|
2541
|
+
case "select":
|
|
2542
|
+
return /* @__PURE__ */ jsxs7(
|
|
2543
|
+
"select",
|
|
2544
|
+
{
|
|
2545
|
+
className: "flex h-9 w-full items-center justify-between rounded-none border border-input bg-transparent px-3 py-2 text-sm shadow-none focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
2546
|
+
disabled: true,
|
|
2547
|
+
children: [
|
|
2548
|
+
/* @__PURE__ */ jsx8("option", { value: "", children: "Select an option" }),
|
|
2549
|
+
field.options?.map((opt) => /* @__PURE__ */ jsx8("option", { value: opt.value, children: opt.label }, opt.value))
|
|
2550
|
+
]
|
|
2551
|
+
}
|
|
2552
|
+
);
|
|
2553
|
+
case "checkbox":
|
|
2554
|
+
return /* @__PURE__ */ jsxs7("div", { className: "flex items-center space-x-2", children: [
|
|
2555
|
+
/* @__PURE__ */ jsx8(
|
|
2556
|
+
"input",
|
|
2557
|
+
{
|
|
2558
|
+
type: "checkbox",
|
|
2559
|
+
className: "h-4 w-4 rounded border-input text-primary focus:ring-primary",
|
|
2560
|
+
disabled: true
|
|
2561
|
+
}
|
|
2562
|
+
),
|
|
2563
|
+
/* @__PURE__ */ jsx8("span", { className: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: field.label })
|
|
2564
|
+
] });
|
|
2565
|
+
case "radio":
|
|
2566
|
+
return /* @__PURE__ */ jsx8("div", { className: "flex flex-col space-y-2", children: field.options?.map((opt) => /* @__PURE__ */ jsxs7("div", { className: "flex items-center space-x-2", children: [
|
|
2567
|
+
/* @__PURE__ */ jsx8(
|
|
2568
|
+
"input",
|
|
2569
|
+
{
|
|
2570
|
+
type: "radio",
|
|
2571
|
+
name: field.name,
|
|
2572
|
+
className: "h-4 w-4 rounded-full border border-input text-primary focus:ring-primary",
|
|
2573
|
+
disabled: true
|
|
2574
|
+
}
|
|
2575
|
+
),
|
|
2576
|
+
/* @__PURE__ */ jsx8("span", { className: "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", children: opt.label })
|
|
2577
|
+
] }, opt.value)) });
|
|
2578
|
+
case "file":
|
|
2579
|
+
return /* @__PURE__ */ jsx8("div", { className: "flex items-center", children: /* @__PURE__ */ jsx8(Input4, { type: "file", disabled: true }) });
|
|
2580
|
+
case "date":
|
|
2581
|
+
return /* @__PURE__ */ jsx8(Input4, { type: "date", disabled: true });
|
|
2582
|
+
case "time":
|
|
2583
|
+
return /* @__PURE__ */ jsx8(Input4, { type: "time", disabled: true });
|
|
2584
|
+
case "hidden":
|
|
2585
|
+
return /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2 p-3 text-sm text-muted-foreground bg-primary/5/20 border-2 border-dashed border-primary/5 rounded-none", children: [
|
|
2586
|
+
/* @__PURE__ */ jsx8("span", { className: "text-lg", children: "\u{1F441}\uFE0F" }),
|
|
2587
|
+
/* @__PURE__ */ jsx8("span", { children: "Hidden field" })
|
|
2588
|
+
] });
|
|
2589
|
+
default:
|
|
2590
|
+
return /* @__PURE__ */ jsx8(Input4, { type: "text", placeholder: "Unknown field type", disabled: true });
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
// src/admin/components/builder/FormSettingsTab.tsx
|
|
2595
|
+
import {
|
|
2596
|
+
Input as Input5,
|
|
2597
|
+
Textarea,
|
|
2598
|
+
Tabs as Tabs2,
|
|
2599
|
+
TabsList as TabsList2,
|
|
2600
|
+
TabsTrigger as TabsTrigger2,
|
|
2601
|
+
Switch
|
|
2602
|
+
} from "@nextlyhq/ui";
|
|
2603
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2604
|
+
function SettingRow({
|
|
2605
|
+
label,
|
|
2606
|
+
description,
|
|
2607
|
+
children,
|
|
2608
|
+
className = ""
|
|
2609
|
+
}) {
|
|
2610
|
+
return /* @__PURE__ */ jsxs8(
|
|
2611
|
+
"div",
|
|
2612
|
+
{
|
|
2613
|
+
className: `flex flex-col sm:flex-row sm:items-center justify-between gap-6 py-2 ${className}`,
|
|
2614
|
+
children: [
|
|
2615
|
+
/* @__PURE__ */ jsxs8("div", { className: "space-y-1 max-w-xl", children: [
|
|
2616
|
+
/* @__PURE__ */ jsx9("h4", { className: "text-[13px] font-semibold text-foreground tracking-tight", children: label }),
|
|
2617
|
+
description && /* @__PURE__ */ jsx9("p", { className: "text-[12px] text-muted-foreground leading-relaxed", children: description })
|
|
2618
|
+
] }),
|
|
2619
|
+
/* @__PURE__ */ jsx9("div", { className: "flex-shrink-0 flex items-center justify-end min-w-[100px]", children })
|
|
2620
|
+
]
|
|
2621
|
+
}
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2624
|
+
function FormSettingsTab() {
|
|
2625
|
+
const { settings, updateSettings } = useFormBuilder();
|
|
2626
|
+
const currentActionTab = settings.redirectUrl !== void 0 ? "redirect" : "message";
|
|
2627
|
+
return /* @__PURE__ */ jsxs8("div", { className: "w-full flex flex-row gap-8", children: [
|
|
2628
|
+
/* @__PURE__ */ jsxs8("div", { className: "w-full flex flex-col gap-8", children: [
|
|
2629
|
+
/* @__PURE__ */ jsxs8("section", { className: "mb-12", children: [
|
|
2630
|
+
/* @__PURE__ */ jsx9("div", { className: "border-b border-primary/5 pb-3", children: /* @__PURE__ */ jsx9("h3", { className: "text-[16px] font-bold text-foreground tracking-tight", children: "Submission Settings" }) }),
|
|
2631
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1", children: [
|
|
2632
|
+
/* @__PURE__ */ jsx9(
|
|
2633
|
+
SettingRow,
|
|
2634
|
+
{
|
|
2635
|
+
label: "Submit Button Text",
|
|
2636
|
+
description: "Label on the primary action button",
|
|
2637
|
+
children: /* @__PURE__ */ jsx9(
|
|
2638
|
+
Input5,
|
|
2639
|
+
{
|
|
2640
|
+
type: "text",
|
|
2641
|
+
value: settings.submitButtonText,
|
|
2642
|
+
onChange: (e) => updateSettings({ submitButtonText: e.target.value }),
|
|
2643
|
+
placeholder: "Submit",
|
|
2644
|
+
className: "bg-transparent h-8 w-40 text-right pr-2 focus-visible:ring-1 border-primary/5 text-[13px]"
|
|
2645
|
+
}
|
|
2646
|
+
)
|
|
2647
|
+
}
|
|
2648
|
+
),
|
|
2649
|
+
/* @__PURE__ */ jsx9(
|
|
2650
|
+
SettingRow,
|
|
2651
|
+
{
|
|
2652
|
+
label: "Show Reset Button",
|
|
2653
|
+
description: "Lets users clear all fields at once",
|
|
2654
|
+
children: /* @__PURE__ */ jsx9(
|
|
2655
|
+
Switch,
|
|
2656
|
+
{
|
|
2657
|
+
checked: settings.showResetButton,
|
|
2658
|
+
onCheckedChange: (checked) => updateSettings({ showResetButton: checked })
|
|
2659
|
+
}
|
|
2660
|
+
)
|
|
2661
|
+
}
|
|
2662
|
+
),
|
|
2663
|
+
settings.showResetButton && /* @__PURE__ */ jsx9(
|
|
2664
|
+
SettingRow,
|
|
2665
|
+
{
|
|
2666
|
+
label: "Reset Button Text",
|
|
2667
|
+
description: "Label on the secondary action button",
|
|
2668
|
+
className: "animate-in fade-in slide-in-from-top-2 duration-300",
|
|
2669
|
+
children: /* @__PURE__ */ jsx9(
|
|
2670
|
+
Input5,
|
|
2671
|
+
{
|
|
2672
|
+
type: "text",
|
|
2673
|
+
value: settings.resetButtonText,
|
|
2674
|
+
onChange: (e) => updateSettings({ resetButtonText: e.target.value }),
|
|
2675
|
+
placeholder: "Reset",
|
|
2676
|
+
className: "bg-transparent h-8 w-40 text-right pr-2 focus-visible:ring-1 border-primary/5 text-[13px]"
|
|
2677
|
+
}
|
|
2678
|
+
)
|
|
2679
|
+
}
|
|
2680
|
+
)
|
|
2681
|
+
] })
|
|
2682
|
+
] }),
|
|
2683
|
+
/* @__PURE__ */ jsxs8("section", { className: "mb-12", children: [
|
|
2684
|
+
/* @__PURE__ */ jsx9("div", { className: "border-b border-primary/5 pb-3", children: /* @__PURE__ */ jsx9("h3", { className: "text-[16px] font-bold text-foreground tracking-tight", children: "After Submission" }) }),
|
|
2685
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-4", children: [
|
|
2686
|
+
/* @__PURE__ */ jsx9(
|
|
2687
|
+
SettingRow,
|
|
2688
|
+
{
|
|
2689
|
+
label: "Action",
|
|
2690
|
+
description: "What happens after the form is submitted",
|
|
2691
|
+
children: /* @__PURE__ */ jsx9(
|
|
2692
|
+
Tabs2,
|
|
2693
|
+
{
|
|
2694
|
+
value: currentActionTab,
|
|
2695
|
+
onValueChange: (v) => {
|
|
2696
|
+
if (v === "message") {
|
|
2697
|
+
updateSettings({ redirectUrl: void 0 });
|
|
2698
|
+
} else if (v === "redirect") {
|
|
2699
|
+
updateSettings({ redirectUrl: "" });
|
|
2700
|
+
}
|
|
2701
|
+
},
|
|
2702
|
+
children: /* @__PURE__ */ jsxs8(TabsList2, { className: "bg-primary/5 p-1 h-8", children: [
|
|
2703
|
+
/* @__PURE__ */ jsx9(
|
|
2704
|
+
TabsTrigger2,
|
|
2705
|
+
{
|
|
2706
|
+
value: "message",
|
|
2707
|
+
className: "px-4 py-1 text-[11px] font-medium data-[state=active]:bg-background data-[state=active]:shadow-sm",
|
|
2708
|
+
children: "Message"
|
|
2709
|
+
}
|
|
2710
|
+
),
|
|
2711
|
+
/* @__PURE__ */ jsx9(
|
|
2712
|
+
TabsTrigger2,
|
|
2713
|
+
{
|
|
2714
|
+
value: "redirect",
|
|
2715
|
+
className: "px-4 py-1 text-[11px] font-medium data-[state=active]:bg-background data-[state=active]:shadow-sm",
|
|
2716
|
+
children: "Redirect"
|
|
2717
|
+
}
|
|
2718
|
+
)
|
|
2719
|
+
] })
|
|
2720
|
+
}
|
|
2721
|
+
)
|
|
2722
|
+
}
|
|
2723
|
+
),
|
|
2724
|
+
currentActionTab === "message" ? /* @__PURE__ */ jsxs8("div", { className: "pt-2 animate-in fade-in duration-300", children: [
|
|
2725
|
+
/* @__PURE__ */ jsxs8("div", { className: "space-y-1.5 mb-4", children: [
|
|
2726
|
+
/* @__PURE__ */ jsx9("h4", { className: "text-[13px] font-semibold text-foreground tracking-tight", children: "Confirmation Message" }),
|
|
2727
|
+
/* @__PURE__ */ jsx9("p", { className: "text-[12px] text-muted-foreground leading-relaxed", children: "Message shown to user after successful submission" })
|
|
2728
|
+
] }),
|
|
2729
|
+
/* @__PURE__ */ jsx9(
|
|
2730
|
+
Textarea,
|
|
2731
|
+
{
|
|
2732
|
+
id: "confirmationMessage",
|
|
2733
|
+
value: settings.confirmationMessage,
|
|
2734
|
+
onChange: (e) => updateSettings({ confirmationMessage: e.target.value }),
|
|
2735
|
+
placeholder: "Thank you for your submission!",
|
|
2736
|
+
rows: 4,
|
|
2737
|
+
className: "bg-transparent text-[13px] leading-relaxed w-full border-primary/5 focus-visible:ring-1"
|
|
2738
|
+
}
|
|
2739
|
+
)
|
|
2740
|
+
] }) : /* @__PURE__ */ jsx9("div", { className: "pt-2 animate-in fade-in duration-300", children: /* @__PURE__ */ jsx9(
|
|
2741
|
+
SettingRow,
|
|
2742
|
+
{
|
|
2743
|
+
label: "Redirect URL",
|
|
2744
|
+
description: "User will be redirected to this URL after submission",
|
|
2745
|
+
children: /* @__PURE__ */ jsx9(
|
|
2746
|
+
Input5,
|
|
2747
|
+
{
|
|
2748
|
+
type: "url",
|
|
2749
|
+
value: settings.redirectUrl || "",
|
|
2750
|
+
onChange: (e) => updateSettings({ redirectUrl: e.target.value }),
|
|
2751
|
+
placeholder: "https://example.com/thank-you",
|
|
2752
|
+
className: "bg-transparent h-8 w-56 text-right pr-2 focus-visible:ring-1 border-primary/5 text-[13px]"
|
|
2753
|
+
}
|
|
2754
|
+
)
|
|
2755
|
+
}
|
|
2756
|
+
) })
|
|
2757
|
+
] })
|
|
2758
|
+
] })
|
|
2759
|
+
] }),
|
|
2760
|
+
/* @__PURE__ */ jsxs8("div", { className: "w-full flex flex-col gap-8", children: [
|
|
2761
|
+
/* @__PURE__ */ jsxs8("section", { children: [
|
|
2762
|
+
/* @__PURE__ */ jsx9("div", { className: "border-b border-primary/5 pb-3", children: /* @__PURE__ */ jsx9("h3", { className: "text-[16px] font-bold text-foreground tracking-tight", children: "Spam Protection" }) }),
|
|
2763
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1", children: [
|
|
2764
|
+
/* @__PURE__ */ jsx9(
|
|
2765
|
+
SettingRow,
|
|
2766
|
+
{
|
|
2767
|
+
label: "Honeypot Field",
|
|
2768
|
+
description: "Hidden field that silently catches bots",
|
|
2769
|
+
children: /* @__PURE__ */ jsx9(
|
|
2770
|
+
Switch,
|
|
2771
|
+
{
|
|
2772
|
+
checked: settings.honeypotEnabled,
|
|
2773
|
+
onCheckedChange: (checkedValue) => updateSettings({ honeypotEnabled: checkedValue })
|
|
2774
|
+
}
|
|
2775
|
+
)
|
|
2776
|
+
}
|
|
2777
|
+
),
|
|
2778
|
+
/* @__PURE__ */ jsx9(
|
|
2779
|
+
SettingRow,
|
|
2780
|
+
{
|
|
2781
|
+
label: "CAPTCHA Challenge",
|
|
2782
|
+
description: "Human verification before submitting",
|
|
2783
|
+
children: /* @__PURE__ */ jsx9(
|
|
2784
|
+
Switch,
|
|
2785
|
+
{
|
|
2786
|
+
checked: settings.captchaEnabled,
|
|
2787
|
+
onCheckedChange: (checkedValue) => updateSettings({ captchaEnabled: checkedValue })
|
|
2788
|
+
}
|
|
2789
|
+
)
|
|
2790
|
+
}
|
|
2791
|
+
)
|
|
2792
|
+
] })
|
|
2793
|
+
] }),
|
|
2794
|
+
/* @__PURE__ */ jsxs8("section", { children: [
|
|
2795
|
+
/* @__PURE__ */ jsx9("div", { className: "border-b border-primary/5 pb-3", children: /* @__PURE__ */ jsx9("h3", { className: "text-[16px] font-bold text-foreground tracking-tight", children: "Data Storage" }) }),
|
|
2796
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1", children: [
|
|
2797
|
+
/* @__PURE__ */ jsx9(
|
|
2798
|
+
SettingRow,
|
|
2799
|
+
{
|
|
2800
|
+
label: "Store Submissions",
|
|
2801
|
+
description: "Save responses to the database",
|
|
2802
|
+
children: /* @__PURE__ */ jsx9(
|
|
2803
|
+
Switch,
|
|
2804
|
+
{
|
|
2805
|
+
checked: settings.storeSubmissions,
|
|
2806
|
+
onCheckedChange: (checkedValue) => updateSettings({ storeSubmissions: checkedValue })
|
|
2807
|
+
}
|
|
2808
|
+
)
|
|
2809
|
+
}
|
|
2810
|
+
),
|
|
2811
|
+
/* @__PURE__ */ jsx9(
|
|
2812
|
+
SettingRow,
|
|
2813
|
+
{
|
|
2814
|
+
label: "Submission Limit",
|
|
2815
|
+
description: "Max number of entries to store",
|
|
2816
|
+
children: /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-3", children: [
|
|
2817
|
+
/* @__PURE__ */ jsx9(
|
|
2818
|
+
Input5,
|
|
2819
|
+
{
|
|
2820
|
+
type: "number",
|
|
2821
|
+
value: settings.submissionLimit || "",
|
|
2822
|
+
onChange: (e) => updateSettings({
|
|
2823
|
+
submissionLimit: e.target.value ? parseInt(e.target.value, 10) : void 0
|
|
2824
|
+
}),
|
|
2825
|
+
placeholder: "Unlimited",
|
|
2826
|
+
min: 1,
|
|
2827
|
+
className: "bg-transparent h-8 w-28 text-right pr-2 focus-visible:ring-1 border-primary/5 text-[13px]"
|
|
2828
|
+
}
|
|
2829
|
+
),
|
|
2830
|
+
/* @__PURE__ */ jsx9("span", { className: "text-[12px] text-muted-foreground font-medium", children: "Entries" })
|
|
2831
|
+
] })
|
|
2832
|
+
}
|
|
2833
|
+
)
|
|
2834
|
+
] })
|
|
2835
|
+
] })
|
|
2836
|
+
] })
|
|
2837
|
+
] });
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
// src/admin/FormBuilderView.tsx
|
|
2841
|
+
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2842
|
+
function slugify(text) {
|
|
2843
|
+
return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2844
|
+
}
|
|
2845
|
+
function SlidersIcon() {
|
|
2846
|
+
return /* @__PURE__ */ jsxs9(
|
|
2847
|
+
"svg",
|
|
2848
|
+
{
|
|
2849
|
+
className: "h-3 w-3 shrink-0",
|
|
2850
|
+
viewBox: "0 0 24 24",
|
|
2851
|
+
fill: "none",
|
|
2852
|
+
stroke: "currentColor",
|
|
2853
|
+
strokeWidth: "2",
|
|
2854
|
+
strokeLinecap: "round",
|
|
2855
|
+
strokeLinejoin: "round",
|
|
2856
|
+
children: [
|
|
2857
|
+
/* @__PURE__ */ jsx10("line", { x1: "4", y1: "21", x2: "4", y2: "14" }),
|
|
2858
|
+
/* @__PURE__ */ jsx10("line", { x1: "4", y1: "10", x2: "4", y2: "3" }),
|
|
2859
|
+
/* @__PURE__ */ jsx10("line", { x1: "12", y1: "21", x2: "12", y2: "12" }),
|
|
2860
|
+
/* @__PURE__ */ jsx10("line", { x1: "12", y1: "8", x2: "12", y2: "3" }),
|
|
2861
|
+
/* @__PURE__ */ jsx10("line", { x1: "20", y1: "21", x2: "20", y2: "16" }),
|
|
2862
|
+
/* @__PURE__ */ jsx10("line", { x1: "20", y1: "12", x2: "20", y2: "3" }),
|
|
2863
|
+
/* @__PURE__ */ jsx10("line", { x1: "1", y1: "14", x2: "7", y2: "14" }),
|
|
2864
|
+
/* @__PURE__ */ jsx10("line", { x1: "9", y1: "8", x2: "15", y2: "8" }),
|
|
2865
|
+
/* @__PURE__ */ jsx10("line", { x1: "17", y1: "16", x2: "23", y2: "16" })
|
|
2866
|
+
]
|
|
2867
|
+
}
|
|
2868
|
+
);
|
|
2869
|
+
}
|
|
2870
|
+
function FormBuilderViewInner({
|
|
2871
|
+
isCreating = false,
|
|
2872
|
+
collectionSlug = "forms",
|
|
2873
|
+
entryId,
|
|
2874
|
+
onSave,
|
|
2875
|
+
onSuccess,
|
|
2876
|
+
onCancel
|
|
2877
|
+
}) {
|
|
2878
|
+
const {
|
|
2879
|
+
fields,
|
|
2880
|
+
selectedFieldId,
|
|
2881
|
+
activeTab,
|
|
2882
|
+
isDirty,
|
|
2883
|
+
formData,
|
|
2884
|
+
settings,
|
|
2885
|
+
notifications,
|
|
2886
|
+
setActiveTab,
|
|
2887
|
+
addField,
|
|
2888
|
+
moveField,
|
|
2889
|
+
selectField,
|
|
2890
|
+
deleteField,
|
|
2891
|
+
updateField,
|
|
2892
|
+
duplicateField,
|
|
2893
|
+
updateFormData,
|
|
2894
|
+
markAsSaved
|
|
2895
|
+
} = useFormBuilder();
|
|
2896
|
+
const queryClient = useQueryClient();
|
|
2897
|
+
const [sidebarTab, setSidebarTab] = useState6("add");
|
|
2898
|
+
const [isSaving, setIsSaving] = useState6(false);
|
|
2899
|
+
const [saveError, setSaveError] = useState6(null);
|
|
2900
|
+
const sensors = useSensors(
|
|
2901
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
|
|
2902
|
+
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
|
2903
|
+
);
|
|
2904
|
+
const handleDragEnd = useCallback6(
|
|
2905
|
+
(event) => {
|
|
2906
|
+
const { active, over } = event;
|
|
2907
|
+
if (!over) return;
|
|
2908
|
+
const isFromLibrary = active.data.current?.isLibraryItem;
|
|
2909
|
+
if (isFromLibrary) {
|
|
2910
|
+
const fieldType = active.data.current?.fieldType;
|
|
2911
|
+
if (fieldType) {
|
|
2912
|
+
const newField = createFieldFromType(fieldType);
|
|
2913
|
+
if (over.id === "canvas-drop-zone") {
|
|
2914
|
+
addField(newField);
|
|
2915
|
+
} else {
|
|
2916
|
+
const overIndex = fields.findIndex((f) => f.name === over.id);
|
|
2917
|
+
addField(newField, overIndex !== -1 ? overIndex + 1 : void 0);
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
} else {
|
|
2921
|
+
if (active.id !== over.id) {
|
|
2922
|
+
const oldIndex = fields.findIndex((f) => f.name === active.id);
|
|
2923
|
+
const newIndex = fields.findIndex((f) => f.name === over.id);
|
|
2924
|
+
if (oldIndex !== -1 && newIndex !== -1) {
|
|
2925
|
+
moveField(oldIndex, newIndex);
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
},
|
|
2930
|
+
[fields, addField, moveField]
|
|
2931
|
+
);
|
|
2932
|
+
const handleSave = useCallback6(async () => {
|
|
2933
|
+
if (fields.length === 0) {
|
|
2934
|
+
toast.error("Please add at least one field to the form");
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
const { id: _id, ...formDataWithoutId } = formData;
|
|
2938
|
+
const saveData = {
|
|
2939
|
+
...formDataWithoutId,
|
|
2940
|
+
title: formDataWithoutId.name,
|
|
2941
|
+
fields,
|
|
2942
|
+
settings,
|
|
2943
|
+
notifications
|
|
2944
|
+
};
|
|
2945
|
+
if (onSave) {
|
|
2946
|
+
onSave(saveData);
|
|
2947
|
+
return;
|
|
2948
|
+
}
|
|
2949
|
+
setIsSaving(true);
|
|
2950
|
+
setSaveError(null);
|
|
2951
|
+
try {
|
|
2952
|
+
const url = isCreating ? `/admin/api/collections/${collectionSlug}/entries` : `/admin/api/collections/${collectionSlug}/entries/${entryId}`;
|
|
2953
|
+
const response = await fetch(url, {
|
|
2954
|
+
method: isCreating ? "POST" : "PATCH",
|
|
2955
|
+
headers: { "Content-Type": "application/json" },
|
|
2956
|
+
credentials: "include",
|
|
2957
|
+
body: JSON.stringify(saveData)
|
|
2958
|
+
});
|
|
2959
|
+
if (!response.ok) {
|
|
2960
|
+
const err = await response.json().catch(() => ({}));
|
|
2961
|
+
throw new Error(
|
|
2962
|
+
err.error?.message ?? `Failed to save form: ${response.statusText}`
|
|
2963
|
+
);
|
|
2964
|
+
}
|
|
2965
|
+
const result = await response.json();
|
|
2966
|
+
const savedEntry = result.item ?? result;
|
|
2967
|
+
void queryClient.invalidateQueries({
|
|
2968
|
+
queryKey: ["entries", "list", collectionSlug]
|
|
2969
|
+
});
|
|
2970
|
+
void queryClient.invalidateQueries({ queryKey: ["entries", "count"] });
|
|
2971
|
+
if (!isCreating && entryId) {
|
|
2972
|
+
void queryClient.invalidateQueries({
|
|
2973
|
+
queryKey: ["entries", "detail", collectionSlug, entryId]
|
|
2974
|
+
});
|
|
2975
|
+
}
|
|
2976
|
+
markAsSaved();
|
|
2977
|
+
toast.success(
|
|
2978
|
+
isCreating ? "Form created successfully" : "Form updated successfully"
|
|
2979
|
+
);
|
|
2980
|
+
if (onSuccess) onSuccess(savedEntry);
|
|
2981
|
+
} catch (error) {
|
|
2982
|
+
const message = error instanceof Error ? error.message : "Failed to save form";
|
|
2983
|
+
setSaveError(message);
|
|
2984
|
+
toast.error(message);
|
|
2985
|
+
} finally {
|
|
2986
|
+
setIsSaving(false);
|
|
2987
|
+
}
|
|
2988
|
+
}, [
|
|
2989
|
+
formData,
|
|
2990
|
+
fields,
|
|
2991
|
+
settings,
|
|
2992
|
+
notifications,
|
|
2993
|
+
onSave,
|
|
2994
|
+
onSuccess,
|
|
2995
|
+
isCreating,
|
|
2996
|
+
collectionSlug,
|
|
2997
|
+
entryId,
|
|
2998
|
+
queryClient,
|
|
2999
|
+
markAsSaved
|
|
3000
|
+
]);
|
|
3001
|
+
const handleCancel = useCallback6(() => {
|
|
3002
|
+
if (onCancel) onCancel();
|
|
3003
|
+
else if (typeof window !== "undefined") window.history.back();
|
|
3004
|
+
}, [onCancel]);
|
|
3005
|
+
const handleFieldSelect = useCallback6(
|
|
3006
|
+
(fieldName) => {
|
|
3007
|
+
selectField(fieldName);
|
|
3008
|
+
if (fieldName) setSidebarTab("properties");
|
|
3009
|
+
},
|
|
3010
|
+
[selectField]
|
|
3011
|
+
);
|
|
3012
|
+
const handleNameChange = useCallback6(
|
|
3013
|
+
(name) => {
|
|
3014
|
+
updateFormData({ name });
|
|
3015
|
+
if (!formData.slug || formData.slug === slugify(formData.name)) {
|
|
3016
|
+
updateFormData({ slug: slugify(name) });
|
|
3017
|
+
}
|
|
3018
|
+
},
|
|
3019
|
+
[formData.name, formData.slug, updateFormData]
|
|
3020
|
+
);
|
|
3021
|
+
const selectedField = fields.find((f) => f.name === selectedFieldId);
|
|
3022
|
+
const hasSelectedField = !!selectedFieldId;
|
|
3023
|
+
const mainTabs = [
|
|
3024
|
+
{
|
|
3025
|
+
value: "builder",
|
|
3026
|
+
label: "Builder",
|
|
3027
|
+
count: fields.length > 0 ? fields.length : null
|
|
3028
|
+
},
|
|
3029
|
+
{
|
|
3030
|
+
value: "preview",
|
|
3031
|
+
label: "Preview",
|
|
3032
|
+
count: null
|
|
3033
|
+
},
|
|
3034
|
+
{ value: "settings", label: "Settings", count: null },
|
|
3035
|
+
{
|
|
3036
|
+
value: "notifications",
|
|
3037
|
+
label: "Notifications",
|
|
3038
|
+
count: notifications.length > 0 ? notifications.length : null
|
|
3039
|
+
}
|
|
3040
|
+
];
|
|
3041
|
+
return /* @__PURE__ */ jsxs9(
|
|
3042
|
+
DndContext,
|
|
3043
|
+
{
|
|
3044
|
+
sensors,
|
|
3045
|
+
collisionDetection: closestCenter,
|
|
3046
|
+
onDragEnd: handleDragEnd,
|
|
3047
|
+
children: [
|
|
3048
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4 mb-6 px-1", children: [
|
|
3049
|
+
/* @__PURE__ */ jsxs9("div", { children: [
|
|
3050
|
+
/* @__PURE__ */ jsx10("h1", { className: "text-2xl font-semibold text-foreground tracking-tight", children: isCreating ? "Create Form" : "Edit Form" }),
|
|
3051
|
+
/* @__PURE__ */ jsx10("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." })
|
|
3052
|
+
] }),
|
|
3053
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
3054
|
+
isDirty && /* @__PURE__ */ jsx10("span", { className: "text-xs font-medium text-amber-600 bg-amber-50 border border-amber-200 px-2.5 py-1 rounded-none whitespace-nowrap", children: "Unsaved changes" }),
|
|
3055
|
+
/* @__PURE__ */ jsx10(
|
|
3056
|
+
Button5,
|
|
3057
|
+
{
|
|
3058
|
+
type: "button",
|
|
3059
|
+
variant: "outline",
|
|
3060
|
+
size: "sm",
|
|
3061
|
+
onClick: handleCancel,
|
|
3062
|
+
disabled: isSaving,
|
|
3063
|
+
children: "Cancel"
|
|
3064
|
+
}
|
|
3065
|
+
),
|
|
3066
|
+
/* @__PURE__ */ jsx10(
|
|
3067
|
+
Button5,
|
|
3068
|
+
{
|
|
3069
|
+
type: "button",
|
|
3070
|
+
size: "sm",
|
|
3071
|
+
onClick: () => {
|
|
3072
|
+
void handleSave();
|
|
3073
|
+
},
|
|
3074
|
+
disabled: isSaving,
|
|
3075
|
+
className: "flex items-center gap-1.5",
|
|
3076
|
+
children: isSaving ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
3077
|
+
/* @__PURE__ */ jsxs9(
|
|
3078
|
+
"svg",
|
|
3079
|
+
{
|
|
3080
|
+
className: "animate-spin h-3.5 w-3.5",
|
|
3081
|
+
viewBox: "0 0 24 24",
|
|
3082
|
+
fill: "none",
|
|
3083
|
+
children: [
|
|
3084
|
+
/* @__PURE__ */ jsx10(
|
|
3085
|
+
"circle",
|
|
3086
|
+
{
|
|
3087
|
+
className: "opacity-25",
|
|
3088
|
+
cx: "12",
|
|
3089
|
+
cy: "12",
|
|
3090
|
+
r: "10",
|
|
3091
|
+
stroke: "currentColor",
|
|
3092
|
+
strokeWidth: "4"
|
|
3093
|
+
}
|
|
3094
|
+
),
|
|
3095
|
+
/* @__PURE__ */ jsx10(
|
|
3096
|
+
"path",
|
|
3097
|
+
{
|
|
3098
|
+
className: "opacity-75",
|
|
3099
|
+
fill: "currentColor",
|
|
3100
|
+
d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
|
|
3101
|
+
}
|
|
3102
|
+
)
|
|
3103
|
+
]
|
|
3104
|
+
}
|
|
3105
|
+
),
|
|
3106
|
+
"Saving\u2026"
|
|
3107
|
+
] }) : isCreating ? "Create" : "Save Changes"
|
|
3108
|
+
}
|
|
3109
|
+
)
|
|
3110
|
+
] })
|
|
3111
|
+
] }),
|
|
3112
|
+
/* @__PURE__ */ jsxs9(
|
|
3113
|
+
"div",
|
|
3114
|
+
{
|
|
3115
|
+
className: "flex border border-primary/5 bg-background overflow-hidden",
|
|
3116
|
+
style: {
|
|
3117
|
+
borderRadius: "0.5rem"
|
|
3118
|
+
},
|
|
3119
|
+
children: [
|
|
3120
|
+
/* @__PURE__ */ jsx10("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsxs9("div", { className: "w-full px-8 pt-8 pb-12 space-y-6", children: [
|
|
3121
|
+
/* @__PURE__ */ jsxs9("div", { className: "bg-background -mx-8 border-b border-primary/5 space-y-4", children: [
|
|
3122
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap gap-4", children: [
|
|
3123
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex-1 min-w-[200px] max-w-sm space-y-1.5", children: [
|
|
3124
|
+
/* @__PURE__ */ jsx10(
|
|
3125
|
+
"label",
|
|
3126
|
+
{
|
|
3127
|
+
htmlFor: "form-name",
|
|
3128
|
+
className: "text-sm font-medium text-foreground",
|
|
3129
|
+
children: "Form Name"
|
|
3130
|
+
}
|
|
3131
|
+
),
|
|
3132
|
+
/* @__PURE__ */ jsx10(
|
|
3133
|
+
Input6,
|
|
3134
|
+
{
|
|
3135
|
+
id: "form-name",
|
|
3136
|
+
type: "text",
|
|
3137
|
+
value: formData.name || "",
|
|
3138
|
+
onChange: (e) => handleNameChange(e.target.value),
|
|
3139
|
+
placeholder: "e.g., Contact Form",
|
|
3140
|
+
className: "bg-transparent"
|
|
3141
|
+
}
|
|
3142
|
+
)
|
|
3143
|
+
] }),
|
|
3144
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex-1 min-w-[160px] max-w-xs space-y-1.5", children: [
|
|
3145
|
+
/* @__PURE__ */ jsx10(
|
|
3146
|
+
"label",
|
|
3147
|
+
{
|
|
3148
|
+
htmlFor: "form-slug",
|
|
3149
|
+
className: "text-sm font-medium text-foreground",
|
|
3150
|
+
children: "Slug"
|
|
3151
|
+
}
|
|
3152
|
+
),
|
|
3153
|
+
/* @__PURE__ */ jsx10(
|
|
3154
|
+
Input6,
|
|
3155
|
+
{
|
|
3156
|
+
id: "form-slug",
|
|
3157
|
+
type: "text",
|
|
3158
|
+
value: formData.slug || "",
|
|
3159
|
+
onChange: (e) => updateFormData({ slug: e.target.value }),
|
|
3160
|
+
placeholder: "e.g., contact-form",
|
|
3161
|
+
className: "bg-transparent placeholder:text-muted-foreground/50"
|
|
3162
|
+
}
|
|
3163
|
+
)
|
|
3164
|
+
] }),
|
|
3165
|
+
/* @__PURE__ */ jsxs9("div", { className: "w-36 space-y-1.5", children: [
|
|
3166
|
+
/* @__PURE__ */ jsx10(
|
|
3167
|
+
"label",
|
|
3168
|
+
{
|
|
3169
|
+
htmlFor: "form-status",
|
|
3170
|
+
className: "text-sm font-medium text-foreground",
|
|
3171
|
+
children: "Status"
|
|
3172
|
+
}
|
|
3173
|
+
),
|
|
3174
|
+
/* @__PURE__ */ jsxs9(
|
|
3175
|
+
Select3,
|
|
3176
|
+
{
|
|
3177
|
+
value: formData.status || "draft",
|
|
3178
|
+
onValueChange: (value) => updateFormData({
|
|
3179
|
+
status: value
|
|
3180
|
+
}),
|
|
3181
|
+
children: [
|
|
3182
|
+
/* @__PURE__ */ jsx10(SelectTrigger3, { id: "form-status", className: "bg-transparent", children: /* @__PURE__ */ jsx10(SelectValue3, { placeholder: "Status" }) }),
|
|
3183
|
+
/* @__PURE__ */ jsxs9(SelectContent3, { children: [
|
|
3184
|
+
/* @__PURE__ */ jsx10(SelectItem3, { value: "draft", children: "Draft" }),
|
|
3185
|
+
/* @__PURE__ */ jsx10(SelectItem3, { value: "published", children: "Published" }),
|
|
3186
|
+
/* @__PURE__ */ jsx10(SelectItem3, { value: "closed", children: "Closed" })
|
|
3187
|
+
] })
|
|
3188
|
+
]
|
|
3189
|
+
}
|
|
3190
|
+
)
|
|
3191
|
+
] })
|
|
3192
|
+
] }),
|
|
3193
|
+
/* @__PURE__ */ jsx10(
|
|
3194
|
+
Tabs3,
|
|
3195
|
+
{
|
|
3196
|
+
value: activeTab,
|
|
3197
|
+
onValueChange: (v) => setActiveTab(v),
|
|
3198
|
+
children: /* @__PURE__ */ jsx10(TabsList3, { className: "bg-transparent justify-start gap-0 -mb-px border-b-0", children: mainTabs.map((tab) => /* @__PURE__ */ jsxs9(
|
|
3199
|
+
TabsTrigger3,
|
|
3200
|
+
{
|
|
3201
|
+
value: tab.value,
|
|
3202
|
+
style: {
|
|
3203
|
+
borderBottomColor: activeTab === tab.value ? "hsl(var(--primary))" : "transparent"
|
|
3204
|
+
},
|
|
3205
|
+
className: "border-b-2 relative -mb-0.5 data-[state=active]:text-primary data-[state=inactive]:text-muted-foreground hover:text-primary hover:border-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/20 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer",
|
|
3206
|
+
children: [
|
|
3207
|
+
tab.label,
|
|
3208
|
+
tab.count !== null && /* @__PURE__ */ jsx10(
|
|
3209
|
+
"span",
|
|
3210
|
+
{
|
|
3211
|
+
className: `inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 text-[10px] font-semibold rounded-none ml-2 transition-colors ${activeTab === tab.value ? "bg-primary/5 text-primary" : "bg-primary/5 text-muted-foreground"}`,
|
|
3212
|
+
children: tab.count
|
|
3213
|
+
}
|
|
3214
|
+
)
|
|
3215
|
+
]
|
|
3216
|
+
},
|
|
3217
|
+
tab.value
|
|
3218
|
+
)) })
|
|
3219
|
+
}
|
|
3220
|
+
)
|
|
3221
|
+
] }),
|
|
3222
|
+
activeTab === "builder" && /* @__PURE__ */ jsx10("div", { children: /* @__PURE__ */ jsx10(
|
|
3223
|
+
SortableContext,
|
|
3224
|
+
{
|
|
3225
|
+
items: fields.map((f) => f.name),
|
|
3226
|
+
strategy: verticalListSortingStrategy,
|
|
3227
|
+
children: /* @__PURE__ */ jsx10(
|
|
3228
|
+
FormCanvas,
|
|
3229
|
+
{
|
|
3230
|
+
fields,
|
|
3231
|
+
selectedFieldId,
|
|
3232
|
+
onFieldSelect: handleFieldSelect,
|
|
3233
|
+
onFieldDelete: deleteField
|
|
3234
|
+
}
|
|
3235
|
+
)
|
|
3236
|
+
}
|
|
3237
|
+
) }),
|
|
3238
|
+
activeTab === "preview" && /* @__PURE__ */ jsx10(FormPreview, { fields, formData }),
|
|
3239
|
+
activeTab === "settings" && /* @__PURE__ */ jsx10("div", { className: "w-full", children: /* @__PURE__ */ jsx10(FormSettingsTab, {}) }),
|
|
3240
|
+
activeTab === "notifications" && /* @__PURE__ */ jsx10("div", { className: "w-full", children: /* @__PURE__ */ jsx10(FormNotificationsTab, {}) }),
|
|
3241
|
+
saveError && /* @__PURE__ */ jsx10("div", { className: "p-3 text-sm text-destructive bg-destructive/10 border border-destructive/20 rounded-none", children: saveError })
|
|
3242
|
+
] }) }),
|
|
3243
|
+
activeTab === "builder" && /* @__PURE__ */ jsxs9(
|
|
3244
|
+
"div",
|
|
3245
|
+
{
|
|
3246
|
+
className: "shrink-0 border-l border-primary/5 bg-background flex flex-col",
|
|
3247
|
+
style: { width: "360px" },
|
|
3248
|
+
children: [
|
|
3249
|
+
/* @__PURE__ */ jsx10(
|
|
3250
|
+
"div",
|
|
3251
|
+
{
|
|
3252
|
+
className: "shrink-0 grid grid-cols-2 gap-1.5 px-3 py-2.5 border-b border-primary/5",
|
|
3253
|
+
style: { minHeight: "52px" },
|
|
3254
|
+
children: [
|
|
3255
|
+
{
|
|
3256
|
+
value: "add",
|
|
3257
|
+
label: "Add Fields",
|
|
3258
|
+
disabled: false,
|
|
3259
|
+
icon: /* @__PURE__ */ jsxs9(
|
|
3260
|
+
"svg",
|
|
3261
|
+
{
|
|
3262
|
+
className: "h-3 w-3 shrink-0",
|
|
3263
|
+
viewBox: "0 0 24 24",
|
|
3264
|
+
fill: "none",
|
|
3265
|
+
stroke: "currentColor",
|
|
3266
|
+
strokeWidth: "2",
|
|
3267
|
+
strokeLinecap: "round",
|
|
3268
|
+
strokeLinejoin: "round",
|
|
3269
|
+
children: [
|
|
3270
|
+
/* @__PURE__ */ jsx10("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
|
|
3271
|
+
/* @__PURE__ */ jsx10("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
|
|
3272
|
+
]
|
|
3273
|
+
}
|
|
3274
|
+
)
|
|
3275
|
+
},
|
|
3276
|
+
{
|
|
3277
|
+
value: "properties",
|
|
3278
|
+
label: "Properties",
|
|
3279
|
+
disabled: !hasSelectedField,
|
|
3280
|
+
icon: /* @__PURE__ */ jsx10(SlidersIcon, {})
|
|
3281
|
+
}
|
|
3282
|
+
].map((tab) => {
|
|
3283
|
+
const isActive = sidebarTab === tab.value;
|
|
3284
|
+
return /* @__PURE__ */ jsxs9(
|
|
3285
|
+
"button",
|
|
3286
|
+
{
|
|
3287
|
+
type: "button",
|
|
3288
|
+
disabled: tab.disabled,
|
|
3289
|
+
onClick: () => !tab.disabled && setSidebarTab(tab.value),
|
|
3290
|
+
className: [
|
|
3291
|
+
"flex items-center justify-center gap-1.5 w-full text-[13px] font-medium transition-all duration-150 border rounded-none select-none",
|
|
3292
|
+
"disabled:opacity-40 disabled:cursor-not-allowed",
|
|
3293
|
+
isActive ? "bg-primary/5 text-primary border-primary/30 cursor-pointer" : "bg-transparent border-primary/5 text-muted-foreground hover:text-foreground hover:bg-primary/5/50 cursor-pointer"
|
|
3294
|
+
].join(" "),
|
|
3295
|
+
style: { padding: "8px 12px" },
|
|
3296
|
+
children: [
|
|
3297
|
+
tab.icon,
|
|
3298
|
+
tab.label
|
|
3299
|
+
]
|
|
3300
|
+
},
|
|
3301
|
+
tab.value
|
|
3302
|
+
);
|
|
3303
|
+
})
|
|
3304
|
+
}
|
|
3305
|
+
),
|
|
3306
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex-1 flex flex-col min-h-0", children: [
|
|
3307
|
+
sidebarTab === "add" && /* @__PURE__ */ jsx10("div", { className: "flex-1 flex flex-col", children: /* @__PURE__ */ jsx10(FieldLibrary, {}) }),
|
|
3308
|
+
sidebarTab === "properties" && /* @__PURE__ */ jsx10("div", { className: "flex-1 flex flex-col relative", children: selectedField ? /* @__PURE__ */ jsx10(
|
|
3309
|
+
FieldEditor,
|
|
3310
|
+
{
|
|
3311
|
+
field: selectedField,
|
|
3312
|
+
allFields: fields,
|
|
3313
|
+
onUpdate: (updates) => updateField(selectedField.name, updates),
|
|
3314
|
+
onDelete: () => {
|
|
3315
|
+
deleteField(selectedField.name);
|
|
3316
|
+
setSidebarTab("add");
|
|
3317
|
+
},
|
|
3318
|
+
onDuplicate: () => duplicateField(selectedField.name)
|
|
3319
|
+
}
|
|
3320
|
+
) : /* @__PURE__ */ jsxs9("div", { className: "flex flex-col items-center justify-center h-full px-6 py-16 text-center", children: [
|
|
3321
|
+
/* @__PURE__ */ jsx10("div", { className: "flex items-center justify-center w-11 h-11 rounded-none bg-primary/5 border border-primary/5 mb-4", children: /* @__PURE__ */ jsx10(SlidersIcon, {}) }),
|
|
3322
|
+
/* @__PURE__ */ jsx10("p", { className: "text-sm font-medium text-foreground", children: "No field selected" }),
|
|
3323
|
+
/* @__PURE__ */ jsx10("p", { className: "text-xs text-muted-foreground mt-1", children: "Click a field in the canvas to edit its properties" })
|
|
3324
|
+
] }) })
|
|
3325
|
+
] })
|
|
3326
|
+
]
|
|
3327
|
+
}
|
|
3328
|
+
)
|
|
3329
|
+
]
|
|
3330
|
+
}
|
|
3331
|
+
)
|
|
3332
|
+
]
|
|
3333
|
+
}
|
|
3334
|
+
);
|
|
3335
|
+
}
|
|
3336
|
+
function FormBuilderView({
|
|
3337
|
+
id,
|
|
3338
|
+
entryId,
|
|
3339
|
+
collection,
|
|
3340
|
+
collectionSlug,
|
|
3341
|
+
isCreating,
|
|
3342
|
+
initialData,
|
|
3343
|
+
onSave,
|
|
3344
|
+
onSuccess,
|
|
3345
|
+
onCancel
|
|
3346
|
+
}) {
|
|
3347
|
+
const resolvedEntryId = entryId || id || initialData?.id;
|
|
3348
|
+
const resolvedCollectionSlug = collectionSlug || collection || "forms";
|
|
3349
|
+
const resolvedIsCreating = isCreating ?? !resolvedEntryId;
|
|
3350
|
+
const providerKey = resolvedEntryId || "new";
|
|
3351
|
+
return /* @__PURE__ */ jsx10(
|
|
3352
|
+
FormBuilderProvider,
|
|
3353
|
+
{
|
|
3354
|
+
initialData: {
|
|
3355
|
+
id: resolvedEntryId,
|
|
3356
|
+
name: initialData?.name,
|
|
3357
|
+
slug: initialData?.slug,
|
|
3358
|
+
description: initialData?.description,
|
|
3359
|
+
status: initialData?.status,
|
|
3360
|
+
fields: initialData?.fields,
|
|
3361
|
+
settings: initialData?.settings,
|
|
3362
|
+
notifications: initialData?.notifications
|
|
3363
|
+
},
|
|
3364
|
+
children: /* @__PURE__ */ jsx10(
|
|
3365
|
+
FormBuilderViewInner,
|
|
3366
|
+
{
|
|
3367
|
+
isCreating: resolvedIsCreating,
|
|
3368
|
+
collectionSlug: resolvedCollectionSlug,
|
|
3369
|
+
entryId: resolvedEntryId,
|
|
3370
|
+
onSave,
|
|
3371
|
+
onSuccess,
|
|
3372
|
+
onCancel
|
|
3373
|
+
}
|
|
3374
|
+
)
|
|
3375
|
+
},
|
|
3376
|
+
providerKey
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
// src/admin/components/FormFieldList.tsx
|
|
3381
|
+
import {
|
|
3382
|
+
DndContext as DndContext2,
|
|
3383
|
+
closestCenter as closestCenter2,
|
|
3384
|
+
KeyboardSensor as KeyboardSensor2,
|
|
3385
|
+
PointerSensor as PointerSensor2,
|
|
3386
|
+
useSensor as useSensor2,
|
|
3387
|
+
useSensors as useSensors2
|
|
3388
|
+
} from "@dnd-kit/core";
|
|
3389
|
+
import {
|
|
3390
|
+
arrayMove,
|
|
3391
|
+
SortableContext as SortableContext2,
|
|
3392
|
+
sortableKeyboardCoordinates as sortableKeyboardCoordinates2,
|
|
3393
|
+
verticalListSortingStrategy as verticalListSortingStrategy2
|
|
3394
|
+
} from "@dnd-kit/sortable";
|
|
3395
|
+
import { useCallback as useCallback8 } from "react";
|
|
3396
|
+
|
|
3397
|
+
// src/admin/components/SortableFieldRow.tsx
|
|
3398
|
+
import { useSortable as useSortable2 } from "@dnd-kit/sortable";
|
|
3399
|
+
import { CSS as CSS2 } from "@dnd-kit/utilities";
|
|
3400
|
+
import { GripVertical, Trash2 } from "lucide-react";
|
|
3401
|
+
import { useCallback as useCallback7 } from "react";
|
|
3402
|
+
|
|
3403
|
+
// src/admin/fields/index.ts
|
|
3404
|
+
import {
|
|
3405
|
+
AlignLeft,
|
|
3406
|
+
Calendar,
|
|
3407
|
+
CheckSquare,
|
|
3408
|
+
ChevronDown,
|
|
3409
|
+
Circle,
|
|
3410
|
+
FileText,
|
|
3411
|
+
Hash,
|
|
3412
|
+
Mail,
|
|
3413
|
+
Type
|
|
3414
|
+
} from "lucide-react";
|
|
3415
|
+
var FIELD_CATEGORIES = [
|
|
3416
|
+
{
|
|
3417
|
+
key: "core",
|
|
3418
|
+
label: "Core Fields",
|
|
3419
|
+
description: "Essential form input fields"
|
|
3420
|
+
}
|
|
3421
|
+
];
|
|
3422
|
+
var FIELD_TYPES2 = [
|
|
3423
|
+
// ---------------------------------------------------------------------------
|
|
3424
|
+
// Core Fields (7 types)
|
|
3425
|
+
// ---------------------------------------------------------------------------
|
|
3426
|
+
{
|
|
3427
|
+
type: "text",
|
|
3428
|
+
label: "Text",
|
|
3429
|
+
icon: "Type",
|
|
3430
|
+
description: "Single-line text input",
|
|
3431
|
+
category: "core",
|
|
3432
|
+
EditorComponent: () => import("../TextFieldEditor-5EU64EA5.js"),
|
|
3433
|
+
defaultConfig: {
|
|
3434
|
+
type: "text",
|
|
3435
|
+
label: "Text Field",
|
|
3436
|
+
placeholder: "",
|
|
3437
|
+
required: false
|
|
3438
|
+
}
|
|
3439
|
+
},
|
|
3440
|
+
{
|
|
3441
|
+
type: "textarea",
|
|
3442
|
+
label: "Textarea",
|
|
3443
|
+
icon: "AlignLeft",
|
|
3444
|
+
description: "Multi-line text input",
|
|
3445
|
+
category: "core",
|
|
3446
|
+
EditorComponent: () => import("../TextareaFieldEditor-773G3LNM.js"),
|
|
3447
|
+
defaultConfig: {
|
|
3448
|
+
type: "textarea",
|
|
3449
|
+
label: "Message",
|
|
3450
|
+
placeholder: "",
|
|
3451
|
+
required: false,
|
|
3452
|
+
rows: 4
|
|
3453
|
+
}
|
|
3454
|
+
},
|
|
3455
|
+
{
|
|
3456
|
+
type: "email",
|
|
3457
|
+
label: "Email",
|
|
3458
|
+
icon: "Mail",
|
|
3459
|
+
description: "Email address with validation",
|
|
3460
|
+
category: "core",
|
|
3461
|
+
EditorComponent: () => import("../EmailFieldEditor-MMOXXL7P.js"),
|
|
3462
|
+
defaultConfig: {
|
|
3463
|
+
type: "email",
|
|
3464
|
+
label: "Email",
|
|
3465
|
+
placeholder: "email@example.com",
|
|
3466
|
+
required: false
|
|
3467
|
+
}
|
|
3468
|
+
},
|
|
3469
|
+
{
|
|
3470
|
+
type: "number",
|
|
3471
|
+
label: "Number",
|
|
3472
|
+
icon: "Hash",
|
|
3473
|
+
description: "Numeric input with min/max",
|
|
3474
|
+
category: "core",
|
|
3475
|
+
EditorComponent: () => import("../NumberFieldEditor-UISAYR6Z.js"),
|
|
3476
|
+
defaultConfig: {
|
|
3477
|
+
type: "number",
|
|
3478
|
+
label: "Number",
|
|
3479
|
+
placeholder: "0",
|
|
3480
|
+
required: false
|
|
3481
|
+
}
|
|
3482
|
+
},
|
|
3483
|
+
{
|
|
3484
|
+
type: "checkbox",
|
|
3485
|
+
label: "Checkbox",
|
|
3486
|
+
icon: "CheckSquare",
|
|
3487
|
+
description: "Single checkbox (yes/no)",
|
|
3488
|
+
category: "core",
|
|
3489
|
+
EditorComponent: () => import("../CheckboxFieldEditor-K4T6QX52.js"),
|
|
3490
|
+
defaultConfig: {
|
|
3491
|
+
type: "checkbox",
|
|
3492
|
+
label: "I agree to the terms",
|
|
3493
|
+
required: false,
|
|
3494
|
+
defaultValue: false
|
|
3495
|
+
}
|
|
3496
|
+
},
|
|
3497
|
+
{
|
|
3498
|
+
type: "select",
|
|
3499
|
+
label: "Dropdown",
|
|
3500
|
+
icon: "ChevronDown",
|
|
3501
|
+
description: "Dropdown select menu",
|
|
3502
|
+
category: "core",
|
|
3503
|
+
EditorComponent: () => import("../SelectFieldEditor-PSO4KHV4.js"),
|
|
3504
|
+
defaultConfig: {
|
|
3505
|
+
type: "select",
|
|
3506
|
+
label: "Select an option",
|
|
3507
|
+
placeholder: "Choose...",
|
|
3508
|
+
required: false,
|
|
3509
|
+
options: [
|
|
3510
|
+
{ label: "Option 1", value: "option1" },
|
|
3511
|
+
{ label: "Option 2", value: "option2" }
|
|
3512
|
+
]
|
|
3513
|
+
}
|
|
3514
|
+
},
|
|
3515
|
+
{
|
|
3516
|
+
type: "radio",
|
|
3517
|
+
label: "Radio",
|
|
3518
|
+
icon: "Circle",
|
|
3519
|
+
description: "Radio button group",
|
|
3520
|
+
category: "core",
|
|
3521
|
+
EditorComponent: () => import("../RadioFieldEditor-6UQFH4WT.js"),
|
|
3522
|
+
defaultConfig: {
|
|
3523
|
+
type: "radio",
|
|
3524
|
+
label: "Choose one",
|
|
3525
|
+
required: false,
|
|
3526
|
+
options: [
|
|
3527
|
+
{ label: "Option 1", value: "option1" },
|
|
3528
|
+
{ label: "Option 2", value: "option2" }
|
|
3529
|
+
]
|
|
3530
|
+
}
|
|
3531
|
+
},
|
|
3532
|
+
// ---------------------------------------------------------------------------
|
|
3533
|
+
// Date Fields
|
|
3534
|
+
// ---------------------------------------------------------------------------
|
|
3535
|
+
{
|
|
3536
|
+
type: "date",
|
|
3537
|
+
label: "Date",
|
|
3538
|
+
icon: "Calendar",
|
|
3539
|
+
description: "Date picker",
|
|
3540
|
+
category: "core",
|
|
3541
|
+
EditorComponent: () => import("../DateFieldEditor-JFJFAVOH.js"),
|
|
3542
|
+
defaultConfig: {
|
|
3543
|
+
type: "date",
|
|
3544
|
+
label: "Date",
|
|
3545
|
+
required: false
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
];
|
|
3549
|
+
function getFieldTypesByCategory() {
|
|
3550
|
+
return FIELD_TYPES2.reduce(
|
|
3551
|
+
(acc, field) => {
|
|
3552
|
+
if (!acc[field.category]) {
|
|
3553
|
+
acc[field.category] = [];
|
|
3554
|
+
}
|
|
3555
|
+
acc[field.category].push(field);
|
|
3556
|
+
return acc;
|
|
3557
|
+
},
|
|
3558
|
+
{}
|
|
3559
|
+
);
|
|
3560
|
+
}
|
|
3561
|
+
function getFieldTypeConfig(type) {
|
|
3562
|
+
return FIELD_TYPES2.find((f) => f.type === type);
|
|
3563
|
+
}
|
|
3564
|
+
function createFieldFromType2(type) {
|
|
3565
|
+
const config = getFieldTypeConfig(type);
|
|
3566
|
+
if (!config) {
|
|
3567
|
+
throw new Error(`Unknown field type: ${type}`);
|
|
3568
|
+
}
|
|
3569
|
+
const timestamp = Date.now().toString(36);
|
|
3570
|
+
const random = Math.random().toString(36).substring(2, 6);
|
|
3571
|
+
const name = `${type}_${timestamp}_${random}`;
|
|
3572
|
+
return {
|
|
3573
|
+
name,
|
|
3574
|
+
...config.defaultConfig
|
|
3575
|
+
};
|
|
3576
|
+
}
|
|
3577
|
+
function getFieldTypeIcon(type) {
|
|
3578
|
+
return getFieldTypeConfig(type)?.icon ?? "FileText";
|
|
3579
|
+
}
|
|
3580
|
+
function getFieldTypeLabel(type) {
|
|
3581
|
+
return getFieldTypeConfig(type)?.label ?? type;
|
|
3582
|
+
}
|
|
3583
|
+
var ICON_MAP = {
|
|
3584
|
+
Type,
|
|
3585
|
+
AlignLeft,
|
|
3586
|
+
Mail,
|
|
3587
|
+
Hash,
|
|
3588
|
+
CheckSquare,
|
|
3589
|
+
ChevronDown,
|
|
3590
|
+
Circle,
|
|
3591
|
+
Calendar,
|
|
3592
|
+
FileText
|
|
3593
|
+
// Fallback icon
|
|
3594
|
+
};
|
|
3595
|
+
function resolveFieldIcon(iconName) {
|
|
3596
|
+
return ICON_MAP[iconName] ?? FileText;
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3599
|
+
// src/admin/components/SortableFieldRow.tsx
|
|
3600
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3601
|
+
function SortableFieldRow({
|
|
3602
|
+
field,
|
|
3603
|
+
isSelected,
|
|
3604
|
+
onSelect,
|
|
3605
|
+
onDelete,
|
|
3606
|
+
disabled = false
|
|
3607
|
+
}) {
|
|
3608
|
+
const {
|
|
3609
|
+
attributes,
|
|
3610
|
+
listeners,
|
|
3611
|
+
setNodeRef,
|
|
3612
|
+
transform,
|
|
3613
|
+
transition,
|
|
3614
|
+
isDragging
|
|
3615
|
+
} = useSortable2({
|
|
3616
|
+
id: field.name,
|
|
3617
|
+
disabled
|
|
3618
|
+
});
|
|
3619
|
+
const style = {
|
|
3620
|
+
transform: CSS2.Transform.toString(transform),
|
|
3621
|
+
transition
|
|
3622
|
+
};
|
|
3623
|
+
const iconName = getFieldTypeIcon(field.type);
|
|
3624
|
+
const typeLabel = getFieldTypeLabel(field.type);
|
|
3625
|
+
const IconComponent = resolveFieldIcon(iconName);
|
|
3626
|
+
const handleDelete = useCallback7(
|
|
3627
|
+
(e) => {
|
|
3628
|
+
e.stopPropagation();
|
|
3629
|
+
onDelete();
|
|
3630
|
+
},
|
|
3631
|
+
[onDelete]
|
|
3632
|
+
);
|
|
3633
|
+
const handleKeyDown = useCallback7(
|
|
3634
|
+
(e) => {
|
|
3635
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
3636
|
+
e.preventDefault();
|
|
3637
|
+
onSelect();
|
|
3638
|
+
}
|
|
3639
|
+
},
|
|
3640
|
+
[onSelect]
|
|
3641
|
+
);
|
|
3642
|
+
const rowClassName = [
|
|
3643
|
+
"form-field-row",
|
|
3644
|
+
isSelected && "form-field-row--selected",
|
|
3645
|
+
isDragging && "form-field-row--dragging",
|
|
3646
|
+
disabled && "form-field-row--disabled"
|
|
3647
|
+
].filter(Boolean).join(" ");
|
|
3648
|
+
return /* @__PURE__ */ jsxs10(
|
|
3649
|
+
"div",
|
|
3650
|
+
{
|
|
3651
|
+
ref: setNodeRef,
|
|
3652
|
+
style,
|
|
3653
|
+
className: rowClassName,
|
|
3654
|
+
onClick: onSelect,
|
|
3655
|
+
onKeyDown: handleKeyDown,
|
|
3656
|
+
role: "button",
|
|
3657
|
+
tabIndex: disabled ? -1 : 0,
|
|
3658
|
+
"aria-label": `${field.label || field.name} field, type: ${typeLabel}${isSelected ? ", selected" : ""}`,
|
|
3659
|
+
"aria-pressed": isSelected,
|
|
3660
|
+
"aria-disabled": disabled,
|
|
3661
|
+
children: [
|
|
3662
|
+
!disabled && /* @__PURE__ */ jsx11(
|
|
3663
|
+
"button",
|
|
3664
|
+
{
|
|
3665
|
+
type: "button",
|
|
3666
|
+
className: "form-field-row__drag-handle",
|
|
3667
|
+
"aria-label": `Drag to reorder ${field.label || field.name}`,
|
|
3668
|
+
...attributes,
|
|
3669
|
+
...listeners,
|
|
3670
|
+
children: /* @__PURE__ */ jsx11(GripVertical, { className: "form-field-row__drag-icon" })
|
|
3671
|
+
}
|
|
3672
|
+
),
|
|
3673
|
+
disabled && /* @__PURE__ */ jsx11("div", { className: "form-field-row__drag-handle-spacer" }),
|
|
3674
|
+
/* @__PURE__ */ jsx11("span", { className: "form-field-row__type-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx11(IconComponent, { className: "form-field-row__icon" }) }),
|
|
3675
|
+
/* @__PURE__ */ jsxs10("div", { className: "form-field-row__content", children: [
|
|
3676
|
+
/* @__PURE__ */ jsxs10("span", { className: "form-field-row__label", children: [
|
|
3677
|
+
field.label || field.name,
|
|
3678
|
+
field.required && /* @__PURE__ */ jsx11("span", { className: "form-field-row__required", "aria-label": "Required", children: "*" })
|
|
3679
|
+
] }),
|
|
3680
|
+
/* @__PURE__ */ jsx11("span", { className: "form-field-row__type-badge", children: typeLabel })
|
|
3681
|
+
] }),
|
|
3682
|
+
/* @__PURE__ */ jsx11("div", { className: "form-field-row__actions", children: /* @__PURE__ */ jsx11(
|
|
3683
|
+
"button",
|
|
3684
|
+
{
|
|
3685
|
+
type: "button",
|
|
3686
|
+
className: "form-field-row__action form-field-row__action--delete",
|
|
3687
|
+
onClick: handleDelete,
|
|
3688
|
+
onKeyDown: (e) => {
|
|
3689
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
3690
|
+
e.preventDefault();
|
|
3691
|
+
handleDelete(e);
|
|
3692
|
+
}
|
|
3693
|
+
},
|
|
3694
|
+
disabled,
|
|
3695
|
+
"aria-label": `Delete ${field.label || field.name}`,
|
|
3696
|
+
title: "Delete field",
|
|
3697
|
+
children: /* @__PURE__ */ jsx11(Trash2, { className: "form-field-row__action-icon" })
|
|
3698
|
+
}
|
|
3699
|
+
) })
|
|
3700
|
+
]
|
|
3701
|
+
}
|
|
3702
|
+
);
|
|
3703
|
+
}
|
|
3704
|
+
|
|
3705
|
+
// src/admin/components/FormFieldList.tsx
|
|
3706
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3707
|
+
function FormFieldList({
|
|
3708
|
+
fields,
|
|
3709
|
+
onFieldsChange,
|
|
3710
|
+
onEditField,
|
|
3711
|
+
onDeleteField,
|
|
3712
|
+
selectedFieldName,
|
|
3713
|
+
disabled = false
|
|
3714
|
+
}) {
|
|
3715
|
+
const sensors = useSensors2(
|
|
3716
|
+
useSensor2(PointerSensor2, {
|
|
3717
|
+
activationConstraint: {
|
|
3718
|
+
distance: 8
|
|
3719
|
+
}
|
|
3720
|
+
}),
|
|
3721
|
+
useSensor2(KeyboardSensor2, {
|
|
3722
|
+
coordinateGetter: sortableKeyboardCoordinates2
|
|
3723
|
+
})
|
|
3724
|
+
);
|
|
3725
|
+
const handleDragEnd = useCallback8(
|
|
3726
|
+
(event) => {
|
|
3727
|
+
const { active, over } = event;
|
|
3728
|
+
if (over && active.id !== over.id) {
|
|
3729
|
+
const oldIndex = fields.findIndex((f) => f.name === active.id);
|
|
3730
|
+
const newIndex = fields.findIndex((f) => f.name === over.id);
|
|
3731
|
+
if (oldIndex !== -1 && newIndex !== -1) {
|
|
3732
|
+
const newFields = arrayMove(fields, oldIndex, newIndex);
|
|
3733
|
+
onFieldsChange(newFields);
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
},
|
|
3737
|
+
[fields, onFieldsChange]
|
|
3738
|
+
);
|
|
3739
|
+
if (fields.length === 0) {
|
|
3740
|
+
return /* @__PURE__ */ jsx12("div", { className: "form-field-list form-field-list--empty", children: /* @__PURE__ */ jsxs11("div", { className: "form-field-list__empty-state", children: [
|
|
3741
|
+
/* @__PURE__ */ jsx12("div", { className: "form-field-list__empty-icon", "aria-hidden": "true", children: "\u{1F4CB}" }),
|
|
3742
|
+
/* @__PURE__ */ jsx12("p", { className: "form-field-list__empty-title", children: "No fields yet" }),
|
|
3743
|
+
/* @__PURE__ */ jsx12("p", { className: "form-field-list__empty-hint", children: 'Click "Add Field" to get started' })
|
|
3744
|
+
] }) });
|
|
3745
|
+
}
|
|
3746
|
+
return /* @__PURE__ */ jsxs11("div", { className: "form-field-list", children: [
|
|
3747
|
+
/* @__PURE__ */ jsx12(
|
|
3748
|
+
DndContext2,
|
|
3749
|
+
{
|
|
3750
|
+
sensors,
|
|
3751
|
+
collisionDetection: closestCenter2,
|
|
3752
|
+
onDragEnd: handleDragEnd,
|
|
3753
|
+
children: /* @__PURE__ */ jsx12(
|
|
3754
|
+
SortableContext2,
|
|
3755
|
+
{
|
|
3756
|
+
items: fields.map((f) => f.name),
|
|
3757
|
+
strategy: verticalListSortingStrategy2,
|
|
3758
|
+
disabled,
|
|
3759
|
+
children: /* @__PURE__ */ jsx12("div", { className: "form-field-list__fields", role: "list", children: fields.map((field) => /* @__PURE__ */ jsx12(
|
|
3760
|
+
SortableFieldRow,
|
|
3761
|
+
{
|
|
3762
|
+
field,
|
|
3763
|
+
isSelected: selectedFieldName === field.name,
|
|
3764
|
+
onSelect: () => onEditField(field.name),
|
|
3765
|
+
onDelete: () => onDeleteField(field.name),
|
|
3766
|
+
disabled
|
|
3767
|
+
},
|
|
3768
|
+
field.name
|
|
3769
|
+
)) })
|
|
3770
|
+
}
|
|
3771
|
+
)
|
|
3772
|
+
}
|
|
3773
|
+
),
|
|
3774
|
+
/* @__PURE__ */ jsx12("div", { className: "form-field-list__footer", children: /* @__PURE__ */ jsxs11("p", { className: "form-field-list__footer-text", children: [
|
|
3775
|
+
fields.length,
|
|
3776
|
+
" field",
|
|
3777
|
+
fields.length !== 1 ? "s" : "",
|
|
3778
|
+
" \u2022 Drag to reorder \u2022 Click to edit"
|
|
3779
|
+
] }) })
|
|
3780
|
+
] });
|
|
3781
|
+
}
|
|
3782
|
+
|
|
3783
|
+
// src/admin/components/AddFieldButton.tsx
|
|
3784
|
+
import {
|
|
3785
|
+
Dialog,
|
|
3786
|
+
DialogContent,
|
|
3787
|
+
DialogDescription,
|
|
3788
|
+
DialogHeader,
|
|
3789
|
+
DialogTitle
|
|
3790
|
+
} from "@nextlyhq/ui";
|
|
3791
|
+
import { Plus } from "lucide-react";
|
|
3792
|
+
import { useCallback as useCallback9, useState as useState7 } from "react";
|
|
3793
|
+
import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3794
|
+
function AddFieldButton({
|
|
3795
|
+
onAddField,
|
|
3796
|
+
disabled = false,
|
|
3797
|
+
className
|
|
3798
|
+
}) {
|
|
3799
|
+
const [isOpen, setIsOpen] = useState7(false);
|
|
3800
|
+
const fieldsByCategory = getFieldTypesByCategory();
|
|
3801
|
+
const handleSelectField = useCallback9(
|
|
3802
|
+
(type) => {
|
|
3803
|
+
const newField = createFieldFromType2(type);
|
|
3804
|
+
onAddField(newField);
|
|
3805
|
+
setIsOpen(false);
|
|
3806
|
+
},
|
|
3807
|
+
[onAddField]
|
|
3808
|
+
);
|
|
3809
|
+
const buttonClassName = [
|
|
3810
|
+
"add-field-button",
|
|
3811
|
+
disabled && "add-field-button--disabled",
|
|
3812
|
+
className
|
|
3813
|
+
].filter(Boolean).join(" ");
|
|
3814
|
+
return /* @__PURE__ */ jsxs12(Fragment3, { children: [
|
|
3815
|
+
/* @__PURE__ */ jsxs12(
|
|
3816
|
+
"button",
|
|
3817
|
+
{
|
|
3818
|
+
type: "button",
|
|
3819
|
+
className: buttonClassName,
|
|
3820
|
+
onClick: () => setIsOpen(true),
|
|
3821
|
+
disabled,
|
|
3822
|
+
"aria-label": "Add a new field to the form",
|
|
3823
|
+
children: [
|
|
3824
|
+
/* @__PURE__ */ jsx13(Plus, { className: "add-field-button__icon", "aria-hidden": "true" }),
|
|
3825
|
+
/* @__PURE__ */ jsx13("span", { className: "add-field-button__label", children: "Add Field" })
|
|
3826
|
+
]
|
|
3827
|
+
}
|
|
3828
|
+
),
|
|
3829
|
+
/* @__PURE__ */ jsx13(Dialog, { open: isOpen, onOpenChange: setIsOpen, children: /* @__PURE__ */ jsxs12(DialogContent, { size: "md", className: "add-field-modal", children: [
|
|
3830
|
+
/* @__PURE__ */ jsxs12(DialogHeader, { children: [
|
|
3831
|
+
/* @__PURE__ */ jsx13(DialogTitle, { children: "Add Field" }),
|
|
3832
|
+
/* @__PURE__ */ jsx13(DialogDescription, { children: "Select a field type to add to your form." })
|
|
3833
|
+
] }),
|
|
3834
|
+
/* @__PURE__ */ jsx13("div", { className: "add-field-modal__content", children: FIELD_CATEGORIES.map((category) => {
|
|
3835
|
+
const fields = fieldsByCategory[category.key] || [];
|
|
3836
|
+
if (fields.length === 0) return null;
|
|
3837
|
+
return /* @__PURE__ */ jsxs12("div", { className: "add-field-modal__category", children: [
|
|
3838
|
+
/* @__PURE__ */ jsx13("h4", { className: "add-field-modal__category-title", children: category.label }),
|
|
3839
|
+
/* @__PURE__ */ jsx13("p", { className: "add-field-modal__category-description", children: category.description }),
|
|
3840
|
+
/* @__PURE__ */ jsx13("div", { className: "add-field-modal__field-grid", children: fields.map((fieldConfig) => /* @__PURE__ */ jsx13(
|
|
3841
|
+
FieldTypeButton,
|
|
3842
|
+
{
|
|
3843
|
+
config: fieldConfig,
|
|
3844
|
+
onClick: () => handleSelectField(fieldConfig.type)
|
|
3845
|
+
},
|
|
3846
|
+
fieldConfig.type
|
|
3847
|
+
)) })
|
|
3848
|
+
] }, category.key);
|
|
3849
|
+
}) })
|
|
3850
|
+
] }) })
|
|
3851
|
+
] });
|
|
3852
|
+
}
|
|
3853
|
+
function FieldTypeButton({ config, onClick }) {
|
|
3854
|
+
const IconComponent = resolveFieldIcon(config.icon);
|
|
3855
|
+
return /* @__PURE__ */ jsxs12(
|
|
3856
|
+
"button",
|
|
3857
|
+
{
|
|
3858
|
+
type: "button",
|
|
3859
|
+
className: "add-field-modal__field-button",
|
|
3860
|
+
onClick,
|
|
3861
|
+
"aria-label": `Add ${config.label} field`,
|
|
3862
|
+
children: [
|
|
3863
|
+
/* @__PURE__ */ jsx13("span", { className: "add-field-modal__field-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx13(IconComponent, { className: "add-field-modal__icon" }) }),
|
|
3864
|
+
/* @__PURE__ */ jsxs12("span", { className: "add-field-modal__field-info", children: [
|
|
3865
|
+
/* @__PURE__ */ jsx13("span", { className: "add-field-modal__field-label", children: config.label }),
|
|
3866
|
+
/* @__PURE__ */ jsx13("span", { className: "add-field-modal__field-description", children: config.description })
|
|
3867
|
+
] })
|
|
3868
|
+
]
|
|
3869
|
+
}
|
|
3870
|
+
);
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
// src/admin/components/FieldEditorPanel.tsx
|
|
3874
|
+
import { X } from "lucide-react";
|
|
3875
|
+
import React, { Suspense, useMemo as useMemo5 } from "react";
|
|
3876
|
+
import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3877
|
+
function EditorSkeleton() {
|
|
3878
|
+
return /* @__PURE__ */ jsxs13("div", { className: "field-editor-panel__skeleton", children: [
|
|
3879
|
+
/* @__PURE__ */ jsxs13("div", { className: "field-editor-panel__skeleton-row", children: [
|
|
3880
|
+
/* @__PURE__ */ jsx14("div", { className: "field-editor-panel__skeleton-label" }),
|
|
3881
|
+
/* @__PURE__ */ jsx14("div", { className: "field-editor-panel__skeleton-input" })
|
|
3882
|
+
] }),
|
|
3883
|
+
/* @__PURE__ */ jsxs13("div", { className: "field-editor-panel__skeleton-row", children: [
|
|
3884
|
+
/* @__PURE__ */ jsx14("div", { className: "field-editor-panel__skeleton-label" }),
|
|
3885
|
+
/* @__PURE__ */ jsx14("div", { className: "field-editor-panel__skeleton-input" })
|
|
3886
|
+
] }),
|
|
3887
|
+
/* @__PURE__ */ jsxs13("div", { className: "field-editor-panel__skeleton-row", children: [
|
|
3888
|
+
/* @__PURE__ */ jsx14("div", { className: "field-editor-panel__skeleton-label" }),
|
|
3889
|
+
/* @__PURE__ */ jsx14("div", { className: "field-editor-panel__skeleton-input" })
|
|
3890
|
+
] }),
|
|
3891
|
+
/* @__PURE__ */ jsxs13("div", { className: "field-editor-panel__skeleton-row", children: [
|
|
3892
|
+
/* @__PURE__ */ jsx14("div", { className: "field-editor-panel__skeleton-checkbox" }),
|
|
3893
|
+
/* @__PURE__ */ jsx14("div", { className: "field-editor-panel__skeleton-label--short" })
|
|
3894
|
+
] })
|
|
3895
|
+
] });
|
|
3896
|
+
}
|
|
3897
|
+
function UnknownFieldType({ type }) {
|
|
3898
|
+
return /* @__PURE__ */ jsxs13("div", { className: "field-editor-panel__unknown", children: [
|
|
3899
|
+
/* @__PURE__ */ jsx14("p", { className: "field-editor-panel__unknown-title", children: "Unknown Field Type" }),
|
|
3900
|
+
/* @__PURE__ */ jsxs13("p", { className: "field-editor-panel__unknown-message", children: [
|
|
3901
|
+
"The field type ",
|
|
3902
|
+
/* @__PURE__ */ jsx14("code", { children: type }),
|
|
3903
|
+
" is not supported by this version of the form builder."
|
|
3904
|
+
] })
|
|
3905
|
+
] });
|
|
3906
|
+
}
|
|
3907
|
+
function FieldEditorPanel({
|
|
3908
|
+
field,
|
|
3909
|
+
allFields,
|
|
3910
|
+
onUpdate,
|
|
3911
|
+
onClose
|
|
3912
|
+
}) {
|
|
3913
|
+
const fieldConfig = getFieldTypeConfig(field.type);
|
|
3914
|
+
const EditorComponent = useMemo5(() => {
|
|
3915
|
+
if (!fieldConfig) return null;
|
|
3916
|
+
return React.lazy(fieldConfig.EditorComponent);
|
|
3917
|
+
}, [fieldConfig]);
|
|
3918
|
+
if (!fieldConfig || !EditorComponent) {
|
|
3919
|
+
return /* @__PURE__ */ jsxs13("div", { className: "field-editor-panel", children: [
|
|
3920
|
+
/* @__PURE__ */ jsxs13("div", { className: "field-editor-panel__header", children: [
|
|
3921
|
+
/* @__PURE__ */ jsx14("div", { className: "field-editor-panel__title", children: /* @__PURE__ */ jsx14("span", { className: "field-editor-panel__title-text", children: "Edit Field" }) }),
|
|
3922
|
+
/* @__PURE__ */ jsx14(
|
|
3923
|
+
"button",
|
|
3924
|
+
{
|
|
3925
|
+
type: "button",
|
|
3926
|
+
className: "field-editor-panel__close",
|
|
3927
|
+
onClick: onClose,
|
|
3928
|
+
"aria-label": "Close editor panel",
|
|
3929
|
+
children: /* @__PURE__ */ jsx14(X, { className: "field-editor-panel__close-icon" })
|
|
3930
|
+
}
|
|
3931
|
+
)
|
|
3932
|
+
] }),
|
|
3933
|
+
/* @__PURE__ */ jsx14("div", { className: "field-editor-panel__content", children: /* @__PURE__ */ jsx14(UnknownFieldType, { type: field.type }) })
|
|
3934
|
+
] });
|
|
3935
|
+
}
|
|
3936
|
+
const IconComponent = resolveFieldIcon(fieldConfig.icon);
|
|
3937
|
+
const typeLabel = getFieldTypeLabel(field.type);
|
|
3938
|
+
return /* @__PURE__ */ jsxs13("div", { className: "field-editor-panel", children: [
|
|
3939
|
+
/* @__PURE__ */ jsxs13("div", { className: "field-editor-panel__header", children: [
|
|
3940
|
+
/* @__PURE__ */ jsxs13("div", { className: "field-editor-panel__title", children: [
|
|
3941
|
+
/* @__PURE__ */ jsx14(
|
|
3942
|
+
IconComponent,
|
|
3943
|
+
{
|
|
3944
|
+
className: "field-editor-panel__title-icon",
|
|
3945
|
+
"aria-hidden": "true"
|
|
3946
|
+
}
|
|
3947
|
+
),
|
|
3948
|
+
/* @__PURE__ */ jsxs13("span", { className: "field-editor-panel__title-text", children: [
|
|
3949
|
+
"Edit ",
|
|
3950
|
+
typeLabel,
|
|
3951
|
+
" Field",
|
|
3952
|
+
field.label && /* @__PURE__ */ jsxs13("span", { className: "field-editor-panel__title-label", children: [
|
|
3953
|
+
" ",
|
|
3954
|
+
"\u2014 ",
|
|
3955
|
+
field.label
|
|
3956
|
+
] })
|
|
3957
|
+
] })
|
|
3958
|
+
] }),
|
|
3959
|
+
/* @__PURE__ */ jsx14(
|
|
3960
|
+
"button",
|
|
3961
|
+
{
|
|
3962
|
+
type: "button",
|
|
3963
|
+
className: "field-editor-panel__close",
|
|
3964
|
+
onClick: onClose,
|
|
3965
|
+
"aria-label": "Close editor panel",
|
|
3966
|
+
children: /* @__PURE__ */ jsx14(X, { className: "field-editor-panel__close-icon" })
|
|
3967
|
+
}
|
|
3968
|
+
)
|
|
3969
|
+
] }),
|
|
3970
|
+
/* @__PURE__ */ jsx14("div", { className: "field-editor-panel__content", children: /* @__PURE__ */ jsx14(Suspense, { fallback: /* @__PURE__ */ jsx14(EditorSkeleton, {}), children: /* @__PURE__ */ jsx14(
|
|
3971
|
+
EditorComponent,
|
|
3972
|
+
{
|
|
3973
|
+
field,
|
|
3974
|
+
allFields,
|
|
3975
|
+
onUpdate
|
|
3976
|
+
}
|
|
3977
|
+
) }) })
|
|
3978
|
+
] });
|
|
3979
|
+
}
|
|
3980
|
+
|
|
3981
|
+
// src/admin/index.ts
|
|
3982
|
+
var FORM_BUILDER_VIEW_PATH = "@nextlyhq/plugin-form-builder/admin#FormBuilderView";
|
|
3983
|
+
var SUBMISSIONS_FILTER_PATH = "@nextlyhq/plugin-form-builder/admin#SubmissionsFilter";
|
|
3984
|
+
function registerFormBuilderAdminComponents() {
|
|
3985
|
+
registerComponents({
|
|
3986
|
+
[FORM_BUILDER_VIEW_PATH]: FormBuilderView
|
|
3987
|
+
});
|
|
3988
|
+
}
|
|
3989
|
+
registerComponents({
|
|
3990
|
+
[FORM_BUILDER_VIEW_PATH]: FormBuilderView,
|
|
3991
|
+
[SUBMISSIONS_FILTER_PATH]: SubmissionsFilter
|
|
3992
|
+
});
|
|
3993
|
+
registerKnownPlugin(
|
|
3994
|
+
"@nextlyhq/plugin-form-builder",
|
|
3995
|
+
// eslint-disable-next-line @typescript-eslint/require-await -- registerKnownPlugin's second arg is typed `() => Promise<void>` (strict, not a union); body is synchronous
|
|
3996
|
+
async () => {
|
|
3997
|
+
registerComponents({
|
|
3998
|
+
[FORM_BUILDER_VIEW_PATH]: FormBuilderView,
|
|
3999
|
+
[SUBMISSIONS_FILTER_PATH]: SubmissionsFilter
|
|
4000
|
+
});
|
|
4001
|
+
}
|
|
4002
|
+
);
|
|
4003
|
+
export {
|
|
4004
|
+
AddFieldButton,
|
|
4005
|
+
DEFAULT_SETTINGS,
|
|
4006
|
+
FORM_BUILDER_VIEW_PATH,
|
|
4007
|
+
FieldEditor,
|
|
4008
|
+
FieldEditorPanel,
|
|
4009
|
+
FieldLibrary,
|
|
4010
|
+
FormBuilderProvider,
|
|
4011
|
+
FormBuilderView,
|
|
4012
|
+
FormCanvas,
|
|
4013
|
+
FormFieldList,
|
|
4014
|
+
FormNotificationsTab,
|
|
4015
|
+
FormPreview,
|
|
4016
|
+
FormSettingsTab,
|
|
4017
|
+
SUBMISSIONS_FILTER_PATH,
|
|
4018
|
+
SortableFieldRow,
|
|
4019
|
+
SubmissionDetail,
|
|
4020
|
+
SubmissionList,
|
|
4021
|
+
SubmissionsFilter,
|
|
4022
|
+
createFieldFromType,
|
|
4023
|
+
createNotification,
|
|
4024
|
+
generateFieldLabel,
|
|
4025
|
+
generateFieldName,
|
|
4026
|
+
registerFormBuilderAdminComponents,
|
|
4027
|
+
useFormBuilder,
|
|
4028
|
+
useOptionalFormBuilder
|
|
4029
|
+
};
|
|
4030
|
+
//# sourceMappingURL=index.js.map
|