@vicacha-devs/strapi-plugin-certificates 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +17 -2
  3. package/dist/admin/CertificatesPage-CFDIEBN_.js +783 -0
  4. package/dist/admin/CertificatesPage-DeGmoX7h.cjs +783 -0
  5. package/dist/admin/SettingsPage-CsWFO1XB.js +447 -0
  6. package/dist/admin/SettingsPage-D7Ob1gV0.cjs +447 -0
  7. package/dist/admin/ar-Bv3T0_7C.cjs +258 -0
  8. package/dist/admin/ar-CDFHNnFd.js +258 -0
  9. package/dist/admin/de-BjMxn-Lt.js +258 -0
  10. package/dist/admin/de-DB5eW-yI.cjs +258 -0
  11. package/dist/admin/en-BI5Z3lPW.js +258 -0
  12. package/dist/admin/en-CsYV0VDf.cjs +258 -0
  13. package/dist/admin/es-B7uy9a-2.js +258 -0
  14. package/dist/admin/es-DEp3j3kD.cjs +258 -0
  15. package/dist/admin/fr-D7y038Pm.cjs +258 -0
  16. package/dist/admin/fr-DxbtdEIA.js +258 -0
  17. package/dist/admin/id-DfW4ARVm.js +258 -0
  18. package/dist/admin/id-Wuw9jl-Y.cjs +258 -0
  19. package/dist/admin/index-BQdS3gc3.cjs +707 -0
  20. package/dist/admin/index-BYO34da4.js +708 -0
  21. package/dist/admin/index.cjs +5 -0
  22. package/dist/admin/index.js +5 -0
  23. package/dist/admin/index.mjs +5 -0
  24. package/dist/admin/pt-C0QaikKS.js +258 -0
  25. package/dist/admin/pt-Dj2oc3Ga.cjs +258 -0
  26. package/dist/admin/src/components/IssueCertificateModal.d.ts +9 -0
  27. package/dist/admin/src/components/SettingsTabHelpers.d.ts +13 -0
  28. package/dist/admin/src/index.d.ts +11 -0
  29. package/dist/admin/src/pages/CertificatesPage.d.ts +3 -0
  30. package/dist/admin/src/pages/SettingsPage.d.ts +3 -0
  31. package/dist/admin/src/translations/cmLabels.d.ts +1 -0
  32. package/dist/admin/src/translations/cmPlaceholders.d.ts +1 -0
  33. package/dist/admin/tr-BbeagXRY.js +258 -0
  34. package/dist/admin/tr-DlGl_XMF.cjs +258 -0
  35. package/dist/admin/ur-Bwn4TrjF.js +258 -0
  36. package/dist/admin/ur-qIlUtgqb.cjs +258 -0
  37. package/dist/admin/zh-Cmg5mEvP.js +258 -0
  38. package/dist/admin/zh-D-3QDsuQ.cjs +258 -0
  39. package/dist/package.json +3 -0
  40. package/dist/server/index.cjs +2282 -0
  41. package/dist/server/index.js +2282 -0
  42. package/dist/server/index.mjs +2282 -0
  43. package/dist/server/src/bootstrap.d.ts +5 -0
  44. package/dist/server/src/content-types/certificate/schema.json.d.ts +165 -0
  45. package/dist/server/src/content-types/index.d.ts +277 -0
  46. package/dist/server/src/content-types/template/schema.json.d.ts +118 -0
  47. package/dist/server/src/controllers/admin.d.ts +31 -0
  48. package/dist/server/src/controllers/content-api.d.ts +28 -0
  49. package/dist/server/src/controllers/index.d.ts +30 -0
  50. package/dist/server/src/index.d.ts +413 -0
  51. package/dist/server/src/register.d.ts +5 -0
  52. package/dist/server/src/routes/admin.d.ts +12 -0
  53. package/dist/server/src/routes/content-api.d.ts +12 -0
  54. package/dist/server/src/routes/index.d.ts +25 -0
  55. package/dist/server/src/services/docusign.d.ts +76 -0
  56. package/dist/server/src/services/index.d.ts +76 -0
  57. package/dist/server/src/services/issuance.d.ts +54 -0
  58. package/dist/server/src/services/issuanceHelpers.d.ts +47 -0
  59. package/dist/server/src/services/settings.d.ts +17 -0
  60. package/dist/server/src/services/urlHelpers.d.ts +2 -0
  61. package/dist/shared/calendarSystems.d.ts +3 -0
  62. package/dist/shared/cryptoEngine.d.ts +27 -0
  63. package/dist/shared/documentRenderer.d.ts +12 -0
  64. package/dist/shared/index.cjs +636 -0
  65. package/dist/shared/index.d.ts +5 -0
  66. package/dist/shared/index.js +613 -0
  67. package/dist/shared/index.mjs +613 -0
  68. package/dist/shared/pluginId.d.ts +2 -0
  69. package/dist/shared/types.d.ts +194 -0
  70. package/package.json +112 -3
