@burdenoff/microfe-channel 2026.624.3 → 2026.625.1
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 +108 -98
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +287 -277
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +140 -131
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js +170 -173
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelsListPage.js +127 -120
- package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +161 -155
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js +70 -68
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateChannelPage.js +27 -24
- package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js +161 -156
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +179 -182
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditChannelPage.js +130 -122
- package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditConnectionPage.js +137 -132
- package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/SendMessagePage.js +87 -84
- package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js +193 -194
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
- package/package.json +2 -2
|
@@ -6,8 +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 { GlassCard as b, ShowMore as x } from "@burdenoff/fe-libs/ui";
|
|
9
10
|
//#region src/channel/modules/channels/pages/ChannelSettingsPage.tsx
|
|
10
|
-
var
|
|
11
|
+
var S = {
|
|
11
12
|
defaultPriority: 2,
|
|
12
13
|
rateLimitPerMinute: 100,
|
|
13
14
|
rateLimitPerHour: 5e3,
|
|
@@ -16,52 +17,52 @@ var b = {
|
|
|
16
17
|
enableWebhookVerification: !0,
|
|
17
18
|
sessionTimeoutMinutes: 30
|
|
18
19
|
};
|
|
19
|
-
function
|
|
20
|
-
let
|
|
21
|
-
let n =
|
|
20
|
+
function C() {
|
|
21
|
+
let C = _(), { t: w } = o(), T = (e, t) => {
|
|
22
|
+
let n = w(e);
|
|
22
23
|
return n === e ? t : n;
|
|
23
|
-
}, { settings:
|
|
24
|
+
}, { settings: E, isLoading: D, error: O, saveSettings: k } = n(), [A, j] = a(S), [M, N] = a(!1), [P, F] = a(!1), [I, L] = a(null), R = i(null);
|
|
24
25
|
r(() => {
|
|
25
|
-
let e =
|
|
26
|
+
let e = R;
|
|
26
27
|
return () => {
|
|
27
28
|
e.current && clearTimeout(e.current);
|
|
28
29
|
};
|
|
29
30
|
}, []);
|
|
30
|
-
let
|
|
31
|
+
let z = i(null);
|
|
31
32
|
r(() => {
|
|
32
|
-
if (!
|
|
33
|
-
let e = `${
|
|
34
|
-
e !==
|
|
35
|
-
|
|
36
|
-
defaultPriority:
|
|
37
|
-
rateLimitPerMinute:
|
|
38
|
-
rateLimitPerHour:
|
|
39
|
-
healthCheckIntervalMinutes:
|
|
40
|
-
enableAuditLogging:
|
|
41
|
-
enableWebhookVerification:
|
|
42
|
-
sessionTimeoutMinutes:
|
|
33
|
+
if (!E) return;
|
|
34
|
+
let e = `${E.organizationId}:${E.updatedAt}`;
|
|
35
|
+
e !== z.current && (z.current = e, Promise.resolve().then(() => {
|
|
36
|
+
j({
|
|
37
|
+
defaultPriority: E.defaultPriority,
|
|
38
|
+
rateLimitPerMinute: E.rateLimitPerMinute,
|
|
39
|
+
rateLimitPerHour: E.rateLimitPerHour,
|
|
40
|
+
healthCheckIntervalMinutes: E.healthCheckIntervalMinutes,
|
|
41
|
+
enableAuditLogging: E.enableAuditLogging,
|
|
42
|
+
enableWebhookVerification: E.enableWebhookVerification,
|
|
43
|
+
sessionTimeoutMinutes: E.sessionTimeoutMinutes
|
|
43
44
|
});
|
|
44
45
|
}));
|
|
45
|
-
}, [
|
|
46
|
-
let
|
|
47
|
-
|
|
46
|
+
}, [E]);
|
|
47
|
+
let B = async () => {
|
|
48
|
+
N(!0), L(null), F(!1);
|
|
48
49
|
try {
|
|
49
|
-
await
|
|
50
|
-
defaultPriority:
|
|
51
|
-
rateLimitPerMinute:
|
|
52
|
-
rateLimitPerHour:
|
|
53
|
-
healthCheckIntervalMinutes:
|
|
54
|
-
enableAuditLogging:
|
|
55
|
-
enableWebhookVerification:
|
|
56
|
-
sessionTimeoutMinutes:
|
|
57
|
-
}) ? (
|
|
50
|
+
await k({
|
|
51
|
+
defaultPriority: A.defaultPriority,
|
|
52
|
+
rateLimitPerMinute: A.rateLimitPerMinute,
|
|
53
|
+
rateLimitPerHour: A.rateLimitPerHour,
|
|
54
|
+
healthCheckIntervalMinutes: A.healthCheckIntervalMinutes,
|
|
55
|
+
enableAuditLogging: A.enableAuditLogging,
|
|
56
|
+
enableWebhookVerification: A.enableWebhookVerification,
|
|
57
|
+
sessionTimeoutMinutes: A.sessionTimeoutMinutes
|
|
58
|
+
}) ? (F(!0), R.current && clearTimeout(R.current), R.current = setTimeout(() => F(!1), 3e3)) : L(T("channel.settingsSaveNotPersisted", "Settings could not be saved — organization context is unavailable."));
|
|
58
59
|
} catch (e) {
|
|
59
|
-
|
|
60
|
+
L(e instanceof Error ? e.message : "Failed to save settings");
|
|
60
61
|
} finally {
|
|
61
|
-
|
|
62
|
+
N(!1);
|
|
62
63
|
}
|
|
63
|
-
},
|
|
64
|
-
|
|
64
|
+
}, V = (e, t) => {
|
|
65
|
+
j((n) => ({
|
|
65
66
|
...n,
|
|
66
67
|
[e]: t
|
|
67
68
|
}));
|
|
@@ -80,7 +81,7 @@ function x() {
|
|
|
80
81
|
className: "flex items-center gap-4",
|
|
81
82
|
children: [/* @__PURE__ */ v("button", {
|
|
82
83
|
type: "button",
|
|
83
|
-
onClick: () =>
|
|
84
|
+
onClick: () => C("./list"),
|
|
84
85
|
className: "p-2 hover:bg-bg-sunken rounded-lg transition-colors",
|
|
85
86
|
children: /* @__PURE__ */ v(c, { className: "size-5 " })
|
|
86
87
|
}), /* @__PURE__ */ y("div", {
|
|
@@ -90,10 +91,10 @@ function x() {
|
|
|
90
91
|
children: /* @__PURE__ */ v(h, { className: "size-5 text-primary" })
|
|
91
92
|
}), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h1", {
|
|
92
93
|
className: "text-2xl font-bold",
|
|
93
|
-
children:
|
|
94
|
+
children: T("channel.channelSettings", "Channel Settings")
|
|
94
95
|
}), /* @__PURE__ */ v("p", {
|
|
95
96
|
className: "text-sm text-text-secondary",
|
|
96
|
-
children:
|
|
97
|
+
children: T("channel.channelSettingsSubtitle", "Configure global channel preferences")
|
|
97
98
|
})] })]
|
|
98
99
|
})]
|
|
99
100
|
})
|
|
@@ -102,52 +103,53 @@ function x() {
|
|
|
102
103
|
}), /* @__PURE__ */ y("div", {
|
|
103
104
|
className: "container mx-auto px-6 py-8 max-w-2xl space-y-8",
|
|
104
105
|
children: [
|
|
105
|
-
|
|
106
|
+
D && /* @__PURE__ */ y("div", {
|
|
106
107
|
className: "flex items-center gap-2 text-text-secondary",
|
|
107
108
|
children: [/* @__PURE__ */ v(p, { className: "size-4 animate-spin" }), /* @__PURE__ */ v("span", {
|
|
108
109
|
className: "text-sm",
|
|
109
110
|
children: "Loading settings…"
|
|
110
111
|
})]
|
|
111
112
|
}),
|
|
112
|
-
|
|
113
|
+
O && /* @__PURE__ */ y("div", {
|
|
113
114
|
className: "p-4 rounded-lg bg-status-error-bg-subtle border border-status-error-border flex items-center gap-3",
|
|
114
115
|
children: [/* @__PURE__ */ v(s, { className: "size-5 text-status-error-text shrink-0" }), /* @__PURE__ */ v("p", {
|
|
115
116
|
className: "text-sm text-status-error-text",
|
|
116
|
-
children:
|
|
117
|
+
children: T("channel.settingsLoadError", "Failed to load settings — saving is disabled to prevent overwriting existing configuration.")
|
|
117
118
|
})]
|
|
118
119
|
}),
|
|
119
|
-
|
|
120
|
+
P && /* @__PURE__ */ y("div", {
|
|
120
121
|
className: "p-4 rounded-lg bg-status-success-bg-subtle border border-status-success-border flex items-center gap-3",
|
|
121
122
|
children: [/* @__PURE__ */ v(u, { className: "size-5 text-status-success-text shrink-0" }), /* @__PURE__ */ v("p", {
|
|
122
123
|
className: "text-sm text-status-success-text",
|
|
123
|
-
children:
|
|
124
|
+
children: T("channel.settingsSaved", "Settings saved successfully")
|
|
124
125
|
})]
|
|
125
126
|
}),
|
|
126
|
-
|
|
127
|
+
I && /* @__PURE__ */ y("div", {
|
|
127
128
|
className: "p-4 rounded-lg bg-status-error-bg/10 border border-status-error-border/20 flex items-center gap-3",
|
|
128
129
|
children: [/* @__PURE__ */ v(s, { className: "size-5 text-status-error-text shrink-0" }), /* @__PURE__ */ v("p", {
|
|
129
130
|
className: "text-sm text-status-error-text",
|
|
130
|
-
children:
|
|
131
|
+
children: I
|
|
131
132
|
})]
|
|
132
133
|
}),
|
|
133
|
-
/* @__PURE__ */ y(
|
|
134
|
-
|
|
134
|
+
/* @__PURE__ */ y(b, {
|
|
135
|
+
treatment: "plain",
|
|
136
|
+
className: "border-border-seam p-6 space-y-4",
|
|
135
137
|
children: [/* @__PURE__ */ y("div", {
|
|
136
138
|
className: "flex items-center gap-2",
|
|
137
139
|
children: [/* @__PURE__ */ v(l, { className: "size-5 text-primary" }), /* @__PURE__ */ v("h2", {
|
|
138
140
|
className: "text-lg font-semibold",
|
|
139
|
-
children:
|
|
141
|
+
children: T("channel.deliverySettings", "Delivery Settings")
|
|
140
142
|
})]
|
|
141
143
|
}), /* @__PURE__ */ y("div", {
|
|
142
144
|
className: "space-y-4",
|
|
143
145
|
children: [/* @__PURE__ */ y("div", { children: [
|
|
144
146
|
/* @__PURE__ */ v("label", {
|
|
145
147
|
className: "text-sm font-medium block mb-1",
|
|
146
|
-
children:
|
|
148
|
+
children: T("channel.defaultPriority", "Default Priority")
|
|
147
149
|
}),
|
|
148
150
|
/* @__PURE__ */ y("select", {
|
|
149
|
-
value:
|
|
150
|
-
onChange: (e) =>
|
|
151
|
+
value: A.defaultPriority,
|
|
152
|
+
onChange: (e) => V("defaultPriority", Number(e.target.value)),
|
|
151
153
|
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
|
|
152
154
|
children: [
|
|
153
155
|
/* @__PURE__ */ v("option", {
|
|
@@ -181,26 +183,26 @@ function x() {
|
|
|
181
183
|
children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
182
184
|
htmlFor: "channel-settings-rate-limit-min",
|
|
183
185
|
className: "text-sm font-medium block mb-1",
|
|
184
|
-
children:
|
|
186
|
+
children: T("channel.rateLimitPerMin", "Rate Limit (per minute)")
|
|
185
187
|
}), /* @__PURE__ */ v("input", {
|
|
186
188
|
id: "channel-settings-rate-limit-min",
|
|
187
|
-
"aria-label":
|
|
189
|
+
"aria-label": T("channel.rateLimitPerMin", "Rate Limit (per minute)"),
|
|
188
190
|
type: "number",
|
|
189
|
-
value:
|
|
190
|
-
onChange: (e) =>
|
|
191
|
+
value: A.rateLimitPerMinute,
|
|
192
|
+
onChange: (e) => V("rateLimitPerMinute", Number(e.target.value)),
|
|
191
193
|
min: 1,
|
|
192
194
|
max: 1e3,
|
|
193
195
|
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
|
|
194
196
|
})] }), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
195
197
|
htmlFor: "channel-settings-rate-limit-hour",
|
|
196
198
|
className: "text-sm font-medium block mb-1",
|
|
197
|
-
children:
|
|
199
|
+
children: T("channel.rateLimitPerHour", "Rate Limit (per hour)")
|
|
198
200
|
}), /* @__PURE__ */ v("input", {
|
|
199
201
|
id: "channel-settings-rate-limit-hour",
|
|
200
|
-
"aria-label":
|
|
202
|
+
"aria-label": T("channel.rateLimitPerHour", "Rate Limit (per hour)"),
|
|
201
203
|
type: "number",
|
|
202
|
-
value:
|
|
203
|
-
onChange: (e) =>
|
|
204
|
+
value: A.rateLimitPerHour,
|
|
205
|
+
onChange: (e) => V("rateLimitPerHour", Number(e.target.value)),
|
|
204
206
|
min: 1,
|
|
205
207
|
max: 5e4,
|
|
206
208
|
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary"
|
|
@@ -208,13 +210,14 @@ function x() {
|
|
|
208
210
|
})]
|
|
209
211
|
})]
|
|
210
212
|
}),
|
|
211
|
-
/* @__PURE__ */ y(
|
|
212
|
-
|
|
213
|
+
/* @__PURE__ */ y(b, {
|
|
214
|
+
treatment: "plain",
|
|
215
|
+
className: "border-border-seam p-6 space-y-4",
|
|
213
216
|
children: [/* @__PURE__ */ y("div", {
|
|
214
217
|
className: "flex items-center gap-2",
|
|
215
218
|
children: [/* @__PURE__ */ v(d, { className: "size-5 text-primary" }), /* @__PURE__ */ v("h2", {
|
|
216
219
|
className: "text-lg font-semibold",
|
|
217
|
-
children:
|
|
220
|
+
children: T("channel.healthMonitoring", "Health Monitoring")
|
|
218
221
|
})]
|
|
219
222
|
}), /* @__PURE__ */ y("div", { children: [
|
|
220
223
|
/* @__PURE__ */ v("label", {
|
|
@@ -224,8 +227,8 @@ function x() {
|
|
|
224
227
|
}),
|
|
225
228
|
/* @__PURE__ */ y("select", {
|
|
226
229
|
id: "channel-settings-health-interval",
|
|
227
|
-
value:
|
|
228
|
-
onChange: (e) =>
|
|
230
|
+
value: A.healthCheckIntervalMinutes,
|
|
231
|
+
onChange: (e) => V("healthCheckIntervalMinutes", Number(e.target.value)),
|
|
229
232
|
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
|
|
230
233
|
children: [
|
|
231
234
|
/* @__PURE__ */ v("option", {
|
|
@@ -260,13 +263,14 @@ function x() {
|
|
|
260
263
|
})
|
|
261
264
|
] })]
|
|
262
265
|
}),
|
|
263
|
-
/* @__PURE__ */ y(
|
|
264
|
-
|
|
266
|
+
/* @__PURE__ */ y(b, {
|
|
267
|
+
treatment: "plain",
|
|
268
|
+
className: "border-border-seam p-6 space-y-4",
|
|
265
269
|
children: [/* @__PURE__ */ y("div", {
|
|
266
270
|
className: "flex items-center gap-2",
|
|
267
271
|
children: [/* @__PURE__ */ v(g, { className: "size-5 text-primary" }), /* @__PURE__ */ v("h2", {
|
|
268
272
|
className: "text-lg font-semibold",
|
|
269
|
-
children:
|
|
273
|
+
children: T("channel.security", "Security")
|
|
270
274
|
})]
|
|
271
275
|
}), /* @__PURE__ */ y("div", {
|
|
272
276
|
className: "space-y-4",
|
|
@@ -275,15 +279,15 @@ function x() {
|
|
|
275
279
|
className: "flex items-center justify-between p-4 rounded-lg border hover:bg-bg-sunken/50 cursor-pointer",
|
|
276
280
|
children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
277
281
|
className: "font-medium",
|
|
278
|
-
children:
|
|
282
|
+
children: T("channel.webhookSignatureVerification", "Webhook Signature Verification")
|
|
279
283
|
}), /* @__PURE__ */ v("p", {
|
|
280
284
|
className: "text-sm text-text-secondary",
|
|
281
285
|
children: "Verify HMAC signatures on incoming webhooks"
|
|
282
286
|
})] }), /* @__PURE__ */ v("input", {
|
|
283
287
|
type: "checkbox",
|
|
284
|
-
"aria-label":
|
|
285
|
-
checked:
|
|
286
|
-
onChange: (e) =>
|
|
288
|
+
"aria-label": T("channel.webhookSignatureVerification", "Webhook Signature Verification"),
|
|
289
|
+
checked: A.enableWebhookVerification,
|
|
290
|
+
onChange: (e) => V("enableWebhookVerification", e.target.checked),
|
|
287
291
|
className: "size-5 rounded border-gray-300 text-primary focus:ring-primary"
|
|
288
292
|
})]
|
|
289
293
|
}),
|
|
@@ -291,24 +295,24 @@ function x() {
|
|
|
291
295
|
className: "flex items-center justify-between p-4 rounded-lg border hover:bg-bg-sunken/50 cursor-pointer",
|
|
292
296
|
children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
293
297
|
className: "font-medium",
|
|
294
|
-
children:
|
|
298
|
+
children: T("channel.auditLogging", "Audit Logging")
|
|
295
299
|
}), /* @__PURE__ */ v("p", {
|
|
296
300
|
className: "text-sm text-text-secondary",
|
|
297
301
|
children: "Log all channel operations for audit trail"
|
|
298
302
|
})] }), /* @__PURE__ */ v("input", {
|
|
299
303
|
type: "checkbox",
|
|
300
|
-
"aria-label":
|
|
301
|
-
checked:
|
|
302
|
-
onChange: (e) =>
|
|
304
|
+
"aria-label": T("channel.auditLogging", "Audit Logging"),
|
|
305
|
+
checked: A.enableAuditLogging,
|
|
306
|
+
onChange: (e) => V("enableAuditLogging", e.target.checked),
|
|
303
307
|
className: "size-5 rounded border-gray-300 text-primary focus:ring-primary"
|
|
304
308
|
})]
|
|
305
309
|
}),
|
|
306
310
|
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("label", {
|
|
307
311
|
className: "text-sm font-medium block mb-1",
|
|
308
|
-
children:
|
|
312
|
+
children: T("channel.sessionTimeout", "Session Timeout (minutes)")
|
|
309
313
|
}), /* @__PURE__ */ y("select", {
|
|
310
|
-
value:
|
|
311
|
-
onChange: (e) =>
|
|
314
|
+
value: A.sessionTimeoutMinutes,
|
|
315
|
+
onChange: (e) => V("sessionTimeoutMinutes", Number(e.target.value)),
|
|
312
316
|
className: "w-full px-4 py-2 rounded-lg border bg-bg-surface focus:outline-none focus:ring-2 focus:ring-primary",
|
|
313
317
|
children: [
|
|
314
318
|
/* @__PURE__ */ v("option", {
|
|
@@ -336,76 +340,81 @@ function x() {
|
|
|
336
340
|
]
|
|
337
341
|
})]
|
|
338
342
|
}),
|
|
339
|
-
/* @__PURE__ */ y(
|
|
340
|
-
|
|
343
|
+
/* @__PURE__ */ y(b, {
|
|
344
|
+
treatment: "plain",
|
|
345
|
+
className: "border-border-seam p-6 space-y-4",
|
|
341
346
|
children: [/* @__PURE__ */ y("div", {
|
|
342
347
|
className: "flex items-center gap-2",
|
|
343
348
|
children: [/* @__PURE__ */ v(f, { className: "size-5 text-primary" }), /* @__PURE__ */ v("h2", {
|
|
344
349
|
className: "text-lg font-semibold",
|
|
345
|
-
children:
|
|
350
|
+
children: T("channel.dataManagement", "Data Management")
|
|
346
351
|
})]
|
|
347
|
-
}), /* @__PURE__ */
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
352
|
+
}), /* @__PURE__ */ v(x, {
|
|
353
|
+
collapsedHeight: 96,
|
|
354
|
+
label: T("channel.showRetentionPolicies", "Show retention policies"),
|
|
355
|
+
children: /* @__PURE__ */ y("div", {
|
|
356
|
+
className: "space-y-4",
|
|
357
|
+
children: [
|
|
358
|
+
/* @__PURE__ */ y("div", {
|
|
359
|
+
className: "p-4 rounded-lg border",
|
|
360
|
+
children: [/* @__PURE__ */ y("div", {
|
|
361
|
+
className: "flex items-center justify-between mb-2",
|
|
362
|
+
children: [/* @__PURE__ */ v("p", {
|
|
363
|
+
className: "font-medium",
|
|
364
|
+
children: T("channel.webhookEventRetention", "Webhook Event Retention")
|
|
365
|
+
}), /* @__PURE__ */ v("span", {
|
|
366
|
+
className: "text-sm text-text-secondary",
|
|
367
|
+
children: "7 days"
|
|
368
|
+
})]
|
|
369
|
+
}), /* @__PURE__ */ v("p", {
|
|
358
370
|
className: "text-sm text-text-secondary",
|
|
359
|
-
children: "7 days"
|
|
371
|
+
children: "Incoming webhook events are automatically deleted after 7 days"
|
|
360
372
|
})]
|
|
361
|
-
}),
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}), /* @__PURE__ */ v("
|
|
373
|
+
}),
|
|
374
|
+
/* @__PURE__ */ y("div", {
|
|
375
|
+
className: "p-4 rounded-lg border",
|
|
376
|
+
children: [/* @__PURE__ */ y("div", {
|
|
377
|
+
className: "flex items-center justify-between mb-2",
|
|
378
|
+
children: [/* @__PURE__ */ v("p", {
|
|
379
|
+
className: "font-medium",
|
|
380
|
+
children: T("channel.deliveryAttemptRetention", "Delivery Attempt Retention")
|
|
381
|
+
}), /* @__PURE__ */ v("span", {
|
|
382
|
+
className: "text-sm text-text-secondary",
|
|
383
|
+
children: "7 days"
|
|
384
|
+
})]
|
|
385
|
+
}), /* @__PURE__ */ v("p", {
|
|
374
386
|
className: "text-sm text-text-secondary",
|
|
375
|
-
children: "7 days"
|
|
387
|
+
children: "Successful delivery attempts are automatically deleted after 7 days"
|
|
376
388
|
})]
|
|
377
|
-
}),
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}), /* @__PURE__ */ v("
|
|
390
|
-
className: "text-sm text-
|
|
391
|
-
children: "30 days"
|
|
389
|
+
}),
|
|
390
|
+
/* @__PURE__ */ y("div", {
|
|
391
|
+
className: "p-4 rounded-lg border border-status-warning-border bg-status-warning-bg-subtle",
|
|
392
|
+
children: [/* @__PURE__ */ y("div", {
|
|
393
|
+
className: "flex items-center justify-between mb-2",
|
|
394
|
+
children: [/* @__PURE__ */ v("p", {
|
|
395
|
+
className: "font-medium",
|
|
396
|
+
children: T("channel.failedDeliveryRetention", "Failed Delivery Retention")
|
|
397
|
+
}), /* @__PURE__ */ v("span", {
|
|
398
|
+
className: "text-sm text-status-warning-text",
|
|
399
|
+
children: "30 days"
|
|
400
|
+
})]
|
|
401
|
+
}), /* @__PURE__ */ v("p", {
|
|
402
|
+
className: "text-sm text-text-secondary",
|
|
403
|
+
children: "Failed delivery attempts are kept for 30 days for debugging"
|
|
392
404
|
})]
|
|
393
|
-
})
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
})]
|
|
397
|
-
})
|
|
398
|
-
]
|
|
405
|
+
})
|
|
406
|
+
]
|
|
407
|
+
})
|
|
399
408
|
})]
|
|
400
409
|
}),
|
|
401
410
|
/* @__PURE__ */ v("div", {
|
|
402
411
|
className: "flex justify-end pt-4",
|
|
403
412
|
children: /* @__PURE__ */ y("button", {
|
|
404
413
|
type: "button",
|
|
405
|
-
onClick:
|
|
406
|
-
disabled:
|
|
414
|
+
onClick: B,
|
|
415
|
+
disabled: M || D || !!O,
|
|
407
416
|
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",
|
|
408
|
-
children: [
|
|
417
|
+
children: [M ? /* @__PURE__ */ v(p, { className: "size-5 animate-spin" }) : /* @__PURE__ */ v(m, { className: "size-5 " }), T("channel.saveSettings", "Save Settings")]
|
|
409
418
|
})
|
|
410
419
|
})
|
|
411
420
|
]
|
|
@@ -414,6 +423,6 @@ function x() {
|
|
|
414
423
|
});
|
|
415
424
|
}
|
|
416
425
|
//#endregion
|
|
417
|
-
export {
|
|
426
|
+
export { C as ChannelSettingsPage };
|
|
418
427
|
|
|
419
428
|
//# 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 { 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 <div className=\"border rounded-lg 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 </div>\n\n {/* Health Check Settings */}\n <div className=\"border rounded-lg 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 </div>\n\n {/* Security Settings */}\n <div className=\"border rounded-lg 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 </div>\n\n {/* Data Management */}\n <div className=\"border rounded-lg 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 <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 </div>\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 { 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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"}
|