@burdenoff/microfe-admin 2026.624.1 → 2026.626.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.
Files changed (76) hide show
  1. package/dist/admin/components/AccessDenied.js +2 -2
  2. package/dist/admin/components/AccessDenied.js.map +1 -1
  3. package/dist/admin/components/ActivityFeed.js +4 -4
  4. package/dist/admin/components/ActivityFeed.js.map +1 -1
  5. package/dist/admin/components/Charts.js.map +1 -1
  6. package/dist/admin/components/ConfirmDialog.js +1 -1
  7. package/dist/admin/components/ConfirmDialog.js.map +1 -1
  8. package/dist/admin/components/DataTable.js +5 -5
  9. package/dist/admin/components/DataTable.js.map +1 -1
  10. package/dist/admin/components/FormSheet.js +1 -1
  11. package/dist/admin/components/FormSheet.js.map +1 -1
  12. package/dist/admin/components/MetricCard.js +40 -37
  13. package/dist/admin/components/MetricCard.js.map +1 -1
  14. package/dist/admin/components/PageLayout.js +28 -22
  15. package/dist/admin/components/PageLayout.js.map +1 -1
  16. package/dist/admin/components/TenantSsoPanel.js +1 -1
  17. package/dist/admin/components/TenantSsoPanel.js.map +1 -1
  18. package/dist/admin/components/Toast.js +11 -11
  19. package/dist/admin/components/Toast.js.map +1 -1
  20. package/dist/admin/components/forms/SubscriptionForm.js +2 -2
  21. package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
  22. package/dist/admin/components/forms/TenantForm.js +1 -1
  23. package/dist/admin/components/forms/UserForm.js +1 -1
  24. package/dist/admin/components/ui.js +48 -41
  25. package/dist/admin/components/ui.js.map +1 -1
  26. package/dist/admin/constants/index.js +33 -24
  27. package/dist/admin/constants/index.js.map +1 -1
  28. package/dist/admin/pages/AuditLogsPage.js +94 -77
  29. package/dist/admin/pages/AuditLogsPage.js.map +1 -1
  30. package/dist/admin/pages/BillingPage.js +70 -18
  31. package/dist/admin/pages/BillingPage.js.map +1 -1
  32. package/dist/admin/pages/CommunicationsPage.js +98 -90
  33. package/dist/admin/pages/CommunicationsPage.js.map +1 -1
  34. package/dist/admin/pages/ContentPagesPage.js +276 -259
  35. package/dist/admin/pages/ContentPagesPage.js.map +1 -1
  36. package/dist/admin/pages/DashboardPage.js +114 -82
  37. package/dist/admin/pages/DashboardPage.js.map +1 -1
  38. package/dist/admin/pages/FeatureFlagsPage.js +94 -74
  39. package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
  40. package/dist/admin/pages/OrganizationsListPage.js +106 -86
  41. package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
  42. package/dist/admin/pages/ProductsPage.js +139 -128
  43. package/dist/admin/pages/ProductsPage.js.map +1 -1
  44. package/dist/admin/pages/ReferralsPage.js +74 -52
  45. package/dist/admin/pages/ReferralsPage.js.map +1 -1
  46. package/dist/admin/pages/RolesPermissionsPage.js +137 -116
  47. package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
  48. package/dist/admin/pages/SREPage.js +38 -32
  49. package/dist/admin/pages/SREPage.js.map +1 -1
  50. package/dist/admin/pages/SandboxSettingsPage.js +361 -322
  51. package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
  52. package/dist/admin/pages/SettingsPage.js +145 -130
  53. package/dist/admin/pages/SettingsPage.js.map +1 -1
  54. package/dist/admin/pages/SyncFailuresPage.js +123 -118
  55. package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
  56. package/dist/admin/pages/TenantDetailPage.js +261 -218
  57. package/dist/admin/pages/TenantDetailPage.js.map +1 -1
  58. package/dist/admin/pages/TenantFormRoutePage.js +65 -61
  59. package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
  60. package/dist/admin/pages/TenantsListPage.js +88 -71
  61. package/dist/admin/pages/TenantsListPage.js.map +1 -1
  62. package/dist/admin/pages/UsersListPage.js +129 -112
  63. package/dist/admin/pages/UsersListPage.js.map +1 -1
  64. package/dist/admin/pages/WaitlistPage.js +80 -63
  65. package/dist/admin/pages/WaitlistPage.js.map +1 -1
  66. package/dist/admin/pages/WorkflowsPage.js +108 -100
  67. package/dist/admin/pages/WorkflowsPage.js.map +1 -1
  68. package/dist/admin/pages/WorkspacesListPage.js +62 -49
  69. package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
  70. package/dist/node_modules/@hookform/resolvers/dist/resolvers.js +2 -2
  71. package/dist/node_modules/@hookform/resolvers/dist/resolvers.js.map +1 -1
  72. package/dist/node_modules/@hookform/resolvers/zod/dist/zod.js +35 -26
  73. package/dist/node_modules/@hookform/resolvers/zod/dist/zod.js.map +1 -1
  74. package/dist/node_modules/react-hook-form/dist/index.esm.js +609 -516
  75. package/dist/node_modules/react-hook-form/dist/index.esm.js.map +1 -1
  76. package/package.json +2 -2
@@ -1,32 +1,33 @@
1
1
  import { useToast as e } from "../components/Toast.js";
2
2
  import { useAdmin as t } from "../providers/AdminProvider.js";
3
3
  import { useAdminPermissions as n } from "../hooks/useAdminPermissions.js";
4
- import { Avatar as r, Button as i, EmptyState as a, Input as ee } from "../components/ui.js";
5
- import { PageLayout as te } from "../components/PageLayout.js";
6
- import { TenantStatusBadge as ne } from "../components/StatusBadge.js";
7
- import { formatBytes as re, formatDate as o, formatNumber as s } from "../utils/formatters.js";
8
- import { DataTable as c, Pagination as l } from "../components/DataTable.js";
9
- import { tWithFallback as ie } from "../utils/i18n.js";
10
- import { TenantForm as ae } from "../components/forms/TenantForm.js";
11
- import { RowActions as oe, createRowActions as se } from "../components/RowActions.js";
12
- import { PermissionGuard as ce } from "../components/PermissionGuard.js";
4
+ import { Avatar as r, Button as i, EmptyState as ee, Input as te } from "../components/ui.js";
5
+ import { PageLayout as ne } from "../components/PageLayout.js";
6
+ import { TenantStatusBadge as re } from "../components/StatusBadge.js";
7
+ import { formatBytes as ie, formatDate as a, formatNumber as o } from "../utils/formatters.js";
8
+ import { DataTable as ae, Pagination as oe } from "../components/DataTable.js";
9
+ import { tWithFallback as se } from "../utils/i18n.js";
10
+ import { TenantForm as ce } from "../components/forms/TenantForm.js";
11
+ import { RowActions as le, createRowActions as ue } from "../components/RowActions.js";
12
+ import { PermissionGuard as s } from "../components/PermissionGuard.js";
13
13
  import "../components/index.js";
