@vicacha-devs/strapi-plugin-broadcasts 0.0.0-stage → 0.1.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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +15 -2
  3. package/dist/admin/BroadcastsHomePage-CM_N_spo.mjs +1210 -0
  4. package/dist/admin/BroadcastsHomePage-DQueM7nj.js +1228 -0
  5. package/dist/admin/SettingsPage-TawvAAiv.js +1187 -0
  6. package/dist/admin/SettingsPage-ikcb0cfh.mjs +1169 -0
  7. package/dist/admin/ar-vwekVqfp.js +232 -0
  8. package/dist/admin/ar-wZTCd3Za.mjs +232 -0
  9. package/dist/admin/de-IZicxuvv.mjs +232 -0
  10. package/dist/admin/de-Qa2SQQfY.js +232 -0
  11. package/dist/admin/en-B-HnZo8U.mjs +232 -0
  12. package/dist/admin/en-D1byrK0s.js +232 -0
  13. package/dist/admin/es-Co7PmAj8.mjs +232 -0
  14. package/dist/admin/es-Dgylqagv.js +232 -0
  15. package/dist/admin/fr-B9zZn3z1.mjs +232 -0
  16. package/dist/admin/fr-C7-MIWnw.js +232 -0
  17. package/dist/admin/index-CApLw9Uu.mjs +1036 -0
  18. package/dist/admin/index-Dw6J7U1v.js +1053 -0
  19. package/dist/admin/index.js +6 -0
  20. package/dist/admin/index.mjs +6 -0
  21. package/dist/admin/it-DJU9iBqT.mjs +232 -0
  22. package/dist/admin/it-D_mulZtx.js +232 -0
  23. package/dist/admin/pt-B9swwjPu.js +232 -0
  24. package/dist/admin/pt-Dr8u5we0.mjs +232 -0
  25. package/dist/admin/ru-B0NNiMNA.js +232 -0
  26. package/dist/admin/ru-Bd4cEKDV.mjs +232 -0
  27. package/dist/admin/selectOptions-BlvNrhvc.mjs +150 -0
  28. package/dist/admin/selectOptions-DuHQ0KK3.js +149 -0
  29. package/dist/admin/src/components/AutomationAndLogsTabs.d.ts +16 -0
  30. package/dist/admin/src/components/BridgesSandboxTab.d.ts +8 -0
  31. package/dist/admin/src/components/CampaignComposerForm.d.ts +26 -0
  32. package/dist/admin/src/components/CampaignsTableSection.d.ts +27 -0
  33. package/dist/admin/src/components/DeliveryLedgerSection.d.ts +17 -0
  34. package/dist/admin/src/components/Initializer.d.ts +6 -0
  35. package/dist/admin/src/components/OrganizationEmailTab.d.ts +10 -0
  36. package/dist/admin/src/components/PluginIcon.d.ts +3 -0
  37. package/dist/admin/src/components/WhatsAppTransportTab.d.ts +11 -0
  38. package/dist/admin/src/index.d.ts +24 -0
  39. package/dist/admin/src/pages/BroadcastsHomePage.d.ts +2 -0
  40. package/dist/admin/src/pages/SettingsPage.d.ts +3 -0
  41. package/dist/admin/src/permissions.d.ts +30 -0
  42. package/dist/admin/src/pluginId.d.ts +2 -0
  43. package/dist/admin/src/translations/cmLabels.d.ts +1 -0
  44. package/dist/admin/src/translations/cmLabelsExtra.d.ts +1 -0
  45. package/dist/admin/src/translations/cmPlaceholders.d.ts +1 -0
  46. package/dist/admin/src/translations/cmPlaceholdersExtra.d.ts +1 -0
  47. package/dist/admin/src/utils/extractApiError.d.ts +1 -0
  48. package/dist/admin/src/utils/selectOptions.d.ts +21 -0
  49. package/dist/admin/tr-Cc-ItZUP.js +232 -0
  50. package/dist/admin/tr-DlyHa2Vk.mjs +232 -0
  51. package/dist/admin/zh-CLzJ7x3y.mjs +232 -0
  52. package/dist/admin/zh-CaqwZk-p.js +232 -0
  53. package/dist/server/index.js +3399 -0
  54. package/dist/server/index.mjs +3399 -0
  55. package/dist/server/src/bootstrap.d.ts +5 -0
  56. package/dist/server/src/config/index.d.ts +5 -0
  57. package/dist/server/src/content-types/automation-rule/index.d.ts +90 -0
  58. package/dist/server/src/content-types/automation-rule/schema.json.d.ts +90 -0
  59. package/dist/server/src/content-types/campaign/index.d.ts +118 -0
  60. package/dist/server/src/content-types/campaign/schema.json.d.ts +118 -0
  61. package/dist/server/src/content-types/delivery-log/index.d.ts +71 -0
  62. package/dist/server/src/content-types/delivery-log/schema.json.d.ts +71 -0
  63. package/dist/server/src/content-types/index.d.ts +279 -0
  64. package/dist/server/src/controllers/broadcastController.d.ts +32 -0
  65. package/dist/server/src/controllers/index.d.ts +22 -0
  66. package/dist/server/src/destroy.d.ts +5 -0
  67. package/dist/server/src/eventHubRegistry.d.ts +5 -0
  68. package/dist/server/src/index.d.ts +463 -0
  69. package/dist/server/src/register.d.ts +5 -0
  70. package/dist/server/src/routes/admin.d.ts +17 -0
  71. package/dist/server/src/routes/content-api.d.ts +13 -0
  72. package/dist/server/src/routes/index.d.ts +31 -0
  73. package/dist/server/src/services/automationService.d.ts +33 -0
  74. package/dist/server/src/services/campaignChannelDispatch.d.ts +20 -0
  75. package/dist/server/src/services/campaignHelpers.d.ts +20 -0
  76. package/dist/server/src/services/campaignLifecycle.d.ts +23 -0
  77. package/dist/server/src/services/campaignService.d.ts +54 -0
  78. package/dist/server/src/services/deliveryLedger.d.ts +18 -0
  79. package/dist/server/src/services/emailDesignerBridge.d.ts +30 -0
  80. package/dist/server/src/services/idempotency.d.ts +5 -0
  81. package/dist/server/src/services/index.d.ts +119 -0
  82. package/dist/server/src/services/settings.d.ts +12 -0
  83. package/dist/server/src/services/transportService.d.ts +70 -0
  84. package/dist/shared/index.d.ts +1 -0
  85. package/dist/shared/index.js +825 -0
  86. package/dist/shared/index.mjs +823 -0
  87. package/package.json +109 -3
