@burdenoff/microfe-subscriberbot 2026.1014.1 → 2026.1023.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/node_modules/clsx/dist/clsx.js.map +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +97 -18
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
- package/dist/subscriberbot/SubscriberBotRoot.js.map +1 -1
- package/dist/subscriberbot/SubscriberBotRoutes.js.map +1 -1
- package/dist/subscriberbot/admin/AdminDashboardPage.js.map +1 -1
- package/dist/subscriberbot/admin/AdminRoot.js.map +1 -1
- package/dist/subscriberbot/components/EmptyState.js.map +1 -1
- package/dist/subscriberbot/components/MetricsCard.js.map +1 -1
- package/dist/subscriberbot/components/Modal.js.map +1 -1
- package/dist/subscriberbot/components/PageLayout.js.map +1 -1
- package/dist/subscriberbot/components/StateViews.js.map +1 -1
- package/dist/subscriberbot/components/StatusBadge.js.map +1 -1
- package/dist/subscriberbot/components/viz.js.map +1 -1
- package/dist/subscriberbot/context/SubscriberBotContext.js.map +1 -1
- package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts +19 -19
- package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts.map +1 -1
- package/dist/subscriberbot/hooks/useSubscriberBotApi.js.map +1 -1
- package/dist/subscriberbot/hooks/useTr.js.map +1 -1
- package/dist/subscriberbot/pages/AssetVaultPage.js.map +1 -1
- package/dist/subscriberbot/pages/DashboardPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/DashboardPage.js +4 -4
- package/dist/subscriberbot/pages/DashboardPage.js.map +1 -1
- package/dist/subscriberbot/pages/InboxPage.js.map +1 -1
- package/dist/subscriberbot/pages/InsightsPage.js.map +1 -1
- package/dist/subscriberbot/pages/InvoicesPage.js.map +1 -1
- package/dist/subscriberbot/pages/WorkflowsPage.js.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentDetailPage.js.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentNewPage.js.map +1 -1
- package/dist/subscriberbot/pages/agents/AgentsListPage.js.map +1 -1
- package/dist/subscriberbot/pages/providers/ProviderDetailPage.js.map +1 -1
- package/dist/subscriberbot/pages/providers/ProviderNewPage.js.map +1 -1
- package/dist/subscriberbot/pages/providers/ProvidersListPage.js.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipNewPage.js.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipsListPage.js.map +1 -1
- package/dist/subscriberbot/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/subscriberbot/store/subscriberbotStore.js.map +1 -1
- package/dist/subscriberbot/utils/cn.js.map +1 -1
- package/dist/subscriberbot/utils/formatters.js.map +1 -1
- package/dist/subscriberbot/utils/navigation.js.map +1 -1
- package/dist/subscriberbot/utils/pageContext.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubscriberBotRoot.js","names":[],"sources":["../../src/subscriberbot/SubscriberBotRoot.tsx"],"sourcesContent":["/**\n * SubscriberBot Root Component\n * @module subscriberbot\n */\n\nimport type { FC } from 'react';\nimport { useCallback, useEffect } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n LayoutDashboard,\n Repeat2,\n Building2,\n Inbox,\n Receipt,\n Bot,\n Workflow,\n BarChart2,\n Vault,\n} from 'lucide-react';\nimport { SubscriberBotProvider } from './context/SubscriberBotContext';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { SubscriberBotRoutes } from './SubscriberBotRoutes';\nimport type { SubscriberBotRootProps, NavItem } from './types';\n\n// Create a query client for this microfrontend\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000,\n retry: 1,\n },\n },\n});\n\nexport const SubscriberBotRoot: FC<SubscriberBotRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/' } = props;\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n // Helper to join paths correctly\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n // Register navigation items with shell on mount\n useEffect(() => {\n if (!registerNavItems) return;\n\n const navItems: NavItem[] = [\n {\n id: 'subscriberbot-overview',\n label: t('subscriberbot.nav.overview', 'Overview'),\n path: joinPath(basePath, ''),\n icon: <LayoutDashboard size={20} />,\n order: 1,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-relationships',\n label: t('subscriberbot.nav.relationships', 'Relationships'),\n path: joinPath(basePath, 'relationships'),\n icon: <Repeat2 size={20} />,\n order: 2,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-providers',\n label: t('subscriberbot.nav.providers', 'Providers'),\n path: joinPath(basePath, 'providers'),\n icon: <Building2 size={20} />,\n order: 3,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-inbox',\n label: t('subscriberbot.nav.inbox', 'Universal Inbox'),\n path: joinPath(basePath, 'inbox'),\n icon: <Inbox size={20} />,\n order: 4,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-invoices',\n label: t('subscriberbot.nav.invoices', 'Invoices'),\n path: joinPath(basePath, 'invoices'),\n icon: <Receipt size={20} />,\n order: 5,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-agents',\n label: t('subscriberbot.nav.agents', 'AI Agents'),\n path: joinPath(basePath, 'agents'),\n icon: <Bot size={20} />,\n order: 6,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-workflows',\n label: t('subscriberbot.nav.workflows', 'Workflows'),\n path: joinPath(basePath, 'workflows'),\n icon: <Workflow size={20} />,\n order: 7,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-insights',\n label: t('subscriberbot.nav.insights', 'Insights'),\n path: joinPath(basePath, 'insights'),\n icon: <BarChart2 size={20} />,\n order: 8,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-vault',\n label: t('subscriberbot.nav.vault', 'Asset Vault'),\n path: joinPath(basePath, 'vault'),\n icon: <Vault size={20} />,\n order: 9,\n group: 'subscriberbot',\n },\n ];\n\n const cleanup = registerNavItems(navItems);\n return cleanup;\n }, [registerNavItems, basePath, t]);\n\n // Register footer items\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: 'subscriberbot-status',\n label: t('subscriberbot.footer.ready', 'SubscriberBot Ready'),\n type: 'status' as const,\n order: 10,\n section: 'left' as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems, t]);\n\n return (\n <QueryClientProvider client={queryClient}>\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <SubscriberBotProvider {...props}>\n <SubscriberBotRoutes />\n </SubscriberBotProvider>\n </FeatureFlagProvider>\n </QueryClientProvider>\n );\n};\n"],"mappings":";;;;;;;;;AA0BA,IAAM,IAAc,IAAI,EAAY,EAClC,gBAAgB,EACd,SAAS;CACP,WAAW,MAAS;CACpB,OAAO;
|
|
1
|
+
{"version":3,"file":"SubscriberBotRoot.js","names":[],"sources":["../../src/subscriberbot/SubscriberBotRoot.tsx"],"sourcesContent":["/**\n * SubscriberBot Root Component\n * @module subscriberbot\n */\n\nimport type { FC } from 'react';\nimport { useCallback, useEffect } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n LayoutDashboard,\n Repeat2,\n Building2,\n Inbox,\n Receipt,\n Bot,\n Workflow,\n BarChart2,\n Vault,\n} from 'lucide-react';\nimport { SubscriberBotProvider } from './context/SubscriberBotContext';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { SubscriberBotRoutes } from './SubscriberBotRoutes';\nimport type { SubscriberBotRootProps, NavItem } from './types';\n\n// Create a query client for this microfrontend\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000,\n retry: 1,\n },\n },\n});\n\nexport const SubscriberBotRoot: FC<SubscriberBotRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/' } = props;\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n // Helper to join paths correctly\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n // Register navigation items with shell on mount\n useEffect(() => {\n if (!registerNavItems) return;\n\n const navItems: NavItem[] = [\n {\n id: 'subscriberbot-overview',\n label: t('subscriberbot.nav.overview', 'Overview'),\n path: joinPath(basePath, ''),\n icon: <LayoutDashboard size={20} />,\n order: 1,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-relationships',\n label: t('subscriberbot.nav.relationships', 'Relationships'),\n path: joinPath(basePath, 'relationships'),\n icon: <Repeat2 size={20} />,\n order: 2,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-providers',\n label: t('subscriberbot.nav.providers', 'Providers'),\n path: joinPath(basePath, 'providers'),\n icon: <Building2 size={20} />,\n order: 3,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-inbox',\n label: t('subscriberbot.nav.inbox', 'Universal Inbox'),\n path: joinPath(basePath, 'inbox'),\n icon: <Inbox size={20} />,\n order: 4,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-invoices',\n label: t('subscriberbot.nav.invoices', 'Invoices'),\n path: joinPath(basePath, 'invoices'),\n icon: <Receipt size={20} />,\n order: 5,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-agents',\n label: t('subscriberbot.nav.agents', 'AI Agents'),\n path: joinPath(basePath, 'agents'),\n icon: <Bot size={20} />,\n order: 6,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-workflows',\n label: t('subscriberbot.nav.workflows', 'Workflows'),\n path: joinPath(basePath, 'workflows'),\n icon: <Workflow size={20} />,\n order: 7,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-insights',\n label: t('subscriberbot.nav.insights', 'Insights'),\n path: joinPath(basePath, 'insights'),\n icon: <BarChart2 size={20} />,\n order: 8,\n group: 'subscriberbot',\n },\n {\n id: 'subscriberbot-vault',\n label: t('subscriberbot.nav.vault', 'Asset Vault'),\n path: joinPath(basePath, 'vault'),\n icon: <Vault size={20} />,\n order: 9,\n group: 'subscriberbot',\n },\n ];\n\n const cleanup = registerNavItems(navItems);\n return cleanup;\n }, [registerNavItems, basePath, t]);\n\n // Register footer items\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: 'subscriberbot-status',\n label: t('subscriberbot.footer.ready', 'SubscriberBot Ready'),\n type: 'status' as const,\n order: 10,\n section: 'left' as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems, t]);\n\n return (\n <QueryClientProvider client={queryClient}>\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <SubscriberBotProvider {...props}>\n <SubscriberBotRoutes />\n </SubscriberBotProvider>\n </FeatureFlagProvider>\n </QueryClientProvider>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubscriberBotRoutes.js","names":[],"sources":["../../src/subscriberbot/SubscriberBotRoutes.tsx"],"sourcesContent":["/**\n * SubscriberBot Routes\n * @module subscriberbot\n *\n * Domain routes for the Subscriber Bot product. The dashboard is eager-loaded\n * (primary landing); all other pages are lazy-loaded.\n */\n\nimport { type FC, lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { useSubscriberBot } from './context/SubscriberBotContext';\n\n// Eager — primary landing\nimport { DashboardPage } from './pages/DashboardPage';\n\n// Relationships\nconst RelationshipsListPage = lazy(() =>\n import('./pages/relationships/RelationshipsListPage').then((m) => ({\n default: m.RelationshipsListPage,\n }))\n);\nconst RelationshipNewPage = lazy(() =>\n import('./pages/relationships/RelationshipNewPage').then((m) => ({\n default: m.RelationshipNewPage,\n }))\n);\nconst RelationshipDetailPage = lazy(() =>\n import('./pages/relationships/RelationshipDetailPage').then((m) => ({\n default: m.RelationshipDetailPage,\n }))\n);\n\n// Providers\nconst ProvidersListPage = lazy(() =>\n import('./pages/providers/ProvidersListPage').then((m) => ({ default: m.ProvidersListPage }))\n);\nconst ProviderNewPage = lazy(() =>\n import('./pages/providers/ProviderNewPage').then((m) => ({ default: m.ProviderNewPage }))\n);\nconst ProviderDetailPage = lazy(() =>\n import('./pages/providers/ProviderDetailPage').then((m) => ({ default: m.ProviderDetailPage }))\n);\n\n// Inbox / Invoices / Workflows\nconst InboxPage = lazy(() => import('./pages/InboxPage').then((m) => ({ default: m.InboxPage })));\nconst InvoicesPage = lazy(() =>\n import('./pages/InvoicesPage').then((m) => ({ default: m.InvoicesPage }))\n);\nconst WorkflowsPage = lazy(() =>\n import('./pages/WorkflowsPage').then((m) => ({ default: m.WorkflowsPage }))\n);\n\n// Agents\nconst AgentsListPage = lazy(() =>\n import('./pages/agents/AgentsListPage').then((m) => ({ default: m.AgentsListPage }))\n);\nconst AgentNewPage = lazy(() =>\n import('./pages/agents/AgentNewPage').then((m) => ({ default: m.AgentNewPage }))\n);\nconst AgentDetailPage = lazy(() =>\n import('./pages/agents/AgentDetailPage').then((m) => ({ default: m.AgentDetailPage }))\n);\n\n// Insights + Asset Vault (aggregated real data)\nconst InsightsPage = lazy(() =>\n import('./pages/InsightsPage').then((m) => ({ default: m.InsightsPage }))\n);\nconst AssetVaultPage = lazy(() =>\n import('./pages/AssetVaultPage').then((m) => ({ default: m.AssetVaultPage }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n);\n\nexport const SubscriberBotRoutes: FC = () => {\n const { basePath, isStandalone } = useSubscriberBot();\n const location = useLocation();\n\n // Normalize base path\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render if the current path matches basePath (integrated mode).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n {/* Overview / dashboard */}\n <Route index element={<DashboardPage />} />\n\n {/* Relationships (the core domain) */}\n <Route path=\"relationships\" element={<RelationshipsListPage />} />\n <Route path=\"relationships/new\" element={<RelationshipNewPage />} />\n <Route path=\"relationships/:id\" element={<RelationshipDetailPage />} />\n\n {/* Providers (registry) */}\n <Route path=\"providers\" element={<ProvidersListPage />} />\n <Route path=\"providers/new\" element={<ProviderNewPage />} />\n <Route path=\"providers/:id\" element={<ProviderDetailPage />} />\n\n {/* Universal Inbox */}\n <Route path=\"inbox\" element={<InboxPage />} />\n\n {/* Invoices */}\n <Route path=\"invoices\" element={<InvoicesPage />} />\n\n {/* Insights (spend analytics + renewal forecast) */}\n <Route path=\"insights\" element={<InsightsPage />} />\n\n {/* Digital Asset Vault */}\n <Route path=\"vault\" element={<AssetVaultPage />} />\n\n {/* AI Agents */}\n <Route path=\"agents\" element={<AgentsListPage />} />\n <Route path=\"agents/new\" element={<AgentNewPage />} />\n <Route path=\"agents/:id\" element={<AgentDetailPage />} />\n\n {/* Workflows */}\n <Route path=\"workflows\" element={<WorkflowsPage />} />\n\n {/* Fallback */}\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":";;;;;;;AAiBA,IAAM,IAAwB,QAC5B,OAAO,kDAA+C,MAAM,OAAO,EACjE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"SubscriberBotRoutes.js","names":[],"sources":["../../src/subscriberbot/SubscriberBotRoutes.tsx"],"sourcesContent":["/**\n * SubscriberBot Routes\n * @module subscriberbot\n *\n * Domain routes for the Subscriber Bot product. The dashboard is eager-loaded\n * (primary landing); all other pages are lazy-loaded.\n */\n\nimport { type FC, lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { useSubscriberBot } from './context/SubscriberBotContext';\n\n// Eager — primary landing\nimport { DashboardPage } from './pages/DashboardPage';\n\n// Relationships\nconst RelationshipsListPage = lazy(() =>\n import('./pages/relationships/RelationshipsListPage').then((m) => ({\n default: m.RelationshipsListPage,\n }))\n);\nconst RelationshipNewPage = lazy(() =>\n import('./pages/relationships/RelationshipNewPage').then((m) => ({\n default: m.RelationshipNewPage,\n }))\n);\nconst RelationshipDetailPage = lazy(() =>\n import('./pages/relationships/RelationshipDetailPage').then((m) => ({\n default: m.RelationshipDetailPage,\n }))\n);\n\n// Providers\nconst ProvidersListPage = lazy(() =>\n import('./pages/providers/ProvidersListPage').then((m) => ({ default: m.ProvidersListPage }))\n);\nconst ProviderNewPage = lazy(() =>\n import('./pages/providers/ProviderNewPage').then((m) => ({ default: m.ProviderNewPage }))\n);\nconst ProviderDetailPage = lazy(() =>\n import('./pages/providers/ProviderDetailPage').then((m) => ({ default: m.ProviderDetailPage }))\n);\n\n// Inbox / Invoices / Workflows\nconst InboxPage = lazy(() => import('./pages/InboxPage').then((m) => ({ default: m.InboxPage })));\nconst InvoicesPage = lazy(() =>\n import('./pages/InvoicesPage').then((m) => ({ default: m.InvoicesPage }))\n);\nconst WorkflowsPage = lazy(() =>\n import('./pages/WorkflowsPage').then((m) => ({ default: m.WorkflowsPage }))\n);\n\n// Agents\nconst AgentsListPage = lazy(() =>\n import('./pages/agents/AgentsListPage').then((m) => ({ default: m.AgentsListPage }))\n);\nconst AgentNewPage = lazy(() =>\n import('./pages/agents/AgentNewPage').then((m) => ({ default: m.AgentNewPage }))\n);\nconst AgentDetailPage = lazy(() =>\n import('./pages/agents/AgentDetailPage').then((m) => ({ default: m.AgentDetailPage }))\n);\n\n// Insights + Asset Vault (aggregated real data)\nconst InsightsPage = lazy(() =>\n import('./pages/InsightsPage').then((m) => ({ default: m.InsightsPage }))\n);\nconst AssetVaultPage = lazy(() =>\n import('./pages/AssetVaultPage').then((m) => ({ default: m.AssetVaultPage }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n);\n\nexport const SubscriberBotRoutes: FC = () => {\n const { basePath, isStandalone } = useSubscriberBot();\n const location = useLocation();\n\n // Normalize base path\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render if the current path matches basePath (integrated mode).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n {/* Overview / dashboard */}\n <Route index element={<DashboardPage />} />\n\n {/* Relationships (the core domain) */}\n <Route path=\"relationships\" element={<RelationshipsListPage />} />\n <Route path=\"relationships/new\" element={<RelationshipNewPage />} />\n <Route path=\"relationships/:id\" element={<RelationshipDetailPage />} />\n\n {/* Providers (registry) */}\n <Route path=\"providers\" element={<ProvidersListPage />} />\n <Route path=\"providers/new\" element={<ProviderNewPage />} />\n <Route path=\"providers/:id\" element={<ProviderDetailPage />} />\n\n {/* Universal Inbox */}\n <Route path=\"inbox\" element={<InboxPage />} />\n\n {/* Invoices */}\n <Route path=\"invoices\" element={<InvoicesPage />} />\n\n {/* Insights (spend analytics + renewal forecast) */}\n <Route path=\"insights\" element={<InsightsPage />} />\n\n {/* Digital Asset Vault */}\n <Route path=\"vault\" element={<AssetVaultPage />} />\n\n {/* AI Agents */}\n <Route path=\"agents\" element={<AgentsListPage />} />\n <Route path=\"agents/new\" element={<AgentNewPage />} />\n <Route path=\"agents/:id\" element={<AgentDetailPage />} />\n\n {/* Workflows */}\n <Route path=\"workflows\" element={<WorkflowsPage />} />\n\n {/* Fallback */}\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminDashboardPage.js","names":[],"sources":["../../../src/subscriberbot/admin/AdminDashboardPage.tsx"],"sourcesContent":["/**\n * SubscriberBot Admin Dashboard\n * @module subscriberbot/admin\n *\n * Product-specific admin surface mounted inside `admin-app`. It exposes the\n * three governance operations requested for SubscriberBot, all wired to the\n * real `wspace-subscriberbot-svc` admin governance schema (BOFF-3237):\n * 1. Provider registry curation — `subListProviders` / `subUpdateProvider`.\n * 2. Adapter approval queue — `subListAdapterSubmissions` /\n * `subApproveAdapter` / `subRejectAdapter`.\n * 3. Marketplace moderation queue — `subListMarketplaceReports` /\n * `subModerateListing`.\n */\n\nimport { Fragment, useState, type FC } from 'react';\nimport {\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n EmptyState,\n Spinner,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { CheckCircle2, Plug, Settings2, Store, XCircle } from 'lucide-react';\n\nimport { PageLayout } from '../components/PageLayout';\nimport { StatusBadge } from '../components/StatusBadge';\nimport {\n useAdapterSubmissions,\n useApproveAdapter,\n useMarketplaceReports,\n useModerateListing,\n useProviders,\n useRejectAdapter,\n useUpdateProvider,\n} from '../hooks';\nimport { formatDate } from '../utils/formatters';\nimport { useTr } from '../hooks/useTr';\nimport type { SubModerationAction, SubProvider } from '../types';\n\nexport const AdminDashboardPage: FC = () => {\n const tr = useTr();\n const [activeTab, setActiveTab] = useState('providers');\n\n // --- Provider registry ----------------------------------------------\n const { data: providersData, isLoading: providersLoading } = useProviders({\n pagination: { limit: 50 },\n });\n const updateProvider = useUpdateProvider();\n\n const handleVerify = (id: string) => {\n updateProvider.mutate({ id, input: { status: 'VERIFIED' } });\n };\n\n const handleDeprecate = (id: string) => {\n updateProvider.mutate({ id, input: { status: 'DEPRECATED' } });\n };\n\n const providers = providersData?.items ?? [];\n\n // --- Adapter approvals -------------------------------------------------\n const { data: adapterSubmissionsData, isLoading: adaptersLoading } = useAdapterSubmissions({\n pagination: { limit: 50 },\n });\n const approveAdapter = useApproveAdapter();\n const rejectAdapter = useRejectAdapter();\n const [rejectingAdapterId, setRejectingAdapterId] = useState<string | null>(null);\n const [adapterRejectReason, setAdapterRejectReason] = useState('');\n\n const adapterSubmissions = adapterSubmissionsData?.items ?? [];\n\n const handleApproveAdapter = (id: string) => {\n approveAdapter.mutate({ id });\n };\n\n const handleStartRejectAdapter = (id: string) => {\n setRejectingAdapterId(id);\n setAdapterRejectReason('');\n };\n\n const handleConfirmRejectAdapter = (id: string) => {\n if (!adapterRejectReason.trim()) return;\n rejectAdapter.mutate(\n { id, reason: adapterRejectReason.trim() },\n {\n onSuccess: () => {\n setRejectingAdapterId(null);\n setAdapterRejectReason('');\n },\n }\n );\n };\n\n // --- Marketplace moderation ---------------------------------------------\n const { data: marketplaceReportsData, isLoading: reportsLoading } = useMarketplaceReports({\n pagination: { limit: 50 },\n });\n const moderateListing = useModerateListing();\n const [moderatingReportId, setModeratingReportId] = useState<string | null>(null);\n const [moderateAction, setModerateAction] = useState<SubModerationAction | null>(null);\n const [moderateReason, setModerateReason] = useState('');\n\n const marketplaceReports = marketplaceReportsData?.items ?? [];\n\n const handleStartModerate = (id: string, action: SubModerationAction) => {\n setModeratingReportId(id);\n setModerateAction(action);\n setModerateReason('');\n };\n\n const handleConfirmModerate = (id: string) => {\n if (!moderateAction || !moderateReason.trim()) return;\n moderateListing.mutate(\n { id, action: moderateAction, reason: moderateReason.trim() },\n {\n onSuccess: () => {\n setModeratingReportId(null);\n setModerateAction(null);\n setModerateReason('');\n },\n }\n );\n };\n\n return (\n <PageLayout\n title={tr('admin.title', 'SubscriberBot Admin')}\n description={tr(\n 'admin.description',\n 'Provider registry curation, adapter approvals, and marketplace moderation.'\n )}\n >\n <Tabs value={activeTab} onValueChange={setActiveTab} className=\"w-full\">\n <TabsList className=\"mb-4 grid w-full grid-cols-3 sm:w-auto sm:inline-flex\">\n <TabsTrigger value=\"providers\" className=\"gap-2\">\n <Settings2 size={16} />\n {tr('admin.tabs.providers', 'Provider Registry')}\n </TabsTrigger>\n <TabsTrigger value=\"adapters\" className=\"gap-2\">\n <Plug size={16} />\n {tr('admin.tabs.adapters', 'Adapter Approvals')}\n </TabsTrigger>\n <TabsTrigger value=\"marketplace\" className=\"gap-2\">\n <Store size={16} />\n {tr('admin.tabs.marketplace', 'Marketplace Moderation')}\n </TabsTrigger>\n </TabsList>\n\n {/* Provider registry curation — backed by real GraphQL queries. */}\n <TabsContent value=\"providers\">\n <Card>\n <CardHeader>\n <CardTitle>{tr('admin.providers.title', 'Provider registry')}</CardTitle>\n <CardDescription>\n {tr(\n 'admin.providers.description',\n 'Curate the global provider catalog. Verify submitted providers or deprecate stale entries.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {providersLoading ? (\n <div className=\"flex min-h-[200px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : providers.length === 0 ? (\n <EmptyState\n icon={<Store className=\"size-6 text-action-primary-bg\" />}\n title={tr('admin.providers.emptyTitle', 'No providers yet')}\n description={tr(\n 'admin.providers.emptyDescription',\n 'Once providers are created or claimed they will appear here for curation.'\n )}\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('admin.providers.table.name', 'Name')}</TableHead>\n <TableHead>{tr('admin.providers.table.category', 'Category')}</TableHead>\n <TableHead>{tr('admin.providers.table.status', 'Status')}</TableHead>\n <TableHead>{tr('admin.providers.table.created', 'Created')}</TableHead>\n <TableHead className=\"text-right\">\n {tr('admin.providers.table.actions', 'Actions')}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {providers.map((provider: SubProvider) => (\n <TableRow key={provider.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {provider.name}\n </TableCell>\n <TableCell className=\"text-text-secondary\">{provider.category}</TableCell>\n <TableCell>\n <StatusBadge status={provider.status} />\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(provider.createdAt)}\n </TableCell>\n <TableCell className=\"text-right\">\n <div className=\"flex justify-end gap-2\">\n {provider.status !== 'VERIFIED' && (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleVerify(provider.id)}\n disabled={updateProvider.isPending}\n >\n <CheckCircle2 size={14} className=\"mr-1\" />\n {tr('admin.providers.verify', 'Verify')}\n </Button>\n )}\n {provider.status !== 'DEPRECATED' && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => handleDeprecate(provider.id)}\n disabled={updateProvider.isPending}\n >\n {tr('admin.providers.deprecate', 'Deprecate')}\n </Button>\n )}\n </div>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n\n {/* Adapter approval queue — backed by real GraphQL queries. */}\n <TabsContent value=\"adapters\">\n <Card>\n <CardHeader>\n <CardTitle>{tr('admin.adapters.title', 'Adapter approvals')}</CardTitle>\n <CardDescription>\n {tr(\n 'admin.adapters.description',\n 'Review submitted integration adapters before they are published to the SubscriberBot adapter marketplace.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {adaptersLoading ? (\n <div className=\"flex min-h-[200px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : adapterSubmissions.length === 0 ? (\n <EmptyState\n icon={<Plug className=\"size-6 text-action-primary-bg\" />}\n title={tr('admin.adapters.emptyTitle', 'No adapter submissions')}\n description={tr(\n 'admin.adapters.emptyDescription',\n 'Submitted integration adapters will appear here for review.'\n )}\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('admin.adapters.table.adapter', 'Adapter')}</TableHead>\n <TableHead>\n {tr('admin.adapters.table.workspace', 'Submitting workspace')}\n </TableHead>\n <TableHead>{tr('admin.adapters.table.submitted', 'Submitted')}</TableHead>\n <TableHead>{tr('admin.adapters.table.status', 'Status')}</TableHead>\n <TableHead className=\"text-right\">\n {tr('admin.adapters.table.actions', 'Actions')}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {adapterSubmissions.map((submission) => (\n <Fragment key={submission.id}>\n <TableRow>\n <TableCell className=\"font-medium text-text-primary\">\n {submission.adapterId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {submission.submitterWorkspaceId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(submission.createdAt)}\n </TableCell>\n <TableCell>\n <StatusBadge status={submission.status} />\n </TableCell>\n <TableCell className=\"text-right\">\n {submission.status === 'PENDING' && (\n <div className=\"flex justify-end gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleApproveAdapter(submission.id)}\n disabled={approveAdapter.isPending || rejectAdapter.isPending}\n >\n <CheckCircle2 size={14} className=\"mr-1\" />\n {tr('admin.adapters.approve', 'Approve')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => handleStartRejectAdapter(submission.id)}\n disabled={approveAdapter.isPending || rejectAdapter.isPending}\n >\n <XCircle size={14} className=\"mr-1\" />\n {tr('admin.adapters.reject', 'Reject')}\n </Button>\n </div>\n )}\n </TableCell>\n </TableRow>\n {rejectingAdapterId === submission.id && (\n <TableRow key={`${submission.id}-reject`}>\n <TableCell colSpan={5} className=\"bg-bg-sunken\">\n <div className=\"flex flex-col gap-2 py-1 sm:flex-row sm:items-start\">\n <Textarea\n value={adapterRejectReason}\n onChange={(event) => setAdapterRejectReason(event.target.value)}\n placeholder={tr(\n 'admin.adapters.rejectPlaceholder',\n 'Reason for rejecting this adapter submission (required)'\n )}\n className=\"min-h-[60px] flex-1\"\n autoFocus\n />\n <div className=\"flex gap-2 sm:flex-col\">\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() => handleConfirmRejectAdapter(submission.id)}\n disabled={\n !adapterRejectReason.trim() || rejectAdapter.isPending\n }\n >\n {tr('admin.adapters.confirmReject', 'Confirm reject')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setRejectingAdapterId(null)}\n disabled={rejectAdapter.isPending}\n >\n {tr('admin.adapters.cancel', 'Cancel')}\n </Button>\n </div>\n </div>\n </TableCell>\n </TableRow>\n )}\n </Fragment>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n\n {/* Marketplace moderation — backed by real GraphQL queries. */}\n <TabsContent value=\"marketplace\">\n <Card>\n <CardHeader>\n <CardTitle>{tr('admin.marketplace.title', 'Marketplace moderation')}</CardTitle>\n <CardDescription>\n {tr(\n 'admin.marketplace.description',\n 'Review reported marketplace listings and take moderation actions.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {reportsLoading ? (\n <div className=\"flex min-h-[200px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : marketplaceReports.length === 0 ? (\n <EmptyState\n icon={<Store className=\"size-6 text-action-primary-bg\" />}\n title={tr('admin.marketplace.emptyTitle', 'No reported listings')}\n description={tr(\n 'admin.marketplace.emptyDescription',\n 'Reported marketplace listings will appear here for moderation.'\n )}\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('admin.marketplace.table.listing', 'Listing')}</TableHead>\n <TableHead>\n {tr('admin.marketplace.table.workspace', 'Reporting workspace')}\n </TableHead>\n <TableHead>{tr('admin.marketplace.table.reported', 'Reported')}</TableHead>\n <TableHead>{tr('admin.marketplace.table.reason', 'Reason')}</TableHead>\n <TableHead>{tr('admin.marketplace.table.status', 'Status')}</TableHead>\n <TableHead className=\"text-right\">\n {tr('admin.marketplace.table.actions', 'Actions')}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {marketplaceReports.map((report) => (\n <Fragment key={report.id}>\n <TableRow>\n <TableCell className=\"font-medium text-text-primary\">\n {report.listingId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {report.reporterWorkspaceId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(report.createdAt)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">{report.reason}</TableCell>\n <TableCell>\n <StatusBadge status={report.status} />\n </TableCell>\n <TableCell className=\"text-right\">\n {report.status === 'PENDING' && (\n <div className=\"flex justify-end gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleStartModerate(report.id, 'DISMISS')}\n disabled={moderateListing.isPending}\n >\n {tr('admin.marketplace.dismiss', 'Dismiss')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() => handleStartModerate(report.id, 'REMOVE_LISTING')}\n disabled={moderateListing.isPending}\n >\n {tr('admin.marketplace.remove', 'Remove')}\n </Button>\n </div>\n )}\n </TableCell>\n </TableRow>\n {moderatingReportId === report.id && (\n <TableRow key={`${report.id}-moderate`}>\n <TableCell colSpan={6} className=\"bg-bg-sunken\">\n <div className=\"flex flex-col gap-2 py-1 sm:flex-row sm:items-start\">\n <Textarea\n value={moderateReason}\n onChange={(event) => setModerateReason(event.target.value)}\n placeholder={\n moderateAction === 'REMOVE_LISTING'\n ? tr(\n 'admin.marketplace.reasonPlaceholderRemove',\n 'Reason to remove this listing (required)'\n )\n : tr(\n 'admin.marketplace.reasonPlaceholderDismiss',\n 'Reason to dismiss this report (required)'\n )\n }\n className=\"min-h-[60px] flex-1\"\n autoFocus\n />\n <div className=\"flex gap-2 sm:flex-col\">\n <Button\n size=\"sm\"\n variant={\n moderateAction === 'REMOVE_LISTING'\n ? 'destructive'\n : 'outline'\n }\n onClick={() => handleConfirmModerate(report.id)}\n disabled={!moderateReason.trim() || moderateListing.isPending}\n >\n {moderateAction === 'REMOVE_LISTING'\n ? tr('admin.marketplace.confirmRemoval', 'Confirm removal')\n : tr(\n 'admin.marketplace.confirmDismissal',\n 'Confirm dismissal'\n )}\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setModeratingReportId(null)}\n disabled={moderateListing.isPending}\n >\n {tr('admin.marketplace.cancel', 'Cancel')}\n </Button>\n </div>\n </div>\n </TableCell>\n </TableRow>\n )}\n </Fragment>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n </Tabs>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AdminDashboardPage.js","names":[],"sources":["../../../src/subscriberbot/admin/AdminDashboardPage.tsx"],"sourcesContent":["/**\n * SubscriberBot Admin Dashboard\n * @module subscriberbot/admin\n *\n * Product-specific admin surface mounted inside `admin-app`. It exposes the\n * three governance operations requested for SubscriberBot, all wired to the\n * real `wspace-subscriberbot-svc` admin governance schema (BOFF-3237):\n * 1. Provider registry curation — `subListProviders` / `subUpdateProvider`.\n * 2. Adapter approval queue — `subListAdapterSubmissions` /\n * `subApproveAdapter` / `subRejectAdapter`.\n * 3. Marketplace moderation queue — `subListMarketplaceReports` /\n * `subModerateListing`.\n */\n\nimport { Fragment, useState, type FC } from 'react';\nimport {\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n EmptyState,\n Spinner,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { CheckCircle2, Plug, Settings2, Store, XCircle } from 'lucide-react';\n\nimport { PageLayout } from '../components/PageLayout';\nimport { StatusBadge } from '../components/StatusBadge';\nimport {\n useAdapterSubmissions,\n useApproveAdapter,\n useMarketplaceReports,\n useModerateListing,\n useProviders,\n useRejectAdapter,\n useUpdateProvider,\n} from '../hooks';\nimport { formatDate } from '../utils/formatters';\nimport { useTr } from '../hooks/useTr';\nimport type { SubModerationAction, SubProvider } from '../types';\n\nexport const AdminDashboardPage: FC = () => {\n const tr = useTr();\n const [activeTab, setActiveTab] = useState('providers');\n\n // --- Provider registry ----------------------------------------------\n const { data: providersData, isLoading: providersLoading } = useProviders({\n pagination: { limit: 50 },\n });\n const updateProvider = useUpdateProvider();\n\n const handleVerify = (id: string) => {\n updateProvider.mutate({ id, input: { status: 'VERIFIED' } });\n };\n\n const handleDeprecate = (id: string) => {\n updateProvider.mutate({ id, input: { status: 'DEPRECATED' } });\n };\n\n const providers = providersData?.items ?? [];\n\n // --- Adapter approvals -------------------------------------------------\n const { data: adapterSubmissionsData, isLoading: adaptersLoading } = useAdapterSubmissions({\n pagination: { limit: 50 },\n });\n const approveAdapter = useApproveAdapter();\n const rejectAdapter = useRejectAdapter();\n const [rejectingAdapterId, setRejectingAdapterId] = useState<string | null>(null);\n const [adapterRejectReason, setAdapterRejectReason] = useState('');\n\n const adapterSubmissions = adapterSubmissionsData?.items ?? [];\n\n const handleApproveAdapter = (id: string) => {\n approveAdapter.mutate({ id });\n };\n\n const handleStartRejectAdapter = (id: string) => {\n setRejectingAdapterId(id);\n setAdapterRejectReason('');\n };\n\n const handleConfirmRejectAdapter = (id: string) => {\n if (!adapterRejectReason.trim()) return;\n rejectAdapter.mutate(\n { id, reason: adapterRejectReason.trim() },\n {\n onSuccess: () => {\n setRejectingAdapterId(null);\n setAdapterRejectReason('');\n },\n }\n );\n };\n\n // --- Marketplace moderation ---------------------------------------------\n const { data: marketplaceReportsData, isLoading: reportsLoading } = useMarketplaceReports({\n pagination: { limit: 50 },\n });\n const moderateListing = useModerateListing();\n const [moderatingReportId, setModeratingReportId] = useState<string | null>(null);\n const [moderateAction, setModerateAction] = useState<SubModerationAction | null>(null);\n const [moderateReason, setModerateReason] = useState('');\n\n const marketplaceReports = marketplaceReportsData?.items ?? [];\n\n const handleStartModerate = (id: string, action: SubModerationAction) => {\n setModeratingReportId(id);\n setModerateAction(action);\n setModerateReason('');\n };\n\n const handleConfirmModerate = (id: string) => {\n if (!moderateAction || !moderateReason.trim()) return;\n moderateListing.mutate(\n { id, action: moderateAction, reason: moderateReason.trim() },\n {\n onSuccess: () => {\n setModeratingReportId(null);\n setModerateAction(null);\n setModerateReason('');\n },\n }\n );\n };\n\n return (\n <PageLayout\n title={tr('admin.title', 'SubscriberBot Admin')}\n description={tr(\n 'admin.description',\n 'Provider registry curation, adapter approvals, and marketplace moderation.'\n )}\n >\n <Tabs value={activeTab} onValueChange={setActiveTab} className=\"w-full\">\n <TabsList className=\"mb-4 grid w-full grid-cols-3 sm:w-auto sm:inline-flex\">\n <TabsTrigger value=\"providers\" className=\"gap-2\">\n <Settings2 size={16} />\n {tr('admin.tabs.providers', 'Provider Registry')}\n </TabsTrigger>\n <TabsTrigger value=\"adapters\" className=\"gap-2\">\n <Plug size={16} />\n {tr('admin.tabs.adapters', 'Adapter Approvals')}\n </TabsTrigger>\n <TabsTrigger value=\"marketplace\" className=\"gap-2\">\n <Store size={16} />\n {tr('admin.tabs.marketplace', 'Marketplace Moderation')}\n </TabsTrigger>\n </TabsList>\n\n {/* Provider registry curation — backed by real GraphQL queries. */}\n <TabsContent value=\"providers\">\n <Card>\n <CardHeader>\n <CardTitle>{tr('admin.providers.title', 'Provider registry')}</CardTitle>\n <CardDescription>\n {tr(\n 'admin.providers.description',\n 'Curate the global provider catalog. Verify submitted providers or deprecate stale entries.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {providersLoading ? (\n <div className=\"flex min-h-[200px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : providers.length === 0 ? (\n <EmptyState\n icon={<Store className=\"size-6 text-action-primary-bg\" />}\n title={tr('admin.providers.emptyTitle', 'No providers yet')}\n description={tr(\n 'admin.providers.emptyDescription',\n 'Once providers are created or claimed they will appear here for curation.'\n )}\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('admin.providers.table.name', 'Name')}</TableHead>\n <TableHead>{tr('admin.providers.table.category', 'Category')}</TableHead>\n <TableHead>{tr('admin.providers.table.status', 'Status')}</TableHead>\n <TableHead>{tr('admin.providers.table.created', 'Created')}</TableHead>\n <TableHead className=\"text-right\">\n {tr('admin.providers.table.actions', 'Actions')}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {providers.map((provider: SubProvider) => (\n <TableRow key={provider.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {provider.name}\n </TableCell>\n <TableCell className=\"text-text-secondary\">{provider.category}</TableCell>\n <TableCell>\n <StatusBadge status={provider.status} />\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(provider.createdAt)}\n </TableCell>\n <TableCell className=\"text-right\">\n <div className=\"flex justify-end gap-2\">\n {provider.status !== 'VERIFIED' && (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleVerify(provider.id)}\n disabled={updateProvider.isPending}\n >\n <CheckCircle2 size={14} className=\"mr-1\" />\n {tr('admin.providers.verify', 'Verify')}\n </Button>\n )}\n {provider.status !== 'DEPRECATED' && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => handleDeprecate(provider.id)}\n disabled={updateProvider.isPending}\n >\n {tr('admin.providers.deprecate', 'Deprecate')}\n </Button>\n )}\n </div>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n\n {/* Adapter approval queue — backed by real GraphQL queries. */}\n <TabsContent value=\"adapters\">\n <Card>\n <CardHeader>\n <CardTitle>{tr('admin.adapters.title', 'Adapter approvals')}</CardTitle>\n <CardDescription>\n {tr(\n 'admin.adapters.description',\n 'Review submitted integration adapters before they are published to the SubscriberBot adapter marketplace.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {adaptersLoading ? (\n <div className=\"flex min-h-[200px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : adapterSubmissions.length === 0 ? (\n <EmptyState\n icon={<Plug className=\"size-6 text-action-primary-bg\" />}\n title={tr('admin.adapters.emptyTitle', 'No adapter submissions')}\n description={tr(\n 'admin.adapters.emptyDescription',\n 'Submitted integration adapters will appear here for review.'\n )}\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('admin.adapters.table.adapter', 'Adapter')}</TableHead>\n <TableHead>\n {tr('admin.adapters.table.workspace', 'Submitting workspace')}\n </TableHead>\n <TableHead>{tr('admin.adapters.table.submitted', 'Submitted')}</TableHead>\n <TableHead>{tr('admin.adapters.table.status', 'Status')}</TableHead>\n <TableHead className=\"text-right\">\n {tr('admin.adapters.table.actions', 'Actions')}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {adapterSubmissions.map((submission) => (\n <Fragment key={submission.id}>\n <TableRow>\n <TableCell className=\"font-medium text-text-primary\">\n {submission.adapterId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {submission.submitterWorkspaceId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(submission.createdAt)}\n </TableCell>\n <TableCell>\n <StatusBadge status={submission.status} />\n </TableCell>\n <TableCell className=\"text-right\">\n {submission.status === 'PENDING' && (\n <div className=\"flex justify-end gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleApproveAdapter(submission.id)}\n disabled={approveAdapter.isPending || rejectAdapter.isPending}\n >\n <CheckCircle2 size={14} className=\"mr-1\" />\n {tr('admin.adapters.approve', 'Approve')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => handleStartRejectAdapter(submission.id)}\n disabled={approveAdapter.isPending || rejectAdapter.isPending}\n >\n <XCircle size={14} className=\"mr-1\" />\n {tr('admin.adapters.reject', 'Reject')}\n </Button>\n </div>\n )}\n </TableCell>\n </TableRow>\n {rejectingAdapterId === submission.id && (\n <TableRow key={`${submission.id}-reject`}>\n <TableCell colSpan={5} className=\"bg-bg-sunken\">\n <div className=\"flex flex-col gap-2 py-1 sm:flex-row sm:items-start\">\n <Textarea\n value={adapterRejectReason}\n onChange={(event) => setAdapterRejectReason(event.target.value)}\n placeholder={tr(\n 'admin.adapters.rejectPlaceholder',\n 'Reason for rejecting this adapter submission (required)'\n )}\n className=\"min-h-[60px] flex-1\"\n autoFocus\n />\n <div className=\"flex gap-2 sm:flex-col\">\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() => handleConfirmRejectAdapter(submission.id)}\n disabled={\n !adapterRejectReason.trim() || rejectAdapter.isPending\n }\n >\n {tr('admin.adapters.confirmReject', 'Confirm reject')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setRejectingAdapterId(null)}\n disabled={rejectAdapter.isPending}\n >\n {tr('admin.adapters.cancel', 'Cancel')}\n </Button>\n </div>\n </div>\n </TableCell>\n </TableRow>\n )}\n </Fragment>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n\n {/* Marketplace moderation — backed by real GraphQL queries. */}\n <TabsContent value=\"marketplace\">\n <Card>\n <CardHeader>\n <CardTitle>{tr('admin.marketplace.title', 'Marketplace moderation')}</CardTitle>\n <CardDescription>\n {tr(\n 'admin.marketplace.description',\n 'Review reported marketplace listings and take moderation actions.'\n )}\n </CardDescription>\n </CardHeader>\n <CardContent>\n {reportsLoading ? (\n <div className=\"flex min-h-[200px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : marketplaceReports.length === 0 ? (\n <EmptyState\n icon={<Store className=\"size-6 text-action-primary-bg\" />}\n title={tr('admin.marketplace.emptyTitle', 'No reported listings')}\n description={tr(\n 'admin.marketplace.emptyDescription',\n 'Reported marketplace listings will appear here for moderation.'\n )}\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{tr('admin.marketplace.table.listing', 'Listing')}</TableHead>\n <TableHead>\n {tr('admin.marketplace.table.workspace', 'Reporting workspace')}\n </TableHead>\n <TableHead>{tr('admin.marketplace.table.reported', 'Reported')}</TableHead>\n <TableHead>{tr('admin.marketplace.table.reason', 'Reason')}</TableHead>\n <TableHead>{tr('admin.marketplace.table.status', 'Status')}</TableHead>\n <TableHead className=\"text-right\">\n {tr('admin.marketplace.table.actions', 'Actions')}\n </TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {marketplaceReports.map((report) => (\n <Fragment key={report.id}>\n <TableRow>\n <TableCell className=\"font-medium text-text-primary\">\n {report.listingId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {report.reporterWorkspaceId}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(report.createdAt)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">{report.reason}</TableCell>\n <TableCell>\n <StatusBadge status={report.status} />\n </TableCell>\n <TableCell className=\"text-right\">\n {report.status === 'PENDING' && (\n <div className=\"flex justify-end gap-2\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleStartModerate(report.id, 'DISMISS')}\n disabled={moderateListing.isPending}\n >\n {tr('admin.marketplace.dismiss', 'Dismiss')}\n </Button>\n <Button\n size=\"sm\"\n variant=\"destructive\"\n onClick={() => handleStartModerate(report.id, 'REMOVE_LISTING')}\n disabled={moderateListing.isPending}\n >\n {tr('admin.marketplace.remove', 'Remove')}\n </Button>\n </div>\n )}\n </TableCell>\n </TableRow>\n {moderatingReportId === report.id && (\n <TableRow key={`${report.id}-moderate`}>\n <TableCell colSpan={6} className=\"bg-bg-sunken\">\n <div className=\"flex flex-col gap-2 py-1 sm:flex-row sm:items-start\">\n <Textarea\n value={moderateReason}\n onChange={(event) => setModerateReason(event.target.value)}\n placeholder={\n moderateAction === 'REMOVE_LISTING'\n ? tr(\n 'admin.marketplace.reasonPlaceholderRemove',\n 'Reason to remove this listing (required)'\n )\n : tr(\n 'admin.marketplace.reasonPlaceholderDismiss',\n 'Reason to dismiss this report (required)'\n )\n }\n className=\"min-h-[60px] flex-1\"\n autoFocus\n />\n <div className=\"flex gap-2 sm:flex-col\">\n <Button\n size=\"sm\"\n variant={\n moderateAction === 'REMOVE_LISTING'\n ? 'destructive'\n : 'outline'\n }\n onClick={() => handleConfirmModerate(report.id)}\n disabled={!moderateReason.trim() || moderateListing.isPending}\n >\n {moderateAction === 'REMOVE_LISTING'\n ? tr('admin.marketplace.confirmRemoval', 'Confirm removal')\n : tr(\n 'admin.marketplace.confirmDismissal',\n 'Confirm dismissal'\n )}\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => setModeratingReportId(null)}\n disabled={moderateListing.isPending}\n >\n {tr('admin.marketplace.cancel', 'Cancel')}\n </Button>\n </div>\n </div>\n </TableCell>\n </TableRow>\n )}\n </Fragment>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </CardContent>\n </Card>\n </TabsContent>\n </Tabs>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminRoot.js","names":[],"sources":["../../../src/subscriberbot/admin/AdminRoot.tsx"],"sourcesContent":["/**\n * SubscriberBot Admin Root\n * @module subscriberbot/admin\n *\n * Host-contract entry point for the SubscriberBot admin surface inside\n * `admin-app`. Mirrors the provider/query-client setup of `SubscriberBotRoot`\n * but does not register product-shell navigation items.\n */\n\nimport type { FC } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { SubscriberBotProvider } from '../context';\nimport type { SubscriberBotRootProps } from '../types';\nimport { AdminDashboardPage } from './AdminDashboardPage';\n\nconst adminQueryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 60 * 1000,\n retry: 1,\n },\n },\n});\n\nexport interface SubscriberBotAdminRootProps extends SubscriberBotRootProps {\n /** Workspace-scoped token supplied by `admin-app` for workspace gateway calls. */\n workspaceToken?: string;\n}\n\nexport const AdminRoot: FC<SubscriberBotAdminRootProps> = (props) => {\n // `workspaceToken` is consumed by the host shell's `GatewayRoute` / graphql\n // transport context; it is not forwarded to the product context, which only\n // expects the standard microfrontend root props.\n const { workspaceToken, ...providerProps } = props;\n void workspaceToken;\n\n return (\n <QueryClientProvider client={adminQueryClient}>\n <SubscriberBotProvider {...providerProps}>\n <AdminDashboardPage />\n </SubscriberBotProvider>\n </QueryClientProvider>\n );\n};\n"],"mappings":";;;;;;AAeA,IAAM,IAAmB,IAAI,EAAY,EACvC,gBAAgB,EACd,SAAS;CACP,WAAW,KAAK;CAChB,OAAO;
|
|
1
|
+
{"version":3,"file":"AdminRoot.js","names":[],"sources":["../../../src/subscriberbot/admin/AdminRoot.tsx"],"sourcesContent":["/**\n * SubscriberBot Admin Root\n * @module subscriberbot/admin\n *\n * Host-contract entry point for the SubscriberBot admin surface inside\n * `admin-app`. Mirrors the provider/query-client setup of `SubscriberBotRoot`\n * but does not register product-shell navigation items.\n */\n\nimport type { FC } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { SubscriberBotProvider } from '../context';\nimport type { SubscriberBotRootProps } from '../types';\nimport { AdminDashboardPage } from './AdminDashboardPage';\n\nconst adminQueryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 60 * 1000,\n retry: 1,\n },\n },\n});\n\nexport interface SubscriberBotAdminRootProps extends SubscriberBotRootProps {\n /** Workspace-scoped token supplied by `admin-app` for workspace gateway calls. */\n workspaceToken?: string;\n}\n\nexport const AdminRoot: FC<SubscriberBotAdminRootProps> = (props) => {\n // `workspaceToken` is consumed by the host shell's `GatewayRoute` / graphql\n // transport context; it is not forwarded to the product context, which only\n // expects the standard microfrontend root props.\n const { workspaceToken, ...providerProps } = props;\n void workspaceToken;\n\n return (\n <QueryClientProvider client={adminQueryClient}>\n <SubscriberBotProvider {...providerProps}>\n <AdminDashboardPage />\n </SubscriberBotProvider>\n </QueryClientProvider>\n );\n};\n"],"mappings":";;;;;;AAeA,IAAM,IAAmB,IAAI,EAAY,EACvC,gBAAgB,EACd,SAAS;CACP,WAAW,KAAK;CAChB,OAAO;AACT,EACF,EACF,CAAC,GAOY,KAA8C,MAAU;CAInE,IAAM,EAAE,mBAAgB,GAAG,MAAkB;CAG7C,OACE,kBAAC,GAAD;EAAqB,QAAQ;YAC3B,kBAAC,GAAD;GAAuB,GAAI;aACzB,kBAAC,GAAD,CAAqB,CAAA;EACA,CAAA;CACJ,CAAA;AAEzB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmptyState.js","names":[],"sources":["../../../src/subscriberbot/components/EmptyState.tsx"],"sourcesContent":["/**\n * Empty State Component\n * @module subscriberbot/components\n *\n * Boff UI 2.0 zero-state. When an `illustration` name is provided it renders the\n * fe-libs {@link IllustratedEmptyState} (on-brand glass illustration); otherwise\n * it falls back to the icon-based layout. Host shell serves illustrations from\n * `/illustrations/<name>.webp`.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ninterface EmptyStateProps {\n icon?: ReactNode;\n /**\n * On-brand illustration name → `/illustrations/<name>.webp`. Standard names:\n * empty-data, empty-search, empty-files, empty-people, empty-notifications,\n * onboarding, empty-generic.\n */\n illustration?: string;\n title: string;\n description?: string;\n action?: ReactNode;\n className?: string;\n}\n\nexport const EmptyState: FC<EmptyStateProps> = ({\n icon,\n illustration,\n title,\n description,\n action,\n className,\n}) => {\n if (illustration) {\n return (\n <IllustratedEmptyState\n illustration={illustration}\n title={title}\n description={description}\n action={action}\n className={className}\n />\n );\n }\n\n return (\n <div\n className={cn('flex flex-col items-center justify-center py-12 px-4 text-center', className)}\n >\n {icon && <div className=\"mb-4 text-text-secondary opacity-50\">{icon}</div>}\n <h3 className=\"text-lg font-medium text-text-primary\">{title}</h3>\n {description && <p className=\"mt-2 text-sm text-text-secondary max-w-sm\">{description}</p>}\n {action && <div className=\"mt-6\">{action}</div>}\n </div>\n );\n};\n"],"mappings":";;;;AA4BA,IAAa,KAAmC,EAC9C,SACA,iBACA,UACA,gBACA,WACA,mBAEI,IAEA,kBAAC,GAAD;CACgB;CACP;CACM;CACL;CACG;
|
|
1
|
+
{"version":3,"file":"EmptyState.js","names":[],"sources":["../../../src/subscriberbot/components/EmptyState.tsx"],"sourcesContent":["/**\n * Empty State Component\n * @module subscriberbot/components\n *\n * Boff UI 2.0 zero-state. When an `illustration` name is provided it renders the\n * fe-libs {@link IllustratedEmptyState} (on-brand glass illustration); otherwise\n * it falls back to the icon-based layout. Host shell serves illustrations from\n * `/illustrations/<name>.webp`.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ninterface EmptyStateProps {\n icon?: ReactNode;\n /**\n * On-brand illustration name → `/illustrations/<name>.webp`. Standard names:\n * empty-data, empty-search, empty-files, empty-people, empty-notifications,\n * onboarding, empty-generic.\n */\n illustration?: string;\n title: string;\n description?: string;\n action?: ReactNode;\n className?: string;\n}\n\nexport const EmptyState: FC<EmptyStateProps> = ({\n icon,\n illustration,\n title,\n description,\n action,\n className,\n}) => {\n if (illustration) {\n return (\n <IllustratedEmptyState\n illustration={illustration}\n title={title}\n description={description}\n action={action}\n className={className}\n />\n );\n }\n\n return (\n <div\n className={cn('flex flex-col items-center justify-center py-12 px-4 text-center', className)}\n >\n {icon && <div className=\"mb-4 text-text-secondary opacity-50\">{icon}</div>}\n <h3 className=\"text-lg font-medium text-text-primary\">{title}</h3>\n {description && <p className=\"mt-2 text-sm text-text-secondary max-w-sm\">{description}</p>}\n {action && <div className=\"mt-6\">{action}</div>}\n </div>\n );\n};\n"],"mappings":";;;;AA4BA,IAAa,KAAmC,EAC9C,SACA,iBACA,UACA,gBACA,WACA,mBAEI,IAEA,kBAAC,GAAD;CACgB;CACP;CACM;CACL;CACG;AACZ,CAAA,IAKH,kBAAC,OAAD;CACE,WAAW,EAAG,oEAAoE,CAAS;WAD7F;EAGG,KAAQ,kBAAC,OAAD;GAAK,WAAU;aAAuC;EAAU,CAAA;EACzE,kBAAC,MAAD;GAAI,WAAU;aAAyC;EAAU,CAAA;EAChE,KAAe,kBAAC,KAAD;GAAG,WAAU;aAA6C;EAAe,CAAA;EACxF,KAAU,kBAAC,OAAD;GAAK,WAAU;aAAQ;EAAY,CAAA;CAC3C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MetricsCard.js","names":[],"sources":["../../../src/subscriberbot/components/MetricsCard.tsx"],"sourcesContent":["/**\n * Metrics Card Component\n * @module subscriberbot/components\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { TrendingUp, TrendingDown, Minus } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport type { TrendDirection } from '../types';\n\ninterface MetricsCardProps {\n icon: ReactNode;\n value: string | number;\n label: string;\n trend?: TrendDirection;\n trendValue?: string;\n className?: string;\n /** Render as the page's hero metric (glass treatment + accent glow). */\n featured?: boolean;\n}\n\nconst trendConfig: Record<TrendDirection, { icon: typeof TrendingUp; color: string }> = {\n up: { icon: TrendingUp, color: 'text-status-success-text' },\n down: { icon: TrendingDown, color: 'text-status-error-text' },\n neutral: { icon: Minus, color: 'text-text-secondary' },\n};\n\nexport const MetricsCard: FC<MetricsCardProps> = ({\n icon,\n value,\n label,\n trend,\n trendValue,\n className,\n featured = false,\n}) => {\n const trendInfo = trend ? trendConfig[trend] : null;\n const TrendIcon = trendInfo?.icon;\n\n return (\n <GlassCard\n treatment={featured ? 'glass' : 'metric'}\n glow={featured}\n className={cn('p-6', className)}\n >\n <div className=\"flex items-center justify-between\">\n <div className=\"text-text-secondary\">{icon}</div>\n {trend && trendInfo && TrendIcon && (\n <div className={cn('flex items-center gap-1 text-xs font-medium', trendInfo.color)}>\n <TrendIcon className=\"h-3.5 w-3.5\" />\n {trendValue && <span>{trendValue}</span>}\n </div>\n )}\n </div>\n <div className=\"mt-3\">\n <p className=\"text-2xl font-bold text-text-primary tabular-nums\">{value}</p>\n <p className=\"mt-1 text-sm text-text-secondary\">{label}</p>\n </div>\n </GlassCard>\n );\n};\n"],"mappings":";;;;;AAsBA,IAAM,IAAkF;CACtF,IAAI;EAAE,MAAM;EAAY,OAAO;
|
|
1
|
+
{"version":3,"file":"MetricsCard.js","names":[],"sources":["../../../src/subscriberbot/components/MetricsCard.tsx"],"sourcesContent":["/**\n * Metrics Card Component\n * @module subscriberbot/components\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { TrendingUp, TrendingDown, Minus } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport type { TrendDirection } from '../types';\n\ninterface MetricsCardProps {\n icon: ReactNode;\n value: string | number;\n label: string;\n trend?: TrendDirection;\n trendValue?: string;\n className?: string;\n /** Render as the page's hero metric (glass treatment + accent glow). */\n featured?: boolean;\n}\n\nconst trendConfig: Record<TrendDirection, { icon: typeof TrendingUp; color: string }> = {\n up: { icon: TrendingUp, color: 'text-status-success-text' },\n down: { icon: TrendingDown, color: 'text-status-error-text' },\n neutral: { icon: Minus, color: 'text-text-secondary' },\n};\n\nexport const MetricsCard: FC<MetricsCardProps> = ({\n icon,\n value,\n label,\n trend,\n trendValue,\n className,\n featured = false,\n}) => {\n const trendInfo = trend ? trendConfig[trend] : null;\n const TrendIcon = trendInfo?.icon;\n\n return (\n <GlassCard\n treatment={featured ? 'glass' : 'metric'}\n glow={featured}\n className={cn('p-6', className)}\n >\n <div className=\"flex items-center justify-between\">\n <div className=\"text-text-secondary\">{icon}</div>\n {trend && trendInfo && TrendIcon && (\n <div className={cn('flex items-center gap-1 text-xs font-medium', trendInfo.color)}>\n <TrendIcon className=\"h-3.5 w-3.5\" />\n {trendValue && <span>{trendValue}</span>}\n </div>\n )}\n </div>\n <div className=\"mt-3\">\n <p className=\"text-2xl font-bold text-text-primary tabular-nums\">{value}</p>\n <p className=\"mt-1 text-sm text-text-secondary\">{label}</p>\n </div>\n </GlassCard>\n );\n};\n"],"mappings":";;;;;AAsBA,IAAM,IAAkF;CACtF,IAAI;EAAE,MAAM;EAAY,OAAO;CAA2B;CAC1D,MAAM;EAAE,MAAM;EAAc,OAAO;CAAyB;CAC5D,SAAS;EAAE,MAAM;EAAO,OAAO;CAAsB;AACvD,GAEa,KAAqC,EAChD,SACA,UACA,UACA,UACA,eACA,cACA,cAAW,SACP;CACJ,IAAM,IAAY,IAAQ,EAAY,KAAS,MACzC,IAAY,GAAW;CAE7B,OACE,kBAAC,GAAD;EACE,WAAW,IAAW,UAAU;EAChC,MAAM;EACN,WAAW,EAAG,OAAO,CAAS;YAHhC,CAKE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAuB;GAAU,CAAA,GAC/C,KAAS,KAAa,KACrB,kBAAC,OAAD;IAAK,WAAW,EAAG,+CAA+C,EAAU,KAAK;cAAjF,CACE,kBAAC,GAAD,EAAW,WAAU,cAAe,CAAA,GACnC,KAAc,kBAAC,QAAD,EAAA,UAAO,EAAiB,CAAA,CACpC;KAEJ;MACL,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cAAqD;GAAS,CAAA,GAC3E,kBAAC,KAAD;IAAG,WAAU;cAAoC;GAAS,CAAA,CACvD;IACI;;AAEf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Modal.js","names":[],"sources":["../../../src/subscriberbot/components/Modal.tsx"],"sourcesContent":["/**\n * Accessible modal dialog\n * @module subscriberbot/components\n *\n * A small, dependency-light dialog used for the MFE's create/edit forms.\n * Handles the a11y contract every modal here must meet:\n * - `role=\"dialog\"` + `aria-modal=\"true\"` + `aria-labelledby` (wired to the\n * heading id),\n * - Escape-to-close,\n * - initial focus moved to the first form field,\n * - a semantic overlay scrim (`bg-bg-overlay`, never raw black),\n * - responsive layout (full-width on mobile, scrolls when the form is long).\n */\n\nimport { useEffect, useId, useRef, type FC, type ReactNode } from 'react';\nimport { X } from 'lucide-react';\n\ninterface ModalProps {\n title: string;\n description?: string;\n onClose: () => void;\n children: ReactNode;\n /** Constrain width. Defaults to a comfortable single-column form width. */\n maxWidthClassName?: string;\n}\n\nexport const Modal: FC<ModalProps> = ({\n title,\n description,\n onClose,\n children,\n maxWidthClassName = 'max-w-lg',\n}) => {\n const titleId = useId();\n const panelRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') onClose();\n };\n document.addEventListener('keydown', onKeyDown);\n\n // Move initial focus to the first interactive form field (falling back to\n // any focusable control) so keyboard users land inside the dialog.\n const panel = panelRef.current;\n const firstField = panel?.querySelector<HTMLElement>(\n 'input:not([type=\"hidden\"]), select, textarea'\n );\n (\n firstField ??\n panel?.querySelector<HTMLElement>('button, [href], [tabindex]:not([tabindex=\"-1\"])')\n )?.focus();\n\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [onClose]);\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4 backdrop-blur-sm\"\n onMouseDown={(e) => {\n if (e.target === e.currentTarget) onClose();\n }}\n >\n <div\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n className={`max-h-[90vh] w-full ${maxWidthClassName} overflow-y-auto rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-lg`}\n >\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <h3 id={titleId} className=\"text-lg font-semibold text-text-primary\">\n {title}\n </h3>\n {description && <p className=\"mt-0.5 text-xs text-text-secondary\">{description}</p>}\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close dialog\"\n className=\"shrink-0 rounded-md p-1 text-text-secondary hover:text-text-primary\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n </div>\n {children}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;AA0BA,IAAa,KAAyB,EACpC,UACA,gBACA,YACA,aACA,uBAAoB,iBAChB;CACJ,IAAM,IAAU,
|
|
1
|
+
{"version":3,"file":"Modal.js","names":[],"sources":["../../../src/subscriberbot/components/Modal.tsx"],"sourcesContent":["/**\n * Accessible modal dialog\n * @module subscriberbot/components\n *\n * A small, dependency-light dialog used for the MFE's create/edit forms.\n * Handles the a11y contract every modal here must meet:\n * - `role=\"dialog\"` + `aria-modal=\"true\"` + `aria-labelledby` (wired to the\n * heading id),\n * - Escape-to-close,\n * - initial focus moved to the first form field,\n * - a semantic overlay scrim (`bg-bg-overlay`, never raw black),\n * - responsive layout (full-width on mobile, scrolls when the form is long).\n */\n\nimport { useEffect, useId, useRef, type FC, type ReactNode } from 'react';\nimport { X } from 'lucide-react';\n\ninterface ModalProps {\n title: string;\n description?: string;\n onClose: () => void;\n children: ReactNode;\n /** Constrain width. Defaults to a comfortable single-column form width. */\n maxWidthClassName?: string;\n}\n\nexport const Modal: FC<ModalProps> = ({\n title,\n description,\n onClose,\n children,\n maxWidthClassName = 'max-w-lg',\n}) => {\n const titleId = useId();\n const panelRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') onClose();\n };\n document.addEventListener('keydown', onKeyDown);\n\n // Move initial focus to the first interactive form field (falling back to\n // any focusable control) so keyboard users land inside the dialog.\n const panel = panelRef.current;\n const firstField = panel?.querySelector<HTMLElement>(\n 'input:not([type=\"hidden\"]), select, textarea'\n );\n (\n firstField ??\n panel?.querySelector<HTMLElement>('button, [href], [tabindex]:not([tabindex=\"-1\"])')\n )?.focus();\n\n return () => document.removeEventListener('keydown', onKeyDown);\n }, [onClose]);\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4 backdrop-blur-sm\"\n onMouseDown={(e) => {\n if (e.target === e.currentTarget) onClose();\n }}\n >\n <div\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n className={`max-h-[90vh] w-full ${maxWidthClassName} overflow-y-auto rounded-lg border border-border-subtle bg-bg-surface p-5 shadow-lg`}\n >\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <h3 id={titleId} className=\"text-lg font-semibold text-text-primary\">\n {title}\n </h3>\n {description && <p className=\"mt-0.5 text-xs text-text-secondary\">{description}</p>}\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close dialog\"\n className=\"shrink-0 rounded-md p-1 text-text-secondary hover:text-text-primary\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n </div>\n {children}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;AA0BA,IAAa,KAAyB,EACpC,UACA,gBACA,YACA,aACA,uBAAoB,iBAChB;CACJ,IAAM,IAAU,EAAM,GAChB,IAAW,EAAuB,IAAI;CAsB5C,OApBA,QAAgB;EACd,IAAM,KAAa,MAAqB;GACtC,AAAI,EAAE,QAAQ,YAAU,EAAQ;EAClC;EACA,SAAS,iBAAiB,WAAW,CAAS;EAI9C,IAAM,IAAQ,EAAS;EASvB,QARmB,GAAO,cACxB,gDACF,KAGE,GAAO,cAA2B,mDAAiD,IAClF,MAAM,SAEI,SAAS,oBAAoB,WAAW,CAAS;CAChE,GAAG,CAAC,CAAO,CAAC,GAGV,kBAAC,OAAD;EACE,WAAU;EACV,cAAc,MAAM;GAClB,AAAI,EAAE,WAAW,EAAE,iBAAe,EAAQ;EAC5C;YAEA,kBAAC,OAAD;GACE,KAAK;GACL,MAAK;GACL,cAAW;GACX,mBAAiB;GACjB,WAAW,uBAAuB,EAAkB;aALtD,CAOE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,IAAI;MAAS,WAAU;gBACxB;KACC,CAAA,GACH,KAAe,kBAAC,KAAD;MAAG,WAAU;gBAAsC;KAAe,CAAA,CAC/E;QACL,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,cAAW;KACX,WAAU;eAEV,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;IAClB,CAAA,CACL;OACJ,CACE;;CACF,CAAA;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageLayout.js","names":[],"sources":["../../../src/subscriberbot/components/PageLayout.tsx"],"sourcesContent":["/**\n * Page Layout Component\n * @module subscriberbot/components\n *\n * Boff UI 2.0 page wrapper. Supports a living-glass {@link AuroraBackground}\n * hero backdrop and a gradient hero title for the MFE's primary page.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { AuroraBackground, GradientText } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ninterface PageLayoutProps {\n title: ReactNode;\n description?: string;\n actions?: ReactNode;\n children: ReactNode;\n className?: string;\n /**\n * Render the title as a gradient hero heading (Boff UI 2.0). Reserve for the\n * ONE primary page of the MFE so it stays a hero accent, never body copy.\n */\n titleGradient?: boolean;\n /**\n * Drop a subtle living-glass {@link AuroraBackground} behind the header.\n * Use on the MFE's primary page only (one aurora per MFE). Sits at -z-10 and\n * never moves content; auto-static under reduced motion.\n */\n aurora?: boolean;\n}\n\nexport const PageLayout: FC<PageLayoutProps> = ({\n title,\n description,\n actions,\n children,\n className,\n titleGradient = false,\n aurora = false,\n}) => {\n return (\n <div className={cn('flex flex-col h-full', className)}>\n {/* Page Header — optionally on a living-glass aurora hero backdrop. The\n aurora layer sits at -z-10 and never moves content. */}\n <div\n className={cn(\n 'relative flex items-center justify-between gap-4 px-6 py-4 border-b border-border-seam',\n aurora ? 'overflow-hidden bg-gloss-card' : 'bg-bg-surface'\n )}\n >\n {aurora && <AuroraBackground intensity=\"subtle\" />}\n <div className=\"min-w-0\">\n {titleGradient && typeof title === 'string' ? (\n <GradientText as=\"h1\" className=\"text-2xl font-semibold tracking-tight\">\n {title}\n </GradientText>\n ) : (\n <h1 className=\"text-2xl font-semibold text-text-primary\">{title}</h1>\n )}\n {description && <p className=\"mt-1 text-sm text-text-secondary\">{description}</p>}\n </div>\n {actions && <div className=\"flex items-center gap-3 shrink-0\">{actions}</div>}\n </div>\n\n {/* Page Content */}\n <div className=\"flex-1 overflow-auto p-6\">{children}</div>\n </div>\n );\n};\n"],"mappings":";;;;AA+BA,IAAa,KAAmC,EAC9C,UACA,gBACA,YACA,aACA,cACA,mBAAgB,IAChB,YAAS,SAGP,kBAAC,OAAD;CAAK,WAAW,EAAG,wBAAwB,
|
|
1
|
+
{"version":3,"file":"PageLayout.js","names":[],"sources":["../../../src/subscriberbot/components/PageLayout.tsx"],"sourcesContent":["/**\n * Page Layout Component\n * @module subscriberbot/components\n *\n * Boff UI 2.0 page wrapper. Supports a living-glass {@link AuroraBackground}\n * hero backdrop and a gradient hero title for the MFE's primary page.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { AuroraBackground, GradientText } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ninterface PageLayoutProps {\n title: ReactNode;\n description?: string;\n actions?: ReactNode;\n children: ReactNode;\n className?: string;\n /**\n * Render the title as a gradient hero heading (Boff UI 2.0). Reserve for the\n * ONE primary page of the MFE so it stays a hero accent, never body copy.\n */\n titleGradient?: boolean;\n /**\n * Drop a subtle living-glass {@link AuroraBackground} behind the header.\n * Use on the MFE's primary page only (one aurora per MFE). Sits at -z-10 and\n * never moves content; auto-static under reduced motion.\n */\n aurora?: boolean;\n}\n\nexport const PageLayout: FC<PageLayoutProps> = ({\n title,\n description,\n actions,\n children,\n className,\n titleGradient = false,\n aurora = false,\n}) => {\n return (\n <div className={cn('flex flex-col h-full', className)}>\n {/* Page Header — optionally on a living-glass aurora hero backdrop. The\n aurora layer sits at -z-10 and never moves content. */}\n <div\n className={cn(\n 'relative flex items-center justify-between gap-4 px-6 py-4 border-b border-border-seam',\n aurora ? 'overflow-hidden bg-gloss-card' : 'bg-bg-surface'\n )}\n >\n {aurora && <AuroraBackground intensity=\"subtle\" />}\n <div className=\"min-w-0\">\n {titleGradient && typeof title === 'string' ? (\n <GradientText as=\"h1\" className=\"text-2xl font-semibold tracking-tight\">\n {title}\n </GradientText>\n ) : (\n <h1 className=\"text-2xl font-semibold text-text-primary\">{title}</h1>\n )}\n {description && <p className=\"mt-1 text-sm text-text-secondary\">{description}</p>}\n </div>\n {actions && <div className=\"flex items-center gap-3 shrink-0\">{actions}</div>}\n </div>\n\n {/* Page Content */}\n <div className=\"flex-1 overflow-auto p-6\">{children}</div>\n </div>\n );\n};\n"],"mappings":";;;;AA+BA,IAAa,KAAmC,EAC9C,UACA,gBACA,YACA,aACA,cACA,mBAAgB,IAChB,YAAS,SAGP,kBAAC,OAAD;CAAK,WAAW,EAAG,wBAAwB,CAAS;WAApD,CAGE,kBAAC,OAAD;EACE,WAAW,EACT,0FACA,IAAS,kCAAkC,eAC7C;YAJF;GAMG,KAAU,kBAAC,GAAD,EAAkB,WAAU,SAAU,CAAA;GACjD,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,KAAiB,OAAO,KAAU,WACjC,kBAAC,GAAD;KAAc,IAAG;KAAK,WAAU;eAC7B;IACW,CAAA,IAEd,kBAAC,MAAD;KAAI,WAAU;eAA4C;IAAU,CAAA,GAErE,KAAe,kBAAC,KAAD;KAAG,WAAU;eAAoC;IAAe,CAAA,CAC7E;;GACJ,KAAW,kBAAC,OAAD;IAAK,WAAU;cAAoC;GAAa,CAAA;EACzE;KAGL,kBAAC,OAAD;EAAK,WAAU;EAA4B;CAAc,CAAA,CACtD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StateViews.js","names":[],"sources":["../../../src/subscriberbot/components/StateViews.tsx"],"sourcesContent":["/**\n * Shared loading / error state views\n * @module subscriberbot/components\n *\n * Small reusable building blocks so every page renders consistent\n * loading / error / empty states without re-implementing them.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { AlertCircle } from 'lucide-react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ninterface LoadingViewProps {\n label?: string;\n className?: string;\n}\n\nexport const LoadingView: FC<LoadingViewProps> = ({ label = 'Loading…', className }) => (\n <div className={cn('flex min-h-[40vh] flex-col items-center justify-center gap-3', className)}>\n <Spinner size=\"lg\" />\n <p className=\"text-sm text-text-secondary\">{label}</p>\n </div>\n);\n\ninterface ErrorViewProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n className?: string;\n}\n\nexport const ErrorView: FC<ErrorViewProps> = ({\n title = 'Something went wrong',\n message,\n onRetry,\n className,\n}) => (\n <div\n className={cn(\n 'flex min-h-[40vh] flex-col items-center justify-center gap-4 text-center',\n className\n )}\n >\n <AlertCircle className=\"h-10 w-10 text-status-error-text\" />\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n {message && <p className=\"mt-1 max-w-md text-sm text-text-secondary\">{message}</p>}\n </div>\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Retry\n </button>\n )}\n </div>\n);\n\ninterface SectionProps {\n title: string;\n description?: string;\n action?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\nexport const Section: FC<SectionProps> = ({ title, description, action, children, className }) => (\n <section className={cn('mt-8 first:mt-0', className)}>\n <div className=\"mb-3 flex items-end justify-between gap-3\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">{title}</h2>\n {description && <p className=\"mt-0.5 text-sm text-text-secondary\">{description}</p>}\n </div>\n {action && <div className=\"shrink-0\">{action}</div>}\n </div>\n {children}\n </section>\n);\n"],"mappings":";;;;;AAkBA,IAAa,KAAqC,EAAE,WAAQ,YAAY,mBACtE,kBAAC,OAAD;CAAK,WAAW,EAAG,gEAAgE,
|
|
1
|
+
{"version":3,"file":"StateViews.js","names":[],"sources":["../../../src/subscriberbot/components/StateViews.tsx"],"sourcesContent":["/**\n * Shared loading / error state views\n * @module subscriberbot/components\n *\n * Small reusable building blocks so every page renders consistent\n * loading / error / empty states without re-implementing them.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { AlertCircle } from 'lucide-react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ninterface LoadingViewProps {\n label?: string;\n className?: string;\n}\n\nexport const LoadingView: FC<LoadingViewProps> = ({ label = 'Loading…', className }) => (\n <div className={cn('flex min-h-[40vh] flex-col items-center justify-center gap-3', className)}>\n <Spinner size=\"lg\" />\n <p className=\"text-sm text-text-secondary\">{label}</p>\n </div>\n);\n\ninterface ErrorViewProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n className?: string;\n}\n\nexport const ErrorView: FC<ErrorViewProps> = ({\n title = 'Something went wrong',\n message,\n onRetry,\n className,\n}) => (\n <div\n className={cn(\n 'flex min-h-[40vh] flex-col items-center justify-center gap-4 text-center',\n className\n )}\n >\n <AlertCircle className=\"h-10 w-10 text-status-error-text\" />\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n {message && <p className=\"mt-1 max-w-md text-sm text-text-secondary\">{message}</p>}\n </div>\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Retry\n </button>\n )}\n </div>\n);\n\ninterface SectionProps {\n title: string;\n description?: string;\n action?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\nexport const Section: FC<SectionProps> = ({ title, description, action, children, className }) => (\n <section className={cn('mt-8 first:mt-0', className)}>\n <div className=\"mb-3 flex items-end justify-between gap-3\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">{title}</h2>\n {description && <p className=\"mt-0.5 text-sm text-text-secondary\">{description}</p>}\n </div>\n {action && <div className=\"shrink-0\">{action}</div>}\n </div>\n {children}\n </section>\n);\n"],"mappings":";;;;;AAkBA,IAAa,KAAqC,EAAE,WAAQ,YAAY,mBACtE,kBAAC,OAAD;CAAK,WAAW,EAAG,gEAAgE,CAAS;WAA5F,CACE,kBAAC,GAAD,EAAS,MAAK,KAAM,CAAA,GACpB,kBAAC,KAAD;EAAG,WAAU;YAA+B;CAAS,CAAA,CAClD;IAUM,KAAiC,EAC5C,WAAQ,wBACR,YACA,YACA,mBAEA,kBAAC,OAAD;CACE,WAAW,EACT,4EACA,CACF;WAJF;EAME,kBAAC,GAAD,EAAa,WAAU,mCAAoC,CAAA;EAC3D,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;GAAI,WAAU;aAA2C;EAAU,CAAA,GAClE,KAAW,kBAAC,KAAD;GAAG,WAAU;aAA6C;EAAW,CAAA,CAC9E,EAAA,CAAA;EACJ,KACC,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;aACX;EAEO,CAAA;CAEP;IAWM,KAA6B,EAAE,UAAO,gBAAa,WAAQ,aAAU,mBAChF,kBAAC,WAAD;CAAS,WAAW,EAAG,mBAAmB,CAAS;WAAnD,CACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;GAAI,WAAU;aAA6C;EAAU,CAAA,GACpE,KAAe,kBAAC,KAAD;GAAG,WAAU;aAAsC;EAAe,CAAA,CAC/E,EAAA,CAAA,GACJ,KAAU,kBAAC,OAAD;GAAK,WAAU;aAAY;EAAY,CAAA,CAC/C;KACJ,CACM"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/subscriberbot/components/StatusBadge.tsx"],"sourcesContent":["/**\n * Status Badge Component\n * @module subscriberbot/components\n *\n * A colored badge for Subscriber Bot domain statuses (relationship lifecycle,\n * invoice status, agent posture, channel verification, provider status,\n * workflow state) with semantic color mapping. Accepts the raw server enum\n * value (e.g. \"ACTIVE\", \"PAST_DUE\", \"SUGGEST_ONLY\").\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\nimport { formatEnumLabel } from '../utils/formatters';\n\ninterface StatusBadgeProps {\n status: string;\n className?: string;\n}\n\ntype BadgeVariant = 'success' | 'warning' | 'destructive' | 'secondary' | 'info' | 'outline';\n\nconst variantStyles: Record<BadgeVariant, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n destructive: 'bg-status-error-bg-subtle text-status-error-text',\n secondary: 'bg-bg-sunken text-text-secondary',\n info: 'bg-[var(--color-accent-soft)] text-text-primary',\n outline: 'border border-border-seam text-text-primary',\n};\n\n// Statuses that represent a \"live\" / in-progress condition get a subtle,\n// opacity-only glow pulse (reduced-motion safe via motion-safe:).\nconst liveStatuses = new Set(['ACTIVE', 'AUTONOMOUS', 'TRIAL', 'EXPIRING']);\n\nconst statusVariantMap: Record<string, BadgeVariant> = {\n // Relationship lifecycle\n ACTIVE: 'success',\n TRIAL: 'info',\n PAUSED: 'warning',\n PAST_DUE: 'destructive',\n LAPSED: 'warning',\n EXPIRED: 'secondary',\n CANCELLED: 'destructive',\n\n // Invoice status\n DRAFT: 'secondary',\n ISSUED: 'info',\n PAID: 'success',\n OVERDUE: 'destructive',\n FAILED: 'destructive',\n REFUNDED: 'outline',\n\n // Agent posture\n AUTONOMOUS: 'success',\n SUGGEST_ONLY: 'info',\n DISABLED: 'secondary',\n\n // Channel verification / payment instrument\n VERIFIED: 'success',\n PENDING: 'warning',\n EXPIRING: 'warning',\n REMOVED: 'outline',\n\n // Provider status\n CLAIMED: 'success',\n UNVERIFIED: 'secondary',\n DEPRECATED: 'outline',\n\n // Workflow status\n ARCHIVED: 'outline',\n\n // Adapter submission / marketplace report moderation status\n APPROVED: 'success',\n REJECTED: 'destructive',\n DISMISSED: 'outline',\n RESOLVED: 'success',\n};\n\nexport const StatusBadge: FC<StatusBadgeProps> = ({ status, className }) => {\n const key = status.toUpperCase();\n const variant = statusVariantMap[key] ?? 'secondary';\n const isLive = liveStatuses.has(key);\n\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',\n variantStyles[variant],\n className\n )}\n >\n {isLive && (\n <span\n aria-hidden=\"true\"\n className=\"size-1.5 rounded-full bg-current motion-safe:animate-vc-glow-pulse\"\n />\n )}\n {formatEnumLabel(status)}\n </span>\n );\n};\n"],"mappings":";;;;AAqBA,IAAM,IAA8C;CAClD,SAAS;CACT,SAAS;CACT,aAAa;CACb,WAAW;CACX,MAAM;CACN,SAAS;
|
|
1
|
+
{"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/subscriberbot/components/StatusBadge.tsx"],"sourcesContent":["/**\n * Status Badge Component\n * @module subscriberbot/components\n *\n * A colored badge for Subscriber Bot domain statuses (relationship lifecycle,\n * invoice status, agent posture, channel verification, provider status,\n * workflow state) with semantic color mapping. Accepts the raw server enum\n * value (e.g. \"ACTIVE\", \"PAST_DUE\", \"SUGGEST_ONLY\").\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\nimport { formatEnumLabel } from '../utils/formatters';\n\ninterface StatusBadgeProps {\n status: string;\n className?: string;\n}\n\ntype BadgeVariant = 'success' | 'warning' | 'destructive' | 'secondary' | 'info' | 'outline';\n\nconst variantStyles: Record<BadgeVariant, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n destructive: 'bg-status-error-bg-subtle text-status-error-text',\n secondary: 'bg-bg-sunken text-text-secondary',\n info: 'bg-[var(--color-accent-soft)] text-text-primary',\n outline: 'border border-border-seam text-text-primary',\n};\n\n// Statuses that represent a \"live\" / in-progress condition get a subtle,\n// opacity-only glow pulse (reduced-motion safe via motion-safe:).\nconst liveStatuses = new Set(['ACTIVE', 'AUTONOMOUS', 'TRIAL', 'EXPIRING']);\n\nconst statusVariantMap: Record<string, BadgeVariant> = {\n // Relationship lifecycle\n ACTIVE: 'success',\n TRIAL: 'info',\n PAUSED: 'warning',\n PAST_DUE: 'destructive',\n LAPSED: 'warning',\n EXPIRED: 'secondary',\n CANCELLED: 'destructive',\n\n // Invoice status\n DRAFT: 'secondary',\n ISSUED: 'info',\n PAID: 'success',\n OVERDUE: 'destructive',\n FAILED: 'destructive',\n REFUNDED: 'outline',\n\n // Agent posture\n AUTONOMOUS: 'success',\n SUGGEST_ONLY: 'info',\n DISABLED: 'secondary',\n\n // Channel verification / payment instrument\n VERIFIED: 'success',\n PENDING: 'warning',\n EXPIRING: 'warning',\n REMOVED: 'outline',\n\n // Provider status\n CLAIMED: 'success',\n UNVERIFIED: 'secondary',\n DEPRECATED: 'outline',\n\n // Workflow status\n ARCHIVED: 'outline',\n\n // Adapter submission / marketplace report moderation status\n APPROVED: 'success',\n REJECTED: 'destructive',\n DISMISSED: 'outline',\n RESOLVED: 'success',\n};\n\nexport const StatusBadge: FC<StatusBadgeProps> = ({ status, className }) => {\n const key = status.toUpperCase();\n const variant = statusVariantMap[key] ?? 'secondary';\n const isLive = liveStatuses.has(key);\n\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',\n variantStyles[variant],\n className\n )}\n >\n {isLive && (\n <span\n aria-hidden=\"true\"\n className=\"size-1.5 rounded-full bg-current motion-safe:animate-vc-glow-pulse\"\n />\n )}\n {formatEnumLabel(status)}\n </span>\n );\n};\n"],"mappings":";;;;AAqBA,IAAM,IAA8C;CAClD,SAAS;CACT,SAAS;CACT,aAAa;CACb,WAAW;CACX,MAAM;CACN,SAAS;AACX,GAIM,IAAe,IAAI,IAAI;CAAC;CAAU;CAAc;CAAS;AAAU,CAAC,GAEpE,IAAiD;CAErD,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,UAAU;CACV,QAAQ;CACR,SAAS;CACT,WAAW;CAGX,OAAO;CACP,QAAQ;CACR,MAAM;CACN,SAAS;CACT,QAAQ;CACR,UAAU;CAGV,YAAY;CACZ,cAAc;CACd,UAAU;CAGV,UAAU;CACV,SAAS;CACT,UAAU;CACV,SAAS;CAGT,SAAS;CACT,YAAY;CACZ,YAAY;CAGZ,UAAU;CAGV,UAAU;CACV,UAAU;CACV,WAAW;CACX,UAAU;AACZ,GAEa,KAAqC,EAAE,WAAQ,mBAAgB;CAC1E,IAAM,IAAM,EAAO,YAAY,GACzB,IAAU,EAAiB,MAAQ,aACnC,IAAS,EAAa,IAAI,CAAG;CAEnC,OACE,kBAAC,QAAD;EACE,WAAW,EACT,mFACA,EAAc,IACd,CACF;YALF,CAOG,KACC,kBAAC,QAAD;GACE,eAAY;GACZ,WAAU;EACX,CAAA,GAEF,EAAgB,CAAM,CACnB;;AAEV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"viz.js","names":[],"sources":["../../../src/subscriberbot/components/viz.tsx"],"sourcesContent":["/**\n * Tokenized, responsive SVG visualization primitives.\n * @module subscriberbot/components/viz\n *\n * Every color is a fe-libs semantic CSS variable (never a raw hex), so charts\n * theme automatically in light/dark and per-product themes. All charts scale to\n * their container (SVG viewBox) and carry an accessible role/label.\n */\nimport type { FC, ReactNode } from 'react';\nimport { cn } from '../utils/cn';\n\n/** Categorical palette — fe-libs chart tokens. */\nexport const CHART_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\n 'var(--color-chart-6)',\n 'var(--color-chart-7)',\n 'var(--color-chart-8)',\n] as const;\n\nconst POSITIVE = 'var(--color-chart-positive)';\nconst NEGATIVE = 'var(--color-chart-negative)';\nconst GRID = 'var(--color-chart-grid)';\nconst WARN = 'var(--color-status-warning-text)';\nconst SUCCESS = 'var(--color-status-success-text)';\n\nexport interface Segment {\n label: string;\n value: number;\n color?: string;\n}\n\nconst colorAt = (i: number, override?: string) => override ?? CHART_COLORS[i % CHART_COLORS.length];\n\n// ---------------------------------------------------------------------------\n// Donut — proportional ring with an optional center label.\n// ---------------------------------------------------------------------------\nexport const Donut: FC<{\n data: Segment[];\n size?: number;\n thickness?: number;\n centerLabel?: string;\n centerSub?: string;\n className?: string;\n}> = ({ data, size = 160, thickness = 18, centerLabel, centerSub, className }) => {\n const total = data.reduce((s, d) => s + Math.max(0, d.value), 0);\n const r = (size - thickness) / 2;\n const c = 2 * Math.PI * r;\n const cx = size / 2;\n let offset = 0;\n const label = data.map((d) => `${d.label}: ${d.value}`).join(', ');\n\n return (\n <div className={cn('relative inline-flex items-center justify-center', className)}>\n <svg\n width={size}\n height={size}\n viewBox={`0 0 ${size} ${size}`}\n role=\"img\"\n aria-label={`Donut chart. ${label}`}\n className=\"max-w-full\"\n >\n <circle\n cx={cx}\n cy={cx}\n r={r}\n fill=\"none\"\n stroke={GRID}\n strokeWidth={thickness}\n opacity={0.4}\n />\n {total > 0 &&\n data.map((d, i) => {\n const frac = Math.max(0, d.value) / total;\n const len = frac * c;\n const seg = (\n <circle\n key={i}\n cx={cx}\n cy={cx}\n r={r}\n fill=\"none\"\n stroke={colorAt(i, d.color)}\n strokeWidth={thickness}\n strokeDasharray={`${len} ${c - len}`}\n strokeDashoffset={-offset}\n strokeLinecap=\"butt\"\n transform={`rotate(-90 ${cx} ${cx})`}\n />\n );\n offset += len;\n return seg;\n })}\n </svg>\n {(centerLabel || centerSub) && (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center text-center\">\n {centerLabel && (\n <span className=\"text-xl font-bold text-text-primary tabular-nums leading-none\">\n {centerLabel}\n </span>\n )}\n {centerSub && <span className=\"mt-1 text-xs text-text-secondary\">{centerSub}</span>}\n </div>\n )}\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Legend — pairs with Donut / charts.\n// ---------------------------------------------------------------------------\nexport const Legend: FC<{\n data: Segment[];\n valueFormat?: (n: number) => string;\n className?: string;\n}> = ({ data, valueFormat, className }) => (\n <ul className={cn('space-y-2', className)}>\n {data.map((d, i) => (\n <li key={i} className=\"flex items-center gap-2 text-sm\">\n <span\n className=\"h-2.5 w-2.5 shrink-0 rounded-full\"\n style={{ backgroundColor: colorAt(i, d.color) }}\n aria-hidden\n />\n <span className=\"flex-1 truncate text-text-secondary\">{d.label}</span>\n <span className=\"font-medium text-text-primary tabular-nums\">\n {valueFormat ? valueFormat(d.value) : d.value}\n </span>\n </li>\n ))}\n </ul>\n);\n\n// ---------------------------------------------------------------------------\n// BarList — horizontal labelled bars (e.g. spend by category).\n// ---------------------------------------------------------------------------\nexport const BarList: FC<{\n data: Segment[];\n valueFormat?: (n: number) => string;\n className?: string;\n}> = ({ data, valueFormat, className }) => {\n const max = Math.max(1, ...data.map((d) => d.value));\n return (\n <ul className={cn('space-y-3', className)} role=\"img\" aria-label=\"Bar chart\">\n {data.map((d, i) => (\n <li key={i}>\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"truncate text-text-secondary\">{d.label}</span>\n <span className=\"font-medium text-text-primary tabular-nums\">\n {valueFormat ? valueFormat(d.value) : d.value}\n </span>\n </div>\n <div\n className=\"mt-1.5 h-2 w-full overflow-hidden rounded-full\"\n style={{ backgroundColor: GRID }}\n >\n <div\n className=\"h-full rounded-full transition-all\"\n style={{ width: `${(d.value / max) * 100}%`, backgroundColor: colorAt(i, d.color) }}\n />\n </div>\n </li>\n ))}\n </ul>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Sparkline / area — small trend line.\n// ---------------------------------------------------------------------------\nexport const Sparkline: FC<{\n points: number[];\n width?: number;\n height?: number;\n positive?: boolean;\n className?: string;\n}> = ({ points, width = 220, height = 56, positive = true, className }) => {\n if (points.length < 2) return null;\n const max = Math.max(...points);\n const min = Math.min(...points);\n const span = max - min || 1;\n const stroke = positive ? POSITIVE : NEGATIVE;\n const step = width / (points.length - 1);\n const coords = points.map((p, i) => [i * step, height - ((p - min) / span) * (height - 8) - 4]);\n const line = coords\n .map(([x, y], i) => `${i === 0 ? 'M' : 'L'}${x.toFixed(1)},${y.toFixed(1)}`)\n .join(' ');\n const area = `${line} L${width},${height} L0,${height} Z`;\n return (\n <svg\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n role=\"img\"\n aria-label=\"Trend sparkline\"\n className={cn('max-w-full', className)}\n preserveAspectRatio=\"none\"\n >\n <path d={area} fill={stroke} opacity={0.12} />\n <path\n d={line}\n fill=\"none\"\n stroke={stroke}\n strokeWidth={2}\n strokeLinejoin=\"round\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Gauge — 0..max semicircle (e.g. health score).\n// ---------------------------------------------------------------------------\nexport const Gauge: FC<{\n value: number;\n max?: number;\n size?: number;\n label?: string;\n className?: string;\n}> = ({ value, max = 100, size = 120, label, className }) => {\n const pct = Math.max(0, Math.min(1, value / max));\n const r = size / 2 - 10;\n const cy = size / 2;\n const circ = Math.PI * r; // half circle\n const tone = pct >= 0.66 ? SUCCESS : pct >= 0.33 ? WARN : NEGATIVE;\n return (\n <div className={cn('inline-flex flex-col items-center', className)}>\n <svg\n width={size}\n height={size / 2 + 8}\n viewBox={`0 0 ${size} ${size / 2 + 8}`}\n role=\"img\"\n aria-label={`Gauge: ${Math.round(pct * 100)} percent`}\n className=\"max-w-full\"\n >\n <path\n d={`M10 ${cy} A ${r} ${r} 0 0 1 ${size - 10} ${cy}`}\n fill=\"none\"\n stroke={GRID}\n strokeWidth={10}\n strokeLinecap=\"round\"\n />\n <path\n d={`M10 ${cy} A ${r} ${r} 0 0 1 ${size - 10} ${cy}`}\n fill=\"none\"\n stroke={tone}\n strokeWidth={10}\n strokeLinecap=\"round\"\n strokeDasharray={`${pct * circ} ${circ}`}\n />\n </svg>\n <div className=\"-mt-4 text-center\">\n <span className=\"text-lg font-bold text-text-primary tabular-nums\">\n {Math.round(value)}\n </span>\n {label && <p className=\"text-xs text-text-secondary\">{label}</p>}\n </div>\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Timeline — vertical events with a connector rail (e.g. renewals).\n// ---------------------------------------------------------------------------\nexport interface TimelineItem {\n title: string;\n sub?: string;\n meta?: ReactNode;\n tone?: 'default' | 'positive' | 'warning' | 'muted';\n}\nconst dotTone: Record<NonNullable<TimelineItem['tone']>, string> = {\n default: 'var(--color-accent-indigo)',\n positive: SUCCESS,\n warning: WARN,\n muted: 'var(--color-text-secondary)',\n};\nexport const Timeline: FC<{ items: TimelineItem[]; className?: string }> = ({\n items,\n className,\n}) => (\n <ol className={cn('relative space-y-5 pl-6', className)}>\n <span className=\"absolute left-[7px] top-1 bottom-1 w-px bg-border-subtle\" aria-hidden />\n {items.map((it, i) => (\n <li key={i} className=\"relative\">\n <span\n className=\"absolute -left-[22px] top-1 h-3 w-3 rounded-full ring-4 ring-bg-surface\"\n style={{ backgroundColor: dotTone[it.tone ?? 'default'] }}\n aria-hidden\n />\n <div className=\"flex items-baseline justify-between gap-2\">\n <p className=\"text-sm font-medium text-text-primary\">{it.title}</p>\n {it.meta && <span className=\"text-sm tabular-nums text-text-primary\">{it.meta}</span>}\n </div>\n {it.sub && <p className=\"text-xs text-text-secondary\">{it.sub}</p>}\n </li>\n ))}\n </ol>\n);\n\n// ---------------------------------------------------------------------------\n// MiniProgress — inline utilization bar (e.g. seats used).\n// ---------------------------------------------------------------------------\nexport const MiniProgress: FC<{\n value: number;\n max?: number;\n tone?: 'accent' | 'positive' | 'warning' | 'negative';\n className?: string;\n}> = ({ value, max = 100, tone = 'accent', className }) => {\n const pct = Math.max(0, Math.min(1, max ? value / max : 0));\n const color =\n tone === 'positive'\n ? SUCCESS\n : tone === 'warning'\n ? WARN\n : tone === 'negative'\n ? NEGATIVE\n : 'var(--color-accent-indigo)';\n return (\n <div\n className={cn('h-2 w-full overflow-hidden rounded-full', className)}\n style={{ backgroundColor: GRID }}\n role=\"progressbar\"\n aria-valuenow={Math.round(pct * 100)}\n aria-valuemin={0}\n aria-valuemax={100}\n >\n <div\n className=\"h-full rounded-full transition-all\"\n style={{ width: `${pct * 100}%`, backgroundColor: color }}\n />\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"viz.js","names":[],"sources":["../../../src/subscriberbot/components/viz.tsx"],"sourcesContent":["/**\n * Tokenized, responsive SVG visualization primitives.\n * @module subscriberbot/components/viz\n *\n * Every color is a fe-libs semantic CSS variable (never a raw hex), so charts\n * theme automatically in light/dark and per-product themes. All charts scale to\n * their container (SVG viewBox) and carry an accessible role/label.\n */\nimport type { FC, ReactNode } from 'react';\nimport { cn } from '../utils/cn';\n\n/** Categorical palette — fe-libs chart tokens. */\nexport const CHART_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\n 'var(--color-chart-6)',\n 'var(--color-chart-7)',\n 'var(--color-chart-8)',\n] as const;\n\nconst POSITIVE = 'var(--color-chart-positive)';\nconst NEGATIVE = 'var(--color-chart-negative)';\nconst GRID = 'var(--color-chart-grid)';\nconst WARN = 'var(--color-status-warning-text)';\nconst SUCCESS = 'var(--color-status-success-text)';\n\nexport interface Segment {\n label: string;\n value: number;\n color?: string;\n}\n\nconst colorAt = (i: number, override?: string) => override ?? CHART_COLORS[i % CHART_COLORS.length];\n\n// ---------------------------------------------------------------------------\n// Donut — proportional ring with an optional center label.\n// ---------------------------------------------------------------------------\nexport const Donut: FC<{\n data: Segment[];\n size?: number;\n thickness?: number;\n centerLabel?: string;\n centerSub?: string;\n className?: string;\n}> = ({ data, size = 160, thickness = 18, centerLabel, centerSub, className }) => {\n const total = data.reduce((s, d) => s + Math.max(0, d.value), 0);\n const r = (size - thickness) / 2;\n const c = 2 * Math.PI * r;\n const cx = size / 2;\n let offset = 0;\n const label = data.map((d) => `${d.label}: ${d.value}`).join(', ');\n\n return (\n <div className={cn('relative inline-flex items-center justify-center', className)}>\n <svg\n width={size}\n height={size}\n viewBox={`0 0 ${size} ${size}`}\n role=\"img\"\n aria-label={`Donut chart. ${label}`}\n className=\"max-w-full\"\n >\n <circle\n cx={cx}\n cy={cx}\n r={r}\n fill=\"none\"\n stroke={GRID}\n strokeWidth={thickness}\n opacity={0.4}\n />\n {total > 0 &&\n data.map((d, i) => {\n const frac = Math.max(0, d.value) / total;\n const len = frac * c;\n const seg = (\n <circle\n key={i}\n cx={cx}\n cy={cx}\n r={r}\n fill=\"none\"\n stroke={colorAt(i, d.color)}\n strokeWidth={thickness}\n strokeDasharray={`${len} ${c - len}`}\n strokeDashoffset={-offset}\n strokeLinecap=\"butt\"\n transform={`rotate(-90 ${cx} ${cx})`}\n />\n );\n offset += len;\n return seg;\n })}\n </svg>\n {(centerLabel || centerSub) && (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center text-center\">\n {centerLabel && (\n <span className=\"text-xl font-bold text-text-primary tabular-nums leading-none\">\n {centerLabel}\n </span>\n )}\n {centerSub && <span className=\"mt-1 text-xs text-text-secondary\">{centerSub}</span>}\n </div>\n )}\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Legend — pairs with Donut / charts.\n// ---------------------------------------------------------------------------\nexport const Legend: FC<{\n data: Segment[];\n valueFormat?: (n: number) => string;\n className?: string;\n}> = ({ data, valueFormat, className }) => (\n <ul className={cn('space-y-2', className)}>\n {data.map((d, i) => (\n <li key={i} className=\"flex items-center gap-2 text-sm\">\n <span\n className=\"h-2.5 w-2.5 shrink-0 rounded-full\"\n style={{ backgroundColor: colorAt(i, d.color) }}\n aria-hidden\n />\n <span className=\"flex-1 truncate text-text-secondary\">{d.label}</span>\n <span className=\"font-medium text-text-primary tabular-nums\">\n {valueFormat ? valueFormat(d.value) : d.value}\n </span>\n </li>\n ))}\n </ul>\n);\n\n// ---------------------------------------------------------------------------\n// BarList — horizontal labelled bars (e.g. spend by category).\n// ---------------------------------------------------------------------------\nexport const BarList: FC<{\n data: Segment[];\n valueFormat?: (n: number) => string;\n className?: string;\n}> = ({ data, valueFormat, className }) => {\n const max = Math.max(1, ...data.map((d) => d.value));\n return (\n <ul className={cn('space-y-3', className)} role=\"img\" aria-label=\"Bar chart\">\n {data.map((d, i) => (\n <li key={i}>\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"truncate text-text-secondary\">{d.label}</span>\n <span className=\"font-medium text-text-primary tabular-nums\">\n {valueFormat ? valueFormat(d.value) : d.value}\n </span>\n </div>\n <div\n className=\"mt-1.5 h-2 w-full overflow-hidden rounded-full\"\n style={{ backgroundColor: GRID }}\n >\n <div\n className=\"h-full rounded-full transition-all\"\n style={{ width: `${(d.value / max) * 100}%`, backgroundColor: colorAt(i, d.color) }}\n />\n </div>\n </li>\n ))}\n </ul>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Sparkline / area — small trend line.\n// ---------------------------------------------------------------------------\nexport const Sparkline: FC<{\n points: number[];\n width?: number;\n height?: number;\n positive?: boolean;\n className?: string;\n}> = ({ points, width = 220, height = 56, positive = true, className }) => {\n if (points.length < 2) return null;\n const max = Math.max(...points);\n const min = Math.min(...points);\n const span = max - min || 1;\n const stroke = positive ? POSITIVE : NEGATIVE;\n const step = width / (points.length - 1);\n const coords = points.map((p, i) => [i * step, height - ((p - min) / span) * (height - 8) - 4]);\n const line = coords\n .map(([x, y], i) => `${i === 0 ? 'M' : 'L'}${x.toFixed(1)},${y.toFixed(1)}`)\n .join(' ');\n const area = `${line} L${width},${height} L0,${height} Z`;\n return (\n <svg\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n role=\"img\"\n aria-label=\"Trend sparkline\"\n className={cn('max-w-full', className)}\n preserveAspectRatio=\"none\"\n >\n <path d={area} fill={stroke} opacity={0.12} />\n <path\n d={line}\n fill=\"none\"\n stroke={stroke}\n strokeWidth={2}\n strokeLinejoin=\"round\"\n strokeLinecap=\"round\"\n />\n </svg>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Gauge — 0..max semicircle (e.g. health score).\n// ---------------------------------------------------------------------------\nexport const Gauge: FC<{\n value: number;\n max?: number;\n size?: number;\n label?: string;\n className?: string;\n}> = ({ value, max = 100, size = 120, label, className }) => {\n const pct = Math.max(0, Math.min(1, value / max));\n const r = size / 2 - 10;\n const cy = size / 2;\n const circ = Math.PI * r; // half circle\n const tone = pct >= 0.66 ? SUCCESS : pct >= 0.33 ? WARN : NEGATIVE;\n return (\n <div className={cn('inline-flex flex-col items-center', className)}>\n <svg\n width={size}\n height={size / 2 + 8}\n viewBox={`0 0 ${size} ${size / 2 + 8}`}\n role=\"img\"\n aria-label={`Gauge: ${Math.round(pct * 100)} percent`}\n className=\"max-w-full\"\n >\n <path\n d={`M10 ${cy} A ${r} ${r} 0 0 1 ${size - 10} ${cy}`}\n fill=\"none\"\n stroke={GRID}\n strokeWidth={10}\n strokeLinecap=\"round\"\n />\n <path\n d={`M10 ${cy} A ${r} ${r} 0 0 1 ${size - 10} ${cy}`}\n fill=\"none\"\n stroke={tone}\n strokeWidth={10}\n strokeLinecap=\"round\"\n strokeDasharray={`${pct * circ} ${circ}`}\n />\n </svg>\n <div className=\"-mt-4 text-center\">\n <span className=\"text-lg font-bold text-text-primary tabular-nums\">\n {Math.round(value)}\n </span>\n {label && <p className=\"text-xs text-text-secondary\">{label}</p>}\n </div>\n </div>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Timeline — vertical events with a connector rail (e.g. renewals).\n// ---------------------------------------------------------------------------\nexport interface TimelineItem {\n title: string;\n sub?: string;\n meta?: ReactNode;\n tone?: 'default' | 'positive' | 'warning' | 'muted';\n}\nconst dotTone: Record<NonNullable<TimelineItem['tone']>, string> = {\n default: 'var(--color-accent-indigo)',\n positive: SUCCESS,\n warning: WARN,\n muted: 'var(--color-text-secondary)',\n};\nexport const Timeline: FC<{ items: TimelineItem[]; className?: string }> = ({\n items,\n className,\n}) => (\n <ol className={cn('relative space-y-5 pl-6', className)}>\n <span className=\"absolute left-[7px] top-1 bottom-1 w-px bg-border-subtle\" aria-hidden />\n {items.map((it, i) => (\n <li key={i} className=\"relative\">\n <span\n className=\"absolute -left-[22px] top-1 h-3 w-3 rounded-full ring-4 ring-bg-surface\"\n style={{ backgroundColor: dotTone[it.tone ?? 'default'] }}\n aria-hidden\n />\n <div className=\"flex items-baseline justify-between gap-2\">\n <p className=\"text-sm font-medium text-text-primary\">{it.title}</p>\n {it.meta && <span className=\"text-sm tabular-nums text-text-primary\">{it.meta}</span>}\n </div>\n {it.sub && <p className=\"text-xs text-text-secondary\">{it.sub}</p>}\n </li>\n ))}\n </ol>\n);\n\n// ---------------------------------------------------------------------------\n// MiniProgress — inline utilization bar (e.g. seats used).\n// ---------------------------------------------------------------------------\nexport const MiniProgress: FC<{\n value: number;\n max?: number;\n tone?: 'accent' | 'positive' | 'warning' | 'negative';\n className?: string;\n}> = ({ value, max = 100, tone = 'accent', className }) => {\n const pct = Math.max(0, Math.min(1, max ? value / max : 0));\n const color =\n tone === 'positive'\n ? SUCCESS\n : tone === 'warning'\n ? WARN\n : tone === 'negative'\n ? NEGATIVE\n : 'var(--color-accent-indigo)';\n return (\n <div\n className={cn('h-2 w-full overflow-hidden rounded-full', className)}\n style={{ backgroundColor: GRID }}\n role=\"progressbar\"\n aria-valuenow={Math.round(pct * 100)}\n aria-valuemin={0}\n aria-valuemax={100}\n >\n <div\n className=\"h-full rounded-full transition-all\"\n style={{ width: `${pct * 100}%`, backgroundColor: color }}\n />\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubscriberBotContext.js","names":[],"sources":["../../../src/subscriberbot/context/SubscriberBotContext.tsx"],"sourcesContent":["/**\n * SubscriberBot Context Provider\n * @module subscriberbot/context\n */\n\nimport type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useCallback } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router';\nimport type { SubscriberBotRootProps } from '../types';\nimport { joinPath } from '../utils/navigation';\n\n// ============================================================================\n// Context\n// ============================================================================\n\ninterface SubscriberBotContextValue extends Omit<\n SubscriberBotRootProps,\n 'registerNavItems' | 'registerFooterItems' | 'navigate'\n> {\n isStandalone: boolean;\n /** Shell's navigate function (only available in integrated mode) */\n navigate?: (path: string) => void;\n /** Navigate to a path relative to basePath */\n navigateTo: (relativePath: string) => void;\n /** Get the full path for a relative path */\n getPath: (relativePath: string) => string;\n}\n\nconst SubscriberBotContext = createContext<SubscriberBotContextValue | null>(null);\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useSubscriberBot(): SubscriberBotContextValue {\n const context = useContext(SubscriberBotContext);\n if (!context) {\n throw new Error('useSubscriberBot must be used within a SubscriberBotProvider');\n }\n return context;\n}\n\n// ============================================================================\n// Provider Component\n// ============================================================================\n\ntype ProviderProps = Omit<SubscriberBotRootProps, 'registerNavItems' | 'registerFooterItems'> & {\n isStandalone: boolean;\n};\n\n/** Provider for standalone mode (has access to MemoryRouter) */\nconst StandaloneProviderInner: FC<PropsWithChildren<ProviderProps>> = ({\n children,\n basePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n}) => {\n const routerNavigate = useRouterNavigate();\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n const fullPath = joinPath(effectiveBasePath, relativePath);\n routerNavigate(fullPath);\n },\n [effectiveBasePath, routerNavigate]\n );\n\n const contextValue = useMemo<SubscriberBotContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate: undefined,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n isStandalone: true,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <SubscriberBotContext.Provider value={contextValue}>{children}</SubscriberBotContext.Provider>\n );\n};\n\n/** Provider for integrated mode (shell provides navigate) */\nconst IntegratedProviderInner: FC<\n PropsWithChildren<ProviderProps & { navigate: (path: string) => void }>\n> = ({\n children,\n basePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n}) => {\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n const fullPath = joinPath(effectiveBasePath, relativePath);\n navigate(fullPath);\n },\n [effectiveBasePath, navigate]\n );\n\n const contextValue = useMemo<SubscriberBotContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n isStandalone: false,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <SubscriberBotContext.Provider value={contextValue}>{children}</SubscriberBotContext.Provider>\n );\n};\n\nexport const SubscriberBotProvider: FC<PropsWithChildren<SubscriberBotRootProps>> = (props) => {\n const { children, navigate } = props;\n // Standalone mode: no navigate function provided (MFE manages its own router)\n // Integrated mode: navigate function provided by shell (shell provides router)\n const isStandalone = !navigate;\n\n // Standalone mode: wrap in MemoryRouter\n if (isStandalone) {\n return (\n <MemoryRouter>\n <StandaloneProviderInner {...props} isStandalone={true}>\n {children}\n </StandaloneProviderInner>\n </MemoryRouter>\n );\n }\n\n // Integrated mode: shell provides router\n return (\n <IntegratedProviderInner {...props} navigate={navigate} isStandalone={false}>\n {children}\n </IntegratedProviderInner>\n );\n};\n"],"mappings":";;;;;AA4BA,IAAM,IAAuB,EAAgD,
|
|
1
|
+
{"version":3,"file":"SubscriberBotContext.js","names":[],"sources":["../../../src/subscriberbot/context/SubscriberBotContext.tsx"],"sourcesContent":["/**\n * SubscriberBot Context Provider\n * @module subscriberbot/context\n */\n\nimport type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useCallback } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router';\nimport type { SubscriberBotRootProps } from '../types';\nimport { joinPath } from '../utils/navigation';\n\n// ============================================================================\n// Context\n// ============================================================================\n\ninterface SubscriberBotContextValue extends Omit<\n SubscriberBotRootProps,\n 'registerNavItems' | 'registerFooterItems' | 'navigate'\n> {\n isStandalone: boolean;\n /** Shell's navigate function (only available in integrated mode) */\n navigate?: (path: string) => void;\n /** Navigate to a path relative to basePath */\n navigateTo: (relativePath: string) => void;\n /** Get the full path for a relative path */\n getPath: (relativePath: string) => string;\n}\n\nconst SubscriberBotContext = createContext<SubscriberBotContextValue | null>(null);\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useSubscriberBot(): SubscriberBotContextValue {\n const context = useContext(SubscriberBotContext);\n if (!context) {\n throw new Error('useSubscriberBot must be used within a SubscriberBotProvider');\n }\n return context;\n}\n\n// ============================================================================\n// Provider Component\n// ============================================================================\n\ntype ProviderProps = Omit<SubscriberBotRootProps, 'registerNavItems' | 'registerFooterItems'> & {\n isStandalone: boolean;\n};\n\n/** Provider for standalone mode (has access to MemoryRouter) */\nconst StandaloneProviderInner: FC<PropsWithChildren<ProviderProps>> = ({\n children,\n basePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n}) => {\n const routerNavigate = useRouterNavigate();\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n const fullPath = joinPath(effectiveBasePath, relativePath);\n routerNavigate(fullPath);\n },\n [effectiveBasePath, routerNavigate]\n );\n\n const contextValue = useMemo<SubscriberBotContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate: undefined,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n isStandalone: true,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <SubscriberBotContext.Provider value={contextValue}>{children}</SubscriberBotContext.Provider>\n );\n};\n\n/** Provider for integrated mode (shell provides navigate) */\nconst IntegratedProviderInner: FC<\n PropsWithChildren<ProviderProps & { navigate: (path: string) => void }>\n> = ({\n children,\n basePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n}) => {\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n const fullPath = joinPath(effectiveBasePath, relativePath);\n navigate(fullPath);\n },\n [effectiveBasePath, navigate]\n );\n\n const contextValue = useMemo<SubscriberBotContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n isStandalone: false,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <SubscriberBotContext.Provider value={contextValue}>{children}</SubscriberBotContext.Provider>\n );\n};\n\nexport const SubscriberBotProvider: FC<PropsWithChildren<SubscriberBotRootProps>> = (props) => {\n const { children, navigate } = props;\n // Standalone mode: no navigate function provided (MFE manages its own router)\n // Integrated mode: navigate function provided by shell (shell provides router)\n const isStandalone = !navigate;\n\n // Standalone mode: wrap in MemoryRouter\n if (isStandalone) {\n return (\n <MemoryRouter>\n <StandaloneProviderInner {...props} isStandalone={true}>\n {children}\n </StandaloneProviderInner>\n </MemoryRouter>\n );\n }\n\n // Integrated mode: shell provides router\n return (\n <IntegratedProviderInner {...props} navigate={navigate} isStandalone={false}>\n {children}\n </IntegratedProviderInner>\n );\n};\n"],"mappings":";;;;;AA4BA,IAAM,IAAuB,EAAgD,IAAI;AAMjF,SAAgB,IAA8C;CAC5D,IAAM,IAAU,EAAW,CAAoB;CAC/C,IAAI,CAAC,GACH,MAAU,MAAM,8DAA8D;CAEhF,OAAO;AACT;AAWA,IAAM,KAAiE,EACrE,aACA,aACA,gBACA,aACA,gBACA,kBACA,mBACI;CACJ,IAAM,IAAiB,EAAkB,GACnC,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,CAAY,GAClE,CAAC,CAAiB,CACpB,GAEM,IAAa,GAChB,MAAyB;EAExB,EADiB,EAAS,GAAmB,CAC9B,CAAQ;CACzB,GACA,CAAC,GAAmB,CAAc,CACpC,GAEM,IAAe,SACZ;EACL,UAAU;EACV,UAAU,KAAA;EACV;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;CACF,IACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CACF;CAEA,OACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;CAAwC,CAAA;AAEjG,GAGM,KAED,EACH,aACA,aACA,aACA,gBACA,aACA,gBACA,kBACA,mBACI;CACJ,IAAM,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,CAAY,GAClE,CAAC,CAAiB,CACpB,GAEM,IAAa,GAChB,MAAyB;EAExB,EADiB,EAAS,GAAmB,CACpC,CAAQ;CACnB,GACA,CAAC,GAAmB,CAAQ,CAC9B,GAEM,IAAe,SACZ;EACL,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;CACF,IACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CACF;CAEA,OACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;CAAwC,CAAA;AAEjG,GAEa,KAAwE,MAAU;CAC7F,IAAM,EAAE,aAAU,gBAAa;CAiB/B,OAdsB,IAepB,kBAAC,GAAD;EAAyB,GAAI;EAAiB;EAAU,cAAc;EACnE;CACsB,CAAA,IAZvB,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAyB,GAAI;EAAO,cAAc;EAC/C;CACsB,CAAA,EACb,CAAA;AAUpB"}
|