14
- import { joinPath as u } from "../utils/navigation.js";
15
- import { mapTenantToUi as d } from "../utils/mappers.js";
16
- import { useActivateTenantMutation as f, useCreateTenantMutation as p, useListTenantsQuery as le, useSuspendTenantMutation as ue, useUpdateTenantMutation as de } from "../../generated/global-operations.js";
17
- import { useAdminStore as m } from "../store/adminStore.js";
18
- import { emitTenantLifecycle as h } from "../utils/tenantEvents.js";
19
- import { useCallback as g, useMemo as _, useState as v } from "react";
20
- import { Building2 as y, Plus as fe, Search as pe } from "lucide-react";
21
- import { useNavigate as me } from "react-router-dom";
14
+ import { joinPath as c } from "../utils/navigation.js";
15
+ import { mapTenantToUi as de } from "../utils/mappers.js";
16
+ import { useActivateTenantMutation as fe, useCreateTenantMutation as l, useListTenantsQuery as u, useSuspendTenantMutation as d, useUpdateTenantMutation as pe } from "../../generated/global-operations.js";
17
+ import { useAdminStore as me } from "../store/adminStore.js";
18
+ import { emitTenantLifecycle as f } from "../utils/tenantEvents.js";
19
+ import { useCallback as p, useMemo as m, useState as h } from "react";
20
+ import { Building2 as g, Plus as _, Search as v } from "lucide-react";
21
+ import { useNavigate as y } from "react-router-dom";
22
22
  import { jsx as b, jsxs as x } from "react/jsx-runtime";
23
23
  import { useFeatureFlag as S } from "@burdenoff/fe-libs/shared/feature-flags";
24
- import { useI18n as he } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
25
- import { useEventBus as ge } from "@burdenoff/fe-libs/shared/events";
24
+ import { EmphasisPanel as he, PagePurpose as ge } from "@burdenoff/fe-libs/ui";
25
+ import { useI18n as _e } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
26
+ import { useEventBus as ve } from "@burdenoff/fe-libs/shared/events";
26
27
  //#region src/admin/pages/TenantsListPage.tsx
27
28
  var C = 20;
