@dani-builder/strapi-plugin-builder 0.16.0 → 1.0.0

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 (140) hide show
  1. package/AGENTS.md +24 -16
  2. package/README.md +24 -4
  3. package/dist/admin/api.d.ts +16 -0
  4. package/dist/admin/api.d.ts.map +1 -0
  5. package/dist/admin/app.d.ts +2 -0
  6. package/dist/admin/app.d.ts.map +1 -0
  7. package/dist/admin/field-editor.d.ts +8 -0
  8. package/dist/admin/field-editor.d.ts.map +1 -0
  9. package/dist/admin/form-editor-page.d.ts +4 -0
  10. package/dist/admin/form-editor-page.d.ts.map +1 -0
  11. package/dist/admin/form-list-page.d.ts +4 -0
  12. package/dist/admin/form-list-page.d.ts.map +1 -0
  13. package/dist/admin/form.d.ts +42 -0
  14. package/dist/admin/form.d.ts.map +1 -0
  15. package/dist/admin/import-form.d.ts +6 -0
  16. package/dist/admin/import-form.d.ts.map +1 -0
  17. package/dist/admin/index.d.ts +6 -0
  18. package/dist/admin/index.d.ts.map +1 -0
  19. package/dist/admin/index.mjs +1006 -0
  20. package/dist/admin/plugin-icon.d.ts +2 -0
  21. package/dist/admin/plugin-icon.d.ts.map +1 -0
  22. package/dist/admin/ui.d.ts +17 -0
  23. package/dist/admin/ui.d.ts.map +1 -0
  24. package/dist/server/src/bootstrap.d.ts.map +1 -1
  25. package/dist/server/src/bootstrap.js +37 -0
  26. package/dist/server/src/bootstrap.js.map +1 -1
  27. package/dist/server/src/components/blocks/inquiry-form.json +5 -1
  28. package/dist/server/src/components/shared/chatbot-setting.json +4 -2
  29. package/dist/server/src/config/builder.d.ts +6 -3
  30. package/dist/server/src/config/builder.d.ts.map +1 -1
  31. package/dist/server/src/config/builder.js +3 -0
  32. package/dist/server/src/config/builder.js.map +1 -1
  33. package/dist/server/src/config/forms.d.ts +58 -0
  34. package/dist/server/src/config/forms.d.ts.map +1 -0
  35. package/dist/server/src/config/forms.js +14 -0
  36. package/dist/server/src/config/forms.js.map +1 -0
  37. package/dist/server/src/config/index.d.ts +2 -22
  38. package/dist/server/src/config/index.d.ts.map +1 -1
  39. package/dist/server/src/config/index.js +17 -82
  40. package/dist/server/src/config/index.js.map +1 -1
  41. package/dist/server/src/content-types/form/index.d.ts +75 -0
  42. package/dist/server/src/content-types/form/index.d.ts.map +1 -0
  43. package/dist/server/src/content-types/form/index.js +8 -0
  44. package/dist/server/src/content-types/form/index.js.map +1 -0
  45. package/dist/server/src/content-types/form/schema.json +75 -0
  46. package/dist/server/src/content-types/index.d.ts +154 -0
  47. package/dist/server/src/content-types/index.d.ts.map +1 -1
  48. package/dist/server/src/content-types/index.js +4 -0
  49. package/dist/server/src/content-types/index.js.map +1 -1
  50. package/dist/server/src/content-types/submission/index.d.ts +83 -0
  51. package/dist/server/src/content-types/submission/index.d.ts.map +1 -0
  52. package/dist/server/src/content-types/submission/index.js +8 -0
  53. package/dist/server/src/content-types/submission/index.js.map +1 -0
  54. package/dist/server/src/content-types/submission/schema.json +88 -0
  55. package/dist/server/src/controllers/form-admin.d.ts +18 -0
  56. package/dist/server/src/controllers/form-admin.d.ts.map +1 -0
  57. package/dist/server/src/controllers/form-admin.js +107 -0
  58. package/dist/server/src/controllers/form-admin.js.map +1 -0
  59. package/dist/server/src/controllers/form.d.ts +8 -0
  60. package/dist/server/src/controllers/form.d.ts.map +1 -0
  61. package/dist/server/src/controllers/form.js +36 -0
  62. package/dist/server/src/controllers/form.js.map +1 -0
  63. package/dist/server/src/controllers/index.d.ts.map +1 -1
  64. package/dist/server/src/controllers/index.js +6 -2
  65. package/dist/server/src/controllers/index.js.map +1 -1
  66. package/dist/server/src/controllers/submission.d.ts +9 -0
  67. package/dist/server/src/controllers/submission.d.ts.map +1 -0
  68. package/dist/server/src/controllers/submission.js +58 -0
  69. package/dist/server/src/controllers/submission.js.map +1 -0
  70. package/dist/server/src/policies/rate-limit.d.ts.map +1 -1
  71. package/dist/server/src/policies/rate-limit.js +4 -4
  72. package/dist/server/src/policies/rate-limit.js.map +1 -1
  73. package/dist/server/src/preview/handler.d.ts +1 -1
  74. package/dist/server/src/preview/handler.d.ts.map +1 -1
  75. package/dist/server/src/preview/handler.js +7 -1
  76. package/dist/server/src/preview/handler.js.map +1 -1
  77. package/dist/server/src/register.d.ts.map +1 -1
  78. package/dist/server/src/register.js +2 -6
  79. package/dist/server/src/register.js.map +1 -1
  80. package/dist/server/src/routes/admin.d.ts +14 -2
  81. package/dist/server/src/routes/admin.d.ts.map +1 -1
  82. package/dist/server/src/routes/admin.js +44 -0
  83. package/dist/server/src/routes/admin.js.map +1 -1
  84. package/dist/server/src/routes/content-api.d.ts +4 -0
  85. package/dist/server/src/routes/content-api.d.ts.map +1 -1
  86. package/dist/server/src/routes/content-api.js +12 -9
  87. package/dist/server/src/routes/content-api.js.map +1 -1
  88. package/dist/server/src/services/form.d.ts +21 -0
  89. package/dist/server/src/services/form.d.ts.map +1 -0
  90. package/dist/server/src/services/form.js +45 -0
  91. package/dist/server/src/services/form.js.map +1 -0
  92. package/dist/server/src/services/idempotency-collision.d.ts +3 -0
  93. package/dist/server/src/services/idempotency-collision.d.ts.map +1 -0
  94. package/dist/server/src/services/idempotency-collision.js +24 -0
  95. package/dist/server/src/services/idempotency-collision.js.map +1 -0
  96. package/dist/server/src/services/index.d.ts.map +1 -1
  97. package/dist/server/src/services/index.js +41 -2
  98. package/dist/server/src/services/index.js.map +1 -1
  99. package/dist/server/src/services/notification.d.ts +26 -0
  100. package/dist/server/src/services/notification.d.ts.map +1 -0
  101. package/dist/server/src/services/notification.js +106 -0
  102. package/dist/server/src/services/notification.js.map +1 -0
  103. package/dist/server/src/services/populate.d.ts +6 -0
  104. package/dist/server/src/services/populate.d.ts.map +1 -1
  105. package/dist/server/src/services/populate.js +2 -0
  106. package/dist/server/src/services/populate.js.map +1 -1
  107. package/dist/server/src/services/submission.d.ts +16 -0
  108. package/dist/server/src/services/submission.d.ts.map +1 -0
  109. package/dist/server/src/services/submission.js +284 -0
  110. package/dist/server/src/services/submission.js.map +1 -0
  111. package/dist/server/src/services/template.d.ts +18 -0
  112. package/dist/server/src/services/template.d.ts.map +1 -0
  113. package/dist/server/src/services/template.js +50 -0
  114. package/dist/server/src/services/template.js.map +1 -0
  115. package/dist/server/src/validation/form.d.ts +48 -0
  116. package/dist/server/src/validation/form.d.ts.map +1 -0
  117. package/dist/server/src/validation/form.js +354 -0
  118. package/dist/server/src/validation/form.js.map +1 -0
  119. package/dist/server/src/validation/notification.d.ts +28 -0
  120. package/dist/server/src/validation/notification.d.ts.map +1 -0
  121. package/dist/server/src/validation/notification.js +142 -0
  122. package/dist/server/src/validation/notification.js.map +1 -0
  123. package/dist/server/src/validation/submission-access.d.ts +3 -0
  124. package/dist/server/src/validation/submission-access.d.ts.map +1 -0
  125. package/dist/server/src/validation/submission-access.js +23 -0
  126. package/dist/server/src/validation/submission-access.js.map +1 -0
  127. package/dist/server/src/validation/submission.d.ts +8 -0
  128. package/dist/server/src/validation/submission.d.ts.map +1 -0
  129. package/dist/server/src/validation/submission.js +113 -0
  130. package/dist/server/src/validation/submission.js.map +1 -0
  131. package/package.json +26 -7
  132. package/strapi-admin.js +1 -0
  133. package/dist/server/src/controllers/inquiry.d.ts +0 -8
  134. package/dist/server/src/controllers/inquiry.d.ts.map +0 -1
  135. package/dist/server/src/controllers/inquiry.js +0 -27
  136. package/dist/server/src/controllers/inquiry.js.map +0 -1
  137. package/dist/server/src/services/inquiry.d.ts +0 -15
  138. package/dist/server/src/services/inquiry.d.ts.map +0 -1
  139. package/dist/server/src/services/inquiry.js +0 -137
  140. package/dist/server/src/services/inquiry.js.map +0 -1
