@burdenoff/microfe-admin 2026.610.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 (81) hide show
  1. package/dist/admin/components/AccessDenied.js +2 -2
  2. package/dist/admin/components/AccessDenied.js.map +1 -1
  3. package/dist/admin/components/ActivityFeed.js +4 -4
  4. package/dist/admin/components/ActivityFeed.js.map +1 -1
  5. package/dist/admin/components/Charts.js +4 -4
  6. package/dist/admin/components/Charts.js.map +1 -1
  7. package/dist/admin/components/ConfirmDialog.js +6 -6
  8. package/dist/admin/components/ConfirmDialog.js.map +1 -1
  9. package/dist/admin/components/DataTable.js +8 -8
  10. package/dist/admin/components/DataTable.js.map +1 -1
  11. package/dist/admin/components/FormSheet.js +7 -7
  12. package/dist/admin/components/FormSheet.js.map +1 -1
  13. package/dist/admin/components/MetricCard.js +43 -40
  14. package/dist/admin/components/MetricCard.js.map +1 -1
  15. package/dist/admin/components/PageLayout.js +28 -22
  16. package/dist/admin/components/PageLayout.js.map +1 -1
  17. package/dist/admin/components/PermissionGuard.js +1 -1
  18. package/dist/admin/components/PermissionGuard.js.map +1 -1
  19. package/dist/admin/components/RowActions.js +2 -2
  20. package/dist/admin/components/RowActions.js.map +1 -1
  21. package/dist/admin/components/TenantMembersPanel.js +15 -15
  22. package/dist/admin/components/TenantMembersPanel.js.map +1 -1
  23. package/dist/admin/components/TenantSsoPanel.js +14 -14
  24. package/dist/admin/components/TenantSsoPanel.js.map +1 -1
  25. package/dist/admin/components/Toast.js +9 -9
  26. package/dist/admin/components/Toast.js.map +1 -1
  27. package/dist/admin/components/forms/SubscriptionForm.js +10 -10
  28. package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
  29. package/dist/admin/components/forms/TenantForm.js +8 -8
  30. package/dist/admin/components/forms/TenantForm.js.map +1 -1
  31. package/dist/admin/components/forms/TicketForm.js +2 -2
  32. package/dist/admin/components/forms/TicketForm.js.map +1 -1
  33. package/dist/admin/components/forms/UserForm.js +8 -8
  34. package/dist/admin/components/forms/UserForm.js.map +1 -1
  35. package/dist/admin/components/ui.js +58 -51
  36. package/dist/admin/components/ui.js.map +1 -1
  37. package/dist/admin/constants/index.js +8 -8
  38. package/dist/admin/constants/index.js.map +1 -1
  39. package/dist/admin/pages/AuditLogsPage.js +10 -8
  40. package/dist/admin/pages/AuditLogsPage.js.map +1 -1
  41. package/dist/admin/pages/BillingPage.js +51 -43
  42. package/dist/admin/pages/BillingPage.js.map +1 -1
  43. package/dist/admin/pages/CommunicationsPage.js +5 -2
  44. package/dist/admin/pages/CommunicationsPage.js.map +1 -1
  45. package/dist/admin/pages/ContentPagesPage.js +2 -0
  46. package/dist/admin/pages/ContentPagesPage.js.map +1 -1
  47. package/dist/admin/pages/DashboardPage.js +7 -4
  48. package/dist/admin/pages/DashboardPage.js.map +1 -1
  49. package/dist/admin/pages/FeatureFlagsPage.js +6 -4
  50. package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
  51. package/dist/admin/pages/OrganizationsListPage.js +8 -6
  52. package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
  53. package/dist/admin/pages/ProductsPage.js +10 -7
  54. package/dist/admin/pages/ProductsPage.js.map +1 -1
  55. package/dist/admin/pages/ReferralsPage.js +27 -23
  56. package/dist/admin/pages/ReferralsPage.js.map +1 -1
  57. package/dist/admin/pages/RolesPermissionsPage.js +11 -8
  58. package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
  59. package/dist/admin/pages/SREPage.js +12 -8
  60. package/dist/admin/pages/SREPage.js.map +1 -1
  61. package/dist/admin/pages/SandboxSettingsPage.js +21 -20
  62. package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
  63. package/dist/admin/pages/SettingsPage.js +154 -142
  64. package/dist/admin/pages/SettingsPage.js.map +1 -1
  65. package/dist/admin/pages/SyncFailuresPage.js +14 -11
  66. package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
  67. package/dist/admin/pages/TenantDetailPage.js +22 -22
  68. package/dist/admin/pages/TenantDetailPage.js.map +1 -1
  69. package/dist/admin/pages/TenantFormRoutePage.js +2 -2
  70. package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
  71. package/dist/admin/pages/TenantsListPage.js +7 -5
  72. package/dist/admin/pages/TenantsListPage.js.map +1 -1
  73. package/dist/admin/pages/UsersListPage.js +8 -6
  74. package/dist/admin/pages/UsersListPage.js.map +1 -1
  75. package/dist/admin/pages/WaitlistPage.js +9 -7
  76. package/dist/admin/pages/WaitlistPage.js.map +1 -1
  77. package/dist/admin/pages/WorkflowsPage.js +6 -3
  78. package/dist/admin/pages/WorkflowsPage.js.map +1 -1
  79. package/dist/admin/pages/WorkspacesListPage.js +9 -8
  80. package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
  81. package/package.json +2 -2
