@burdenoff/microfe-admin 2026.608.1 → 2026.624.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 (80) hide show
  1. package/dist/admin/AdminRoutes.js +14 -10
  2. package/dist/admin/AdminRoutes.js.map +1 -1
  3. package/dist/admin/components/AccessDenied.js +2 -2
  4. package/dist/admin/components/AccessDenied.js.map +1 -1
  5. package/dist/admin/components/ActivityFeed.js +4 -4
  6. package/dist/admin/components/ActivityFeed.js.map +1 -1
  7. package/dist/admin/components/Charts.js +4 -4
  8. package/dist/admin/components/Charts.js.map +1 -1
  9. package/dist/admin/components/ConfirmDialog.js +44 -25
  10. package/dist/admin/components/ConfirmDialog.js.map +1 -1
  11. package/dist/admin/components/DataTable.js +7 -7
  12. package/dist/admin/components/DataTable.js.map +1 -1
  13. package/dist/admin/components/FormSheet.js +7 -7
  14. package/dist/admin/components/FormSheet.js.map +1 -1
  15. package/dist/admin/components/MetricCard.js +4 -4
  16. package/dist/admin/components/MetricCard.js.map +1 -1
  17. package/dist/admin/components/PageLayout.js +3 -3
  18. package/dist/admin/components/PageLayout.js.map +1 -1
  19. package/dist/admin/components/PermissionGuard.js +1 -1
  20. package/dist/admin/components/PermissionGuard.js.map +1 -1
  21. package/dist/admin/components/RowActions.js +2 -2
  22. package/dist/admin/components/RowActions.js.map +1 -1
  23. package/dist/admin/components/TenantMembersPanel.js +15 -15
  24. package/dist/admin/components/TenantMembersPanel.js.map +1 -1
  25. package/dist/admin/components/TenantSsoPanel.js +14 -14
  26. package/dist/admin/components/TenantSsoPanel.js.map +1 -1
  27. package/dist/admin/components/Toast.js +9 -9
  28. package/dist/admin/components/Toast.js.map +1 -1
  29. package/dist/admin/components/forms/SubscriptionForm.js +10 -10
  30. package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
  31. package/dist/admin/components/forms/TenantForm.js +8 -8
  32. package/dist/admin/components/forms/TenantForm.js.map +1 -1
  33. package/dist/admin/components/forms/TicketForm.js +2 -2
  34. package/dist/admin/components/forms/TicketForm.js.map +1 -1
  35. package/dist/admin/components/forms/UserForm.js +8 -8
  36. package/dist/admin/components/forms/UserForm.js.map +1 -1
  37. package/dist/admin/components/ui.js +16 -16
  38. package/dist/admin/components/ui.js.map +1 -1
  39. package/dist/admin/constants/index.js +8 -8
  40. package/dist/admin/constants/index.js.map +1 -1
  41. package/dist/admin/pages/AuditLogsPage.js +8 -8
  42. package/dist/admin/pages/AuditLogsPage.js.map +1 -1
  43. package/dist/admin/pages/CommunicationsPage.js +2 -2
  44. package/dist/admin/pages/CommunicationsPage.js.map +1 -1
  45. package/dist/admin/pages/ContentPagesPage.js +661 -0
  46. package/dist/admin/pages/ContentPagesPage.js.map +1 -0
  47. package/dist/admin/pages/FeatureFlagsPage.js +4 -4
  48. package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
  49. package/dist/admin/pages/OrganizationsListPage.js +6 -6
  50. package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
  51. package/dist/admin/pages/ProductsPage.js +7 -7
  52. package/dist/admin/pages/ProductsPage.js.map +1 -1
  53. package/dist/admin/pages/ReferralsPage.js +23 -23
  54. package/dist/admin/pages/ReferralsPage.js.map +1 -1
  55. package/dist/admin/pages/RolesPermissionsPage.js +8 -8
  56. package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
  57. package/dist/admin/pages/SREPage.js +8 -8
  58. package/dist/admin/pages/SREPage.js.map +1 -1
  59. package/dist/admin/pages/SandboxSettingsPage.js +20 -20
  60. package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
  61. package/dist/admin/pages/SettingsPage.js +20 -20
  62. package/dist/admin/pages/SettingsPage.js.map +1 -1
  63. package/dist/admin/pages/SyncFailuresPage.js +11 -11
  64. package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
  65. package/dist/admin/pages/TenantDetailPage.js +22 -22
  66. package/dist/admin/pages/TenantDetailPage.js.map +1 -1
  67. package/dist/admin/pages/TenantFormRoutePage.js +2 -2
  68. package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
  69. package/dist/admin/pages/TenantsListPage.js +5 -5
  70. package/dist/admin/pages/TenantsListPage.js.map +1 -1
  71. package/dist/admin/pages/UsersListPage.js +6 -6
  72. package/dist/admin/pages/UsersListPage.js.map +1 -1
  73. package/dist/admin/pages/WaitlistPage.js +7 -7
  74. package/dist/admin/pages/WaitlistPage.js.map +1 -1
  75. package/dist/admin/pages/WorkflowsPage.js +3 -3
  76. package/dist/admin/pages/WorkflowsPage.js.map +1 -1
  77. package/dist/admin/pages/WorkspacesListPage.js +8 -8
  78. package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
  79. package/dist/admin/pages/index.js +1 -0
  80. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ContentPagesPage.js","names":[],"sources":["../../../src/admin/pages/ContentPagesPage.tsx"],"sourcesContent":["import { useState, useCallback, useEffect, useRef } from 'react';\nimport { gql } from '@apollo/client';\nimport { useApolloClient } from '@apollo/client/react';\nimport { FileText, Search, Plus } from 'lucide-react';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport {\n PageLayout,\n Input,\n DataTable,\n Pagination,\n EmptyState,\n Badge,\n Button,\n PermissionGuard,\n FormSheet,\n FormSheetFooter,\n FormSection,\n FormRow,\n DeleteConfirmDialog,\n useToast,\n createRowActions,\n RowActions,\n} from '../components';\nimport { formatDate } from '../utils/formatters';\n\nconst PAGE_SIZE = 20;\n\n// Content \"type\" is stored on the ProductContentPage `tags` array (one tag per\n// type, matching the website SDK filters: BlogListPage → \"blog\",\n// CaseStudiesListPage → \"case-study\", ChangelogPage → \"changelog\").\nconst CONTENT_TYPES = [\n { value: 'blog', label: 'Blog', category: 'Blog' },\n { value: 'case-study', label: 'Case Study', category: 'Case Study' },\n { value: 'changelog', label: 'Changelog', category: 'Changelog' },\n] as const;\n\ntype ContentType = (typeof CONTENT_TYPES)[number]['value'];\n\nconst TYPE_VARIANT: Record<ContentType, 'default' | 'secondary' | 'outline'> = {\n blog: 'default',\n 'case-study': 'secondary',\n changelog: 'outline',\n};\n\n// ---------------------------------------------------------------------------\n// GraphQL\n// ---------------------------------------------------------------------------\n\nconst LIST_PRODUCTS = gql`\n query AdminContentListProducts($limit: Int, $offset: Int) {\n platformProducts(limit: $limit, offset: $offset) {\n items {\n id\n name\n slug\n }\n }\n }\n`;\n\nconst LIST_CONTENT_PAGES = gql`\n query AdminContentPages(\n $productId: String\n $filter: ContentPageFilterInput\n $limit: Int\n $offset: Int\n ) {\n contentPages(productId: $productId, filter: $filter, limit: $limit, offset: $offset) {\n items {\n id\n productId\n slug\n title\n content\n contentFormat\n tags\n category\n excerpt\n thumbnail\n author\n publishedAt\n readingTime\n featured\n isPublished\n visibility\n updatedAt\n }\n total\n }\n }\n`;\n\nconst CREATE_CONTENT_PAGE = gql`\n mutation AdminCreateContentPage($input: CreateContentPageInput!) {\n createContentPage(input: $input) {\n id\n slug\n }\n }\n`;\n\nconst UPDATE_CONTENT_PAGE = gql`\n mutation AdminUpdateContentPage($id: ID!, $input: UpdateContentPageInput!) {\n updateContentPage(id: $id, input: $input) {\n id\n slug\n }\n }\n`;\n\nconst DELETE_CONTENT_PAGE = gql`\n mutation AdminDeleteContentPage($id: ID!) {\n deleteContentPage(id: $id)\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface ProductOption {\n id: string;\n name: string;\n slug: string;\n}\n\ninterface ContentPageItem {\n id: string;\n productId: string | null;\n slug: string;\n title: string;\n content: string;\n contentFormat: string | null;\n tags: string[];\n category: string | null;\n excerpt: string | null;\n thumbnail: string | null;\n author: string | null;\n publishedAt: string | null;\n readingTime: number | null;\n featured: boolean;\n isPublished: boolean;\n visibility: string;\n updatedAt: string;\n}\n\ninterface FormState {\n productId: string;\n type: ContentType;\n slug: string;\n title: string;\n excerpt: string;\n content: string;\n author: string;\n publishedAt: string;\n thumbnail: string;\n readingTime: string;\n featured: boolean;\n isPublished: boolean;\n}\n\nconst EMPTY_FORM: FormState = {\n productId: '',\n type: 'blog',\n slug: '',\n title: '',\n excerpt: '',\n content: '',\n author: '',\n publishedAt: '',\n thumbnail: '',\n readingTime: '',\n featured: false,\n isPublished: true,\n};\n\nfunction typeOf(tags: string[]): ContentType {\n if (tags.includes('changelog')) return 'changelog';\n if (tags.includes('case-study')) return 'case-study';\n return 'blog';\n}\n\nconst inputClass =\n 'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-50';\n\n// ---------------------------------------------------------------------------\n// Page\n// ---------------------------------------------------------------------------\n\nexport function ContentPagesPage() {\n const {\n canViewProducts,\n canManageProducts,\n isLoading: permissionsLoading,\n } = useAdminPermissions();\n const apolloClient = useApolloClient();\n const toast = useToast();\n\n const [products, setProducts] = useState<ProductOption[]>([]);\n const [productId, setProductId] = useState('');\n const [typeFilter, setTypeFilter] = useState<'' | ContentType>('');\n const [searchInput, setSearchInput] = useState('');\n const [search, setSearch] = useState(''); // debounced value that actually drives the query\n const [page, setPage] = useState(1);\n\n // Monotonic request id so a slow earlier fetch can't overwrite a newer one.\n const fetchId = useRef(0);\n\n // Debounce the search box: only the settled value triggers a fetch, and a\n // new search resets to page 1.\n useEffect(() => {\n const t = setTimeout(() => {\n setSearch(searchInput);\n setPage(1);\n }, 300);\n return () => clearTimeout(t);\n }, [searchInput]);\n\n const [items, setItems] = useState<ContentPageItem[]>([]);\n const [total, setTotal] = useState(0);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const [sheetOpen, setSheetOpen] = useState(false);\n const [editing, setEditing] = useState<ContentPageItem | null>(null);\n const [form, setForm] = useState<FormState>(EMPTY_FORM);\n const [submitting, setSubmitting] = useState(false);\n const [deleteTarget, setDeleteTarget] = useState<ContentPageItem | null>(null);\n const [deleting, setDeleting] = useState(false);\n\n const skip = permissionsLoading || !canViewProducts;\n\n // Load product list for the selector. Capped at 100 — the platform has well\n // under that many products, and this drives a simple filter dropdown, not an\n // exhaustive picker. Revisit with pagination/search if the catalogue grows.\n useEffect(() => {\n if (skip) return;\n // Guard against setting state after unmount if the user navigates away\n // before this resolves.\n let active = true;\n apolloClient\n .query<{ platformProducts: { items: ProductOption[] } }>({\n query: LIST_PRODUCTS,\n variables: { limit: 100, offset: 0 },\n fetchPolicy: 'network-only',\n })\n .then((res) => {\n if (!active) return;\n const list = res.data?.platformProducts?.items ?? [];\n setProducts(list);\n setProductId((prev) => prev || list[0]?.id || '');\n })\n .catch((e) => {\n if (!active) return;\n setError(e instanceof Error ? e.message : 'Failed to load products');\n });\n return () => {\n active = false;\n };\n }, [apolloClient, skip]);\n\n const fetchPages = useCallback(async () => {\n if (skip || !productId) return;\n const id = ++fetchId.current;\n setLoading(true);\n try {\n const res = await apolloClient.query<{\n contentPages: { items: ContentPageItem[]; total: number };\n }>({\n query: LIST_CONTENT_PAGES,\n variables: {\n productId,\n filter: {\n ...(search ? { search } : {}),\n ...(typeFilter ? { tag: typeFilter } : {}),\n },\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n fetchPolicy: 'network-only',\n });\n if (id !== fetchId.current) return; // a newer fetch superseded this one\n setItems(res.data?.contentPages?.items ?? []);\n setTotal(res.data?.contentPages?.total ?? 0);\n setError(null);\n } catch (e) {\n if (id !== fetchId.current) return;\n setError(e instanceof Error ? e.message : 'Failed to load content pages');\n } finally {\n if (id === fetchId.current) setLoading(false);\n }\n }, [apolloClient, skip, productId, search, typeFilter, page]);\n\n useEffect(() => {\n fetchPages();\n }, [fetchPages]);\n\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n const openCreate = useCallback(() => {\n setEditing(null);\n setForm({ ...EMPTY_FORM, productId, type: typeFilter || 'blog' });\n setSheetOpen(true);\n }, [productId, typeFilter]);\n\n const openEdit = useCallback((item: ContentPageItem) => {\n setEditing(item);\n setForm({\n productId: item.productId ?? '',\n type: typeOf(item.tags),\n slug: item.slug,\n title: item.title,\n excerpt: item.excerpt ?? '',\n content: item.content,\n author: item.author ?? '',\n publishedAt: item.publishedAt ? item.publishedAt.slice(0, 10) : '',\n thumbnail: item.thumbnail ?? '',\n readingTime: item.readingTime != null ? String(item.readingTime) : '',\n featured: item.featured,\n isPublished: item.isPublished,\n });\n setSheetOpen(true);\n }, []);\n\n const handleSubmit = useCallback(async () => {\n const missing = [\n !form.title.trim() && 'Title',\n !form.slug.trim() && 'Slug',\n !form.content.trim() && 'Content',\n ].filter(Boolean);\n if (missing.length) {\n toast.error(`${missing.join(', ')} ${missing.length > 1 ? 'are' : 'is'} required`);\n return;\n }\n // The thumbnail is rendered as an <img src> on the public sites. Only accept\n // http(s) URLs, protocol-relative (`//…`) or site-absolute (`/…`) paths so a\n // `javascript:`/`data:` scheme can't be stored and later become an XSS vector.\n const thumb = form.thumbnail.trim();\n if (thumb && !/^(https?:\\/\\/|\\/\\/|\\/)/i.test(thumb)) {\n toast.error('Thumbnail must be an http(s) URL or an absolute path');\n return;\n }\n setSubmitting(true);\n const typeMeta = CONTENT_TYPES.find((t) => t.value === form.type)!;\n // On EDIT, an emptied optional field must send `null` so the backend clears\n // the previous value (an omitted/`undefined` field is dropped by JSON\n // serialization and leaves the old value untouched). On CREATE we omit.\n const empty = editing ? null : undefined;\n // tags/category are NOT in the shared (update) payload: the type selector is\n // locked on edit, so an edit must NOT overwrite the row's tags/category.\n // That preserves pages whose tags aren't one of blog/case-study/changelog\n // (e.g. legal/privacy) which can surface in the \"All types\" list — editing\n // their title/content must not silently retag them as a blog post.\n const shared = {\n title: form.title.trim(),\n content: form.content,\n contentFormat: 'markdown',\n excerpt: form.excerpt.trim() || empty,\n author: form.author.trim() || empty,\n publishedAt: form.publishedAt ? new Date(form.publishedAt).toISOString() : empty,\n thumbnail: form.thumbnail.trim() || empty,\n readingTime: form.readingTime !== '' ? Number(form.readingTime) : empty,\n featured: form.featured,\n isPublished: form.isPublished,\n // NOTE: `visibility` is intentionally NOT in the shared (update) payload.\n // The form has no visibility control, so an edit must not clobber a page\n // whose visibility was set to INTERNAL/AUTHENTICATED elsewhere back to\n // PUBLIC. It is only defaulted on CREATE below.\n };\n try {\n if (editing) {\n await apolloClient.mutate({\n mutation: UPDATE_CONTENT_PAGE,\n variables: { id: editing.id, input: shared },\n });\n toast.success('Content page updated');\n } else {\n await apolloClient.mutate({\n mutation: CREATE_CONTENT_PAGE,\n variables: {\n input: {\n ...shared,\n visibility: 'PUBLIC',\n tags: [form.type],\n category: typeMeta.category,\n productId: form.productId || undefined,\n // Collapse internal hyphen runs (a `--` can arise from adjacent\n // invalid chars) and trim leading/trailing hyphens — done once on\n // submit so it never fights the user mid-typing.\n slug: form.slug\n .trim()\n .replace(/-+/g, '-')\n .replace(/^-+|-+$/g, ''),\n },\n },\n });\n toast.success('Content page created');\n }\n setSheetOpen(false);\n await fetchPages();\n } catch (e) {\n toast.error(e instanceof Error ? e.message : 'Failed to save content page');\n } finally {\n setSubmitting(false);\n }\n }, [form, editing, apolloClient, toast, fetchPages]);\n\n const handleDelete = useCallback(async () => {\n // Guard against a double-click firing two DELETE mutations for the same row\n // (the second would NOT_FOUND once the first commits).\n if (!deleteTarget || deleting) return;\n setDeleting(true);\n try {\n await apolloClient.mutate({\n mutation: DELETE_CONTENT_PAGE,\n variables: { id: deleteTarget.id },\n });\n toast.success('Content page deleted');\n setDeleteTarget(null);\n // If we just removed the last row on a non-first page, step back a page —\n // the page-change effect refetches at the correct offset. Otherwise the\n // stale offset would land past the new total and strand an empty table.\n if (items.length === 1 && page > 1) {\n setPage((p) => p - 1);\n } else {\n await fetchPages();\n }\n } catch (e) {\n toast.error(e instanceof Error ? e.message : 'Failed to delete content page');\n } finally {\n setDeleting(false);\n }\n }, [deleteTarget, deleting, apolloClient, toast, fetchPages, items.length, page]);\n\n const columns = [\n {\n key: 'title',\n header: 'Title',\n render: (p: ContentPageItem) => (\n <div className=\"min-w-0\">\n <div className=\"font-medium text-foreground\">{p.title}</div>\n <div className=\"truncate text-xs text-muted-foreground\">{p.slug}</div>\n </div>\n ),\n },\n {\n key: 'type',\n header: 'Type',\n width: '120px',\n render: (p: ContentPageItem) => {\n // Don't fall back to \"Blog\" for tags outside our set (e.g. legal/privacy\n // pages that surface in the \"All types\" list) — label those \"Other\".\n const known = CONTENT_TYPES.find((x) => p.tags.includes(x.value));\n return (\n <Badge\n variant={known ? TYPE_VARIANT[known.value] : 'outline'}\n className=\"text-xs capitalize\"\n >\n {known?.label ?? 'Other'}\n </Badge>\n );\n },\n },\n {\n key: 'status',\n header: 'Status',\n width: '110px',\n render: (p: ContentPageItem) => (\n <div className=\"flex items-center gap-1\">\n <Badge variant={p.isPublished ? 'default' : 'outline'} className=\"text-xs\">\n {p.isPublished ? 'Published' : 'Draft'}\n </Badge>\n {p.featured && (\n <Badge variant=\"secondary\" className=\"text-xs\">\n Featured\n </Badge>\n )}\n </div>\n ),\n },\n {\n key: 'updatedAt',\n header: 'Updated',\n width: '110px',\n render: (p: ContentPageItem) => formatDate(p.updatedAt),\n },\n {\n key: 'actions',\n header: '',\n width: '60px',\n align: 'right' as const,\n render: (p: ContentPageItem) => (\n <RowActions\n actions={createRowActions({\n onEdit: canManageProducts ? () => openEdit(p) : undefined,\n onDelete: canManageProducts ? () => setDeleteTarget(p) : undefined,\n })}\n />\n ),\n },\n ];\n\n const formId = 'content-page-form';\n\n // True when editing a page whose tags don't map to a managed content type —\n // used to show a disabled \"Other\" in the (edit-locked) type selector.\n const editingUnmanagedType =\n !!editing && !CONTENT_TYPES.some((t) => editing.tags.includes(t.value));\n\n return (\n <PermissionGuard\n allowed={canViewProducts}\n isLoading={permissionsLoading}\n pageTitle=\"Content\"\n denyMessage=\"You don't have permission to manage content pages.\"\n >\n <PageLayout\n title=\"Content\"\n description={`Blog posts, case studies, and changelog entries${\n total ? ` · ${total} total` : ''\n }`}\n actions={\n <Button onClick={openCreate} disabled={!canManageProducts || !productId}>\n <Plus className=\"mr-2 h-4 w-4\" />\n New Content\n </Button>\n }\n >\n <div className=\"space-y-4\">\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-3\">\n <select\n aria-label=\"Filter by product\"\n className={`${inputClass} max-w-xs`}\n value={productId}\n onChange={(e) => {\n setPage(1);\n setProductId(e.target.value);\n }}\n >\n {products.length === 0 && <option value=\"\">Loading products…</option>}\n {products.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n\n <select\n aria-label=\"Filter by content type\"\n className={`${inputClass} max-w-[180px]`}\n value={typeFilter}\n onChange={(e) => {\n setPage(1);\n setTypeFilter(e.target.value as '' | ContentType);\n }}\n >\n <option value=\"\">All types</option>\n {CONTENT_TYPES.map((t) => (\n <option key={t.value} value={t.value}>\n {t.label}\n </option>\n ))}\n </select>\n\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-muted-foreground\" />\n <Input\n placeholder=\"Search content...\"\n value={searchInput}\n onChange={(e) => setSearchInput(e.target.value)}\n className=\"pl-9\"\n />\n </div>\n </div>\n\n {error && (\n <div className=\"rounded-md border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive\">\n {error}\n </div>\n )}\n\n <DataTable\n columns={columns}\n data={items}\n keyExtractor={(p) => p.id}\n loading={loading}\n emptyState={\n <EmptyState\n icon={<FileText className=\"h-8 w-8\" />}\n title=\"No content yet\"\n description=\"Create a blog post, case study, or changelog entry to get started.\"\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\n {/* Create / Edit sheet */}\n <FormSheet\n open={sheetOpen}\n onClose={() => setSheetOpen(false)}\n title={editing ? 'Edit content' : 'New content'}\n footer={\n <FormSheetFooter\n formId={formId}\n isSubmitting={submitting}\n submitLabel={editing ? 'Save changes' : 'Create'}\n onCancel={() => setSheetOpen(false)}\n />\n }\n >\n <form\n id={formId}\n onSubmit={(e) => {\n e.preventDefault();\n handleSubmit();\n }}\n className=\"space-y-6\"\n >\n <FormSection title=\"Basics\">\n <FormRow cols={2}>\n <label className=\"space-y-1.5\">\n <span className=\"text-sm font-medium\">Type</span>\n <select\n className={inputClass}\n // When editing a page whose tags fall outside the managed set\n // (e.g. legal/privacy), show a disabled \"Other\" rather than\n // mislabelling it as the typeOf() fallback \"Blog\". The control\n // is disabled on edit and tags are never written on update, so\n // this is display-only.\n value={editingUnmanagedType ? '__other' : form.type}\n disabled={!!editing}\n onChange={(e) => setForm((f) => ({ ...f, type: e.target.value as ContentType }))}\n >\n {editingUnmanagedType && <option value=\"__other\">Other</option>}\n {CONTENT_TYPES.map((t) => (\n <option key={t.value} value={t.value}>\n {t.label}\n </option>\n ))}\n </select>\n </label>\n <label className=\"space-y-1.5\">\n <span className=\"text-sm font-medium\">Product</span>\n <select\n className={inputClass}\n value={form.productId}\n disabled={!!editing}\n onChange={(e) => setForm((f) => ({ ...f, productId: e.target.value }))}\n >\n {products.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n </label>\n </FormRow>\n <FormRow cols={2}>\n <label className=\"space-y-1.5\">\n <span className=\"text-sm font-medium\">Title</span>\n <Input\n value={form.title}\n onChange={(e) => setForm((f) => ({ ...f, title: e.target.value }))}\n placeholder=\"A clear, descriptive title\"\n />\n </label>\n <label className=\"space-y-1.5\">\n <span className=\"text-sm font-medium\">Slug</span>\n <Input\n value={form.slug}\n disabled={!!editing}\n onChange={(e) =>\n setForm((f) => ({\n ...f,\n // Don't strip leading/trailing hyphens here — that breaks\n // typing \"my-slug\" (the hyphen would vanish mid-type).\n // Leading/trailing hyphens are trimmed on submit instead.\n slug: e.target.value.toLowerCase().replace(/[^a-z0-9-]+/g, '-'),\n }))\n }\n placeholder=\"url-friendly-slug\"\n />\n </label>\n </FormRow>\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium\">Excerpt</span>\n <textarea\n className={`${inputClass} h-16 py-2`}\n value={form.excerpt}\n onChange={(e) => setForm((f) => ({ ...f, excerpt: e.target.value }))}\n placeholder=\"Short summary shown in cards and previews\"\n />\n </label>\n </FormSection>\n\n <FormSection\n title=\"Content\"\n description=\"Markdown supported (headings, lists, links, bold).\"\n >\n <textarea\n aria-label=\"Content\"\n className={`${inputClass} h-64 py-2 font-mono text-xs`}\n value={form.content}\n onChange={(e) => setForm((f) => ({ ...f, content: e.target.value }))}\n placeholder={'## Heading\\n\\nWrite your content in markdown...'}\n />\n </FormSection>\n\n <FormSection title=\"Meta\">\n <FormRow cols={3}>\n <label className=\"space-y-1.5\">\n <span className=\"text-sm font-medium\">Author</span>\n <Input\n value={form.author}\n onChange={(e) => setForm((f) => ({ ...f, author: e.target.value }))}\n placeholder=\"The Team\"\n />\n </label>\n <label className=\"space-y-1.5\">\n <span className=\"text-sm font-medium\">Published date</span>\n <Input\n type=\"date\"\n value={form.publishedAt}\n onChange={(e) => setForm((f) => ({ ...f, publishedAt: e.target.value }))}\n />\n </label>\n <label className=\"space-y-1.5\">\n <span className=\"text-sm font-medium\">Reading time (min)</span>\n <Input\n type=\"number\"\n min={0}\n value={form.readingTime}\n onChange={(e) => setForm((f) => ({ ...f, readingTime: e.target.value }))}\n />\n </label>\n </FormRow>\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium\">Thumbnail URL</span>\n <Input\n value={form.thumbnail}\n onChange={(e) => setForm((f) => ({ ...f, thumbnail: e.target.value }))}\n placeholder=\"https://…/image.png\"\n />\n </label>\n <div className=\"flex items-center gap-6 pt-1\">\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={form.featured}\n onChange={(e) => setForm((f) => ({ ...f, featured: e.target.checked }))}\n />\n Featured\n </label>\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={form.isPublished}\n onChange={(e) => setForm((f) => ({ ...f, isPublished: e.target.checked }))}\n />\n Published\n </label>\n </div>\n </FormSection>\n </form>\n </FormSheet>\n\n <DeleteConfirmDialog\n open={!!deleteTarget}\n onClose={() => setDeleteTarget(null)}\n onConfirm={handleDelete}\n itemName={deleteTarget?.title ?? 'this content page'}\n itemType=\"content page\"\n loading={deleting}\n />\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