28
29
  function w() {
29
- let { canViewTenants: w, canManageTenants: T, isLoading: E } = n(), D = me(), { basePath: O = "" } = t(), { tenantFilter: k, updateTenantFilter: A, resetTenantFilter: j } = m(), M = e(), { t: N } = he(), P = S("admin.tenant.create", !0), F = S("admin.tenant.edit", !0), I = ge(), L = g((e, t, n) => ie(N, e, t, n), [N]), [R, z] = v(1), [B, V] = v(!1), [_e, H] = v(void 0), ve = _(() => {
30
+ let { canViewTenants: w, canManageTenants: T, isLoading: E } = n(), D = y(), { basePath: O = "" } = t(), { tenantFilter: k, updateTenantFilter: A, resetTenantFilter: j } = me(), M = e(), { t: N } = _e(), P = S("admin.tenant.create", !0), F = S("admin.tenant.edit", !0), I = ve(), L = p((e, t, n) => se(N, e, t, n), [N]), [R, z] = h(1), [ye, B] = h(!1), [be, V] = h(void 0), xe = m(() => {
30
31
  let e = {};
31
32
  k.search && (e.search = k.search);
32
33
  let t = {
@@ -35,29 +36,29 @@ function w() {
35
36
  deleted: "DELETED"
36
37
  };
37
38
  return k.status !== "all" && t[k.status] && (e.status = t[k.status]), e;
38
- }, [k.search, k.status]), ye = E || !w, { data: U, loading: be, error: W, refetch: G } = le({
39
+ }, [k.search, k.status]), H = E || !w, { data: U, loading: Se, error: W, refetch: G } = u({
39
40
  variables: {
40
- filter: ve,
41
+ filter: xe,
41
42
  limit: C,
42
43
  offset: (R - 1) * C
43
44
  },
44
45
  fetchPolicy: "cache-and-network",
45
- skip: ye
46
- }), [K, { loading: xe }] = p({ onCompleted: () => {
46
+ skip: H
47
+ }), [K, { loading: Ce }] = l({ onCompleted: () => {
47
48
  G();
48
- } }), [q, { loading: Se }] = de({ onCompleted: () => {
49
+ } }), [q, { loading: we }] = pe({ onCompleted: () => {
49
50
  G();
50
- } }), [J] = ue({ onCompleted: () => {
51
+ } }), [J] = d({ onCompleted: () => {
51
52
  G();
52
- } }), [Y] = f({ onCompleted: () => {
53
+ } }), [Y] = fe({ onCompleted: () => {
53
54
  G();
54
- } }), Ce = _(() => (U?.tenants?.items ?? []).map(d), [U]), X = U?.tenants?.total ?? 0, we = Math.ceil(X / C), Te = g(() => {
55
- H(void 0), V(!0);
56
- }, []), Z = g((e) => {
57
- H(e), V(!0);
58
- }, []), Q = g(async (e) => {
55
+ } }), Te = m(() => (U?.tenants?.items ?? []).map(de), [U]), X = U?.tenants?.total ?? 0, Ee = Math.ceil(X / C), De = p(() => {
56
+ V(void 0), B(!0);
57
+ }, []), Z = p((e) => {
58
+ V(e), B(!0);
59
+ }, []), Q = p(async (e) => {
59
60
  try {
60
- await J({ variables: { id: e.id } }), h(I, "tenant.tenant.suspended", {
61
+ await J({ variables: { id: e.id } }), f(I, "tenant.tenant.suspended", {
61
62
  tenantId: e.id,
62
63
  tenantSlug: e.slug,
63
64
  status: "SUSPENDED"
@@ -70,9 +71,9 @@ function w() {
70
71
  M,
71
72
  L,
72
73
  I
73
- ]), $ = g(async (e) => {
74
+ ]), $ = p(async (e) => {
74
75
  try {
75
- await Y({ variables: { id: e.id } }), h(I, "tenant.tenant.activated", {
76
+ await Y({ variables: { id: e.id } }), f(I, "tenant.tenant.activated", {
76
77
  tenantId: e.id,
77
78
  tenantSlug: e.slug,
78
79
  status: "ACTIVE"
@@ -85,7 +86,7 @@ function w() {
85
86
  M,
86
87
  L,
87
88
  I
88
- ]), Ee = g(async (e) => {
89
+ ]), Oe = p(async (e) => {
89
90
  let t = "id" in e && e.id;
90
91
  try {
91
92
  if (t) {
@@ -101,7 +102,7 @@ function w() {
101
102
  }
102
103
  }
103
104
  } }), n = t.data?.updateTenant?.id ?? e.id, r = t.data?.updateTenant?.slug;
104
- h(I, "tenant.tenant.updated", {
105
+ f(I, "tenant.tenant.updated", {
105
106
  tenantId: n,
106
107
  tenantSlug: r
107
108
  }), M.success(L("admin.tenants.toast.updatedTitle", "Tenant updated"), L("admin.tenants.toast.updatedDescription", "{{name}} has been updated.", { name: e.name ?? "" }));
@@ -116,12 +117,12 @@ function w() {
116
117
  plan: e.plan
117
118
  }
118
119
  } } })).data?.createTenant;
119
- t?.id && h(I, "tenant.tenant.created", {
120
+ t?.id && f(I, "tenant.tenant.created", {
120
121
  tenantId: t.id,
121
122
  tenantSlug: t.slug
122
123
  }), M.success(L("admin.tenants.toast.createdTitle", "Tenant created"), L("admin.tenants.toast.createdDescription", "{{name}} has been created.", { name: e.name ?? "" }));
123
124
  }
124
- V(!1);
125
+ B(!1);
125
126
  } catch (e) {
126
127
  let n = e instanceof Error ? e.message : "Unknown error";
127
128
  throw M.error(t ? L("admin.tenants.toast.updateFailedTitle", "Update failed") : L("admin.tenants.toast.createFailedTitle", "Create failed"), n), e;
@@ -132,11 +133,11 @@ function w() {
132
133
  M,
133
134
  L,
134
135
  I
135
- ]), De = (e) => {
136
- D(u(O, `tenants/${e.id}`));
137
- }, Oe = g((e) => {
138
- let t = se({
139
- onView: () => D(u(O, `tenants/${e.id}`)),
136
+ ]), ke = (e) => {
137
+ D(c(O, `tenants/${e.id}`));
138
+ }, Ae = p((e) => {
139
+ let t = ue({
140
+ onView: () => D(c(O, `tenants/${e.id}`)),
140
141
  onEdit: T && F ? () => Z(e) : void 0
141
142
  });
142
143
  return T && t.push({
@@ -154,7 +155,7 @@ function w() {
154
155
  Q,
155
156
  $,
156
157
  L
157
- ]), ke = [
158
+ ]), je = [
158
159
  {
159
160
  key: "name",
160
161
  header: L("admin.tenants.table.tenant", "Tenant"),
@@ -180,7 +181,7 @@ function w() {
180
181
  key: "status",
181
182
  header: L("admin.tenants.table.status", "Status"),
182
183
  width: "120px",
183
- render: (e) => /* @__PURE__ */ b(ne, { status: e.status })
184
+ render: (e) => /* @__PURE__ */ b(re, { status: e.status })
184
185
  },
185
186
  {
186
187
  key: "plan",
@@ -193,54 +194,69 @@ function w() {
193
194
  width: "80px",
194
195
  align: "right",
195
196
  sortable: !0,
196
- render: (e) => s(e.userCount)
197
+ render: (e) => o(e.userCount)
197
198
  },
198
199
  {
199
200
  key: "storageUsed",
200
201
  header: L("admin.tenants.table.storage", "Storage"),
201
202
  width: "100px",
202
203
  align: "right",
203
- render: (e) => re(e.storageUsed)
204
+ render: (e) => ie(e.storageUsed)
204
205
  },
205
206
  {
206
207
  key: "createdAt",
207
208
  header: L("admin.tenants.table.created", "Created"),
208
209
  width: "120px",
209
210
  sortable: !0,
210
- render: (e) => o(e.createdAt)
211
+ render: (e) => a(e.createdAt)
211
212
  },
212
213
  {
213
214
  key: "actions",
214
215
  header: "",
215
216
  width: "50px",
216
217
  align: "right",
217
- render: (e) => /* @__PURE__ */ b(oe, {
218
- actions: Oe(e),
218
+ render: (e) => /* @__PURE__ */ b(le, {
219
+ actions: Ae(e),
219
220
  ariaLabel: L("admin.tenants.table.actionsFor", "Actions for {{name}}", { name: e.name })
220
221
  })
221
222
  }
222
223
  ];
223
- return /* @__PURE__ */ b(ce, {
224
+ return /* @__PURE__ */ b(s, {
224
225
  allowed: w,
225
226
  isLoading: E,
226
227
  pageTitle: L("admin.tenants.title", "Tenants"),
227
228
  denyMessage: L("admin.tenants.denyMessage", "You don't have permission to view tenants."),
228
- children: /* @__PURE__ */ x(te, {
229
+ children: /* @__PURE__ */ x(ne, {
229
230
  title: L("admin.tenants.title", "Tenants"),
231
+ gradientWord: L("admin.tenants.title", "Tenants"),
230
232
  description: X ? L("admin.tenants.descriptionWithTotal", "Manage all tenants on the platform · {{total}} total", { total: X }) : L("admin.tenants.description", "Manage all tenants on the platform"),
231
233
  actions: T && P ? /* @__PURE__ */ x(i, {
232
- onClick: Te,
233
- disabled: xe || Se,
234
- children: [/* @__PURE__ */ b(fe, { className: "mr-2 h-4 w-4" }), L("admin.tenants.actions.createTenant", "Create Tenant")]
234
+ onClick: De,
235
+ disabled: Ce || we,
236
+ children: [/* @__PURE__ */ b(_, { className: "mr-2 h-4 w-4" }), L("admin.tenants.actions.createTenant", "Create Tenant")]
235
237
  }) : void 0,
236
238
  children: [/* @__PURE__ */ x("div", {
237
239
  className: "space-y-4",
238
240
  children: [
241
+ /* @__PURE__ */ b(ge, { children: L("admin.tenants.purpose", "Tenants are the isolated customer accounts on the platform — each with its own users, data, plan and billing. Use this page to find a tenant, onboard a new one, and suspend or reactivate access when a customer churns or returns.") }),
242
+ /* @__PURE__ */ x(he, {
243
+ className: "flex items-center gap-4 p-5",
244
+ children: [/* @__PURE__ */ b("div", {
245
+ className: "flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-action-primary-bg/15 text-action-primary-bg",
246
+ children: /* @__PURE__ */ b(g, { className: "h-6 w-6" })
247
+ }), /* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("div", {
248
+ className: "text-3xl font-bold tabular-nums text-text-primary",
249
+ children: o(X)
250
+ }), /* @__PURE__ */ b("p", {
251
+ className: "text-sm text-text-secondary",
252
+ children: L("admin.tenants.emphasis.label", "tenants on the platform")
253
+ })] })]
254
+ }),
239
255
  /* @__PURE__ */ x("div", {
240
256
  className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
241
257
  children: [/* @__PURE__ */ x("div", {
242
258
  className: "relative max-w-sm flex-1",
243
- children: [/* @__PURE__ */ b(pe, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ b(ee, {
259
+ children: [/* @__PURE__ */ b(v, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ b(te, {
244
260
  placeholder: L("admin.tenants.searchPlaceholder", "Search tenants..."),
245
261
  value: k.search,
246
262
  onChange: (e) => {
@@ -291,20 +307,21 @@ function w() {
291
307
  W.message
292
308
  ]
293
309
  }),
294
- /* @__PURE__ */ b(c, {
295
- columns: ke,
296
- data: Ce,
310
+ /* @__PURE__ */ b(ae, {
311
+ columns: je,
312
+ data: Te,
297
313
  keyExtractor: (e) => e.id,
298
- onRowClick: De,
314
+ onRowClick: ke,
299
315
  sortBy: k.sortBy,
300
316
  sortOrder: k.sortOrder,
301
317
  onSort: (e, t) => A({
302
318
  sortBy: e,
303
319
  sortOrder: t
304
320
  }),
305
- loading: be,
306
- emptyState: /* @__PURE__ */ b(a, {
307
- icon: /* @__PURE__ */ b(y, { className: "h-8 w-8" }),
321
+ loading: Se,
322
+ emptyState: /* @__PURE__ */ b(ee, {
323
+ illustration: "empty-search",
324
+ icon: /* @__PURE__ */ b(g, { className: "h-8 w-8" }),
308
325
  title: L("admin.tenants.empty.title", "No tenants found"),
309
326
  description: L("admin.tenants.empty.description", "Try adjusting your search or filters"),
310
327
  action: /* @__PURE__ */ b(i, {
@@ -316,19 +333,19 @@ function w() {
316
333
  })
317
334
  })
318
335
  }),
319
- X > C && /* @__PURE__ */ b(l, {
336
+ X > C && /* @__PURE__ */ b(oe, {
320
337
  page: R,
321
- totalPages: we,
338
+ totalPages: Ee,
322
339
  totalItems: X,
323
340
  pageSize: C,
324
341
  onPageChange: z
325
342
  })
326
343
  ]
327
- }), /* @__PURE__ */ b(ae, {
328
- open: B,
329
- onClose: () => V(!1),
330
- tenant: _e,
331
- onSave: Ee
344
+ }), /* @__PURE__ */ b(ce, {
345
+ open: ye,
346
+ onClose: () => B(!1),
347
+ tenant: be,
348
+ onSave: Oe
332
349
  })]
333
350
  })
334
351
  });
@@ -1 +1 @@
1
- {"version":3,"file":"TenantsListPage.js","names":[],"sources":["../../../src/admin/pages/TenantsListPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { Plus, Search, Building2 } from 'lucide-react';\nimport { useAdmin } from '../providers/AdminProvider';\nimport { useAdminStore } from '../store/adminStore';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport {\n PageLayout,\n Button,\n Input,\n DataTable,\n Pagination,\n TenantStatusBadge,\n EmptyState,\n Avatar,\n TenantForm,\n RowActions,\n createRowActions,\n useToast,\n PermissionGuard,\n} from '../components';\nimport { joinPath } from '../utils/navigation';\nimport { formatDate, formatNumber, formatBytes } from '../utils/formatters';\nimport { mapTenantToUi } from '../utils/mappers';\nimport type { Tenant, TenantFilter } from '../types';\nimport type { CreateTenantInput, UpdateTenantInput } from '../schemas';\nimport {\n useListTenantsQuery,\n useCreateTenantMutation,\n useUpdateTenantMutation,\n useSuspendTenantMutation,\n useActivateTenantMutation,\n} from '../../generated/global-operations';\nimport type { RowAction } from '../components/RowActions';\nimport type { TenantStatus as GqlTenantStatus } from '../../generated/global-types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useFeatureFlag } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { tWithFallback } from '../utils/i18n';\nimport { emitTenantLifecycle } from '../utils/tenantEvents';\n\nconst PAGE_SIZE = 20;\n\n/**\n * Tenants list page\n */\nexport function TenantsListPage() {\n const { canViewTenants, canManageTenants, isLoading: permissionsLoading } = useAdminPermissions();\n\n const navigate = useNavigate();\n const { basePath = '' } = useAdmin();\n const { tenantFilter, updateTenantFilter, resetTenantFilter } = useAdminStore();\n const toast = useToast();\n const { t } = useI18n();\n // Page-level feature flags. Default-allow keeps prior behavior intact.\n // Both list-page entry points (the Create CTA and the row Edit action that\n // opens the local TenantForm modal) have to honor the flags — otherwise\n // turning the edit flag off would still let an admin edit via the modal\n // even though the route-level gate denies /admin/tenants/:id/edit.\n const createFlagEnabled = useFeatureFlag('admin.tenant.create', true);\n const editFlagEnabled = useFeatureFlag('admin.tenant.edit', true);\n const events = useEventBus();\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params),\n [t]\n );\n\n const [page, setPage] = useState(1);\n\n // Form state\n const [formOpen, setFormOpen] = useState(false);\n const [editingTenant, setEditingTenant] = useState<Tenant | undefined>(undefined);\n\n // Build filter for GraphQL — only pass supported fields\n const gqlFilter = useMemo(() => {\n const f: { search?: string; status?: GqlTenantStatus } = {};\n if (tenantFilter.search) f.search = tenantFilter.search;\n const statusMap: Record<string, GqlTenantStatus> = {\n active: 'ACTIVE' as GqlTenantStatus,\n suspended: 'SUSPENDED' as GqlTenantStatus,\n deleted: 'DELETED' as GqlTenantStatus,\n };\n if (tenantFilter.status !== 'all' && statusMap[tenantFilter.status]) {\n f.status = statusMap[tenantFilter.status];\n }\n return f;\n }, [tenantFilter.search, tenantFilter.status]);\n\n const skipQueries = permissionsLoading || !canViewTenants;\n\n // Query tenants from backend\n const { data, loading, error, refetch } = useListTenantsQuery({\n variables: {\n filter: gqlFilter,\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n // Mutations\n const [createTenant, { loading: creating }] = useCreateTenantMutation({\n onCompleted: () => {\n void refetch();\n },\n });\n const [updateTenant, { loading: updating }] = useUpdateTenantMutation({\n onCompleted: () => {\n void refetch();\n },\n });\n const [suspendTenant] = useSuspendTenantMutation({\n onCompleted: () => {\n void refetch();\n },\n });\n const [activateTenant] = useActivateTenantMutation({\n onCompleted: () => {\n void refetch();\n },\n });\n\n const tenants: Tenant[] = useMemo(() => (data?.tenants?.items ?? []).map(mapTenantToUi), [data]);\n\n const total = data?.tenants?.total ?? 0;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n const handleCreateTenant = useCallback(() => {\n setEditingTenant(undefined);\n setFormOpen(true);\n }, []);\n\n const handleEditTenant = useCallback((tenant: Tenant) => {\n setEditingTenant(tenant);\n setFormOpen(true);\n }, []);\n\n const handleSuspendTenant = useCallback(\n async (tenant: Tenant) => {\n try {\n await suspendTenant({ variables: { id: tenant.id } });\n emitTenantLifecycle(events, 'tenant.tenant.suspended', {\n tenantId: tenant.id,\n tenantSlug: tenant.slug,\n status: 'SUSPENDED',\n });\n toast.success(\n tr('admin.tenants.toast.suspendedTitle', 'Tenant suspended'),\n tr('admin.tenants.toast.suspendedDescription', '{{name}} has been suspended.', {\n name: tenant.name,\n })\n );\n } catch {\n toast.error(\n tr('admin.tenants.toast.errorTitle', 'Error'),\n tr('admin.tenants.toast.suspendFailed', 'Failed to suspend tenant.')\n );\n }\n },\n [suspendTenant, toast, tr, events]\n );\n\n const handleActivateTenant = useCallback(\n async (tenant: Tenant) => {\n try {\n await activateTenant({ variables: { id: tenant.id } });\n emitTenantLifecycle(events, 'tenant.tenant.activated', {\n tenantId: tenant.id,\n tenantSlug: tenant.slug,\n status: 'ACTIVE',\n });\n toast.success(\n tr('admin.tenants.toast.activatedTitle', 'Tenant activated'),\n tr('admin.tenants.toast.activatedDescription', '{{name}} has been activated.', {\n name: tenant.name,\n })\n );\n } catch {\n toast.error(\n tr('admin.tenants.toast.errorTitle', 'Error'),\n tr('admin.tenants.toast.activateFailed', 'Failed to activate tenant.')\n );\n }\n },\n [activateTenant, toast, tr, events]\n );\n\n const handleSaveTenant = useCallback(\n async (data: CreateTenantInput | UpdateTenantInput) => {\n const isEditing = 'id' in data && data.id;\n try {\n if (isEditing) {\n const updated = await updateTenant({\n variables: {\n id: (data as UpdateTenantInput).id,\n input: {\n name: data.name,\n customDomain: data.domain,\n metadata: {\n ownerEmail: data.ownerEmail,\n billingEmail: data.billingEmail,\n plan: data.plan,\n } as Record<string, unknown>,\n },\n },\n });\n const updatedId = updated.data?.updateTenant?.id ?? (data as UpdateTenantInput).id;\n // Use the slug from the persisted record. The update mutation does\n // not accept slug as input, so reading it from the form would emit\n // a value that may not match what the backend actually has.\n const persistedSlug = updated.data?.updateTenant?.slug;\n emitTenantLifecycle(events, 'tenant.tenant.updated', {\n tenantId: updatedId,\n tenantSlug: persistedSlug,\n });\n toast.success(\n tr('admin.tenants.toast.updatedTitle', 'Tenant updated'),\n tr('admin.tenants.toast.updatedDescription', '{{name}} has been updated.', {\n name: data.name ?? '',\n })\n );\n } else {\n const created = await createTenant({\n variables: {\n input: {\n name: data.name!,\n slug: data.slug!,\n customDomain: data.domain,\n metadata: {\n ownerEmail: data.ownerEmail,\n billingEmail: data.billingEmail,\n plan: data.plan,\n } as Record<string, unknown>,\n },\n },\n });\n const createdRecord = created.data?.createTenant;\n if (createdRecord?.id) {\n // Slug from the server-confirmed record so listeners never see a\n // form value the backend rejected (e.g. uniqueness collision).\n emitTenantLifecycle(events, 'tenant.tenant.created', {\n tenantId: createdRecord.id,\n tenantSlug: createdRecord.slug,\n });\n }\n toast.success(\n tr('admin.tenants.toast.createdTitle', 'Tenant created'),\n tr('admin.tenants.toast.createdDescription', '{{name}} has been created.', {\n name: data.name ?? '',\n })\n );\n }\n setFormOpen(false);\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Unknown error';\n toast.error(\n isEditing\n ? tr('admin.tenants.toast.updateFailedTitle', 'Update failed')\n : tr('admin.tenants.toast.createFailedTitle', 'Create failed'),\n msg\n );\n // Rethrow so TenantForm.onSubmit's await rejects and the modal stays\n // open with the user's input intact. Symmetric to the rethrow already\n // applied in TenantFormRoutePage.handleSave.\n throw err;\n }\n },\n [createTenant, updateTenant, toast, tr, events]\n );\n\n const handleRowClick = (tenant: Tenant) => {\n navigate(joinPath(basePath, `tenants/${tenant.id}`));\n };\n\n const getRowActions = useCallback(\n (tenant: Tenant): RowAction[] => {\n const actions = createRowActions({\n onView: () => navigate(joinPath(basePath, `tenants/${tenant.id}`)),\n onEdit: canManageTenants && editFlagEnabled ? () => handleEditTenant(tenant) : undefined,\n });\n if (canManageTenants) {\n actions.push({\n key: tenant.status === 'active' ? 'suspend' : 'activate',\n label:\n tenant.status === 'active'\n ? tr('admin.tenants.actions.suspend', 'Suspend')\n : tr('admin.tenants.actions.activate', 'Activate'),\n separator: true,\n onClick:\n tenant.status === 'active'\n ? () => handleSuspendTenant(tenant)\n : () => handleActivateTenant(tenant),\n });\n }\n return actions;\n },\n [\n navigate,\n basePath,\n canManageTenants,\n editFlagEnabled,\n handleEditTenant,\n handleSuspendTenant,\n handleActivateTenant,\n tr,\n ]\n );\n\n const columns = [\n {\n key: 'name',\n header: tr('admin.tenants.table.tenant', 'Tenant'),\n sortable: true,\n render: (tenant: Tenant) => (\n <div className=\"flex items-center gap-3\">\n <Avatar fallback={tenant.name.slice(0, 2).toUpperCase()} size=\"sm\" />\n <div className=\"min-w-0\">\n <div className=\"truncate font-medium text-text-primary\">{tenant.name}</div>\n <div className=\"truncate text-xs text-text-secondary\">{tenant.slug}</div>\n </div>\n </div>\n ),\n },\n {\n key: 'status',\n header: tr('admin.tenants.table.status', 'Status'),\n width: '120px',\n render: (tenant: Tenant) => <TenantStatusBadge status={tenant.status} />,\n },\n {\n key: 'plan',\n header: tr('admin.tenants.table.plan', 'Plan'),\n width: '100px',\n },\n {\n key: 'userCount',\n header: tr('admin.tenants.table.users', 'Users'),\n width: '80px',\n align: 'right' as const,\n sortable: true,\n render: (tenant: Tenant) => formatNumber(tenant.userCount),\n },\n {\n key: 'storageUsed',\n header: tr('admin.tenants.table.storage', 'Storage'),\n width: '100px',\n align: 'right' as const,\n render: (tenant: Tenant) => formatBytes(tenant.storageUsed),\n },\n {\n key: 'createdAt',\n header: tr('admin.tenants.table.created', 'Created'),\n width: '120px',\n sortable: true,\n render: (tenant: Tenant) => formatDate(tenant.createdAt),\n },\n {\n key: 'actions',\n header: '',\n width: '50px',\n align: 'right' as const,\n render: (tenant: Tenant) => (\n <RowActions\n actions={getRowActions(tenant)}\n ariaLabel={tr('admin.tenants.table.actionsFor', 'Actions for {{name}}', {\n name: tenant.name,\n })}\n />\n ),\n },\n ];\n\n return (\n <PermissionGuard\n allowed={canViewTenants}\n isLoading={permissionsLoading}\n pageTitle={tr('admin.tenants.title', 'Tenants')}\n denyMessage={tr('admin.tenants.denyMessage', \"You don't have permission to view tenants.\")}\n >\n <PageLayout\n title={tr('admin.tenants.title', 'Tenants')}\n description={\n total\n ? tr(\n 'admin.tenants.descriptionWithTotal',\n 'Manage all tenants on the platform · {{total}} total',\n { total }\n )\n : tr('admin.tenants.description', 'Manage all tenants on the platform')\n }\n actions={\n canManageTenants && createFlagEnabled ? (\n <Button onClick={handleCreateTenant} disabled={creating || updating}>\n <Plus className=\"mr-2 h-4 w-4\" />\n {tr('admin.tenants.actions.createTenant', 'Create Tenant')}\n </Button>\n ) : undefined\n }\n >\n <div className=\"space-y-4\">\n {/* Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"relative max-w-sm flex-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder={tr('admin.tenants.searchPlaceholder', 'Search tenants...')}\n value={tenantFilter.search}\n onChange={(e) => {\n setPage(1);\n updateTenantFilter({ search: e.target.value });\n }}\n className=\"pl-9\"\n />\n </div>\n <div className=\"flex items-center gap-2\">\n <select\n value={tenantFilter.status}\n onChange={(e) => {\n setPage(1);\n updateTenantFilter({ status: e.target.value as TenantFilter['status'] });\n }}\n className=\"h-10 rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2\"\n >\n <option value=\"all\">{tr('admin.tenants.filters.status.all', 'All Status')}</option>\n <option value=\"active\">\n {tr('admin.tenants.filters.status.active', 'Active')}\n </option>\n <option value=\"suspended\">\n {tr('admin.tenants.filters.status.suspended', 'Suspended')}\n </option>\n <option value=\"deleted\">\n {tr('admin.tenants.filters.status.deleted', 'Deleted')}\n </option>\n </select>\n {(tenantFilter.search || tenantFilter.status !== 'all') && (\n <Button\n variant=\"ghost\"\n onClick={() => {\n setPage(1);\n resetTenantFilter();\n }}\n >\n {tr('admin.tenants.filters.clear', 'Clear')}\n </Button>\n )}\n </div>\n </div>\n\n {/* Error state */}\n {error && (\n <div className=\"rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n {tr('admin.tenants.error.loadFailed', 'Failed to load tenants')}: {error.message}\n </div>\n )}\n\n {/* Table */}\n <DataTable\n columns={columns}\n data={tenants}\n keyExtractor={(t) => t.id}\n onRowClick={handleRowClick}\n sortBy={tenantFilter.sortBy}\n sortOrder={tenantFilter.sortOrder}\n onSort={(column, order) =>\n updateTenantFilter({\n sortBy: column as TenantFilter['sortBy'],\n sortOrder: order,\n })\n }\n loading={loading}\n emptyState={\n <EmptyState\n icon={<Building2 className=\"h-8 w-8\" />}\n title={tr('admin.tenants.empty.title', 'No tenants found')}\n description={tr(\n 'admin.tenants.empty.description',\n 'Try adjusting your search or filters'\n )}\n action={\n <Button\n variant=\"outline\"\n onClick={() => {\n setPage(1);\n resetTenantFilter();\n }}\n >\n {tr('admin.tenants.empty.clearFilters', 'Clear filters')}\n </Button>\n }\n />\n }\n />\n\n {/* Pagination */}\n {total > PAGE_SIZE && (\n <Pagination\n page={page}\n totalPages={totalPages}\n totalItems={total}\n pageSize={PAGE_SIZE}\n onPageChange={setPage}\n />\n )}\n </div>\n\n {/* Tenant Form */}\n <TenantForm\n open={formOpen}\n onClose={() => setFormOpen(false)}\n tenant={editingTenant}\n onSave={handleSaveTenant}\n />\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TenantsListPage.js","names":[],"sources":["../../../src/admin/pages/TenantsListPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { Plus, Search, Building2 } from 'lucide-react';\nimport { useAdmin } from '../providers/AdminProvider';\nimport { useAdminStore } from '../store/adminStore';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport {\n PageLayout,\n Button,\n Input,\n DataTable,\n Pagination,\n TenantStatusBadge,\n EmptyState,\n Avatar,\n TenantForm,\n RowActions,\n createRowActions,\n useToast,\n PermissionGuard,\n} from '../components';\nimport { joinPath } from '../utils/navigation';\nimport { formatDate, formatNumber, formatBytes } from '../utils/formatters';\nimport { mapTenantToUi } from '../utils/mappers';\nimport type { Tenant, TenantFilter } from '../types';\nimport type { CreateTenantInput, UpdateTenantInput } from '../schemas';\nimport {\n useListTenantsQuery,\n useCreateTenantMutation,\n useUpdateTenantMutation,\n useSuspendTenantMutation,\n useActivateTenantMutation,\n} from '../../generated/global-operations';\nimport type { RowAction } from '../components/RowActions';\nimport type { TenantStatus as GqlTenantStatus } from '../../generated/global-types';\nimport { PagePurpose, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useFeatureFlag } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\nimport { tWithFallback } from '../utils/i18n';\nimport { emitTenantLifecycle } from '../utils/tenantEvents';\n\nconst PAGE_SIZE = 20;\n\n/**\n * Tenants list page\n */\nexport function TenantsListPage() {\n const { canViewTenants, canManageTenants, isLoading: permissionsLoading } = useAdminPermissions();\n\n const navigate = useNavigate();\n const { basePath = '' } = useAdmin();\n const { tenantFilter, updateTenantFilter, resetTenantFilter } = useAdminStore();\n const toast = useToast();\n const { t } = useI18n();\n // Page-level feature flags. Default-allow keeps prior behavior intact.\n // Both list-page entry points (the Create CTA and the row Edit action that\n // opens the local TenantForm modal) have to honor the flags — otherwise\n // turning the edit flag off would still let an admin edit via the modal\n // even though the route-level gate denies /admin/tenants/:id/edit.\n const createFlagEnabled = useFeatureFlag('admin.tenant.create', true);\n const editFlagEnabled = useFeatureFlag('admin.tenant.edit', true);\n const events = useEventBus();\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params),\n [t]\n );\n\n const [page, setPage] = useState(1);\n\n // Form state\n const [formOpen, setFormOpen] = useState(false);\n const [editingTenant, setEditingTenant] = useState<Tenant | undefined>(undefined);\n\n // Build filter for GraphQL — only pass supported fields\n const gqlFilter = useMemo(() => {\n const f: { search?: string; status?: GqlTenantStatus } = {};\n if (tenantFilter.search) f.search = tenantFilter.search;\n const statusMap: Record<string, GqlTenantStatus> = {\n active: 'ACTIVE' as GqlTenantStatus,\n suspended: 'SUSPENDED' as GqlTenantStatus,\n deleted: 'DELETED' as GqlTenantStatus,\n };\n if (tenantFilter.status !== 'all' && statusMap[tenantFilter.status]) {\n f.status = statusMap[tenantFilter.status];\n }\n return f;\n }, [tenantFilter.search, tenantFilter.status]);\n\n const skipQueries = permissionsLoading || !canViewTenants;\n\n // Query tenants from backend\n const { data, loading, error, refetch } = useListTenantsQuery({\n variables: {\n filter: gqlFilter,\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n // Mutations\n const [createTenant, { loading: creating }] = useCreateTenantMutation({\n onCompleted: () => {\n void refetch();\n },\n });\n const [updateTenant, { loading: updating }] = useUpdateTenantMutation({\n onCompleted: () => {\n void refetch();\n },\n });\n const [suspendTenant] = useSuspendTenantMutation({\n onCompleted: () => {\n void refetch();\n },\n });\n const [activateTenant] = useActivateTenantMutation({\n onCompleted: () => {\n void refetch();\n },\n });\n\n const tenants: Tenant[] = useMemo(() => (data?.tenants?.items ?? []).map(mapTenantToUi), [data]);\n\n const total = data?.tenants?.total ?? 0;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n const handleCreateTenant = useCallback(() => {\n setEditingTenant(undefined);\n setFormOpen(true);\n }, []);\n\n const handleEditTenant = useCallback((tenant: Tenant) => {\n setEditingTenant(tenant);\n setFormOpen(true);\n }, []);\n\n const handleSuspendTenant = useCallback(\n async (tenant: Tenant) => {\n try {\n await suspendTenant({ variables: { id: tenant.id } });\n emitTenantLifecycle(events, 'tenant.tenant.suspended', {\n tenantId: tenant.id,\n tenantSlug: tenant.slug,\n status: 'SUSPENDED',\n });\n toast.success(\n tr('admin.tenants.toast.suspendedTitle', 'Tenant suspended'),\n tr('admin.tenants.toast.suspendedDescription', '{{name}} has been suspended.', {\n name: tenant.name,\n })\n );\n } catch {\n toast.error(\n tr('admin.tenants.toast.errorTitle', 'Error'),\n tr('admin.tenants.toast.suspendFailed', 'Failed to suspend tenant.')\n );\n }\n },\n [suspendTenant, toast, tr, events]\n );\n\n const handleActivateTenant = useCallback(\n async (tenant: Tenant) => {\n try {\n await activateTenant({ variables: { id: tenant.id } });\n emitTenantLifecycle(events, 'tenant.tenant.activated', {\n tenantId: tenant.id,\n tenantSlug: tenant.slug,\n status: 'ACTIVE',\n });\n toast.success(\n tr('admin.tenants.toast.activatedTitle', 'Tenant activated'),\n tr('admin.tenants.toast.activatedDescription', '{{name}} has been activated.', {\n name: tenant.name,\n })\n );\n } catch {\n toast.error(\n tr('admin.tenants.toast.errorTitle', 'Error'),\n tr('admin.tenants.toast.activateFailed', 'Failed to activate tenant.')\n );\n }\n },\n [activateTenant, toast, tr, events]\n );\n\n const handleSaveTenant = useCallback(\n async (data: CreateTenantInput | UpdateTenantInput) => {\n const isEditing = 'id' in data && data.id;\n try {\n if (isEditing) {\n const updated = await updateTenant({\n variables: {\n id: (data as UpdateTenantInput).id,\n input: {\n name: data.name,\n customDomain: data.domain,\n metadata: {\n ownerEmail: data.ownerEmail,\n billingEmail: data.billingEmail,\n plan: data.plan,\n } as Record<string, unknown>,\n },\n },\n });\n const updatedId = updated.data?.updateTenant?.id ?? (data as UpdateTenantInput).id;\n // Use the slug from the persisted record. The update mutation does\n // not accept slug as input, so reading it from the form would emit\n // a value that may not match what the backend actually has.\n const persistedSlug = updated.data?.updateTenant?.slug;\n emitTenantLifecycle(events, 'tenant.tenant.updated', {\n tenantId: updatedId,\n tenantSlug: persistedSlug,\n });\n toast.success(\n tr('admin.tenants.toast.updatedTitle', 'Tenant updated'),\n tr('admin.tenants.toast.updatedDescription', '{{name}} has been updated.', {\n name: data.name ?? '',\n })\n );\n } else {\n const created = await createTenant({\n variables: {\n input: {\n name: data.name!,\n slug: data.slug!,\n customDomain: data.domain,\n metadata: {\n ownerEmail: data.ownerEmail,\n billingEmail: data.billingEmail,\n plan: data.plan,\n } as Record<string, unknown>,\n },\n },\n });\n const createdRecord = created.data?.createTenant;\n if (createdRecord?.id) {\n // Slug from the server-confirmed record so listeners never see a\n // form value the backend rejected (e.g. uniqueness collision).\n emitTenantLifecycle(events, 'tenant.tenant.created', {\n tenantId: createdRecord.id,\n tenantSlug: createdRecord.slug,\n });\n }\n toast.success(\n tr('admin.tenants.toast.createdTitle', 'Tenant created'),\n tr('admin.tenants.toast.createdDescription', '{{name}} has been created.', {\n name: data.name ?? '',\n })\n );\n }\n setFormOpen(false);\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Unknown error';\n toast.error(\n isEditing\n ? tr('admin.tenants.toast.updateFailedTitle', 'Update failed')\n : tr('admin.tenants.toast.createFailedTitle', 'Create failed'),\n msg\n );\n // Rethrow so TenantForm.onSubmit's await rejects and the modal stays\n // open with the user's input intact. Symmetric to the rethrow already\n // applied in TenantFormRoutePage.handleSave.\n throw err;\n }\n },\n [createTenant, updateTenant, toast, tr, events]\n );\n\n const handleRowClick = (tenant: Tenant) => {\n navigate(joinPath(basePath, `tenants/${tenant.id}`));\n };\n\n const getRowActions = useCallback(\n (tenant: Tenant): RowAction[] => {\n const actions = createRowActions({\n onView: () => navigate(joinPath(basePath, `tenants/${tenant.id}`)),\n onEdit: canManageTenants && editFlagEnabled ? () => handleEditTenant(tenant) : undefined,\n });\n if (canManageTenants) {\n actions.push({\n key: tenant.status === 'active' ? 'suspend' : 'activate',\n label:\n tenant.status === 'active'\n ? tr('admin.tenants.actions.suspend', 'Suspend')\n : tr('admin.tenants.actions.activate', 'Activate'),\n separator: true,\n onClick:\n tenant.status === 'active'\n ? () => handleSuspendTenant(tenant)\n : () => handleActivateTenant(tenant),\n });\n }\n return actions;\n },\n [\n navigate,\n basePath,\n canManageTenants,\n editFlagEnabled,\n handleEditTenant,\n handleSuspendTenant,\n handleActivateTenant,\n tr,\n ]\n );\n\n const columns = [\n {\n key: 'name',\n header: tr('admin.tenants.table.tenant', 'Tenant'),\n sortable: true,\n render: (tenant: Tenant) => (\n <div className=\"flex items-center gap-3\">\n <Avatar fallback={tenant.name.slice(0, 2).toUpperCase()} size=\"sm\" />\n <div className=\"min-w-0\">\n <div className=\"truncate font-medium text-text-primary\">{tenant.name}</div>\n <div className=\"truncate text-xs text-text-secondary\">{tenant.slug}</div>\n </div>\n </div>\n ),\n },\n {\n key: 'status',\n header: tr('admin.tenants.table.status', 'Status'),\n width: '120px',\n render: (tenant: Tenant) => <TenantStatusBadge status={tenant.status} />,\n },\n {\n key: 'plan',\n header: tr('admin.tenants.table.plan', 'Plan'),\n width: '100px',\n },\n {\n key: 'userCount',\n header: tr('admin.tenants.table.users', 'Users'),\n width: '80px',\n align: 'right' as const,\n sortable: true,\n render: (tenant: Tenant) => formatNumber(tenant.userCount),\n },\n {\n key: 'storageUsed',\n header: tr('admin.tenants.table.storage', 'Storage'),\n width: '100px',\n align: 'right' as const,\n render: (tenant: Tenant) => formatBytes(tenant.storageUsed),\n },\n {\n key: 'createdAt',\n header: tr('admin.tenants.table.created', 'Created'),\n width: '120px',\n sortable: true,\n render: (tenant: Tenant) => formatDate(tenant.createdAt),\n },\n {\n key: 'actions',\n header: '',\n width: '50px',\n align: 'right' as const,\n render: (tenant: Tenant) => (\n <RowActions\n actions={getRowActions(tenant)}\n ariaLabel={tr('admin.tenants.table.actionsFor', 'Actions for {{name}}', {\n name: tenant.name,\n })}\n />\n ),\n },\n ];\n\n return (\n <PermissionGuard\n allowed={canViewTenants}\n isLoading={permissionsLoading}\n pageTitle={tr('admin.tenants.title', 'Tenants')}\n denyMessage={tr('admin.tenants.denyMessage', \"You don't have permission to view tenants.\")}\n >\n <PageLayout\n title={tr('admin.tenants.title', 'Tenants')}\n gradientWord={tr('admin.tenants.title', 'Tenants')}\n description={\n total\n ? tr(\n 'admin.tenants.descriptionWithTotal',\n 'Manage all tenants on the platform · {{total}} total',\n { total }\n )\n : tr('admin.tenants.description', 'Manage all tenants on the platform')\n }\n actions={\n canManageTenants && createFlagEnabled ? (\n <Button onClick={handleCreateTenant} disabled={creating || updating}>\n <Plus className=\"mr-2 h-4 w-4\" />\n {tr('admin.tenants.actions.createTenant', 'Create Tenant')}\n </Button>\n ) : undefined\n }\n >\n <div className=\"space-y-4\">\n <PagePurpose>\n {tr(\n 'admin.tenants.purpose',\n 'Tenants are the isolated customer accounts on the platform — each with its own users, data, plan and billing. Use this page to find a tenant, onboard a new one, and suspend or reactivate access when a customer churns or returns.'\n )}\n </PagePurpose>\n\n {/* Emphasis: the headline tenant count — the page's most important stat. */}\n <EmphasisPanel className=\"flex items-center gap-4 p-5\">\n <div className=\"flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-action-primary-bg/15 text-action-primary-bg\">\n <Building2 className=\"h-6 w-6\" />\n </div>\n <div>\n <div className=\"text-3xl font-bold tabular-nums text-text-primary\">\n {formatNumber(total)}\n </div>\n <p className=\"text-sm text-text-secondary\">\n {tr('admin.tenants.emphasis.label', 'tenants on the platform')}\n </p>\n </div>\n </EmphasisPanel>\n\n {/* Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"relative max-w-sm flex-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder={tr('admin.tenants.searchPlaceholder', 'Search tenants...')}\n value={tenantFilter.search}\n onChange={(e) => {\n setPage(1);\n updateTenantFilter({ search: e.target.value });\n }}\n className=\"pl-9\"\n />\n </div>\n <div className=\"flex items-center gap-2\">\n <select\n value={tenantFilter.status}\n onChange={(e) => {\n setPage(1);\n updateTenantFilter({ status: e.target.value as TenantFilter['status'] });\n }}\n className=\"h-10 rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2\"\n >\n <option value=\"all\">{tr('admin.tenants.filters.status.all', 'All Status')}</option>\n <option value=\"active\">\n {tr('admin.tenants.filters.status.active', 'Active')}\n </option>\n <option value=\"suspended\">\n {tr('admin.tenants.filters.status.suspended', 'Suspended')}\n </option>\n <option value=\"deleted\">\n {tr('admin.tenants.filters.status.deleted', 'Deleted')}\n </option>\n </select>\n {(tenantFilter.search || tenantFilter.status !== 'all') && (\n <Button\n variant=\"ghost\"\n onClick={() => {\n setPage(1);\n resetTenantFilter();\n }}\n >\n {tr('admin.tenants.filters.clear', 'Clear')}\n </Button>\n )}\n </div>\n </div>\n\n {/* Error state */}\n {error && (\n <div className=\"rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n {tr('admin.tenants.error.loadFailed', 'Failed to load tenants')}: {error.message}\n </div>\n )}\n\n {/* Table */}\n <DataTable\n columns={columns}\n data={tenants}\n keyExtractor={(t) => t.id}\n onRowClick={handleRowClick}\n sortBy={tenantFilter.sortBy}\n sortOrder={tenantFilter.sortOrder}\n onSort={(column, order) =>\n updateTenantFilter({\n sortBy: column as TenantFilter['sortBy'],\n sortOrder: order,\n })\n }\n loading={loading}\n emptyState={\n <EmptyState\n illustration=\"empty-search\"\n icon={<Building2 className=\"h-8 w-8\" />}\n title={tr('admin.tenants.empty.title', 'No tenants found')}\n description={tr(\n 'admin.tenants.empty.description',\n 'Try adjusting your search or filters'\n )}\n action={\n <Button\n variant=\"outline\"\n onClick={() => {\n setPage(1);\n resetTenantFilter();\n }}\n >\n {tr('admin.tenants.empty.clearFilters', 'Clear filters')}\n </Button>\n }\n />\n }\n />\n\n {/* Pagination */}\n {total > PAGE_SIZE && (\n <Pagination\n page={page}\n totalPages={totalPages}\n totalItems={total}\n pageSize={PAGE_SIZE}\n onPageChange={setPage}\n />\n )}\n </div>\n\n {/* Tenant Form */}\n <TenantForm\n open={formOpen}\n onClose={() => setFormOpen(false)}\n tenant={editingTenant}\n onSave={handleSaveTenant}\n />\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}