@burdenoff/microfe-admin 2026.624.1 → 2026.625.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/admin/components/DataTable.js +5 -5
  2. package/dist/admin/components/DataTable.js.map +1 -1
  3. package/dist/admin/components/MetricCard.js +40 -37
  4. package/dist/admin/components/MetricCard.js.map +1 -1
  5. package/dist/admin/components/PageLayout.js +28 -22
  6. package/dist/admin/components/PageLayout.js.map +1 -1
  7. package/dist/admin/components/ui.js +45 -38
  8. package/dist/admin/components/ui.js.map +1 -1
  9. package/dist/admin/pages/AuditLogsPage.js +2 -0
  10. package/dist/admin/pages/AuditLogsPage.js.map +1 -1
  11. package/dist/admin/pages/BillingPage.js +51 -43
  12. package/dist/admin/pages/BillingPage.js.map +1 -1
  13. package/dist/admin/pages/CommunicationsPage.js +3 -0
  14. package/dist/admin/pages/CommunicationsPage.js.map +1 -1
  15. package/dist/admin/pages/ContentPagesPage.js +2 -0
  16. package/dist/admin/pages/ContentPagesPage.js.map +1 -1
  17. package/dist/admin/pages/DashboardPage.js +7 -4
  18. package/dist/admin/pages/DashboardPage.js.map +1 -1
  19. package/dist/admin/pages/FeatureFlagsPage.js +2 -0
  20. package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
  21. package/dist/admin/pages/OrganizationsListPage.js +2 -0
  22. package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
  23. package/dist/admin/pages/ProductsPage.js +3 -0
  24. package/dist/admin/pages/ProductsPage.js.map +1 -1
  25. package/dist/admin/pages/ReferralsPage.js +4 -0
  26. package/dist/admin/pages/ReferralsPage.js.map +1 -1
  27. package/dist/admin/pages/RolesPermissionsPage.js +3 -0
  28. package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
  29. package/dist/admin/pages/SREPage.js +4 -0
  30. package/dist/admin/pages/SREPage.js.map +1 -1
  31. package/dist/admin/pages/SandboxSettingsPage.js +1 -0
  32. package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
  33. package/dist/admin/pages/SettingsPage.js +139 -127
  34. package/dist/admin/pages/SettingsPage.js.map +1 -1
  35. package/dist/admin/pages/SyncFailuresPage.js +3 -0
  36. package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
  37. package/dist/admin/pages/TenantsListPage.js +2 -0
  38. package/dist/admin/pages/TenantsListPage.js.map +1 -1
  39. package/dist/admin/pages/UsersListPage.js +2 -0
  40. package/dist/admin/pages/UsersListPage.js.map +1 -1
  41. package/dist/admin/pages/WaitlistPage.js +2 -0
  42. package/dist/admin/pages/WaitlistPage.js.map +1 -1
  43. package/dist/admin/pages/WorkflowsPage.js +3 -0
  44. package/dist/admin/pages/WorkflowsPage.js.map +1 -1
  45. package/dist/admin/pages/WorkspacesListPage.js +1 -0
  46. package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
  47. package/package.json +2 -2
