@burdenoff/microfe-channel 2026.625.1 → 2026.625.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js +73 -76
  2. package/dist/channel/modules/channels/pages/ChannelDashboardPage.js.map +1 -1
  3. package/dist/channel/modules/channels/pages/ChannelDetailsPage.js +407 -353
  4. package/dist/channel/modules/channels/pages/ChannelDetailsPage.js.map +1 -1
  5. package/dist/channel/modules/channels/pages/ChannelSettingsPage.js +93 -87
  6. package/dist/channel/modules/channels/pages/ChannelSettingsPage.js.map +1 -1
  7. package/dist/channel/modules/channels/pages/ChannelUsersPage.js +119 -96
  8. package/dist/channel/modules/channels/pages/ChannelUsersPage.js.map +1 -1
  9. package/dist/channel/modules/channels/pages/ChannelsListPage.js +219 -196
  10. package/dist/channel/modules/channels/pages/ChannelsListPage.js.map +1 -1
  11. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js +277 -232
  12. package/dist/channel/modules/channels/pages/ConnectionDetailsPage.js.map +1 -1
  13. package/dist/channel/modules/channels/pages/ConnectionsListPage.js +112 -91
  14. package/dist/channel/modules/channels/pages/ConnectionsListPage.js.map +1 -1
  15. package/dist/channel/modules/channels/pages/CreateChannelPage.js +28 -22
  16. package/dist/channel/modules/channels/pages/CreateChannelPage.js.map +1 -1
  17. package/dist/channel/modules/channels/pages/CreateConnectionPage.js +72 -65
  18. package/dist/channel/modules/channels/pages/CreateConnectionPage.js.map +1 -1
  19. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js +101 -93
  20. package/dist/channel/modules/channels/pages/DeliveryHistoryPage.js.map +1 -1
  21. package/dist/channel/modules/channels/pages/EditChannelPage.js +154 -148
  22. package/dist/channel/modules/channels/pages/EditChannelPage.js.map +1 -1
  23. package/dist/channel/modules/channels/pages/EditConnectionPage.js +69 -62
  24. package/dist/channel/modules/channels/pages/EditConnectionPage.js.map +1 -1
  25. package/dist/channel/modules/channels/pages/SendMessagePage.js +131 -124
  26. package/dist/channel/modules/channels/pages/SendMessagePage.js.map +1 -1
  27. package/dist/channel/modules/channels/pages/WebhookEventsPage.js +152 -129
  28. package/dist/channel/modules/channels/pages/WebhookEventsPage.js.map +1 -1
  29. package/package.json +2 -2
@@ -2,14 +2,14 @@ import { useChannel as e } from "../../../providers/ChannelProvider.js";
2
2
  import { useChannel as t } from "../../../hooks/useChannels.js";
3
3
  import { PermissionGuard as n } from "../../../components/PermissionGuard.js";
4
4
  import { CHANNEL_PERMISSIONS as r } from "../../../constants/permissions.js";
5
- import { useExportChannelUsers as ee } from "../hooks/useExport.js";
6
- import { useCallback as i, useEffect as a, useRef as o, useState as s } from "react";
7
- import { useI18n as c } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
8
- import { AlertCircle as l, ArrowLeft as u, Bell as d, BellOff as f, CheckCircle2 as p, ChevronLeft as m, ChevronRight as h, Clock as g, Download as _, Loader2 as v, RefreshCw as te, User as ne, UserCheck as re, XCircle as y } from "lucide-react";
9
- import { useNavigate as b, useParams as ie } from "react-router-dom";
10
- import { usePermissions as ae } from "@burdenoff/fe-libs/shared/providers/shell";
11
- import { Fragment as oe, jsx as x, jsxs as S } from "react/jsx-runtime";
12
- import { CTAOverflowMenu as C, GlassCard as w, ResponsiveTable as T } from "@burdenoff/fe-libs/ui";
5
+ import { useExportChannelUsers as i } from "../hooks/useExport.js";
6
+ import { useCallback as a, useEffect as o, useRef as s, useState as c } from "react";
7
+ import { useI18n as l } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
8
+ import { AlertCircle as u, ArrowLeft as d, Bell as f, BellOff as p, CheckCircle2 as m, ChevronLeft as ee, ChevronRight as te, Clock as h, Download as ne, Loader2 as g, RefreshCw as _, User as v, UserCheck as y, XCircle as b } from "lucide-react";
9
+ import { useNavigate as re, useParams as x } from "react-router-dom";
10
+ import { usePermissions as S } from "@burdenoff/fe-libs/shared/providers/shell";
11
+ import { Fragment as C, jsx as w, jsxs as T } from "react/jsx-runtime";
12
+ import { CTAOverflowMenu as ie, EmphasisPanel as ae, IllustratedEmptyState as oe, PagePurpose as se, ResponsiveTable as ce } from "@burdenoff/fe-libs/ui";
13
13
  //#region src/channel/modules/channels/pages/ChannelUsersPage.tsx
