@burdenoff/microfe-subscriberbot 2026.1030.2 → 2026.1030.4
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/subscriberbot/SubscriberBotRoutes.d.ts.map +1 -1
- package/dist/subscriberbot/SubscriberBotRoutes.js +12 -8
- package/dist/subscriberbot/SubscriberBotRoutes.js.map +1 -1
- package/dist/subscriberbot/components/savings/SavingsCards.d.ts +11 -0
- package/dist/subscriberbot/components/savings/SavingsCards.d.ts.map +1 -0
- package/dist/subscriberbot/components/savings/SavingsCards.js +448 -0
- package/dist/subscriberbot/components/savings/SavingsCards.js.map +1 -0
- package/dist/subscriberbot/components/savings/index.d.ts +8 -0
- package/dist/subscriberbot/components/savings/index.d.ts.map +1 -0
- package/dist/subscriberbot/components/savings/index.js +2 -0
- package/dist/subscriberbot/components/savings/savingsModel.d.ts +53 -0
- package/dist/subscriberbot/components/savings/savingsModel.d.ts.map +1 -0
- package/dist/subscriberbot/components/savings/savingsModel.js +105 -0
- package/dist/subscriberbot/components/savings/savingsModel.js.map +1 -0
- package/dist/subscriberbot/hooks/index.d.ts +1 -1
- package/dist/subscriberbot/hooks/index.d.ts.map +1 -1
- package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts +11 -2
- package/dist/subscriberbot/hooks/useSubscriberBotApi.d.ts.map +1 -1
- package/dist/subscriberbot/hooks/useSubscriberBotApi.js +48 -26
- package/dist/subscriberbot/hooks/useSubscriberBotApi.js.map +1 -1
- package/dist/subscriberbot/pages/InsightsPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/InsightsPage.js +121 -110
- package/dist/subscriberbot/pages/InsightsPage.js.map +1 -1
- package/dist/subscriberbot/pages/SavingsPage.d.ts +4 -0
- package/dist/subscriberbot/pages/SavingsPage.d.ts.map +1 -0
- package/dist/subscriberbot/pages/SavingsPage.js +205 -0
- package/dist/subscriberbot/pages/SavingsPage.js.map +1 -0
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.d.ts.map +1 -1
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js +318 -312
- package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js.map +1 -1
- package/dist/subscriberbot/types/index.d.ts +61 -1
- package/dist/subscriberbot/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubscriberBotRoutes.d.ts","sourceRoot":"","sources":["../../src/subscriberbot/SubscriberBotRoutes.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,EAAkB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"SubscriberBotRoutes.d.ts","sourceRoot":"","sources":["../../src/subscriberbot/SubscriberBotRoutes.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,EAAkB,MAAM,OAAO,CAAC;AAmFhD,eAAO,MAAM,mBAAmB,EAAE,EAkEjC,CAAC"}
|
|
@@ -5,15 +5,15 @@ import { Navigate as i, Route as a, Routes as o, useLocation as s } from "react-
|
|
|
5
5
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
6
6
|
import { Spinner as u } from "@burdenoff/fe-libs/ui";
|
|
7
7
|
//#region src/subscriberbot/SubscriberBotRoutes.tsx
|
|
8
|
-
var d = r(() => import("./pages/relationships/RelationshipsListPage.js").then((e) => ({ default: e.RelationshipsListPage }))), f = r(() => import("./pages/relationships/RelationshipNewPage.js").then((e) => ({ default: e.RelationshipNewPage }))), p = r(() => import("./pages/relationships/RelationshipDetailPage.js").then((e) => ({ default: e.RelationshipDetailPage }))), m = r(() => import("./pages/providers/ProvidersListPage.js").then((e) => ({ default: e.ProvidersListPage }))), h = r(() => import("./pages/providers/ProviderNewPage.js").then((e) => ({ default: e.ProviderNewPage }))), g = r(() => import("./pages/providers/ProviderDetailPage.js").then((e) => ({ default: e.ProviderDetailPage }))), _ = r(() => import("./pages/InboxPage.js").then((e) => ({ default: e.InboxPage }))), v = r(() => import("./pages/InvoicesPage.js").then((e) => ({ default: e.InvoicesPage }))), y = r(() => import("./pages/WorkflowsPage.js").then((e) => ({ default: e.WorkflowsPage }))), b = r(() => import("./pages/agents/AgentsListPage.js").then((e) => ({ default: e.AgentsListPage }))), x = r(() => import("./pages/agents/AgentNewPage.js").then((e) => ({ default: e.AgentNewPage }))), S = r(() => import("./pages/agents/AgentDetailPage.js").then((e) => ({ default: e.AgentDetailPage }))), C = r(() => import("./pages/decisions/DecisionsPage.js").then((e) => ({ default: e.DecisionsPage }))), w = r(() => import("./pages/decisions/DecisionDetailPage.js").then((e) => ({ default: e.DecisionDetailPage }))), T = r(() => import("./pages/InsightsPage.js").then((e) => ({ default: e.InsightsPage }))),
|
|
8
|
+
var d = r(() => import("./pages/relationships/RelationshipsListPage.js").then((e) => ({ default: e.RelationshipsListPage }))), f = r(() => import("./pages/relationships/RelationshipNewPage.js").then((e) => ({ default: e.RelationshipNewPage }))), p = r(() => import("./pages/relationships/RelationshipDetailPage.js").then((e) => ({ default: e.RelationshipDetailPage }))), m = r(() => import("./pages/providers/ProvidersListPage.js").then((e) => ({ default: e.ProvidersListPage }))), h = r(() => import("./pages/providers/ProviderNewPage.js").then((e) => ({ default: e.ProviderNewPage }))), g = r(() => import("./pages/providers/ProviderDetailPage.js").then((e) => ({ default: e.ProviderDetailPage }))), _ = r(() => import("./pages/InboxPage.js").then((e) => ({ default: e.InboxPage }))), v = r(() => import("./pages/InvoicesPage.js").then((e) => ({ default: e.InvoicesPage }))), y = r(() => import("./pages/WorkflowsPage.js").then((e) => ({ default: e.WorkflowsPage }))), b = r(() => import("./pages/agents/AgentsListPage.js").then((e) => ({ default: e.AgentsListPage }))), x = r(() => import("./pages/agents/AgentNewPage.js").then((e) => ({ default: e.AgentNewPage }))), S = r(() => import("./pages/agents/AgentDetailPage.js").then((e) => ({ default: e.AgentDetailPage }))), C = r(() => import("./pages/decisions/DecisionsPage.js").then((e) => ({ default: e.DecisionsPage }))), w = r(() => import("./pages/decisions/DecisionDetailPage.js").then((e) => ({ default: e.DecisionDetailPage }))), T = r(() => import("./pages/SavingsPage.js").then((e) => ({ default: e.SavingsPage }))), E = r(() => import("./pages/InsightsPage.js").then((e) => ({ default: e.InsightsPage }))), D = r(() => import("./pages/AssetVaultPage.js").then((e) => ({ default: e.AssetVaultPage }))), O = () => /* @__PURE__ */ c("div", {
|
|
9
9
|
className: "flex min-h-[400px] items-center justify-center",
|
|
10
10
|
children: /* @__PURE__ */ c(u, { size: "lg" })
|
|
11
|
-
}),
|
|
12
|
-
let { basePath: r, isStandalone: u } = e(),
|
|
13
|
-
return
|
|
11
|
+
}), k = () => {
|
|
12
|
+
let { basePath: r, isStandalone: u } = e(), k = s(), A = r === "/" ? "" : (r ?? "").replace(/\/$/, "");
|
|
13
|
+
return A && !u && !(k.pathname === A || k.pathname.startsWith(A + "/")) ? null : /* @__PURE__ */ c("div", {
|
|
14
14
|
className: "flex min-h-full flex-col",
|
|
15
15
|
children: /* @__PURE__ */ c(n, {
|
|
16
|
-
fallback: /* @__PURE__ */ c(
|
|
16
|
+
fallback: /* @__PURE__ */ c(O, {}),
|
|
17
17
|
children: /* @__PURE__ */ l(o, { children: [
|
|
18
18
|
/* @__PURE__ */ c(a, {
|
|
19
19
|
index: !0,
|
|
@@ -53,11 +53,11 @@ var d = r(() => import("./pages/relationships/RelationshipsListPage.js").then((e
|
|
|
53
53
|
}),
|
|
54
54
|
/* @__PURE__ */ c(a, {
|
|
55
55
|
path: "insights",
|
|
56
|
-
element: /* @__PURE__ */ c(
|
|
56
|
+
element: /* @__PURE__ */ c(E, {})
|
|
57
57
|
}),
|
|
58
58
|
/* @__PURE__ */ c(a, {
|
|
59
59
|
path: "vault",
|
|
60
|
-
element: /* @__PURE__ */ c(
|
|
60
|
+
element: /* @__PURE__ */ c(D, {})
|
|
61
61
|
}),
|
|
62
62
|
/* @__PURE__ */ c(a, {
|
|
63
63
|
path: "agents",
|
|
@@ -75,6 +75,10 @@ var d = r(() => import("./pages/relationships/RelationshipsListPage.js").then((e
|
|
|
75
75
|
path: "decisions",
|
|
76
76
|
element: /* @__PURE__ */ c(C, {})
|
|
77
77
|
}),
|
|
78
|
+
/* @__PURE__ */ c(a, {
|
|
79
|
+
path: "savings",
|
|
80
|
+
element: /* @__PURE__ */ c(T, {})
|
|
81
|
+
}),
|
|
78
82
|
/* @__PURE__ */ c(a, {
|
|
79
83
|
path: "decisions/:id",
|
|
80
84
|
element: /* @__PURE__ */ c(w, {})
|
|
@@ -95,6 +99,6 @@ var d = r(() => import("./pages/relationships/RelationshipsListPage.js").then((e
|
|
|
95
99
|
});
|
|
96
100
|
};
|
|
97
101
|
//#endregion
|
|
98
|
-
export {
|
|
102
|
+
export { k as SubscriberBotRoutes };
|
|
99
103
|
|
|
100
104
|
//# sourceMappingURL=SubscriberBotRoutes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubscriberBotRoutes.js","names":[],"sources":["../../src/subscriberbot/SubscriberBotRoutes.tsx"],"sourcesContent":["/**\n * SubscriberBot Routes\n * @module subscriberbot\n *\n * Domain routes for the Subscriber Bot product. The dashboard is eager-loaded\n * (primary landing); all other pages are lazy-loaded.\n */\n\nimport { type FC, lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { useSubscriberBot } from './context/SubscriberBotContext';\n\n// Eager — primary landing\nimport { DashboardPage } from './pages/DashboardPage';\n\n// Relationships\nconst RelationshipsListPage = lazy(() =>\n import('./pages/relationships/RelationshipsListPage').then((m) => ({\n default: m.RelationshipsListPage,\n }))\n);\nconst RelationshipNewPage = lazy(() =>\n import('./pages/relationships/RelationshipNewPage').then((m) => ({\n default: m.RelationshipNewPage,\n }))\n);\nconst RelationshipDetailPage = lazy(() =>\n import('./pages/relationships/RelationshipDetailPage').then((m) => ({\n default: m.RelationshipDetailPage,\n }))\n);\n\n// Providers\nconst ProvidersListPage = lazy(() =>\n import('./pages/providers/ProvidersListPage').then((m) => ({ default: m.ProvidersListPage }))\n);\nconst ProviderNewPage = lazy(() =>\n import('./pages/providers/ProviderNewPage').then((m) => ({ default: m.ProviderNewPage }))\n);\nconst ProviderDetailPage = lazy(() =>\n import('./pages/providers/ProviderDetailPage').then((m) => ({ default: m.ProviderDetailPage }))\n);\n\n// Inbox / Invoices / Workflows\nconst InboxPage = lazy(() => import('./pages/InboxPage').then((m) => ({ default: m.InboxPage })));\nconst InvoicesPage = lazy(() =>\n import('./pages/InvoicesPage').then((m) => ({ default: m.InvoicesPage }))\n);\nconst WorkflowsPage = lazy(() =>\n import('./pages/WorkflowsPage').then((m) => ({ default: m.WorkflowsPage }))\n);\n\n// Agents\nconst AgentsListPage = lazy(() =>\n import('./pages/agents/AgentsListPage').then((m) => ({ default: m.AgentsListPage }))\n);\nconst AgentNewPage = lazy(() =>\n import('./pages/agents/AgentNewPage').then((m) => ({ default: m.AgentNewPage }))\n);\nconst AgentDetailPage = lazy(() =>\n import('./pages/agents/AgentDetailPage').then((m) => ({ default: m.AgentDetailPage }))\n);\n\n// AI decision queue (BOFF-3663)\nconst DecisionsPage = lazy(() =>\n import('./pages/decisions/DecisionsPage').then((m) => ({ default: m.DecisionsPage }))\n);\nconst DecisionDetailPage = lazy(() =>\n import('./pages/decisions/DecisionDetailPage').then((m) => ({ default: m.DecisionDetailPage }))\n);\n\n// Insights + Asset Vault (aggregated real data)\nconst InsightsPage = lazy(() =>\n import('./pages/InsightsPage').then((m) => ({ default: m.InsightsPage }))\n);\nconst AssetVaultPage = lazy(() =>\n import('./pages/AssetVaultPage').then((m) => ({ default: m.AssetVaultPage }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n);\n\nexport const SubscriberBotRoutes: FC = () => {\n const { basePath, isStandalone } = useSubscriberBot();\n const location = useLocation();\n\n // Normalize base path\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render if the current path matches basePath (integrated mode).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n {/* Overview / dashboard */}\n <Route index element={<DashboardPage />} />\n\n {/* Relationships (the core domain) */}\n <Route path=\"relationships\" element={<RelationshipsListPage />} />\n <Route path=\"relationships/new\" element={<RelationshipNewPage />} />\n <Route path=\"relationships/:id\" element={<RelationshipDetailPage />} />\n\n {/* Providers (registry) */}\n <Route path=\"providers\" element={<ProvidersListPage />} />\n <Route path=\"providers/new\" element={<ProviderNewPage />} />\n <Route path=\"providers/:id\" element={<ProviderDetailPage />} />\n\n {/* Universal Inbox */}\n {/* One optional-segment route so selecting a message never remounts the page */}\n <Route path=\"inbox/:selectedId?\" element={<InboxPage />} />\n\n {/* Invoices */}\n <Route path=\"invoices\" element={<InvoicesPage />} />\n\n {/* Insights (spend analytics + renewal forecast) */}\n <Route path=\"insights\" element={<InsightsPage />} />\n\n {/* Digital Asset Vault */}\n <Route path=\"vault\" element={<AssetVaultPage />} />\n\n {/* AI Agents */}\n <Route path=\"agents\" element={<AgentsListPage />} />\n <Route path=\"agents/new\" element={<AgentNewPage />} />\n <Route path=\"agents/:id\" element={<AgentDetailPage />} />\n\n {/* AI decision queue */}\n <Route path=\"decisions\" element={<DecisionsPage />} />\n <Route path=\"decisions/:id\" element={<DecisionDetailPage />} />\n\n {/* Workflows */}\n <Route path=\"workflows/:selectedId?\" element={<WorkflowsPage />} />\n\n {/* Fallback */}\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SubscriberBotRoutes.js","names":[],"sources":["../../src/subscriberbot/SubscriberBotRoutes.tsx"],"sourcesContent":["/**\n * SubscriberBot Routes\n * @module subscriberbot\n *\n * Domain routes for the Subscriber Bot product. The dashboard is eager-loaded\n * (primary landing); all other pages are lazy-loaded.\n */\n\nimport { type FC, lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { useSubscriberBot } from './context/SubscriberBotContext';\n\n// Eager — primary landing\nimport { DashboardPage } from './pages/DashboardPage';\n\n// Relationships\nconst RelationshipsListPage = lazy(() =>\n import('./pages/relationships/RelationshipsListPage').then((m) => ({\n default: m.RelationshipsListPage,\n }))\n);\nconst RelationshipNewPage = lazy(() =>\n import('./pages/relationships/RelationshipNewPage').then((m) => ({\n default: m.RelationshipNewPage,\n }))\n);\nconst RelationshipDetailPage = lazy(() =>\n import('./pages/relationships/RelationshipDetailPage').then((m) => ({\n default: m.RelationshipDetailPage,\n }))\n);\n\n// Providers\nconst ProvidersListPage = lazy(() =>\n import('./pages/providers/ProvidersListPage').then((m) => ({ default: m.ProvidersListPage }))\n);\nconst ProviderNewPage = lazy(() =>\n import('./pages/providers/ProviderNewPage').then((m) => ({ default: m.ProviderNewPage }))\n);\nconst ProviderDetailPage = lazy(() =>\n import('./pages/providers/ProviderDetailPage').then((m) => ({ default: m.ProviderDetailPage }))\n);\n\n// Inbox / Invoices / Workflows\nconst InboxPage = lazy(() => import('./pages/InboxPage').then((m) => ({ default: m.InboxPage })));\nconst InvoicesPage = lazy(() =>\n import('./pages/InvoicesPage').then((m) => ({ default: m.InvoicesPage }))\n);\nconst WorkflowsPage = lazy(() =>\n import('./pages/WorkflowsPage').then((m) => ({ default: m.WorkflowsPage }))\n);\n\n// Agents\nconst AgentsListPage = lazy(() =>\n import('./pages/agents/AgentsListPage').then((m) => ({ default: m.AgentsListPage }))\n);\nconst AgentNewPage = lazy(() =>\n import('./pages/agents/AgentNewPage').then((m) => ({ default: m.AgentNewPage }))\n);\nconst AgentDetailPage = lazy(() =>\n import('./pages/agents/AgentDetailPage').then((m) => ({ default: m.AgentDetailPage }))\n);\n\n// AI decision queue (BOFF-3663)\nconst DecisionsPage = lazy(() =>\n import('./pages/decisions/DecisionsPage').then((m) => ({ default: m.DecisionsPage }))\n);\nconst DecisionDetailPage = lazy(() =>\n import('./pages/decisions/DecisionDetailPage').then((m) => ({ default: m.DecisionDetailPage }))\n);\n\n// Cost optimization dashboard (BOFF-4001)\nconst SavingsPage = lazy(() =>\n import('./pages/SavingsPage').then((m) => ({ default: m.SavingsPage }))\n);\n\n// Insights + Asset Vault (aggregated real data)\nconst InsightsPage = lazy(() =>\n import('./pages/InsightsPage').then((m) => ({ default: m.InsightsPage }))\n);\nconst AssetVaultPage = lazy(() =>\n import('./pages/AssetVaultPage').then((m) => ({ default: m.AssetVaultPage }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n);\n\nexport const SubscriberBotRoutes: FC = () => {\n const { basePath, isStandalone } = useSubscriberBot();\n const location = useLocation();\n\n // Normalize base path\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render if the current path matches basePath (integrated mode).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n {/* Overview / dashboard */}\n <Route index element={<DashboardPage />} />\n\n {/* Relationships (the core domain) */}\n <Route path=\"relationships\" element={<RelationshipsListPage />} />\n <Route path=\"relationships/new\" element={<RelationshipNewPage />} />\n <Route path=\"relationships/:id\" element={<RelationshipDetailPage />} />\n\n {/* Providers (registry) */}\n <Route path=\"providers\" element={<ProvidersListPage />} />\n <Route path=\"providers/new\" element={<ProviderNewPage />} />\n <Route path=\"providers/:id\" element={<ProviderDetailPage />} />\n\n {/* Universal Inbox */}\n {/* One optional-segment route so selecting a message never remounts the page */}\n <Route path=\"inbox/:selectedId?\" element={<InboxPage />} />\n\n {/* Invoices */}\n <Route path=\"invoices\" element={<InvoicesPage />} />\n\n {/* Insights (spend analytics + renewal forecast) */}\n <Route path=\"insights\" element={<InsightsPage />} />\n\n {/* Digital Asset Vault */}\n <Route path=\"vault\" element={<AssetVaultPage />} />\n\n {/* AI Agents */}\n <Route path=\"agents\" element={<AgentsListPage />} />\n <Route path=\"agents/new\" element={<AgentNewPage />} />\n <Route path=\"agents/:id\" element={<AgentDetailPage />} />\n\n {/* AI decision queue */}\n <Route path=\"decisions\" element={<DecisionsPage />} />\n <Route path=\"savings\" element={<SavingsPage />} />\n <Route path=\"decisions/:id\" element={<DecisionDetailPage />} />\n\n {/* Workflows */}\n <Route path=\"workflows/:selectedId?\" element={<WorkflowsPage />} />\n\n {/* Fallback */}\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { SubBundleRecommendation, SubConsolidationResult, SubDecision } from '../../types';
|
|
3
|
+
|
|
4
|
+
export declare const SavingsCard: FC<{
|
|
5
|
+
decision: SubDecision;
|
|
6
|
+
}>;
|
|
7
|
+
export declare const BundleCard: FC<{
|
|
8
|
+
bundle: SubBundleRecommendation;
|
|
9
|
+
onConsolidated?: (result: SubConsolidationResult) => void;
|
|
10
|
+
}>;
|
|
11
|
+
//# sourceMappingURL=SavingsCards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SavingsCards.d.ts","sourceRoot":"","sources":["../../../../src/subscriberbot/components/savings/SavingsCards.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAY,KAAK,EAAE,EAAkB,MAAM,OAAO,CAAC;AA6B1D,OAAO,KAAK,EAEV,uBAAuB,EACvB,sBAAsB,EACtB,WAAW,EACZ,MAAM,aAAa,CAAC;AA8BrB,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC;IAAE,QAAQ,EAAE,WAAW,CAAA;CAAE,CA0HrD,CAAC;AAgRF,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC;IAC1B,MAAM,EAAE,uBAAuB,CAAC;IAChC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;CAC3D,CAkIA,CAAC"}
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import { useSubscriberBot as e } from "../../context/SubscriberBotContext.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { formatCurrency as n, formatEnumLabel as r } from "../../utils/formatters.js";
|
|
4
|
+
import { Modal as i } from "../Modal.js";
|
|
5
|
+
import { useTr as a } from "../../hooks/useTr.js";
|
|
6
|
+
import { useConsolidateRelationships as o, useDismissBundle as s } from "../../hooks/useSubscriberBotApi.js";
|
|
7
|
+
import { RECOMMENDATION_LABEL as c, confidencePercent as l, isDestructive as u, relationshipSnapshot as d } from "../decisions/decisionModel.js";
|
|
8
|
+
import { ApplyDecisionDialog as f, DecisionSources as p, DismissDecisionDialog as m } from "../decisions/DecisionCard.js";
|
|
9
|
+
import "../decisions/index.js";
|
|
10
|
+
import { bundleMembers as h, parseBundledAmount as g, projectedConsolidationSavings as _, sectionOf as v } from "./savingsModel.js";
|
|
11
|
+
import { useState as y } from "react";
|
|
12
|
+
import { ArrowDownRight as b, CalendarRange as x, CheckCircle2 as S, Combine as C, ExternalLink as w, Gauge as T, Layers as E, ShieldAlert as D, Trash2 as O, XCircle as k } from "lucide-react";
|
|
13
|
+
import { jsx as A, jsxs as j } from "react/jsx-runtime";
|
|
14
|
+
import { GlassCard as M } from "@burdenoff/fe-libs/ui";
|
|
15
|
+
//#region src/subscriberbot/components/savings/SavingsCards.tsx
|
|
16
|
+
var N = {
|
|
17
|
+
cancel: O,
|
|
18
|
+
downgrade: b,
|
|
19
|
+
annual: x,
|
|
20
|
+
other: E
|
|
21
|
+
}, P = [
|
|
22
|
+
"MONTHLY",
|
|
23
|
+
"QUARTERLY",
|
|
24
|
+
"YEARLY"
|
|
25
|
+
];
|
|
26
|
+
function F(e, t) {
|
|
27
|
+
return e instanceof Error && e.message ? e.message : t;
|
|
28
|
+
}
|
|
29
|
+
function I(e, t, i) {
|
|
30
|
+
return `${n(e, t)} / ${r(i).toLowerCase()}`;
|
|
31
|
+
}
|
|
32
|
+
var L = ({ decision: r }) => {
|
|
33
|
+
let i = a(), { navigateTo: o } = e(), [s, h] = y(null), g = c[r.recommendation], _ = u(r.recommendation), b = d(r), x = N[v(r.recommendation)], C = b.label ?? r.relationshipId;
|
|
34
|
+
return /* @__PURE__ */ j(M, {
|
|
35
|
+
treatment: "metric",
|
|
36
|
+
className: "flex flex-col gap-3 p-4",
|
|
37
|
+
"data-testid": "savings-card",
|
|
38
|
+
"data-decision-id": r.id,
|
|
39
|
+
"data-recommendation": r.recommendation,
|
|
40
|
+
children: [
|
|
41
|
+
/* @__PURE__ */ j("div", {
|
|
42
|
+
className: "flex items-start gap-3",
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ A("span", {
|
|
45
|
+
className: t("flex size-9 shrink-0 items-center justify-center rounded-lg", _ ? "bg-status-error-bg-subtle text-status-error-text" : "bg-bg-sunken text-text-secondary"),
|
|
46
|
+
"aria-hidden": !0,
|
|
47
|
+
children: /* @__PURE__ */ A(x, { className: "h-4 w-4" })
|
|
48
|
+
}),
|
|
49
|
+
/* @__PURE__ */ j("div", {
|
|
50
|
+
className: "min-w-0 flex-1",
|
|
51
|
+
children: [
|
|
52
|
+
/* @__PURE__ */ A("p", {
|
|
53
|
+
className: "text-sm font-semibold text-text-primary",
|
|
54
|
+
children: i(g.key, g.fallback)
|
|
55
|
+
}),
|
|
56
|
+
/* @__PURE__ */ A("button", {
|
|
57
|
+
type: "button",
|
|
58
|
+
onClick: () => o(`/relationships/${r.relationshipId}`),
|
|
59
|
+
className: "block max-w-full truncate text-left text-sm text-text-primary hover:underline",
|
|
60
|
+
"data-testid": "savings-card-relationship",
|
|
61
|
+
children: C
|
|
62
|
+
}),
|
|
63
|
+
b.amount !== null && /* @__PURE__ */ A("p", {
|
|
64
|
+
className: "text-xs tabular-nums text-text-secondary",
|
|
65
|
+
children: i("savings.card.current", "Now {{price}}", { price: I(b.amount, b.currency ?? r.currency, b.interval ?? "MONTHLY") })
|
|
66
|
+
})
|
|
67
|
+
]
|
|
68
|
+
}),
|
|
69
|
+
r.estimatedSavings > 0 && /* @__PURE__ */ j("p", {
|
|
70
|
+
className: "shrink-0 text-right text-sm font-semibold tabular-nums text-status-success-text",
|
|
71
|
+
"data-testid": "savings-card-amount",
|
|
72
|
+
children: [i("savings.card.save", "Save {{amount}}", { amount: n(r.estimatedSavings, r.currency) }), /* @__PURE__ */ A("span", {
|
|
73
|
+
className: "block text-[11px] font-normal text-text-secondary",
|
|
74
|
+
children: i("savings.card.perYear", "per year")
|
|
75
|
+
})]
|
|
76
|
+
})
|
|
77
|
+
]
|
|
78
|
+
}),
|
|
79
|
+
/* @__PURE__ */ A("p", {
|
|
80
|
+
className: "text-sm text-text-secondary",
|
|
81
|
+
children: r.rationale
|
|
82
|
+
}),
|
|
83
|
+
/* @__PURE__ */ j("div", {
|
|
84
|
+
className: "flex flex-wrap items-center gap-2 text-xs",
|
|
85
|
+
children: [/* @__PURE__ */ j("span", {
|
|
86
|
+
className: "inline-flex items-center gap-1.5 rounded-md bg-bg-sunken px-2 py-0.5 text-text-secondary",
|
|
87
|
+
children: [/* @__PURE__ */ A(T, {
|
|
88
|
+
className: "h-3.5 w-3.5",
|
|
89
|
+
"aria-hidden": !0
|
|
90
|
+
}), i("decisions.card.confidence", "Confidence {{pct}}%", { pct: l(r.confidence) })]
|
|
91
|
+
}), /* @__PURE__ */ j("button", {
|
|
92
|
+
type: "button",
|
|
93
|
+
onClick: () => o(`/decisions/${r.id}`),
|
|
94
|
+
className: "inline-flex items-center gap-1 rounded-md px-1 text-text-secondary hover:text-text-primary",
|
|
95
|
+
children: [i("savings.card.details", "Details"), /* @__PURE__ */ A(w, {
|
|
96
|
+
className: "h-3 w-3",
|
|
97
|
+
"aria-hidden": !0
|
|
98
|
+
})]
|
|
99
|
+
})]
|
|
100
|
+
}),
|
|
101
|
+
/* @__PURE__ */ A(p, {
|
|
102
|
+
decision: r,
|
|
103
|
+
limit: 3
|
|
104
|
+
}),
|
|
105
|
+
/* @__PURE__ */ j("div", {
|
|
106
|
+
className: "flex flex-wrap justify-end gap-2 border-t border-border-subtle pt-3",
|
|
107
|
+
children: [/* @__PURE__ */ j("button", {
|
|
108
|
+
type: "button",
|
|
109
|
+
onClick: () => h("dismiss"),
|
|
110
|
+
"data-testid": "savings-dismiss",
|
|
111
|
+
className: "inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken",
|
|
112
|
+
children: [/* @__PURE__ */ A(k, {
|
|
113
|
+
className: "h-3.5 w-3.5",
|
|
114
|
+
"aria-hidden": !0
|
|
115
|
+
}), i("decisions.card.dismiss", "Dismiss")]
|
|
116
|
+
}), /* @__PURE__ */ j("button", {
|
|
117
|
+
type: "button",
|
|
118
|
+
onClick: () => h("apply"),
|
|
119
|
+
"data-testid": "savings-apply",
|
|
120
|
+
className: t("inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium hover:opacity-90", _ ? "border border-status-error-text/40 bg-status-error-bg-subtle text-status-error-text" : "bg-action-primary-bg text-action-primary-text"),
|
|
121
|
+
children: [/* @__PURE__ */ A(S, {
|
|
122
|
+
className: "h-3.5 w-3.5",
|
|
123
|
+
"aria-hidden": !0
|
|
124
|
+
}), i("savings.card.apply", "Apply")]
|
|
125
|
+
})]
|
|
126
|
+
}),
|
|
127
|
+
s === "apply" && /* @__PURE__ */ A(f, {
|
|
128
|
+
decision: r,
|
|
129
|
+
onClose: () => h(null)
|
|
130
|
+
}),
|
|
131
|
+
s === "dismiss" && /* @__PURE__ */ A(m, {
|
|
132
|
+
decision: r,
|
|
133
|
+
onClose: () => h(null)
|
|
134
|
+
})
|
|
135
|
+
]
|
|
136
|
+
});
|
|
137
|
+
}, R = ({ bundle: e, onClose: s, onDone: c }) => {
|
|
138
|
+
let l = a(), u = o(), [d, f] = y(e.keepRelationshipId), [p, m] = y(""), [v, b] = y("MONTHLY"), [x, S] = y(!1), [C, w] = y(null), { keep: T, cancel: E } = h(e, d), D = g(p), O = Number.isNaN(D), k = D !== null && !O ? {
|
|
139
|
+
amount: D,
|
|
140
|
+
interval: v
|
|
141
|
+
} : null, M = _(e, d, k), N = E.map((e) => `"${e.label}"`).join(", "), L = x && !O && !u.isPending && T !== null;
|
|
142
|
+
return /* @__PURE__ */ A(i, {
|
|
143
|
+
title: l("savings.bundle.consolidateTitle", "Consolidate subscriptions"),
|
|
144
|
+
description: l("savings.bundle.consolidateDescription", "Keep one subscription and cancel the others. Each cancel is the same change you could make by hand."),
|
|
145
|
+
onClose: s,
|
|
146
|
+
children: /* @__PURE__ */ j("form", {
|
|
147
|
+
onSubmit: (e) => {
|
|
148
|
+
e.preventDefault(), L && (w(null), u.mutate({
|
|
149
|
+
keepRelationshipId: d,
|
|
150
|
+
cancelRelationshipIds: E.map((e) => e.id),
|
|
151
|
+
...k ? {
|
|
152
|
+
amount: k.amount,
|
|
153
|
+
interval: k.interval
|
|
154
|
+
} : {}
|
|
155
|
+
}, {
|
|
156
|
+
onSuccess: (e) => {
|
|
157
|
+
c(e), s();
|
|
158
|
+
},
|
|
159
|
+
onError: (e) => w(F(e, l("savings.bundle.consolidateError", "Could not consolidate.")))
|
|
160
|
+
}));
|
|
161
|
+
},
|
|
162
|
+
className: "space-y-4",
|
|
163
|
+
"data-testid": "consolidate-dialog",
|
|
164
|
+
children: [
|
|
165
|
+
/* @__PURE__ */ j("fieldset", { children: [/* @__PURE__ */ A("legend", {
|
|
166
|
+
className: "mb-1.5 text-xs font-medium text-text-secondary",
|
|
167
|
+
children: l("savings.bundle.keepLabel", "Keep")
|
|
168
|
+
}), /* @__PURE__ */ A("div", {
|
|
169
|
+
className: "space-y-1.5",
|
|
170
|
+
children: e.relationships.map((n) => /* @__PURE__ */ j("label", {
|
|
171
|
+
className: t("flex items-center gap-2.5 rounded-lg border px-3 py-2 text-sm", n.id === d ? "border-action-primary-bg bg-bg-sunken" : "border-border-subtle"),
|
|
172
|
+
children: [
|
|
173
|
+
/* @__PURE__ */ A("input", {
|
|
174
|
+
type: "radio",
|
|
175
|
+
name: `keep-${e.id}`,
|
|
176
|
+
value: n.id,
|
|
177
|
+
checked: n.id === d,
|
|
178
|
+
onChange: () => {
|
|
179
|
+
f(n.id), S(!1);
|
|
180
|
+
},
|
|
181
|
+
className: "size-4 shrink-0"
|
|
182
|
+
}),
|
|
183
|
+
/* @__PURE__ */ A("span", {
|
|
184
|
+
className: "min-w-0 flex-1 truncate text-text-primary",
|
|
185
|
+
children: n.label
|
|
186
|
+
}),
|
|
187
|
+
/* @__PURE__ */ A("span", {
|
|
188
|
+
className: "shrink-0 tabular-nums text-text-secondary",
|
|
189
|
+
children: I(n.amount, n.currency, n.interval)
|
|
190
|
+
})
|
|
191
|
+
]
|
|
192
|
+
}, n.id))
|
|
193
|
+
})] }),
|
|
194
|
+
/* @__PURE__ */ j("div", { children: [
|
|
195
|
+
/* @__PURE__ */ A("p", {
|
|
196
|
+
className: "mb-1.5 text-xs font-medium text-text-secondary",
|
|
197
|
+
children: l("savings.bundle.planLabel", "Bundled plan price for the kept subscription (optional)")
|
|
198
|
+
}),
|
|
199
|
+
/* @__PURE__ */ j("div", {
|
|
200
|
+
className: "flex gap-2",
|
|
201
|
+
children: [/* @__PURE__ */ A("input", {
|
|
202
|
+
type: "number",
|
|
203
|
+
inputMode: "decimal",
|
|
204
|
+
min: "0",
|
|
205
|
+
step: "0.01",
|
|
206
|
+
value: p,
|
|
207
|
+
onChange: (e) => m(e.target.value),
|
|
208
|
+
placeholder: T ? String(T.amount) : l("savings.bundle.planPlaceholder", "Same price"),
|
|
209
|
+
"aria-label": l("savings.bundle.planAmount", "Bundled plan amount"),
|
|
210
|
+
"aria-invalid": O,
|
|
211
|
+
"data-testid": "consolidate-amount",
|
|
212
|
+
className: "w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
|
|
213
|
+
}), /* @__PURE__ */ A("select", {
|
|
214
|
+
value: v,
|
|
215
|
+
onChange: (e) => b(e.target.value),
|
|
216
|
+
"aria-label": l("savings.bundle.planInterval", "Billing interval"),
|
|
217
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface px-2 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
218
|
+
children: P.map((e) => /* @__PURE__ */ A("option", {
|
|
219
|
+
value: e,
|
|
220
|
+
children: r(e)
|
|
221
|
+
}, e))
|
|
222
|
+
})]
|
|
223
|
+
}),
|
|
224
|
+
O && /* @__PURE__ */ A("p", {
|
|
225
|
+
className: "mt-1 text-xs text-status-error-text",
|
|
226
|
+
children: l("savings.bundle.planInvalid", "Enter zero or a positive amount.")
|
|
227
|
+
})
|
|
228
|
+
] }),
|
|
229
|
+
/* @__PURE__ */ A("p", {
|
|
230
|
+
className: "text-sm text-text-primary",
|
|
231
|
+
"data-testid": "consolidate-projected",
|
|
232
|
+
children: l("savings.bundle.projected", "Estimated saving: {{amount}} a year", { amount: n(Math.max(M, 0), e.currency) })
|
|
233
|
+
}),
|
|
234
|
+
/* @__PURE__ */ j("label", {
|
|
235
|
+
className: "flex items-start gap-2.5 rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-3 text-sm text-status-error-text",
|
|
236
|
+
children: [/* @__PURE__ */ A("input", {
|
|
237
|
+
type: "checkbox",
|
|
238
|
+
checked: x,
|
|
239
|
+
onChange: (e) => S(e.target.checked),
|
|
240
|
+
className: "mt-0.5 size-4 shrink-0 rounded border-border-subtle",
|
|
241
|
+
"data-testid": "consolidate-acknowledge"
|
|
242
|
+
}), /* @__PURE__ */ A("span", { children: l("savings.bundle.acknowledge", "I want to cancel {{names}} and keep \"{{keep}}\". I understand the cancelled subscriptions will stop.", {
|
|
243
|
+
names: N,
|
|
244
|
+
keep: T?.label ?? ""
|
|
245
|
+
}) })]
|
|
246
|
+
}),
|
|
247
|
+
C && /* @__PURE__ */ A("div", {
|
|
248
|
+
className: "rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text",
|
|
249
|
+
children: C
|
|
250
|
+
}),
|
|
251
|
+
/* @__PURE__ */ j("div", {
|
|
252
|
+
className: "flex justify-end gap-3",
|
|
253
|
+
children: [/* @__PURE__ */ A("button", {
|
|
254
|
+
type: "button",
|
|
255
|
+
onClick: s,
|
|
256
|
+
className: "rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken",
|
|
257
|
+
children: l("decisions.apply.back", "Not now")
|
|
258
|
+
}), /* @__PURE__ */ A("button", {
|
|
259
|
+
type: "submit",
|
|
260
|
+
disabled: !L,
|
|
261
|
+
"data-testid": "confirm-consolidate",
|
|
262
|
+
className: "rounded-lg bg-action-danger-bg px-4 py-2 text-sm font-medium text-action-danger-text hover:bg-action-danger-bg-hover disabled:cursor-not-allowed disabled:opacity-50",
|
|
263
|
+
children: u.isPending ? l("savings.bundle.consolidating", "Consolidating…") : l("savings.bundle.consolidateSubmit", "Consolidate")
|
|
264
|
+
})]
|
|
265
|
+
})
|
|
266
|
+
]
|
|
267
|
+
})
|
|
268
|
+
});
|
|
269
|
+
}, z = ({ bundle: e, onClose: t }) => {
|
|
270
|
+
let n = a(), r = s(), [o, c] = y(""), [l, u] = y(null);
|
|
271
|
+
return /* @__PURE__ */ A(i, {
|
|
272
|
+
title: n("savings.bundle.dismissTitle", "Dismiss bundle suggestion"),
|
|
273
|
+
description: n("savings.bundle.dismissDescription", "It stays hidden until one of these subscriptions changes."),
|
|
274
|
+
onClose: t,
|
|
275
|
+
children: /* @__PURE__ */ j("form", {
|
|
276
|
+
onSubmit: (i) => {
|
|
277
|
+
i.preventDefault(), u(null), r.mutate({
|
|
278
|
+
id: e.id,
|
|
279
|
+
reason: o
|
|
280
|
+
}, {
|
|
281
|
+
onSuccess: () => t(),
|
|
282
|
+
onError: (e) => u(F(e, n("savings.bundle.dismissError", "Could not dismiss.")))
|
|
283
|
+
});
|
|
284
|
+
},
|
|
285
|
+
className: "space-y-4",
|
|
286
|
+
"data-testid": "dismiss-bundle-dialog",
|
|
287
|
+
children: [
|
|
288
|
+
/* @__PURE__ */ j("div", { children: [/* @__PURE__ */ A("label", {
|
|
289
|
+
htmlFor: `dismiss-bundle-${e.id}`,
|
|
290
|
+
className: "mb-1 block text-xs font-medium text-text-secondary",
|
|
291
|
+
children: n("decisions.dismiss.reason", "Reason (optional)")
|
|
292
|
+
}), /* @__PURE__ */ A("textarea", {
|
|
293
|
+
id: `dismiss-bundle-${e.id}`,
|
|
294
|
+
value: o,
|
|
295
|
+
onChange: (e) => c(e.target.value),
|
|
296
|
+
rows: 3,
|
|
297
|
+
maxLength: 500,
|
|
298
|
+
placeholder: n("savings.bundle.dismissPlaceholder", "e.g. They cover different needs"),
|
|
299
|
+
className: "w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
|
|
300
|
+
})] }),
|
|
301
|
+
l && /* @__PURE__ */ A("div", {
|
|
302
|
+
className: "rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text",
|
|
303
|
+
children: l
|
|
304
|
+
}),
|
|
305
|
+
/* @__PURE__ */ j("div", {
|
|
306
|
+
className: "flex justify-end gap-3",
|
|
307
|
+
children: [/* @__PURE__ */ A("button", {
|
|
308
|
+
type: "button",
|
|
309
|
+
onClick: t,
|
|
310
|
+
className: "rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken",
|
|
311
|
+
children: n("decisions.dismiss.back", "Back")
|
|
312
|
+
}), /* @__PURE__ */ A("button", {
|
|
313
|
+
type: "submit",
|
|
314
|
+
disabled: r.isPending,
|
|
315
|
+
"data-testid": "confirm-dismiss-bundle",
|
|
316
|
+
className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50",
|
|
317
|
+
children: r.isPending ? n("decisions.dismiss.dismissing", "Dismissing…") : n("decisions.dismiss.submit", "Dismiss")
|
|
318
|
+
})]
|
|
319
|
+
})
|
|
320
|
+
]
|
|
321
|
+
})
|
|
322
|
+
});
|
|
323
|
+
}, B = ({ bundle: i, onConsolidated: o }) => {
|
|
324
|
+
let s = a(), { navigateTo: c } = e(), [u, d] = y(null), f = i.basis === "SAME_PROVIDER" ? s("savings.bundle.sameProvider", "Merge duplicate subscriptions") : s("savings.bundle.sameCategory", "Overlapping {{category}} services", { category: r(i.category ?? "OTHER").toLowerCase() });
|
|
325
|
+
return /* @__PURE__ */ j(M, {
|
|
326
|
+
treatment: "metric",
|
|
327
|
+
className: "flex flex-col gap-3 border border-status-warning-text/30 p-4",
|
|
328
|
+
"data-testid": "bundle-card",
|
|
329
|
+
"data-bundle-id": i.id,
|
|
330
|
+
"data-basis": i.basis,
|
|
331
|
+
children: [
|
|
332
|
+
/* @__PURE__ */ j("div", {
|
|
333
|
+
className: "flex items-start gap-3",
|
|
334
|
+
children: [
|
|
335
|
+
/* @__PURE__ */ A("span", {
|
|
336
|
+
className: "flex size-9 shrink-0 items-center justify-center rounded-lg bg-status-warning-bg-subtle text-status-warning-text",
|
|
337
|
+
"aria-hidden": !0,
|
|
338
|
+
children: /* @__PURE__ */ A(C, { className: "h-4 w-4" })
|
|
339
|
+
}),
|
|
340
|
+
/* @__PURE__ */ j("div", {
|
|
341
|
+
className: "min-w-0 flex-1",
|
|
342
|
+
children: [/* @__PURE__ */ j("p", {
|
|
343
|
+
className: "text-[11px] font-medium uppercase tracking-wide text-text-secondary",
|
|
344
|
+
children: [s("savings.bundle.label", "Bundle suggestion"), i.providerName ? ` · ${i.providerName}` : ""]
|
|
345
|
+
}), /* @__PURE__ */ A("p", {
|
|
346
|
+
className: "text-sm font-semibold text-text-primary",
|
|
347
|
+
children: f
|
|
348
|
+
})]
|
|
349
|
+
}),
|
|
350
|
+
/* @__PURE__ */ j("p", {
|
|
351
|
+
className: "shrink-0 text-right text-sm font-semibold tabular-nums text-status-success-text",
|
|
352
|
+
"data-testid": "bundle-card-amount",
|
|
353
|
+
children: [s("savings.card.save", "Save {{amount}}", { amount: n(i.estimatedSavings, i.currency) }), /* @__PURE__ */ A("span", {
|
|
354
|
+
className: "block text-[11px] font-normal text-text-secondary",
|
|
355
|
+
children: s("savings.card.perYear", "per year")
|
|
356
|
+
})]
|
|
357
|
+
})
|
|
358
|
+
]
|
|
359
|
+
}),
|
|
360
|
+
/* @__PURE__ */ A("p", {
|
|
361
|
+
className: "text-sm text-text-secondary",
|
|
362
|
+
children: i.rationale
|
|
363
|
+
}),
|
|
364
|
+
/* @__PURE__ */ A("ul", {
|
|
365
|
+
className: "space-y-1",
|
|
366
|
+
"data-testid": "bundle-relationships",
|
|
367
|
+
children: i.relationships.map((e) => {
|
|
368
|
+
let n = e.id === i.keepRelationshipId;
|
|
369
|
+
return /* @__PURE__ */ A("li", { children: /* @__PURE__ */ j("button", {
|
|
370
|
+
type: "button",
|
|
371
|
+
onClick: () => c(`/relationships/${e.id}`),
|
|
372
|
+
"data-relationship-id": e.id,
|
|
373
|
+
className: "flex w-full items-center gap-2 rounded-md border border-border-subtle bg-bg-sunken px-2.5 py-1.5 text-left text-xs hover:bg-bg-surface",
|
|
374
|
+
children: [
|
|
375
|
+
/* @__PURE__ */ A("span", {
|
|
376
|
+
className: t("shrink-0 rounded px-1.5 py-0.5 text-[10px] font-medium uppercase", n ? "bg-status-success-bg-subtle text-status-success-text" : "bg-status-error-bg-subtle text-status-error-text"),
|
|
377
|
+
children: n ? s("savings.bundle.keep", "Keep") : s("savings.bundle.cancel", "Cancel")
|
|
378
|
+
}),
|
|
379
|
+
/* @__PURE__ */ A("span", {
|
|
380
|
+
className: "min-w-0 flex-1 truncate font-medium text-text-primary",
|
|
381
|
+
children: e.label
|
|
382
|
+
}),
|
|
383
|
+
/* @__PURE__ */ A("span", {
|
|
384
|
+
className: "shrink-0 tabular-nums text-text-secondary",
|
|
385
|
+
children: I(e.amount, e.currency, e.interval)
|
|
386
|
+
}),
|
|
387
|
+
/* @__PURE__ */ A(w, {
|
|
388
|
+
className: "h-3 w-3 shrink-0 text-text-secondary",
|
|
389
|
+
"aria-hidden": !0
|
|
390
|
+
})
|
|
391
|
+
]
|
|
392
|
+
}) }, e.id);
|
|
393
|
+
})
|
|
394
|
+
}),
|
|
395
|
+
/* @__PURE__ */ j("div", {
|
|
396
|
+
className: "flex flex-wrap gap-2 text-xs",
|
|
397
|
+
children: [/* @__PURE__ */ A("span", {
|
|
398
|
+
className: "inline-flex items-center rounded-md bg-bg-sunken px-2 py-0.5 tabular-nums text-text-secondary",
|
|
399
|
+
children: s("savings.bundle.costChange", "{{before}} → {{after}} a year", {
|
|
400
|
+
before: n(i.currentAnnualCost, i.currency),
|
|
401
|
+
after: n(i.projectedAnnualCost, i.currency)
|
|
402
|
+
})
|
|
403
|
+
}), /* @__PURE__ */ j("span", {
|
|
404
|
+
className: "inline-flex items-center gap-1.5 rounded-md bg-bg-sunken px-2 py-0.5 text-text-secondary",
|
|
405
|
+
children: [/* @__PURE__ */ A(T, {
|
|
406
|
+
className: "h-3.5 w-3.5",
|
|
407
|
+
"aria-hidden": !0
|
|
408
|
+
}), s("decisions.card.confidence", "Confidence {{pct}}%", { pct: l(i.confidence) })]
|
|
409
|
+
})]
|
|
410
|
+
}),
|
|
411
|
+
/* @__PURE__ */ j("div", {
|
|
412
|
+
className: "flex flex-wrap justify-end gap-2 border-t border-border-subtle pt-3",
|
|
413
|
+
children: [/* @__PURE__ */ j("button", {
|
|
414
|
+
type: "button",
|
|
415
|
+
onClick: () => d("dismiss"),
|
|
416
|
+
"data-testid": "bundle-dismiss",
|
|
417
|
+
className: "inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken",
|
|
418
|
+
children: [/* @__PURE__ */ A(k, {
|
|
419
|
+
className: "h-3.5 w-3.5",
|
|
420
|
+
"aria-hidden": !0
|
|
421
|
+
}), s("decisions.card.dismiss", "Dismiss")]
|
|
422
|
+
}), /* @__PURE__ */ j("button", {
|
|
423
|
+
type: "button",
|
|
424
|
+
onClick: () => d("consolidate"),
|
|
425
|
+
"data-testid": "bundle-consolidate",
|
|
426
|
+
className: "inline-flex items-center gap-1.5 rounded-lg border border-status-error-text/40 bg-status-error-bg-subtle px-3 py-1.5 text-xs font-medium text-status-error-text hover:opacity-90",
|
|
427
|
+
children: [/* @__PURE__ */ A(D, {
|
|
428
|
+
className: "h-3.5 w-3.5",
|
|
429
|
+
"aria-hidden": !0
|
|
430
|
+
}), s("savings.bundle.consolidate", "Review and consolidate")]
|
|
431
|
+
})]
|
|
432
|
+
}),
|
|
433
|
+
u === "consolidate" && /* @__PURE__ */ A(R, {
|
|
434
|
+
bundle: i,
|
|
435
|
+
onClose: () => d(null),
|
|
436
|
+
onDone: (e) => o?.(e)
|
|
437
|
+
}),
|
|
438
|
+
u === "dismiss" && /* @__PURE__ */ A(z, {
|
|
439
|
+
bundle: i,
|
|
440
|
+
onClose: () => d(null)
|
|
441
|
+
})
|
|
442
|
+
]
|
|
443
|
+
});
|
|
444
|
+
};
|
|
445
|
+
//#endregion
|
|
446
|
+
export { B as BundleCard, L as SavingsCard };
|
|
447
|
+
|
|
448
|
+
//# sourceMappingURL=SavingsCards.js.map
|