@dani-builder/strapi-plugin-builder 1.5.1 → 2.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 (59) hide show
  1. package/AGENTS.md +4 -0
  2. package/README.md +6 -0
  3. package/dist/admin/api.d.ts +4 -8
  4. package/dist/admin/api.d.ts.map +1 -1
  5. package/dist/admin/app.d.ts +0 -1
  6. package/dist/admin/app.d.ts.map +1 -1
  7. package/dist/admin/field-editor.d.ts.map +1 -1
  8. package/dist/admin/field-modal.d.ts +9 -0
  9. package/dist/admin/field-modal.d.ts.map +1 -0
  10. package/dist/admin/form-editor-page.d.ts.map +1 -1
  11. package/dist/admin/form-list-page.d.ts.map +1 -1
  12. package/dist/admin/form-select.d.ts +21 -0
  13. package/dist/admin/form-select.d.ts.map +1 -0
  14. package/dist/admin/form.d.ts +9 -1
  15. package/dist/admin/form.d.ts.map +1 -1
  16. package/dist/admin/index.d.ts +6 -0
  17. package/dist/admin/index.d.ts.map +1 -1
  18. package/dist/admin/index.mjs +2476 -1119
  19. package/dist/admin/notification-editor.d.ts.map +1 -1
  20. package/dist/admin/notifications.d.ts +1 -1
  21. package/dist/admin/notifications.d.ts.map +1 -1
  22. package/dist/admin/submission-detail-modal.d.ts +14 -0
  23. package/dist/admin/submission-detail-modal.d.ts.map +1 -0
  24. package/dist/admin/submission-page.d.ts +3 -0
  25. package/dist/admin/submission-page.d.ts.map +1 -1
  26. package/dist/admin/submission.d.ts +32 -0
  27. package/dist/admin/submission.d.ts.map +1 -1
  28. package/dist/admin/ui.d.ts.map +1 -1
  29. package/dist/server/src/components/blocks/inquiry-form.json +2 -8
  30. package/dist/server/src/controllers/submission.d.ts +1 -0
  31. package/dist/server/src/controllers/submission.d.ts.map +1 -1
  32. package/dist/server/src/controllers/submission.js +11 -1
  33. package/dist/server/src/controllers/submission.js.map +1 -1
  34. package/dist/server/src/mcp/index.d.ts.map +1 -1
  35. package/dist/server/src/mcp/index.js +3 -1
  36. package/dist/server/src/mcp/index.js.map +1 -1
  37. package/dist/server/src/register.d.ts.map +1 -1
  38. package/dist/server/src/register.js +5 -0
  39. package/dist/server/src/register.js.map +1 -1
  40. package/dist/server/src/routes/admin.d.ts.map +1 -1
  41. package/dist/server/src/routes/admin.js +1 -0
  42. package/dist/server/src/routes/admin.js.map +1 -1
  43. package/dist/server/src/services/form.d.ts.map +1 -1
  44. package/dist/server/src/services/form.js +65 -39
  45. package/dist/server/src/services/form.js.map +1 -1
  46. package/dist/server/src/services/submission-detail.d.ts +24 -0
  47. package/dist/server/src/services/submission-detail.d.ts.map +1 -0
  48. package/dist/server/src/services/submission-detail.js +125 -0
  49. package/dist/server/src/services/submission-detail.js.map +1 -0
  50. package/dist/server/src/services/submission.d.ts +25 -1
  51. package/dist/server/src/services/submission.d.ts.map +1 -1
  52. package/dist/server/src/services/submission.js +53 -3
  53. package/dist/server/src/services/submission.js.map +1 -1
  54. package/dist/server/src/validation/form.d.ts.map +1 -1
  55. package/dist/server/src/validation/form.js +12 -4
  56. package/dist/server/src/validation/form.js.map +1 -1
  57. package/package.json +7 -3
  58. package/dist/admin/import-form.d.ts +0 -6
  59. package/dist/admin/import-form.d.ts.map +0 -1
@@ -13,7 +13,186 @@ var __export = (target, all) => {
13
13
  __defProp(target, name, { get: all[name], enumerable: true });
14
14
  };
15
15
 
16
+ // admin/src/form-select.tsx
17
+ var form_select_exports = {};
18
+ __export(form_select_exports, {
19
+ FormSelect: () => FormSelect
20
+ });
21
+ import { forwardRef, useEffect, useId, useState } from "react";
22
+ import {
23
+ Field,
24
+ SingleSelect,
25
+ SingleSelectOption,
26
+ Typography
27
+ } from "@strapi/design-system";
28
+ import { useFetchClient, useRBAC } from "@strapi/strapi/admin";
29
+ import { Link } from "react-router-dom";
30
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
31
+ var FormSelect;
32
+ var init_form_select = __esm({
33
+ "admin/src/form-select.tsx"() {
34
+ "use strict";
35
+ FormSelect = forwardRef(
36
+ function FormSelect2({
37
+ name = "",
38
+ value,
39
+ onChange,
40
+ label,
41
+ hint,
42
+ error,
43
+ required,
44
+ disabled,
45
+ onBlur
46
+ }, ref) {
47
+ const id = useId();
48
+ const { get } = useFetchClient();
49
+ const { allowedActions, isLoading: checkingPermissions } = useRBAC([
50
+ { action: "plugin::builder.forms.read", subject: null }
51
+ ]);
52
+ const canRead = allowedActions.canRead === true;
53
+ const [forms, setForms] = useState([]);
54
+ const [loading, setLoading] = useState(true);
55
+ const [loadError, setLoadError] = useState("");
56
+ useEffect(() => {
57
+ if (checkingPermissions || !canRead) return;
58
+ let active = true;
59
+ setLoading(true);
60
+ setLoadError("");
61
+ void get("/plugins/builder/forms").then(
62
+ ({ data }) => {
63
+ if (active) {
64
+ setForms(data.data.map(({ name: name2, slug }) => ({ name: name2, slug })));
65
+ setLoading(false);
66
+ }
67
+ },
68
+ () => {
69
+ if (active) {
70
+ setLoadError(
71
+ "\uD3FC \uBAA9\uB85D\uC744 \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uD398\uC774\uC9C0\uB97C \uC0C8\uB85C\uACE0\uCE68\uD574 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694."
72
+ );
73
+ setLoading(false);
74
+ }
75
+ }
76
+ );
77
+ return () => {
78
+ active = false;
79
+ };
80
+ }, [get, canRead, checkingPermissions]);
81
+ const ready = !checkingPermissions && canRead && !loading && !loadError;
82
+ const savedOutsideList = Boolean(
83
+ value && !forms.some((form) => form.slug === value)
84
+ );
85
+ const change = (slug) => {
86
+ if (disabled || !ready) return;
87
+ onChange?.({ target: { name, value: slug, type: "string" } });
88
+ };
89
+ return /* @__PURE__ */ jsxs2(
90
+ Field.Root,
91
+ {
92
+ id,
93
+ name,
94
+ error,
95
+ hint,
96
+ required,
97
+ onBlur,
98
+ style: { minWidth: 0 },
99
+ children: [
100
+ /* @__PURE__ */ jsx2(Field.Label, { children: label || "Form" }),
101
+ /* @__PURE__ */ jsxs2(
102
+ SingleSelect,
103
+ {
104
+ ref,
105
+ name,
106
+ value: value ?? "",
107
+ disabled: disabled || !ready,
108
+ placeholder: checkingPermissions || canRead && loading ? "\uD3FC \uBAA9\uB85D\uC744 \uBD88\uB7EC\uC624\uB294 \uC911\u2026" : "\uD3FC \uC120\uD0DD",
109
+ onChange: (slug) => change(String(slug)),
110
+ onClear: value ? () => change(null) : void 0,
111
+ children: [
112
+ savedOutsideList && /* @__PURE__ */ jsx2(
113
+ SingleSelectOption,
114
+ {
115
+ value,
116
+ style: {
117
+ maxWidth: "calc(100vw - 32px)",
118
+ whiteSpace: "normal",
119
+ overflowWrap: "anywhere"
120
+ },
121
+ children: value
122
+ }
123
+ ),
124
+ forms.map((form) => /* @__PURE__ */ jsxs2(
125
+ SingleSelectOption,
126
+ {
127
+ value: form.slug,
128
+ style: {
129
+ maxWidth: "calc(100vw - 32px)",
130
+ whiteSpace: "normal",
131
+ overflowWrap: "anywhere"
132
+ },
133
+ children: [
134
+ form.name,
135
+ " (",
136
+ form.slug,
137
+ ")"
138
+ ]
139
+ },
140
+ form.slug
141
+ ))
142
+ ]
143
+ }
144
+ ),
145
+ /* @__PURE__ */ jsx2(Field.Hint, {}),
146
+ /* @__PURE__ */ jsx2(Field.Error, {}),
147
+ value && /* @__PURE__ */ jsxs2(
148
+ Typography,
149
+ {
150
+ variant: "pi",
151
+ textColor: "neutral600",
152
+ style: { overflowWrap: "anywhere" },
153
+ children: [
154
+ "Slug: ",
155
+ value
156
+ ]
157
+ }
158
+ ),
159
+ !checkingPermissions && !canRead && /* @__PURE__ */ jsx2(Typography, { role: "alert", children: "\uD3FC \uBAA9\uB85D\uC744 \uBCFC \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. Forms \uC77D\uAE30 \uAD8C\uD55C\uC744 \uC694\uCCAD\uD558\uC138\uC694." }),
160
+ loadError && /* @__PURE__ */ jsx2(Typography, { role: "alert", textColor: "danger700", children: loadError }),
161
+ ready && savedOutsideList && /* @__PURE__ */ jsxs2(Typography, { role: "alert", textColor: "danger700", children: [
162
+ "\uC800\uC7A5\uB41C \uD3FC \u201C",
163
+ value,
164
+ "\u201D\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uB978 \uD3FC\uC744 \uC120\uD0DD\uD558\uC138\uC694."
165
+ ] }),
166
+ ready && forms.length === 0 && /* @__PURE__ */ jsx2(Typography, { children: "\uD3FC \uBE4C\uB354\uC5D0\uC11C \uBA3C\uC800 \uD3FC\uC744 \uB9CC\uB4DC\uC138\uC694" }),
167
+ !checkingPermissions && canRead && /* @__PURE__ */ jsx2(Link, { to: "/plugins/builder/forms", children: "Forms \uAD00\uB9AC" })
168
+ ]
169
+ }
170
+ );
171
+ }
172
+ );
173
+ }
174
+ });
175
+
16
176
  // admin/src/form.ts
177
+ function suggestConsentVersion(current = "", date = /* @__PURE__ */ new Date(), savedVersion = "") {
178
+ const day = [
179
+ date.getFullYear(),
180
+ String(date.getMonth() + 1).padStart(2, "0"),
181
+ String(date.getDate()).padStart(2, "0")
182
+ ].join("-");
183
+ const revisions = [current, savedVersion].map((version) => {
184
+ const match = version.match(new RegExp(`^${day}-v(\\d+)$`));
185
+ return match ? Number(match[1]) : 0;
186
+ });
187
+ return `${day}-v${Math.max(...revisions) + 1}`;
188
+ }
189
+ function createEmptyForm() {
190
+ return {
191
+ ...emptyForm,
192
+ fields: [],
193
+ consent: { label: "", version: suggestConsentVersion() }
194
+ };
195
+ }
17
196
  function savePayload(form, includeNotifications = false) {
18
197
  const notifications = structuredClone(form.notifications ?? {});
19
198
  if (notifications.email) {
@@ -32,35 +211,20 @@ function savePayload(form, includeNotifications = false) {
32
211
  consent: { label: form.consent.label, version: form.consent.version }
33
212
  };
34
213
  }
35
- function moveField(fields, index, direction) {
36
- const next = [...fields];
37
- const current = next[index];
38
- const other = next[index + direction];
39
- if (!current || !other) return fields;
40
- next[index] = other;
41
- next[index + direction] = current;
42
- return next;
43
- }
44
- function setRequired(field, required, schema) {
45
- return { ...field, required: schema.required || required };
46
- }
47
- function parseImport(source) {
48
- let value;
49
- try {
50
- value = JSON.parse(source);
51
- } catch {
52
- throw new Error("Invalid JSON. Choose a form export JSON file.");
53
- }
54
- if (!value || typeof value !== "object" || Array.isArray(value))
55
- throw new Error("Expected a form definition object.");
56
- return value;
57
- }
58
214
  function errorMessage(error) {
59
215
  if (error && typeof error === "object" && "response" in error) {
60
216
  const response = error.response;
61
217
  if (response?.data?.error?.message) return response.data.error.message;
62
218
  }
63
- return error instanceof Error ? error.message : "Unable to complete the request.";
219
+ return "Unable to complete the request.";
220
+ }
221
+ function suggestSlug(name) {
222
+ return name.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 120).replace(/-$/, "");
223
+ }
224
+ function missingRequired(fields, schema) {
225
+ return schema.filter(
226
+ (attribute) => attribute.required && !fields.some((field) => field.name === attribute.name)
227
+ );
64
228
  }
65
229
  var emptyForm;
66
230
  var init_form = __esm({
@@ -126,22 +290,17 @@ function formApi(client) {
126
290
  }
127
291
  };
128
292
  }