14
14
  var E = {
15
15
  ONLINE: {
@@ -32,25 +32,25 @@ var E = {
32
32
  PENDING: {
33
33
  label: "Pending",
34
34
  color: "text-status-warning-text bg-status-warning-bg-subtle",
35
- icon: g
35
+ icon: h
36
36
  },
37
37
  APPROVED: {
38
38
  label: "Approved",
39
39
  color: "text-status-success-text bg-status-success-bg-subtle",
40
- icon: p
40
+ icon: m
41
41
  },
42
42
  EXPIRED: {
43
43
  label: "Expired",
44
44
  color: "text-text-secondary bg-bg-sunken",
45
- icon: y
45
+ icon: b
46
46
  }
47
47
  };
48
48
  function O() {
49
- let { channelId: p } = ie(), g = b(), { hasPermission: O } = ae(), k = O(r.CHANNEL_USER_EXPORT), { t: A } = c(), j = (e, t) => {
49
+ let { channelId: m } = x(), h = re(), { hasPermission: O } = S(), k = O(r.CHANNEL_USER_EXPORT), { t: A } = l(), j = (e, t) => {
50
50
  let n = A(e);
51
51
  return n === e ? t : n;
52
- }, { organizationId: M } = e(), { triggerExport: N, isExporting: P, exportError: F } = ee(M ?? void 0, p), [I, L] = s(!0), [R, z] = s([]), [B, V] = s(25), [H, U] = s(0), [W, G] = s(0), { channel: K, isLoading: se, error: q, fetchUserPlatformConnections: J } = t(p), Y = i(async (e = 0) => {
53
- if (p) {
52
+ }, { organizationId: M } = e(), { triggerExport: N, isExporting: P, exportError: F } = i(M ?? void 0, m), [I, L] = c(!0), [R, z] = c([]), [B, V] = c(25), [H, U] = c(0), [W, G] = c(0), { channel: K, isLoading: le, error: q, fetchUserPlatformConnections: J } = t(m), Y = a(async (e = 0) => {
53
+ if (m) {
54
54
  L(!0);
55
55
  try {
56
56
  let t = await J(B, e * B);
@@ -60,44 +60,44 @@ function O() {
60
60
  }
61
61
  }
62
62
  }, [
63
- p,
63
+ m,
64
64
  J,
65
65
  B
66
- ]), X = o(Y);
67
- a(() => {
66
+ ]), X = s(Y);
67
+ o(() => {
68
68
  X.current = Y;
69
69
  });
70
- let Z = o(!0), Q = o(!1);
71
- a(() => {
70
+ let Z = s(!0), Q = s(!1);
71
+ o(() => {
72
72
  Promise.resolve().then(() => {
73
73
  Q.current = !0, G(0), X.current(0).finally(() => {
74
74
  Q.current = !1;
75
75
  });
76
76
  });
77
- }, [B]), a(() => {
77
+ }, [B]), o(() => {
78
78
  if (Z.current) {
79
79
  Z.current = !1;
80
80
  return;
81
81
  }
82
82
  Q.current || Promise.resolve().then(() => X.current(W));
83
83
  }, [W]);
84
- let $ = Math.max(1, Math.ceil(H / B)), ce = W > 0, le = W < $ - 1, ue = (e) => E[e] || E.OFFLINE, de = (e) => D[e] || D.EXPIRED, fe = [
84
+ let $ = Math.max(1, Math.ceil(H / B)), ue = W > 0, de = W < $ - 1, fe = (e) => E[e] || E.OFFLINE, pe = (e) => D[e] || D.EXPIRED, me = [
85
85
  {
86
86
  key: "user",
87
87
  header: j("channel.user", "User"),
88
88
  priority: "primary",
89
- cell: (e) => /* @__PURE__ */ S("div", {
89
+ cell: (e) => /* @__PURE__ */ T("div", {
90
90
  className: "flex items-center gap-3",
91
- children: [/* @__PURE__ */ x("div", {
91
+ children: [/* @__PURE__ */ w("div", {
92
92
  className: "size-8 bg-accent-primary/10 rounded-full flex items-center justify-center",
93
- children: /* @__PURE__ */ x(ne, {
93
+ children: /* @__PURE__ */ w(v, {
94
94
  size: 16,
95
95
  className: "text-accent-primary"
96
96
  })
97
- }), /* @__PURE__ */ S("div", { children: [/* @__PURE__ */ x("p", {
97
+ }), /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("p", {
98
98
  className: "text-sm font-medium text-text-primary",
99
99
  children: e.externalDisplayName || "Unknown"
100
- }), e.externalUsername && /* @__PURE__ */ x("p", {
100
+ }), e.externalUsername && /* @__PURE__ */ w("p", {
101
101
  className: "text-xs text-text-secondary",
102
102
  children: e.externalUsername
103
103
  })] })]
@@ -114,7 +114,7 @@ function O() {
114
114
  key: "source",
115
115
  header: j("channel.source", "Source"),
116
116
  priority: "secondary",
117
- cell: (e) => /* @__PURE__ */ x("span", {
117
+ cell: (e) => /* @__PURE__ */ w("span", {
118
118
  className: "text-xs text-text-secondary bg-bg-sunken px-2 py-1 rounded",
119
119
  children: e.source
120
120
  })
@@ -124,10 +124,10 @@ function O() {
124
124
  header: j("channel.status", "Status"),
125
125
  priority: "primary",
126
126
  cell: (e) => {
127
- let t = ue(e.presenceStatus);
128
- return /* @__PURE__ */ S("div", {
127
+ let t = fe(e.presenceStatus);
128
+ return /* @__PURE__ */ T("div", {
129
129
  className: "flex items-center gap-2",
130
- children: [/* @__PURE__ */ x("span", { className: `w-2 h-2 rounded-full ${t.color.replace("text-", "bg-")}` }), /* @__PURE__ */ x("span", {
130
+ children: [/* @__PURE__ */ w("span", { className: `w-2 h-2 rounded-full ${t.color.replace("text-", "bg-")}` }), /* @__PURE__ */ w("span", {
131
131
  className: `text-sm ${t.color}`,
132
132
  children: t.label
133
133
  })]
@@ -139,11 +139,11 @@ function O() {
139
139
  header: j("channel.pairing", "Pairing"),
140
140
  priority: "secondary",
141
141
  cell: (e) => {
142
- let t = e.pairingStatus ? de(e.pairingStatus) : null, n = t?.icon ?? y;
143
- return e.pairingStatus && t ? /* @__PURE__ */ S("span", {
142
+ let t = e.pairingStatus ? pe(e.pairingStatus) : null, n = t?.icon ?? b;
143
+ return e.pairingStatus && t ? /* @__PURE__ */ T("span", {
144
144
  className: `inline-flex items-center gap-1 text-xs px-2 py-1 rounded ${t.color}`,
145
- children: [/* @__PURE__ */ x(n, { size: 12 }), t.label]
146
- }) : /* @__PURE__ */ x("span", {
145
+ children: [/* @__PURE__ */ w(n, { size: 12 }), t.label]
146
+ }) : /* @__PURE__ */ w("span", {
147
147
  className: "text-xs text-text-secondary",
148
148
  children: "-"
149
149
  });
@@ -153,23 +153,23 @@ function O() {
153
153
  key: "notifications",
154
154
  header: j("channel.notifications", "Notifications"),
155
155
  priority: "secondary",
156
- cell: (e) => e.notificationsEnabled ? /* @__PURE__ */ S("span", {
156
+ cell: (e) => e.notificationsEnabled ? /* @__PURE__ */ T("span", {
157
157
  className: "inline-flex items-center gap-1",
158
- children: [/* @__PURE__ */ x(d, {
158
+ children: [/* @__PURE__ */ w(f, {
159
159
  size: 16,
160
160
  className: "text-status-success-text",
161
161
  "aria-hidden": "true"
162
- }), /* @__PURE__ */ x("span", {
162
+ }), /* @__PURE__ */ w("span", {
163
163
  className: "sr-only",
164
164
  children: j("channel.enabled", "Enabled")
165
165
  })]
166
- }) : /* @__PURE__ */ S("span", {
166
+ }) : /* @__PURE__ */ T("span", {
167
167
  className: "inline-flex items-center gap-1",
168
- children: [/* @__PURE__ */ x(f, {
168
+ children: [/* @__PURE__ */ w(p, {
169
169
  size: 16,
170
170
  className: "text-text-secondary",
171
171
  "aria-hidden": "true"
172
- }), /* @__PURE__ */ x("span", {
172
+ }), /* @__PURE__ */ w("span", {
173
173
  className: "sr-only",
174
174
  children: j("channel.disabled", "Disabled")
175
175
  })]
@@ -179,94 +179,94 @@ function O() {
179
179
  key: "dmAllowed",
180
180
  header: j("channel.dmAllowed", "DM Allowed"),
181
181
  priority: "secondary",
182
- cell: (e) => e.dmAllowed ? /* @__PURE__ */ S("span", {
182
+ cell: (e) => e.dmAllowed ? /* @__PURE__ */ T("span", {
183
183
  className: "inline-flex items-center gap-1 text-sm text-status-success-text",
184
- children: [/* @__PURE__ */ x(re, { size: 16 }), j("channel.allowed", "Allowed")]
185
- }) : /* @__PURE__ */ S("span", {
184
+ children: [/* @__PURE__ */ w(y, { size: 16 }), j("channel.allowed", "Allowed")]
185
+ }) : /* @__PURE__ */ T("span", {
186
186
  className: "inline-flex items-center gap-1 text-sm text-text-secondary",
187
- children: [/* @__PURE__ */ x(y, { size: 16 }), j("channel.blocked", "Blocked")]
187
+ children: [/* @__PURE__ */ w(b, { size: 16 }), j("channel.blocked", "Blocked")]
188
188
  })
189
189
  }
190
190
  ];
191
- return se ? /* @__PURE__ */ x("div", {
191
+ return le ? /* @__PURE__ */ w("div", {
192
192
  className: "flex items-center justify-center min-h-screen",
193
- children: /* @__PURE__ */ x(v, {
193
+ children: /* @__PURE__ */ w(g, {
194
194
  size: 32,
195
195
  className: "animate-spin text-accent-primary"
196
196
  })
197
- }) : q || !K ? /* @__PURE__ */ x("div", {
197
+ }) : q || !K ? /* @__PURE__ */ w("div", {
198
198
  className: "p-6",
199
- children: /* @__PURE__ */ S("div", {
199
+ children: /* @__PURE__ */ T("div", {
200
200
  className: "flex items-center gap-3 p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg",
201
201
  children: [
202
- /* @__PURE__ */ x(l, {
202
+ /* @__PURE__ */ w(u, {
203
203
  size: 20,
204
204
  className: "text-status-error-text"
205
205
  }),
206
- /* @__PURE__ */ x("div", { children: /* @__PURE__ */ x("p", {
206
+ /* @__PURE__ */ w("div", { children: /* @__PURE__ */ w("p", {
207
207
  className: "text-sm font-medium text-status-error-text",
208
208
  children: q || "Channel not found"
209
209
  }) }),
210
- /* @__PURE__ */ x("button", {
210
+ /* @__PURE__ */ w("button", {
211
211
  type: "button",
212
- onClick: () => g(`/channels/${p}`),
212
+ onClick: () => h(`/channels/${m}`),
213
213
  className: "ml-auto px-3 py-1 text-sm text-status-error-text hover:bg-status-error-bg-subtle rounded transition-colors",
214
214
  children: "Go Back"
215
215
  })
216
216
  ]
217
217
  })
218
- }) : /* @__PURE__ */ x(n, {
218
+ }) : /* @__PURE__ */ w(n, {
219
219
  permission: r.USER_PLATFORM_CONNECTION_READ,
220
- children: /* @__PURE__ */ S("div", {
220
+ children: /* @__PURE__ */ T("div", {
221
221
  className: "space-y-6 p-3 sm:p-6",
222
222
  children: [
223
- /* @__PURE__ */ S("div", {
223
+ /* @__PURE__ */ T("div", {
224
224
  className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
225
- children: [/* @__PURE__ */ S("div", {
225
+ children: [/* @__PURE__ */ T("div", {
226
226
  className: "flex items-center gap-3",
227
- children: [/* @__PURE__ */ x("button", {
227
+ children: [/* @__PURE__ */ w("button", {
228
228
  type: "button",
229
- onClick: () => g(`/channels/${p}`),
229
+ onClick: () => h(`/channels/${m}`),
230
230
  "aria-label": j("channel.backToChannel", "Back to channel"),
231
231
  className: "p-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors",
232
- children: /* @__PURE__ */ x(u, {
232
+ children: /* @__PURE__ */ w(d, {
233
233
  size: 20,
234
234
  "aria-hidden": "true"
235
235
  })
236
- }), /* @__PURE__ */ S("div", { children: [/* @__PURE__ */ x("h1", {
236
+ }), /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ w("h1", {
237
237
  className: "text-xl sm:text-2xl font-bold text-text-primary",
238
238
  children: j("channel.channelUsers", "Channel Users")
239
- }), /* @__PURE__ */ S("p", {
239
+ }), /* @__PURE__ */ T("p", {
240
240
  className: "text-sm text-text-secondary mt-1",
241
241
  children: [K.name, " - User connections"]
242
242
  })] })]
243
- }), /* @__PURE__ */ S("div", {
243
+ }), /* @__PURE__ */ T("div", {
244
244
  className: "flex items-center gap-3",
245
- children: [/* @__PURE__ */ S("select", {
245
+ children: [/* @__PURE__ */ T("select", {
246
246
  value: B,
247
247
  onChange: (e) => V(Number(e.target.value)),
248
248
  className: "px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-primary/50 text-text-primary",
249
249
  children: [
250
- /* @__PURE__ */ x("option", {
250
+ /* @__PURE__ */ w("option", {
251
251
  value: 10,
252
252
  children: "10 results"
253
253
  }),
254
- /* @__PURE__ */ x("option", {
254
+ /* @__PURE__ */ w("option", {
255
255
  value: 25,
256
256
  children: "25 results"
257
257
  }),
258
- /* @__PURE__ */ x("option", {
258
+ /* @__PURE__ */ w("option", {
259
259
  value: 50,
260
260
  children: "50 results"
261
261
  }),
262
- /* @__PURE__ */ x("option", {
262
+ /* @__PURE__ */ w("option", {
263
263
  value: 100,
264
264
  children: "100 results"
265
265
  })
266
266
  ]
267
- }), /* @__PURE__ */ x(C, { actions: [{
267
+ }), /* @__PURE__ */ w(ie, { actions: [{
268
268
  label: j("channel.refresh", "Refresh"),
269
- icon: /* @__PURE__ */ x(te, {
269
+ icon: /* @__PURE__ */ w(_, {
270
270
  size: 16,
271
271
  className: I ? "animate-spin" : "",
272
272
  "aria-hidden": "true"
@@ -275,11 +275,11 @@ function O() {
275
275
  onSelect: () => void Y(W)
276
276
  }, ...k ? [{
277
277
  label: j("channel.export", "Export"),
278
- icon: P ? /* @__PURE__ */ x(v, {
278
+ icon: P ? /* @__PURE__ */ w(g, {
279
279
  size: 16,
280
280
  className: "animate-spin",
281
281
  "aria-hidden": "true"
282
- }) : /* @__PURE__ */ x(_, {
282
+ }) : /* @__PURE__ */ w(ne, {
283
283
  size: 16,
284
284
  "aria-hidden": "true"
285
285
  }),
@@ -288,39 +288,62 @@ function O() {
288
288
  }] : []] })]
289
289
  })]
290
290
  }),
291
- F && /* @__PURE__ */ S("div", {
291
+ /* @__PURE__ */ w(se, {
292
+ icon: /* @__PURE__ */ w(v, {
293
+ size: 15,
294
+ "aria-hidden": "true"
295
+ }),
296
+ children: j("channel.usersPurpose", "The people reachable through this channel — linked via OAuth or a pairing code on platforms like Slack, Teams or WhatsApp. See who is online, whether their pairing is approved, and whether they have opted into notifications and direct messages. Use it to verify a recipient is actually connected before you target them, or to audit who can receive messages here.")
297
+ }),
298
+ F && /* @__PURE__ */ T("div", {
292
299
  className: "p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg flex items-center gap-3",
293
- children: [/* @__PURE__ */ x(l, {
300
+ children: [/* @__PURE__ */ w(u, {
294
301
  size: 20,
295
302
  className: "text-status-error-text"
296
- }), /* @__PURE__ */ x("p", {
303
+ }), /* @__PURE__ */ w("p", {
297
304
  className: "text-sm text-status-error-text flex-1",
298
305
  children: F
299
306
  })]
300
307
  }),
301
- /* @__PURE__ */ x(w, {
302
- treatment: "insight",
303
- className: "p-4 border-border-seam",
304
- children: /* @__PURE__ */ x("p", {
308
+ /* @__PURE__ */ T(ae, {
309
+ className: "flex items-center gap-3 p-4",
310
+ children: [/* @__PURE__ */ w("span", {
311
+ className: "rounded-lg bg-accent-blue-subtle p-2",
312
+ children: /* @__PURE__ */ w(y, {
313
+ size: 18,
314
+ className: "text-accent-blue",
315
+ "aria-hidden": "true"
316
+ })
317
+ }), /* @__PURE__ */ T("div", { children: [/* @__PURE__ */ T("p", {
318
+ className: "text-sm font-semibold text-text-primary",
319
+ children: [
320
+ H,
321
+ " ",
322
+ H === 1 ? j("channel.connectedUserSingular", "connected user") : j("channel.connectedUsersPlural", "connected users")
323
+ ]
324
+ }), /* @__PURE__ */ w("p", {
305
325
  className: "text-sm text-text-secondary",
306
- children: "Users connected to this channel through OAuth or pairing. Manage notification preferences and DM authorization here."
307
- })
326
+ children: j("channel.usersBannerHint", "Users connected to this channel through OAuth or pairing. Notification preferences and DM authorization are managed per user.")
327
+ })] })]
308
328
  }),
309
- /* @__PURE__ */ x(T, {
310
- columns: fe,
329
+ /* @__PURE__ */ w(ce, {
330
+ columns: me,
311
331
  data: R,
312
332
  rowKey: (e) => e.id,
313
- emptyState: j("channel.noUsersConnected", "No users connected to this channel"),
333
+ emptyState: /* @__PURE__ */ w(oe, {
334
+ illustration: "empty-people",
335
+ title: j("channel.noUsersConnected", "No users connected to this channel")
336
+ }),
314
337
  className: "bg-surface border border-border-seam rounded-lg"
315
338
  }),
316
- /* @__PURE__ */ S("div", {
339
+ /* @__PURE__ */ T("div", {
317
340
  className: "flex items-center justify-between",
318
- children: [/* @__PURE__ */ x("p", {
341
+ children: [/* @__PURE__ */ w("p", {
319
342
  className: "text-sm text-text-secondary",
320
- children: H > 0 && /* @__PURE__ */ S(oe, { children: [
343
+ children: H > 0 && /* @__PURE__ */ T(C, { children: [
321
344
  j("channel.showing", "Showing"),
322
345
  " ",
323
- /* @__PURE__ */ S("span", {
346
+ /* @__PURE__ */ T("span", {
324
347
  className: "font-medium",
325
348
  children: [
326
349
  W * B + 1,
@@ -331,22 +354,22 @@ function O() {
331
354
  " ",
332
355
  j("channel.of", "of"),
333
356
  " ",
334
- /* @__PURE__ */ x("span", {
357
+ /* @__PURE__ */ w("span", {
335
358
  className: "font-medium",
336
359
  children: H
337
360
  })
338
361
  ] })
339
- }), /* @__PURE__ */ S("div", {
362
+ }), /* @__PURE__ */ T("div", {
340
363
  className: "flex items-center gap-2",
341
364
  children: [
342
- /* @__PURE__ */ S("button", {
365
+ /* @__PURE__ */ T("button", {
343
366
  type: "button",
344
367
  onClick: () => G((e) => Math.max(0, e - 1)),
345
- disabled: !ce || I,
368
+ disabled: !ue || I,
346
369
  className: "flex items-center gap-1 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",
347
- children: [/* @__PURE__ */ x(m, { size: 16 }), j("channel.previous", "Previous")]
370
+ children: [/* @__PURE__ */ w(ee, { size: 16 }), j("channel.previous", "Previous")]
348
371
  }),
349
- /* @__PURE__ */ S("span", {
372
+ /* @__PURE__ */ T("span", {
350
373
  className: "text-sm text-text-secondary",
351
374
  children: [
352
375
  W + 1,
@@ -354,12 +377,12 @@ function O() {
354
377
  $
355
378
  ]
356
379
  }),
357
- /* @__PURE__ */ S("button", {
380
+ /* @__PURE__ */ T("button", {
358
381
  type: "button",
359
382
  onClick: () => G((e) => Math.min($ - 1, e + 1)),
360
- disabled: !le || I,
383
+ disabled: !de || I,
361
384
  className: "flex items-center gap-1 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",
362
- children: [j("channel.next", "Next"), /* @__PURE__ */ x(h, { size: 16 })]
385
+ children: [j("channel.next", "Next"), /* @__PURE__ */ w(te, { size: 16 })]
363
386
  })
364
387
  ]
365
388
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"ChannelUsersPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ChannelUsersPage.tsx"],"sourcesContent":["/**\n * Channel Users Page\n *\n * Displays user platform connections for a specific channel\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport { useParams, useNavigate } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Loader2,\n AlertCircle,\n CheckCircle2,\n XCircle,\n Clock,\n User,\n UserCheck,\n Bell,\n BellOff,\n RefreshCw,\n Download,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\nimport {\n CTAOverflowMenu,\n GlassCard,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n useChannel,\n type UserPlatformConnection,\n type UserPresenceStatus,\n type PairingStatus,\n} from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { useExportChannelUsers } from '../hooks/useExport';\n\nconst PRESENCE_CONFIG: Record<UserPresenceStatus, { label: string; color: string }> = {\n ONLINE: { label: 'Online', color: 'text-status-success-text' },\n OFFLINE: { label: 'Offline', color: 'text-text-secondary' },\n AWAY: { label: 'Away', color: 'text-status-warning-text' },\n DND: { label: 'Do Not Disturb', color: 'text-status-error-text' },\n};\n\nconst PAIRING_CONFIG: Record<\n PairingStatus,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n PENDING: {\n label: 'Pending',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: Clock,\n },\n APPROVED: {\n label: 'Approved',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n EXPIRED: { label: 'Expired', color: 'text-text-secondary bg-bg-sunken', icon: XCircle },\n};\n\nexport function ChannelUsersPage() {\n const { channelId } = useParams<{ channelId: string }>();\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n const canExport = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_USER_EXPORT);\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 { organizationId } = useChannelContext();\n const {\n triggerExport: exportUsers,\n isExporting: isExportingUsers,\n exportError: exportUsersError,\n } = useExportChannelUsers(organizationId ?? undefined, channelId);\n const [isLoadingUsers, setIsLoadingUsers] = useState(true);\n const [users, setUsers] = useState<UserPlatformConnection[]>([]);\n const [limit, setLimit] = useState(25);\n const [total, setTotal] = useState(0);\n const [currentPage, setCurrentPage] = useState(0);\n\n const { channel, isLoading, error, fetchUserPlatformConnections } = useChannel(channelId);\n\n const loadUsers = useCallback(\n async (page: number = 0) => {\n if (!channelId) return;\n setIsLoadingUsers(true);\n try {\n const result = await fetchUserPlatformConnections(limit, page * limit);\n setUsers(result.items);\n setTotal(result.total);\n } finally {\n setIsLoadingUsers(false);\n }\n },\n [channelId, fetchUserPlatformConnections, limit]\n );\n\n const loadUsersRef = useRef(loadUsers);\n useEffect(() => {\n loadUsersRef.current = loadUsers;\n });\n const isFirstPageRender = useRef(true);\n // Prevents the [currentPage] effect from double-loading when limit changes\n // reset the page back to 0 (the [limit] effect already handles that load).\n const limitChangingRef = useRef(false);\n\n useEffect(() => {\n void Promise.resolve().then(() => {\n // Set the guard before resetting page so the [currentPage] effect skips\n // the redundant load that would otherwise fire when page resets to 0.\n limitChangingRef.current = true;\n setCurrentPage(0);\n void loadUsersRef.current(0).finally(() => {\n limitChangingRef.current = false;\n });\n });\n }, [limit]);\n\n useEffect(() => {\n if (isFirstPageRender.current) {\n isFirstPageRender.current = false;\n return;\n }\n if (limitChangingRef.current) return;\n void Promise.resolve().then(() => loadUsersRef.current(currentPage));\n }, [currentPage]);\n\n const totalPages = Math.max(1, Math.ceil(total / limit));\n const hasPrevPage = currentPage > 0;\n const hasNextPage = currentPage < totalPages - 1;\n\n const getPresenceConfig = (status: UserPresenceStatus) => {\n return PRESENCE_CONFIG[status] || PRESENCE_CONFIG.OFFLINE;\n };\n\n const getPairingConfig = (status: PairingStatus) => {\n return PAIRING_CONFIG[status] || PAIRING_CONFIG.EXPIRED;\n };\n\n const userColumns: ResponsiveTableColumn<UserPlatformConnection>[] = [\n {\n key: 'user',\n header: tr('channel.user', 'User'),\n priority: 'primary',\n cell: (user) => (\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 bg-accent-primary/10 rounded-full flex items-center justify-center\">\n <User size={16} className=\"text-accent-primary\" />\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {user.externalDisplayName || 'Unknown'}\n </p>\n {user.externalUsername && (\n <p className=\"text-xs text-text-secondary\">{user.externalUsername}</p>\n )}\n </div>\n </div>\n ),\n },\n {\n key: 'platformId',\n header: tr('channel.platformId', 'Platform ID'),\n priority: 'secondary',\n cellClassName: 'text-sm font-mono text-text-secondary',\n cell: (user) => user.externalUserId,\n },\n {\n key: 'source',\n header: tr('channel.source', 'Source'),\n priority: 'secondary',\n cell: (user) => (\n <span className=\"text-xs text-text-secondary bg-bg-sunken px-2 py-1 rounded\">\n {user.source}\n </span>\n ),\n },\n {\n key: 'status',\n header: tr('channel.status', 'Status'),\n priority: 'primary',\n cell: (user) => {\n const presenceConfig = getPresenceConfig(user.presenceStatus as UserPresenceStatus);\n return (\n <div className=\"flex items-center gap-2\">\n <span\n className={`w-2 h-2 rounded-full ${presenceConfig.color.replace('text-', 'bg-')}`}\n />\n <span className={`text-sm ${presenceConfig.color}`}>{presenceConfig.label}</span>\n </div>\n );\n },\n },\n {\n key: 'pairing',\n header: tr('channel.pairing', 'Pairing'),\n priority: 'secondary',\n cell: (user) => {\n const pairingConfig = user.pairingStatus\n ? getPairingConfig(user.pairingStatus as PairingStatus)\n : null;\n const PairingIcon = pairingConfig?.icon ?? XCircle;\n return user.pairingStatus && pairingConfig ? (\n <span\n className={`inline-flex items-center gap-1 text-xs px-2 py-1 rounded ${pairingConfig.color}`}\n >\n <PairingIcon size={12} />\n {pairingConfig.label}\n </span>\n ) : (\n <span className=\"text-xs text-text-secondary\">-</span>\n );\n },\n },\n {\n key: 'notifications',\n header: tr('channel.notifications', 'Notifications'),\n priority: 'secondary',\n cell: (user) =>\n user.notificationsEnabled ? (\n <span className=\"inline-flex items-center gap-1\">\n <Bell size={16} className=\"text-status-success-text\" aria-hidden=\"true\" />\n <span className=\"sr-only\">{tr('channel.enabled', 'Enabled')}</span>\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1\">\n <BellOff size={16} className=\"text-text-secondary\" aria-hidden=\"true\" />\n <span className=\"sr-only\">{tr('channel.disabled', 'Disabled')}</span>\n </span>\n ),\n },\n {\n key: 'dmAllowed',\n header: tr('channel.dmAllowed', 'DM Allowed'),\n priority: 'secondary',\n cell: (user) =>\n user.dmAllowed ? (\n <span className=\"inline-flex items-center gap-1 text-sm text-status-success-text\">\n <UserCheck size={16} />\n {tr('channel.allowed', 'Allowed')}\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1 text-sm text-text-secondary\">\n <XCircle size={16} />\n {tr('channel.blocked', 'Blocked')}\n </span>\n ),\n },\n ];\n\n // Loading state (only full-screen for initial channel load)\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" />\n </div>\n );\n }\n\n // Error state\n if (error || !channel) {\n return (\n <div className=\"p-6\">\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 {error || 'Channel not found'}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\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 Go Back\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.USER_PLATFORM_CONNECTION_READ}>\n <div className=\"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 className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n aria-label={tr('channel.backToChannel', 'Back to channel')}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft size={20} aria-hidden=\"true\" />\n </button>\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold text-text-primary\">\n {tr('channel.channelUsers', 'Channel Users')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {channel.name} - User connections\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <select\n value={limit}\n onChange={(e) => setLimit(Number(e.target.value))}\n className=\"px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-primary/50 text-text-primary\"\n >\n <option value={10}>10 results</option>\n <option value={25}>25 results</option>\n <option value={50}>50 results</option>\n <option value={100}>100 results</option>\n </select>\n <CTAOverflowMenu\n actions={[\n {\n label: tr('channel.refresh', 'Refresh'),\n icon: (\n <RefreshCw\n size={16}\n className={isLoadingUsers ? 'animate-spin' : ''}\n aria-hidden=\"true\"\n />\n ),\n disabled: isLoadingUsers,\n onSelect: () => void loadUsers(currentPage),\n },\n ...(canExport\n ? [\n {\n label: tr('channel.export', 'Export'),\n icon: isExportingUsers ? (\n <Loader2 size={16} className=\"animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Download size={16} aria-hidden=\"true\" />\n ),\n disabled: isExportingUsers,\n onSelect: () => void exportUsers(),\n },\n ]\n : []),\n ]}\n />\n </div>\n </div>\n\n {/* Export Error Banner */}\n {exportUsersError && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg flex items-center gap-3\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <p className=\"text-sm text-status-error-text flex-1\">{exportUsersError}</p>\n </div>\n )}\n\n {/* Info Banner */}\n <GlassCard treatment=\"insight\" className=\"p-4 border-border-seam\">\n <p className=\"text-sm text-text-secondary\">\n Users connected to this channel through OAuth or pairing. Manage notification\n preferences and DM authorization here.\n </p>\n </GlassCard>\n\n {/* Users List */}\n <ResponsiveTable\n columns={userColumns}\n data={users}\n rowKey={(user) => user.id}\n emptyState={tr('channel.noUsersConnected', 'No users connected to this channel')}\n className=\"bg-surface border border-border-seam rounded-lg\"\n />\n\n {/* Pagination footer */}\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-text-secondary\">\n {total > 0 && (\n <>\n {tr('channel.showing', 'Showing')}{' '}\n <span className=\"font-medium\">\n {currentPage * limit + 1}–{Math.min((currentPage + 1) * limit, total)}\n </span>{' '}\n {tr('channel.of', 'of')} <span className=\"font-medium\">{total}</span>\n </>\n )}\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={!hasPrevPage || isLoadingUsers}\n className=\"flex items-center gap-1 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 <ChevronLeft size={16} />\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={!hasNextPage || isLoadingUsers}\n className=\"flex items-center gap-1 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 <ChevronRight size={16} />\n </button>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ChannelUsersPage.js","names":[],"sources":["../../../../../src/channel/modules/channels/pages/ChannelUsersPage.tsx"],"sourcesContent":["/**\n * Channel Users Page\n *\n * Displays user platform connections for a specific channel\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport { useParams, useNavigate } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ArrowLeft,\n Loader2,\n AlertCircle,\n CheckCircle2,\n XCircle,\n Clock,\n User,\n UserCheck,\n Bell,\n BellOff,\n RefreshCw,\n Download,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\nimport {\n CTAOverflowMenu,\n EmphasisPanel,\n ResponsiveTable,\n IllustratedEmptyState,\n PagePurpose,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n useChannel,\n type UserPlatformConnection,\n type UserPresenceStatus,\n type PairingStatus,\n} from '../../../hooks/useChannels';\nimport { useChannel as useChannelContext } from '../../../providers/ChannelProvider';\nimport { PermissionGuard } from '../../../components/PermissionGuard';\nimport { CHANNEL_PERMISSIONS } from '../../../constants/permissions';\nimport { useExportChannelUsers } from '../hooks/useExport';\n\nconst PRESENCE_CONFIG: Record<UserPresenceStatus, { label: string; color: string }> = {\n ONLINE: { label: 'Online', color: 'text-status-success-text' },\n OFFLINE: { label: 'Offline', color: 'text-text-secondary' },\n AWAY: { label: 'Away', color: 'text-status-warning-text' },\n DND: { label: 'Do Not Disturb', color: 'text-status-error-text' },\n};\n\nconst PAIRING_CONFIG: Record<\n PairingStatus,\n { label: string; color: string; icon: typeof CheckCircle2 }\n> = {\n PENDING: {\n label: 'Pending',\n color: 'text-status-warning-text bg-status-warning-bg-subtle',\n icon: Clock,\n },\n APPROVED: {\n label: 'Approved',\n color: 'text-status-success-text bg-status-success-bg-subtle',\n icon: CheckCircle2,\n },\n EXPIRED: { label: 'Expired', color: 'text-text-secondary bg-bg-sunken', icon: XCircle },\n};\n\nexport function ChannelUsersPage() {\n const { channelId } = useParams<{ channelId: string }>();\n const navigate = useNavigate();\n const { hasPermission } = usePermissions();\n const canExport = hasPermission(CHANNEL_PERMISSIONS.CHANNEL_USER_EXPORT);\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 { organizationId } = useChannelContext();\n const {\n triggerExport: exportUsers,\n isExporting: isExportingUsers,\n exportError: exportUsersError,\n } = useExportChannelUsers(organizationId ?? undefined, channelId);\n const [isLoadingUsers, setIsLoadingUsers] = useState(true);\n const [users, setUsers] = useState<UserPlatformConnection[]>([]);\n const [limit, setLimit] = useState(25);\n const [total, setTotal] = useState(0);\n const [currentPage, setCurrentPage] = useState(0);\n\n const { channel, isLoading, error, fetchUserPlatformConnections } = useChannel(channelId);\n\n const loadUsers = useCallback(\n async (page: number = 0) => {\n if (!channelId) return;\n setIsLoadingUsers(true);\n try {\n const result = await fetchUserPlatformConnections(limit, page * limit);\n setUsers(result.items);\n setTotal(result.total);\n } finally {\n setIsLoadingUsers(false);\n }\n },\n [channelId, fetchUserPlatformConnections, limit]\n );\n\n const loadUsersRef = useRef(loadUsers);\n useEffect(() => {\n loadUsersRef.current = loadUsers;\n });\n const isFirstPageRender = useRef(true);\n // Prevents the [currentPage] effect from double-loading when limit changes\n // reset the page back to 0 (the [limit] effect already handles that load).\n const limitChangingRef = useRef(false);\n\n useEffect(() => {\n void Promise.resolve().then(() => {\n // Set the guard before resetting page so the [currentPage] effect skips\n // the redundant load that would otherwise fire when page resets to 0.\n limitChangingRef.current = true;\n setCurrentPage(0);\n void loadUsersRef.current(0).finally(() => {\n limitChangingRef.current = false;\n });\n });\n }, [limit]);\n\n useEffect(() => {\n if (isFirstPageRender.current) {\n isFirstPageRender.current = false;\n return;\n }\n if (limitChangingRef.current) return;\n void Promise.resolve().then(() => loadUsersRef.current(currentPage));\n }, [currentPage]);\n\n const totalPages = Math.max(1, Math.ceil(total / limit));\n const hasPrevPage = currentPage > 0;\n const hasNextPage = currentPage < totalPages - 1;\n\n const getPresenceConfig = (status: UserPresenceStatus) => {\n return PRESENCE_CONFIG[status] || PRESENCE_CONFIG.OFFLINE;\n };\n\n const getPairingConfig = (status: PairingStatus) => {\n return PAIRING_CONFIG[status] || PAIRING_CONFIG.EXPIRED;\n };\n\n const userColumns: ResponsiveTableColumn<UserPlatformConnection>[] = [\n {\n key: 'user',\n header: tr('channel.user', 'User'),\n priority: 'primary',\n cell: (user) => (\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 bg-accent-primary/10 rounded-full flex items-center justify-center\">\n <User size={16} className=\"text-accent-primary\" />\n </div>\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n {user.externalDisplayName || 'Unknown'}\n </p>\n {user.externalUsername && (\n <p className=\"text-xs text-text-secondary\">{user.externalUsername}</p>\n )}\n </div>\n </div>\n ),\n },\n {\n key: 'platformId',\n header: tr('channel.platformId', 'Platform ID'),\n priority: 'secondary',\n cellClassName: 'text-sm font-mono text-text-secondary',\n cell: (user) => user.externalUserId,\n },\n {\n key: 'source',\n header: tr('channel.source', 'Source'),\n priority: 'secondary',\n cell: (user) => (\n <span className=\"text-xs text-text-secondary bg-bg-sunken px-2 py-1 rounded\">\n {user.source}\n </span>\n ),\n },\n {\n key: 'status',\n header: tr('channel.status', 'Status'),\n priority: 'primary',\n cell: (user) => {\n const presenceConfig = getPresenceConfig(user.presenceStatus as UserPresenceStatus);\n return (\n <div className=\"flex items-center gap-2\">\n <span\n className={`w-2 h-2 rounded-full ${presenceConfig.color.replace('text-', 'bg-')}`}\n />\n <span className={`text-sm ${presenceConfig.color}`}>{presenceConfig.label}</span>\n </div>\n );\n },\n },\n {\n key: 'pairing',\n header: tr('channel.pairing', 'Pairing'),\n priority: 'secondary',\n cell: (user) => {\n const pairingConfig = user.pairingStatus\n ? getPairingConfig(user.pairingStatus as PairingStatus)\n : null;\n const PairingIcon = pairingConfig?.icon ?? XCircle;\n return user.pairingStatus && pairingConfig ? (\n <span\n className={`inline-flex items-center gap-1 text-xs px-2 py-1 rounded ${pairingConfig.color}`}\n >\n <PairingIcon size={12} />\n {pairingConfig.label}\n </span>\n ) : (\n <span className=\"text-xs text-text-secondary\">-</span>\n );\n },\n },\n {\n key: 'notifications',\n header: tr('channel.notifications', 'Notifications'),\n priority: 'secondary',\n cell: (user) =>\n user.notificationsEnabled ? (\n <span className=\"inline-flex items-center gap-1\">\n <Bell size={16} className=\"text-status-success-text\" aria-hidden=\"true\" />\n <span className=\"sr-only\">{tr('channel.enabled', 'Enabled')}</span>\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1\">\n <BellOff size={16} className=\"text-text-secondary\" aria-hidden=\"true\" />\n <span className=\"sr-only\">{tr('channel.disabled', 'Disabled')}</span>\n </span>\n ),\n },\n {\n key: 'dmAllowed',\n header: tr('channel.dmAllowed', 'DM Allowed'),\n priority: 'secondary',\n cell: (user) =>\n user.dmAllowed ? (\n <span className=\"inline-flex items-center gap-1 text-sm text-status-success-text\">\n <UserCheck size={16} />\n {tr('channel.allowed', 'Allowed')}\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1 text-sm text-text-secondary\">\n <XCircle size={16} />\n {tr('channel.blocked', 'Blocked')}\n </span>\n ),\n },\n ];\n\n // Loading state (only full-screen for initial channel load)\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-screen\">\n <Loader2 size={32} className=\"animate-spin text-accent-primary\" />\n </div>\n );\n }\n\n // Error state\n if (error || !channel) {\n return (\n <div className=\"p-6\">\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 {error || 'Channel not found'}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\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 Go Back\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <PermissionGuard permission={CHANNEL_PERMISSIONS.USER_PLATFORM_CONNECTION_READ}>\n <div className=\"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 className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`/channels/${channelId}`)}\n aria-label={tr('channel.backToChannel', 'Back to channel')}\n className=\"p-2 text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n <ArrowLeft size={20} aria-hidden=\"true\" />\n </button>\n <div>\n <h1 className=\"text-xl sm:text-2xl font-bold text-text-primary\">\n {tr('channel.channelUsers', 'Channel Users')}\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {channel.name} - User connections\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <select\n value={limit}\n onChange={(e) => setLimit(Number(e.target.value))}\n className=\"px-3 py-2 text-sm bg-surface border border-border-subtle rounded-lg focus:outline-none focus:ring-2 focus:ring-accent-primary/50 text-text-primary\"\n >\n <option value={10}>10 results</option>\n <option value={25}>25 results</option>\n <option value={50}>50 results</option>\n <option value={100}>100 results</option>\n </select>\n <CTAOverflowMenu\n actions={[\n {\n label: tr('channel.refresh', 'Refresh'),\n icon: (\n <RefreshCw\n size={16}\n className={isLoadingUsers ? 'animate-spin' : ''}\n aria-hidden=\"true\"\n />\n ),\n disabled: isLoadingUsers,\n onSelect: () => void loadUsers(currentPage),\n },\n ...(canExport\n ? [\n {\n label: tr('channel.export', 'Export'),\n icon: isExportingUsers ? (\n <Loader2 size={16} className=\"animate-spin\" aria-hidden=\"true\" />\n ) : (\n <Download size={16} aria-hidden=\"true\" />\n ),\n disabled: isExportingUsers,\n onSelect: () => void exportUsers(),\n },\n ]\n : []),\n ]}\n />\n </div>\n </div>\n\n {/* Page purpose */}\n <PagePurpose icon={<User size={15} aria-hidden=\"true\" />}>\n {tr(\n 'channel.usersPurpose',\n 'The people reachable through this channel — linked via OAuth or a pairing code on platforms like Slack, Teams or WhatsApp. See who is online, whether their pairing is approved, and whether they have opted into notifications and direct messages. Use it to verify a recipient is actually connected before you target them, or to audit who can receive messages here.'\n )}\n </PagePurpose>\n\n {/* Export Error Banner */}\n {exportUsersError && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-status-error-border rounded-lg flex items-center gap-3\">\n <AlertCircle size={20} className=\"text-status-error-text\" />\n <p className=\"text-sm text-status-error-text flex-1\">{exportUsersError}</p>\n </div>\n )}\n\n {/* Emphasis — connected-users summary */}\n <EmphasisPanel className=\"flex items-center gap-3 p-4\">\n <span className=\"rounded-lg bg-accent-blue-subtle p-2\">\n <UserCheck size={18} className=\"text-accent-blue\" aria-hidden=\"true\" />\n </span>\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {total}{' '}\n {total === 1\n ? tr('channel.connectedUserSingular', 'connected user')\n : tr('channel.connectedUsersPlural', 'connected users')}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'channel.usersBannerHint',\n 'Users connected to this channel through OAuth or pairing. Notification preferences and DM authorization are managed per user.'\n )}\n </p>\n </div>\n </EmphasisPanel>\n\n {/* Users List */}\n <ResponsiveTable\n columns={userColumns}\n data={users}\n rowKey={(user) => user.id}\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={tr('channel.noUsersConnected', 'No users connected to this channel')}\n />\n }\n className=\"bg-surface border border-border-seam rounded-lg\"\n />\n\n {/* Pagination footer */}\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-text-secondary\">\n {total > 0 && (\n <>\n {tr('channel.showing', 'Showing')}{' '}\n <span className=\"font-medium\">\n {currentPage * limit + 1}–{Math.min((currentPage + 1) * limit, total)}\n </span>{' '}\n {tr('channel.of', 'of')} <span className=\"font-medium\">{total}</span>\n </>\n )}\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={!hasPrevPage || isLoadingUsers}\n className=\"flex items-center gap-1 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 <ChevronLeft size={16} />\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={!hasNextPage || isLoadingUsers}\n className=\"flex items-center gap-1 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 <ChevronRight size={16} />\n </button>\n </div>\n </div>\n </div>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}