@@ -0,0 +1,783 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const designSystem = require("@strapi/design-system");
5
+ const admin = require("@strapi/strapi/admin");
6
+ const react = require("react");
7
+ const reactIntl = require("react-intl");
8
+ const index = require("./index-BQdS3gc3.cjs");
9
+ function trimOrUndefined(value) {
10
+ const trimmed = value.trim();
11
+ return trimmed.length > 0 ? trimmed : void 0;
12
+ }
13
+ function buildWitnessEntry(name, docId, email, role) {
14
+ const fullName = trimOrUndefined(name);
15
+ if (!fullName) return null;
16
+ return {
17
+ fullName,
18
+ documentIdRaw: trimOrUndefined(docId),
19
+ email: trimOrUndefined(email),
20
+ routingOrder: 1,
21
+ role
22
+ };
23
+ }
24
+ const IssueCertificateModal = ({
25
+ templates,
26
+ onClose,
27
+ onSubmit
28
+ }) => {
29
+ const { formatMessage } = reactIntl.useIntl();
30
+ const t = (id, defaultMessage, values) => formatMessage({ id: `${"certificates"}.${id}`, defaultMessage }, values);
31
+ const [templateSlug, setTemplateSlug] = react.useState(
32
+ templates[0]?.slug || ""
33
+ );
34
+ const [recipientFullName, setRecipientFullName] = react.useState("");
35
+ const [recipientChosenName, setRecipientChosenName] = react.useState("");
36
+ const [recipientEmail, setRecipientEmail] = react.useState("");
37
+ const [recipientPhone, _setRecipientPhone] = react.useState("");
38
+ const [recipientNationality, setRecipientNationality] = react.useState("");
39
+ const [nationalIdRaw, setNationalIdRaw] = react.useState("");
40
+ const [witness1Name, setWitness1Name] = react.useState("");
41
+ const [witness1Id, setWitness1Id] = react.useState("");
42
+ const [witness1Email, setWitness1Email] = react.useState("");
43
+ const [witness2Name, setWitness2Name] = react.useState("");
44
+ const [witness2Id, setWitness2Id] = react.useState("");
45
+ const [witness2Email, setWitness2Email] = react.useState("");
46
+ const [linkedBookingCode, setLinkedBookingCode] = react.useState("");
47
+ const [notes, setNotes] = react.useState("");
48
+ const [autoApprove, setAutoApprove] = react.useState(true);
49
+ const [sendViaESign, setSendViaESign] = react.useState(false);
50
+ const [submitting, setSubmitting] = react.useState(false);
51
+ const selectedTemplate = templates.find((tpl) => tpl.slug === templateSlug);
52
+ const handleCreate = async () => {
53
+ const cleanName = trimOrUndefined(recipientFullName);
54
+ if (!cleanName || !templateSlug) return;
55
+ setSubmitting(true);
56
+ try {
57
+ const w1 = buildWitnessEntry(
58
+ witness1Name,
59
+ witness1Id,
60
+ witness1Email,
61
+ "Official Witness 1"
62
+ );
63
+ const w2 = buildWitnessEntry(
64
+ witness2Name,
65
+ witness2Id,
66
+ witness2Email,
67
+ "Official Witness 2"
68
+ );
69
+ const witnesses = [w1, w2].filter(
70
+ (w) => w !== null
71
+ );
72
+ await onSubmit({
73
+ templateSlug,
74
+ recipientFullName: cleanName,
75
+ recipientChosenName: trimOrUndefined(recipientChosenName),
76
+ recipientEmail: trimOrUndefined(recipientEmail),
77
+ recipientPhone: trimOrUndefined(recipientPhone),
78
+ recipientNationality: trimOrUndefined(recipientNationality),
79
+ nationalIdRaw: trimOrUndefined(nationalIdRaw),
80
+ witnesses,
81
+ linkedBookingCode: trimOrUndefined(linkedBookingCode),
82
+ notes: trimOrUndefined(notes),
83
+ autoApprove: !sendViaESign && autoApprove,
84
+ sendViaESign
85
+ });
86
+ } finally {
87
+ setSubmitting(false);
88
+ }
89
+ };
90
+ return /* @__PURE__ */ jsxRuntime.jsxs(
91
+ designSystem.Box,
92
+ {
93
+ padding: 6,
94
+ background: "neutral0",
95
+ hasRadius: true,
96
+ shadow: "filterShadow",
97
+ borderColor: "neutral200",
98
+ marginBottom: 6,
99
+ children: [
100
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 4, children: [
101
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", fontWeight: "bold", children: t(
102
+ "modal.title_issue_official_signed_certificate",
103
+ "Issue Official Signed Certificate"
104
+ ) }),
105
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", onClick: onClose, children: t("modal.close", "Close") })
106
+ ] }),
107
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 4, alignItems: "stretch", children: [
108
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { required: true, children: [
109
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.certificate_template", "Certificate Template") }),
110
+ /* @__PURE__ */ jsxRuntime.jsx(
111
+ designSystem.SingleSelect,
112
+ {
113
+ placeholder: t(
114
+ "placeholder.select_certificate_template",
115
+ "Select certificate template"
116
+ ),
117
+ value: templateSlug,
118
+ onChange: (val) => setTemplateSlug(String(val)),
119
+ children: templates.map((tpl) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelectOption, { value: tpl.slug, children: [
120
+ tpl.name,
121
+ " (",
122
+ tpl.serialPrefix,
123
+ ")"
124
+ ] }, tpl.slug))
125
+ }
126
+ )
127
+ ] }),
128
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, wrap: "wrap", children: [
129
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 240 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { required: true, children: [
130
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t(
131
+ "field.recipient_full_legal_name",
132
+ "Recipient Full Legal Name"
133
+ ) }),
134
+ /* @__PURE__ */ jsxRuntime.jsx(
135
+ designSystem.TextInput,
136
+ {
137
+ value: recipientFullName,
138
+ onChange: (e) => setRecipientFullName(e.target.value),
139
+ placeholder: t(
140
+ "placeholder.e_g_carlos_andres_perez",
141
+ "e.g. Carlos Andres Perez"
142
+ )
143
+ }
144
+ )
145
+ ] }) }),
146
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 240 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
147
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t(
148
+ "field.chosen_name_honorific_title_optional",
149
+ "Chosen Name / Honorific / Title (Optional)"
150
+ ) }),
151
+ /* @__PURE__ */ jsxRuntime.jsx(
152
+ designSystem.TextInput,
153
+ {
154
+ value: recipientChosenName,
155
+ onChange: (e) => setRecipientChosenName(e.target.value),
156
+ placeholder: t(
157
+ "placeholder.e_g_ibrahim_chosen_muslim_name",
158
+ "e.g. Ibrahim / Chosen Muslim Name"
159
+ )
160
+ }
161
+ )
162
+ ] }) })
163
+ ] }),
164
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, wrap: "wrap", children: [
165
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
166
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t(
167
+ "field.national_id_passport_hashed_sha_256_at_rest",
168
+ "National ID / Passport (Hashed SHA-256 at rest)"
169
+ ) }),
170
+ /* @__PURE__ */ jsxRuntime.jsx(
171
+ designSystem.TextInput,
172
+ {
173
+ value: nationalIdRaw,
174
+ onChange: (e) => setNationalIdRaw(e.target.value),
175
+ placeholder: t(
176
+ "placeholder.e_g_cc_1020304050",
177
+ "e.g. CC 1020304050"
178
+ )
179
+ }
180
+ )
181
+ ] }) }),
182
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
183
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.nationality", "Nationality") }),
184
+ /* @__PURE__ */ jsxRuntime.jsx(
185
+ designSystem.TextInput,
186
+ {
187
+ value: recipientNationality,
188
+ onChange: (e) => setRecipientNationality(e.target.value),
189
+ placeholder: t("placeholder.e_g_colombian", "e.g. Colombian")
190
+ }
191
+ )
192
+ ] }) }),
193
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 200 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
194
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.recipient_email", "Recipient Email") }),
195
+ /* @__PURE__ */ jsxRuntime.jsx(
196
+ designSystem.TextInput,
197
+ {
198
+ value: recipientEmail,
199
+ onChange: (e) => setRecipientEmail(e.target.value),
200
+ placeholder: t(
201
+ "placeholder.recipient_example_org",
202
+ "recipient@example.org"
203
+ )
204
+ }
205
+ )
206
+ ] }) }),
207
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 180 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
208
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.linked_booking_code", "Linked Booking Code") }),
209
+ /* @__PURE__ */ jsxRuntime.jsx(
210
+ designSystem.TextInput,
211
+ {
212
+ value: linkedBookingCode,
213
+ onChange: (e) => setLinkedBookingCode(e.target.value),
214
+ placeholder: t(
215
+ "placeholder.e_g_bk_a1b2c3d4",
216
+ "e.g. BK-A1B2C3D4"
217
+ )
218
+ }
219
+ )
220
+ ] }) })
221
+ ] }),
222
+ /* @__PURE__ */ jsxRuntime.jsxs(
223
+ designSystem.Box,
224
+ {
225
+ padding: 4,
226
+ background: "neutral100",
227
+ hasRadius: true,
228
+ borderColor: "neutral200",
229
+ style: { border: "1px solid rgba(128, 128, 128, 0.18)" },
230
+ children: [
231
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: [
232
+ t(
233
+ "modal.official_witnesses_attestation",
234
+ "Official Witnesses Attestation"
235
+ ),
236
+ " ",
237
+ selectedTemplate?.requiresWitnesses ? t(
238
+ "modal.minimum_witnesses_required",
239
+ "(Minimum {count} required)",
240
+ {
241
+ count: selectedTemplate.minWitnesses
242
+ }
243
+ ) : t("modal.optional_suffix", "(Optional)")
244
+ ] }),
245
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, wrap: "wrap", marginTop: 3, children: [
246
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 180 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
247
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.witness_1_full_name", "Witness 1 Full Name") }),
248
+ /* @__PURE__ */ jsxRuntime.jsx(
249
+ designSystem.TextInput,
250
+ {
251
+ value: witness1Name,
252
+ onChange: (e) => setWitness1Name(e.target.value),
253
+ placeholder: t(
254
+ "placeholder.e_g_ahmad_al_mansour",
255
+ "e.g., Ahmad Al-Mansour"
256
+ )
257
+ }
258
+ )
259
+ ] }) }),
260
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 150 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
261
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.witness_1_id_document", "Witness 1 ID Document") }),
262
+ /* @__PURE__ */ jsxRuntime.jsx(
263
+ designSystem.TextInput,
264
+ {
265
+ value: witness1Id,
266
+ onChange: (e) => setWitness1Id(e.target.value),
267
+ placeholder: t(
268
+ "placeholder.e_g_cc_79123456_masked",
269
+ "e.g., CC 79123456 (masked)"
270
+ )
271
+ }
272
+ )
273
+ ] }) }),
274
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 180 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
275
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t(
276
+ "field.witness_1_email_docusign",
277
+ "Witness 1 Email (DocuSign)"
278
+ ) }),
279
+ /* @__PURE__ */ jsxRuntime.jsx(
280
+ designSystem.TextInput,
281
+ {
282
+ value: witness1Email,
283
+ onChange: (e) => setWitness1Email(e.target.value),
284
+ placeholder: t(
285
+ "placeholder.e_g_witness1_example_org",
286
+ "e.g., witness1@example.org"
287
+ )
288
+ }
289
+ )
290
+ ] }) })
291
+ ] }),
292
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, wrap: "wrap", marginTop: 3, children: [
293
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 180 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
294
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.witness_2_full_name", "Witness 2 Full Name") }),
295
+ /* @__PURE__ */ jsxRuntime.jsx(
296
+ designSystem.TextInput,
297
+ {
298
+ value: witness2Name,
299
+ onChange: (e) => setWitness2Name(e.target.value),
300
+ placeholder: t(
301
+ "placeholder.e_g_yusuf_benavides",
302
+ "e.g., Yusuf Benavides"
303
+ )
304
+ }
305
+ )
306
+ ] }) }),
307
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 150 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
308
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.witness_2_id_document", "Witness 2 ID Document") }),
309
+ /* @__PURE__ */ jsxRuntime.jsx(
310
+ designSystem.TextInput,
311
+ {
312
+ value: witness2Id,
313
+ onChange: (e) => setWitness2Id(e.target.value),
314
+ placeholder: t(
315
+ "placeholder.e_g_cc_80234567_masked",
316
+ "e.g., CC 80234567 (masked)"
317
+ )
318
+ }
319
+ )
320
+ ] }) }),
321
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1, minWidth: 180 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
322
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t(
323
+ "field.witness_2_email_docusign",
324
+ "Witness 2 Email (DocuSign)"
325
+ ) }),
326
+ /* @__PURE__ */ jsxRuntime.jsx(
327
+ designSystem.TextInput,
328
+ {
329
+ value: witness2Email,
330
+ onChange: (e) => setWitness2Email(e.target.value),
331
+ placeholder: t(
332
+ "placeholder.e_g_witness2_example_org",
333
+ "e.g., witness2@example.org"
334
+ )
335
+ }
336
+ )
337
+ ] }) })
338
+ ] })
339
+ ]
340
+ }
341
+ ),
342
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
343
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.internal_registry_notes", "Internal Registry Notes") }),
344
+ /* @__PURE__ */ jsxRuntime.jsx(
345
+ designSystem.Textarea,
346
+ {
347
+ placeholder: t(
348
+ "placeholder.attestation_context_officiating_imam_or_ceremony",
349
+ "Attestation context, officiating Imam, or ceremony notes..."
350
+ ),
351
+ value: notes,
352
+ onChange: (e) => setNotes(e.target.value)
353
+ }
354
+ )
355
+ ] }),
356
+ /* @__PURE__ */ jsxRuntime.jsx(
357
+ designSystem.Checkbox,
358
+ {
359
+ checked: autoApprove && !sendViaESign,
360
+ disabled: sendViaESign,
361
+ onCheckedChange: (val) => setAutoApprove(Boolean(val)),
362
+ children: t(
363
+ "checkbox.immediately_sign_with_ed25519",
364
+ "Immediately sign with Ed25519 private key and mark as Issued"
365
+ )
366
+ }
367
+ ),
368
+ /* @__PURE__ */ jsxRuntime.jsx(
369
+ designSystem.Checkbox,
370
+ {
371
+ checked: sendViaESign,
372
+ onCheckedChange: (val) => setSendViaESign(Boolean(val)),
373
+ children: t(
374
+ "checkbox.dispatch_docusign_esignature_envelope",
375
+ "Dispatch DocuSign eSignature Envelope to Witnesses & Signatories first (auto-approves on completion webhook)"
376
+ )
377
+ }
378
+ ),
379
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "flex-end", gap: 3, children: [
380
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", onClick: onClose, children: t("modal.cancel", "Cancel") }),
381
+ /* @__PURE__ */ jsxRuntime.jsx(
382
+ designSystem.Button,
383
+ {
384
+ onClick: handleCreate,
385
+ loading: submitting,
386
+ disabled: !recipientFullName.trim() || !templateSlug,
387
+ children: sendViaESign ? t(
388
+ "modal.create_and_send_docusign",
389
+ "Create & Send via DocuSign"
390
+ ) : t(
391
+ "modal.sign_and_issue_certificate",
392
+ "Sign & Issue Certificate"
393
+ )
394
+ }
395
+ )
396
+ ] })
397
+ ] })
398
+ ]
399
+ }
400
+ );
401
+ };
402
+ const CertificatesPage = () => {
403
+ const { formatMessage } = reactIntl.useIntl();
404
+ const t = (id, defaultMessage, values) => formatMessage({ id: `${index.PLUGIN_ID}.${id}`, defaultMessage }, values);
405
+ const { get, post, put } = admin.useFetchClient();
406
+ const { toggleNotification } = admin.useNotification();
407
+ const [records, setRecords] = react.useState([]);
408
+ const [templates, setTemplates] = react.useState([]);
409
+ const [stats, setStats] = react.useState({
410
+ total: 0,
411
+ issued: 0,
412
+ pending_approval: 0,
413
+ revoked: 0,
414
+ expired: 0
415
+ });
416
+ const [statusFilter, setStatusFilter] = react.useState("all");
417
+ const [searchQuery, setSearchQuery] = react.useState("");
418
+ const [showIssueModal, setShowIssueModal] = react.useState(false);
419
+ const [loading, setLoading] = react.useState(true);
420
+ const loadData = react.useCallback(async () => {
421
+ setLoading(true);
422
+ try {
423
+ const queryParams = new URLSearchParams();
424
+ if (statusFilter && statusFilter !== "all") {
425
+ queryParams.set("status", statusFilter);
426
+ }
427
+ if (searchQuery.trim()) {
428
+ queryParams.set("search", searchQuery.trim());
429
+ }
430
+ const [certRes, tplRes] = await Promise.all([
431
+ get(
432
+ `/${index.PLUGIN_ID}/certificates?${queryParams.toString()}`
433
+ ),
434
+ get(`/${index.PLUGIN_ID}/templates`)
435
+ ]);
436
+ setRecords(certRes.data?.data || []);
437
+ if (certRes.data?.meta?.stats) {
438
+ setStats(certRes.data.meta.stats);
439
+ }
440
+ setTemplates(tplRes.data?.data || []);
441
+ } catch {
442
+ toggleNotification({
443
+ type: "danger",
444
+ message: t(
445
+ "page.ui.msg_failed_to_load_certificates_registry",
446
+ "Failed to load certificates registry"
447
+ )
448
+ });
449
+ } finally {
450
+ setLoading(false);
451
+ }
452
+ }, [get, searchQuery, statusFilter, toggleNotification]);
453
+ react.useEffect(() => {
454
+ void loadData();
455
+ }, [loadData]);
456
+ const handleIssue = async (payload) => {
457
+ try {
458
+ const res = await post(
459
+ `/${index.PLUGIN_ID}/certificates/issue`,
460
+ payload
461
+ );
462
+ toggleNotification({
463
+ type: "success",
464
+ message: `Certificate ${res.data?.data?.serialNumber} created`
465
+ });
466
+ setShowIssueModal(false);
467
+ await loadData();
468
+ } catch (err) {
469
+ toggleNotification({
470
+ type: "danger",
471
+ message: err.message || "Failed to issue certificate"
472
+ });
473
+ }
474
+ };
475
+ const handleSendDocuSign = async (documentId) => {
476
+ try {
477
+ const res = await post(
478
+ `/${index.PLUGIN_ID}/certificates/${documentId}/send-docusign`,
479
+ {}
480
+ );
481
+ toggleNotification({
482
+ type: "success",
483
+ message: `Dispatched DocuSign envelope ${res.data?.data?.envelopeId}`
484
+ });
485
+ await loadData();
486
+ } catch (err) {
487
+ toggleNotification({
488
+ type: "danger",
489
+ message: err.message || "Failed to send DocuSign eSignature envelope"
490
+ });
491
+ }
492
+ };
493
+ const handleApprove = async (documentId) => {
494
+ try {
495
+ await put(`/${index.PLUGIN_ID}/certificates/${documentId}/approve`, {});
496
+ toggleNotification({
497
+ type: "success",
498
+ message: t(
499
+ "page.ui.msg_certificate_approved_and_signed_with_ed25519",
500
+ "Certificate approved and signed with Ed25519"
501
+ )
502
+ });
503
+ await loadData();
504
+ } catch (err) {
505
+ toggleNotification({
506
+ type: "danger",
507
+ message: err.message || "Failed to approve certificate"
508
+ });
509
+ }
510
+ };
511
+ const handleRevoke = async (documentId) => {
512
+ try {
513
+ await put(`/${index.PLUGIN_ID}/certificates/${documentId}/revoke`, {
514
+ reason: "Revoked via Admin Registry"
515
+ });
516
+ toggleNotification({
517
+ type: "warning",
518
+ message: t("page.ui.msg_certificate_revoked", "Certificate revoked")
519
+ });
520
+ await loadData();
521
+ } catch {
522
+ toggleNotification({
523
+ type: "danger",
524
+ message: t(
525
+ "page.ui.msg_failed_to_revoke_certificate",
526
+ "Failed to revoke certificate"
527
+ )
528
+ });
529
+ }
530
+ };
531
+ const handleOpenPrintable = async (documentId) => {
532
+ try {
533
+ const res = await get(
534
+ `/${index.PLUGIN_ID}/certificates/${documentId}/print`
535
+ );
536
+ const htmlContent = typeof res.data === "string" ? res.data : String(res.data || "");
537
+ const blob = new Blob([htmlContent], { type: "text/html;charset=utf-8" });
538
+ const blobUrl = URL.createObjectURL(blob);
539
+ window.open(blobUrl, "_blank", "noopener,noreferrer");
540
+ } catch {
541
+ toggleNotification({
542
+ type: "danger",
543
+ message: t(
544
+ "page.ui.msg_unable_to_render_printable_certificate_document",
545
+ "Unable to render printable certificate document"
546
+ )
547
+ });
548
+ }
549
+ };
550
+ return /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Root, { children: [
551
+ /* @__PURE__ */ jsxRuntime.jsx(
552
+ admin.Layouts.Header,
553
+ {
554
+ title: t(
555
+ "page.ui.title_official_certificates_cryptographic_registry",
556
+ "Official Certificates & Cryptographic Registry"
557
+ ),
558
+ subtitle: t(
559
+ "page.ui.subtitle_ed25519_digitally_signed_certificates_optional_d",
560
+ "Ed25519 digitally signed certificates, optional DocuSign eSignature envelopes, witness attestation, and QR verification"
561
+ ),
562
+ primaryAction: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: () => setShowIssueModal(true), children: t(
563
+ "page.ui.issue_signed_certificate",
564
+ "+ Issue Signed Certificate"
565
+ ) })
566
+ }
567
+ ),
568
+ /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Content, { children: [
569
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, marginBottom: 6, wrap: "wrap", children: [
570
+ /* @__PURE__ */ jsxRuntime.jsxs(
571
+ designSystem.Box,
572
+ {
573
+ padding: 4,
574
+ background: "neutral0",
575
+ hasRadius: true,
576
+ shadow: "filterShadow",
577
+ style: { flex: 1, minWidth: 160 },
578
+ children: [
579
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: t("page.ui.total_certificates", "Total Certificates") }),
580
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", fontWeight: "bold", children: stats.total }) })
581
+ ]
582
+ }
583
+ ),
584
+ /* @__PURE__ */ jsxRuntime.jsxs(
585
+ designSystem.Box,
586
+ {
587
+ padding: 4,
588
+ background: "neutral0",
589
+ hasRadius: true,
590
+ shadow: "filterShadow",
591
+ style: { flex: 1, minWidth: 160 },
592
+ children: [
593
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "success600", children: t("page.ui.issued_signed", "Issued & Signed") }),
594
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", fontWeight: "bold", children: stats.issued }) })
595
+ ]
596
+ }
597
+ ),
598
+ /* @__PURE__ */ jsxRuntime.jsxs(
599
+ designSystem.Box,
600
+ {
601
+ padding: 4,
602
+ background: "neutral0",
603
+ hasRadius: true,
604
+ shadow: "filterShadow",
605
+ style: { flex: 1, minWidth: 160 },
606
+ children: [
607
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "warning600", children: t("page.ui.pending_approval", "Pending Approval") }),
608
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", fontWeight: "bold", children: stats.pending_approval }) })
609
+ ]
610
+ }
611
+ ),
612
+ /* @__PURE__ */ jsxRuntime.jsxs(
613
+ designSystem.Box,
614
+ {
615
+ padding: 4,
616
+ background: "neutral0",
617
+ hasRadius: true,
618
+ shadow: "filterShadow",
619
+ style: { flex: 1, minWidth: 160 },
620
+ children: [
621
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "danger600", children: t("page.ui.revoked_expired", "Revoked / Expired") }),
622
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", fontWeight: "bold", children: stats.revoked + stats.expired }) })
623
+ ]
624
+ }
625
+ )
626
+ ] }),
627
+ showIssueModal && /* @__PURE__ */ jsxRuntime.jsx(
628
+ IssueCertificateModal,
629
+ {
630
+ templates,
631
+ onClose: () => setShowIssueModal(false),
632
+ onSubmit: handleIssue
633
+ }
634
+ ),
635
+ /* @__PURE__ */ jsxRuntime.jsx(
636
+ designSystem.Box,
637
+ {
638
+ padding: 4,
639
+ background: "neutral0",
640
+ hasRadius: true,
641
+ shadow: "filterShadow",
642
+ marginBottom: 4,
643
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "flex-end", wrap: "wrap", children: [
644
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { minWidth: 260, flex: 1 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
645
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.search_certificates", "Search Certificates") }),
646
+ /* @__PURE__ */ jsxRuntime.jsx(
647
+ designSystem.TextInput,
648
+ {
649
+ placeholder: t(
650
+ "placeholder.search_by_serial_number_recipient_name_chosen_na",
651
+ "Search by serial number, recipient name, chosen name, or email..."
652
+ ),
653
+ value: searchQuery,
654
+ onChange: (e) => setSearchQuery(e.target.value)
655
+ }
656
+ )
657
+ ] }) }),
658
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { minWidth: 200 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
659
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.certificate_status", "Certificate Status") }),
660
+ /* @__PURE__ */ jsxRuntime.jsxs(
661
+ designSystem.SingleSelect,
662
+ {
663
+ placeholder: t("placeholder.all_statuses", "All Statuses"),
664
+ value: statusFilter,
665
+ onChange: (val) => setStatusFilter(String(val)),
666
+ children: [
667
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "all", children: t("page.ui.all_statuses", "All Statuses") }),
668
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "issued", children: t("page.ui.issued", "Issued") }),
669
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "pending_approval", children: t("page.ui.pending_approval", "Pending Approval") }),
670
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "revoked", children: t("page.ui.revoked", "Revoked") }),
671
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "expired", children: t("page.ui.expired", "Expired") })
672
+ ]
673
+ }
674
+ )
675
+ ] }) }),
676
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", onClick: () => void loadData(), children: t("page.ui.refresh", "Refresh") })
677
+ ] })
678
+ }
679
+ ),
680
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, background: "neutral0", hasRadius: true, shadow: "filterShadow", children: loading ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: t(
681
+ "page.ui.loading_official_certificates_registry",
682
+ "Loading official certificates registry..."
683
+ ) }) : records.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: t(
684
+ "page.ui.no_certificates_found_matching_the_current_filte",
685
+ "No certificates found matching the current filters."
686
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 3, alignItems: "stretch", children: records.map((rec) => {
687
+ const secondaryText = Object.values(rec.secondaryDates || {}).map((d) => `${d.label}: ${d.compact || d.formatted}`).join(" • ");
688
+ return /* @__PURE__ */ jsxRuntime.jsx(
689
+ designSystem.Box,
690
+ {
691
+ padding: 4,
692
+ background: "neutral100",
693
+ hasRadius: true,
694
+ borderColor: "neutral200",
695
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
696
+ designSystem.Flex,
697
+ {
698
+ justifyContent: "space-between",
699
+ alignItems: "center",
700
+ wrap: "wrap",
701
+ gap: 3,
702
+ children: [
703
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
704
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
705
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", variant: "omega", children: rec.serialNumber }),
706
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: rec.status.toUpperCase() }),
707
+ rec.esignEnvelopeId && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: t(
708
+ "page.ui.docusign_badge",
709
+ "DOCUSIGN: {status}",
710
+ {
711
+ status: (rec.esignEnvelopeStatus || "sent").toUpperCase()
712
+ }
713
+ ) }),
714
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: rec.templateName })
715
+ ] }),
716
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { fontWeight: "semiBold", children: [
717
+ rec.recipientFullName,
718
+ rec.recipientChosenName ? ` (${rec.recipientChosenName})` : ""
719
+ ] }) }),
720
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginTop: 1, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
721
+ t("page.ui.issued_date", "Issued: {date}", {
722
+ date: rec.issuedAt.slice(0, 10)
723
+ }),
724
+ secondaryText ? ` • ${secondaryText}` : "",
725
+ rec.nationalIdMasked ? ` • ${t("page.ui.id_masked", "ID: {id}", {
726
+ id: rec.nationalIdMasked
727
+ })}` : "",
728
+ " • ",
729
+ t(
730
+ "page.ui.witnesses_count",
731
+ "Witnesses: {count}",
732
+ {
733
+ count: (rec.witnesses || []).length
734
+ }
735
+ )
736
+ ] }) })
737
+ ] }),
738
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
739
+ /* @__PURE__ */ jsxRuntime.jsx(
740
+ designSystem.Button,
741
+ {
742
+ variant: "secondary",
743
+ onClick: () => void handleOpenPrintable(rec.documentId),
744
+ children: t("page.ui.print_pdf", "Print / PDF")
745
+ }
746
+ ),
747
+ rec.status === "pending_approval" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
748
+ /* @__PURE__ */ jsxRuntime.jsx(
749
+ designSystem.Button,
750
+ {
751
+ variant: "tertiary",
752
+ onClick: () => void handleSendDocuSign(rec.documentId),
753
+ children: t("page.ui.send_docusign", "Send DocuSign")
754
+ }
755
+ ),
756
+ /* @__PURE__ */ jsxRuntime.jsx(
757
+ designSystem.Button,
758
+ {
759
+ onClick: () => void handleApprove(rec.documentId),
760
+ children: t("page.ui.approve_sign", "Approve & Sign")
761
+ }
762
+ )
763
+ ] }),
764
+ rec.status === "issued" && /* @__PURE__ */ jsxRuntime.jsx(
765
+ designSystem.Button,
766
+ {
767
+ variant: "danger-light",
768
+ onClick: () => void handleRevoke(rec.documentId),
769
+ children: t("page.ui.revoke", "Revoke")
770
+ }
771
+ )
772
+ ] })
773
+ ]
774
+ }
775
+ )
776
+ },
777
+ rec.documentId
778
+ );
779
+ }) }) })
780
+ ] })
781
+ ] });
782
+ };
783
+ exports.CertificatesPage = CertificatesPage;