@@ -71,6 +71,8 @@ function O() {
71
71
  children: /* @__PURE__ */ T(n, {
72
72
  title: M("admin.dashboard.title", "Dashboard"),
73
73
  description: M("admin.dashboard.description", "Platform overview and key metrics"),
74
+ gradientWord: M("admin.dashboard.title", "Dashboard"),
75
+ aurora: !0,
74
76
  children: /* @__PURE__ */ E("div", {
75
77
  className: "space-y-6",
76
78
  children: [/* @__PURE__ */ E(i, { children: [
@@ -78,25 +80,26 @@ function O() {
78
80
  title: M("admin.dashboard.metrics.totalTenants", "Total Tenants"),
79
81
  value: W.tenants,
80
82
  loading: q,
81
- icon: /* @__PURE__ */ T(v, { className: "h-5 w-5 text-blue-500" })
83
+ hero: !0,
84
+ icon: /* @__PURE__ */ T(v, { className: "h-5 w-5 text-action-primary-bg" })
82
85
  }),
83
86
  /* @__PURE__ */ T(r, {
84
87
  title: M("admin.dashboard.metrics.organizations", "Organizations"),
85
88
  value: W.organizations,
86
89
  loading: q,
87
- icon: /* @__PURE__ */ T(_, { className: "h-5 w-5 text-purple-500" })
90
+ icon: /* @__PURE__ */ T(_, { className: "h-5 w-5 text-text-secondary" })
88
91
  }),
89
92
  /* @__PURE__ */ T(r, {
90
93
  title: M("admin.dashboard.metrics.activeWorkspaces", "Active Workspaces"),
91
94
  value: W.workspaces,
92
95
  loading: q,
93
- icon: /* @__PURE__ */ T(x, { className: "h-5 w-5 text-green-500" })
96
+ icon: /* @__PURE__ */ T(x, { className: "h-5 w-5 text-status-success-text" })
94
97
  }),
95
98
  /* @__PURE__ */ T(r, {
96
99
  title: M("admin.dashboard.metrics.totalUsers", "Total Users"),
97
100
  value: W.users,
98
101
  loading: q,
99
- icon: /* @__PURE__ */ T(w, { className: "h-5 w-5 text-orange-500" })
102
+ icon: /* @__PURE__ */ T(w, { className: "h-5 w-5 text-text-secondary" })
100
103
  })
101
104
  ] }), /* @__PURE__ */ E("div", {
102
105
  className: "grid gap-6 lg:grid-cols-3",
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/admin/pages/DashboardPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n Building2,\n Users,\n Layout,\n Briefcase,\n Plus,\n UserPlus,\n FileText,\n HelpCircle,\n} from 'lucide-react';\nimport { useAdmin } from '../providers/AdminProvider';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport {\n PageLayout,\n MetricCard,\n MetricsGrid,\n ActivityFeed,\n QuickLinks,\n PermissionGuard,\n} from '../components';\nimport { joinPath } from '../utils/navigation';\nimport { mapActivityLogToUi } from '../utils/mappers';\nimport type { Activity } from '../types';\nimport {\n useGetDashboardTenantCountQuery,\n useGetDashboardOrganizationCountQuery,\n useGetDashboardActiveWorkspaceCountQuery,\n useGetDashboardUserCountQuery,\n useGetRecentActivityLogsQuery,\n} from '../../generated/global-operations';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\n\n/**\n * Dashboard page — platform overview with real-time metrics\n */\nexport function DashboardPage() {\n const { canViewDashboard, isLoading: permissionsLoading } = useAdminPermissions();\n const { basePath = '' } = useAdmin();\n const { t } = useI18n();\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params);\n\n const skipQueries = permissionsLoading || !canViewDashboard;\n\n const { data: tenantData, loading: tenantLoading } = useGetDashboardTenantCountQuery({\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n const { data: orgData, loading: orgLoading } = useGetDashboardOrganizationCountQuery({\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n const { data: workspaceData, loading: workspaceLoading } =\n useGetDashboardActiveWorkspaceCountQuery({\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n const { data: userData, loading: userLoading } = useGetDashboardUserCountQuery({\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n const { data: activityData, loading: activityLoading } = useGetRecentActivityLogsQuery({\n variables: { limit: 10 },\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n const metrics = {\n tenants: tenantData?.tenants?.total ?? 0,\n organizations: orgData?.allOrganizations?.total ?? 0,\n workspaces: workspaceData?.allWorkspaces?.total ?? 0,\n users: userData?.userCount ?? 0,\n };\n\n const recentActivity: Activity[] = useMemo(\n () => (activityData?.activityLogs?.nodes ?? []).map(mapActivityLogToUi),\n [activityData]\n );\n\n const quickLinks = [\n {\n to: joinPath(basePath, 'tenants'),\n label: tr('admin.dashboard.quickLinks.addTenant', 'Add Tenant'),\n icon: <Plus className=\"h-4 w-4\" />,\n description: tr('admin.dashboard.quickLinks.addTenantDescription', 'Create a new tenant'),\n },\n {\n to: joinPath(basePath, 'users'),\n label: tr('admin.dashboard.quickLinks.inviteUser', 'Invite User'),\n icon: <UserPlus className=\"h-4 w-4\" />,\n description: tr('admin.dashboard.quickLinks.inviteUserDescription', 'Invite a new user'),\n },\n {\n to: joinPath(basePath, 'audit'),\n label: tr('admin.dashboard.quickLinks.auditLogs', 'Audit Logs'),\n icon: <FileText className=\"h-4 w-4\" />,\n description: tr(\n 'admin.dashboard.quickLinks.auditLogsDescription',\n 'View security audit trail'\n ),\n },\n {\n to: joinPath(basePath, 'support'),\n label: tr('admin.dashboard.quickLinks.support', 'Support'),\n icon: <HelpCircle className=\"h-4 w-4\" />,\n description: tr('admin.dashboard.quickLinks.supportDescription', 'Manage support tickets'),\n },\n ];\n\n const isLoading = tenantLoading || orgLoading || workspaceLoading || userLoading;\n\n return (\n <PermissionGuard\n allowed={canViewDashboard}\n isLoading={permissionsLoading}\n pageTitle={tr('admin.dashboard.title', 'Dashboard')}\n denyMessage={tr(\n 'admin.dashboard.denyMessage',\n \"You don't have permission to view the admin dashboard.\"\n )}\n >\n <PageLayout\n title={tr('admin.dashboard.title', 'Dashboard')}\n description={tr('admin.dashboard.description', 'Platform overview and key metrics')}\n >\n <div className=\"space-y-6\">\n {/* Metrics Grid */}\n <MetricsGrid>\n <MetricCard\n title={tr('admin.dashboard.metrics.totalTenants', 'Total Tenants')}\n value={metrics.tenants}\n loading={isLoading}\n icon={<Building2 className=\"h-5 w-5 text-blue-500\" />}\n />\n <MetricCard\n title={tr('admin.dashboard.metrics.organizations', 'Organizations')}\n value={metrics.organizations}\n loading={isLoading}\n icon={<Briefcase className=\"h-5 w-5 text-purple-500\" />}\n />\n <MetricCard\n title={tr('admin.dashboard.metrics.activeWorkspaces', 'Active Workspaces')}\n value={metrics.workspaces}\n loading={isLoading}\n icon={<Layout className=\"h-5 w-5 text-green-500\" />}\n />\n <MetricCard\n title={tr('admin.dashboard.metrics.totalUsers', 'Total Users')}\n value={metrics.users}\n loading={isLoading}\n icon={<Users className=\"h-5 w-5 text-orange-500\" />}\n />\n </MetricsGrid>\n\n {/* Content Area */}\n <div className=\"grid gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <ActivityFeed activities={recentActivity} loading={activityLoading} />\n </div>\n <div className=\"space-y-4\">\n <QuickLinks links={quickLinks} />\n </div>\n </div>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/admin/pages/DashboardPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n Building2,\n Users,\n Layout,\n Briefcase,\n Plus,\n UserPlus,\n FileText,\n HelpCircle,\n} from 'lucide-react';\nimport { useAdmin } from '../providers/AdminProvider';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport {\n PageLayout,\n MetricCard,\n MetricsGrid,\n ActivityFeed,\n QuickLinks,\n PermissionGuard,\n} from '../components';\nimport { joinPath } from '../utils/navigation';\nimport { mapActivityLogToUi } from '../utils/mappers';\nimport type { Activity } from '../types';\nimport {\n useGetDashboardTenantCountQuery,\n useGetDashboardOrganizationCountQuery,\n useGetDashboardActiveWorkspaceCountQuery,\n useGetDashboardUserCountQuery,\n useGetRecentActivityLogsQuery,\n} from '../../generated/global-operations';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\n\n/**\n * Dashboard page — platform overview with real-time metrics\n */\nexport function DashboardPage() {\n const { canViewDashboard, isLoading: permissionsLoading } = useAdminPermissions();\n const { basePath = '' } = useAdmin();\n const { t } = useI18n();\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params);\n\n const skipQueries = permissionsLoading || !canViewDashboard;\n\n const { data: tenantData, loading: tenantLoading } = useGetDashboardTenantCountQuery({\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n const { data: orgData, loading: orgLoading } = useGetDashboardOrganizationCountQuery({\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n const { data: workspaceData, loading: workspaceLoading } =\n useGetDashboardActiveWorkspaceCountQuery({\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n const { data: userData, loading: userLoading } = useGetDashboardUserCountQuery({\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n const { data: activityData, loading: activityLoading } = useGetRecentActivityLogsQuery({\n variables: { limit: 10 },\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n const metrics = {\n tenants: tenantData?.tenants?.total ?? 0,\n organizations: orgData?.allOrganizations?.total ?? 0,\n workspaces: workspaceData?.allWorkspaces?.total ?? 0,\n users: userData?.userCount ?? 0,\n };\n\n const recentActivity: Activity[] = useMemo(\n () => (activityData?.activityLogs?.nodes ?? []).map(mapActivityLogToUi),\n [activityData]\n );\n\n const quickLinks = [\n {\n to: joinPath(basePath, 'tenants'),\n label: tr('admin.dashboard.quickLinks.addTenant', 'Add Tenant'),\n icon: <Plus className=\"h-4 w-4\" />,\n description: tr('admin.dashboard.quickLinks.addTenantDescription', 'Create a new tenant'),\n },\n {\n to: joinPath(basePath, 'users'),\n label: tr('admin.dashboard.quickLinks.inviteUser', 'Invite User'),\n icon: <UserPlus className=\"h-4 w-4\" />,\n description: tr('admin.dashboard.quickLinks.inviteUserDescription', 'Invite a new user'),\n },\n {\n to: joinPath(basePath, 'audit'),\n label: tr('admin.dashboard.quickLinks.auditLogs', 'Audit Logs'),\n icon: <FileText className=\"h-4 w-4\" />,\n description: tr(\n 'admin.dashboard.quickLinks.auditLogsDescription',\n 'View security audit trail'\n ),\n },\n {\n to: joinPath(basePath, 'support'),\n label: tr('admin.dashboard.quickLinks.support', 'Support'),\n icon: <HelpCircle className=\"h-4 w-4\" />,\n description: tr('admin.dashboard.quickLinks.supportDescription', 'Manage support tickets'),\n },\n ];\n\n const isLoading = tenantLoading || orgLoading || workspaceLoading || userLoading;\n\n return (\n <PermissionGuard\n allowed={canViewDashboard}\n isLoading={permissionsLoading}\n pageTitle={tr('admin.dashboard.title', 'Dashboard')}\n denyMessage={tr(\n 'admin.dashboard.denyMessage',\n \"You don't have permission to view the admin dashboard.\"\n )}\n >\n <PageLayout\n title={tr('admin.dashboard.title', 'Dashboard')}\n description={tr('admin.dashboard.description', 'Platform overview and key metrics')}\n gradientWord={tr('admin.dashboard.title', 'Dashboard')}\n aurora\n >\n <div className=\"space-y-6\">\n {/* Metrics Grid */}\n <MetricsGrid>\n <MetricCard\n title={tr('admin.dashboard.metrics.totalTenants', 'Total Tenants')}\n value={metrics.tenants}\n loading={isLoading}\n hero\n icon={<Building2 className=\"h-5 w-5 text-action-primary-bg\" />}\n />\n <MetricCard\n title={tr('admin.dashboard.metrics.organizations', 'Organizations')}\n value={metrics.organizations}\n loading={isLoading}\n icon={<Briefcase className=\"h-5 w-5 text-text-secondary\" />}\n />\n <MetricCard\n title={tr('admin.dashboard.metrics.activeWorkspaces', 'Active Workspaces')}\n value={metrics.workspaces}\n loading={isLoading}\n icon={<Layout className=\"h-5 w-5 text-status-success-text\" />}\n />\n <MetricCard\n title={tr('admin.dashboard.metrics.totalUsers', 'Total Users')}\n value={metrics.users}\n loading={isLoading}\n icon={<Users className=\"h-5 w-5 text-text-secondary\" />}\n />\n </MetricsGrid>\n\n {/* Content Area */}\n <div className=\"grid gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <ActivityFeed activities={recentActivity} loading={activityLoading} />\n </div>\n <div className=\"space-y-4\">\n <QuickLinks links={quickLinks} />\n </div>\n </div>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
@@ -36,6 +36,7 @@ function _() {
36
36
  })
37
37
  }) : /* @__PURE__ */ h(r, {
38
38
  title: "Feature Flags",
39
+ gradientWord: "Flags",
39
40
  description: "Manage and monitor platform feature flags",
40
41
  actions: /* @__PURE__ */ g(e, { children: [/* @__PURE__ */ h(d, { className: "mr-2 h-4 w-4" }), "Create Feature Flag"] }),
41
42
  children: /* @__PURE__ */ g("div", {
@@ -102,6 +103,7 @@ function _() {
102
103
  keyExtractor: (e) => e.id,
103
104
  loading: b,
104
105
  emptyState: /* @__PURE__ */ h(t, {
106
+ illustration: "empty-data",
105
107
  icon: /* @__PURE__ */ h(p, { className: "h-8 w-8" }),
106
108
  title: "No feature flags found",
107
109
  description: "Feature flags will appear here when they are created"
@@ -1 +1 @@
1
- {"version":3,"file":"FeatureFlagsPage.js","names":[],"sources":["../../../src/admin/pages/FeatureFlagsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { ToggleLeft, Search, Plus, Trash2 } from 'lucide-react';\nimport { PageLayout, Input, Button, EmptyState, DataTable, RowActions } from '../components';\nimport {\n useListFeatureFlagsQuery,\n useUpdateFeatureFlagMutation,\n useDeleteFeatureFlagMutation,\n} from '../../generated/global-operations';\nimport { FeatureFlagStatus } from '../../generated/global-types';\n\nexport function FeatureFlagsPage() {\n const [search, setSearch] = useState('');\n const { data, loading, error, refetch } = useListFeatureFlagsQuery({\n variables: {\n filter: search ? { name: { contains: search } } : undefined,\n },\n });\n\n const [updateFeatureFlag] = useUpdateFeatureFlagMutation();\n const [deleteFeatureFlag] = useDeleteFeatureFlagMutation();\n\n const flags = data?.featureFlags?.items || [];\n\n const handleToggleStatus = async (id: string, currentStatus: FeatureFlagStatus) => {\n const newStatus =\n currentStatus === FeatureFlagStatus.Active\n ? FeatureFlagStatus.Archived\n : FeatureFlagStatus.Active;\n\n try {\n await updateFeatureFlag({\n variables: {\n id,\n input: { status: newStatus },\n },\n });\n refetch();\n } catch (err) {\n console.error('Failed to update status:', err);\n }\n };\n\n const handleDelete = async (id: string) => {\n if (confirm('Are you sure you want to delete this feature flag?')) {\n try {\n await deleteFeatureFlag({ variables: { id } });\n refetch();\n } catch (err) {\n console.error('Failed to delete flag:', err);\n }\n }\n };\n\n const columns = [\n {\n key: 'name',\n header: 'Name',\n render: (flag: any) => (\n <div className=\"flex flex-col text-left\">\n <span className=\"font-medium\">{flag.name}</span>\n <span className=\"text-xs text-text-secondary\">{flag.key}</span>\n </div>\n ),\n },\n {\n key: 'flagType',\n header: 'Type',\n },\n {\n key: 'status',\n header: 'Status',\n render: (flag: any) => {\n const isActive = flag.status === FeatureFlagStatus.Active;\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleToggleStatus(flag.id, flag.status)}\n className={isActive ? 'border-primary text-primary' : 'text-text-secondary'}\n >\n {isActive ? 'Active' : 'Archived'}\n </Button>\n );\n },\n },\n {\n key: 'actions',\n header: '',\n align: 'right' as const,\n render: (flag: any) => (\n <RowActions\n actions={[\n {\n key: 'delete',\n label: 'Delete',\n icon: <Trash2 className=\"h-4 w-4\" />,\n onClick: () => handleDelete(flag.id),\n destructive: true,\n },\n ]}\n />\n ),\n },\n ];\n\n if (error) {\n return (\n <PageLayout title=\"Feature Flags\" description=\"Error loading feature flags\">\n <div className=\"p-4 text-status-error-text\">Error: {error.message}</div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Feature Flags\"\n description=\"Manage and monitor platform feature flags\"\n actions={\n <Button>\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Feature Flag\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex items-center 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=\"Search feature flags...\"\n className=\"pl-9\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n </div>\n </div>\n\n <DataTable\n columns={columns}\n data={flags}\n keyExtractor={(flag) => flag.id}\n loading={loading}\n emptyState={\n <EmptyState\n icon={<ToggleLeft className=\"h-8 w-8\" />}\n title=\"No feature flags found\"\n description=\"Feature flags will appear here when they are created\"\n />\n }\n />\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;AAUA,SAAgB,IAAmB;CACjC,IAAM,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,EAAE,SAAM,YAAS,UAAO,eAAY,EAAyB,EACjE,WAAW,EACT,QAAQ,IAAS,EAAE,MAAM,EAAE,UAAU,GAAQ,EAAE,GAAG,KAAA,GACnD,EACF,CAAC,EAEI,CAAC,KAAqB,GAA8B,EACpD,CAAC,KAAqB,GAA8B,EAEpD,IAAQ,GAAM,cAAc,SAAS,EAAE,EAEvC,IAAqB,OAAO,GAAY,MAAqC;EACjF,IAAM,IACJ,MAAkB,EAAkB,SAChC,EAAkB,WAClB,EAAkB;AAExB,MAAI;AAOF,GANA,MAAM,EAAkB,EACtB,WAAW;IACT;IACA,OAAO,EAAE,QAAQ,GAAW;IAC7B,EACF,CAAC,EACF,GAAS;WACF,GAAK;AACZ,WAAQ,MAAM,4BAA4B,EAAI;;IAI5C,IAAe,OAAO,MAAe;AACzC,MAAI,QAAQ,qDAAqD,CAC/D,KAAI;AAEF,GADA,MAAM,EAAkB,EAAE,WAAW,EAAE,OAAI,EAAE,CAAC,EAC9C,GAAS;WACF,GAAK;AACZ,WAAQ,MAAM,0BAA0B,EAAI;;;AAiElD,QARI,IAEA,kBAAC,GAAD;EAAY,OAAM;EAAgB,aAAY;YAC5C,kBAAC,OAAD;GAAK,WAAU;aAAf,CAA4C,WAAQ,EAAM,QAAc;;EAC7D,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,sBAE1B,EAAA,CAAA;YAGX,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,GAAD;MACE,aAAY;MACZ,WAAU;MACV,OAAO;MACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;MAC1C,CAAA,CACE;;IACF,CAAA,EAEN,kBAAC,GAAD;IACW,SArFD;KACd;MACE,KAAK;MACL,QAAQ;MACR,SAAS,MACP,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAe,EAAK;QAAY,CAAA,EAChD,kBAAC,QAAD;QAAM,WAAU;kBAA+B,EAAK;QAAW,CAAA,CAC3D;;MAET;KACD;MACE,KAAK;MACL,QAAQ;MACT;KACD;MACE,KAAK;MACL,QAAQ;MACR,SAAS,MAAc;OACrB,IAAM,IAAW,EAAK,WAAW,EAAkB;AACnD,cACE,kBAAC,GAAD;QACE,SAAQ;QACR,MAAK;QACL,eAAe,EAAmB,EAAK,IAAI,EAAK,OAAO;QACvD,WAAW,IAAW,gCAAgC;kBAErD,IAAW,WAAW;QAChB,CAAA;;MAGd;KACD;MACE,KAAK;MACL,QAAQ;MACR,OAAO;MACP,SAAS,MACP,kBAAC,GAAD,EACE,SAAS,CACP;OACE,KAAK;OACL,OAAO;OACP,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;OACpC,eAAe,EAAa,EAAK,GAAG;OACpC,aAAa;OACd,CACF,EACD,CAAA;MAEL;KACF;IAoCO,MAAM;IACN,eAAe,MAAS,EAAK;IACpB;IACT,YACE,kBAAC,GAAD;KACE,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;KACxC,OAAM;KACN,aAAY;KACZ,CAAA;IAEJ,CAAA,CACE;;EACK,CAAA"}
1
+ {"version":3,"file":"FeatureFlagsPage.js","names":[],"sources":["../../../src/admin/pages/FeatureFlagsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { ToggleLeft, Search, Plus, Trash2 } from 'lucide-react';\nimport { PageLayout, Input, Button, EmptyState, DataTable, RowActions } from '../components';\nimport {\n useListFeatureFlagsQuery,\n useUpdateFeatureFlagMutation,\n useDeleteFeatureFlagMutation,\n} from '../../generated/global-operations';\nimport { FeatureFlagStatus } from '../../generated/global-types';\n\nexport function FeatureFlagsPage() {\n const [search, setSearch] = useState('');\n const { data, loading, error, refetch } = useListFeatureFlagsQuery({\n variables: {\n filter: search ? { name: { contains: search } } : undefined,\n },\n });\n\n const [updateFeatureFlag] = useUpdateFeatureFlagMutation();\n const [deleteFeatureFlag] = useDeleteFeatureFlagMutation();\n\n const flags = data?.featureFlags?.items || [];\n\n const handleToggleStatus = async (id: string, currentStatus: FeatureFlagStatus) => {\n const newStatus =\n currentStatus === FeatureFlagStatus.Active\n ? FeatureFlagStatus.Archived\n : FeatureFlagStatus.Active;\n\n try {\n await updateFeatureFlag({\n variables: {\n id,\n input: { status: newStatus },\n },\n });\n refetch();\n } catch (err) {\n console.error('Failed to update status:', err);\n }\n };\n\n const handleDelete = async (id: string) => {\n if (confirm('Are you sure you want to delete this feature flag?')) {\n try {\n await deleteFeatureFlag({ variables: { id } });\n refetch();\n } catch (err) {\n console.error('Failed to delete flag:', err);\n }\n }\n };\n\n const columns = [\n {\n key: 'name',\n header: 'Name',\n render: (flag: any) => (\n <div className=\"flex flex-col text-left\">\n <span className=\"font-medium\">{flag.name}</span>\n <span className=\"text-xs text-text-secondary\">{flag.key}</span>\n </div>\n ),\n },\n {\n key: 'flagType',\n header: 'Type',\n },\n {\n key: 'status',\n header: 'Status',\n render: (flag: any) => {\n const isActive = flag.status === FeatureFlagStatus.Active;\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleToggleStatus(flag.id, flag.status)}\n className={isActive ? 'border-primary text-primary' : 'text-text-secondary'}\n >\n {isActive ? 'Active' : 'Archived'}\n </Button>\n );\n },\n },\n {\n key: 'actions',\n header: '',\n align: 'right' as const,\n render: (flag: any) => (\n <RowActions\n actions={[\n {\n key: 'delete',\n label: 'Delete',\n icon: <Trash2 className=\"h-4 w-4\" />,\n onClick: () => handleDelete(flag.id),\n destructive: true,\n },\n ]}\n />\n ),\n },\n ];\n\n if (error) {\n return (\n <PageLayout title=\"Feature Flags\" description=\"Error loading feature flags\">\n <div className=\"p-4 text-status-error-text\">Error: {error.message}</div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Feature Flags\"\n gradientWord=\"Flags\"\n description=\"Manage and monitor platform feature flags\"\n actions={\n <Button>\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Feature Flag\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex items-center 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=\"Search feature flags...\"\n className=\"pl-9\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n </div>\n </div>\n\n <DataTable\n columns={columns}\n data={flags}\n keyExtractor={(flag) => flag.id}\n loading={loading}\n emptyState={\n <EmptyState\n illustration=\"empty-data\"\n icon={<ToggleLeft className=\"h-8 w-8\" />}\n title=\"No feature flags found\"\n description=\"Feature flags will appear here when they are created\"\n />\n }\n />\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;AAUA,SAAgB,IAAmB;CACjC,IAAM,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,EAAE,SAAM,YAAS,UAAO,eAAY,EAAyB,EACjE,WAAW,EACT,QAAQ,IAAS,EAAE,MAAM,EAAE,UAAU,GAAQ,EAAE,GAAG,KAAA,GACnD,EACF,CAAC,EAEI,CAAC,KAAqB,GAA8B,EACpD,CAAC,KAAqB,GAA8B,EAEpD,IAAQ,GAAM,cAAc,SAAS,EAAE,EAEvC,IAAqB,OAAO,GAAY,MAAqC;EACjF,IAAM,IACJ,MAAkB,EAAkB,SAChC,EAAkB,WAClB,EAAkB;AAExB,MAAI;AAOF,GANA,MAAM,EAAkB,EACtB,WAAW;IACT;IACA,OAAO,EAAE,QAAQ,GAAW;IAC7B,EACF,CAAC,EACF,GAAS;WACF,GAAK;AACZ,WAAQ,MAAM,4BAA4B,EAAI;;IAI5C,IAAe,OAAO,MAAe;AACzC,MAAI,QAAQ,qDAAqD,CAC/D,KAAI;AAEF,GADA,MAAM,EAAkB,EAAE,WAAW,EAAE,OAAI,EAAE,CAAC,EAC9C,GAAS;WACF,GAAK;AACZ,WAAQ,MAAM,0BAA0B,EAAI;;;AAiElD,QARI,IAEA,kBAAC,GAAD;EAAY,OAAM;EAAgB,aAAY;YAC5C,kBAAC,OAAD;GAAK,WAAU;aAAf,CAA4C,WAAQ,EAAM,QAAc;;EAC7D,CAAA,GAKf,kBAAC,GAAD;EACE,OAAM;EACN,cAAa;EACb,aAAY;EACZ,SACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,sBAE1B,EAAA,CAAA;YAGX,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,GAAD;MACE,aAAY;MACZ,WAAU;MACV,OAAO;MACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;MAC1C,CAAA,CACE;;IACF,CAAA,EAEN,kBAAC,GAAD;IACW,SAtFD;KACd;MACE,KAAK;MACL,QAAQ;MACR,SAAS,MACP,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAe,EAAK;QAAY,CAAA,EAChD,kBAAC,QAAD;QAAM,WAAU;kBAA+B,EAAK;QAAW,CAAA,CAC3D;;MAET;KACD;MACE,KAAK;MACL,QAAQ;MACT;KACD;MACE,KAAK;MACL,QAAQ;MACR,SAAS,MAAc;OACrB,IAAM,IAAW,EAAK,WAAW,EAAkB;AACnD,cACE,kBAAC,GAAD;QACE,SAAQ;QACR,MAAK;QACL,eAAe,EAAmB,EAAK,IAAI,EAAK,OAAO;QACvD,WAAW,IAAW,gCAAgC;kBAErD,IAAW,WAAW;QAChB,CAAA;;MAGd;KACD;MACE,KAAK;MACL,QAAQ;MACR,OAAO;MACP,SAAS,MACP,kBAAC,GAAD,EACE,SAAS,CACP;OACE,KAAK;OACL,OAAO;OACP,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;OACpC,eAAe,EAAa,EAAK,GAAG;OACpC,aAAa;OACd,CACF,EACD,CAAA;MAEL;KACF;IAqCO,MAAM;IACN,eAAe,MAAS,EAAK;IACpB;IACT,YACE,kBAAC,GAAD;KACE,cAAa;KACb,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;KACxC,OAAM;KACN,aAAY;KACZ,CAAA;IAEJ,CAAA,CACE;;EACK,CAAA"}
@@ -18,6 +18,7 @@ function p() {
18
18
  }, b = h?.allOrganizations?.items || [];
19
19
  return /* @__PURE__ */ d(r, {
20
20
  title: "Organizations",
21
+ gradientWord: "Organizations",
21
22
  description: "Manage and monitor platform organizations",
22
23
  actions: /* @__PURE__ */ f(e, { children: [/* @__PURE__ */ d(l, { className: "mr-2 h-4 w-4" }), "Create Organization"] }),
23
24
  children: /* @__PURE__ */ f("div", {
@@ -99,6 +100,7 @@ function p() {
99
100
  }, t.id)) })]
100
101
  })
101
102
  }) : /* @__PURE__ */ d(t, {
103
+ illustration: "empty-data",
102
104
  icon: /* @__PURE__ */ d(s, { className: "h-8 w-8" }),
103
105
  title: "No organizations found",
104
106
  description: "Organizations will appear here when they are created"
@@ -1 +1 @@
1
- {"version":3,"file":"OrganizationsListPage.js","names":[],"sources":["../../../src/admin/pages/OrganizationsListPage.tsx"],"sourcesContent":["import { Building, Search, Plus, Loader2 } from 'lucide-react';\nimport { PageLayout, Input, Button, EmptyState } from '../components';\nimport {\n useListOrganizationsQuery,\n useUpdateOrganizationMutation,\n} from '../../generated/global-operations';\nimport { useState } from 'react';\n\nexport function OrganizationsListPage() {\n const [searchTerm, setSearchTerm] = useState('');\n const { data, loading, refetch } = useListOrganizationsQuery({\n variables: {\n filter: searchTerm ? { search: searchTerm } : undefined,\n limit: 50,\n },\n });\n\n const [updateOrganization] = useUpdateOrganizationMutation();\n\n const handleToggleStatus = async (id: string, currentStatus: string) => {\n const newStatus = currentStatus === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE';\n await updateOrganization({\n variables: {\n id,\n input: { status: newStatus },\n },\n });\n refetch();\n };\n\n const organizations = data?.allOrganizations?.items || [];\n\n return (\n <PageLayout\n title=\"Organizations\"\n description=\"Manage and monitor platform organizations\"\n actions={\n <Button>\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Organization\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex items-center 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=\"Search organizations...\"\n className=\"pl-9\"\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n />\n </div>\n </div>\n\n {loading ? (\n <div className=\"flex h-64 items-center justify-center\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n ) : organizations.length > 0 ? (\n <div className=\"rounded-md border\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b bg-bg-sunken/50 text-left\">\n <th className=\"p-4 font-medium\">Name</th>\n <th className=\"p-4 font-medium\">Slug</th>\n <th className=\"p-4 font-medium\">Status</th>\n <th className=\"p-4 font-medium\">Created At</th>\n <th className=\"p-4 font-medium text-right\">Actions</th>\n </tr>\n </thead>\n <tbody>\n {organizations.map((org: any) => (\n <tr key={org.id} className=\"border-b transition-colors hover:bg-bg-sunken/50\">\n <td className=\"p-4 font-medium\">{org.name}</td>\n <td className=\"p-4 text-text-secondary\">{org.slug}</td>\n <td className=\"p-4\">\n <span\n className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ${\n org.status === 'ACTIVE'\n ? 'bg-green-100 text-green-800'\n : 'bg-red-100 text-red-800'\n }`}\n >\n {org.status}\n </span>\n </td>\n <td className=\"p-4 text-text-secondary\">\n {new Date(org.createdAt).toLocaleDateString()}\n </td>\n <td className=\"p-4 text-right\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleToggleStatus(org.id, org.status)}\n >\n Toggle Status\n </Button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n ) : (\n <EmptyState\n icon={<Building className=\"h-8 w-8\" />}\n title=\"No organizations found\"\n description=\"Organizations will appear here when they are created\"\n />\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,IAAwB;CACtC,IAAM,CAAC,GAAY,KAAiB,EAAS,GAAG,EAC1C,EAAE,SAAM,YAAS,eAAY,EAA0B,EAC3D,WAAW;EACT,QAAQ,IAAa,EAAE,QAAQ,GAAY,GAAG,KAAA;EAC9C,OAAO;EACR,EACF,CAAC,EAEI,CAAC,KAAsB,GAA+B,EAEtD,IAAqB,OAAO,GAAY,MAA0B;AAQtE,EANA,MAAM,EAAmB,EACvB,WAAW;GACT;GACA,OAAO,EAAE,QAJK,MAAkB,WAAW,aAAa,UAI5B;GAC7B,EACF,CAAC,EACF,GAAS;IAGL,IAAgB,GAAM,kBAAkB,SAAS,EAAE;AAEzD,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,sBAE1B,EAAA,CAAA;YAGX,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,GAAD;MACE,aAAY;MACZ,WAAU;MACV,OAAO;MACP,WAAW,MAAM,EAAc,EAAE,OAAO,MAAM;MAC9C,CAAA,CACE;;IACF,CAAA,EAEL,IACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAS,WAAU,4CAA6C,CAAA;IAC5D,CAAA,GACJ,EAAc,SAAS,IACzB,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;MAAI,WAAU;gBAAd;OACE,kBAAC,MAAD;QAAI,WAAU;kBAAkB;QAAS,CAAA;OACzC,kBAAC,MAAD;QAAI,WAAU;kBAAkB;QAAS,CAAA;OACzC,kBAAC,MAAD;QAAI,WAAU;kBAAkB;QAAW,CAAA;OAC3C,kBAAC,MAAD;QAAI,WAAU;kBAAkB;QAAe,CAAA;OAC/C,kBAAC,MAAD;QAAI,WAAU;kBAA6B;QAAY,CAAA;OACpD;SACC,CAAA,EACR,kBAAC,SAAD,EAAA,UACG,EAAc,KAAK,MAClB,kBAAC,MAAD;MAAiB,WAAU;gBAA3B;OACE,kBAAC,MAAD;QAAI,WAAU;kBAAmB,EAAI;QAAU,CAAA;OAC/C,kBAAC,MAAD;QAAI,WAAU;kBAA2B,EAAI;QAAU,CAAA;OACvD,kBAAC,MAAD;QAAI,WAAU;kBACZ,kBAAC,QAAD;SACE,WAAW,6EACT,EAAI,WAAW,WACX,gCACA;mBAGL,EAAI;SACA,CAAA;QACJ,CAAA;OACL,kBAAC,MAAD;QAAI,WAAU;kBACX,IAAI,KAAK,EAAI,UAAU,CAAC,oBAAoB;QAC1C,CAAA;OACL,kBAAC,MAAD;QAAI,WAAU;kBACZ,kBAAC,GAAD;SACE,SAAQ;SACR,MAAK;SACL,eAAe,EAAmB,EAAI,IAAI,EAAI,OAAO;mBACtD;SAEQ,CAAA;QACN,CAAA;OACF;QA1BI,EAAI,GA0BR,CACL,EACI,CAAA,CACF;;IACJ,CAAA,GAEN,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;IACtC,OAAM;IACN,aAAY;IACZ,CAAA,CAEA;;EACK,CAAA"}
1
+ {"version":3,"file":"OrganizationsListPage.js","names":[],"sources":["../../../src/admin/pages/OrganizationsListPage.tsx"],"sourcesContent":["import { Building, Search, Plus, Loader2 } from 'lucide-react';\nimport { PageLayout, Input, Button, EmptyState } from '../components';\nimport {\n useListOrganizationsQuery,\n useUpdateOrganizationMutation,\n} from '../../generated/global-operations';\nimport { useState } from 'react';\n\nexport function OrganizationsListPage() {\n const [searchTerm, setSearchTerm] = useState('');\n const { data, loading, refetch } = useListOrganizationsQuery({\n variables: {\n filter: searchTerm ? { search: searchTerm } : undefined,\n limit: 50,\n },\n });\n\n const [updateOrganization] = useUpdateOrganizationMutation();\n\n const handleToggleStatus = async (id: string, currentStatus: string) => {\n const newStatus = currentStatus === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE';\n await updateOrganization({\n variables: {\n id,\n input: { status: newStatus },\n },\n });\n refetch();\n };\n\n const organizations = data?.allOrganizations?.items || [];\n\n return (\n <PageLayout\n title=\"Organizations\"\n gradientWord=\"Organizations\"\n description=\"Manage and monitor platform organizations\"\n actions={\n <Button>\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Organization\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex items-center 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=\"Search organizations...\"\n className=\"pl-9\"\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n />\n </div>\n </div>\n\n {loading ? (\n <div className=\"flex h-64 items-center justify-center\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n ) : organizations.length > 0 ? (\n <div className=\"rounded-md border\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b bg-bg-sunken/50 text-left\">\n <th className=\"p-4 font-medium\">Name</th>\n <th className=\"p-4 font-medium\">Slug</th>\n <th className=\"p-4 font-medium\">Status</th>\n <th className=\"p-4 font-medium\">Created At</th>\n <th className=\"p-4 font-medium text-right\">Actions</th>\n </tr>\n </thead>\n <tbody>\n {organizations.map((org: any) => (\n <tr key={org.id} className=\"border-b transition-colors hover:bg-bg-sunken/50\">\n <td className=\"p-4 font-medium\">{org.name}</td>\n <td className=\"p-4 text-text-secondary\">{org.slug}</td>\n <td className=\"p-4\">\n <span\n className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ${\n org.status === 'ACTIVE'\n ? 'bg-green-100 text-green-800'\n : 'bg-red-100 text-red-800'\n }`}\n >\n {org.status}\n </span>\n </td>\n <td className=\"p-4 text-text-secondary\">\n {new Date(org.createdAt).toLocaleDateString()}\n </td>\n <td className=\"p-4 text-right\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleToggleStatus(org.id, org.status)}\n >\n Toggle Status\n </Button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Building className=\"h-8 w-8\" />}\n title=\"No organizations found\"\n description=\"Organizations will appear here when they are created\"\n />\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,IAAwB;CACtC,IAAM,CAAC,GAAY,KAAiB,EAAS,GAAG,EAC1C,EAAE,SAAM,YAAS,eAAY,EAA0B,EAC3D,WAAW;EACT,QAAQ,IAAa,EAAE,QAAQ,GAAY,GAAG,KAAA;EAC9C,OAAO;EACR,EACF,CAAC,EAEI,CAAC,KAAsB,GAA+B,EAEtD,IAAqB,OAAO,GAAY,MAA0B;AAQtE,EANA,MAAM,EAAmB,EACvB,WAAW;GACT;GACA,OAAO,EAAE,QAJK,MAAkB,WAAW,aAAa,UAI5B;GAC7B,EACF,CAAC,EACF,GAAS;IAGL,IAAgB,GAAM,kBAAkB,SAAS,EAAE;AAEzD,QACE,kBAAC,GAAD;EACE,OAAM;EACN,cAAa;EACb,aAAY;EACZ,SACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,sBAE1B,EAAA,CAAA;YAGX,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,GAAD;MACE,aAAY;MACZ,WAAU;MACV,OAAO;MACP,WAAW,MAAM,EAAc,EAAE,OAAO,MAAM;MAC9C,CAAA,CACE;;IACF,CAAA,EAEL,IACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAS,WAAU,4CAA6C,CAAA;IAC5D,CAAA,GACJ,EAAc,SAAS,IACzB,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;MAAI,WAAU;gBAAd;OACE,kBAAC,MAAD;QAAI,WAAU;kBAAkB;QAAS,CAAA;OACzC,kBAAC,MAAD;QAAI,WAAU;kBAAkB;QAAS,CAAA;OACzC,kBAAC,MAAD;QAAI,WAAU;kBAAkB;QAAW,CAAA;OAC3C,kBAAC,MAAD;QAAI,WAAU;kBAAkB;QAAe,CAAA;OAC/C,kBAAC,MAAD;QAAI,WAAU;kBAA6B;QAAY,CAAA;OACpD;SACC,CAAA,EACR,kBAAC,SAAD,EAAA,UACG,EAAc,KAAK,MAClB,kBAAC,MAAD;MAAiB,WAAU;gBAA3B;OACE,kBAAC,MAAD;QAAI,WAAU;kBAAmB,EAAI;QAAU,CAAA;OAC/C,kBAAC,MAAD;QAAI,WAAU;kBAA2B,EAAI;QAAU,CAAA;OACvD,kBAAC,MAAD;QAAI,WAAU;kBACZ,kBAAC,QAAD;SACE,WAAW,6EACT,EAAI,WAAW,WACX,gCACA;mBAGL,EAAI;SACA,CAAA;QACJ,CAAA;OACL,kBAAC,MAAD;QAAI,WAAU;kBACX,IAAI,KAAK,EAAI,UAAU,CAAC,oBAAoB;QAC1C,CAAA;OACL,kBAAC,MAAD;QAAI,WAAU;kBACZ,kBAAC,GAAD;SACE,SAAQ;SACR,MAAK;SACL,eAAe,EAAmB,EAAI,IAAI,EAAI,OAAO;mBACtD;SAEQ,CAAA;QACN,CAAA;OACF;QA1BI,EAAI,GA0BR,CACL,EACI,CAAA,CACF;;IACJ,CAAA,GAEN,kBAAC,GAAD;IACE,cAAa;IACb,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;IACtC,OAAM;IACN,aAAY;IACZ,CAAA,CAEA;;EACK,CAAA"}
@@ -254,6 +254,7 @@ function M() {
254
254
  denyMessage: "You don't have permission to view products.",
255
255
  children: /* @__PURE__ */ C(a, {
256
256
  title: "Products",
257
+ gradientWord: "Products",
257
258
  description: `Platform products and services${Z ? ` · ${Z} total` : ""}`,
258
259
  actions: /* @__PURE__ */ C(n, {
259
260
  onClick: ee,
@@ -265,6 +266,7 @@ function M() {
265
266
  children: [
266
267
  /* @__PURE__ */ S(s, {
267
268
  title: "Total Products",
269
+ hero: !0,
268
270
  value: $.total,
269
271
  icon: /* @__PURE__ */ S(y, { className: "h-5 w-5" })
270
272
  }),
@@ -369,6 +371,7 @@ function M() {
369
371
  keyExtractor: (e) => e.id,
370
372
  loading: q,
371
373
  emptyState: /* @__PURE__ */ S(r, {
374
+ illustration: "empty-search",
372
375
  icon: /* @__PURE__ */ S(y, { className: "h-8 w-8" }),
373
376
  title: "No products found",
374
377
  description: "No platform products match your search"
@@ -1 +1 @@
1
- {"version":3,"file":"ProductsPage.js","names":[],"sources":["../../../src/admin/pages/ProductsPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport { gql } from '@apollo/client';\nimport { useApolloClient } from '@apollo/client/react';\nimport { Package, Search, ExternalLink, Server, Database } from 'lucide-react';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport {\n PageLayout,\n Input,\n DataTable,\n Pagination,\n EmptyState,\n MetricCard,\n MetricsGrid,\n Badge,\n Button,\n PermissionGuard,\n} from '../components';\nimport { formatDate } from '../utils/formatters';\nimport {\n useListPlatformProductsQuery,\n useGetPlatformProductStatsQuery,\n} from '../../generated/global-operations';\n\nconst PAGE_SIZE = 20;\n\ntype ProductStatus = 'ACTIVE' | 'COMING_SOON' | 'WAITLIST' | 'DRAFT';\n\nconst STATUS_VARIANT: Record<ProductStatus, 'default' | 'secondary' | 'outline' | 'destructive'> = {\n ACTIVE: 'default',\n COMING_SOON: 'secondary',\n WAITLIST: 'outline',\n DRAFT: 'destructive',\n};\n\nconst PLATFORM_PRODUCT_FIELDS = gql`\n fragment AdminSeedPlatformProductFields on PlatformProduct {\n id\n name\n slug\n }\n`;\n\nconst UPSERT_PRODUCTS_QUERY = gql`\n ${PLATFORM_PRODUCT_FIELDS}\n query AdminSeedListProducts($limit: Int, $offset: Int) {\n platformProducts(limit: $limit, offset: $offset) {\n items {\n ...AdminSeedPlatformProductFields\n }\n }\n }\n`;\n\nconst CREATE_PRODUCT_MUTATION = gql`\n mutation AdminSeedCreateProduct($input: CreatePlatformProductInput!) {\n createPlatformProduct(input: $input) {\n id\n slug\n }\n }\n`;\n\nconst UPDATE_PRODUCT_MUTATION = gql`\n mutation AdminSeedUpdateProduct($id: ID!, $input: UpdatePlatformProductInput!) {\n updatePlatformProduct(id: $id, input: $input) {\n id\n slug\n }\n }\n`;\n\ntype SeedProductInput = {\n name: string;\n slug: string;\n description: string;\n status: ProductStatus;\n website: string | null;\n appUrl: string;\n docsUrl: string | null;\n githubUrl: string;\n statusPageUrl: string;\n contactEmail: string;\n supportEmail: string;\n contactPhone: string;\n contactAddress: string;\n seoTitle: string;\n seoDescription: string;\n seoKeywords: string;\n socialLinks: Record<string, string>;\n metadata: Record<string, string>;\n};\n\nconst PRODUCT_SEEDS: SeedProductInput[] = [\n {\n name: 'Workspaces',\n slug: 'workspaces',\n description: 'Enterprise collaboration and workspace management platform.',\n status: 'ACTIVE',\n website: 'https://workspaces.burdenoff.com',\n appUrl: 'https://app.local.burdenoff.com/',\n docsUrl: 'https://docs.burdenoff.com/workspaces',\n githubUrl: 'https://github.com/algoshred/workspaces',\n statusPageUrl: 'https://status.burdenoff.com',\n contactEmail: 'contact@burdenoff.com',\n supportEmail: 'support@burdenoff.com',\n contactPhone: '+91-7358445777',\n contactAddress: 'Plot No.43, Veeramani Nagar, 2nd Cross Street, Nanmangalam, Chennai - 600117',\n seoTitle: 'Workspaces - Enterprise Collaboration Platform',\n seoDescription: 'The enterprise-grade foundation for collaborative digital workspaces.',\n seoKeywords: 'enterprise,collaboration,workspace,saas,multi-tenant',\n socialLinks: {\n twitter: 'https://twitter.com/burdenoff',\n github: 'https://github.com/algoshred',\n discord: 'https://discord.gg/burdenoff',\n linkedin: 'https://linkedin.com/company/burdenoff',\n },\n metadata: {\n tagline: 'The Enterprise-Grade Foundation for Collaborative Digital Workspaces',\n businessDomain: 'Enterprise Collaboration & Workspace Management',\n category: 'Core Infrastructure',\n company: 'algoshred',\n pricingRange: '$15-$125/user/month',\n },\n },\n {\n name: 'VibeControls',\n slug: 'vibecontrols',\n description: 'Developer control panel for environments, terminals, tunnels, and remote work.',\n status: 'ACTIVE',\n website: 'https://vibecontrols.com',\n appUrl: 'https://vibecontrols.local.burdenoff.com/',\n docsUrl: 'https://docs.vibecontrols.com',\n githubUrl: 'https://github.com/algoshred/vibecontrols',\n statusPageUrl: 'https://status.burdenoff.com',\n contactEmail: 'contact@vibecontrols.com',\n supportEmail: 'support@burdenoff.com',\n contactPhone: '+91-7358445777',\n contactAddress: 'Plot No.43, Veeramani Nagar, 2nd Cross Street, Nanmangalam, Chennai - 600117',\n seoTitle: 'VibeControls - Developer Environment Management',\n seoDescription: \"The Developer's Command Center for Multi-Environment Development\",\n seoKeywords: 'developer tools,environment management,terminal,remote development,devops',\n socialLinks: {\n twitter: 'https://twitter.com/burdenoff',\n github: 'https://github.com/algoshred',\n discord: 'https://discord.gg/burdenoff',\n linkedin: 'https://linkedin.com/company/burdenoff',\n },\n metadata: {\n tagline: \"The Developer's Command Center for Multi-Environment Development\",\n businessDomain: 'Developer Control Panel',\n category: 'Core Infrastructure',\n company: 'algoshred',\n pricingRange: '$25-$150/user/month',\n },\n },\n {\n name: 'FluidGrids',\n slug: 'fluidgrids',\n description:\n 'Visual workflow automation platform for building and operating complex workflows.',\n status: 'ACTIVE',\n website: 'https://fluidgrids.com',\n appUrl: 'https://fluidgrids.local.burdenoff.com/',\n docsUrl: 'https://docs.fluidgrids.com',\n githubUrl: 'https://github.com/algoshred/fluidgrids',\n statusPageUrl: 'https://status.burdenoff.com',\n contactEmail: 'contact@fluidgrids.com',\n supportEmail: 'support@burdenoff.com',\n contactPhone: '+91-7358445777',\n contactAddress: 'Plot No.43, Veeramani Nagar, 2nd Cross Street, Nanmangalam, Chennai - 600117',\n seoTitle: 'FluidGrids - Visual Workflow Automation',\n seoDescription:\n 'The Visual Automation Platform That Scales From Simple Tasks to Enterprise Workflows',\n seoKeywords: 'workflow,automation,drag-and-drop,visual,enterprise,integration,process',\n socialLinks: {\n twitter: 'https://twitter.com/burdenoff',\n github: 'https://github.com/algoshred',\n discord: 'https://discord.gg/burdenoff',\n linkedin: 'https://linkedin.com/company/burdenoff',\n },\n metadata: {\n tagline:\n 'The Visual Automation Platform That Scales From Simple Tasks to Enterprise Workflows',\n businessDomain: 'Visual Workflow Automation Platform',\n category: 'Workflow & Automation',\n company: 'algoshred',\n pricingRange: '$29-$299/user/month',\n },\n },\n {\n name: 'BigConsole',\n slug: 'bigconsole',\n description: 'AI-first dashboard and data visualization platform.',\n status: 'ACTIVE',\n website: 'https://bigconsole.com',\n appUrl: 'https://bigconsole.local.burdenoff.com/',\n docsUrl: 'https://docs.bigconsole.com',\n githubUrl: 'https://github.com/algoshred/bigconsole',\n statusPageUrl: 'https://status.burdenoff.com',\n contactEmail: 'contact@bigconsole.com',\n supportEmail: 'support@burdenoff.com',\n contactPhone: '+91-7358445777',\n contactAddress: 'Plot No.43, Veeramani Nagar, 2nd Cross Street, Nanmangalam, Chennai - 600117',\n seoTitle: 'BigConsole - AI Data Visualization Platform',\n seoDescription: 'Where Data Becomes Intelligence',\n seoKeywords: 'data visualization,business intelligence,AI,dashboard,analytics,reporting',\n socialLinks: {\n twitter: 'https://twitter.com/burdenoff',\n github: 'https://github.com/algoshred',\n discord: 'https://discord.gg/burdenoff',\n linkedin: 'https://linkedin.com/company/burdenoff',\n },\n metadata: {\n tagline: 'Where Data Becomes Intelligence',\n businessDomain: 'Data Visualization and Business Intelligence',\n category: 'AI & Intelligence',\n company: 'algoshred',\n pricingRange: '$29-$149/user/month',\n },\n },\n {\n name: 'Botlit',\n slug: 'botlit',\n description: 'Enterprise AI agent and bot automation platform.',\n status: 'ACTIVE',\n website: 'https://botlit.ai',\n appUrl: 'https://botlit.local.burdenoff.com/',\n docsUrl: 'https://docs.botlit.ai',\n githubUrl: 'https://github.com/algoshred/botlit',\n statusPageUrl: 'https://status.burdenoff.com',\n contactEmail: 'contact@botlit.ai',\n supportEmail: 'support@burdenoff.com',\n contactPhone: '+91-7358445777',\n contactAddress: 'Plot No.43, Veeramani Nagar, 2nd Cross Street, Nanmangalam, Chennai - 600117',\n seoTitle: 'Botlit - Enterprise AI Agent Platform',\n seoDescription:\n 'The Enterprise AI Platform That Makes Intelligent Automation Accessible to Everyone',\n seoKeywords: 'AI,bots,automation,agentic AI,intelligent automation,chatbot,enterprise AI',\n socialLinks: {\n twitter: 'https://twitter.com/burdenoff',\n github: 'https://github.com/algoshred',\n discord: 'https://discord.gg/burdenoff',\n linkedin: 'https://linkedin.com/company/burdenoff',\n },\n metadata: {\n tagline:\n 'The Enterprise AI Platform That Makes Intelligent Automation Accessible to Everyone',\n businessDomain: 'AI Platform',\n category: 'AI & Intelligence',\n company: 'algoshred',\n pricingRange: '$99-$1,999/month',\n },\n },\n];\n\ntype ProductItem = {\n id: string;\n name: string;\n slug: string;\n description: string | null | undefined;\n status: string;\n appUrl: string | null | undefined;\n activeUsers: number;\n apiCalls: number;\n contactEmail: string | null | undefined;\n createdAt: string;\n updatedAt: string;\n};\n\n/**\n * Products page — connected to real backend\n */\nexport function ProductsPage() {\n const { canViewProducts, isLoading: permissionsLoading } = useAdminPermissions();\n const apolloClient = useApolloClient();\n\n const [page, setPage] = useState(1);\n const [search, setSearch] = useState('');\n const [isSeeding, setIsSeeding] = useState(false);\n const [seedMessage, setSeedMessage] = useState<string | null>(null);\n const [seedError, setSeedError] = useState<string | null>(null);\n\n const skipQueries = permissionsLoading || !canViewProducts;\n\n const { data: statsData } = useGetPlatformProductStatsQuery({\n skip: skipQueries,\n });\n const { data, loading, error, refetch } = useListPlatformProductsQuery({\n variables: {\n filter: search ? { search } : undefined,\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n const products: ProductItem[] = useMemo(\n () => (data?.platformProducts?.items ?? []) as ProductItem[],\n [data]\n );\n\n const total = data?.platformProducts?.total ?? 0;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n const stats = statsData?.platformProductStats;\n\n const handleSeedProducts = useCallback(async () => {\n setIsSeeding(true);\n setSeedMessage(null);\n setSeedError(null);\n\n try {\n const existingResult = await apolloClient.query<{\n platformProducts: { items: Array<{ id: string; slug: string }> };\n }>({\n query: UPSERT_PRODUCTS_QUERY,\n variables: { limit: 200, offset: 0 },\n fetchPolicy: 'network-only',\n });\n const existingProducts = existingResult.data?.platformProducts?.items ?? [];\n const existingBySlug = new Map(existingProducts.map((product) => [product.slug, product]));\n\n let created = 0;\n let updated = 0;\n for (const seed of PRODUCT_SEEDS) {\n const existing = existingBySlug.get(seed.slug);\n if (existing) {\n const input = Object.fromEntries(Object.entries(seed).filter(([key]) => key !== 'slug'));\n await apolloClient.mutate({\n mutation: UPDATE_PRODUCT_MUTATION,\n variables: { id: existing.id, input },\n });\n updated += 1;\n } else {\n await apolloClient.mutate({\n mutation: CREATE_PRODUCT_MUTATION,\n variables: { input: seed },\n });\n created += 1;\n }\n }\n\n setSeedMessage(`Seeded products: ${created} created, ${updated} updated.`);\n await refetch();\n } catch (err) {\n setSeedError(err instanceof Error ? err.message : 'Failed to seed products');\n } finally {\n setIsSeeding(false);\n }\n }, [apolloClient, refetch]);\n\n const columns = [\n {\n key: 'name',\n header: 'Product',\n render: (p: ProductItem) => (\n <div className=\"flex items-center gap-3\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium text-text-primary\">{p.name}</span>\n {p.appUrl && (\n <a\n href={p.appUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n >\n <ExternalLink className=\"h-3 w-3 text-text-secondary hover:text-text-primary\" />\n </a>\n )}\n </div>\n <div className=\"truncate text-xs text-text-secondary\">{p.slug}</div>\n </div>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n width: '110px',\n render: (p: ProductItem) => (\n <Badge\n variant={STATUS_VARIANT[p.status as ProductStatus] ?? 'outline'}\n className=\"text-xs capitalize\"\n >\n {p.status.toLowerCase().replace('_', ' ')}\n </Badge>\n ),\n },\n {\n key: 'activeUsers',\n header: 'Users',\n width: '80px',\n align: 'right' as const,\n render: (p: ProductItem) => <span className=\"text-sm\">{p.activeUsers}</span>,\n },\n {\n key: 'updatedAt',\n header: 'Updated',\n width: '110px',\n render: (p: ProductItem) => formatDate(p.updatedAt),\n },\n ];\n\n return (\n <PermissionGuard\n allowed={canViewProducts}\n isLoading={permissionsLoading}\n pageTitle=\"Products\"\n denyMessage=\"You don't have permission to view products.\"\n >\n <PageLayout\n title=\"Products\"\n description={`Platform products and services${total ? ` · ${total} total` : ''}`}\n actions={\n <Button onClick={handleSeedProducts} disabled={isSeeding}>\n <Database className=\"mr-2 h-4 w-4\" />\n {isSeeding ? 'Seeding...' : 'Seed Active Products'}\n </Button>\n }\n >\n {/* Stats */}\n {stats && (\n <MetricsGrid className=\"mb-6\">\n <MetricCard\n title=\"Total Products\"\n value={stats.total}\n icon={<Package className=\"h-5 w-5\" />}\n />\n <MetricCard title=\"Active\" value={stats.active} icon={<Server className=\"h-5 w-5\" />} />\n <MetricCard\n title=\"Waitlist\"\n value={stats.waitlist}\n icon={<Package className=\"h-5 w-5\" />}\n />\n <MetricCard title=\"Draft\" value={stats.draft} icon={<Package className=\"h-5 w-5\" />} />\n </MetricsGrid>\n )}\n\n <div className=\"space-y-4\">\n <div className=\"relative max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search products...\"\n value={search}\n onChange={(e) => {\n setPage(1);\n setSearch(e.target.value);\n }}\n className=\"pl-9\"\n />\n </div>\n\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 Failed to load products: {error.message}\n </div>\n )}\n {seedMessage && (\n <div className=\"rounded-md border border-border-subtle bg-bg-sunken p-4 text-sm text-text-primary\">\n {seedMessage}\n </div>\n )}\n {seedError && (\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 {seedError}\n </div>\n )}\n\n <DataTable\n columns={columns}\n data={products}\n keyExtractor={(p) => p.id}\n loading={loading}\n emptyState={\n <EmptyState\n icon={<Package className=\"h-8 w-8\" />}\n title=\"No products found\"\n description=\"No platform products match your search\"\n />\n }\n />\n\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 </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProductsPage.js","names":[],"sources":["../../../src/admin/pages/ProductsPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport { gql } from '@apollo/client';\nimport { useApolloClient } from '@apollo/client/react';\nimport { Package, Search, ExternalLink, Server, Database } from 'lucide-react';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport {\n PageLayout,\n Input,\n DataTable,\n Pagination,\n EmptyState,\n MetricCard,\n MetricsGrid,\n Badge,\n Button,\n PermissionGuard,\n} from '../components';\nimport { formatDate } from '../utils/formatters';\nimport {\n useListPlatformProductsQuery,\n useGetPlatformProductStatsQuery,\n} from '../../generated/global-operations';\n\nconst PAGE_SIZE = 20;\n\ntype ProductStatus = 'ACTIVE' | 'COMING_SOON' | 'WAITLIST' | 'DRAFT';\n\nconst STATUS_VARIANT: Record<ProductStatus, 'default' | 'secondary' | 'outline' | 'destructive'> = {\n ACTIVE: 'default',\n COMING_SOON: 'secondary',\n WAITLIST: 'outline',\n DRAFT: 'destructive',\n};\n\nconst PLATFORM_PRODUCT_FIELDS = gql`\n fragment AdminSeedPlatformProductFields on PlatformProduct {\n id\n name\n slug\n }\n`;\n\nconst UPSERT_PRODUCTS_QUERY = gql`\n ${PLATFORM_PRODUCT_FIELDS}\n query AdminSeedListProducts($limit: Int, $offset: Int) {\n platformProducts(limit: $limit, offset: $offset) {\n items {\n ...AdminSeedPlatformProductFields\n }\n }\n }\n`;\n\nconst CREATE_PRODUCT_MUTATION = gql`\n mutation AdminSeedCreateProduct($input: CreatePlatformProductInput!) {\n createPlatformProduct(input: $input) {\n id\n slug\n }\n }\n`;\n\nconst UPDATE_PRODUCT_MUTATION = gql`\n mutation AdminSeedUpdateProduct($id: ID!, $input: UpdatePlatformProductInput!) {\n updatePlatformProduct(id: $id, input: $input) {\n id\n slug\n }\n }\n`;\n\ntype SeedProductInput = {\n name: string;\n slug: string;\n description: string;\n status: ProductStatus;\n website: string | null;\n appUrl: string;\n docsUrl: string | null;\n githubUrl: string;\n statusPageUrl: string;\n contactEmail: string;\n supportEmail: string;\n contactPhone: string;\n contactAddress: string;\n seoTitle: string;\n seoDescription: string;\n seoKeywords: string;\n socialLinks: Record<string, string>;\n metadata: Record<string, string>;\n};\n\nconst PRODUCT_SEEDS: SeedProductInput[] = [\n {\n name: 'Workspaces',\n slug: 'workspaces',\n description: 'Enterprise collaboration and workspace management platform.',\n status: 'ACTIVE',\n website: 'https://workspaces.burdenoff.com',\n appUrl: 'https://app.local.burdenoff.com/',\n docsUrl: 'https://docs.burdenoff.com/workspaces',\n githubUrl: 'https://github.com/algoshred/workspaces',\n statusPageUrl: 'https://status.burdenoff.com',\n contactEmail: 'contact@burdenoff.com',\n supportEmail: 'support@burdenoff.com',\n contactPhone: '+91-7358445777',\n contactAddress: 'Plot No.43, Veeramani Nagar, 2nd Cross Street, Nanmangalam, Chennai - 600117',\n seoTitle: 'Workspaces - Enterprise Collaboration Platform',\n seoDescription: 'The enterprise-grade foundation for collaborative digital workspaces.',\n seoKeywords: 'enterprise,collaboration,workspace,saas,multi-tenant',\n socialLinks: {\n twitter: 'https://twitter.com/burdenoff',\n github: 'https://github.com/algoshred',\n discord: 'https://discord.gg/burdenoff',\n linkedin: 'https://linkedin.com/company/burdenoff',\n },\n metadata: {\n tagline: 'The Enterprise-Grade Foundation for Collaborative Digital Workspaces',\n businessDomain: 'Enterprise Collaboration & Workspace Management',\n category: 'Core Infrastructure',\n company: 'algoshred',\n pricingRange: '$15-$125/user/month',\n },\n },\n {\n name: 'VibeControls',\n slug: 'vibecontrols',\n description: 'Developer control panel for environments, terminals, tunnels, and remote work.',\n status: 'ACTIVE',\n website: 'https://vibecontrols.com',\n appUrl: 'https://vibecontrols.local.burdenoff.com/',\n docsUrl: 'https://docs.vibecontrols.com',\n githubUrl: 'https://github.com/algoshred/vibecontrols',\n statusPageUrl: 'https://status.burdenoff.com',\n contactEmail: 'contact@vibecontrols.com',\n supportEmail: 'support@burdenoff.com',\n contactPhone: '+91-7358445777',\n contactAddress: 'Plot No.43, Veeramani Nagar, 2nd Cross Street, Nanmangalam, Chennai - 600117',\n seoTitle: 'VibeControls - Developer Environment Management',\n seoDescription: \"The Developer's Command Center for Multi-Environment Development\",\n seoKeywords: 'developer tools,environment management,terminal,remote development,devops',\n socialLinks: {\n twitter: 'https://twitter.com/burdenoff',\n github: 'https://github.com/algoshred',\n discord: 'https://discord.gg/burdenoff',\n linkedin: 'https://linkedin.com/company/burdenoff',\n },\n metadata: {\n tagline: \"The Developer's Command Center for Multi-Environment Development\",\n businessDomain: 'Developer Control Panel',\n category: 'Core Infrastructure',\n company: 'algoshred',\n pricingRange: '$25-$150/user/month',\n },\n },\n {\n name: 'FluidGrids',\n slug: 'fluidgrids',\n description:\n 'Visual workflow automation platform for building and operating complex workflows.',\n status: 'ACTIVE',\n website: 'https://fluidgrids.com',\n appUrl: 'https://fluidgrids.local.burdenoff.com/',\n docsUrl: 'https://docs.fluidgrids.com',\n githubUrl: 'https://github.com/algoshred/fluidgrids',\n statusPageUrl: 'https://status.burdenoff.com',\n contactEmail: 'contact@fluidgrids.com',\n supportEmail: 'support@burdenoff.com',\n contactPhone: '+91-7358445777',\n contactAddress: 'Plot No.43, Veeramani Nagar, 2nd Cross Street, Nanmangalam, Chennai - 600117',\n seoTitle: 'FluidGrids - Visual Workflow Automation',\n seoDescription:\n 'The Visual Automation Platform That Scales From Simple Tasks to Enterprise Workflows',\n seoKeywords: 'workflow,automation,drag-and-drop,visual,enterprise,integration,process',\n socialLinks: {\n twitter: 'https://twitter.com/burdenoff',\n github: 'https://github.com/algoshred',\n discord: 'https://discord.gg/burdenoff',\n linkedin: 'https://linkedin.com/company/burdenoff',\n },\n metadata: {\n tagline:\n 'The Visual Automation Platform That Scales From Simple Tasks to Enterprise Workflows',\n businessDomain: 'Visual Workflow Automation Platform',\n category: 'Workflow & Automation',\n company: 'algoshred',\n pricingRange: '$29-$299/user/month',\n },\n },\n {\n name: 'BigConsole',\n slug: 'bigconsole',\n description: 'AI-first dashboard and data visualization platform.',\n status: 'ACTIVE',\n website: 'https://bigconsole.com',\n appUrl: 'https://bigconsole.local.burdenoff.com/',\n docsUrl: 'https://docs.bigconsole.com',\n githubUrl: 'https://github.com/algoshred/bigconsole',\n statusPageUrl: 'https://status.burdenoff.com',\n contactEmail: 'contact@bigconsole.com',\n supportEmail: 'support@burdenoff.com',\n contactPhone: '+91-7358445777',\n contactAddress: 'Plot No.43, Veeramani Nagar, 2nd Cross Street, Nanmangalam, Chennai - 600117',\n seoTitle: 'BigConsole - AI Data Visualization Platform',\n seoDescription: 'Where Data Becomes Intelligence',\n seoKeywords: 'data visualization,business intelligence,AI,dashboard,analytics,reporting',\n socialLinks: {\n twitter: 'https://twitter.com/burdenoff',\n github: 'https://github.com/algoshred',\n discord: 'https://discord.gg/burdenoff',\n linkedin: 'https://linkedin.com/company/burdenoff',\n },\n metadata: {\n tagline: 'Where Data Becomes Intelligence',\n businessDomain: 'Data Visualization and Business Intelligence',\n category: 'AI & Intelligence',\n company: 'algoshred',\n pricingRange: '$29-$149/user/month',\n },\n },\n {\n name: 'Botlit',\n slug: 'botlit',\n description: 'Enterprise AI agent and bot automation platform.',\n status: 'ACTIVE',\n website: 'https://botlit.ai',\n appUrl: 'https://botlit.local.burdenoff.com/',\n docsUrl: 'https://docs.botlit.ai',\n githubUrl: 'https://github.com/algoshred/botlit',\n statusPageUrl: 'https://status.burdenoff.com',\n contactEmail: 'contact@botlit.ai',\n supportEmail: 'support@burdenoff.com',\n contactPhone: '+91-7358445777',\n contactAddress: 'Plot No.43, Veeramani Nagar, 2nd Cross Street, Nanmangalam, Chennai - 600117',\n seoTitle: 'Botlit - Enterprise AI Agent Platform',\n seoDescription:\n 'The Enterprise AI Platform That Makes Intelligent Automation Accessible to Everyone',\n seoKeywords: 'AI,bots,automation,agentic AI,intelligent automation,chatbot,enterprise AI',\n socialLinks: {\n twitter: 'https://twitter.com/burdenoff',\n github: 'https://github.com/algoshred',\n discord: 'https://discord.gg/burdenoff',\n linkedin: 'https://linkedin.com/company/burdenoff',\n },\n metadata: {\n tagline:\n 'The Enterprise AI Platform That Makes Intelligent Automation Accessible to Everyone',\n businessDomain: 'AI Platform',\n category: 'AI & Intelligence',\n company: 'algoshred',\n pricingRange: '$99-$1,999/month',\n },\n },\n];\n\ntype ProductItem = {\n id: string;\n name: string;\n slug: string;\n description: string | null | undefined;\n status: string;\n appUrl: string | null | undefined;\n activeUsers: number;\n apiCalls: number;\n contactEmail: string | null | undefined;\n createdAt: string;\n updatedAt: string;\n};\n\n/**\n * Products page — connected to real backend\n */\nexport function ProductsPage() {\n const { canViewProducts, isLoading: permissionsLoading } = useAdminPermissions();\n const apolloClient = useApolloClient();\n\n const [page, setPage] = useState(1);\n const [search, setSearch] = useState('');\n const [isSeeding, setIsSeeding] = useState(false);\n const [seedMessage, setSeedMessage] = useState<string | null>(null);\n const [seedError, setSeedError] = useState<string | null>(null);\n\n const skipQueries = permissionsLoading || !canViewProducts;\n\n const { data: statsData } = useGetPlatformProductStatsQuery({\n skip: skipQueries,\n });\n const { data, loading, error, refetch } = useListPlatformProductsQuery({\n variables: {\n filter: search ? { search } : undefined,\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n const products: ProductItem[] = useMemo(\n () => (data?.platformProducts?.items ?? []) as ProductItem[],\n [data]\n );\n\n const total = data?.platformProducts?.total ?? 0;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n const stats = statsData?.platformProductStats;\n\n const handleSeedProducts = useCallback(async () => {\n setIsSeeding(true);\n setSeedMessage(null);\n setSeedError(null);\n\n try {\n const existingResult = await apolloClient.query<{\n platformProducts: { items: Array<{ id: string; slug: string }> };\n }>({\n query: UPSERT_PRODUCTS_QUERY,\n variables: { limit: 200, offset: 0 },\n fetchPolicy: 'network-only',\n });\n const existingProducts = existingResult.data?.platformProducts?.items ?? [];\n const existingBySlug = new Map(existingProducts.map((product) => [product.slug, product]));\n\n let created = 0;\n let updated = 0;\n for (const seed of PRODUCT_SEEDS) {\n const existing = existingBySlug.get(seed.slug);\n if (existing) {\n const input = Object.fromEntries(Object.entries(seed).filter(([key]) => key !== 'slug'));\n await apolloClient.mutate({\n mutation: UPDATE_PRODUCT_MUTATION,\n variables: { id: existing.id, input },\n });\n updated += 1;\n } else {\n await apolloClient.mutate({\n mutation: CREATE_PRODUCT_MUTATION,\n variables: { input: seed },\n });\n created += 1;\n }\n }\n\n setSeedMessage(`Seeded products: ${created} created, ${updated} updated.`);\n await refetch();\n } catch (err) {\n setSeedError(err instanceof Error ? err.message : 'Failed to seed products');\n } finally {\n setIsSeeding(false);\n }\n }, [apolloClient, refetch]);\n\n const columns = [\n {\n key: 'name',\n header: 'Product',\n render: (p: ProductItem) => (\n <div className=\"flex items-center gap-3\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium text-text-primary\">{p.name}</span>\n {p.appUrl && (\n <a\n href={p.appUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => e.stopPropagation()}\n >\n <ExternalLink className=\"h-3 w-3 text-text-secondary hover:text-text-primary\" />\n </a>\n )}\n </div>\n <div className=\"truncate text-xs text-text-secondary\">{p.slug}</div>\n </div>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n width: '110px',\n render: (p: ProductItem) => (\n <Badge\n variant={STATUS_VARIANT[p.status as ProductStatus] ?? 'outline'}\n className=\"text-xs capitalize\"\n >\n {p.status.toLowerCase().replace('_', ' ')}\n </Badge>\n ),\n },\n {\n key: 'activeUsers',\n header: 'Users',\n width: '80px',\n align: 'right' as const,\n render: (p: ProductItem) => <span className=\"text-sm\">{p.activeUsers}</span>,\n },\n {\n key: 'updatedAt',\n header: 'Updated',\n width: '110px',\n render: (p: ProductItem) => formatDate(p.updatedAt),\n },\n ];\n\n return (\n <PermissionGuard\n allowed={canViewProducts}\n isLoading={permissionsLoading}\n pageTitle=\"Products\"\n denyMessage=\"You don't have permission to view products.\"\n >\n <PageLayout\n title=\"Products\"\n gradientWord=\"Products\"\n description={`Platform products and services${total ? ` · ${total} total` : ''}`}\n actions={\n <Button onClick={handleSeedProducts} disabled={isSeeding}>\n <Database className=\"mr-2 h-4 w-4\" />\n {isSeeding ? 'Seeding...' : 'Seed Active Products'}\n </Button>\n }\n >\n {/* Stats */}\n {stats && (\n <MetricsGrid className=\"mb-6\">\n <MetricCard\n title=\"Total Products\"\n hero\n value={stats.total}\n icon={<Package className=\"h-5 w-5\" />}\n />\n <MetricCard title=\"Active\" value={stats.active} icon={<Server className=\"h-5 w-5\" />} />\n <MetricCard\n title=\"Waitlist\"\n value={stats.waitlist}\n icon={<Package className=\"h-5 w-5\" />}\n />\n <MetricCard title=\"Draft\" value={stats.draft} icon={<Package className=\"h-5 w-5\" />} />\n </MetricsGrid>\n )}\n\n <div className=\"space-y-4\">\n <div className=\"relative max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search products...\"\n value={search}\n onChange={(e) => {\n setPage(1);\n setSearch(e.target.value);\n }}\n className=\"pl-9\"\n />\n </div>\n\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 Failed to load products: {error.message}\n </div>\n )}\n {seedMessage && (\n <div className=\"rounded-md border border-border-subtle bg-bg-sunken p-4 text-sm text-text-primary\">\n {seedMessage}\n </div>\n )}\n {seedError && (\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 {seedError}\n </div>\n )}\n\n <DataTable\n columns={columns}\n data={products}\n keyExtractor={(p) => p.id}\n loading={loading}\n emptyState={\n <EmptyState\n illustration=\"empty-search\"\n icon={<Package className=\"h-8 w-8\" />}\n title=\"No products found\"\n description=\"No platform products match your search\"\n />\n }\n />\n\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 </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
@@ -81,6 +81,7 @@ function g() {
81
81
  denyMessage: "You don't have permission to view referral analytics.",
82
82
  children: /* @__PURE__ */ m(r, {
83
83
  title: "Referrals",
84
+ gradientWord: "Referrals",
84
85
  description: "Inspect top affiliates, journey drilldowns, conversions, and revenue attribution.",
85
86
  actions: /* @__PURE__ */ h("button", {
86
87
  type: "button",
@@ -151,6 +152,7 @@ function g() {
151
152
  })
152
153
  }, e.codeId))
153
154
  }) : /* @__PURE__ */ m(n, {
155
+ illustration: "empty-data",
154
156
  icon: /* @__PURE__ */ m(d, { className: "h-8 w-8" }),
155
157
  title: "No affiliate performance yet",
156
158
  description: "Top-performing affiliate codes will appear here once journeys are recorded."
@@ -226,6 +228,7 @@ function g() {
226
228
  }, e.id)) })]
227
229
  })
228
230
  }) : /* @__PURE__ */ m(n, {
231
+ illustration: "empty-search",
229
232
  icon: /* @__PURE__ */ m(u, { className: "h-8 w-8" }),
230
233
  title: "No journeys found",
231
234
  description: "Journey-level drilldowns will appear once referral traffic flows through product touchpoints."
@@ -254,6 +257,7 @@ function g() {
254
257
  })
255
258
  }, e.id))
256
259
  }) : /* @__PURE__ */ m(n, {
260
+ illustration: "empty-generic",
257
261
  icon: /* @__PURE__ */ m(u, { className: "h-8 w-8" }),
258
262
  title: "Select a journey",
259
263
  description: "Choose a journey from the table above to inspect its critical events."
@@ -1 +1 @@
1
- {"version":3,"file":"ReferralsPage.js","names":[],"sources":["../../../src/admin/pages/ReferralsPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Activity, Link2, RefreshCw, Users } from 'lucide-react';\n\nimport { PageLayout, EmptyState, PermissionGuard } from '../components';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport { useAdmin } from '../providers/AdminProvider';\nimport { directActivityGraphqlRequest } from '../utils/directActivityGraphql';\nimport { formatRelativeTime } from '../utils/formatters';\n\ntype ReferralSummary = {\n totalCodes: number;\n totalVisits: number;\n totalJourneys: number;\n totalSignups: number;\n totalPaidConversions: number;\n totalStorePurchases: number;\n totalRevenueAmount: number;\n totalRewardAmount: number;\n topAffiliates: Array<{\n codeId: string;\n codeValue: string;\n ownerUserId: string;\n visits: number;\n signups: number;\n paidConversions: number;\n storePurchases: number;\n revenueAmount: number;\n rewardAmount: number;\n }>;\n};\n\ntype ReferralJourney = {\n id: string;\n codeValue: string;\n identifiedUserId?: string | null;\n productSlug: string;\n currentPath?: string | null;\n geographyCountry?: string | null;\n mode?: string | null;\n status: string;\n lastVisitedAt: string;\n};\n\ntype ReferralEvent = {\n id: string;\n type: string;\n valueAmount?: number | null;\n currency?: string | null;\n occurredAt: string;\n};\n\nexport function ReferralsPage() {\n const { canViewDashboard, isLoading: permissionsLoading } = useAdminPermissions();\n const { apiGatewayUrl, authToken, currentUser } = useAdmin();\n const canViewReferrals = canViewDashboard || currentUser?.role === 'PLATFORM_SUPERADMIN';\n\n const [summary, setSummary] = useState<ReferralSummary | null>(null);\n const [journeys, setJourneys] = useState<ReferralJourney[]>([]);\n const [selectedJourneyId, setSelectedJourneyId] = useState<string | null>(null);\n const [selectedJourneyEvents, setSelectedJourneyEvents] = useState<ReferralEvent[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const requestContext = useMemo(\n () => ({\n apiGatewayUrl,\n authToken,\n actorId: currentUser?.id,\n actorType: 'super_admin' as const,\n }),\n [apiGatewayUrl, authToken, currentUser?.id]\n );\n\n const loadData = async () => {\n setLoading(true);\n setError(null);\n try {\n const [summaryData, journeyData] = await Promise.all([\n directActivityGraphqlRequest<{ adminAffiliateAnalytics: ReferralSummary }>({\n ...requestContext,\n query: `query AdminAffiliateAnalytics {\n adminAffiliateAnalytics {\n totalCodes\n totalVisits\n totalJourneys\n totalSignups\n totalPaidConversions\n totalStorePurchases\n totalRevenueAmount\n totalRewardAmount\n topAffiliates {\n codeId\n codeValue\n ownerUserId\n visits\n signups\n paidConversions\n storePurchases\n revenueAmount\n rewardAmount\n }\n }\n }`,\n }),\n directActivityGraphqlRequest<{ affiliateJourneys: { items: ReferralJourney[] } }>({\n ...requestContext,\n query: `query AffiliateJourneys {\n affiliateJourneys(limit: 25, offset: 0) {\n items {\n id\n codeValue\n identifiedUserId\n productSlug\n currentPath\n geographyCountry\n mode\n status\n lastVisitedAt\n }\n }\n }`,\n }),\n ]);\n\n setSummary(summaryData.adminAffiliateAnalytics);\n setJourneys(journeyData.affiliateJourneys.items);\n } catch (loadError) {\n setError(\n loadError instanceof Error ? loadError.message : 'Failed to load referrals dashboard'\n );\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [requestContext]);\n\n useEffect(() => {\n if (!selectedJourneyId) {\n setSelectedJourneyEvents([]);\n return;\n }\n\n void directActivityGraphqlRequest<\n { affiliateJourneyEvents: ReferralEvent[] },\n { journeyId: string }\n >({\n ...requestContext,\n query: `query AffiliateJourneyEvents($journeyId: ID!) {\n affiliateJourneyEvents(journeyId: $journeyId) {\n id\n type\n valueAmount\n currency\n occurredAt\n }\n }`,\n variables: { journeyId: selectedJourneyId },\n })\n .then((response) => setSelectedJourneyEvents(response.affiliateJourneyEvents))\n .catch((loadError) => {\n setError(loadError instanceof Error ? loadError.message : 'Failed to load journey events');\n });\n }, [requestContext, selectedJourneyId]);\n\n const cards = summary\n ? [\n { title: 'Codes', value: summary.totalCodes, icon: <Link2 className=\"h-5 w-5\" /> },\n { title: 'Visits', value: summary.totalVisits, icon: <Activity className=\"h-5 w-5\" /> },\n { title: 'Signups', value: summary.totalSignups, icon: <Users className=\"h-5 w-5\" /> },\n {\n title: 'Paid conversions',\n value: summary.totalPaidConversions + summary.totalStorePurchases,\n icon: <Users className=\"h-5 w-5\" />,\n },\n ]\n : [];\n\n return (\n <PermissionGuard\n allowed={canViewReferrals}\n isLoading={permissionsLoading}\n pageTitle=\"Referrals\"\n denyMessage=\"You don't have permission to view referral analytics.\"\n >\n <PageLayout\n title=\"Referrals\"\n description=\"Inspect top affiliates, journey drilldowns, conversions, and revenue attribution.\"\n actions={\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary\"\n >\n <RefreshCw className=\"h-4 w-4\" />\n Refresh\n </button>\n }\n >\n <div className=\"space-y-6\">\n {loading ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {[1, 2, 3, 4].map((item) => (\n <div\n key={item}\n className=\"h-28 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n ) : summary ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {cards.map((card) => (\n <div key={card.title} className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <div className=\"mb-3 flex items-center justify-between text-text-secondary\">\n <span className=\"text-sm font-medium\">{card.title}</span>\n {card.icon}\n </div>\n <p className=\"text-2xl font-semibold text-text-primary\">{card.value}</p>\n </div>\n ))}\n </div>\n ) : null}\n\n {error ? (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n\n <div className=\"grid gap-6 xl:grid-cols-[1.15fr,1.85fr]\">\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Top affiliates</h2>\n {summary?.topAffiliates.length ? (\n <div className=\"space-y-3\">\n {summary.topAffiliates.map((affiliate) => (\n <div\n key={affiliate.codeId}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {affiliate.codeValue}\n </p>\n <p className=\"text-xs text-text-secondary\">\n Owner: {affiliate.ownerUserId}\n </p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {affiliate.visits} visits\n </p>\n <p className=\"text-xs text-text-secondary\">\n {affiliate.paidConversions + affiliate.storePurchases} conversions\n </p>\n </div>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <EmptyState\n icon={<Link2 className=\"h-8 w-8\" />}\n title=\"No affiliate performance yet\"\n description=\"Top-performing affiliate codes will appear here once journeys are recorded.\"\n />\n )}\n </section>\n\n <section className=\"space-y-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Recent journeys</h2>\n {journeys.length ? (\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle text-left text-text-secondary\">\n <th className=\"px-3 py-2\">Code</th>\n <th className=\"px-3 py-2\">Product</th>\n <th className=\"px-3 py-2\">Path</th>\n <th className=\"px-3 py-2\">Country</th>\n <th className=\"px-3 py-2\">Status</th>\n <th className=\"px-3 py-2\">Visited</th>\n </tr>\n </thead>\n <tbody>\n {journeys.map((journey) => (\n <tr\n key={journey.id}\n className=\"cursor-pointer border-b border-border-subtle/70 text-text-primary\"\n onClick={() => setSelectedJourneyId(journey.id)}\n >\n <td className=\"px-3 py-2 font-medium\">{journey.codeValue}</td>\n <td className=\"px-3 py-2\">{journey.productSlug}</td>\n <td className=\"px-3 py-2\">{journey.currentPath || '-'}</td>\n <td className=\"px-3 py-2\">{journey.geographyCountry || '-'}</td>\n <td className=\"px-3 py-2\">{journey.status}</td>\n <td className=\"px-3 py-2\">\n {formatRelativeTime(journey.lastVisitedAt)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n ) : (\n <EmptyState\n icon={<Activity className=\"h-8 w-8\" />}\n title=\"No journeys found\"\n description=\"Journey-level drilldowns will appear once referral traffic flows through product touchpoints.\"\n />\n )}\n </div>\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Journey events</h2>\n {selectedJourneyEvents.length ? (\n <div className=\"space-y-3\">\n {selectedJourneyEvents.map((event) => (\n <div\n key={event.id}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">{event.type}</p>\n <p className=\"text-xs text-text-secondary\">\n {formatRelativeTime(event.occurredAt)}\n </p>\n </div>\n <p className=\"text-sm font-medium text-text-primary\">\n {event.valueAmount\n ? `${event.valueAmount} ${event.currency || 'USD'}`\n : '—'}\n </p>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <EmptyState\n icon={<Activity className=\"h-8 w-8\" />}\n title=\"Select a journey\"\n description=\"Choose a journey from the table above to inspect its critical events.\"\n />\n )}\n </div>\n </section>\n </div>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ReferralsPage.js","names":[],"sources":["../../../src/admin/pages/ReferralsPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Activity, Link2, RefreshCw, Users } from 'lucide-react';\n\nimport { PageLayout, EmptyState, PermissionGuard } from '../components';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport { useAdmin } from '../providers/AdminProvider';\nimport { directActivityGraphqlRequest } from '../utils/directActivityGraphql';\nimport { formatRelativeTime } from '../utils/formatters';\n\ntype ReferralSummary = {\n totalCodes: number;\n totalVisits: number;\n totalJourneys: number;\n totalSignups: number;\n totalPaidConversions: number;\n totalStorePurchases: number;\n totalRevenueAmount: number;\n totalRewardAmount: number;\n topAffiliates: Array<{\n codeId: string;\n codeValue: string;\n ownerUserId: string;\n visits: number;\n signups: number;\n paidConversions: number;\n storePurchases: number;\n revenueAmount: number;\n rewardAmount: number;\n }>;\n};\n\ntype ReferralJourney = {\n id: string;\n codeValue: string;\n identifiedUserId?: string | null;\n productSlug: string;\n currentPath?: string | null;\n geographyCountry?: string | null;\n mode?: string | null;\n status: string;\n lastVisitedAt: string;\n};\n\ntype ReferralEvent = {\n id: string;\n type: string;\n valueAmount?: number | null;\n currency?: string | null;\n occurredAt: string;\n};\n\nexport function ReferralsPage() {\n const { canViewDashboard, isLoading: permissionsLoading } = useAdminPermissions();\n const { apiGatewayUrl, authToken, currentUser } = useAdmin();\n const canViewReferrals = canViewDashboard || currentUser?.role === 'PLATFORM_SUPERADMIN';\n\n const [summary, setSummary] = useState<ReferralSummary | null>(null);\n const [journeys, setJourneys] = useState<ReferralJourney[]>([]);\n const [selectedJourneyId, setSelectedJourneyId] = useState<string | null>(null);\n const [selectedJourneyEvents, setSelectedJourneyEvents] = useState<ReferralEvent[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const requestContext = useMemo(\n () => ({\n apiGatewayUrl,\n authToken,\n actorId: currentUser?.id,\n actorType: 'super_admin' as const,\n }),\n [apiGatewayUrl, authToken, currentUser?.id]\n );\n\n const loadData = async () => {\n setLoading(true);\n setError(null);\n try {\n const [summaryData, journeyData] = await Promise.all([\n directActivityGraphqlRequest<{ adminAffiliateAnalytics: ReferralSummary }>({\n ...requestContext,\n query: `query AdminAffiliateAnalytics {\n adminAffiliateAnalytics {\n totalCodes\n totalVisits\n totalJourneys\n totalSignups\n totalPaidConversions\n totalStorePurchases\n totalRevenueAmount\n totalRewardAmount\n topAffiliates {\n codeId\n codeValue\n ownerUserId\n visits\n signups\n paidConversions\n storePurchases\n revenueAmount\n rewardAmount\n }\n }\n }`,\n }),\n directActivityGraphqlRequest<{ affiliateJourneys: { items: ReferralJourney[] } }>({\n ...requestContext,\n query: `query AffiliateJourneys {\n affiliateJourneys(limit: 25, offset: 0) {\n items {\n id\n codeValue\n identifiedUserId\n productSlug\n currentPath\n geographyCountry\n mode\n status\n lastVisitedAt\n }\n }\n }`,\n }),\n ]);\n\n setSummary(summaryData.adminAffiliateAnalytics);\n setJourneys(journeyData.affiliateJourneys.items);\n } catch (loadError) {\n setError(\n loadError instanceof Error ? loadError.message : 'Failed to load referrals dashboard'\n );\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [requestContext]);\n\n useEffect(() => {\n if (!selectedJourneyId) {\n setSelectedJourneyEvents([]);\n return;\n }\n\n void directActivityGraphqlRequest<\n { affiliateJourneyEvents: ReferralEvent[] },\n { journeyId: string }\n >({\n ...requestContext,\n query: `query AffiliateJourneyEvents($journeyId: ID!) {\n affiliateJourneyEvents(journeyId: $journeyId) {\n id\n type\n valueAmount\n currency\n occurredAt\n }\n }`,\n variables: { journeyId: selectedJourneyId },\n })\n .then((response) => setSelectedJourneyEvents(response.affiliateJourneyEvents))\n .catch((loadError) => {\n setError(loadError instanceof Error ? loadError.message : 'Failed to load journey events');\n });\n }, [requestContext, selectedJourneyId]);\n\n const cards = summary\n ? [\n { title: 'Codes', value: summary.totalCodes, icon: <Link2 className=\"h-5 w-5\" /> },\n { title: 'Visits', value: summary.totalVisits, icon: <Activity className=\"h-5 w-5\" /> },\n { title: 'Signups', value: summary.totalSignups, icon: <Users className=\"h-5 w-5\" /> },\n {\n title: 'Paid conversions',\n value: summary.totalPaidConversions + summary.totalStorePurchases,\n icon: <Users className=\"h-5 w-5\" />,\n },\n ]\n : [];\n\n return (\n <PermissionGuard\n allowed={canViewReferrals}\n isLoading={permissionsLoading}\n pageTitle=\"Referrals\"\n denyMessage=\"You don't have permission to view referral analytics.\"\n >\n <PageLayout\n title=\"Referrals\"\n gradientWord=\"Referrals\"\n description=\"Inspect top affiliates, journey drilldowns, conversions, and revenue attribution.\"\n actions={\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary\"\n >\n <RefreshCw className=\"h-4 w-4\" />\n Refresh\n </button>\n }\n >\n <div className=\"space-y-6\">\n {loading ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {[1, 2, 3, 4].map((item) => (\n <div\n key={item}\n className=\"h-28 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n ) : summary ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {cards.map((card) => (\n <div key={card.title} className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <div className=\"mb-3 flex items-center justify-between text-text-secondary\">\n <span className=\"text-sm font-medium\">{card.title}</span>\n {card.icon}\n </div>\n <p className=\"text-2xl font-semibold text-text-primary\">{card.value}</p>\n </div>\n ))}\n </div>\n ) : null}\n\n {error ? (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n\n <div className=\"grid gap-6 xl:grid-cols-[1.15fr,1.85fr]\">\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Top affiliates</h2>\n {summary?.topAffiliates.length ? (\n <div className=\"space-y-3\">\n {summary.topAffiliates.map((affiliate) => (\n <div\n key={affiliate.codeId}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {affiliate.codeValue}\n </p>\n <p className=\"text-xs text-text-secondary\">\n Owner: {affiliate.ownerUserId}\n </p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {affiliate.visits} visits\n </p>\n <p className=\"text-xs text-text-secondary\">\n {affiliate.paidConversions + affiliate.storePurchases} conversions\n </p>\n </div>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Link2 className=\"h-8 w-8\" />}\n title=\"No affiliate performance yet\"\n description=\"Top-performing affiliate codes will appear here once journeys are recorded.\"\n />\n )}\n </section>\n\n <section className=\"space-y-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Recent journeys</h2>\n {journeys.length ? (\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle text-left text-text-secondary\">\n <th className=\"px-3 py-2\">Code</th>\n <th className=\"px-3 py-2\">Product</th>\n <th className=\"px-3 py-2\">Path</th>\n <th className=\"px-3 py-2\">Country</th>\n <th className=\"px-3 py-2\">Status</th>\n <th className=\"px-3 py-2\">Visited</th>\n </tr>\n </thead>\n <tbody>\n {journeys.map((journey) => (\n <tr\n key={journey.id}\n className=\"cursor-pointer border-b border-border-subtle/70 text-text-primary\"\n onClick={() => setSelectedJourneyId(journey.id)}\n >\n <td className=\"px-3 py-2 font-medium\">{journey.codeValue}</td>\n <td className=\"px-3 py-2\">{journey.productSlug}</td>\n <td className=\"px-3 py-2\">{journey.currentPath || '-'}</td>\n <td className=\"px-3 py-2\">{journey.geographyCountry || '-'}</td>\n <td className=\"px-3 py-2\">{journey.status}</td>\n <td className=\"px-3 py-2\">\n {formatRelativeTime(journey.lastVisitedAt)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-search\"\n icon={<Activity className=\"h-8 w-8\" />}\n title=\"No journeys found\"\n description=\"Journey-level drilldowns will appear once referral traffic flows through product touchpoints.\"\n />\n )}\n </div>\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Journey events</h2>\n {selectedJourneyEvents.length ? (\n <div className=\"space-y-3\">\n {selectedJourneyEvents.map((event) => (\n <div\n key={event.id}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">{event.type}</p>\n <p className=\"text-xs text-text-secondary\">\n {formatRelativeTime(event.occurredAt)}\n </p>\n </div>\n <p className=\"text-sm font-medium text-text-primary\">\n {event.valueAmount\n ? `${event.valueAmount} ${event.currency || 'USD'}`\n : '—'}\n </p>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-generic\"\n icon={<Activity className=\"h-8 w-8\" />}\n title=\"Select a journey\"\n description=\"Choose a journey from the table above to inspect its critical events.\"\n />\n )}\n </div>\n </section>\n </div>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
@@ -16,6 +16,7 @@ function v() {
16
16
  }, [w, v]);
17
17
  return /* @__PURE__ */ g(i, {
18
18
  title: "Roles & Permissions",
19
+ gradientWord: "Permissions",
19
20
  description: "Manage platform roles and global permissions",
20
21
  actions: /* @__PURE__ */ _(t, {
21
22
  size: "sm",
@@ -42,6 +43,7 @@ function v() {
42
43
  })
43
44
  })]
44
45
  }), S ? /* @__PURE__ */ g(n, {
46
+ illustration: "empty-generic",
45
47
  icon: /* @__PURE__ */ g(m, { className: "h-8 w-8 text-status-error-text" }),
46
48
  title: "Error loading roles",
47
49
  description: S.message
@@ -125,6 +127,7 @@ function v() {
125
127
  keyExtractor: (e) => e.id,
126
128
  loading: x,
127
129
  emptyState: /* @__PURE__ */ g(n, {
130
+ illustration: "empty-people",
128
131
  icon: /* @__PURE__ */ g(m, { className: "h-8 w-8" }),
129
132
  title: "No roles found",
130
133
  description: v ? `No roles match "${v}"` : "Roles and permissions will appear here when they are created",
@@ -1 +1 @@
1
- {"version":3,"file":"RolesPermissionsPage.js","names":[],"sources":["../../../src/admin/pages/RolesPermissionsPage.tsx"],"sourcesContent":["import { Shield, Search, Plus, Pencil, Trash2 } from 'lucide-react';\nimport { useState, useMemo } from 'react';\nimport { PageLayout, Input, Button, EmptyState, DataTable, Badge, RowActions } from '../components';\nimport { useListRolesQuery, useGetResourceTypesQuery } from '../../generated/global-operations';\n\nexport function RolesPermissionsPage() {\n const [searchQuery, setSearchQuery] = useState('');\n\n const {\n data: rolesData,\n loading: rolesLoading,\n error: rolesError,\n } = useListRolesQuery({\n variables: { scopeId: 'global' },\n });\n\n const { data: resourceTypesData } = useGetResourceTypesQuery();\n\n const roles = useMemo(() => rolesData?.roles ?? [], [rolesData]);\n\n const filteredRoles = useMemo(() => {\n if (!searchQuery.trim()) return roles;\n const query = searchQuery.toLowerCase();\n return roles.filter(\n (role: any) =>\n role.name.toLowerCase().includes(query) || role.description?.toLowerCase().includes(query)\n );\n }, [roles, searchQuery]);\n\n const columns = [\n {\n key: 'name',\n header: 'Role Name',\n render: (role: any) => (\n <div className=\"flex flex-col text-left\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-semibold text-text-primary\">{role.name}</span>\n {role.isSystem && (\n <Badge variant=\"secondary\" className=\"h-4 px-1 text-[10px]\">\n System\n </Badge>\n )}\n </div>\n {role.description && (\n <span className=\"mt-0.5 line-clamp-1 text-xs text-text-secondary\">\n {role.description}\n </span>\n )}\n </div>\n ),\n },\n {\n key: 'priority',\n header: 'Priority',\n align: 'center' as const,\n width: '80px',\n render: (role: any) => (\n <span className=\"rounded bg-bg-sunken px-2 py-0.5 text-xs font-medium\">{role.priority}</span>\n ),\n },\n {\n key: 'permissions',\n header: 'Default Permissions',\n render: (role: any) => (\n <div className=\"flex max-w-md flex-wrap gap-1 text-left\">\n {role.defaultPermissions && role.defaultPermissions.length > 0 ? (\n role.defaultPermissions.map((perm: any) => (\n <Badge\n key={perm.id}\n variant=\"outline\"\n className=\"bg-bg-surface/50 px-1 py-0 font-normal text-[10px]\"\n >\n <span className=\"mr-1 text-text-secondary\">{perm.resourceType}:</span>\n <span className=\"font-medium\">{perm.level}</span>\n </Badge>\n ))\n ) : (\n <span className=\"text-xs italic text-text-secondary\">No permissions defined</span>\n )}\n </div>\n ),\n },\n {\n key: 'actions',\n header: '',\n align: 'right' as const,\n width: '50px',\n render: (role: any) => (\n <RowActions\n actions={[\n {\n key: 'edit',\n label: 'Edit Role',\n icon: <Pencil className=\"h-4 w-4\" />,\n onClick: () => console.log('Edit', role.id),\n disabled: role.isSystem,\n },\n {\n key: 'delete',\n label: 'Delete Role',\n icon: <Trash2 className=\"h-4 w-4\" />,\n onClick: () => console.log('Delete', role.id),\n destructive: true,\n disabled: role.isSystem,\n separator: true,\n },\n ]}\n />\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"Roles & Permissions\"\n description=\"Manage platform roles and global permissions\"\n actions={\n <Button size=\"sm\">\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Role\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"relative flex-1 max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search roles...\"\n className=\"pl-9\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n />\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"outline\" className=\"h-8\">\n {resourceTypesData?.resourceTypes?.length ?? 0} Resource Types\n </Badge>\n </div>\n </div>\n\n {rolesError ? (\n <EmptyState\n icon={<Shield className=\"h-8 w-8 text-status-error-text\" />}\n title=\"Error loading roles\"\n description={rolesError.message}\n />\n ) : (\n <DataTable\n columns={columns}\n data={filteredRoles}\n keyExtractor={(role) => role.id}\n loading={rolesLoading}\n emptyState={\n <EmptyState\n icon={<Shield className=\"h-8 w-8\" />}\n title=\"No roles found\"\n description={\n searchQuery\n ? `No roles match \"${searchQuery}\"`\n : 'Roles and permissions will appear here when they are created'\n }\n action={\n searchQuery ? (\n <Button variant=\"outline\" size=\"sm\" onClick={() => setSearchQuery('')}>\n Clear Search\n </Button>\n ) : undefined\n }\n />\n }\n />\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RolesPermissionsPage.js","names":[],"sources":["../../../src/admin/pages/RolesPermissionsPage.tsx"],"sourcesContent":["import { Shield, Search, Plus, Pencil, Trash2 } from 'lucide-react';\nimport { useState, useMemo } from 'react';\nimport { PageLayout, Input, Button, EmptyState, DataTable, Badge, RowActions } from '../components';\nimport { useListRolesQuery, useGetResourceTypesQuery } from '../../generated/global-operations';\n\nexport function RolesPermissionsPage() {\n const [searchQuery, setSearchQuery] = useState('');\n\n const {\n data: rolesData,\n loading: rolesLoading,\n error: rolesError,\n } = useListRolesQuery({\n variables: { scopeId: 'global' },\n });\n\n const { data: resourceTypesData } = useGetResourceTypesQuery();\n\n const roles = useMemo(() => rolesData?.roles ?? [], [rolesData]);\n\n const filteredRoles = useMemo(() => {\n if (!searchQuery.trim()) return roles;\n const query = searchQuery.toLowerCase();\n return roles.filter(\n (role: any) =>\n role.name.toLowerCase().includes(query) || role.description?.toLowerCase().includes(query)\n );\n }, [roles, searchQuery]);\n\n const columns = [\n {\n key: 'name',\n header: 'Role Name',\n render: (role: any) => (\n <div className=\"flex flex-col text-left\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-semibold text-text-primary\">{role.name}</span>\n {role.isSystem && (\n <Badge variant=\"secondary\" className=\"h-4 px-1 text-[10px]\">\n System\n </Badge>\n )}\n </div>\n {role.description && (\n <span className=\"mt-0.5 line-clamp-1 text-xs text-text-secondary\">\n {role.description}\n </span>\n )}\n </div>\n ),\n },\n {\n key: 'priority',\n header: 'Priority',\n align: 'center' as const,\n width: '80px',\n render: (role: any) => (\n <span className=\"rounded bg-bg-sunken px-2 py-0.5 text-xs font-medium\">{role.priority}</span>\n ),\n },\n {\n key: 'permissions',\n header: 'Default Permissions',\n render: (role: any) => (\n <div className=\"flex max-w-md flex-wrap gap-1 text-left\">\n {role.defaultPermissions && role.defaultPermissions.length > 0 ? (\n role.defaultPermissions.map((perm: any) => (\n <Badge\n key={perm.id}\n variant=\"outline\"\n className=\"bg-bg-surface/50 px-1 py-0 font-normal text-[10px]\"\n >\n <span className=\"mr-1 text-text-secondary\">{perm.resourceType}:</span>\n <span className=\"font-medium\">{perm.level}</span>\n </Badge>\n ))\n ) : (\n <span className=\"text-xs italic text-text-secondary\">No permissions defined</span>\n )}\n </div>\n ),\n },\n {\n key: 'actions',\n header: '',\n align: 'right' as const,\n width: '50px',\n render: (role: any) => (\n <RowActions\n actions={[\n {\n key: 'edit',\n label: 'Edit Role',\n icon: <Pencil className=\"h-4 w-4\" />,\n onClick: () => console.log('Edit', role.id),\n disabled: role.isSystem,\n },\n {\n key: 'delete',\n label: 'Delete Role',\n icon: <Trash2 className=\"h-4 w-4\" />,\n onClick: () => console.log('Delete', role.id),\n destructive: true,\n disabled: role.isSystem,\n separator: true,\n },\n ]}\n />\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"Roles & Permissions\"\n gradientWord=\"Permissions\"\n description=\"Manage platform roles and global permissions\"\n actions={\n <Button size=\"sm\">\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Role\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"relative flex-1 max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search roles...\"\n className=\"pl-9\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n />\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"outline\" className=\"h-8\">\n {resourceTypesData?.resourceTypes?.length ?? 0} Resource Types\n </Badge>\n </div>\n </div>\n\n {rolesError ? (\n <EmptyState\n illustration=\"empty-generic\"\n icon={<Shield className=\"h-8 w-8 text-status-error-text\" />}\n title=\"Error loading roles\"\n description={rolesError.message}\n />\n ) : (\n <DataTable\n columns={columns}\n data={filteredRoles}\n keyExtractor={(role) => role.id}\n loading={rolesLoading}\n emptyState={\n <EmptyState\n illustration=\"empty-people\"\n icon={<Shield className=\"h-8 w-8\" />}\n title=\"No roles found\"\n description={\n searchQuery\n ? `No roles match \"${searchQuery}\"`\n : 'Roles and permissions will appear here when they are created'\n }\n action={\n searchQuery ? (\n <Button variant=\"outline\" size=\"sm\" onClick={() => setSearchQuery('')}>\n Clear Search\n </Button>\n ) : undefined\n }\n />\n }\n />\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;AAKA,SAAgB,IAAuB;CACrC,IAAM,CAAC,GAAa,KAAkB,EAAS,GAAG,EAE5C,EACJ,MAAM,GACN,SAAS,GACT,OAAO,MACL,EAAkB,EACpB,WAAW,EAAE,SAAS,UAAU,EACjC,CAAC,EAEI,EAAE,MAAM,MAAsB,GAA0B,EAExD,IAAQ,QAAc,GAAW,SAAS,EAAE,EAAE,CAAC,EAAU,CAAC,EAE1D,IAAgB,QAAc;AAClC,MAAI,CAAC,EAAY,MAAM,CAAE,QAAO;EAChC,IAAM,IAAQ,EAAY,aAAa;AACvC,SAAO,EAAM,QACV,MACC,EAAK,KAAK,aAAa,CAAC,SAAS,EAAM,IAAI,EAAK,aAAa,aAAa,CAAC,SAAS,EAAM,CAC7F;IACA,CAAC,GAAO,EAAY,CAAC;AAqFxB,QACE,kBAAC,GAAD;EACE,OAAM;EACN,cAAa;EACb,aAAY;EACZ,SACE,kBAAC,GAAD;GAAQ,MAAK;aAAb,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,cAE1B;;YAGX,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,GAAD;MACE,aAAY;MACZ,WAAU;MACV,OAAO;MACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;MAC/C,CAAA,CACE;QAEN,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAO,SAAQ;MAAU,WAAU;gBAAnC,CACG,GAAmB,eAAe,UAAU,GAAE,kBACzC;;KACJ,CAAA,CACF;OAEL,IACC,kBAAC,GAAD;IACE,cAAa;IACb,MAAM,kBAAC,GAAD,EAAQ,WAAU,kCAAmC,CAAA;IAC3D,OAAM;IACN,aAAa,EAAW;IACxB,CAAA,GAEF,kBAAC,GAAD;IACW,SA3HH;KACd;MACE,KAAK;MACL,QAAQ;MACR,SAAS,MACP,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAmC,EAAK;SAAY,CAAA,EACnE,EAAK,YACJ,kBAAC,GAAD;SAAO,SAAQ;SAAY,WAAU;mBAAuB;SAEpD,CAAA,CAEN;WACL,EAAK,eACJ,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAK;QACD,CAAA,CAEL;;MAET;KACD;MACE,KAAK;MACL,QAAQ;MACR,OAAO;MACP,OAAO;MACP,SAAS,MACP,kBAAC,QAAD;OAAM,WAAU;iBAAwD,EAAK;OAAgB,CAAA;MAEhG;KACD;MACE,KAAK;MACL,QAAQ;MACR,SAAS,MACP,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAK,sBAAsB,EAAK,mBAAmB,SAAS,IAC3D,EAAK,mBAAmB,KAAK,MAC3B,kBAAC,GAAD;QAEE,SAAQ;QACR,WAAU;kBAHZ,CAKE,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CAA4C,EAAK,cAAa,IAAQ;YACtE,kBAAC,QAAD;SAAM,WAAU;mBAAe,EAAK;SAAa,CAAA,CAC3C;UAND,EAAK,GAMJ,CACR,GAEF,kBAAC,QAAD;QAAM,WAAU;kBAAqC;QAA6B,CAAA;OAEhF,CAAA;MAET;KACD;MACE,KAAK;MACL,QAAQ;MACR,OAAO;MACP,OAAO;MACP,SAAS,MACP,kBAAC,GAAD,EACE,SAAS,CACP;OACE,KAAK;OACL,OAAO;OACP,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;OACpC,eAAe,QAAQ,IAAI,QAAQ,EAAK,GAAG;OAC3C,UAAU,EAAK;OAChB,EACD;OACE,KAAK;OACL,OAAO;OACP,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;OACpC,eAAe,QAAQ,IAAI,UAAU,EAAK,GAAG;OAC7C,aAAa;OACb,UAAU,EAAK;OACf,WAAW;OACZ,CACF,EACD,CAAA;MAEL;KACF;IA2CS,MAAM;IACN,eAAe,MAAS,EAAK;IAC7B,SAAS;IACT,YACE,kBAAC,GAAD;KACE,cAAa;KACb,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;KACpC,OAAM;KACN,aACE,IACI,mBAAmB,EAAY,KAC/B;KAEN,QACE,IACE,kBAAC,GAAD;MAAQ,SAAQ;MAAU,MAAK;MAAK,eAAe,EAAe,GAAG;gBAAE;MAE9D,CAAA,GACP,KAAA;KAEN,CAAA;IAEJ,CAAA,CAEA;;EACK,CAAA"}
@@ -185,6 +185,7 @@ function P() {
185
185
  denyMessage: "You don't have permission to view SRE monitoring.",
186
186
  children: /* @__PURE__ */ D(c, {
187
187
  title: "SRE Monitoring",
188
+ gradientWord: "Monitoring",
188
189
  description: "Service health, incidents, and deployment monitoring",
189
190
  actions: /* @__PURE__ */ O(n, {
190
191
  variant: "outline",
@@ -200,6 +201,7 @@ function P() {
200
201
  /* @__PURE__ */ O(p, { children: [
201
202
  /* @__PURE__ */ D(f, {
202
203
  title: "Healthy Services",
204
+ hero: !0,
203
205
  value: P,
204
206
  icon: /* @__PURE__ */ D(y, { className: "h-5 w-5 text-success" }),
205
207
  subtitle: `of ${k.length} total`
@@ -338,6 +340,7 @@ function P() {
338
340
  data: k,
339
341
  keyExtractor: (e) => e.id,
340
342
  emptyState: /* @__PURE__ */ D(s, {
343
+ illustration: "empty-data",
341
344
  icon: /* @__PURE__ */ D(w, { className: "h-8 w-8" }),
342
345
  title: "No services found"
343
346
  })
@@ -390,6 +393,7 @@ function P() {
390
393
  data: j,
391
394
  keyExtractor: (e) => e.id,
392
395
  emptyState: /* @__PURE__ */ D(s, {
396
+ illustration: "empty-data",
393
397
  icon: /* @__PURE__ */ D(C, { className: "h-8 w-8" }),
394
398
  title: "No recent deployments"
395
399
  })