@burdenoff/microfe-channel 2026.625.1 → 2026.625.3
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/channel/modules/channels/pages/ChannelDashboardPage.js +73 -76
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +407 -353
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +93 -87
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js +119 -96
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelsListPage.js +219 -196
- package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +277 -232
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js +112 -91
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateChannelPage.js +28 -22
- package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js +72 -65
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +101 -93
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditChannelPage.js +154 -148
- package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditConnectionPage.js +69 -62
- package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/SendMessagePage.js +131 -124
- package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js +152 -129
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -6,9 +6,9 @@ import { useI18n as o } from "@burdenoff/fe-libs/shared/providers/shell/I18nProv
|
|
|
6
6
|
import { AlertCircle as s, ArrowLeft as c, Bell as l, CheckCircle2 as u, Clock as d, Database as f, Loader2 as p, Save as m, Settings as h, Shield as g } from "lucide-react";
|
|
7
7
|
import { useNavigate as _ } from "react-router-dom";
|
|
8
8
|
import { jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
9
|
-
import {
|
|
9
|
+
import { EmphasisPanel as b, GlassCard as x, PagePurpose as S, ShowMore as C } from "@burdenoff/fe-libs/ui";
|
|
10
10
|
//#region src/channel/modules/channels/pages/ChannelSettingsPage.tsx
|
|
11
|
-
var
|
|
11
|
+
var w = {
|
|
12
12
|
defaultPriority: 2,
|
|
13
13
|
rateLimitPerMinute: 100,
|
|
14
14
|
rateLimitPerHour: 5e3,
|
|
@@ -17,52 +17,52 @@ var S = {
|
|
|
17
17
|
enableWebhookVerification: !0,
|
|
18
18
|
sessionTimeoutMinutes: 30
|
|
19
19
|
};
|
|
20
|
-
function
|
|
21
|
-
let
|
|
22
|
-
let n =
|
|
20
|
+
function T() {
|
|
21
|
+
let T = _(), { t: E } = o(), D = (e, t) => {
|
|
22
|
+
let n = E(e);
|
|
23
23
|
return n === e ? t : n;
|
|
24
|
-
}, { settings:
|
|
24
|
+
}, { settings: O, isLoading: k, error: A, saveSettings: j } = n(), [M, N] = a(w), [P, F] = a(!1), [I, L] = a(!1), [R, z] = a(null), B = i(null);
|
|
25
25
|
r(() => {
|
|
26
|
-
let e =
|
|
26
|
+
let e = B;
|
|
27
27
|
return () => {
|
|
28
28
|
e.current && clearTimeout(e.current);
|
|
29
29
|
};
|
|
30
30
|
}, []);
|
|
31
|
-
let
|
|
31
|
+
let V = i(null);
|
|
32
32
|
r(() => {
|
|
33
|
-
if (!
|
|
34
|
-
let e = `${
|
|
35
|
-
e !==
|
|
36
|
-
|
|
37
|
-
defaultPriority:
|
|
38
|
-
rateLimitPerMinute:
|
|
39
|
-
rateLimitPerHour:
|
|
40
|
-
healthCheckIntervalMinutes:
|
|
41
|
-
enableAuditLogging:
|
|
42
|
-
enableWebhookVerification:
|
|
43
|
-
sessionTimeoutMinutes:
|
|
33
|
+
if (!O) return;
|
|
34
|
+
let e = `${O.organizationId}:${O.updatedAt}`;
|
|
35
|
+
e !== V.current && (V.current = e, Promise.resolve().then(() => {
|
|
36
|
+
N({
|
|
37
|
+
defaultPriority: O.defaultPriority,
|
|
38
|
+
rateLimitPerMinute: O.rateLimitPerMinute,
|
|
39
|
+
rateLimitPerHour: O.rateLimitPerHour,
|
|
40
|
+
healthCheckIntervalMinutes: O.healthCheckIntervalMinutes,
|
|
41
|
+
enableAuditLogging: O.enableAuditLogging,
|
|
42
|
+
enableWebhookVerification: O.enableWebhookVerification,
|
|
43
|
+
sessionTimeoutMinutes: O.sessionTimeoutMinutes
|
|
44
44
|
});
|
|
45
45
|
}));
|
|
46
|
-
}, [
|
|
47
|
-
let
|
|
48
|
-
|
|
46
|
+
}, [O]);
|
|
47
|
+
let H = async () => {
|
|
48
|
+
F(!0), z(null), L(!1);
|
|
49
49
|
try {
|
|
50
|
-
await
|
|
51
|
-
defaultPriority:
|
|
52
|
-
rateLimitPerMinute:
|
|
53
|
-
rateLimitPerHour:
|
|
54
|
-
healthCheckIntervalMinutes:
|
|
55
|
-
enableAuditLogging:
|
|
56
|
-
enableWebhookVerification:
|
|
57
|
-
sessionTimeoutMinutes:
|
|
58
|
-
}) ? (
|
|
50
|
+
await j({
|
|
51
|
+
defaultPriority: M.defaultPriority,
|
|
52
|
+
rateLimitPerMinute: M.rateLimitPerMinute,
|
|
53
|
+
rateLimitPerHour: M.rateLimitPerHour,
|
|
54
|
+
healthCheckIntervalMinutes: M.healthCheckIntervalMinutes,
|
|
55
|
+
enableAuditLogging: M.enableAuditLogging,
|
|
56
|
+
enableWebhookVerification: M.enableWebhookVerification,
|
|
57
|
+
sessionTimeoutMinutes: M.sessionTimeoutMinutes
|
|
58
|
+
}) ? (L(!0), B.current && clearTimeout(B.current), B.current = setTimeout(() => L(!1), 3e3)) : z(D("channel.settingsSaveNotPersisted", "Settings could not be saved — organization context is unavailable."));
|
|
59
59
|
} catch (e) {
|
|
60
|
-
|
|
60
|
+
z(e instanceof Error ? e.message : "Failed to save settings");
|
|
61
61
|
} finally {
|
|
62
|
-
|
|
62
|
+
F(!1);
|
|
63
63
|
}
|
|
64
|
-
},
|
|
65
|
-
|
|
64
|
+
}, U = (e, t) => {
|
|
65
|
+
N((n) => ({
|
|
66
66
|
...n,
|
|
67
67
|
[e]: t
|
|
68
68
|
}));
|
|
@@ -81,7 +81,7 @@ function C() {
|
|
|
81
81
|
className: "flex items-center gap-4",
|
|
82
82
|
children: [/* @__PURE__ */ v("button", {
|
|
83
83
|
type: "button",
|
|
84
|
-
onClick: () =>
|
|
84
|
+
onClick: () => T("./list"),
|
|
85
85
|
className: "p-2 hover:bg-bg-sunken rounded-lg transition-colors",
|
|
86
86
|
children: /* @__PURE__ */ v(c, { className: "size-5 " })
|
|
87
87
|
}), /* @__PURE__ */ y("div", {
|
|
@@ -91,10 +91,10 @@ function C() {
|
|
|
91
91
|
children: /* @__PURE__ */ v(h, { className: "size-5 text-primary" })
|
|
92
92
|
}), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h1", {
|
|
93
93
|
className: "text-2xl font-bold",
|
|
94
|
-
children:
|
|
94
|
+
children: D("channel.channelSettings", "Channel Settings")
|
|
95
95
|
}), /* @__PURE__ */ v("p", {
|
|
96
96
|
className: "text-sm text-text-secondary",
|
|
97
|
-
children:
|
|
97
|
+
children: D("channel.channelSettingsSubtitle", "Configure global channel preferences")
|
|
98
98
|
})] })]
|
|
99
99
|
})]
|
|
100
100
|
})
|
|
@@ -103,53 +103,59 @@ function C() {
|
|
|
103
103
|
}), /* @__PURE__ */ y("div", {
|
|
104
104
|
className: "container mx-auto px-6 py-8 max-w-2xl space-y-8",
|
|
105
105
|
children: [
|
|
106
|
-
|
|
106
|
+
/* @__PURE__ */ v(S, {
|
|
107
|
+
icon: /* @__PURE__ */ v(h, {
|
|
108
|
+
size: 15,
|
|
109
|
+
"aria-hidden": "true"
|
|
110
|
+
}),
|
|
111
|
+
children: D("channel.settingsPurpose", "Organization-wide defaults that apply to every channel — how fast messages may go out (rate limits), the default priority for new channels, how often health checks run, and security policies like webhook signature verification and audit logging. Tune these to protect provider quotas, keep an audit trail, and set sensible defaults so each new channel starts configured the way you want.")
|
|
112
|
+
}),
|
|
113
|
+
k && /* @__PURE__ */ y("div", {
|
|
107
114
|
className: "flex items-center gap-2 text-text-secondary",
|
|
108
115
|
children: [/* @__PURE__ */ v(p, { className: "size-4 animate-spin" }), /* @__PURE__ */ v("span", {
|
|
109
116
|
className: "text-sm",
|
|
110
117
|
children: "Loading settings…"
|
|
111
118
|
})]
|
|
112
119
|
}),
|
|
113
|
-
|
|
120
|
+
A && /* @__PURE__ */ y("div", {
|
|
114
121
|
className: "p-4 rounded-lg bg-status-error-bg-subtle border border-status-error-border flex items-center gap-3",
|
|
115
122
|
children: [/* @__PURE__ */ v(s, { className: "size-5 text-status-error-text shrink-0" }), /* @__PURE__ */ v("p", {
|
|
116
123
|
className: "text-sm text-status-error-text",
|
|
117
|
-
children:
|
|
124
|
+
children: D("channel.settingsLoadError", "Failed to load settings — saving is disabled to prevent overwriting existing configuration.")
|
|
118
125
|
})]
|
|
119
126
|
}),
|
|
120
|
-
|
|
127
|
+
I && /* @__PURE__ */ y("div", {
|
|
121
128
|
className: "p-4 rounded-lg bg-status-success-bg-subtle border border-status-success-border flex items-center gap-3",
|
|
122
129
|
children: [/* @__PURE__ */ v(u, { className: "size-5 text-status-success-text shrink-0" }), /* @__PURE__ */ v("p", {
|
|
123
130
|
className: "text-sm text-status-success-text",
|
|
124
|
-
children:
|
|
131
|
+
children: D("channel.settingsSaved", "Settings saved successfully")
|
|
125
132
|
})]
|
|
126
133
|
}),
|
|
127
|
-
|
|
134
|
+
R && /* @__PURE__ */ y("div", {
|
|
128
135
|
className: "p-4 rounded-lg bg-status-error-bg/10 border border-status-error-border/20 flex items-center gap-3",
|
|
129
136
|
children: [/* @__PURE__ */ v(s, { className: "size-5 text-status-error-text shrink-0" }), /* @__PURE__ */ v("p", {
|
|
130
137
|
className: "text-sm text-status-error-text",
|
|
131
|
-
children:
|
|
138
|
+
children: R
|
|
132
139
|
})]
|
|
133
140
|
}),
|
|
134
141
|
/* @__PURE__ */ y(b, {
|
|
135
|
-
|
|
136
|
-
className: "border-border-seam p-6 space-y-4",
|
|
142
|
+
className: "space-y-4 p-6",
|
|
137
143
|
children: [/* @__PURE__ */ y("div", {
|
|
138
144
|
className: "flex items-center gap-2",
|
|
139
145
|
children: [/* @__PURE__ */ v(l, { className: "size-5 text-primary" }), /* @__PURE__ */ v("h2", {
|
|
140
146
|
className: "text-lg font-semibold",
|
|
141
|
-
children:
|
|
147
|
+
children: D("channel.deliverySettings", "Delivery Settings")
|
|
142
148
|
})]
|
|
143
149
|
}), /* @__PURE__ */ y("div", {
|
|
144
150
|
className: "space-y-4",
|
|
145
151
|
children: [/* @__PURE__ */ y("div", { children: [
|
|
146
152
|
/* @__PURE__ */ v("label", {
|
|
147
153
|
className: "text-sm font-medium block mb-1",
|
|
148
|
-
children:
|
|
154
|
+
children: D("channel.defaultPriority", "Default Priority")
|
|
149
155
|
}),
|
|
150
156
|
/* @__PURE__ */ y("select", {
|
|
151
|
-
value:
|
|
152
|
-
onChange: (e) =>
|
|
157
|
+
value: M.defaultPriority,
|
|
158
|
+
onChange: (e) => U("defaultPriority", Number(e.target.value)),
|
|
153
159
|
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
|
|
154
160
|
children: [
|
|
155
161
|
/* @__PURE__ */ v("option", {
|
|
@@ -183,26 +189,26 @@ function C() {
|
|
|
183
189
|
children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
184
190
|
htmlFor: "channel-settings-rate-limit-min",
|
|
185
191
|
className: "text-sm font-medium block mb-1",
|
|
186
|
-
children:
|
|
192
|
+
children: D("channel.rateLimitPerMin", "Rate Limit (per minute)")
|
|
187
193
|
}), /* @__PURE__ */ v("input", {
|
|
188
194
|
id: "channel-settings-rate-limit-min",
|
|
189
|
-
"aria-label":
|
|
195
|
+
"aria-label": D("channel.rateLimitPerMin", "Rate Limit (per minute)"),
|
|
190
196
|
type: "number",
|
|
191
|
-
value:
|
|
192
|
-
onChange: (e) =>
|
|
197
|
+
value: M.rateLimitPerMinute,
|
|
198
|
+
onChange: (e) => U("rateLimitPerMinute", Number(e.target.value)),
|
|
193
199
|
min: 1,
|
|
194
200
|
max: 1e3,
|
|
195
201
|
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
|
|
196
202
|
})] }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
197
203
|
htmlFor: "channel-settings-rate-limit-hour",
|
|
198
204
|
className: "text-sm font-medium block mb-1",
|
|
199
|
-
children:
|
|
205
|
+
children: D("channel.rateLimitPerHour", "Rate Limit (per hour)")
|
|
200
206
|
}), /* @__PURE__ */ v("input", {
|
|
201
207
|
id: "channel-settings-rate-limit-hour",
|
|
202
|
-
"aria-label":
|
|
208
|
+
"aria-label": D("channel.rateLimitPerHour", "Rate Limit (per hour)"),
|
|
203
209
|
type: "number",
|
|
204
|
-
value:
|
|
205
|
-
onChange: (e) =>
|
|
210
|
+
value: M.rateLimitPerHour,
|
|
211
|
+
onChange: (e) => U("rateLimitPerHour", Number(e.target.value)),
|
|
206
212
|
min: 1,
|
|
207
213
|
max: 5e4,
|
|
208
214
|
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
|
|
@@ -210,14 +216,14 @@ function C() {
|
|
|
210
216
|
})]
|
|
211
217
|
})]
|
|
212
218
|
}),
|
|
213
|
-
/* @__PURE__ */ y(
|
|
219
|
+
/* @__PURE__ */ y(x, {
|
|
214
220
|
treatment: "plain",
|
|
215
221
|
className: "border-border-seam p-6 space-y-4",
|
|
216
222
|
children: [/* @__PURE__ */ y("div", {
|
|
217
223
|
className: "flex items-center gap-2",
|
|
218
224
|
children: [/* @__PURE__ */ v(d, { className: "size-5 text-primary" }), /* @__PURE__ */ v("h2", {
|
|
219
225
|
className: "text-lg font-semibold",
|
|
220
|
-
children:
|
|
226
|
+
children: D("channel.healthMonitoring", "Health Monitoring")
|
|
221
227
|
})]
|
|
222
228
|
}), /* @__PURE__ */ y("div", { children: [
|
|
223
229
|
/* @__PURE__ */ v("label", {
|
|
@@ -227,8 +233,8 @@ function C() {
|
|
|
227
233
|
}),
|
|
228
234
|
/* @__PURE__ */ y("select", {
|
|
229
235
|
id: "channel-settings-health-interval",
|
|
230
|
-
value:
|
|
231
|
-
onChange: (e) =>
|
|
236
|
+
value: M.healthCheckIntervalMinutes,
|
|
237
|
+
onChange: (e) => U("healthCheckIntervalMinutes", Number(e.target.value)),
|
|
232
238
|
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
|
|
233
239
|
children: [
|
|
234
240
|
/* @__PURE__ */ v("option", {
|
|
@@ -263,14 +269,14 @@ function C() {
|
|
|
263
269
|
})
|
|
264
270
|
] })]
|
|
265
271
|
}),
|
|
266
|
-
/* @__PURE__ */ y(
|
|
272
|
+
/* @__PURE__ */ y(x, {
|
|
267
273
|
treatment: "plain",
|
|
268
274
|
className: "border-border-seam p-6 space-y-4",
|
|
269
275
|
children: [/* @__PURE__ */ y("div", {
|
|
270
276
|
className: "flex items-center gap-2",
|
|
271
277
|
children: [/* @__PURE__ */ v(g, { className: "size-5 text-primary" }), /* @__PURE__ */ v("h2", {
|
|
272
278
|
className: "text-lg font-semibold",
|
|
273
|
-
children:
|
|
279
|
+
children: D("channel.security", "Security")
|
|
274
280
|
})]
|
|
275
281
|
}), /* @__PURE__ */ y("div", {
|
|
276
282
|
className: "space-y-4",
|
|
@@ -279,15 +285,15 @@ function C() {
|
|
|
279
285
|
className: "flex items-center justify-between p-4 rounded-lg border hover:bg-bg-sunken/50 cursor-pointer",
|
|
280
286
|
children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
281
287
|
className: "font-medium",
|
|
282
|
-
children:
|
|
288
|
+
children: D("channel.webhookSignatureVerification", "Webhook Signature Verification")
|
|
283
289
|
}), /* @__PURE__ */ v("p", {
|
|
284
290
|
className: "text-sm text-text-secondary",
|
|
285
291
|
children: "Verify HMAC signatures on incoming webhooks"
|
|
286
292
|
})] }), /* @__PURE__ */ v("input", {
|
|
287
293
|
type: "checkbox",
|
|
288
|
-
"aria-label":
|
|
289
|
-
checked:
|
|
290
|
-
onChange: (e) =>
|
|
294
|
+
"aria-label": D("channel.webhookSignatureVerification", "Webhook Signature Verification"),
|
|
295
|
+
checked: M.enableWebhookVerification,
|
|
296
|
+
onChange: (e) => U("enableWebhookVerification", e.target.checked),
|
|
291
297
|
className: "size-5 rounded border-gray-300 text-primary focus:ring-primary"
|
|
292
298
|
})]
|
|
293
299
|
}),
|
|
@@ -295,24 +301,24 @@ function C() {
|
|
|
295
301
|
className: "flex items-center justify-between p-4 rounded-lg border hover:bg-bg-sunken/50 cursor-pointer",
|
|
296
302
|
children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
297
303
|
className: "font-medium",
|
|
298
|
-
children:
|
|
304
|
+
children: D("channel.auditLogging", "Audit Logging")
|
|
299
305
|
}), /* @__PURE__ */ v("p", {
|
|
300
306
|
className: "text-sm text-text-secondary",
|
|
301
307
|
children: "Log all channel operations for audit trail"
|
|
302
308
|
})] }), /* @__PURE__ */ v("input", {
|
|
303
309
|
type: "checkbox",
|
|
304
|
-
"aria-label":
|
|
305
|
-
checked:
|
|
306
|
-
onChange: (e) =>
|
|
310
|
+
"aria-label": D("channel.auditLogging", "Audit Logging"),
|
|
311
|
+
checked: M.enableAuditLogging,
|
|
312
|
+
onChange: (e) => U("enableAuditLogging", e.target.checked),
|
|
307
313
|
className: "size-5 rounded border-gray-300 text-primary focus:ring-primary"
|
|
308
314
|
})]
|
|
309
315
|
}),
|
|
310
316
|
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
311
317
|
className: "text-sm font-medium block mb-1",
|
|
312
|
-
children:
|
|
318
|
+
children: D("channel.sessionTimeout", "Session Timeout (minutes)")
|
|
313
319
|
}), /* @__PURE__ */ y("select", {
|
|
314
|
-
value:
|
|
315
|
-
onChange: (e) =>
|
|
320
|
+
value: M.sessionTimeoutMinutes,
|
|
321
|
+
onChange: (e) => U("sessionTimeoutMinutes", Number(e.target.value)),
|
|
316
322
|
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
|
|
317
323
|
children: [
|
|
318
324
|
/* @__PURE__ */ v("option", {
|
|
@@ -340,18 +346,18 @@ function C() {
|
|
|
340
346
|
]
|
|
341
347
|
})]
|
|
342
348
|
}),
|
|
343
|
-
/* @__PURE__ */ y(
|
|
349
|
+
/* @__PURE__ */ y(x, {
|
|
344
350
|
treatment: "plain",
|
|
345
351
|
className: "border-border-seam p-6 space-y-4",
|
|
346
352
|
children: [/* @__PURE__ */ y("div", {
|
|
347
353
|
className: "flex items-center gap-2",
|
|
348
354
|
children: [/* @__PURE__ */ v(f, { className: "size-5 text-primary" }), /* @__PURE__ */ v("h2", {
|
|
349
355
|
className: "text-lg font-semibold",
|
|
350
|
-
children:
|
|
356
|
+
children: D("channel.dataManagement", "Data Management")
|
|
351
357
|
})]
|
|
352
|
-
}), /* @__PURE__ */ v(
|
|
358
|
+
}), /* @__PURE__ */ v(C, {
|
|
353
359
|
collapsedHeight: 96,
|
|
354
|
-
label:
|
|
360
|
+
label: D("channel.showRetentionPolicies", "Show retention policies"),
|
|
355
361
|
children: /* @__PURE__ */ y("div", {
|
|
356
362
|
className: "space-y-4",
|
|
357
363
|
children: [
|
|
@@ -361,7 +367,7 @@ function C() {
|
|
|
361
367
|
className: "flex items-center justify-between mb-2",
|
|
362
368
|
children: [/* @__PURE__ */ v("p", {
|
|
363
369
|
className: "font-medium",
|
|
364
|
-
children:
|
|
370
|
+
children: D("channel.webhookEventRetention", "Webhook Event Retention")
|
|
365
371
|
}), /* @__PURE__ */ v("span", {
|
|
366
372
|
className: "text-sm text-text-secondary",
|
|
367
373
|
children: "7 days"
|
|
@@ -377,7 +383,7 @@ function C() {
|
|
|
377
383
|
className: "flex items-center justify-between mb-2",
|
|
378
384
|
children: [/* @__PURE__ */ v("p", {
|
|
379
385
|
className: "font-medium",
|
|
380
|
-
children:
|
|
386
|
+
children: D("channel.deliveryAttemptRetention", "Delivery Attempt Retention")
|
|
381
387
|
}), /* @__PURE__ */ v("span", {
|
|
382
388
|
className: "text-sm text-text-secondary",
|
|
383
389
|
children: "7 days"
|
|
@@ -393,7 +399,7 @@ function C() {
|
|
|
393
399
|
className: "flex items-center justify-between mb-2",
|
|
394
400
|
children: [/* @__PURE__ */ v("p", {
|
|
395
401
|
className: "font-medium",
|
|
396
|
-
children:
|
|
402
|
+
children: D("channel.failedDeliveryRetention", "Failed Delivery Retention")
|
|
397
403
|
}), /* @__PURE__ */ v("span", {
|
|
398
404
|
className: "text-sm text-status-warning-text",
|
|
399
405
|
children: "30 days"
|
|
@@ -411,10 +417,10 @@ function C() {
|
|
|
411
417
|
className: "flex justify-end pt-4",
|
|
412
418
|
children: /* @__PURE__ */ y("button", {
|
|
413
419
|
type: "button",
|
|
414
|
-
onClick:
|
|
415
|
-
disabled:
|
|
420
|
+
onClick: H,
|
|
421
|
+
disabled: P || k || !!A,
|
|
416
422
|
className: "px-6 py-3 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors disabled:opacity-50 flex items-center gap-2",
|
|
417
|
-
children: [
|
|
423
|
+
children: [P ? /* @__PURE__ */ v(p, { className: "size-5 animate-spin" }) : /* @__PURE__ */ v(m, { className: "size-5 " }), D("channel.saveSettings", "Save Settings")]
|
|
418
424
|
})
|
|
419
425
|
})
|
|
420
426
|
]
|
|
@@ -423,6 +429,6 @@ function C() {
|
|
|
423
429
|
});
|
|
424
430
|
}
|
|
425
431
|
//#endregion
|
|
426
|
-
export {
|
|
432
|
+
export { T as ChannelSettingsPage };
|
|
427
433
|
|
|
428
434
|
//# sourceMappingURL=ChannelSettingsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChannelSettingsPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ChannelSettingsPage.tsx"],"sourcesContent":["/**\n * Channel Settings Page\n *\n * Global settings for the channels module.\n */\n\nimport { useState, useEffect, useRef } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Settings,\n Bell,\n Shield,\n Clock,\n Database,\n Save,\n Loader2,\n AlertCircle,\n CheckCircle2,\n} from 'lucide-react';\nimport { GlassCard, ShowMore } from '@burdenoff/fe-libs/ui';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { useChannelOrgSettings } from '../../../hooks/useChannelOrgSettings';\nimport type { ChannelOrgSettingsInput } from '../../../hooks/useChannelOrgSettings';\n\ninterface ChannelSettings {\n defaultPriority: number;\n rateLimitPerMinute: number;\n rateLimitPerHour: number;\n healthCheckIntervalMinutes: number;\n enableAuditLogging: boolean;\n enableWebhookVerification: boolean;\n sessionTimeoutMinutes: number;\n}\n\nconst DEFAULT_SETTINGS: ChannelSettings = {\n defaultPriority: 2,\n rateLimitPerMinute: 100,\n rateLimitPerHour: 5000,\n healthCheckIntervalMinutes: 5,\n enableAuditLogging: true,\n enableWebhookVerification: true,\n sessionTimeoutMinutes: 30,\n};\n\nexport function ChannelSettingsPage() {\n const navigate = useNavigate();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const {\n settings: remoteSettings,\n isLoading: isLoadingSettings,\n error: settingsLoadError,\n saveSettings,\n } = useChannelOrgSettings();\n\n const [settings, setSettings] = useState<ChannelSettings>(DEFAULT_SETTINGS);\n const [isSaving, setIsSaving] = useState(false);\n const [saveSuccess, setSaveSuccess] = useState(false);\n const [saveError, setSaveError] = useState<string | null>(null);\n const saveSuccessTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Populate / re-sync form from remote settings whenever they change.\n // This handles both the initial load and server-side normalisations returned\n // by saveSettings (e.g. clamped values), so the form always reflects the\n // true persisted state rather than potentially stale local values.\n // Defer setState to a microtask to avoid react-hooks/exhaustive-deps warnings.\n useEffect(() => {\n const timerRef = saveSuccessTimerRef;\n return () => {\n if (timerRef.current) clearTimeout(timerRef.current);\n };\n }, []);\n\n const loadedOrgIdRef = useRef<string | null>(null);\n useEffect(() => {\n if (!remoteSettings) return;\n // Re-hydrate whenever the org changes *or* the updatedAt timestamp changes,\n // so server-side coercion after a save is reflected immediately.\n const changeKey = `${remoteSettings.organizationId}:${remoteSettings.updatedAt}`;\n if (changeKey === loadedOrgIdRef.current) return;\n loadedOrgIdRef.current = changeKey;\n void Promise.resolve().then(() => {\n setSettings({\n defaultPriority: remoteSettings.defaultPriority,\n rateLimitPerMinute: remoteSettings.rateLimitPerMinute,\n rateLimitPerHour: remoteSettings.rateLimitPerHour,\n healthCheckIntervalMinutes: remoteSettings.healthCheckIntervalMinutes,\n enableAuditLogging: remoteSettings.enableAuditLogging,\n enableWebhookVerification: remoteSettings.enableWebhookVerification,\n sessionTimeoutMinutes: remoteSettings.sessionTimeoutMinutes,\n });\n });\n }, [remoteSettings]);\n\n const handleSave = async () => {\n setIsSaving(true);\n setSaveError(null);\n setSaveSuccess(false);\n\n try {\n const input: ChannelOrgSettingsInput = {\n defaultPriority: settings.defaultPriority,\n rateLimitPerMinute: settings.rateLimitPerMinute,\n rateLimitPerHour: settings.rateLimitPerHour,\n healthCheckIntervalMinutes: settings.healthCheckIntervalMinutes,\n enableAuditLogging: settings.enableAuditLogging,\n enableWebhookVerification: settings.enableWebhookVerification,\n sessionTimeoutMinutes: settings.sessionTimeoutMinutes,\n };\n // saveSettings returns null when organizationId is absent (context not\n // ready); only show success when something was actually persisted (P2).\n const saved = await saveSettings(input);\n if (saved) {\n setSaveSuccess(true);\n if (saveSuccessTimerRef.current) clearTimeout(saveSuccessTimerRef.current);\n saveSuccessTimerRef.current = setTimeout(() => setSaveSuccess(false), 3000);\n } else {\n setSaveError(\n tr(\n 'channel.settingsSaveNotPersisted',\n 'Settings could not be saved — organization context is unavailable.'\n )\n );\n }\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : 'Failed to save settings';\n setSaveError(message);\n } finally {\n setIsSaving(false);\n }\n };\n\n const updateSetting = <K extends keyof ChannelSettings>(key: K, value: ChannelSettings[K]) => {\n setSettings((prev) => ({ ...prev, [key]: value }));\n };\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <div className=\"min-h-screen bg-bg-surface\">\n {/* Header */}\n <div className=\"border-b bg-bg-surface\">\n <div className=\"container mx-auto px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate('./list')}\n className=\"p-2 hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft className=\"size-5 \" />\n </button>\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 bg-action-primary-bg/10 rounded-lg\">\n <Settings className=\"size-5 text-primary\" />\n </div>\n <div>\n <h1 className=\"text-2xl font-bold\">\n {tr('channel.channelSettings', 'Channel Settings')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.channelSettingsSubtitle',\n 'Configure global channel preferences'\n )}\n </p>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"container mx-auto px-6 py-8 max-w-2xl space-y-8\">\n {/* Loading state */}\n {isLoadingSettings && (\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Loader2 className=\"size-4 animate-spin\" />\n <span className=\"text-sm\">Loading settings…</span>\n </div>\n )}\n\n {/* Settings load error */}\n {settingsLoadError && (\n <div className=\"p-4 rounded-lg bg-status-error-bg-subtle border border-status-error-border flex items-center gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">\n {tr(\n 'channel.settingsLoadError',\n 'Failed to load settings — saving is disabled to prevent overwriting existing configuration.'\n )}\n </p>\n </div>\n )}\n\n {/* Alerts */}\n {saveSuccess && (\n <div className=\"p-4 rounded-lg bg-status-success-bg-subtle border border-status-success-border flex items-center gap-3\">\n <CheckCircle2 className=\"size-5 text-status-success-text shrink-0\" />\n <p className=\"text-sm text-status-success-text\">\n {tr('channel.settingsSaved', 'Settings saved successfully')}\n </p>\n </div>\n )}\n\n {saveError && (\n <div className=\"p-4 rounded-lg bg-status-error-bg/10 border border-status-error-border/20 flex items-center gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">{saveError}</p>\n </div>\n )}\n\n {/* Delivery Settings */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4\">\n <div className=\"flex items-center gap-2\">\n <Bell className=\"size-5 text-primary\" />\n <h2 className=\"text-lg font-semibold\">\n {tr('channel.deliverySettings', 'Delivery Settings')}\n </h2>\n </div>\n\n <div className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium block mb-1\">\n {tr('channel.defaultPriority', 'Default Priority')}\n </label>\n <select\n value={settings.defaultPriority}\n onChange={(e) => updateSetting('defaultPriority', Number(e.target.value))}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value={0}>0 - Lowest</option>\n <option value={1}>1 - Low</option>\n <option value={2}>2 - Normal</option>\n <option value={3}>3 - High</option>\n <option value={4}>4 - Highest</option>\n </select>\n <p className=\"text-xs text-text-secondary mt-1\">\n Priority used when creating new channels without explicit priority\n </p>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label\n htmlFor=\"channel-settings-rate-limit-min\"\n className=\"text-sm font-medium block mb-1\"\n >\n {tr('channel.rateLimitPerMin', 'Rate Limit (per minute)')}\n </label>\n <input\n id=\"channel-settings-rate-limit-min\"\n aria-label={tr('channel.rateLimitPerMin', 'Rate Limit (per minute)')}\n type=\"number\"\n value={settings.rateLimitPerMinute}\n onChange={(e) => updateSetting('rateLimitPerMinute', Number(e.target.value))}\n min={1}\n max={1000}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n <div>\n <label\n htmlFor=\"channel-settings-rate-limit-hour\"\n className=\"text-sm font-medium block mb-1\"\n >\n {tr('channel.rateLimitPerHour', 'Rate Limit (per hour)')}\n </label>\n <input\n id=\"channel-settings-rate-limit-hour\"\n aria-label={tr('channel.rateLimitPerHour', 'Rate Limit (per hour)')}\n type=\"number\"\n value={settings.rateLimitPerHour}\n onChange={(e) => updateSetting('rateLimitPerHour', Number(e.target.value))}\n min={1}\n max={50000}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n </div>\n </div>\n </GlassCard>\n\n {/* Health Check Settings */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-5 text-primary\" />\n <h2 className=\"text-lg font-semibold\">\n {tr('channel.healthMonitoring', 'Health Monitoring')}\n </h2>\n </div>\n\n <div>\n <label\n htmlFor=\"channel-settings-health-interval\"\n className=\"text-sm font-medium block mb-1\"\n >\n Health Check Interval (minutes)\n </label>\n <select\n id=\"channel-settings-health-interval\"\n value={settings.healthCheckIntervalMinutes}\n onChange={(e) =>\n updateSetting('healthCheckIntervalMinutes', Number(e.target.value))\n }\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value={1}>1 minute</option>\n <option value={5}>5 minutes</option>\n <option value={10}>10 minutes</option>\n <option value={15}>15 minutes</option>\n <option value={30}>30 minutes</option>\n <option value={60}>1 hour</option>\n </select>\n <p className=\"text-xs text-text-secondary mt-1\">\n How often to check connection health status\n </p>\n </div>\n </GlassCard>\n\n {/* Security Settings */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4\">\n <div className=\"flex items-center gap-2\">\n <Shield className=\"size-5 text-primary\" />\n <h2 className=\"text-lg font-semibold\">{tr('channel.security', 'Security')}</h2>\n </div>\n\n <div className=\"space-y-4\">\n <label className=\"flex items-center justify-between p-4 rounded-lg border hover:bg-bg-sunken/50 cursor-pointer\">\n <div>\n <p className=\"font-medium\">\n {tr('channel.webhookSignatureVerification', 'Webhook Signature Verification')}\n </p>\n <p className=\"text-sm text-text-secondary\">\n Verify HMAC signatures on incoming webhooks\n </p>\n </div>\n <input\n type=\"checkbox\"\n aria-label={tr(\n 'channel.webhookSignatureVerification',\n 'Webhook Signature Verification'\n )}\n checked={settings.enableWebhookVerification}\n onChange={(e) => updateSetting('enableWebhookVerification', e.target.checked)}\n className=\"size-5 rounded border-gray-300 text-primary focus:ring-primary\"\n />\n </label>\n\n <label className=\"flex items-center justify-between p-4 rounded-lg border hover:bg-bg-sunken/50 cursor-pointer\">\n <div>\n <p className=\"font-medium\">{tr('channel.auditLogging', 'Audit Logging')}</p>\n <p className=\"text-sm text-text-secondary\">\n Log all channel operations for audit trail\n </p>\n </div>\n <input\n type=\"checkbox\"\n aria-label={tr('channel.auditLogging', 'Audit Logging')}\n checked={settings.enableAuditLogging}\n onChange={(e) => updateSetting('enableAuditLogging', e.target.checked)}\n className=\"size-5 rounded border-gray-300 text-primary focus:ring-primary\"\n />\n </label>\n\n <div>\n <label className=\"text-sm font-medium block mb-1\">\n {tr('channel.sessionTimeout', 'Session Timeout (minutes)')}\n </label>\n <select\n value={settings.sessionTimeoutMinutes}\n onChange={(e) => updateSetting('sessionTimeoutMinutes', Number(e.target.value))}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value={15}>15 minutes</option>\n <option value={30}>30 minutes</option>\n <option value={60}>1 hour</option>\n <option value={120}>2 hours</option>\n <option value={240}>4 hours</option>\n </select>\n </div>\n </div>\n </GlassCard>\n\n {/* Data Management */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4\">\n <div className=\"flex items-center gap-2\">\n <Database className=\"size-5 text-primary\" />\n <h2 className=\"text-lg font-semibold\">\n {tr('channel.dataManagement', 'Data Management')}\n </h2>\n </div>\n\n <ShowMore\n collapsedHeight={96}\n label={tr('channel.showRetentionPolicies', 'Show retention policies')}\n >\n <div className=\"space-y-4\">\n <div className=\"p-4 rounded-lg border\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"font-medium\">\n {tr('channel.webhookEventRetention', 'Webhook Event Retention')}\n </p>\n <span className=\"text-sm text-text-secondary\">7 days</span>\n </div>\n <p className=\"text-sm text-text-secondary\">\n Incoming webhook events are automatically deleted after 7 days\n </p>\n </div>\n\n <div className=\"p-4 rounded-lg border\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"font-medium\">\n {tr('channel.deliveryAttemptRetention', 'Delivery Attempt Retention')}\n </p>\n <span className=\"text-sm text-text-secondary\">7 days</span>\n </div>\n <p className=\"text-sm text-text-secondary\">\n Successful delivery attempts are automatically deleted after 7 days\n </p>\n </div>\n\n <div className=\"p-4 rounded-lg border border-status-warning-border bg-status-warning-bg-subtle\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"font-medium\">\n {tr('channel.failedDeliveryRetention', 'Failed Delivery Retention')}\n </p>\n <span className=\"text-sm text-status-warning-text\">30 days</span>\n </div>\n <p className=\"text-sm text-text-secondary\">\n Failed delivery attempts are kept for 30 days for debugging\n </p>\n </div>\n </div>\n </ShowMore>\n </GlassCard>\n\n {/* Save Button */}\n <div className=\"flex justify-end pt-4\">\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={isSaving || isLoadingSettings || !!settingsLoadError}\n className=\"px-6 py-3 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors disabled:opacity-50 flex items-center gap-2\"\n >\n {isSaving ? (\n <Loader2 className=\"size-5 animate-spin\" />\n ) : (\n <Save className=\"size-5 \" />\n )}\n {tr('channel.saveSettings', 'Save Settings')}\n </button>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChannelSettingsPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ChannelSettingsPage.tsx"],"sourcesContent":["/**\n * Channel Settings Page\n *\n * Global settings for the channels module.\n */\n\nimport { useState, useEffect, useRef } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Settings,\n Bell,\n Shield,\n Clock,\n Database,\n Save,\n Loader2,\n AlertCircle,\n CheckCircle2,\n} from 'lucide-react';\nimport { EmphasisPanel, GlassCard, PagePurpose, ShowMore } from '@burdenoff/fe-libs/ui';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { useChannelOrgSettings } from '../../../hooks/useChannelOrgSettings';\nimport type { ChannelOrgSettingsInput } from '../../../hooks/useChannelOrgSettings';\n\ninterface ChannelSettings {\n defaultPriority: number;\n rateLimitPerMinute: number;\n rateLimitPerHour: number;\n healthCheckIntervalMinutes: number;\n enableAuditLogging: boolean;\n enableWebhookVerification: boolean;\n sessionTimeoutMinutes: number;\n}\n\nconst DEFAULT_SETTINGS: ChannelSettings = {\n defaultPriority: 2,\n rateLimitPerMinute: 100,\n rateLimitPerHour: 5000,\n healthCheckIntervalMinutes: 5,\n enableAuditLogging: true,\n enableWebhookVerification: true,\n sessionTimeoutMinutes: 30,\n};\n\nexport function ChannelSettingsPage() {\n const navigate = useNavigate();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const {\n settings: remoteSettings,\n isLoading: isLoadingSettings,\n error: settingsLoadError,\n saveSettings,\n } = useChannelOrgSettings();\n\n const [settings, setSettings] = useState<ChannelSettings>(DEFAULT_SETTINGS);\n const [isSaving, setIsSaving] = useState(false);\n const [saveSuccess, setSaveSuccess] = useState(false);\n const [saveError, setSaveError] = useState<string | null>(null);\n const saveSuccessTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Populate / re-sync form from remote settings whenever they change.\n // This handles both the initial load and server-side normalisations returned\n // by saveSettings (e.g. clamped values), so the form always reflects the\n // true persisted state rather than potentially stale local values.\n // Defer setState to a microtask to avoid react-hooks/exhaustive-deps warnings.\n useEffect(() => {\n const timerRef = saveSuccessTimerRef;\n return () => {\n if (timerRef.current) clearTimeout(timerRef.current);\n };\n }, []);\n\n const loadedOrgIdRef = useRef<string | null>(null);\n useEffect(() => {\n if (!remoteSettings) return;\n // Re-hydrate whenever the org changes *or* the updatedAt timestamp changes,\n // so server-side coercion after a save is reflected immediately.\n const changeKey = `${remoteSettings.organizationId}:${remoteSettings.updatedAt}`;\n if (changeKey === loadedOrgIdRef.current) return;\n loadedOrgIdRef.current = changeKey;\n void Promise.resolve().then(() => {\n setSettings({\n defaultPriority: remoteSettings.defaultPriority,\n rateLimitPerMinute: remoteSettings.rateLimitPerMinute,\n rateLimitPerHour: remoteSettings.rateLimitPerHour,\n healthCheckIntervalMinutes: remoteSettings.healthCheckIntervalMinutes,\n enableAuditLogging: remoteSettings.enableAuditLogging,\n enableWebhookVerification: remoteSettings.enableWebhookVerification,\n sessionTimeoutMinutes: remoteSettings.sessionTimeoutMinutes,\n });\n });\n }, [remoteSettings]);\n\n const handleSave = async () => {\n setIsSaving(true);\n setSaveError(null);\n setSaveSuccess(false);\n\n try {\n const input: ChannelOrgSettingsInput = {\n defaultPriority: settings.defaultPriority,\n rateLimitPerMinute: settings.rateLimitPerMinute,\n rateLimitPerHour: settings.rateLimitPerHour,\n healthCheckIntervalMinutes: settings.healthCheckIntervalMinutes,\n enableAuditLogging: settings.enableAuditLogging,\n enableWebhookVerification: settings.enableWebhookVerification,\n sessionTimeoutMinutes: settings.sessionTimeoutMinutes,\n };\n // saveSettings returns null when organizationId is absent (context not\n // ready); only show success when something was actually persisted (P2).\n const saved = await saveSettings(input);\n if (saved) {\n setSaveSuccess(true);\n if (saveSuccessTimerRef.current) clearTimeout(saveSuccessTimerRef.current);\n saveSuccessTimerRef.current = setTimeout(() => setSaveSuccess(false), 3000);\n } else {\n setSaveError(\n tr(\n 'channel.settingsSaveNotPersisted',\n 'Settings could not be saved — organization context is unavailable.'\n )\n );\n }\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : 'Failed to save settings';\n setSaveError(message);\n } finally {\n setIsSaving(false);\n }\n };\n\n const updateSetting = <K extends keyof ChannelSettings>(key: K, value: ChannelSettings[K]) => {\n setSettings((prev) => ({ ...prev, [key]: value }));\n };\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <div className=\"min-h-screen bg-bg-surface\">\n {/* Header */}\n <div className=\"border-b bg-bg-surface\">\n <div className=\"container mx-auto px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigate('./list')}\n className=\"p-2 hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft className=\"size-5 \" />\n </button>\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 bg-action-primary-bg/10 rounded-lg\">\n <Settings className=\"size-5 text-primary\" />\n </div>\n <div>\n <h1 className=\"text-2xl font-bold\">\n {tr('channel.channelSettings', 'Channel Settings')}\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.channelSettingsSubtitle',\n 'Configure global channel preferences'\n )}\n </p>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n {/* Content */}\n <div className=\"container mx-auto px-6 py-8 max-w-2xl space-y-8\">\n {/* Page purpose */}\n <PagePurpose icon={<Settings size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.settingsPurpose',\n 'Organization-wide defaults that apply to every channel — how fast messages may go out (rate limits), the default priority for new channels, how often health checks run, and security policies like webhook signature verification and audit logging. Tune these to protect provider quotas, keep an audit trail, and set sensible defaults so each new channel starts configured the way you want.'\n )}\n </PagePurpose>\n\n {/* Loading state */}\n {isLoadingSettings && (\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Loader2 className=\"size-4 animate-spin\" />\n <span className=\"text-sm\">Loading settings…</span>\n </div>\n )}\n\n {/* Settings load error */}\n {settingsLoadError && (\n <div className=\"p-4 rounded-lg bg-status-error-bg-subtle border border-status-error-border flex items-center gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">\n {tr(\n 'channel.settingsLoadError',\n 'Failed to load settings — saving is disabled to prevent overwriting existing configuration.'\n )}\n </p>\n </div>\n )}\n\n {/* Alerts */}\n {saveSuccess && (\n <div className=\"p-4 rounded-lg bg-status-success-bg-subtle border border-status-success-border flex items-center gap-3\">\n <CheckCircle2 className=\"size-5 text-status-success-text shrink-0\" />\n <p className=\"text-sm text-status-success-text\">\n {tr('channel.settingsSaved', 'Settings saved successfully')}\n </p>\n </div>\n )}\n\n {saveError && (\n <div className=\"p-4 rounded-lg bg-status-error-bg/10 border border-status-error-border/20 flex items-center gap-3\">\n <AlertCircle className=\"size-5 text-status-error-text shrink-0\" />\n <p className=\"text-sm text-status-error-text\">{saveError}</p>\n </div>\n )}\n\n {/* Delivery Settings — the primary, most-impactful section */}\n <EmphasisPanel className=\"space-y-4 p-6\">\n <div className=\"flex items-center gap-2\">\n <Bell className=\"size-5 text-primary\" />\n <h2 className=\"text-lg font-semibold\">\n {tr('channel.deliverySettings', 'Delivery Settings')}\n </h2>\n </div>\n\n <div className=\"space-y-4\">\n <div>\n <label className=\"text-sm font-medium block mb-1\">\n {tr('channel.defaultPriority', 'Default Priority')}\n </label>\n <select\n value={settings.defaultPriority}\n onChange={(e) => updateSetting('defaultPriority', Number(e.target.value))}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value={0}>0 - Lowest</option>\n <option value={1}>1 - Low</option>\n <option value={2}>2 - Normal</option>\n <option value={3}>3 - High</option>\n <option value={4}>4 - Highest</option>\n </select>\n <p className=\"text-xs text-text-secondary mt-1\">\n Priority used when creating new channels without explicit priority\n </p>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label\n htmlFor=\"channel-settings-rate-limit-min\"\n className=\"text-sm font-medium block mb-1\"\n >\n {tr('channel.rateLimitPerMin', 'Rate Limit (per minute)')}\n </label>\n <input\n id=\"channel-settings-rate-limit-min\"\n aria-label={tr('channel.rateLimitPerMin', 'Rate Limit (per minute)')}\n type=\"number\"\n value={settings.rateLimitPerMinute}\n onChange={(e) => updateSetting('rateLimitPerMinute', Number(e.target.value))}\n min={1}\n max={1000}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n <div>\n <label\n htmlFor=\"channel-settings-rate-limit-hour\"\n className=\"text-sm font-medium block mb-1\"\n >\n {tr('channel.rateLimitPerHour', 'Rate Limit (per hour)')}\n </label>\n <input\n id=\"channel-settings-rate-limit-hour\"\n aria-label={tr('channel.rateLimitPerHour', 'Rate Limit (per hour)')}\n type=\"number\"\n value={settings.rateLimitPerHour}\n onChange={(e) => updateSetting('rateLimitPerHour', Number(e.target.value))}\n min={1}\n max={50000}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n </div>\n </div>\n </EmphasisPanel>\n\n {/* Health Check Settings */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-5 text-primary\" />\n <h2 className=\"text-lg font-semibold\">\n {tr('channel.healthMonitoring', 'Health Monitoring')}\n </h2>\n </div>\n\n <div>\n <label\n htmlFor=\"channel-settings-health-interval\"\n className=\"text-sm font-medium block mb-1\"\n >\n Health Check Interval (minutes)\n </label>\n <select\n id=\"channel-settings-health-interval\"\n value={settings.healthCheckIntervalMinutes}\n onChange={(e) =>\n updateSetting('healthCheckIntervalMinutes', Number(e.target.value))\n }\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value={1}>1 minute</option>\n <option value={5}>5 minutes</option>\n <option value={10}>10 minutes</option>\n <option value={15}>15 minutes</option>\n <option value={30}>30 minutes</option>\n <option value={60}>1 hour</option>\n </select>\n <p className=\"text-xs text-text-secondary mt-1\">\n How often to check connection health status\n </p>\n </div>\n </GlassCard>\n\n {/* Security Settings */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4\">\n <div className=\"flex items-center gap-2\">\n <Shield className=\"size-5 text-primary\" />\n <h2 className=\"text-lg font-semibold\">{tr('channel.security', 'Security')}</h2>\n </div>\n\n <div className=\"space-y-4\">\n <label className=\"flex items-center justify-between p-4 rounded-lg border hover:bg-bg-sunken/50 cursor-pointer\">\n <div>\n <p className=\"font-medium\">\n {tr('channel.webhookSignatureVerification', 'Webhook Signature Verification')}\n </p>\n <p className=\"text-sm text-text-secondary\">\n Verify HMAC signatures on incoming webhooks\n </p>\n </div>\n <input\n type=\"checkbox\"\n aria-label={tr(\n 'channel.webhookSignatureVerification',\n 'Webhook Signature Verification'\n )}\n checked={settings.enableWebhookVerification}\n onChange={(e) => updateSetting('enableWebhookVerification', e.target.checked)}\n className=\"size-5 rounded border-gray-300 text-primary focus:ring-primary\"\n />\n </label>\n\n <label className=\"flex items-center justify-between p-4 rounded-lg border hover:bg-bg-sunken/50 cursor-pointer\">\n <div>\n <p className=\"font-medium\">{tr('channel.auditLogging', 'Audit Logging')}</p>\n <p className=\"text-sm text-text-secondary\">\n Log all channel operations for audit trail\n </p>\n </div>\n <input\n type=\"checkbox\"\n aria-label={tr('channel.auditLogging', 'Audit Logging')}\n checked={settings.enableAuditLogging}\n onChange={(e) => updateSetting('enableAuditLogging', e.target.checked)}\n className=\"size-5 rounded border-gray-300 text-primary focus:ring-primary\"\n />\n </label>\n\n <div>\n <label className=\"text-sm font-medium block mb-1\">\n {tr('channel.sessionTimeout', 'Session Timeout (minutes)')}\n </label>\n <select\n value={settings.sessionTimeoutMinutes}\n onChange={(e) => updateSetting('sessionTimeoutMinutes', Number(e.target.value))}\n className=\"w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value={15}>15 minutes</option>\n <option value={30}>30 minutes</option>\n <option value={60}>1 hour</option>\n <option value={120}>2 hours</option>\n <option value={240}>4 hours</option>\n </select>\n </div>\n </div>\n </GlassCard>\n\n {/* Data Management */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam p-6 space-y-4\">\n <div className=\"flex items-center gap-2\">\n <Database className=\"size-5 text-primary\" />\n <h2 className=\"text-lg font-semibold\">\n {tr('channel.dataManagement', 'Data Management')}\n </h2>\n </div>\n\n <ShowMore\n collapsedHeight={96}\n label={tr('channel.showRetentionPolicies', 'Show retention policies')}\n >\n <div className=\"space-y-4\">\n <div className=\"p-4 rounded-lg border\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"font-medium\">\n {tr('channel.webhookEventRetention', 'Webhook Event Retention')}\n </p>\n <span className=\"text-sm text-text-secondary\">7 days</span>\n </div>\n <p className=\"text-sm text-text-secondary\">\n Incoming webhook events are automatically deleted after 7 days\n </p>\n </div>\n\n <div className=\"p-4 rounded-lg border\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"font-medium\">\n {tr('channel.deliveryAttemptRetention', 'Delivery Attempt Retention')}\n </p>\n <span className=\"text-sm text-text-secondary\">7 days</span>\n </div>\n <p className=\"text-sm text-text-secondary\">\n Successful delivery attempts are automatically deleted after 7 days\n </p>\n </div>\n\n <div className=\"p-4 rounded-lg border border-status-warning-border bg-status-warning-bg-subtle\">\n <div className=\"flex items-center justify-between mb-2\">\n <p className=\"font-medium\">\n {tr('channel.failedDeliveryRetention', 'Failed Delivery Retention')}\n </p>\n <span className=\"text-sm text-status-warning-text\">30 days</span>\n </div>\n <p className=\"text-sm text-text-secondary\">\n Failed delivery attempts are kept for 30 days for debugging\n </p>\n </div>\n </div>\n </ShowMore>\n </GlassCard>\n\n {/* Save Button */}\n <div className=\"flex justify-end pt-4\">\n <button\n type=\"button\"\n onClick={handleSave}\n disabled={isSaving || isLoadingSettings || !!settingsLoadError}\n className=\"px-6 py-3 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90 transition-colors disabled:opacity-50 flex items-center gap-2\"\n >\n {isSaving ? (\n <Loader2 className=\"size-5 animate-spin\" />\n ) : (\n <Save className=\"size-5 \" />\n )}\n {tr('channel.saveSettings', 'Save Settings')}\n </button>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|