@burdenoff/microfe-botmanor 2026.926.1 → 2026.927.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/botlit/BotmanorRootInner.js +1 -1
- package/dist/botlit/BotmanorRootInner.js.map +1 -1
- package/dist/botlit/components/WorkspaceRequired.js.map +1 -1
- package/dist/botlit/pages/BotChannelsTab.js +1 -1
- package/dist/botlit/pages/BotChannelsTab.js.map +1 -1
- package/dist/botlit/store/botlitStore.js +14 -13
- package/dist/botlit/store/botlitStore.js.map +1 -1
- package/dist/botlit/store/renamedKeyStorage.js +15 -0
- package/dist/botlit/store/renamedKeyStorage.js.map +1 -0
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@ import { ErrorBoundary as h } from "@burdenoff/fe-libs/shared/components";
|
|
|
11
11
|
import { FeatureFlagProvider as g } from "@burdenoff/fe-libs/shared/feature-flags";
|
|
12
12
|
import { refetchStoreNodes as _, resetFluidGridsClient as v, resetFluidGridsConfig as y, resetGlobalStoreClient as b, setFluidGridsClient as x, setFluidGridsConfig as S, setGlobalStoreClient as C } from "@burdenoff/fluidgrids-fe-sdk";
|
|
13
13
|
//#region src/botlit/BotmanorRootInner.tsx
|
|
14
|
-
var w = "
|
|
14
|
+
var w = "botmanor", T = "BotManor", E = 1e4, D = 2e3, O = new p({ defaultOptions: { queries: {
|
|
15
15
|
staleTime: 300 * 1e3,
|
|
16
16
|
retry: 1
|
|
17
17
|
} } }), k = (p) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BotmanorRootInner.js","names":[],"sources":["../../src/botlit/BotmanorRootInner.tsx"],"sourcesContent":["/**\n * BotManor Root — implementation\n * @module botlit\n *\n * The heavy half of {@link BotmanorRoot}. Everything that pulls a large\n * dependency graph lives behind this module so the package entry point stays\n * small: `BotmanorRootInner` statically imports `BotlitRoutes`, which in turn\n * pulls the whole `@burdenoff/fluidgrids-fe-sdk` barrel (workflow builder,\n * @xyflow/react, framer-motion …). `BotmanorRoot` loads this module lazily and\n * paints a skeleton meanwhile, so the host never shows an empty content\n * region while that graph downloads.\n */\n\nimport type { FC } from 'react';\nimport { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { ApolloProvider } from '@apollo/client/react';\nimport { ErrorBoundary } from '@burdenoff/fe-libs/shared/components';\nimport { BotlitProvider } from './providers/BotlitProvider';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { BotlitRoutes } from './BotlitRoutes';\nimport { RouteLoader } from './components/RouteLoader';\nimport { WorkspaceRequired } from './components/WorkspaceRequired';\nimport type { BotmanorRootProps } from './types';\n\n// FluidGrids SDK client injection (needed for workflow components)\nimport {\n setFluidGridsClient,\n resetFluidGridsClient,\n setFluidGridsConfig,\n resetFluidGridsConfig,\n setGlobalStoreClient,\n resetGlobalStoreClient,\n refetchStoreNodes,\n type WorkflowSavedResult,\n} from '@burdenoff/fluidgrids-fe-sdk';\n// Use the shared gateway Apollo clients from the shell\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\n\n/** Product identity handed to the FluidGrids SDK when the shell does not override it. */\nconst DEFAULT_PRODUCT_ID = 'botlit';\nconst DEFAULT_PRODUCT_NAME = 'BotManor';\n\n/** How long to wait for the shell's gateway clients before showing an error. */\nconst CLIENT_READY_TIMEOUT_MS = 10_000;\n\n/**\n * How long to keep showing the loading skeleton before concluding that the\n * shell really has no active workspace.\n *\n * The shell resolves org/workspace asynchronously and passes `workspaceId=\"\"`\n * until it lands, which is indistinguishable from \"this user has no\n * workspace\". Without this settle window the guided zero state would flash on\n * every cold load — a get-started screen appearing and vanishing reads as a\n * broken page, which is exactly what this component exists to avoid.\n */\nconst WORKSPACE_SETTLE_MS = 2_000;\n\n// Create a query client for this microfrontend\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000,\n retry: 1,\n },\n },\n});\n\nexport const BotmanorRootInner: FC<BotmanorRootProps> = (props) => {\n // NOTE: Navigation is now handled by the shell's NavRegistration.tsx\n // The shell manages all product submenu items in a centralized way\n\n // Get shell's Apollo clients (both workspace and global for FluidGrids SDK)\n const workspaceClient = useGatewayClient('workspace');\n const globalClient = useGatewayClient('global');\n const [clientReady, setClientReady] = useState(false);\n const [clientError, setClientError] = useState<string | null>(null);\n const [workspaceSettled, setWorkspaceSettled] = useState(false);\n\n // Inject Apollo clients into FluidGrids SDK (for workflow components).\n // This synchronizes external stores (module-level client singletons in the SDK)\n // with React's lifecycle. The setState call is intentional — it gates rendering\n // so children don't mount before the SDK clients are available.\n //\n // KNOWN TRADE-OFF (mirrors microfe-bigconsole): clientReady is NOT reset in\n // cleanup. When the client references change (e.g. token refresh) children keep\n // rendering against a briefly uninitialised SDK client until the effect re-runs.\n // That avoids a spinner flash mid-session; failed requests are retried by\n // Apollo/React Query.\n useEffect(() => {\n if (workspaceClient && globalClient) {\n setFluidGridsClient(workspaceClient);\n setGlobalStoreClient(globalClient);\n // Node definitions come from the global store — refresh them now that the\n // SDK has a client, otherwise the builder palette stays empty.\n void refetchStoreNodes().catch((error: unknown) => {\n console.warn('[BotManor] Failed to refresh FluidGrids store nodes', error);\n });\n setClientReady(true);\n }\n return () => {\n resetFluidGridsClient();\n resetGlobalStoreClient();\n };\n }, [workspaceClient, globalClient]);\n\n // Timeout fallback: if gateway clients are never provided (test env, shell\n // misconfiguration, auth failure before hydration), show an error rather than\n // spinning indefinitely.\n useEffect(() => {\n if (clientReady) return;\n const timer = setTimeout(() => {\n setClientError(\n 'BotManor could not connect to the gateway. Please refresh the page or check your configuration.'\n );\n }, CLIENT_READY_TIMEOUT_MS);\n return () => clearTimeout(timer);\n }, [clientReady]);\n\n // Give the shell a bounded window to resolve the active workspace before the\n // guided zero state is allowed to render. Cancelled the moment a workspace\n // arrives — a workspace that is already present never starts the timer.\n useEffect(() => {\n if (props.workspaceId) return;\n const timer = setTimeout(() => setWorkspaceSettled(true), WORKSPACE_SETTLE_MS);\n return () => clearTimeout(timer);\n }, [props.workspaceId]);\n\n // Hook point for syncing a saved workflow back into BotManor. BotManor keeps no\n // separate workflow record (unlike BigConsole), so there is nothing to write —\n // the bridge exists so a future sync can be added without re-initialising the SDK.\n const handleWorkflowSaved = useCallback((result: WorkflowSavedResult) => {\n if (import.meta.env.DEV) {\n console.info('[BotManor] Workflow saved', result.workflowKey, result.operation);\n }\n }, []);\n\n // Ref-forwarding: decouple the callback from the SDK config effect so that\n // workspaceClient reference changes don't trigger resetFluidGridsConfig() + re-init.\n const handleWorkflowSavedRef = useRef(handleWorkflowSaved);\n useLayoutEffect(() => {\n handleWorkflowSavedRef.current = handleWorkflowSaved;\n }, [handleWorkflowSaved]);\n\n // Configure FluidGrids SDK settings\n useEffect(() => {\n if (!props.apiGatewayUrl) return;\n\n const normalizedBase = props.apiGatewayUrl.endsWith('/')\n ? props.apiGatewayUrl.slice(0, -1)\n : props.apiGatewayUrl;\n\n setFluidGridsConfig({\n apiGatewayUrl: normalizedBase,\n authToken: props.authToken,\n workspaceId: props.workspaceId ?? '',\n tenantId: props.tenantId,\n // The SDK filters workflows by productId. It MUST be this product — falling\n // back to the shell's default ('workspaces') would leak another product's\n // workflows into BotManor.\n productId: props.productId ?? DEFAULT_PRODUCT_ID,\n productName: props.productName ?? DEFAULT_PRODUCT_NAME,\n // Stable wrapper via ref — avoids SDK re-init when workspaceClient changes reference\n onWorkflowSaved: (result: WorkflowSavedResult) => handleWorkflowSavedRef.current(result),\n });\n\n return () => {\n resetFluidGridsConfig();\n };\n }, [\n props.apiGatewayUrl,\n props.authToken,\n props.workspaceId,\n props.tenantId,\n props.productId,\n props.productName,\n ]);\n\n // Without a workspace every BotManor query is unscoped and every write is\n // rejected — guide the user instead of rendering empty lists. Until the\n // settle window elapses we cannot tell \"still resolving\" from \"none\", so we\n // keep the same skeleton the shell was already showing.\n if (!props.workspaceId) {\n if (!workspaceSettled) {\n return <RouteLoader label=\"Loading your workspace…\" />;\n }\n return <WorkspaceRequired basePath={props.basePath} navigate={props.navigate} />;\n }\n\n // Don't render until clients are ready (same pattern as microfe-bigconsole)\n if (!clientReady) {\n if (clientError) {\n return (\n <div className=\"flex h-full min-h-[200px] w-full items-center justify-center p-6\">\n <div className=\"text-center\">\n <p className=\"mb-4 text-sm text-text-secondary\">{clientError}</p>\n <button\n type=\"button\"\n onClick={() => window.location.reload()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n Reload Page\n </button>\n </div>\n </div>\n );\n }\n // Same skeleton as the lazy-chunk fallback, so the content region never\n // switches loading idioms between first paint and the page rendering.\n return <RouteLoader />;\n }\n\n const isIntegrated = !!props.basePath;\n\n const content = (\n <BotlitProvider {...props}>\n <BotlitRoutes />\n </BotlitProvider>\n );\n\n return (\n <ErrorBoundary\n fallback={\n <div className=\"p-6 text-center text-text-secondary\">\n Something went wrong loading BotManor. Please refresh the page.\n </div>\n }\n >\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <QueryClientProvider client={queryClient}>\n {isIntegrated ? (\n content\n ) : (\n <ApolloProvider client={workspaceClient}>{content}</ApolloProvider>\n )}\n </QueryClientProvider>\n </FeatureFlagProvider>\n </ErrorBoundary>\n );\n};\n"],"mappings":";;;;;;;;;;;;;AAwCA,IAAM,IAAqB,UACrB,IAAuB,YAGvB,IAA0B,KAY1B,IAAsB,KAGtB,IAAc,IAAI,EAAY,EAClC,gBAAgB,EACd,SAAS;CACP,WAAW,MAAS;CACpB,OAAO;CACR,EACF,EACF,CAAC,EAEW,KAA4C,MAAU;CAKjE,IAAM,IAAkB,EAAiB,YAAY,EAC/C,IAAe,EAAiB,SAAS,EACzC,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAkB,KAAuB,EAAS,GAAM;AA6C/D,CAjCA,SACM,KAAmB,MACrB,EAAoB,EAAgB,EACpC,EAAqB,EAAa,EAG7B,GAAmB,CAAC,OAAO,MAAmB;AACjD,UAAQ,KAAK,uDAAuD,EAAM;GAC1E,EACF,EAAe,GAAK,SAET;AAEX,EADA,GAAuB,EACvB,GAAwB;KAEzB,CAAC,GAAiB,EAAa,CAAC,EAKnC,QAAgB;AACd,MAAI,EAAa;EACjB,IAAM,IAAQ,iBAAiB;AAC7B,KACE,kGACD;KACA,EAAwB;AAC3B,eAAa,aAAa,EAAM;IAC/B,CAAC,EAAY,CAAC,EAKjB,QAAgB;AACd,MAAI,EAAM,YAAa;EACvB,IAAM,IAAQ,iBAAiB,EAAoB,GAAK,EAAE,EAAoB;AAC9E,eAAa,aAAa,EAAM;IAC/B,CAAC,EAAM,YAAY,CAAC;CAKvB,IAAM,IAAsB,GAAa,MAAgC,IAItE,EAAE,CAAC,EAIA,IAAyB,EAAO,EAAoB;AA2C1D,KA1CA,QAAsB;AACpB,IAAuB,UAAU;IAChC,CAAC,EAAoB,CAAC,EAGzB,QAAgB;AACT,QAAM,cAoBX,QAdA,EAAoB;GAClB,eALqB,EAAM,cAAc,SAAS,IAAI,GACpD,EAAM,cAAc,MAAM,GAAG,GAAG,GAChC,EAAM;GAIR,WAAW,EAAM;GACjB,aAAa,EAAM,eAAe;GAClC,UAAU,EAAM;GAIhB,WAAW,EAAM,aAAa;GAC9B,aAAa,EAAM,eAAe;GAElC,kBAAkB,MAAgC,EAAuB,QAAQ,EAAO;GACzF,CAAC,QAEW;AACX,MAAuB;;IAExB;EACD,EAAM;EACN,EAAM;EACN,EAAM;EACN,EAAM;EACN,EAAM;EACN,EAAM;EACP,CAAC,EAME,CAAC,EAAM,YAIT,QAHK,IAGE,kBAAC,GAAD;EAAmB,UAAU,EAAM;EAAU,UAAU,EAAM;EAAY,CAAA,GAFvE,kBAAC,GAAD,EAAa,OAAM,2BAA4B,CAAA;AAM1D,KAAI,CAAC,EAmBH,QAlBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cAAoC;IAAgB,CAAA,EACjE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,OAAO,SAAS,QAAQ;IACvC,WAAU;cACX;IAEQ,CAAA,CACL;;EACF,CAAA,GAKH,kBAAC,GAAD,EAAe,CAAA;CAGxB,IAAM,IAAe,CAAC,CAAC,EAAM,UAEvB,IACJ,kBAAC,GAAD;EAAgB,GAAI;YAClB,kBAAC,GAAD,EAAgB,CAAA;EACD,CAAA;AAGnB,QACE,kBAAC,GAAD;EACE,UACE,kBAAC,OAAD;GAAK,WAAU;aAAsC;GAE/C,CAAA;YAGR,kBAAC,GAAD;GAAqB,aAAa;GAAM,SAAQ;GAAS,gBAAgB;aACvE,kBAAC,GAAD;IAAqB,QAAQ;cAC1B,IACC,IAEA,kBAAC,GAAD;KAAgB,QAAQ;eAAkB;KAAyB,CAAA;IAEjD,CAAA;GACF,CAAA;EACR,CAAA"}
|
|
1
|
+
{"version":3,"file":"BotmanorRootInner.js","names":[],"sources":["../../src/botlit/BotmanorRootInner.tsx"],"sourcesContent":["/**\n * BotManor Root — implementation\n * @module botlit\n *\n * The heavy half of {@link BotmanorRoot}. Everything that pulls a large\n * dependency graph lives behind this module so the package entry point stays\n * small: `BotmanorRootInner` statically imports `BotlitRoutes`, which in turn\n * pulls the whole `@burdenoff/fluidgrids-fe-sdk` barrel (workflow builder,\n * @xyflow/react, framer-motion …). `BotmanorRoot` loads this module lazily and\n * paints a skeleton meanwhile, so the host never shows an empty content\n * region while that graph downloads.\n */\n\nimport type { FC } from 'react';\nimport { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { ApolloProvider } from '@apollo/client/react';\nimport { ErrorBoundary } from '@burdenoff/fe-libs/shared/components';\nimport { BotlitProvider } from './providers/BotlitProvider';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { BotlitRoutes } from './BotlitRoutes';\nimport { RouteLoader } from './components/RouteLoader';\nimport { WorkspaceRequired } from './components/WorkspaceRequired';\nimport type { BotmanorRootProps } from './types';\n\n// FluidGrids SDK client injection (needed for workflow components)\nimport {\n setFluidGridsClient,\n resetFluidGridsClient,\n setFluidGridsConfig,\n resetFluidGridsConfig,\n setGlobalStoreClient,\n resetGlobalStoreClient,\n refetchStoreNodes,\n type WorkflowSavedResult,\n} from '@burdenoff/fluidgrids-fe-sdk';\n// Use the shared gateway Apollo clients from the shell\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\n\n/**\n * Product identity handed to the FluidGrids SDK when the shell does not override it.\n * `botmanor` since the Botlit -> BotManor rename's Stage 5a; workflow\n * definitions stored under the legacy `botlit` were migrated, and the workflow\n * service matches either slug.\n */\nconst DEFAULT_PRODUCT_ID = 'botmanor';\nconst DEFAULT_PRODUCT_NAME = 'BotManor';\n\n/** How long to wait for the shell's gateway clients before showing an error. */\nconst CLIENT_READY_TIMEOUT_MS = 10_000;\n\n/**\n * How long to keep showing the loading skeleton before concluding that the\n * shell really has no active workspace.\n *\n * The shell resolves org/workspace asynchronously and passes `workspaceId=\"\"`\n * until it lands, which is indistinguishable from \"this user has no\n * workspace\". Without this settle window the guided zero state would flash on\n * every cold load — a get-started screen appearing and vanishing reads as a\n * broken page, which is exactly what this component exists to avoid.\n */\nconst WORKSPACE_SETTLE_MS = 2_000;\n\n// Create a query client for this microfrontend\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000,\n retry: 1,\n },\n },\n});\n\nexport const BotmanorRootInner: FC<BotmanorRootProps> = (props) => {\n // NOTE: Navigation is now handled by the shell's NavRegistration.tsx\n // The shell manages all product submenu items in a centralized way\n\n // Get shell's Apollo clients (both workspace and global for FluidGrids SDK)\n const workspaceClient = useGatewayClient('workspace');\n const globalClient = useGatewayClient('global');\n const [clientReady, setClientReady] = useState(false);\n const [clientError, setClientError] = useState<string | null>(null);\n const [workspaceSettled, setWorkspaceSettled] = useState(false);\n\n // Inject Apollo clients into FluidGrids SDK (for workflow components).\n // This synchronizes external stores (module-level client singletons in the SDK)\n // with React's lifecycle. The setState call is intentional — it gates rendering\n // so children don't mount before the SDK clients are available.\n //\n // KNOWN TRADE-OFF (mirrors microfe-bigconsole): clientReady is NOT reset in\n // cleanup. When the client references change (e.g. token refresh) children keep\n // rendering against a briefly uninitialised SDK client until the effect re-runs.\n // That avoids a spinner flash mid-session; failed requests are retried by\n // Apollo/React Query.\n useEffect(() => {\n if (workspaceClient && globalClient) {\n setFluidGridsClient(workspaceClient);\n setGlobalStoreClient(globalClient);\n // Node definitions come from the global store — refresh them now that the\n // SDK has a client, otherwise the builder palette stays empty.\n void refetchStoreNodes().catch((error: unknown) => {\n console.warn('[BotManor] Failed to refresh FluidGrids store nodes', error);\n });\n setClientReady(true);\n }\n return () => {\n resetFluidGridsClient();\n resetGlobalStoreClient();\n };\n }, [workspaceClient, globalClient]);\n\n // Timeout fallback: if gateway clients are never provided (test env, shell\n // misconfiguration, auth failure before hydration), show an error rather than\n // spinning indefinitely.\n useEffect(() => {\n if (clientReady) return;\n const timer = setTimeout(() => {\n setClientError(\n 'BotManor could not connect to the gateway. Please refresh the page or check your configuration.'\n );\n }, CLIENT_READY_TIMEOUT_MS);\n return () => clearTimeout(timer);\n }, [clientReady]);\n\n // Give the shell a bounded window to resolve the active workspace before the\n // guided zero state is allowed to render. Cancelled the moment a workspace\n // arrives — a workspace that is already present never starts the timer.\n useEffect(() => {\n if (props.workspaceId) return;\n const timer = setTimeout(() => setWorkspaceSettled(true), WORKSPACE_SETTLE_MS);\n return () => clearTimeout(timer);\n }, [props.workspaceId]);\n\n // Hook point for syncing a saved workflow back into BotManor. BotManor keeps no\n // separate workflow record (unlike BigConsole), so there is nothing to write —\n // the bridge exists so a future sync can be added without re-initialising the SDK.\n const handleWorkflowSaved = useCallback((result: WorkflowSavedResult) => {\n if (import.meta.env.DEV) {\n console.info('[BotManor] Workflow saved', result.workflowKey, result.operation);\n }\n }, []);\n\n // Ref-forwarding: decouple the callback from the SDK config effect so that\n // workspaceClient reference changes don't trigger resetFluidGridsConfig() + re-init.\n const handleWorkflowSavedRef = useRef(handleWorkflowSaved);\n useLayoutEffect(() => {\n handleWorkflowSavedRef.current = handleWorkflowSaved;\n }, [handleWorkflowSaved]);\n\n // Configure FluidGrids SDK settings\n useEffect(() => {\n if (!props.apiGatewayUrl) return;\n\n const normalizedBase = props.apiGatewayUrl.endsWith('/')\n ? props.apiGatewayUrl.slice(0, -1)\n : props.apiGatewayUrl;\n\n setFluidGridsConfig({\n apiGatewayUrl: normalizedBase,\n authToken: props.authToken,\n workspaceId: props.workspaceId ?? '',\n tenantId: props.tenantId,\n // The SDK filters workflows by productId. It MUST be this product — falling\n // back to the shell's default ('workspaces') would leak another product's\n // workflows into BotManor.\n productId: props.productId ?? DEFAULT_PRODUCT_ID,\n productName: props.productName ?? DEFAULT_PRODUCT_NAME,\n // Stable wrapper via ref — avoids SDK re-init when workspaceClient changes reference\n onWorkflowSaved: (result: WorkflowSavedResult) => handleWorkflowSavedRef.current(result),\n });\n\n return () => {\n resetFluidGridsConfig();\n };\n }, [\n props.apiGatewayUrl,\n props.authToken,\n props.workspaceId,\n props.tenantId,\n props.productId,\n props.productName,\n ]);\n\n // Without a workspace every BotManor query is unscoped and every write is\n // rejected — guide the user instead of rendering empty lists. Until the\n // settle window elapses we cannot tell \"still resolving\" from \"none\", so we\n // keep the same skeleton the shell was already showing.\n if (!props.workspaceId) {\n if (!workspaceSettled) {\n return <RouteLoader label=\"Loading your workspace…\" />;\n }\n return <WorkspaceRequired basePath={props.basePath} navigate={props.navigate} />;\n }\n\n // Don't render until clients are ready (same pattern as microfe-bigconsole)\n if (!clientReady) {\n if (clientError) {\n return (\n <div className=\"flex h-full min-h-[200px] w-full items-center justify-center p-6\">\n <div className=\"text-center\">\n <p className=\"mb-4 text-sm text-text-secondary\">{clientError}</p>\n <button\n type=\"button\"\n onClick={() => window.location.reload()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n Reload Page\n </button>\n </div>\n </div>\n );\n }\n // Same skeleton as the lazy-chunk fallback, so the content region never\n // switches loading idioms between first paint and the page rendering.\n return <RouteLoader />;\n }\n\n const isIntegrated = !!props.basePath;\n\n const content = (\n <BotlitProvider {...props}>\n <BotlitRoutes />\n </BotlitProvider>\n );\n\n return (\n <ErrorBoundary\n fallback={\n <div className=\"p-6 text-center text-text-secondary\">\n Something went wrong loading BotManor. Please refresh the page.\n </div>\n }\n >\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <QueryClientProvider client={queryClient}>\n {isIntegrated ? (\n content\n ) : (\n <ApolloProvider client={workspaceClient}>{content}</ApolloProvider>\n )}\n </QueryClientProvider>\n </FeatureFlagProvider>\n </ErrorBoundary>\n );\n};\n"],"mappings":";;;;;;;;;;;;;AA6CA,IAAM,IAAqB,YACrB,IAAuB,YAGvB,IAA0B,KAY1B,IAAsB,KAGtB,IAAc,IAAI,EAAY,EAClC,gBAAgB,EACd,SAAS;CACP,WAAW,MAAS;CACpB,OAAO;CACR,EACF,EACF,CAAC,EAEW,KAA4C,MAAU;CAKjE,IAAM,IAAkB,EAAiB,YAAY,EAC/C,IAAe,EAAiB,SAAS,EACzC,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAkB,KAAuB,EAAS,GAAM;AA6C/D,CAjCA,SACM,KAAmB,MACrB,EAAoB,EAAgB,EACpC,EAAqB,EAAa,EAG7B,GAAmB,CAAC,OAAO,MAAmB;AACjD,UAAQ,KAAK,uDAAuD,EAAM;GAC1E,EACF,EAAe,GAAK,SAET;AAEX,EADA,GAAuB,EACvB,GAAwB;KAEzB,CAAC,GAAiB,EAAa,CAAC,EAKnC,QAAgB;AACd,MAAI,EAAa;EACjB,IAAM,IAAQ,iBAAiB;AAC7B,KACE,kGACD;KACA,EAAwB;AAC3B,eAAa,aAAa,EAAM;IAC/B,CAAC,EAAY,CAAC,EAKjB,QAAgB;AACd,MAAI,EAAM,YAAa;EACvB,IAAM,IAAQ,iBAAiB,EAAoB,GAAK,EAAE,EAAoB;AAC9E,eAAa,aAAa,EAAM;IAC/B,CAAC,EAAM,YAAY,CAAC;CAKvB,IAAM,IAAsB,GAAa,MAAgC,IAItE,EAAE,CAAC,EAIA,IAAyB,EAAO,EAAoB;AA2C1D,KA1CA,QAAsB;AACpB,IAAuB,UAAU;IAChC,CAAC,EAAoB,CAAC,EAGzB,QAAgB;AACT,QAAM,cAoBX,QAdA,EAAoB;GAClB,eALqB,EAAM,cAAc,SAAS,IAAI,GACpD,EAAM,cAAc,MAAM,GAAG,GAAG,GAChC,EAAM;GAIR,WAAW,EAAM;GACjB,aAAa,EAAM,eAAe;GAClC,UAAU,EAAM;GAIhB,WAAW,EAAM,aAAa;GAC9B,aAAa,EAAM,eAAe;GAElC,kBAAkB,MAAgC,EAAuB,QAAQ,EAAO;GACzF,CAAC,QAEW;AACX,MAAuB;;IAExB;EACD,EAAM;EACN,EAAM;EACN,EAAM;EACN,EAAM;EACN,EAAM;EACN,EAAM;EACP,CAAC,EAME,CAAC,EAAM,YAIT,QAHK,IAGE,kBAAC,GAAD;EAAmB,UAAU,EAAM;EAAU,UAAU,EAAM;EAAY,CAAA,GAFvE,kBAAC,GAAD,EAAa,OAAM,2BAA4B,CAAA;AAM1D,KAAI,CAAC,EAmBH,QAlBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cAAoC;IAAgB,CAAA,EACjE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,OAAO,SAAS,QAAQ;IACvC,WAAU;cACX;IAEQ,CAAA,CACL;;EACF,CAAA,GAKH,kBAAC,GAAD,EAAe,CAAA;CAGxB,IAAM,IAAe,CAAC,CAAC,EAAM,UAEvB,IACJ,kBAAC,GAAD;EAAgB,GAAI;YAClB,kBAAC,GAAD,EAAgB,CAAA;EACD,CAAA;AAGnB,QACE,kBAAC,GAAD;EACE,UACE,kBAAC,OAAD;GAAK,WAAU;aAAsC;GAE/C,CAAA;YAGR,kBAAC,GAAD;GAAqB,aAAa;GAAM,SAAQ;GAAS,gBAAgB;aACvE,kBAAC,GAAD;IAAqB,QAAQ;cAC1B,IACC,IAEA,kBAAC,GAAD;KAAgB,QAAQ;eAAkB;KAAyB,CAAA;IAEjD,CAAA;GACF,CAAA;EACR,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceRequired.js","names":[],"sources":["../../../src/botlit/components/WorkspaceRequired.tsx"],"sourcesContent":["/**\n * Workspace Required Gate\n * @module botlit/components\n */\n\nimport type { FC } from 'react';\nimport { Bot, MessagesSquare, Sparkles } from 'lucide-react';\nimport {\n EmphasisPanel,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { joinPath } from '../utils/navigation';\n\n/**\n * Shown when BotManor is mounted without an active workspace.\n *\n * Every BotManor query is workspace-scoped and every write is rejected without\n * `x-workspace-id`, so the module genuinely cannot run — but a single muted\n * sentence in a tall empty region reads as a broken page. This uses the shared\n * guidance pattern (purpose → illustrated zero state → next steps) so the user\n * is told what BotManor is for and what to do next, and the steps land on real\n * routes the moment a workspace becomes active.\n */\nexport interface WorkspaceRequiredProps {\n /** Absolute path BotManor is mounted at in the host shell, e.g. `/
|
|
1
|
+
{"version":3,"file":"WorkspaceRequired.js","names":[],"sources":["../../../src/botlit/components/WorkspaceRequired.tsx"],"sourcesContent":["/**\n * Workspace Required Gate\n * @module botlit/components\n */\n\nimport type { FC } from 'react';\nimport { Bot, MessagesSquare, Sparkles } from 'lucide-react';\nimport {\n EmphasisPanel,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { joinPath } from '../utils/navigation';\n\n/**\n * Shown when BotManor is mounted without an active workspace.\n *\n * Every BotManor query is workspace-scoped and every write is rejected without\n * `x-workspace-id`, so the module genuinely cannot run — but a single muted\n * sentence in a tall empty region reads as a broken page. This uses the shared\n * guidance pattern (purpose → illustrated zero state → next steps) so the user\n * is told what BotManor is for and what to do next, and the steps land on real\n * routes the moment a workspace becomes active.\n */\nexport interface WorkspaceRequiredProps {\n /** Absolute path BotManor is mounted at in the host shell, e.g. `/botmanor`. */\n basePath: string;\n /** Host shell navigation. Absent in standalone/dev mounts. */\n navigate?: (path: string) => void;\n}\n\nexport const WorkspaceRequired: FC<WorkspaceRequiredProps> = ({ basePath, navigate }) => {\n const t = useTr();\n const go = (relativePath: string) => {\n navigate?.(joinPath(basePath, relativePath));\n };\n\n return (\n <div className=\"mx-auto flex h-full min-h-[60vh] w-full max-w-2xl flex-col justify-center gap-6 px-4 py-10\">\n {/* The 'story': plain-language purpose of this module for a first-time user. */}\n <PagePurpose icon={<Bot className=\"size-4\" aria-hidden=\"true\" />}>\n {t(\n 'workspaceRequired.purpose',\n 'BotManor is where you build and run AI agents — you give an agent a prompt, a model, the knowledge it may read and the tools it may call, then publish it to a channel such as web chat, Slack or an API so your team and customers can talk to it.'\n )}\n </PagePurpose>\n\n {/* The single emphasis zone for this screen: the get-started callout. */}\n <EmphasisPanel className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"onboarding\"\n title={t('workspaceRequired.emptyTitle', 'Pick a workspace to start building')}\n description={t(\n 'workspaceRequired.emptyDescription',\n 'Agents, knowledge bases and channels live inside a workspace so they stay scoped to the right team. Select or create an active workspace from the shell to load your BotManor workspace.'\n )}\n />\n </EmphasisPanel>\n\n {/* Dismissable, reopenable next-step guidance wired to real routes. */}\n <NextSteps\n storageKey=\"botlit-get-started\"\n title={t('workspaceRequired.nextStepsTitle', 'Once your workspace is active')}\n steps={[\n {\n id: 'open-agents',\n label: t('workspaceRequired.openAgents', 'Open your agents'),\n description: t(\n 'workspaceRequired.openAgentsDescription',\n 'Browse, search and manage every agent in this workspace.'\n ),\n icon: <Bot className=\"size-4\" aria-hidden=\"true\" />,\n onClick: () => go('agents'),\n },\n {\n id: 'build-agent',\n label: t('workspaceRequired.buildAgent', 'Build an agent'),\n description: t(\n 'workspaceRequired.buildAgentDescription',\n 'Pick a model, write the prompt and wire up the tools it may call.'\n ),\n icon: <Sparkles className=\"size-4\" aria-hidden=\"true\" />,\n onClick: () => go('agents/new'),\n },\n {\n id: 'connect-channel',\n label: t('workspaceRequired.connectChannel', 'Connect a channel'),\n description: t(\n 'workspaceRequired.connectChannelDescription',\n 'Publish an agent to web chat, Slack or an API so people can reach it.'\n ),\n icon: <MessagesSquare className=\"size-4\" aria-hidden=\"true\" />,\n onClick: () => go('channels'),\n },\n ]}\n />\n </div>\n );\n};\n"],"mappings":";;;;;;AAiCA,IAAa,KAAiD,EAAE,aAAU,kBAAe;CACvF,IAAM,IAAI,GAAO,EACX,KAAM,MAAyB;AACnC,MAAW,EAAS,GAAU,EAAa,CAAC;;AAG9C,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,GAAD;IAAa,MAAM,kBAAC,GAAD;KAAK,WAAU;KAAS,eAAY;KAAS,CAAA;cAC7D,EACC,6BACA,sPACD;IACW,CAAA;GAGd,kBAAC,GAAD;IAAe,WAAU;cACvB,kBAAC,GAAD;KACE,cAAa;KACb,OAAO,EAAE,gCAAgC,qCAAqC;KAC9E,aAAa,EACX,sCACA,2LACD;KACD,CAAA;IACY,CAAA;GAGhB,kBAAC,GAAD;IACE,YAAW;IACX,OAAO,EAAE,oCAAoC,gCAAgC;IAC7E,OAAO;KACL;MACE,IAAI;MACJ,OAAO,EAAE,gCAAgC,mBAAmB;MAC5D,aAAa,EACX,2CACA,2DACD;MACD,MAAM,kBAAC,GAAD;OAAK,WAAU;OAAS,eAAY;OAAS,CAAA;MACnD,eAAe,EAAG,SAAS;MAC5B;KACD;MACE,IAAI;MACJ,OAAO,EAAE,gCAAgC,iBAAiB;MAC1D,aAAa,EACX,2CACA,oEACD;MACD,MAAM,kBAAC,GAAD;OAAU,WAAU;OAAS,eAAY;OAAS,CAAA;MACxD,eAAe,EAAG,aAAa;MAChC;KACD;MACE,IAAI;MACJ,OAAO,EAAE,oCAAoC,oBAAoB;MACjE,aAAa,EACX,+CACA,wEACD;MACD,MAAM,kBAAC,GAAD;OAAgB,WAAU;OAAS,eAAY;OAAS,CAAA;MAC9D,eAAe,EAAG,WAAW;MAC9B;KACF;IACD,CAAA;GACE"}
|
|
@@ -324,7 +324,7 @@ var q = ({ routeId: e, connectorId: t, channelId: n, platform: r, onRemoved: i,
|
|
|
324
324
|
let { basePath: m } = e(), [h] = g(), [_, v] = T(!1), y = c.platform, b = K(y), x = G(c.adapterType), S = _, C = w((e) => {
|
|
325
325
|
let t = U[y];
|
|
326
326
|
if (!t) return;
|
|
327
|
-
let n = window.location.origin + window.location.pathname + window.location.search, r = `${m ? m.replace(/\/botlit(\/.*)?$/, "/integrations") : "/integrations"}/connections/add?${new URLSearchParams({
|
|
327
|
+
let n = window.location.origin + window.location.pathname + window.location.search, r = `${m ? m.replace(/\/(botmanor|botlit)(\/.*)?$/, "/integrations") : "/integrations"}/connections/add?${new URLSearchParams({
|
|
328
328
|
providerKey: t,
|
|
329
329
|
returnTo: n,
|
|
330
330
|
channelId: e
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BotChannelsTab.js","names":[],"sources":["../../../src/botlit/pages/BotChannelsTab.tsx"],"sourcesContent":["/**\n * BotChannelsTab\n * @module botlit/pages\n *\n * Shown inside BotDetailPage → \"Channels\" tab.\n *\n * Flow:\n * 1. Fetches all ACTIVE COMMUNICATION channels from global-channel-svc via useGlobalChannels()\n * 2. Filters to show only channelType = COMMUNICATION (bi-directional platforms)\n * 3. OAuth platforms (SLACK, TEAMS, DISCORD):\n * - No bot route → \"Set up channel\" button\n * → creates ChannelRoute + ChannelRoute, then opens integrations in new tab\n * - Route exists → PlatformRouteCard (manual link flow + connected state + toggle / disconnect)\n * - Manual link → User pastes integrationConnectionId → clicks \"Link Connection\"\n * → calls linkIntegrationConnection mutation → toast success\n * 4. WhatsApp: pairing code flow (coming soon / handled by wspace-integrations CUSTOM auth)\n * 5. NOTIFICATION channels (EMAIL/SMS): hidden (not shown for bots)\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { useState, useCallback } from 'react';\nimport {\n Slack,\n Mail,\n MessageSquare,\n Radio,\n CheckCircle2,\n Bell,\n BellOff,\n Loader2,\n Trash2,\n Unlink,\n MessageCircle,\n Users,\n Plus,\n} from 'lucide-react';\nimport { toast } from 'sonner';\nimport { Card, CardHeader, CardTitle, CardContent } from '../components/Card';\nimport { Button } from '../components/Button';\nimport { Badge } from '../components/Badge';\nimport { EmptyState } from '../components/EmptyState';\nimport { Input } from '../components/Input';\nimport { ChannelSelectorSheet } from '../components/ChannelSelectorSheet';\nimport { useBotlit } from '../providers/BotlitProvider';\nimport {\n isNative,\n nativeCopyText,\n nativeNotify,\n nativeOpenUrl,\n nativeToast,\n} from '../../utils/nativeBridge';\nimport {\n useBotChannelRoutes,\n useChannelConnector,\n useCreateChannelRoute,\n useDeleteChannelRoute,\n} from '../hooks';\nimport {\n useMyUserPlatformConnection,\n useLinkIntegrationConnection,\n useToggleSlackNotifications,\n useRevokeSlackConnection,\n useGlobalChannels,\n} from '../hooks/useUserPlatformConnection';\nimport type { GlobalChannelData } from '../hooks/useUserPlatformConnection';\nimport type { ChannelRouteRecord } from '../hooks/useChannels';\n// `listChannelRoutes` is published — routes carry the real record shape.\ntype ChannelRoute = ChannelRouteRecord;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platform → provider key mapping (must match keys seeded in wspace-integrations-svc)\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst PLATFORM_PROVIDER_KEY: Record<string, string> = {\n SLACK: 'slack-user',\n TEAMS: 'teams-user',\n DISCORD: 'discord-user',\n WHATSAPP: 'whatsapp-user',\n};\n\n/** Platforms that use the OAuth flow via microfe-integrations */\nconst OAUTH_PLATFORMS = new Set(['SLACK', 'TEAMS', 'DISCORD']);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Human-readable label for an AdapterType enum value */\nfunction getAdapterLabel(adapterType: string | null | undefined): string | null {\n if (!adapterType) return null;\n const labels: Record<string, string> = {\n SLACK_BOT: 'Slack Bot',\n SLACK_WEBHOOK: 'Slack Webhook',\n AWS_SES: 'AWS SES',\n AZURE_ACS: 'Azure ACS',\n SENDGRID: 'SendGrid',\n MAILGUN: 'Mailgun',\n MAILJET: 'Mailjet',\n POSTMARK: 'Postmark',\n RESEND: 'Resend',\n SMTP: 'SMTP',\n TWILIO_SMS: 'Twilio SMS',\n AWS_SNS: 'AWS SNS',\n SINCH: 'Sinch',\n VONAGE: 'Vonage',\n PLIVO: 'Plivo',\n DISCORD_BOT: 'Discord Bot',\n DISCORD_WEBHOOK: 'Discord Webhook',\n TEAMS_BOT: 'Teams Bot',\n TEAMS_WEBHOOK: 'Teams Webhook',\n FCM: 'Firebase Cloud Messaging',\n APNS: 'Apple Push (APNs)',\n WEBHOOK_HTTP: 'HTTP Webhook',\n };\n return (\n labels[adapterType] ??\n adapterType\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n );\n}\n\n/** Platform display config — icon element, badge colours, accent colour */\ninterface PlatformMeta {\n label: string;\n badgeClass: string;\n icon: ReactNode;\n accentBg: string;\n accentText: string;\n}\n\nfunction getPlatformMeta(platform: string): PlatformMeta {\n switch (platform) {\n case 'SLACK':\n return {\n label: 'Slack',\n badgeClass: 'bg-pink-100 text-pink-700',\n icon: <Slack size={18} />,\n accentBg: 'bg-[#611f69]/10',\n accentText: 'text-[#611f69]',\n };\n case 'TEAMS':\n return {\n label: 'Teams',\n badgeClass: 'bg-indigo-100 text-indigo-700',\n icon: <Users size={18} />,\n accentBg: 'bg-indigo-500/10',\n accentText: 'text-indigo-600',\n };\n case 'DISCORD':\n return {\n label: 'Discord',\n badgeClass: 'bg-violet-100 text-violet-700',\n icon: <MessageCircle size={18} />,\n accentBg: 'bg-violet-500/10',\n accentText: 'text-violet-600',\n };\n case 'WHATSAPP':\n return {\n label: 'WhatsApp',\n badgeClass: 'bg-green-100 text-green-700',\n icon: <MessageSquare size={18} />,\n accentBg: 'bg-green-500/10',\n accentText: 'text-green-700',\n };\n case 'EMAIL':\n return {\n label: 'Email',\n badgeClass: 'bg-blue-100 text-blue-700',\n icon: <Mail size={18} />,\n accentBg: 'bg-blue-500/10',\n accentText: 'text-blue-600',\n };\n case 'SMS':\n return {\n label: 'SMS',\n badgeClass: 'bg-green-100 text-green-700',\n icon: <MessageSquare size={18} />,\n accentBg: 'bg-green-500/10',\n accentText: 'text-green-600',\n };\n default:\n return {\n label: platform,\n badgeClass: 'bg-gray-100 text-gray-700',\n icon: <Radio size={18} />,\n accentBg: 'bg-gray-500/10',\n accentText: 'text-gray-600',\n };\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// PlatformRouteCard — one connected OAuth platform route + personal connection UI\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface PlatformRouteCardProps {\n routeId: string;\n connectorId: string;\n /** The global channel ID (credentialsRefId) stored on the connector */\n channelId: string;\n platform: string;\n onRemoved: () => void;\n /** Navigate to microfe-integrations to reconnect */\n onReconnect: () => void;\n}\n\nconst PlatformRouteCard: FC<PlatformRouteCardProps> = ({\n routeId,\n connectorId,\n channelId,\n platform,\n onRemoved,\n onReconnect,\n}) => {\n const { data: connector, loading: connectorLoading } = useChannelConnector(connectorId);\n // The channel ID for the UPC lookup is stored on connector.credentialsRefId\n const upcChannelId = connector?.credentialsRefId ?? channelId;\n\n const {\n data: platformConnection,\n loading: connectionLoading,\n refetch: refetchConnection,\n } = useMyUserPlatformConnection(upcChannelId);\n\n const { toggle: toggleNotifications, loading: togglingNotifications } =\n useToggleSlackNotifications();\n const { revoke: revokeConnection, loading: revoking } = useRevokeSlackConnection();\n const { link: linkIntegration } = useLinkIntegrationConnection();\n const [deleteRoute] = useDeleteChannelRoute();\n\n // Manual linking state\n const [integrationConnId, setIntegrationConnId] = useState('');\n const [linking, setLinking] = useState(false);\n\n const meta = getPlatformMeta(platform);\n\n const handleLinkConnection = async () => {\n if (!integrationConnId.trim()) {\n toast.error('Please enter an integration connection ID');\n return;\n }\n setLinking(true);\n try {\n const conn = await linkIntegration(integrationConnId.trim(), upcChannelId);\n if (conn) {\n toast.success(\n `${conn.externalDisplayName ?? conn.externalUsername ?? 'Account'} connected! The bot can now send you direct messages.`\n );\n await refetchConnection();\n setIntegrationConnId('');\n } else {\n toast.error('Failed to link connection. Please verify the ID and try again.');\n }\n } catch (err) {\n toast.error(err instanceof Error ? err.message : 'Failed to link connection');\n } finally {\n setLinking(false);\n }\n };\n\n const handleRemove = async () => {\n if (!confirm(`Remove this ${meta.label} channel from the bot?`)) return;\n try {\n await deleteRoute(routeId);\n onRemoved();\n toast.success(`${meta.label} channel removed`);\n if (isNative()) void nativeNotify('success');\n } catch (err) {\n toast.error(err instanceof Error ? err.message : 'Failed to remove');\n if (isNative()) void nativeNotify('error');\n }\n };\n\n if (connectorLoading) {\n return <div className=\"h-32 bg-bg-sunken motion-safe:animate-vc-shimmer rounded-lg\" />;\n }\n\n return (\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/30 p-4 space-y-3\">\n {/* Route header */}\n <div className=\"flex items-center justify-between\">\n <span className=\"text-xs text-text-secondary font-medium uppercase tracking-wide\">\n Connected workspace\n </span>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={handleRemove}\n className=\"h-7 w-7 text-text-secondary hover:text-status-error-text\"\n title=\"Remove channel\"\n >\n <Trash2 size={13} />\n </Button>\n </div>\n\n {connectionLoading ? (\n <div className=\"flex items-center gap-2 py-2 text-text-secondary\">\n <Loader2 size={14} className=\"animate-spin\" />\n <span className=\"text-xs\">Checking {meta.label} connection…</span>\n </div>\n ) : platformConnection?.dmAllowed ? (\n /* ── Personal account connected ── */\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-3 p-3 rounded-lg bg-bg-surface border border-border-subtle\">\n <CheckCircle2 size={16} className=\"text-green-600 shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-medium text-sm\">\n {platformConnection.externalDisplayName ||\n platformConnection.externalUsername ||\n platformConnection.externalUserId}\n </p>\n {platformConnection.externalUsername && (\n <p className=\"text-xs text-text-secondary\">\n @{platformConnection.externalUsername}\n </p>\n )}\n {platformConnection.connectedAt && (\n <p className=\"text-xs text-text-secondary\">\n Connected {new Date(platformConnection.connectedAt).toLocaleDateString()}\n </p>\n )}\n </div>\n {platformConnection.isTokenExpired && (\n <Badge variant=\"outline\" className=\"text-xs text-amber-600 border-amber-400\">\n Token expired\n </Badge>\n )}\n </div>\n\n {/* Integration Connection ID (read-only) */}\n {platformConnection.integrationConnectionId && (\n <div className=\"space-y-1.5\">\n <label className=\"text-xs font-medium text-text-secondary\">\n Integration Connection ID\n </label>\n <Input\n value={platformConnection.integrationConnectionId}\n readOnly\n className=\"text-xs font-mono bg-bg-sunken/50 cursor-default\"\n onClick={(e) => {\n e.currentTarget.select();\n void nativeCopyText(platformConnection.integrationConnectionId!);\n toast.success('Connection ID copied to clipboard');\n if (isNative()) void nativeToast('Copied');\n }}\n title=\"Click to copy\"\n />\n </div>\n )}\n\n {/* Notification toggle */}\n <div className=\"flex items-center justify-between p-3 border border-border-subtle rounded-lg bg-bg-surface\">\n <div className=\"flex items-center gap-2\">\n {platformConnection.notificationsEnabled ? (\n <Bell size={14} className=\"text-primary\" />\n ) : (\n <BellOff size={14} className=\"text-text-secondary\" />\n )}\n <div>\n <p className=\"text-sm font-medium\">DM Notifications</p>\n <p className=\"text-xs text-text-secondary\">\n {platformConnection.notificationsEnabled\n ? `Bot will DM you on ${meta.label}`\n : 'DM notifications paused'}\n </p>\n </div>\n </div>\n <Button\n variant=\"outline\"\n size=\"sm\"\n isLoading={togglingNotifications}\n onClick={async () => {\n const updated = await toggleNotifications(\n platformConnection.id,\n !platformConnection.notificationsEnabled\n );\n if (updated) await refetchConnection();\n }}\n >\n {platformConnection.notificationsEnabled ? 'Disable' : 'Enable'}\n </Button>\n </div>\n\n <div className=\"flex gap-2 justify-end pt-1\">\n {platformConnection.isTokenExpired && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n leftIcon={meta.icon as React.ReactElement}\n onClick={onReconnect}\n >\n Reconnect\n </Button>\n )}\n <Button\n variant=\"destructive\"\n size=\"sm\"\n leftIcon={<Unlink size={13} />}\n isLoading={revoking}\n onClick={async () => {\n const ok = await revokeConnection(platformConnection.id);\n if (ok) await refetchConnection();\n }}\n >\n Disconnect\n </Button>\n </div>\n </div>\n ) : (\n /* ── Personal account not connected - Manual linking flow ── */\n <div className=\"space-y-3 py-2\">\n <div className=\"flex items-center gap-2 p-3 rounded-lg bg-bg-surface border border-border-subtle\">\n <div className=\"p-2 rounded-full bg-bg-sunken shrink-0\">\n <span className=\"text-text-secondary\">{meta.icon}</span>\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium text-sm\">No personal account connected</p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Connect your {meta.label} account to receive bot DMs\n </p>\n </div>\n </div>\n\n <div className=\"p-3 rounded-lg bg-bg-sunken/50 space-y-2\">\n <p className=\"text-xs font-medium text-text-secondary\">Connect your account:</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs text-text-secondary\">\n <li>Click \"Open Integrations\" below (opens in new tab)</li>\n <li>Connect your {meta.label} account via OAuth</li>\n <li>Copy the Connection ID from the integrations page</li>\n <li>Paste it below and click \"Link Connection\"</li>\n </ol>\n </div>\n\n <Button\n variant=\"outline\"\n size=\"sm\"\n leftIcon={meta.icon as React.ReactElement}\n onClick={onReconnect}\n className=\"w-full\"\n >\n Open Integrations →\n </Button>\n\n <div className=\"flex gap-2\">\n <Input\n placeholder=\"Paste integration connection ID...\"\n value={integrationConnId}\n onChange={(e) => setIntegrationConnId(e.target.value)}\n className=\"flex-1 text-sm\"\n />\n <Button\n size=\"sm\"\n onClick={handleLinkConnection}\n isLoading={linking}\n disabled={!integrationConnId.trim() || linking}\n >\n Link Connection\n </Button>\n </div>\n </div>\n )}\n </div>\n );\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// OAuthGlobalChannelCard\n// Generic card for OAuth platforms (Slack, Teams, Discord).\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface OAuthGlobalChannelCardProps {\n channel: GlobalChannelData;\n routes: ChannelRoute[];\n botId: string;\n botName: string;\n onRoutesChanged: () => void;\n}\n\nconst OAuthGlobalChannelCard: FC<OAuthGlobalChannelCardProps> = ({\n channel,\n routes,\n botId,\n botName,\n onRoutesChanged,\n}) => {\n const { basePath } = useBotlit();\n const [createRoute] = useCreateChannelRoute();\n const [connecting, setConnecting] = useState(false);\n\n const platform = channel.platform as string;\n const meta = getPlatformMeta(platform);\n const adapterLabel = getAdapterLabel(channel.adapterType as string | null);\n const isCreating = connecting;\n\n /**\n * Navigate to microfe-integrations wizard pre-configured for this platform.\n * The integrations wizard will OAuth the user, then redirect back here with\n * ?integrationConnectionId=<id>&channelId=<id> appended to the returnTo URL.\n *\n * returnTo MUST be an absolute URL so the backend's new URL(returnTo) doesn't throw.\n */\n const navigateToIntegrations = useCallback(\n (currentChannelId: string) => {\n const providerKey = PLATFORM_PROVIDER_KEY[platform];\n if (!providerKey) return;\n\n // Absolute URL so the backend oauth-callback can do new URL(returnTo) without error.\n const returnTo = window.location.origin + window.location.pathname + window.location.search;\n\n // Integrations MFE base path — shell mounts it at /integrations by convention.\n // Derive it by replacing the /botlit segment of basePath.\n const integrationsBase = basePath\n ? basePath.replace(/\\/botlit(\\/.*)?$/, '/integrations')\n : '/integrations';\n\n const params = new URLSearchParams({\n providerKey,\n returnTo,\n channelId: currentChannelId,\n });\n\n const targetPath = `${integrationsBase}/connections/add?${params.toString()}`;\n\n // Always open in new tab for manual copy-paste workflow\n if (isNative() && /^https?:\\/\\//.test(targetPath)) {\n void nativeOpenUrl(targetPath);\n } else {\n window.open(targetPath, '_blank');\n }\n },\n [platform, basePath]\n );\n\n const handleConnect = useCallback(async () => {\n setConnecting(true);\n try {\n // Route the global channel at this bot. `workspaceId` is supplied by the\n // hook from the BotManor context — never pass an empty string here.\n await createRoute({\n connectorId: channel.id, // Global channel id doubles as the connector reference\n appId: botId,\n locationId: '*',\n metadata: { locationName: 'All channels & DMs', appName: botName },\n });\n\n await onRoutesChanged();\n\n // Navigate to microfe-integrations for OAuth\n navigateToIntegrations(channel.id);\n // page will navigate away — don't setConnecting(false)\n } catch (err) {\n toast.error(err instanceof Error ? err.message : `Failed to connect ${meta.label}`);\n setConnecting(false);\n }\n }, [\n botId,\n botName,\n channel.id,\n createRoute,\n meta.label,\n navigateToIntegrations,\n onRoutesChanged,\n ]);\n\n const handleReconnect = useCallback(() => {\n navigateToIntegrations(channel.id);\n }, [channel.id, navigateToIntegrations]);\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between pb-3\">\n <div className=\"flex items-center gap-2.5\">\n <div className={`p-1.5 rounded-md ${meta.accentBg}`}>\n <span className={meta.accentText}>{meta.icon}</span>\n </div>\n <div>\n <CardTitle className=\"text-base\">{channel.name}</CardTitle>\n {adapterLabel && <p className=\"text-xs text-text-secondary mt-0.5\">{adapterLabel}</p>}\n </div>\n </div>\n <div className=\"flex items-center gap-1.5\">\n <span className={`px-2 py-0.5 text-xs font-semibold rounded-full ${meta.badgeClass}`}>\n {meta.label}\n </span>\n {routes.length > 0 && (\n <Badge\n variant=\"outline\"\n className=\"text-xs text-green-600 border-green-400 bg-green-50\"\n >\n Active\n </Badge>\n )}\n </div>\n </CardHeader>\n\n <CardContent>\n {routes.length > 0 ? (\n <div className=\"space-y-3\">\n {routes.map((route: ChannelRoute) => (\n <PlatformRouteCard\n key={route.id}\n routeId={route.id}\n connectorId={route.connectorId}\n channelId={channel.id}\n platform={platform}\n onRemoved={onRoutesChanged}\n onReconnect={handleReconnect}\n />\n ))}\n </div>\n ) : (\n <div className=\"flex flex-col items-center gap-4 py-8 text-center\">\n <div className=\"p-4 rounded-full bg-bg-sunken\">\n <span className=\"text-text-secondary\">{meta.icon}</span>\n </div>\n <div>\n <p className=\"font-medium text-sm\">Not connected to this bot</p>\n <p className=\"text-xs text-text-secondary mt-1\">\n Enable {meta.label} for this bot. Users will connect their personal accounts\n separately.\n </p>\n </div>\n <Button\n leftIcon={meta.icon as React.ReactElement}\n onClick={handleConnect}\n isLoading={isCreating}\n >\n Set up {meta.label} channel\n </Button>\n </div>\n )}\n </CardContent>\n </Card>\n );\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ComingSoonChannelCard\n// EMAIL and SMS channels shown in the UI — connect functionality coming later\n// NOTE: This component is currently unused - we now only show OAuth platforms\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Commented out - not needed for OAuth-only flow\n// interface ComingSoonChannelCardProps {\n// channel: GlobalChannelData;\n// }\n//\n// const ComingSoonChannelCard: FC<ComingSoonChannelCardProps> = ({ channel }) => {\n// const meta = getPlatformMeta(channel.platform as string);\n// const adapterLabel = getAdapterLabel(channel.adapterType as string | null);\n//\n// return (\n// <Card className=\"opacity-80\">\n// <CardHeader className=\"flex flex-row items-center justify-between pb-3\">\n// <div className=\"flex items-center gap-2.5\">\n// <div className={`p-1.5 rounded-md ${meta.accentBg}`}>\n// <span className={meta.accentText}>{meta.icon}</span>\n// </div>\n// <div>\n// <CardTitle className=\"text-base\">{channel.name}</CardTitle>\n// {adapterLabel && <p className=\"text-xs text-text-secondary mt-0.5\">{adapterLabel}</p>}\n// </div>\n// </div>\n// <div className=\"flex items-center gap-1.5\">\n// <span className={`px-2 py-0.5 text-xs font-semibold rounded-full ${meta.badgeClass}`}>\n// {meta.label}\n// </span>\n// <Badge variant=\"outline\" className=\"text-xs text-text-secondary\">\n// <Clock size={10} className=\"mr-1\" />\n// Coming soon\n// </Badge>\n// </div>\n// </CardHeader>\n// <CardContent>\n// <p className=\"text-sm text-text-secondary text-center py-4\">\n// Bot integration for <strong>{meta.label}</strong> channels is coming soon. This channel is\n// available for notification delivery — two-way bot communication will be enabled in a\n// future update.\n// </p>\n// </CardContent>\n// </Card>\n// );\n// };\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ConfiguredChannelCard - Wrapper that fetches connector and matches channel\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface ConfiguredChannelCardProps {\n route: ChannelRoute;\n oauthChannels: GlobalChannelData[];\n botId: string;\n botName: string;\n onRoutesChanged: () => void;\n}\n\nconst ConfiguredChannelCard: FC<ConfiguredChannelCardProps> = ({\n route,\n oauthChannels,\n botId,\n botName,\n onRoutesChanged,\n}) => {\n // Fetch the connector to get credentialsRefId\n const { data: connector } = useChannelConnector(route.connectorId);\n\n // Find the matching channel by credentialsRefId\n const channel = oauthChannels.find((ch) => ch.id === connector?.credentialsRefId);\n\n // If connector is still loading or channel not found, don't render\n if (!connector || !channel) return null;\n\n // Only show OAuth channels\n if (!OAUTH_PLATFORMS.has(channel.platform as string)) return null;\n\n return (\n <OAuthGlobalChannelCard\n channel={channel}\n routes={[route]}\n botId={botId}\n botName={botName}\n onRoutesChanged={onRoutesChanged}\n />\n );\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// BotChannelsTab - Main component\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface BotChannelsTabProps {\n botId: string;\n botName: string;\n}\n\nexport const BotChannelsTab: FC<BotChannelsTabProps> = ({ botId, botName }) => {\n const {\n data: globalChannels,\n loading: channelsLoading,\n error: channelsError,\n } = useGlobalChannels();\n\n const {\n data: routes,\n loading: routesLoading,\n refetch: refetchRoutes,\n } = useBotChannelRoutes(botId);\n\n // State for channel selector sheet\n const [showChannelSelector, setShowChannelSelector] = useState(false);\n const [selectedChannelForSetup, setSelectedChannelForSetup] = useState<string | null>(null);\n\n const handleRoutesChanged = useCallback(async () => {\n await refetchRoutes();\n }, [refetchRoutes]);\n\n // Filter to show only OAuth platforms (Slack, Teams, Discord)\n const oauthChannels = globalChannels.filter((ch) => OAUTH_PLATFORMS.has(ch.platform as string));\n\n // Calculate which platforms are already configured for this bot\n const configuredPlatforms = new Set(\n routes\n .map((_route: ChannelRoute) => {\n // Find the channel for this route to get its platform\n const channel = oauthChannels.find((_ch) => {\n // Match by checking if any route uses this channel\n return routes.some((r: ChannelRoute) => r.connectorId);\n });\n return channel?.platform as string;\n })\n .filter(Boolean)\n );\n\n // Get available channels (OAuth platforms not yet configured)\n const availableChannels = oauthChannels.filter(\n (ch) => !configuredPlatforms.has(ch.platform as string)\n );\n\n // Platform icons mapping for ChannelSelectorSheet\n const platformIcons: Record<string, ReactNode> = {\n SLACK: <Slack size={18} />,\n TEAMS: <Users size={18} />,\n DISCORD: <MessageCircle size={18} />,\n };\n\n // Platform metadata mapping for ChannelSelectorSheet\n const platformMetaMap: Record<string, PlatformMeta> = {};\n oauthChannels.forEach((ch) => {\n const platform = ch.platform as string;\n platformMetaMap[platform] = getPlatformMeta(platform);\n });\n\n const handleSelectChannel = useCallback((channelId: string, _platform: string) => {\n setSelectedChannelForSetup(channelId);\n }, []);\n\n const isLoading = channelsLoading || routesLoading;\n\n if (isLoading) {\n return (\n <div className=\"space-y-4\">\n <div className=\"h-10 bg-bg-sunken motion-safe:animate-vc-shimmer rounded-lg w-52\" />\n <div className=\"h-48 bg-bg-sunken motion-safe:animate-vc-shimmer rounded-lg\" />\n <div className=\"h-48 bg-bg-sunken motion-safe:animate-vc-shimmer rounded-lg\" />\n </div>\n );\n }\n\n // Empty state: no channels configured yet\n const hasConfiguredChannels = routes.length > 0;\n\n return (\n <div className=\"space-y-4\">\n {/* Header */}\n <div>\n <h3 className=\"font-semibold text-base\">Channel Connections</h3>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n Communication platforms available for this bot\n </p>\n </div>\n\n {/* Channel error banner */}\n {channelsError && (\n <div className=\"flex items-start gap-2 rounded-lg border border-status-error-border/40 bg-status-error-bg/10 px-3 py-2 text-sm text-status-error-text\">\n <span>Failed to load channels: {channelsError.message}</span>\n </div>\n )}\n\n {/* Empty state: no OAuth channels available from backend */}\n {!channelsError && oauthChannels.length === 0 && (\n <Card>\n <CardContent className=\"py-12\">\n <EmptyState\n illustration=\"empty-notifications\"\n title=\"No channels available\"\n description=\"No active communication channels have been configured yet. Contact your administrator to set up channels.\"\n />\n </CardContent>\n </Card>\n )}\n\n {/* Empty state: channels available but none configured yet */}\n {!channelsError &&\n oauthChannels.length > 0 &&\n !hasConfiguredChannels &&\n !selectedChannelForSetup && (\n <Card>\n <CardContent className=\"py-12\">\n <EmptyState\n illustration=\"empty-notifications\"\n title=\"No channels connected\"\n description=\"Connect a communication channel to enable bot conversations on platforms like Slack, Teams, or Discord.\"\n action={\n <Button\n leftIcon={<Plus size={16} />}\n onClick={() => setShowChannelSelector(true)}\n >\n Connect Channel\n </Button>\n }\n />\n </CardContent>\n </Card>\n )}\n\n {/* Configured channels + channel in setup */}\n {oauthChannels.length > 0 && (hasConfiguredChannels || selectedChannelForSetup) && (\n <div className=\"space-y-4\">\n {/* Show configured channels */}\n {routes.map((route: ChannelRoute) => {\n return (\n <ConfiguredChannelCard\n key={route.id}\n route={route}\n oauthChannels={oauthChannels}\n botId={botId}\n botName={botName}\n onRoutesChanged={handleRoutesChanged}\n />\n );\n })}\n\n {/* Show channel currently being set up */}\n {selectedChannelForSetup && (\n <>\n {oauthChannels\n .filter((ch) => ch.id === selectedChannelForSetup)\n .map((channel) => (\n <OAuthGlobalChannelCard\n key={channel.id}\n channel={channel}\n routes={routes.filter((_r: ChannelRoute) => {\n // Only show routes for this specific channel\n return false; // Initially no routes exist\n })}\n botId={botId}\n botName={botName}\n onRoutesChanged={async () => {\n await handleRoutesChanged();\n setSelectedChannelForSetup(null);\n }}\n />\n ))}\n </>\n )}\n </div>\n )}\n\n {/* Channel Selector Sheet */}\n <ChannelSelectorSheet\n open={showChannelSelector}\n onClose={() => setShowChannelSelector(false)}\n onSelectChannel={handleSelectChannel}\n availableChannels={availableChannels}\n platformIcons={platformIcons}\n platformMeta={platformMetaMap}\n />\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BotChannelsTab.js","names":[],"sources":["../../../src/botlit/pages/BotChannelsTab.tsx"],"sourcesContent":["/**\n * BotChannelsTab\n * @module botlit/pages\n *\n * Shown inside BotDetailPage → \"Channels\" tab.\n *\n * Flow:\n * 1. Fetches all ACTIVE COMMUNICATION channels from global-channel-svc via useGlobalChannels()\n * 2. Filters to show only channelType = COMMUNICATION (bi-directional platforms)\n * 3. OAuth platforms (SLACK, TEAMS, DISCORD):\n * - No bot route → \"Set up channel\" button\n * → creates ChannelRoute + ChannelRoute, then opens integrations in new tab\n * - Route exists → PlatformRouteCard (manual link flow + connected state + toggle / disconnect)\n * - Manual link → User pastes integrationConnectionId → clicks \"Link Connection\"\n * → calls linkIntegrationConnection mutation → toast success\n * 4. WhatsApp: pairing code flow (coming soon / handled by wspace-integrations CUSTOM auth)\n * 5. NOTIFICATION channels (EMAIL/SMS): hidden (not shown for bots)\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { useState, useCallback } from 'react';\nimport {\n Slack,\n Mail,\n MessageSquare,\n Radio,\n CheckCircle2,\n Bell,\n BellOff,\n Loader2,\n Trash2,\n Unlink,\n MessageCircle,\n Users,\n Plus,\n} from 'lucide-react';\nimport { toast } from 'sonner';\nimport { Card, CardHeader, CardTitle, CardContent } from '../components/Card';\nimport { Button } from '../components/Button';\nimport { Badge } from '../components/Badge';\nimport { EmptyState } from '../components/EmptyState';\nimport { Input } from '../components/Input';\nimport { ChannelSelectorSheet } from '../components/ChannelSelectorSheet';\nimport { useBotlit } from '../providers/BotlitProvider';\nimport {\n isNative,\n nativeCopyText,\n nativeNotify,\n nativeOpenUrl,\n nativeToast,\n} from '../../utils/nativeBridge';\nimport {\n useBotChannelRoutes,\n useChannelConnector,\n useCreateChannelRoute,\n useDeleteChannelRoute,\n} from '../hooks';\nimport {\n useMyUserPlatformConnection,\n useLinkIntegrationConnection,\n useToggleSlackNotifications,\n useRevokeSlackConnection,\n useGlobalChannels,\n} from '../hooks/useUserPlatformConnection';\nimport type { GlobalChannelData } from '../hooks/useUserPlatformConnection';\nimport type { ChannelRouteRecord } from '../hooks/useChannels';\n// `listChannelRoutes` is published — routes carry the real record shape.\ntype ChannelRoute = ChannelRouteRecord;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platform → provider key mapping (must match keys seeded in wspace-integrations-svc)\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst PLATFORM_PROVIDER_KEY: Record<string, string> = {\n SLACK: 'slack-user',\n TEAMS: 'teams-user',\n DISCORD: 'discord-user',\n WHATSAPP: 'whatsapp-user',\n};\n\n/** Platforms that use the OAuth flow via microfe-integrations */\nconst OAUTH_PLATFORMS = new Set(['SLACK', 'TEAMS', 'DISCORD']);\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Human-readable label for an AdapterType enum value */\nfunction getAdapterLabel(adapterType: string | null | undefined): string | null {\n if (!adapterType) return null;\n const labels: Record<string, string> = {\n SLACK_BOT: 'Slack Bot',\n SLACK_WEBHOOK: 'Slack Webhook',\n AWS_SES: 'AWS SES',\n AZURE_ACS: 'Azure ACS',\n SENDGRID: 'SendGrid',\n MAILGUN: 'Mailgun',\n MAILJET: 'Mailjet',\n POSTMARK: 'Postmark',\n RESEND: 'Resend',\n SMTP: 'SMTP',\n TWILIO_SMS: 'Twilio SMS',\n AWS_SNS: 'AWS SNS',\n SINCH: 'Sinch',\n VONAGE: 'Vonage',\n PLIVO: 'Plivo',\n DISCORD_BOT: 'Discord Bot',\n DISCORD_WEBHOOK: 'Discord Webhook',\n TEAMS_BOT: 'Teams Bot',\n TEAMS_WEBHOOK: 'Teams Webhook',\n FCM: 'Firebase Cloud Messaging',\n APNS: 'Apple Push (APNs)',\n WEBHOOK_HTTP: 'HTTP Webhook',\n };\n return (\n labels[adapterType] ??\n adapterType\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n );\n}\n\n/** Platform display config — icon element, badge colours, accent colour */\ninterface PlatformMeta {\n label: string;\n badgeClass: string;\n icon: ReactNode;\n accentBg: string;\n accentText: string;\n}\n\nfunction getPlatformMeta(platform: string): PlatformMeta {\n switch (platform) {\n case 'SLACK':\n return {\n label: 'Slack',\n badgeClass: 'bg-pink-100 text-pink-700',\n icon: <Slack size={18} />,\n accentBg: 'bg-[#611f69]/10',\n accentText: 'text-[#611f69]',\n };\n case 'TEAMS':\n return {\n label: 'Teams',\n badgeClass: 'bg-indigo-100 text-indigo-700',\n icon: <Users size={18} />,\n accentBg: 'bg-indigo-500/10',\n accentText: 'text-indigo-600',\n };\n case 'DISCORD':\n return {\n label: 'Discord',\n badgeClass: 'bg-violet-100 text-violet-700',\n icon: <MessageCircle size={18} />,\n accentBg: 'bg-violet-500/10',\n accentText: 'text-violet-600',\n };\n case 'WHATSAPP':\n return {\n label: 'WhatsApp',\n badgeClass: 'bg-green-100 text-green-700',\n icon: <MessageSquare size={18} />,\n accentBg: 'bg-green-500/10',\n accentText: 'text-green-700',\n };\n case 'EMAIL':\n return {\n label: 'Email',\n badgeClass: 'bg-blue-100 text-blue-700',\n icon: <Mail size={18} />,\n accentBg: 'bg-blue-500/10',\n accentText: 'text-blue-600',\n };\n case 'SMS':\n return {\n label: 'SMS',\n badgeClass: 'bg-green-100 text-green-700',\n icon: <MessageSquare size={18} />,\n accentBg: 'bg-green-500/10',\n accentText: 'text-green-600',\n };\n default:\n return {\n label: platform,\n badgeClass: 'bg-gray-100 text-gray-700',\n icon: <Radio size={18} />,\n accentBg: 'bg-gray-500/10',\n accentText: 'text-gray-600',\n };\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// PlatformRouteCard — one connected OAuth platform route + personal connection UI\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface PlatformRouteCardProps {\n routeId: string;\n connectorId: string;\n /** The global channel ID (credentialsRefId) stored on the connector */\n channelId: string;\n platform: string;\n onRemoved: () => void;\n /** Navigate to microfe-integrations to reconnect */\n onReconnect: () => void;\n}\n\nconst PlatformRouteCard: FC<PlatformRouteCardProps> = ({\n routeId,\n connectorId,\n channelId,\n platform,\n onRemoved,\n onReconnect,\n}) => {\n const { data: connector, loading: connectorLoading } = useChannelConnector(connectorId);\n // The channel ID for the UPC lookup is stored on connector.credentialsRefId\n const upcChannelId = connector?.credentialsRefId ?? channelId;\n\n const {\n data: platformConnection,\n loading: connectionLoading,\n refetch: refetchConnection,\n } = useMyUserPlatformConnection(upcChannelId);\n\n const { toggle: toggleNotifications, loading: togglingNotifications } =\n useToggleSlackNotifications();\n const { revoke: revokeConnection, loading: revoking } = useRevokeSlackConnection();\n const { link: linkIntegration } = useLinkIntegrationConnection();\n const [deleteRoute] = useDeleteChannelRoute();\n\n // Manual linking state\n const [integrationConnId, setIntegrationConnId] = useState('');\n const [linking, setLinking] = useState(false);\n\n const meta = getPlatformMeta(platform);\n\n const handleLinkConnection = async () => {\n if (!integrationConnId.trim()) {\n toast.error('Please enter an integration connection ID');\n return;\n }\n setLinking(true);\n try {\n const conn = await linkIntegration(integrationConnId.trim(), upcChannelId);\n if (conn) {\n toast.success(\n `${conn.externalDisplayName ?? conn.externalUsername ?? 'Account'} connected! The bot can now send you direct messages.`\n );\n await refetchConnection();\n setIntegrationConnId('');\n } else {\n toast.error('Failed to link connection. Please verify the ID and try again.');\n }\n } catch (err) {\n toast.error(err instanceof Error ? err.message : 'Failed to link connection');\n } finally {\n setLinking(false);\n }\n };\n\n const handleRemove = async () => {\n if (!confirm(`Remove this ${meta.label} channel from the bot?`)) return;\n try {\n await deleteRoute(routeId);\n onRemoved();\n toast.success(`${meta.label} channel removed`);\n if (isNative()) void nativeNotify('success');\n } catch (err) {\n toast.error(err instanceof Error ? err.message : 'Failed to remove');\n if (isNative()) void nativeNotify('error');\n }\n };\n\n if (connectorLoading) {\n return <div className=\"h-32 bg-bg-sunken motion-safe:animate-vc-shimmer rounded-lg\" />;\n }\n\n return (\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/30 p-4 space-y-3\">\n {/* Route header */}\n <div className=\"flex items-center justify-between\">\n <span className=\"text-xs text-text-secondary font-medium uppercase tracking-wide\">\n Connected workspace\n </span>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={handleRemove}\n className=\"h-7 w-7 text-text-secondary hover:text-status-error-text\"\n title=\"Remove channel\"\n >\n <Trash2 size={13} />\n </Button>\n </div>\n\n {connectionLoading ? (\n <div className=\"flex items-center gap-2 py-2 text-text-secondary\">\n <Loader2 size={14} className=\"animate-spin\" />\n <span className=\"text-xs\">Checking {meta.label} connection…</span>\n </div>\n ) : platformConnection?.dmAllowed ? (\n /* ── Personal account connected ── */\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-3 p-3 rounded-lg bg-bg-surface border border-border-subtle\">\n <CheckCircle2 size={16} className=\"text-green-600 shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-medium text-sm\">\n {platformConnection.externalDisplayName ||\n platformConnection.externalUsername ||\n platformConnection.externalUserId}\n </p>\n {platformConnection.externalUsername && (\n <p className=\"text-xs text-text-secondary\">\n @{platformConnection.externalUsername}\n </p>\n )}\n {platformConnection.connectedAt && (\n <p className=\"text-xs text-text-secondary\">\n Connected {new Date(platformConnection.connectedAt).toLocaleDateString()}\n </p>\n )}\n </div>\n {platformConnection.isTokenExpired && (\n <Badge variant=\"outline\" className=\"text-xs text-amber-600 border-amber-400\">\n Token expired\n </Badge>\n )}\n </div>\n\n {/* Integration Connection ID (read-only) */}\n {platformConnection.integrationConnectionId && (\n <div className=\"space-y-1.5\">\n <label className=\"text-xs font-medium text-text-secondary\">\n Integration Connection ID\n </label>\n <Input\n value={platformConnection.integrationConnectionId}\n readOnly\n className=\"text-xs font-mono bg-bg-sunken/50 cursor-default\"\n onClick={(e) => {\n e.currentTarget.select();\n void nativeCopyText(platformConnection.integrationConnectionId!);\n toast.success('Connection ID copied to clipboard');\n if (isNative()) void nativeToast('Copied');\n }}\n title=\"Click to copy\"\n />\n </div>\n )}\n\n {/* Notification toggle */}\n <div className=\"flex items-center justify-between p-3 border border-border-subtle rounded-lg bg-bg-surface\">\n <div className=\"flex items-center gap-2\">\n {platformConnection.notificationsEnabled ? (\n <Bell size={14} className=\"text-primary\" />\n ) : (\n <BellOff size={14} className=\"text-text-secondary\" />\n )}\n <div>\n <p className=\"text-sm font-medium\">DM Notifications</p>\n <p className=\"text-xs text-text-secondary\">\n {platformConnection.notificationsEnabled\n ? `Bot will DM you on ${meta.label}`\n : 'DM notifications paused'}\n </p>\n </div>\n </div>\n <Button\n variant=\"outline\"\n size=\"sm\"\n isLoading={togglingNotifications}\n onClick={async () => {\n const updated = await toggleNotifications(\n platformConnection.id,\n !platformConnection.notificationsEnabled\n );\n if (updated) await refetchConnection();\n }}\n >\n {platformConnection.notificationsEnabled ? 'Disable' : 'Enable'}\n </Button>\n </div>\n\n <div className=\"flex gap-2 justify-end pt-1\">\n {platformConnection.isTokenExpired && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n leftIcon={meta.icon as React.ReactElement}\n onClick={onReconnect}\n >\n Reconnect\n </Button>\n )}\n <Button\n variant=\"destructive\"\n size=\"sm\"\n leftIcon={<Unlink size={13} />}\n isLoading={revoking}\n onClick={async () => {\n const ok = await revokeConnection(platformConnection.id);\n if (ok) await refetchConnection();\n }}\n >\n Disconnect\n </Button>\n </div>\n </div>\n ) : (\n /* ── Personal account not connected - Manual linking flow ── */\n <div className=\"space-y-3 py-2\">\n <div className=\"flex items-center gap-2 p-3 rounded-lg bg-bg-surface border border-border-subtle\">\n <div className=\"p-2 rounded-full bg-bg-sunken shrink-0\">\n <span className=\"text-text-secondary\">{meta.icon}</span>\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium text-sm\">No personal account connected</p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Connect your {meta.label} account to receive bot DMs\n </p>\n </div>\n </div>\n\n <div className=\"p-3 rounded-lg bg-bg-sunken/50 space-y-2\">\n <p className=\"text-xs font-medium text-text-secondary\">Connect your account:</p>\n <ol className=\"list-decimal list-inside space-y-1 text-xs text-text-secondary\">\n <li>Click \"Open Integrations\" below (opens in new tab)</li>\n <li>Connect your {meta.label} account via OAuth</li>\n <li>Copy the Connection ID from the integrations page</li>\n <li>Paste it below and click \"Link Connection\"</li>\n </ol>\n </div>\n\n <Button\n variant=\"outline\"\n size=\"sm\"\n leftIcon={meta.icon as React.ReactElement}\n onClick={onReconnect}\n className=\"w-full\"\n >\n Open Integrations →\n </Button>\n\n <div className=\"flex gap-2\">\n <Input\n placeholder=\"Paste integration connection ID...\"\n value={integrationConnId}\n onChange={(e) => setIntegrationConnId(e.target.value)}\n className=\"flex-1 text-sm\"\n />\n <Button\n size=\"sm\"\n onClick={handleLinkConnection}\n isLoading={linking}\n disabled={!integrationConnId.trim() || linking}\n >\n Link Connection\n </Button>\n </div>\n </div>\n )}\n </div>\n );\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// OAuthGlobalChannelCard\n// Generic card for OAuth platforms (Slack, Teams, Discord).\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface OAuthGlobalChannelCardProps {\n channel: GlobalChannelData;\n routes: ChannelRoute[];\n botId: string;\n botName: string;\n onRoutesChanged: () => void;\n}\n\nconst OAuthGlobalChannelCard: FC<OAuthGlobalChannelCardProps> = ({\n channel,\n routes,\n botId,\n botName,\n onRoutesChanged,\n}) => {\n const { basePath } = useBotlit();\n const [createRoute] = useCreateChannelRoute();\n const [connecting, setConnecting] = useState(false);\n\n const platform = channel.platform as string;\n const meta = getPlatformMeta(platform);\n const adapterLabel = getAdapterLabel(channel.adapterType as string | null);\n const isCreating = connecting;\n\n /**\n * Navigate to microfe-integrations wizard pre-configured for this platform.\n * The integrations wizard will OAuth the user, then redirect back here with\n * ?integrationConnectionId=<id>&channelId=<id> appended to the returnTo URL.\n *\n * returnTo MUST be an absolute URL so the backend's new URL(returnTo) doesn't throw.\n */\n const navigateToIntegrations = useCallback(\n (currentChannelId: string) => {\n const providerKey = PLATFORM_PROVIDER_KEY[platform];\n if (!providerKey) return;\n\n // Absolute URL so the backend oauth-callback can do new URL(returnTo) without error.\n const returnTo = window.location.origin + window.location.pathname + window.location.search;\n\n // Integrations MFE base path — shell mounts it at /integrations by convention.\n // Derive it by replacing the product segment of basePath (/botmanor, or\n // /botlit on shells that still mount the pre-rename route).\n const integrationsBase = basePath\n ? basePath.replace(/\\/(botmanor|botlit)(\\/.*)?$/, '/integrations')\n : '/integrations';\n\n const params = new URLSearchParams({\n providerKey,\n returnTo,\n channelId: currentChannelId,\n });\n\n const targetPath = `${integrationsBase}/connections/add?${params.toString()}`;\n\n // Always open in new tab for manual copy-paste workflow\n if (isNative() && /^https?:\\/\\//.test(targetPath)) {\n void nativeOpenUrl(targetPath);\n } else {\n window.open(targetPath, '_blank');\n }\n },\n [platform, basePath]\n );\n\n const handleConnect = useCallback(async () => {\n setConnecting(true);\n try {\n // Route the global channel at this bot. `workspaceId` is supplied by the\n // hook from the BotManor context — never pass an empty string here.\n await createRoute({\n connectorId: channel.id, // Global channel id doubles as the connector reference\n appId: botId,\n locationId: '*',\n metadata: { locationName: 'All channels & DMs', appName: botName },\n });\n\n await onRoutesChanged();\n\n // Navigate to microfe-integrations for OAuth\n navigateToIntegrations(channel.id);\n // page will navigate away — don't setConnecting(false)\n } catch (err) {\n toast.error(err instanceof Error ? err.message : `Failed to connect ${meta.label}`);\n setConnecting(false);\n }\n }, [\n botId,\n botName,\n channel.id,\n createRoute,\n meta.label,\n navigateToIntegrations,\n onRoutesChanged,\n ]);\n\n const handleReconnect = useCallback(() => {\n navigateToIntegrations(channel.id);\n }, [channel.id, navigateToIntegrations]);\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between pb-3\">\n <div className=\"flex items-center gap-2.5\">\n <div className={`p-1.5 rounded-md ${meta.accentBg}`}>\n <span className={meta.accentText}>{meta.icon}</span>\n </div>\n <div>\n <CardTitle className=\"text-base\">{channel.name}</CardTitle>\n {adapterLabel && <p className=\"text-xs text-text-secondary mt-0.5\">{adapterLabel}</p>}\n </div>\n </div>\n <div className=\"flex items-center gap-1.5\">\n <span className={`px-2 py-0.5 text-xs font-semibold rounded-full ${meta.badgeClass}`}>\n {meta.label}\n </span>\n {routes.length > 0 && (\n <Badge\n variant=\"outline\"\n className=\"text-xs text-green-600 border-green-400 bg-green-50\"\n >\n Active\n </Badge>\n )}\n </div>\n </CardHeader>\n\n <CardContent>\n {routes.length > 0 ? (\n <div className=\"space-y-3\">\n {routes.map((route: ChannelRoute) => (\n <PlatformRouteCard\n key={route.id}\n routeId={route.id}\n connectorId={route.connectorId}\n channelId={channel.id}\n platform={platform}\n onRemoved={onRoutesChanged}\n onReconnect={handleReconnect}\n />\n ))}\n </div>\n ) : (\n <div className=\"flex flex-col items-center gap-4 py-8 text-center\">\n <div className=\"p-4 rounded-full bg-bg-sunken\">\n <span className=\"text-text-secondary\">{meta.icon}</span>\n </div>\n <div>\n <p className=\"font-medium text-sm\">Not connected to this bot</p>\n <p className=\"text-xs text-text-secondary mt-1\">\n Enable {meta.label} for this bot. Users will connect their personal accounts\n separately.\n </p>\n </div>\n <Button\n leftIcon={meta.icon as React.ReactElement}\n onClick={handleConnect}\n isLoading={isCreating}\n >\n Set up {meta.label} channel\n </Button>\n </div>\n )}\n </CardContent>\n </Card>\n );\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ComingSoonChannelCard\n// EMAIL and SMS channels shown in the UI — connect functionality coming later\n// NOTE: This component is currently unused - we now only show OAuth platforms\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Commented out - not needed for OAuth-only flow\n// interface ComingSoonChannelCardProps {\n// channel: GlobalChannelData;\n// }\n//\n// const ComingSoonChannelCard: FC<ComingSoonChannelCardProps> = ({ channel }) => {\n// const meta = getPlatformMeta(channel.platform as string);\n// const adapterLabel = getAdapterLabel(channel.adapterType as string | null);\n//\n// return (\n// <Card className=\"opacity-80\">\n// <CardHeader className=\"flex flex-row items-center justify-between pb-3\">\n// <div className=\"flex items-center gap-2.5\">\n// <div className={`p-1.5 rounded-md ${meta.accentBg}`}>\n// <span className={meta.accentText}>{meta.icon}</span>\n// </div>\n// <div>\n// <CardTitle className=\"text-base\">{channel.name}</CardTitle>\n// {adapterLabel && <p className=\"text-xs text-text-secondary mt-0.5\">{adapterLabel}</p>}\n// </div>\n// </div>\n// <div className=\"flex items-center gap-1.5\">\n// <span className={`px-2 py-0.5 text-xs font-semibold rounded-full ${meta.badgeClass}`}>\n// {meta.label}\n// </span>\n// <Badge variant=\"outline\" className=\"text-xs text-text-secondary\">\n// <Clock size={10} className=\"mr-1\" />\n// Coming soon\n// </Badge>\n// </div>\n// </CardHeader>\n// <CardContent>\n// <p className=\"text-sm text-text-secondary text-center py-4\">\n// Bot integration for <strong>{meta.label}</strong> channels is coming soon. This channel is\n// available for notification delivery — two-way bot communication will be enabled in a\n// future update.\n// </p>\n// </CardContent>\n// </Card>\n// );\n// };\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ConfiguredChannelCard - Wrapper that fetches connector and matches channel\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface ConfiguredChannelCardProps {\n route: ChannelRoute;\n oauthChannels: GlobalChannelData[];\n botId: string;\n botName: string;\n onRoutesChanged: () => void;\n}\n\nconst ConfiguredChannelCard: FC<ConfiguredChannelCardProps> = ({\n route,\n oauthChannels,\n botId,\n botName,\n onRoutesChanged,\n}) => {\n // Fetch the connector to get credentialsRefId\n const { data: connector } = useChannelConnector(route.connectorId);\n\n // Find the matching channel by credentialsRefId\n const channel = oauthChannels.find((ch) => ch.id === connector?.credentialsRefId);\n\n // If connector is still loading or channel not found, don't render\n if (!connector || !channel) return null;\n\n // Only show OAuth channels\n if (!OAUTH_PLATFORMS.has(channel.platform as string)) return null;\n\n return (\n <OAuthGlobalChannelCard\n channel={channel}\n routes={[route]}\n botId={botId}\n botName={botName}\n onRoutesChanged={onRoutesChanged}\n />\n );\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// BotChannelsTab - Main component\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface BotChannelsTabProps {\n botId: string;\n botName: string;\n}\n\nexport const BotChannelsTab: FC<BotChannelsTabProps> = ({ botId, botName }) => {\n const {\n data: globalChannels,\n loading: channelsLoading,\n error: channelsError,\n } = useGlobalChannels();\n\n const {\n data: routes,\n loading: routesLoading,\n refetch: refetchRoutes,\n } = useBotChannelRoutes(botId);\n\n // State for channel selector sheet\n const [showChannelSelector, setShowChannelSelector] = useState(false);\n const [selectedChannelForSetup, setSelectedChannelForSetup] = useState<string | null>(null);\n\n const handleRoutesChanged = useCallback(async () => {\n await refetchRoutes();\n }, [refetchRoutes]);\n\n // Filter to show only OAuth platforms (Slack, Teams, Discord)\n const oauthChannels = globalChannels.filter((ch) => OAUTH_PLATFORMS.has(ch.platform as string));\n\n // Calculate which platforms are already configured for this bot\n const configuredPlatforms = new Set(\n routes\n .map((_route: ChannelRoute) => {\n // Find the channel for this route to get its platform\n const channel = oauthChannels.find((_ch) => {\n // Match by checking if any route uses this channel\n return routes.some((r: ChannelRoute) => r.connectorId);\n });\n return channel?.platform as string;\n })\n .filter(Boolean)\n );\n\n // Get available channels (OAuth platforms not yet configured)\n const availableChannels = oauthChannels.filter(\n (ch) => !configuredPlatforms.has(ch.platform as string)\n );\n\n // Platform icons mapping for ChannelSelectorSheet\n const platformIcons: Record<string, ReactNode> = {\n SLACK: <Slack size={18} />,\n TEAMS: <Users size={18} />,\n DISCORD: <MessageCircle size={18} />,\n };\n\n // Platform metadata mapping for ChannelSelectorSheet\n const platformMetaMap: Record<string, PlatformMeta> = {};\n oauthChannels.forEach((ch) => {\n const platform = ch.platform as string;\n platformMetaMap[platform] = getPlatformMeta(platform);\n });\n\n const handleSelectChannel = useCallback((channelId: string, _platform: string) => {\n setSelectedChannelForSetup(channelId);\n }, []);\n\n const isLoading = channelsLoading || routesLoading;\n\n if (isLoading) {\n return (\n <div className=\"space-y-4\">\n <div className=\"h-10 bg-bg-sunken motion-safe:animate-vc-shimmer rounded-lg w-52\" />\n <div className=\"h-48 bg-bg-sunken motion-safe:animate-vc-shimmer rounded-lg\" />\n <div className=\"h-48 bg-bg-sunken motion-safe:animate-vc-shimmer rounded-lg\" />\n </div>\n );\n }\n\n // Empty state: no channels configured yet\n const hasConfiguredChannels = routes.length > 0;\n\n return (\n <div className=\"space-y-4\">\n {/* Header */}\n <div>\n <h3 className=\"font-semibold text-base\">Channel Connections</h3>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n Communication platforms available for this bot\n </p>\n </div>\n\n {/* Channel error banner */}\n {channelsError && (\n <div className=\"flex items-start gap-2 rounded-lg border border-status-error-border/40 bg-status-error-bg/10 px-3 py-2 text-sm text-status-error-text\">\n <span>Failed to load channels: {channelsError.message}</span>\n </div>\n )}\n\n {/* Empty state: no OAuth channels available from backend */}\n {!channelsError && oauthChannels.length === 0 && (\n <Card>\n <CardContent className=\"py-12\">\n <EmptyState\n illustration=\"empty-notifications\"\n title=\"No channels available\"\n description=\"No active communication channels have been configured yet. Contact your administrator to set up channels.\"\n />\n </CardContent>\n </Card>\n )}\n\n {/* Empty state: channels available but none configured yet */}\n {!channelsError &&\n oauthChannels.length > 0 &&\n !hasConfiguredChannels &&\n !selectedChannelForSetup && (\n <Card>\n <CardContent className=\"py-12\">\n <EmptyState\n illustration=\"empty-notifications\"\n title=\"No channels connected\"\n description=\"Connect a communication channel to enable bot conversations on platforms like Slack, Teams, or Discord.\"\n action={\n <Button\n leftIcon={<Plus size={16} />}\n onClick={() => setShowChannelSelector(true)}\n >\n Connect Channel\n </Button>\n }\n />\n </CardContent>\n </Card>\n )}\n\n {/* Configured channels + channel in setup */}\n {oauthChannels.length > 0 && (hasConfiguredChannels || selectedChannelForSetup) && (\n <div className=\"space-y-4\">\n {/* Show configured channels */}\n {routes.map((route: ChannelRoute) => {\n return (\n <ConfiguredChannelCard\n key={route.id}\n route={route}\n oauthChannels={oauthChannels}\n botId={botId}\n botName={botName}\n onRoutesChanged={handleRoutesChanged}\n />\n );\n })}\n\n {/* Show channel currently being set up */}\n {selectedChannelForSetup && (\n <>\n {oauthChannels\n .filter((ch) => ch.id === selectedChannelForSetup)\n .map((channel) => (\n <OAuthGlobalChannelCard\n key={channel.id}\n channel={channel}\n routes={routes.filter((_r: ChannelRoute) => {\n // Only show routes for this specific channel\n return false; // Initially no routes exist\n })}\n botId={botId}\n botName={botName}\n onRoutesChanged={async () => {\n await handleRoutesChanged();\n setSelectedChannelForSetup(null);\n }}\n />\n ))}\n </>\n )}\n </div>\n )}\n\n {/* Channel Selector Sheet */}\n <ChannelSelectorSheet\n open={showChannelSelector}\n onClose={() => setShowChannelSelector(false)}\n onSelectChannel={handleSelectChannel}\n availableChannels={availableChannels}\n platformIcons={platformIcons}\n platformMeta={platformMetaMap}\n />\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { withRenamedKey as e } from "./renamedKeyStorage.js";
|
|
2
|
+
import { create as t } from "zustand";
|
|
3
|
+
import { devtools as n, persist as r } from "zustand/middleware";
|
|
4
|
+
import { createProfileScopedStorage as i } from "@burdenoff/fe-libs/shared/utils";
|
|
4
5
|
//#region src/botlit/store/botlitStore.ts
|
|
5
|
-
var
|
|
6
|
+
var a = {
|
|
6
7
|
viewMode: "grid",
|
|
7
8
|
sidebarCollapsed: !1,
|
|
8
9
|
activeTab: "bots",
|
|
@@ -40,8 +41,8 @@ var i = {
|
|
|
40
41
|
recentBots: [],
|
|
41
42
|
recentAgents: [],
|
|
42
43
|
recentConversations: []
|
|
43
|
-
},
|
|
44
|
-
...
|
|
44
|
+
}, o = t()(n(r((e) => ({
|
|
45
|
+
...a,
|
|
45
46
|
setViewMode: (t) => e({ viewMode: t }),
|
|
46
47
|
toggleSidebar: () => e((e) => ({ sidebarCollapsed: !e.sidebarCollapsed })),
|
|
47
48
|
setActiveTab: (t) => e({ activeTab: t }),
|
|
@@ -62,9 +63,9 @@ var i = {
|
|
|
62
63
|
...t
|
|
63
64
|
} })),
|
|
64
65
|
resetFilters: () => e({
|
|
65
|
-
botFilter:
|
|
66
|
-
agentFilter:
|
|
67
|
-
conversationFilter:
|
|
66
|
+
botFilter: a.botFilter,
|
|
67
|
+
agentFilter: a.agentFilter,
|
|
68
|
+
conversationFilter: a.conversationFilter
|
|
68
69
|
}),
|
|
69
70
|
setIsCreatingBot: (t) => e({ isCreatingBot: t }),
|
|
70
71
|
setIsCreatingAgent: (t) => e({ isCreatingAgent: t }),
|
|
@@ -80,10 +81,10 @@ var i = {
|
|
|
80
81
|
addRecentBot: (t) => e((e) => ({ recentBots: [t, ...e.recentBots.filter((e) => e !== t)].slice(0, 10) })),
|
|
81
82
|
addRecentAgent: (t) => e((e) => ({ recentAgents: [t, ...e.recentAgents.filter((e) => e !== t)].slice(0, 10) })),
|
|
82
83
|
addRecentConversation: (t) => e((e) => ({ recentConversations: [t, ...e.recentConversations.filter((e) => e !== t)].slice(0, 10) })),
|
|
83
|
-
reset: () => e(
|
|
84
|
+
reset: () => e(a)
|
|
84
85
|
}), {
|
|
85
|
-
name: "burdenoff-
|
|
86
|
-
storage:
|
|
86
|
+
name: "burdenoff-botmanor-storage",
|
|
87
|
+
storage: e(i(), "burdenoff-botlit-storage"),
|
|
87
88
|
partialize: (e) => ({
|
|
88
89
|
viewMode: e.viewMode,
|
|
89
90
|
sidebarCollapsed: e.sidebarCollapsed,
|
|
@@ -96,6 +97,6 @@ var i = {
|
|
|
96
97
|
enabled: !1
|
|
97
98
|
}));
|
|
98
99
|
//#endregion
|
|
99
|
-
export {
|
|
100
|
+
export { o as useBotlitStore };
|
|
100
101
|
|
|
101
102
|
//# sourceMappingURL=botlitStore.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"botlitStore.js","names":[],"sources":["../../../src/botlit/store/botlitStore.ts"],"sourcesContent":["/**\n * BotManor Zustand Store\n * @module botlit/store\n */\n\nimport { create } from 'zustand';\nimport { devtools, persist } from 'zustand/middleware';\nimport { createProfileScopedStorage } from '@burdenoff/fe-libs/shared/utils';\nimport type {\n BotSummary,\n AgentSummary,\n KnowledgeBaseSummary,\n Conversation,\n ViewMode,\n BotFilter,\n AgentFilter,\n BaseFilter,\n} from '../types';\n\n// ConversationFilter extends BaseFilter with conversation-specific fields\ninterface ConversationFilter extends BaseFilter {\n botId?: string;\n agentId?: string;\n}\n\n// ============================================================================\n// State Interface\n// ============================================================================\n\ninterface BotlitState {\n // View state\n viewMode: ViewMode;\n sidebarCollapsed: boolean;\n activeTab: string;\n\n // Selected entities\n selectedBot: BotSummary | null;\n selectedAgent: AgentSummary | null;\n selectedKnowledgeBase: KnowledgeBaseSummary | null;\n selectedConversation: Conversation | null;\n\n // Filters\n botFilter: BotFilter;\n agentFilter: AgentFilter;\n conversationFilter: ConversationFilter;\n\n // UI State\n isCreatingBot: boolean;\n isCreatingAgent: boolean;\n isUploadingDocument: boolean;\n isChatOpen: boolean;\n chatAgentId: string | null;\n\n // Recent items\n recentBots: string[];\n recentAgents: string[];\n recentConversations: string[];\n\n // Actions\n setViewMode: (mode: ViewMode) => void;\n toggleSidebar: () => void;\n setActiveTab: (tab: string) => void;\n\n setSelectedBot: (bot: BotSummary | null) => void;\n setSelectedAgent: (agent: AgentSummary | null) => void;\n setSelectedKnowledgeBase: (kb: KnowledgeBaseSummary | null) => void;\n setSelectedConversation: (conversation: Conversation | null) => void;\n\n updateBotFilter: (filter: Partial<BotFilter>) => void;\n updateAgentFilter: (filter: Partial<AgentFilter>) => void;\n updateConversationFilter: (filter: Partial<ConversationFilter>) => void;\n resetFilters: () => void;\n\n setIsCreatingBot: (value: boolean) => void;\n setIsCreatingAgent: (value: boolean) => void;\n setIsUploadingDocument: (value: boolean) => void;\n openChat: (agentId: string) => void;\n closeChat: () => void;\n\n addRecentBot: (botId: string) => void;\n addRecentAgent: (agentId: string) => void;\n addRecentConversation: (conversationId: string) => void;\n\n reset: () => void;\n}\n\n// ============================================================================\n// Initial State\n// ============================================================================\n\nconst initialState = {\n viewMode: 'grid' as ViewMode,\n sidebarCollapsed: false,\n activeTab: 'bots',\n\n selectedBot: null,\n selectedAgent: null,\n selectedKnowledgeBase: null,\n selectedConversation: null,\n\n botFilter: {\n search: '',\n status: undefined,\n sortBy: 'updatedAt' as const,\n sortOrder: 'desc' as const,\n },\n agentFilter: {\n search: '',\n appId: undefined,\n type: undefined,\n status: undefined,\n sortBy: 'updatedAt' as const,\n sortOrder: 'desc' as const,\n },\n conversationFilter: {\n search: '',\n botId: undefined,\n agentId: undefined,\n status: undefined,\n sortBy: 'updatedAt' as const,\n sortOrder: 'desc' as const,\n },\n\n isCreatingBot: false,\n isCreatingAgent: false,\n isUploadingDocument: false,\n isChatOpen: false,\n chatAgentId: null,\n\n recentBots: [] as string[],\n recentAgents: [] as string[],\n recentConversations: [] as string[],\n};\n\n// ============================================================================\n// Store\n// ============================================================================\n\nexport const useBotlitStore = create<BotlitState>()(\n devtools(\n persist(\n (set) => ({\n ...initialState,\n\n // View actions\n setViewMode: (mode) => set({ viewMode: mode }),\n toggleSidebar: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })),\n setActiveTab: (tab) => set({ activeTab: tab }),\n\n // Selection actions\n setSelectedBot: (bot) => set({ selectedBot: bot }),\n setSelectedAgent: (agent) => set({ selectedAgent: agent }),\n setSelectedKnowledgeBase: (kb) => set({ selectedKnowledgeBase: kb }),\n setSelectedConversation: (conversation) => set({ selectedConversation: conversation }),\n\n // Filter actions\n updateBotFilter: (filter) =>\n set((state) => ({\n botFilter: { ...state.botFilter, ...filter },\n })),\n updateAgentFilter: (filter) =>\n set((state) => ({\n agentFilter: { ...state.agentFilter, ...filter },\n })),\n updateConversationFilter: (filter) =>\n set((state) => ({\n conversationFilter: { ...state.conversationFilter, ...filter },\n })),\n resetFilters: () =>\n set({\n botFilter: initialState.botFilter,\n agentFilter: initialState.agentFilter,\n conversationFilter: initialState.conversationFilter,\n }),\n\n // UI state actions\n setIsCreatingBot: (value) => set({ isCreatingBot: value }),\n setIsCreatingAgent: (value) => set({ isCreatingAgent: value }),\n setIsUploadingDocument: (value) => set({ isUploadingDocument: value }),\n openChat: (agentId) => set({ isChatOpen: true, chatAgentId: agentId }),\n closeChat: () => set({ isChatOpen: false, chatAgentId: null }),\n\n // Recent items actions\n addRecentBot: (botId) =>\n set((state) => ({\n recentBots: [botId, ...state.recentBots.filter((id) => id !== botId)].slice(0, 10),\n })),\n addRecentAgent: (agentId) =>\n set((state) => ({\n recentAgents: [agentId, ...state.recentAgents.filter((id) => id !== agentId)].slice(\n 0,\n 10\n ),\n })),\n addRecentConversation: (conversationId) =>\n set((state) => ({\n recentConversations: [\n conversationId,\n ...state.recentConversations.filter((id) => id !== conversationId),\n ].slice(0, 10),\n })),\n\n // Reset\n reset: () => set(initialState),\n }),\n {\n name: 'burdenoff-
|
|
1
|
+
{"version":3,"file":"botlitStore.js","names":[],"sources":["../../../src/botlit/store/botlitStore.ts"],"sourcesContent":["/**\n * BotManor Zustand Store\n * @module botlit/store\n */\n\nimport { create } from 'zustand';\nimport { devtools, persist } from 'zustand/middleware';\nimport { createProfileScopedStorage } from '@burdenoff/fe-libs/shared/utils';\nimport { withRenamedKey } from './renamedKeyStorage';\nimport type {\n BotSummary,\n AgentSummary,\n KnowledgeBaseSummary,\n Conversation,\n ViewMode,\n BotFilter,\n AgentFilter,\n BaseFilter,\n} from '../types';\n\n// ConversationFilter extends BaseFilter with conversation-specific fields\ninterface ConversationFilter extends BaseFilter {\n botId?: string;\n agentId?: string;\n}\n\n// ============================================================================\n// State Interface\n// ============================================================================\n\ninterface BotlitState {\n // View state\n viewMode: ViewMode;\n sidebarCollapsed: boolean;\n activeTab: string;\n\n // Selected entities\n selectedBot: BotSummary | null;\n selectedAgent: AgentSummary | null;\n selectedKnowledgeBase: KnowledgeBaseSummary | null;\n selectedConversation: Conversation | null;\n\n // Filters\n botFilter: BotFilter;\n agentFilter: AgentFilter;\n conversationFilter: ConversationFilter;\n\n // UI State\n isCreatingBot: boolean;\n isCreatingAgent: boolean;\n isUploadingDocument: boolean;\n isChatOpen: boolean;\n chatAgentId: string | null;\n\n // Recent items\n recentBots: string[];\n recentAgents: string[];\n recentConversations: string[];\n\n // Actions\n setViewMode: (mode: ViewMode) => void;\n toggleSidebar: () => void;\n setActiveTab: (tab: string) => void;\n\n setSelectedBot: (bot: BotSummary | null) => void;\n setSelectedAgent: (agent: AgentSummary | null) => void;\n setSelectedKnowledgeBase: (kb: KnowledgeBaseSummary | null) => void;\n setSelectedConversation: (conversation: Conversation | null) => void;\n\n updateBotFilter: (filter: Partial<BotFilter>) => void;\n updateAgentFilter: (filter: Partial<AgentFilter>) => void;\n updateConversationFilter: (filter: Partial<ConversationFilter>) => void;\n resetFilters: () => void;\n\n setIsCreatingBot: (value: boolean) => void;\n setIsCreatingAgent: (value: boolean) => void;\n setIsUploadingDocument: (value: boolean) => void;\n openChat: (agentId: string) => void;\n closeChat: () => void;\n\n addRecentBot: (botId: string) => void;\n addRecentAgent: (agentId: string) => void;\n addRecentConversation: (conversationId: string) => void;\n\n reset: () => void;\n}\n\n// ============================================================================\n// Initial State\n// ============================================================================\n\nconst initialState = {\n viewMode: 'grid' as ViewMode,\n sidebarCollapsed: false,\n activeTab: 'bots',\n\n selectedBot: null,\n selectedAgent: null,\n selectedKnowledgeBase: null,\n selectedConversation: null,\n\n botFilter: {\n search: '',\n status: undefined,\n sortBy: 'updatedAt' as const,\n sortOrder: 'desc' as const,\n },\n agentFilter: {\n search: '',\n appId: undefined,\n type: undefined,\n status: undefined,\n sortBy: 'updatedAt' as const,\n sortOrder: 'desc' as const,\n },\n conversationFilter: {\n search: '',\n botId: undefined,\n agentId: undefined,\n status: undefined,\n sortBy: 'updatedAt' as const,\n sortOrder: 'desc' as const,\n },\n\n isCreatingBot: false,\n isCreatingAgent: false,\n isUploadingDocument: false,\n isChatOpen: false,\n chatAgentId: null,\n\n recentBots: [] as string[],\n recentAgents: [] as string[],\n recentConversations: [] as string[],\n};\n\n// ============================================================================\n// Store\n// ============================================================================\n\nexport const useBotlitStore = create<BotlitState>()(\n devtools(\n persist(\n (set) => ({\n ...initialState,\n\n // View actions\n setViewMode: (mode) => set({ viewMode: mode }),\n toggleSidebar: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })),\n setActiveTab: (tab) => set({ activeTab: tab }),\n\n // Selection actions\n setSelectedBot: (bot) => set({ selectedBot: bot }),\n setSelectedAgent: (agent) => set({ selectedAgent: agent }),\n setSelectedKnowledgeBase: (kb) => set({ selectedKnowledgeBase: kb }),\n setSelectedConversation: (conversation) => set({ selectedConversation: conversation }),\n\n // Filter actions\n updateBotFilter: (filter) =>\n set((state) => ({\n botFilter: { ...state.botFilter, ...filter },\n })),\n updateAgentFilter: (filter) =>\n set((state) => ({\n agentFilter: { ...state.agentFilter, ...filter },\n })),\n updateConversationFilter: (filter) =>\n set((state) => ({\n conversationFilter: { ...state.conversationFilter, ...filter },\n })),\n resetFilters: () =>\n set({\n botFilter: initialState.botFilter,\n agentFilter: initialState.agentFilter,\n conversationFilter: initialState.conversationFilter,\n }),\n\n // UI state actions\n setIsCreatingBot: (value) => set({ isCreatingBot: value }),\n setIsCreatingAgent: (value) => set({ isCreatingAgent: value }),\n setIsUploadingDocument: (value) => set({ isUploadingDocument: value }),\n openChat: (agentId) => set({ isChatOpen: true, chatAgentId: agentId }),\n closeChat: () => set({ isChatOpen: false, chatAgentId: null }),\n\n // Recent items actions\n addRecentBot: (botId) =>\n set((state) => ({\n recentBots: [botId, ...state.recentBots.filter((id) => id !== botId)].slice(0, 10),\n })),\n addRecentAgent: (agentId) =>\n set((state) => ({\n recentAgents: [agentId, ...state.recentAgents.filter((id) => id !== agentId)].slice(\n 0,\n 10\n ),\n })),\n addRecentConversation: (conversationId) =>\n set((state) => ({\n recentConversations: [\n conversationId,\n ...state.recentConversations.filter((id) => id !== conversationId),\n ].slice(0, 10),\n })),\n\n // Reset\n reset: () => set(initialState),\n }),\n {\n name: 'burdenoff-botmanor-storage',\n // Renamed from the Botlit-era key; saved UI state moves over on first load.\n storage: withRenamedKey(createProfileScopedStorage(), 'burdenoff-botlit-storage'),\n partialize: (state) => ({\n viewMode: state.viewMode,\n sidebarCollapsed: state.sidebarCollapsed,\n recentBots: state.recentBots,\n recentAgents: state.recentAgents,\n recentConversations: state.recentConversations,\n }),\n }\n ),\n { name: 'BotManor Store', enabled: import.meta.env?.DEV === true }\n )\n);\n"],"mappings":";;;;;AA2FA,IAAM,IAAe;CACnB,UAAU;CACV,kBAAkB;CAClB,WAAW;CAEX,aAAa;CACb,eAAe;CACf,uBAAuB;CACvB,sBAAsB;CAEtB,WAAW;EACT,QAAQ;EACR,QAAQ,KAAA;EACR,QAAQ;EACR,WAAW;EACZ;CACD,aAAa;EACX,QAAQ;EACR,OAAO,KAAA;EACP,MAAM,KAAA;EACN,QAAQ,KAAA;EACR,QAAQ;EACR,WAAW;EACZ;CACD,oBAAoB;EAClB,QAAQ;EACR,OAAO,KAAA;EACP,SAAS,KAAA;EACT,QAAQ,KAAA;EACR,QAAQ;EACR,WAAW;EACZ;CAED,eAAe;CACf,iBAAiB;CACjB,qBAAqB;CACrB,YAAY;CACZ,aAAa;CAEb,YAAY,EAAE;CACd,cAAc,EAAE;CAChB,qBAAqB,EAAE;CACxB,EAMY,IAAiB,GAAqB,CACjD,EACE,GACG,OAAS;CACR,GAAG;CAGH,cAAc,MAAS,EAAI,EAAE,UAAU,GAAM,CAAC;CAC9C,qBAAqB,GAAK,OAAW,EAAE,kBAAkB,CAAC,EAAM,kBAAkB,EAAE;CACpF,eAAe,MAAQ,EAAI,EAAE,WAAW,GAAK,CAAC;CAG9C,iBAAiB,MAAQ,EAAI,EAAE,aAAa,GAAK,CAAC;CAClD,mBAAmB,MAAU,EAAI,EAAE,eAAe,GAAO,CAAC;CAC1D,2BAA2B,MAAO,EAAI,EAAE,uBAAuB,GAAI,CAAC;CACpE,0BAA0B,MAAiB,EAAI,EAAE,sBAAsB,GAAc,CAAC;CAGtF,kBAAkB,MAChB,GAAK,OAAW,EACd,WAAW;EAAE,GAAG,EAAM;EAAW,GAAG;EAAQ,EAC7C,EAAE;CACL,oBAAoB,MAClB,GAAK,OAAW,EACd,aAAa;EAAE,GAAG,EAAM;EAAa,GAAG;EAAQ,EACjD,EAAE;CACL,2BAA2B,MACzB,GAAK,OAAW,EACd,oBAAoB;EAAE,GAAG,EAAM;EAAoB,GAAG;EAAQ,EAC/D,EAAE;CACL,oBACE,EAAI;EACF,WAAW,EAAa;EACxB,aAAa,EAAa;EAC1B,oBAAoB,EAAa;EAClC,CAAC;CAGJ,mBAAmB,MAAU,EAAI,EAAE,eAAe,GAAO,CAAC;CAC1D,qBAAqB,MAAU,EAAI,EAAE,iBAAiB,GAAO,CAAC;CAC9D,yBAAyB,MAAU,EAAI,EAAE,qBAAqB,GAAO,CAAC;CACtE,WAAW,MAAY,EAAI;EAAE,YAAY;EAAM,aAAa;EAAS,CAAC;CACtE,iBAAiB,EAAI;EAAE,YAAY;EAAO,aAAa;EAAM,CAAC;CAG9D,eAAe,MACb,GAAK,OAAW,EACd,YAAY,CAAC,GAAO,GAAG,EAAM,WAAW,QAAQ,MAAO,MAAO,EAAM,CAAC,CAAC,MAAM,GAAG,GAAG,EACnF,EAAE;CACL,iBAAiB,MACf,GAAK,OAAW,EACd,cAAc,CAAC,GAAS,GAAG,EAAM,aAAa,QAAQ,MAAO,MAAO,EAAQ,CAAC,CAAC,MAC5E,GACA,GACD,EACF,EAAE;CACL,wBAAwB,MACtB,GAAK,OAAW,EACd,qBAAqB,CACnB,GACA,GAAG,EAAM,oBAAoB,QAAQ,MAAO,MAAO,EAAe,CACnE,CAAC,MAAM,GAAG,GAAG,EACf,EAAE;CAGL,aAAa,EAAI,EAAa;CAC/B,GACD;CACE,MAAM;CAEN,SAAS,EAAe,GAA4B,EAAE,2BAA2B;CACjF,aAAa,OAAW;EACtB,UAAU,EAAM;EAChB,kBAAkB,EAAM;EACxB,YAAY,EAAM;EAClB,cAAc,EAAM;EACpB,qBAAqB,EAAM;EAC5B;CACF,CACF,EACD;CAAE,MAAM;CAAkB,SAAS;CAA+B,CACnE,CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/botlit/store/renamedKeyStorage.ts
|
|
2
|
+
function e(e, t) {
|
|
3
|
+
return e instanceof Promise ? e.then(t) : t(e);
|
|
4
|
+
}
|
|
5
|
+
function t(t, n) {
|
|
6
|
+
return t && {
|
|
7
|
+
getItem: (r) => e(t.getItem(r), (i) => i !== null || r === n ? i : e(t.getItem(n), (i) => i === null ? null : e(t.setItem(r, i), () => e(t.removeItem(n), () => i)))),
|
|
8
|
+
setItem: (e, n) => t.setItem(e, n),
|
|
9
|
+
removeItem: (r) => e(t.removeItem(r), () => r === n ? void 0 : t.removeItem(n))
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { t as withRenamedKey };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=renamedKeyStorage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renamedKeyStorage.js","names":[],"sources":["../../../src/botlit/store/renamedKeyStorage.ts"],"sourcesContent":["/**\n * Zustand persist storage that moves a store's saved state from a renamed key.\n * @module botlit/store/renamedKeyStorage\n */\n\nimport type { PersistStorage, StorageValue } from 'zustand/middleware';\n\ntype MaybePromise<T> = T | Promise<T>;\n\nfunction then<T, R>(value: MaybePromise<T>, fn: (resolved: T) => MaybePromise<R>): MaybePromise<R> {\n return value instanceof Promise ? value.then(fn) : fn(value);\n}\n\n/**\n * Wrap a persist storage so a store renamed from `legacyName` keeps its\n * saved state. On a read that finds nothing under the new name, the value\n * under `legacyName` is copied to the new name and the legacy entry is\n * removed, so the move happens once, on first load. Writes and removals go\n * to the new name only (removals also clear any legacy leftover).\n *\n * Used for the Botlit -> BotManor rename (`burdenoff-botlit-storage` ->\n * `burdenoff-botmanor-storage`); the wrapped storage keeps its own profile\n * scoping, so each profile's entry moves independently.\n */\nexport function withRenamedKey<S>(\n inner: PersistStorage<S> | undefined,\n legacyName: string\n): PersistStorage<S> | undefined {\n if (!inner) return inner;\n return {\n getItem: (name) =>\n then(inner.getItem(name), (current): MaybePromise<StorageValue<S> | null> => {\n if (current !== null || name === legacyName) return current;\n return then(inner.getItem(legacyName), (legacy) => {\n if (legacy === null) return null;\n return then(inner.setItem(name, legacy), () =>\n then(inner.removeItem(legacyName), () => legacy)\n );\n });\n }),\n setItem: (name, value) => inner.setItem(name, value),\n removeItem: (name) =>\n then(inner.removeItem(name), () =>\n name === legacyName ? undefined : inner.removeItem(legacyName)\n ),\n };\n}\n"],"mappings":";AASA,SAAS,EAAW,GAAwB,GAAuD;AACjG,QAAO,aAAiB,UAAU,EAAM,KAAK,EAAG,GAAG,EAAG,EAAM;;AAc9D,SAAgB,EACd,GACA,GAC+B;AAE/B,QADK,KACE;EACL,UAAU,MACR,EAAK,EAAM,QAAQ,EAAK,GAAG,MACrB,MAAY,QAAQ,MAAS,IAAmB,IAC7C,EAAK,EAAM,QAAQ,EAAW,GAAG,MAClC,MAAW,OAAa,OACrB,EAAK,EAAM,QAAQ,GAAM,EAAO,QACrC,EAAK,EAAM,WAAW,EAAW,QAAQ,EAAO,CACjD,CACD,CACF;EACJ,UAAU,GAAM,MAAU,EAAM,QAAQ,GAAM,EAAM;EACpD,aAAa,MACX,EAAK,EAAM,WAAW,EAAK,QACzB,MAAS,IAAa,KAAA,IAAY,EAAM,WAAW,EAAW,CAC/D;EACJ"}
|
package/package.json
CHANGED