@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.
Files changed (37) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +122 -0
  3. package/dist/CheckboxFieldEditor-K4T6QX52.js +121 -0
  4. package/dist/CheckboxFieldEditor-K4T6QX52.js.map +1 -0
  5. package/dist/DateFieldEditor-JFJFAVOH.js +187 -0
  6. package/dist/DateFieldEditor-JFJFAVOH.js.map +1 -0
  7. package/dist/EmailFieldEditor-MMOXXL7P.js +138 -0
  8. package/dist/EmailFieldEditor-MMOXXL7P.js.map +1 -0
  9. package/dist/NumberFieldEditor-UISAYR6Z.js +175 -0
  10. package/dist/NumberFieldEditor-UISAYR6Z.js.map +1 -0
  11. package/dist/RadioFieldEditor-6UQFH4WT.js +165 -0
  12. package/dist/RadioFieldEditor-6UQFH4WT.js.map +1 -0
  13. package/dist/SelectFieldEditor-PSO4KHV4.js +213 -0
  14. package/dist/SelectFieldEditor-PSO4KHV4.js.map +1 -0
  15. package/dist/TextFieldEditor-5EU64EA5.js +187 -0
  16. package/dist/TextFieldEditor-5EU64EA5.js.map +1 -0
  17. package/dist/TextareaFieldEditor-773G3LNM.js +190 -0
  18. package/dist/TextareaFieldEditor-773G3LNM.js.map +1 -0
  19. package/dist/admin/index.css +934 -0
  20. package/dist/admin/index.css.map +1 -0
  21. package/dist/admin/index.d.ts +547 -0
  22. package/dist/admin/index.js +4030 -0
  23. package/dist/admin/index.js.map +1 -0
  24. package/dist/chunk-ABNZFTQK.js +586 -0
  25. package/dist/chunk-ABNZFTQK.js.map +1 -0
  26. package/dist/chunk-XTCEPJ7B.js +256 -0
  27. package/dist/chunk-XTCEPJ7B.js.map +1 -0
  28. package/dist/components/index.d.ts +3 -0
  29. package/dist/components/index.js +9 -0
  30. package/dist/components/index.js.map +1 -0
  31. package/dist/index-C0kBvYjm.d.ts +1012 -0
  32. package/dist/index.d.ts +1663 -0
  33. package/dist/index.js +2470 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/styles/form-builder.css +1150 -0
  36. package/dist/styles/submissions-filter.css +75 -0
  37. 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