@@ -0,0 +1,1006 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res, err) => function __init() {
4
+ if (err) throw err[0];
5
+ try {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ } catch (e) {
8
+ throw err = [e], e;
9
+ }
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+
16
+ // admin/src/form.ts
17
+ function savePayload(form) {
18
+ return {
19
+ slug: form.slug,
20
+ name: form.name,
21
+ targetUid: form.targetUid,
22
+ fields: form.fields.map((field) => ({
23
+ ...field,
24
+ options: field.options?.map((option) => ({ ...option }))
25
+ })),
26
+ consent: { label: form.consent.label, version: form.consent.version }
27
+ };
28
+ }
29
+ function moveField(fields, index, direction) {
30
+ const next = [...fields];
31
+ const current = next[index];
32
+ const other = next[index + direction];
33
+ if (!current || !other) return fields;
34
+ next[index] = other;
35
+ next[index + direction] = current;
36
+ return next;
37
+ }
38
+ function setRequired(field, required, schema) {
39
+ return { ...field, required: schema.required || required };
40
+ }
41
+ function parseImport(source) {
42
+ let value;
43
+ try {
44
+ value = JSON.parse(source);
45
+ } catch {
46
+ throw new Error("Invalid JSON. Choose a form export JSON file.");
47
+ }
48
+ if (!value || typeof value !== "object" || Array.isArray(value))
49
+ throw new Error("Expected a form definition object.");
50
+ return value;
51
+ }
52
+ function errorMessage(error) {
53
+ if (error && typeof error === "object" && "response" in error) {
54
+ const response = error.response;
55
+ if (response?.data?.error?.message) return response.data.error.message;
56
+ }
57
+ return error instanceof Error ? error.message : "Unable to complete the request.";
58
+ }
59
+ var emptyForm;
60
+ var init_form = __esm({
61
+ "admin/src/form.ts"() {
62
+ "use strict";
63
+ emptyForm = {
64
+ slug: "",
65
+ name: "",
66
+ targetUid: "",
67
+ fields: [],
68
+ consent: { label: "", version: "1" }
69
+ };
70
+ }
71
+ });
72
+
73
+ // admin/src/api.ts
74
+ function formApi(client) {
75
+ return {
76
+ async list() {
77
+ return (await client.get(root)).data.data;
78
+ },
79
+ async get(id) {
80
+ return (await client.get(
81
+ `${root}/${encodeURIComponent(id)}`
82
+ )).data.data;
83
+ },
84
+ async targets() {
85
+ return (await client.get(`${root}/targets`)).data.data;
86
+ },
87
+ async fields(targetUid) {
88
+ return (await client.get(`${root}/fields`, {
89
+ params: { targetUid }
90
+ })).data.data;
91
+ },
92
+ async save(form, id) {
93
+ const body = { data: savePayload(form) };
94
+ return (id ? await client.put(
95
+ `${root}/${encodeURIComponent(id)}`,
96
+ body
97
+ ) : await client.post(root, body)).data.data;
98
+ },
99
+ async remove(id) {
100
+ await client.del(`${root}/${encodeURIComponent(id)}`);
101
+ },
102
+ async export(id) {
103
+ return (await client.get(
104
+ `${root}/${encodeURIComponent(id)}/export`
105
+ )).data.data;
106
+ },
107
+ async import(data, id) {
108
+ return (id ? await client.put(
109
+ `${root}/${encodeURIComponent(id)}/import`,
110
+ { data }
111
+ ) : await client.post(`${root}/import`, {
112
+ data
113
+ })).data.data;
114
+ }
115
+ };
116
+ }
117
+ function downloadForm(value, slug) {
118
+ const url = URL.createObjectURL(
119
+ new Blob([JSON.stringify(value, null, 2)], { type: "application/json" })
120
+ );
121
+ const anchor = document.createElement("a");
122
+ anchor.href = url;
123
+ anchor.download = `${slug}.json`;
124
+ anchor.click();
125
+ URL.revokeObjectURL(url);
126
+ }
127
+ var root;
128
+ var init_api = __esm({
129
+ "admin/src/api.ts"() {
130
+ "use strict";
131
+ init_form();
132
+ root = "/plugins/builder/forms";
133
+ }
134
+ });
135
+
136
+ // admin/src/ui.tsx
137
+ import { Box, Flex, Typography } from "@strapi/design-system";
138
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
139
+ function Section({
140
+ title,
141
+ children
142
+ }) {
143
+ return /* @__PURE__ */ jsx2(Box, { background: "neutral0", padding: 6, hasRadius: true, shadow: "tableShadow", children: /* @__PURE__ */ jsxs2(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
144
+ /* @__PURE__ */ jsx2(Typography, { tag: "h2", variant: "delta", children: title }),
145
+ children
146
+ ] }) });
147
+ }
148
+ function TextField({
149
+ label,
150
+ value,
151
+ onChange,
152
+ disabled,
153
+ required,
154
+ multiline
155
+ }) {
156
+ const props = {
157
+ value,
158
+ disabled,
159
+ required,
160
+ onChange: (event) => onChange(event.target.value),
161
+ style: {
162
+ width: "100%",
163
+ padding: "10px",
164
+ border: "1px solid #8e8ea9",
165
+ borderRadius: 4,
166
+ font: "inherit",
167
+ color: "#32324d",
168
+ background: disabled ? "#f6f6f9" : "#fff"
169
+ }
170
+ };
171
+ return /* @__PURE__ */ jsxs2("label", { style: { display: "grid", gap: 6 }, children: [
172
+ /* @__PURE__ */ jsx2(Typography, { children: label }),
173
+ multiline ? /* @__PURE__ */ jsx2("textarea", { ...props, rows: 3 }) : /* @__PURE__ */ jsx2("input", { ...props })
174
+ ] });
175
+ }
176
+ function ErrorNotice({ message }) {
177
+ return message ? /* @__PURE__ */ jsx2(Box, { role: "alert", padding: 4, background: "danger100", children: /* @__PURE__ */ jsx2(Typography, { textColor: "danger700", children: message }) }) : null;
178
+ }
179
+ var init_ui = __esm({
180
+ "admin/src/ui.tsx"() {
181
+ "use strict";
182
+ }
183
+ });
184
+
185
+ // admin/src/import-form.tsx
186
+ import { useRef, useState } from "react";
187
+ import { Button, Flex as Flex2, Typography as Typography2 } from "@strapi/design-system";
188
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
189
+ function ImportForm({
190
+ replace,
191
+ disabled,
192
+ onImport
193
+ }) {
194
+ const [source, setSource] = useState("");
195
+ const [error, setError] = useState("");
196
+ const [busy, setBusy] = useState(false);
197
+ const lock = useRef(false);
198
+ async function submit() {
199
+ if (lock.current) return;
200
+ lock.current = true;
201
+ setBusy(true);
202
+ setError("");
203
+ try {
204
+ await onImport(parseImport(source));
205
+ } catch (cause) {
206
+ setError(errorMessage(cause));
207
+ } finally {
208
+ lock.current = false;
209
+ setBusy(false);
210
+ }
211
+ }
212
+ return /* @__PURE__ */ jsxs3(Flex2, { direction: "column", alignItems: "stretch", gap: 3, children: [
213
+ /* @__PURE__ */ jsxs3(Typography2, { children: [
214
+ replace ? "Replace this saved form with an exported definition. The slug must match. The server increments the form version." : "Import a form export. A new form starts at version 1.",
215
+ " ",
216
+ "Consent version is preserved. Existing slugs cannot be duplicated."
217
+ ] }),
218
+ /* @__PURE__ */ jsxs3("label", { children: [
219
+ "Choose JSON file",
220
+ " ",
221
+ /* @__PURE__ */ jsx3(
222
+ "input",
223
+ {
224
+ type: "file",
225
+ accept: ".json,application/json",
226
+ disabled: busy || disabled,
227
+ onChange: (event) => {
228
+ const file = event.target.files?.[0];
229
+ if (file)
230
+ void file.text().then(setSource).catch((cause) => setError(errorMessage(cause)));
231
+ event.target.value = "";
232
+ }
233
+ }
234
+ )
235
+ ] }),
236
+ /* @__PURE__ */ jsx3(
237
+ TextField,
238
+ {
239
+ label: "Form JSON",
240
+ value: source,
241
+ onChange: setSource,
242
+ multiline: true,
243
+ disabled: busy || disabled
244
+ }
245
+ ),
246
+ /* @__PURE__ */ jsx3(ErrorNotice, { message: error }),
247
+ /* @__PURE__ */ jsx3(
248
+ Button,
249
+ {
250
+ type: "button",
251
+ disabled: busy || disabled || !source.trim(),
252
+ onClick: () => void submit(),
253
+ children: busy ? "Importing\u2026" : replace ? "Replace saved form from JSON" : "Import new form"
254
+ }
255
+ )
256
+ ] });
257
+ }
258
+ var init_import_form = __esm({
259
+ "admin/src/import-form.tsx"() {
260
+ "use strict";
261
+ init_form();
262
+ init_ui();
263
+ }
264
+ });
265
+
266
+ // admin/src/form-list-page.tsx
267
+ import { useCallback, useEffect, useRef as useRef2, useState as useState2 } from "react";
268
+ import {
269
+ Button as Button2,
270
+ Flex as Flex3,
271
+ Table,
272
+ Tbody,
273
+ Td,
274
+ Th,
275
+ Thead,
276
+ Tr,
277
+ Typography as Typography3
278
+ } from "@strapi/design-system";
279
+ import { Layouts, Page, useFetchClient } from "@strapi/strapi/admin";
280
+ import { useNavigate } from "react-router-dom";
281
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
282
+ function FormListPage({ canUpdate }) {
283
+ const client = useFetchClient();
284
+ const { get } = client;
285
+ const navigate = useNavigate();
286
+ const [rows, setRows] = useState2([]);
287
+ const [loading, setLoading] = useState2(true);
288
+ const [error, setError] = useState2("");
289
+ const [busy, setBusy] = useState2(false);
290
+ const [removing, setRemoving] = useState2(null);
291
+ const lock = useRef2(false);
292
+ const load = useCallback(async () => {
293
+ setLoading(true);
294
+ setError("");
295
+ try {
296
+ setRows(
297
+ (await get("/plugins/builder/forms")).data.data
298
+ );
299
+ } catch (cause) {
300
+ setError(errorMessage(cause));
301
+ } finally {
302
+ setLoading(false);
303
+ }
304
+ }, [get]);
305
+ useEffect(() => {
306
+ void load();
307
+ }, [load]);
308
+ async function mutate(action) {
309
+ if (lock.current) return;
310
+ lock.current = true;
311
+ setBusy(true);
312
+ setError("");
313
+ try {
314
+ await action();
315
+ } catch (cause) {
316
+ setError(errorMessage(cause));
317
+ } finally {
318
+ lock.current = false;
319
+ setBusy(false);
320
+ }
321
+ }
322
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
323
+ /* @__PURE__ */ jsx4(
324
+ Layouts.Header,
325
+ {
326
+ title: "Builder Forms",
327
+ subtitle: "Manage public form definitions and consent.",
328
+ primaryAction: canUpdate ? /* @__PURE__ */ jsx4(
329
+ Button2,
330
+ {
331
+ disabled: busy,
332
+ onClick: () => navigate("/plugins/builder/forms/new"),
333
+ children: "Create form"
334
+ }
335
+ ) : void 0
336
+ }
337
+ ),
338
+ /* @__PURE__ */ jsx4(Layouts.Content, { children: /* @__PURE__ */ jsxs4(Flex3, { direction: "column", alignItems: "stretch", gap: 6, children: [
339
+ /* @__PURE__ */ jsx4(ErrorNotice, { message: error }),
340
+ error && /* @__PURE__ */ jsx4(Button2, { onClick: () => void load(), disabled: busy || loading, children: "Retry list" }),
341
+ /* @__PURE__ */ jsx4(Section, { title: "Forms", children: loading ? /* @__PURE__ */ jsx4(Page.Loading, {}) : rows.length === 0 ? /* @__PURE__ */ jsx4(Typography3, { children: "No forms yet." }) : /* @__PURE__ */ jsxs4(Table, { colCount: 5, rowCount: rows.length, children: [
342
+ /* @__PURE__ */ jsx4(Thead, { children: /* @__PURE__ */ jsx4(Tr, { children: ["Name", "Slug", "Target", "Version", "Actions"].map(
343
+ (name) => /* @__PURE__ */ jsx4(Th, { children: /* @__PURE__ */ jsx4(Typography3, { variant: "sigma", children: name }) }, name)
344
+ ) }) }),
345
+ /* @__PURE__ */ jsx4(Tbody, { children: rows.map((row) => /* @__PURE__ */ jsxs4(Tr, { children: [
346
+ /* @__PURE__ */ jsx4(Td, { children: /* @__PURE__ */ jsx4(Typography3, { children: row.name }) }),
347
+ /* @__PURE__ */ jsx4(Td, { children: /* @__PURE__ */ jsx4(Typography3, { children: row.slug }) }),
348
+ /* @__PURE__ */ jsx4(Td, { children: /* @__PURE__ */ jsx4(Typography3, { children: row.targetUid }) }),
349
+ /* @__PURE__ */ jsx4(Td, { children: /* @__PURE__ */ jsx4(Typography3, { children: row.version }) }),
350
+ /* @__PURE__ */ jsx4(Td, { children: /* @__PURE__ */ jsxs4(Flex3, { gap: 2, children: [
351
+ /* @__PURE__ */ jsxs4(
352
+ Button2,
353
+ {
354
+ variant: "secondary",
355
+ disabled: busy,
356
+ onClick: () => navigate(
357
+ `/plugins/builder/forms/${row.documentId}`
358
+ ),
359
+ children: [
360
+ canUpdate ? "Edit" : "View",
361
+ " ",
362
+ row.name
363
+ ]
364
+ }
365
+ ),
366
+ /* @__PURE__ */ jsx4(
367
+ Button2,
368
+ {
369
+ variant: "tertiary",
370
+ disabled: busy,
371
+ onClick: () => void mutate(
372
+ async () => downloadForm(
373
+ await formApi(client).export(row.documentId),
374
+ row.slug
375
+ )
376
+ ),
377
+ children: "Export"
378
+ }
379
+ ),
380
+ canUpdate && /* @__PURE__ */ jsx4(
381
+ Button2,
382
+ {
383
+ variant: "danger-light",
384
+ disabled: busy,
385
+ onClick: () => setRemoving(row),
386
+ children: "Delete"
387
+ }
388
+ )
389
+ ] }) })
390
+ ] }, row.documentId)) })
391
+ ] }) }),
392
+ removing && /* @__PURE__ */ jsxs4(Section, { title: `Delete ${removing.name}?`, children: [
393
+ /* @__PURE__ */ jsx4(Typography3, { children: "This permanently deletes the form definition. Forms with submissions cannot be deleted." }),
394
+ /* @__PURE__ */ jsxs4(Flex3, { gap: 3, children: [
395
+ /* @__PURE__ */ jsx4(
396
+ Button2,
397
+ {
398
+ variant: "danger",
399
+ disabled: busy,
400
+ onClick: () => void mutate(async () => {
401
+ await formApi(client).remove(removing.documentId);
402
+ setRemoving(null);
403
+ await load();
404
+ }),
405
+ children: "Confirm delete"
406
+ }
407
+ ),
408
+ /* @__PURE__ */ jsx4(
409
+ Button2,
410
+ {
411
+ variant: "secondary",
412
+ disabled: busy,
413
+ onClick: () => setRemoving(null),
414
+ children: "Cancel"
415
+ }
416
+ )
417
+ ] })
418
+ ] }),
419
+ canUpdate && /* @__PURE__ */ jsx4(Section, { title: "Import form", children: /* @__PURE__ */ jsx4(
420
+ ImportForm,
421
+ {
422
+ disabled: busy,
423
+ onImport: async (value) => {
424
+ if (lock.current) return;
425
+ lock.current = true;
426
+ setBusy(true);
427
+ try {
428
+ const saved = await formApi(client).import(value);
429
+ navigate(`/plugins/builder/forms/${saved.documentId}`);
430
+ } finally {
431
+ lock.current = false;
432
+ setBusy(false);
433
+ }
434
+ }
435
+ }
436
+ ) })
437
+ ] }) })
438
+ ] });
439
+ }
440
+ var init_form_list_page = __esm({
441
+ "admin/src/form-list-page.tsx"() {
442
+ "use strict";
443
+ init_api();
444
+ init_form();
445
+ init_ui();
446
+ init_import_form();
447
+ }
448
+ });
449
+
450
+ // admin/src/field-editor.tsx
451
+ import { Button as Button3, Flex as Flex4, Typography as Typography4 } from "@strapi/design-system";
452
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
453
+ function FieldEditor({
454
+ fields,
455
+ schema,
456
+ disabled,
457
+ onChange
458
+ }) {
459
+ function update(index, field) {
460
+ onChange(fields.map((item, at) => at === index ? field : item));
461
+ }
462
+ return /* @__PURE__ */ jsxs5(Section, { title: "Fields", children: [
463
+ /* @__PURE__ */ jsx5(Typography4, { children: "Select target fields, then adjust their order and presentation. Required target fields cannot be removed or made optional. Hidden fields remain part of submission validation." }),
464
+ /* @__PURE__ */ jsx5(Flex4, { wrap: "wrap", gap: 4, children: schema.map((item) => /* @__PURE__ */ jsxs5("label", { children: [
465
+ /* @__PURE__ */ jsx5(
466
+ "input",
467
+ {
468
+ type: "checkbox",
469
+ checked: fields.some((field) => field.name === item.name),
470
+ disabled: disabled || item.required,
471
+ onChange: (event) => onChange(
472
+ event.target.checked ? [...fields, { ...item }] : fields.filter((field) => field.name !== item.name)
473
+ )
474
+ }
475
+ ),
476
+ " ",
477
+ item.name,
478
+ item.required ? " (schema required)" : ""
479
+ ] }, item.name)) }),
480
+ fields.map((field, index) => {
481
+ const attribute = schema.find((item) => item.name === field.name);
482
+ return /* @__PURE__ */ jsxs5(
483
+ Section,
484
+ {
485
+ title: `${index + 1}. ${field.name} (${field.type})`,
486
+ children: [
487
+ /* @__PURE__ */ jsxs5(Flex4, { gap: 3, children: [
488
+ /* @__PURE__ */ jsxs5(
489
+ Button3,
490
+ {
491
+ type: "button",
492
+ variant: "secondary",
493
+ disabled: disabled || index === 0,
494
+ onClick: () => onChange(moveField(fields, index, -1)),
495
+ children: [
496
+ "Move ",
497
+ field.name,
498
+ " up"
499
+ ]
500
+ }
501
+ ),
502
+ /* @__PURE__ */ jsxs5(
503
+ Button3,
504
+ {
505
+ type: "button",
506
+ variant: "secondary",
507
+ disabled: disabled || index === fields.length - 1,
508
+ onClick: () => onChange(moveField(fields, index, 1)),
509
+ children: [
510
+ "Move ",
511
+ field.name,
512
+ " down"
513
+ ]
514
+ }
515
+ )
516
+ ] }),
517
+ /* @__PURE__ */ jsx5(
518
+ TextField,
519
+ {
520
+ label: "Label",
521
+ required: true,
522
+ disabled,
523
+ value: field.label,
524
+ onChange: (label) => update(index, { ...field, label })
525
+ }
526
+ ),
527
+ /* @__PURE__ */ jsx5(
528
+ TextField,
529
+ {
530
+ label: "Placeholder",
531
+ disabled,
532
+ value: field.placeholder ?? "",
533
+ onChange: (placeholder) => update(index, { ...field, placeholder })
534
+ }
535
+ ),
536
+ /* @__PURE__ */ jsx5(
537
+ TextField,
538
+ {
539
+ label: "Help text",
540
+ disabled,
541
+ value: field.help ?? "",
542
+ onChange: (help) => update(index, { ...field, help })
543
+ }
544
+ ),
545
+ /* @__PURE__ */ jsxs5("label", { children: [
546
+ /* @__PURE__ */ jsx5(
547
+ "input",
548
+ {
549
+ type: "checkbox",
550
+ checked: field.required,
551
+ disabled: disabled || !attribute || attribute.required,
552
+ onChange: (event) => {
553
+ if (attribute)
554
+ update(
555
+ index,
556
+ setRequired(field, event.target.checked, attribute)
557
+ );
558
+ }
559
+ }
560
+ ),
561
+ " ",
562
+ "Required",
563
+ attribute?.required ? " (required by target schema)" : ""
564
+ ] }),
565
+ /* @__PURE__ */ jsxs5("label", { children: [
566
+ /* @__PURE__ */ jsx5(
567
+ "input",
568
+ {
569
+ type: "checkbox",
570
+ checked: field.visibility === "hidden",
571
+ disabled,
572
+ onChange: (event) => update(index, {
573
+ ...field,
574
+ visibility: event.target.checked ? "hidden" : "form"
575
+ })
576
+ }
577
+ ),
578
+ " ",
579
+ "Hidden from form"
580
+ ] }),
581
+ (field.maxLength !== void 0 || field.min !== void 0 || field.max !== void 0) && /* @__PURE__ */ jsxs5(Typography4, { variant: "pi", children: [
582
+ "Constraints:",
583
+ " ",
584
+ JSON.stringify({
585
+ maxLength: field.maxLength,
586
+ min: field.min,
587
+ max: field.max
588
+ })
589
+ ] }),
590
+ field.options?.map((option, optionIndex) => /* @__PURE__ */ jsx5(
591
+ TextField,
592
+ {
593
+ label: `Option label: ${option.value}`,
594
+ required: true,
595
+ disabled,
596
+ value: option.label,
597
+ onChange: (label) => update(index, {
598
+ ...field,
599
+ options: field.options?.map(
600
+ (item, at) => at === optionIndex ? { ...item, label } : item
601
+ )
602
+ })
603
+ },
604
+ option.value
605
+ ))
606
+ ]
607
+ },
608
+ field.name
609
+ );
610
+ })
611
+ ] });
612
+ }
613
+ var init_field_editor = __esm({
614
+ "admin/src/field-editor.tsx"() {
615
+ "use strict";
616
+ init_form();
617
+ init_ui();
618
+ }
619
+ });
620
+
621
+ // admin/src/form-editor-page.tsx
622
+ import { useEffect as useEffect2, useRef as useRef3, useState as useState3 } from "react";
623
+ import { Button as Button4, Flex as Flex5, Typography as Typography5 } from "@strapi/design-system";
624
+ import { Layouts as Layouts2, Page as Page2, useFetchClient as useFetchClient2 } from "@strapi/strapi/admin";
625
+ import { useNavigate as useNavigate2, useParams } from "react-router-dom";
626
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
627
+ function FormEditorPage({ canUpdate }) {
628
+ const { documentId } = useParams();
629
+ const client = useFetchClient2();
630
+ const { get } = client;
631
+ const navigate = useNavigate2();
632
+ const [draft, setDraft] = useState3(emptyForm);
633
+ const [version, setVersion] = useState3(null);
634
+ const [targets, setTargets] = useState3([]);
635
+ const [schema, setSchema] = useState3([]);
636
+ const [loading, setLoading] = useState3(true);
637
+ const [fieldsLoading, setFieldsLoading] = useState3(false);
638
+ const [error, setError] = useState3("");
639
+ const [loaded, setLoaded] = useState3(false);
640
+ const [busy, setBusy] = useState3(false);
641
+ const [retry, setRetry] = useState3(0);
642
+ const lock = useRef3(false);
643
+ const targetRequest = useRef3(0);
644
+ useEffect2(() => {
645
+ let active = true;
646
+ setLoading(true);
647
+ setLoaded(false);
648
+ setError("");
649
+ async function load() {
650
+ try {
651
+ const api = formApi(client);
652
+ const [available, saved] = await Promise.all([
653
+ api.targets(),
654
+ documentId ? api.get(documentId) : Promise.resolve(null)
655
+ ]);
656
+ const attrs = saved ? await api.fields(saved.targetUid) : [];
657
+ if (!active) return;
658
+ setTargets(available);
659
+ setDraft(
660
+ saved ?? {
661
+ ...emptyForm,
662
+ fields: [],
663
+ consent: { ...emptyForm.consent }
664
+ }
665
+ );
666
+ setVersion(saved?.version ?? null);
667
+ setSchema(attrs);
668
+ setLoaded(true);
669
+ } catch (cause) {
670
+ if (active) setError(errorMessage(cause));
671
+ } finally {
672
+ if (active) setLoading(false);
673
+ }
674
+ }
675
+ void load();
676
+ return () => {
677
+ active = false;
678
+ targetRequest.current += 1;
679
+ };
680
+ }, [documentId, get, retry]);
681
+ async function selectTarget(targetUid) {
682
+ const request = ++targetRequest.current;
683
+ setDraft((current) => ({ ...current, targetUid, fields: [] }));
684
+ setSchema([]);
685
+ setFieldsLoading(true);
686
+ setError("");
687
+ try {
688
+ const attrs = await formApi(client).fields(targetUid);
689
+ if (request !== targetRequest.current) return;
690
+ setSchema(attrs);
691
+ setDraft((current) => ({
692
+ ...current,
693
+ fields: attrs.filter((field) => field.required)
694
+ }));
695
+ } catch (cause) {
696
+ if (request === targetRequest.current) setError(errorMessage(cause));
697
+ } finally {
698
+ if (request === targetRequest.current) setFieldsLoading(false);
699
+ }
700
+ }
701
+ async function run(action) {
702
+ if (lock.current) return;
703
+ lock.current = true;
704
+ setBusy(true);
705
+ setError("");
706
+ try {
707
+ await action();
708
+ } catch (cause) {
709
+ setError(errorMessage(cause));
710
+ } finally {
711
+ lock.current = false;
712
+ setBusy(false);
713
+ }
714
+ }
715
+ if (loading) return /* @__PURE__ */ jsx6(Page2.Loading, {});
716
+ const disabled = !canUpdate || busy || fieldsLoading;
717
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
718
+ /* @__PURE__ */ jsx6(
719
+ Layouts2.Header,
720
+ {
721
+ title: documentId ? draft.name || "Form" : "Create form",
722
+ subtitle: version ? `Form version ${version} \xB7 assigned by server on every save` : "New forms start at version 1.",
723
+ navigationAction: /* @__PURE__ */ jsx6(
724
+ Button4,
725
+ {
726
+ variant: "tertiary",
727
+ onClick: () => navigate("/plugins/builder/forms"),
728
+ children: "Back to forms"
729
+ }
730
+ )
731
+ }
732
+ ),
733
+ /* @__PURE__ */ jsx6(Layouts2.Content, { children: /* @__PURE__ */ jsxs6(Flex5, { direction: "column", alignItems: "stretch", gap: 6, children: [
734
+ /* @__PURE__ */ jsx6(ErrorNotice, { message: error }),
735
+ !loaded ? /* @__PURE__ */ jsx6(Button4, { onClick: () => setRetry((value) => value + 1), children: "Retry loading form" }) : /* @__PURE__ */ jsxs6(Fragment2, { children: [
736
+ /* @__PURE__ */ jsx6(
737
+ "form",
738
+ {
739
+ onSubmit: (event) => {
740
+ event.preventDefault();
741
+ if (disabled) return;
742
+ void run(async () => {
743
+ const saved = await formApi(client).save(draft, documentId);
744
+ setDraft(saved);
745
+ setVersion(saved.version);
746
+ if (!documentId)
747
+ navigate(`/plugins/builder/forms/${saved.documentId}`, {
748
+ replace: true
749
+ });
750
+ });
751
+ },
752
+ children: /* @__PURE__ */ jsxs6(Flex5, { direction: "column", alignItems: "stretch", gap: 6, children: [
753
+ /* @__PURE__ */ jsxs6(Section, { title: "Definition", children: [
754
+ /* @__PURE__ */ jsx6(
755
+ TextField,
756
+ {
757
+ label: "Name",
758
+ required: true,
759
+ disabled,
760
+ value: draft.name,
761
+ onChange: (name) => setDraft({ ...draft, name })
762
+ }
763
+ ),
764
+ /* @__PURE__ */ jsx6(
765
+ TextField,
766
+ {
767
+ label: "Slug",
768
+ required: true,
769
+ disabled: disabled || Boolean(documentId),
770
+ value: draft.slug,
771
+ onChange: (slug) => setDraft({ ...draft, slug })
772
+ }
773
+ ),
774
+ /* @__PURE__ */ jsx6(Typography5, { variant: "pi", children: "Use lowercase letters, numbers and hyphens. The slug cannot change after creation." }),
775
+ /* @__PURE__ */ jsxs6("label", { children: [
776
+ "Target content type",
777
+ " ",
778
+ /* @__PURE__ */ jsxs6(
779
+ "select",
780
+ {
781
+ required: true,
782
+ disabled,
783
+ value: draft.targetUid,
784
+ onChange: (event) => void selectTarget(event.target.value),
785
+ style: { padding: 10, width: "100%" },
786
+ children: [
787
+ /* @__PURE__ */ jsx6("option", { value: "", children: "Select a target" }),
788
+ targets.map((target) => /* @__PURE__ */ jsxs6("option", { value: target.uid, children: [
789
+ target.name,
790
+ " \u2014 ",
791
+ target.uid
792
+ ] }, target.uid))
793
+ ]
794
+ }
795
+ )
796
+ ] }),
797
+ fieldsLoading && /* @__PURE__ */ jsx6(Typography5, { children: "Loading target fields\u2026" })
798
+ ] }),
799
+ /* @__PURE__ */ jsx6(
800
+ FieldEditor,
801
+ {
802
+ fields: draft.fields,
803
+ schema,
804
+ disabled,
805
+ onChange: (fields) => setDraft({ ...draft, fields })
806
+ }
807
+ ),
808
+ /* @__PURE__ */ jsxs6(Section, { title: "Consent", children: [
809
+ /* @__PURE__ */ jsx6(
810
+ TextField,
811
+ {
812
+ label: "Consent label",
813
+ required: true,
814
+ multiline: true,
815
+ disabled,
816
+ value: draft.consent.label,
817
+ onChange: (label) => setDraft({
818
+ ...draft,
819
+ consent: { ...draft.consent, label }
820
+ })
821
+ }
822
+ ),
823
+ /* @__PURE__ */ jsx6(
824
+ TextField,
825
+ {
826
+ label: "Consent version",
827
+ required: true,
828
+ disabled,
829
+ value: draft.consent.version,
830
+ onChange: (consentVersion) => setDraft({
831
+ ...draft,
832
+ consent: {
833
+ ...draft.consent,
834
+ version: consentVersion
835
+ }
836
+ })
837
+ }
838
+ ),
839
+ /* @__PURE__ */ jsx6(Typography5, { variant: "pi", children: "Consent version identifies the terms accepted by the visitor and is separate from the server form version." })
840
+ ] }),
841
+ canUpdate && /* @__PURE__ */ jsx6(
842
+ Button4,
843
+ {
844
+ type: "submit",
845
+ disabled: disabled || !draft.targetUid || !draft.fields.length,
846
+ children: busy ? "Saving\u2026" : "Save form"
847
+ }
848
+ )
849
+ ] })
850
+ }
851
+ ),
852
+ documentId && /* @__PURE__ */ jsxs6(Section, { title: "Portable definition", children: [
853
+ /* @__PURE__ */ jsx6(
854
+ Button4,
855
+ {
856
+ disabled: busy,
857
+ onClick: () => void run(
858
+ async () => downloadForm(
859
+ await formApi(client).export(documentId),
860
+ draft.slug
861
+ )
862
+ ),
863
+ children: "Export saved form JSON"
864
+ }
865
+ ),
866
+ /* @__PURE__ */ jsx6(Typography5, { variant: "pi", children: "Export uses the saved definition. Notification credentials are masked by the server." }),
867
+ canUpdate && /* @__PURE__ */ jsx6(
868
+ ImportForm,
869
+ {
870
+ replace: true,
871
+ disabled: busy || fieldsLoading,
872
+ onImport: async (value) => {
873
+ if (lock.current) return;
874
+ lock.current = true;
875
+ setBusy(true);
876
+ try {
877
+ const saved = await formApi(client).import(
878
+ value,
879
+ documentId
880
+ );
881
+ setDraft(saved);
882
+ setVersion(saved.version);
883
+ setSchema(
884
+ await formApi(client).fields(saved.targetUid)
885
+ );
886
+ } finally {
887
+ lock.current = false;
888
+ setBusy(false);
889
+ }
890
+ }
891
+ }
892
+ )
893
+ ] })
894
+ ] })
895
+ ] }) })
896
+ ] });
897
+ }
898
+ var init_form_editor_page = __esm({
899
+ "admin/src/form-editor-page.tsx"() {
900
+ "use strict";
901
+ init_api();
902
+ init_form();
903
+ init_ui();
904
+ init_field_editor();
905
+ init_import_form();
906
+ }
907
+ });
908
+
909
+ // admin/src/app.tsx
910
+ var app_exports = {};
911
+ __export(app_exports, {
912
+ default: () => App
913
+ });
914
+ import { Navigate, Route, Routes } from "react-router-dom";
915
+ import { Layouts as Layouts3, Page as Page3, useRBAC } from "@strapi/strapi/admin";
916
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
917
+ function App() {
918
+ const { allowedActions, isLoading } = useRBAC(
919
+ ["read", "update"].map((action) => ({
920
+ action: `plugin::builder.forms.${action}`,
921
+ subject: null
922
+ }))
923
+ );
924
+ if (isLoading) return /* @__PURE__ */ jsx7(Page3.Loading, {});
925
+ if (!allowedActions.canRead) return /* @__PURE__ */ jsx7(Page3.NoPermissions, {});
926
+ const canUpdate = allowedActions.canUpdate === true;
927
+ return /* @__PURE__ */ jsx7(Layouts3.Root, { children: /* @__PURE__ */ jsxs7(Routes, { children: [
928
+ /* @__PURE__ */ jsx7(Route, { index: true, element: /* @__PURE__ */ jsx7(Navigate, { to: "forms", replace: true }) }),
929
+ /* @__PURE__ */ jsx7(Route, { path: "forms", element: /* @__PURE__ */ jsx7(FormListPage, { canUpdate }) }),
930
+ /* @__PURE__ */ jsx7(
931
+ Route,
932
+ {
933
+ path: "forms/new",
934
+ element: canUpdate ? /* @__PURE__ */ jsx7(FormEditorPage, { canUpdate: true }) : /* @__PURE__ */ jsx7(Page3.NoPermissions, {})
935
+ }
936
+ ),
937
+ /* @__PURE__ */ jsx7(
938
+ Route,
939
+ {
940
+ path: "forms/:documentId",
941
+ element: /* @__PURE__ */ jsx7(FormEditorPage, { canUpdate })
942
+ }
943
+ ),
944
+ /* @__PURE__ */ jsx7(
945
+ Route,
946
+ {
947
+ path: "*",
948
+ element: /* @__PURE__ */ jsx7(Page3.Error, { content: "Form page not found." })
949
+ }
950
+ )
951
+ ] }) });
952
+ }
953
+ var init_app = __esm({
954
+ "admin/src/app.tsx"() {
955
+ "use strict";
956
+ init_form_list_page();
957
+ init_form_editor_page();
958
+ }
959
+ });
960
+
961
+ // admin/src/plugin-icon.tsx
962
+ import { jsx, jsxs } from "react/jsx-runtime";
963
+ function BuilderIcon() {
964
+ return /* @__PURE__ */ jsxs(
965
+ "svg",
966
+ {
967
+ width: "24",
968
+ height: "24",
969
+ viewBox: "0 0 24 24",
970
+ fill: "none",
971
+ "aria-hidden": "true",
972
+ children: [
973
+ /* @__PURE__ */ jsx(
974
+ "rect",
975
+ {
976
+ x: "5",
977
+ y: "3",
978
+ width: "14",
979
+ height: "18",
980
+ rx: "2",
981
+ stroke: "currentColor",
982
+ strokeWidth: "1.5"
983
+ }
984
+ ),
985
+ /* @__PURE__ */ jsx("path", { d: "M8 8h8M8 12h8M8 16h4", stroke: "currentColor", strokeWidth: "1.5" })
986
+ ]
987
+ }
988
+ );
989
+ }
990
+
991
+ // admin/src/index.ts
992
+ var index_default = {
993
+ register(app) {
994
+ app.addMenuLink({
995
+ to: "plugins/builder",
996
+ icon: BuilderIcon,
997
+ intlLabel: { id: "builder.forms", defaultMessage: "Builder Forms" },
998
+ Component: () => Promise.resolve().then(() => (init_app(), app_exports)),
999
+ permissions: [{ action: "plugin::builder.forms.read", subject: null }]
1000
+ });
1001
+ app.registerPlugin({ id: "builder", name: "Builder" });
1002
+ }
1003
+ };
1004
+ export {
1005
+ index_default as default
1006
+ };