@burdenoff/microfe-admin 2026.610.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.
- package/dist/admin/components/AccessDenied.js +2 -2
- package/dist/admin/components/AccessDenied.js.map +1 -1
- package/dist/admin/components/ActivityFeed.js +4 -4
- package/dist/admin/components/ActivityFeed.js.map +1 -1
- package/dist/admin/components/Charts.js +4 -4
- package/dist/admin/components/Charts.js.map +1 -1
- package/dist/admin/components/ConfirmDialog.js +6 -6
- package/dist/admin/components/ConfirmDialog.js.map +1 -1
- package/dist/admin/components/DataTable.js +7 -7
- package/dist/admin/components/DataTable.js.map +1 -1
- package/dist/admin/components/FormSheet.js +7 -7
- package/dist/admin/components/FormSheet.js.map +1 -1
- package/dist/admin/components/MetricCard.js +4 -4
- package/dist/admin/components/MetricCard.js.map +1 -1
- package/dist/admin/components/PageLayout.js +3 -3
- package/dist/admin/components/PageLayout.js.map +1 -1
- package/dist/admin/components/PermissionGuard.js +1 -1
- package/dist/admin/components/PermissionGuard.js.map +1 -1
- package/dist/admin/components/RowActions.js +2 -2
- package/dist/admin/components/RowActions.js.map +1 -1
- package/dist/admin/components/TenantMembersPanel.js +15 -15
- package/dist/admin/components/TenantMembersPanel.js.map +1 -1
- package/dist/admin/components/TenantSsoPanel.js +14 -14
- package/dist/admin/components/TenantSsoPanel.js.map +1 -1
- package/dist/admin/components/Toast.js +9 -9
- package/dist/admin/components/Toast.js.map +1 -1
- package/dist/admin/components/forms/SubscriptionForm.js +10 -10
- package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
- package/dist/admin/components/forms/TenantForm.js +8 -8
- package/dist/admin/components/forms/TenantForm.js.map +1 -1
- package/dist/admin/components/forms/TicketForm.js +2 -2
- package/dist/admin/components/forms/TicketForm.js.map +1 -1
- package/dist/admin/components/forms/UserForm.js +8 -8
- package/dist/admin/components/forms/UserForm.js.map +1 -1
- package/dist/admin/components/ui.js +16 -16
- package/dist/admin/components/ui.js.map +1 -1
- package/dist/admin/constants/index.js +8 -8
- package/dist/admin/constants/index.js.map +1 -1
- package/dist/admin/pages/AuditLogsPage.js +8 -8
- package/dist/admin/pages/AuditLogsPage.js.map +1 -1
- package/dist/admin/pages/CommunicationsPage.js +2 -2
- package/dist/admin/pages/CommunicationsPage.js.map +1 -1
- package/dist/admin/pages/FeatureFlagsPage.js +4 -4
- package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
- package/dist/admin/pages/OrganizationsListPage.js +6 -6
- package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
- package/dist/admin/pages/ProductsPage.js +7 -7
- package/dist/admin/pages/ProductsPage.js.map +1 -1
- package/dist/admin/pages/ReferralsPage.js +23 -23
- package/dist/admin/pages/ReferralsPage.js.map +1 -1
- package/dist/admin/pages/RolesPermissionsPage.js +8 -8
- package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
- package/dist/admin/pages/SREPage.js +8 -8
- package/dist/admin/pages/SREPage.js.map +1 -1
- package/dist/admin/pages/SandboxSettingsPage.js +20 -20
- package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
- package/dist/admin/pages/SettingsPage.js +20 -20
- package/dist/admin/pages/SettingsPage.js.map +1 -1
- package/dist/admin/pages/SyncFailuresPage.js +11 -11
- package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
- package/dist/admin/pages/TenantDetailPage.js +22 -22
- package/dist/admin/pages/TenantDetailPage.js.map +1 -1
- package/dist/admin/pages/TenantFormRoutePage.js +2 -2
- package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
- package/dist/admin/pages/TenantsListPage.js +5 -5
- package/dist/admin/pages/TenantsListPage.js.map +1 -1
- package/dist/admin/pages/UsersListPage.js +6 -6
- package/dist/admin/pages/UsersListPage.js.map +1 -1
- package/dist/admin/pages/WaitlistPage.js +7 -7
- package/dist/admin/pages/WaitlistPage.js.map +1 -1
- package/dist/admin/pages/WorkflowsPage.js +3 -3
- package/dist/admin/pages/WorkflowsPage.js.map +1 -1
- package/dist/admin/pages/WorkspacesListPage.js +8 -8
- package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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":"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"}
|
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
136
|
+
className: "text-sm font-semibold text-text-primary",
|
|
137
137
|
children: e.codeValue
|
|
138
138
|
}), /* @__PURE__ */ h("p", {
|
|
139
|
-
className: "text-xs text-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
245
|
+
className: "text-sm font-semibold text-text-primary",
|
|
246
246
|
children: e.type
|
|
247
247
|
}), /* @__PURE__ */ m("p", {
|
|
248
|
-
className: "text-xs text-
|
|
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-
|
|
251
|
+
className: "text-sm font-medium text-text-primary",
|
|
252
252
|
children: e.valueAmount ? `${e.valueAmount} ${e.currency || "USD"}` : "—"
|
|
253
253
|
})]
|
|
254
254
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReferralsPage.js","names":[],"sources":["../../../src/admin/pages/ReferralsPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Activity, Link2, RefreshCw, Users } from 'lucide-react';\n\nimport { PageLayout, EmptyState, PermissionGuard } from '../components';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport { useAdmin } from '../providers/AdminProvider';\nimport { directActivityGraphqlRequest } from '../utils/directActivityGraphql';\nimport { formatRelativeTime } from '../utils/formatters';\n\ntype ReferralSummary = {\n totalCodes: number;\n totalVisits: number;\n totalJourneys: number;\n totalSignups: number;\n totalPaidConversions: number;\n totalStorePurchases: number;\n totalRevenueAmount: number;\n totalRewardAmount: number;\n topAffiliates: Array<{\n codeId: string;\n codeValue: string;\n ownerUserId: string;\n visits: number;\n signups: number;\n paidConversions: number;\n storePurchases: number;\n revenueAmount: number;\n rewardAmount: number;\n }>;\n};\n\ntype ReferralJourney = {\n id: string;\n codeValue: string;\n identifiedUserId?: string | null;\n productSlug: string;\n currentPath?: string | null;\n geographyCountry?: string | null;\n mode?: string | null;\n status: string;\n lastVisitedAt: string;\n};\n\ntype ReferralEvent = {\n id: string;\n type: string;\n valueAmount?: number | null;\n currency?: string | null;\n occurredAt: string;\n};\n\nexport function ReferralsPage() {\n const { canViewDashboard, isLoading: permissionsLoading } = useAdminPermissions();\n const { apiGatewayUrl, authToken, currentUser } = useAdmin();\n const canViewReferrals = canViewDashboard || currentUser?.role === 'PLATFORM_SUPERADMIN';\n\n const [summary, setSummary] = useState<ReferralSummary | null>(null);\n const [journeys, setJourneys] = useState<ReferralJourney[]>([]);\n const [selectedJourneyId, setSelectedJourneyId] = useState<string | null>(null);\n const [selectedJourneyEvents, setSelectedJourneyEvents] = useState<ReferralEvent[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const requestContext = useMemo(\n () => ({\n apiGatewayUrl,\n authToken,\n actorId: currentUser?.id,\n actorType: 'super_admin' as const,\n }),\n [apiGatewayUrl, authToken, currentUser?.id]\n );\n\n const loadData = async () => {\n setLoading(true);\n setError(null);\n try {\n const [summaryData, journeyData] = await Promise.all([\n directActivityGraphqlRequest<{ adminAffiliateAnalytics: ReferralSummary }>({\n ...requestContext,\n query: `query AdminAffiliateAnalytics {\n adminAffiliateAnalytics {\n totalCodes\n totalVisits\n totalJourneys\n totalSignups\n totalPaidConversions\n totalStorePurchases\n totalRevenueAmount\n totalRewardAmount\n topAffiliates {\n codeId\n codeValue\n ownerUserId\n visits\n signups\n paidConversions\n storePurchases\n revenueAmount\n rewardAmount\n }\n }\n }`,\n }),\n directActivityGraphqlRequest<{ affiliateJourneys: { items: ReferralJourney[] } }>({\n ...requestContext,\n query: `query AffiliateJourneys {\n affiliateJourneys(limit: 25, offset: 0) {\n items {\n id\n codeValue\n identifiedUserId\n productSlug\n currentPath\n geographyCountry\n mode\n status\n lastVisitedAt\n }\n }\n }`,\n }),\n ]);\n\n setSummary(summaryData.adminAffiliateAnalytics);\n setJourneys(journeyData.affiliateJourneys.items);\n } catch (loadError) {\n setError(\n loadError instanceof Error ? loadError.message : 'Failed to load referrals dashboard'\n );\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [requestContext]);\n\n useEffect(() => {\n if (!selectedJourneyId) {\n setSelectedJourneyEvents([]);\n return;\n }\n\n void directActivityGraphqlRequest<\n { affiliateJourneyEvents: ReferralEvent[] },\n { journeyId: string }\n >({\n ...requestContext,\n query: `query AffiliateJourneyEvents($journeyId: ID!) {\n affiliateJourneyEvents(journeyId: $journeyId) {\n id\n type\n valueAmount\n currency\n occurredAt\n }\n }`,\n variables: { journeyId: selectedJourneyId },\n })\n .then((response) => setSelectedJourneyEvents(response.affiliateJourneyEvents))\n .catch((loadError) => {\n setError(loadError instanceof Error ? loadError.message : 'Failed to load journey events');\n });\n }, [requestContext, selectedJourneyId]);\n\n const cards = summary\n ? [\n { title: 'Codes', value: summary.totalCodes, icon: <Link2 className=\"h-5 w-5\" /> },\n { title: 'Visits', value: summary.totalVisits, icon: <Activity className=\"h-5 w-5\" /> },\n { title: 'Signups', value: summary.totalSignups, icon: <Users className=\"h-5 w-5\" /> },\n {\n title: 'Paid conversions',\n value: summary.totalPaidConversions + summary.totalStorePurchases,\n icon: <Users className=\"h-5 w-5\" />,\n },\n ]\n : [];\n\n return (\n <PermissionGuard\n allowed={canViewReferrals}\n isLoading={permissionsLoading}\n pageTitle=\"Referrals\"\n denyMessage=\"You don't have permission to view referral analytics.\"\n >\n <PageLayout\n title=\"Referrals\"\n description=\"Inspect top affiliates, journey drilldowns, conversions, and revenue attribution.\"\n actions={\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-sm font-medium text-foreground\"\n >\n <RefreshCw className=\"h-4 w-4\" />\n Refresh\n </button>\n }\n >\n <div className=\"space-y-6\">\n {loading ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {[1, 2, 3, 4].map((item) => (\n <div\n key={item}\n className=\"h-28 animate-pulse rounded-lg border border-border bg-card\"\n />\n ))}\n </div>\n ) : summary ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {cards.map((card) => (\n <div key={card.title} className=\"rounded-lg border border-border bg-card p-4\">\n <div className=\"mb-3 flex items-center justify-between text-muted-foreground\">\n <span className=\"text-sm font-medium\">{card.title}</span>\n {card.icon}\n </div>\n <p className=\"text-2xl font-semibold text-foreground\">{card.value}</p>\n </div>\n ))}\n </div>\n ) : null}\n\n {error ? (\n <div className=\"rounded-lg border border-border bg-card p-4 text-sm text-destructive\">\n {error}\n </div>\n ) : null}\n\n <div className=\"grid gap-6 xl:grid-cols-[1.15fr,1.85fr]\">\n <section className=\"rounded-lg border border-border bg-card p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-foreground\">Top affiliates</h2>\n {summary?.topAffiliates.length ? (\n <div className=\"space-y-3\">\n {summary.topAffiliates.map((affiliate) => (\n <div\n key={affiliate.codeId}\n className=\"rounded-lg border border-border bg-background p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-sm font-semibold text-foreground\">\n {affiliate.codeValue}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n Owner: {affiliate.ownerUserId}\n </p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-semibold text-foreground\">\n {affiliate.visits} visits\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {affiliate.paidConversions + affiliate.storePurchases} conversions\n </p>\n </div>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <EmptyState\n icon={<Link2 className=\"h-8 w-8\" />}\n title=\"No affiliate performance yet\"\n description=\"Top-performing affiliate codes will appear here once journeys are recorded.\"\n />\n )}\n </section>\n\n <section className=\"space-y-4\">\n <div className=\"rounded-lg border border-border bg-card p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-foreground\">Recent journeys</h2>\n {journeys.length ? (\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border text-left text-muted-foreground\">\n <th className=\"px-3 py-2\">Code</th>\n <th className=\"px-3 py-2\">Product</th>\n <th className=\"px-3 py-2\">Path</th>\n <th className=\"px-3 py-2\">Country</th>\n <th className=\"px-3 py-2\">Status</th>\n <th className=\"px-3 py-2\">Visited</th>\n </tr>\n </thead>\n <tbody>\n {journeys.map((journey) => (\n <tr\n key={journey.id}\n className=\"cursor-pointer border-b border-border/70 text-foreground\"\n onClick={() => setSelectedJourneyId(journey.id)}\n >\n <td className=\"px-3 py-2 font-medium\">{journey.codeValue}</td>\n <td className=\"px-3 py-2\">{journey.productSlug}</td>\n <td className=\"px-3 py-2\">{journey.currentPath || '-'}</td>\n <td className=\"px-3 py-2\">{journey.geographyCountry || '-'}</td>\n <td className=\"px-3 py-2\">{journey.status}</td>\n <td className=\"px-3 py-2\">\n {formatRelativeTime(journey.lastVisitedAt)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n ) : (\n <EmptyState\n icon={<Activity className=\"h-8 w-8\" />}\n title=\"No journeys found\"\n description=\"Journey-level drilldowns will appear once referral traffic flows through product touchpoints.\"\n />\n )}\n </div>\n\n <div className=\"rounded-lg border border-border bg-card p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-foreground\">Journey events</h2>\n {selectedJourneyEvents.length ? (\n <div className=\"space-y-3\">\n {selectedJourneyEvents.map((event) => (\n <div\n key={event.id}\n className=\"rounded-lg border border-border bg-background p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-sm font-semibold text-foreground\">{event.type}</p>\n <p className=\"text-xs text-muted-foreground\">\n {formatRelativeTime(event.occurredAt)}\n </p>\n </div>\n <p className=\"text-sm font-medium text-foreground\">\n {event.valueAmount\n ? `${event.valueAmount} ${event.currency || 'USD'}`\n : '—'}\n </p>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <EmptyState\n icon={<Activity className=\"h-8 w-8\" />}\n title=\"Select a journey\"\n description=\"Choose a journey from the table above to inspect its critical events.\"\n />\n )}\n </div>\n </section>\n </div>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ReferralsPage.js","names":[],"sources":["../../../src/admin/pages/ReferralsPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Activity, Link2, RefreshCw, Users } from 'lucide-react';\n\nimport { PageLayout, EmptyState, PermissionGuard } from '../components';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport { useAdmin } from '../providers/AdminProvider';\nimport { directActivityGraphqlRequest } from '../utils/directActivityGraphql';\nimport { formatRelativeTime } from '../utils/formatters';\n\ntype ReferralSummary = {\n totalCodes: number;\n totalVisits: number;\n totalJourneys: number;\n totalSignups: number;\n totalPaidConversions: number;\n totalStorePurchases: number;\n totalRevenueAmount: number;\n totalRewardAmount: number;\n topAffiliates: Array<{\n codeId: string;\n codeValue: string;\n ownerUserId: string;\n visits: number;\n signups: number;\n paidConversions: number;\n storePurchases: number;\n revenueAmount: number;\n rewardAmount: number;\n }>;\n};\n\ntype ReferralJourney = {\n id: string;\n codeValue: string;\n identifiedUserId?: string | null;\n productSlug: string;\n currentPath?: string | null;\n geographyCountry?: string | null;\n mode?: string | null;\n status: string;\n lastVisitedAt: string;\n};\n\ntype ReferralEvent = {\n id: string;\n type: string;\n valueAmount?: number | null;\n currency?: string | null;\n occurredAt: string;\n};\n\nexport function ReferralsPage() {\n const { canViewDashboard, isLoading: permissionsLoading } = useAdminPermissions();\n const { apiGatewayUrl, authToken, currentUser } = useAdmin();\n const canViewReferrals = canViewDashboard || currentUser?.role === 'PLATFORM_SUPERADMIN';\n\n const [summary, setSummary] = useState<ReferralSummary | null>(null);\n const [journeys, setJourneys] = useState<ReferralJourney[]>([]);\n const [selectedJourneyId, setSelectedJourneyId] = useState<string | null>(null);\n const [selectedJourneyEvents, setSelectedJourneyEvents] = useState<ReferralEvent[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const requestContext = useMemo(\n () => ({\n apiGatewayUrl,\n authToken,\n actorId: currentUser?.id,\n actorType: 'super_admin' as const,\n }),\n [apiGatewayUrl, authToken, currentUser?.id]\n );\n\n const loadData = async () => {\n setLoading(true);\n setError(null);\n try {\n const [summaryData, journeyData] = await Promise.all([\n directActivityGraphqlRequest<{ adminAffiliateAnalytics: ReferralSummary }>({\n ...requestContext,\n query: `query AdminAffiliateAnalytics {\n adminAffiliateAnalytics {\n totalCodes\n totalVisits\n totalJourneys\n totalSignups\n totalPaidConversions\n totalStorePurchases\n totalRevenueAmount\n totalRewardAmount\n topAffiliates {\n codeId\n codeValue\n ownerUserId\n visits\n signups\n paidConversions\n storePurchases\n revenueAmount\n rewardAmount\n }\n }\n }`,\n }),\n directActivityGraphqlRequest<{ affiliateJourneys: { items: ReferralJourney[] } }>({\n ...requestContext,\n query: `query AffiliateJourneys {\n affiliateJourneys(limit: 25, offset: 0) {\n items {\n id\n codeValue\n identifiedUserId\n productSlug\n currentPath\n geographyCountry\n mode\n status\n lastVisitedAt\n }\n }\n }`,\n }),\n ]);\n\n setSummary(summaryData.adminAffiliateAnalytics);\n setJourneys(journeyData.affiliateJourneys.items);\n } catch (loadError) {\n setError(\n loadError instanceof Error ? loadError.message : 'Failed to load referrals dashboard'\n );\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [requestContext]);\n\n useEffect(() => {\n if (!selectedJourneyId) {\n setSelectedJourneyEvents([]);\n return;\n }\n\n void directActivityGraphqlRequest<\n { affiliateJourneyEvents: ReferralEvent[] },\n { journeyId: string }\n >({\n ...requestContext,\n query: `query AffiliateJourneyEvents($journeyId: ID!) {\n affiliateJourneyEvents(journeyId: $journeyId) {\n id\n type\n valueAmount\n currency\n occurredAt\n }\n }`,\n variables: { journeyId: selectedJourneyId },\n })\n .then((response) => setSelectedJourneyEvents(response.affiliateJourneyEvents))\n .catch((loadError) => {\n setError(loadError instanceof Error ? loadError.message : 'Failed to load journey events');\n });\n }, [requestContext, selectedJourneyId]);\n\n const cards = summary\n ? [\n { title: 'Codes', value: summary.totalCodes, icon: <Link2 className=\"h-5 w-5\" /> },\n { title: 'Visits', value: summary.totalVisits, icon: <Activity className=\"h-5 w-5\" /> },\n { title: 'Signups', value: summary.totalSignups, icon: <Users className=\"h-5 w-5\" /> },\n {\n title: 'Paid conversions',\n value: summary.totalPaidConversions + summary.totalStorePurchases,\n icon: <Users className=\"h-5 w-5\" />,\n },\n ]\n : [];\n\n return (\n <PermissionGuard\n allowed={canViewReferrals}\n isLoading={permissionsLoading}\n pageTitle=\"Referrals\"\n denyMessage=\"You don't have permission to view referral analytics.\"\n >\n <PageLayout\n title=\"Referrals\"\n description=\"Inspect top affiliates, journey drilldowns, conversions, and revenue attribution.\"\n actions={\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary\"\n >\n <RefreshCw className=\"h-4 w-4\" />\n Refresh\n </button>\n }\n >\n <div className=\"space-y-6\">\n {loading ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {[1, 2, 3, 4].map((item) => (\n <div\n key={item}\n className=\"h-28 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n ) : summary ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {cards.map((card) => (\n <div key={card.title} className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <div className=\"mb-3 flex items-center justify-between text-text-secondary\">\n <span className=\"text-sm font-medium\">{card.title}</span>\n {card.icon}\n </div>\n <p className=\"text-2xl font-semibold text-text-primary\">{card.value}</p>\n </div>\n ))}\n </div>\n ) : null}\n\n {error ? (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n\n <div className=\"grid gap-6 xl:grid-cols-[1.15fr,1.85fr]\">\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Top affiliates</h2>\n {summary?.topAffiliates.length ? (\n <div className=\"space-y-3\">\n {summary.topAffiliates.map((affiliate) => (\n <div\n key={affiliate.codeId}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {affiliate.codeValue}\n </p>\n <p className=\"text-xs text-text-secondary\">\n Owner: {affiliate.ownerUserId}\n </p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {affiliate.visits} visits\n </p>\n <p className=\"text-xs text-text-secondary\">\n {affiliate.paidConversions + affiliate.storePurchases} conversions\n </p>\n </div>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <EmptyState\n icon={<Link2 className=\"h-8 w-8\" />}\n title=\"No affiliate performance yet\"\n description=\"Top-performing affiliate codes will appear here once journeys are recorded.\"\n />\n )}\n </section>\n\n <section className=\"space-y-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Recent journeys</h2>\n {journeys.length ? (\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle text-left text-text-secondary\">\n <th className=\"px-3 py-2\">Code</th>\n <th className=\"px-3 py-2\">Product</th>\n <th className=\"px-3 py-2\">Path</th>\n <th className=\"px-3 py-2\">Country</th>\n <th className=\"px-3 py-2\">Status</th>\n <th className=\"px-3 py-2\">Visited</th>\n </tr>\n </thead>\n <tbody>\n {journeys.map((journey) => (\n <tr\n key={journey.id}\n className=\"cursor-pointer border-b border-border-subtle/70 text-text-primary\"\n onClick={() => setSelectedJourneyId(journey.id)}\n >\n <td className=\"px-3 py-2 font-medium\">{journey.codeValue}</td>\n <td className=\"px-3 py-2\">{journey.productSlug}</td>\n <td className=\"px-3 py-2\">{journey.currentPath || '-'}</td>\n <td className=\"px-3 py-2\">{journey.geographyCountry || '-'}</td>\n <td className=\"px-3 py-2\">{journey.status}</td>\n <td className=\"px-3 py-2\">\n {formatRelativeTime(journey.lastVisitedAt)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n ) : (\n <EmptyState\n icon={<Activity className=\"h-8 w-8\" />}\n title=\"No journeys found\"\n description=\"Journey-level drilldowns will appear once referral traffic flows through product touchpoints.\"\n />\n )}\n </div>\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Journey events</h2>\n {selectedJourneyEvents.length ? (\n <div className=\"space-y-3\">\n {selectedJourneyEvents.map((event) => (\n <div\n key={event.id}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">{event.type}</p>\n <p className=\"text-xs text-text-secondary\">\n {formatRelativeTime(event.occurredAt)}\n </p>\n </div>\n <p className=\"text-sm font-medium text-text-primary\">\n {event.valueAmount\n ? `${event.valueAmount} ${event.currency || 'USD'}`\n : '—'}\n </p>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <EmptyState\n icon={<Activity className=\"h-8 w-8\" />}\n title=\"Select a journey\"\n description=\"Choose a journey from the table above to inspect its critical events.\"\n />\n )}\n </div>\n </section>\n </div>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -27,7 +27,7 @@ function v() {
|
|
|
27
27
|
className: "flex items-center justify-between",
|
|
28
28
|
children: [/* @__PURE__ */ _("div", {
|
|
29
29
|
className: "relative flex-1 max-w-sm",
|
|
30
|
-
children: [/* @__PURE__ */ g(p, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-
|
|
30
|
+
children: [/* @__PURE__ */ g(p, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ g(r, {
|
|
31
31
|
placeholder: "Search roles...",
|
|
32
32
|
className: "pl-9",
|
|
33
33
|
value: v,
|
|
@@ -42,7 +42,7 @@ function v() {
|
|
|
42
42
|
})
|
|
43
43
|
})]
|
|
44
44
|
}), S ? /* @__PURE__ */ g(n, {
|
|
45
|
-
icon: /* @__PURE__ */ g(m, { className: "h-8 w-8 text-
|
|
45
|
+
icon: /* @__PURE__ */ g(m, { className: "h-8 w-8 text-status-error-text" }),
|
|
46
46
|
title: "Error loading roles",
|
|
47
47
|
description: S.message
|
|
48
48
|
}) : /* @__PURE__ */ g(a, {
|
|
@@ -55,7 +55,7 @@ function v() {
|
|
|
55
55
|
children: [/* @__PURE__ */ _("div", {
|
|
56
56
|
className: "flex items-center gap-2",
|
|
57
57
|
children: [/* @__PURE__ */ g("span", {
|
|
58
|
-
className: "font-semibold text-
|
|
58
|
+
className: "font-semibold text-text-primary",
|
|
59
59
|
children: t.name
|
|
60
60
|
}), t.isSystem && /* @__PURE__ */ g(e, {
|
|
61
61
|
variant: "secondary",
|
|
@@ -63,7 +63,7 @@ function v() {
|
|
|
63
63
|
children: "System"
|
|
64
64
|
})]
|
|
65
65
|
}), t.description && /* @__PURE__ */ g("span", {
|
|
66
|
-
className: "mt-0.5 line-clamp-1 text-xs text-
|
|
66
|
+
className: "mt-0.5 line-clamp-1 text-xs text-text-secondary",
|
|
67
67
|
children: t.description
|
|
68
68
|
})]
|
|
69
69
|
})
|
|
@@ -74,7 +74,7 @@ function v() {
|
|
|
74
74
|
align: "center",
|
|
75
75
|
width: "80px",
|
|
76
76
|
render: (e) => /* @__PURE__ */ g("span", {
|
|
77
|
-
className: "rounded bg-
|
|
77
|
+
className: "rounded bg-bg-sunken px-2 py-0.5 text-xs font-medium",
|
|
78
78
|
children: e.priority
|
|
79
79
|
})
|
|
80
80
|
},
|
|
@@ -85,16 +85,16 @@ function v() {
|
|
|
85
85
|
className: "flex max-w-md flex-wrap gap-1 text-left",
|
|
86
86
|
children: t.defaultPermissions && t.defaultPermissions.length > 0 ? t.defaultPermissions.map((t) => /* @__PURE__ */ _(e, {
|
|
87
87
|
variant: "outline",
|
|
88
|
-
className: "bg-
|
|
88
|
+
className: "bg-bg-surface/50 px-1 py-0 font-normal text-[10px]",
|
|
89
89
|
children: [/* @__PURE__ */ _("span", {
|
|
90
|
-
className: "mr-1 text-
|
|
90
|
+
className: "mr-1 text-text-secondary",
|
|
91
91
|
children: [t.resourceType, ":"]
|
|
92
92
|
}), /* @__PURE__ */ g("span", {
|
|
93
93
|
className: "font-medium",
|
|
94
94
|
children: t.level
|
|
95
95
|
})]
|
|
96
96
|
}, t.id)) : /* @__PURE__ */ g("span", {
|
|
97
|
-
className: "text-xs italic text-
|
|
97
|
+
className: "text-xs italic text-text-secondary",
|
|
98
98
|
children: "No permissions defined"
|
|
99
99
|
})
|
|
100
100
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RolesPermissionsPage.js","names":[],"sources":["../../../src/admin/pages/RolesPermissionsPage.tsx"],"sourcesContent":["import { Shield, Search, Plus, Pencil, Trash2 } from 'lucide-react';\nimport { useState, useMemo } from 'react';\nimport { PageLayout, Input, Button, EmptyState, DataTable, Badge, RowActions } from '../components';\nimport { useListRolesQuery, useGetResourceTypesQuery } from '../../generated/global-operations';\n\nexport function RolesPermissionsPage() {\n const [searchQuery, setSearchQuery] = useState('');\n\n const {\n data: rolesData,\n loading: rolesLoading,\n error: rolesError,\n } = useListRolesQuery({\n variables: { scopeId: 'global' },\n });\n\n const { data: resourceTypesData } = useGetResourceTypesQuery();\n\n const roles = useMemo(() => rolesData?.roles ?? [], [rolesData]);\n\n const filteredRoles = useMemo(() => {\n if (!searchQuery.trim()) return roles;\n const query = searchQuery.toLowerCase();\n return roles.filter(\n (role: any) =>\n role.name.toLowerCase().includes(query) || role.description?.toLowerCase().includes(query)\n );\n }, [roles, searchQuery]);\n\n const columns = [\n {\n key: 'name',\n header: 'Role Name',\n render: (role: any) => (\n <div className=\"flex flex-col text-left\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-semibold text-
|
|
1
|
+
{"version":3,"file":"RolesPermissionsPage.js","names":[],"sources":["../../../src/admin/pages/RolesPermissionsPage.tsx"],"sourcesContent":["import { Shield, Search, Plus, Pencil, Trash2 } from 'lucide-react';\nimport { useState, useMemo } from 'react';\nimport { PageLayout, Input, Button, EmptyState, DataTable, Badge, RowActions } from '../components';\nimport { useListRolesQuery, useGetResourceTypesQuery } from '../../generated/global-operations';\n\nexport function RolesPermissionsPage() {\n const [searchQuery, setSearchQuery] = useState('');\n\n const {\n data: rolesData,\n loading: rolesLoading,\n error: rolesError,\n } = useListRolesQuery({\n variables: { scopeId: 'global' },\n });\n\n const { data: resourceTypesData } = useGetResourceTypesQuery();\n\n const roles = useMemo(() => rolesData?.roles ?? [], [rolesData]);\n\n const filteredRoles = useMemo(() => {\n if (!searchQuery.trim()) return roles;\n const query = searchQuery.toLowerCase();\n return roles.filter(\n (role: any) =>\n role.name.toLowerCase().includes(query) || role.description?.toLowerCase().includes(query)\n );\n }, [roles, searchQuery]);\n\n const columns = [\n {\n key: 'name',\n header: 'Role Name',\n render: (role: any) => (\n <div className=\"flex flex-col text-left\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-semibold text-text-primary\">{role.name}</span>\n {role.isSystem && (\n <Badge variant=\"secondary\" className=\"h-4 px-1 text-[10px]\">\n System\n </Badge>\n )}\n </div>\n {role.description && (\n <span className=\"mt-0.5 line-clamp-1 text-xs text-text-secondary\">\n {role.description}\n </span>\n )}\n </div>\n ),\n },\n {\n key: 'priority',\n header: 'Priority',\n align: 'center' as const,\n width: '80px',\n render: (role: any) => (\n <span className=\"rounded bg-bg-sunken px-2 py-0.5 text-xs font-medium\">{role.priority}</span>\n ),\n },\n {\n key: 'permissions',\n header: 'Default Permissions',\n render: (role: any) => (\n <div className=\"flex max-w-md flex-wrap gap-1 text-left\">\n {role.defaultPermissions && role.defaultPermissions.length > 0 ? (\n role.defaultPermissions.map((perm: any) => (\n <Badge\n key={perm.id}\n variant=\"outline\"\n className=\"bg-bg-surface/50 px-1 py-0 font-normal text-[10px]\"\n >\n <span className=\"mr-1 text-text-secondary\">{perm.resourceType}:</span>\n <span className=\"font-medium\">{perm.level}</span>\n </Badge>\n ))\n ) : (\n <span className=\"text-xs italic text-text-secondary\">No permissions defined</span>\n )}\n </div>\n ),\n },\n {\n key: 'actions',\n header: '',\n align: 'right' as const,\n width: '50px',\n render: (role: any) => (\n <RowActions\n actions={[\n {\n key: 'edit',\n label: 'Edit Role',\n icon: <Pencil className=\"h-4 w-4\" />,\n onClick: () => console.log('Edit', role.id),\n disabled: role.isSystem,\n },\n {\n key: 'delete',\n label: 'Delete Role',\n icon: <Trash2 className=\"h-4 w-4\" />,\n onClick: () => console.log('Delete', role.id),\n destructive: true,\n disabled: role.isSystem,\n separator: true,\n },\n ]}\n />\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"Roles & Permissions\"\n description=\"Manage platform roles and global permissions\"\n actions={\n <Button size=\"sm\">\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Role\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"relative flex-1 max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search roles...\"\n className=\"pl-9\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n />\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"outline\" className=\"h-8\">\n {resourceTypesData?.resourceTypes?.length ?? 0} Resource Types\n </Badge>\n </div>\n </div>\n\n {rolesError ? (\n <EmptyState\n icon={<Shield className=\"h-8 w-8 text-status-error-text\" />}\n title=\"Error loading roles\"\n description={rolesError.message}\n />\n ) : (\n <DataTable\n columns={columns}\n data={filteredRoles}\n keyExtractor={(role) => role.id}\n loading={rolesLoading}\n emptyState={\n <EmptyState\n icon={<Shield className=\"h-8 w-8\" />}\n title=\"No roles found\"\n description={\n searchQuery\n ? `No roles match \"${searchQuery}\"`\n : 'Roles and permissions will appear here when they are created'\n }\n action={\n searchQuery ? (\n <Button variant=\"outline\" size=\"sm\" onClick={() => setSearchQuery('')}>\n Clear Search\n </Button>\n ) : undefined\n }\n />\n }\n />\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|