@@ -31,7 +31,7 @@ function _() {
31
31
  title: "Feature Flags",
32
32
  description: "Error loading feature flags",
33
33
  children: /* @__PURE__ */ g("div", {
34
- className: "p-4 text-destructive",
34
+ className: "p-4 text-status-error-text",
35
35
  children: ["Error: ", x.message]
36
36
  })
37
37
  }) : /* @__PURE__ */ h(r, {
@@ -44,7 +44,7 @@ function _() {
44
44
  className: "flex items-center justify-between",
45
45
  children: /* @__PURE__ */ g("div", {
46
46
  className: "relative max-w-sm flex-1",
47
- children: [/* @__PURE__ */ h(f, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), /* @__PURE__ */ h(n, {
47
+ children: [/* @__PURE__ */ h(f, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ h(n, {
48
48
  placeholder: "Search feature flags...",
49
49
  className: "pl-9",
50
50
  value: _,
@@ -62,7 +62,7 @@ function _() {
62
62
  className: "font-medium",
63
63
  children: e.name
64
64
  }), /* @__PURE__ */ h("span", {
65
- className: "text-xs text-muted-foreground",
65
+ className: "text-xs text-text-secondary",
66
66
  children: e.key
67
67
  })]
68
68
  })
@@ -80,7 +80,7 @@ function _() {
80
80
  variant: "outline",
81
81
  size: "sm",
82
82
  onClick: () => E(t.id, t.status),
83
- className: n ? "border-primary text-primary" : "text-muted-foreground",
83
+ className: n ? "border-primary text-primary" : "text-text-secondary",
84
84
  children: n ? "Active" : "Archived"
85
85
  });
86
86
  }
@@ -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-muted-foreground\">{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-muted-foreground'}\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-destructive\">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-muted-foreground\" />\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,CAAsC,WAAQ,EAAM,QAAc;;EACvD,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,0EAA2E,CAAA,EAC7F,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;kBAAiC,EAAK;QAAW,CAAA,CAC7D;;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 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"}
@@ -26,7 +26,7 @@ function p() {
26
26
  className: "flex items-center justify-between",
27
27
  children: /* @__PURE__ */ f("div", {
28
28
  className: "relative max-w-sm flex-1",
29
- children: [/* @__PURE__ */ d(u, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), /* @__PURE__ */ d(n, {
29
+ children: [/* @__PURE__ */ d(u, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ d(n, {
30
30
  placeholder: "Search organizations...",
31
31
  className: "pl-9",
32
32
  value: p,
@@ -35,13 +35,13 @@ function p() {
35
35
  })
36
36
  }), g ? /* @__PURE__ */ d("div", {
37
37
  className: "flex h-64 items-center justify-center",
38
- children: /* @__PURE__ */ d(c, { className: "h-8 w-8 animate-spin text-muted-foreground" })
38
+ children: /* @__PURE__ */ d(c, { className: "h-8 w-8 animate-spin text-text-secondary" })
39
39
  }) : b.length > 0 ? /* @__PURE__ */ d("div", {
40
40
  className: "rounded-md border",
41
41
  children: /* @__PURE__ */ f("table", {
42
42
  className: "w-full text-sm",
43
43
  children: [/* @__PURE__ */ d("thead", { children: /* @__PURE__ */ f("tr", {
44
- className: "border-b bg-muted/50 text-left",
44
+ className: "border-b bg-bg-sunken/50 text-left",
45
45
  children: [
46
46
  /* @__PURE__ */ d("th", {
47
47
  className: "p-4 font-medium",
@@ -65,14 +65,14 @@ function p() {
65
65
  })
66
66
  ]
67
67
  }) }), /* @__PURE__ */ d("tbody", { children: b.map((t) => /* @__PURE__ */ f("tr", {
68
- className: "border-b transition-colors hover:bg-muted/50",
68
+ className: "border-b transition-colors hover:bg-bg-sunken/50",
69
69
  children: [
70
70
  /* @__PURE__ */ d("td", {
71
71
  className: "p-4 font-medium",
72
72
  children: t.name
73
73
  }),
74
74
  /* @__PURE__ */ d("td", {
75
- className: "p-4 text-muted-foreground",
75
+ className: "p-4 text-text-secondary",
76
76
  children: t.slug
77
77
  }),
78
78
  /* @__PURE__ */ d("td", {
@@ -83,7 +83,7 @@ function p() {
83
83
  })
84
84
  }),
85
85
  /* @__PURE__ */ d("td", {
86
- className: "p-4 text-muted-foreground",
86
+ className: "p-4 text-text-secondary",
87
87
  children: new Date(t.createdAt).toLocaleDateString()
88
88
  }),
89
89
  /* @__PURE__ */ d("td", {
@@ -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-muted-foreground\" />\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-muted-foreground\" />\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-muted/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-muted/50\">\n <td className=\"p-4 font-medium\">{org.name}</td>\n <td className=\"p-4 text-muted-foreground\">{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-muted-foreground\">\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,0EAA2E,CAAA,EAC7F,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,8CAA+C,CAAA;IAC9D,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;kBAA6B,EAAI;QAAU,CAAA;OACzD,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 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"}
@@ -289,7 +289,7 @@ function M() {
289
289
  children: [
290
290
  /* @__PURE__ */ C("div", {
291
291
  className: "relative max-w-sm",
292
- children: [/* @__PURE__ */ S(b, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), /* @__PURE__ */ S(i, {
292
+ children: [/* @__PURE__ */ S(b, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ S(i, {
293
293
  placeholder: "Search products...",
294
294
  value: I,
295
295
  onChange: (e) => {
@@ -299,15 +299,15 @@ function M() {
299
299
  })]
300
300
  }),
301
301
  J && /* @__PURE__ */ C("div", {
302
- className: "rounded-md border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive",
302
+ className: "rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
303
303
  children: ["Failed to load products: ", J.message]
304
304
  }),
305
305
  B && /* @__PURE__ */ S("div", {
306
- className: "rounded-md border border-border bg-muted p-4 text-sm text-foreground",
306
+ className: "rounded-md border border-border-subtle bg-bg-sunken p-4 text-sm text-text-primary",
307
307
  children: B
308
308
  }),
309
309
  H && /* @__PURE__ */ S("div", {
310
- className: "rounded-md border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive",
310
+ className: "rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
311
311
  children: H
312
312
  }),
313
313
  /* @__PURE__ */ S(l, {
@@ -322,17 +322,17 @@ function M() {
322
322
  children: [/* @__PURE__ */ C("div", {
323
323
  className: "flex items-center gap-2",
324
324
  children: [/* @__PURE__ */ S("span", {
325
- className: "font-medium text-foreground",
325
+ className: "font-medium text-text-primary",
326
326
  children: e.name
327
327
  }), e.appUrl && /* @__PURE__ */ S("a", {
328
328
  href: e.appUrl,
329
329
  target: "_blank",
330
330
  rel: "noopener noreferrer",
331
331
  onClick: (e) => e.stopPropagation(),
332
- children: /* @__PURE__ */ S(v, { className: "h-3 w-3 text-muted-foreground hover:text-foreground" })
332
+ children: /* @__PURE__ */ S(v, { className: "h-3 w-3 text-text-secondary hover:text-text-primary" })
333
333
  })]
334
334
  }), /* @__PURE__ */ S("div", {
335
- className: "truncate text-xs text-muted-foreground",
335
+ className: "truncate text-xs text-text-secondary",
336
336
  children: e.slug
337
337
  })]
338
338
  })
@@ -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 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":";;;;;;;;;;;;;;;AAuBA,IAAM,IAAY,IAIZ,IAA6F;CACjG,QAAQ;CACR,aAAa;CACb,UAAU;CACV,OAAO;CACR,EAUK,IAAwB,CAAG;IARD,CAAG;;;;;;EASP;;;;;;;;GAUtB,IAA0B,CAAG;;;;;;;GAS7B,IAA0B,CAAG;;;;;;;GA8B7B,IAAoC;CACxC;EACE,MAAM;EACN,MAAM;EACN,aAAa;EACb,QAAQ;EACR,SAAS;EACT,QAAQ;EACR,SAAS;EACT,WAAW;EACX,eAAe;EACf,cAAc;EACd,cAAc;EACd,cAAc;EACd,gBAAgB;EAChB,UAAU;EACV,gBAAgB;EAChB,aAAa;EACb,aAAa;GACX,SAAS;GACT,QAAQ;GACR,SAAS;GACT,UAAU;GACX;EACD,UAAU;GACR,SAAS;GACT,gBAAgB;GAChB,UAAU;GACV,SAAS;GACT,cAAc;GACf;EACF;CACD;EACE,MAAM;EACN,MAAM;EACN,aAAa;EACb,QAAQ;EACR,SAAS;EACT,QAAQ;EACR,SAAS;EACT,WAAW;EACX,eAAe;EACf,cAAc;EACd,cAAc;EACd,cAAc;EACd,gBAAgB;EAChB,UAAU;EACV,gBAAgB;EAChB,aAAa;EACb,aAAa;GACX,SAAS;GACT,QAAQ;GACR,SAAS;GACT,UAAU;GACX;EACD,UAAU;GACR,SAAS;GACT,gBAAgB;GAChB,UAAU;GACV,SAAS;GACT,cAAc;GACf;EACF;CACD;EACE,MAAM;EACN,MAAM;EACN,aACE;EACF,QAAQ;EACR,SAAS;EACT,QAAQ;EACR,SAAS;EACT,WAAW;EACX,eAAe;EACf,cAAc;EACd,cAAc;EACd,cAAc;EACd,gBAAgB;EAChB,UAAU;EACV,gBACE;EACF,aAAa;EACb,aAAa;GACX,SAAS;GACT,QAAQ;GACR,SAAS;GACT,UAAU;GACX;EACD,UAAU;GACR,SACE;GACF,gBAAgB;GAChB,UAAU;GACV,SAAS;GACT,cAAc;GACf;EACF;CACD;EACE,MAAM;EACN,MAAM;EACN,aAAa;EACb,QAAQ;EACR,SAAS;EACT,QAAQ;EACR,SAAS;EACT,WAAW;EACX,eAAe;EACf,cAAc;EACd,cAAc;EACd,cAAc;EACd,gBAAgB;EAChB,UAAU;EACV,gBAAgB;EAChB,aAAa;EACb,aAAa;GACX,SAAS;GACT,QAAQ;GACR,SAAS;GACT,UAAU;GACX;EACD,UAAU;GACR,SAAS;GACT,gBAAgB;GAChB,UAAU;GACV,SAAS;GACT,cAAc;GACf;EACF;CACD;EACE,MAAM;EACN,MAAM;EACN,aAAa;EACb,QAAQ;EACR,SAAS;EACT,QAAQ;EACR,SAAS;EACT,WAAW;EACX,eAAe;EACf,cAAc;EACd,cAAc;EACd,cAAc;EACd,gBAAgB;EAChB,UAAU;EACV,gBACE;EACF,aAAa;EACb,aAAa;GACX,SAAS;GACT,QAAQ;GACR,SAAS;GACT,UAAU;GACX;EACD,UAAU;GACR,SACE;GACF,gBAAgB;GAChB,UAAU;GACV,SAAS;GACT,cAAc;GACf;EACF;CACF;AAmBD,SAAgB,IAAe;CAC7B,IAAM,EAAE,oBAAiB,WAAW,MAAuB,GAAqB,EAC1E,IAAe,GAAiB,EAEhC,CAAC,GAAM,KAAW,EAAS,EAAE,EAC7B,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,CAAC,GAAW,KAAgB,EAAS,GAAM,EAC3C,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAW,KAAgB,EAAwB,KAAK,EAEzD,IAAc,KAAsB,CAAC,GAErC,EAAE,MAAM,MAAc,EAAgC,EAC1D,MAAM,GACP,CAAC,EACI,EAAE,SAAM,YAAS,UAAO,eAAY,EAA6B;EACrE,WAAW;GACT,QAAQ,IAAS,EAAE,WAAQ,GAAG,KAAA;GAC9B,OAAO;GACP,SAAS,IAAO,KAAK;GACtB;EACD,aAAa;EACb,MAAM;EACP,CAAC,EAEI,IAA0B,QACvB,GAAM,kBAAkB,SAAS,EAAE,EAC1C,CAAC,EAAK,CACP,EAEK,IAAQ,GAAM,kBAAkB,SAAS,GACzC,IAAa,KAAK,KAAK,IAAQ,EAAU,EACzC,IAAQ,GAAW,sBAEnB,KAAqB,EAAY,YAAY;AAGjD,EAFA,EAAa,GAAK,EAClB,EAAe,KAAK,EACpB,EAAa,KAAK;AAElB,MAAI;GAQF,IAAM,KAPiB,MAAM,EAAa,MAEvC;IACD,OAAO;IACP,WAAW;KAAE,OAAO;KAAK,QAAQ;KAAG;IACpC,aAAa;IACd,CAAC,EACsC,MAAM,kBAAkB,SAAS,EAAE,EACrE,IAAiB,IAAI,IAAI,EAAiB,KAAK,MAAY,CAAC,EAAQ,MAAM,EAAQ,CAAC,CAAC,EAEtF,IAAU,GACV,IAAU;AACd,QAAK,IAAM,KAAQ,GAAe;IAChC,IAAM,IAAW,EAAe,IAAI,EAAK,KAAK;AAC9C,QAAI,GAAU;KACZ,IAAM,IAAQ,OAAO,YAAY,OAAO,QAAQ,EAAK,CAAC,QAAQ,CAAC,OAAS,MAAQ,OAAO,CAAC;AAKxF,KAJA,MAAM,EAAa,OAAO;MACxB,UAAU;MACV,WAAW;OAAE,IAAI,EAAS;OAAI;OAAO;MACtC,CAAC,EACF,KAAW;UAMX,CAJA,MAAM,EAAa,OAAO;KACxB,UAAU;KACV,WAAW,EAAE,OAAO,GAAM;KAC3B,CAAC,EACF,KAAW;;AAKf,GADA,EAAe,oBAAoB,EAAQ,YAAY,EAAQ,WAAW,EAC1E,MAAM,GAAS;WACR,GAAK;AACZ,KAAa,aAAe,QAAQ,EAAI,UAAU,0BAA0B;YACpE;AACR,KAAa,GAAM;;IAEpB,CAAC,GAAc,EAAQ,CAAC;AAuD3B,QACE,kBAAC,GAAD;EACE,SAAS;EACT,WAAW;EACX,WAAU;EACV,aAAY;YAEZ,kBAAC,GAAD;GACE,OAAM;GACN,aAAa,iCAAiC,IAAQ,MAAM,EAAM,UAAU;GAC5E,SACE,kBAAC,GAAD;IAAQ,SAAS;IAAoB,UAAU;cAA/C,CACE,kBAAC,GAAD,EAAU,WAAU,gBAAiB,CAAA,EACpC,IAAY,eAAe,uBACrB;;aAPb,CAWG,KACC,kBAAC,GAAD;IAAa,WAAU;cAAvB;KACE,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAM;MACb,MAAM,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;MACrC,CAAA;KACF,kBAAC,GAAD;MAAY,OAAM;MAAS,OAAO,EAAM;MAAQ,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;MAAI,CAAA;KACxF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAM;MACb,MAAM,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;MACrC,CAAA;KACF,kBAAC,GAAD;MAAY,OAAM;MAAQ,OAAO,EAAM;MAAO,MAAM,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;MAAI,CAAA;KAC3E;OAGhB,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,GAAD;OACE,aAAY;OACZ,OAAO;OACP,WAAW,MAAM;AAEf,QADA,EAAQ,EAAE,EACV,EAAU,EAAE,OAAO,MAAM;;OAE3B,WAAU;OACV,CAAA,CACE;;KAEL,KACC,kBAAC,OAAD;MAAK,WAAU;gBAAf,CAA0H,6BAC9F,EAAM,QAC5B;;KAEP,KACC,kBAAC,OAAD;MAAK,WAAU;gBACZ;MACG,CAAA;KAEP,KACC,kBAAC,OAAD;MAAK,WAAU;gBACZ;MACG,CAAA;KAGR,kBAAC,GAAD;MACW,SAvHH;OACd;QACE,KAAK;QACL,QAAQ;QACR,SAAS,MACP,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,QAAD;YAAM,WAAU;sBAAiC,EAAE;YAAY,CAAA,EAC9D,EAAE,UACD,kBAAC,KAAD;YACE,MAAM,EAAE;YACR,QAAO;YACP,KAAI;YACJ,UAAU,MAAM,EAAE,iBAAiB;sBAEnC,kBAAC,GAAD,EAAc,WAAU,uDAAwD,CAAA;YAC9E,CAAA,CAEF;cACN,kBAAC,OAAD;WAAK,WAAU;qBAAwC,EAAE;WAAW,CAAA,CAChE;;SACF,CAAA;QAET;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,SAAS,MACP,kBAAC,GAAD;SACE,SAAS,EAAe,EAAE,WAA4B;SACtD,WAAU;mBAET,EAAE,OAAO,aAAa,CAAC,QAAQ,KAAK,IAAI;SACnC,CAAA;QAEX;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,OAAO;QACP,SAAS,MAAmB,kBAAC,QAAD;SAAM,WAAU;mBAAW,EAAE;SAAmB,CAAA;QAC7E;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,SAAS,MAAmB,EAAW,EAAE,UAAU;QACpD;OACF;MAqES,MAAM;MACN,eAAe,MAAM,EAAE;MACd;MACT,YACE,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;OACrC,OAAM;OACN,aAAY;OACZ,CAAA;MAEJ,CAAA;KAED,IAAQ,KACP,kBAAC,GAAD;MACQ;MACM;MACZ,YAAY;MACZ,UAAU;MACV,cAAc;MACd,CAAA;KAEA;MACK;;EACG,CAAA"}
@@ -85,7 +85,7 @@ function g() {
85
85
  actions: /* @__PURE__ */ h("button", {
86
86
  type: "button",
87
87
  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",
88
+ 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
89
  children: [/* @__PURE__ */ m(f, { className: "h-4 w-4" }), "Refresh"]
90
90
  }),
91
91
  children: /* @__PURE__ */ h("div", {
@@ -98,53 +98,53 @@ function g() {
98
98
  2,
99
99
  3,
100
100
  4
101
- ].map((e) => /* @__PURE__ */ m("div", { className: "h-28 animate-pulse rounded-lg border border-border bg-card" }, e))
101
+ ].map((e) => /* @__PURE__ */ m("div", { className: "h-28 animate-pulse rounded-lg border border-border-subtle bg-bg-surface" }, e))
102
102
  }) : S ? /* @__PURE__ */ m("div", {
103
103
  className: "grid gap-4 md:grid-cols-2 xl:grid-cols-4",
104
104
  children: I.map((e) => /* @__PURE__ */ h("div", {
105
- className: "rounded-lg border border-border bg-card p-4",
105
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
106
106
  children: [/* @__PURE__ */ h("div", {
107
- className: "mb-3 flex items-center justify-between text-muted-foreground",
107
+ className: "mb-3 flex items-center justify-between text-text-secondary",
108
108
  children: [/* @__PURE__ */ m("span", {
109
109
  className: "text-sm font-medium",
110
110
  children: e.title
111
111
  }), e.icon]
112
112
  }), /* @__PURE__ */ m("p", {
113
- className: "text-2xl font-semibold text-foreground",
113
+ className: "text-2xl font-semibold text-text-primary",
114
114
  children: e.value
115
115
  })]
116
116
  }, e.title))
117
117
  }) : null,
118
118
  M ? /* @__PURE__ */ m("div", {
119
- className: "rounded-lg border border-border bg-card p-4 text-sm text-destructive",
119
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm text-status-error-text",
120
120
  children: M
121
121
  }) : null,
122
122
  /* @__PURE__ */ h("div", {
123
123
  className: "grid gap-6 xl:grid-cols-[1.15fr,1.85fr]",
124
124
  children: [/* @__PURE__ */ h("section", {
125
- className: "rounded-lg border border-border bg-card p-4",
125
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
126
126
  children: [/* @__PURE__ */ m("h2", {
127
- className: "mb-4 text-lg font-semibold text-foreground",
127
+ className: "mb-4 text-lg font-semibold text-text-primary",
128
128
  children: "Top affiliates"
129
129
  }), S?.topAffiliates.length ? /* @__PURE__ */ m("div", {
130
130
  className: "space-y-3",
131
131
  children: S.topAffiliates.map((e) => /* @__PURE__ */ m("div", {
132
- className: "rounded-lg border border-border bg-background p-3",
132
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-3",
133
133
  children: /* @__PURE__ */ h("div", {
134
134
  className: "flex items-center justify-between gap-3",
135
135
  children: [/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("p", {
136
- className: "text-sm font-semibold text-foreground",
136
+ className: "text-sm font-semibold text-text-primary",
137
137
  children: e.codeValue
138
138
  }), /* @__PURE__ */ h("p", {
139
- className: "text-xs text-muted-foreground",
139
+ className: "text-xs text-text-secondary",
140
140
  children: ["Owner: ", e.ownerUserId]
141
141
  })] }), /* @__PURE__ */ h("div", {
142
142
  className: "text-right",
143
143
  children: [/* @__PURE__ */ h("p", {
144
- className: "text-sm font-semibold text-foreground",
144
+ className: "text-sm font-semibold text-text-primary",
145
145
  children: [e.visits, " visits"]
146
146
  }), /* @__PURE__ */ h("p", {
147
- className: "text-xs text-muted-foreground",
147
+ className: "text-xs text-text-secondary",
148
148
  children: [e.paidConversions + e.storePurchases, " conversions"]
149
149
  })]
150
150
  })]
@@ -158,16 +158,16 @@ function g() {
158
158
  }), /* @__PURE__ */ h("section", {
159
159
  className: "space-y-4",
160
160
  children: [/* @__PURE__ */ h("div", {
161
- className: "rounded-lg border border-border bg-card p-4",
161
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
162
162
  children: [/* @__PURE__ */ m("h2", {
163
- className: "mb-4 text-lg font-semibold text-foreground",
163
+ className: "mb-4 text-lg font-semibold text-text-primary",
164
164
  children: "Recent journeys"
165
165
  }), w.length ? /* @__PURE__ */ m("div", {
166
166
  className: "overflow-x-auto",
167
167
  children: /* @__PURE__ */ h("table", {
168
168
  className: "min-w-full text-sm",
169
169
  children: [/* @__PURE__ */ m("thead", { children: /* @__PURE__ */ h("tr", {
170
- className: "border-b border-border text-left text-muted-foreground",
170
+ className: "border-b border-border-subtle text-left text-text-secondary",
171
171
  children: [
172
172
  /* @__PURE__ */ m("th", {
173
173
  className: "px-3 py-2",
@@ -195,7 +195,7 @@ function g() {
195
195
  })
196
196
  ]
197
197
  }) }), /* @__PURE__ */ m("tbody", { children: w.map((e) => /* @__PURE__ */ h("tr", {
198
- className: "cursor-pointer border-b border-border/70 text-foreground",
198
+ className: "cursor-pointer border-b border-border-subtle/70 text-text-primary",
199
199
  onClick: () => D(e.id),
200
200
  children: [
201
201
  /* @__PURE__ */ m("td", {
@@ -231,24 +231,24 @@ function g() {
231
231
  description: "Journey-level drilldowns will appear once referral traffic flows through product touchpoints."
232
232
  })]
233
233
  }), /* @__PURE__ */ h("div", {
234
- className: "rounded-lg border border-border bg-card p-4",
234
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
235
235
  children: [/* @__PURE__ */ m("h2", {
236
- className: "mb-4 text-lg font-semibold text-foreground",
236
+ className: "mb-4 text-lg font-semibold text-text-primary",
237
237
  children: "Journey events"
238
238
  }), O.length ? /* @__PURE__ */ m("div", {
239
239
  className: "space-y-3",
240
240
  children: O.map((e) => /* @__PURE__ */ m("div", {
241
- className: "rounded-lg border border-border bg-background p-3",
241
+ className: "rounded-lg border border-border-subtle bg-bg-surface p-3",
242
242
  children: /* @__PURE__ */ h("div", {
243
243
  className: "flex items-center justify-between gap-3",
244
244
  children: [/* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("p", {
245
- className: "text-sm font-semibold text-foreground",
245
+ className: "text-sm font-semibold text-text-primary",
246
246
  children: e.type
247
247
  }), /* @__PURE__ */ m("p", {
248
- className: "text-xs text-muted-foreground",
248
+ className: "text-xs text-text-secondary",
249
249
  children: i(e.occurredAt)
250
250
  })] }), /* @__PURE__ */ m("p", {
251
- className: "text-sm font-medium text-foreground",
251
+ className: "text-sm font-medium text-text-primary",
252
252
  children: e.valueAmount ? `${e.valueAmount} ${e.currency || "USD"}` : "—"
253
253
  })]
254
254
  })