@dani-builder/strapi-plugin-builder 1.0.0 → 1.2.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.
- package/dist/admin/api.d.ts +14 -2
- package/dist/admin/api.d.ts.map +1 -1
- package/dist/admin/app.d.ts +1 -0
- package/dist/admin/app.d.ts.map +1 -1
- package/dist/admin/form-editor-page.d.ts.map +1 -1
- package/dist/admin/form.d.ts +24 -2
- package/dist/admin/form.d.ts.map +1 -1
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.mjs +1084 -159
- package/dist/admin/notification-editor.d.ts +16 -0
- package/dist/admin/notification-editor.d.ts.map +1 -0
- package/dist/admin/notifications.d.ts +8 -0
- package/dist/admin/notifications.d.ts.map +1 -0
- package/dist/admin/submission-page.d.ts +4 -0
- package/dist/admin/submission-page.d.ts.map +1 -0
- package/dist/admin/submission.d.ts +27 -0
- package/dist/admin/submission.d.ts.map +1 -0
- package/dist/admin/ui.d.ts +3 -1
- package/dist/admin/ui.d.ts.map +1 -1
- package/dist/server/src/bootstrap.d.ts.map +1 -1
- package/dist/server/src/bootstrap.js +6 -0
- package/dist/server/src/bootstrap.js.map +1 -1
- package/dist/server/src/content-types/submission/schema.json +1 -1
- package/dist/server/src/controllers/submission.d.ts +3 -0
- package/dist/server/src/controllers/submission.d.ts.map +1 -1
- package/dist/server/src/controllers/submission.js +41 -0
- package/dist/server/src/controllers/submission.js.map +1 -1
- package/dist/server/src/routes/admin.d.ts.map +1 -1
- package/dist/server/src/routes/admin.js +15 -6
- package/dist/server/src/routes/admin.js.map +1 -1
- package/dist/server/src/services/submission.d.ts +64 -0
- package/dist/server/src/services/submission.d.ts.map +1 -1
- package/dist/server/src/services/submission.js +84 -0
- package/dist/server/src/services/submission.js.map +1 -1
- package/package.json +2 -2
package/dist/admin/index.mjs
CHANGED
|
@@ -14,8 +14,14 @@ var __export = (target, all) => {
|
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
// admin/src/form.ts
|
|
17
|
-
function savePayload(form) {
|
|
17
|
+
function savePayload(form, includeNotifications = false) {
|
|
18
|
+
const notifications = structuredClone(form.notifications ?? {});
|
|
19
|
+
if (notifications.email) {
|
|
20
|
+
notifications.email.to = notifications.email.to.map((value) => value.trim()).filter(Boolean);
|
|
21
|
+
notifications.email.cc = notifications.email.cc?.map((value) => value.trim()).filter(Boolean);
|
|
22
|
+
}
|
|
18
23
|
return {
|
|
24
|
+
...includeNotifications ? { notifications } : {},
|
|
19
25
|
slug: form.slug,
|
|
20
26
|
name: form.name,
|
|
21
27
|
targetUid: form.targetUid,
|
|
@@ -89,13 +95,19 @@ function formApi(client) {
|
|
|
89
95
|
params: { targetUid }
|
|
90
96
|
})).data.data;
|
|
91
97
|
},
|
|
92
|
-
async save(form, id) {
|
|
93
|
-
const body = { data: savePayload(form) };
|
|
98
|
+
async save(form, id, includeNotifications = false) {
|
|
99
|
+
const body = { data: savePayload(form, includeNotifications) };
|
|
94
100
|
return (id ? await client.put(
|
|
95
101
|
`${root}/${encodeURIComponent(id)}`,
|
|
96
102
|
body
|
|
97
103
|
) : await client.post(root, body)).data.data;
|
|
98
104
|
},
|
|
105
|
+
async testSend(id, sampleFields) {
|
|
106
|
+
return (await client.post(
|
|
107
|
+
`${root}/${encodeURIComponent(id)}/test-send`,
|
|
108
|
+
{ data: sampleFields }
|
|
109
|
+
)).data.data;
|
|
110
|
+
},
|
|
99
111
|
async remove(id) {
|
|
100
112
|
await client.del(`${root}/${encodeURIComponent(id)}`);
|
|
101
113
|
},
|
|
@@ -124,6 +136,30 @@ function downloadForm(value, slug) {
|
|
|
124
136
|
anchor.click();
|
|
125
137
|
URL.revokeObjectURL(url);
|
|
126
138
|
}
|
|
139
|
+
function submissionApi(client) {
|
|
140
|
+
const root2 = "/plugins/builder/submissions";
|
|
141
|
+
return {
|
|
142
|
+
async list(params) {
|
|
143
|
+
return (await client.get(root2, { params })).data;
|
|
144
|
+
},
|
|
145
|
+
async get(id) {
|
|
146
|
+
return (await client.get(
|
|
147
|
+
`${root2}/${encodeURIComponent(id)}`
|
|
148
|
+
)).data.data;
|
|
149
|
+
},
|
|
150
|
+
async status(id, status) {
|
|
151
|
+
return (await client.put(
|
|
152
|
+
`${root2}/${encodeURIComponent(id)}/status`,
|
|
153
|
+
{ data: { status } }
|
|
154
|
+
)).data.data;
|
|
155
|
+
},
|
|
156
|
+
async redeliver(id) {
|
|
157
|
+
return (await client.post(
|
|
158
|
+
`${root2}/${encodeURIComponent(id)}/redeliver`
|
|
159
|
+
)).data.data;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
127
163
|
var root;
|
|
128
164
|
var init_api = __esm({
|
|
129
165
|
"admin/src/api.ts"() {
|
|
@@ -133,7 +169,22 @@ var init_api = __esm({
|
|
|
133
169
|
}
|
|
134
170
|
});
|
|
135
171
|
|
|
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
|
+
|
|
136
186
|
// admin/src/ui.tsx
|
|
187
|
+
import { useId } from "react";
|
|
137
188
|
import { Box, Flex, Typography } from "@strapi/design-system";
|
|
138
189
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
139
190
|
function Section({
|
|
@@ -151,9 +202,13 @@ function TextField({
|
|
|
151
202
|
onChange,
|
|
152
203
|
disabled,
|
|
153
204
|
required,
|
|
154
|
-
multiline
|
|
205
|
+
multiline,
|
|
206
|
+
secret,
|
|
207
|
+
error
|
|
155
208
|
}) {
|
|
209
|
+
const errorId = useId();
|
|
156
210
|
const props = {
|
|
211
|
+
"aria-describedby": error ? errorId : void 0,
|
|
157
212
|
value,
|
|
158
213
|
disabled,
|
|
159
214
|
required,
|
|
@@ -170,7 +225,16 @@ function TextField({
|
|
|
170
225
|
};
|
|
171
226
|
return /* @__PURE__ */ jsxs2("label", { style: { display: "grid", gap: 6 }, children: [
|
|
172
227
|
/* @__PURE__ */ jsx2(Typography, { children: label }),
|
|
173
|
-
multiline ? /* @__PURE__ */ jsx2("textarea", { ...props, rows: 3 }) : /* @__PURE__ */ jsx2(
|
|
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 })
|
|
174
238
|
] });
|
|
175
239
|
}
|
|
176
240
|
function ErrorNotice({ message }) {
|
|
@@ -182,19 +246,309 @@ var init_ui = __esm({
|
|
|
182
246
|
}
|
|
183
247
|
});
|
|
184
248
|
|
|
249
|
+
// admin/src/submission-page.tsx
|
|
250
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
251
|
+
import {
|
|
252
|
+
Button,
|
|
253
|
+
Flex as Flex2,
|
|
254
|
+
Table,
|
|
255
|
+
Tbody,
|
|
256
|
+
Td,
|
|
257
|
+
Th,
|
|
258
|
+
Thead,
|
|
259
|
+
Tr,
|
|
260
|
+
Typography as Typography2
|
|
261
|
+
} 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();
|
|
284
|
+
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("");
|
|
294
|
+
const lock = useRef(false);
|
|
295
|
+
const generation = useRef(0);
|
|
296
|
+
const load = useCallback(async () => {
|
|
297
|
+
const current = ++generation.current;
|
|
298
|
+
setLoading(true);
|
|
299
|
+
setError("");
|
|
300
|
+
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);
|
|
309
|
+
} catch (cause) {
|
|
310
|
+
if (current === generation.current) setError(errorMessage(cause));
|
|
311
|
+
} finally {
|
|
312
|
+
if (current === generation.current) setLoading(false);
|
|
313
|
+
}
|
|
314
|
+
}, [get, page, formFilter, status]);
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
setSelected(null);
|
|
317
|
+
void load();
|
|
318
|
+
return () => {
|
|
319
|
+
generation.current++;
|
|
320
|
+
};
|
|
321
|
+
}, [load]);
|
|
322
|
+
async function mutate(action) {
|
|
323
|
+
if (lock.current) return;
|
|
324
|
+
lock.current = true;
|
|
325
|
+
setBusy(true);
|
|
326
|
+
setError("");
|
|
327
|
+
try {
|
|
328
|
+
await action();
|
|
329
|
+
} catch (cause) {
|
|
330
|
+
setError(errorMessage(cause));
|
|
331
|
+
} finally {
|
|
332
|
+
lock.current = false;
|
|
333
|
+
setBusy(false);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
const api = submissionApi(client);
|
|
337
|
+
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
338
|
+
/* @__PURE__ */ jsx3(
|
|
339
|
+
Layouts.Header,
|
|
340
|
+
{
|
|
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(
|
|
359
|
+
Button,
|
|
360
|
+
{
|
|
361
|
+
disabled: busy,
|
|
362
|
+
onClick: () => {
|
|
363
|
+
setPage(1);
|
|
364
|
+
setFormFilter(slug.trim());
|
|
365
|
+
},
|
|
366
|
+
children: "Apply form filter"
|
|
367
|
+
}
|
|
368
|
+
),
|
|
369
|
+
/* @__PURE__ */ jsxs3("label", { children: [
|
|
370
|
+
"Status",
|
|
371
|
+
" ",
|
|
372
|
+
/* @__PURE__ */ jsxs3(
|
|
373
|
+
"select",
|
|
374
|
+
{
|
|
375
|
+
"aria-label": "Filter status",
|
|
376
|
+
value: status,
|
|
377
|
+
disabled: busy,
|
|
378
|
+
onChange: (event) => {
|
|
379
|
+
setPage(1);
|
|
380
|
+
setStatus(event.target.value);
|
|
381
|
+
},
|
|
382
|
+
children: [
|
|
383
|
+
/* @__PURE__ */ jsx3("option", { value: "", children: "All statuses" }),
|
|
384
|
+
submissionStatuses.map((value) => /* @__PURE__ */ jsx3("option", { children: value }, value))
|
|
385
|
+
]
|
|
386
|
+
}
|
|
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(
|
|
415
|
+
Button,
|
|
416
|
+
{
|
|
417
|
+
disabled: busy,
|
|
418
|
+
onClick: () => void mutate(async () => {
|
|
419
|
+
setSelected(await api.get(row.documentId));
|
|
420
|
+
}),
|
|
421
|
+
children: [
|
|
422
|
+
"View ",
|
|
423
|
+
row.documentId
|
|
424
|
+
]
|
|
425
|
+
}
|
|
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
|
+
}
|
|
437
|
+
),
|
|
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
|
+
);
|
|
490
|
+
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
|
+
] })
|
|
526
|
+
] }) })
|
|
527
|
+
] });
|
|
528
|
+
}
|
|
529
|
+
var init_submission_page = __esm({
|
|
530
|
+
"admin/src/submission-page.tsx"() {
|
|
531
|
+
"use strict";
|
|
532
|
+
init_api();
|
|
533
|
+
init_form();
|
|
534
|
+
init_submission();
|
|
535
|
+
init_ui();
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
185
539
|
// admin/src/import-form.tsx
|
|
186
|
-
import { useRef, useState } from "react";
|
|
187
|
-
import { Button, Flex as
|
|
188
|
-
import { jsx as
|
|
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";
|
|
189
543
|
function ImportForm({
|
|
190
544
|
replace,
|
|
191
545
|
disabled,
|
|
192
546
|
onImport
|
|
193
547
|
}) {
|
|
194
|
-
const [source, setSource] =
|
|
195
|
-
const [error, setError] =
|
|
196
|
-
const [busy, setBusy] =
|
|
197
|
-
const lock =
|
|
548
|
+
const [source, setSource] = useState2("");
|
|
549
|
+
const [error, setError] = useState2("");
|
|
550
|
+
const [busy, setBusy] = useState2(false);
|
|
551
|
+
const lock = useRef2(false);
|
|
198
552
|
async function submit() {
|
|
199
553
|
if (lock.current) return;
|
|
200
554
|
lock.current = true;
|
|
@@ -209,16 +563,16 @@ function ImportForm({
|
|
|
209
563
|
setBusy(false);
|
|
210
564
|
}
|
|
211
565
|
}
|
|
212
|
-
return /* @__PURE__ */
|
|
213
|
-
/* @__PURE__ */
|
|
566
|
+
return /* @__PURE__ */ jsxs4(Flex3, { direction: "column", alignItems: "stretch", gap: 3, children: [
|
|
567
|
+
/* @__PURE__ */ jsxs4(Typography3, { children: [
|
|
214
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.",
|
|
215
569
|
" ",
|
|
216
570
|
"Consent version is preserved. Existing slugs cannot be duplicated."
|
|
217
571
|
] }),
|
|
218
|
-
/* @__PURE__ */
|
|
572
|
+
/* @__PURE__ */ jsxs4("label", { children: [
|
|
219
573
|
"Choose JSON file",
|
|
220
574
|
" ",
|
|
221
|
-
/* @__PURE__ */
|
|
575
|
+
/* @__PURE__ */ jsx4(
|
|
222
576
|
"input",
|
|
223
577
|
{
|
|
224
578
|
type: "file",
|
|
@@ -233,7 +587,7 @@ function ImportForm({
|
|
|
233
587
|
}
|
|
234
588
|
)
|
|
235
589
|
] }),
|
|
236
|
-
/* @__PURE__ */
|
|
590
|
+
/* @__PURE__ */ jsx4(
|
|
237
591
|
TextField,
|
|
238
592
|
{
|
|
239
593
|
label: "Form JSON",
|
|
@@ -243,9 +597,9 @@ function ImportForm({
|
|
|
243
597
|
disabled: busy || disabled
|
|
244
598
|
}
|
|
245
599
|
),
|
|
246
|
-
/* @__PURE__ */
|
|
247
|
-
/* @__PURE__ */
|
|
248
|
-
|
|
600
|
+
/* @__PURE__ */ jsx4(ErrorNotice, { message: error }),
|
|
601
|
+
/* @__PURE__ */ jsx4(
|
|
602
|
+
Button2,
|
|
249
603
|
{
|
|
250
604
|
type: "button",
|
|
251
605
|
disabled: busy || disabled || !source.trim(),
|
|
@@ -264,32 +618,32 @@ var init_import_form = __esm({
|
|
|
264
618
|
});
|
|
265
619
|
|
|
266
620
|
// admin/src/form-list-page.tsx
|
|
267
|
-
import { useCallback, useEffect, useRef as
|
|
621
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef3, useState as useState3 } from "react";
|
|
268
622
|
import {
|
|
269
|
-
Button as
|
|
270
|
-
Flex as
|
|
271
|
-
Table,
|
|
272
|
-
Tbody,
|
|
273
|
-
Td,
|
|
274
|
-
Th,
|
|
275
|
-
Thead,
|
|
276
|
-
Tr,
|
|
277
|
-
Typography as
|
|
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
|
|
278
632
|
} from "@strapi/design-system";
|
|
279
|
-
import { Layouts, Page, useFetchClient } from "@strapi/strapi/admin";
|
|
633
|
+
import { Layouts as Layouts2, Page as Page2, useFetchClient as useFetchClient2 } from "@strapi/strapi/admin";
|
|
280
634
|
import { useNavigate } from "react-router-dom";
|
|
281
|
-
import { Fragment, jsx as
|
|
635
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
282
636
|
function FormListPage({ canUpdate }) {
|
|
283
|
-
const client =
|
|
637
|
+
const client = useFetchClient2();
|
|
284
638
|
const { get } = client;
|
|
285
639
|
const navigate = useNavigate();
|
|
286
|
-
const [rows, setRows] =
|
|
287
|
-
const [loading, setLoading] =
|
|
288
|
-
const [error, setError] =
|
|
289
|
-
const [busy, setBusy] =
|
|
290
|
-
const [removing, setRemoving] =
|
|
291
|
-
const lock =
|
|
292
|
-
const load =
|
|
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 () => {
|
|
293
647
|
setLoading(true);
|
|
294
648
|
setError("");
|
|
295
649
|
try {
|
|
@@ -302,7 +656,7 @@ function FormListPage({ canUpdate }) {
|
|
|
302
656
|
setLoading(false);
|
|
303
657
|
}
|
|
304
658
|
}, [get]);
|
|
305
|
-
|
|
659
|
+
useEffect2(() => {
|
|
306
660
|
void load();
|
|
307
661
|
}, [load]);
|
|
308
662
|
async function mutate(action) {
|
|
@@ -319,14 +673,14 @@ function FormListPage({ canUpdate }) {
|
|
|
319
673
|
setBusy(false);
|
|
320
674
|
}
|
|
321
675
|
}
|
|
322
|
-
return /* @__PURE__ */
|
|
323
|
-
/* @__PURE__ */
|
|
324
|
-
|
|
676
|
+
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
677
|
+
/* @__PURE__ */ jsx5(
|
|
678
|
+
Layouts2.Header,
|
|
325
679
|
{
|
|
326
680
|
title: "Builder Forms",
|
|
327
681
|
subtitle: "Manage public form definitions and consent.",
|
|
328
|
-
primaryAction: canUpdate ? /* @__PURE__ */
|
|
329
|
-
|
|
682
|
+
primaryAction: canUpdate ? /* @__PURE__ */ jsx5(
|
|
683
|
+
Button3,
|
|
330
684
|
{
|
|
331
685
|
disabled: busy,
|
|
332
686
|
onClick: () => navigate("/plugins/builder/forms/new"),
|
|
@@ -335,21 +689,21 @@ function FormListPage({ canUpdate }) {
|
|
|
335
689
|
) : void 0
|
|
336
690
|
}
|
|
337
691
|
),
|
|
338
|
-
/* @__PURE__ */
|
|
339
|
-
/* @__PURE__ */
|
|
340
|
-
error && /* @__PURE__ */
|
|
341
|
-
/* @__PURE__ */
|
|
342
|
-
/* @__PURE__ */
|
|
343
|
-
(name) => /* @__PURE__ */
|
|
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)
|
|
344
698
|
) }) }),
|
|
345
|
-
/* @__PURE__ */
|
|
346
|
-
/* @__PURE__ */
|
|
347
|
-
/* @__PURE__ */
|
|
348
|
-
/* @__PURE__ */
|
|
349
|
-
/* @__PURE__ */
|
|
350
|
-
/* @__PURE__ */
|
|
351
|
-
/* @__PURE__ */
|
|
352
|
-
|
|
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: [
|
|
705
|
+
/* @__PURE__ */ jsxs5(
|
|
706
|
+
Button3,
|
|
353
707
|
{
|
|
354
708
|
variant: "secondary",
|
|
355
709
|
disabled: busy,
|
|
@@ -363,8 +717,8 @@ function FormListPage({ canUpdate }) {
|
|
|
363
717
|
]
|
|
364
718
|
}
|
|
365
719
|
),
|
|
366
|
-
/* @__PURE__ */
|
|
367
|
-
|
|
720
|
+
/* @__PURE__ */ jsx5(
|
|
721
|
+
Button3,
|
|
368
722
|
{
|
|
369
723
|
variant: "tertiary",
|
|
370
724
|
disabled: busy,
|
|
@@ -377,8 +731,8 @@ function FormListPage({ canUpdate }) {
|
|
|
377
731
|
children: "Export"
|
|
378
732
|
}
|
|
379
733
|
),
|
|
380
|
-
canUpdate && /* @__PURE__ */
|
|
381
|
-
|
|
734
|
+
canUpdate && /* @__PURE__ */ jsx5(
|
|
735
|
+
Button3,
|
|
382
736
|
{
|
|
383
737
|
variant: "danger-light",
|
|
384
738
|
disabled: busy,
|
|
@@ -389,11 +743,11 @@ function FormListPage({ canUpdate }) {
|
|
|
389
743
|
] }) })
|
|
390
744
|
] }, row.documentId)) })
|
|
391
745
|
] }) }),
|
|
392
|
-
removing && /* @__PURE__ */
|
|
393
|
-
/* @__PURE__ */
|
|
394
|
-
/* @__PURE__ */
|
|
395
|
-
/* @__PURE__ */
|
|
396
|
-
|
|
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,
|
|
397
751
|
{
|
|
398
752
|
variant: "danger",
|
|
399
753
|
disabled: busy,
|
|
@@ -405,8 +759,8 @@ function FormListPage({ canUpdate }) {
|
|
|
405
759
|
children: "Confirm delete"
|
|
406
760
|
}
|
|
407
761
|
),
|
|
408
|
-
/* @__PURE__ */
|
|
409
|
-
|
|
762
|
+
/* @__PURE__ */ jsx5(
|
|
763
|
+
Button3,
|
|
410
764
|
{
|
|
411
765
|
variant: "secondary",
|
|
412
766
|
disabled: busy,
|
|
@@ -416,7 +770,7 @@ function FormListPage({ canUpdate }) {
|
|
|
416
770
|
)
|
|
417
771
|
] })
|
|
418
772
|
] }),
|
|
419
|
-
canUpdate && /* @__PURE__ */
|
|
773
|
+
canUpdate && /* @__PURE__ */ jsx5(Section, { title: "Import form", children: /* @__PURE__ */ jsx5(
|
|
420
774
|
ImportForm,
|
|
421
775
|
{
|
|
422
776
|
disabled: busy,
|
|
@@ -448,8 +802,8 @@ var init_form_list_page = __esm({
|
|
|
448
802
|
});
|
|
449
803
|
|
|
450
804
|
// admin/src/field-editor.tsx
|
|
451
|
-
import { Button as
|
|
452
|
-
import { jsx as
|
|
805
|
+
import { Button as Button4, Flex as Flex5, Typography as Typography5 } from "@strapi/design-system";
|
|
806
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
453
807
|
function FieldEditor({
|
|
454
808
|
fields,
|
|
455
809
|
schema,
|
|
@@ -459,10 +813,10 @@ function FieldEditor({
|
|
|
459
813
|
function update(index, field) {
|
|
460
814
|
onChange(fields.map((item, at) => at === index ? field : item));
|
|
461
815
|
}
|
|
462
|
-
return /* @__PURE__ */
|
|
463
|
-
/* @__PURE__ */
|
|
464
|
-
/* @__PURE__ */
|
|
465
|
-
/* @__PURE__ */
|
|
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(
|
|
466
820
|
"input",
|
|
467
821
|
{
|
|
468
822
|
type: "checkbox",
|
|
@@ -479,14 +833,14 @@ function FieldEditor({
|
|
|
479
833
|
] }, item.name)) }),
|
|
480
834
|
fields.map((field, index) => {
|
|
481
835
|
const attribute = schema.find((item) => item.name === field.name);
|
|
482
|
-
return /* @__PURE__ */
|
|
836
|
+
return /* @__PURE__ */ jsxs6(
|
|
483
837
|
Section,
|
|
484
838
|
{
|
|
485
839
|
title: `${index + 1}. ${field.name} (${field.type})`,
|
|
486
840
|
children: [
|
|
487
|
-
/* @__PURE__ */
|
|
488
|
-
/* @__PURE__ */
|
|
489
|
-
|
|
841
|
+
/* @__PURE__ */ jsxs6(Flex5, { gap: 3, children: [
|
|
842
|
+
/* @__PURE__ */ jsxs6(
|
|
843
|
+
Button4,
|
|
490
844
|
{
|
|
491
845
|
type: "button",
|
|
492
846
|
variant: "secondary",
|
|
@@ -499,8 +853,8 @@ function FieldEditor({
|
|
|
499
853
|
]
|
|
500
854
|
}
|
|
501
855
|
),
|
|
502
|
-
/* @__PURE__ */
|
|
503
|
-
|
|
856
|
+
/* @__PURE__ */ jsxs6(
|
|
857
|
+
Button4,
|
|
504
858
|
{
|
|
505
859
|
type: "button",
|
|
506
860
|
variant: "secondary",
|
|
@@ -514,7 +868,7 @@ function FieldEditor({
|
|
|
514
868
|
}
|
|
515
869
|
)
|
|
516
870
|
] }),
|
|
517
|
-
/* @__PURE__ */
|
|
871
|
+
/* @__PURE__ */ jsx6(
|
|
518
872
|
TextField,
|
|
519
873
|
{
|
|
520
874
|
label: "Label",
|
|
@@ -524,7 +878,7 @@ function FieldEditor({
|
|
|
524
878
|
onChange: (label) => update(index, { ...field, label })
|
|
525
879
|
}
|
|
526
880
|
),
|
|
527
|
-
/* @__PURE__ */
|
|
881
|
+
/* @__PURE__ */ jsx6(
|
|
528
882
|
TextField,
|
|
529
883
|
{
|
|
530
884
|
label: "Placeholder",
|
|
@@ -533,7 +887,7 @@ function FieldEditor({
|
|
|
533
887
|
onChange: (placeholder) => update(index, { ...field, placeholder })
|
|
534
888
|
}
|
|
535
889
|
),
|
|
536
|
-
/* @__PURE__ */
|
|
890
|
+
/* @__PURE__ */ jsx6(
|
|
537
891
|
TextField,
|
|
538
892
|
{
|
|
539
893
|
label: "Help text",
|
|
@@ -542,8 +896,8 @@ function FieldEditor({
|
|
|
542
896
|
onChange: (help) => update(index, { ...field, help })
|
|
543
897
|
}
|
|
544
898
|
),
|
|
545
|
-
/* @__PURE__ */
|
|
546
|
-
/* @__PURE__ */
|
|
899
|
+
/* @__PURE__ */ jsxs6("label", { children: [
|
|
900
|
+
/* @__PURE__ */ jsx6(
|
|
547
901
|
"input",
|
|
548
902
|
{
|
|
549
903
|
type: "checkbox",
|
|
@@ -562,8 +916,8 @@ function FieldEditor({
|
|
|
562
916
|
"Required",
|
|
563
917
|
attribute?.required ? " (required by target schema)" : ""
|
|
564
918
|
] }),
|
|
565
|
-
/* @__PURE__ */
|
|
566
|
-
/* @__PURE__ */
|
|
919
|
+
/* @__PURE__ */ jsxs6("label", { children: [
|
|
920
|
+
/* @__PURE__ */ jsx6(
|
|
567
921
|
"input",
|
|
568
922
|
{
|
|
569
923
|
type: "checkbox",
|
|
@@ -578,7 +932,7 @@ function FieldEditor({
|
|
|
578
932
|
" ",
|
|
579
933
|
"Hidden from form"
|
|
580
934
|
] }),
|
|
581
|
-
(field.maxLength !== void 0 || field.min !== void 0 || field.max !== void 0) && /* @__PURE__ */
|
|
935
|
+
(field.maxLength !== void 0 || field.min !== void 0 || field.max !== void 0) && /* @__PURE__ */ jsxs6(Typography5, { variant: "pi", children: [
|
|
582
936
|
"Constraints:",
|
|
583
937
|
" ",
|
|
584
938
|
JSON.stringify({
|
|
@@ -587,7 +941,7 @@ function FieldEditor({
|
|
|
587
941
|
max: field.max
|
|
588
942
|
})
|
|
589
943
|
] }),
|
|
590
|
-
field.options?.map((option, optionIndex) => /* @__PURE__ */
|
|
944
|
+
field.options?.map((option, optionIndex) => /* @__PURE__ */ jsx6(
|
|
591
945
|
TextField,
|
|
592
946
|
{
|
|
593
947
|
label: `Option label: ${option.value}`,
|
|
@@ -618,32 +972,537 @@ var init_field_editor = __esm({
|
|
|
618
972
|
}
|
|
619
973
|
});
|
|
620
974
|
|
|
975
|
+
// admin/src/notifications.ts
|
|
976
|
+
function placeholders(fields) {
|
|
977
|
+
return [
|
|
978
|
+
...fields.map((field) => `field.${field.name}`),
|
|
979
|
+
"form.name",
|
|
980
|
+
"submission.id",
|
|
981
|
+
"submission.receivedAt"
|
|
982
|
+
];
|
|
983
|
+
}
|
|
984
|
+
function templateError(value, fields) {
|
|
985
|
+
const allowed = placeholders(fields);
|
|
986
|
+
let invalid = false;
|
|
987
|
+
const rest = value.replace(/{{\s*([^{}]+?)\s*}}/g, (_, key) => {
|
|
988
|
+
if (!allowed.includes(key)) invalid = true;
|
|
989
|
+
return "";
|
|
990
|
+
});
|
|
991
|
+
return invalid || rest.includes("{{") || rest.includes("}}") ? "Use only the available placeholders." : "";
|
|
992
|
+
}
|
|
993
|
+
function notificationErrors(config, fields) {
|
|
994
|
+
const errors = {};
|
|
995
|
+
if (config.email) {
|
|
996
|
+
if (config.email.replyToField && !fields.some(
|
|
997
|
+
(field) => field.type === "email" && field.name === config.email?.replyToField
|
|
998
|
+
))
|
|
999
|
+
errors.replyToField = "Choose an email field in this form, or None.";
|
|
1000
|
+
for (const key of ["subject", "body"]) {
|
|
1001
|
+
const error = templateError(config.email[key], fields);
|
|
1002
|
+
if (error) errors[key] = error;
|
|
1003
|
+
}
|
|
1004
|
+
for (const key of ["to", "cc"]) {
|
|
1005
|
+
if (config.email[key]?.some((value) => /[{}]/.test(value)))
|
|
1006
|
+
errors[key] = "Recipients must be fixed email addresses; placeholders are not allowed.";
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (config.webhook) {
|
|
1010
|
+
try {
|
|
1011
|
+
const url = new URL(config.webhook.url);
|
|
1012
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password)
|
|
1013
|
+
throw Error("url");
|
|
1014
|
+
} catch {
|
|
1015
|
+
errors.url = "Enter an HTTP or HTTPS URL without credentials.";
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return errors;
|
|
1019
|
+
}
|
|
1020
|
+
function samplePayload(fields, values) {
|
|
1021
|
+
return Object.fromEntries(
|
|
1022
|
+
fields.flatMap((field) => {
|
|
1023
|
+
const value = values[field.name] ?? "";
|
|
1024
|
+
if (!value && !field.required) return [];
|
|
1025
|
+
return [
|
|
1026
|
+
[
|
|
1027
|
+
field.name,
|
|
1028
|
+
value && ["integer", "decimal"].includes(field.type) ? Number(value) : value && field.type === "boolean" ? value === "true" : value
|
|
1029
|
+
]
|
|
1030
|
+
];
|
|
1031
|
+
})
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
function sampleErrors(error) {
|
|
1035
|
+
const response = error && typeof error === "object" && "response" in error ? error.response : void 0;
|
|
1036
|
+
const details = response?.data?.error?.details?.errors;
|
|
1037
|
+
if (!Array.isArray(details)) return {};
|
|
1038
|
+
return Object.fromEntries(
|
|
1039
|
+
details.flatMap((entry) => {
|
|
1040
|
+
if (!entry || typeof entry !== "object" || !("field" in entry) || !("code" in entry) || typeof entry.field !== "string" || typeof entry.code !== "string")
|
|
1041
|
+
return [];
|
|
1042
|
+
return [[entry.field, entry.code]];
|
|
1043
|
+
})
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
var init_notifications = __esm({
|
|
1047
|
+
"admin/src/notifications.ts"() {
|
|
1048
|
+
"use strict";
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
// 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";
|
|
1055
|
+
import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1056
|
+
function NotificationEditor({
|
|
1057
|
+
value,
|
|
1058
|
+
fields,
|
|
1059
|
+
opened,
|
|
1060
|
+
onOpen,
|
|
1061
|
+
onChange,
|
|
1062
|
+
disabled,
|
|
1063
|
+
saveError
|
|
1064
|
+
}) {
|
|
1065
|
+
const [target, setTarget] = useState4("body");
|
|
1066
|
+
const errors = notificationErrors(value, fields);
|
|
1067
|
+
function toggle(channel, enabled) {
|
|
1068
|
+
const next = { ...value };
|
|
1069
|
+
if (!enabled) delete next[channel];
|
|
1070
|
+
else if (channel === "email")
|
|
1071
|
+
next.email = {
|
|
1072
|
+
to: [],
|
|
1073
|
+
cc: [],
|
|
1074
|
+
subject: "New submission: {{form.name}}",
|
|
1075
|
+
body: "Submission {{submission.id}} received at {{submission.receivedAt}}"
|
|
1076
|
+
};
|
|
1077
|
+
else next.webhook = { url: "" };
|
|
1078
|
+
onChange(next);
|
|
1079
|
+
}
|
|
1080
|
+
return /* @__PURE__ */ jsxs7(Section, { title: "Notifications", children: [
|
|
1081
|
+
/* @__PURE__ */ jsx7(
|
|
1082
|
+
Button5,
|
|
1083
|
+
{
|
|
1084
|
+
type: "button",
|
|
1085
|
+
variant: "tertiary",
|
|
1086
|
+
onClick: onOpen,
|
|
1087
|
+
disabled: opened,
|
|
1088
|
+
children: opened ? "Notification settings opened" : "Open notification settings"
|
|
1089
|
+
}
|
|
1090
|
+
),
|
|
1091
|
+
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." }),
|
|
1093
|
+
saveError.startsWith("notifications:") && /* @__PURE__ */ jsx7(ErrorNotice, { message: saveError }),
|
|
1094
|
+
/* @__PURE__ */ jsxs7("label", { children: [
|
|
1095
|
+
/* @__PURE__ */ jsx7(
|
|
1096
|
+
"input",
|
|
1097
|
+
{
|
|
1098
|
+
type: "checkbox",
|
|
1099
|
+
disabled,
|
|
1100
|
+
checked: Boolean(value.email),
|
|
1101
|
+
onChange: (event) => toggle("email", event.target.checked)
|
|
1102
|
+
}
|
|
1103
|
+
),
|
|
1104
|
+
" ",
|
|
1105
|
+
"Email notifications"
|
|
1106
|
+
] }),
|
|
1107
|
+
value.email && /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
1108
|
+
["to", "cc"].map((key) => /* @__PURE__ */ jsx7(
|
|
1109
|
+
TextField,
|
|
1110
|
+
{
|
|
1111
|
+
label: key === "to" ? "To (one address per line)" : "Cc (one address per line)",
|
|
1112
|
+
multiline: true,
|
|
1113
|
+
value: (value.email?.[key] ?? []).join("\n"),
|
|
1114
|
+
disabled,
|
|
1115
|
+
error: errors[key],
|
|
1116
|
+
onChange: (text) => onChange({
|
|
1117
|
+
...value,
|
|
1118
|
+
email: { ...value.email, [key]: text.split("\n") }
|
|
1119
|
+
})
|
|
1120
|
+
},
|
|
1121
|
+
key
|
|
1122
|
+
)),
|
|
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",
|
|
1126
|
+
/* @__PURE__ */ jsxs7(
|
|
1127
|
+
"select",
|
|
1128
|
+
{
|
|
1129
|
+
style: { width: "100%", padding: 10 },
|
|
1130
|
+
disabled,
|
|
1131
|
+
value: value.email.replyToField ?? "",
|
|
1132
|
+
"aria-invalid": Boolean(errors.replyToField),
|
|
1133
|
+
onChange: (event) => {
|
|
1134
|
+
const email = { ...value.email };
|
|
1135
|
+
delete email.replyToField;
|
|
1136
|
+
if (event.target.value)
|
|
1137
|
+
email.replyToField = event.target.value;
|
|
1138
|
+
onChange({ ...value, email });
|
|
1139
|
+
},
|
|
1140
|
+
children: [
|
|
1141
|
+
/* @__PURE__ */ jsx7("option", { value: "", children: "None" }),
|
|
1142
|
+
fields.filter((field) => field.type === "email").map((field) => /* @__PURE__ */ jsxs7("option", { value: field.name, children: [
|
|
1143
|
+
field.label,
|
|
1144
|
+
" (",
|
|
1145
|
+
field.name,
|
|
1146
|
+
")"
|
|
1147
|
+
] }, field.name))
|
|
1148
|
+
]
|
|
1149
|
+
}
|
|
1150
|
+
)
|
|
1151
|
+
] }),
|
|
1152
|
+
errors.replyToField && /* @__PURE__ */ jsx7(ErrorNotice, { message: errors.replyToField }),
|
|
1153
|
+
/* @__PURE__ */ jsx7(
|
|
1154
|
+
TextField,
|
|
1155
|
+
{
|
|
1156
|
+
label: "Email subject",
|
|
1157
|
+
value: value.email.subject,
|
|
1158
|
+
disabled,
|
|
1159
|
+
error: errors.subject,
|
|
1160
|
+
onChange: (subject) => onChange({ ...value, email: { ...value.email, subject } })
|
|
1161
|
+
}
|
|
1162
|
+
),
|
|
1163
|
+
/* @__PURE__ */ jsx7(
|
|
1164
|
+
TextField,
|
|
1165
|
+
{
|
|
1166
|
+
label: "Email body",
|
|
1167
|
+
multiline: true,
|
|
1168
|
+
value: value.email.body,
|
|
1169
|
+
disabled,
|
|
1170
|
+
error: errors.body,
|
|
1171
|
+
onChange: (body) => onChange({ ...value, email: { ...value.email, body } })
|
|
1172
|
+
}
|
|
1173
|
+
),
|
|
1174
|
+
/* @__PURE__ */ jsxs7("label", { children: [
|
|
1175
|
+
"Insert placeholder into",
|
|
1176
|
+
/* @__PURE__ */ jsxs7(
|
|
1177
|
+
"select",
|
|
1178
|
+
{
|
|
1179
|
+
style: { width: "100%", padding: 10 },
|
|
1180
|
+
disabled,
|
|
1181
|
+
value: target,
|
|
1182
|
+
onChange: (event) => setTarget(
|
|
1183
|
+
event.target.value === "subject" ? "subject" : "body"
|
|
1184
|
+
),
|
|
1185
|
+
children: [
|
|
1186
|
+
/* @__PURE__ */ jsx7("option", { value: "subject", children: "Subject" }),
|
|
1187
|
+
/* @__PURE__ */ jsx7("option", { value: "body", children: "Body" })
|
|
1188
|
+
]
|
|
1189
|
+
}
|
|
1190
|
+
)
|
|
1191
|
+
] }),
|
|
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
|
+
)) })
|
|
1216
|
+
] }),
|
|
1217
|
+
/* @__PURE__ */ jsxs7("label", { children: [
|
|
1218
|
+
/* @__PURE__ */ jsx7(
|
|
1219
|
+
"input",
|
|
1220
|
+
{
|
|
1221
|
+
type: "checkbox",
|
|
1222
|
+
disabled,
|
|
1223
|
+
checked: Boolean(value.webhook),
|
|
1224
|
+
onChange: (event) => toggle("webhook", event.target.checked)
|
|
1225
|
+
}
|
|
1226
|
+
),
|
|
1227
|
+
" ",
|
|
1228
|
+
"Webhook notifications"
|
|
1229
|
+
] }),
|
|
1230
|
+
value.webhook && /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
1231
|
+
/* @__PURE__ */ jsx7(
|
|
1232
|
+
TextField,
|
|
1233
|
+
{
|
|
1234
|
+
label: "Webhook URL",
|
|
1235
|
+
value: value.webhook.url,
|
|
1236
|
+
disabled,
|
|
1237
|
+
error: errors.url,
|
|
1238
|
+
onChange: (url) => onChange({ ...value, webhook: { ...value.webhook, url } })
|
|
1239
|
+
}
|
|
1240
|
+
),
|
|
1241
|
+
/* @__PURE__ */ jsx7(
|
|
1242
|
+
TextField,
|
|
1243
|
+
{
|
|
1244
|
+
label: "Webhook secret",
|
|
1245
|
+
secret: true,
|
|
1246
|
+
value: value.webhook.secret ?? "",
|
|
1247
|
+
disabled,
|
|
1248
|
+
onChange: (secret) => {
|
|
1249
|
+
const webhook = { ...value.webhook };
|
|
1250
|
+
delete webhook.secret;
|
|
1251
|
+
if (secret) webhook.secret = secret;
|
|
1252
|
+
onChange({ ...value, webhook });
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
),
|
|
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." }),
|
|
1257
|
+
Object.entries(value.webhook.headers ?? {}).map(
|
|
1258
|
+
([name, header]) => /* @__PURE__ */ jsxs7(
|
|
1259
|
+
Flex6,
|
|
1260
|
+
{
|
|
1261
|
+
direction: "column",
|
|
1262
|
+
alignItems: "stretch",
|
|
1263
|
+
gap: 2,
|
|
1264
|
+
children: [
|
|
1265
|
+
/* @__PURE__ */ jsx7(
|
|
1266
|
+
TextField,
|
|
1267
|
+
{
|
|
1268
|
+
label: `Header: ${name}`,
|
|
1269
|
+
secret: true,
|
|
1270
|
+
value: header,
|
|
1271
|
+
disabled,
|
|
1272
|
+
onChange: (next) => onChange({
|
|
1273
|
+
...value,
|
|
1274
|
+
webhook: {
|
|
1275
|
+
...value.webhook,
|
|
1276
|
+
headers: {
|
|
1277
|
+
...value.webhook.headers,
|
|
1278
|
+
[name]: next
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
})
|
|
1282
|
+
}
|
|
1283
|
+
),
|
|
1284
|
+
/* @__PURE__ */ jsxs7(
|
|
1285
|
+
Button5,
|
|
1286
|
+
{
|
|
1287
|
+
type: "button",
|
|
1288
|
+
variant: "tertiary",
|
|
1289
|
+
disabled,
|
|
1290
|
+
onClick: () => {
|
|
1291
|
+
const headers = { ...value.webhook.headers };
|
|
1292
|
+
delete headers[name];
|
|
1293
|
+
onChange({
|
|
1294
|
+
...value,
|
|
1295
|
+
webhook: { ...value.webhook, headers }
|
|
1296
|
+
});
|
|
1297
|
+
},
|
|
1298
|
+
children: [
|
|
1299
|
+
"Remove ",
|
|
1300
|
+
name
|
|
1301
|
+
]
|
|
1302
|
+
}
|
|
1303
|
+
)
|
|
1304
|
+
]
|
|
1305
|
+
},
|
|
1306
|
+
name
|
|
1307
|
+
)
|
|
1308
|
+
),
|
|
1309
|
+
/* @__PURE__ */ jsx7(
|
|
1310
|
+
AddHeader,
|
|
1311
|
+
{
|
|
1312
|
+
disabled,
|
|
1313
|
+
names: Object.keys(value.webhook.headers ?? {}),
|
|
1314
|
+
onAdd: (name, header) => onChange({
|
|
1315
|
+
...value,
|
|
1316
|
+
webhook: {
|
|
1317
|
+
...value.webhook,
|
|
1318
|
+
headers: { ...value.webhook.headers, [name]: header }
|
|
1319
|
+
}
|
|
1320
|
+
})
|
|
1321
|
+
}
|
|
1322
|
+
)
|
|
1323
|
+
] })
|
|
1324
|
+
] })
|
|
1325
|
+
] });
|
|
1326
|
+
}
|
|
1327
|
+
function AddHeader({
|
|
1328
|
+
disabled,
|
|
1329
|
+
names,
|
|
1330
|
+
onAdd
|
|
1331
|
+
}) {
|
|
1332
|
+
const [name, setName] = useState4("");
|
|
1333
|
+
const [value, setValue] = useState4("");
|
|
1334
|
+
return /* @__PURE__ */ jsxs7(Fragment3, { children: [
|
|
1335
|
+
/* @__PURE__ */ jsx7(
|
|
1336
|
+
TextField,
|
|
1337
|
+
{
|
|
1338
|
+
label: "New header name",
|
|
1339
|
+
value: name,
|
|
1340
|
+
disabled,
|
|
1341
|
+
onChange: setName
|
|
1342
|
+
}
|
|
1343
|
+
),
|
|
1344
|
+
/* @__PURE__ */ jsx7(
|
|
1345
|
+
TextField,
|
|
1346
|
+
{
|
|
1347
|
+
label: "New header value",
|
|
1348
|
+
secret: true,
|
|
1349
|
+
value,
|
|
1350
|
+
disabled,
|
|
1351
|
+
onChange: setValue
|
|
1352
|
+
}
|
|
1353
|
+
),
|
|
1354
|
+
/* @__PURE__ */ jsx7(
|
|
1355
|
+
Button5,
|
|
1356
|
+
{
|
|
1357
|
+
type: "button",
|
|
1358
|
+
variant: "tertiary",
|
|
1359
|
+
disabled: disabled || !name.trim() || !value || names.some((item) => item.toLowerCase() === name.trim().toLowerCase()),
|
|
1360
|
+
onClick: () => {
|
|
1361
|
+
onAdd(name.trim(), value);
|
|
1362
|
+
setName("");
|
|
1363
|
+
setValue("");
|
|
1364
|
+
},
|
|
1365
|
+
children: "Add header"
|
|
1366
|
+
}
|
|
1367
|
+
)
|
|
1368
|
+
] });
|
|
1369
|
+
}
|
|
1370
|
+
function NotificationTest({
|
|
1371
|
+
fields,
|
|
1372
|
+
disabled,
|
|
1373
|
+
send
|
|
1374
|
+
}) {
|
|
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",
|
|
1393
|
+
{
|
|
1394
|
+
disabled: disabled || busy,
|
|
1395
|
+
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)),
|
|
1415
|
+
/* @__PURE__ */ jsx7(
|
|
1416
|
+
Button5,
|
|
1417
|
+
{
|
|
1418
|
+
type: "button",
|
|
1419
|
+
disabled: disabled || busy,
|
|
1420
|
+
onClick: async () => {
|
|
1421
|
+
setBusy(true);
|
|
1422
|
+
setError("");
|
|
1423
|
+
setErrors({});
|
|
1424
|
+
setResults(null);
|
|
1425
|
+
try {
|
|
1426
|
+
setResults(await send(samplePayload(fields, values)));
|
|
1427
|
+
} catch (cause) {
|
|
1428
|
+
setError(errorMessage(cause));
|
|
1429
|
+
setErrors(sampleErrors(cause));
|
|
1430
|
+
} finally {
|
|
1431
|
+
setBusy(false);
|
|
1432
|
+
}
|
|
1433
|
+
},
|
|
1434
|
+
children: busy ? "Sending test\u2026" : "Send test notifications"
|
|
1435
|
+
}
|
|
1436
|
+
),
|
|
1437
|
+
/* @__PURE__ */ jsx7(ErrorNotice, { message: error }),
|
|
1438
|
+
/* @__PURE__ */ jsxs7("div", { "aria-live": "polite", children: [
|
|
1439
|
+
results?.length === 0 && /* @__PURE__ */ jsx7(Typography6, { children: "No notification channels are enabled in the saved settings." }),
|
|
1440
|
+
results?.map((delivery) => /* @__PURE__ */ jsx7(
|
|
1441
|
+
"div",
|
|
1442
|
+
{
|
|
1443
|
+
style: { overflowWrap: "anywhere", marginTop: 12 },
|
|
1444
|
+
children: /* @__PURE__ */ jsxs7(
|
|
1445
|
+
Typography6,
|
|
1446
|
+
{
|
|
1447
|
+
textColor: delivery.status === "sent" ? "success700" : "danger700",
|
|
1448
|
+
children: [
|
|
1449
|
+
delivery.channel,
|
|
1450
|
+
":",
|
|
1451
|
+
" ",
|
|
1452
|
+
delivery.status === "sent" ? "Success" : "Failed",
|
|
1453
|
+
" \xB7",
|
|
1454
|
+
" ",
|
|
1455
|
+
delivery.attempts,
|
|
1456
|
+
" attempt(s)",
|
|
1457
|
+
delivery.lastError ? ` \xB7 ${delivery.lastError}` : "",
|
|
1458
|
+
delivery.sentAt ? ` \xB7 ${delivery.sentAt}` : ""
|
|
1459
|
+
]
|
|
1460
|
+
}
|
|
1461
|
+
)
|
|
1462
|
+
},
|
|
1463
|
+
delivery.channel
|
|
1464
|
+
))
|
|
1465
|
+
] })
|
|
1466
|
+
] });
|
|
1467
|
+
}
|
|
1468
|
+
var init_notification_editor = __esm({
|
|
1469
|
+
"admin/src/notification-editor.tsx"() {
|
|
1470
|
+
"use strict";
|
|
1471
|
+
init_form();
|
|
1472
|
+
init_notifications();
|
|
1473
|
+
init_ui();
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
|
|
621
1477
|
// admin/src/form-editor-page.tsx
|
|
622
|
-
import { useEffect as
|
|
623
|
-
import { Button as
|
|
624
|
-
import { Layouts as
|
|
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";
|
|
625
1481
|
import { useNavigate as useNavigate2, useParams } from "react-router-dom";
|
|
626
|
-
import { Fragment as
|
|
1482
|
+
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
627
1483
|
function FormEditorPage({ canUpdate }) {
|
|
628
1484
|
const { documentId } = useParams();
|
|
629
|
-
const client =
|
|
1485
|
+
const client = useFetchClient3();
|
|
630
1486
|
const { get } = client;
|
|
631
1487
|
const navigate = useNavigate2();
|
|
632
|
-
const [draft, setDraft] =
|
|
633
|
-
const [
|
|
634
|
-
const [
|
|
635
|
-
const [
|
|
636
|
-
const [
|
|
637
|
-
const [
|
|
638
|
-
const [
|
|
639
|
-
const [
|
|
640
|
-
const [
|
|
641
|
-
const [
|
|
642
|
-
const
|
|
643
|
-
const
|
|
644
|
-
|
|
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);
|
|
1502
|
+
useEffect3(() => {
|
|
645
1503
|
let active = true;
|
|
646
1504
|
setLoading(true);
|
|
1505
|
+
setNotificationsOpened(false);
|
|
647
1506
|
setLoaded(false);
|
|
648
1507
|
setError("");
|
|
649
1508
|
async function load() {
|
|
@@ -663,6 +1522,7 @@ function FormEditorPage({ canUpdate }) {
|
|
|
663
1522
|
consent: { ...emptyForm.consent }
|
|
664
1523
|
}
|
|
665
1524
|
);
|
|
1525
|
+
setSavedDraft(saved ? JSON.stringify(savePayload(saved, true)) : "");
|
|
666
1526
|
setVersion(saved?.version ?? null);
|
|
667
1527
|
setSchema(attrs);
|
|
668
1528
|
setLoaded(true);
|
|
@@ -712,16 +1572,16 @@ function FormEditorPage({ canUpdate }) {
|
|
|
712
1572
|
setBusy(false);
|
|
713
1573
|
}
|
|
714
1574
|
}
|
|
715
|
-
if (loading) return /* @__PURE__ */
|
|
1575
|
+
if (loading) return /* @__PURE__ */ jsx8(Page3.Loading, {});
|
|
716
1576
|
const disabled = !canUpdate || busy || fieldsLoading;
|
|
717
|
-
return /* @__PURE__ */
|
|
718
|
-
/* @__PURE__ */
|
|
719
|
-
|
|
1577
|
+
return /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
1578
|
+
/* @__PURE__ */ jsx8(
|
|
1579
|
+
Layouts3.Header,
|
|
720
1580
|
{
|
|
721
1581
|
title: documentId ? draft.name || "Form" : "Create form",
|
|
722
1582
|
subtitle: version ? `Form version ${version} \xB7 assigned by server on every save` : "New forms start at version 1.",
|
|
723
|
-
navigationAction: /* @__PURE__ */
|
|
724
|
-
|
|
1583
|
+
navigationAction: /* @__PURE__ */ jsx8(
|
|
1584
|
+
Button6,
|
|
725
1585
|
{
|
|
726
1586
|
variant: "tertiary",
|
|
727
1587
|
onClick: () => navigate("/plugins/builder/forms"),
|
|
@@ -730,18 +1590,23 @@ function FormEditorPage({ canUpdate }) {
|
|
|
730
1590
|
)
|
|
731
1591
|
}
|
|
732
1592
|
),
|
|
733
|
-
/* @__PURE__ */
|
|
734
|
-
/* @__PURE__ */
|
|
735
|
-
!loaded ? /* @__PURE__ */
|
|
736
|
-
/* @__PURE__ */
|
|
1593
|
+
/* @__PURE__ */ jsx8(Layouts3.Content, { children: /* @__PURE__ */ jsxs8(Flex7, { direction: "column", alignItems: "stretch", gap: 6, children: [
|
|
1594
|
+
/* @__PURE__ */ jsx8(ErrorNotice, { message: error }),
|
|
1595
|
+
!loaded ? /* @__PURE__ */ jsx8(Button6, { onClick: () => setRetry((value) => value + 1), children: "Retry loading form" }) : /* @__PURE__ */ jsxs8(Fragment4, { children: [
|
|
1596
|
+
/* @__PURE__ */ jsx8(
|
|
737
1597
|
"form",
|
|
738
1598
|
{
|
|
739
1599
|
onSubmit: (event) => {
|
|
740
1600
|
event.preventDefault();
|
|
741
1601
|
if (disabled) return;
|
|
742
1602
|
void run(async () => {
|
|
743
|
-
const saved = await formApi(client).save(
|
|
1603
|
+
const saved = await formApi(client).save(
|
|
1604
|
+
draft,
|
|
1605
|
+
documentId,
|
|
1606
|
+
notificationsOpened
|
|
1607
|
+
);
|
|
744
1608
|
setDraft(saved);
|
|
1609
|
+
setSavedDraft(JSON.stringify(savePayload(saved, true)));
|
|
745
1610
|
setVersion(saved.version);
|
|
746
1611
|
if (!documentId)
|
|
747
1612
|
navigate(`/plugins/builder/forms/${saved.documentId}`, {
|
|
@@ -749,9 +1614,9 @@ function FormEditorPage({ canUpdate }) {
|
|
|
749
1614
|
});
|
|
750
1615
|
});
|
|
751
1616
|
},
|
|
752
|
-
children: /* @__PURE__ */
|
|
753
|
-
/* @__PURE__ */
|
|
754
|
-
/* @__PURE__ */
|
|
1617
|
+
children: /* @__PURE__ */ jsxs8(Flex7, { direction: "column", alignItems: "stretch", gap: 6, children: [
|
|
1618
|
+
/* @__PURE__ */ jsxs8(Section, { title: "Definition", children: [
|
|
1619
|
+
/* @__PURE__ */ jsx8(
|
|
755
1620
|
TextField,
|
|
756
1621
|
{
|
|
757
1622
|
label: "Name",
|
|
@@ -761,7 +1626,7 @@ function FormEditorPage({ canUpdate }) {
|
|
|
761
1626
|
onChange: (name) => setDraft({ ...draft, name })
|
|
762
1627
|
}
|
|
763
1628
|
),
|
|
764
|
-
/* @__PURE__ */
|
|
1629
|
+
/* @__PURE__ */ jsx8(
|
|
765
1630
|
TextField,
|
|
766
1631
|
{
|
|
767
1632
|
label: "Slug",
|
|
@@ -771,11 +1636,11 @@ function FormEditorPage({ canUpdate }) {
|
|
|
771
1636
|
onChange: (slug) => setDraft({ ...draft, slug })
|
|
772
1637
|
}
|
|
773
1638
|
),
|
|
774
|
-
/* @__PURE__ */
|
|
775
|
-
/* @__PURE__ */
|
|
1639
|
+
/* @__PURE__ */ jsx8(Typography7, { variant: "pi", children: "Use lowercase letters, numbers and hyphens. The slug cannot change after creation." }),
|
|
1640
|
+
/* @__PURE__ */ jsxs8("label", { children: [
|
|
776
1641
|
"Target content type",
|
|
777
1642
|
" ",
|
|
778
|
-
/* @__PURE__ */
|
|
1643
|
+
/* @__PURE__ */ jsxs8(
|
|
779
1644
|
"select",
|
|
780
1645
|
{
|
|
781
1646
|
required: true,
|
|
@@ -784,8 +1649,8 @@ function FormEditorPage({ canUpdate }) {
|
|
|
784
1649
|
onChange: (event) => void selectTarget(event.target.value),
|
|
785
1650
|
style: { padding: 10, width: "100%" },
|
|
786
1651
|
children: [
|
|
787
|
-
/* @__PURE__ */
|
|
788
|
-
targets.map((target) => /* @__PURE__ */
|
|
1652
|
+
/* @__PURE__ */ jsx8("option", { value: "", children: "Select a target" }),
|
|
1653
|
+
targets.map((target) => /* @__PURE__ */ jsxs8("option", { value: target.uid, children: [
|
|
789
1654
|
target.name,
|
|
790
1655
|
" \u2014 ",
|
|
791
1656
|
target.uid
|
|
@@ -794,9 +1659,9 @@ function FormEditorPage({ canUpdate }) {
|
|
|
794
1659
|
}
|
|
795
1660
|
)
|
|
796
1661
|
] }),
|
|
797
|
-
fieldsLoading && /* @__PURE__ */
|
|
1662
|
+
fieldsLoading && /* @__PURE__ */ jsx8(Typography7, { children: "Loading target fields\u2026" })
|
|
798
1663
|
] }),
|
|
799
|
-
/* @__PURE__ */
|
|
1664
|
+
/* @__PURE__ */ jsx8(
|
|
800
1665
|
FieldEditor,
|
|
801
1666
|
{
|
|
802
1667
|
fields: draft.fields,
|
|
@@ -805,8 +1670,8 @@ function FormEditorPage({ canUpdate }) {
|
|
|
805
1670
|
onChange: (fields) => setDraft({ ...draft, fields })
|
|
806
1671
|
}
|
|
807
1672
|
),
|
|
808
|
-
/* @__PURE__ */
|
|
809
|
-
/* @__PURE__ */
|
|
1673
|
+
/* @__PURE__ */ jsxs8(Section, { title: "Consent", children: [
|
|
1674
|
+
/* @__PURE__ */ jsx8(
|
|
810
1675
|
TextField,
|
|
811
1676
|
{
|
|
812
1677
|
label: "Consent label",
|
|
@@ -820,7 +1685,7 @@ function FormEditorPage({ canUpdate }) {
|
|
|
820
1685
|
})
|
|
821
1686
|
}
|
|
822
1687
|
),
|
|
823
|
-
/* @__PURE__ */
|
|
1688
|
+
/* @__PURE__ */ jsx8(
|
|
824
1689
|
TextField,
|
|
825
1690
|
{
|
|
826
1691
|
label: "Consent version",
|
|
@@ -836,10 +1701,22 @@ function FormEditorPage({ canUpdate }) {
|
|
|
836
1701
|
})
|
|
837
1702
|
}
|
|
838
1703
|
),
|
|
839
|
-
/* @__PURE__ */
|
|
1704
|
+
/* @__PURE__ */ jsx8(Typography7, { variant: "pi", children: "Consent version identifies the terms accepted by the visitor and is separate from the server form version." })
|
|
840
1705
|
] }),
|
|
841
|
-
|
|
842
|
-
|
|
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,
|
|
843
1720
|
{
|
|
844
1721
|
type: "submit",
|
|
845
1722
|
disabled: disabled || !draft.targetUid || !draft.fields.length,
|
|
@@ -849,9 +1726,18 @@ function FormEditorPage({ canUpdate }) {
|
|
|
849
1726
|
] })
|
|
850
1727
|
}
|
|
851
1728
|
),
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1729
|
+
notificationsOpened && /* @__PURE__ */ jsx8(
|
|
1730
|
+
NotificationTest,
|
|
1731
|
+
{
|
|
1732
|
+
fields: draft.fields,
|
|
1733
|
+
disabled: disabled || !documentId || JSON.stringify(savePayload(draft, true)) !== savedDraft,
|
|
1734
|
+
send: (sample) => formApi(client).testSend(documentId, sample)
|
|
1735
|
+
},
|
|
1736
|
+
savedDraft
|
|
1737
|
+
),
|
|
1738
|
+
documentId && /* @__PURE__ */ jsxs8(Section, { title: "Portable definition", children: [
|
|
1739
|
+
/* @__PURE__ */ jsx8(
|
|
1740
|
+
Button6,
|
|
855
1741
|
{
|
|
856
1742
|
disabled: busy,
|
|
857
1743
|
onClick: () => void run(
|
|
@@ -863,8 +1749,8 @@ function FormEditorPage({ canUpdate }) {
|
|
|
863
1749
|
children: "Export saved form JSON"
|
|
864
1750
|
}
|
|
865
1751
|
),
|
|
866
|
-
/* @__PURE__ */
|
|
867
|
-
canUpdate && /* @__PURE__ */
|
|
1752
|
+
/* @__PURE__ */ jsx8(Typography7, { variant: "pi", children: "Export uses the saved definition. Notification credentials are masked by the server." }),
|
|
1753
|
+
canUpdate && /* @__PURE__ */ jsx8(
|
|
868
1754
|
ImportForm,
|
|
869
1755
|
{
|
|
870
1756
|
replace: true,
|
|
@@ -879,6 +1765,11 @@ function FormEditorPage({ canUpdate }) {
|
|
|
879
1765
|
documentId
|
|
880
1766
|
);
|
|
881
1767
|
setDraft(saved);
|
|
1768
|
+
setSavedDraft(
|
|
1769
|
+
JSON.stringify(savePayload(saved, true))
|
|
1770
|
+
);
|
|
1771
|
+
setNotificationsOpened(false);
|
|
1772
|
+
setError("");
|
|
882
1773
|
setVersion(saved.version);
|
|
883
1774
|
setSchema(
|
|
884
1775
|
await formApi(client).fields(saved.targetUid)
|
|
@@ -902,6 +1793,7 @@ var init_form_editor_page = __esm({
|
|
|
902
1793
|
init_form();
|
|
903
1794
|
init_ui();
|
|
904
1795
|
init_field_editor();
|
|
1796
|
+
init_notification_editor();
|
|
905
1797
|
init_import_form();
|
|
906
1798
|
}
|
|
907
1799
|
});
|
|
@@ -909,50 +1801,69 @@ var init_form_editor_page = __esm({
|
|
|
909
1801
|
// admin/src/app.tsx
|
|
910
1802
|
var app_exports = {};
|
|
911
1803
|
__export(app_exports, {
|
|
1804
|
+
SubmissionsApp: () => SubmissionsApp,
|
|
912
1805
|
default: () => App
|
|
913
1806
|
});
|
|
914
1807
|
import { Navigate, Route, Routes } from "react-router-dom";
|
|
915
|
-
import { Layouts as
|
|
916
|
-
import { jsx as
|
|
917
|
-
function
|
|
918
|
-
const { allowedActions, isLoading } =
|
|
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(
|
|
919
1812
|
["read", "update"].map((action) => ({
|
|
920
1813
|
action: `plugin::builder.forms.${action}`,
|
|
921
1814
|
subject: null
|
|
922
1815
|
}))
|
|
923
1816
|
);
|
|
924
|
-
if (isLoading) return /* @__PURE__ */
|
|
925
|
-
if (!allowedActions.canRead) return /* @__PURE__ */
|
|
1817
|
+
if (isLoading) return /* @__PURE__ */ jsx9(Page4.Loading, {});
|
|
1818
|
+
if (!allowedActions.canRead) return /* @__PURE__ */ jsx9(Page4.NoPermissions, {});
|
|
926
1819
|
const canUpdate = allowedActions.canUpdate === true;
|
|
927
|
-
return /* @__PURE__ */
|
|
928
|
-
/* @__PURE__ */
|
|
929
|
-
/* @__PURE__ */
|
|
930
|
-
/* @__PURE__ */
|
|
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(
|
|
931
1824
|
Route,
|
|
932
1825
|
{
|
|
933
1826
|
path: "forms/new",
|
|
934
|
-
element: canUpdate ? /* @__PURE__ */
|
|
1827
|
+
element: canUpdate ? /* @__PURE__ */ jsx9(FormEditorPage, { canUpdate: true }) : /* @__PURE__ */ jsx9(Page4.NoPermissions, {})
|
|
935
1828
|
}
|
|
936
1829
|
),
|
|
937
|
-
/* @__PURE__ */
|
|
1830
|
+
/* @__PURE__ */ jsx9(
|
|
938
1831
|
Route,
|
|
939
1832
|
{
|
|
940
1833
|
path: "forms/:documentId",
|
|
941
|
-
element: /* @__PURE__ */
|
|
1834
|
+
element: /* @__PURE__ */ jsx9(FormEditorPage, { canUpdate })
|
|
942
1835
|
}
|
|
943
1836
|
),
|
|
944
|
-
/* @__PURE__ */
|
|
1837
|
+
/* @__PURE__ */ jsx9(
|
|
945
1838
|
Route,
|
|
946
1839
|
{
|
|
947
1840
|
path: "*",
|
|
948
|
-
element: /* @__PURE__ */
|
|
1841
|
+
element: /* @__PURE__ */ jsx9(Page4.Error, { content: "Form page not found." })
|
|
949
1842
|
}
|
|
950
1843
|
)
|
|
951
1844
|
] }) });
|
|
952
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 }) });
|
|
1856
|
+
}
|
|
1857
|
+
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
|
+
] });
|
|
1862
|
+
}
|
|
953
1863
|
var init_app = __esm({
|
|
954
1864
|
"admin/src/app.tsx"() {
|
|
955
1865
|
"use strict";
|
|
1866
|
+
init_submission_page();
|
|
956
1867
|
init_form_list_page();
|
|
957
1868
|
init_form_editor_page();
|
|
958
1869
|
}
|
|
@@ -998,6 +1909,20 @@ var index_default = {
|
|
|
998
1909
|
Component: () => Promise.resolve().then(() => (init_app(), app_exports)),
|
|
999
1910
|
permissions: [{ action: "plugin::builder.forms.read", subject: null }]
|
|
1000
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
|
+
permissions: [
|
|
1923
|
+
{ action: "plugin::builder.submissions.read", subject: null }
|
|
1924
|
+
]
|
|
1925
|
+
});
|
|
1001
1926
|
app.registerPlugin({ id: "builder", name: "Builder" });
|
|
1002
1927
|
}
|
|
1003
1928
|
};
|