@@ -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-foreground\">{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-muted-foreground hover:text-foreground\" />\n </a>\n )}\n </div>\n <div className=\"truncate text-xs text-muted-foreground\">{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-muted-foreground\" />\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-destructive/50 bg-destructive/10 p-4 text-sm text-destructive\">\n Failed to load products: {error.message}\n </div>\n )}\n {seedMessage && (\n <div className=\"rounded-md border border-border bg-muted p-4 text-sm text-foreground\">\n {seedMessage}\n </div>\n )}\n {seedError && (\n <div className=\"rounded-md border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive\">\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,11 +81,12 @@ 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",
87
88
  onClick: () => void F(),
88
- className: "inline-flex items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-sm font-medium text-foreground",
89
+ 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",
89
90
  children: [/* @__PURE__ */ m(f, { className: "h-4 w-4" }), "Refresh"]
90
91
  }),
91
92
  children: /* @__PURE__ */ h("div", {
@@ -98,59 +99,60 @@ function g() {
98
99
  2,
99
100
  3,
100
101
  4
101
- ].map((e) => /* @__PURE__ */ m("div", { className: "h-28 animate-pulse rounded-lg border border-border bg-card" }, e))
102
+ ].map((e) => /* @__PURE__ */ m("div", { className: "h-28 animate-pulse rounded-lg border border-border-subtle bg-bg-surface" }, e))
102
103
  }) : S ? /* @__PURE__ */ m("div", {
103
104
  className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4",
104
105
  children: I.map((e) => /* @__PURE__ */ h("div", {
105
- className: "rounded-lg border border-border bg-card p-4",
106
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
106
107
  children: [/* @__PURE__ */ h("div", {
107
- className: "mb-3 flex items-center justify-between text-muted-foreground",
108
+ className: "mb-3 flex items-center justify-between text-text-secondary",
108
109
  children: [/* @__PURE__ */ m("span", {
109
110
  className: "text-sm font-medium",
110
111
  children: e.title
111
112
  }), e.icon]
112
113
  }), /* @__PURE__ */ m("p", {
113
- className: "text-2xl font-semibold text-foreground",
114
+ className: "text-2xl font-semibold text-text-primary",
114
115
  children: e.value
115
116
  })]
116
117
  }, e.title))
117
118
  }) : null,
118
119
  M ? /* @__PURE__ */ m("div", {
119
- className: "rounded-lg border border-border bg-card p-4 text-sm text-destructive",
120
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm text-status-error-text",
120
121
  children: M
121
122
  }) : null,
