@burdenoff/microfe-channel 2026.608.1 → 2026.624.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/components/AdapterCredentialsForm.js +11 -11
- package/dist/channel/components/AdapterCredentialsForm.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +53 -53
- package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +78 -78
- package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +26 -26
- package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js +32 -32
- package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ChannelsListPage.js +41 -41
- package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +30 -30
- package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js +29 -29
- package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateChannelPage.js +28 -28
- package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js +8 -8
- package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +28 -28
- package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditChannelPage.js +25 -25
- package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/EditConnectionPage.js +4 -4
- package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
- package/dist/channel/modules/channels/pages/SendMessagePage.js +27 -27
- package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js +29 -29
- package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -63,7 +63,7 @@ var C = {
|
|
|
63
63
|
},
|
|
64
64
|
INACTIVE: {
|
|
65
65
|
label: "Inactive",
|
|
66
|
-
color: "text-
|
|
66
|
+
color: "text-text-secondary bg-bg-sunken",
|
|
67
67
|
icon: y
|
|
68
68
|
},
|
|
69
69
|
ERROR: {
|
|
@@ -78,7 +78,7 @@ var C = {
|
|
|
78
78
|
},
|
|
79
79
|
ARCHIVED: {
|
|
80
80
|
label: "Archived",
|
|
81
|
-
color: "text-
|
|
81
|
+
color: "text-text-secondary bg-bg-sunken",
|
|
82
82
|
icon: y
|
|
83
83
|
}
|
|
84
84
|
}, T = {
|
|
@@ -99,7 +99,7 @@ var C = {
|
|
|
99
99
|
},
|
|
100
100
|
UNKNOWN: {
|
|
101
101
|
label: "Unknown",
|
|
102
|
-
color: "text-
|
|
102
|
+
color: "text-text-secondary",
|
|
103
103
|
icon: g
|
|
104
104
|
}
|
|
105
105
|
};
|
|
@@ -245,10 +245,10 @@ function _e() {
|
|
|
245
245
|
/* @__PURE__ */ S("div", {
|
|
246
246
|
className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
|
247
247
|
children: [/* @__PURE__ */ S("div", { children: [/* @__PURE__ */ x("h1", {
|
|
248
|
-
className: "text-xl sm:text-2xl font-bold text-
|
|
248
|
+
className: "text-xl sm:text-2xl font-bold text-text-primary",
|
|
249
249
|
children: E("channel.channels", "Channels")
|
|
250
250
|
}), /* @__PURE__ */ S("p", {
|
|
251
|
-
className: "text-sm text-
|
|
251
|
+
className: "text-sm text-text-secondary mt-1",
|
|
252
252
|
children: [
|
|
253
253
|
E("channel.manageChannels", "Manage your communication channels"),
|
|
254
254
|
" (",
|
|
@@ -263,7 +263,7 @@ function _e() {
|
|
|
263
263
|
type: "button",
|
|
264
264
|
onClick: () => void xe(),
|
|
265
265
|
disabled: N,
|
|
266
|
-
className: "flex items-center gap-2 px-4 py-2 text-sm font-medium text-fg border border-border hover:bg-bg-elevated rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
266
|
+
className: "flex items-center gap-2 px-4 py-2 text-sm font-medium text-fg border border-border-subtle hover:bg-bg-elevated rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
|
|
267
267
|
title: E("channel.exportCSV", "Export to CSV"),
|
|
268
268
|
"data-tour": "channels-list-export-button",
|
|
269
269
|
children: [N ? /* @__PURE__ */ x(_, {
|
|
@@ -286,20 +286,20 @@ function _e() {
|
|
|
286
286
|
className: "relative flex-1",
|
|
287
287
|
children: [/* @__PURE__ */ x(fe, {
|
|
288
288
|
size: 16,
|
|
289
|
-
className: "absolute left-3 top-1/2 -translate-y-1/2 text-
|
|
289
|
+
className: "absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary"
|
|
290
290
|
}), /* @__PURE__ */ x("input", {
|
|
291
291
|
"aria-label": E("channel.searchChannels", "Search channels..."),
|
|
292
292
|
type: "text",
|
|
293
293
|
placeholder: E("channel.searchChannels", "Search channels..."),
|
|
294
294
|
value: V.search || "",
|
|
295
295
|
onChange: (e) => H({ search: e.target.value }),
|
|
296
|
-
className: "w-full pl-9 pr-4 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg"
|
|
296
|
+
className: "w-full pl-9 pr-4 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg"
|
|
297
297
|
})]
|
|
298
298
|
}),
|
|
299
299
|
/* @__PURE__ */ S("select", {
|
|
300
300
|
value: V.platform || "",
|
|
301
301
|
onChange: (e) => H({ platform: e.target.value || void 0 }),
|
|
302
|
-
className: "px-3 py-2 text-sm bg-bg-surface text-text-primary border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg",
|
|
302
|
+
className: "px-3 py-2 text-sm bg-bg-surface text-text-primary border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg",
|
|
303
303
|
children: [/* @__PURE__ */ x("option", {
|
|
304
304
|
value: "",
|
|
305
305
|
children: E("channel.allPlatforms", "All Platforms")
|
|
@@ -311,7 +311,7 @@ function _e() {
|
|
|
311
311
|
/* @__PURE__ */ S("select", {
|
|
312
312
|
value: V.status || "",
|
|
313
313
|
onChange: (e) => H({ status: e.target.value || void 0 }),
|
|
314
|
-
className: "px-3 py-2 text-sm bg-bg-surface text-text-primary border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg",
|
|
314
|
+
className: "px-3 py-2 text-sm bg-bg-surface text-text-primary border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg",
|
|
315
315
|
children: [/* @__PURE__ */ x("option", {
|
|
316
316
|
value: "",
|
|
317
317
|
children: E("channel.allStatus", "All Status")
|
|
@@ -333,10 +333,10 @@ function _e() {
|
|
|
333
333
|
})]
|
|
334
334
|
}),
|
|
335
335
|
R.length > 0 && /* @__PURE__ */ S("div", {
|
|
336
|
-
className: "flex flex-col gap-3 rounded-lg border border-border bg-surface p-4 sm:flex-row sm:items-center sm:justify-between",
|
|
336
|
+
className: "flex flex-col gap-3 rounded-lg border border-border-subtle bg-surface p-4 sm:flex-row sm:items-center sm:justify-between",
|
|
337
337
|
"data-tour": "channels-list-bulk-actions",
|
|
338
338
|
children: [/* @__PURE__ */ S("label", {
|
|
339
|
-
className: "flex items-center gap-2 text-sm text-
|
|
339
|
+
className: "flex items-center gap-2 text-sm text-text-primary",
|
|
340
340
|
children: [/* @__PURE__ */ x("input", {
|
|
341
341
|
type: "checkbox",
|
|
342
342
|
checked: $,
|
|
@@ -345,13 +345,13 @@ function _e() {
|
|
|
345
345
|
},
|
|
346
346
|
onChange: (e) => Oe(e.target.checked),
|
|
347
347
|
"aria-label": "Select all channels",
|
|
348
|
-
className: "size-4 rounded border-border text-primary"
|
|
348
|
+
className: "size-4 rounded border-border-subtle text-primary"
|
|
349
349
|
}), /* @__PURE__ */ x("span", { children: E("channel.selectAllVisible", "Select all visible") })]
|
|
350
350
|
}), /* @__PURE__ */ S("div", {
|
|
351
351
|
className: "flex flex-wrap items-center gap-2",
|
|
352
352
|
children: [
|
|
353
353
|
/* @__PURE__ */ S("span", {
|
|
354
|
-
className: "text-sm text-
|
|
354
|
+
className: "text-sm text-text-secondary",
|
|
355
355
|
children: [
|
|
356
356
|
k.size,
|
|
357
357
|
" ",
|
|
@@ -361,7 +361,7 @@ function _e() {
|
|
|
361
361
|
k.size > 0 && /* @__PURE__ */ x("button", {
|
|
362
362
|
type: "button",
|
|
363
363
|
onClick: () => A(/* @__PURE__ */ new Set()),
|
|
364
|
-
className: "text-sm text-
|
|
364
|
+
className: "text-sm text-text-secondary hover:text-text-primary",
|
|
365
365
|
children: E("channel.clearSelection", "Clear selection")
|
|
366
366
|
}),
|
|
367
367
|
/* @__PURE__ */ x(s, {
|
|
@@ -370,7 +370,7 @@ function _e() {
|
|
|
370
370
|
type: "button",
|
|
371
371
|
onClick: Me,
|
|
372
372
|
disabled: X.length === 0 || j !== null,
|
|
373
|
-
className: "px-3 py-2 text-sm font-medium border border-border rounded-md hover:bg-
|
|
373
|
+
className: "px-3 py-2 text-sm font-medium border border-border-subtle rounded-md hover:bg-bg-sunken transition-colors disabled:opacity-50",
|
|
374
374
|
children: [E("channel.healthCheck", "Health Check"), X.length > 0 ? ` (${X.length})` : ""]
|
|
375
375
|
})
|
|
376
376
|
}),
|
|
@@ -441,14 +441,14 @@ function _e() {
|
|
|
441
441
|
children: [
|
|
442
442
|
/* @__PURE__ */ x(ue, {
|
|
443
443
|
size: 48,
|
|
444
|
-
className: "text-
|
|
444
|
+
className: "text-text-secondary mb-4"
|
|
445
445
|
}),
|
|
446
446
|
/* @__PURE__ */ x("h3", {
|
|
447
|
-
className: "text-lg font-semibold text-
|
|
447
|
+
className: "text-lg font-semibold text-text-primary mb-2",
|
|
448
448
|
children: E("channel.noChannelsFound", "No channels found")
|
|
449
449
|
}),
|
|
450
450
|
/* @__PURE__ */ x("p", {
|
|
451
|
-
className: "text-sm text-
|
|
451
|
+
className: "text-sm text-text-secondary max-w-md mb-4",
|
|
452
452
|
children: V.search || V.platform || V.status ? E("channel.adjustFilters", "Try adjusting your filters to see more channels.") : E("channel.createFirstChannelPrompt", "Create your first communication channel to get started.")
|
|
453
453
|
}),
|
|
454
454
|
g && !V.search && !V.platform && !V.status && /* @__PURE__ */ S("button", {
|
|
@@ -465,7 +465,7 @@ function _e() {
|
|
|
465
465
|
children: R.map((e) => {
|
|
466
466
|
let t = Ne(e.platform), n = Pe(e.status), r = Fe(e.connectionHealth), i = n.icon, a = r.icon, o = ye === e.id;
|
|
467
467
|
return /* @__PURE__ */ S("div", {
|
|
468
|
-
className: `p-4 bg-surface border rounded-lg transition-colors group ${k.has(e.id) ? "ring-2 ring-action-primary-bg border-action-primary-bg/40" : "border-border hover:border-action-primary-bg/40"}`,
|
|
468
|
+
className: `p-4 bg-surface border rounded-lg transition-colors group ${k.has(e.id) ? "ring-2 ring-action-primary-bg border-action-primary-bg/40" : "border-border-subtle hover:border-action-primary-bg/40"}`,
|
|
469
469
|
children: [
|
|
470
470
|
/* @__PURE__ */ S("div", {
|
|
471
471
|
className: "flex items-start justify-between mb-3",
|
|
@@ -477,7 +477,7 @@ function _e() {
|
|
|
477
477
|
checked: k.has(e.id),
|
|
478
478
|
onChange: (t) => De(e.id, t.target.checked),
|
|
479
479
|
"aria-label": `Select channel ${e.name}`,
|
|
480
|
-
className: "size-4 rounded border-border text-primary"
|
|
480
|
+
className: "size-4 rounded border-border-subtle text-primary"
|
|
481
481
|
}),
|
|
482
482
|
/* @__PURE__ */ x(ue, {
|
|
483
483
|
size: 20,
|
|
@@ -509,19 +509,19 @@ function _e() {
|
|
|
509
509
|
className: "cursor-pointer",
|
|
510
510
|
children: [
|
|
511
511
|
/* @__PURE__ */ x("h3", {
|
|
512
|
-
className: "text-sm font-semibold text-
|
|
512
|
+
className: "text-sm font-semibold text-text-primary mb-1 truncate",
|
|
513
513
|
children: e.name
|
|
514
514
|
}),
|
|
515
515
|
e.description && /* @__PURE__ */ x("p", {
|
|
516
|
-
className: "text-xs text-
|
|
516
|
+
className: "text-xs text-text-secondary mb-2 line-clamp-2",
|
|
517
517
|
children: e.description
|
|
518
518
|
}),
|
|
519
519
|
e.adapterType && /* @__PURE__ */ x("p", {
|
|
520
|
-
className: "text-xs text-
|
|
520
|
+
className: "text-xs text-text-secondary mb-2 font-mono",
|
|
521
521
|
children: e.adapterType.replace(/_/g, " ")
|
|
522
522
|
}),
|
|
523
523
|
/* @__PURE__ */ x("div", {
|
|
524
|
-
className: "flex items-center gap-3 text-xs text-
|
|
524
|
+
className: "flex items-center gap-3 text-xs text-text-secondary mb-2",
|
|
525
525
|
children: /* @__PURE__ */ S("span", { children: [
|
|
526
526
|
E("channel.priority", "Priority"),
|
|
527
527
|
": ",
|
|
@@ -543,7 +543,7 @@ function _e() {
|
|
|
543
543
|
}),
|
|
544
544
|
/* @__PURE__ */ x("span", { children: "•" }),
|
|
545
545
|
/* @__PURE__ */ x("span", {
|
|
546
|
-
className: e.isConnected ? "text-status-success-text" : "text-
|
|
546
|
+
className: e.isConnected ? "text-status-success-text" : "text-text-secondary",
|
|
547
547
|
children: e.isConnected ? E("channel.connected", "Connected") : E("channel.notConnected", "Not Connected")
|
|
548
548
|
})
|
|
549
549
|
]
|
|
@@ -558,26 +558,26 @@ function _e() {
|
|
|
558
558
|
label: e.tagId,
|
|
559
559
|
size: "sm"
|
|
560
560
|
}, e.id)), e.tagAssignments.length > 4 && /* @__PURE__ */ S("span", {
|
|
561
|
-
className: "text-xs text-
|
|
561
|
+
className: "text-xs text-text-secondary",
|
|
562
562
|
children: ["+", e.tagAssignments.length - 4]
|
|
563
563
|
})]
|
|
564
564
|
}),
|
|
565
565
|
/* @__PURE__ */ S("div", {
|
|
566
|
-
className: "mt-3 pt-3 border-t border-border flex items-center justify-between opacity-0 group-hover:opacity-100 transition-opacity",
|
|
566
|
+
className: "mt-3 pt-3 border-t border-border-subtle flex items-center justify-between opacity-0 group-hover:opacity-100 transition-opacity",
|
|
567
567
|
children: [/* @__PURE__ */ S("div", {
|
|
568
568
|
className: "flex items-center gap-1",
|
|
569
569
|
children: [
|
|
570
570
|
/* @__PURE__ */ x("button", {
|
|
571
571
|
type: "button",
|
|
572
572
|
onClick: () => m(`/channels/${e.id}`),
|
|
573
|
-
className: "p-1.5 text-
|
|
573
|
+
className: "p-1.5 text-text-secondary hover:text-accent-primary hover:bg-accent-primary/10 rounded transition-colors cursor-pointer",
|
|
574
574
|
title: E("channel.viewDetails", "View Details"),
|
|
575
575
|
children: /* @__PURE__ */ x(se, { size: 14 })
|
|
576
576
|
}),
|
|
577
577
|
b && /* @__PURE__ */ x("button", {
|
|
578
578
|
type: "button",
|
|
579
579
|
onClick: () => m(`/channels/${e.id}/edit`),
|
|
580
|
-
className: "p-1.5 text-
|
|
580
|
+
className: "p-1.5 text-text-secondary hover:text-accent-primary hover:bg-accent-primary/10 rounded transition-colors cursor-pointer",
|
|
581
581
|
title: E("channel.editChannel", "Edit Channel"),
|
|
582
582
|
children: /* @__PURE__ */ x(ce, { size: 14 })
|
|
583
583
|
}),
|
|
@@ -587,7 +587,7 @@ function _e() {
|
|
|
587
587
|
type: "button",
|
|
588
588
|
onClick: () => Te(e.id),
|
|
589
589
|
disabled: o,
|
|
590
|
-
className: "p-1.5 text-
|
|
590
|
+
className: "p-1.5 text-text-secondary hover:text-status-info-text hover:bg-status-info-bg-subtle rounded transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed",
|
|
591
591
|
title: E("channel.runHealthCheck", "Run Health Check"),
|
|
592
592
|
children: o ? /* @__PURE__ */ x(_, {
|
|
593
593
|
size: 14,
|
|
@@ -601,7 +601,7 @@ function _e() {
|
|
|
601
601
|
type: "button",
|
|
602
602
|
onClick: () => we(e),
|
|
603
603
|
disabled: o,
|
|
604
|
-
className: `p-1.5 rounded transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed ${e.status === "ACTIVE" ? "text-
|
|
604
|
+
className: `p-1.5 rounded transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed ${e.status === "ACTIVE" ? "text-text-secondary hover:text-status-warning-text hover:bg-status-warning-bg-subtle" : "text-text-secondary hover:text-status-success-text hover:bg-status-success-bg-subtle"}`,
|
|
605
605
|
title: e.status === "ACTIVE" ? E("channel.deactivate", "Deactivate") : E("channel.activate", "Activate"),
|
|
606
606
|
children: e.status === "ACTIVE" ? /* @__PURE__ */ x(y, { size: 14 }) : /* @__PURE__ */ x(le, { size: 14 })
|
|
607
607
|
})
|
|
@@ -611,7 +611,7 @@ function _e() {
|
|
|
611
611
|
className: "flex items-center gap-2",
|
|
612
612
|
children: [
|
|
613
613
|
/* @__PURE__ */ x("span", {
|
|
614
|
-
className: "text-xs text-
|
|
614
|
+
className: "text-xs text-text-secondary",
|
|
615
615
|
children: E("channel.deleteQuestion", "Delete?")
|
|
616
616
|
}),
|
|
617
617
|
/* @__PURE__ */ x("button", {
|
|
@@ -624,7 +624,7 @@ function _e() {
|
|
|
624
624
|
/* @__PURE__ */ x("button", {
|
|
625
625
|
type: "button",
|
|
626
626
|
onClick: () => D(null),
|
|
627
|
-
className: "px-2 py-1 text-xs text-
|
|
627
|
+
className: "px-2 py-1 text-xs text-text-secondary hover:bg-bg-sunken rounded transition-colors cursor-pointer",
|
|
628
628
|
children: E("channel.no", "No")
|
|
629
629
|
})
|
|
630
630
|
]
|
|
@@ -633,7 +633,7 @@ function _e() {
|
|
|
633
633
|
children: /* @__PURE__ */ x("button", {
|
|
634
634
|
type: "button",
|
|
635
635
|
onClick: () => D(e.id),
|
|
636
|
-
className: "p-1.5 text-
|
|
636
|
+
className: "p-1.5 text-text-secondary hover:text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors cursor-pointer",
|
|
637
637
|
title: E("channel.deleteChannel", "Delete Channel"),
|
|
638
638
|
children: /* @__PURE__ */ x(pe, { size: 14 })
|
|
639
639
|
})
|
|
@@ -646,12 +646,12 @@ function _e() {
|
|
|
646
646
|
B > F && /* @__PURE__ */ S("div", {
|
|
647
647
|
className: "flex items-center justify-between px-1 py-2",
|
|
648
648
|
children: [/* @__PURE__ */ S("p", {
|
|
649
|
-
className: "text-sm text-
|
|
649
|
+
className: "text-sm text-text-secondary",
|
|
650
650
|
children: [
|
|
651
651
|
E("channel.showing", "Showing"),
|
|
652
652
|
" ",
|
|
653
653
|
/* @__PURE__ */ S("span", {
|
|
654
|
-
className: "font-medium text-
|
|
654
|
+
className: "font-medium text-text-primary",
|
|
655
655
|
children: [
|
|
656
656
|
I * F + 1,
|
|
657
657
|
"–",
|
|
@@ -662,7 +662,7 @@ function _e() {
|
|
|
662
662
|
E("channel.of", "of"),
|
|
663
663
|
" ",
|
|
664
664
|
/* @__PURE__ */ x("span", {
|
|
665
|
-
className: "font-medium text-
|
|
665
|
+
className: "font-medium text-text-primary",
|
|
666
666
|
children: B
|
|
667
667
|
}),
|
|
668
668
|
" ",
|
|
@@ -675,11 +675,11 @@ function _e() {
|
|
|
675
675
|
type: "button",
|
|
676
676
|
onClick: () => L((e) => Math.max(0, e - 1)),
|
|
677
677
|
disabled: I === 0,
|
|
678
|
-
className: "px-3 py-1.5 text-sm border border-border rounded-lg hover:bg-
|
|
678
|
+
className: "px-3 py-1.5 text-sm border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
|
|
679
679
|
children: E("channel.previous", "Previous")
|
|
680
680
|
}),
|
|
681
681
|
/* @__PURE__ */ S("span", {
|
|
682
|
-
className: "text-sm text-
|
|
682
|
+
className: "text-sm text-text-secondary",
|
|
683
683
|
children: [
|
|
684
684
|
I + 1,
|
|
685
685
|
" / ",
|
|
@@ -690,7 +690,7 @@ function _e() {
|
|
|
690
690
|
type: "button",
|
|
691
691
|
onClick: () => L((e) => Math.min(J - 1, e + 1)),
|
|
692
692
|
disabled: I >= J - 1,
|
|
693
|
-
className: "px-3 py-1.5 text-sm border border-border rounded-lg hover:bg-
|
|
693
|
+
className: "px-3 py-1.5 text-sm border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed",
|
|
694
694
|
children: E("channel.next", "Next")
|
|
695
695
|
})
|
|
696
696
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChannelsListPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ChannelsListPage.tsx"],"sourcesContent":["/**\n * Channels List Page\n *\n * Displays a list of all channels with CRUD operations\n */\n\nimport { useEffect, useMemo, useState, useRef } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport { reportShellError, subscribeShellEvent } from '../../../utils/shellBridge';\nimport {\n Radio,\n Plus,\n Search,\n Loader2,\n AlertCircle,\n Trash2,\n RefreshCw,\n Power,\n PowerOff,\n Eye,\n Pencil,\n CheckCircle2,\n XCircle,\n AlertTriangle,\n HelpCircle,\n Download,\n} from 'lucide-react';\nimport {\n useChannels,\n type Channel,\n type ChannelPlatform,\n type ChannelStatus,\n type ConnectionHealth,\n} from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { HEALTH_I18N, PLATFORM_I18N, STATUS_I18N, translateLabel } from '../i18n';\nimport { useExportChannels } from '../hooks/useExport';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\n\n// Platform display configuration\nconst PLATFORM_CONFIG: Record<ChannelPlatform, { label: string; color: string }> = {\n EMAIL: { label: 'Email', color: 'bg-accent-blue-subtle text-accent-blue' },\n SMS: { label: 'SMS', color: 'bg-status-success-bg-subtle text-status-success-text' },\n PUSH: { label: 'Push', color: 'bg-accent-purple-subtle text-accent-purple' },\n INAPP: { label: 'In-App', color: 'bg-accent-indigo-subtle text-accent-indigo' },\n WEBHOOK: { label: 'Webhook', color: 'bg-accent-orange-subtle text-accent-orange' },\n SLACK: { label: 'Slack', color: 'bg-accent-pink-subtle text-accent-pink' },\n TEAMS: { label: 'Teams', color: 'bg-accent-cyan-subtle text-accent-cyan' },\n DISCORD: { label: 'Discord', color: 'bg-accent-violet-subtle text-accent-violet' },\n WHATSAPP: { label: 'WhatsApp', color: 'bg-accent-emerald-subtle text-accent-emerald' },\n WEBCHAT: { label: 'Webchat', color: 'bg-accent-teal-subtle text-accent-teal' },\n};\n\n// Status display configuration\nconst STATUS_CONFIG: Record<\n ChannelStatus,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n ACTIVE: {\n label: 'Active',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n INACTIVE: { label: 'Inactive', color: 'text-muted-foreground bg-muted', icon: PowerOff },\n ERROR: {\n label: 'Error',\n color: 'text-status-error-text bg-status-error-bg-subtle',\n icon: XCircle,\n },\n SUSPENDED: {\n label: 'Suspended',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: AlertTriangle,\n },\n ARCHIVED: { label: 'Archived', color: 'text-muted-foreground bg-muted', icon: PowerOff },\n};\n\n// Health display configuration\nconst HEALTH_CONFIG: Record<\n ConnectionHealth,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n HEALTHY: { label: 'Healthy', color: 'text-status-success-text', icon: CheckCircle2 },\n DEGRADED: { label: 'Degraded', color: 'text-status-warning-text', icon: AlertTriangle },\n UNHEALTHY: { label: 'Unhealthy', color: 'text-status-error-text', icon: XCircle },\n UNKNOWN: { label: 'Unknown', color: 'text-muted-foreground', icon: HelpCircle },\n};\n\nexport function ChannelsListPage() {\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n // Channel creation is an admin-only action (channel:create). Hide the\n // affordance for users without it (per MICROFRONTEND-SPEC: {canCreateChannel && ...}).\n const canCreate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_CREATE);\n const canUpdate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_UPDATE);\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 const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);\n const [actionLoading, setActionLoading] = useState<string | null>(null);\n const [selectedChannelIds, setSelectedChannelIds] = useState<Set<string>>(new Set());\n const [bulkActionLoading, setBulkActionLoading] = useState<string | null>(null);\n\n const { organizationId } = useChannelContext();\n const {\n triggerExport: exportChannels,\n isExporting: isExportingChannels,\n exportError: exportChannelsError,\n } = useExportChannels(organizationId ?? undefined);\n const [pageSize] = useState(12);\n const [currentPage, setCurrentPage] = useState(0);\n\n const {\n channels,\n isLoading,\n error,\n total,\n filter,\n updateFilter,\n deleteChannel,\n activateChannel,\n deactivateChannel,\n runHealthCheck,\n refetch,\n } = useChannels();\n\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n\n const refetchRef = useRef(refetch);\n useEffect(() => {\n refetchRef.current = refetch;\n });\n\n // Fetch the current page whenever page or page size changes\n useEffect(() => {\n void refetchRef.current({ offset: currentPage * pageSize, limit: pageSize });\n }, [currentPage, pageSize]);\n\n // Reset to page 0 when filter changes\n useEffect(() => {\n void Promise.resolve().then(() => setCurrentPage(0));\n }, [filter.search, filter.platform, filter.status]);\n\n // Cross-MFE refresh: refetch the list whenever channel/connection events\n // are emitted by other tabs or microfrontends.\n useEffect(() => {\n const events = [\n 'channel.created',\n 'channel.updated',\n 'channel.deleted',\n 'connection.created',\n 'connection.updated',\n 'connection.deleted',\n ];\n const unsubs = events.map((name) => subscribeShellEvent(name, () => void refetch()));\n return () => unsubs.forEach((u) => u());\n }, [refetch]);\n\n const handleDelete = async (id: string) => {\n void nativeImpact('medium');\n setActionLoading(id);\n try {\n await deleteChannel(id);\n void nativeNotify('success');\n setDeleteConfirm(null);\n } catch (err) {\n void nativeNotify('error');\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to delete channel' } },\n '[channel] Failed to delete channel'\n );\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleToggleStatus = async (channel: Channel) => {\n setActionLoading(channel.id);\n try {\n if (channel.status === 'ACTIVE') {\n await deactivateChannel(channel.id);\n } else {\n await activateChannel(channel.id);\n }\n } catch (err) {\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to toggle channel status' } },\n '[channel] Failed to toggle channel status'\n );\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleHealthCheck = async (id: string) => {\n setActionLoading(id);\n try {\n await runHealthCheck(id);\n } catch (err) {\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to run health check' } },\n '[channel] Failed to run health check'\n );\n } finally {\n setActionLoading(null);\n }\n };\n\n const selectedChannels = useMemo(\n () => channels.filter((channel) => selectedChannelIds.has(channel.id)),\n [channels, selectedChannelIds]\n );\n const selectedActiveChannels = useMemo(\n () => selectedChannels.filter((channel) => channel.status === 'ACTIVE'),\n [selectedChannels]\n );\n const selectedInactiveChannels = useMemo(\n () => selectedChannels.filter((channel) => channel.status !== 'ACTIVE'),\n [selectedChannels]\n );\n const allVisibleSelected =\n channels.length > 0 && channels.every((channel) => selectedChannelIds.has(channel.id));\n const partiallySelected =\n channels.some((channel) => selectedChannelIds.has(channel.id)) && !allVisibleSelected;\n\n const toggleChannelSelection = (channelId: string, checked: boolean) => {\n setSelectedChannelIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(channelId);\n } else {\n next.delete(channelId);\n }\n return next;\n });\n };\n\n const toggleAllChannels = (checked: boolean) => {\n setSelectedChannelIds((prev) => {\n const next = new Set(prev);\n channels.forEach((channel) => {\n if (checked) {\n next.add(channel.id);\n } else {\n next.delete(channel.id);\n }\n });\n return next;\n });\n };\n\n const handleBulkDelete = async () => {\n if (selectedChannels.length === 0) return;\n void nativeImpact('medium');\n const message = tr(\n selectedChannels.length === 1\n ? 'channel.bulkDeleteConfirmSingle'\n : 'channel.bulkDeleteConfirmMultiple',\n selectedChannels.length === 1\n ? 'Delete 1 selected channel?'\n : `Delete ${selectedChannels.length} selected channels?`\n );\n const native = await nativeConfirm({\n title: 'Delete channels',\n message,\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const confirmed = native === null ? window.confirm(message) : native;\n if (!confirmed) return;\n\n setBulkActionLoading('delete');\n try {\n await Promise.allSettled(selectedChannels.map((channel) => deleteChannel(channel.id)));\n setSelectedChannelIds(new Set());\n } finally {\n setBulkActionLoading(null);\n }\n };\n\n const handleBulkActivate = async () => {\n if (selectedInactiveChannels.length === 0) return;\n setBulkActionLoading('activate');\n try {\n await Promise.allSettled(\n selectedInactiveChannels.map((channel) => activateChannel(channel.id))\n );\n setSelectedChannelIds(new Set());\n } finally {\n setBulkActionLoading(null);\n }\n };\n\n const handleBulkDeactivate = async () => {\n if (selectedActiveChannels.length === 0) return;\n setBulkActionLoading('deactivate');\n try {\n await Promise.allSettled(\n selectedActiveChannels.map((channel) => deactivateChannel(channel.id))\n );\n setSelectedChannelIds(new Set());\n } finally {\n setBulkActionLoading(null);\n }\n };\n\n const handleBulkHealthCheck = async () => {\n if (selectedChannels.length === 0) return;\n setBulkActionLoading('health');\n try {\n await Promise.allSettled(selectedChannels.map((channel) => runHealthCheck(channel.id)));\n } finally {\n setBulkActionLoading(null);\n }\n };\n\n const getPlatformConfig = (platform: ChannelPlatform) => {\n const config = PLATFORM_CONFIG[platform] || {\n label: platform,\n color: 'bg-bg-sunken text-text-secondary',\n };\n return {\n ...config,\n label: translateLabel(platform, PLATFORM_I18N, tr),\n };\n };\n\n const getStatusConfig = (status: ChannelStatus) => {\n const config = STATUS_CONFIG[status] || STATUS_CONFIG.INACTIVE;\n return {\n ...config,\n label: translateLabel(status, STATUS_I18N, tr),\n };\n };\n\n const getHealthConfig = (health: ConnectionHealth) => {\n const config = HEALTH_CONFIG[health] || HEALTH_CONFIG.UNKNOWN;\n return {\n ...config,\n label: translateLabel(health, HEALTH_I18N, tr),\n };\n };\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_READ}>\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold text-foreground\">\n {tr('channel.channels', 'Channels')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('channel.manageChannels', 'Manage your communication channels')} ({total} total)\n </p>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXPORT}>\n <button\n type=\"button\"\n onClick={() => void exportChannels()}\n disabled={isExportingChannels}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-fg border border-border hover:bg-bg-elevated rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n title={tr('channel.exportCSV', 'Export to CSV')}\n data-tour=\"channels-list-export-button\"\n >\n {isExportingChannels ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Download size={16} />\n )}\n {tr('channel.export', 'Export')}\n </button>\n </PermissionGuard>\n {canCreate && (\n <button\n type=\"button\"\n onClick={() => navigate('/channels/create')}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors\"\n >\n <Plus size={16} />\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n )}\n </div>\n </div>\n\n {/* Search and Filters */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n />\n <input\n aria-label={tr('channel.searchChannels', 'Search channels...')}\n type=\"text\"\n placeholder={tr('channel.searchChannels', 'Search channels...')}\n value={filter.search || ''}\n onChange={(e) => updateFilter({ search: e.target.value })}\n className=\"w-full pl-9 pr-4 py-2 text-sm bg-surface border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n />\n </div>\n\n <select\n value={filter.platform || ''}\n onChange={(e) =>\n updateFilter({ platform: (e.target.value as ChannelPlatform) || undefined })\n }\n className=\"px-3 py-2 text-sm bg-bg-surface text-text-primary border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">{tr('channel.allPlatforms', 'All Platforms')}</option>\n {Object.entries(PLATFORM_CONFIG).map(([key, config]) => (\n <option key={key} value={key}>\n {config.label}\n </option>\n ))}\n </select>\n\n <select\n value={filter.status || ''}\n onChange={(e) =>\n updateFilter({ status: (e.target.value as ChannelStatus) || undefined })\n }\n className=\"px-3 py-2 text-sm bg-bg-surface text-text-primary border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">{tr('channel.allStatus', 'All Status')}</option>\n {Object.entries(STATUS_CONFIG).map(([key, config]) => (\n <option key={key} value={key}>\n {config.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Export Error */}\n {exportChannelsError && (\n <div className=\"flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <p className=\"text-sm text-status-error-text flex-1\">{exportChannelsError}</p>\n </div>\n )}\n\n {channels.length > 0 && (\n <div\n className=\"flex flex-col gap-3 rounded-lg border border-border bg-surface p-4 sm:flex-row sm:items-center sm:justify-between\"\n data-tour=\"channels-list-bulk-actions\"\n >\n <label className=\"flex items-center gap-2 text-sm text-foreground\">\n <input\n type=\"checkbox\"\n checked={allVisibleSelected}\n ref={(input) => {\n if (input) {\n input.indeterminate = partiallySelected && !allVisibleSelected;\n }\n }}\n onChange={(event) => toggleAllChannels(event.target.checked)}\n aria-label=\"Select all channels\"\n className=\"size-4 rounded border-border text-primary\"\n />\n <span>{tr('channel.selectAllVisible', 'Select all visible')}</span>\n </label>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-muted-foreground\">\n {selectedChannelIds.size} {tr('channel.selectedCountLabel', 'selected')}\n </span>\n {selectedChannelIds.size > 0 && (\n <button\n type=\"button\"\n onClick={() => setSelectedChannelIds(new Set())}\n className=\"text-sm text-muted-foreground hover:text-foreground\"\n >\n {tr('channel.clearSelection', 'Clear selection')}\n </button>\n )}\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXECUTE}>\n <button\n type=\"button\"\n onClick={handleBulkHealthCheck}\n disabled={selectedChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-border rounded-md hover:bg-muted transition-colors disabled:opacity-50\"\n >\n {tr('channel.healthCheck', 'Health Check')}\n {selectedChannels.length > 0 ? ` (${selectedChannels.length})` : ''}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n type=\"button\"\n onClick={handleBulkActivate}\n disabled={selectedInactiveChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-status-success-border/30 text-status-success-text rounded-md hover:bg-status-success-bg-subtle transition-colors disabled:opacity-50\"\n >\n {tr('channel.activateSelected', 'Activate Selected')}\n {selectedInactiveChannels.length > 0\n ? ` (${selectedInactiveChannels.length})`\n : ''}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n type=\"button\"\n onClick={handleBulkDeactivate}\n disabled={selectedActiveChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-status-warning-border text-status-warning-text rounded-md hover:bg-status-warning-bg-subtle transition-colors disabled:opacity-50\"\n >\n {tr('channel.deactivateSelected', 'Deactivate Selected')}\n {selectedActiveChannels.length > 0 ? ` (${selectedActiveChannels.length})` : ''}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n type=\"button\"\n onClick={handleBulkDelete}\n disabled={selectedChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-status-error-border/30 text-status-error-text rounded-md hover:bg-status-error-bg-subtle transition-colors disabled:opacity-50\"\n >\n {tr('channel.deleteSelected', 'Delete Selected')}\n {selectedChannels.length > 0 ? ` (${selectedChannels.length})` : ''}\n </button>\n </PermissionGuard>\n </div>\n </div>\n )}\n\n {/* Error State */}\n {error && (\n <div className=\"flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <div>\n <p className=\"text-sm font-medium text-status-error-text\">\n {tr('channel.loadFailed', 'Failed to load channels')}\n </p>\n <p className=\"text-xs text-status-error-text\">{error}</p>\n </div>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"ml-auto px-3 py-1 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n >\n {tr('channel.retry', 'Retry')}\n </button>\n </div>\n )}\n\n {/* Loading State */}\n {isLoading && channels.length === 0 && (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" />\n </div>\n )}\n\n {/* Empty State */}\n {!isLoading && !error && channels.length === 0 && (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <Radio size={48} className=\"text-muted-foreground mb-4\" />\n <h3 className=\"text-lg font-semibold text-foreground mb-2\">\n {tr('channel.noChannelsFound', 'No channels found')}\n </h3>\n <p className=\"text-sm text-muted-foreground max-w-md mb-4\">\n {filter.search || filter.platform || filter.status\n ? tr('channel.adjustFilters', 'Try adjusting your filters to see more channels.')\n : tr(\n 'channel.createFirstChannelPrompt',\n 'Create your first communication channel to get started.'\n )}\n </p>\n {canCreate && !filter.search && !filter.platform && !filter.status && (\n <button\n type=\"button\"\n onClick={() => navigate('/channels/create')}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors\"\n >\n <Plus size={16} />\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n )}\n </div>\n )}\n\n {/* Channels List */}\n {channels.length > 0 && (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\" data-tour=\"channels-list-grid\">\n {channels.map((channel: Channel) => {\n const platformConfig = getPlatformConfig(channel.platform);\n const statusConfig = getStatusConfig(channel.status);\n const healthConfig = getHealthConfig(channel.connectionHealth);\n const StatusIcon = statusConfig.icon;\n const HealthIcon = healthConfig.icon;\n const isActionLoading = actionLoading === channel.id;\n\n return (\n <div\n key={channel.id}\n className={`p-4 bg-surface border rounded-lg transition-colors group ${selectedChannelIds.has(channel.id) ? 'ring-2 ring-action-primary-bg border-action-primary-bg/40' : 'border-border hover:border-action-primary-bg/40'}`}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between mb-3\">\n <div className=\"flex items-center gap-2\">\n <input\n type=\"checkbox\"\n checked={selectedChannelIds.has(channel.id)}\n onChange={(event) =>\n toggleChannelSelection(channel.id, event.target.checked)\n }\n aria-label={`Select channel ${channel.name}`}\n className=\"size-4 rounded border-border text-primary\"\n />\n <Radio size={20} className=\"text-accent-primary\" />\n <span\n className={`px-2 py-0.5 text-xs font-semibold rounded-full ${platformConfig.color}`}\n >\n {platformConfig.label}\n </span>\n </div>\n <div className=\"flex items-center gap-1\">\n <StatusIcon size={14} className={statusConfig.color.split(' ')[0]} />\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded-full ${statusConfig.color}`}\n >\n {statusConfig.label}\n </span>\n </div>\n </div>\n\n {/* Content - Clickable area */}\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => navigate(`/channels/${channel.id}`)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigate(`/channels/${channel.id}`);\n }\n }}\n className=\"cursor-pointer\"\n >\n <h3 className=\"text-sm font-semibold text-foreground mb-1 truncate\">\n {channel.name}\n </h3>\n\n {channel.description && (\n <p className=\"text-xs text-muted-foreground mb-2 line-clamp-2\">\n {channel.description}\n </p>\n )}\n\n {/* Adapter */}\n {channel.adapterType && (\n <p className=\"text-xs text-muted-foreground mb-2 font-mono\">\n {channel.adapterType.replace(/_/g, ' ')}\n </p>\n )}\n\n {/* Stats Row */}\n <div className=\"flex items-center gap-3 text-xs text-muted-foreground mb-2\">\n <span>\n {tr('channel.priority', 'Priority')}: {channel.priority}\n </span>\n </div>\n\n {/* Health & Connection Status */}\n <div className=\"flex items-center gap-3 text-xs\">\n <div className=\"flex items-center gap-1\">\n <HealthIcon size={12} className={healthConfig.color} />\n <span className={healthConfig.color}>{healthConfig.label}</span>\n </div>\n <span>•</span>\n <span\n className={\n channel.isConnected ? 'text-status-success-text' : 'text-muted-foreground'\n }\n >\n {channel.isConnected\n ? tr('channel.connected', 'Connected')\n : tr('channel.notConnected', 'Not Connected')}\n </span>\n </div>\n </div>\n\n {/* Tags */}\n {channel.tagAssignments && channel.tagAssignments.length > 0 && (\n <div className=\"flex flex-wrap gap-1 mt-2\" aria-label=\"Channel tags\">\n {channel.tagAssignments.slice(0, 4).map((a) => (\n <TagBadge key={a.id} id={a.tagId} label={a.tagId} size=\"sm\" />\n ))}\n {channel.tagAssignments.length > 4 && (\n <span className=\"text-xs text-muted-foreground\">\n +{channel.tagAssignments.length - 4}\n </span>\n )}\n </div>\n )}\n\n {/* Actions */}\n <div className=\"mt-3 pt-3 border-t border-border flex items-center justify-between opacity-0 group-hover:opacity-100 transition-opacity\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channel.id}`)}\n className=\"p-1.5 text-muted-foreground hover:text-accent-primary hover:bg-accent-primary/10 rounded transition-colors cursor-pointer\"\n title={tr('channel.viewDetails', 'View Details')}\n >\n <Eye size={14} />\n </button>\n {canUpdate && (\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channel.id}/edit`)}\n className=\"p-1.5 text-muted-foreground hover:text-accent-primary hover:bg-accent-primary/10 rounded transition-colors cursor-pointer\"\n title={tr('channel.editChannel', 'Edit Channel')}\n >\n <Pencil size={14} />\n </button>\n )}\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXECUTE}>\n <button\n type=\"button\"\n onClick={() => handleHealthCheck(channel.id)}\n disabled={isActionLoading}\n className=\"p-1.5 text-muted-foreground hover:text-status-info-text hover:bg-status-info-bg-subtle rounded transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed\"\n title={tr('channel.runHealthCheck', 'Run Health Check')}\n >\n {isActionLoading ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : (\n <RefreshCw size={14} />\n )}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n type=\"button\"\n onClick={() => handleToggleStatus(channel)}\n disabled={isActionLoading}\n className={`p-1.5 rounded transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed ${\n channel.status === 'ACTIVE'\n ? 'text-muted-foreground hover:text-status-warning-text hover:bg-status-warning-bg-subtle'\n : 'text-muted-foreground hover:text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n title={\n channel.status === 'ACTIVE'\n ? tr('channel.deactivate', 'Deactivate')\n : tr('channel.activate', 'Activate')\n }\n >\n {channel.status === 'ACTIVE' ? (\n <PowerOff size={14} />\n ) : (\n <Power size={14} />\n )}\n </button>\n </PermissionGuard>\n </div>\n\n {/* Delete */}\n {deleteConfirm === channel.id ? (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs text-muted-foreground\">\n {tr('channel.deleteQuestion', 'Delete?')}\n </span>\n <button\n type=\"button\"\n onClick={() => handleDelete(channel.id)}\n disabled={isActionLoading}\n className=\"px-2 py-1 text-xs text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors cursor-pointer disabled:cursor-not-allowed\"\n >\n {isActionLoading ? '...' : tr('channel.yes', 'Yes')}\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(null)}\n className=\"px-2 py-1 text-xs text-muted-foreground hover:bg-muted rounded transition-colors cursor-pointer\"\n >\n {tr('channel.no', 'No')}\n </button>\n </div>\n ) : (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(channel.id)}\n className=\"p-1.5 text-muted-foreground hover:text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors cursor-pointer\"\n title={tr('channel.deleteChannel', 'Delete Channel')}\n >\n <Trash2 size={14} />\n </button>\n </PermissionGuard>\n )}\n </div>\n </div>\n );\n })}\n </div>\n )}\n\n {/* Pagination */}\n {total > pageSize && (\n <div className=\"flex items-center justify-between px-1 py-2\">\n <p className=\"text-sm text-muted-foreground\">\n {tr('channel.showing', 'Showing')}{' '}\n <span className=\"font-medium text-foreground\">\n {currentPage * pageSize + 1}–{Math.min((currentPage + 1) * pageSize, total)}\n </span>{' '}\n {tr('channel.of', 'of')} <span className=\"font-medium text-foreground\">{total}</span>{' '}\n {tr('channel.channelsLower', 'channels')}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setCurrentPage((p) => Math.max(0, p - 1))}\n disabled={currentPage === 0}\n className=\"px-3 py-1.5 text-sm border border-border rounded-lg hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.previous', 'Previous')}\n </button>\n <span className=\"text-sm text-muted-foreground\">\n {currentPage + 1} / {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setCurrentPage((p) => Math.min(totalPages - 1, p + 1))}\n disabled={currentPage >= totalPages - 1}\n className=\"px-3 py-1.5 text-sm border border-border rounded-lg hover:bg-muted transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.next', 'Next')}\n </button>\n </div>\n </div>\n )}\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChannelsListPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ChannelsListPage.tsx"],"sourcesContent":["/**\n * Channels List Page\n *\n * Displays a list of all channels with CRUD operations\n */\n\nimport { useEffect, useMemo, useState, useRef } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport { reportShellError, subscribeShellEvent } from '../../../utils/shellBridge';\nimport {\n Radio,\n Plus,\n Search,\n Loader2,\n AlertCircle,\n Trash2,\n RefreshCw,\n Power,\n PowerOff,\n Eye,\n Pencil,\n CheckCircle2,\n XCircle,\n AlertTriangle,\n HelpCircle,\n Download,\n} from 'lucide-react';\nimport {\n useChannels,\n type Channel,\n type ChannelPlatform,\n type ChannelStatus,\n type ConnectionHealth,\n} from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { HEALTH_I18N, PLATFORM_I18N, STATUS_I18N, translateLabel } from '../i18n';\nimport { useExportChannels } from '../hooks/useExport';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\n\n// Platform display configuration\nconst PLATFORM_CONFIG: Record<ChannelPlatform, { label: string; color: string }> = {\n EMAIL: { label: 'Email', color: 'bg-accent-blue-subtle text-accent-blue' },\n SMS: { label: 'SMS', color: 'bg-status-success-bg-subtle text-status-success-text' },\n PUSH: { label: 'Push', color: 'bg-accent-purple-subtle text-accent-purple' },\n INAPP: { label: 'In-App', color: 'bg-accent-indigo-subtle text-accent-indigo' },\n WEBHOOK: { label: 'Webhook', color: 'bg-accent-orange-subtle text-accent-orange' },\n SLACK: { label: 'Slack', color: 'bg-accent-pink-subtle text-accent-pink' },\n TEAMS: { label: 'Teams', color: 'bg-accent-cyan-subtle text-accent-cyan' },\n DISCORD: { label: 'Discord', color: 'bg-accent-violet-subtle text-accent-violet' },\n WHATSAPP: { label: 'WhatsApp', color: 'bg-accent-emerald-subtle text-accent-emerald' },\n WEBCHAT: { label: 'Webchat', color: 'bg-accent-teal-subtle text-accent-teal' },\n};\n\n// Status display configuration\nconst STATUS_CONFIG: Record<\n ChannelStatus,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n ACTIVE: {\n label: 'Active',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n INACTIVE: { label: 'Inactive', color: 'text-text-secondary bg-bg-sunken', icon: PowerOff },\n ERROR: {\n label: 'Error',\n color: 'text-status-error-text bg-status-error-bg-subtle',\n icon: XCircle,\n },\n SUSPENDED: {\n label: 'Suspended',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: AlertTriangle,\n },\n ARCHIVED: { label: 'Archived', color: 'text-text-secondary bg-bg-sunken', icon: PowerOff },\n};\n\n// Health display configuration\nconst HEALTH_CONFIG: Record<\n ConnectionHealth,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n HEALTHY: { label: 'Healthy', color: 'text-status-success-text', icon: CheckCircle2 },\n DEGRADED: { label: 'Degraded', color: 'text-status-warning-text', icon: AlertTriangle },\n UNHEALTHY: { label: 'Unhealthy', color: 'text-status-error-text', icon: XCircle },\n UNKNOWN: { label: 'Unknown', color: 'text-text-secondary', icon: HelpCircle },\n};\n\nexport function ChannelsListPage() {\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n // Channel creation is an admin-only action (channel:create). Hide the\n // affordance for users without it (per MICROFRONTEND-SPEC: {canCreateChannel && ...}).\n const canCreate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_CREATE);\n const canUpdate = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_UPDATE);\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 const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);\n const [actionLoading, setActionLoading] = useState<string | null>(null);\n const [selectedChannelIds, setSelectedChannelIds] = useState<Set<string>>(new Set());\n const [bulkActionLoading, setBulkActionLoading] = useState<string | null>(null);\n\n const { organizationId } = useChannelContext();\n const {\n triggerExport: exportChannels,\n isExporting: isExportingChannels,\n exportError: exportChannelsError,\n } = useExportChannels(organizationId ?? undefined);\n const [pageSize] = useState(12);\n const [currentPage, setCurrentPage] = useState(0);\n\n const {\n channels,\n isLoading,\n error,\n total,\n filter,\n updateFilter,\n deleteChannel,\n activateChannel,\n deactivateChannel,\n runHealthCheck,\n refetch,\n } = useChannels();\n\n const totalPages = Math.max(1, Math.ceil(total / pageSize));\n\n const refetchRef = useRef(refetch);\n useEffect(() => {\n refetchRef.current = refetch;\n });\n\n // Fetch the current page whenever page or page size changes\n useEffect(() => {\n void refetchRef.current({ offset: currentPage * pageSize, limit: pageSize });\n }, [currentPage, pageSize]);\n\n // Reset to page 0 when filter changes\n useEffect(() => {\n void Promise.resolve().then(() => setCurrentPage(0));\n }, [filter.search, filter.platform, filter.status]);\n\n // Cross-MFE refresh: refetch the list whenever channel/connection events\n // are emitted by other tabs or microfrontends.\n useEffect(() => {\n const events = [\n 'channel.created',\n 'channel.updated',\n 'channel.deleted',\n 'connection.created',\n 'connection.updated',\n 'connection.deleted',\n ];\n const unsubs = events.map((name) => subscribeShellEvent(name, () => void refetch()));\n return () => unsubs.forEach((u) => u());\n }, [refetch]);\n\n const handleDelete = async (id: string) => {\n void nativeImpact('medium');\n setActionLoading(id);\n try {\n await deleteChannel(id);\n void nativeNotify('success');\n setDeleteConfirm(null);\n } catch (err) {\n void nativeNotify('error');\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to delete channel' } },\n '[channel] Failed to delete channel'\n );\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleToggleStatus = async (channel: Channel) => {\n setActionLoading(channel.id);\n try {\n if (channel.status === 'ACTIVE') {\n await deactivateChannel(channel.id);\n } else {\n await activateChannel(channel.id);\n }\n } catch (err) {\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to toggle channel status' } },\n '[channel] Failed to toggle channel status'\n );\n } finally {\n setActionLoading(null);\n }\n };\n\n const handleHealthCheck = async (id: string) => {\n setActionLoading(id);\n try {\n await runHealthCheck(id);\n } catch (err) {\n reportShellError(\n err,\n { module: 'channel', context: { action: 'Failed to run health check' } },\n '[channel] Failed to run health check'\n );\n } finally {\n setActionLoading(null);\n }\n };\n\n const selectedChannels = useMemo(\n () => channels.filter((channel) => selectedChannelIds.has(channel.id)),\n [channels, selectedChannelIds]\n );\n const selectedActiveChannels = useMemo(\n () => selectedChannels.filter((channel) => channel.status === 'ACTIVE'),\n [selectedChannels]\n );\n const selectedInactiveChannels = useMemo(\n () => selectedChannels.filter((channel) => channel.status !== 'ACTIVE'),\n [selectedChannels]\n );\n const allVisibleSelected =\n channels.length > 0 && channels.every((channel) => selectedChannelIds.has(channel.id));\n const partiallySelected =\n channels.some((channel) => selectedChannelIds.has(channel.id)) && !allVisibleSelected;\n\n const toggleChannelSelection = (channelId: string, checked: boolean) => {\n setSelectedChannelIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(channelId);\n } else {\n next.delete(channelId);\n }\n return next;\n });\n };\n\n const toggleAllChannels = (checked: boolean) => {\n setSelectedChannelIds((prev) => {\n const next = new Set(prev);\n channels.forEach((channel) => {\n if (checked) {\n next.add(channel.id);\n } else {\n next.delete(channel.id);\n }\n });\n return next;\n });\n };\n\n const handleBulkDelete = async () => {\n if (selectedChannels.length === 0) return;\n void nativeImpact('medium');\n const message = tr(\n selectedChannels.length === 1\n ? 'channel.bulkDeleteConfirmSingle'\n : 'channel.bulkDeleteConfirmMultiple',\n selectedChannels.length === 1\n ? 'Delete 1 selected channel?'\n : `Delete ${selectedChannels.length} selected channels?`\n );\n const native = await nativeConfirm({\n title: 'Delete channels',\n message,\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const confirmed = native === null ? window.confirm(message) : native;\n if (!confirmed) return;\n\n setBulkActionLoading('delete');\n try {\n await Promise.allSettled(selectedChannels.map((channel) => deleteChannel(channel.id)));\n setSelectedChannelIds(new Set());\n } finally {\n setBulkActionLoading(null);\n }\n };\n\n const handleBulkActivate = async () => {\n if (selectedInactiveChannels.length === 0) return;\n setBulkActionLoading('activate');\n try {\n await Promise.allSettled(\n selectedInactiveChannels.map((channel) => activateChannel(channel.id))\n );\n setSelectedChannelIds(new Set());\n } finally {\n setBulkActionLoading(null);\n }\n };\n\n const handleBulkDeactivate = async () => {\n if (selectedActiveChannels.length === 0) return;\n setBulkActionLoading('deactivate');\n try {\n await Promise.allSettled(\n selectedActiveChannels.map((channel) => deactivateChannel(channel.id))\n );\n setSelectedChannelIds(new Set());\n } finally {\n setBulkActionLoading(null);\n }\n };\n\n const handleBulkHealthCheck = async () => {\n if (selectedChannels.length === 0) return;\n setBulkActionLoading('health');\n try {\n await Promise.allSettled(selectedChannels.map((channel) => runHealthCheck(channel.id)));\n } finally {\n setBulkActionLoading(null);\n }\n };\n\n const getPlatformConfig = (platform: ChannelPlatform) => {\n const config = PLATFORM_CONFIG[platform] || {\n label: platform,\n color: 'bg-bg-sunken text-text-secondary',\n };\n return {\n ...config,\n label: translateLabel(platform, PLATFORM_I18N, tr),\n };\n };\n\n const getStatusConfig = (status: ChannelStatus) => {\n const config = STATUS_CONFIG[status] || STATUS_CONFIG.INACTIVE;\n return {\n ...config,\n label: translateLabel(status, STATUS_I18N, tr),\n };\n };\n\n const getHealthConfig = (health: ConnectionHealth) => {\n const config = HEALTH_CONFIG[health] || HEALTH_CONFIG.UNKNOWN;\n return {\n ...config,\n label: translateLabel(health, HEALTH_I18N, tr),\n };\n };\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_READ}>\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold text-text-primary\">\n {tr('channel.channels', 'Channels')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr('channel.manageChannels', 'Manage your communication channels')} ({total} total)\n </p>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXPORT}>\n <button\n type=\"button\"\n onClick={() => void exportChannels()}\n disabled={isExportingChannels}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-fg border border-border-subtle hover:bg-bg-elevated rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n title={tr('channel.exportCSV', 'Export to CSV')}\n data-tour=\"channels-list-export-button\"\n >\n {isExportingChannels ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <Download size={16} />\n )}\n {tr('channel.export', 'Export')}\n </button>\n </PermissionGuard>\n {canCreate && (\n <button\n type=\"button\"\n onClick={() => navigate('/channels/create')}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors\"\n >\n <Plus size={16} />\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n )}\n </div>\n </div>\n\n {/* Search and Filters */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n aria-label={tr('channel.searchChannels', 'Search channels...')}\n type=\"text\"\n placeholder={tr('channel.searchChannels', 'Search channels...')}\n value={filter.search || ''}\n onChange={(e) => updateFilter({ search: e.target.value })}\n className=\"w-full pl-9 pr-4 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n />\n </div>\n\n <select\n value={filter.platform || ''}\n onChange={(e) =>\n updateFilter({ platform: (e.target.value as ChannelPlatform) || undefined })\n }\n className=\"px-3 py-2 text-sm bg-bg-surface text-text-primary border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">{tr('channel.allPlatforms', 'All Platforms')}</option>\n {Object.entries(PLATFORM_CONFIG).map(([key, config]) => (\n <option key={key} value={key}>\n {config.label}\n </option>\n ))}\n </select>\n\n <select\n value={filter.status || ''}\n onChange={(e) =>\n updateFilter({ status: (e.target.value as ChannelStatus) || undefined })\n }\n className=\"px-3 py-2 text-sm bg-bg-surface text-text-primary border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">{tr('channel.allStatus', 'All Status')}</option>\n {Object.entries(STATUS_CONFIG).map(([key, config]) => (\n <option key={key} value={key}>\n {config.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Export Error */}\n {exportChannelsError && (\n <div className=\"flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <p className=\"text-sm text-status-error-text flex-1\">{exportChannelsError}</p>\n </div>\n )}\n\n {channels.length > 0 && (\n <div\n className=\"flex flex-col gap-3 rounded-lg border border-border-subtle bg-surface p-4 sm:flex-row sm:items-center sm:justify-between\"\n data-tour=\"channels-list-bulk-actions\"\n >\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={allVisibleSelected}\n ref={(input) => {\n if (input) {\n input.indeterminate = partiallySelected && !allVisibleSelected;\n }\n }}\n onChange={(event) => toggleAllChannels(event.target.checked)}\n aria-label=\"Select all channels\"\n className=\"size-4 rounded border-border-subtle text-primary\"\n />\n <span>{tr('channel.selectAllVisible', 'Select all visible')}</span>\n </label>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">\n {selectedChannelIds.size} {tr('channel.selectedCountLabel', 'selected')}\n </span>\n {selectedChannelIds.size > 0 && (\n <button\n type=\"button\"\n onClick={() => setSelectedChannelIds(new Set())}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n {tr('channel.clearSelection', 'Clear selection')}\n </button>\n )}\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXECUTE}>\n <button\n type=\"button\"\n onClick={handleBulkHealthCheck}\n disabled={selectedChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-border-subtle rounded-md hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n {tr('channel.healthCheck', 'Health Check')}\n {selectedChannels.length > 0 ? ` (${selectedChannels.length})` : ''}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n type=\"button\"\n onClick={handleBulkActivate}\n disabled={selectedInactiveChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-status-success-border/30 text-status-success-text rounded-md hover:bg-status-success-bg-subtle transition-colors disabled:opacity-50\"\n >\n {tr('channel.activateSelected', 'Activate Selected')}\n {selectedInactiveChannels.length > 0\n ? ` (${selectedInactiveChannels.length})`\n : ''}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n type=\"button\"\n onClick={handleBulkDeactivate}\n disabled={selectedActiveChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-status-warning-border text-status-warning-text rounded-md hover:bg-status-warning-bg-subtle transition-colors disabled:opacity-50\"\n >\n {tr('channel.deactivateSelected', 'Deactivate Selected')}\n {selectedActiveChannels.length > 0 ? ` (${selectedActiveChannels.length})` : ''}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n type=\"button\"\n onClick={handleBulkDelete}\n disabled={selectedChannels.length === 0 || bulkActionLoading !== null}\n className=\"px-3 py-2 text-sm font-medium border border-status-error-border/30 text-status-error-text rounded-md hover:bg-status-error-bg-subtle transition-colors disabled:opacity-50\"\n >\n {tr('channel.deleteSelected', 'Delete Selected')}\n {selectedChannels.length > 0 ? ` (${selectedChannels.length})` : ''}\n </button>\n </PermissionGuard>\n </div>\n </div>\n )}\n\n {/* Error State */}\n {error && (\n <div className=\"flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <div>\n <p className=\"text-sm font-medium text-status-error-text\">\n {tr('channel.loadFailed', 'Failed to load channels')}\n </p>\n <p className=\"text-xs text-status-error-text\">{error}</p>\n </div>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"ml-auto px-3 py-1 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors\"\n >\n {tr('channel.retry', 'Retry')}\n </button>\n </div>\n )}\n\n {/* Loading State */}\n {isLoading && channels.length === 0 && (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" />\n </div>\n )}\n\n {/* Empty State */}\n {!isLoading && !error && channels.length === 0 && (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <Radio size={48} className=\"text-text-secondary mb-4\" />\n <h3 className=\"text-lg font-semibold text-text-primary mb-2\">\n {tr('channel.noChannelsFound', 'No channels found')}\n </h3>\n <p className=\"text-sm text-text-secondary max-w-md mb-4\">\n {filter.search || filter.platform || filter.status\n ? tr('channel.adjustFilters', 'Try adjusting your filters to see more channels.')\n : tr(\n 'channel.createFirstChannelPrompt',\n 'Create your first communication channel to get started.'\n )}\n </p>\n {canCreate && !filter.search && !filter.platform && !filter.status && (\n <button\n type=\"button\"\n onClick={() => navigate('/channels/create')}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bg-hover rounded-lg transition-colors\"\n >\n <Plus size={16} />\n {tr('channel.createChannel', 'Create Channel')}\n </button>\n )}\n </div>\n )}\n\n {/* Channels List */}\n {channels.length > 0 && (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\" data-tour=\"channels-list-grid\">\n {channels.map((channel: Channel) => {\n const platformConfig = getPlatformConfig(channel.platform);\n const statusConfig = getStatusConfig(channel.status);\n const healthConfig = getHealthConfig(channel.connectionHealth);\n const StatusIcon = statusConfig.icon;\n const HealthIcon = healthConfig.icon;\n const isActionLoading = actionLoading === channel.id;\n\n return (\n <div\n key={channel.id}\n className={`p-4 bg-surface border rounded-lg transition-colors group ${selectedChannelIds.has(channel.id) ? 'ring-2 ring-action-primary-bg border-action-primary-bg/40' : 'border-border-subtle hover:border-action-primary-bg/40'}`}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between mb-3\">\n <div className=\"flex items-center gap-2\">\n <input\n type=\"checkbox\"\n checked={selectedChannelIds.has(channel.id)}\n onChange={(event) =>\n toggleChannelSelection(channel.id, event.target.checked)\n }\n aria-label={`Select channel ${channel.name}`}\n className=\"size-4 rounded border-border-subtle text-primary\"\n />\n <Radio size={20} className=\"text-accent-primary\" />\n <span\n className={`px-2 py-0.5 text-xs font-semibold rounded-full ${platformConfig.color}`}\n >\n {platformConfig.label}\n </span>\n </div>\n <div className=\"flex items-center gap-1\">\n <StatusIcon size={14} className={statusConfig.color.split(' ')[0]} />\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded-full ${statusConfig.color}`}\n >\n {statusConfig.label}\n </span>\n </div>\n </div>\n\n {/* Content - Clickable area */}\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => navigate(`/channels/${channel.id}`)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigate(`/channels/${channel.id}`);\n }\n }}\n className=\"cursor-pointer\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary mb-1 truncate\">\n {channel.name}\n </h3>\n\n {channel.description && (\n <p className=\"text-xs text-text-secondary mb-2 line-clamp-2\">\n {channel.description}\n </p>\n )}\n\n {/* Adapter */}\n {channel.adapterType && (\n <p className=\"text-xs text-text-secondary mb-2 font-mono\">\n {channel.adapterType.replace(/_/g, ' ')}\n </p>\n )}\n\n {/* Stats Row */}\n <div className=\"flex items-center gap-3 text-xs text-text-secondary mb-2\">\n <span>\n {tr('channel.priority', 'Priority')}: {channel.priority}\n </span>\n </div>\n\n {/* Health & Connection Status */}\n <div className=\"flex items-center gap-3 text-xs\">\n <div className=\"flex items-center gap-1\">\n <HealthIcon size={12} className={healthConfig.color} />\n <span className={healthConfig.color}>{healthConfig.label}</span>\n </div>\n <span>•</span>\n <span\n className={\n channel.isConnected ? 'text-status-success-text' : 'text-text-secondary'\n }\n >\n {channel.isConnected\n ? tr('channel.connected', 'Connected')\n : tr('channel.notConnected', 'Not Connected')}\n </span>\n </div>\n </div>\n\n {/* Tags */}\n {channel.tagAssignments && channel.tagAssignments.length > 0 && (\n <div className=\"flex flex-wrap gap-1 mt-2\" aria-label=\"Channel tags\">\n {channel.tagAssignments.slice(0, 4).map((a) => (\n <TagBadge key={a.id} id={a.tagId} label={a.tagId} size=\"sm\" />\n ))}\n {channel.tagAssignments.length > 4 && (\n <span className=\"text-xs text-text-secondary\">\n +{channel.tagAssignments.length - 4}\n </span>\n )}\n </div>\n )}\n\n {/* Actions */}\n <div className=\"mt-3 pt-3 border-t border-border-subtle flex items-center justify-between opacity-0 group-hover:opacity-100 transition-opacity\">\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channel.id}`)}\n className=\"p-1.5 text-text-secondary hover:text-accent-primary hover:bg-accent-primary/10 rounded transition-colors cursor-pointer\"\n title={tr('channel.viewDetails', 'View Details')}\n >\n <Eye size={14} />\n </button>\n {canUpdate && (\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channel.id}/edit`)}\n className=\"p-1.5 text-text-secondary hover:text-accent-primary hover:bg-accent-primary/10 rounded transition-colors cursor-pointer\"\n title={tr('channel.editChannel', 'Edit Channel')}\n >\n <Pencil size={14} />\n </button>\n )}\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_EXECUTE}>\n <button\n type=\"button\"\n onClick={() => handleHealthCheck(channel.id)}\n disabled={isActionLoading}\n className=\"p-1.5 text-text-secondary hover:text-status-info-text hover:bg-status-info-bg-subtle rounded transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed\"\n title={tr('channel.runHealthCheck', 'Run Health Check')}\n >\n {isActionLoading ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : (\n <RefreshCw size={14} />\n )}\n </button>\n </PermissionGuard>\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_UPDATE}>\n <button\n type=\"button\"\n onClick={() => handleToggleStatus(channel)}\n disabled={isActionLoading}\n className={`p-1.5 rounded transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed ${\n channel.status === 'ACTIVE'\n ? 'text-text-secondary hover:text-status-warning-text hover:bg-status-warning-bg-subtle'\n : 'text-text-secondary hover:text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n title={\n channel.status === 'ACTIVE'\n ? tr('channel.deactivate', 'Deactivate')\n : tr('channel.activate', 'Activate')\n }\n >\n {channel.status === 'ACTIVE' ? (\n <PowerOff size={14} />\n ) : (\n <Power size={14} />\n )}\n </button>\n </PermissionGuard>\n </div>\n\n {/* Delete */}\n {deleteConfirm === channel.id ? (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs text-text-secondary\">\n {tr('channel.deleteQuestion', 'Delete?')}\n </span>\n <button\n type=\"button\"\n onClick={() => handleDelete(channel.id)}\n disabled={isActionLoading}\n className=\"px-2 py-1 text-xs text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors cursor-pointer disabled:cursor-not-allowed\"\n >\n {isActionLoading ? '...' : tr('channel.yes', 'Yes')}\n </button>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(null)}\n className=\"px-2 py-1 text-xs text-text-secondary hover:bg-bg-sunken rounded transition-colors cursor-pointer\"\n >\n {tr('channel.no', 'No')}\n </button>\n </div>\n ) : (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.CHANNEL_DELETE}>\n <button\n type=\"button\"\n onClick={() => setDeleteConfirm(channel.id)}\n className=\"p-1.5 text-text-secondary hover:text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors cursor-pointer\"\n title={tr('channel.deleteChannel', 'Delete Channel')}\n >\n <Trash2 size={14} />\n </button>\n </PermissionGuard>\n )}\n </div>\n </div>\n );\n })}\n </div>\n )}\n\n {/* Pagination */}\n {total > pageSize && (\n <div className=\"flex items-center justify-between px-1 py-2\">\n <p className=\"text-sm text-text-secondary\">\n {tr('channel.showing', 'Showing')}{' '}\n <span className=\"font-medium text-text-primary\">\n {currentPage * pageSize + 1}–{Math.min((currentPage + 1) * pageSize, total)}\n </span>{' '}\n {tr('channel.of', 'of')} <span className=\"font-medium text-text-primary\">{total}</span>{' '}\n {tr('channel.channelsLower', 'channels')}\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setCurrentPage((p) => Math.max(0, p - 1))}\n disabled={currentPage === 0}\n className=\"px-3 py-1.5 text-sm border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.previous', 'Previous')}\n </button>\n <span className=\"text-sm text-text-secondary\">\n {currentPage + 1} / {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setCurrentPage((p) => Math.min(totalPages - 1, p + 1))}\n disabled={currentPage >= totalPages - 1}\n className=\"px-3 py-1.5 text-sm border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {tr('channel.next', 'Next')}\n </button>\n </div>\n </div>\n )}\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|