129
- function downloadForm(value, slug) {
130
- const url = URL.createObjectURL(
131
- new Blob([JSON.stringify(value, null, 2)], { type: "application/json" })
132
- );
133
- const anchor = document.createElement("a");
134
- anchor.href = url;
135
- anchor.download = `${slug}.json`;
136
- anchor.click();
137
- URL.revokeObjectURL(url);
138
- }
139
293
  function submissionApi(client) {
140
294
  const root2 = "/plugins/builder/submissions";
141
295
  return {
142
296
  async list(params) {
143
297
  return (await client.get(root2, { params })).data;
144
298
  },
299
+ async filterOptions() {
300
+ return (await client.get(
301
+ `${root2}/filter-options`
302
+ )).data.data;
303
+ },
145
304
  async get(id) {
146
305
  return (await client.get(
147
306
  `${root2}/${encodeURIComponent(id)}`
@@ -169,76 +328,32 @@ var init_api = __esm({
169
328
  }
170
329
  });
171
330
 
172
- // admin/src/submission.ts
173
- var submissionStatuses;
174
- var init_submission = __esm({
175
- "admin/src/submission.ts"() {
176
- "use strict";
177
- submissionStatuses = [
178
- "received",
179
- "reviewing",
180
- "responded",
181
- "closed"
182
- ];
183
- }
184
- });
185
-
186
331
  // admin/src/ui.tsx
187
- import { useId } from "react";
188
- import { Box, Flex, Typography } from "@strapi/design-system";
189
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
332
+ import { useIntl } from "react-intl";
333
+ import { useId as useId2 } from "react";
334
+ import { Box, Flex, Typography as Typography2 } from "@strapi/design-system";
335
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
190
336
  function Section({
191
337
  title,
192
338
  children
193
339
  }) {
194
- return /* @__PURE__ */ jsx2(Box, { background: "neutral0", padding: 6, hasRadius: true, shadow: "tableShadow", children: /* @__PURE__ */ jsxs2(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
195
- /* @__PURE__ */ jsx2(Typography, { tag: "h2", variant: "delta", children: title }),
340
+ return /* @__PURE__ */ jsx3(Box, { background: "neutral0", padding: 6, hasRadius: true, shadow: "tableShadow", children: /* @__PURE__ */ jsxs3(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
341
+ /* @__PURE__ */ jsx3(Typography2, { tag: "h2", variant: "delta", children: title }),
196
342
  children
197
343
  ] }) });
198
344
  }
199
- function TextField({
200
- label,
201
- value,
202
- onChange,
203
- disabled,
204
- required,
205
- multiline,
206
- secret,
207
- error
208
- }) {
209
- const errorId = useId();
210
- const props = {
211
- "aria-describedby": error ? errorId : void 0,
212
- value,
213
- disabled,
214
- required,
215
- onChange: (event) => onChange(event.target.value),
216
- style: {
217
- width: "100%",
218
- padding: "10px",
219
- border: "1px solid #8e8ea9",
220
- borderRadius: 4,
221
- font: "inherit",
222
- color: "#32324d",
223
- background: disabled ? "#f6f6f9" : "#fff"
224
- }
225
- };
226
- return /* @__PURE__ */ jsxs2("label", { style: { display: "grid", gap: 6 }, children: [
227
- /* @__PURE__ */ jsx2(Typography, { children: label }),
228
- multiline ? /* @__PURE__ */ jsx2("textarea", { ...props, rows: 3, "aria-invalid": Boolean(error) }) : /* @__PURE__ */ jsx2(
229
- "input",
230
- {
231
- ...props,
232
- type: secret ? "password" : "text",
233
- autoComplete: secret ? "new-password" : void 0,
234
- "aria-invalid": Boolean(error)
235
- }
236
- ),
237
- error && /* @__PURE__ */ jsx2(Typography, { id: errorId, textColor: "danger700", role: "alert", children: error })
238
- ] });
239
- }
240
345
  function ErrorNotice({ message }) {
241
- return message ? /* @__PURE__ */ jsx2(Box, { role: "alert", padding: 4, background: "danger100", children: /* @__PURE__ */ jsx2(Typography, { textColor: "danger700", children: message }) }) : null;
346
+ const { formatMessage } = useIntl();
347
+ return message ? /* @__PURE__ */ jsx3(
348
+ Box,
349
+ {
350
+ role: "alert",
351
+ "aria-label": formatMessage({ id: "builder.common.error" }),
352
+ padding: 4,
353
+ background: "danger100",
354
+ children: /* @__PURE__ */ jsx3(Typography2, { textColor: "danger700", children: message === "Unable to complete the request." ? formatMessage({ id: "builder.common.requestError" }) : message })
355
+ }
356
+ ) : null;
242
357
  }
243
358
  var init_ui = __esm({
244
359
  "admin/src/ui.tsx"() {
@@ -246,10 +361,14 @@ var init_ui = __esm({
246
361
  }
247
362
  });
248
363
 
249
- // admin/src/submission-page.tsx
250
- import { useCallback, useEffect, useRef, useState } from "react";
364
+ // admin/src/form-list-page.tsx
365
+ import { useIntl as useIntl2 } from "react-intl";
366
+ import { useCallback, useEffect as useEffect2, useRef, useState as useState2 } from "react";
251
367
  import {
368
+ Badge,
252
369
  Button,
370
+ EmptyStateLayout,
371
+ IconButton,
253
372
  Flex as Flex2,
254
373
  Table,
255
374
  Tbody,
@@ -257,67 +376,40 @@ import {
257
376
  Th,
258
377
  Thead,
259
378
  Tr,
260
- Typography as Typography2
379
+ Typography as Typography3
261
380
  } from "@strapi/design-system";
262
- import { Layouts, Page, useFetchClient, useRBAC } from "@strapi/strapi/admin";
263
- import { Link } from "react-router-dom";
264
- import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
265
- function TargetLink({ row }) {
266
- const { allowedActions, isLoading } = useRBAC([
267
- {
268
- action: "plugin::content-manager.explorer.read",
269
- subject: row.targetUid
270
- }
271
- ]);
272
- if (isLoading || !allowedActions.canRead || !row.targetDocumentId)
273
- return null;
274
- return /* @__PURE__ */ jsx3(
275
- Link,
276
- {
277
- to: `/content-manager/collection-types/${encodeURIComponent(row.targetUid)}/${encodeURIComponent(row.targetDocumentId)}`,
278
- children: "Open submitted record"
279
- }
280
- );
281
- }
282
- function SubmissionPage({ canUpdate }) {
283
- const client = useFetchClient();
381
+ import { Layouts, Page, useFetchClient as useFetchClient2 } from "@strapi/strapi/admin";
382
+ import { Duplicate } from "@strapi/icons";
383
+ import { Link as Link2, useNavigate } from "react-router-dom";
384
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
385
+ function FormListPage({ canUpdate }) {
386
+ const { formatMessage } = useIntl2();
387
+ const t = useCallback((id) => formatMessage({ id }), [formatMessage]);
388
+ const client = useFetchClient2();
284
389
  const { get } = client;
285
- const [slug, setSlug] = useState("");
286
- const [formFilter, setFormFilter] = useState("");
287
- const [status, setStatus] = useState("");
288
- const [page, setPage] = useState(1);
289
- const [result, setResult] = useState(null);
290
- const [selected, setSelected] = useState(null);
291
- const [loading, setLoading] = useState(true);
292
- const [busy, setBusy] = useState(false);
293
- const [error, setError] = useState("");
390
+ const navigate = useNavigate();
391
+ const [rows, setRows] = useState2([]);
392
+ const [loading, setLoading] = useState2(true);
393
+ const [error, setError] = useState2("");
394
+ const [copyStatus, setCopyStatus] = useState2("");
395
+ const [busy, setBusy] = useState2(false);
396
+ const [removing, setRemoving] = useState2(null);
294
397
  const lock = useRef(false);
295
- const generation = useRef(0);
296
398
  const load = useCallback(async () => {
297
- const current = ++generation.current;
298
399
  setLoading(true);
299
400
  setError("");
300
401
  try {
301
- const response = (await get("/plugins/builder/submissions", {
302
- params: {
303
- page,
304
- ...formFilter ? { slug: formFilter } : {},
305
- ...status ? { status } : {}
306
- }
307
- })).data;
308
- if (current === generation.current) setResult(response);
402
+ setRows(
403
+ (await get("/plugins/builder/forms")).data.data
404
+ );
309
405
  } catch (cause) {
310
- if (current === generation.current) setError(errorMessage(cause));
406
+ setError(errorMessage(cause));
311
407
  } finally {
312
- if (current === generation.current) setLoading(false);
408
+ setLoading(false);
313
409
  }
314
- }, [get, page, formFilter, status]);
315
- useEffect(() => {
316
- setSelected(null);
410
+ }, [get]);
411
+ useEffect2(() => {
317
412
  void load();
318
- return () => {
319
- generation.current++;
320
- };
321
413
  }, [load]);
322
414
  async function mutate(action) {
323
415
  if (lock.current) return;
@@ -333,476 +425,448 @@ function SubmissionPage({ canUpdate }) {
333
425
  setBusy(false);
334
426
  }
335
427
  }
336
- const api = submissionApi(client);
337
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
338
- /* @__PURE__ */ jsx3(
428
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
429
+ /* @__PURE__ */ jsx4(
339
430
  Layouts.Header,
340
431
  {
341
- title: "Builder Submissions",
342
- subtitle: "Review receipt metadata, delivery history and processing status."
343
- }
344
- ),
345
- /* @__PURE__ */ jsx3(Layouts.Content, { children: /* @__PURE__ */ jsxs3(Flex2, { direction: "column", alignItems: "stretch", gap: 6, children: [
346
- /* @__PURE__ */ jsx3(ErrorNotice, { message: error }),
347
- error && /* @__PURE__ */ jsx3(Button, { disabled: loading || busy, onClick: () => void load(), children: "Retry list" }),
348
- /* @__PURE__ */ jsx3(Section, { title: "Filters", children: /* @__PURE__ */ jsxs3(Flex2, { gap: 4, wrap: "wrap", alignItems: "end", children: [
349
- /* @__PURE__ */ jsx3(
350
- TextField,
351
- {
352
- label: "Form slug",
353
- value: slug,
354
- onChange: setSlug,
355
- disabled: busy
356
- }
357
- ),
358
- /* @__PURE__ */ jsx3(
432
+ title: t("builder.forms.title"),
433
+ subtitle: t("builder.forms.description"),
434
+ primaryAction: canUpdate ? /* @__PURE__ */ jsx4(
359
435
  Button,
360
436
  {
361
437
  disabled: busy,
362
- onClick: () => {
363
- setPage(1);
364
- setFormFilter(slug.trim());
365
- },
366
- children: "Apply form filter"
438
+ onClick: () => navigate("/plugins/builder/forms/new"),
439
+ children: t("builder.forms.create")
367
440
  }
368
- ),
369
- /* @__PURE__ */ jsxs3("label", { children: [
370
- "Status",
371
- " ",
372
- /* @__PURE__ */ jsxs3(
373
- "select",
441
+ ) : void 0
442
+ }
443
+ ),
444
+ /* @__PURE__ */ jsx4(Layouts.Content, { children: /* @__PURE__ */ jsxs4(Flex2, { direction: "column", alignItems: "stretch", gap: 6, children: [
445
+ /* @__PURE__ */ jsx4(ErrorNotice, { message: error }),
446
+ /* @__PURE__ */ jsx4("div", { role: "status", "aria-live": "polite", children: copyStatus }),
447
+ error && /* @__PURE__ */ jsx4(Button, { onClick: () => void load(), disabled: busy || loading, children: t("builder.common.retry") }),
448
+ /* @__PURE__ */ jsx4(Section, { title: t("builder.forms.title"), children: loading ? /* @__PURE__ */ jsx4(Page.Loading, {}) : rows.length === 0 ? /* @__PURE__ */ jsx4(
449
+ EmptyStateLayout,
450
+ {
451
+ content: t("builder.forms.empty"),
452
+ action: canUpdate ? /* @__PURE__ */ jsx4(
453
+ Button,
374
454
  {
375
- "aria-label": "Filter status",
376
- value: status,
377
- disabled: busy,
378
- onChange: (event) => {
379
- setPage(1);
380
- setStatus(event.target.value);
455
+ onClick: () => navigate("/plugins/builder/forms/new"),
456
+ children: t("builder.forms.create")
457
+ }
458
+ ) : void 0
459
+ }
460
+ ) : /* @__PURE__ */ jsxs4(Table, { colCount: 8, rowCount: rows.length, children: [
461
+ /* @__PURE__ */ jsx4(Thead, { children: /* @__PURE__ */ jsx4(Tr, { children: [
462
+ "builder.common.name",
463
+ "builder.forms.endpoint",
464
+ "builder.common.target",
465
+ "builder.forms.fieldCount",
466
+ "builder.common.version",
467
+ "builder.forms.latestSubmission",
468
+ "builder.forms.notifications",
469
+ "builder.common.actions"
470
+ ].map((name) => /* @__PURE__ */ jsx4(Th, { children: /* @__PURE__ */ jsx4(Typography3, { variant: "sigma", children: t(name) }) }, name)) }) }),
471
+ /* @__PURE__ */ jsx4(Tbody, { children: rows.map((row) => /* @__PURE__ */ jsxs4(Tr, { children: [
472
+ /* @__PURE__ */ jsxs4(Td, { children: [
473
+ /* @__PURE__ */ jsx4(
474
+ Link2,
475
+ {
476
+ to: `/plugins/builder/forms/${row.documentId}`,
477
+ style: {
478
+ overflowWrap: "anywhere",
479
+ whiteSpace: "normal"
480
+ },
481
+ children: row.name
482
+ }
483
+ ),
484
+ /* @__PURE__ */ jsx4(
485
+ Typography3,
486
+ {
487
+ tag: "div",
488
+ variant: "pi",
489
+ style: {
490
+ overflowWrap: "anywhere",
491
+ whiteSpace: "normal"
492
+ },
493
+ children: row.slug
494
+ }
495
+ )
496
+ ] }),
497
+ /* @__PURE__ */ jsx4(Td, { children: /* @__PURE__ */ jsxs4(Flex2, { gap: 2, children: [
498
+ /* @__PURE__ */ jsx4(
499
+ Typography3,
500
+ {
501
+ style: {
502
+ overflowWrap: "anywhere",
503
+ whiteSpace: "normal"
504
+ },
505
+ children: row.publicUrl
506
+ }
507
+ ),
508
+ /* @__PURE__ */ jsx4(
509
+ IconButton,
510
+ {
511
+ label: formatMessage(
512
+ { id: "builder.forms.copyUrl" },
513
+ { name: row.name }
514
+ ),
515
+ onClick: () => {
516
+ void (async () => {
517
+ try {
518
+ await navigator.clipboard.writeText(
519
+ row.publicUrl ?? ""
520
+ );
521
+ setCopyStatus(t("builder.forms.copied"));
522
+ } catch {
523
+ setError(t("builder.forms.copyFailed"));
524
+ }
525
+ })();
526
+ },
527
+ children: /* @__PURE__ */ jsx4(Duplicate, {})
528
+ }
529
+ )
530
+ ] }) }),
531
+ /* @__PURE__ */ jsx4(Td, { children: /* @__PURE__ */ jsx4(
532
+ Typography3,
533
+ {
534
+ style: {
535
+ overflowWrap: "anywhere",
536
+ whiteSpace: "normal"
381
537
  },
382
- children: [
383
- /* @__PURE__ */ jsx3("option", { value: "", children: "All statuses" }),
384
- submissionStatuses.map((value) => /* @__PURE__ */ jsx3("option", { children: value }, value))
385
- ]
538
+ children: row.targetDisplayName ?? t("builder.forms.targetUnavailable")
386
539
  }
387
- )
388
- ] })
389
- ] }) }),
390
- /* @__PURE__ */ jsxs3(Section, { title: "Submissions", children: [
391
- loading ? /* @__PURE__ */ jsx3(Page.Loading, {}) : !result?.data.length ? /* @__PURE__ */ jsx3(Typography2, { children: "No submissions found." }) : /* @__PURE__ */ jsxs3(Table, { colCount: 5, rowCount: result.data.length, children: [
392
- /* @__PURE__ */ jsx3(Thead, { children: /* @__PURE__ */ jsx3(Tr, { children: [
393
- "Form",
394
- "Received",
395
- "Status",
396
- "Deliveries",
397
- "Actions"
398
- ].map((label) => /* @__PURE__ */ jsx3(Th, { children: /* @__PURE__ */ jsx3(Typography2, { variant: "sigma", children: label }) }, label)) }) }),
399
- /* @__PURE__ */ jsx3(Tbody, { children: result.data.map((row) => /* @__PURE__ */ jsxs3(Tr, { children: [
400
- /* @__PURE__ */ jsx3(Td, { children: /* @__PURE__ */ jsx3(Typography2, { children: row.form }) }),
401
- /* @__PURE__ */ jsx3(Td, { children: /* @__PURE__ */ jsx3(Typography2, { children: row.createdAt }) }),
402
- /* @__PURE__ */ jsx3(Td, { children: /* @__PURE__ */ jsx3(Typography2, { children: row.status }) }),
403
- /* @__PURE__ */ jsx3(Td, { children: /* @__PURE__ */ jsx3(Flex2, { direction: "column", alignItems: "start", gap: 2, children: row.deliveries.length ? row.deliveries.map((delivery, i) => /* @__PURE__ */ jsxs3(Typography2, { children: [
404
- delivery.channel,
405
- ": ",
406
- delivery.status,
407
- " \xB7",
408
- " ",
409
- delivery.attempts,
410
- " attempts \xB7",
411
- " ",
412
- delivery.sentAt ?? "Not sent"
413
- ] }, i)) : /* @__PURE__ */ jsx3(Typography2, { children: "No deliveries recorded" }) }) }),
414
- /* @__PURE__ */ jsx3(Td, { children: /* @__PURE__ */ jsxs3(
540
+ ) }),
541
+ /* @__PURE__ */ jsx4(Td, { children: /* @__PURE__ */ jsx4(Typography3, { children: row.fields.length }) }),
542
+ /* @__PURE__ */ jsx4(Td, { children: /* @__PURE__ */ jsx4(Badge, { children: row.version }) }),
543
+ /* @__PURE__ */ jsx4(Td, { children: /* @__PURE__ */ jsx4(Typography3, { children: row.latestSubmissionAt ? formatMessage(
544
+ { id: "builder.forms.submittedAt" },
545
+ { date: new Date(row.latestSubmissionAt) }
546
+ ) : "\u2014" }) }),
547
+ /* @__PURE__ */ jsx4(Td, { children: /* @__PURE__ */ jsxs4(Flex2, { gap: 2, children: [
548
+ row.notifications?.email && /* @__PURE__ */ jsx4(Badge, { children: t("builder.forms.email") }),
549
+ row.notifications?.webhook && /* @__PURE__ */ jsx4(Badge, { children: t("builder.forms.webhook") }),
550
+ !row.notifications?.email && !row.notifications?.webhook && /* @__PURE__ */ jsx4(Typography3, { children: t("builder.forms.unconfigured") })
551
+ ] }) }),
552
+ /* @__PURE__ */ jsx4(Td, { children: /* @__PURE__ */ jsxs4(Flex2, { gap: 2, children: [
553
+ /* @__PURE__ */ jsxs4(
415
554
  Button,
416
555
  {
556
+ variant: "secondary",
417
557
  disabled: busy,
418
- onClick: () => void mutate(async () => {
419
- setSelected(await api.get(row.documentId));
420
- }),
558
+ onClick: () => navigate(
559
+ `/plugins/builder/forms/${row.documentId}`
560
+ ),
421
561
  children: [
422
- "View ",
423
- row.documentId
562
+ t(
563
+ canUpdate ? "builder.common.edit" : "builder.common.view"
564
+ ),
565
+ " ",
566
+ row.name
424
567
  ]
425
568
  }
426
- ) })
427
- ] }, row.documentId)) })
428
- ] }),
429
- /* @__PURE__ */ jsxs3(Flex2, { gap: 3, children: [
430
- /* @__PURE__ */ jsx3(
431
- Button,
432
- {
433
- disabled: busy || loading || page <= 1,
434
- onClick: () => setPage(page - 1),
435
- children: "Previous"
436
- }
569
+ ),
570
+ canUpdate && /* @__PURE__ */ jsx4(
571
+ Button,
572
+ {
573
+ variant: "danger-light",
574
+ disabled: busy,
575
+ onClick: () => setRemoving(row),
576
+ children: t("builder.common.delete")
577
+ }
578
+ )
579
+ ] }) })
580
+ ] }, row.documentId)) })
581
+ ] }) }),
582
+ removing && /* @__PURE__ */ jsxs4(
583
+ Section,
584
+ {
585
+ title: formatMessage(
586
+ { id: "builder.forms.deleteTitle" },
587
+ { name: removing.name }
437
588
  ),
438
- /* @__PURE__ */ jsxs3(Typography2, { children: [
439
- "Page ",
440
- page,
441
- " \xB7 ",
442
- result?.meta.pagination.total ?? 0,
443
- " submissions"
444
- ] }),
445
- /* @__PURE__ */ jsx3(
446
- Button,
447
- {
448
- disabled: busy || loading || page >= (result?.meta.pagination.pageCount ?? 0),
449
- onClick: () => setPage(page + 1),
450
- children: "Next"
451
- }
452
- )
453
- ] })
454
- ] }),
455
- selected && /* @__PURE__ */ jsxs3(Section, { title: `Submission ${selected.documentId}`, children: [
456
- /* @__PURE__ */ jsxs3(Typography2, { children: [
457
- selected.form,
458
- " \xB7 Received ",
459
- selected.createdAt,
460
- " \xB7 Updated",
461
- " ",
462
- selected.updatedAt
463
- ] }),
464
- /* @__PURE__ */ jsxs3(Typography2, { children: [
465
- "Consent ",
466
- selected.consentVersion,
467
- " \xB7 ",
468
- selected.consentAt
469
- ] }),
470
- /* @__PURE__ */ jsx3(TargetLink, { row: selected }),
471
- /* @__PURE__ */ jsxs3("label", { children: [
472
- "Processing status",
473
- " ",
474
- /* @__PURE__ */ jsx3(
475
- "select",
476
- {
477
- "aria-label": "Processing status",
478
- value: selected.status,
479
- disabled: !canUpdate || busy,
480
- onChange: (event) => {
481
- const value = submissionStatuses.find(
482
- (value2) => value2 === event.target.value
483
- );
484
- if (value)
485
- void mutate(async () => {
486
- const updated = await api.status(
487
- selected.documentId,
488
- value
489
- );
589
+ children: [
590
+ /* @__PURE__ */ jsx4(Typography3, { children: t("builder.forms.deleteWarning") }),
591
+ /* @__PURE__ */ jsxs4(Flex2, { gap: 3, children: [
592
+ /* @__PURE__ */ jsx4(
593
+ Button,
594
+ {
595
+ variant: "danger",
596
+ disabled: busy,
597
+ onClick: () => void mutate(async () => {
598
+ await formApi(client).remove(removing.documentId);
599
+ setRemoving(null);
490
600
  await load();
491
- setSelected(updated);
492
- });
493
- },
494
- children: submissionStatuses.map((value) => /* @__PURE__ */ jsx3("option", { children: value }, value))
495
- }
496
- )
497
- ] }),
498
- selected.deliveries.map((delivery, i) => /* @__PURE__ */ jsxs3(Typography2, { children: [
499
- delivery.channel,
500
- ": ",
501
- delivery.status,
502
- " \xB7 ",
503
- delivery.attempts,
504
- " ",
505
- "attempts \xB7 Sent: ",
506
- delivery.sentAt ?? "Not sent",
507
- delivery.lastError ? ` \xB7 ${delivery.lastError}` : ""
508
- ] }, i)),
509
- /* @__PURE__ */ jsx3(
510
- Button,
511
- {
512
- disabled: !canUpdate || busy || !selected.deliveries.some(
513
- (delivery) => delivery.channel !== "afterSave" && delivery.status === "failed"
514
- ),
515
- onClick: () => void mutate(async () => {
516
- await api.redeliver(selected.documentId);
517
- const updated = await api.get(selected.documentId);
518
- await load();
519
- setSelected(updated);
520
- }),
521
- children: "Retry notifications"
522
- }
523
- ),
524
- !canUpdate && /* @__PURE__ */ jsx3(Typography2, { children: "Read only. Submission update permission is required to change status or retry notifications." })
525
- ] })
601
+ }),
602
+ children: t("builder.common.confirmDelete")
603
+ }
604
+ ),
605
+ /* @__PURE__ */ jsx4(
606
+ Button,
607
+ {
608
+ variant: "secondary",
609
+ disabled: busy,
610
+ onClick: () => setRemoving(null),
611
+ children: t("builder.common.cancel")
612
+ }
613
+ )
614
+ ] })
615
+ ]
616
+ }
617
+ )
526
618
  ] }) })
527
619
  ] });
528
620
  }
529
- var init_submission_page = __esm({
530
- "admin/src/submission-page.tsx"() {
621
+ var init_form_list_page = __esm({
622
+ "admin/src/form-list-page.tsx"() {
531
623
  "use strict";
532
624
  init_api();
533
625
  init_form();
534
- init_submission();
535
626
  init_ui();
536
627
  }
537
628
  });
538
629
 
539
- // admin/src/import-form.tsx
540
- import { useRef as useRef2, useState as useState2 } from "react";
541
- import { Button as Button2, Flex as Flex3, Typography as Typography3 } from "@strapi/design-system";
542
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
543
- function ImportForm({
544
- replace,
545
- disabled,
546
- onImport
630
+ // admin/src/field-modal.tsx
631
+ import { useState as useState3 } from "react";
632
+ import { useIntl as useIntl3 } from "react-intl";
633
+ import {
634
+ Badge as Badge2,
635
+ Box as Box2,
636
+ Button as Button2,
637
+ Field as Field2,
638
+ Flex as Flex3,
639
+ Modal,
640
+ NumberInput,
641
+ SingleSelect as SingleSelect2,
642
+ SingleSelectOption as SingleSelectOption2,
643
+ TextInput,
644
+ Toggle,
645
+ Typography as Typography4
646
+ } from "@strapi/design-system";
647
+ import { Calendar, Check, Code, Hashtag, Mail, Paragraph } from "@strapi/icons";
648
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
649
+ function FieldModal({
650
+ fields,
651
+ schema,
652
+ initial,
653
+ onApply,
654
+ onClose
547
655
  }) {
548
- const [source, setSource] = useState2("");
549
- const [error, setError] = useState2("");
550
- const [busy, setBusy] = useState2(false);
551
- const lock = useRef2(false);
552
- async function submit() {
553
- if (lock.current) return;
554
- lock.current = true;
555
- setBusy(true);
556
- setError("");
557
- try {
558
- await onImport(parseImport(source));
559
- } catch (cause) {
560
- setError(errorMessage(cause));
561
- } finally {
562
- lock.current = false;
563
- setBusy(false);
564
- }
656
+ const { formatMessage } = useIntl3();
657
+ const t = (key) => formatMessage({ id: `builder.forms.fields.${key}` });
658
+ const [draft, setDraft] = useState3(
659
+ initial ? structuredClone(initial) : void 0
660
+ );
661
+ const attribute = schema.find((field) => field.name === draft?.name);
662
+ const invalid = !draft?.label.trim() || Boolean(
663
+ draft && ["maxLength", "min", "max"].some((key) => {
664
+ const constraint = key;
665
+ const value = draft[constraint];
666
+ const limit = attribute?.[constraint];
667
+ return value !== void 0 && (!Number.isFinite(value) || constraint === "maxLength" && (!Number.isInteger(value) || value < 1) || draft.type === "integer" && !Number.isInteger(value) || limit !== void 0 && (constraint === "min" ? value < limit : value > limit));
668
+ })
669
+ ) || Boolean(
670
+ draft?.min !== void 0 && draft.max !== void 0 && draft.min > draft.max
671
+ ) || Boolean(draft?.options?.some((option) => !option.label.trim()));
672
+ function update(value) {
673
+ if (draft) setDraft({ ...draft, ...value });
565
674
  }
566
- return /* @__PURE__ */ jsxs4(Flex3, { direction: "column", alignItems: "stretch", gap: 3, children: [
567
- /* @__PURE__ */ jsxs4(Typography3, { children: [
568
- 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.",
569
- " ",
570
- "Consent version is preserved. Existing slugs cannot be duplicated."
571
- ] }),
572
- /* @__PURE__ */ jsxs4("label", { children: [
573
- "Choose JSON file",
574
- " ",
575
- /* @__PURE__ */ jsx4(
576
- "input",
675
+ function input(key, required = false) {
676
+ return /* @__PURE__ */ jsxs5(Field2.Root, { name: key, required, children: [
677
+ /* @__PURE__ */ jsx5(Field2.Label, { children: t(key) }),
678
+ /* @__PURE__ */ jsx5(
679
+ TextInput,
577
680
  {
578
- type: "file",
579
- accept: ".json,application/json",
580
- disabled: busy || disabled,
581
- onChange: (event) => {
582
- const file = event.target.files?.[0];
583
- if (file)
584
- void file.text().then(setSource).catch((cause) => setError(errorMessage(cause)));
585
- event.target.value = "";
586
- }
681
+ value: draft?.[key] ?? "",
682
+ onChange: (event) => update({ [key]: event.target.value })
587
683
  }
588
684
  )
589
- ] }),
590
- /* @__PURE__ */ jsx4(
591
- TextField,
592
- {
593
- label: "Form JSON",
594
- value: source,
595
- onChange: setSource,
596
- multiline: true,
597
- disabled: busy || disabled
598
- }
599
- ),
600
- /* @__PURE__ */ jsx4(ErrorNotice, { message: error }),
601
- /* @__PURE__ */ jsx4(
602
- Button2,
603
- {
604
- type: "button",
605
- disabled: busy || disabled || !source.trim(),
606
- onClick: () => void submit(),
607
- children: busy ? "Importing\u2026" : replace ? "Replace saved form from JSON" : "Import new form"
608
- }
609
- )
610
- ] });
611
- }
612
- var init_import_form = __esm({
613
- "admin/src/import-form.tsx"() {
614
- "use strict";
615
- init_form();
616
- init_ui();
617
- }
618
- });
619
-
620
- // admin/src/form-list-page.tsx
621
- import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef3, useState as useState3 } from "react";
622
- import {
623
- Button as Button3,
624
- Flex as Flex4,
625
- Table as Table2,
626
- Tbody as Tbody2,
627
- Td as Td2,
628
- Th as Th2,
629
- Thead as Thead2,
630
- Tr as Tr2,
631
- Typography as Typography4
632
- } from "@strapi/design-system";
633
- import { Layouts as Layouts2, Page as Page2, useFetchClient as useFetchClient2 } from "@strapi/strapi/admin";
634
- import { useNavigate } from "react-router-dom";
635
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
636
- function FormListPage({ canUpdate }) {
637
- const client = useFetchClient2();
638
- const { get } = client;
639
- const navigate = useNavigate();
640
- const [rows, setRows] = useState3([]);
641
- const [loading, setLoading] = useState3(true);
642
- const [error, setError] = useState3("");
643
- const [busy, setBusy] = useState3(false);
644
- const [removing, setRemoving] = useState3(null);
645
- const lock = useRef3(false);
646
- const load = useCallback2(async () => {
647
- setLoading(true);
648
- setError("");
649
- try {
650
- setRows(
651
- (await get("/plugins/builder/forms")).data.data
652
- );
653
- } catch (cause) {
654
- setError(errorMessage(cause));
655
- } finally {
656
- setLoading(false);
657
- }
658
- }, [get]);
659
- useEffect2(() => {
660
- void load();
661
- }, [load]);
662
- async function mutate(action) {
663
- if (lock.current) return;
664
- lock.current = true;
665
- setBusy(true);
666
- setError("");
667
- try {
668
- await action();
669
- } catch (cause) {
670
- setError(errorMessage(cause));
671
- } finally {
672
- lock.current = false;
673
- setBusy(false);
674
- }
685
+ ] });
675
686
  }
676
- return /* @__PURE__ */ jsxs5(Fragment2, { children: [
677
- /* @__PURE__ */ jsx5(
678
- Layouts2.Header,
679
- {
680
- title: "Builder Forms",
681
- subtitle: "Manage public form definitions and consent.",
682
- primaryAction: canUpdate ? /* @__PURE__ */ jsx5(
683
- Button3,
687
+ return /* @__PURE__ */ jsx5(
688
+ Modal.Root,
689
+ {
690
+ open: true,
691
+ onOpenChange: (open) => {
692
+ if (!open) onClose();
693
+ },
694
+ children: /* @__PURE__ */ jsxs5(Modal.Content, { children: [
695
+ /* @__PURE__ */ jsx5(
696
+ Modal.Header,
684
697
  {
685
- disabled: busy,
686
- onClick: () => navigate("/plugins/builder/forms/new"),
687
- children: "Create form"
698
+ closeLabel: formatMessage({ id: "builder.common.cancel" }),
699
+ children: /* @__PURE__ */ jsx5(Modal.Title, { children: t(draft ? "settings" : "select") })
688
700
  }
689
- ) : void 0
690
- }
691
- ),
692
- /* @__PURE__ */ jsx5(Layouts2.Content, { children: /* @__PURE__ */ jsxs5(Flex4, { direction: "column", alignItems: "stretch", gap: 6, children: [
693
- /* @__PURE__ */ jsx5(ErrorNotice, { message: error }),
694
- error && /* @__PURE__ */ jsx5(Button3, { onClick: () => void load(), disabled: busy || loading, children: "Retry list" }),
695
- /* @__PURE__ */ jsx5(Section, { title: "Forms", children: loading ? /* @__PURE__ */ jsx5(Page2.Loading, {}) : rows.length === 0 ? /* @__PURE__ */ jsx5(Typography4, { children: "No forms yet." }) : /* @__PURE__ */ jsxs5(Table2, { colCount: 5, rowCount: rows.length, children: [
696
- /* @__PURE__ */ jsx5(Thead2, { children: /* @__PURE__ */ jsx5(Tr2, { children: ["Name", "Slug", "Target", "Version", "Actions"].map(
697
- (name) => /* @__PURE__ */ jsx5(Th2, { children: /* @__PURE__ */ jsx5(Typography4, { variant: "sigma", children: name }) }, name)
698
- ) }) }),
699
- /* @__PURE__ */ jsx5(Tbody2, { children: rows.map((row) => /* @__PURE__ */ jsxs5(Tr2, { children: [
700
- /* @__PURE__ */ jsx5(Td2, { children: /* @__PURE__ */ jsx5(Typography4, { children: row.name }) }),
701
- /* @__PURE__ */ jsx5(Td2, { children: /* @__PURE__ */ jsx5(Typography4, { children: row.slug }) }),
702
- /* @__PURE__ */ jsx5(Td2, { children: /* @__PURE__ */ jsx5(Typography4, { children: row.targetUid }) }),
703
- /* @__PURE__ */ jsx5(Td2, { children: /* @__PURE__ */ jsx5(Typography4, { children: row.version }) }),
704
- /* @__PURE__ */ jsx5(Td2, { children: /* @__PURE__ */ jsxs5(Flex4, { gap: 2, children: [
701
+ ),
702
+ /* @__PURE__ */ jsx5(Modal.Body, { children: /* @__PURE__ */ jsx5(Flex3, { direction: "column", alignItems: "stretch", gap: 4, children: !draft ? schema.map((field) => {
703
+ const selected = fields.some(
704
+ (item) => item.name === field.name
705
+ );
706
+ const Icon = icons[field.type] ?? Paragraph;
707
+ return /* @__PURE__ */ jsx5(
708
+ Box2,
709
+ {
710
+ padding: 4,
711
+ background: selected ? "neutral100" : "neutral0",
712
+ borderColor: "neutral200",
713
+ hasRadius: true,
714
+ style: { opacity: selected ? 0.6 : 1 },
715
+ children: /* @__PURE__ */ jsxs5(Flex3, { gap: 3, wrap: "wrap", children: [
716
+ /* @__PURE__ */ jsx5(Icon, { "aria-hidden": true }),
717
+ /* @__PURE__ */ jsx5(Typography4, { fontWeight: "bold", children: field.name }),
718
+ /* @__PURE__ */ jsx5(Typography4, { children: t(`types.${field.type}`) }),
719
+ field.required && /* @__PURE__ */ jsx5(Badge2, { children: t(selected ? "required" : "missing") }),
720
+ ["maxLength", "min", "max"].map(
721
+ (key) => field[key] !== void 0 && /* @__PURE__ */ jsxs5(Badge2, { children: [
722
+ t(key),
723
+ ": ",
724
+ field[key]
725
+ ] }, key)
726
+ ),
727
+ field.options && /* @__PURE__ */ jsx5(Typography4, { children: field.options.map((option) => option.value).join(", ") }),
728
+ /* @__PURE__ */ jsx5(
729
+ Button2,
730
+ {
731
+ type: "button",
732
+ disabled: selected,
733
+ variant: "secondary",
734
+ onClick: () => setDraft({ ...structuredClone(field), label: "" }),
735
+ children: t(selected ? "selected" : "choose")
736
+ }
737
+ )
738
+ ] })
739
+ },
740
+ field.name
741
+ );
742
+ }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
743
+ /* @__PURE__ */ jsxs5(Typography4, { children: [
744
+ draft.name,
745
+ " \xB7 ",
746
+ t(`types.${draft.type}`)
747
+ ] }),
748
+ input("label", true),
749
+ input("placeholder"),
750
+ input("help"),
751
+ /* @__PURE__ */ jsxs5(Field2.Root, { children: [
752
+ /* @__PURE__ */ jsx5(Field2.Label, { children: t("required") }),
753
+ /* @__PURE__ */ jsx5(
754
+ Toggle,
755
+ {
756
+ "aria-label": t("required"),
757
+ checked: draft.required,
758
+ disabled: attribute?.required,
759
+ onLabel: t("required"),
760
+ offLabel: t("optional"),
761
+ onChange: (event) => update({
762
+ required: Boolean(attribute?.required) || event.target.checked
763
+ })
764
+ }
765
+ ),
766
+ attribute?.required && /* @__PURE__ */ jsx5(Typography4, { variant: "pi", children: t("schemaRequired") })
767
+ ] }),
768
+ /* @__PURE__ */ jsxs5(Field2.Root, { children: [
769
+ /* @__PURE__ */ jsx5(Field2.Label, { children: t("visibility") }),
705
770
  /* @__PURE__ */ jsxs5(
706
- Button3,
771
+ SingleSelect2,
707
772
  {
708
- variant: "secondary",
709
- disabled: busy,
710
- onClick: () => navigate(
711
- `/plugins/builder/forms/${row.documentId}`
712
- ),
773
+ value: draft.visibility,
774
+ onChange: (value) => update({ visibility: value }),
713
775
  children: [
714
- canUpdate ? "Edit" : "View",
715
- " ",
716
- row.name
776
+ /* @__PURE__ */ jsx5(SingleSelectOption2, { value: "form", children: t("visible") }),
777
+ /* @__PURE__ */ jsx5(SingleSelectOption2, { value: "hidden", children: t("hidden") })
717
778
  ]
718
779
  }
719
780
  ),
781
+ /* @__PURE__ */ jsx5(Typography4, { variant: "pi", children: t("hiddenHint") })
782
+ ] }),
783
+ (["string", "text", "email"].includes(draft.type) ? ["maxLength"] : ["integer", "decimal"].includes(draft.type) ? ["min", "max"] : []).map((key) => /* @__PURE__ */ jsxs5(Field2.Root, { children: [
784
+ /* @__PURE__ */ jsx5(Field2.Label, { children: t(key) }),
720
785
  /* @__PURE__ */ jsx5(
721
- Button3,
786
+ NumberInput,
722
787
  {
723
- variant: "tertiary",
724
- disabled: busy,
725
- onClick: () => void mutate(
726
- async () => downloadForm(
727
- await formApi(client).export(row.documentId),
728
- row.slug
729
- )
730
- ),
731
- children: "Export"
788
+ "aria-label": t(key),
789
+ value: draft[key],
790
+ min: key === "maxLength" ? 1 : key === "min" ? attribute?.min : draft.min,
791
+ max: key === "maxLength" ? attribute?.maxLength : key === "max" ? attribute?.max : draft.max,
792
+ step: draft.type === "decimal" ? 0.01 : 1,
793
+ onValueChange: (value) => update({ [key]: value })
732
794
  }
733
795
  ),
734
- canUpdate && /* @__PURE__ */ jsx5(
735
- Button3,
796
+ attribute?.[key] !== void 0 && /* @__PURE__ */ jsxs5(Typography4, { variant: "pi", children: [
797
+ t("schemaLimit"),
798
+ ": ",
799
+ attribute[key]
800
+ ] })
801
+ ] }, key)),
802
+ draft.options?.map((option, index) => /* @__PURE__ */ jsxs5(Field2.Root, { required: true, children: [
803
+ /* @__PURE__ */ jsxs5(Field2.Label, { children: [
804
+ t("optionLabel"),
805
+ ": ",
806
+ option.value
807
+ ] }),
808
+ /* @__PURE__ */ jsx5(
809
+ TextInput,
736
810
  {
737
- variant: "danger-light",
738
- disabled: busy,
739
- onClick: () => setRemoving(row),
740
- children: "Delete"
811
+ value: option.label,
812
+ onChange: (event) => update({
813
+ options: draft.options?.map(
814
+ (item, at) => at === index ? { ...item, label: event.target.value } : item
815
+ )
816
+ })
741
817
  }
742
818
  )
743
- ] }) })
744
- ] }, row.documentId)) })
745
- ] }) }),
746
- removing && /* @__PURE__ */ jsxs5(Section, { title: `Delete ${removing.name}?`, children: [
747
- /* @__PURE__ */ jsx5(Typography4, { children: "This permanently deletes the form definition. Forms with submissions cannot be deleted." }),
748
- /* @__PURE__ */ jsxs5(Flex4, { gap: 3, children: [
749
- /* @__PURE__ */ jsx5(
750
- Button3,
751
- {
752
- variant: "danger",
753
- disabled: busy,
754
- onClick: () => void mutate(async () => {
755
- await formApi(client).remove(removing.documentId);
756
- setRemoving(null);
757
- await load();
758
- }),
759
- children: "Confirm delete"
760
- }
761
- ),
762
- /* @__PURE__ */ jsx5(
763
- Button3,
764
- {
765
- variant: "secondary",
766
- disabled: busy,
767
- onClick: () => setRemoving(null),
768
- children: "Cancel"
769
- }
770
- )
819
+ ] }, option.value)),
820
+ invalid && /* @__PURE__ */ jsx5(Typography4, { textColor: "danger600", children: t("invalid") })
821
+ ] }) }) }),
822
+ /* @__PURE__ */ jsxs5(Modal.Footer, { children: [
823
+ /* @__PURE__ */ jsx5(Button2, { variant: "tertiary", onClick: onClose, children: formatMessage({ id: "builder.common.cancel" }) }),
824
+ /* @__PURE__ */ jsxs5(Flex3, { gap: 2, children: [
825
+ draft && !initial && /* @__PURE__ */ jsx5(Button2, { variant: "secondary", onClick: () => setDraft(void 0), children: t("back") }),
826
+ draft && /* @__PURE__ */ jsx5(Button2, { disabled: invalid, onClick: () => onApply(draft), children: t("apply") })
827
+ ] })
771
828
  ] })
772
- ] }),
773
- canUpdate && /* @__PURE__ */ jsx5(Section, { title: "Import form", children: /* @__PURE__ */ jsx5(
774
- ImportForm,
775
- {
776
- disabled: busy,
777
- onImport: async (value) => {
778
- if (lock.current) return;
779
- lock.current = true;
780
- setBusy(true);
781
- try {
782
- const saved = await formApi(client).import(value);
783
- navigate(`/plugins/builder/forms/${saved.documentId}`);
784
- } finally {
785
- lock.current = false;
786
- setBusy(false);
787
- }
788
- }
789
- }
790
- ) })
791
- ] }) })
792
- ] });
829
+ ] })
830
+ }
831
+ );
793
832
  }
794
- var init_form_list_page = __esm({
795
- "admin/src/form-list-page.tsx"() {
833
+ var icons;
834
+ var init_field_modal = __esm({
835
+ "admin/src/field-modal.tsx"() {
796
836
  "use strict";
797
- init_api();
798
- init_form();
799
- init_ui();
800
- init_import_form();
837
+ icons = {
838
+ string: Paragraph,
839
+ text: Paragraph,
840
+ email: Mail,
841
+ integer: Hashtag,
842
+ decimal: Hashtag,
843
+ boolean: Check,
844
+ enumeration: Code,
845
+ date: Calendar,
846
+ datetime: Calendar
847
+ };
801
848
  }
802
849
  });
803
850
 
804
851
  // admin/src/field-editor.tsx
805
- import { Button as Button4, Flex as Flex5, Typography as Typography5 } from "@strapi/design-system";
852
+ import { useLayoutEffect, useRef as useRef2, useState as useState4 } from "react";
853
+ import { useIntl as useIntl4 } from "react-intl";
854
+ import {
855
+ Alert,
856
+ Badge as Badge3,
857
+ Button as Button3,
858
+ Flex as Flex4,
859
+ IconButton as IconButton2,
860
+ Table as Table2,
861
+ Tbody as Tbody2,
862
+ Td as Td2,
863
+ Th as Th2,
864
+ Thead as Thead2,
865
+ Tooltip,
866
+ Tr as Tr2,
867
+ Typography as Typography5
868
+ } from "@strapi/design-system";
869
+ import { ArrowDown, ArrowUp, Pencil, Trash } from "@strapi/icons";
806
870
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
807
871
  function FieldEditor({
808
872
  fields,
@@ -810,165 +874,219 @@ function FieldEditor({
810
874
  disabled,
811
875
  onChange
812
876
  }) {
813
- function update(index, field) {
814
- onChange(fields.map((item, at) => at === index ? field : item));
877
+ const { formatMessage } = useIntl4();
878
+ const t = (key) => formatMessage({ id: `builder.forms.fields.${key}` });
879
+ const [modal, setModal] = useState4();
880
+ const [announcement, setAnnouncement] = useState4("");
881
+ const drag = useRef2(null);
882
+ const controls = useRef2(/* @__PURE__ */ new Map());
883
+ const returnFocus = useRef2(null);
884
+ const pendingFocus = useRef2(void 0);
885
+ useLayoutEffect(() => {
886
+ const key = pendingFocus.current;
887
+ if (key) {
888
+ const node = controls.current.get(key);
889
+ const alternative = controls.current.get(
890
+ key.endsWith(":up") ? key.slice(0, -2) + "down" : key.slice(0, -4) + "up"
891
+ );
892
+ (node?.disabled ? alternative : node)?.focus();
893
+ pendingFocus.current = void 0;
894
+ }
895
+ if (modal === void 0) {
896
+ returnFocus.current?.focus();
897
+ returnFocus.current = null;
898
+ }
899
+ }, [fields, modal]);
900
+ function openModal(value, trigger) {
901
+ returnFocus.current = trigger;
902
+ setModal(value);
815
903
  }
816
- return /* @__PURE__ */ jsxs6(Section, { title: "Fields", children: [
817
- /* @__PURE__ */ jsx6(Typography5, { 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." }),
818
- /* @__PURE__ */ jsx6(Flex5, { wrap: "wrap", gap: 4, children: schema.map((item) => /* @__PURE__ */ jsxs6("label", { children: [
819
- /* @__PURE__ */ jsx6(
820
- "input",
821
- {
822
- type: "checkbox",
823
- checked: fields.some((field) => field.name === item.name),
824
- disabled: disabled || item.required,
825
- onChange: (event) => onChange(
826
- event.target.checked ? [...fields, { ...item }] : fields.filter((field) => field.name !== item.name)
827
- )
828
- }
829
- ),
830
- " ",
831
- item.name,
832
- item.required ? " (schema required)" : ""
833
- ] }, item.name)) }),
834
- fields.map((field, index) => {
835
- const attribute = schema.find((item) => item.name === field.name);
836
- return /* @__PURE__ */ jsxs6(
837
- Section,
838
- {
839
- title: `${index + 1}. ${field.name} (${field.type})`,
840
- children: [
841
- /* @__PURE__ */ jsxs6(Flex5, { gap: 3, children: [
842
- /* @__PURE__ */ jsxs6(
843
- Button4,
844
- {
845
- type: "button",
846
- variant: "secondary",
847
- disabled: disabled || index === 0,
848
- onClick: () => onChange(moveField(fields, index, -1)),
849
- children: [
850
- "Move ",
851
- field.name,
852
- " up"
853
- ]
854
- }
855
- ),
856
- /* @__PURE__ */ jsxs6(
857
- Button4,
858
- {
859
- type: "button",
860
- variant: "secondary",
861
- disabled: disabled || index === fields.length - 1,
862
- onClick: () => onChange(moveField(fields, index, 1)),
863
- children: [
864
- "Move ",
865
- field.name,
866
- " down"
867
- ]
868
- }
869
- )
870
- ] }),
871
- /* @__PURE__ */ jsx6(
872
- TextField,
873
- {
874
- label: "Label",
875
- required: true,
876
- disabled,
877
- value: field.label,
878
- onChange: (label) => update(index, { ...field, label })
879
- }
880
- ),
881
- /* @__PURE__ */ jsx6(
882
- TextField,
883
- {
884
- label: "Placeholder",
885
- disabled,
886
- value: field.placeholder ?? "",
887
- onChange: (placeholder) => update(index, { ...field, placeholder })
888
- }
889
- ),
890
- /* @__PURE__ */ jsx6(
891
- TextField,
892
- {
893
- label: "Help text",
894
- disabled,
895
- value: field.help ?? "",
896
- onChange: (help) => update(index, { ...field, help })
897
- }
898
- ),
899
- /* @__PURE__ */ jsxs6("label", { children: [
900
- /* @__PURE__ */ jsx6(
901
- "input",
902
- {
903
- type: "checkbox",
904
- checked: field.required,
905
- disabled: disabled || !attribute || attribute.required,
906
- onChange: (event) => {
907
- if (attribute)
908
- update(
909
- index,
910
- setRequired(field, event.target.checked, attribute)
911
- );
904
+ const missing = missingRequired(fields, schema);
905
+ const legacy = fields.filter(
906
+ (field) => !schema.some(
907
+ (item) => item.name === field.name && item.type === field.type
908
+ )
909
+ );
910
+ function reorder(from, to, control) {
911
+ if (disabled || from === to || to < 0 || to >= fields.length) return;
912
+ const next = [...fields];
913
+ const [field] = next.splice(from, 1);
914
+ if (!field) return;
915
+ next.splice(to, 0, field);
916
+ pendingFocus.current = control ? `${field.name}:${control}` : void 0;
917
+ onChange(next);
918
+ setAnnouncement(
919
+ formatMessage(
920
+ { id: "builder.forms.fields.moved" },
921
+ { name: field.label, position: to + 1, total: fields.length }
922
+ )
923
+ );
924
+ }
925
+ return /* @__PURE__ */ jsxs6(Flex4, { direction: "column", alignItems: "stretch", gap: 4, children: [
926
+ /* @__PURE__ */ jsx6(Typography5, { children: t("description") }),
927
+ missing.length > 0 && /* @__PURE__ */ jsx6(
928
+ Alert,
929
+ {
930
+ closeLabel: t("dismiss"),
931
+ variant: "warning",
932
+ title: t("missingRequired"),
933
+ action: /* @__PURE__ */ jsx6(
934
+ Button3,
935
+ {
936
+ disabled,
937
+ onClick: (event) => openModal(null, event.currentTarget),
938
+ children: t("add")
939
+ }
940
+ ),
941
+ children: missing.map((field) => field.name).join(", ")
942
+ }
943
+ ),
944
+ legacy.length > 0 && /* @__PURE__ */ jsx6(
945
+ Alert,
946
+ {
947
+ closeLabel: t("dismiss"),
948
+ variant: "warning",
949
+ title: t("legacyTitle"),
950
+ children: t("legacy")
951
+ }
952
+ ),
953
+ /* @__PURE__ */ jsx6(
954
+ Button3,
955
+ {
956
+ disabled: disabled || !schema.length,
957
+ onClick: (event) => openModal(null, event.currentTarget),
958
+ children: t("add")
959
+ }
960
+ ),
961
+ /* @__PURE__ */ jsx6("div", { role: "status", "aria-live": "polite", children: announcement }),
962
+ /* @__PURE__ */ jsxs6(Table2, { colCount: 5, rowCount: fields.length, children: [
963
+ /* @__PURE__ */ jsx6(Thead2, { children: /* @__PURE__ */ jsx6(Tr2, { children: ["order", "label", "type", "constraints", "actions"].map((key) => /* @__PURE__ */ jsx6(Th2, { children: /* @__PURE__ */ jsx6(Typography5, { variant: "sigma", children: t(key) }) }, key)) }) }),
964
+ /* @__PURE__ */ jsx6(Tbody2, { children: fields.map((field, index) => {
965
+ const attribute = schema.find((item) => item.name === field.name);
966
+ return /* @__PURE__ */ jsxs6(
967
+ Tr2,
968
+ {
969
+ draggable: !disabled,
970
+ onDragStart: (event) => {
971
+ drag.current = index;
972
+ event.dataTransfer.setData("text/plain", field.name);
973
+ event.dataTransfer.effectAllowed = "move";
974
+ },
975
+ onDragEnd: () => {
976
+ drag.current = null;
977
+ },
978
+ onDragOver: (event) => event.preventDefault(),
979
+ onDrop: (event) => {
980
+ event.preventDefault();
981
+ if (drag.current !== null) reorder(drag.current, index);
982
+ drag.current = null;
983
+ },
984
+ children: [
985
+ /* @__PURE__ */ jsx6(Td2, { children: /* @__PURE__ */ jsx6(Flex4, { gap: 1, children: [-1, 1].map((direction) => {
986
+ const key = direction < 0 ? "up" : "down";
987
+ return /* @__PURE__ */ jsx6(
988
+ IconButton2,
989
+ {
990
+ ref: (node) => {
991
+ const control = `${field.name}:${key}`;
992
+ if (node) controls.current.set(control, node);
993
+ else controls.current.delete(control);
994
+ },
995
+ label: formatMessage(
996
+ { id: `builder.forms.fields.${key}` },
997
+ { name: field.label }
998
+ ),
999
+ disabled: disabled || (direction < 0 ? index === 0 : index === fields.length - 1),
1000
+ onClick: () => reorder(index, index + direction, key),
1001
+ children: direction < 0 ? /* @__PURE__ */ jsx6(ArrowUp, {}) : /* @__PURE__ */ jsx6(ArrowDown, {})
1002
+ },
1003
+ key
1004
+ );
1005
+ }) }) }),
1006
+ /* @__PURE__ */ jsxs6(Td2, { children: [
1007
+ /* @__PURE__ */ jsx6(
1008
+ Typography5,
1009
+ {
1010
+ fontWeight: "bold",
1011
+ style: { whiteSpace: "normal", overflowWrap: "anywhere" },
1012
+ children: field.label
912
1013
  }
913
- }
914
- ),
915
- " ",
916
- "Required",
917
- attribute?.required ? " (required by target schema)" : ""
918
- ] }),
919
- /* @__PURE__ */ jsxs6("label", { children: [
920
- /* @__PURE__ */ jsx6(
921
- "input",
922
- {
923
- type: "checkbox",
924
- checked: field.visibility === "hidden",
925
- disabled,
926
- onChange: (event) => update(index, {
927
- ...field,
928
- visibility: event.target.checked ? "hidden" : "form"
929
- })
930
- }
931
- ),
932
- " ",
933
- "Hidden from form"
934
- ] }),
935
- (field.maxLength !== void 0 || field.min !== void 0 || field.max !== void 0) && /* @__PURE__ */ jsxs6(Typography5, { variant: "pi", children: [
936
- "Constraints:",
937
- " ",
938
- JSON.stringify({
939
- maxLength: field.maxLength,
940
- min: field.min,
941
- max: field.max
942
- })
943
- ] }),
944
- field.options?.map((option, optionIndex) => /* @__PURE__ */ jsx6(
945
- TextField,
946
- {
947
- label: `Option label: ${option.value}`,
948
- required: true,
949
- disabled,
950
- value: option.label,
951
- onChange: (label) => update(index, {
952
- ...field,
953
- options: field.options?.map(
954
- (item, at) => at === optionIndex ? { ...item, label } : item
955
- )
956
- })
957
- },
958
- option.value
959
- ))
960
- ]
961
- },
962
- field.name
963
- );
964
- })
1014
+ ),
1015
+ /* @__PURE__ */ jsx6(Typography5, { tag: "div", variant: "pi", children: field.name })
1016
+ ] }),
1017
+ /* @__PURE__ */ jsx6(Td2, { children: /* @__PURE__ */ jsx6(Typography5, { children: t(`types.${field.type}`) }) }),
1018
+ /* @__PURE__ */ jsx6(Td2, { children: /* @__PURE__ */ jsxs6(Flex4, { gap: 1, wrap: "wrap", children: [
1019
+ field.required && /* @__PURE__ */ jsx6(Badge3, { children: t("required") }),
1020
+ field.visibility === "hidden" && /* @__PURE__ */ jsx6(Badge3, { children: t("hidden") }),
1021
+ field.maxLength !== void 0 && /* @__PURE__ */ jsxs6(Badge3, { children: [
1022
+ t("maxLength"),
1023
+ ": ",
1024
+ field.maxLength
1025
+ ] })
1026
+ ] }) }),
1027
+ /* @__PURE__ */ jsx6(Td2, { children: /* @__PURE__ */ jsxs6(Flex4, { gap: 1, children: [
1028
+ /* @__PURE__ */ jsx6(
1029
+ IconButton2,
1030
+ {
1031
+ label: formatMessage(
1032
+ { id: "builder.forms.fields.edit" },
1033
+ { name: field.label }
1034
+ ),
1035
+ disabled: disabled || !attribute,
1036
+ onClick: (event) => openModal(field, event.currentTarget),
1037
+ children: /* @__PURE__ */ jsx6(Pencil, {})
1038
+ }
1039
+ ),
1040
+ /* @__PURE__ */ jsx6(
1041
+ Tooltip,
1042
+ {
1043
+ label: attribute?.required ? t("schemaRequired") : t("remove"),
1044
+ children: /* @__PURE__ */ jsx6("span", { tabIndex: attribute?.required ? 0 : void 0, children: /* @__PURE__ */ jsx6(
1045
+ IconButton2,
1046
+ {
1047
+ label: formatMessage(
1048
+ { id: "builder.forms.fields.removeName" },
1049
+ { name: field.label }
1050
+ ),
1051
+ disabled: disabled || attribute?.required,
1052
+ onClick: () => onChange(fields.filter((_, at) => at !== index)),
1053
+ children: /* @__PURE__ */ jsx6(Trash, {})
1054
+ }
1055
+ ) })
1056
+ }
1057
+ )
1058
+ ] }) })
1059
+ ]
1060
+ },
1061
+ field.name
1062
+ );
1063
+ }) })
1064
+ ] }),
1065
+ !fields.length && /* @__PURE__ */ jsx6(Typography5, { children: t("empty") }),
1066
+ modal !== void 0 && !disabled && /* @__PURE__ */ jsx6(
1067
+ FieldModal,
1068
+ {
1069
+ fields,
1070
+ schema,
1071
+ initial: modal ?? void 0,
1072
+ onClose: () => setModal(void 0),
1073
+ onApply: (field) => {
1074
+ onChange(
1075
+ modal ? fields.map(
1076
+ (item) => item.name === modal.name ? field : item
1077
+ ) : [...fields, field]
1078
+ );
1079
+ setModal(void 0);
1080
+ }
1081
+ }
1082
+ )
965
1083
  ] });
966
1084
  }
967
1085
  var init_field_editor = __esm({
968
1086
  "admin/src/field-editor.tsx"() {
969
1087
  "use strict";
970
1088
  init_form();
971
- init_ui();
1089
+ init_field_modal();
972
1090
  }
973
1091
  });
974
1092
 
@@ -990,7 +1108,7 @@ function templateError(value, fields) {
990
1108
  });
991
1109
  return invalid || rest.includes("{{") || rest.includes("}}") ? "Use only the available placeholders." : "";
992
1110
  }
993
- function notificationErrors(config, fields) {
1111
+ function notificationErrors(config, fields, translate) {
994
1112
  const errors = {};
995
1113
  if (config.email) {
996
1114
  if (config.email.replyToField && !fields.some(
@@ -1015,6 +1133,17 @@ function notificationErrors(config, fields) {
1015
1133
  errors.url = "Enter an HTTP or HTTPS URL without credentials.";
1016
1134
  }
1017
1135
  }
1136
+ if (translate) {
1137
+ const keys = {
1138
+ replyToField: "replyToError",
1139
+ subject: "templateError",
1140
+ body: "templateError",
1141
+ to: "recipientsError",
1142
+ cc: "recipientsError",
1143
+ url: "urlError"
1144
+ };
1145
+ for (const key of Object.keys(errors)) errors[key] = translate(keys[key]);
1146
+ }
1018
1147
  return errors;
1019
1148
  }
1020
1149
  function samplePayload(fields, values) {
@@ -1050,8 +1179,20 @@ var init_notifications = __esm({
1050
1179
  });
1051
1180
 
1052
1181
  // admin/src/notification-editor.tsx
1053
- import { useState as useState4 } from "react";
1054
- import { Button as Button5, Flex as Flex6, Typography as Typography6 } from "@strapi/design-system";
1182
+ import { useIntl as useIntl5 } from "react-intl";
1183
+ import { useState as useState5 } from "react";
1184
+ import {
1185
+ Button as Button4,
1186
+ Field as Field3,
1187
+ Flex as Flex5,
1188
+ Popover,
1189
+ SingleSelect as SingleSelect3,
1190
+ SingleSelectOption as SingleSelectOption3,
1191
+ TextInput as TextInput2,
1192
+ Textarea,
1193
+ Toggle as Toggle2,
1194
+ Typography as Typography6
1195
+ } from "@strapi/design-system";
1055
1196
  import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1056
1197
  function NotificationEditor({
1057
1198
  value,
@@ -1062,8 +1203,10 @@ function NotificationEditor({
1062
1203
  disabled,
1063
1204
  saveError
1064
1205
  }) {
1065
- const [target, setTarget] = useState4("body");
1066
- const errors = notificationErrors(value, fields);
1206
+ const { formatMessage } = useIntl5();
1207
+ const t = (key, values) => formatMessage({ id: `builder.notifications.${key}` }, values);
1208
+ const [target, setTarget] = useState5("body");
1209
+ const errors = notificationErrors(value, fields, (key) => t(key));
1067
1210
  function toggle(channel, enabled) {
1068
1211
  const next = { ...value };
1069
1212
  if (!enabled) delete next[channel];
@@ -1071,44 +1214,45 @@ function NotificationEditor({
1071
1214
  next.email = {
1072
1215
  to: [],
1073
1216
  cc: [],
1074
- subject: "New submission: {{form.name}}",
1075
- body: "Submission {{submission.id}} received at {{submission.receivedAt}}"
1217
+ subject: t("defaultSubject"),
1218
+ body: t("defaultBody")
1076
1219
  };
1077
1220
  else next.webhook = { url: "" };
1078
1221
  onChange(next);
1079
1222
  }
1080
- return /* @__PURE__ */ jsxs7(Section, { title: "Notifications", children: [
1223
+ return /* @__PURE__ */ jsxs7(Section, { title: t("notifications"), children: [
1081
1224
  /* @__PURE__ */ jsx7(
1082
- Button5,
1225
+ Button4,
1083
1226
  {
1084
1227
  type: "button",
1085
1228
  variant: "tertiary",
1086
1229
  onClick: onOpen,
1087
1230
  disabled: opened,
1088
- children: opened ? "Notification settings opened" : "Open notification settings"
1231
+ children: opened ? t("opened") : t("open")
1089
1232
  }
1090
1233
  ),
1091
1234
  opened && /* @__PURE__ */ jsxs7(Fragment3, { children: [
1092
- /* @__PURE__ */ jsx7(Typography6, { variant: "pi", children: "Enable a channel to send notifications. Changes take effect after saving the form." }),
1235
+ /* @__PURE__ */ jsx7(Typography6, { variant: "pi", children: t("hint") }),
1093
1236
  saveError.startsWith("notifications:") && /* @__PURE__ */ jsx7(ErrorNotice, { message: saveError }),
1094
- /* @__PURE__ */ jsxs7("label", { children: [
1237
+ /* @__PURE__ */ jsxs7(Field3.Root, { children: [
1238
+ /* @__PURE__ */ jsx7(Field3.Label, { children: t("email") }),
1095
1239
  /* @__PURE__ */ jsx7(
1096
- "input",
1240
+ Toggle2,
1097
1241
  {
1098
- type: "checkbox",
1242
+ "aria-label": t("email"),
1243
+ onLabel: t("on"),
1244
+ offLabel: t("off"),
1099
1245
  disabled,
1100
1246
  checked: Boolean(value.email),
1101
1247
  onChange: (event) => toggle("email", event.target.checked)
1102
1248
  }
1103
- ),
1104
- " ",
1105
- "Email notifications"
1249
+ )
1106
1250
  ] }),
1107
1251
  value.email && /* @__PURE__ */ jsxs7(Fragment3, { children: [
1108
1252
  ["to", "cc"].map((key) => /* @__PURE__ */ jsx7(
1109
- TextField,
1253
+ NativeField,
1110
1254
  {
1111
- label: key === "to" ? "To (one address per line)" : "Cc (one address per line)",
1255
+ label: key === "to" ? t("to") : t("cc"),
1112
1256
  multiline: true,
1113
1257
  value: (value.email?.[key] ?? []).join("\n"),
1114
1258
  disabled,
@@ -1120,26 +1264,23 @@ function NotificationEditor({
1120
1264
  },
1121
1265
  key
1122
1266
  )),
1123
- /* @__PURE__ */ jsx7(Typography6, { variant: "pi", children: "Use fixed email addresses. Empty lines are ignored when saving." }),
1124
- /* @__PURE__ */ jsxs7("label", { children: [
1125
- "Reply-to field",
1267
+ /* @__PURE__ */ jsx7(Typography6, { variant: "pi", children: t("recipientsHint") }),
1268
+ /* @__PURE__ */ jsxs7(Field3.Root, { error: errors.replyToField, children: [
1269
+ /* @__PURE__ */ jsx7(Field3.Label, { children: t("replyTo") }),
1126
1270
  /* @__PURE__ */ jsxs7(
1127
- "select",
1271
+ SingleSelect3,
1128
1272
  {
1129
- style: { width: "100%", padding: 10 },
1130
1273
  disabled,
1131
1274
  value: value.email.replyToField ?? "",
1132
- "aria-invalid": Boolean(errors.replyToField),
1133
- onChange: (event) => {
1275
+ onChange: (next) => {
1134
1276
  const email = { ...value.email };
1135
1277
  delete email.replyToField;
1136
- if (event.target.value)
1137
- email.replyToField = event.target.value;
1278
+ if (next) email.replyToField = String(next);
1138
1279
  onChange({ ...value, email });
1139
1280
  },
1140
1281
  children: [
1141
- /* @__PURE__ */ jsx7("option", { value: "", children: "None" }),
1142
- fields.filter((field) => field.type === "email").map((field) => /* @__PURE__ */ jsxs7("option", { value: field.name, children: [
1282
+ /* @__PURE__ */ jsx7(SingleSelectOption3, { value: "", children: t("none") }),
1283
+ fields.filter((field) => field.type === "email").map((field) => /* @__PURE__ */ jsxs7(SingleSelectOption3, { value: field.name, children: [
1143
1284
  field.label,
1144
1285
  " (",
1145
1286
  field.name,
@@ -1147,13 +1288,13 @@ function NotificationEditor({
1147
1288
  ] }, field.name))
1148
1289
  ]
1149
1290
  }
1150
- )
1291
+ ),
1292
+ /* @__PURE__ */ jsx7(Field3.Error, {})
1151
1293
  ] }),
1152
- errors.replyToField && /* @__PURE__ */ jsx7(ErrorNotice, { message: errors.replyToField }),
1153
1294
  /* @__PURE__ */ jsx7(
1154
- TextField,
1295
+ NativeField,
1155
1296
  {
1156
- label: "Email subject",
1297
+ label: t("subject"),
1157
1298
  value: value.email.subject,
1158
1299
  disabled,
1159
1300
  error: errors.subject,
@@ -1161,9 +1302,9 @@ function NotificationEditor({
1161
1302
  }
1162
1303
  ),
1163
1304
  /* @__PURE__ */ jsx7(
1164
- TextField,
1305
+ NativeField,
1165
1306
  {
1166
- label: "Email body",
1307
+ label: t("body"),
1167
1308
  multiline: true,
1168
1309
  value: value.email.body,
1169
1310
  disabled,
@@ -1171,67 +1312,80 @@ function NotificationEditor({
1171
1312
  onChange: (body) => onChange({ ...value, email: { ...value.email, body } })
1172
1313
  }
1173
1314
  ),
1174
- /* @__PURE__ */ jsxs7("label", { children: [
1175
- "Insert placeholder into",
1315
+ /* @__PURE__ */ jsxs7(Field3.Root, { children: [
1316
+ /* @__PURE__ */ jsx7(Field3.Label, { children: t("insertTarget") }),
1176
1317
  /* @__PURE__ */ jsxs7(
1177
- "select",
1318
+ SingleSelect3,
1178
1319
  {
1179
- style: { width: "100%", padding: 10 },
1180
1320
  disabled,
1181
1321
  value: target,
1182
- onChange: (event) => setTarget(
1183
- event.target.value === "subject" ? "subject" : "body"
1184
- ),
1322
+ onChange: (next) => setTarget(next === "subject" ? "subject" : "body"),
1185
1323
  children: [
1186
- /* @__PURE__ */ jsx7("option", { value: "subject", children: "Subject" }),
1187
- /* @__PURE__ */ jsx7("option", { value: "body", children: "Body" })
1324
+ /* @__PURE__ */ jsx7(SingleSelectOption3, { value: "subject", children: t("subject") }),
1325
+ /* @__PURE__ */ jsx7(SingleSelectOption3, { value: "body", children: t("body") })
1188
1326
  ]
1189
1327
  }
1190
1328
  )
1191
1329
  ] }),
1192
- /* @__PURE__ */ jsx7(Flex6, { gap: 2, wrap: "wrap", children: placeholders(fields).map((key) => /* @__PURE__ */ jsx7(
1193
- Button5,
1194
- {
1195
- type: "button",
1196
- style: {
1197
- maxWidth: "100%",
1198
- height: "auto",
1199
- whiteSpace: "normal",
1200
- overflowWrap: "anywhere"
1201
- },
1202
- size: "S",
1203
- variant: "tertiary",
1204
- disabled,
1205
- onClick: () => onChange({
1206
- ...value,
1207
- email: {
1208
- ...value.email,
1209
- [target]: `${value.email[target]}{{${key}}}`
1210
- }
1211
- }),
1212
- children: `{{${key}}}`
1213
- },
1214
- key
1215
- )) })
1330
+ /* @__PURE__ */ jsxs7(Popover.Root, { children: [
1331
+ /* @__PURE__ */ jsx7(Popover.Trigger, { children: /* @__PURE__ */ jsx7(Button4, { type: "button", variant: "tertiary", disabled, children: t("insert") }) }),
1332
+ /* @__PURE__ */ jsx7(
1333
+ Popover.Content,
1334
+ {
1335
+ "aria-label": t("placeholderList"),
1336
+ collisionPadding: 16,
1337
+ style: {
1338
+ width: "min(480px, calc(100vw - 32px))",
1339
+ maxWidth: "var(--radix-popover-content-available-width)",
1340
+ boxSizing: "border-box"
1341
+ },
1342
+ children: /* @__PURE__ */ jsx7(Flex5, { gap: 2, wrap: "wrap", children: placeholders(fields).map((key) => /* @__PURE__ */ jsx7(
1343
+ Button4,
1344
+ {
1345
+ type: "button",
1346
+ style: {
1347
+ maxWidth: "100%",
1348
+ height: "auto",
1349
+ whiteSpace: "normal",
1350
+ overflowWrap: "anywhere"
1351
+ },
1352
+ size: "S",
1353
+ variant: "tertiary",
1354
+ disabled,
1355
+ onClick: () => onChange({
1356
+ ...value,
1357
+ email: {
1358
+ ...value.email,
1359
+ [target]: `${value.email[target]}{{${key}}}`
1360
+ }
1361
+ }),
1362
+ children: `{{${key}}}`
1363
+ },
1364
+ key
1365
+ )) })
1366
+ }
1367
+ )
1368
+ ] })
1216
1369
  ] }),
1217
- /* @__PURE__ */ jsxs7("label", { children: [
1370
+ /* @__PURE__ */ jsxs7(Field3.Root, { children: [
1371
+ /* @__PURE__ */ jsx7(Field3.Label, { children: t("webhook") }),
1218
1372
  /* @__PURE__ */ jsx7(
1219
- "input",
1373
+ Toggle2,
1220
1374
  {
1221
- type: "checkbox",
1375
+ "aria-label": t("webhook"),
1376
+ onLabel: t("on"),
1377
+ offLabel: t("off"),
1222
1378
  disabled,
1223
1379
  checked: Boolean(value.webhook),
1224
1380
  onChange: (event) => toggle("webhook", event.target.checked)
1225
1381
  }
1226
- ),
1227
- " ",
1228
- "Webhook notifications"
1382
+ )
1229
1383
  ] }),
1230
1384
  value.webhook && /* @__PURE__ */ jsxs7(Fragment3, { children: [
1231
1385
  /* @__PURE__ */ jsx7(
1232
- TextField,
1386
+ NativeField,
1233
1387
  {
1234
- label: "Webhook URL",
1388
+ label: t("url"),
1235
1389
  value: value.webhook.url,
1236
1390
  disabled,
1237
1391
  error: errors.url,
@@ -1239,9 +1393,9 @@ function NotificationEditor({
1239
1393
  }
1240
1394
  ),
1241
1395
  /* @__PURE__ */ jsx7(
1242
- TextField,
1396
+ NativeField,
1243
1397
  {
1244
- label: "Webhook secret",
1398
+ label: t("secret"),
1245
1399
  secret: true,
1246
1400
  value: value.webhook.secret ?? "",
1247
1401
  disabled,
@@ -1253,19 +1407,19 @@ function NotificationEditor({
1253
1407
  }
1254
1408
  }
1255
1409
  ),
1256
- /* @__PURE__ */ jsx7(Typography6, { variant: "pi", children: "Stored secrets and header values appear as ********. Leave them untouched to preserve them, or enter a replacement. Clear a secret or remove a header to delete it." }),
1410
+ /* @__PURE__ */ jsx7(Typography6, { variant: "pi", children: t("maskHint") }),
1257
1411
  Object.entries(value.webhook.headers ?? {}).map(
1258
1412
  ([name, header]) => /* @__PURE__ */ jsxs7(
1259
- Flex6,
1413
+ Flex5,
1260
1414
  {
1261
1415
  direction: "column",
1262
1416
  alignItems: "stretch",
1263
1417
  gap: 2,
1264
1418
  children: [
1265
1419
  /* @__PURE__ */ jsx7(
1266
- TextField,
1420
+ NativeField,
1267
1421
  {
1268
- label: `Header: ${name}`,
1422
+ label: t("header", { name }),
1269
1423
  secret: true,
1270
1424
  value: header,
1271
1425
  disabled,
@@ -1281,8 +1435,8 @@ function NotificationEditor({
1281
1435
  })
1282
1436
  }
1283
1437
  ),
1284
- /* @__PURE__ */ jsxs7(
1285
- Button5,
1438
+ /* @__PURE__ */ jsx7(
1439
+ Button4,
1286
1440
  {
1287
1441
  type: "button",
1288
1442
  variant: "tertiary",
@@ -1295,10 +1449,7 @@ function NotificationEditor({
1295
1449
  webhook: { ...value.webhook, headers }
1296
1450
  });
1297
1451
  },
1298
- children: [
1299
- "Remove ",
1300
- name
1301
- ]
1452
+ children: t("remove", { name })
1302
1453
  }
1303
1454
  )
1304
1455
  ]
@@ -1329,30 +1480,32 @@ function AddHeader({
1329
1480
  names,
1330
1481
  onAdd
1331
1482
  }) {
1332
- const [name, setName] = useState4("");
1333
- const [value, setValue] = useState4("");
1483
+ const { formatMessage } = useIntl5();
1484
+ const t = (key) => formatMessage({ id: `builder.notifications.${key}` });
1485
+ const [name, setName] = useState5("");
1486
+ const [value, setValue] = useState5("");
1334
1487
  return /* @__PURE__ */ jsxs7(Fragment3, { children: [
1335
1488
  /* @__PURE__ */ jsx7(
1336
- TextField,
1489
+ NativeField,
1337
1490
  {
1338
- label: "New header name",
1491
+ label: t("headerName"),
1339
1492
  value: name,
1340
1493
  disabled,
1341
1494
  onChange: setName
1342
1495
  }
1343
1496
  ),
1344
1497
  /* @__PURE__ */ jsx7(
1345
- TextField,
1498
+ NativeField,
1346
1499
  {
1347
- label: "New header value",
1500
+ label: t("headerValue"),
1348
1501
  secret: true,
1349
1502
  value,
1350
1503
  disabled,
1351
1504
  onChange: setValue
1352
1505
  }
1353
1506
  ),
1354
- /* @__PURE__ */ jsx7(
1355
- Button5,
1507
+ /* @__PURE__ */ jsxs7(
1508
+ Button4,
1356
1509
  {
1357
1510
  type: "button",
1358
1511
  variant: "tertiary",
@@ -1362,7 +1515,10 @@ function AddHeader({
1362
1515
  setName("");
1363
1516
  setValue("");
1364
1517
  },
1365
- children: "Add header"
1518
+ children: [
1519
+ t("addHeader"),
1520
+ " "
1521
+ ]
1366
1522
  }
1367
1523
  )
1368
1524
  ] });
@@ -1372,48 +1528,56 @@ function NotificationTest({
1372
1528
  disabled,
1373
1529
  send
1374
1530
  }) {
1375
- const [values, setValues] = useState4({});
1376
- const [results, setResults] = useState4(null);
1377
- const [error, setError] = useState4("");
1378
- const [errors, setErrors] = useState4({});
1379
- const [busy, setBusy] = useState4(false);
1380
- return /* @__PURE__ */ jsxs7(Section, { title: "Test notifications", children: [
1381
- /* @__PURE__ */ jsx7(Typography6, { variant: "pi", children: "Sends real notifications using the saved settings and these sample values, including hidden fields. Save all changes before testing. No submission record is created." }),
1382
- disabled && /* @__PURE__ */ jsx7(Typography6, { variant: "pi", children: "Testing requires a saved form, no unsaved changes and forms.update permission." }),
1383
- fields.map((field) => /* @__PURE__ */ jsx7("div", { style: { display: "grid", gap: 6 }, children: field.type === "boolean" ? /* @__PURE__ */ jsxs7("label", { children: [
1384
- /* @__PURE__ */ jsxs7(Typography6, { children: [
1385
- field.label,
1386
- " (",
1387
- field.name,
1388
- ")",
1389
- field.visibility === "hidden" ? " \xB7 hidden" : ""
1390
- ] }),
1391
- /* @__PURE__ */ jsxs7(
1392
- "select",
1531
+ const { formatMessage } = useIntl5();
1532
+ const t = (key, values2) => formatMessage({ id: `builder.notifications.${key}` }, values2);
1533
+ const [values, setValues] = useState5({});
1534
+ const [results, setResults] = useState5(null);
1535
+ const [error, setError] = useState5("");
1536
+ const [errors, setErrors] = useState5({});
1537
+ const [busy, setBusy] = useState5(false);
1538
+ return /* @__PURE__ */ jsxs7(Section, { title: t("testTitle"), children: [
1539
+ /* @__PURE__ */ jsx7(Typography6, { variant: "pi", children: t("testHint") }),
1540
+ disabled && /* @__PURE__ */ jsx7(Typography6, { variant: "pi", children: t("testDisabled") }),
1541
+ fields.map((field) => {
1542
+ const label = `${field.label} (${field.name})${field.visibility === "hidden" ? ` \xB7 ${t("hidden")}` : ""}`;
1543
+ const error2 = errors[field.name] ? t(
1544
+ ["required", "invalid", "maxLength", "min", "max"].includes(
1545
+ errors[field.name]
1546
+ ) ? `sample.${errors[field.name]}` : "sampleError"
1547
+ ) : void 0;
1548
+ const update = (next) => setValues({ ...values, [field.name]: next });
1549
+ return field.type === "boolean" || field.type === "enumeration" ? /* @__PURE__ */ jsxs7(Field3.Root, { required: field.required, error: error2, children: [
1550
+ /* @__PURE__ */ jsx7(Field3.Label, { children: label }),
1551
+ /* @__PURE__ */ jsxs7(
1552
+ SingleSelect3,
1553
+ {
1554
+ disabled: disabled || busy,
1555
+ value: values[field.name] ?? "",
1556
+ onChange: (next) => update(String(next ?? "")),
1557
+ children: [
1558
+ /* @__PURE__ */ jsx7(SingleSelectOption3, { value: "", children: t("choose") }),
1559
+ field.type === "boolean" ? ["true", "false"].map((value) => /* @__PURE__ */ jsx7(SingleSelectOption3, { value, children: t(value) }, value)) : field.options?.map((option) => /* @__PURE__ */ jsx7(SingleSelectOption3, { value: option.value, children: option.label }, option.value))
1560
+ ]
1561
+ }
1562
+ ),
1563
+ /* @__PURE__ */ jsx7(Field3.Error, {})
1564
+ ] }, field.name) : /* @__PURE__ */ jsx7(
1565
+ NativeField,
1393
1566
  {
1394
- disabled: disabled || busy,
1567
+ label,
1568
+ required: field.required,
1569
+ type: ["integer", "decimal"].includes(field.type) ? "number" : field.type === "email" ? "email" : field.type === "date" ? "date" : "text",
1570
+ multiline: field.type === "text",
1395
1571
  value: values[field.name] ?? "",
1396
- onChange: (event) => setValues({ ...values, [field.name]: event.target.value }),
1397
- children: [
1398
- /* @__PURE__ */ jsx7("option", { value: "", children: "Choose a value" }),
1399
- /* @__PURE__ */ jsx7("option", { value: "true", children: "True" }),
1400
- /* @__PURE__ */ jsx7("option", { value: "false", children: "False" })
1401
- ]
1402
- }
1403
- ),
1404
- errors[field.name] && /* @__PURE__ */ jsx7(ErrorNotice, { message: errors[field.name] ?? "" })
1405
- ] }) : /* @__PURE__ */ jsx7(
1406
- TextField,
1407
- {
1408
- label: `${field.label} (${field.name})${field.visibility === "hidden" ? " \xB7 hidden" : ""}`,
1409
- value: values[field.name] ?? "",
1410
- disabled: disabled || busy,
1411
- error: errors[field.name],
1412
- onChange: (next) => setValues({ ...values, [field.name]: next })
1413
- }
1414
- ) }, field.name)),
1572
+ disabled: disabled || busy,
1573
+ error: error2,
1574
+ onChange: update
1575
+ },
1576
+ field.name
1577
+ );
1578
+ }),
1415
1579
  /* @__PURE__ */ jsx7(
1416
- Button5,
1580
+ Button4,
1417
1581
  {
1418
1582
  type: "button",
1419
1583
  disabled: disabled || busy,
@@ -1431,12 +1595,12 @@ function NotificationTest({
1431
1595
  setBusy(false);
1432
1596
  }
1433
1597
  },
1434
- children: busy ? "Sending test\u2026" : "Send test notifications"
1598
+ children: busy ? t("sending") : t("send")
1435
1599
  }
1436
1600
  ),
1437
1601
  /* @__PURE__ */ jsx7(ErrorNotice, { message: error }),
1438
1602
  /* @__PURE__ */ jsxs7("div", { "aria-live": "polite", children: [
1439
- results?.length === 0 && /* @__PURE__ */ jsx7(Typography6, { children: "No notification channels are enabled in the saved settings." }),
1603
+ results?.length === 0 && /* @__PURE__ */ jsx7(Typography6, { children: t("noChannels") }),
1440
1604
  results?.map((delivery) => /* @__PURE__ */ jsx7(
1441
1605
  "div",
1442
1606
  {
@@ -1446,14 +1610,14 @@ function NotificationTest({
1446
1610
  {
1447
1611
  textColor: delivery.status === "sent" ? "success700" : "danger700",
1448
1612
  children: [
1449
- delivery.channel,
1450
- ":",
1451
- " ",
1452
- delivery.status === "sent" ? "Success" : "Failed",
1613
+ t(
1614
+ delivery.channel === "email" ? "email" : delivery.channel === "webhook" ? "webhook" : "afterSave"
1615
+ ),
1616
+ ": ",
1617
+ delivery.status === "sent" ? t("sent") : t("failed"),
1453
1618
  " \xB7",
1454
1619
  " ",
1455
- delivery.attempts,
1456
- " attempt(s)",
1620
+ t("attempts", { count: delivery.attempts }),
1457
1621
  delivery.lastError ? ` \xB7 ${delivery.lastError}` : "",
1458
1622
  delivery.sentAt ? ` \xB7 ${delivery.sentAt}` : ""
1459
1623
  ]
@@ -1465,6 +1629,40 @@ function NotificationTest({
1465
1629
  ] })
1466
1630
  ] });
1467
1631
  }
1632
+ function NativeField({
1633
+ label,
1634
+ value,
1635
+ onChange,
1636
+ disabled,
1637
+ error,
1638
+ multiline,
1639
+ secret,
1640
+ required,
1641
+ type = "text"
1642
+ }) {
1643
+ return /* @__PURE__ */ jsxs7(Field3.Root, { error, required, children: [
1644
+ /* @__PURE__ */ jsx7(Field3.Label, { children: label }),
1645
+ multiline ? /* @__PURE__ */ jsx7(
1646
+ Textarea,
1647
+ {
1648
+ value,
1649
+ disabled,
1650
+ onChange: (event) => onChange(event.target.value)
1651
+ }
1652
+ ) : /* @__PURE__ */ jsx7(
1653
+ TextInput2,
1654
+ {
1655
+ autoComplete: secret ? "new-password" : void 0,
1656
+ type: secret ? "password" : type,
1657
+ step: type === "number" ? "any" : void 0,
1658
+ value,
1659
+ disabled,
1660
+ onChange: (event) => onChange(event.target.value)
1661
+ }
1662
+ ),
1663
+ /* @__PURE__ */ jsx7(Field3.Error, {})
1664
+ ] });
1665
+ }
1468
1666
  var init_notification_editor = __esm({
1469
1667
  "admin/src/notification-editor.tsx"() {
1470
1668
  "use strict";
@@ -1475,30 +1673,53 @@ var init_notification_editor = __esm({
1475
1673
  });
1476
1674
 
1477
1675
  // admin/src/form-editor-page.tsx
1478
- import { useEffect as useEffect3, useRef as useRef4, useState as useState5 } from "react";
1479
- import { Button as Button6, Flex as Flex7, Typography as Typography7 } from "@strapi/design-system";
1480
- import { Layouts as Layouts3, Page as Page3, useFetchClient as useFetchClient3 } from "@strapi/strapi/admin";
1676
+ import { useIntl as useIntl6 } from "react-intl";
1677
+ import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState6 } from "react";
1678
+ import {
1679
+ Alert as Alert2,
1680
+ Badge as Badge4,
1681
+ Button as Button5,
1682
+ Combobox,
1683
+ ComboboxOption,
1684
+ Field as Field4,
1685
+ Flex as Flex6,
1686
+ Tabs,
1687
+ TextInput as TextInput3,
1688
+ Textarea as Textarea2,
1689
+ Typography as Typography7
1690
+ } from "@strapi/design-system";
1691
+ import { Layouts as Layouts2, Page as Page2, useFetchClient as useFetchClient3 } from "@strapi/strapi/admin";
1481
1692
  import { useNavigate as useNavigate2, useParams } from "react-router-dom";
1482
1693
  import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1483
1694
  function FormEditorPage({ canUpdate }) {
1695
+ const { formatMessage } = useIntl6();
1696
+ const t = useCallback2((id) => formatMessage({ id }), [formatMessage]);
1484
1697
  const { documentId } = useParams();
1485
1698
  const client = useFetchClient3();
1486
1699
  const { get } = client;
1487
1700
  const navigate = useNavigate2();
1488
- const [draft, setDraft] = useState5(emptyForm);
1489
- const [notificationsOpened, setNotificationsOpened] = useState5(false);
1490
- const [savedDraft, setSavedDraft] = useState5("");
1491
- const [version, setVersion] = useState5(null);
1492
- const [targets, setTargets] = useState5([]);
1493
- const [schema, setSchema] = useState5([]);
1494
- const [loading, setLoading] = useState5(true);
1495
- const [fieldsLoading, setFieldsLoading] = useState5(false);
1496
- const [error, setError] = useState5("");
1497
- const [loaded, setLoaded] = useState5(false);
1498
- const [busy, setBusy] = useState5(false);
1499
- const [retry, setRetry] = useState5(0);
1500
- const lock = useRef4(false);
1501
- const targetRequest = useRef4(0);
1701
+ const [draft, setDraft] = useState6(createEmptyForm);
1702
+ const [slugEdited, setSlugEdited] = useState6(false);
1703
+ const [hasSubmissions, setHasSubmissions] = useState6(false);
1704
+ const [targetChanged, setTargetChanged] = useState6(false);
1705
+ const [tab, setTab] = useState6("basics");
1706
+ const [invalidTabs, setInvalidTabs] = useState6([]);
1707
+ const [notificationsOpened, setNotificationsOpened] = useState6(false);
1708
+ const [savedConsent, setSavedConsent] = useState6(
1709
+ null
1710
+ );
1711
+ const [savedDraft, setSavedDraft] = useState6("");
1712
+ const [version, setVersion] = useState6(null);
1713
+ const [targets, setTargets] = useState6([]);
1714
+ const [schema, setSchema] = useState6([]);
1715
+ const [loading, setLoading] = useState6(true);
1716
+ const [fieldsLoading, setFieldsLoading] = useState6(false);
1717
+ const [error, setError] = useState6("");
1718
+ const [loaded, setLoaded] = useState6(false);
1719
+ const [busy, setBusy] = useState6(false);
1720
+ const [retry, setRetry] = useState6(0);
1721
+ const lock = useRef3(false);
1722
+ const targetRequest = useRef3(0);
1502
1723
  useEffect3(() => {
1503
1724
  let active = true;
1504
1725
  setLoading(true);
@@ -1515,15 +1736,14 @@ function FormEditorPage({ canUpdate }) {
1515
1736
  const attrs = saved ? await api.fields(saved.targetUid) : [];
1516
1737
  if (!active) return;
1517
1738
  setTargets(available);
1518
- setDraft(
1519
- saved ?? {
1520
- ...emptyForm,
1521
- fields: [],
1522
- consent: { ...emptyForm.consent }
1523
- }
1524
- );
1739
+ setDraft(saved ?? createEmptyForm());
1740
+ setSavedConsent(saved ? { ...saved.consent } : null);
1525
1741
  setSavedDraft(saved ? JSON.stringify(savePayload(saved, true)) : "");
1526
1742
  setVersion(saved?.version ?? null);
1743
+ setHasSubmissions(saved?.hasSubmissions ?? false);
1744
+ setSlugEdited(Boolean(saved));
1745
+ setTargetChanged(false);
1746
+ setInvalidTabs([]);
1527
1747
  setSchema(attrs);
1528
1748
  setLoaded(true);
1529
1749
  } catch (cause) {
@@ -1541,6 +1761,7 @@ function FormEditorPage({ canUpdate }) {
1541
1761
  async function selectTarget(targetUid) {
1542
1762
  const request = ++targetRequest.current;
1543
1763
  setDraft((current) => ({ ...current, targetUid, fields: [] }));
1764
+ setTargetChanged(true);
1544
1765
  setSchema([]);
1545
1766
  setFieldsLoading(true);
1546
1767
  setError("");
@@ -1548,10 +1769,6 @@ function FormEditorPage({ canUpdate }) {
1548
1769
  const attrs = await formApi(client).fields(targetUid);
1549
1770
  if (request !== targetRequest.current) return;
1550
1771
  setSchema(attrs);
1551
- setDraft((current) => ({
1552
- ...current,
1553
- fields: attrs.filter((field) => field.required)
1554
- }));
1555
1772
  } catch (cause) {
1556
1773
  if (request === targetRequest.current) setError(errorMessage(cause));
1557
1774
  } finally {
@@ -1566,39 +1783,86 @@ function FormEditorPage({ canUpdate }) {
1566
1783
  try {
1567
1784
  await action();
1568
1785
  } catch (cause) {
1569
- setError(errorMessage(cause));
1786
+ const message = errorMessage(cause);
1787
+ setError(message);
1788
+ const section = message.startsWith("fields") ? "fields" : message.startsWith("consent") ? "consent" : message.startsWith("notifications") ? "notifications" : "basics";
1789
+ setInvalidTabs([section]);
1790
+ setTab(section);
1570
1791
  } finally {
1571
1792
  lock.current = false;
1572
1793
  setBusy(false);
1573
1794
  }
1574
1795
  }
1575
- if (loading) return /* @__PURE__ */ jsx8(Page3.Loading, {});
1796
+ if (loading) return /* @__PURE__ */ jsx8(Page2.Loading, {});
1576
1797
  const disabled = !canUpdate || busy || fieldsLoading;
1577
1798
  return /* @__PURE__ */ jsxs8(Fragment4, { children: [
1578
1799
  /* @__PURE__ */ jsx8(
1579
- Layouts3.Header,
1800
+ Layouts2.Header,
1580
1801
  {
1581
- title: documentId ? draft.name || "Form" : "Create form",
1582
- subtitle: version ? `Form version ${version} \xB7 assigned by server on every save` : "New forms start at version 1.",
1802
+ title: documentId ? draft.name || t("builder.forms.form") : t("builder.forms.create"),
1803
+ subtitle: /* @__PURE__ */ jsx8(Badge4, { children: version ? formatMessage({ id: "builder.forms.savedVersion" }, { version }) : t("builder.forms.newVersion") }),
1804
+ primaryAction: loaded ? /* @__PURE__ */ jsxs8(Flex6, { gap: 3, children: [
1805
+ /* @__PURE__ */ jsx8(
1806
+ Button5,
1807
+ {
1808
+ variant: "tertiary",
1809
+ disabled: busy,
1810
+ onClick: () => navigate("/plugins/builder/forms"),
1811
+ children: t("builder.common.cancel")
1812
+ }
1813
+ ),
1814
+ canUpdate && /* @__PURE__ */ jsx8(
1815
+ Button5,
1816
+ {
1817
+ type: "submit",
1818
+ form: "builder-form-editor",
1819
+ disabled: !canUpdate || busy || fieldsLoading,
1820
+ children: t(busy ? "builder.common.saving" : "builder.common.save")
1821
+ }
1822
+ )
1823
+ ] }) : void 0,
1583
1824
  navigationAction: /* @__PURE__ */ jsx8(
1584
- Button6,
1825
+ Button5,
1585
1826
  {
1586
1827
  variant: "tertiary",
1587
1828
  onClick: () => navigate("/plugins/builder/forms"),
1588
- children: "Back to forms"
1829
+ children: t("builder.forms.back")
1589
1830
  }
1590
1831
  )
1591
1832
  }
1592
1833
  ),
1593
- /* @__PURE__ */ jsx8(Layouts3.Content, { children: /* @__PURE__ */ jsxs8(Flex7, { direction: "column", alignItems: "stretch", gap: 6, children: [
1834
+ /* @__PURE__ */ jsx8(Layouts2.Content, { children: /* @__PURE__ */ jsxs8(Flex6, { direction: "column", alignItems: "stretch", gap: 6, children: [
1594
1835
  /* @__PURE__ */ jsx8(ErrorNotice, { message: error }),
1595
- !loaded ? /* @__PURE__ */ jsx8(Button6, { onClick: () => setRetry((value) => value + 1), children: "Retry loading form" }) : /* @__PURE__ */ jsxs8(Fragment4, { children: [
1836
+ !loaded ? /* @__PURE__ */ jsx8(Button5, { onClick: () => setRetry((value) => value + 1), children: t("builder.common.retry") }) : /* @__PURE__ */ jsxs8(Fragment4, { children: [
1596
1837
  /* @__PURE__ */ jsx8(
1597
1838
  "form",
1598
1839
  {
1840
+ id: "builder-form-editor",
1841
+ noValidate: true,
1599
1842
  onSubmit: (event) => {
1600
1843
  event.preventDefault();
1601
1844
  if (disabled) return;
1845
+ const failures = [
1846
+ ...!draft.name.trim() || !draft.targetUid || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(draft.slug) || draft.slug.length > 120 || draft.name.length > 120 ? ["basics"] : [],
1847
+ ...!draft.fields.length || missingRequired(draft.fields, schema).length || draft.fields.some(
1848
+ (field) => !field.label.trim() || !schema.some(
1849
+ (item) => item.name === field.name && item.type === field.type
1850
+ )
1851
+ ) ? ["fields"] : [],
1852
+ ...Object.keys(
1853
+ notificationErrors(
1854
+ draft.notifications ?? {},
1855
+ draft.fields
1856
+ )
1857
+ ).length ? ["notifications"] : [],
1858
+ ...!draft.consent.label.trim() || !draft.consent.version.trim() ? ["consent"] : []
1859
+ ];
1860
+ setInvalidTabs(failures);
1861
+ if (failures.length) {
1862
+ setError(t("builder.forms.validationError"));
1863
+ setTab(failures[0]);
1864
+ return;
1865
+ }
1602
1866
  void run(async () => {
1603
1867
  const saved = await formApi(client).save(
1604
1868
  draft,
@@ -1606,127 +1870,223 @@ function FormEditorPage({ canUpdate }) {
1606
1870
  notificationsOpened
1607
1871
  );
1608
1872
  setDraft(saved);
1873
+ setSavedConsent({ ...saved.consent });
1609
1874
  setSavedDraft(JSON.stringify(savePayload(saved, true)));
1610
1875
  setVersion(saved.version);
1876
+ setHasSubmissions(saved.hasSubmissions ?? false);
1877
+ setTargetChanged(false);
1611
1878
  if (!documentId)
1612
1879
  navigate(`/plugins/builder/forms/${saved.documentId}`, {
1613
1880
  replace: true
1614
1881
  });
1615
1882
  });
1616
1883
  },
1617
- children: /* @__PURE__ */ jsxs8(Flex7, { direction: "column", alignItems: "stretch", gap: 6, children: [
1618
- /* @__PURE__ */ jsxs8(Section, { title: "Definition", children: [
1619
- /* @__PURE__ */ jsx8(
1620
- TextField,
1621
- {
1622
- label: "Name",
1623
- required: true,
1624
- disabled,
1625
- value: draft.name,
1626
- onChange: (name) => setDraft({ ...draft, name })
1627
- }
1628
- ),
1629
- /* @__PURE__ */ jsx8(
1630
- TextField,
1631
- {
1632
- label: "Slug",
1633
- required: true,
1634
- disabled: disabled || Boolean(documentId),
1635
- value: draft.slug,
1636
- onChange: (slug) => setDraft({ ...draft, slug })
1637
- }
1638
- ),
1639
- /* @__PURE__ */ jsx8(Typography7, { variant: "pi", children: "Use lowercase letters, numbers and hyphens. The slug cannot change after creation." }),
1640
- /* @__PURE__ */ jsxs8("label", { children: [
1641
- "Target content type",
1642
- " ",
1643
- /* @__PURE__ */ jsxs8(
1644
- "select",
1884
+ children: /* @__PURE__ */ jsxs8(
1885
+ Tabs.Root,
1886
+ {
1887
+ value: tab,
1888
+ onValueChange: setTab,
1889
+ hasError: invalidTabs[0],
1890
+ children: [
1891
+ /* @__PURE__ */ jsx8(Tabs.List, { "aria-label": t("builder.forms.editorTabs"), children: ["basics", "fields", "consent", "notifications"].map(
1892
+ (value) => /* @__PURE__ */ jsxs8(Tabs.Trigger, { value, children: [
1893
+ t(`builder.forms.tabs.${value}`),
1894
+ invalidTabs.includes(value) ? " \u2022" : ""
1895
+ ] }, value)
1896
+ ) }),
1897
+ /* @__PURE__ */ jsx8(Tabs.Content, { value: "basics", children: /* @__PURE__ */ jsxs8(Section, { title: t("builder.forms.definition"), children: [
1898
+ /* @__PURE__ */ jsxs8(
1899
+ Field4.Root,
1900
+ {
1901
+ required: true,
1902
+ error: invalidTabs.includes("basics") && !draft.name.trim() ? t("builder.forms.nameRequired") : void 0,
1903
+ children: [
1904
+ /* @__PURE__ */ jsx8(Field4.Label, { children: t("builder.common.name") }),
1905
+ /* @__PURE__ */ jsx8(
1906
+ TextInput3,
1907
+ {
1908
+ disabled,
1909
+ value: draft.name,
1910
+ maxLength: 120,
1911
+ onChange: (event) => {
1912
+ const name = event.target.value;
1913
+ setDraft({
1914
+ ...draft,
1915
+ name,
1916
+ slug: !documentId && !slugEdited ? suggestSlug(name) : draft.slug
1917
+ });
1918
+ }
1919
+ }
1920
+ ),
1921
+ /* @__PURE__ */ jsx8(Field4.Error, {})
1922
+ ]
1923
+ }
1924
+ ),
1925
+ /* @__PURE__ */ jsxs8(
1926
+ Field4.Root,
1927
+ {
1928
+ required: true,
1929
+ error: invalidTabs.includes("basics") && (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(draft.slug) || draft.slug.length > 120) ? t("builder.forms.slugHint") : void 0,
1930
+ children: [
1931
+ /* @__PURE__ */ jsx8(Field4.Label, { children: t("builder.common.slug") }),
1932
+ /* @__PURE__ */ jsx8(
1933
+ TextInput3,
1934
+ {
1935
+ disabled: disabled || hasSubmissions,
1936
+ value: draft.slug,
1937
+ maxLength: 120,
1938
+ onChange: (event) => {
1939
+ setSlugEdited(true);
1940
+ setDraft({ ...draft, slug: event.target.value });
1941
+ }
1942
+ }
1943
+ ),
1944
+ /* @__PURE__ */ jsx8(Typography7, { variant: "pi", children: t(
1945
+ hasSubmissions ? "builder.forms.slugLocked" : "builder.forms.slugHint"
1946
+ ) }),
1947
+ /* @__PURE__ */ jsx8(Field4.Error, {})
1948
+ ]
1949
+ }
1950
+ ),
1951
+ /* @__PURE__ */ jsxs8(
1952
+ Field4.Root,
1953
+ {
1954
+ required: true,
1955
+ error: invalidTabs.includes("basics") && !draft.targetUid ? t("builder.forms.selectTarget") : void 0,
1956
+ children: [
1957
+ /* @__PURE__ */ jsx8(Field4.Label, { children: t("builder.common.target") }),
1958
+ /* @__PURE__ */ jsx8(
1959
+ Combobox,
1960
+ {
1961
+ clearLabel: t("builder.forms.clearTarget"),
1962
+ disabled,
1963
+ value: draft.targetUid,
1964
+ placeholder: t("builder.forms.selectTarget"),
1965
+ noOptionsMessage: () => t("builder.forms.noTargets"),
1966
+ onChange: (value) => {
1967
+ if (value && value !== draft.targetUid)
1968
+ void selectTarget(value);
1969
+ },
1970
+ children: targets.map((target) => /* @__PURE__ */ jsx8(ComboboxOption, { value: target.uid, children: target.name }, target.uid))
1971
+ }
1972
+ ),
1973
+ /* @__PURE__ */ jsx8(Typography7, { variant: "pi", children: draft.targetUid }),
1974
+ /* @__PURE__ */ jsx8(Field4.Error, {})
1975
+ ]
1976
+ }
1977
+ ),
1978
+ fieldsLoading && /* @__PURE__ */ jsx8(Typography7, { children: t("builder.forms.loadingFields") }),
1979
+ targetChanged && /* @__PURE__ */ jsx8(Typography7, { role: "status", children: t("builder.forms.targetChanged") })
1980
+ ] }) }),
1981
+ /* @__PURE__ */ jsx8(Tabs.Content, { value: "fields", children: /* @__PURE__ */ jsx8(
1982
+ FieldEditor,
1645
1983
  {
1646
- required: true,
1984
+ fields: draft.fields,
1985
+ schema,
1647
1986
  disabled,
1648
- value: draft.targetUid,
1649
- onChange: (event) => void selectTarget(event.target.value),
1650
- style: { padding: 10, width: "100%" },
1651
- children: [
1652
- /* @__PURE__ */ jsx8("option", { value: "", children: "Select a target" }),
1653
- targets.map((target) => /* @__PURE__ */ jsxs8("option", { value: target.uid, children: [
1654
- target.name,
1655
- " \u2014 ",
1656
- target.uid
1657
- ] }, target.uid))
1658
- ]
1987
+ onChange: (fields) => setDraft({ ...draft, fields })
1659
1988
  }
1660
- )
1661
- ] }),
1662
- fieldsLoading && /* @__PURE__ */ jsx8(Typography7, { children: "Loading target fields\u2026" })
1663
- ] }),
1664
- /* @__PURE__ */ jsx8(
1665
- FieldEditor,
1666
- {
1667
- fields: draft.fields,
1668
- schema,
1669
- disabled,
1670
- onChange: (fields) => setDraft({ ...draft, fields })
1671
- }
1672
- ),
1673
- /* @__PURE__ */ jsxs8(Section, { title: "Consent", children: [
1674
- /* @__PURE__ */ jsx8(
1675
- TextField,
1676
- {
1677
- label: "Consent label",
1678
- required: true,
1679
- multiline: true,
1680
- disabled,
1681
- value: draft.consent.label,
1682
- onChange: (label) => setDraft({
1683
- ...draft,
1684
- consent: { ...draft.consent, label }
1685
- })
1686
- }
1687
- ),
1688
- /* @__PURE__ */ jsx8(
1689
- TextField,
1690
- {
1691
- label: "Consent version",
1692
- required: true,
1693
- disabled,
1694
- value: draft.consent.version,
1695
- onChange: (consentVersion) => setDraft({
1696
- ...draft,
1697
- consent: {
1698
- ...draft.consent,
1699
- version: consentVersion
1989
+ ) }),
1990
+ /* @__PURE__ */ jsx8(Tabs.Content, { value: "consent", children: /* @__PURE__ */ jsxs8(Section, { title: t("builder.forms.consent"), children: [
1991
+ /* @__PURE__ */ jsxs8(
1992
+ Field4.Root,
1993
+ {
1994
+ required: true,
1995
+ error: invalidTabs.includes("consent") && !draft.consent.label.trim() ? t("builder.forms.requiredValue") : void 0,
1996
+ children: [
1997
+ /* @__PURE__ */ jsx8(Field4.Label, { children: t("builder.forms.consentLabel") }),
1998
+ /* @__PURE__ */ jsx8(
1999
+ Textarea2,
2000
+ {
2001
+ disabled,
2002
+ value: draft.consent.label,
2003
+ onChange: (event) => setDraft({
2004
+ ...draft,
2005
+ consent: {
2006
+ ...draft.consent,
2007
+ label: event.target.value
2008
+ }
2009
+ })
2010
+ }
2011
+ ),
2012
+ /* @__PURE__ */ jsx8(Field4.Error, {})
2013
+ ]
1700
2014
  }
1701
- })
1702
- }
1703
- ),
1704
- /* @__PURE__ */ jsx8(Typography7, { variant: "pi", children: "Consent version identifies the terms accepted by the visitor and is separate from the server form version." })
1705
- ] }),
1706
- /* @__PURE__ */ jsx8(
1707
- NotificationEditor,
1708
- {
1709
- value: draft.notifications ?? {},
1710
- fields: draft.fields,
1711
- opened: notificationsOpened,
1712
- onOpen: () => setNotificationsOpened(true),
1713
- onChange: (notifications) => setDraft({ ...draft, notifications }),
1714
- disabled,
1715
- saveError: error
1716
- }
1717
- ),
1718
- canUpdate && /* @__PURE__ */ jsx8(
1719
- Button6,
1720
- {
1721
- type: "submit",
1722
- disabled: disabled || !draft.targetUid || !draft.fields.length,
1723
- children: busy ? "Saving\u2026" : "Save form"
1724
- }
1725
- )
1726
- ] })
2015
+ ),
2016
+ savedConsent !== null && savedConsent.label !== draft.consent.label && /* @__PURE__ */ jsx8(
2017
+ Alert2,
2018
+ {
2019
+ variant: "warning",
2020
+ title: t("builder.forms.consentChangedTitle"),
2021
+ closeLabel: t("builder.common.cancel"),
2022
+ children: t("builder.forms.consentChanged")
2023
+ }
2024
+ ),
2025
+ /* @__PURE__ */ jsxs8(
2026
+ Field4.Root,
2027
+ {
2028
+ required: true,
2029
+ error: invalidTabs.includes("consent") && !draft.consent.version.trim() ? t("builder.forms.requiredValue") : void 0,
2030
+ children: [
2031
+ /* @__PURE__ */ jsx8(Field4.Label, { children: t("builder.forms.consentVersion") }),
2032
+ /* @__PURE__ */ jsx8(
2033
+ TextInput3,
2034
+ {
2035
+ disabled,
2036
+ value: draft.consent.version,
2037
+ onChange: (event) => setDraft({
2038
+ ...draft,
2039
+ consent: {
2040
+ ...draft.consent,
2041
+ version: event.target.value
2042
+ }
2043
+ })
2044
+ }
2045
+ ),
2046
+ /* @__PURE__ */ jsx8(Field4.Error, {})
2047
+ ]
2048
+ }
2049
+ ),
2050
+ /* @__PURE__ */ jsx8(
2051
+ Button5,
2052
+ {
2053
+ type: "button",
2054
+ variant: "tertiary",
2055
+ disabled,
2056
+ onClick: () => setDraft({
2057
+ ...draft,
2058
+ consent: {
2059
+ ...draft.consent,
2060
+ version: suggestConsentVersion(
2061
+ draft.consent.version,
2062
+ /* @__PURE__ */ new Date(),
2063
+ savedConsent?.version
2064
+ )
2065
+ }
2066
+ }),
2067
+ children: t("builder.forms.suggestConsentVersion")
2068
+ }
2069
+ ),
2070
+ /* @__PURE__ */ jsx8(Typography7, { variant: "pi", children: t("builder.forms.consentHint") })
2071
+ ] }) }),
2072
+ /* @__PURE__ */ jsx8(Tabs.Content, { value: "notifications", children: /* @__PURE__ */ jsx8(
2073
+ NotificationEditor,
2074
+ {
2075
+ value: draft.notifications ?? {},
2076
+ fields: draft.fields,
2077
+ opened: notificationsOpened,
2078
+ onOpen: () => setNotificationsOpened(true),
2079
+ onChange: (notifications) => setDraft({ ...draft, notifications }),
2080
+ disabled,
2081
+ saveError: error
2082
+ }
2083
+ ) })
2084
+ ]
2085
+ }
2086
+ )
1727
2087
  }
1728
2088
  ),
1729
- notificationsOpened && /* @__PURE__ */ jsx8(
2089
+ tab === "notifications" && notificationsOpened && /* @__PURE__ */ jsx8(
1730
2090
  NotificationTest,
1731
2091
  {
1732
2092
  fields: draft.fields,
@@ -1734,54 +2094,7 @@ function FormEditorPage({ canUpdate }) {
1734
2094
  send: (sample) => formApi(client).testSend(documentId, sample)
1735
2095
  },
1736
2096
  savedDraft
1737
- ),
1738
- documentId && /* @__PURE__ */ jsxs8(Section, { title: "Portable definition", children: [
1739
- /* @__PURE__ */ jsx8(
1740
- Button6,
1741
- {
1742
- disabled: busy,
1743
- onClick: () => void run(
1744
- async () => downloadForm(
1745
- await formApi(client).export(documentId),
1746
- draft.slug
1747
- )
1748
- ),
1749
- children: "Export saved form JSON"
1750
- }
1751
- ),
1752
- /* @__PURE__ */ jsx8(Typography7, { variant: "pi", children: "Export uses the saved definition. Notification credentials are masked by the server." }),
1753
- canUpdate && /* @__PURE__ */ jsx8(
1754
- ImportForm,
1755
- {
1756
- replace: true,
1757
- disabled: busy || fieldsLoading,
1758
- onImport: async (value) => {
1759
- if (lock.current) return;
1760
- lock.current = true;
1761
- setBusy(true);
1762
- try {
1763
- const saved = await formApi(client).import(
1764
- value,
1765
- documentId
1766
- );
1767
- setDraft(saved);
1768
- setSavedDraft(
1769
- JSON.stringify(savePayload(saved, true))
1770
- );
1771
- setNotificationsOpened(false);
1772
- setError("");
1773
- setVersion(saved.version);
1774
- setSchema(
1775
- await formApi(client).fields(saved.targetUid)
1776
- );
1777
- } finally {
1778
- lock.current = false;
1779
- setBusy(false);
1780
- }
1781
- }
1782
- }
1783
- )
1784
- ] })
2097
+ )
1785
2098
  ] })
1786
2099
  ] }) })
1787
2100
  ] });
@@ -1793,79 +2106,674 @@ var init_form_editor_page = __esm({
1793
2106
  init_form();
1794
2107
  init_ui();
1795
2108
  init_field_editor();
2109
+ init_notifications();
1796
2110
  init_notification_editor();
1797
- init_import_form();
1798
2111
  }
1799
2112
  });
1800
2113
 
1801
- // admin/src/app.tsx
1802
- var app_exports = {};
1803
- __export(app_exports, {
1804
- SubmissionsApp: () => SubmissionsApp,
1805
- default: () => App
2114
+ // admin/src/submission.ts
2115
+ var submissionStatuses;
2116
+ var init_submission = __esm({
2117
+ "admin/src/submission.ts"() {
2118
+ "use strict";
2119
+ submissionStatuses = [
2120
+ "received",
2121
+ "reviewing",
2122
+ "responded",
2123
+ "closed"
2124
+ ];
2125
+ }
1806
2126
  });
1807
- import { Navigate, Route, Routes } from "react-router-dom";
1808
- import { Layouts as Layouts4, Page as Page4, useRBAC as useRBAC2 } from "@strapi/strapi/admin";
1809
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1810
- function FormsApp() {
1811
- const { allowedActions, isLoading } = useRBAC2(
1812
- ["read", "update"].map((action) => ({
1813
- action: `plugin::builder.forms.${action}`,
1814
- subject: null
1815
- }))
2127
+
2128
+ // admin/src/submission-detail-modal.tsx
2129
+ import { useIntl as useIntl7 } from "react-intl";
2130
+ import {
2131
+ Badge as Badge5,
2132
+ Button as Button6,
2133
+ Field as Field5,
2134
+ Flex as Flex7,
2135
+ Modal as Modal2,
2136
+ SingleSelect as SingleSelect4,
2137
+ SingleSelectOption as SingleSelectOption4,
2138
+ Typography as Typography8
2139
+ } from "@strapi/design-system";
2140
+ import { Page as Page3 } from "@strapi/strapi/admin";
2141
+ import { Link as Link3 } from "react-router-dom";
2142
+ import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2143
+ function SubmissionDetailModal({
2144
+ id,
2145
+ detail,
2146
+ loading,
2147
+ busy,
2148
+ error,
2149
+ canUpdate,
2150
+ onClose,
2151
+ onRetry,
2152
+ onStatus,
2153
+ onRedeliver
2154
+ }) {
2155
+ const { formatMessage, formatDate, formatTime } = useIntl7();
2156
+ const t = (id2) => formatMessage({ id: id2 });
2157
+ const time = (value) => value ? `${formatDate(value)} ${formatTime(value, { hour: "2-digit", minute: "2-digit", second: "2-digit" })}` : t("builder.submissions.unavailable");
2158
+ const current = detail?.currentRecord;
2159
+ const valueText = (value) => typeof value === "boolean" ? t(`builder.submissions.boolean.${value}`) : value === null ? t("builder.submissions.noValue") : String(value ?? "");
2160
+ return /* @__PURE__ */ jsx9(
2161
+ Modal2.Root,
2162
+ {
2163
+ open: true,
2164
+ onOpenChange: (open) => {
2165
+ if (!open && !busy) onClose();
2166
+ },
2167
+ children: /* @__PURE__ */ jsxs9(Modal2.Content, { children: [
2168
+ /* @__PURE__ */ jsx9(Modal2.Header, { closeLabel: t("builder.common.close"), children: /* @__PURE__ */ jsx9(Modal2.Title, { children: formatMessage({ id: "builder.submissions.detailTitle" }, { id }) }) }),
2169
+ /* @__PURE__ */ jsx9(Modal2.Body, { children: /* @__PURE__ */ jsxs9(Flex7, { direction: "column", alignItems: "stretch", gap: 5, children: [
2170
+ /* @__PURE__ */ jsx9(ErrorNotice, { message: error }),
2171
+ error && /* @__PURE__ */ jsx9(Button6, { disabled: loading || busy, onClick: onRetry, children: t("builder.common.retry") }),
2172
+ loading ? /* @__PURE__ */ jsx9(Page3.Loading, {}) : detail && /* @__PURE__ */ jsxs9(Fragment5, { children: [
2173
+ /* @__PURE__ */ jsxs9(Typography8, { children: [
2174
+ detail.form,
2175
+ " \xB7 ",
2176
+ time(detail.createdAt)
2177
+ ] }),
2178
+ /* @__PURE__ */ jsx9(Typography8, { tag: "h3", variant: "delta", children: t("builder.submissions.currentValues") }),
2179
+ /* @__PURE__ */ jsx9(Typography8, { children: t("builder.submissions.currentValuesNotice") }),
2180
+ !current?.available || !current.fields.length ? /* @__PURE__ */ jsx9(Typography8, { children: t("builder.submissions.unavailable") }) : current.fields.map((field) => /* @__PURE__ */ jsxs9(
2181
+ Flex7,
2182
+ {
2183
+ direction: "column",
2184
+ alignItems: "stretch",
2185
+ gap: 1,
2186
+ children: [
2187
+ /* @__PURE__ */ jsxs9(Typography8, { fontWeight: "bold", children: [
2188
+ field.label,
2189
+ " (",
2190
+ field.name,
2191
+ ")"
2192
+ ] }),
2193
+ /* @__PURE__ */ jsx9(
2194
+ Typography8,
2195
+ {
2196
+ style: {
2197
+ whiteSpace: "pre-wrap",
2198
+ overflowWrap: "anywhere"
2199
+ },
2200
+ children: field.available ? valueText(field.value) : t("builder.submissions.unavailable")
2201
+ }
2202
+ )
2203
+ ]
2204
+ },
2205
+ field.name
2206
+ )),
2207
+ /* @__PURE__ */ jsx9(Typography8, { tag: "h3", variant: "delta", children: t("builder.submissions.consent") }),
2208
+ /* @__PURE__ */ jsx9(Typography8, { children: formatMessage(
2209
+ { id: "builder.submissions.consentReceipt" },
2210
+ {
2211
+ version: detail.consentVersion,
2212
+ time: time(detail.consentAt)
2213
+ }
2214
+ ) }),
2215
+ /* @__PURE__ */ jsx9(Typography8, { tag: "h3", variant: "delta", children: t("builder.submissions.sourcePath") }),
2216
+ /* @__PURE__ */ jsx9(Typography8, { style: { overflowWrap: "anywhere" }, children: current?.sourcePath.available ? current.sourcePath.value : t("builder.submissions.unavailable") }),
2217
+ /* @__PURE__ */ jsx9(Typography8, { tag: "h3", variant: "delta", children: t("builder.submissions.metadata") }),
2218
+ current?.metadata.available ? Object.entries(current.metadata.value ?? {}).map(
2219
+ ([key, value]) => /* @__PURE__ */ jsxs9(
2220
+ Typography8,
2221
+ {
2222
+ style: { overflowWrap: "anywhere" },
2223
+ children: [
2224
+ key,
2225
+ ": ",
2226
+ valueText(value)
2227
+ ]
2228
+ },
2229
+ key
2230
+ )
2231
+ ) : /* @__PURE__ */ jsx9(Typography8, { children: t("builder.submissions.unavailable") }),
2232
+ current?.contentManagerUrl && /* @__PURE__ */ jsx9(Link3, { to: current.contentManagerUrl, children: t("builder.submissions.openRecord") }),
2233
+ /* @__PURE__ */ jsxs9(Field5.Root, { name: "processingStatus", children: [
2234
+ /* @__PURE__ */ jsx9(Field5.Label, { children: t("builder.submissions.processingStatus") }),
2235
+ /* @__PURE__ */ jsx9(
2236
+ SingleSelect4,
2237
+ {
2238
+ "aria-label": t("builder.submissions.processingStatus"),
2239
+ value: detail.status,
2240
+ disabled: !canUpdate || busy,
2241
+ onChange: (value) => {
2242
+ const status = submissionStatuses.find(
2243
+ (status2) => status2 === value
2244
+ );
2245
+ if (status) onStatus(status);
2246
+ },
2247
+ children: submissionStatuses.map((status) => /* @__PURE__ */ jsx9(SingleSelectOption4, { value: status, children: t(`builder.submissions.status.${status}`) }, status))
2248
+ }
2249
+ )
2250
+ ] }),
2251
+ /* @__PURE__ */ jsx9(Typography8, { tag: "h3", variant: "delta", children: t("builder.submissions.deliveries") }),
2252
+ !detail.deliveries.length && /* @__PURE__ */ jsx9(Typography8, { children: t("builder.submissions.noDeliveries") }),
2253
+ detail.deliveries.map((delivery, i) => /* @__PURE__ */ jsxs9(Flex7, { direction: "column", alignItems: "start", gap: 2, children: [
2254
+ /* @__PURE__ */ jsxs9(Badge5, { children: [
2255
+ t(`builder.submissions.channel.${delivery.channel}`),
2256
+ ":",
2257
+ " ",
2258
+ t(`builder.submissions.delivery.${delivery.status}`)
2259
+ ] }),
2260
+ /* @__PURE__ */ jsx9(Typography8, { children: formatMessage(
2261
+ { id: "builder.submissions.deliveryAttempt" },
2262
+ {
2263
+ attempts: delivery.attempts,
2264
+ time: time(delivery.sentAt)
2265
+ }
2266
+ ) }),
2267
+ delivery.lastError && /* @__PURE__ */ jsx9(Typography8, { children: t("builder.submissions.deliveryError") })
2268
+ ] }, i)),
2269
+ /* @__PURE__ */ jsx9(
2270
+ Button6,
2271
+ {
2272
+ disabled: !canUpdate || busy || !detail.deliveries.some(
2273
+ (delivery) => delivery.channel !== "afterSave" && delivery.status === "failed"
2274
+ ),
2275
+ onClick: onRedeliver,
2276
+ children: t("builder.submissions.retryNotifications")
2277
+ }
2278
+ ),
2279
+ !canUpdate && /* @__PURE__ */ jsx9(Typography8, { children: t("builder.submissions.readOnly") })
2280
+ ] })
2281
+ ] }) }),
2282
+ /* @__PURE__ */ jsx9(Modal2.Footer, { children: /* @__PURE__ */ jsx9(Button6, { variant: "tertiary", disabled: busy, onClick: onClose, children: t("builder.common.close") }) })
2283
+ ] })
2284
+ }
1816
2285
  );
1817
- if (isLoading) return /* @__PURE__ */ jsx9(Page4.Loading, {});
1818
- if (!allowedActions.canRead) return /* @__PURE__ */ jsx9(Page4.NoPermissions, {});
1819
- const canUpdate = allowedActions.canUpdate === true;
1820
- return /* @__PURE__ */ jsx9(Layouts4.Root, { children: /* @__PURE__ */ jsxs9(Routes, { children: [
1821
- /* @__PURE__ */ jsx9(Route, { index: true, element: /* @__PURE__ */ jsx9(Navigate, { to: "forms", replace: true }) }),
1822
- /* @__PURE__ */ jsx9(Route, { path: "forms", element: /* @__PURE__ */ jsx9(FormListPage, { canUpdate }) }),
1823
- /* @__PURE__ */ jsx9(
1824
- Route,
1825
- {
1826
- path: "forms/new",
1827
- element: canUpdate ? /* @__PURE__ */ jsx9(FormEditorPage, { canUpdate: true }) : /* @__PURE__ */ jsx9(Page4.NoPermissions, {})
2286
+ }
2287
+ var init_submission_detail_modal = __esm({
2288
+ "admin/src/submission-detail-modal.tsx"() {
2289
+ "use strict";
2290
+ init_submission();
2291
+ init_ui();
2292
+ }
2293
+ });
2294
+
2295
+ // admin/src/submission-page.tsx
2296
+ import { useIntl as useIntl8 } from "react-intl";
2297
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4, useState as useState7 } from "react";
2298
+ import {
2299
+ Badge as Badge6,
2300
+ Box as Box3,
2301
+ Button as Button7,
2302
+ DatePicker,
2303
+ EmptyStateLayout as EmptyStateLayout2,
2304
+ Field as Field6,
2305
+ Flex as Flex8,
2306
+ SingleSelect as SingleSelect5,
2307
+ SingleSelectOption as SingleSelectOption5,
2308
+ Status,
2309
+ Table as Table3,
2310
+ Tbody as Tbody3,
2311
+ Td as Td3,
2312
+ Th as Th3,
2313
+ Thead as Thead3,
2314
+ Tr as Tr3,
2315
+ Typography as Typography9
2316
+ } from "@strapi/design-system";
2317
+ import {
2318
+ Layouts as Layouts3,
2319
+ Page as Page4,
2320
+ Pagination,
2321
+ useFetchClient as useFetchClient4
2322
+ } from "@strapi/strapi/admin";
2323
+ import { useSearchParams } from "react-router-dom";
2324
+ import { Fragment as Fragment6, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2325
+ function submissionCalendarDate(value) {
2326
+ return value ? `${String(value.getFullYear()).padStart(4, "0")}-${String(value.getMonth() + 1).padStart(2, "0")}-${String(value.getDate()).padStart(2, "0")}` : "";
2327
+ }
2328
+ function SubmissionPage({ canUpdate }) {
2329
+ const { formatMessage, formatDate, formatTime, locale } = useIntl8();
2330
+ const t = useCallback3((id) => formatMessage({ id }), [formatMessage]);
2331
+ const client = useFetchClient4();
2332
+ const { get } = client;
2333
+ const [search, setSearch] = useSearchParams();
2334
+ const page = Number(search.get("page") ?? 1);
2335
+ const pageSize = Number(search.get("pageSize") ?? 25);
2336
+ const [slug, setSlug] = useState7("");
2337
+ const [status, setStatus] = useState7("");
2338
+ const [dateFrom, setDateFrom] = useState7();
2339
+ const [dateTo, setDateTo] = useState7();
2340
+ const [options, setOptions] = useState7([]);
2341
+ const [result, setResult] = useState7(null);
2342
+ const [loading, setLoading] = useState7(true);
2343
+ const [error, setError] = useState7("");
2344
+ const [selectedId, setSelectedId] = useState7(null);
2345
+ const [detail, setDetail] = useState7(null);
2346
+ const [detailLoading, setDetailLoading] = useState7(false);
2347
+ const [detailError, setDetailError] = useState7("");
2348
+ const [busy, setBusy] = useState7(false);
2349
+ const lock = useRef4(false);
2350
+ const generation = useRef4(0);
2351
+ const detailGeneration = useRef4(0);
2352
+ const api = submissionApi(client);
2353
+ const from = submissionCalendarDate(dateFrom);
2354
+ const to = submissionCalendarDate(dateTo);
2355
+ const load = useCallback3(async () => {
2356
+ const current = ++generation.current;
2357
+ setLoading(true);
2358
+ setError("");
2359
+ try {
2360
+ const [response, filters] = await Promise.all([
2361
+ get("/plugins/builder/submissions", {
2362
+ params: {
2363
+ page,
2364
+ pageSize,
2365
+ ...slug ? { slug } : {},
2366
+ ...status ? { status } : {},
2367
+ ...from ? { dateFrom: from } : {},
2368
+ ...to ? { dateTo: to } : {}
2369
+ }
2370
+ }),
2371
+ get(
2372
+ "/plugins/builder/submissions/filter-options"
2373
+ )
2374
+ ]);
2375
+ if (current === generation.current) {
2376
+ setResult(response.data);
2377
+ setOptions(filters.data.data);
1828
2378
  }
1829
- ),
1830
- /* @__PURE__ */ jsx9(
1831
- Route,
1832
- {
1833
- path: "forms/:documentId",
1834
- element: /* @__PURE__ */ jsx9(FormEditorPage, { canUpdate })
2379
+ } catch (cause) {
2380
+ if (current === generation.current) {
2381
+ setError(
2382
+ errorMessage(cause) === "invalid_submission_query" ? t("builder.submissions.invalidQuery") : t("builder.common.requestError")
2383
+ );
2384
+ setResult(null);
1835
2385
  }
1836
- ),
1837
- /* @__PURE__ */ jsx9(
1838
- Route,
2386
+ } finally {
2387
+ if (current === generation.current) setLoading(false);
2388
+ }
2389
+ }, [get, page, pageSize, slug, status, from, to, t]);
2390
+ useEffect4(() => {
2391
+ detailGeneration.current++;
2392
+ setSelectedId(null);
2393
+ setDetail(null);
2394
+ void load();
2395
+ return () => {
2396
+ generation.current++;
2397
+ detailGeneration.current++;
2398
+ };
2399
+ }, [load]);
2400
+ function resetPage() {
2401
+ setSearch((previous) => {
2402
+ const next = new URLSearchParams(previous);
2403
+ next.set("page", "1");
2404
+ return next;
2405
+ });
2406
+ }
2407
+ async function openDetail(id) {
2408
+ const current = ++detailGeneration.current;
2409
+ setSelectedId(id);
2410
+ setDetail(null);
2411
+ setDetailError("");
2412
+ setDetailLoading(true);
2413
+ try {
2414
+ const next = await api.get(id);
2415
+ if (current === detailGeneration.current) setDetail(next);
2416
+ } catch {
2417
+ if (current === detailGeneration.current)
2418
+ setDetailError(t("builder.common.requestError"));
2419
+ } finally {
2420
+ if (current === detailGeneration.current) setDetailLoading(false);
2421
+ }
2422
+ }
2423
+ async function mutate(action) {
2424
+ if (!canUpdate || lock.current || !selectedId) return;
2425
+ lock.current = true;
2426
+ setBusy(true);
2427
+ setDetailError("");
2428
+ const id = selectedId;
2429
+ const current = detailGeneration.current;
2430
+ let failure = "";
2431
+ try {
2432
+ await action();
2433
+ } catch {
2434
+ failure = t("builder.common.requestError");
2435
+ }
2436
+ try {
2437
+ const next = await api.get(id);
2438
+ if (current === detailGeneration.current) setDetail(next);
2439
+ } catch {
2440
+ if (current === detailGeneration.current) setDetail(null);
2441
+ failure ||= t("builder.common.requestError");
2442
+ }
2443
+ await load();
2444
+ if (current === detailGeneration.current) setDetailError(failure);
2445
+ lock.current = false;
2446
+ setBusy(false);
2447
+ }
2448
+ const formName = (slug2) => options.find((option) => option.slug === slug2)?.name ?? slug2;
2449
+ return /* @__PURE__ */ jsxs10(Fragment6, { children: [
2450
+ /* @__PURE__ */ jsx10(
2451
+ Layouts3.Header,
1839
2452
  {
1840
- path: "*",
1841
- element: /* @__PURE__ */ jsx9(Page4.Error, { content: "Form page not found." })
2453
+ title: t("builder.submissions.title"),
2454
+ subtitle: t("builder.submissions.description")
1842
2455
  }
1843
- )
1844
- ] }) });
1845
- }
1846
- function SubmissionsApp() {
1847
- const { allowedActions, isLoading } = useRBAC2(
1848
- ["read", "update"].map((action) => ({
1849
- action: `plugin::builder.submissions.${action}`,
1850
- subject: null
1851
- }))
1852
- );
1853
- if (isLoading) return /* @__PURE__ */ jsx9(Page4.Loading, {});
1854
- if (!allowedActions.canRead) return /* @__PURE__ */ jsx9(Page4.NoPermissions, {});
1855
- return /* @__PURE__ */ jsx9(Layouts4.Root, { children: /* @__PURE__ */ jsx9(SubmissionPage, { canUpdate: allowedActions.canUpdate === true }) });
2456
+ ),
2457
+ /* @__PURE__ */ jsxs10(Layouts3.Content, { children: [
2458
+ /* @__PURE__ */ jsxs10(Flex8, { direction: "column", alignItems: "stretch", gap: 6, children: [
2459
+ /* @__PURE__ */ jsx10(ErrorNotice, { message: error }),
2460
+ error && /* @__PURE__ */ jsx10(Button7, { disabled: loading || busy, onClick: () => void load(), children: t("builder.common.retry") }),
2461
+ /* @__PURE__ */ jsxs10(Section, { title: t("builder.submissions.filters"), children: [
2462
+ /* @__PURE__ */ jsx10(Typography9, { children: t("builder.submissions.filters.utc") }),
2463
+ /* @__PURE__ */ jsxs10(Flex8, { gap: 4, wrap: "wrap", alignItems: "end", children: [
2464
+ /* @__PURE__ */ jsxs10(Field6.Root, { name: "formFilter", children: [
2465
+ /* @__PURE__ */ jsx10(Field6.Label, { children: t("builder.submissions.filters.form") }),
2466
+ /* @__PURE__ */ jsxs10(
2467
+ SingleSelect5,
2468
+ {
2469
+ value: slug,
2470
+ disabled: busy,
2471
+ onChange: (value) => {
2472
+ setSlug(String(value));
2473
+ resetPage();
2474
+ },
2475
+ children: [
2476
+ /* @__PURE__ */ jsx10(SingleSelectOption5, { value: "", children: t("builder.submissions.filters.allForms") }),
2477
+ options.map((option) => /* @__PURE__ */ jsxs10(SingleSelectOption5, { value: option.slug, children: [
2478
+ option.name,
2479
+ " (",
2480
+ option.slug,
2481
+ ")"
2482
+ ] }, option.slug))
2483
+ ]
2484
+ }
2485
+ )
2486
+ ] }),
2487
+ /* @__PURE__ */ jsxs10(Field6.Root, { name: "statusFilter", children: [
2488
+ /* @__PURE__ */ jsx10(Field6.Label, { children: t("builder.common.status") }),
2489
+ /* @__PURE__ */ jsxs10(
2490
+ SingleSelect5,
2491
+ {
2492
+ value: status,
2493
+ disabled: busy,
2494
+ onChange: (value) => {
2495
+ setStatus(String(value));
2496
+ resetPage();
2497
+ },
2498
+ children: [
2499
+ /* @__PURE__ */ jsx10(SingleSelectOption5, { value: "", children: t("builder.submissions.filters.allStatuses") }),
2500
+ submissionStatuses.map((value) => /* @__PURE__ */ jsx10(SingleSelectOption5, { value, children: t(`builder.submissions.status.${value}`) }, value))
2501
+ ]
2502
+ }
2503
+ )
2504
+ ] }),
2505
+ ["from", "to"].map((key) => /* @__PURE__ */ jsxs10(Field6.Root, { name: `date-${key}`, children: [
2506
+ /* @__PURE__ */ jsx10(Field6.Label, { children: t(`builder.submissions.filters.${key}`) }),
2507
+ /* @__PURE__ */ jsx10(
2508
+ DatePicker,
2509
+ {
2510
+ locale,
2511
+ value: (key === "from" ? from : to) ? /* @__PURE__ */ new Date(`${key === "from" ? from : to}T00:00:00.000Z`) : void 0,
2512
+ disabled: busy,
2513
+ clearLabel: t("builder.submissions.filters.clearDate"),
2514
+ calendarLabel: t("builder.submissions.filters.calendar"),
2515
+ monthSelectLabel: t("builder.submissions.filters.month"),
2516
+ yearSelectLabel: t("builder.submissions.filters.year"),
2517
+ onChange: (value) => {
2518
+ (key === "from" ? setDateFrom : setDateTo)(value);
2519
+ resetPage();
2520
+ },
2521
+ onClear: () => {
2522
+ (key === "from" ? setDateFrom : setDateTo)(void 0);
2523
+ resetPage();
2524
+ }
2525
+ }
2526
+ )
2527
+ ] }, key))
2528
+ ] })
2529
+ ] }),
2530
+ /* @__PURE__ */ jsxs10(Section, { title: t("builder.submissions.title"), children: [
2531
+ loading ? /* @__PURE__ */ jsx10(Page4.Loading, {}) : !error && !result?.data.length ? /* @__PURE__ */ jsx10(EmptyStateLayout2, { content: t("builder.submissions.empty") }) : result && /* @__PURE__ */ jsx10(Box3, { overflow: "auto", children: /* @__PURE__ */ jsxs10(Table3, { colCount: 6, rowCount: result.data.length, children: [
2532
+ /* @__PURE__ */ jsx10(Thead3, { children: /* @__PURE__ */ jsx10(Tr3, { children: [
2533
+ "builder.submissions.receipt",
2534
+ "builder.forms.form",
2535
+ "builder.submissions.received",
2536
+ "builder.common.status",
2537
+ "builder.submissions.deliveries",
2538
+ "builder.common.actions"
2539
+ ].map((label) => /* @__PURE__ */ jsx10(Th3, { children: /* @__PURE__ */ jsx10(Typography9, { variant: "sigma", children: t(label) }) }, label)) }) }),
2540
+ /* @__PURE__ */ jsx10(Tbody3, { children: result.data.map((row) => /* @__PURE__ */ jsxs10(Tr3, { children: [
2541
+ /* @__PURE__ */ jsx10(Td3, { children: /* @__PURE__ */ jsx10(Typography9, { children: row.documentId }) }),
2542
+ /* @__PURE__ */ jsx10(Td3, { children: /* @__PURE__ */ jsxs10(
2543
+ Typography9,
2544
+ {
2545
+ style: {
2546
+ overflowWrap: "anywhere",
2547
+ whiteSpace: "normal"
2548
+ },
2549
+ children: [
2550
+ formName(row.form),
2551
+ /* @__PURE__ */ jsx10("br", {}),
2552
+ row.form
2553
+ ]
2554
+ }
2555
+ ) }),
2556
+ /* @__PURE__ */ jsx10(Td3, { children: /* @__PURE__ */ jsxs10(Typography9, { children: [
2557
+ formatDate(row.createdAt),
2558
+ " ",
2559
+ formatTime(row.createdAt)
2560
+ ] }) }),
2561
+ /* @__PURE__ */ jsx10(Td3, { children: /* @__PURE__ */ jsx10(Status, { variant: statusVariants[row.status], children: /* @__PURE__ */ jsx10(Typography9, { children: t(`builder.submissions.status.${row.status}`) }) }) }),
2562
+ /* @__PURE__ */ jsx10(Td3, { children: /* @__PURE__ */ jsx10(Flex8, { gap: 2, wrap: "wrap", children: row.deliveries.length ? row.deliveries.map((delivery, i) => /* @__PURE__ */ jsxs10(Badge6, { children: [
2563
+ t(
2564
+ `builder.submissions.channel.${delivery.channel}`
2565
+ ),
2566
+ ":",
2567
+ " ",
2568
+ t(
2569
+ `builder.submissions.delivery.${delivery.status}`
2570
+ )
2571
+ ] }, i)) : /* @__PURE__ */ jsx10(Typography9, { children: t("builder.submissions.noDeliveries") }) }) }),
2572
+ /* @__PURE__ */ jsx10(Td3, { children: /* @__PURE__ */ jsx10(
2573
+ Button7,
2574
+ {
2575
+ disabled: busy,
2576
+ "aria-label": formatMessage(
2577
+ { id: "builder.submissions.viewReceipt" },
2578
+ { id: row.documentId }
2579
+ ),
2580
+ onClick: () => void openDetail(row.documentId),
2581
+ children: t("builder.common.view")
2582
+ }
2583
+ ) })
2584
+ ] }, row.documentId)) })
2585
+ ] }) }),
2586
+ !loading && !error && result && /* @__PURE__ */ jsxs10(
2587
+ Pagination.Root,
2588
+ {
2589
+ defaultPageSize: 25,
2590
+ pageCount: result.meta.pagination.pageCount,
2591
+ total: result.meta.pagination.total,
2592
+ children: [
2593
+ /* @__PURE__ */ jsx10(Pagination.PageSize, { options: ["10", "25", "50", "100"] }),
2594
+ /* @__PURE__ */ jsx10(Pagination.Links, {})
2595
+ ]
2596
+ }
2597
+ )
2598
+ ] })
2599
+ ] }),
2600
+ selectedId && /* @__PURE__ */ jsx10(
2601
+ SubmissionDetailModal,
2602
+ {
2603
+ id: selectedId,
2604
+ detail,
2605
+ loading: detailLoading,
2606
+ busy,
2607
+ error: detailError,
2608
+ canUpdate,
2609
+ onClose: () => {
2610
+ detailGeneration.current++;
2611
+ setSelectedId(null);
2612
+ setDetail(null);
2613
+ },
2614
+ onRetry: () => void openDetail(selectedId),
2615
+ onStatus: (status2) => void mutate(() => api.status(selectedId, status2)),
2616
+ onRedeliver: () => void mutate(() => api.redeliver(selectedId))
2617
+ }
2618
+ )
2619
+ ] })
2620
+ ] });
1856
2621
  }
2622
+ var statusVariants;
2623
+ var init_submission_page = __esm({
2624
+ "admin/src/submission-page.tsx"() {
2625
+ "use strict";
2626
+ init_form();
2627
+ init_api();
2628
+ init_submission();
2629
+ init_submission_detail_modal();
2630
+ init_ui();
2631
+ statusVariants = {
2632
+ received: "primary",
2633
+ reviewing: "warning",
2634
+ responded: "success",
2635
+ closed: "neutral"
2636
+ };
2637
+ }
2638
+ });
2639
+
2640
+ // admin/src/app.tsx
2641
+ var app_exports = {};
2642
+ __export(app_exports, {
2643
+ default: () => App
2644
+ });
2645
+ import { Navigate, NavLink, Route, Routes } from "react-router-dom";
2646
+ import { Box as Box4, Divider, Flex as Flex9, Link as Link4 } from "@strapi/design-system";
2647
+ import { Layouts as Layouts4, Page as Page5, SubNav, useRBAC as useRBAC2 } from "@strapi/strapi/admin";
2648
+ import { useIntl as useIntl9 } from "react-intl";
2649
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1857
2650
  function App() {
1858
- return /* @__PURE__ */ jsxs9(Routes, { children: [
1859
- /* @__PURE__ */ jsx9(Route, { path: "submissions/*", element: /* @__PURE__ */ jsx9(SubmissionsApp, {}) }),
1860
- /* @__PURE__ */ jsx9(Route, { path: "*", element: /* @__PURE__ */ jsx9(FormsApp, {}) })
1861
- ] });
2651
+ const { formatMessage } = useIntl9();
2652
+ const forms = useRBAC2(formsPermissions);
2653
+ const submissions = useRBAC2(submissionsPermissions);
2654
+ if (forms.isLoading || submissions.isLoading) return /* @__PURE__ */ jsx11(Page5.Loading, {});
2655
+ const canReadForms = forms.allowedActions.canRead === true;
2656
+ const canReadSubmissions = submissions.allowedActions.canRead === true;
2657
+ if (!canReadForms && !canReadSubmissions) return /* @__PURE__ */ jsx11(Page5.NoPermissions, {});
2658
+ const canUpdateForms = forms.allowedActions.canUpdate === true;
2659
+ const title = formatMessage({
2660
+ id: "builder.forms.title",
2661
+ defaultMessage: "Forms"
2662
+ });
2663
+ const submissionTitle = formatMessage({
2664
+ id: "builder.submissions.title",
2665
+ defaultMessage: "Submissions"
2666
+ });
2667
+ return /* @__PURE__ */ jsxs11(
2668
+ Layouts4.Root,
2669
+ {
2670
+ sideNav: /* @__PURE__ */ jsxs11(SubNav.Main, { "aria-label": title, children: [
2671
+ /* @__PURE__ */ jsx11(SubNav.Header, { label: title }),
2672
+ /* @__PURE__ */ jsx11(Divider, {}),
2673
+ /* @__PURE__ */ jsx11(SubNav.Content, { children: /* @__PURE__ */ jsx11(SubNav.Sections, { children: [
2674
+ /* @__PURE__ */ jsxs11(SubNav.Section, { label: title, children: [
2675
+ canReadForms && /* @__PURE__ */ jsx11(SubNav.Link, { to: "/plugins/builder/forms", label: title }),
2676
+ canReadSubmissions && /* @__PURE__ */ jsx11(
2677
+ SubNav.Link,
2678
+ {
2679
+ to: "/plugins/builder/submissions",
2680
+ label: submissionTitle
2681
+ }
2682
+ )
2683
+ ] }, "forms")
2684
+ ] }) })
2685
+ ] }),
2686
+ children: [
2687
+ /* @__PURE__ */ jsx11(
2688
+ Box4,
2689
+ {
2690
+ tag: "nav",
2691
+ "aria-label": title,
2692
+ display: { initial: "block", medium: "none" },
2693
+ padding: 4,
2694
+ background: "neutral0",
2695
+ children: /* @__PURE__ */ jsxs11(Flex9, { gap: 4, wrap: "wrap", children: [
2696
+ canReadForms && /* @__PURE__ */ jsx11(Link4, { tag: NavLink, to: "/plugins/builder/forms", children: title }),
2697
+ canReadSubmissions && /* @__PURE__ */ jsx11(Link4, { tag: NavLink, to: "/plugins/builder/submissions", children: submissionTitle })
2698
+ ] })
2699
+ }
2700
+ ),
2701
+ /* @__PURE__ */ jsxs11(Routes, { children: [
2702
+ /* @__PURE__ */ jsx11(
2703
+ Route,
2704
+ {
2705
+ index: true,
2706
+ element: /* @__PURE__ */ jsx11(Navigate, { to: canReadForms ? "forms" : "submissions", replace: true })
2707
+ }
2708
+ ),
2709
+ /* @__PURE__ */ jsx11(
2710
+ Route,
2711
+ {
2712
+ path: "forms",
2713
+ element: canReadForms ? /* @__PURE__ */ jsx11(FormListPage, { canUpdate: canUpdateForms }) : /* @__PURE__ */ jsx11(Page5.NoPermissions, {})
2714
+ }
2715
+ ),
2716
+ /* @__PURE__ */ jsx11(
2717
+ Route,
2718
+ {
2719
+ path: "forms/new",
2720
+ element: canReadForms && canUpdateForms ? /* @__PURE__ */ jsx11(FormEditorPage, { canUpdate: true }) : /* @__PURE__ */ jsx11(Page5.NoPermissions, {})
2721
+ }
2722
+ ),
2723
+ /* @__PURE__ */ jsx11(
2724
+ Route,
2725
+ {
2726
+ path: "forms/:documentId",
2727
+ element: canReadForms ? /* @__PURE__ */ jsx11(FormEditorPage, { canUpdate: canUpdateForms }) : /* @__PURE__ */ jsx11(Page5.NoPermissions, {})
2728
+ }
2729
+ ),
2730
+ /* @__PURE__ */ jsx11(
2731
+ Route,
2732
+ {
2733
+ path: "submissions",
2734
+ element: canReadSubmissions ? /* @__PURE__ */ jsx11(
2735
+ SubmissionPage,
2736
+ {
2737
+ canUpdate: submissions.allowedActions.canUpdate === true
2738
+ }
2739
+ ) : /* @__PURE__ */ jsx11(Page5.NoPermissions, {})
2740
+ }
2741
+ ),
2742
+ /* @__PURE__ */ jsx11(
2743
+ Route,
2744
+ {
2745
+ path: "*",
2746
+ element: /* @__PURE__ */ jsx11(
2747
+ Page5.Error,
2748
+ {
2749
+ content: formatMessage({
2750
+ id: "builder.common.notFound",
2751
+ defaultMessage: "Page not found."
2752
+ })
2753
+ }
2754
+ )
2755
+ }
2756
+ )
2757
+ ] })
2758
+ ]
2759
+ }
2760
+ );
1862
2761
  }
2762
+ var formsPermissions, submissionsPermissions;
1863
2763
  var init_app = __esm({
1864
2764
  "admin/src/app.tsx"() {
1865
2765
  "use strict";
1866
- init_submission_page();
1867
2766
  init_form_list_page();
1868
2767
  init_form_editor_page();
2768
+ init_submission_page();
2769
+ formsPermissions = ["read", "update"].map((action) => ({
2770
+ action: `plugin::builder.forms.${action}`,
2771
+ subject: null
2772
+ }));
2773
+ submissionsPermissions = ["read", "update"].map((action) => ({
2774
+ action: `plugin::builder.submissions.${action}`,
2775
+ subject: null
2776
+ }));
1869
2777
  }
1870
2778
  });
1871
2779
 
@@ -1899,31 +2807,480 @@ function BuilderIcon() {
1899
2807
  );
1900
2808
  }
1901
2809
 
2810
+ // admin/src/translations/en.json
2811
+ var en_default = {
2812
+ "builder.common.actions": "Actions",
2813
+ "builder.common.cancel": "Cancel",
2814
+ "builder.common.confirmDelete": "Confirm delete",
2815
+ "builder.common.delete": "Delete",
2816
+ "builder.common.edit": "Edit",
2817
+ "builder.common.error": "Error",
2818
+ "builder.common.name": "Name",
2819
+ "builder.common.next": "Next",
2820
+ "builder.common.notFound": "Page not found.",
2821
+ "builder.common.previous": "Previous",
2822
+ "builder.common.requestError": "Unable to complete the request.",
2823
+ "builder.common.retry": "Retry",
2824
+ "builder.common.save": "Save form",
2825
+ "builder.common.saving": "Saving\u2026",
2826
+ "builder.common.slug": "Slug",
2827
+ "builder.common.status": "Status",
2828
+ "builder.common.target": "Target",
2829
+ "builder.common.version": "Version",
2830
+ "builder.common.view": "View",
2831
+ "builder.forms.back": "Back to forms",
2832
+ "builder.forms.consent": "Consent",
2833
+ "builder.forms.create": "Create form",
2834
+ "builder.forms.definition": "Definition",
2835
+ "builder.forms.deleteTitle": "Delete {name}?",
2836
+ "builder.forms.deleteWarning": "This permanently deletes the form definition. Forms with submissions cannot be deleted.",
2837
+ "builder.forms.description": "Manage public form definitions and consent.",
2838
+ "builder.forms.empty": "No forms yet.",
2839
+ "builder.forms.form": "Form",
2840
+ "builder.forms.newVersion": "New forms start at version 1.",
2841
+ "builder.forms.savedVersion": "Form version {version} \xB7 assigned by server on every save",
2842
+ "builder.forms.title": "Forms",
2843
+ "builder.submissions.applyFilter": "Apply form filter",
2844
+ "builder.submissions.deliveries": "Deliveries",
2845
+ "builder.submissions.description": "Review receipt metadata, delivery history and processing status.",
2846
+ "builder.submissions.detailTitle": "Submission {id}",
2847
+ "builder.submissions.empty": "No submissions found.",
2848
+ "builder.submissions.filters": "Filters",
2849
+ "builder.submissions.openRecord": "Open submitted record",
2850
+ "builder.submissions.pagination": "Page {page} \xB7 {total} submissions",
2851
+ "builder.submissions.readOnly": "Read only. Submission update permission is required to change status or retry notifications.",
2852
+ "builder.submissions.received": "Received",
2853
+ "builder.submissions.retryNotifications": "Retry notifications",
2854
+ "builder.submissions.title": "Submissions",
2855
+ "builder.common.close": "Close",
2856
+ "builder.submissions.receipt": "Receipt",
2857
+ "builder.submissions.viewReceipt": "View submission {id}",
2858
+ "builder.submissions.filters.form": "Form",
2859
+ "builder.submissions.filters.allForms": "All forms",
2860
+ "builder.submissions.filters.allStatuses": "All statuses",
2861
+ "builder.submissions.filters.from": "Start date",
2862
+ "builder.submissions.filters.to": "End date",
2863
+ "builder.submissions.filters.utc": "Date filters use UTC, including the entire end date.",
2864
+ "builder.submissions.filters.clearDate": "Clear date",
2865
+ "builder.submissions.filters.calendar": "Choose date",
2866
+ "builder.submissions.filters.month": "Month",
2867
+ "builder.submissions.filters.year": "Year",
2868
+ "builder.submissions.status.received": "Received",
2869
+ "builder.submissions.status.reviewing": "Reviewing",
2870
+ "builder.submissions.status.responded": "Responded",
2871
+ "builder.submissions.status.closed": "Closed",
2872
+ "builder.submissions.currentValues": "Current record values",
2873
+ "builder.submissions.currentValuesNotice": "Values and field labels come from the current record and current form settings. They are not a snapshot of the submission.",
2874
+ "builder.submissions.unavailable": "Unavailable",
2875
+ "builder.submissions.noValue": "No value",
2876
+ "builder.submissions.boolean.true": "Yes",
2877
+ "builder.submissions.boolean.false": "No",
2878
+ "builder.submissions.consent": "Consent receipt",
2879
+ "builder.submissions.consentReceipt": "Version {version} \xB7 {time}",
2880
+ "builder.submissions.sourcePath": "Source path",
2881
+ "builder.submissions.metadata": "Attribution metadata",
2882
+ "builder.submissions.processingStatus": "Processing status",
2883
+ "builder.submissions.noDeliveries": "No deliveries recorded",
2884
+ "builder.submissions.channel.email": "Email",
2885
+ "builder.submissions.channel.webhook": "Webhook",
2886
+ "builder.submissions.channel.afterSave": "After-save hook",
2887
+ "builder.submissions.delivery.sent": "Sent",
2888
+ "builder.submissions.delivery.failed": "Failed",
2889
+ "builder.submissions.delivery.skipped": "Skipped",
2890
+ "builder.submissions.deliveryAttempt": "{attempts} attempts \xB7 Sent: {time}",
2891
+ "builder.submissions.deliveryError": "The notification could not be delivered.",
2892
+ "builder.submissions.invalidQuery": "Choose valid dates with the start date on or before the end date, and a valid page size.",
2893
+ "builder.forms.endpoint": "Public endpoint",
2894
+ "builder.forms.fieldCount": "Fields",
2895
+ "builder.forms.latestSubmission": "Latest submission",
2896
+ "builder.forms.notifications": "Notifications",
2897
+ "builder.forms.copyUrl": "Copy URL for {name}",
2898
+ "builder.forms.copied": "URL copied.",
2899
+ "builder.forms.copyFailed": "Unable to copy the URL.",
2900
+ "builder.forms.targetUnavailable": "Target unavailable",
2901
+ "builder.forms.submittedAt": "{date, date, medium} {date, time, short}",
2902
+ "builder.forms.email": "Email",
2903
+ "builder.forms.webhook": "Webhook",
2904
+ "builder.forms.unconfigured": "Not configured",
2905
+ "builder.forms.validationError": "Check the marked tabs. Required values or selected fields are missing or invalid.",
2906
+ "builder.forms.editorTabs": "Form settings",
2907
+ "builder.forms.nameRequired": "Enter a name.",
2908
+ "builder.forms.slugLocked": "The slug is locked because this form has submissions.",
2909
+ "builder.forms.slugHint": "Use lowercase letters, numbers and separating hyphens (maximum 120). Editable until the first submission. Enter a slug manually if the name cannot suggest one.",
2910
+ "builder.forms.selectTarget": "Select a storage target",
2911
+ "builder.forms.noTargets": "No matching targets",
2912
+ "builder.forms.loadingFields": "Loading target fields\u2026",
2913
+ "builder.forms.targetChanged": "The target changed. Select and configure its fields again in Fields. Required fields are not added automatically.",
2914
+ "builder.forms.consentLabel": "Consent label",
2915
+ "builder.forms.consentVersion": "Consent version",
2916
+ "builder.forms.consentHint": "Consent version identifies the terms accepted by the visitor and is separate from the server form version.",
2917
+ "builder.forms.fields.label": "Label",
2918
+ "builder.forms.fields.placeholder": "Placeholder",
2919
+ "builder.forms.fields.help": "Help text",
2920
+ "builder.forms.fields.settings": "Field display settings",
2921
+ "builder.forms.fields.select": "Select a schema field",
2922
+ "builder.forms.fields.required": "Required",
2923
+ "builder.forms.fields.optional": "Optional",
2924
+ "builder.forms.fields.missing": "Missing required",
2925
+ "builder.forms.fields.maxLength": "Maximum length",
2926
+ "builder.forms.fields.min": "Minimum",
2927
+ "builder.forms.fields.max": "Maximum",
2928
+ "builder.forms.fields.selected": "Selected",
2929
+ "builder.forms.fields.choose": "Choose",
2930
+ "builder.forms.fields.schemaRequired": "Required by the storage target. It cannot be made optional or removed.",
2931
+ "builder.forms.fields.visibility": "Visibility",
2932
+ "builder.forms.fields.visible": "Visible",
2933
+ "builder.forms.fields.hidden": "Hidden",
2934
+ "builder.forms.fields.hiddenHint": "Hidden fields are filled by website code. Required validation still applies.",
2935
+ "builder.forms.fields.schemaLimit": "Schema limit",
2936
+ "builder.forms.fields.optionLabel": "Option label",
2937
+ "builder.forms.fields.invalid": "Enter a label and valid constraints within the schema limits.",
2938
+ "builder.forms.fields.back": "Back to selection",
2939
+ "builder.forms.fields.apply": "Apply",
2940
+ "builder.forms.fields.description": "Select schema fields and configure their labels, order and presentation. Drag rows or use the arrow buttons to reorder.",
2941
+ "builder.forms.fields.missingRequired": "Required fields are missing",
2942
+ "builder.forms.fields.add": "Add field",
2943
+ "builder.forms.fields.legacyTitle": "Some saved fields are no longer selectable",
2944
+ "builder.forms.fields.legacy": "Review the target schema and form definition in project code. These fields have not been removed automatically; saving requires a valid definition.",
2945
+ "builder.forms.fields.moved": "{name} moved to position {position} of {total}.",
2946
+ "builder.forms.fields.order": "Order",
2947
+ "builder.forms.fields.type": "Type",
2948
+ "builder.forms.fields.constraints": "Constraints",
2949
+ "builder.forms.fields.actions": "Actions",
2950
+ "builder.forms.fields.up": "Move {name} up",
2951
+ "builder.forms.fields.down": "Move {name} down",
2952
+ "builder.forms.fields.edit": "Edit {name}",
2953
+ "builder.forms.fields.remove": "Remove field",
2954
+ "builder.forms.fields.removeName": "Remove {name}",
2955
+ "builder.forms.fields.empty": "No fields selected. Add a schema field to begin.",
2956
+ "builder.forms.fields.types.string": "Short text",
2957
+ "builder.forms.fields.types.text": "Long text",
2958
+ "builder.forms.fields.types.email": "Email",
2959
+ "builder.forms.fields.types.integer": "Integer",
2960
+ "builder.forms.fields.types.decimal": "Decimal",
2961
+ "builder.forms.fields.types.boolean": "Boolean",
2962
+ "builder.forms.fields.types.enumeration": "Selection",
2963
+ "builder.forms.fields.types.date": "Date",
2964
+ "builder.forms.fields.types.datetime": "Date and time",
2965
+ "builder.forms.tabs.basics": "Basics",
2966
+ "builder.forms.tabs.fields": "Fields",
2967
+ "builder.forms.tabs.consent": "Consent",
2968
+ "builder.forms.tabs.notifications": "Notifications",
2969
+ "builder.forms.fields.dismiss": "Dismiss",
2970
+ "builder.forms.requiredValue": "Enter a value.",
2971
+ "builder.forms.clearTarget": "Clear target",
2972
+ "builder.notifications.notifications": "Notifications",
2973
+ "builder.notifications.opened": "Notification settings opened",
2974
+ "builder.notifications.open": "Open notification settings",
2975
+ "builder.notifications.hint": "Enable a channel to send notifications. Changes take effect after saving the form.",
2976
+ "builder.notifications.email": "Email notifications",
2977
+ "builder.notifications.webhook": "Webhook notifications",
2978
+ "builder.notifications.on": "On",
2979
+ "builder.notifications.off": "Off",
2980
+ "builder.notifications.to": "To (one address per line)",
2981
+ "builder.notifications.cc": "Cc (one address per line)",
2982
+ "builder.notifications.recipientsHint": "Use fixed email addresses. Empty lines are ignored when saving.",
2983
+ "builder.notifications.replyTo": "Reply-to field",
2984
+ "builder.notifications.none": "None",
2985
+ "builder.notifications.subject": "Email subject",
2986
+ "builder.notifications.body": "Email body",
2987
+ "builder.notifications.insertTarget": "Insert placeholder into",
2988
+ "builder.notifications.insert": "Insert placeholder",
2989
+ "builder.notifications.placeholderList": "Available placeholders",
2990
+ "builder.notifications.url": "Webhook URL",
2991
+ "builder.notifications.secret": "Webhook secret",
2992
+ "builder.notifications.maskHint": "Stored secrets and header values appear as ********. Leave them untouched to preserve them, or enter a replacement. Clear a secret or remove a header to delete it.",
2993
+ "builder.notifications.header": "Header: {name}",
2994
+ "builder.notifications.remove": "Remove {name}",
2995
+ "builder.notifications.headerName": "New header name",
2996
+ "builder.notifications.headerValue": "New header value",
2997
+ "builder.notifications.addHeader": "Add header",
2998
+ "builder.notifications.testTitle": "Test notifications",
2999
+ "builder.notifications.testHint": "Sends real notifications using the saved settings and these sample values, including hidden fields. Save all changes before testing. No submission record is created.",
3000
+ "builder.notifications.testDisabled": "Testing requires a saved form, no unsaved changes and forms.update permission.",
3001
+ "builder.notifications.hidden": "hidden",
3002
+ "builder.notifications.choose": "Choose a value",
3003
+ "builder.notifications.true": "True",
3004
+ "builder.notifications.false": "False",
3005
+ "builder.notifications.sending": "Sending test\u2026",
3006
+ "builder.notifications.send": "Send test notifications",
3007
+ "builder.notifications.noChannels": "No notification channels are enabled in the saved settings.",
3008
+ "builder.notifications.sent": "Success",
3009
+ "builder.notifications.failed": "Failed",
3010
+ "builder.notifications.attempts": "{count} attempt(s)",
3011
+ "builder.notifications.afterSave": "After save",
3012
+ "builder.notifications.defaultSubject": "New submission: '{{form.name}}'",
3013
+ "builder.notifications.defaultBody": "Submission '{{submission.id}}' received at '{{submission.receivedAt}}'",
3014
+ "builder.notifications.templateError": "Use only the available placeholders.",
3015
+ "builder.notifications.replyToError": "Choose an email field in this form, or None.",
3016
+ "builder.notifications.recipientsError": "Recipients must be fixed email addresses; placeholders are not allowed.",
3017
+ "builder.notifications.urlError": "Enter an HTTP or HTTPS URL without credentials.",
3018
+ "builder.notifications.sampleError": "Check this sample value.",
3019
+ "builder.forms.consentChangedTitle": "Consent text changed",
3020
+ "builder.forms.consentChanged": "The text has changed. Save it with a new version.",
3021
+ "builder.forms.suggestConsentVersion": "Suggest a new version",
3022
+ "builder.notifications.sample.required": "Enter a sample value.",
3023
+ "builder.notifications.sample.invalid": "Enter a valid value for this field type.",
3024
+ "builder.notifications.sample.maxLength": "The sample exceeds the maximum length.",
3025
+ "builder.notifications.sample.min": "The sample is below the minimum value.",
3026
+ "builder.notifications.sample.max": "The sample exceeds the maximum value."
3027
+ };
3028
+
3029
+ // admin/src/translations/ko.json
3030
+ var ko_default = {
3031
+ "builder.common.actions": "\uC791\uC5C5",
3032
+ "builder.common.cancel": "\uCDE8\uC18C",
3033
+ "builder.common.confirmDelete": "\uC0AD\uC81C \uD655\uC778",
3034
+ "builder.common.delete": "\uC0AD\uC81C",
3035
+ "builder.common.edit": "\uD3B8\uC9D1",
3036
+ "builder.common.error": "\uC624\uB958",
3037
+ "builder.common.name": "\uC774\uB984",
3038
+ "builder.common.next": "\uB2E4\uC74C",
3039
+ "builder.common.notFound": "\uD398\uC774\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
3040
+ "builder.common.previous": "\uC774\uC804",
3041
+ "builder.common.requestError": "\uC694\uCCAD\uC744 \uC644\uB8CC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
3042
+ "builder.common.retry": "\uB2E4\uC2DC \uC2DC\uB3C4",
3043
+ "builder.common.save": "\uD3FC \uC800\uC7A5",
3044
+ "builder.common.saving": "\uC800\uC7A5 \uC911\u2026",
3045
+ "builder.common.slug": "\uC2AC\uB7EC\uADF8",
3046
+ "builder.common.status": "\uC0C1\uD0DC",
3047
+ "builder.common.target": "\uC800\uC7A5 \uB300\uC0C1",
3048
+ "builder.common.version": "\uBC84\uC804",
3049
+ "builder.common.view": "\uBCF4\uAE30",
3050
+ "builder.forms.back": "\uD3FC \uBAA9\uB85D\uC73C\uB85C",
3051
+ "builder.forms.consent": "\uB3D9\uC758",
3052
+ "builder.forms.create": "\uC0C8 \uD3FC",
3053
+ "builder.forms.definition": "\uD3FC \uC815\uC758",
3054
+ "builder.forms.deleteTitle": "{name} \uD3FC\uC744 \uC0AD\uC81C\uD560\uAE4C\uC694?",
3055
+ "builder.forms.deleteWarning": "\uD3FC \uC815\uC758\uB97C \uC601\uAD6C \uC0AD\uC81C\uD569\uB2C8\uB2E4. \uC811\uC218 \uB0B4\uC5ED\uC774 \uC788\uB294 \uD3FC\uC740 \uC0AD\uC81C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
3056
+ "builder.forms.description": "\uACF5\uAC1C \uD3FC \uC815\uC758\uC640 \uB3D9\uC758\uB97C \uAD00\uB9AC\uD569\uB2C8\uB2E4.",
3057
+ "builder.forms.empty": "\uC544\uC9C1 \uD3FC\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
3058
+ "builder.forms.form": "\uD3FC",
3059
+ "builder.forms.newVersion": "\uC0C8 \uD3FC\uC740 \uBC84\uC804 1\uB85C \uC2DC\uC791\uD569\uB2C8\uB2E4.",
3060
+ "builder.forms.savedVersion": "\uD3FC \uBC84\uC804 {version} \xB7 \uC800\uC7A5\uD560 \uB54C\uB9C8\uB2E4 \uC11C\uBC84\uC5D0\uC11C \uBD80\uC5EC\uD569\uB2C8\uB2E4",
3061
+ "builder.forms.title": "\uD3FC",
3062
+ "builder.submissions.applyFilter": "\uD3FC \uD544\uD130 \uC801\uC6A9",
3063
+ "builder.submissions.deliveries": "\uBC1C\uC1A1 \uB0B4\uC5ED",
3064
+ "builder.submissions.description": "\uC811\uC218 \uC815\uBCF4, \uBC1C\uC1A1 \uC774\uB825\uACFC \uCC98\uB9AC \uC0C1\uD0DC\uB97C \uD655\uC778\uD569\uB2C8\uB2E4.",
3065
+ "builder.submissions.detailTitle": "\uC811\uC218 {id}",
3066
+ "builder.submissions.empty": "\uC811\uC218 \uB0B4\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
3067
+ "builder.submissions.filters": "\uD544\uD130",
3068
+ "builder.submissions.openRecord": "\uC811\uC218\uB41C \uB808\uCF54\uB4DC \uC5F4\uAE30",
3069
+ "builder.submissions.pagination": "{page}\uD398\uC774\uC9C0 \xB7 \uC811\uC218 {total}\uAC74",
3070
+ "builder.submissions.readOnly": "\uC77D\uAE30 \uC804\uC6A9\uC785\uB2C8\uB2E4. \uC0C1\uD0DC \uBCC0\uACBD \uB610\uB294 \uC54C\uB9BC \uC7AC\uBC1C\uC1A1\uC5D0\uB294 \uC811\uC218 \uC218\uC815 \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
3071
+ "builder.submissions.received": "\uC811\uC218 \uC2DC\uAC01",
3072
+ "builder.submissions.retryNotifications": "\uC54C\uB9BC \uC7AC\uBC1C\uC1A1",
3073
+ "builder.submissions.title": "\uC811\uC218 \uB0B4\uC5ED",
3074
+ "builder.common.close": "\uB2EB\uAE30",
3075
+ "builder.submissions.receipt": "\uC811\uC218 \uBC88\uD638",
3076
+ "builder.submissions.viewReceipt": "\uC811\uC218 {id} \uC0C1\uC138 \uBCF4\uAE30",
3077
+ "builder.submissions.filters.form": "\uD3FC",
3078
+ "builder.submissions.filters.allForms": "\uBAA8\uB4E0 \uD3FC",
3079
+ "builder.submissions.filters.allStatuses": "\uBAA8\uB4E0 \uC0C1\uD0DC",
3080
+ "builder.submissions.filters.from": "\uC2DC\uC791\uC77C",
3081
+ "builder.submissions.filters.to": "\uC885\uB8CC\uC77C",
3082
+ "builder.submissions.filters.utc": "\uAE30\uAC04\uC740 UTC \uAE30\uC900\uC774\uBA70 \uC885\uB8CC\uC77C \uC804\uCCB4\uB97C \uD3EC\uD568\uD569\uB2C8\uB2E4.",
3083
+ "builder.submissions.filters.clearDate": "\uB0A0\uC9DC \uC9C0\uC6B0\uAE30",
3084
+ "builder.submissions.filters.calendar": "\uB0A0\uC9DC \uC120\uD0DD",
3085
+ "builder.submissions.filters.month": "\uC6D4",
3086
+ "builder.submissions.filters.year": "\uC5F0\uB3C4",
3087
+ "builder.submissions.status.received": "\uC811\uC218",
3088
+ "builder.submissions.status.reviewing": "\uAC80\uD1A0 \uC911",
3089
+ "builder.submissions.status.responded": "\uC751\uB2F5 \uC644\uB8CC",
3090
+ "builder.submissions.status.closed": "\uC885\uB8CC",
3091
+ "builder.submissions.currentValues": "\uD604\uC7AC \uB808\uCF54\uB4DC \uAC12",
3092
+ "builder.submissions.currentValuesNotice": "\uD604\uC7AC \uB808\uCF54\uB4DC\uC758 \uAC12\uACFC \uD604\uC7AC \uD3FC \uC124\uC815\uC758 \uD544\uB4DC \uB77C\uBCA8\uC785\uB2C8\uB2E4. \uC81C\uCD9C \uB2F9\uC2DC\uC758 \uC2A4\uB0C5\uC0F7\uC774 \uC544\uB2D9\uB2C8\uB2E4.",
3093
+ "builder.submissions.unavailable": "\uC870\uD68C\uD560 \uC218 \uC5C6\uC74C",
3094
+ "builder.submissions.noValue": "\uAC12 \uC5C6\uC74C",
3095
+ "builder.submissions.boolean.true": "\uC608",
3096
+ "builder.submissions.boolean.false": "\uC544\uB2C8\uC694",
3097
+ "builder.submissions.consent": "\uB3D9\uC758 \uAE30\uB85D",
3098
+ "builder.submissions.consentReceipt": "\uBC84\uC804 {version} \xB7 {time}",
3099
+ "builder.submissions.sourcePath": "\uC720\uC785 \uACBD\uB85C",
3100
+ "builder.submissions.metadata": "\uC720\uC785 \uBA54\uD0C0\uB370\uC774\uD130",
3101
+ "builder.submissions.processingStatus": "\uCC98\uB9AC \uC0C1\uD0DC",
3102
+ "builder.submissions.noDeliveries": "\uBC1C\uC1A1 \uAE30\uB85D \uC5C6\uC74C",
3103
+ "builder.submissions.channel.email": "\uC774\uBA54\uC77C",
3104
+ "builder.submissions.channel.webhook": "\uC6F9\uD6C5",
3105
+ "builder.submissions.channel.afterSave": "\uC800\uC7A5 \uD6C4 \uD6C5",
3106
+ "builder.submissions.delivery.sent": "\uBC1C\uC1A1 \uC644\uB8CC",
3107
+ "builder.submissions.delivery.failed": "\uBC1C\uC1A1 \uC2E4\uD328",
3108
+ "builder.submissions.delivery.skipped": "\uAC74\uB108\uB700",
3109
+ "builder.submissions.deliveryAttempt": "\uC2DC\uB3C4 {attempts}\uD68C \xB7 \uBC1C\uC1A1 \uC2DC\uAC01: {time}",
3110
+ "builder.submissions.deliveryError": "\uC54C\uB9BC\uC744 \uBC1C\uC1A1\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
3111
+ "builder.submissions.invalidQuery": "\uC2DC\uC791\uC77C\uC774 \uC885\uB8CC\uC77C\uBCF4\uB2E4 \uB2A6\uC9C0 \uC54A\uB3C4\uB85D \uC62C\uBC14\uB978 \uB0A0\uC9DC\uC640 \uD398\uC774\uC9C0 \uD06C\uAE30\uB97C \uC120\uD0DD\uD558\uC138\uC694.",
3112
+ "builder.forms.endpoint": "\uACF5\uAC1C \uC5D4\uB4DC\uD3EC\uC778\uD2B8",
3113
+ "builder.forms.fieldCount": "\uD544\uB4DC \uC218",
3114
+ "builder.forms.latestSubmission": "\uCD5C\uADFC \uC811\uC218",
3115
+ "builder.forms.notifications": "\uC54C\uB9BC",
3116
+ "builder.forms.copyUrl": "{name} URL \uBCF5\uC0AC",
3117
+ "builder.forms.copied": "URL\uC744 \uBCF5\uC0AC\uD588\uC2B5\uB2C8\uB2E4.",
3118
+ "builder.forms.copyFailed": "URL\uC744 \uBCF5\uC0AC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
3119
+ "builder.forms.targetUnavailable": "\uB300\uC0C1 \uC870\uD68C \uBD88\uAC00",
3120
+ "builder.forms.submittedAt": "{date, date, medium} {date, time, short}",
3121
+ "builder.forms.email": "\uC774\uBA54\uC77C",
3122
+ "builder.forms.webhook": "\uC6F9\uD6C5",
3123
+ "builder.forms.unconfigured": "\uBBF8\uC124\uC815",
3124
+ "builder.forms.validationError": "\uD45C\uC2DC\uB41C \uD0ED\uC744 \uD655\uC778\uD558\uC138\uC694. \uD544\uC218 \uAC12 \uB610\uB294 \uC120\uD0DD\uD55C \uD544\uB4DC\uAC00 \uB204\uB77D\uB418\uC5C8\uAC70\uB098 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
3125
+ "builder.forms.editorTabs": "\uD3FC \uC124\uC815",
3126
+ "builder.forms.nameRequired": "\uC774\uB984\uC744 \uC785\uB825\uD558\uC138\uC694.",
3127
+ "builder.forms.slugLocked": "\uC811\uC218\uAC00 \uC788\uB294 \uD3FC\uC758 slug\uB294 \uBCC0\uACBD\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
3128
+ "builder.forms.slugHint": "\uC18C\uBB38\uC790 \uC601\uBB38\xB7\uC22B\uC790\xB7\uAD6C\uBD84 \uD558\uC774\uD508\uC744 \uCD5C\uB300 120\uC790 \uC0AC\uC6A9\uD558\uC138\uC694. \uCCAB \uC811\uC218 \uC804\uAE4C\uC9C0 \uBCC0\uACBD\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. \uC774\uB984\uC73C\uB85C \uC0DD\uC131\uD560 \uC218 \uC5C6\uB2E4\uBA74 \uC9C1\uC811 \uC785\uB825\uD558\uC138\uC694.",
3129
+ "builder.forms.selectTarget": "\uC800\uC7A5 \uB300\uC0C1\uC744 \uC120\uD0DD\uD558\uC138\uC694",
3130
+ "builder.forms.noTargets": "\uC77C\uCE58\uD558\uB294 \uB300\uC0C1\uC774 \uC5C6\uC2B5\uB2C8\uB2E4",
3131
+ "builder.forms.loadingFields": "\uB300\uC0C1 \uD544\uB4DC\uB97C \uBD88\uB7EC\uC624\uB294 \uC911\u2026",
3132
+ "builder.forms.targetChanged": "\uC800\uC7A5 \uB300\uC0C1\uC774 \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uD544\uB4DC \uD0ED\uC5D0\uC11C \uD544\uB4DC\uB97C \uB2E4\uC2DC \uC120\uD0DD\uD558\uACE0 \uC124\uC815\uD558\uC138\uC694. \uD544\uC218 \uD544\uB4DC\uB294 \uC790\uB3D9 \uCD94\uAC00\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
3133
+ "builder.forms.consentLabel": "\uB3D9\uC758 \uBB38\uAD6C",
3134
+ "builder.forms.consentVersion": "\uB3D9\uC758 \uBC84\uC804",
3135
+ "builder.forms.consentHint": "\uB3D9\uC758 \uBC84\uC804\uC740 \uBC29\uBB38\uC790\uAC00 \uC218\uB77D\uD55C \uC57D\uAD00\uC744 \uC2DD\uBCC4\uD558\uBA70 \uC11C\uBC84 \uD3FC \uBC84\uC804\uACFC \uBCC4\uAC1C\uC785\uB2C8\uB2E4.",
3136
+ "builder.forms.fields.label": "\uD45C\uC2DC \uB77C\uBCA8",
3137
+ "builder.forms.fields.placeholder": "\uD50C\uB808\uC774\uC2A4\uD640\uB354",
3138
+ "builder.forms.fields.help": "\uB3C4\uC6C0\uB9D0",
3139
+ "builder.forms.fields.settings": "\uD544\uB4DC \uD45C\uC2DC \uC124\uC815",
3140
+ "builder.forms.fields.select": "\uC2A4\uD0A4\uB9C8 \uD544\uB4DC \uC120\uD0DD",
3141
+ "builder.forms.fields.required": "\uD544\uC218",
3142
+ "builder.forms.fields.optional": "\uC120\uD0DD",
3143
+ "builder.forms.fields.missing": "\uB204\uB77D \uD544\uC218",
3144
+ "builder.forms.fields.maxLength": "\uCD5C\uB300 \uAE38\uC774",
3145
+ "builder.forms.fields.min": "\uCD5C\uC19F\uAC12",
3146
+ "builder.forms.fields.max": "\uCD5C\uB313\uAC12",
3147
+ "builder.forms.fields.selected": "\uC120\uD0DD\uB428",
3148
+ "builder.forms.fields.choose": "\uC120\uD0DD",
3149
+ "builder.forms.fields.schemaRequired": "\uC800\uC7A5 \uB300\uC0C1\uC758 \uD544\uC218 \uD544\uB4DC\uC785\uB2C8\uB2E4. \uD544\uC218 \uD574\uC81C\uD558\uAC70\uB098 \uC81C\uAC70\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
3150
+ "builder.forms.fields.visibility": "\uD45C\uC2DC \uC5EC\uBD80",
3151
+ "builder.forms.fields.visible": "\uD45C\uC2DC",
3152
+ "builder.forms.fields.hidden": "\uC228\uAE40",
3153
+ "builder.forms.fields.hiddenHint": "\uC228\uAE40 \uD544\uB4DC\uB294 \uC6F9\uC0AC\uC774\uD2B8 \uCF54\uB4DC\uAC00 \uAC12\uC744 \uCC44\uC6C1\uB2C8\uB2E4. \uD544\uC218 \uAC80\uC99D\uC740 \uADF8\uB300\uB85C \uC801\uC6A9\uB429\uB2C8\uB2E4.",
3154
+ "builder.forms.fields.schemaLimit": "\uC2A4\uD0A4\uB9C8 \uC81C\uD55C",
3155
+ "builder.forms.fields.optionLabel": "\uC120\uD0DD\uC9C0 \uB77C\uBCA8",
3156
+ "builder.forms.fields.invalid": "\uB77C\uBCA8\uACFC \uC2A4\uD0A4\uB9C8 \uC81C\uD55C \uC548\uC758 \uC720\uD6A8\uD55C \uC81C\uC57D\uC744 \uC785\uB825\uD558\uC138\uC694.",
3157
+ "builder.forms.fields.back": "\uC120\uD0DD\uC73C\uB85C \uB3CC\uC544\uAC00\uAE30",
3158
+ "builder.forms.fields.apply": "\uC801\uC6A9",
3159
+ "builder.forms.fields.description": "\uC2A4\uD0A4\uB9C8 \uD544\uB4DC\uB97C \uC120\uD0DD\uD558\uACE0 \uB77C\uBCA8\xB7\uC21C\uC11C\xB7\uD45C\uC2DC\uB97C \uC124\uC815\uD558\uC138\uC694. \uD589\uC744 \uB4DC\uB798\uADF8\uD558\uAC70\uB098 \uD654\uC0B4\uD45C \uBC84\uD2BC\uC73C\uB85C \uC21C\uC11C\uB97C \uBCC0\uACBD\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
3160
+ "builder.forms.fields.missingRequired": "\uD544\uC218 \uD544\uB4DC\uAC00 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
3161
+ "builder.forms.fields.add": "\uD544\uB4DC \uCD94\uAC00",
3162
+ "builder.forms.fields.legacyTitle": "\uC77C\uBD80 \uC800\uC7A5\uB41C \uD544\uB4DC\uB97C \uB354 \uC774\uC0C1 \uC120\uD0DD\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
3163
+ "builder.forms.fields.legacy": "\uD504\uB85C\uC81D\uD2B8 \uCF54\uB4DC\uC5D0\uC11C \uB300\uC0C1 \uC2A4\uD0A4\uB9C8\uC640 \uD3FC \uC815\uC758\uB97C \uC810\uAC80\uD558\uC138\uC694. \uD544\uB4DC\uB97C \uC790\uB3D9 \uC81C\uAC70\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uC800\uC7A5\uD558\uB824\uBA74 \uC720\uD6A8\uD55C \uC815\uC758\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
3164
+ "builder.forms.fields.moved": "{name} \uD544\uB4DC\uB97C {total}\uAC1C \uC911 {position}\uBC88\uC9F8\uB85C \uC774\uB3D9\uD588\uC2B5\uB2C8\uB2E4.",
3165
+ "builder.forms.fields.order": "\uC21C\uC11C",
3166
+ "builder.forms.fields.type": "\uD0C0\uC785",
3167
+ "builder.forms.fields.constraints": "\uC81C\uC57D",
3168
+ "builder.forms.fields.actions": "\uC791\uC5C5",
3169
+ "builder.forms.fields.up": "{name} \uC704\uB85C \uC774\uB3D9",
3170
+ "builder.forms.fields.down": "{name} \uC544\uB798\uB85C \uC774\uB3D9",
3171
+ "builder.forms.fields.edit": "{name} \uD3B8\uC9D1",
3172
+ "builder.forms.fields.remove": "\uD544\uB4DC \uC81C\uAC70",
3173
+ "builder.forms.fields.removeName": "{name} \uC81C\uAC70",
3174
+ "builder.forms.fields.empty": "\uC120\uD0DD\uD55C \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uC2A4\uD0A4\uB9C8 \uD544\uB4DC\uB97C \uCD94\uAC00\uD558\uC138\uC694.",
3175
+ "builder.forms.fields.types.string": "\uC9E7\uC740 \uD14D\uC2A4\uD2B8",
3176
+ "builder.forms.fields.types.text": "\uAE34 \uD14D\uC2A4\uD2B8",
3177
+ "builder.forms.fields.types.email": "\uC774\uBA54\uC77C",
3178
+ "builder.forms.fields.types.integer": "\uC815\uC218",
3179
+ "builder.forms.fields.types.decimal": "\uC18C\uC218",
3180
+ "builder.forms.fields.types.boolean": "\uCC38/\uAC70\uC9D3",
3181
+ "builder.forms.fields.types.enumeration": "\uC120\uD0DD \uBAA9\uB85D",
3182
+ "builder.forms.fields.types.date": "\uB0A0\uC9DC",
3183
+ "builder.forms.fields.types.datetime": "\uB0A0\uC9DC\uC640 \uC2DC\uAC04",
3184
+ "builder.forms.tabs.basics": "\uAE30\uBCF8 \uC815\uBCF4",
3185
+ "builder.forms.tabs.fields": "\uD544\uB4DC",
3186
+ "builder.forms.tabs.consent": "\uB3D9\uC758",
3187
+ "builder.forms.tabs.notifications": "\uC54C\uB9BC",
3188
+ "builder.forms.fields.dismiss": "\uB2EB\uAE30",
3189
+ "builder.forms.requiredValue": "\uAC12\uC744 \uC785\uB825\uD558\uC138\uC694.",
3190
+ "builder.forms.clearTarget": "\uB300\uC0C1 \uC120\uD0DD \uC9C0\uC6B0\uAE30",
3191
+ "builder.notifications.notifications": "\uC54C\uB9BC",
3192
+ "builder.notifications.opened": "\uC54C\uB9BC \uC124\uC815 \uC5F4\uB9BC",
3193
+ "builder.notifications.open": "\uC54C\uB9BC \uC124\uC815 \uC5F4\uAE30",
3194
+ "builder.notifications.hint": "\uBC1C\uC1A1\uD560 \uCC44\uB110\uC744 \uCF1C\uC138\uC694. \uBCC0\uACBD \uC0AC\uD56D\uC740 \uD3FC\uC744 \uC800\uC7A5\uD55C \uB4A4 \uC801\uC6A9\uB429\uB2C8\uB2E4.",
3195
+ "builder.notifications.email": "\uC774\uBA54\uC77C \uC54C\uB9BC",
3196
+ "builder.notifications.webhook": "\uC6F9\uD6C5 \uC54C\uB9BC",
3197
+ "builder.notifications.on": "\uCF1C\uC9D0",
3198
+ "builder.notifications.off": "\uAEBC\uC9D0",
3199
+ "builder.notifications.to": "\uC218\uC2E0\uC790 (\uD55C \uC904\uC5D0 \uC8FC\uC18C \uD558\uB098)",
3200
+ "builder.notifications.cc": "\uCC38\uC870 (\uD55C \uC904\uC5D0 \uC8FC\uC18C \uD558\uB098)",
3201
+ "builder.notifications.recipientsHint": "\uACE0\uC815 \uC774\uBA54\uC77C \uC8FC\uC18C\uB97C \uC785\uB825\uD558\uC138\uC694. \uBE48 \uC904\uC740 \uC800\uC7A5\uD560 \uB54C \uC81C\uC678\uB429\uB2C8\uB2E4.",
3202
+ "builder.notifications.replyTo": "\uD68C\uC2E0 \uC774\uBA54\uC77C \uD544\uB4DC",
3203
+ "builder.notifications.none": "\uC5C6\uC74C",
3204
+ "builder.notifications.subject": "\uC774\uBA54\uC77C \uC81C\uBAA9",
3205
+ "builder.notifications.body": "\uC774\uBA54\uC77C \uBCF8\uBB38",
3206
+ "builder.notifications.insertTarget": "\uCE58\uD658\uC790 \uC0BD\uC785 \uC704\uCE58",
3207
+ "builder.notifications.insert": "\uCE58\uD658\uC790 \uC0BD\uC785",
3208
+ "builder.notifications.placeholderList": "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uCE58\uD658\uC790",
3209
+ "builder.notifications.url": "\uC6F9\uD6C5 URL",
3210
+ "builder.notifications.secret": "\uC6F9\uD6C5 \uC2DC\uD06C\uB9BF",
3211
+ "builder.notifications.maskHint": "\uC800\uC7A5\uB41C \uC2DC\uD06C\uB9BF\uACFC \uD5E4\uB354 \uAC12\uC740 ********\uB85C \uD45C\uC2DC\uB429\uB2C8\uB2E4. \uADF8\uB300\uB85C \uB450\uBA74 \uC720\uC9C0\uB418\uACE0 \uC0C8 \uAC12\uC744 \uC785\uB825\uD558\uBA74 \uAD50\uCCB4\uB429\uB2C8\uB2E4. \uC2DC\uD06C\uB9BF\uC744 \uBE44\uC6B0\uAC70\uB098 \uD5E4\uB354\uB97C \uC81C\uAC70\uD558\uBA74 \uC0AD\uC81C\uB429\uB2C8\uB2E4.",
3212
+ "builder.notifications.header": "\uD5E4\uB354: {name}",
3213
+ "builder.notifications.remove": "{name} \uC81C\uAC70",
3214
+ "builder.notifications.headerName": "\uC0C8 \uD5E4\uB354 \uC774\uB984",
3215
+ "builder.notifications.headerValue": "\uC0C8 \uD5E4\uB354 \uAC12",
3216
+ "builder.notifications.addHeader": "\uD5E4\uB354 \uCD94\uAC00",
3217
+ "builder.notifications.testTitle": "\uC54C\uB9BC \uD14C\uC2A4\uD2B8",
3218
+ "builder.notifications.testHint": "\uC800\uC7A5\uB41C \uC124\uC815\uACFC hidden \uD544\uB4DC\uB97C \uD3EC\uD568\uD55C \uC0D8\uD50C \uAC12\uC73C\uB85C \uC2E4\uC81C \uC54C\uB9BC\uC744 \uBCF4\uB0C5\uB2C8\uB2E4. \uBA3C\uC800 \uBAA8\uB4E0 \uBCC0\uACBD \uC0AC\uD56D\uC744 \uC800\uC7A5\uD558\uC138\uC694. \uC811\uC218 \uAE30\uB85D\uC740 \uC0DD\uC131\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
3219
+ "builder.notifications.testDisabled": "\uD3FC\uC744 \uC800\uC7A5\uD558\uACE0 \uBBF8\uC800\uC7A5 \uBCC0\uACBD \uC0AC\uD56D\uC774 \uC5C6\uC5B4\uC57C \uD558\uBA70 forms.update \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
3220
+ "builder.notifications.hidden": "\uC228\uAE40",
3221
+ "builder.notifications.choose": "\uAC12 \uC120\uD0DD",
3222
+ "builder.notifications.true": "\uCC38",
3223
+ "builder.notifications.false": "\uAC70\uC9D3",
3224
+ "builder.notifications.sending": "\uD14C\uC2A4\uD2B8 \uBC1C\uC1A1 \uC911\u2026",
3225
+ "builder.notifications.send": "\uD14C\uC2A4\uD2B8 \uC54C\uB9BC \uBC1C\uC1A1",
3226
+ "builder.notifications.noChannels": "\uC800\uC7A5\uB41C \uC124\uC815\uC5D0 \uD65C\uC131\uD654\uB41C \uC54C\uB9BC \uCC44\uB110\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
3227
+ "builder.notifications.sent": "\uC131\uACF5",
3228
+ "builder.notifications.failed": "\uC2E4\uD328",
3229
+ "builder.notifications.attempts": "{count}\uD68C \uC2DC\uB3C4",
3230
+ "builder.notifications.afterSave": "\uC800\uC7A5 \uD6C4 \uCC98\uB9AC",
3231
+ "builder.notifications.defaultSubject": "\uC0C8 \uC811\uC218: '{{form.name}}'",
3232
+ "builder.notifications.defaultBody": "\uC811\uC218 '{{submission.id}}' \xB7 \uC811\uC218 \uC2DC\uAC01 '{{submission.receivedAt}}'",
3233
+ "builder.notifications.templateError": "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uCE58\uD658\uC790\uB9CC \uC0AC\uC6A9\uD558\uC138\uC694.",
3234
+ "builder.notifications.replyToError": "\uC774 \uD3FC\uC758 \uC774\uBA54\uC77C \uD544\uB4DC \uB610\uB294 \uC5C6\uC74C\uC744 \uC120\uD0DD\uD558\uC138\uC694.",
3235
+ "builder.notifications.recipientsError": "\uC218\uC2E0\uC790\uB294 \uACE0\uC815 \uC774\uBA54\uC77C \uC8FC\uC18C\uC5EC\uC57C \uD569\uB2C8\uB2E4. \uCE58\uD658\uC790\uB294 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
3236
+ "builder.notifications.urlError": "\uC778\uC99D \uC815\uBCF4\uAC00 \uC5C6\uB294 HTTP \uB610\uB294 HTTPS URL\uC744 \uC785\uB825\uD558\uC138\uC694.",
3237
+ "builder.notifications.sampleError": "\uC0D8\uD50C \uAC12\uC744 \uD655\uC778\uD558\uC138\uC694.",
3238
+ "builder.forms.consentChangedTitle": "\uB3D9\uC758 \uBB38\uAD6C \uBCC0\uACBD",
3239
+ "builder.forms.consentChanged": "\uBB38\uAD6C\uAC00 \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4. \uC0C8 \uBC84\uC804\uC73C\uB85C \uC800\uC7A5\uD558\uC138\uC694",
3240
+ "builder.forms.suggestConsentVersion": "\uC0C8 \uBC84\uC804 \uC81C\uC548",
3241
+ "builder.notifications.sample.required": "\uC0D8\uD50C \uAC12\uC744 \uC785\uB825\uD558\uC138\uC694.",
3242
+ "builder.notifications.sample.invalid": "\uC774 \uD544\uB4DC \uC720\uD615\uC5D0 \uB9DE\uB294 \uAC12\uC744 \uC785\uB825\uD558\uC138\uC694.",
3243
+ "builder.notifications.sample.maxLength": "\uC0D8\uD50C \uAC12\uC774 \uCD5C\uB300 \uAE38\uC774\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.",
3244
+ "builder.notifications.sample.min": "\uC0D8\uD50C \uAC12\uC774 \uCD5C\uC19F\uAC12\uBCF4\uB2E4 \uC791\uC2B5\uB2C8\uB2E4.",
3245
+ "builder.notifications.sample.max": "\uC0D8\uD50C \uAC12\uC774 \uCD5C\uB313\uAC12\uC744 \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4."
3246
+ };
3247
+
1902
3248
  // admin/src/index.ts
3249
+ var translations = { en: en_default, ko: ko_default };
1903
3250
  var index_default = {
1904
3251
  register(app) {
3252
+ app.customFields.register({
3253
+ name: "form-select",
3254
+ pluginId: "builder",
3255
+ type: "string",
3256
+ intlLabel: { id: "builder.form-select", defaultMessage: "Builder form" },
3257
+ intlDescription: {
3258
+ id: "builder.form-select.description",
3259
+ defaultMessage: "Select a form from Builder Forms"
3260
+ },
3261
+ components: {
3262
+ Input: () => Promise.resolve().then(() => (init_form_select(), form_select_exports)).then(({ FormSelect: FormSelect3 }) => ({
3263
+ default: FormSelect3
3264
+ }))
3265
+ }
3266
+ });
1905
3267
  app.addMenuLink({
1906
3268
  to: "plugins/builder",
1907
3269
  icon: BuilderIcon,
1908
- intlLabel: { id: "builder.forms", defaultMessage: "Builder Forms" },
3270
+ intlLabel: { id: "builder.forms.title", defaultMessage: "Forms" },
1909
3271
  Component: () => Promise.resolve().then(() => (init_app(), app_exports)),
1910
- permissions: [{ action: "plugin::builder.forms.read", subject: null }]
1911
- });
1912
- app.addMenuLink({
1913
- to: "plugins/builder/submissions",
1914
- icon: BuilderIcon,
1915
- intlLabel: {
1916
- id: "builder.submissions",
1917
- defaultMessage: "Builder Submissions"
1918
- },
1919
- Component: () => Promise.resolve().then(() => (init_app(), app_exports)).then(({ SubmissionsApp: SubmissionsApp2 }) => ({
1920
- default: SubmissionsApp2
1921
- })),
1922
3272
  permissions: [
3273
+ { action: "plugin::builder.forms.read", subject: null },
1923
3274
  { action: "plugin::builder.submissions.read", subject: null }
1924
3275
  ]
1925
3276
  });
1926
- app.registerPlugin({ id: "builder", name: "Builder" });
3277
+ app.registerPlugin({ id: "builder", name: "Forms" });
3278
+ },
3279
+ async registerTrads({ locales }) {
3280
+ return locales.map((locale) => ({
3281
+ locale,
3282
+ data: translations[locale] ?? {}
3283
+ }));
1927
3284
  }
1928
3285
  };
1929
3286
  export {