122
123
  /* @__PURE__ */ h("div", {
123
124
  className: "grid gap-6 xl:grid-cols-[1.15fr,1.85fr]",
124
125
  children: [/* @__PURE__ */ h("section", {
125
- className: "rounded-lg border border-border bg-card p-4",
126
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
126
127
  children: [/* @__PURE__ */ m("h2", {
127
- className: "mb-4 text-lg font-semibold text-foreground",
128
+ className: "mb-4 text-lg font-semibold text-text-primary",
128
129
  children: "Top affiliates"
129
130
  }), S?.topAffiliates.length ? /* @__PURE__ */ m("div", {
130
131
  className: "space-y-3",
131
132
  children: S.topAffiliates.map((e) => /* @__PURE__ */ m("div", {
132
- className: "rounded-lg border border-border bg-background p-3",
133
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-3",
133
134
  children: /* @__PURE__ */ h("div", {
134
135
  className: "flex items-center justify-between gap-3",
135
136
  children: [/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("p", {
136
- className: "text-sm font-semibold text-foreground",
137
+ className: "text-sm font-semibold text-text-primary",
137
138
  children: e.codeValue
138
139
  }), /* @__PURE__ */ h("p", {
139
- className: "text-xs text-muted-foreground",
140
+ className: "text-xs text-text-secondary",
140
141
  children: ["Owner: ", e.ownerUserId]
141
142
  })] }), /* @__PURE__ */ h("div", {
142
143
  className: "text-right",
143
144
  children: [/* @__PURE__ */ h("p", {
144
- className: "text-sm font-semibold text-foreground",
145
+ className: "text-sm font-semibold text-text-primary",
145
146
  children: [e.visits, " visits"]
146
147
  }), /* @__PURE__ */ h("p", {
147
- className: "text-xs text-muted-foreground",
148
+ className: "text-xs text-text-secondary",
148
149
  children: [e.paidConversions + e.storePurchases, " conversions"]
149
150
  })]
150
151
  })]
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."
@@ -158,16 +160,16 @@ function g() {
158
160
  }), /* @__PURE__ */ h("section", {
159
161
  className: "space-y-4",
160
162
  children: [/* @__PURE__ */ h("div", {
161
- className: "rounded-lg border border-border bg-card p-4",
163
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
162
164
  children: [/* @__PURE__ */ m("h2", {
163
- className: "mb-4 text-lg font-semibold text-foreground",
165
+ className: "mb-4 text-lg font-semibold text-text-primary",
164
166
  children: "Recent journeys"
165
167
  }), w.length ? /* @__PURE__ */ m("div", {
166
168
  className: "overflow-x-auto",
167
169
  children: /* @__PURE__ */ h("table", {
168
170
  className: "min-w-full text-sm",
169
171
  children: [/* @__PURE__ */ m("thead", { children: /* @__PURE__ */ h("tr", {
170
- className: "border-b border-border text-left text-muted-foreground",
172
+ className: "border-b border-border-subtle text-left text-text-secondary",
171
173
  children: [
172
174
  /* @__PURE__ */ m("th", {
173
175
  className: "px-3 py-2",
@@ -195,7 +197,7 @@ function g() {
195
197
  })
196
198
  ]
197
199
  }) }), /* @__PURE__ */ m("tbody", { children: w.map((e) => /* @__PURE__ */ h("tr", {
198
- className: "cursor-pointer border-b border-border/70 text-foreground",
200
+ className: "cursor-pointer border-b border-border-subtle/70 text-text-primary",
199
201
  onClick: () => D(e.id),
200
202
  children: [
201
203
  /* @__PURE__ */ m("td", {
@@ -226,34 +228,36 @@ 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."
232
235
  })]
233
236
  }), /* @__PURE__ */ h("div", {
234
- className: "rounded-lg border border-border bg-card p-4",
237
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
235
238
  children: [/* @__PURE__ */ m("h2", {
236
- className: "mb-4 text-lg font-semibold text-foreground",
239
+ className: "mb-4 text-lg font-semibold text-text-primary",
237
240
  children: "Journey events"
238
241
  }), O.length ? /* @__PURE__ */ m("div", {
239
242
  className: "space-y-3",
240
243
  children: O.map((e) => /* @__PURE__ */ m("div", {
241
- className: "rounded-lg border border-border bg-background p-3",
244
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-3",
242
245
  children: /* @__PURE__ */ h("div", {
243
246
  className: "flex items-center justify-between gap-3",
244
247
  children: [/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("p", {
245
- className: "text-sm font-semibold text-foreground",
248
+ className: "text-sm font-semibold text-text-primary",
246
249
  children: e.type
247
250
  }), /* @__PURE__ */ m("p", {
248
- className: "text-xs text-muted-foreground",
251
+ className: "text-xs text-text-secondary",
249
252
  children: i(e.occurredAt)
250
253
  })] }), /* @__PURE__ */ m("p", {
251
- className: "text-sm font-medium text-foreground",
254
+ className: "text-sm font-medium text-text-primary",
252
255
  children: e.valueAmount ? `${e.valueAmount} ${e.currency || "USD"}` : "—"
253
256
  })]
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 bg-card px-3 py-2 text-sm font-medium text-foreground\"\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 bg-card\"\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 bg-card p-4\">\n <div className=\"mb-3 flex items-center justify-between text-muted-foreground\">\n <span className=\"text-sm font-medium\">{card.title}</span>\n {card.icon}\n </div>\n <p className=\"text-2xl font-semibold text-foreground\">{card.value}</p>\n </div>\n ))}\n </div>\n ) : null}\n\n {error ? (\n <div className=\"rounded-lg border border-border bg-card p-4 text-sm text-destructive\">\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 bg-card p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-foreground\">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 bg-background p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-sm font-semibold text-foreground\">\n {affiliate.codeValue}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n Owner: {affiliate.ownerUserId}\n </p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-semibold text-foreground\">\n {affiliate.visits} visits\n </p>\n <p className=\"text-xs text-muted-foreground\">\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 bg-card p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-foreground\">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 text-left text-muted-foreground\">\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/70 text-foreground\"\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 bg-card p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-foreground\">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 bg-background p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-sm font-semibold text-foreground\">{event.type}</p>\n <p className=\"text-xs text-muted-foreground\">\n {formatRelativeTime(event.occurredAt)}\n </p>\n </div>\n <p className=\"text-sm font-medium text-foreground\">\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",
@@ -27,7 +28,7 @@ function v() {
27
28
  className: "flex items-center justify-between",
28
29
  children: [/* @__PURE__ */ _("div", {
29
30
  className: "relative flex-1 max-w-sm",
30
- children: [/* @__PURE__ */ g(p, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), /* @__PURE__ */ g(r, {
31
+ children: [/* @__PURE__ */ g(p, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ g(r, {
31
32
  placeholder: "Search roles...",
32
33
  className: "pl-9",
33
34
  value: v,
@@ -42,7 +43,8 @@ function v() {
42
43
  })
43
44
  })]
44
45
  }), S ? /* @__PURE__ */ g(n, {
45
- icon: /* @__PURE__ */ g(m, { className: "h-8 w-8 text-destructive" }),
46
+ illustration: "empty-generic",
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
48
50
  }) : /* @__PURE__ */ g(a, {
@@ -55,7 +57,7 @@ function v() {
55
57
  children: [/* @__PURE__ */ _("div", {
56
58
  className: "flex items-center gap-2",
57
59
  children: [/* @__PURE__ */ g("span", {
58
- className: "font-semibold text-foreground",
60
+ className: "font-semibold text-text-primary",
59
61
  children: t.name
60
62
  }), t.isSystem && /* @__PURE__ */ g(e, {
61
63
  variant: "secondary",
@@ -63,7 +65,7 @@ function v() {
63
65
  children: "System"
64
66
  })]
65
67
  }), t.description && /* @__PURE__ */ g("span", {
66
- className: "mt-0.5 line-clamp-1 text-xs text-muted-foreground",
68
+ className: "mt-0.5 line-clamp-1 text-xs text-text-secondary",
67
69
  children: t.description
68
70
  })]
69
71
  })
@@ -74,7 +76,7 @@ function v() {
74
76
  align: "center",
75
77
  width: "80px",
76
78
  render: (e) => /* @__PURE__ */ g("span", {
77
- className: "rounded bg-muted px-2 py-0.5 text-xs font-medium",
79
+ className: "rounded bg-bg-sunken px-2 py-0.5 text-xs font-medium",
78
80
  children: e.priority
79
81
  })
80
82
  },
@@ -85,16 +87,16 @@ function v() {
85
87
  className: "flex max-w-md flex-wrap gap-1 text-left",
86
88
  children: t.defaultPermissions && t.defaultPermissions.length > 0 ? t.defaultPermissions.map((t) => /* @__PURE__ */ _(e, {
87
89
  variant: "outline",
88
- className: "bg-background/50 px-1 py-0 font-normal text-[10px]",
90
+ className: "bg-bg-surface/50 px-1 py-0 font-normal text-[10px]",
89
91
  children: [/* @__PURE__ */ _("span", {
90
- className: "mr-1 text-muted-foreground",
92
+ className: "mr-1 text-text-secondary",
91
93
  children: [t.resourceType, ":"]
92
94
  }), /* @__PURE__ */ g("span", {
93
95
  className: "font-medium",
94
96
  children: t.level
95
97
  })]
96
98
  }, t.id)) : /* @__PURE__ */ g("span", {
97
- className: "text-xs italic text-muted-foreground",
99
+ className: "text-xs italic text-text-secondary",
98
100
  children: "No permissions defined"
99
101
  })
100
102
  })
@@ -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-foreground\">{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-muted-foreground\">\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-muted 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-background/50 px-1 py-0 font-normal text-[10px]\"\n >\n <span className=\"mr-1 text-muted-foreground\">{perm.resourceType}:</span>\n <span className=\"font-medium\">{perm.level}</span>\n </Badge>\n ))\n ) : (\n <span className=\"text-xs italic text-muted-foreground\">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-muted-foreground\" />\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-destructive\" />}\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"}
@@ -170,9 +170,9 @@ var k = [
170
170
  cache: /* @__PURE__ */ D(S, { className: "h-4 w-4" }),
171
171
  queue: /* @__PURE__ */ D(E, { className: "h-4 w-4" })
172
172
  }, N = {
173
- critical: "bg-destructive/10 text-destructive",
173
+ critical: "bg-status-error-bg/10 text-status-error-text",
174
174
  major: "bg-warning/10 text-warning",
175
- minor: "bg-muted text-muted-foreground"
175
+ minor: "bg-bg-sunken text-text-secondary"
176
176
  };
177
177
  function P() {
178
178
  let { canViewSRE: b, isLoading: x } = e(), [S, E] = g(!1), P = k.filter((e) => e.status === "healthy").length, F = k.filter((e) => e.status === "degraded").length;
@@ -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`
@@ -234,7 +236,7 @@ function P() {
234
236
  }) }), /* @__PURE__ */ D(i, { children: /* @__PURE__ */ D("div", {
235
237
  className: "space-y-3",
236
238
  children: A.filter((e) => e.status !== "resolved").map((e) => /* @__PURE__ */ O("div", {
237
- className: "flex items-start justify-between rounded-lg border border-border bg-background p-4",
239
+ className: "flex items-start justify-between rounded-lg border border-border-subtle bg-bg-surface p-4",
238
240
  children: [/* @__PURE__ */ O("div", {
239
241
  className: "space-y-1",
240
242
  children: [
@@ -249,7 +251,7 @@ function P() {
249
251
  })]
250
252
  }),
251
253
  /* @__PURE__ */ O("div", {
252
- className: "flex items-center gap-4 text-sm text-muted-foreground",
254
+ className: "flex items-center gap-4 text-sm text-text-secondary",
253
255
  children: [
254
256
  /* @__PURE__ */ O("span", { children: ["Started ", d(e.startedAt)] }),
255
257
  /* @__PURE__ */ D("span", { children: "•" }),
@@ -321,7 +323,7 @@ function P() {
321
323
  width: "100px",
322
324
  align: "right",
323
325
  render: (e) => /* @__PURE__ */ O("span", {
324
- className: e.errorRate > 1 ? "text-destructive" : "",
326
+ className: e.errorRate > 1 ? "text-status-error-text" : "",
325
327
  children: [e.errorRate.toFixed(2), "%"]
326
328
  })
327
329
  },
@@ -330,7 +332,7 @@ function P() {
330
332
  header: "Region",
331
333
  width: "120px",
332
334
  render: (e) => /* @__PURE__ */ D("span", {
333
- className: "text-muted-foreground",
335
+ className: "text-text-secondary",
334
336
  children: e.region
335
337
  })
336
338
  }
@@ -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
  })
@@ -360,7 +363,7 @@ function P() {
360
363
  header: "Version",
361
364
  width: "140px",
362
365
  render: (e) => /* @__PURE__ */ D("code", {
363
- className: "text-xs bg-muted px-1.5 py-0.5 rounded",
366
+ className: "text-xs bg-bg-sunken px-1.5 py-0.5 rounded",
364
367
  children: e.version
365
368
  })
366
369
  },
@@ -382,7 +385,7 @@ function P() {
382
385
  header: "Deployed",
383
386
  width: "140px",
384
387
  render: (e) => /* @__PURE__ */ D("span", {
385
- className: "text-muted-foreground",
388
+ className: "text-text-secondary",
386
389
  children: d(e.deployedAt)
387
390
  })
388
391
  }
@@ -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
  })