@@ -0,0 +1,1228 @@
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 reactRouterDom = require("react-router-dom");
9
+ const index = require("./index-Dw6J7U1v.js");
10
+ const selectOptions = require("./selectOptions-DuHQ0KK3.js");
11
+ function _interopNamespace(e) {
12
+ if (e && e.__esModule) return e;
13
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
14
+ if (e) {
15
+ for (const k in e) {
16
+ if (k !== "default") {
17
+ const d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: () => e[k]
21
+ });
22
+ }
23
+ }
24
+ }
25
+ n.default = e;
26
+ return Object.freeze(n);
27
+ }
28
+ const React__namespace = /* @__PURE__ */ _interopNamespace(React);
29
+ const RequiredTextField = ({ name, label, hint, placeholder, value, onChange }) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name, required: true, hint, children: [
30
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: label }),
31
+ /* @__PURE__ */ jsxRuntime.jsx(
32
+ designSystem.TextInput,
33
+ {
34
+ name,
35
+ placeholder,
36
+ value,
37
+ onChange: (e) => onChange(e.target.value)
38
+ }
39
+ ),
40
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
41
+ ] });
42
+ const EmailTemplateSelectorField = ({
43
+ t,
44
+ emailTemplates,
45
+ value,
46
+ onChange
47
+ }) => /* @__PURE__ */ jsxRuntime.jsxs(
48
+ designSystem.Field.Root,
49
+ {
50
+ name: "emailTemplateRef",
51
+ hint: t(
52
+ "page.ui.email_designer_template_hint",
53
+ "Wraps email broadcasts in an Email Designer 5 layout (injects '{{subject}}', '{{bodyHtml}}', '{{recipientName}}', '{{secondaryDate}}')."
54
+ ),
55
+ children: [
56
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t(
57
+ "field.email_designer_html_template_optional",
58
+ "Email Designer HTML Template (optional)"
59
+ ) }),
60
+ emailTemplates.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
61
+ designSystem.SingleSelect,
62
+ {
63
+ placeholder: t(
64
+ "placeholder.none_use_inline_campaign_body",
65
+ "None (use inline campaign body)"
66
+ ),
67
+ value: value || "__none__",
68
+ onChange: (val) => onChange(val === "__none__" ? "" : String(val)),
69
+ children: [
70
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "__none__", children: t(
71
+ "page.ui.none_use_inline_campaign_html",
72
+ "None (use inline campaign HTML / text)"
73
+ ) }),
74
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "broadcastNewsletter", children: t(
75
+ "page.ui.broadcast_newsletter_auto_localized",
76
+ "broadcastNewsletter (Auto-localized es / en / ar / tr)"
77
+ ) }),
78
+ emailTemplates.map((tpl) => /* @__PURE__ */ jsxRuntime.jsxs(
79
+ designSystem.SingleSelectOption,
80
+ {
81
+ value: String(tpl.templateReferenceId),
82
+ children: [
83
+ "#",
84
+ tpl.templateReferenceId,
85
+ " — ",
86
+ tpl.name
87
+ ]
88
+ },
89
+ `${tpl.templateReferenceId}-${tpl.name}`
90
+ ))
91
+ ]
92
+ }
93
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
94
+ designSystem.TextInput,
95
+ {
96
+ name: "emailTemplateRef",
97
+ placeholder: t(
98
+ "placeholder.optional_e_g_401_or_broadcastnewsletter",
99
+ "Optional: e.g. 401 or broadcastNewsletter"
100
+ ),
101
+ value,
102
+ onChange: (e) => onChange(e.target.value)
103
+ }
104
+ ),
105
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
106
+ ]
107
+ }
108
+ );
109
+ const CampaignsTableSection = ({
110
+ t,
111
+ campaigns,
112
+ onEdit,
113
+ onDispatch,
114
+ navigate,
115
+ isBusy = false
116
+ }) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 7, rowCount: campaigns.length + 1, children: [
117
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
118
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.slug_title", "Slug / Title") }) }),
119
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.topic", "Topic") }) }),
120
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.channels", "Channels") }) }),
121
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.status", "Status") }) }),
122
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.hijri_secondary_date", "Hijri / Secondary Date") }) }),
123
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.delivery_stats", "Delivery Stats") }) }),
124
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.actions", "Actions") }) })
125
+ ] }) }),
126
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: campaigns.map((camp) => {
127
+ const sentTotal = (camp.stats?.sentCount || 0) + (camp.stats?.simulatedCount || 0);
128
+ const failedTotal = camp.stats?.failedCount || 0;
129
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
130
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
131
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: camp.title }),
132
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
133
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: camp.slug }),
134
+ camp.emailTemplateRef && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { children: [
135
+ t("page.ui.email_template_badge", "Email Template:"),
136
+ " ",
137
+ camp.emailTemplateRef
138
+ ] })
139
+ ] })
140
+ ] }) }),
141
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: camp.topic }) }),
142
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: (camp.channels || []).join(", ") }) }),
143
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: camp.status }) }),
144
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: camp.secondaryDateLabel || "-" }) }),
145
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { children: [
146
+ t("page.ui.sent_label", "Sent:"),
147
+ " ",
148
+ sentTotal,
149
+ " /",
150
+ " ",
151
+ t("page.ui.failed_label", "Failed:"),
152
+ " ",
153
+ failedTotal
154
+ ] }) }),
155
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", children: [
156
+ /* @__PURE__ */ jsxRuntime.jsx(
157
+ designSystem.Button,
158
+ {
159
+ variant: "tertiary",
160
+ size: "S",
161
+ disabled: isBusy,
162
+ onClick: () => onEdit(camp),
163
+ children: t("common.edit", "Edit")
164
+ }
165
+ ),
166
+ camp.documentId && /* @__PURE__ */ jsxRuntime.jsx(
167
+ designSystem.Button,
168
+ {
169
+ variant: "tertiary",
170
+ size: "S",
171
+ disabled: isBusy,
172
+ onClick: () => navigate(
173
+ `/content-manager/collection-types/${index.CAMPAIGN_UID}/${camp.documentId}`
174
+ ),
175
+ children: t("common.content_manager", "Content Manager")
176
+ }
177
+ ),
178
+ /* @__PURE__ */ jsxRuntime.jsx(
179
+ designSystem.Button,
180
+ {
181
+ variant: "secondary",
182
+ size: "S",
183
+ disabled: isBusy,
184
+ onClick: () => onDispatch(camp.documentId || camp.slug),
185
+ children: t("page.ui.dispatch_now", "Dispatch Now")
186
+ }
187
+ )
188
+ ] }) })
189
+ ] }, camp.slug);
190
+ }) })
191
+ ] });
192
+ const DeliveryLedgerSection = ({
193
+ t,
194
+ logs
195
+ }) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingTop: 6, children: [
196
+ /* @__PURE__ */ jsxRuntime.jsxs(
197
+ designSystem.Box,
198
+ {
199
+ padding: 4,
200
+ marginBottom: 4,
201
+ background: "neutral100",
202
+ hasRadius: true,
203
+ style: { border: "1px solid rgba(128, 128, 128, 0.18)" },
204
+ children: [
205
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", fontWeight: "bold", children: t(
206
+ "page.ui.what_is_the_delivery_ledger",
207
+ "What is the Delivery Ledger?"
208
+ ) }),
209
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: t(
210
+ "page.ui.delivery_ledger_description",
211
+ "The Delivery Ledger is an immutable audit and idempotency log of every message dispatched across Email, Telegram, and WhatsApp. Each delivery records a deterministic idempotency key (campaignSlug + channel + recipient) so retrying a campaign never sends duplicate messages, masks recipient PII (e.g. a***@domain.org, +57***4567), and tracks whether the message was sent live or simulated in Dry-Run mode."
212
+ ) }) })
213
+ ]
214
+ }
215
+ ),
216
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 6, rowCount: Math.max(logs.length, 1) + 1, children: [
217
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
218
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.campaign", "Campaign") }) }),
219
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.channel_driver", "Channel / Driver") }) }),
220
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.masked_recipient", "Masked Recipient") }) }),
221
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.locale", "Locale") }) }),
222
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.status", "Status") }) }),
223
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.sent_at", "Sent At") }) })
224
+ ] }) }),
225
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: logs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tr, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { colSpan: 6, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: t(
226
+ "page.ui.no_deliveries_recorded_yet",
227
+ 'No deliveries recorded yet. Click "Dispatch Now" on any campaign in the Campaigns tab to dispatch (or simulate in Dry-Run mode) and populate the ledger.'
228
+ ) }) }) }) }) : logs.map((log) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
229
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: log.campaignSlug }) }),
230
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { children: [
231
+ log.channel,
232
+ " (",
233
+ log.driver,
234
+ ")"
235
+ ] }) }),
236
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: log.recipientMasked }) }),
237
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: log.locale }) }),
238
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: log.status }) }),
239
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: log.sentAt?.slice(0, 19) }) })
240
+ ] }, log.idempotencyKey)) })
241
+ ] })
242
+ ] });
243
+ const AutomationRulesTable = ({
244
+ t,
245
+ rules,
246
+ onEdit,
247
+ onToggleActive,
248
+ navigate,
249
+ isBusy = false
250
+ }) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 7, rowCount: rules.length + 1, children: [
251
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
252
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.rule_slug", "Rule Slug") }) }),
253
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.eventhub_trigger", "EventHub Trigger") }) }),
254
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.topic", "Topic") }) }),
255
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.channels", "Channels") }) }),
256
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.trigger_count", "Trigger Count") }) }),
257
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.status", "Status") }) }),
258
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("page.ui.actions", "Actions") }) })
259
+ ] }) }),
260
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: rules.map((rule) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
261
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
262
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: rule.name }),
263
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: rule.slug })
264
+ ] }) }),
265
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: rule.eventName }) }),
266
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: rule.topic }) }),
267
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: (rule.channels || []).join(", ") }) }),
268
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: rule.triggerCount || 0 }) }),
269
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { active: rule.active, children: rule.active ? t("common.active", "active") : t("common.paused", "paused") }) }),
270
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", children: [
271
+ /* @__PURE__ */ jsxRuntime.jsx(
272
+ designSystem.Button,
273
+ {
274
+ variant: "tertiary",
275
+ size: "S",
276
+ disabled: isBusy,
277
+ onClick: () => onEdit(rule),
278
+ children: t("common.edit", "Edit")
279
+ }
280
+ ),
281
+ /* @__PURE__ */ jsxRuntime.jsx(
282
+ designSystem.Button,
283
+ {
284
+ variant: "secondary",
285
+ size: "S",
286
+ disabled: isBusy,
287
+ onClick: () => onToggleActive(rule),
288
+ children: rule.active ? t("common.pause", "Pause") : t("common.activate", "Activate")
289
+ }
290
+ ),
291
+ rule.documentId && /* @__PURE__ */ jsxRuntime.jsx(
292
+ designSystem.Button,
293
+ {
294
+ variant: "tertiary",
295
+ size: "S",
296
+ disabled: isBusy,
297
+ onClick: () => navigate(
298
+ `/content-manager/collection-types/${index.AUTOMATION_RULE_UID}/${rule.documentId}`
299
+ ),
300
+ children: t("common.content_manager", "Content Manager")
301
+ }
302
+ )
303
+ ] }) })
304
+ ] }, rule.slug)) })
305
+ ] });
306
+ function parseChannelsInput(raw, fallback = ["email", "telegram"]) {
307
+ const allowed = ["email", "telegram", "whatsapp"];
308
+ const items = Array.isArray(raw) ? raw : raw.split(",");
309
+ const parsed = items.map((item) => item.trim().toLowerCase()).filter((item) => allowed.includes(item));
310
+ return parsed.length > 0 ? Array.from(new Set(parsed)) : fallback;
311
+ }
312
+ const AutomationAndLogsTabs = ({
313
+ activeTab,
314
+ rules,
315
+ logs,
316
+ t,
317
+ navigate,
318
+ post,
319
+ put,
320
+ notifyStatus,
321
+ loadAll
322
+ }) => {
323
+ const [editingRuleId, setEditingRuleId] = React__namespace.useState(null);
324
+ const [ruleActive, setRuleActive] = React__namespace.useState(true);
325
+ const [rulePriority, setRulePriority] = React__namespace.useState("normal");
326
+ const [isBusy, setIsBusy] = React__namespace.useState(false);
327
+ const [ruleSlug, setRuleSlug] = React__namespace.useState("");
328
+ const [ruleName, setRuleName] = React__namespace.useState("");
329
+ const [ruleEventName, setRuleEventName] = React__namespace.useState("event.published");
330
+ const [ruleTopic, setRuleTopic] = React__namespace.useState("events");
331
+ const [ruleChannels, setRuleChannels] = React__namespace.useState([
332
+ "telegram",
333
+ "email"
334
+ ]);
335
+ const [ruleSubjectTemplate, setRuleSubjectTemplate] = React__namespace.useState(
336
+ "📅 {{title}} ({{secondaryDate}})"
337
+ );
338
+ const [ruleBodyTextTemplate, setRuleBodyTextTemplate] = React__namespace.useState("{{title}}: {{summary}} ({{secondaryDate}})");
339
+ const resetRuleForm = () => {
340
+ setEditingRuleId(null);
341
+ setRuleActive(true);
342
+ setRulePriority("normal");
343
+ setRuleSlug("");
344
+ setRuleName("");
345
+ setRuleEventName("event.published");
346
+ setRuleTopic("events");
347
+ setRuleChannels(["telegram", "email"]);
348
+ setRuleSubjectTemplate("📅 {{title}} ({{secondaryDate}})");
349
+ setRuleBodyTextTemplate("{{title}}: {{summary}} ({{secondaryDate}})");
350
+ };
351
+ const handleStartEditRule = (rule) => {
352
+ setEditingRuleId(rule.documentId || rule.slug);
353
+ setRuleActive(rule.active ?? true);
354
+ setRulePriority(rule.priority || "normal");
355
+ setRuleSlug(rule.slug);
356
+ setRuleName(rule.name);
357
+ setRuleEventName(rule.eventName);
358
+ setRuleTopic(rule.topic || "announcements");
359
+ setRuleChannels(
360
+ Array.isArray(rule.channels) && rule.channels.length > 0 ? rule.channels : ["telegram"]
361
+ );
362
+ setRuleSubjectTemplate(rule.subjectTemplate || "");
363
+ setRuleBodyTextTemplate(rule.bodyTextTemplate || "");
364
+ notifyStatus(`${t("page.ui.editing_rule", "Editing rule")}: ${rule.name}`);
365
+ };
366
+ const handleSaveRule = async () => {
367
+ const isComplete = Boolean(
368
+ ruleSlug && ruleName && ruleEventName && ruleSubjectTemplate && ruleBodyTextTemplate
369
+ );
370
+ if (!isComplete || isBusy) return;
371
+ const payload = {
372
+ slug: ruleSlug.trim(),
373
+ name: ruleName.trim(),
374
+ eventName: ruleEventName.trim(),
375
+ topic: ruleTopic.trim() || "announcements",
376
+ channels: parseChannelsInput(ruleChannels, ["telegram", "email"]),
377
+ active: ruleActive,
378
+ priority: rulePriority,
379
+ subjectTemplate: ruleSubjectTemplate.trim(),
380
+ bodyTextTemplate: ruleBodyTextTemplate.trim()
381
+ };
382
+ setIsBusy(true);
383
+ try {
384
+ if (editingRuleId) {
385
+ await put(`/${index.PLUGIN_ID}/automation-rules/${editingRuleId}`, payload);
386
+ notifyStatus(
387
+ `${t("page.ui.updated_rule", "Updated automation rule")}: ${payload.name}`
388
+ );
389
+ } else {
390
+ await post(`/${index.PLUGIN_ID}/automation-rules`, payload);
391
+ notifyStatus(
392
+ `${t("page.ui.created_rule", "Created automation rule")}: ${payload.name}`
393
+ );
394
+ }
395
+ resetRuleForm();
396
+ await loadAll();
397
+ } catch (err) {
398
+ notifyStatus(
399
+ selectOptions.extractApiErrorMessage(
400
+ err,
401
+ t("page.ui.operation_failed", "Broadcast operation failed.")
402
+ ),
403
+ true
404
+ );
405
+ } finally {
406
+ setIsBusy(false);
407
+ }
408
+ };
409
+ const handleToggleRuleActive = async (rule) => {
410
+ if (isBusy) return;
411
+ const targetId = rule.documentId || rule.slug;
412
+ const nextActive = !rule.active;
413
+ setIsBusy(true);
414
+ try {
415
+ await put(`/${index.PLUGIN_ID}/automation-rules/${targetId}`, {
416
+ ...rule,
417
+ active: nextActive
418
+ });
419
+ const stateLabel = nextActive ? t("page.ui.rule_activated", "activated") : t("page.ui.rule_paused", "paused");
420
+ notifyStatus(`${rule.name}: ${stateLabel}`);
421
+ await loadAll();
422
+ } catch (err) {
423
+ notifyStatus(
424
+ selectOptions.extractApiErrorMessage(
425
+ err,
426
+ t("page.ui.operation_failed", "Broadcast operation failed.")
427
+ ),
428
+ true
429
+ );
430
+ } finally {
431
+ setIsBusy(false);
432
+ }
433
+ };
434
+ const handleSeedStarterRule = async (preset) => {
435
+ if (isBusy) return;
436
+ const existing = rules.find((r) => r.slug === preset.slug);
437
+ if (existing) {
438
+ handleStartEditRule(existing);
439
+ return;
440
+ }
441
+ setIsBusy(true);
442
+ try {
443
+ await post(`/${index.PLUGIN_ID}/automation-rules`, preset);
444
+ notifyStatus(
445
+ `${t("page.ui.added_starter_rule", "Added starter automation rule")}: ${preset.name}`
446
+ );
447
+ await loadAll();
448
+ } catch (err) {
449
+ notifyStatus(
450
+ selectOptions.extractApiErrorMessage(
451
+ err,
452
+ t("page.ui.operation_failed", "Broadcast operation failed.")
453
+ ),
454
+ true
455
+ );
456
+ } finally {
457
+ setIsBusy(false);
458
+ }
459
+ };
460
+ if (activeTab === "logs") {
461
+ return /* @__PURE__ */ jsxRuntime.jsx(DeliveryLedgerSection, { t, logs });
462
+ }
463
+ if (activeTab !== "automations") {
464
+ return null;
465
+ }
466
+ const formHeading = editingRuleId ? `${t("page.ui.edit_automation_rule", "Edit Automation Rule")} (${ruleSlug})` : t(
467
+ "page.ui.create_new_eventhub_automation_rule",
468
+ "Create New EventHub Automation Rule"
469
+ );
470
+ const topicOpts = index.mergeSelectOptions(selectOptions.getBroadcastTopicOptions(t), [
471
+ ruleTopic
472
+ ]);
473
+ const channelOpts = selectOptions.getBroadcastChannelOptions(t);
474
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingTop: 6, children: [
475
+ /* @__PURE__ */ jsxRuntime.jsxs(
476
+ designSystem.Flex,
477
+ {
478
+ justifyContent: "space-between",
479
+ alignItems: "center",
480
+ paddingBottom: 4,
481
+ wrap: "wrap",
482
+ gap: 2,
483
+ children: [
484
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h2", children: t("page.ui.eventhub_automation_rules", "EventHub Automation Rules") }),
485
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, wrap: "wrap", children: index.STARTER_AUTOMATION_RULES.map((preset) => /* @__PURE__ */ jsxRuntime.jsxs(
486
+ designSystem.Button,
487
+ {
488
+ variant: "secondary",
489
+ disabled: isBusy,
490
+ onClick: () => handleSeedStarterRule(preset),
491
+ children: [
492
+ "+ ",
493
+ preset.slug
494
+ ]
495
+ },
496
+ preset.slug
497
+ )) })
498
+ ]
499
+ }
500
+ ),
501
+ /* @__PURE__ */ jsxRuntime.jsxs(
502
+ designSystem.Box,
503
+ {
504
+ padding: 5,
505
+ marginBottom: 5,
506
+ background: "neutral0",
507
+ hasRadius: true,
508
+ shadow: "filterShadow",
509
+ style: { border: "1px solid rgba(128, 128, 128, 0.18)" },
510
+ children: [
511
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
512
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h3", fontWeight: "bold", children: formHeading }),
513
+ editingRuleId && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { active: true, children: t("common.editing_mode", "Editing Mode") })
514
+ ] }),
515
+ /* @__PURE__ */ jsxRuntime.jsxs(
516
+ designSystem.Box,
517
+ {
518
+ paddingTop: 3,
519
+ style: {
520
+ display: "grid",
521
+ gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
522
+ gap: 14,
523
+ alignItems: "start"
524
+ },
525
+ children: [
526
+ /* @__PURE__ */ jsxRuntime.jsx(
527
+ RequiredTextField,
528
+ {
529
+ name: "ruleSlug",
530
+ label: t("field.rule_slug", "Rule Slug"),
531
+ hint: t(
532
+ "page.ui.rule_slug_hint",
533
+ "Unique identifier (lowercase & hyphens)."
534
+ ),
535
+ placeholder: t(
536
+ "placeholder.e_g_auto_course_published",
537
+ "e.g., auto-course-published"
538
+ ),
539
+ value: ruleSlug,
540
+ onChange: setRuleSlug
541
+ }
542
+ ),
543
+ /* @__PURE__ */ jsxRuntime.jsx(
544
+ RequiredTextField,
545
+ {
546
+ name: "ruleName",
547
+ label: t("field.rule_name", "Rule Name"),
548
+ hint: t(
549
+ "page.ui.rule_name_hint",
550
+ "Human-readable rule description."
551
+ ),
552
+ placeholder: t(
553
+ "placeholder.e_g_auto_broadcast_new_courses",
554
+ "e.g., Auto-Broadcast New Courses"
555
+ ),
556
+ value: ruleName,
557
+ onChange: setRuleName
558
+ }
559
+ ),
560
+ /* @__PURE__ */ jsxRuntime.jsx(
561
+ RequiredTextField,
562
+ {
563
+ name: "ruleEventName",
564
+ label: t("field.eventhub_trigger_name", "EventHub Trigger Name"),
565
+ hint: t(
566
+ "page.ui.rule_event_name_hint",
567
+ "Strapi EventHub event that triggers this rule."
568
+ ),
569
+ placeholder: t(
570
+ "placeholder.e_g_event_published",
571
+ "e.g., event.published or inbox.alert.published"
572
+ ),
573
+ value: ruleEventName,
574
+ onChange: setRuleEventName
575
+ }
576
+ ),
577
+ /* @__PURE__ */ jsxRuntime.jsxs(
578
+ designSystem.Field.Root,
579
+ {
580
+ name: "ruleTopic",
581
+ hint: t(
582
+ "page.ui.rule_topic_hint",
583
+ "Subscriber topic segment to notify."
584
+ ),
585
+ children: [
586
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.topic_segment", "Topic Segment") }),
587
+ /* @__PURE__ */ jsxRuntime.jsx(
588
+ designSystem.SingleSelect,
589
+ {
590
+ name: "ruleTopic",
591
+ value: ruleTopic || "events",
592
+ onChange: (val) => setRuleTopic(String(val)),
593
+ children: topicOpts.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: opt.value, children: opt.label }, opt.value))
594
+ }
595
+ ),
596
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
597
+ ]
598
+ }
599
+ ),
600
+ /* @__PURE__ */ jsxRuntime.jsxs(
601
+ designSystem.Field.Root,
602
+ {
603
+ name: "ruleChannels",
604
+ hint: t(
605
+ "page.ui.rule_channels_hint",
606
+ "Channels dispatched when triggered."
607
+ ),
608
+ children: [
609
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.channels", "Channels") }),
610
+ /* @__PURE__ */ jsxRuntime.jsx(
611
+ designSystem.MultiSelect,
612
+ {
613
+ name: "ruleChannels",
614
+ withTags: true,
615
+ placeholder: t(
616
+ "placeholder.select_channels",
617
+ "Select channels..."
618
+ ),
619
+ value: ruleChannels,
620
+ onChange: (nextValues) => setRuleChannels(
621
+ nextValues.length > 0 ? nextValues : ["telegram"]
622
+ ),
623
+ onClear: () => setRuleChannels(["telegram"]),
624
+ children: channelOpts.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.MultiSelectOption, { value: opt.value, children: opt.label }, opt.value))
625
+ }
626
+ ),
627
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
628
+ ]
629
+ }
630
+ ),
631
+ /* @__PURE__ */ jsxRuntime.jsx(
632
+ RequiredTextField,
633
+ {
634
+ name: "ruleSubjectTemplate",
635
+ label: t("field.subject_template", "Subject Template"),
636
+ hint: t(
637
+ "page.ui.rule_subject_template_hint",
638
+ "Supports '{{title}}', '{{secondaryDate}}', '{{organizationName}}'."
639
+ ),
640
+ placeholder: t(
641
+ "placeholder.rule_subject_template",
642
+ "🚨 '{{title}}' — '{{organizationName}}'"
643
+ ),
644
+ value: ruleSubjectTemplate,
645
+ onChange: setRuleSubjectTemplate
646
+ }
647
+ ),
648
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { gridColumn: "1 / -1" }, children: /* @__PURE__ */ jsxRuntime.jsx(
649
+ RequiredTextField,
650
+ {
651
+ name: "ruleBodyTextTemplate",
652
+ label: t("field.body_text_template", "Body Text Template"),
653
+ hint: t(
654
+ "page.ui.rule_body_text_template_hint",
655
+ "Interpolated with event payload fields ('{{title}}', '{{message}}', '{{summary}}', '{{secondaryDate}}')."
656
+ ),
657
+ placeholder: t(
658
+ "placeholder.rule_body_text_template",
659
+ "'{{title}}': '{{message}}' ('{{secondaryDate}}')"
660
+ ),
661
+ value: ruleBodyTextTemplate,
662
+ onChange: setRuleBodyTextTemplate
663
+ }
664
+ ) })
665
+ ]
666
+ }
667
+ ),
668
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "flex-end", paddingTop: 3, gap: 2, children: [
669
+ editingRuleId && /* @__PURE__ */ jsxRuntime.jsx(
670
+ designSystem.Button,
671
+ {
672
+ variant: "tertiary",
673
+ disabled: isBusy,
674
+ onClick: () => resetRuleForm(),
675
+ children: t("common.cancel", "Cancel")
676
+ }
677
+ ),
678
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { loading: isBusy, disabled: isBusy, onClick: handleSaveRule, children: editingRuleId ? t("page.ui.save_rule_changes", "Save Rule Changes") : t("page.ui.create_automation_rule", "Create Automation Rule") })
679
+ ] })
680
+ ]
681
+ }
682
+ ),
683
+ /* @__PURE__ */ jsxRuntime.jsx(
684
+ AutomationRulesTable,
685
+ {
686
+ t,
687
+ rules,
688
+ onEdit: handleStartEditRule,
689
+ onToggleActive: handleToggleRuleActive,
690
+ navigate,
691
+ isBusy
692
+ }
693
+ )
694
+ ] });
695
+ };
696
+ const CampaignComposerForm = ({
697
+ t,
698
+ editingCampaignId,
699
+ isBusy,
700
+ newSlug,
701
+ setNewSlug,
702
+ newTitle,
703
+ setNewTitle,
704
+ newTopic,
705
+ setNewTopic,
706
+ newChannels,
707
+ setNewChannels,
708
+ newEmailTemplateRef,
709
+ setNewEmailTemplateRef,
710
+ newSubject,
711
+ setNewSubject,
712
+ newBodyText,
713
+ setNewBodyText,
714
+ emailTemplates,
715
+ onCancelEdit,
716
+ onSave
717
+ }) => {
718
+ const campaignFormTitle = editingCampaignId ? `${t("page.ui.edit_broadcast_campaign", "Edit Broadcast Campaign")} (${newSlug})` : t(
719
+ "page.ui.compose_new_omnichannel_broadcast",
720
+ "Compose New Omnichannel Broadcast"
721
+ );
722
+ const topicOpts = index.mergeSelectOptions(selectOptions.getBroadcastTopicOptions(t), [newTopic]);
723
+ const channelOpts = selectOptions.getBroadcastChannelOptions(t);
724
+ return /* @__PURE__ */ jsxRuntime.jsxs(
725
+ designSystem.Box,
726
+ {
727
+ padding: 5,
728
+ marginBottom: 5,
729
+ background: "neutral0",
730
+ hasRadius: true,
731
+ shadow: "filterShadow",
732
+ style: { border: "1px solid rgba(128, 128, 128, 0.18)" },
733
+ children: [
734
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
735
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h3", fontWeight: "bold", children: campaignFormTitle }),
736
+ editingCampaignId && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { active: true, children: t("common.editing_mode", "Editing Mode") })
737
+ ] }),
738
+ /* @__PURE__ */ jsxRuntime.jsxs(
739
+ designSystem.Box,
740
+ {
741
+ paddingTop: 3,
742
+ style: {
743
+ display: "grid",
744
+ gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
745
+ gap: 14,
746
+ alignItems: "start"
747
+ },
748
+ children: [
749
+ /* @__PURE__ */ jsxRuntime.jsx(
750
+ RequiredTextField,
751
+ {
752
+ name: "slug",
753
+ label: t("field.campaign_slug", "Campaign Slug"),
754
+ hint: t(
755
+ "page.ui.unique_url_safe_identifier_for_idempotency_track",
756
+ "Unique URL-safe identifier for idempotency tracking."
757
+ ),
758
+ placeholder: t(
759
+ "placeholder.e_g_friday_khutbah_bulletin",
760
+ "e.g., friday-khutbah-bulletin"
761
+ ),
762
+ value: newSlug,
763
+ onChange: setNewSlug
764
+ }
765
+ ),
766
+ /* @__PURE__ */ jsxRuntime.jsx(
767
+ RequiredTextField,
768
+ {
769
+ name: "title",
770
+ label: t("field.campaign_title", "Campaign Title"),
771
+ hint: t(
772
+ "page.ui.internal_display_name_in_the_campaign_table",
773
+ "Internal display name in the campaign table."
774
+ ),
775
+ placeholder: t(
776
+ "placeholder.e_g_bolet_n_semanal_de_jumu_ah",
777
+ "e.g., Boletín Semanal de Jumu'ah"
778
+ ),
779
+ value: newTitle,
780
+ onChange: setNewTitle
781
+ }
782
+ ),
783
+ /* @__PURE__ */ jsxRuntime.jsxs(
784
+ designSystem.Field.Root,
785
+ {
786
+ name: "topic",
787
+ hint: t(
788
+ "page.ui.target_subscriber_segment_and_telegram_forum_thr",
789
+ "Target subscriber segment and Telegram forum thread."
790
+ ),
791
+ children: [
792
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.topic_segment", "Topic Segment") }),
793
+ /* @__PURE__ */ jsxRuntime.jsx(
794
+ designSystem.SingleSelect,
795
+ {
796
+ name: "topic",
797
+ value: newTopic || "announcements",
798
+ onChange: (val) => setNewTopic(String(val)),
799
+ children: topicOpts.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: opt.value, children: opt.label }, opt.value))
800
+ }
801
+ ),
802
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
803
+ ]
804
+ }
805
+ ),
806
+ /* @__PURE__ */ jsxRuntime.jsxs(
807
+ designSystem.Field.Root,
808
+ {
809
+ name: "channels",
810
+ hint: t(
811
+ "page.ui.channels_hint_combination",
812
+ "Any combination of: email, telegram, whatsapp"
813
+ ),
814
+ children: [
815
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("field.channels", "Channels") }),
816
+ /* @__PURE__ */ jsxRuntime.jsx(
817
+ designSystem.MultiSelect,
818
+ {
819
+ name: "channels",
820
+ withTags: true,
821
+ placeholder: t("placeholder.select_channels", "Select channels..."),
822
+ value: newChannels,
823
+ onChange: (nextValues) => setNewChannels(
824
+ nextValues.length > 0 ? nextValues : ["email"]
825
+ ),
826
+ onClear: () => setNewChannels(["email"]),
827
+ children: channelOpts.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.MultiSelectOption, { value: opt.value, children: opt.label }, opt.value))
828
+ }
829
+ ),
830
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
831
+ ]
832
+ }
833
+ ),
834
+ /* @__PURE__ */ jsxRuntime.jsx(
835
+ EmailTemplateSelectorField,
836
+ {
837
+ t,
838
+ emailTemplates,
839
+ value: newEmailTemplateRef,
840
+ onChange: setNewEmailTemplateRef
841
+ }
842
+ ),
843
+ /* @__PURE__ */ jsxRuntime.jsx(
844
+ RequiredTextField,
845
+ {
846
+ name: "subject",
847
+ label: t("field.subject_headline", "Subject / Headline"),
848
+ hint: t(
849
+ "page.ui.email_subject_line_and_bold_header_on_telegram_w",
850
+ "Email subject line and bold header on Telegram/WhatsApp."
851
+ ),
852
+ placeholder: t(
853
+ "placeholder.e_g_as_salamu_alaykum_horarios_y_anuncios",
854
+ "e.g., As-salamu alaykum — Prayer Times & Announcements"
855
+ ),
856
+ value: newSubject,
857
+ onChange: setNewSubject
858
+ }
859
+ ),
860
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { gridColumn: "1 / -1" }, children: /* @__PURE__ */ jsxRuntime.jsx(
861
+ RequiredTextField,
862
+ {
863
+ name: "bodyText",
864
+ label: t(
865
+ "field.message_body_with_placeholders",
866
+ "Message Body (supports '{{recipientName}}', '{{secondaryDate}}', '{{organizationName}}')"
867
+ ),
868
+ hint: t(
869
+ "page.ui.main_text_dispatched_across_email_telegram_and_w",
870
+ "Main text dispatched across Email, Telegram, and WhatsApp channels."
871
+ ),
872
+ placeholder: t(
873
+ "placeholder.e_g_hola_recipientname_hoy_secondarydate_te_invi",
874
+ "e.g., Hello '{{recipientName}}', today ('{{secondaryDate}}') we invite you to..."
875
+ ),
876
+ value: newBodyText,
877
+ onChange: setNewBodyText
878
+ }
879
+ ) })
880
+ ]
881
+ }
882
+ ),
883
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "flex-end", paddingTop: 3, gap: 2, children: [
884
+ editingCampaignId && /* @__PURE__ */ jsxRuntime.jsx(
885
+ designSystem.Button,
886
+ {
887
+ variant: "tertiary",
888
+ disabled: isBusy,
889
+ onClick: () => onCancelEdit(),
890
+ children: t("common.cancel", "Cancel")
891
+ }
892
+ ),
893
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { loading: isBusy, disabled: isBusy, onClick: onSave, children: editingCampaignId ? t("page.ui.save_campaign_changes", "Save Campaign Changes") : t("page.ui.create_campaign", "Create Campaign") })
894
+ ] })
895
+ ]
896
+ }
897
+ );
898
+ };
899
+ const DEFAULT_CHANNELS = ["email", "telegram", "whatsapp"];
900
+ const BroadcastsHomePage = () => {
901
+ const { formatMessage } = reactIntl.useIntl();
902
+ const navigate = reactRouterDom.useNavigate();
903
+ const t = (id, defaultMessage) => formatMessage({ id: `${index.PLUGIN_ID}.${id}`, defaultMessage });
904
+ const { get, post, put } = admin.useFetchClient();
905
+ const [activeTab, setActiveTab] = React__namespace.useState("campaigns");
906
+ const [campaigns, setCampaigns] = React__namespace.useState([]);
907
+ const [rules, setRules] = React__namespace.useState([]);
908
+ const [logs, setLogs] = React__namespace.useState([]);
909
+ const [emailTemplates, setEmailTemplates] = React__namespace.useState([]);
910
+ const [statusMessage, setStatusMessage] = React__namespace.useState("");
911
+ const [statusIsError, setStatusIsError] = React__namespace.useState(false);
912
+ const [isBusy, setIsBusy] = React__namespace.useState(false);
913
+ const [editingCampaignId, setEditingCampaignId] = React__namespace.useState(null);
914
+ const [newSlug, setNewSlug] = React__namespace.useState("");
915
+ const [newTitle, setNewTitle] = React__namespace.useState("");
916
+ const [newTopic, setNewTopic] = React__namespace.useState("announcements");
917
+ const [newPriority, setNewPriority] = React__namespace.useState("normal");
918
+ const [newChannels, setNewChannels] = React__namespace.useState(DEFAULT_CHANNELS);
919
+ const [newEmailTemplateRef, setNewEmailTemplateRef] = React__namespace.useState("");
920
+ const [newSubject, setNewSubject] = React__namespace.useState("");
921
+ const [newBodyText, setNewBodyText] = React__namespace.useState("");
922
+ const notifyStatus = (message, isError = false) => {
923
+ setStatusIsError(isError);
924
+ setStatusMessage(message);
925
+ };
926
+ const loadAll = React__namespace.useCallback(
927
+ async (isMountedCheck = () => true) => {
928
+ try {
929
+ const [campRes, rulesRes, logsRes] = await Promise.all([
930
+ get(`/${index.PLUGIN_ID}/campaigns`),
931
+ get(
932
+ `/${index.PLUGIN_ID}/automation-rules`
933
+ ),
934
+ get(`/${index.PLUGIN_ID}/delivery-logs`)
935
+ ]);
936
+ if (!isMountedCheck()) return;
937
+ setCampaigns(campRes?.data?.data || []);
938
+ setRules(rulesRes?.data?.data || []);
939
+ setLogs(logsRes?.data?.data || []);
940
+ } catch {
941
+ }
942
+ try {
943
+ const tplRes = await get(
944
+ `/${index.PLUGIN_ID}/email-templates`
945
+ );
946
+ if (isMountedCheck() && Array.isArray(tplRes?.data?.data)) {
947
+ setEmailTemplates(tplRes.data.data);
948
+ }
949
+ } catch {
950
+ }
951
+ },
952
+ [get]
953
+ );
954
+ React__namespace.useEffect(() => {
955
+ let isMounted = true;
956
+ void loadAll(() => isMounted);
957
+ return () => {
958
+ isMounted = false;
959
+ };
960
+ }, [loadAll]);
961
+ const resetCampaignForm = (camp, editId = null, fallbackChannels = DEFAULT_CHANNELS) => {
962
+ setEditingCampaignId(editId);
963
+ setNewSlug(camp?.slug || "");
964
+ setNewTitle(camp?.title || "");
965
+ setNewTopic(camp?.topic || "announcements");
966
+ setNewPriority(camp?.priority || "normal");
967
+ setNewChannels(camp?.channels?.length ? camp.channels : fallbackChannels);
968
+ setNewEmailTemplateRef(camp?.emailTemplateRef || "");
969
+ setNewSubject(camp?.subject || "");
970
+ setNewBodyText(camp?.bodyText || "");
971
+ };
972
+ const handleStartEditCampaign = (camp) => {
973
+ resetCampaignForm(camp, camp.documentId || camp.slug, [
974
+ "email",
975
+ "telegram"
976
+ ]);
977
+ notifyStatus(
978
+ `${t("page.ui.editing_campaign", "Editing campaign")}: ${camp.title}`
979
+ );
980
+ };
981
+ const handleSeedTemplate = async (templateId) => {
982
+ if (isBusy) return;
983
+ setIsBusy(true);
984
+ try {
985
+ await post(`/${index.PLUGIN_ID}/campaigns/from-template`, { templateId });
986
+ notifyStatus(
987
+ `${t("page.ui.created_from_starter_template", "Created campaign from starter template")}: ${templateId}`
988
+ );
989
+ await loadAll();
990
+ } catch (err) {
991
+ notifyStatus(
992
+ selectOptions.extractApiErrorMessage(
993
+ err,
994
+ t("page.ui.operation_failed", "Broadcast operation failed.")
995
+ ),
996
+ true
997
+ );
998
+ } finally {
999
+ setIsBusy(false);
1000
+ }
1001
+ };
1002
+ const handleDispatchCampaign = async (slugOrDocId) => {
1003
+ if (!slugOrDocId || isBusy) return;
1004
+ setIsBusy(true);
1005
+ try {
1006
+ await post(`/${index.PLUGIN_ID}/campaigns/${slugOrDocId}/send`, {});
1007
+ notifyStatus(
1008
+ `${t("page.ui.dispatched_campaign", "Dispatched broadcast campaign")}: ${slugOrDocId}`
1009
+ );
1010
+ await loadAll();
1011
+ } catch (err) {
1012
+ notifyStatus(
1013
+ selectOptions.extractApiErrorMessage(
1014
+ err,
1015
+ t("page.ui.operation_failed", "Broadcast operation failed.")
1016
+ ),
1017
+ true
1018
+ );
1019
+ } finally {
1020
+ setIsBusy(false);
1021
+ }
1022
+ };
1023
+ const handleSaveCampaign = async () => {
1024
+ if (!newSlug || !newTitle || !newSubject || !newBodyText || isBusy) return;
1025
+ const payload = {
1026
+ slug: newSlug.trim(),
1027
+ title: newTitle.trim(),
1028
+ topic: newTopic.trim() || "announcements",
1029
+ channels: parseChannelsInput(newChannels, DEFAULT_CHANNELS),
1030
+ priority: editingCampaignId ? newPriority : "normal",
1031
+ emailTemplateRef: newEmailTemplateRef.trim(),
1032
+ subject: newSubject.trim(),
1033
+ bodyText: newBodyText.trim()
1034
+ };
1035
+ setIsBusy(true);
1036
+ try {
1037
+ if (editingCampaignId) {
1038
+ await put(`/${index.PLUGIN_ID}/campaigns/${editingCampaignId}`, payload);
1039
+ notifyStatus(
1040
+ `${t("page.ui.updated_campaign", "Updated broadcast campaign")}: ${payload.title}`
1041
+ );
1042
+ } else {
1043
+ await post(`/${index.PLUGIN_ID}/campaigns`, payload);
1044
+ notifyStatus(
1045
+ t(
1046
+ "page.ui.created_omnichannel_campaign",
1047
+ "Created omnichannel broadcast campaign"
1048
+ )
1049
+ );
1050
+ }
1051
+ resetCampaignForm();
1052
+ await loadAll();
1053
+ } catch (err) {
1054
+ notifyStatus(
1055
+ selectOptions.extractApiErrorMessage(
1056
+ err,
1057
+ t("page.ui.operation_failed", "Broadcast operation failed.")
1058
+ ),
1059
+ true
1060
+ );
1061
+ } finally {
1062
+ setIsBusy(false);
1063
+ }
1064
+ };
1065
+ const tabs = [
1066
+ ["campaigns", t("page.ui.campaigns_tab", "Campaigns"), campaigns.length],
1067
+ [
1068
+ "automations",
1069
+ t("page.ui.automation_rules_tab", "Automation Rules"),
1070
+ rules.length
1071
+ ],
1072
+ ["logs", t("page.ui.delivery_ledger_tab", "Delivery Ledger"), logs.length]
1073
+ ];
1074
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 8, children: [
1075
+ /* @__PURE__ */ jsxRuntime.jsxs(
1076
+ designSystem.Flex,
1077
+ {
1078
+ justifyContent: "space-between",
1079
+ alignItems: "flex-start",
1080
+ paddingBottom: 4,
1081
+ wrap: "wrap",
1082
+ gap: 4,
1083
+ children: [
1084
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
1085
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", tag: "h1", children: t(
1086
+ "page.ui.omnichannel_community_broadcasts_automations",
1087
+ "Omnichannel Community Broadcasts & Automations"
1088
+ ) }),
1089
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "epsilon", textColor: "neutral600", children: t(
1090
+ "page.ui.email_newsletters_telegram_bot_forum_topics_what",
1091
+ "Email newsletters, Telegram Bot & Forum topics, WhatsApp Cloud API / Gateways, EventHub auto-triggers & Hijri dates"
1092
+ ) })
1093
+ ] }),
1094
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, wrap: "wrap", children: tabs.map(([tabKey, label, count]) => /* @__PURE__ */ jsxRuntime.jsxs(
1095
+ designSystem.Button,
1096
+ {
1097
+ variant: activeTab === tabKey ? "default" : "tertiary",
1098
+ onClick: () => setActiveTab(tabKey),
1099
+ children: [
1100
+ label,
1101
+ " (",
1102
+ count,
1103
+ ")"
1104
+ ]
1105
+ },
1106
+ tabKey
1107
+ )) })
1108
+ ]
1109
+ }
1110
+ ),
1111
+ statusMessage && /* @__PURE__ */ jsxRuntime.jsx(
1112
+ designSystem.Box,
1113
+ {
1114
+ padding: 3,
1115
+ marginBottom: 4,
1116
+ background: statusIsError ? "danger100" : "success100",
1117
+ hasRadius: true,
1118
+ role: statusIsError ? "alert" : "status",
1119
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1120
+ designSystem.Typography,
1121
+ {
1122
+ textColor: statusIsError ? "danger700" : "success700",
1123
+ fontWeight: "semiBold",
1124
+ children: statusMessage
1125
+ }
1126
+ )
1127
+ }
1128
+ ),
1129
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {}),
1130
+ activeTab === "campaigns" && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingTop: 6, children: [
1131
+ /* @__PURE__ */ jsxRuntime.jsxs(
1132
+ designSystem.Flex,
1133
+ {
1134
+ justifyContent: "space-between",
1135
+ alignItems: "center",
1136
+ paddingBottom: 4,
1137
+ wrap: "wrap",
1138
+ gap: 2,
1139
+ children: [
1140
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h2", children: t(
1141
+ "page.ui.starter_templates_broadcast_campaigns",
1142
+ "Starter Templates & Broadcast Campaigns"
1143
+ ) }),
1144
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, wrap: "wrap", children: [
1145
+ /* @__PURE__ */ jsxRuntime.jsxs(
1146
+ designSystem.Button,
1147
+ {
1148
+ variant: "tertiary",
1149
+ onClick: () => navigate("/plugins/email-designer-5"),
1150
+ children: [
1151
+ t("page.ui.open_email_designer", "Open Email Designer"),
1152
+ " (",
1153
+ emailTemplates.length,
1154
+ ")"
1155
+ ]
1156
+ }
1157
+ ),
1158
+ index.STARTER_CAMPAIGN_TEMPLATES.map((tpl) => /* @__PURE__ */ jsxRuntime.jsxs(
1159
+ designSystem.Button,
1160
+ {
1161
+ variant: "secondary",
1162
+ disabled: isBusy,
1163
+ onClick: () => handleSeedTemplate(tpl.id),
1164
+ children: [
1165
+ "+ ",
1166
+ tpl.slug
1167
+ ]
1168
+ },
1169
+ tpl.id
1170
+ ))
1171
+ ] })
1172
+ ]
1173
+ }
1174
+ ),
1175
+ /* @__PURE__ */ jsxRuntime.jsx(
1176
+ CampaignComposerForm,
1177
+ {
1178
+ t,
1179
+ editingCampaignId,
1180
+ isBusy,
1181
+ newSlug,
1182
+ setNewSlug,
1183
+ newTitle,
1184
+ setNewTitle,
1185
+ newTopic,
1186
+ setNewTopic,
1187
+ newChannels,
1188
+ setNewChannels,
1189
+ newEmailTemplateRef,
1190
+ setNewEmailTemplateRef,
1191
+ newSubject,
1192
+ setNewSubject,
1193
+ newBodyText,
1194
+ setNewBodyText,
1195
+ emailTemplates,
1196
+ onCancelEdit: () => resetCampaignForm(),
1197
+ onSave: handleSaveCampaign
1198
+ }
1199
+ ),
1200
+ /* @__PURE__ */ jsxRuntime.jsx(
1201
+ CampaignsTableSection,
1202
+ {
1203
+ t,
1204
+ campaigns,
1205
+ onEdit: handleStartEditCampaign,
1206
+ onDispatch: handleDispatchCampaign,
1207
+ navigate,
1208
+ isBusy
1209
+ }
1210
+ )
1211
+ ] }),
1212
+ /* @__PURE__ */ jsxRuntime.jsx(
1213
+ AutomationAndLogsTabs,
1214
+ {
1215
+ activeTab,
1216
+ rules,
1217
+ logs,
1218
+ t,
1219
+ navigate,
1220
+ post,
1221
+ put,
1222
+ notifyStatus,
1223
+ loadAll
1224
+ }
1225
+ )
1226
+ ] }) });
1227
+ };
1228
+ exports.BroadcastsHomePage = BroadcastsHomePage;