@burdenoff/microfe-subscriberbot 2026.731.1 → 2026.801.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.
@@ -1,7 +1,7 @@
1
1
  import { useSubscriberBot as e } from "./context/SubscriberBotContext.js";
2
2
  import { DashboardPage as t } from "./pages/DashboardPage.js";
3
3
  import { Suspense as n, lazy as r } from "react";
4
- import { Navigate as i, Route as a, Routes as o, useLocation as s } from "react-router-dom";
4
+ import { Navigate as i, Route as a, Routes as o, useLocation as s } from "react-router";
5
5
  import { jsx as c, jsxs as l } from "react/jsx-runtime";
6
6
  import { Spinner as u } from "@burdenoff/fe-libs/ui";
7
7
  //#region src/subscriberbot/SubscriberBotRoutes.tsx
@@ -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-dom';\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
+ {"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,uBACZ,EAAE,CACJ,EACK,IAAsB,QAC1B,OAAO,gDAA6C,MAAM,OAAO,EAC/D,SAAS,EAAE,qBACZ,EAAE,CACJ,EACK,IAAyB,QAC7B,OAAO,mDAAgD,MAAM,OAAO,EAClE,SAAS,EAAE,wBACZ,EAAE,CACJ,EAGK,IAAoB,QACxB,OAAO,0CAAuC,MAAM,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,CAC9F,EACK,IAAkB,QACtB,OAAO,wCAAqC,MAAM,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAC1F,EACK,IAAqB,QACzB,OAAO,2CAAwC,MAAM,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,CAChG,EAGK,IAAY,QAAW,OAAO,wBAAqB,MAAM,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,EAC3F,IAAe,QACnB,OAAO,2BAAwB,MAAM,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,CAC1E,EACK,IAAgB,QACpB,OAAO,4BAAyB,MAAM,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,CAC5E,EAGK,IAAiB,QACrB,OAAO,oCAAiC,MAAM,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,CACrF,EACK,IAAe,QACnB,OAAO,kCAA+B,MAAM,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,CACjF,EACK,IAAkB,QACtB,OAAO,qCAAkC,MAAM,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,CACvF,EAGK,IAAe,QACnB,OAAO,2BAAwB,MAAM,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,CAC1E,EACK,IAAiB,QACrB,OAAO,6BAA0B,MAAM,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAC9E,EAEK,UACJ,kBAAC,OAAD;CAAK,WAAU;WACb,kBAAC,GAAD,EAAS,MAAK,MAAO,CAAA;CACjB,CAAA,EAGK,UAAgC;CAC3C,IAAM,EAAE,aAAU,oBAAiB,GAAkB,EAC/C,IAAW,GAAa,EAGxB,IAAqB,MAAa,MAAM,MAAM,KAAY,IAAI,QAAQ,OAAO,GAAG;AAYtF,QATI,KAAsB,CAAC,KAIrB,EAFF,EAAS,aAAa,KACtB,EAAS,SAAS,WAAW,IAAqB,IAAI,IAE/C,OAKT,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GAAU,UAAU,kBAAC,GAAD,EAAc,CAAA;aAChC,kBAAC,GAAD,EAAA,UAAA;IAEE,kBAAC,GAAD;KAAO,OAAA;KAAM,SAAS,kBAAC,GAAD,EAAiB,CAAA;KAAI,CAAA;IAG3C,kBAAC,GAAD;KAAO,MAAK;KAAgB,SAAS,kBAAC,GAAD,EAAyB,CAAA;KAAI,CAAA;IAClE,kBAAC,GAAD;KAAO,MAAK;KAAoB,SAAS,kBAAC,GAAD,EAAuB,CAAA;KAAI,CAAA;IACpE,kBAAC,GAAD;KAAO,MAAK;KAAoB,SAAS,kBAAC,GAAD,EAA0B,CAAA;KAAI,CAAA;IAGvE,kBAAC,GAAD;KAAO,MAAK;KAAY,SAAS,kBAAC,GAAD,EAAqB,CAAA;KAAI,CAAA;IAC1D,kBAAC,GAAD;KAAO,MAAK;KAAgB,SAAS,kBAAC,GAAD,EAAmB,CAAA;KAAI,CAAA;IAC5D,kBAAC,GAAD;KAAO,MAAK;KAAgB,SAAS,kBAAC,GAAD,EAAsB,CAAA;KAAI,CAAA;IAG/D,kBAAC,GAAD;KAAO,MAAK;KAAQ,SAAS,kBAAC,GAAD,EAAa,CAAA;KAAI,CAAA;IAG9C,kBAAC,GAAD;KAAO,MAAK;KAAW,SAAS,kBAAC,GAAD,EAAgB,CAAA;KAAI,CAAA;IAGpD,kBAAC,GAAD;KAAO,MAAK;KAAW,SAAS,kBAAC,GAAD,EAAgB,CAAA;KAAI,CAAA;IAGpD,kBAAC,GAAD;KAAO,MAAK;KAAQ,SAAS,kBAAC,GAAD,EAAkB,CAAA;KAAI,CAAA;IAGnD,kBAAC,GAAD;KAAO,MAAK;KAAS,SAAS,kBAAC,GAAD,EAAkB,CAAA;KAAI,CAAA;IACpD,kBAAC,GAAD;KAAO,MAAK;KAAa,SAAS,kBAAC,GAAD,EAAgB,CAAA;KAAI,CAAA;IACtD,kBAAC,GAAD;KAAO,MAAK;KAAa,SAAS,kBAAC,GAAD,EAAmB,CAAA;KAAI,CAAA;IAGzD,kBAAC,GAAD;KAAO,MAAK;KAAY,SAAS,kBAAC,GAAD,EAAiB,CAAA;KAAI,CAAA;IAGtD,kBAAC,GAAD;KAAO,MAAK;KAAI,SAAS,kBAAC,GAAD;MAAU,IAAG;MAAG,SAAA;MAAU,CAAA;KAAI,CAAA;IAChD,EAAA,CAAA;GACA,CAAA;EACP,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import { joinPath as e } from "../utils/navigation.js";
2
2
  import { createContext as t, useCallback as n, useContext as r, useMemo as i } from "react";
3
- import { MemoryRouter as a, useNavigate as o } from "react-router-dom";
3
+ import { MemoryRouter as a, useNavigate as o } from "react-router";
4
4
  import { jsx as s } from "react/jsx-runtime";
5
5
  //#region src/subscriberbot/context/SubscriberBotContext.tsx
6
6
  var c = t(null);
@@ -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-dom';\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,KAAK;AAMlF,SAAgB,IAA8C;CAC5D,IAAM,IAAU,EAAW,EAAqB;AAChD,KAAI,CAAC,EACH,OAAU,MAAM,+DAA+D;AAEjF,QAAO;;AAYT,IAAM,KAAiE,EACrE,aACA,aACA,gBACA,aACA,gBACA,kBACA,mBACI;CACJ,IAAM,IAAiB,GAAmB,EACpC,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AAExB,IADiB,EAAS,GAAmB,EAAa,CAClC;IAE1B,CAAC,GAAmB,EAAe,CACpC,EAEK,IAAe,SACZ;EACL,UAAU;EACV,UAAU,KAAA;EACV;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;EAAyC,CAAA;GAK5F,KAED,EACH,aACA,aACA,aACA,gBACA,aACA,gBACA,kBACA,mBACI;CACJ,IAAM,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AAExB,IADiB,EAAS,GAAmB,EAAa,CACxC;IAEpB,CAAC,GAAmB,EAAS,CAC9B,EAEK,IAAe,SACZ;EACL,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;EAAyC,CAAA;GAIrF,KAAwE,MAAU;CAC7F,IAAM,EAAE,aAAU,gBAAa;AAiB/B,QAdsB,IAepB,kBAAC,GAAD;EAAyB,GAAI;EAAiB;EAAU,cAAc;EACnE;EACuB,CAAA,GAZxB,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAyB,GAAI;EAAO,cAAc;EAC/C;EACuB,CAAA,EACb,CAAA"}
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,KAAK;AAMlF,SAAgB,IAA8C;CAC5D,IAAM,IAAU,EAAW,EAAqB;AAChD,KAAI,CAAC,EACH,OAAU,MAAM,+DAA+D;AAEjF,QAAO;;AAYT,IAAM,KAAiE,EACrE,aACA,aACA,gBACA,aACA,gBACA,kBACA,mBACI;CACJ,IAAM,IAAiB,GAAmB,EACpC,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AAExB,IADiB,EAAS,GAAmB,EAAa,CAClC;IAE1B,CAAC,GAAmB,EAAe,CACpC,EAEK,IAAe,SACZ;EACL,UAAU;EACV,UAAU,KAAA;EACV;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;EAAyC,CAAA;GAK5F,KAED,EACH,aACA,aACA,aACA,gBACA,aACA,gBACA,kBACA,mBACI;CACJ,IAAM,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AAExB,IADiB,EAAS,GAAmB,EAAa,CACxC;IAEpB,CAAC,GAAmB,EAAS,CAC9B,EAEK,IAAe,SACZ;EACL,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;EAAyC,CAAA;GAIrF,KAAwE,MAAU;CAC7F,IAAM,EAAE,aAAU,gBAAa;AAiB/B,QAdsB,IAepB,kBAAC,GAAD;EAAyB,GAAI;EAAiB;EAAU,cAAc;EACnE;EACuB,CAAA,GAZxB,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAyB,GAAI;EAAO,cAAc;EAC/C;EACuB,CAAA,EACb,CAAA"}
@@ -11,7 +11,7 @@ import "../../components/index.js";
11
11
  import { useAgent as f, useCreatePolicy as p, useSetAgentStatus as m } from "../../hooks/useSubscriberBotApi.js";
12
12
  import { useState as h } from "react";
13
13
  import { ArrowLeft as g, ArrowLeftRight as _, Bot as v, Building2 as y, Clock as b, ConciergeBell as x, DollarSign as S, Handshake as C, History as w, ListChecks as T, Mail as E, Plus as D, RefreshCw as O, Search as k, ShieldCheck as A, Zap as j } from "lucide-react";
14
- import { useParams as M } from "react-router-dom";
14
+ import { useParams as M } from "react-router";
15
15
  import { jsx as N, jsxs as P } from "react/jsx-runtime";
16
16
  import { GlassCard as F } from "@burdenoff/fe-libs/ui";
17
17
  //#region src/subscriberbot/pages/agents/AgentDetailPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"AgentDetailPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/agents/AgentDetailPage.tsx"],"sourcesContent":["/**\n * Agent detail\n * @module subscriberbot/pages/agents\n *\n * A single AI agent: its kind, posture, last run, and the autonomous policies\n * that bound it. Reads `subAgent(id)`; changes posture via `subSetAgentStatus`.\n * The \"Run history\" timeline is derived from the agent's real `lastRunAt` and\n * each policy's real `lastTriggeredAt` — never mocked.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n Bot,\n Search,\n RefreshCw,\n DollarSign,\n Mail,\n Building2,\n ShieldCheck,\n Handshake,\n ArrowLeftRight,\n ConciergeBell,\n Plus,\n Clock,\n Zap,\n ListChecks,\n History,\n type LucideIcon,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n EmptyState,\n Modal,\n MetricsCard,\n Timeline,\n LoadingView,\n ErrorView,\n Section,\n type TimelineItem,\n} from '../../components';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport { useAgent, useSetAgentStatus, useCreatePolicy } from '../../hooks/useSubscriberBotApi';\nimport { formatEnumLabel, formatRelativeTime } from '../../utils/formatters';\nimport type { SubAgentKind, SubAgentStatus, SubPolicyAction, SubPolicy } from '../../types';\n\nconst STATUS_OPTIONS: SubAgentStatus[] = ['DISABLED', 'SUGGEST_ONLY', 'AUTONOMOUS'];\n\nconst POLICY_ACTIONS: SubPolicyAction[] = [\n 'ALERT',\n 'CREATE_TASK',\n 'RENEW',\n 'CANCEL',\n 'PAUSE',\n 'UPGRADE',\n 'DOWNGRADE',\n];\n\nconst KIND_ICON: Record<SubAgentKind, LucideIcon> = {\n DISCOVERY: Search,\n RENEWAL: RefreshCw,\n COST: DollarSign,\n NEWSLETTER: Mail,\n VENDOR: Building2,\n COMPLIANCE: ShieldCheck,\n NEGOTIATION: Handshake,\n MIGRATION: ArrowLeftRight,\n CONCIERGE: ConciergeBell,\n};\n\n/** Map a policy action to a timeline tone (semantic only). */\nfunction actionTone(action: SubPolicyAction): NonNullable<TimelineItem['tone']> {\n switch (action) {\n case 'RENEW':\n case 'UPGRADE':\n return 'positive';\n case 'CANCEL':\n case 'PAUSE':\n case 'DOWNGRADE':\n case 'ALERT':\n return 'warning';\n default:\n return 'default';\n }\n}\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\n// ---------------------------------------------------------------------------\n// Read-only enabled indicator (there is no policy-toggle mutation on the svc\n// contract, so this reflects the real `isEnabled` state without pretending to\n// mutate it).\n// ---------------------------------------------------------------------------\nfunction EnabledPill({ enabled }: { enabled: boolean }) {\n return (\n <span\n className={\n enabled\n ? 'inline-flex items-center gap-1.5 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text'\n : 'inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary'\n }\n >\n <span\n aria-hidden\n className=\"size-1.5 rounded-full bg-current\"\n style={{ opacity: enabled ? 1 : 0.6 }}\n />\n {enabled ? 'Enabled' : 'Disabled'}\n </span>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Add policy modal → subCreatePolicy\n// ---------------------------------------------------------------------------\n\nfunction AddPolicyModal({ agentId, onClose }: { agentId: string; onClose: () => void }) {\n const create = useCreatePolicy();\n const [name, setName] = useState('');\n const [action, setAction] = useState<SubPolicyAction>('ALERT');\n const [description, setDescription] = useState('');\n const [condition, setCondition] = useState('');\n const [isEnabled, setIsEnabled] = useState(true);\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError('A policy name is required.');\n return;\n }\n let parsedCondition: Record<string, unknown> | undefined;\n if (condition.trim()) {\n try {\n parsedCondition = JSON.parse(condition) as Record<string, unknown>;\n } catch {\n setFormError('Condition must be valid JSON (e.g. {\"amountOver\": 100}).');\n return;\n }\n }\n create.mutate(\n {\n agentId,\n name: name.trim(),\n action,\n isEnabled,\n ...(description.trim() ? { description: description.trim() } : {}),\n ...(parsedCondition ? { condition: parsedCondition } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not add policy.'),\n }\n );\n };\n\n return (\n <Modal title=\"Add policy\" onClose={onClose} maxWidthClassName=\"max-w-xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"policy-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"policy-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Alert on price hike over 20%\"\n className={inputCls}\n required\n />\n </div>\n <div>\n <label htmlFor=\"policy-action\" className={labelCls}>\n Action\n </label>\n <select\n id=\"policy-action\"\n value={action}\n onChange={(e) => setAction(e.target.value as SubPolicyAction)}\n className={inputCls}\n >\n {POLICY_ACTIONS.map((a) => (\n <option key={a} value={a}>\n {a.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"policy-condition\" className={labelCls}>\n Condition <span className=\"text-text-secondary\">(optional JSON)</span>\n </label>\n <textarea\n id=\"policy-condition\"\n value={condition}\n onChange={(e) => setCondition(e.target.value)}\n rows={3}\n placeholder={'{ \"priceIncreasePct\": 20 }'}\n className={`${inputCls} font-mono`}\n />\n </div>\n <div>\n <label htmlFor=\"policy-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"policy-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={inputCls}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={isEnabled}\n onChange={(e) => setIsEnabled(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Enabled\n </label>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Adding…' : 'Add policy'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Policy card\n// ---------------------------------------------------------------------------\nfunction PolicyCard({ policy }: { policy: SubPolicy }) {\n return (\n <GlassCard treatment=\"metric\" className=\"flex flex-col p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"text-sm font-semibold text-text-primary\">{policy.name}</p>\n <EnabledPill enabled={policy.isEnabled} />\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <span className=\"inline-flex items-center gap-1.5 rounded-md bg-[var(--color-accent-soft)] px-2 py-0.5 text-[11px] font-medium text-text-primary\">\n <ShieldCheck className=\"h-3 w-3\" aria-hidden /> {formatEnumLabel(policy.action)}\n </span>\n <span className=\"inline-flex items-center gap-1 rounded-md bg-bg-sunken px-2 py-0.5 text-[11px] font-medium text-text-secondary tabular-nums\">\n Triggered {policy.triggerCount}×\n </span>\n </div>\n {policy.description && (\n <p className=\"mt-2 line-clamp-2 text-xs text-text-secondary\">{policy.description}</p>\n )}\n <p className=\"mt-2 flex items-center gap-1.5 text-xs text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5\" aria-hidden />\n {policy.lastTriggeredAt\n ? `Last triggered ${formatRelativeTime(policy.lastTriggeredAt)}`\n : 'Not yet triggered'}\n </p>\n </GlassCard>\n );\n}\n\nexport function AgentDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useSubscriberBot();\n const { data: agent, isLoading, isError, refetch } = useAgent(id);\n const setAgentStatus = useSetAgentStatus();\n const [actionError, setActionError] = useState<string | null>(null);\n const [showAddPolicy, setShowAddPolicy] = useState(false);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Agent\">\n <LoadingView label=\"Loading agent…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Agent\">\n <ErrorView message=\"Could not load this agent.\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!agent) {\n return (\n <PageLayout title=\"Agent\">\n <EmptyState\n illustration=\"empty-data\"\n title=\"Agent not found\"\n description=\"It may have been removed, or you may not have access to it.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/agents')}\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 Back to agents\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n const a = agent;\n const Icon = KIND_ICON[a.kind] ?? Bot;\n const policies = a.policies ?? [];\n const enabledCount = policies.filter((p) => p.isEnabled).length;\n const totalTriggers = policies.reduce((s, p) => s + (p.triggerCount || 0), 0);\n\n // --- Run history: derived from real timestamps, sorted most-recent first --\n const events: Array<{ ts: number; item: TimelineItem }> = [];\n if (a.lastRunAt) {\n events.push({\n ts: new Date(a.lastRunAt).getTime(),\n item: {\n title: 'Agent run',\n sub: 'Latest autonomous cycle',\n meta: formatRelativeTime(a.lastRunAt),\n tone: 'positive',\n },\n });\n }\n for (const p of policies) {\n if (p.lastTriggeredAt) {\n events.push({\n ts: new Date(p.lastTriggeredAt).getTime(),\n item: {\n title: p.name,\n sub: `${formatEnumLabel(p.action)} · fired ${p.triggerCount}×`,\n meta: formatRelativeTime(p.lastTriggeredAt),\n tone: actionTone(p.action),\n },\n });\n }\n }\n events.sort((x, y) => y.ts - x.ts);\n const timelineItems = events.map((e) => e.item);\n\n return (\n <PageLayout\n title={a.name}\n description={`${formatEnumLabel(a.kind)} agent`}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/agents')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n }\n >\n {/* Identity + posture header */}\n <GlassCard treatment=\"glass\" glow={a.status === 'AUTONOMOUS'} className=\"p-5\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex items-start gap-4\">\n <span className=\"flex size-12 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\">\n <Icon className=\"h-6 w-6\" />\n </span>\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-lg font-semibold text-text-primary\">{a.name}</p>\n <StatusBadge status={a.status} />\n </div>\n <p className=\"mt-1 flex items-center gap-1.5 text-xs text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5\" aria-hidden />\n {a.lastRunAt ? `Last run ${formatRelativeTime(a.lastRunAt)}` : 'Never run'}\n </p>\n {a.description && (\n <p className=\"mt-2 max-w-2xl text-sm text-text-secondary\">{a.description}</p>\n )}\n </div>\n </div>\n\n {/* Posture control */}\n <div className=\"w-full sm:w-56 sm:shrink-0\">\n <label\n htmlFor=\"agent-posture\"\n className=\"mb-1 block text-xs font-medium text-text-secondary\"\n >\n Operating posture\n </label>\n <select\n id=\"agent-posture\"\n value={a.status}\n disabled={setAgentStatus.isPending}\n onChange={(e) => {\n setActionError(null);\n setAgentStatus.mutate(\n { id: a.id, status: e.target.value as SubAgentStatus },\n {\n onError: (err) =>\n setActionError(\n err instanceof Error ? err.message : 'Could not change posture.'\n ),\n }\n );\n }}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] disabled:opacity-50\"\n >\n {STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {formatEnumLabel(s)}\n </option>\n ))}\n </select>\n <p className=\"mt-1.5 text-[11px] leading-snug text-text-secondary\">\n Disabled never acts · Suggest-only proposes · Autonomous acts within its policies.\n </p>\n </div>\n </div>\n </GlassCard>\n\n {actionError && (\n <div className=\"mt-4 rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {actionError}\n </div>\n )}\n\n {/* Policy stats (computed) */}\n <div className=\"mt-6 grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={<ListChecks className=\"h-5 w-5\" />}\n value={policies.length}\n label=\"Policies\"\n />\n <MetricsCard\n icon={<ShieldCheck className=\"h-5 w-5\" />}\n value={enabledCount}\n label=\"Enabled\"\n />\n <MetricsCard\n icon={<Zap className=\"h-5 w-5\" />}\n value={totalTriggers}\n label=\"Total triggers\"\n />\n </div>\n\n {/* Policies + Run history */}\n <div className=\"mt-8 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <Section\n title=\"Policies\"\n description=\"The autonomous rules that bound this agent\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddPolicy(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Add policy\n </button>\n }\n >\n {policies.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No policies\"\n description=\"Policies define what this agent may do autonomously, and what it must alert on.\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddPolicy(true)}\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 Add policy\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {policies.map((policy) => (\n <PolicyCard key={policy.id} policy={policy} />\n ))}\n </div>\n )}\n </Section>\n </div>\n\n <div className=\"lg:col-span-1\">\n <Section title=\"Run history\" description=\"Recent agent activity\">\n <GlassCard treatment=\"metric\" className=\"p-5\">\n {timelineItems.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center gap-2 py-6 text-center\">\n <History className=\"h-8 w-8 text-text-secondary\" aria-hidden />\n <p className=\"text-sm font-medium text-text-primary\">No runs yet</p>\n <p className=\"text-xs text-text-secondary\">\n Activity appears here once the agent runs or a policy fires.\n </p>\n </div>\n ) : (\n <Timeline items={timelineItems} />\n )}\n </GlassCard>\n </Section>\n </div>\n </div>\n\n {showAddPolicy && <AddPolicyModal agentId={a.id} onClose={() => setShowAddPolicy(false)} />}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiDA,IAAM,IAAmC;CAAC;CAAY;CAAgB;CAAa,EAE7E,IAAoC;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,IAA8C;CAClD,WAAW;CACX,SAAS;CACT,MAAM;CACN,YAAY;CACZ,QAAQ;CACR,YAAY;CACZ,aAAa;CACb,WAAW;CACX,WAAW;CACZ;AAGD,SAAS,EAAW,GAA4D;AAC9E,SAAQ,GAAR;EACE,KAAK;EACL,KAAK,UACH,QAAO;EACT,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,QACH,QAAO;EACT,QACE,QAAO;;;AAIb,IAAM,IACJ,wMACI,IAAW;AAOjB,SAAS,EAAY,EAAE,cAAiC;AACtD,QACE,kBAAC,QAAD;EACE,WACE,IACI,yIACA;YAJR,CAOE,kBAAC,QAAD;GACE,eAAA;GACA,WAAU;GACV,OAAO,EAAE,SAAS,IAAU,IAAI,IAAK;GACrC,CAAA,EACD,IAAU,YAAY,WAClB;;;AAQX,SAAS,EAAe,EAAE,YAAS,cAAqD;CACtF,IAAM,IAAS,GAAiB,EAC1B,CAAC,GAAM,KAAW,EAAS,GAAG,EAC9B,CAAC,GAAQ,KAAa,EAA0B,QAAQ,EACxD,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAW,KAAgB,EAAS,GAAG,EACxC,CAAC,GAAW,KAAgB,EAAS,GAAK,EAC1C,CAAC,GAAW,KAAgB,EAAwB,KAAK;AAmC/D,QACE,kBAAC,GAAD;EAAO,OAAM;EAAsB;EAAS,mBAAkB;YAC5D,kBAAC,QAAD;GAAM,WAnCY,MAAuB;AAG3C,QAFA,EAAE,gBAAgB,EAClB,EAAa,KAAK,EACd,CAAC,EAAK,MAAM,EAAE;AAChB,OAAa,6BAA6B;AAC1C;;IAEF,IAAI;AACJ,QAAI,EAAU,MAAM,CAClB,KAAI;AACF,SAAkB,KAAK,MAAM,EAAU;YACjC;AACN,OAAa,6DAA2D;AACxE;;AAGJ,MAAO,OACL;KACE;KACA,MAAM,EAAK,MAAM;KACjB;KACA;KACA,GAAI,EAAY,MAAM,GAAG,EAAE,aAAa,EAAY,MAAM,EAAE,GAAG,EAAE;KACjE,GAAI,IAAkB,EAAE,WAAW,GAAiB,GAAG,EAAE;KAC1D,EACD;KACE,iBAAiB,GAAS;KAC1B,UAAU,MACR,EAAa,aAAe,QAAQ,EAAI,UAAU,wBAAwB;KAC7E,CACF;;GAK+B,WAAU;aAAxC;IACG,KACC,kBAAC,OAAD;KAAK,WAAU;eACZ;KACG,CAAA;IAER,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAc,WAAW;eAAxC,CAAkD,SAC3C,kBAAC,QAAD;MAAM,WAAU;gBAAyB;MAAQ,CAAA,CAChD;QACR,kBAAC,SAAD;KACE,IAAG;KACH,MAAK;KACL,OAAO;KACP,WAAW,MAAM,EAAQ,EAAE,OAAO,MAAM;KACxC,aAAY;KACZ,WAAW;KACX,UAAA;KACA,CAAA,CACE,EAAA,CAAA;IACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAgB,WAAW;eAAU;KAE5C,CAAA,EACR,kBAAC,UAAD;KACE,IAAG;KACH,OAAO;KACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAyB;KAC7D,WAAW;eAEV,EAAe,KAAK,MACnB,kBAAC,UAAD;MAAgB,OAAO;gBACpB,EAAE,QAAQ,MAAM,IAAI;MACd,EAFI,EAEJ,CACT;KACK,CAAA,CACL,EAAA,CAAA;IACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAmB,WAAW;eAA7C,CAAuD,cAC3C,kBAAC,QAAD;MAAM,WAAU;gBAAsB;MAAsB,CAAA,CAChE;QACR,kBAAC,YAAD;KACE,IAAG;KACH,OAAO;KACP,WAAW,MAAM,EAAa,EAAE,OAAO,MAAM;KAC7C,MAAM;KACN,aAAa;KACb,WAAW,GAAG,EAAS;KACvB,CAAA,CACE,EAAA,CAAA;IACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAc,WAAW;eAAU;KAE1C,CAAA,EACR,kBAAC,YAAD;KACE,IAAG;KACH,OAAO;KACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;KAC/C,MAAM;KACN,WAAW;KACX,CAAA,CACE,EAAA,CAAA;IACN,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,SAAD;MACE,MAAK;MACL,SAAS;MACT,WAAW,MAAM,EAAa,EAAE,OAAO,QAAQ;MAC/C,WAAU;MACV,CAAA,EAAA,UAEI;;IACR,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;gBACX;MAEQ,CAAA,EACT,kBAAC,UAAD;MACE,MAAK;MACL,UAAU,EAAO;MACjB,WAAU;gBAET,EAAO,YAAY,YAAY;MACzB,CAAA,CACL;;IACD;;EACD,CAAA;;AAOZ,SAAS,EAAW,EAAE,aAAiC;AACrD,QACE,kBAAC,GAAD;EAAW,WAAU;EAAS,WAAU;YAAxC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAA2C,EAAO;KAAS,CAAA,EACxE,kBAAC,GAAD,EAAa,SAAS,EAAO,WAAa,CAAA,CACtC;;GACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACE,kBAAC,GAAD;OAAa,WAAU;OAAU,eAAA;OAAc,CAAA;;MAAE,EAAgB,EAAO,OAAO;MAC1E;QACP,kBAAC,QAAD;KAAM,WAAU;eAAhB;MAA8I;MACjI,EAAO;MAAa;MAC1B;OACH;;GACL,EAAO,eACN,kBAAC,KAAD;IAAG,WAAU;cAAiD,EAAO;IAAgB,CAAA;GAEvF,kBAAC,KAAD;IAAG,WAAU;cAAb,CACE,kBAAC,GAAD;KAAO,WAAU;KAAc,eAAA;KAAc,CAAA,EAC5C,EAAO,kBACJ,kBAAkB,EAAmB,EAAO,gBAAgB,KAC5D,oBACF;;GACM;;;AAIhB,SAAgB,IAAkB;CAChC,IAAM,EAAE,UAAO,GAA2B,EACpC,EAAE,kBAAe,GAAkB,EACnC,EAAE,MAAM,GAAO,cAAW,YAAS,eAAY,EAAS,EAAG,EAC3D,IAAiB,GAAmB,EACpC,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAe,KAAoB,EAAS,GAAM;AAEzD,KAAI,EACF,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAa,OAAM,kBAAmB,CAAA;EAC3B,CAAA;AAIjB,KAAI,EACF,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GAAW,SAAQ;GAA6B,eAAe,KAAK,GAAS;GAAI,CAAA;EACtE,CAAA;AAIjB,KAAI,CAAC,EACH,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,UAAU;IACpC,WAAU;cACX;IAEQ,CAAA;GAEX,CAAA;EACS,CAAA;CAIjB,IAAM,IAAI,GACJ,IAAO,EAAU,EAAE,SAAS,GAC5B,IAAW,EAAE,YAAY,EAAE,EAC3B,IAAe,EAAS,QAAQ,MAAM,EAAE,UAAU,CAAC,QACnD,IAAgB,EAAS,QAAQ,GAAG,MAAM,KAAK,EAAE,gBAAgB,IAAI,EAAE,EAGvE,IAAoD,EAAE;AAC5D,CAAI,EAAE,aACJ,EAAO,KAAK;EACV,IAAI,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS;EACnC,MAAM;GACJ,OAAO;GACP,KAAK;GACL,MAAM,EAAmB,EAAE,UAAU;GACrC,MAAM;GACP;EACF,CAAC;AAEJ,MAAK,IAAM,KAAK,EACd,CAAI,EAAE,mBACJ,EAAO,KAAK;EACV,IAAI,IAAI,KAAK,EAAE,gBAAgB,CAAC,SAAS;EACzC,MAAM;GACJ,OAAO,EAAE;GACT,KAAK,GAAG,EAAgB,EAAE,OAAO,CAAC,WAAW,EAAE,aAAa;GAC5D,MAAM,EAAmB,EAAE,gBAAgB;GAC3C,MAAM,EAAW,EAAE,OAAO;GAC3B;EACF,CAAC;AAGN,GAAO,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,GAAG;CAClC,IAAM,IAAgB,EAAO,KAAK,MAAM,EAAE,KAAK;AAE/C,QACE,kBAAC,GAAD;EACE,OAAO,EAAE;EACT,aAAa,GAAG,EAAgB,EAAE,KAAK,CAAC;EACxC,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,UAAU;GACpC,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,QAC1B;;YAVb;GAcE,kBAAC,GAAD;IAAW,WAAU;IAAQ,MAAM,EAAE,WAAW;IAAc,WAAU;cACtE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBACd,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;OACvB,CAAA,EACP,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBAA2C,EAAE;UAAS,CAAA,EACnE,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA,CAC7B;;QACN,kBAAC,KAAD;SAAG,WAAU;mBAAb,CACE,kBAAC,GAAD;UAAO,WAAU;UAAc,eAAA;UAAc,CAAA,EAC5C,EAAE,YAAY,YAAY,EAAmB,EAAE,UAAU,KAAK,YAC7D;;QACH,EAAE,eACD,kBAAC,KAAD;SAAG,WAAU;mBAA8C,EAAE;SAAgB,CAAA;QAE3E;SACF;SAGN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,SAAD;QACE,SAAQ;QACR,WAAU;kBACX;QAEO,CAAA;OACR,kBAAC,UAAD;QACE,IAAG;QACH,OAAO,EAAE;QACT,UAAU,EAAe;QACzB,WAAW,MAAM;AAEf,SADA,EAAe,KAAK,EACpB,EAAe,OACb;UAAE,IAAI,EAAE;UAAI,QAAQ,EAAE,OAAO;UAAyB,EACtD,EACE,UAAU,MACR,EACE,aAAe,QAAQ,EAAI,UAAU,4BACtC,EACJ,CACF;;QAEH,WAAU;kBAET,EAAe,KAAK,MACnB,kBAAC,UAAD;SAAgB,OAAO;mBACpB,EAAgB,EAAE;SACZ,EAFI,EAEJ,CACT;QACK,CAAA;OACT,kBAAC,KAAD;QAAG,WAAU;kBAAsD;QAE/D,CAAA;OACA;QACF;;IACI,CAAA;GAEX,KACC,kBAAC,OAAD;IAAK,WAAU;cACZ;IACG,CAAA;GAIR,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;MACxC,OAAO,EAAS;MAChB,OAAM;MACN,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA;MACzC,OAAO;MACP,OAAM;MACN,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;MACjC,OAAO;MACP,OAAM;MACN,CAAA;KACE;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MACE,OAAM;MACN,aAAY;MACZ,QACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAiB,GAAK;OACrC,WAAU;iBAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,eAAgB,CAAA,EAAA,cACzB;;gBAGV,EAAS,WAAW,IACnB,kBAAC,GAAD;OACE,cAAa;OACb,OAAM;OACN,aAAY;OACZ,QACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAiB,GAAK;QACrC,WAAU;kBACX;QAEQ,CAAA;OAEX,CAAA,GAEF,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAS,KAAK,MACb,kBAAC,GAAD,EAAoC,WAAU,EAA7B,EAAO,GAAsB,CAC9C;OACE,CAAA;MAEA,CAAA;KACN,CAAA,EAEN,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAS,OAAM;MAAc,aAAY;gBACvC,kBAAC,GAAD;OAAW,WAAU;OAAS,WAAU;iBACrC,EAAc,WAAW,IACxB,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD;UAAS,WAAU;UAA8B,eAAA;UAAc,CAAA;SAC/D,kBAAC,KAAD;UAAG,WAAU;oBAAwC;UAAe,CAAA;SACpE,kBAAC,KAAD;UAAG,WAAU;oBAA8B;UAEvC,CAAA;SACA;YAEN,kBAAC,GAAD,EAAU,OAAO,GAAiB,CAAA;OAE1B,CAAA;MACJ,CAAA;KACN,CAAA,CACF;;GAEL,KAAiB,kBAAC,GAAD;IAAgB,SAAS,EAAE;IAAI,eAAe,EAAiB,GAAM;IAAI,CAAA;GAChF"}
1
+ {"version":3,"file":"AgentDetailPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/agents/AgentDetailPage.tsx"],"sourcesContent":["/**\n * Agent detail\n * @module subscriberbot/pages/agents\n *\n * A single AI agent: its kind, posture, last run, and the autonomous policies\n * that bound it. Reads `subAgent(id)`; changes posture via `subSetAgentStatus`.\n * The \"Run history\" timeline is derived from the agent's real `lastRunAt` and\n * each policy's real `lastTriggeredAt` — never mocked.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n Bot,\n Search,\n RefreshCw,\n DollarSign,\n Mail,\n Building2,\n ShieldCheck,\n Handshake,\n ArrowLeftRight,\n ConciergeBell,\n Plus,\n Clock,\n Zap,\n ListChecks,\n History,\n type LucideIcon,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n EmptyState,\n Modal,\n MetricsCard,\n Timeline,\n LoadingView,\n ErrorView,\n Section,\n type TimelineItem,\n} from '../../components';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport { useAgent, useSetAgentStatus, useCreatePolicy } from '../../hooks/useSubscriberBotApi';\nimport { formatEnumLabel, formatRelativeTime } from '../../utils/formatters';\nimport type { SubAgentKind, SubAgentStatus, SubPolicyAction, SubPolicy } from '../../types';\n\nconst STATUS_OPTIONS: SubAgentStatus[] = ['DISABLED', 'SUGGEST_ONLY', 'AUTONOMOUS'];\n\nconst POLICY_ACTIONS: SubPolicyAction[] = [\n 'ALERT',\n 'CREATE_TASK',\n 'RENEW',\n 'CANCEL',\n 'PAUSE',\n 'UPGRADE',\n 'DOWNGRADE',\n];\n\nconst KIND_ICON: Record<SubAgentKind, LucideIcon> = {\n DISCOVERY: Search,\n RENEWAL: RefreshCw,\n COST: DollarSign,\n NEWSLETTER: Mail,\n VENDOR: Building2,\n COMPLIANCE: ShieldCheck,\n NEGOTIATION: Handshake,\n MIGRATION: ArrowLeftRight,\n CONCIERGE: ConciergeBell,\n};\n\n/** Map a policy action to a timeline tone (semantic only). */\nfunction actionTone(action: SubPolicyAction): NonNullable<TimelineItem['tone']> {\n switch (action) {\n case 'RENEW':\n case 'UPGRADE':\n return 'positive';\n case 'CANCEL':\n case 'PAUSE':\n case 'DOWNGRADE':\n case 'ALERT':\n return 'warning';\n default:\n return 'default';\n }\n}\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\n// ---------------------------------------------------------------------------\n// Read-only enabled indicator (there is no policy-toggle mutation on the svc\n// contract, so this reflects the real `isEnabled` state without pretending to\n// mutate it).\n// ---------------------------------------------------------------------------\nfunction EnabledPill({ enabled }: { enabled: boolean }) {\n return (\n <span\n className={\n enabled\n ? 'inline-flex items-center gap-1.5 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text'\n : 'inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary'\n }\n >\n <span\n aria-hidden\n className=\"size-1.5 rounded-full bg-current\"\n style={{ opacity: enabled ? 1 : 0.6 }}\n />\n {enabled ? 'Enabled' : 'Disabled'}\n </span>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Add policy modal → subCreatePolicy\n// ---------------------------------------------------------------------------\n\nfunction AddPolicyModal({ agentId, onClose }: { agentId: string; onClose: () => void }) {\n const create = useCreatePolicy();\n const [name, setName] = useState('');\n const [action, setAction] = useState<SubPolicyAction>('ALERT');\n const [description, setDescription] = useState('');\n const [condition, setCondition] = useState('');\n const [isEnabled, setIsEnabled] = useState(true);\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError('A policy name is required.');\n return;\n }\n let parsedCondition: Record<string, unknown> | undefined;\n if (condition.trim()) {\n try {\n parsedCondition = JSON.parse(condition) as Record<string, unknown>;\n } catch {\n setFormError('Condition must be valid JSON (e.g. {\"amountOver\": 100}).');\n return;\n }\n }\n create.mutate(\n {\n agentId,\n name: name.trim(),\n action,\n isEnabled,\n ...(description.trim() ? { description: description.trim() } : {}),\n ...(parsedCondition ? { condition: parsedCondition } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not add policy.'),\n }\n );\n };\n\n return (\n <Modal title=\"Add policy\" onClose={onClose} maxWidthClassName=\"max-w-xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"policy-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"policy-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Alert on price hike over 20%\"\n className={inputCls}\n required\n />\n </div>\n <div>\n <label htmlFor=\"policy-action\" className={labelCls}>\n Action\n </label>\n <select\n id=\"policy-action\"\n value={action}\n onChange={(e) => setAction(e.target.value as SubPolicyAction)}\n className={inputCls}\n >\n {POLICY_ACTIONS.map((a) => (\n <option key={a} value={a}>\n {a.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"policy-condition\" className={labelCls}>\n Condition <span className=\"text-text-secondary\">(optional JSON)</span>\n </label>\n <textarea\n id=\"policy-condition\"\n value={condition}\n onChange={(e) => setCondition(e.target.value)}\n rows={3}\n placeholder={'{ \"priceIncreasePct\": 20 }'}\n className={`${inputCls} font-mono`}\n />\n </div>\n <div>\n <label htmlFor=\"policy-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"policy-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={inputCls}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={isEnabled}\n onChange={(e) => setIsEnabled(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Enabled\n </label>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Adding…' : 'Add policy'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Policy card\n// ---------------------------------------------------------------------------\nfunction PolicyCard({ policy }: { policy: SubPolicy }) {\n return (\n <GlassCard treatment=\"metric\" className=\"flex flex-col p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"text-sm font-semibold text-text-primary\">{policy.name}</p>\n <EnabledPill enabled={policy.isEnabled} />\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <span className=\"inline-flex items-center gap-1.5 rounded-md bg-[var(--color-accent-soft)] px-2 py-0.5 text-[11px] font-medium text-text-primary\">\n <ShieldCheck className=\"h-3 w-3\" aria-hidden /> {formatEnumLabel(policy.action)}\n </span>\n <span className=\"inline-flex items-center gap-1 rounded-md bg-bg-sunken px-2 py-0.5 text-[11px] font-medium text-text-secondary tabular-nums\">\n Triggered {policy.triggerCount}×\n </span>\n </div>\n {policy.description && (\n <p className=\"mt-2 line-clamp-2 text-xs text-text-secondary\">{policy.description}</p>\n )}\n <p className=\"mt-2 flex items-center gap-1.5 text-xs text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5\" aria-hidden />\n {policy.lastTriggeredAt\n ? `Last triggered ${formatRelativeTime(policy.lastTriggeredAt)}`\n : 'Not yet triggered'}\n </p>\n </GlassCard>\n );\n}\n\nexport function AgentDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useSubscriberBot();\n const { data: agent, isLoading, isError, refetch } = useAgent(id);\n const setAgentStatus = useSetAgentStatus();\n const [actionError, setActionError] = useState<string | null>(null);\n const [showAddPolicy, setShowAddPolicy] = useState(false);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Agent\">\n <LoadingView label=\"Loading agent…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Agent\">\n <ErrorView message=\"Could not load this agent.\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!agent) {\n return (\n <PageLayout title=\"Agent\">\n <EmptyState\n illustration=\"empty-data\"\n title=\"Agent not found\"\n description=\"It may have been removed, or you may not have access to it.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/agents')}\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 Back to agents\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n const a = agent;\n const Icon = KIND_ICON[a.kind] ?? Bot;\n const policies = a.policies ?? [];\n const enabledCount = policies.filter((p) => p.isEnabled).length;\n const totalTriggers = policies.reduce((s, p) => s + (p.triggerCount || 0), 0);\n\n // --- Run history: derived from real timestamps, sorted most-recent first --\n const events: Array<{ ts: number; item: TimelineItem }> = [];\n if (a.lastRunAt) {\n events.push({\n ts: new Date(a.lastRunAt).getTime(),\n item: {\n title: 'Agent run',\n sub: 'Latest autonomous cycle',\n meta: formatRelativeTime(a.lastRunAt),\n tone: 'positive',\n },\n });\n }\n for (const p of policies) {\n if (p.lastTriggeredAt) {\n events.push({\n ts: new Date(p.lastTriggeredAt).getTime(),\n item: {\n title: p.name,\n sub: `${formatEnumLabel(p.action)} · fired ${p.triggerCount}×`,\n meta: formatRelativeTime(p.lastTriggeredAt),\n tone: actionTone(p.action),\n },\n });\n }\n }\n events.sort((x, y) => y.ts - x.ts);\n const timelineItems = events.map((e) => e.item);\n\n return (\n <PageLayout\n title={a.name}\n description={`${formatEnumLabel(a.kind)} agent`}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/agents')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n }\n >\n {/* Identity + posture header */}\n <GlassCard treatment=\"glass\" glow={a.status === 'AUTONOMOUS'} className=\"p-5\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"flex items-start gap-4\">\n <span className=\"flex size-12 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\">\n <Icon className=\"h-6 w-6\" />\n </span>\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-lg font-semibold text-text-primary\">{a.name}</p>\n <StatusBadge status={a.status} />\n </div>\n <p className=\"mt-1 flex items-center gap-1.5 text-xs text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5\" aria-hidden />\n {a.lastRunAt ? `Last run ${formatRelativeTime(a.lastRunAt)}` : 'Never run'}\n </p>\n {a.description && (\n <p className=\"mt-2 max-w-2xl text-sm text-text-secondary\">{a.description}</p>\n )}\n </div>\n </div>\n\n {/* Posture control */}\n <div className=\"w-full sm:w-56 sm:shrink-0\">\n <label\n htmlFor=\"agent-posture\"\n className=\"mb-1 block text-xs font-medium text-text-secondary\"\n >\n Operating posture\n </label>\n <select\n id=\"agent-posture\"\n value={a.status}\n disabled={setAgentStatus.isPending}\n onChange={(e) => {\n setActionError(null);\n setAgentStatus.mutate(\n { id: a.id, status: e.target.value as SubAgentStatus },\n {\n onError: (err) =>\n setActionError(\n err instanceof Error ? err.message : 'Could not change posture.'\n ),\n }\n );\n }}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] disabled:opacity-50\"\n >\n {STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {formatEnumLabel(s)}\n </option>\n ))}\n </select>\n <p className=\"mt-1.5 text-[11px] leading-snug text-text-secondary\">\n Disabled never acts · Suggest-only proposes · Autonomous acts within its policies.\n </p>\n </div>\n </div>\n </GlassCard>\n\n {actionError && (\n <div className=\"mt-4 rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {actionError}\n </div>\n )}\n\n {/* Policy stats (computed) */}\n <div className=\"mt-6 grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={<ListChecks className=\"h-5 w-5\" />}\n value={policies.length}\n label=\"Policies\"\n />\n <MetricsCard\n icon={<ShieldCheck className=\"h-5 w-5\" />}\n value={enabledCount}\n label=\"Enabled\"\n />\n <MetricsCard\n icon={<Zap className=\"h-5 w-5\" />}\n value={totalTriggers}\n label=\"Total triggers\"\n />\n </div>\n\n {/* Policies + Run history */}\n <div className=\"mt-8 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <Section\n title=\"Policies\"\n description=\"The autonomous rules that bound this agent\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddPolicy(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Add policy\n </button>\n }\n >\n {policies.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No policies\"\n description=\"Policies define what this agent may do autonomously, and what it must alert on.\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddPolicy(true)}\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 Add policy\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {policies.map((policy) => (\n <PolicyCard key={policy.id} policy={policy} />\n ))}\n </div>\n )}\n </Section>\n </div>\n\n <div className=\"lg:col-span-1\">\n <Section title=\"Run history\" description=\"Recent agent activity\">\n <GlassCard treatment=\"metric\" className=\"p-5\">\n {timelineItems.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center gap-2 py-6 text-center\">\n <History className=\"h-8 w-8 text-text-secondary\" aria-hidden />\n <p className=\"text-sm font-medium text-text-primary\">No runs yet</p>\n <p className=\"text-xs text-text-secondary\">\n Activity appears here once the agent runs or a policy fires.\n </p>\n </div>\n ) : (\n <Timeline items={timelineItems} />\n )}\n </GlassCard>\n </Section>\n </div>\n </div>\n\n {showAddPolicy && <AddPolicyModal agentId={a.id} onClose={() => setShowAddPolicy(false)} />}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -11,7 +11,7 @@ import "../../components/index.js";
11
11
  import { useArchiveProvider as m, useCreatePlan as h, useProvider as g, useUpdateProvider as _ } from "../../hooks/useSubscriberBotApi.js";
12
12
  import { useState as v } from "react";
13
13
  import { Archive as y, ArrowLeft as b, Building2 as x, Check as S, Globe as C, Layers as w, Pencil as T, Plus as E, ShieldCheck as D, Sparkles as O, Tag as k, TrendingDown as A } from "lucide-react";
14
- import { useParams as j } from "react-router-dom";
14
+ import { useParams as j } from "react-router";
15
15
  import { Fragment as M, jsx as N, jsxs as P } from "react/jsx-runtime";
16
16
  import { GlassCard as F } from "@burdenoff/fe-libs/ui";
17
17
  //#region src/subscriberbot/pages/providers/ProviderDetailPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"ProviderDetailPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/providers/ProviderDetailPage.tsx"],"sourcesContent":["/**\n * Provider detail\n * @module subscriberbot/pages/providers\n *\n * A provider profile from the registry: category, status, region, scores,\n * tags and its published plans. Reads `subProvider(id)`; writes\n * `subUpdateProvider`, `subArchiveProvider`, `subCreatePlan`.\n */\n\nimport { useState } from 'react';\nimport {\n ArrowLeft,\n Building2,\n Globe,\n ShieldCheck,\n TrendingDown,\n Tag,\n Pencil,\n Plus,\n Archive,\n Check,\n Sparkles,\n Layers,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Modal } from '../../components/Modal';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { Gauge, MiniProgress } from '../../components';\nimport { useParams } from 'react-router-dom';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport {\n useProvider,\n useUpdateProvider,\n useArchiveProvider,\n useCreatePlan,\n} from '../../hooks/useSubscriberBotApi';\nimport { formatCurrency, formatEnumLabel, formatIntervalSuffix } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type {\n SubBillingInterval,\n SubPlan,\n SubProvider,\n SubProviderCategory,\n SubProviderStatus,\n SubUpdateProviderInput,\n} from '../../types';\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\nconst CATEGORIES: SubProviderCategory[] = [\n 'SAAS',\n 'AI_SERVICE',\n 'STREAMING',\n 'NEWSLETTER',\n 'NEWS_DIGEST',\n 'MEMBERSHIP',\n 'COMMUNITY',\n 'EDUCATION',\n 'FINANCIAL',\n 'INSURANCE',\n 'TELECOM',\n 'UTILITY',\n 'ASSOCIATION',\n 'LICENSE',\n 'CONTRACT',\n 'OTHER',\n];\n\nconst STATUSES: SubProviderStatus[] = ['UNVERIFIED', 'VERIFIED', 'CLAIMED', 'DEPRECATED'];\n\nconst INTERVALS: SubBillingInterval[] = [\n 'MONTHLY',\n 'YEARLY',\n 'QUARTERLY',\n 'WEEKLY',\n 'DAILY',\n 'USAGE_BASED',\n 'LIFETIME',\n 'FREE',\n];\n\n/** Vendor-health 0–100 → label + semantic chip classes. Higher is healthier. */\nfunction healthMeta(score: number): { label: string; cls: string } {\n if (score >= 70)\n return { label: 'Healthy', cls: 'bg-status-success-bg-subtle text-status-success-text' };\n if (score >= 45)\n return { label: 'Fair', cls: 'bg-status-warning-bg-subtle text-status-warning-text' };\n return { label: 'At risk', cls: 'bg-status-error-bg-subtle text-status-error-text' };\n}\n\n/** Churn-risk 0–100 → label + tone. Higher is worse. */\nfunction churnMeta(score: number): {\n label: string;\n chip: string;\n tone: 'positive' | 'warning' | 'negative';\n} {\n if (score >= 67)\n return {\n label: 'High',\n chip: 'bg-status-error-bg-subtle text-status-error-text',\n tone: 'negative',\n };\n if (score >= 34)\n return {\n label: 'Moderate',\n chip: 'bg-status-warning-bg-subtle text-status-warning-text',\n tone: 'warning',\n };\n return {\n label: 'Low',\n chip: 'bg-status-success-bg-subtle text-status-success-text',\n tone: 'positive',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Edit provider modal → subUpdateProvider\n// ---------------------------------------------------------------------------\n\nfunction ProviderEditModal({ provider, onClose }: { provider: SubProvider; onClose: () => void }) {\n const update = useUpdateProvider();\n const [name, setName] = useState(provider.name);\n const [category, setCategory] = useState<SubProviderCategory>(provider.category);\n const [status, setStatus] = useState<SubProviderStatus>(provider.status);\n const [description, setDescription] = useState(provider.description ?? '');\n const [industry, setIndustry] = useState(provider.industry ?? '');\n const [region, setRegion] = useState(provider.region ?? '');\n const [language, setLanguage] = useState(provider.language ?? '');\n const [websiteUrl, setWebsiteUrl] = useState(provider.websiteUrl ?? '');\n const [churnRiskScore, setChurnRiskScore] = useState(\n provider.churnRiskScore != null ? String(provider.churnRiskScore) : ''\n );\n const [vendorHealthScore, setVendorHealthScore] = useState(\n provider.vendorHealthScore != null ? String(provider.vendorHealthScore) : ''\n );\n const [tags, setTags] = useState(provider.tags.join(', '));\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError('A provider name is required.');\n return;\n }\n const parsedTags = tags\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n const input: SubUpdateProviderInput = {\n name: name.trim(),\n category,\n status,\n description: description.trim(),\n tags: parsedTags,\n ...(industry.trim() ? { industry: industry.trim() } : {}),\n ...(region.trim() ? { region: region.trim() } : {}),\n ...(language.trim() ? { language: language.trim() } : {}),\n ...(websiteUrl.trim() ? { websiteUrl: websiteUrl.trim() } : {}),\n ...(churnRiskScore.trim() ? { churnRiskScore: Number(churnRiskScore) } : {}),\n ...(vendorHealthScore.trim() ? { vendorHealthScore: Number(vendorHealthScore) } : {}),\n };\n update.mutate(\n { id: provider.id, input },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not update provider.'),\n }\n );\n };\n\n return (\n <Modal title=\"Edit provider\" onClose={onClose} maxWidthClassName=\"max-w-2xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"prov-edit-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"prov-edit-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className={inputCls}\n required\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"prov-edit-category\" className={labelCls}>\n Category\n </label>\n <select\n id=\"prov-edit-category\"\n value={category}\n onChange={(e) => setCategory(e.target.value as SubProviderCategory)}\n className={inputCls}\n >\n {CATEGORIES.map((c) => (\n <option key={c} value={c}>\n {c.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"prov-edit-status\" className={labelCls}>\n Status\n </label>\n <select\n id=\"prov-edit-status\"\n value={status}\n onChange={(e) => setStatus(e.target.value as SubProviderStatus)}\n className={inputCls}\n >\n {STATUSES.map((s) => (\n <option key={s} value={s}>\n {s.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"prov-edit-industry\" className={labelCls}>\n Industry\n </label>\n <input\n id=\"prov-edit-industry\"\n type=\"text\"\n value={industry}\n onChange={(e) => setIndustry(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-region\" className={labelCls}>\n Region\n </label>\n <input\n id=\"prov-edit-region\"\n type=\"text\"\n value={region}\n onChange={(e) => setRegion(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-language\" className={labelCls}>\n Language\n </label>\n <input\n id=\"prov-edit-language\"\n type=\"text\"\n value={language}\n onChange={(e) => setLanguage(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"prov-edit-website\" className={labelCls}>\n Website\n </label>\n <input\n id=\"prov-edit-website\"\n type=\"url\"\n value={websiteUrl}\n onChange={(e) => setWebsiteUrl(e.target.value)}\n placeholder=\"https://…\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"prov-edit-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={3}\n className={inputCls}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"prov-edit-churn\" className={labelCls}>\n Churn risk (0–100)\n </label>\n <input\n id=\"prov-edit-churn\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n value={churnRiskScore}\n onChange={(e) => setChurnRiskScore(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-health\" className={labelCls}>\n Vendor health (0–100)\n </label>\n <input\n id=\"prov-edit-health\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n value={vendorHealthScore}\n onChange={(e) => setVendorHealthScore(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"prov-edit-tags\" className={labelCls}>\n Tags <span className=\"text-text-secondary\">(comma-separated)</span>\n </label>\n <input\n id=\"prov-edit-tags\"\n type=\"text\"\n value={tags}\n onChange={(e) => setTags(e.target.value)}\n className={inputCls}\n />\n </div>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={update.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {update.isPending ? 'Saving…' : 'Save changes'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Add plan modal → subCreatePlan\n// ---------------------------------------------------------------------------\n\nfunction AddPlanModal({ providerId, onClose }: { providerId: string; onClose: () => void }) {\n const create = useCreatePlan();\n const [name, setName] = useState('');\n const [amount, setAmount] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [interval, setInterval] = useState<SubBillingInterval>('MONTHLY');\n const [description, setDescription] = useState('');\n const [features, setFeatures] = useState('');\n const [isActive, setIsActive] = useState(true);\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError('A plan name is required.');\n return;\n }\n const parsedFeatures = features\n .split('\\n')\n .map((f) => f.trim())\n .filter(Boolean);\n create.mutate(\n {\n providerId,\n name: name.trim(),\n interval,\n currency: currency.trim() || 'USD',\n ...(amount.trim() ? { amount: Number(amount) } : {}),\n ...(description.trim() ? { description: description.trim() } : {}),\n ...(parsedFeatures.length ? { features: parsedFeatures } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) => setFormError(err instanceof Error ? err.message : 'Could not add plan.'),\n }\n );\n };\n\n return (\n <Modal title=\"Add plan\" onClose={onClose} maxWidthClassName=\"max-w-xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"plan-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"plan-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Pro, Team, Enterprise\"\n className={inputCls}\n required\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"plan-amount\" className={labelCls}>\n Amount\n </label>\n <input\n id=\"plan-amount\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n placeholder=\"0.00\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"plan-currency\" className={labelCls}>\n Currency\n </label>\n <input\n id=\"plan-currency\"\n type=\"text\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value.toUpperCase())}\n maxLength={3}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"plan-interval\" className={labelCls}>\n Interval\n </label>\n <select\n id=\"plan-interval\"\n value={interval}\n onChange={(e) => setInterval(e.target.value as SubBillingInterval)}\n className={inputCls}\n >\n {INTERVALS.map((i) => (\n <option key={i} value={i}>\n {i.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label htmlFor=\"plan-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"plan-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"plan-features\" className={labelCls}>\n Features <span className=\"text-text-secondary\">(one per line)</span>\n </label>\n <textarea\n id=\"plan-features\"\n value={features}\n onChange={(e) => setFeatures(e.target.value)}\n rows={3}\n placeholder={'Unlimited projects\\nPriority support\\nSSO'}\n className={inputCls}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Active\n </label>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Adding…' : 'Add plan'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Archive confirmation modal → subArchiveProvider\n// ---------------------------------------------------------------------------\n\nfunction ArchiveProviderModal({\n provider,\n onClose,\n}: {\n provider: SubProvider;\n onClose: () => void;\n}) {\n const { navigateTo } = useSubscriberBot();\n const archive = useArchiveProvider();\n const [error, setError] = useState<string | null>(null);\n\n const handleArchive = () => {\n setError(null);\n archive.mutate(provider.id, {\n onSuccess: () => navigateTo('/providers'),\n onError: (err) =>\n setError(err instanceof Error ? err.message : 'Could not archive this provider.'),\n });\n };\n\n return (\n <Modal title=\"Archive provider\" onClose={onClose}>\n <div className=\"space-y-4\">\n {error && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n Archive <span className=\"font-medium text-text-primary\">{provider.name}</span>? It will be\n removed from the active registry. This can be reversed by an administrator.\n </p>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleArchive}\n disabled={archive.isPending}\n className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm font-medium text-status-error-text hover:opacity-90 disabled:opacity-50\"\n >\n {archive.isPending ? 'Archiving…' : 'Archive provider'}\n </button>\n </div>\n </div>\n </Modal>\n );\n}\n\nexport function ProviderDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useSubscriberBot();\n const { data: provider, isLoading, isError, refetch } = useProvider(id);\n const [showEdit, setShowEdit] = useState(false);\n const [showAddPlan, setShowAddPlan] = useState(false);\n const [showArchive, setShowArchive] = useState(false);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Provider\">\n <LoadingView label=\"Loading provider…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Provider\">\n <ErrorView message=\"Could not load this provider.\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!provider) {\n return (\n <PageLayout title=\"Provider\">\n <EmptyState\n illustration=\"empty-data\"\n title=\"Provider not found\"\n description=\"It may have been removed, or you may not have access to it.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/providers')}\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 Back to providers\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n const p = provider;\n // Only render the website as a link when it is a safe http(s) URL — websiteUrl\n // is user-controlled, so guard against javascript:/data: schemes and add\n // rel=\"noopener noreferrer\" to the target=\"_blank\" anchor.\n const safeWebsiteUrl = p.websiteUrl && /^https?:\\/\\//i.test(p.websiteUrl) ? p.websiteUrl : null;\n\n // Highlight the cheapest active priced plan as \"Best value\" (derived from real\n // plan data — guard for the no-plans / all-free case).\n const bestValuePlanId =\n p.plans && p.plans.length > 1\n ? (p.plans\n .filter((pl) => pl.isActive && pl.amount > 0)\n .reduce<SubPlan | null>(\n (min, pl) => (min === null || pl.amount < min.amount ? pl : min),\n null\n )?.id ?? null)\n : null;\n\n return (\n <PageLayout\n title={p.name}\n description={formatEnumLabel(p.category)}\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setShowEdit(true)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n <button\n type=\"button\"\n onClick={() => setShowArchive(true)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-status-error-text/30 px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle\"\n >\n <Archive className=\"h-4 w-4\" /> Archive\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/providers')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n </div>\n }\n >\n {/* Identity row */}\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"flex size-11 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\">\n <Building2 className=\"h-5 w-5\" />\n </span>\n <StatusBadge status={p.status} />\n {p.industry && <span className=\"text-sm text-text-secondary\">{p.industry}</span>}\n {p.region && <span className=\"text-sm text-text-secondary\">· {p.region}</span>}\n {safeWebsiteUrl && (\n <a\n href={safeWebsiteUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1 text-sm text-text-link hover:underline\"\n >\n <Globe className=\"h-3.5 w-3.5\" /> Website\n </a>\n )}\n </div>\n\n {p.description && (\n <p className=\"mt-4 max-w-3xl text-sm leading-relaxed text-text-secondary\">\n {p.description}\n </p>\n )}\n\n {p.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap items-center gap-2\">\n <Tag className=\"h-3.5 w-3.5 text-text-secondary\" />\n {p.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Plans — tier cards */}\n <div className=\"lg:col-span-2\">\n <Section\n title=\"Plans\"\n description=\"Pricing this provider publishes\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddPlan(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Add plan\n </button>\n }\n >\n {!p.plans || p.plans.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No published plans\"\n description=\"When this provider publishes plans, they'll appear here.\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddPlan(true)}\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 Add plan\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {p.plans.map((plan) => {\n const isBestValue = plan.id === bestValuePlanId && p.plans!.length > 1;\n return (\n <GlassCard\n key={plan.id}\n treatment={isBestValue ? 'insight' : 'metric'}\n glow={isBestValue}\n className=\"flex flex-col p-5\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"text-sm font-semibold text-text-primary\">{plan.name}</p>\n {isBestValue ? (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-[11px] font-medium text-status-success-text\">\n <Sparkles className=\"h-3 w-3\" /> Best value\n </span>\n ) : (\n <StatusBadge status={plan.isActive ? 'ACTIVE' : 'DISABLED'} />\n )}\n </div>\n <p className=\"mt-3 text-2xl font-bold tabular-nums text-text-primary\">\n {formatCurrency(plan.amount, plan.currency)}\n <span className=\"text-sm font-normal text-text-secondary\">\n {formatIntervalSuffix(plan.interval)}\n </span>\n </p>\n {plan.description && (\n <p className=\"mt-2 text-xs leading-relaxed text-text-secondary\">\n {plan.description}\n </p>\n )}\n {plan.features.length > 0 && (\n <ul className=\"mt-4 space-y-2 border-t border-border-subtle pt-4\">\n {plan.features.slice(0, 6).map((f) => (\n <li\n key={f}\n className=\"flex items-start gap-2 text-xs text-text-secondary\"\n >\n <Check className=\"mt-0.5 h-3.5 w-3.5 shrink-0 text-status-success-text\" />\n <span>{f}</span>\n </li>\n ))}\n {plan.features.length > 6 && (\n <li className=\"pl-6 text-[11px] text-text-secondary\">\n +{plan.features.length - 6} more\n </li>\n )}\n </ul>\n )}\n </GlassCard>\n );\n })}\n </div>\n )}\n </Section>\n </div>\n\n {/* Sidebar — vendor health, churn risk, at-a-glance */}\n <div className=\"space-y-4\">\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center justify-between\">\n <span className=\"inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <ShieldCheck className=\"h-4 w-4 text-text-secondary\" /> Vendor health\n </span>\n {p.vendorHealthScore != null && (\n <span\n className={cn(\n 'rounded-full px-2 py-0.5 text-[11px] font-medium',\n healthMeta(p.vendorHealthScore).cls\n )}\n >\n {healthMeta(p.vendorHealthScore).label}\n </span>\n )}\n </div>\n {p.vendorHealthScore != null ? (\n <div className=\"mt-2 flex flex-col items-center\">\n <Gauge value={p.vendorHealthScore} max={100} size={168} label=\"out of 100\" />\n </div>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">No health score yet.</p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center justify-between\">\n <span className=\"inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <TrendingDown className=\"h-4 w-4 text-text-secondary\" /> Churn risk\n </span>\n {p.churnRiskScore != null && (\n <span\n className={cn(\n 'rounded-full px-2 py-0.5 text-[11px] font-medium',\n churnMeta(p.churnRiskScore).chip\n )}\n >\n {churnMeta(p.churnRiskScore).label}\n </span>\n )}\n </div>\n {p.churnRiskScore != null ? (\n <>\n <div className=\"mt-3 flex items-baseline justify-between\">\n <span className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {p.churnRiskScore}\n </span>\n <span className=\"text-xs text-text-secondary\">/ 100</span>\n </div>\n <MiniProgress\n value={p.churnRiskScore}\n tone={churnMeta(p.churnRiskScore).tone}\n className=\"mt-2\"\n />\n </>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">No churn signal yet.</p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <span className=\"inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Layers className=\"h-4 w-4 text-text-secondary\" /> At a glance\n </span>\n <dl className=\"mt-3 space-y-2.5 text-sm\">\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Category</dt>\n <dd className=\"font-medium text-text-primary\">{formatEnumLabel(p.category)}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Industry</dt>\n <dd className=\"font-medium text-text-primary\">{p.industry ?? '—'}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Region</dt>\n <dd className=\"font-medium text-text-primary\">{p.region ?? '—'}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Language</dt>\n <dd className=\"font-medium text-text-primary\">{p.language ?? '—'}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Published plans</dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">\n {p.plans?.length ?? 0}\n </dd>\n </div>\n </dl>\n </GlassCard>\n </div>\n </div>\n\n {showEdit && <ProviderEditModal provider={p} onClose={() => setShowEdit(false)} />}\n {showAddPlan && <AddPlanModal providerId={p.id} onClose={() => setShowAddPlan(false)} />}\n {showArchive && <ArchiveProviderModal provider={p} onClose={() => setShowArchive(false)} />}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAkDA,IAAM,IACJ,wMACI,IAAW,sDAEX,IAAoC;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,IAAgC;CAAC;CAAc;CAAY;CAAW;CAAa,EAEnF,IAAkC;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAGD,SAAS,EAAW,GAA+C;AAKjE,QAJI,KAAS,KACJ;EAAE,OAAO;EAAW,KAAK;EAAwD,GACtF,KAAS,KACJ;EAAE,OAAO;EAAQ,KAAK;EAAwD,GAChF;EAAE,OAAO;EAAW,KAAK;EAAoD;;AAItF,SAAS,EAAU,GAIjB;AAaA,QAZI,KAAS,KACJ;EACL,OAAO;EACP,MAAM;EACN,MAAM;EACP,GACC,KAAS,KACJ;EACL,OAAO;EACP,MAAM;EACN,MAAM;EACP,GACI;EACL,OAAO;EACP,MAAM;EACN,MAAM;EACP;;AAOH,SAAS,EAAkB,EAAE,aAAU,cAA2D;CAChG,IAAM,IAAS,GAAmB,EAC5B,CAAC,GAAM,KAAW,EAAS,EAAS,KAAK,EACzC,CAAC,GAAU,KAAe,EAA8B,EAAS,SAAS,EAC1E,CAAC,GAAQ,KAAa,EAA4B,EAAS,OAAO,EAClE,CAAC,GAAa,KAAkB,EAAS,EAAS,eAAe,GAAG,EACpE,CAAC,GAAU,KAAe,EAAS,EAAS,YAAY,GAAG,EAC3D,CAAC,GAAQ,KAAa,EAAS,EAAS,UAAU,GAAG,EACrD,CAAC,GAAU,KAAe,EAAS,EAAS,YAAY,GAAG,EAC3D,CAAC,GAAY,KAAiB,EAAS,EAAS,cAAc,GAAG,EACjE,CAAC,GAAgB,KAAqB,EAC1C,EAAS,kBAAkB,OAAyC,KAAlC,OAAO,EAAS,eAAe,CAClE,EACK,CAAC,GAAmB,KAAwB,EAChD,EAAS,qBAAqB,OAA4C,KAArC,OAAO,EAAS,kBAAkB,CACxE,EACK,CAAC,GAAM,KAAW,EAAS,EAAS,KAAK,KAAK,KAAK,CAAC,EACpD,CAAC,GAAW,KAAgB,EAAwB,KAAK;AAoC/D,QACE,kBAAC,GAAD;EAAO,OAAM;EAAyB;EAAS,mBAAkB;YAC/D,kBAAC,QAAD;GAAM,WApCY,MAAuB;AAG3C,QAFA,EAAE,gBAAgB,EAClB,EAAa,KAAK,EACd,CAAC,EAAK,MAAM,EAAE;AAChB,OAAa,+BAA+B;AAC5C;;IAEF,IAAM,IAAa,EAChB,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,OAAO,QAAQ,EACZ,IAAgC;KACpC,MAAM,EAAK,MAAM;KACjB;KACA;KACA,aAAa,EAAY,MAAM;KAC/B,MAAM;KACN,GAAI,EAAS,MAAM,GAAG,EAAE,UAAU,EAAS,MAAM,EAAE,GAAG,EAAE;KACxD,GAAI,EAAO,MAAM,GAAG,EAAE,QAAQ,EAAO,MAAM,EAAE,GAAG,EAAE;KAClD,GAAI,EAAS,MAAM,GAAG,EAAE,UAAU,EAAS,MAAM,EAAE,GAAG,EAAE;KACxD,GAAI,EAAW,MAAM,GAAG,EAAE,YAAY,EAAW,MAAM,EAAE,GAAG,EAAE;KAC9D,GAAI,EAAe,MAAM,GAAG,EAAE,gBAAgB,OAAO,EAAe,EAAE,GAAG,EAAE;KAC3E,GAAI,EAAkB,MAAM,GAAG,EAAE,mBAAmB,OAAO,EAAkB,EAAE,GAAG,EAAE;KACrF;AACD,MAAO,OACL;KAAE,IAAI,EAAS;KAAI;KAAO,EAC1B;KACE,iBAAiB,GAAS;KAC1B,UAAU,MACR,EAAa,aAAe,QAAQ,EAAI,UAAU,6BAA6B;KAClF,CACF;;GAK+B,WAAU;aAAxC;IACG,KACC,kBAAC,OAAD;KAAK,WAAU;eACZ;KACG,CAAA;IAER,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAiB,WAAW;eAA3C,CAAqD,SAC9C,kBAAC,QAAD;MAAM,WAAU;gBAAyB;MAAQ,CAAA,CAChD;QACR,kBAAC,SAAD;KACE,IAAG;KACH,MAAK;KACL,OAAO;KACP,WAAW,MAAM,EAAQ,EAAE,OAAO,MAAM;KACxC,WAAW;KACX,UAAA;KACA,CAAA,CACE,EAAA,CAAA;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,SAAQ;MAAqB,WAAW;gBAAU;MAEjD,CAAA,EACR,kBAAC,UAAD;MACE,IAAG;MACH,OAAO;MACP,WAAW,MAAM,EAAY,EAAE,OAAO,MAA6B;MACnE,WAAW;gBAEV,EAAW,KAAK,MACf,kBAAC,UAAD;OAAgB,OAAO;iBACpB,EAAE,QAAQ,MAAM,IAAI;OACd,EAFI,EAEJ,CACT;MACK,CAAA,CACL,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,SAAQ;MAAmB,WAAW;gBAAU;MAE/C,CAAA,EACR,kBAAC,UAAD;MACE,IAAG;MACH,OAAO;MACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAA2B;MAC/D,WAAW;gBAEV,EAAS,KAAK,MACb,kBAAC,UAAD;OAAgB,OAAO;iBACpB,EAAE,QAAQ,MAAM,IAAI;OACd,EAFI,EAEJ,CACT;MACK,CAAA,CACL,EAAA,CAAA,CACF;;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAqB,WAAW;iBAAU;OAEjD,CAAA,EACR,kBAAC,SAAD;OACE,IAAG;OACH,MAAK;OACL,OAAO;OACP,WAAW,MAAM,EAAY,EAAE,OAAO,MAAM;OAC5C,WAAW;OACX,CAAA,CACE,EAAA,CAAA;MACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAmB,WAAW;iBAAU;OAE/C,CAAA,EACR,kBAAC,SAAD;OACE,IAAG;OACH,MAAK;OACL,OAAO;OACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;OAC1C,WAAW;OACX,CAAA,CACE,EAAA,CAAA;MACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAqB,WAAW;iBAAU;OAEjD,CAAA,EACR,kBAAC,SAAD;OACE,IAAG;OACH,MAAK;OACL,OAAO;OACP,WAAW,MAAM,EAAY,EAAE,OAAO,MAAM;OAC5C,WAAW;OACX,CAAA,CACE,EAAA,CAAA;MACF;;IACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAoB,WAAW;eAAU;KAEhD,CAAA,EACR,kBAAC,SAAD;KACE,IAAG;KACH,MAAK;KACL,OAAO;KACP,WAAW,MAAM,EAAc,EAAE,OAAO,MAAM;KAC9C,aAAY;KACZ,WAAW;KACX,CAAA,CACE,EAAA,CAAA;IACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAiB,WAAW;eAAU;KAE7C,CAAA,EACR,kBAAC,YAAD;KACE,IAAG;KACH,OAAO;KACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;KAC/C,MAAM;KACN,WAAW;KACX,CAAA,CACE,EAAA,CAAA;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,SAAQ;MAAkB,WAAW;gBAAU;MAE9C,CAAA,EACR,kBAAC,SAAD;MACE,IAAG;MACH,MAAK;MACL,KAAI;MACJ,KAAI;MACJ,OAAO;MACP,WAAW,MAAM,EAAkB,EAAE,OAAO,MAAM;MAClD,WAAW;MACX,CAAA,CACE,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MAAO,SAAQ;MAAmB,WAAW;gBAAU;MAE/C,CAAA,EACR,kBAAC,SAAD;MACE,IAAG;MACH,MAAK;MACL,KAAI;MACJ,KAAI;MACJ,OAAO;MACP,WAAW,MAAM,EAAqB,EAAE,OAAO,MAAM;MACrD,WAAW;MACX,CAAA,CACE,EAAA,CAAA,CACF;;IACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAiB,WAAW;eAA3C,CAAqD,SAC9C,kBAAC,QAAD;MAAM,WAAU;gBAAsB;MAAwB,CAAA,CAC7D;QACR,kBAAC,SAAD;KACE,IAAG;KACH,MAAK;KACL,OAAO;KACP,WAAW,MAAM,EAAQ,EAAE,OAAO,MAAM;KACxC,WAAW;KACX,CAAA,CACE,EAAA,CAAA;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;gBACX;MAEQ,CAAA,EACT,kBAAC,UAAD;MACE,MAAK;MACL,UAAU,EAAO;MACjB,WAAU;gBAET,EAAO,YAAY,YAAY;MACzB,CAAA,CACL;;IACD;;EACD,CAAA;;AAQZ,SAAS,EAAa,EAAE,eAAY,cAAwD;CAC1F,IAAM,IAAS,GAAe,EACxB,CAAC,GAAM,KAAW,EAAS,GAAG,EAC9B,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,CAAC,GAAU,KAAe,EAAS,MAAM,EACzC,CAAC,GAAU,KAAe,EAA6B,UAAU,EACjE,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAU,KAAe,EAAS,GAAG,EACtC,CAAC,GAAU,KAAe,EAAS,GAAK,EACxC,CAAC,GAAW,KAAgB,EAAwB,KAAK;AA8B/D,QACE,kBAAC,GAAD;EAAO,OAAM;EAAoB;EAAS,mBAAkB;YAC1D,kBAAC,QAAD;GAAM,WA9BY,MAAuB;AAG3C,QAFA,EAAE,gBAAgB,EAClB,EAAa,KAAK,EACd,CAAC,EAAK,MAAM,EAAE;AAChB,OAAa,2BAA2B;AACxC;;IAEF,IAAM,IAAiB,EACpB,MAAM,KAAK,CACX,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,OAAO,QAAQ;AAClB,MAAO,OACL;KACE;KACA,MAAM,EAAK,MAAM;KACjB;KACA,UAAU,EAAS,MAAM,IAAI;KAC7B,GAAI,EAAO,MAAM,GAAG,EAAE,QAAQ,OAAO,EAAO,EAAE,GAAG,EAAE;KACnD,GAAI,EAAY,MAAM,GAAG,EAAE,aAAa,EAAY,MAAM,EAAE,GAAG,EAAE;KACjE,GAAI,EAAe,SAAS,EAAE,UAAU,GAAgB,GAAG,EAAE;KAC9D,EACD;KACE,iBAAiB,GAAS;KAC1B,UAAU,MAAQ,EAAa,aAAe,QAAQ,EAAI,UAAU,sBAAsB;KAC3F,CACF;;GAK+B,WAAU;aAAxC;IACG,KACC,kBAAC,OAAD;KAAK,WAAU;eACZ;KACG,CAAA;IAER,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAY,WAAW;eAAtC,CAAgD,SACzC,kBAAC,QAAD;MAAM,WAAU;gBAAyB;MAAQ,CAAA,CAChD;QACR,kBAAC,SAAD;KACE,IAAG;KACH,MAAK;KACL,OAAO;KACP,WAAW,MAAM,EAAQ,EAAE,OAAO,MAAM;KACxC,aAAY;KACZ,WAAW;KACX,UAAA;KACA,CAAA,CACE,EAAA,CAAA;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAc,WAAW;iBAAU;OAE1C,CAAA,EACR,kBAAC,SAAD;OACE,IAAG;OACH,MAAK;OACL,KAAI;OACJ,MAAK;OACL,OAAO;OACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;OAC1C,aAAY;OACZ,WAAW;OACX,CAAA,CACE,EAAA,CAAA;MACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAgB,WAAW;iBAAU;OAE5C,CAAA,EACR,kBAAC,SAAD;OACE,IAAG;OACH,MAAK;OACL,OAAO;OACP,WAAW,MAAM,EAAY,EAAE,OAAO,MAAM,aAAa,CAAC;OAC1D,WAAW;OACX,WAAW;OACX,CAAA,CACE,EAAA,CAAA;MACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,SAAQ;OAAgB,WAAW;iBAAU;OAE5C,CAAA,EACR,kBAAC,UAAD;OACE,IAAG;OACH,OAAO;OACP,WAAW,MAAM,EAAY,EAAE,OAAO,MAA4B;OAClE,WAAW;iBAEV,EAAU,KAAK,MACd,kBAAC,UAAD;QAAgB,OAAO;kBACpB,EAAE,QAAQ,MAAM,IAAI;QACd,EAFI,EAEJ,CACT;OACK,CAAA,CACL,EAAA,CAAA;MACF;;IACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAY,WAAW;eAAU;KAExC,CAAA,EACR,kBAAC,YAAD;KACE,IAAG;KACH,OAAO;KACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;KAC/C,MAAM;KACN,WAAW;KACX,CAAA,CACE,EAAA,CAAA;IACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;KAAO,SAAQ;KAAgB,WAAW;eAA1C,CAAoD,aACzC,kBAAC,QAAD;MAAM,WAAU;gBAAsB;MAAqB,CAAA,CAC9D;QACR,kBAAC,YAAD;KACE,IAAG;KACH,OAAO;KACP,WAAW,MAAM,EAAY,EAAE,OAAO,MAAM;KAC5C,MAAM;KACN,aAAa;KACb,WAAW;KACX,CAAA,CACE,EAAA,CAAA;IACN,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,SAAD;MACE,MAAK;MACL,SAAS;MACT,WAAW,MAAM,EAAY,EAAE,OAAO,QAAQ;MAC9C,WAAU;MACV,CAAA,EAAA,SAEI;;IACR,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;gBACX;MAEQ,CAAA,EACT,kBAAC,UAAD;MACE,MAAK;MACL,UAAU,EAAO;MACjB,WAAU;gBAET,EAAO,YAAY,YAAY;MACzB,CAAA,CACL;;IACD;;EACD,CAAA;;AAQZ,SAAS,EAAqB,EAC5B,aACA,cAIC;CACD,IAAM,EAAE,kBAAe,GAAkB,EACnC,IAAU,GAAoB,EAC9B,CAAC,GAAO,KAAY,EAAwB,KAAK;AAWvD,QACE,kBAAC,GAAD;EAAO,OAAM;EAA4B;YACvC,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,KACC,kBAAC,OAAD;KAAK,WAAU;eACZ;KACG,CAAA;IAER,kBAAC,KAAD;KAAG,WAAU;eAAb;MAA2C;MACjC,kBAAC,QAAD;OAAM,WAAU;iBAAiC,EAAS;OAAY,CAAA;;MAE5E;;IACJ,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;gBACX;MAEQ,CAAA,EACT,kBAAC,UAAD;MACE,MAAK;MACL,eA/BkB;AAE1B,OADA,EAAS,KAAK,EACd,EAAQ,OAAO,EAAS,IAAI;QAC1B,iBAAiB,EAAW,aAAa;QACzC,UAAU,MACR,EAAS,aAAe,QAAQ,EAAI,UAAU,mCAAmC;QACpF,CAAC;;MA0BM,UAAU,EAAQ;MAClB,WAAU;gBAET,EAAQ,YAAY,eAAe;MAC7B,CAAA,CACL;;IACF;;EACA,CAAA;;AAIZ,SAAgB,IAAqB;CACnC,IAAM,EAAE,UAAO,GAA2B,EACpC,EAAE,kBAAe,GAAkB,EACnC,EAAE,MAAM,GAAU,cAAW,YAAS,eAAY,EAAY,EAAG,EACjE,CAAC,GAAU,KAAe,EAAS,GAAM,EACzC,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAa,KAAkB,EAAS,GAAM;AAErD,KAAI,EACF,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAa,OAAM,qBAAsB,CAAA;EAC9B,CAAA;AAIjB,KAAI,EACF,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GAAW,SAAQ;GAAgC,eAAe,KAAK,GAAS;GAAI,CAAA;EACzE,CAAA;AAIjB,KAAI,CAAC,EACH,QACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,aAAa;IACvC,WAAU;cACX;IAEQ,CAAA;GAEX,CAAA;EACS,CAAA;CAIjB,IAAM,IAAI,GAIJ,IAAiB,EAAE,cAAc,gBAAgB,KAAK,EAAE,WAAW,GAAG,EAAE,aAAa,MAIrF,IACJ,EAAE,SAAS,EAAE,MAAM,SAAS,IACvB,EAAE,MACA,QAAQ,MAAO,EAAG,YAAY,EAAG,SAAS,EAAE,CAC5C,QACE,GAAK,MAAQ,MAAQ,QAAQ,EAAG,SAAS,EAAI,SAAS,IAAK,GAC5D,KACD,EAAE,MAAM,OACX;AAEN,QACE,kBAAC,GAAD;EACE,OAAO,EAAE;EACT,aAAa,EAAgB,EAAE,SAAS;EACxC,SACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAY,GAAK;KAChC,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAAA,QACvB;;IACT,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAe,GAAK;KACnC,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA,EAAA,WACxB;;IACT,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,aAAa;KACvC,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,QAC1B;;IACL;;YA1BV;GA8BE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,QAAD;MAAM,WAAU;gBACd,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;MAC5B,CAAA;KACP,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA;KAChC,EAAE,YAAY,kBAAC,QAAD;MAAM,WAAU;gBAA+B,EAAE;MAAgB,CAAA;KAC/E,EAAE,UAAU,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CAA8C,MAAG,EAAE,OAAc;;KAC7E,KACC,kBAAC,KAAD;MACE,MAAM;MACN,QAAO;MACP,KAAI;MACJ,WAAU;gBAJZ,CAME,kBAAC,GAAD,EAAO,WAAU,eAAgB,CAAA,EAAA,WAC/B;;KAEF;;GAEL,EAAE,eACD,kBAAC,KAAD;IAAG,WAAU;cACV,EAAE;IACD,CAAA;GAGL,EAAE,KAAK,SAAS,KACf,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAK,WAAU,mCAAoC,CAAA,EAClD,EAAE,KAAK,KAAK,MACX,kBAAC,QAAD;KAEE,WAAU;eAET;KACI,EAJA,EAIA,CACP,CACE;;GAGR,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MACE,OAAM;MACN,aAAY;MACZ,QACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAe,GAAK;OACnC,WAAU;iBAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,eAAgB,CAAA,EAAA,YACzB;;gBAGV,CAAC,EAAE,SAAS,EAAE,MAAM,WAAW,IAC9B,kBAAC,GAAD;OACE,cAAa;OACb,OAAM;OACN,aAAY;OACZ,QACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAe,GAAK;QACnC,WAAU;kBACX;QAEQ,CAAA;OAEX,CAAA,GAEF,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAE,MAAM,KAAK,MAAS;QACrB,IAAM,IAAc,EAAK,OAAO,KAAmB,EAAE,MAAO,SAAS;AACrE,eACE,kBAAC,GAAD;SAEE,WAAW,IAAc,YAAY;SACrC,MAAM;SACN,WAAU;mBAJZ;UAME,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,KAAD;YAAG,WAAU;sBAA2C,EAAK;YAAS,CAAA,EACrE,IACC,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CACE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,cAC3B;gBAEP,kBAAC,GAAD,EAAa,QAAQ,EAAK,WAAW,WAAW,YAAc,CAAA,CAE5D;;UACN,kBAAC,KAAD;WAAG,WAAU;qBAAb,CACG,EAAe,EAAK,QAAQ,EAAK,SAAS,EAC3C,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAqB,EAAK,SAAS;YAC/B,CAAA,CACL;;UACH,EAAK,eACJ,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAK;WACJ,CAAA;UAEL,EAAK,SAAS,SAAS,KACtB,kBAAC,MAAD;WAAI,WAAU;qBAAd,CACG,EAAK,SAAS,MAAM,GAAG,EAAE,CAAC,KAAK,MAC9B,kBAAC,MAAD;YAEE,WAAU;sBAFZ,CAIE,kBAAC,GAAD,EAAO,WAAU,wDAAyD,CAAA,EAC1E,kBAAC,QAAD,EAAA,UAAO,GAAS,CAAA,CACb;cALE,EAKF,CACL,EACD,EAAK,SAAS,SAAS,KACtB,kBAAC,MAAD;YAAI,WAAU;sBAAd;aAAqD;aACjD,EAAK,SAAS,SAAS;aAAE;aACxB;cAEJ;;UAEG;WA5CL,EAAK,GA4CA;SAEd;OACE,CAAA;MAEA,CAAA;KACN,CAAA,EAGN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OAAW,WAAU;OAAS,WAAU;iBAAxC,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CACE,kBAAC,GAAD,EAAa,WAAU,+BAAgC,CAAA,EAAA,iBAClD;YACN,EAAE,qBAAqB,QACtB,kBAAC,QAAD;SACE,WAAW,EACT,oDACA,EAAW,EAAE,kBAAkB,CAAC,IACjC;mBAEA,EAAW,EAAE,kBAAkB,CAAC;SAC5B,CAAA,CAEL;WACL,EAAE,qBAAqB,OAKtB,kBAAC,KAAD;QAAG,WAAU;kBAAmC;QAAwB,CAAA,GAJxE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD;SAAO,OAAO,EAAE;SAAmB,KAAK;SAAK,MAAM;SAAK,OAAM;SAAe,CAAA;QACzE,CAAA,CAIE;;MAEZ,kBAAC,GAAD;OAAW,WAAU;OAAS,WAAU;iBAAxC,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CACE,kBAAC,GAAD,EAAc,WAAU,+BAAgC,CAAA,EAAA,cACnD;YACN,EAAE,kBAAkB,QACnB,kBAAC,QAAD;SACE,WAAW,EACT,oDACA,EAAU,EAAE,eAAe,CAAC,KAC7B;mBAEA,EAAU,EAAE,eAAe,CAAC;SACxB,CAAA,CAEL;WACL,EAAE,kBAAkB,OAenB,kBAAC,KAAD;QAAG,WAAU;kBAAmC;QAAwB,CAAA,GAdxE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAE;SACE,CAAA,EACP,kBAAC,QAAD;SAAM,WAAU;mBAA8B;SAAY,CAAA,CACtD;WACN,kBAAC,GAAD;QACE,OAAO,EAAE;QACT,MAAM,EAAU,EAAE,eAAe,CAAC;QAClC,WAAU;QACV,CAAA,CACD,EAAA,CAAA,CAIK;;MAEZ,kBAAC,GAAD;OAAW,WAAU;OAAS,WAAU;iBAAxC,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACE,kBAAC,GAAD,EAAQ,WAAU,+BAAgC,CAAA,EAAA,eAC7C;WACP,kBAAC,MAAD;QAAI,WAAU;kBAAd;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBAAsB;WAAa,CAAA,EACjD,kBAAC,MAAD;WAAI,WAAU;qBAAiC,EAAgB,EAAE,SAAS;WAAM,CAAA,CAC5E;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBAAsB;WAAa,CAAA,EACjD,kBAAC,MAAD;WAAI,WAAU;qBAAiC,EAAE,YAAY;WAAS,CAAA,CAClE;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBAAsB;WAAW,CAAA,EAC/C,kBAAC,MAAD;WAAI,WAAU;qBAAiC,EAAE,UAAU;WAAS,CAAA,CAChE;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBAAsB;WAAa,CAAA,EACjD,kBAAC,MAAD;WAAI,WAAU;qBAAiC,EAAE,YAAY;WAAS,CAAA,CAClE;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBAAsB;WAAoB,CAAA,EACxD,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAE,OAAO,UAAU;WACjB,CAAA,CACD;;SACH;UACK;;MACR;OACF;;GAEL,KAAY,kBAAC,GAAD;IAAmB,UAAU;IAAG,eAAe,EAAY,GAAM;IAAI,CAAA;GACjF,KAAe,kBAAC,GAAD;IAAc,YAAY,EAAE;IAAI,eAAe,EAAe,GAAM;IAAI,CAAA;GACvF,KAAe,kBAAC,GAAD;IAAsB,UAAU;IAAG,eAAe,EAAe,GAAM;IAAI,CAAA;GAChF"}
1
+ {"version":3,"file":"ProviderDetailPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/providers/ProviderDetailPage.tsx"],"sourcesContent":["/**\n * Provider detail\n * @module subscriberbot/pages/providers\n *\n * A provider profile from the registry: category, status, region, scores,\n * tags and its published plans. Reads `subProvider(id)`; writes\n * `subUpdateProvider`, `subArchiveProvider`, `subCreatePlan`.\n */\n\nimport { useState } from 'react';\nimport {\n ArrowLeft,\n Building2,\n Globe,\n ShieldCheck,\n TrendingDown,\n Tag,\n Pencil,\n Plus,\n Archive,\n Check,\n Sparkles,\n Layers,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Modal } from '../../components/Modal';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { Gauge, MiniProgress } from '../../components';\nimport { useParams } from 'react-router';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport {\n useProvider,\n useUpdateProvider,\n useArchiveProvider,\n useCreatePlan,\n} from '../../hooks/useSubscriberBotApi';\nimport { formatCurrency, formatEnumLabel, formatIntervalSuffix } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type {\n SubBillingInterval,\n SubPlan,\n SubProvider,\n SubProviderCategory,\n SubProviderStatus,\n SubUpdateProviderInput,\n} from '../../types';\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\nconst CATEGORIES: SubProviderCategory[] = [\n 'SAAS',\n 'AI_SERVICE',\n 'STREAMING',\n 'NEWSLETTER',\n 'NEWS_DIGEST',\n 'MEMBERSHIP',\n 'COMMUNITY',\n 'EDUCATION',\n 'FINANCIAL',\n 'INSURANCE',\n 'TELECOM',\n 'UTILITY',\n 'ASSOCIATION',\n 'LICENSE',\n 'CONTRACT',\n 'OTHER',\n];\n\nconst STATUSES: SubProviderStatus[] = ['UNVERIFIED', 'VERIFIED', 'CLAIMED', 'DEPRECATED'];\n\nconst INTERVALS: SubBillingInterval[] = [\n 'MONTHLY',\n 'YEARLY',\n 'QUARTERLY',\n 'WEEKLY',\n 'DAILY',\n 'USAGE_BASED',\n 'LIFETIME',\n 'FREE',\n];\n\n/** Vendor-health 0–100 → label + semantic chip classes. Higher is healthier. */\nfunction healthMeta(score: number): { label: string; cls: string } {\n if (score >= 70)\n return { label: 'Healthy', cls: 'bg-status-success-bg-subtle text-status-success-text' };\n if (score >= 45)\n return { label: 'Fair', cls: 'bg-status-warning-bg-subtle text-status-warning-text' };\n return { label: 'At risk', cls: 'bg-status-error-bg-subtle text-status-error-text' };\n}\n\n/** Churn-risk 0–100 → label + tone. Higher is worse. */\nfunction churnMeta(score: number): {\n label: string;\n chip: string;\n tone: 'positive' | 'warning' | 'negative';\n} {\n if (score >= 67)\n return {\n label: 'High',\n chip: 'bg-status-error-bg-subtle text-status-error-text',\n tone: 'negative',\n };\n if (score >= 34)\n return {\n label: 'Moderate',\n chip: 'bg-status-warning-bg-subtle text-status-warning-text',\n tone: 'warning',\n };\n return {\n label: 'Low',\n chip: 'bg-status-success-bg-subtle text-status-success-text',\n tone: 'positive',\n };\n}\n\n// ---------------------------------------------------------------------------\n// Edit provider modal → subUpdateProvider\n// ---------------------------------------------------------------------------\n\nfunction ProviderEditModal({ provider, onClose }: { provider: SubProvider; onClose: () => void }) {\n const update = useUpdateProvider();\n const [name, setName] = useState(provider.name);\n const [category, setCategory] = useState<SubProviderCategory>(provider.category);\n const [status, setStatus] = useState<SubProviderStatus>(provider.status);\n const [description, setDescription] = useState(provider.description ?? '');\n const [industry, setIndustry] = useState(provider.industry ?? '');\n const [region, setRegion] = useState(provider.region ?? '');\n const [language, setLanguage] = useState(provider.language ?? '');\n const [websiteUrl, setWebsiteUrl] = useState(provider.websiteUrl ?? '');\n const [churnRiskScore, setChurnRiskScore] = useState(\n provider.churnRiskScore != null ? String(provider.churnRiskScore) : ''\n );\n const [vendorHealthScore, setVendorHealthScore] = useState(\n provider.vendorHealthScore != null ? String(provider.vendorHealthScore) : ''\n );\n const [tags, setTags] = useState(provider.tags.join(', '));\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError('A provider name is required.');\n return;\n }\n const parsedTags = tags\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n const input: SubUpdateProviderInput = {\n name: name.trim(),\n category,\n status,\n description: description.trim(),\n tags: parsedTags,\n ...(industry.trim() ? { industry: industry.trim() } : {}),\n ...(region.trim() ? { region: region.trim() } : {}),\n ...(language.trim() ? { language: language.trim() } : {}),\n ...(websiteUrl.trim() ? { websiteUrl: websiteUrl.trim() } : {}),\n ...(churnRiskScore.trim() ? { churnRiskScore: Number(churnRiskScore) } : {}),\n ...(vendorHealthScore.trim() ? { vendorHealthScore: Number(vendorHealthScore) } : {}),\n };\n update.mutate(\n { id: provider.id, input },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not update provider.'),\n }\n );\n };\n\n return (\n <Modal title=\"Edit provider\" onClose={onClose} maxWidthClassName=\"max-w-2xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"prov-edit-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"prov-edit-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className={inputCls}\n required\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"prov-edit-category\" className={labelCls}>\n Category\n </label>\n <select\n id=\"prov-edit-category\"\n value={category}\n onChange={(e) => setCategory(e.target.value as SubProviderCategory)}\n className={inputCls}\n >\n {CATEGORIES.map((c) => (\n <option key={c} value={c}>\n {c.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"prov-edit-status\" className={labelCls}>\n Status\n </label>\n <select\n id=\"prov-edit-status\"\n value={status}\n onChange={(e) => setStatus(e.target.value as SubProviderStatus)}\n className={inputCls}\n >\n {STATUSES.map((s) => (\n <option key={s} value={s}>\n {s.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"prov-edit-industry\" className={labelCls}>\n Industry\n </label>\n <input\n id=\"prov-edit-industry\"\n type=\"text\"\n value={industry}\n onChange={(e) => setIndustry(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-region\" className={labelCls}>\n Region\n </label>\n <input\n id=\"prov-edit-region\"\n type=\"text\"\n value={region}\n onChange={(e) => setRegion(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-language\" className={labelCls}>\n Language\n </label>\n <input\n id=\"prov-edit-language\"\n type=\"text\"\n value={language}\n onChange={(e) => setLanguage(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"prov-edit-website\" className={labelCls}>\n Website\n </label>\n <input\n id=\"prov-edit-website\"\n type=\"url\"\n value={websiteUrl}\n onChange={(e) => setWebsiteUrl(e.target.value)}\n placeholder=\"https://…\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"prov-edit-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={3}\n className={inputCls}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"prov-edit-churn\" className={labelCls}>\n Churn risk (0–100)\n </label>\n <input\n id=\"prov-edit-churn\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n value={churnRiskScore}\n onChange={(e) => setChurnRiskScore(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"prov-edit-health\" className={labelCls}>\n Vendor health (0–100)\n </label>\n <input\n id=\"prov-edit-health\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n value={vendorHealthScore}\n onChange={(e) => setVendorHealthScore(e.target.value)}\n className={inputCls}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"prov-edit-tags\" className={labelCls}>\n Tags <span className=\"text-text-secondary\">(comma-separated)</span>\n </label>\n <input\n id=\"prov-edit-tags\"\n type=\"text\"\n value={tags}\n onChange={(e) => setTags(e.target.value)}\n className={inputCls}\n />\n </div>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={update.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {update.isPending ? 'Saving…' : 'Save changes'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Add plan modal → subCreatePlan\n// ---------------------------------------------------------------------------\n\nfunction AddPlanModal({ providerId, onClose }: { providerId: string; onClose: () => void }) {\n const create = useCreatePlan();\n const [name, setName] = useState('');\n const [amount, setAmount] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [interval, setInterval] = useState<SubBillingInterval>('MONTHLY');\n const [description, setDescription] = useState('');\n const [features, setFeatures] = useState('');\n const [isActive, setIsActive] = useState(true);\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError('A plan name is required.');\n return;\n }\n const parsedFeatures = features\n .split('\\n')\n .map((f) => f.trim())\n .filter(Boolean);\n create.mutate(\n {\n providerId,\n name: name.trim(),\n interval,\n currency: currency.trim() || 'USD',\n ...(amount.trim() ? { amount: Number(amount) } : {}),\n ...(description.trim() ? { description: description.trim() } : {}),\n ...(parsedFeatures.length ? { features: parsedFeatures } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) => setFormError(err instanceof Error ? err.message : 'Could not add plan.'),\n }\n );\n };\n\n return (\n <Modal title=\"Add plan\" onClose={onClose} maxWidthClassName=\"max-w-xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"plan-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"plan-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Pro, Team, Enterprise\"\n className={inputCls}\n required\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"plan-amount\" className={labelCls}>\n Amount\n </label>\n <input\n id=\"plan-amount\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n placeholder=\"0.00\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"plan-currency\" className={labelCls}>\n Currency\n </label>\n <input\n id=\"plan-currency\"\n type=\"text\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value.toUpperCase())}\n maxLength={3}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"plan-interval\" className={labelCls}>\n Interval\n </label>\n <select\n id=\"plan-interval\"\n value={interval}\n onChange={(e) => setInterval(e.target.value as SubBillingInterval)}\n className={inputCls}\n >\n {INTERVALS.map((i) => (\n <option key={i} value={i}>\n {i.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label htmlFor=\"plan-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"plan-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"plan-features\" className={labelCls}>\n Features <span className=\"text-text-secondary\">(one per line)</span>\n </label>\n <textarea\n id=\"plan-features\"\n value={features}\n onChange={(e) => setFeatures(e.target.value)}\n rows={3}\n placeholder={'Unlimited projects\\nPriority support\\nSSO'}\n className={inputCls}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={isActive}\n onChange={(e) => setIsActive(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Active\n </label>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Adding…' : 'Add plan'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Archive confirmation modal → subArchiveProvider\n// ---------------------------------------------------------------------------\n\nfunction ArchiveProviderModal({\n provider,\n onClose,\n}: {\n provider: SubProvider;\n onClose: () => void;\n}) {\n const { navigateTo } = useSubscriberBot();\n const archive = useArchiveProvider();\n const [error, setError] = useState<string | null>(null);\n\n const handleArchive = () => {\n setError(null);\n archive.mutate(provider.id, {\n onSuccess: () => navigateTo('/providers'),\n onError: (err) =>\n setError(err instanceof Error ? err.message : 'Could not archive this provider.'),\n });\n };\n\n return (\n <Modal title=\"Archive provider\" onClose={onClose}>\n <div className=\"space-y-4\">\n {error && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n Archive <span className=\"font-medium text-text-primary\">{provider.name}</span>? It will be\n removed from the active registry. This can be reversed by an administrator.\n </p>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleArchive}\n disabled={archive.isPending}\n className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm font-medium text-status-error-text hover:opacity-90 disabled:opacity-50\"\n >\n {archive.isPending ? 'Archiving…' : 'Archive provider'}\n </button>\n </div>\n </div>\n </Modal>\n );\n}\n\nexport function ProviderDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useSubscriberBot();\n const { data: provider, isLoading, isError, refetch } = useProvider(id);\n const [showEdit, setShowEdit] = useState(false);\n const [showAddPlan, setShowAddPlan] = useState(false);\n const [showArchive, setShowArchive] = useState(false);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Provider\">\n <LoadingView label=\"Loading provider…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Provider\">\n <ErrorView message=\"Could not load this provider.\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!provider) {\n return (\n <PageLayout title=\"Provider\">\n <EmptyState\n illustration=\"empty-data\"\n title=\"Provider not found\"\n description=\"It may have been removed, or you may not have access to it.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/providers')}\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 Back to providers\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n const p = provider;\n // Only render the website as a link when it is a safe http(s) URL — websiteUrl\n // is user-controlled, so guard against javascript:/data: schemes and add\n // rel=\"noopener noreferrer\" to the target=\"_blank\" anchor.\n const safeWebsiteUrl = p.websiteUrl && /^https?:\\/\\//i.test(p.websiteUrl) ? p.websiteUrl : null;\n\n // Highlight the cheapest active priced plan as \"Best value\" (derived from real\n // plan data — guard for the no-plans / all-free case).\n const bestValuePlanId =\n p.plans && p.plans.length > 1\n ? (p.plans\n .filter((pl) => pl.isActive && pl.amount > 0)\n .reduce<SubPlan | null>(\n (min, pl) => (min === null || pl.amount < min.amount ? pl : min),\n null\n )?.id ?? null)\n : null;\n\n return (\n <PageLayout\n title={p.name}\n description={formatEnumLabel(p.category)}\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setShowEdit(true)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n <button\n type=\"button\"\n onClick={() => setShowArchive(true)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-status-error-text/30 px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle\"\n >\n <Archive className=\"h-4 w-4\" /> Archive\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/providers')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n </div>\n }\n >\n {/* Identity row */}\n <div className=\"flex flex-wrap items-center gap-3\">\n <span className=\"flex size-11 items-center justify-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\">\n <Building2 className=\"h-5 w-5\" />\n </span>\n <StatusBadge status={p.status} />\n {p.industry && <span className=\"text-sm text-text-secondary\">{p.industry}</span>}\n {p.region && <span className=\"text-sm text-text-secondary\">· {p.region}</span>}\n {safeWebsiteUrl && (\n <a\n href={safeWebsiteUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1 text-sm text-text-link hover:underline\"\n >\n <Globe className=\"h-3.5 w-3.5\" /> Website\n </a>\n )}\n </div>\n\n {p.description && (\n <p className=\"mt-4 max-w-3xl text-sm leading-relaxed text-text-secondary\">\n {p.description}\n </p>\n )}\n\n {p.tags.length > 0 && (\n <div className=\"mt-4 flex flex-wrap items-center gap-2\">\n <Tag className=\"h-3.5 w-3.5 text-text-secondary\" />\n {p.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Plans — tier cards */}\n <div className=\"lg:col-span-2\">\n <Section\n title=\"Plans\"\n description=\"Pricing this provider publishes\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddPlan(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Add plan\n </button>\n }\n >\n {!p.plans || p.plans.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No published plans\"\n description=\"When this provider publishes plans, they'll appear here.\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddPlan(true)}\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 Add plan\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {p.plans.map((plan) => {\n const isBestValue = plan.id === bestValuePlanId && p.plans!.length > 1;\n return (\n <GlassCard\n key={plan.id}\n treatment={isBestValue ? 'insight' : 'metric'}\n glow={isBestValue}\n className=\"flex flex-col p-5\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"text-sm font-semibold text-text-primary\">{plan.name}</p>\n {isBestValue ? (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-[11px] font-medium text-status-success-text\">\n <Sparkles className=\"h-3 w-3\" /> Best value\n </span>\n ) : (\n <StatusBadge status={plan.isActive ? 'ACTIVE' : 'DISABLED'} />\n )}\n </div>\n <p className=\"mt-3 text-2xl font-bold tabular-nums text-text-primary\">\n {formatCurrency(plan.amount, plan.currency)}\n <span className=\"text-sm font-normal text-text-secondary\">\n {formatIntervalSuffix(plan.interval)}\n </span>\n </p>\n {plan.description && (\n <p className=\"mt-2 text-xs leading-relaxed text-text-secondary\">\n {plan.description}\n </p>\n )}\n {plan.features.length > 0 && (\n <ul className=\"mt-4 space-y-2 border-t border-border-subtle pt-4\">\n {plan.features.slice(0, 6).map((f) => (\n <li\n key={f}\n className=\"flex items-start gap-2 text-xs text-text-secondary\"\n >\n <Check className=\"mt-0.5 h-3.5 w-3.5 shrink-0 text-status-success-text\" />\n <span>{f}</span>\n </li>\n ))}\n {plan.features.length > 6 && (\n <li className=\"pl-6 text-[11px] text-text-secondary\">\n +{plan.features.length - 6} more\n </li>\n )}\n </ul>\n )}\n </GlassCard>\n );\n })}\n </div>\n )}\n </Section>\n </div>\n\n {/* Sidebar — vendor health, churn risk, at-a-glance */}\n <div className=\"space-y-4\">\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center justify-between\">\n <span className=\"inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <ShieldCheck className=\"h-4 w-4 text-text-secondary\" /> Vendor health\n </span>\n {p.vendorHealthScore != null && (\n <span\n className={cn(\n 'rounded-full px-2 py-0.5 text-[11px] font-medium',\n healthMeta(p.vendorHealthScore).cls\n )}\n >\n {healthMeta(p.vendorHealthScore).label}\n </span>\n )}\n </div>\n {p.vendorHealthScore != null ? (\n <div className=\"mt-2 flex flex-col items-center\">\n <Gauge value={p.vendorHealthScore} max={100} size={168} label=\"out of 100\" />\n </div>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">No health score yet.</p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center justify-between\">\n <span className=\"inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <TrendingDown className=\"h-4 w-4 text-text-secondary\" /> Churn risk\n </span>\n {p.churnRiskScore != null && (\n <span\n className={cn(\n 'rounded-full px-2 py-0.5 text-[11px] font-medium',\n churnMeta(p.churnRiskScore).chip\n )}\n >\n {churnMeta(p.churnRiskScore).label}\n </span>\n )}\n </div>\n {p.churnRiskScore != null ? (\n <>\n <div className=\"mt-3 flex items-baseline justify-between\">\n <span className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {p.churnRiskScore}\n </span>\n <span className=\"text-xs text-text-secondary\">/ 100</span>\n </div>\n <MiniProgress\n value={p.churnRiskScore}\n tone={churnMeta(p.churnRiskScore).tone}\n className=\"mt-2\"\n />\n </>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">No churn signal yet.</p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <span className=\"inline-flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Layers className=\"h-4 w-4 text-text-secondary\" /> At a glance\n </span>\n <dl className=\"mt-3 space-y-2.5 text-sm\">\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Category</dt>\n <dd className=\"font-medium text-text-primary\">{formatEnumLabel(p.category)}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Industry</dt>\n <dd className=\"font-medium text-text-primary\">{p.industry ?? '—'}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Region</dt>\n <dd className=\"font-medium text-text-primary\">{p.region ?? '—'}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Language</dt>\n <dd className=\"font-medium text-text-primary\">{p.language ?? '—'}</dd>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <dt className=\"text-text-secondary\">Published plans</dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">\n {p.plans?.length ?? 0}\n </dd>\n </div>\n </dl>\n </GlassCard>\n </div>\n </div>\n\n {showEdit && <ProviderEditModal provider={p} onClose={() => setShowEdit(false)} />}\n {showAddPlan && <AddPlanModal providerId={p.id} onClose={() => setShowAddPlan(false)} />}\n {showArchive && <ArchiveProviderModal provider={p} onClose={() => setShowArchive(false)} />}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -10,7 +10,7 @@ import "../../components/index.js";
10
10
  import { useCancelRelationship as h, useCreateAsset as g, useCreateEntitlement as _, useFileDownloadUrl as v, usePlans as y, useProviders as b, useRelationship as x, useTransitionRelationship as S, useUpdateRelationship as C, useUploadFile as w } from "../../hooks/useSubscriberBotApi.js";
11
11
  import { useState as T } from "react";
12
12
  import { Activity as E, ArrowLeft as D, Ban as O, CalendarClock as k, CreditCard as A, Download as j, Paperclip as M, Pause as N, Pencil as P, Play as F, Plus as I, ShieldCheck as L, Tag as ee, Wallet as te } from "lucide-react";
13
- import { useParams as ne } from "react-router-dom";
13
+ import { useParams as ne } from "react-router";
14
14
  import { Fragment as R, jsx as z, jsxs as B } from "react/jsx-runtime";
15
15
  import { GlassCard as V } from "@burdenoff/fe-libs/ui";
16
16
  //#region src/subscriberbot/pages/relationships/RelationshipDetailPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"RelationshipDetailPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/relationships/RelationshipDetailPage.tsx"],"sourcesContent":["/**\n * Relationship detail\n * @module subscriberbot/pages/relationships\n *\n * Full per-relationship view: plan, price, billing period, next renewal,\n * health score, entitlements, attached assets and renewal events — plus\n * lifecycle transitions (validated server-side), edit, and cancel.\n * Reads `subRelationship(id)`; writes `subTransitionRelationship`,\n * `subCancelRelationship`, `subUpdateRelationship`, `subCreateEntitlement`,\n * `subCreateAsset`.\n */\n\nimport { useState, type ReactNode } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n CreditCard,\n Wallet,\n ShieldCheck,\n CalendarClock,\n Paperclip,\n Activity,\n Tag,\n Pencil,\n Plus,\n Pause,\n Play,\n Ban,\n Download,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Modal } from '../../components/Modal';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { Gauge, Timeline, MiniProgress } from '../../components';\nimport type { TimelineItem } from '../../components';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport {\n useRelationship,\n useTransitionRelationship,\n useCancelRelationship,\n useUpdateRelationship,\n useCreateEntitlement,\n useCreateAsset,\n useUploadFile,\n useFileDownloadUrl,\n useProviders,\n usePlans,\n} from '../../hooks/useSubscriberBotApi';\nimport {\n formatCurrency,\n formatDate,\n formatEnumLabel,\n formatIntervalSuffix,\n} from '../../utils/formatters';\nimport type {\n SubAssetType,\n SubBillingInterval,\n SubEntitlementType,\n SubRelationship,\n SubRelationshipStatus,\n SubUpdateRelationshipInput,\n} from '../../types';\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\nconst INTERVALS: SubBillingInterval[] = [\n 'MONTHLY',\n 'YEARLY',\n 'QUARTERLY',\n 'WEEKLY',\n 'DAILY',\n 'USAGE_BASED',\n 'LIFETIME',\n 'FREE',\n];\n\nconst ENTITLEMENT_TYPES: SubEntitlementType[] = [\n 'FEATURE_ACCESS',\n 'SEAT',\n 'USAGE_QUOTA',\n 'CONTENT',\n 'DISCOUNT',\n 'SUPPORT_TIER',\n];\n\nconst ASSET_TYPES: SubAssetType[] = [\n 'RECEIPT',\n 'INVOICE',\n 'CONTRACT',\n 'LICENSE_KEY',\n 'CREDENTIAL',\n 'CERTIFICATE',\n 'WARRANTY',\n 'DOMAIN',\n];\n\n// Lifecycle actions surfaced as buttons; the server validates the transition.\nconst TRANSITIONS: Array<{ status: SubRelationshipStatus; label: string; icon: typeof Play }> = [\n { status: 'ACTIVE', label: 'Activate', icon: Play },\n { status: 'PAUSED', label: 'Pause', icon: Pause },\n { status: 'EXPIRED', label: 'Mark expired', icon: Ban },\n];\n\n/** Compact label/value row for the detail info cards. */\nfunction InfoRow({ label, value }: { label: string; value: ReactNode }) {\n return (\n <div className=\"flex items-center justify-between gap-3 border-b border-border-subtle/60 py-2.5 last:border-0 last:pb-0\">\n <span className=\"shrink-0 text-sm text-text-secondary\">{label}</span>\n <span className=\"min-w-0 truncate text-right text-sm font-medium text-text-primary\">\n {value}\n </span>\n </div>\n );\n}\n\n/** Map a 0–100 health score onto a MiniProgress tone. */\nfunction healthTone(\n score: number | null | undefined\n): 'positive' | 'accent' | 'warning' | 'negative' {\n if (score == null) return 'accent';\n if (score >= 66) return 'positive';\n if (score >= 33) return 'warning';\n return 'negative';\n}\n\n/** ISO string → yyyy-mm-dd for a native <input type=\"date\">. */\nfunction toDateInputValue(iso: string | null | undefined): string {\n if (!iso) return '';\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return '';\n return d.toISOString().slice(0, 10);\n}\n\n// ---------------------------------------------------------------------------\n// Edit relationship modal → subUpdateRelationship\n// ---------------------------------------------------------------------------\n\nfunction RelationshipEditModal({\n relationship,\n onClose,\n}: {\n relationship: SubRelationship;\n onClose: () => void;\n}) {\n const update = useUpdateRelationship();\n const providersQ = useProviders({ pagination: { limit: 100, offset: 0 } });\n\n const [label, setLabel] = useState(relationship.label);\n const [providerId, setProviderId] = useState(relationship.providerId ?? '');\n const [planId, setPlanId] = useState(relationship.planId ?? '');\n const [amount, setAmount] = useState(String(relationship.amount ?? ''));\n const [currency, setCurrency] = useState(relationship.currency);\n const [interval, setInterval] = useState<SubBillingInterval>(relationship.interval);\n const [renewsAt, setRenewsAt] = useState(toDateInputValue(relationship.renewsAt));\n const [autoRenew, setAutoRenew] = useState(relationship.autoRenew);\n const [healthScore, setHealthScore] = useState(\n relationship.healthScore != null ? String(relationship.healthScore) : ''\n );\n const [notes, setNotes] = useState(relationship.notes ?? '');\n const [formError, setFormError] = useState<string | null>(null);\n\n const plansQ = usePlans(providerId || undefined);\n\n // Ensure the currently-linked provider is always selectable even if it isn't\n // in the first page of the registry.\n const providerOptions = (() => {\n const list = providersQ.data?.items ?? [];\n if (relationship.provider && !list.some((p) => p.id === relationship.provider?.id)) {\n return [relationship.provider, ...list];\n }\n return list;\n })();\n const plans = plansQ.data ?? [];\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!label.trim()) {\n setFormError('A label is required.');\n return;\n }\n const input: SubUpdateRelationshipInput = {\n label: label.trim(),\n interval,\n autoRenew,\n currency: currency.trim() || 'USD',\n notes: notes.trim(),\n ...(providerId ? { providerId } : {}),\n ...(planId ? { planId } : {}),\n ...(amount.trim() ? { amount: Number(amount) } : {}),\n ...(healthScore.trim() ? { healthScore: Number(healthScore) } : {}),\n ...(renewsAt ? { renewsAt: new Date(renewsAt).toISOString() } : {}),\n };\n update.mutate(\n { id: relationship.id, input },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not update relationship.'),\n }\n );\n };\n\n return (\n <Modal title=\"Edit relationship\" onClose={onClose} maxWidthClassName=\"max-w-2xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n\n <div>\n <label htmlFor=\"edit-label\" className={labelCls}>\n Label <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"edit-label\"\n type=\"text\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n className={inputCls}\n required\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"edit-provider\" className={labelCls}>\n Provider\n </label>\n <select\n id=\"edit-provider\"\n value={providerId}\n onChange={(e) => {\n setProviderId(e.target.value);\n setPlanId('');\n }}\n className={inputCls}\n >\n <option value=\"\">— None —</option>\n {providerOptions.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"edit-plan\" className={labelCls}>\n Plan\n </label>\n <select\n id=\"edit-plan\"\n value={planId}\n onChange={(e) => setPlanId(e.target.value)}\n className={inputCls}\n disabled={!providerId || plans.length === 0}\n >\n <option value=\"\">— None —</option>\n {plans.map((pl) => (\n <option key={pl.id} value={pl.id}>\n {pl.name}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"edit-amount\" className={labelCls}>\n Amount\n </label>\n <input\n id=\"edit-amount\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"edit-currency\" className={labelCls}>\n Currency\n </label>\n <input\n id=\"edit-currency\"\n type=\"text\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value.toUpperCase())}\n maxLength={3}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"edit-interval\" className={labelCls}>\n Billing interval\n </label>\n <select\n id=\"edit-interval\"\n value={interval}\n onChange={(e) => setInterval(e.target.value as SubBillingInterval)}\n className={inputCls}\n >\n {INTERVALS.map((i) => (\n <option key={i} value={i}>\n {i.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"edit-renews\" className={labelCls}>\n Renews on\n </label>\n <input\n id=\"edit-renews\"\n type=\"date\"\n value={renewsAt}\n onChange={(e) => setRenewsAt(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"edit-health\" className={labelCls}>\n Health score (0–100)\n </label>\n <input\n id=\"edit-health\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n value={healthScore}\n onChange={(e) => setHealthScore(e.target.value)}\n className={inputCls}\n />\n </div>\n <div className=\"flex items-end\">\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={autoRenew}\n onChange={(e) => setAutoRenew(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Auto-renews\n </label>\n </div>\n </div>\n\n <div>\n <label htmlFor=\"edit-notes\" className={labelCls}>\n Notes\n </label>\n <textarea\n id=\"edit-notes\"\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n rows={3}\n className={inputCls}\n />\n </div>\n\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={update.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {update.isPending ? 'Saving…' : 'Save changes'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Add entitlement modal → subCreateEntitlement\n// ---------------------------------------------------------------------------\n\nfunction AddEntitlementModal({\n relationshipId,\n onClose,\n}: {\n relationshipId: string;\n onClose: () => void;\n}) {\n const create = useCreateEntitlement();\n const [name, setName] = useState('');\n const [type, setType] = useState<SubEntitlementType>('FEATURE_ACCESS');\n const [quota, setQuota] = useState('');\n const [unit, setUnit] = useState('');\n const [description, setDescription] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError('An entitlement name is required.');\n return;\n }\n create.mutate(\n {\n relationshipId,\n name: name.trim(),\n type,\n ...(quota.trim() ? { quota: Number(quota) } : {}),\n ...(unit.trim() ? { unit: unit.trim() } : {}),\n ...(description.trim() ? { description: description.trim() } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not add entitlement.'),\n }\n );\n };\n\n return (\n <Modal title=\"Add entitlement\" onClose={onClose}>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"ent-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"ent-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Seats, API calls, Priority support\"\n className={inputCls}\n required\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"ent-type\" className={labelCls}>\n Type\n </label>\n <select\n id=\"ent-type\"\n value={type}\n onChange={(e) => setType(e.target.value as SubEntitlementType)}\n className={inputCls}\n >\n {ENTITLEMENT_TYPES.map((t) => (\n <option key={t} value={t}>\n {t.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ent-quota\" className={labelCls}>\n Quota\n </label>\n <input\n id=\"ent-quota\"\n type=\"number\"\n min=\"0\"\n value={quota}\n onChange={(e) => setQuota(e.target.value)}\n placeholder=\"e.g. 5\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"ent-unit\" className={labelCls}>\n Unit\n </label>\n <input\n id=\"ent-unit\"\n type=\"text\"\n value={unit}\n onChange={(e) => setUnit(e.target.value)}\n placeholder=\"e.g. seats, GB\"\n className={inputCls}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"ent-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"ent-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={inputCls}\n />\n </div>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Adding…' : 'Add entitlement'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Attach asset modal → upload to files-svc, then subCreateAsset\n// ---------------------------------------------------------------------------\n\nfunction AttachAssetModal({\n relationshipId,\n onClose,\n}: {\n relationshipId: string;\n onClose: () => void;\n}) {\n const create = useCreateAsset();\n const { uploadFile, progress, isUploading } = useUploadFile();\n const [name, setName] = useState('');\n const [type, setType] = useState<SubAssetType>('RECEIPT');\n const [file, setFile] = useState<File | null>(null);\n const [expiresAt, setExpiresAt] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const busy = isUploading || create.isPending;\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!file) {\n setFormError('Choose a file to attach.');\n return;\n }\n try {\n const { fileId, fileName } = await uploadFile(file);\n await create.mutateAsync({\n relationshipId,\n name: name.trim() || fileName,\n type,\n fileId,\n ...(expiresAt ? { expiresAt: new Date(expiresAt).toISOString() } : {}),\n });\n onClose();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Could not attach the asset.');\n }\n };\n\n return (\n <Modal title=\"Attach asset\" onClose={onClose}>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"asset-file\" className={labelCls}>\n File <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"asset-file\"\n type=\"file\"\n onChange={(e) => {\n const f = e.target.files?.[0] ?? null;\n setFile(f);\n if (f && !name.trim()) setName(f.name);\n }}\n className=\"w-full text-sm text-text-secondary file:mr-3 file:rounded-lg file:border file:border-border-subtle file:bg-bg-surface file:px-3 file:py-1.5 file:text-sm file:font-medium file:text-text-primary hover:file:bg-bg-sunken\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n Stored securely in files — only a reference is kept here.\n </p>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"asset-name\" className={labelCls}>\n Name\n </label>\n <input\n id=\"asset-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"Defaults to the file name\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"asset-type\" className={labelCls}>\n Type\n </label>\n <select\n id=\"asset-type\"\n value={type}\n onChange={(e) => setType(e.target.value as SubAssetType)}\n className={inputCls}\n >\n {ASSET_TYPES.map((t) => (\n <option key={t} value={t}>\n {t.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label htmlFor=\"asset-expires\" className={labelCls}>\n Expires on\n </label>\n <input\n id=\"asset-expires\"\n type=\"date\"\n value={expiresAt}\n onChange={(e) => setExpiresAt(e.target.value)}\n className={inputCls}\n />\n </div>\n {isUploading && <p className=\"text-xs text-text-secondary\">Uploading… {progress}%</p>}\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={busy}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {busy ? 'Attaching…' : 'Attach asset'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Asset row — resolves a presigned download URL on demand\n// ---------------------------------------------------------------------------\n\nfunction AssetRow({ asset }: { asset: NonNullable<SubRelationship['assets']>[number] }) {\n const getDownloadUrl = useFileDownloadUrl();\n const [downloading, setDownloading] = useState(false);\n\n const handleDownload = async () => {\n if (!asset.file?.id) return;\n setDownloading(true);\n try {\n const url = await getDownloadUrl(asset.file.id);\n window.open(url, '_blank', 'noopener,noreferrer');\n } catch {\n // Non-fatal: a broken/expired file reference just can't be downloaded.\n } finally {\n setDownloading(false);\n }\n };\n\n return (\n <div className=\"flex items-center justify-between gap-3 p-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <Paperclip className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{asset.name}</p>\n <p className=\"text-xs text-text-secondary\">{formatEnumLabel(asset.type)}</p>\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-3\">\n {asset.expiresAt && (\n <span className=\"text-xs text-text-secondary\">Expires {formatDate(asset.expiresAt)}</span>\n )}\n {asset.file?.id && (\n <button\n type=\"button\"\n onClick={handleDownload}\n disabled={downloading}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Download className=\"h-3.5 w-3.5\" /> {downloading ? 'Opening…' : 'Download'}\n </button>\n )}\n </div>\n </div>\n );\n}\n\nexport function RelationshipDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useSubscriberBot();\n const { data: relationship, isLoading, isError, refetch } = useRelationship(id);\n const transition = useTransitionRelationship();\n const cancel = useCancelRelationship();\n const [actionError, setActionError] = useState<string | null>(null);\n const [showEdit, setShowEdit] = useState(false);\n const [showAddEntitlement, setShowAddEntitlement] = useState(false);\n const [showAttachAsset, setShowAttachAsset] = useState(false);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Relationship\">\n <LoadingView label=\"Loading relationship…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Relationship\">\n <ErrorView message=\"Could not load this relationship.\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!relationship) {\n return (\n <PageLayout title=\"Relationship\">\n <EmptyState\n illustration=\"empty-data\"\n title=\"Relationship not found\"\n description=\"It may have been removed, or you may not have access to it.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships')}\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 Back to relationships\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n const r = relationship;\n const isBusy = transition.isPending || cancel.isPending;\n\n const runTransition = (status: SubRelationshipStatus) => {\n setActionError(null);\n transition.mutate(\n { id: r.id, status },\n { onError: (e) => setActionError(e instanceof Error ? e.message : 'Transition failed') }\n );\n };\n\n const runCancel = () => {\n setActionError(null);\n cancel.mutate(r.id, {\n onError: (e) => setActionError(e instanceof Error ? e.message : 'Cancel failed'),\n });\n };\n\n // Renewal timeline — built entirely from the real renewalEvents, oldest → newest.\n const renewalTimeline: TimelineItem[] = [...(r.renewalEvents ?? [])]\n .sort((a, b) => new Date(a.occursAt).getTime() - new Date(b.occursAt).getTime())\n .map((event) => {\n const ts = new Date(event.occursAt).getTime();\n const isFuture = !Number.isNaN(ts) && ts > Date.now();\n return {\n title: formatEnumLabel(event.eventType),\n sub: `${formatDate(event.occursAt)}${event.detail ? ` · ${event.detail}` : ''}`,\n meta:\n event.amount != null\n ? formatCurrency(event.amount, event.currency ?? r.currency)\n : undefined,\n tone: isFuture ? 'warning' : 'muted',\n };\n });\n\n const autoRenewPill = r.autoRenew ? (\n <span className=\"inline-flex items-center rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-xs font-medium text-status-success-text\">\n On\n </span>\n ) : (\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n Off\n </span>\n );\n\n const availableTransitions = TRANSITIONS.filter((t) => t.status !== r.status);\n const planFeatures = r.plan?.features ?? [];\n\n return (\n <PageLayout\n title={r.label}\n description={r.provider?.name ?? formatEnumLabel(r.kind)}\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setShowEdit(true)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n </div>\n }\n >\n {/* Hero summary + lifecycle actions */}\n <GlassCard treatment=\"glass\" glow className=\"p-5 sm:p-6\">\n <div className=\"flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"min-w-0\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {formatEnumLabel(r.kind)}\n </p>\n <div className=\"mt-2 flex flex-wrap items-center gap-3\">\n <span className=\"text-3xl font-bold tabular-nums text-text-primary\">\n {formatCurrency(r.amount, r.currency)}\n <span className=\"text-base font-normal text-text-secondary\">\n {formatIntervalSuffix(r.interval)}\n </span>\n </span>\n <StatusBadge status={r.status} />\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-text-secondary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <CalendarClock className=\"h-3.5 w-3.5\" />\n {r.renewsAt ? `Renews ${formatDate(r.renewsAt)}` : 'No renewal scheduled'}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <ShieldCheck className=\"h-3.5 w-3.5\" />\n {formatEnumLabel(r.discoverySource)}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">Auto-renew {autoRenewPill}</span>\n </div>\n {r.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap items-center gap-2\">\n <Tag className=\"h-3.5 w-3.5 text-text-secondary\" />\n {r.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n </div>\n\n {/* Lifecycle actions */}\n <div className=\"flex shrink-0 flex-wrap gap-2 lg:flex-col lg:items-stretch\">\n {availableTransitions.map((t) => {\n const Icon = t.icon;\n return (\n <button\n key={t.status}\n type=\"button\"\n disabled={isBusy}\n onClick={() => runTransition(t.status)}\n className=\"inline-flex items-center justify-center gap-1.5 rounded-lg border border-border-subtle bg-bg-surface px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Icon className=\"h-3.5 w-3.5\" /> {t.label}\n </button>\n );\n })}\n {r.status !== 'CANCELLED' && (\n <button\n type=\"button\"\n disabled={isBusy}\n onClick={runCancel}\n className=\"inline-flex items-center justify-center gap-1.5 rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-3 py-1.5 text-xs font-medium text-status-error-text hover:opacity-90 disabled:opacity-50\"\n >\n <Ban className=\"h-3.5 w-3.5\" /> Cancel\n </button>\n )}\n </div>\n </div>\n </GlassCard>\n\n {actionError && (\n <div className=\"mt-4 rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {actionError}\n </div>\n )}\n\n {/* Main layout: details (left) + health & timeline rail (right) */}\n <div className=\"mt-6 grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <div className=\"space-y-4 lg:col-span-2\">\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {/* Plan & pricing */}\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <CreditCard className=\"h-4 w-4 text-text-secondary\" /> Plan &amp; pricing\n </div>\n <div className=\"mt-3\">\n <InfoRow label=\"Plan\" value={r.plan?.name ?? '—'} />\n <InfoRow\n label=\"Price\"\n value={\n <>\n {formatCurrency(r.amount, r.currency)}\n <span className=\"font-normal text-text-secondary\">\n {formatIntervalSuffix(r.interval)}\n </span>\n </>\n }\n />\n <InfoRow label=\"Billing interval\" value={formatEnumLabel(r.interval)} />\n <InfoRow label=\"Provider\" value={r.provider?.name ?? '—'} />\n </div>\n {planFeatures.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {planFeatures.map((f) => (\n <span\n key={f}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {f}\n </span>\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Payment & billing */}\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Wallet className=\"h-4 w-4 text-text-secondary\" /> Payment &amp; billing\n </div>\n <div className=\"mt-3\">\n <InfoRow label=\"Currency\" value={r.currency} />\n <InfoRow label=\"Auto-renew\" value={autoRenewPill} />\n <InfoRow label=\"Next renewal\" value={r.renewsAt ? formatDate(r.renewsAt) : '—'} />\n <InfoRow label=\"Started\" value={r.startedAt ? formatDate(r.startedAt) : '—'} />\n {r.trialEndsAt && <InfoRow label=\"Trial ends\" value={formatDate(r.trialEndsAt)} />}\n </div>\n </GlassCard>\n </div>\n\n {/* Entitlements */}\n <Section\n className=\"mt-0\"\n title=\"Entitlements\"\n description=\"What this relationship grants you\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddEntitlement(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Add entitlement\n </button>\n }\n >\n {!r.entitlements || r.entitlements.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No entitlements\"\n description=\"Entitlements (seats, quotas, feature access) will appear here.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {r.entitlements.map((e) => {\n const hasQuota = e.quota != null && e.quota > 0;\n return (\n <GlassCard key={e.id} treatment=\"plain\" className=\"p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"min-w-0 truncate text-sm font-medium text-text-primary\">\n {e.name}\n </p>\n <StatusBadge status={e.isActive ? 'ACTIVE' : 'DISABLED'} />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">{formatEnumLabel(e.type)}</p>\n {hasQuota && (\n <div className=\"mt-3\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {e.used} / {e.quota}\n {e.unit ? ` ${e.unit}` : ''}\n </span>\n <span className=\"tabular-nums\">\n {Math.round((e.used / (e.quota as number)) * 100)}%\n </span>\n </div>\n <MiniProgress\n value={e.used}\n max={e.quota as number}\n tone={\n e.used / (e.quota as number) >= 0.9\n ? 'negative'\n : e.used / (e.quota as number) >= 0.7\n ? 'warning'\n : 'accent'\n }\n className=\"mt-1.5\"\n />\n </div>\n )}\n </GlassCard>\n );\n })}\n </div>\n )}\n </Section>\n\n {/* Assets */}\n <Section\n className=\"mt-0\"\n title=\"Assets\"\n description=\"Invoices, contracts, licenses and credentials\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAttachAsset(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Attach asset\n </button>\n }\n >\n {!r.assets || r.assets.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No assets attached\"\n description=\"Attach invoices, contracts, licenses or receipts to this relationship.\"\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"divide-y divide-border-subtle p-0\">\n {r.assets.map((a) => (\n <AssetRow key={a.id} asset={a} />\n ))}\n </GlassCard>\n )}\n </Section>\n\n {r.notes && (\n <GlassCard treatment=\"plain\" className=\"p-4\">\n <p className=\"text-xs font-medium text-text-secondary\">Notes</p>\n <p className=\"mt-1 whitespace-pre-wrap text-sm text-text-primary\">{r.notes}</p>\n </GlassCard>\n )}\n </div>\n\n {/* Right rail: health gauge + renewal timeline */}\n <div className=\"space-y-4\">\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Activity className=\"h-4 w-4 text-text-secondary\" /> Health score\n </div>\n {r.healthScore != null ? (\n <div className=\"mt-4 flex flex-col items-center\">\n <Gauge value={r.healthScore} max={100} size={160} label=\"of 100\" />\n <div className=\"mt-2 w-full\">\n <MiniProgress value={r.healthScore} max={100} tone={healthTone(r.healthScore)} />\n </div>\n </div>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">\n Not scored yet. A health score appears once Subscriber Bot has enough signal.\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <CalendarClock className=\"h-4 w-4 text-text-secondary\" /> Renewal timeline\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Detected renewals and price changes\n </p>\n {renewalTimeline.length === 0 ? (\n <div className=\"mt-4\">\n <EmptyState\n illustration=\"empty-calendar\"\n title=\"No renewal events\"\n description=\"Renewals and price changes will appear here as they're detected.\"\n />\n </div>\n ) : (\n <Timeline items={renewalTimeline} className=\"mt-4\" />\n )}\n </GlassCard>\n </div>\n </div>\n\n {showEdit && <RelationshipEditModal relationship={r} onClose={() => setShowEdit(false)} />}\n {showAddEntitlement && (\n <AddEntitlementModal relationshipId={r.id} onClose={() => setShowAddEntitlement(false)} />\n )}\n {showAttachAsset && (\n <AttachAssetModal relationshipId={r.id} onClose={() => setShowAttachAsset(false)} />\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RelationshipDetailPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/relationships/RelationshipDetailPage.tsx"],"sourcesContent":["/**\n * Relationship detail\n * @module subscriberbot/pages/relationships\n *\n * Full per-relationship view: plan, price, billing period, next renewal,\n * health score, entitlements, attached assets and renewal events — plus\n * lifecycle transitions (validated server-side), edit, and cancel.\n * Reads `subRelationship(id)`; writes `subTransitionRelationship`,\n * `subCancelRelationship`, `subUpdateRelationship`, `subCreateEntitlement`,\n * `subCreateAsset`.\n */\n\nimport { useState, type ReactNode } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n CreditCard,\n Wallet,\n ShieldCheck,\n CalendarClock,\n Paperclip,\n Activity,\n Tag,\n Pencil,\n Plus,\n Pause,\n Play,\n Ban,\n Download,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Modal } from '../../components/Modal';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { Gauge, Timeline, MiniProgress } from '../../components';\nimport type { TimelineItem } from '../../components';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport {\n useRelationship,\n useTransitionRelationship,\n useCancelRelationship,\n useUpdateRelationship,\n useCreateEntitlement,\n useCreateAsset,\n useUploadFile,\n useFileDownloadUrl,\n useProviders,\n usePlans,\n} from '../../hooks/useSubscriberBotApi';\nimport {\n formatCurrency,\n formatDate,\n formatEnumLabel,\n formatIntervalSuffix,\n} from '../../utils/formatters';\nimport type {\n SubAssetType,\n SubBillingInterval,\n SubEntitlementType,\n SubRelationship,\n SubRelationshipStatus,\n SubUpdateRelationshipInput,\n} from '../../types';\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\nconst INTERVALS: SubBillingInterval[] = [\n 'MONTHLY',\n 'YEARLY',\n 'QUARTERLY',\n 'WEEKLY',\n 'DAILY',\n 'USAGE_BASED',\n 'LIFETIME',\n 'FREE',\n];\n\nconst ENTITLEMENT_TYPES: SubEntitlementType[] = [\n 'FEATURE_ACCESS',\n 'SEAT',\n 'USAGE_QUOTA',\n 'CONTENT',\n 'DISCOUNT',\n 'SUPPORT_TIER',\n];\n\nconst ASSET_TYPES: SubAssetType[] = [\n 'RECEIPT',\n 'INVOICE',\n 'CONTRACT',\n 'LICENSE_KEY',\n 'CREDENTIAL',\n 'CERTIFICATE',\n 'WARRANTY',\n 'DOMAIN',\n];\n\n// Lifecycle actions surfaced as buttons; the server validates the transition.\nconst TRANSITIONS: Array<{ status: SubRelationshipStatus; label: string; icon: typeof Play }> = [\n { status: 'ACTIVE', label: 'Activate', icon: Play },\n { status: 'PAUSED', label: 'Pause', icon: Pause },\n { status: 'EXPIRED', label: 'Mark expired', icon: Ban },\n];\n\n/** Compact label/value row for the detail info cards. */\nfunction InfoRow({ label, value }: { label: string; value: ReactNode }) {\n return (\n <div className=\"flex items-center justify-between gap-3 border-b border-border-subtle/60 py-2.5 last:border-0 last:pb-0\">\n <span className=\"shrink-0 text-sm text-text-secondary\">{label}</span>\n <span className=\"min-w-0 truncate text-right text-sm font-medium text-text-primary\">\n {value}\n </span>\n </div>\n );\n}\n\n/** Map a 0–100 health score onto a MiniProgress tone. */\nfunction healthTone(\n score: number | null | undefined\n): 'positive' | 'accent' | 'warning' | 'negative' {\n if (score == null) return 'accent';\n if (score >= 66) return 'positive';\n if (score >= 33) return 'warning';\n return 'negative';\n}\n\n/** ISO string → yyyy-mm-dd for a native <input type=\"date\">. */\nfunction toDateInputValue(iso: string | null | undefined): string {\n if (!iso) return '';\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return '';\n return d.toISOString().slice(0, 10);\n}\n\n// ---------------------------------------------------------------------------\n// Edit relationship modal → subUpdateRelationship\n// ---------------------------------------------------------------------------\n\nfunction RelationshipEditModal({\n relationship,\n onClose,\n}: {\n relationship: SubRelationship;\n onClose: () => void;\n}) {\n const update = useUpdateRelationship();\n const providersQ = useProviders({ pagination: { limit: 100, offset: 0 } });\n\n const [label, setLabel] = useState(relationship.label);\n const [providerId, setProviderId] = useState(relationship.providerId ?? '');\n const [planId, setPlanId] = useState(relationship.planId ?? '');\n const [amount, setAmount] = useState(String(relationship.amount ?? ''));\n const [currency, setCurrency] = useState(relationship.currency);\n const [interval, setInterval] = useState<SubBillingInterval>(relationship.interval);\n const [renewsAt, setRenewsAt] = useState(toDateInputValue(relationship.renewsAt));\n const [autoRenew, setAutoRenew] = useState(relationship.autoRenew);\n const [healthScore, setHealthScore] = useState(\n relationship.healthScore != null ? String(relationship.healthScore) : ''\n );\n const [notes, setNotes] = useState(relationship.notes ?? '');\n const [formError, setFormError] = useState<string | null>(null);\n\n const plansQ = usePlans(providerId || undefined);\n\n // Ensure the currently-linked provider is always selectable even if it isn't\n // in the first page of the registry.\n const providerOptions = (() => {\n const list = providersQ.data?.items ?? [];\n if (relationship.provider && !list.some((p) => p.id === relationship.provider?.id)) {\n return [relationship.provider, ...list];\n }\n return list;\n })();\n const plans = plansQ.data ?? [];\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!label.trim()) {\n setFormError('A label is required.');\n return;\n }\n const input: SubUpdateRelationshipInput = {\n label: label.trim(),\n interval,\n autoRenew,\n currency: currency.trim() || 'USD',\n notes: notes.trim(),\n ...(providerId ? { providerId } : {}),\n ...(planId ? { planId } : {}),\n ...(amount.trim() ? { amount: Number(amount) } : {}),\n ...(healthScore.trim() ? { healthScore: Number(healthScore) } : {}),\n ...(renewsAt ? { renewsAt: new Date(renewsAt).toISOString() } : {}),\n };\n update.mutate(\n { id: relationship.id, input },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not update relationship.'),\n }\n );\n };\n\n return (\n <Modal title=\"Edit relationship\" onClose={onClose} maxWidthClassName=\"max-w-2xl\">\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n\n <div>\n <label htmlFor=\"edit-label\" className={labelCls}>\n Label <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"edit-label\"\n type=\"text\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n className={inputCls}\n required\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"edit-provider\" className={labelCls}>\n Provider\n </label>\n <select\n id=\"edit-provider\"\n value={providerId}\n onChange={(e) => {\n setProviderId(e.target.value);\n setPlanId('');\n }}\n className={inputCls}\n >\n <option value=\"\">— None —</option>\n {providerOptions.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"edit-plan\" className={labelCls}>\n Plan\n </label>\n <select\n id=\"edit-plan\"\n value={planId}\n onChange={(e) => setPlanId(e.target.value)}\n className={inputCls}\n disabled={!providerId || plans.length === 0}\n >\n <option value=\"\">— None —</option>\n {plans.map((pl) => (\n <option key={pl.id} value={pl.id}>\n {pl.name}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"edit-amount\" className={labelCls}>\n Amount\n </label>\n <input\n id=\"edit-amount\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"edit-currency\" className={labelCls}>\n Currency\n </label>\n <input\n id=\"edit-currency\"\n type=\"text\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value.toUpperCase())}\n maxLength={3}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"edit-interval\" className={labelCls}>\n Billing interval\n </label>\n <select\n id=\"edit-interval\"\n value={interval}\n onChange={(e) => setInterval(e.target.value as SubBillingInterval)}\n className={inputCls}\n >\n {INTERVALS.map((i) => (\n <option key={i} value={i}>\n {i.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"edit-renews\" className={labelCls}>\n Renews on\n </label>\n <input\n id=\"edit-renews\"\n type=\"date\"\n value={renewsAt}\n onChange={(e) => setRenewsAt(e.target.value)}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"edit-health\" className={labelCls}>\n Health score (0–100)\n </label>\n <input\n id=\"edit-health\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n value={healthScore}\n onChange={(e) => setHealthScore(e.target.value)}\n className={inputCls}\n />\n </div>\n <div className=\"flex items-end\">\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={autoRenew}\n onChange={(e) => setAutoRenew(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Auto-renews\n </label>\n </div>\n </div>\n\n <div>\n <label htmlFor=\"edit-notes\" className={labelCls}>\n Notes\n </label>\n <textarea\n id=\"edit-notes\"\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n rows={3}\n className={inputCls}\n />\n </div>\n\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={update.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {update.isPending ? 'Saving…' : 'Save changes'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Add entitlement modal → subCreateEntitlement\n// ---------------------------------------------------------------------------\n\nfunction AddEntitlementModal({\n relationshipId,\n onClose,\n}: {\n relationshipId: string;\n onClose: () => void;\n}) {\n const create = useCreateEntitlement();\n const [name, setName] = useState('');\n const [type, setType] = useState<SubEntitlementType>('FEATURE_ACCESS');\n const [quota, setQuota] = useState('');\n const [unit, setUnit] = useState('');\n const [description, setDescription] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!name.trim()) {\n setFormError('An entitlement name is required.');\n return;\n }\n create.mutate(\n {\n relationshipId,\n name: name.trim(),\n type,\n ...(quota.trim() ? { quota: Number(quota) } : {}),\n ...(unit.trim() ? { unit: unit.trim() } : {}),\n ...(description.trim() ? { description: description.trim() } : {}),\n },\n {\n onSuccess: () => onClose(),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not add entitlement.'),\n }\n );\n };\n\n return (\n <Modal title=\"Add entitlement\" onClose={onClose}>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"ent-name\" className={labelCls}>\n Name <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"ent-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"e.g. Seats, API calls, Priority support\"\n className={inputCls}\n required\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"ent-type\" className={labelCls}>\n Type\n </label>\n <select\n id=\"ent-type\"\n value={type}\n onChange={(e) => setType(e.target.value as SubEntitlementType)}\n className={inputCls}\n >\n {ENTITLEMENT_TYPES.map((t) => (\n <option key={t} value={t}>\n {t.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"ent-quota\" className={labelCls}>\n Quota\n </label>\n <input\n id=\"ent-quota\"\n type=\"number\"\n min=\"0\"\n value={quota}\n onChange={(e) => setQuota(e.target.value)}\n placeholder=\"e.g. 5\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"ent-unit\" className={labelCls}>\n Unit\n </label>\n <input\n id=\"ent-unit\"\n type=\"text\"\n value={unit}\n onChange={(e) => setUnit(e.target.value)}\n placeholder=\"e.g. seats, GB\"\n className={inputCls}\n />\n </div>\n </div>\n <div>\n <label htmlFor=\"ent-desc\" className={labelCls}>\n Description\n </label>\n <textarea\n id=\"ent-desc\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className={inputCls}\n />\n </div>\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Adding…' : 'Add entitlement'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Attach asset modal → upload to files-svc, then subCreateAsset\n// ---------------------------------------------------------------------------\n\nfunction AttachAssetModal({\n relationshipId,\n onClose,\n}: {\n relationshipId: string;\n onClose: () => void;\n}) {\n const create = useCreateAsset();\n const { uploadFile, progress, isUploading } = useUploadFile();\n const [name, setName] = useState('');\n const [type, setType] = useState<SubAssetType>('RECEIPT');\n const [file, setFile] = useState<File | null>(null);\n const [expiresAt, setExpiresAt] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const busy = isUploading || create.isPending;\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!file) {\n setFormError('Choose a file to attach.');\n return;\n }\n try {\n const { fileId, fileName } = await uploadFile(file);\n await create.mutateAsync({\n relationshipId,\n name: name.trim() || fileName,\n type,\n fileId,\n ...(expiresAt ? { expiresAt: new Date(expiresAt).toISOString() } : {}),\n });\n onClose();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Could not attach the asset.');\n }\n };\n\n return (\n <Modal title=\"Attach asset\" onClose={onClose}>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n <div>\n <label htmlFor=\"asset-file\" className={labelCls}>\n File <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"asset-file\"\n type=\"file\"\n onChange={(e) => {\n const f = e.target.files?.[0] ?? null;\n setFile(f);\n if (f && !name.trim()) setName(f.name);\n }}\n className=\"w-full text-sm text-text-secondary file:mr-3 file:rounded-lg file:border file:border-border-subtle file:bg-bg-surface file:px-3 file:py-1.5 file:text-sm file:font-medium file:text-text-primary hover:file:bg-bg-sunken\"\n />\n <p className=\"mt-1 text-xs text-text-secondary\">\n Stored securely in files — only a reference is kept here.\n </p>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"asset-name\" className={labelCls}>\n Name\n </label>\n <input\n id=\"asset-name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n placeholder=\"Defaults to the file name\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"asset-type\" className={labelCls}>\n Type\n </label>\n <select\n id=\"asset-type\"\n value={type}\n onChange={(e) => setType(e.target.value as SubAssetType)}\n className={inputCls}\n >\n {ASSET_TYPES.map((t) => (\n <option key={t} value={t}>\n {t.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label htmlFor=\"asset-expires\" className={labelCls}>\n Expires on\n </label>\n <input\n id=\"asset-expires\"\n type=\"date\"\n value={expiresAt}\n onChange={(e) => setExpiresAt(e.target.value)}\n className={inputCls}\n />\n </div>\n {isUploading && <p className=\"text-xs text-text-secondary\">Uploading… {progress}%</p>}\n <div className=\"flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={busy}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {busy ? 'Attaching…' : 'Attach asset'}\n </button>\n </div>\n </form>\n </Modal>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Asset row — resolves a presigned download URL on demand\n// ---------------------------------------------------------------------------\n\nfunction AssetRow({ asset }: { asset: NonNullable<SubRelationship['assets']>[number] }) {\n const getDownloadUrl = useFileDownloadUrl();\n const [downloading, setDownloading] = useState(false);\n\n const handleDownload = async () => {\n if (!asset.file?.id) return;\n setDownloading(true);\n try {\n const url = await getDownloadUrl(asset.file.id);\n window.open(url, '_blank', 'noopener,noreferrer');\n } catch {\n // Non-fatal: a broken/expired file reference just can't be downloaded.\n } finally {\n setDownloading(false);\n }\n };\n\n return (\n <div className=\"flex items-center justify-between gap-3 p-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <Paperclip className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{asset.name}</p>\n <p className=\"text-xs text-text-secondary\">{formatEnumLabel(asset.type)}</p>\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-3\">\n {asset.expiresAt && (\n <span className=\"text-xs text-text-secondary\">Expires {formatDate(asset.expiresAt)}</span>\n )}\n {asset.file?.id && (\n <button\n type=\"button\"\n onClick={handleDownload}\n disabled={downloading}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-2.5 py-1 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Download className=\"h-3.5 w-3.5\" /> {downloading ? 'Opening…' : 'Download'}\n </button>\n )}\n </div>\n </div>\n );\n}\n\nexport function RelationshipDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useSubscriberBot();\n const { data: relationship, isLoading, isError, refetch } = useRelationship(id);\n const transition = useTransitionRelationship();\n const cancel = useCancelRelationship();\n const [actionError, setActionError] = useState<string | null>(null);\n const [showEdit, setShowEdit] = useState(false);\n const [showAddEntitlement, setShowAddEntitlement] = useState(false);\n const [showAttachAsset, setShowAttachAsset] = useState(false);\n\n if (isLoading) {\n return (\n <PageLayout title=\"Relationship\">\n <LoadingView label=\"Loading relationship…\" />\n </PageLayout>\n );\n }\n\n if (isError) {\n return (\n <PageLayout title=\"Relationship\">\n <ErrorView message=\"Could not load this relationship.\" onRetry={() => void refetch()} />\n </PageLayout>\n );\n }\n\n if (!relationship) {\n return (\n <PageLayout title=\"Relationship\">\n <EmptyState\n illustration=\"empty-data\"\n title=\"Relationship not found\"\n description=\"It may have been removed, or you may not have access to it.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships')}\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 Back to relationships\n </button>\n }\n />\n </PageLayout>\n );\n }\n\n const r = relationship;\n const isBusy = transition.isPending || cancel.isPending;\n\n const runTransition = (status: SubRelationshipStatus) => {\n setActionError(null);\n transition.mutate(\n { id: r.id, status },\n { onError: (e) => setActionError(e instanceof Error ? e.message : 'Transition failed') }\n );\n };\n\n const runCancel = () => {\n setActionError(null);\n cancel.mutate(r.id, {\n onError: (e) => setActionError(e instanceof Error ? e.message : 'Cancel failed'),\n });\n };\n\n // Renewal timeline — built entirely from the real renewalEvents, oldest → newest.\n const renewalTimeline: TimelineItem[] = [...(r.renewalEvents ?? [])]\n .sort((a, b) => new Date(a.occursAt).getTime() - new Date(b.occursAt).getTime())\n .map((event) => {\n const ts = new Date(event.occursAt).getTime();\n const isFuture = !Number.isNaN(ts) && ts > Date.now();\n return {\n title: formatEnumLabel(event.eventType),\n sub: `${formatDate(event.occursAt)}${event.detail ? ` · ${event.detail}` : ''}`,\n meta:\n event.amount != null\n ? formatCurrency(event.amount, event.currency ?? r.currency)\n : undefined,\n tone: isFuture ? 'warning' : 'muted',\n };\n });\n\n const autoRenewPill = r.autoRenew ? (\n <span className=\"inline-flex items-center rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-xs font-medium text-status-success-text\">\n On\n </span>\n ) : (\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n Off\n </span>\n );\n\n const availableTransitions = TRANSITIONS.filter((t) => t.status !== r.status);\n const planFeatures = r.plan?.features ?? [];\n\n return (\n <PageLayout\n title={r.label}\n description={r.provider?.name ?? formatEnumLabel(r.kind)}\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setShowEdit(true)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Pencil className=\"h-4 w-4\" /> Edit\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n </div>\n }\n >\n {/* Hero summary + lifecycle actions */}\n <GlassCard treatment=\"glass\" glow className=\"p-5 sm:p-6\">\n <div className=\"flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"min-w-0\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-secondary\">\n {formatEnumLabel(r.kind)}\n </p>\n <div className=\"mt-2 flex flex-wrap items-center gap-3\">\n <span className=\"text-3xl font-bold tabular-nums text-text-primary\">\n {formatCurrency(r.amount, r.currency)}\n <span className=\"text-base font-normal text-text-secondary\">\n {formatIntervalSuffix(r.interval)}\n </span>\n </span>\n <StatusBadge status={r.status} />\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-text-secondary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <CalendarClock className=\"h-3.5 w-3.5\" />\n {r.renewsAt ? `Renews ${formatDate(r.renewsAt)}` : 'No renewal scheduled'}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <ShieldCheck className=\"h-3.5 w-3.5\" />\n {formatEnumLabel(r.discoverySource)}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">Auto-renew {autoRenewPill}</span>\n </div>\n {r.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap items-center gap-2\">\n <Tag className=\"h-3.5 w-3.5 text-text-secondary\" />\n {r.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n </div>\n\n {/* Lifecycle actions */}\n <div className=\"flex shrink-0 flex-wrap gap-2 lg:flex-col lg:items-stretch\">\n {availableTransitions.map((t) => {\n const Icon = t.icon;\n return (\n <button\n key={t.status}\n type=\"button\"\n disabled={isBusy}\n onClick={() => runTransition(t.status)}\n className=\"inline-flex items-center justify-center gap-1.5 rounded-lg border border-border-subtle bg-bg-surface px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Icon className=\"h-3.5 w-3.5\" /> {t.label}\n </button>\n );\n })}\n {r.status !== 'CANCELLED' && (\n <button\n type=\"button\"\n disabled={isBusy}\n onClick={runCancel}\n className=\"inline-flex items-center justify-center gap-1.5 rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-3 py-1.5 text-xs font-medium text-status-error-text hover:opacity-90 disabled:opacity-50\"\n >\n <Ban className=\"h-3.5 w-3.5\" /> Cancel\n </button>\n )}\n </div>\n </div>\n </GlassCard>\n\n {actionError && (\n <div className=\"mt-4 rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {actionError}\n </div>\n )}\n\n {/* Main layout: details (left) + health & timeline rail (right) */}\n <div className=\"mt-6 grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <div className=\"space-y-4 lg:col-span-2\">\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {/* Plan & pricing */}\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <CreditCard className=\"h-4 w-4 text-text-secondary\" /> Plan &amp; pricing\n </div>\n <div className=\"mt-3\">\n <InfoRow label=\"Plan\" value={r.plan?.name ?? '—'} />\n <InfoRow\n label=\"Price\"\n value={\n <>\n {formatCurrency(r.amount, r.currency)}\n <span className=\"font-normal text-text-secondary\">\n {formatIntervalSuffix(r.interval)}\n </span>\n </>\n }\n />\n <InfoRow label=\"Billing interval\" value={formatEnumLabel(r.interval)} />\n <InfoRow label=\"Provider\" value={r.provider?.name ?? '—'} />\n </div>\n {planFeatures.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {planFeatures.map((f) => (\n <span\n key={f}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {f}\n </span>\n ))}\n </div>\n )}\n </GlassCard>\n\n {/* Payment & billing */}\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Wallet className=\"h-4 w-4 text-text-secondary\" /> Payment &amp; billing\n </div>\n <div className=\"mt-3\">\n <InfoRow label=\"Currency\" value={r.currency} />\n <InfoRow label=\"Auto-renew\" value={autoRenewPill} />\n <InfoRow label=\"Next renewal\" value={r.renewsAt ? formatDate(r.renewsAt) : '—'} />\n <InfoRow label=\"Started\" value={r.startedAt ? formatDate(r.startedAt) : '—'} />\n {r.trialEndsAt && <InfoRow label=\"Trial ends\" value={formatDate(r.trialEndsAt)} />}\n </div>\n </GlassCard>\n </div>\n\n {/* Entitlements */}\n <Section\n className=\"mt-0\"\n title=\"Entitlements\"\n description=\"What this relationship grants you\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAddEntitlement(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Add entitlement\n </button>\n }\n >\n {!r.entitlements || r.entitlements.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No entitlements\"\n description=\"Entitlements (seats, quotas, feature access) will appear here.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {r.entitlements.map((e) => {\n const hasQuota = e.quota != null && e.quota > 0;\n return (\n <GlassCard key={e.id} treatment=\"plain\" className=\"p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"min-w-0 truncate text-sm font-medium text-text-primary\">\n {e.name}\n </p>\n <StatusBadge status={e.isActive ? 'ACTIVE' : 'DISABLED'} />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">{formatEnumLabel(e.type)}</p>\n {hasQuota && (\n <div className=\"mt-3\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {e.used} / {e.quota}\n {e.unit ? ` ${e.unit}` : ''}\n </span>\n <span className=\"tabular-nums\">\n {Math.round((e.used / (e.quota as number)) * 100)}%\n </span>\n </div>\n <MiniProgress\n value={e.used}\n max={e.quota as number}\n tone={\n e.used / (e.quota as number) >= 0.9\n ? 'negative'\n : e.used / (e.quota as number) >= 0.7\n ? 'warning'\n : 'accent'\n }\n className=\"mt-1.5\"\n />\n </div>\n )}\n </GlassCard>\n );\n })}\n </div>\n )}\n </Section>\n\n {/* Assets */}\n <Section\n className=\"mt-0\"\n title=\"Assets\"\n description=\"Invoices, contracts, licenses and credentials\"\n action={\n <button\n type=\"button\"\n onClick={() => setShowAttachAsset(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <Plus className=\"h-3.5 w-3.5\" /> Attach asset\n </button>\n }\n >\n {!r.assets || r.assets.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No assets attached\"\n description=\"Attach invoices, contracts, licenses or receipts to this relationship.\"\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"divide-y divide-border-subtle p-0\">\n {r.assets.map((a) => (\n <AssetRow key={a.id} asset={a} />\n ))}\n </GlassCard>\n )}\n </Section>\n\n {r.notes && (\n <GlassCard treatment=\"plain\" className=\"p-4\">\n <p className=\"text-xs font-medium text-text-secondary\">Notes</p>\n <p className=\"mt-1 whitespace-pre-wrap text-sm text-text-primary\">{r.notes}</p>\n </GlassCard>\n )}\n </div>\n\n {/* Right rail: health gauge + renewal timeline */}\n <div className=\"space-y-4\">\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Activity className=\"h-4 w-4 text-text-secondary\" /> Health score\n </div>\n {r.healthScore != null ? (\n <div className=\"mt-4 flex flex-col items-center\">\n <Gauge value={r.healthScore} max={100} size={160} label=\"of 100\" />\n <div className=\"mt-2 w-full\">\n <MiniProgress value={r.healthScore} max={100} tone={healthTone(r.healthScore)} />\n </div>\n </div>\n ) : (\n <p className=\"mt-4 text-sm text-text-secondary\">\n Not scored yet. A health score appears once Subscriber Bot has enough signal.\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <CalendarClock className=\"h-4 w-4 text-text-secondary\" /> Renewal timeline\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n Detected renewals and price changes\n </p>\n {renewalTimeline.length === 0 ? (\n <div className=\"mt-4\">\n <EmptyState\n illustration=\"empty-calendar\"\n title=\"No renewal events\"\n description=\"Renewals and price changes will appear here as they're detected.\"\n />\n </div>\n ) : (\n <Timeline items={renewalTimeline} className=\"mt-4\" />\n )}\n </GlassCard>\n </div>\n </div>\n\n {showEdit && <RelationshipEditModal relationship={r} onClose={() => setShowEdit(false)} />}\n {showAddEntitlement && (\n <AddEntitlementModal relationshipId={r.id} onClose={() => setShowAddEntitlement(false)} />\n )}\n {showAttachAsset && (\n <AttachAssetModal relationshipId={r.id} onClose={() => setShowAttachAsset(false)} />\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-subscriberbot",
3
- "version": "2026.731.1",
3
+ "version": "2026.801.1",
4
4
  "description": "Burdenoff SubscriberBot Microfrontend",
5
5
  "type": "module",
6
6
  "files": [
@@ -47,14 +47,14 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@apollo/client": "^4.0.11",
50
- "@burdenoff/fe-libs": "2026.713.6",
50
+ "@burdenoff/fe-libs": "2026.801.1",
51
51
  "@tanstack/react-query": "^5.90.16",
52
52
  "clsx": "^2.1.1",
53
53
  "graphql": "^16.12.0",
54
54
  "lucide-react": "^0.468.0",
55
55
  "react": "^19.0.0",
56
56
  "react-dom": "^19.0.0",
57
- "react-router-dom": "^7.1.3",
57
+ "react-router": "^8.3.0",
58
58
  "tailwind-merge": "^3.4.0",
59
59
  "zustand": "^5.0.7"
60
60
  },
@@ -101,7 +101,7 @@
101
101
  "access": "restricted"
102
102
  },
103
103
  "overrides": {
104
- "@burdenoff/fe-libs": "2026.713.6",
104
+ "@burdenoff/fe-libs": "2026.801.1",
105
105
  "lodash": ">=4.17.21",
106
106
  "lodash-es": ">=4.17.21",
107
107
  "dompurify": ">=3.3.4",