@burdenoff/microfe-admin 2026.626.1 → 2026.720.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 (32) hide show
  1. package/dist/admin/AdminRoot.js +2 -2
  2. package/dist/admin/AdminRoot.js.map +1 -1
  3. package/dist/admin/AdminRoutes.js +130 -78
  4. package/dist/admin/AdminRoutes.js.map +1 -1
  5. package/dist/admin/components/ConfirmDialog.js.map +1 -1
  6. package/dist/admin/components/DataTable.js.map +1 -1
  7. package/dist/admin/components/MarkdownEditor.js +116 -0
  8. package/dist/admin/components/MarkdownEditor.js.map +1 -0
  9. package/dist/admin/components/index.js +1 -0
  10. package/dist/admin/constants/featureFlags.js +12 -0
  11. package/dist/admin/constants/featureFlags.js.map +1 -0
  12. package/dist/admin/pages/AuditLogsPage.js.map +1 -1
  13. package/dist/admin/pages/CommunicationsPage.js.map +1 -1
  14. package/dist/admin/pages/ContentPagesPage.js +197 -197
  15. package/dist/admin/pages/ContentPagesPage.js.map +1 -1
  16. package/dist/admin/pages/DashboardPage.js +5 -5
  17. package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
  18. package/dist/admin/pages/ReferralsPage.js.map +1 -1
  19. package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
  20. package/dist/admin/pages/SREPage.js.map +1 -1
  21. package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
  22. package/dist/admin/pages/SettingsPage.js +6 -6
  23. package/dist/admin/pages/SettingsPage.js.map +1 -1
  24. package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
  25. package/dist/admin/pages/TenantDetailPage.js +169 -157
  26. package/dist/admin/pages/TenantDetailPage.js.map +1 -1
  27. package/dist/admin/pages/TenantFormRoutePage.js +75 -74
  28. package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
  29. package/dist/admin/pages/TenantsListPage.js +169 -157
  30. package/dist/admin/pages/TenantsListPage.js.map +1 -1
  31. package/dist/admin/pages/UsersListPage.js +5 -5
  32. package/package.json +2 -2
@@ -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 { 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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"}
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';\nimport { TENANT_FEATURE_FLAGS } from '../constants/featureFlags';\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 managementFlagEnabled = useFeatureFlag(TENANT_FEATURE_FLAGS.management, true);\n const listFlagEnabled = useFeatureFlag(TENANT_FEATURE_FLAGS.list, true);\n const createFlagEnabled = useFeatureFlag(TENANT_FEATURE_FLAGS.create, true);\n const editFlagEnabled = useFeatureFlag(TENANT_FEATURE_FLAGS.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 pageEnabled = managementFlagEnabled && listFlagEnabled;\n const canLifecycleTenants = canManageTenants && managementFlagEnabled;\n const skipQueries = permissionsLoading || !canViewTenants || !pageEnabled;\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 (canLifecycleTenants) {\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 canLifecycleTenants,\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 if (!permissionsLoading && !canViewTenants) {\n return (\n <PermissionGuard\n allowed={false}\n isLoading={false}\n pageTitle={tr('admin.tenants.title', 'Tenants')}\n denyMessage={tr('admin.tenants.denyMessage', \"You don't have permission to view tenants.\")}\n >\n {null}\n </PermissionGuard>\n );\n }\n\n if (!pageEnabled) {\n return (\n <PageLayout\n title={tr('admin.tenants.featureDisabledTitle', 'Tenant management unavailable')}\n description={tr('admin.tenants.featureDisabled', 'This feature is currently disabled.')}\n >\n {null}\n </PageLayout>\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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"}
@@ -19,12 +19,12 @@ import { useCallback as b, useMemo as x, useState as S } from "react";
19
19
  import { Search as C, Users as w } from "lucide-react";
20
20
  import { useNavigate as T } from "react-router-dom";
21
21
  import { jsx as E, jsxs as D } from "react/jsx-runtime";
22
- import { EmphasisPanel as O, PagePurpose as k } from "@burdenoff/fe-libs/ui";
23
- import { useI18n as A } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
22
+ import { useI18n as O } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
23
+ import { EmphasisPanel as k, PagePurpose as A } from "@burdenoff/fe-libs/ui";
24
24
  //#region src/admin/pages/UsersListPage.tsx
25
25
  var j = 20;
26
26
  function M() {
27
- let { canViewUsers: M, canUpdateUsers: N, isLoading: P } = n(), F = T(), { basePath: I = "" } = t(), { userFilter: L, updateUserFilter: R } = v(), z = e(), { t: B } = A(), V = b((e, t, n) => f(B, e, t, n), [B]), [H, U] = S(1), W = x(() => {
27
+ let { canViewUsers: M, canUpdateUsers: N, isLoading: P } = n(), F = T(), { basePath: I = "" } = t(), { userFilter: L, updateUserFilter: R } = v(), z = e(), { t: B } = O(), V = b((e, t, n) => f(B, e, t, n), [B]), [H, U] = S(1), W = x(() => {
28
28
  let e = {};
29
29
  L.search && (e.search = L.search);
30
30
  let t = {
@@ -150,8 +150,8 @@ function M() {
150
150
  children: /* @__PURE__ */ D("div", {
151
151
  className: "space-y-4",
152
152
  children: [
153
- /* @__PURE__ */ E(k, { children: V("admin.users.purpose", "Every person with an account across all tenants lives here. Use this page to find someone by name or email, check when they last signed in, and enable or disable their access when they join, leave, or need to be locked out.") }),
154
- /* @__PURE__ */ D(O, {
153
+ /* @__PURE__ */ E(A, { children: V("admin.users.purpose", "Every person with an account across all tenants lives here. Use this page to find someone by name or email, check when they last signed in, and enable or disable their access when they join, leave, or need to be locked out.") }),
154
+ /* @__PURE__ */ D(k, {
155
155
  className: "flex items-center gap-4 p-5",
156
156
  children: [/* @__PURE__ */ E("div", {
157
157
  className: "flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-action-primary-bg/15 text-action-primary-bg",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-admin",
3
- "version": "2026.626.1",
3
+ "version": "2026.720.1",
4
4
  "description": "Burdenoff Admin Console Microfrontend - Platform administration and management interface",
5
5
  "type": "module",
6
6
  "files": [
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@apollo/client": "^4.0.11",
45
- "@burdenoff/fe-libs": "2026.625.5",
45
+ "@burdenoff/fe-libs": "2026.720.3",
46
46
  "@hookform/resolvers": "^5.1.1",
47
47
  "@tanstack/react-query": "^5.90.16",
48
48
  "clsx": "^2.1.1",