@burdenoff/microfe-subscriberbot 2026.626.2 → 2026.628.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/dist/index.js +4 -4
  2. package/dist/subscriberbot/SubscriberBotRoot.js +61 -133
  3. package/dist/subscriberbot/SubscriberBotRoot.js.map +1 -1
  4. package/dist/subscriberbot/SubscriberBotRoutes.js +56 -187
  5. package/dist/subscriberbot/SubscriberBotRoutes.js.map +1 -1
  6. package/dist/subscriberbot/components/StateViews.js +49 -0
  7. package/dist/subscriberbot/components/StateViews.js.map +1 -0
  8. package/dist/subscriberbot/components/StatusBadge.js +45 -47
  9. package/dist/subscriberbot/components/StatusBadge.js.map +1 -1
  10. package/dist/subscriberbot/context/SubscriberBotContext.js +22 -30
  11. package/dist/subscriberbot/context/SubscriberBotContext.js.map +1 -1
  12. package/dist/subscriberbot/hooks/useSubscriberBotApi.js +342 -195
  13. package/dist/subscriberbot/hooks/useSubscriberBotApi.js.map +1 -1
  14. package/dist/subscriberbot/index.js +1 -1
  15. package/dist/subscriberbot/pages/DashboardPage.js +269 -0
  16. package/dist/subscriberbot/pages/DashboardPage.js.map +1 -0
  17. package/dist/subscriberbot/pages/InboxPage.js +186 -0
  18. package/dist/subscriberbot/pages/InboxPage.js.map +1 -0
  19. package/dist/subscriberbot/pages/InvoicesPage.js +211 -0
  20. package/dist/subscriberbot/pages/InvoicesPage.js.map +1 -0
  21. package/dist/subscriberbot/pages/WorkflowsPage.js +236 -0
  22. package/dist/subscriberbot/pages/WorkflowsPage.js.map +1 -0
  23. package/dist/subscriberbot/pages/agents/AgentDetailPage.js +160 -0
  24. package/dist/subscriberbot/pages/agents/AgentDetailPage.js.map +1 -0
  25. package/dist/subscriberbot/pages/agents/AgentNewPage.js +140 -0
  26. package/dist/subscriberbot/pages/agents/AgentNewPage.js.map +1 -0
  27. package/dist/subscriberbot/pages/agents/AgentsListPage.js +178 -0
  28. package/dist/subscriberbot/pages/agents/AgentsListPage.js.map +1 -0
  29. package/dist/subscriberbot/pages/providers/ProviderDetailPage.js +179 -0
  30. package/dist/subscriberbot/pages/providers/ProviderDetailPage.js.map +1 -0
  31. package/dist/subscriberbot/pages/providers/ProviderNewPage.js +185 -0
  32. package/dist/subscriberbot/pages/providers/ProviderNewPage.js.map +1 -0
  33. package/dist/subscriberbot/pages/providers/ProvidersListPage.js +254 -0
  34. package/dist/subscriberbot/pages/providers/ProvidersListPage.js.map +1 -0
  35. package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js +306 -0
  36. package/dist/subscriberbot/pages/relationships/RelationshipDetailPage.js.map +1 -0
  37. package/dist/subscriberbot/pages/relationships/RelationshipNewPage.js +253 -0
  38. package/dist/subscriberbot/pages/relationships/RelationshipNewPage.js.map +1 -0
  39. package/dist/subscriberbot/pages/relationships/RelationshipsListPage.js +248 -0
  40. package/dist/subscriberbot/pages/relationships/RelationshipsListPage.js.map +1 -0
  41. package/dist/subscriberbot/utils/formatters.js +50 -32
  42. package/dist/subscriberbot/utils/formatters.js.map +1 -1
  43. package/package.json +1 -1
  44. package/dist/subscriberbot/components/InvestmentCard.js +0 -97
  45. package/dist/subscriberbot/components/InvestmentCard.js.map +0 -1
  46. package/dist/subscriberbot/components/RatingDisplay.js +0 -35
  47. package/dist/subscriberbot/components/RatingDisplay.js.map +0 -1
  48. package/dist/subscriberbot/mocks/data.js +0 -1517
  49. package/dist/subscriberbot/mocks/data.js.map +0 -1
  50. package/dist/subscriberbot/mocks/users.js +0 -34
  51. package/dist/subscriberbot/mocks/users.js.map +0 -1
  52. package/dist/subscriberbot/pages/HomePage.js +0 -930
  53. package/dist/subscriberbot/pages/HomePage.js.map +0 -1
  54. package/dist/subscriberbot/pages/admin/AdminAnalyticsPage.js +0 -651
  55. package/dist/subscriberbot/pages/admin/AdminAnalyticsPage.js.map +0 -1
  56. package/dist/subscriberbot/pages/admin/AdminDashboard.js +0 -679
  57. package/dist/subscriberbot/pages/admin/AdminDashboard.js.map +0 -1
  58. package/dist/subscriberbot/pages/admin/AdminFinancialPage.js +0 -427
  59. package/dist/subscriberbot/pages/admin/AdminFinancialPage.js.map +0 -1
  60. package/dist/subscriberbot/pages/admin/AdminOrdersPage.js +0 -665
  61. package/dist/subscriberbot/pages/admin/AdminOrdersPage.js.map +0 -1
  62. package/dist/subscriberbot/pages/admin/AdminProfilePage.js +0 -261
  63. package/dist/subscriberbot/pages/admin/AdminProfilePage.js.map +0 -1
  64. package/dist/subscriberbot/pages/admin/AdminSettingsPage.js +0 -654
  65. package/dist/subscriberbot/pages/admin/AdminSettingsPage.js.map +0 -1
  66. package/dist/subscriberbot/pages/admin/CartManagementPage.js +0 -874
  67. package/dist/subscriberbot/pages/admin/CartManagementPage.js.map +0 -1
  68. package/dist/subscriberbot/pages/admin/UserManagementPage.js +0 -898
  69. package/dist/subscriberbot/pages/admin/UserManagementPage.js.map +0 -1
  70. package/dist/subscriberbot/pages/cart-owner/AnalyticsDashboard.js +0 -839
  71. package/dist/subscriberbot/pages/cart-owner/AnalyticsDashboard.js.map +0 -1
  72. package/dist/subscriberbot/pages/cart-owner/BusinessSettingsPage.js +0 -705
  73. package/dist/subscriberbot/pages/cart-owner/BusinessSettingsPage.js.map +0 -1
  74. package/dist/subscriberbot/pages/cart-owner/CartOwnerDashboard.js +0 -802
  75. package/dist/subscriberbot/pages/cart-owner/CartOwnerDashboard.js.map +0 -1
  76. package/dist/subscriberbot/pages/cart-owner/CartOwnerProfilePage.js +0 -891
  77. package/dist/subscriberbot/pages/cart-owner/CartOwnerProfilePage.js.map +0 -1
  78. package/dist/subscriberbot/pages/cart-owner/CartServiceConfigPage.js +0 -633
  79. package/dist/subscriberbot/pages/cart-owner/CartServiceConfigPage.js.map +0 -1
  80. package/dist/subscriberbot/pages/cart-owner/InventoryManagementPage.js +0 -910
  81. package/dist/subscriberbot/pages/cart-owner/InventoryManagementPage.js.map +0 -1
  82. package/dist/subscriberbot/pages/cart-owner/OrderManagementPage.js +0 -879
  83. package/dist/subscriberbot/pages/cart-owner/OrderManagementPage.js.map +0 -1
  84. package/dist/subscriberbot/pages/cart-owner/StaffManagementPage.js +0 -838
  85. package/dist/subscriberbot/pages/cart-owner/StaffManagementPage.js.map +0 -1
  86. package/dist/subscriberbot/pages/common/HelpSupportPage.js +0 -1323
  87. package/dist/subscriberbot/pages/common/HelpSupportPage.js.map +0 -1
  88. package/dist/subscriberbot/pages/common/NotificationCenterPage.js +0 -672
  89. package/dist/subscriberbot/pages/common/NotificationCenterPage.js.map +0 -1
  90. package/dist/subscriberbot/pages/common/SettingsPage.js +0 -973
  91. package/dist/subscriberbot/pages/common/SettingsPage.js.map +0 -1
  92. package/dist/subscriberbot/pages/customer/BookingDetailsPage.js +0 -626
  93. package/dist/subscriberbot/pages/customer/BookingDetailsPage.js.map +0 -1
  94. package/dist/subscriberbot/pages/customer/CartDetailPage.js +0 -654
  95. package/dist/subscriberbot/pages/customer/CartDetailPage.js.map +0 -1
  96. package/dist/subscriberbot/pages/customer/CartDiscoveryPage.js +0 -555
  97. package/dist/subscriberbot/pages/customer/CartDiscoveryPage.js.map +0 -1
  98. package/dist/subscriberbot/pages/customer/CheckoutPage.js +0 -529
  99. package/dist/subscriberbot/pages/customer/CheckoutPage.js.map +0 -1
  100. package/dist/subscriberbot/pages/customer/CustomerDashboard.js +0 -532
  101. package/dist/subscriberbot/pages/customer/CustomerDashboard.js.map +0 -1
  102. package/dist/subscriberbot/pages/customer/CustomerProfilePage.js +0 -630
  103. package/dist/subscriberbot/pages/customer/CustomerProfilePage.js.map +0 -1
  104. package/dist/subscriberbot/pages/customer/DirectServiceBookingPage.js +0 -633
  105. package/dist/subscriberbot/pages/customer/DirectServiceBookingPage.js.map +0 -1
  106. package/dist/subscriberbot/pages/customer/FavoritesPage.js +0 -484
  107. package/dist/subscriberbot/pages/customer/FavoritesPage.js.map +0 -1
  108. package/dist/subscriberbot/pages/customer/OrderHistoryPage.js +0 -446
  109. package/dist/subscriberbot/pages/customer/OrderHistoryPage.js.map +0 -1
  110. package/dist/subscriberbot/pages/customer/OrderTrackingPage.js +0 -394
  111. package/dist/subscriberbot/pages/customer/OrderTrackingPage.js.map +0 -1
  112. package/dist/subscriberbot/pages/customer/PaymentMethodsPage.js +0 -495
  113. package/dist/subscriberbot/pages/customer/PaymentMethodsPage.js.map +0 -1
  114. package/dist/subscriberbot/pages/customer/ReviewsPage.js +0 -613
  115. package/dist/subscriberbot/pages/customer/ReviewsPage.js.map +0 -1
  116. package/dist/subscriberbot/pages/customer/SavedAddressesPage.js +0 -450
  117. package/dist/subscriberbot/pages/customer/SavedAddressesPage.js.map +0 -1
  118. package/dist/subscriberbot/pages/investor/InvestmentDetailsPage.js +0 -886
  119. package/dist/subscriberbot/pages/investor/InvestmentDetailsPage.js.map +0 -1
  120. package/dist/subscriberbot/pages/investor/InvestmentOpportunitiesPage.js +0 -947
  121. package/dist/subscriberbot/pages/investor/InvestmentOpportunitiesPage.js.map +0 -1
  122. package/dist/subscriberbot/pages/investor/InvestorAnalyticsPage.js +0 -687
  123. package/dist/subscriberbot/pages/investor/InvestorAnalyticsPage.js.map +0 -1
  124. package/dist/subscriberbot/pages/investor/InvestorDashboard.js +0 -451
  125. package/dist/subscriberbot/pages/investor/InvestorDashboard.js.map +0 -1
  126. package/dist/subscriberbot/pages/investor/InvestorInvestmentsPage.js +0 -406
  127. package/dist/subscriberbot/pages/investor/InvestorInvestmentsPage.js.map +0 -1
  128. package/dist/subscriberbot/pages/investor/InvestorPortfolioPage.js +0 -575
  129. package/dist/subscriberbot/pages/investor/InvestorPortfolioPage.js.map +0 -1
  130. package/dist/subscriberbot/pages/investor/InvestorProfilePage.js +0 -1070
  131. package/dist/subscriberbot/pages/investor/InvestorProfilePage.js.map +0 -1
  132. package/dist/subscriberbot/pages/worker/TaskDetailsPage.js +0 -535
  133. package/dist/subscriberbot/pages/worker/TaskDetailsPage.js.map +0 -1
  134. package/dist/subscriberbot/pages/worker/WorkerDashboard.js +0 -666
  135. package/dist/subscriberbot/pages/worker/WorkerDashboard.js.map +0 -1
  136. package/dist/subscriberbot/pages/worker/WorkerOrdersPage.js +0 -681
  137. package/dist/subscriberbot/pages/worker/WorkerOrdersPage.js.map +0 -1
  138. package/dist/subscriberbot/pages/worker/WorkerProfilePage.js +0 -692
  139. package/dist/subscriberbot/pages/worker/WorkerProfilePage.js.map +0 -1
  140. package/dist/subscriberbot/pages/worker/WorkerSchedulePage.js +0 -588
  141. package/dist/subscriberbot/pages/worker/WorkerSchedulePage.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RelationshipNewPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/relationships/RelationshipNewPage.tsx"],"sourcesContent":["/**\n * Create relationship\n * @module subscriberbot/pages/relationships\n *\n * Manual-add form for a recurring relationship AI can't auto-detect.\n * Writes `subCreateRelationship`. Provider + plan are optional pickers\n * sourced from the real registry (`subListProviders` / `subListPlans`).\n */\n\nimport { useState } from 'react';\nimport { ArrowLeft } from 'lucide-react';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { ErrorView } from '../../components/StateViews';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport {\n useCreateRelationship,\n useProviders,\n usePlans,\n} from '../../hooks/useSubscriberBotApi';\nimport type {\n SubBillingInterval,\n SubCreateRelationshipInput,\n SubRelationshipKind,\n} from '../../types';\n\nconst KINDS: SubRelationshipKind[] = [\n 'SUBSCRIPTION',\n 'MEMBERSHIP',\n 'LICENSE',\n 'CONTRACT',\n 'NEWSLETTER',\n 'RECURRING_PAYMENT',\n 'ENROLLMENT',\n 'TRIAL',\n];\n\nconst INTERVALS: SubBillingInterval[] = [\n 'MONTHLY',\n 'YEARLY',\n 'QUARTERLY',\n 'WEEKLY',\n 'DAILY',\n 'USAGE_BASED',\n 'LIFETIME',\n 'FREE',\n];\n\nconst inputCls =\n 'w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\nconst labelCls = 'mb-1 block text-xs font-medium text-text-secondary';\n\nexport function RelationshipNewPage() {\n const { navigateTo } = useSubscriberBot();\n const create = useCreateRelationship();\n const providersQ = useProviders({ pagination: { limit: 100, offset: 0 } });\n\n const [label, setLabel] = useState('');\n const [kind, setKind] = useState<SubRelationshipKind>('SUBSCRIPTION');\n const [providerId, setProviderId] = useState('');\n const [planId, setPlanId] = useState('');\n const [amount, setAmount] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [interval, setInterval] = useState<SubBillingInterval>('MONTHLY');\n const [renewsAt, setRenewsAt] = useState('');\n const [autoRenew, setAutoRenew] = useState(true);\n const [notes, setNotes] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const plansQ = usePlans(providerId || undefined);\n const providers = providersQ.data?.items ?? [];\n const plans = plansQ.data ?? [];\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setFormError(null);\n if (!label.trim()) {\n setFormError('A label is required.');\n return;\n }\n const input: SubCreateRelationshipInput = {\n label: label.trim(),\n kind,\n interval,\n autoRenew,\n currency: currency.trim() || 'USD',\n ...(providerId ? { providerId } : {}),\n ...(planId ? { planId } : {}),\n ...(amount ? { amount: Number(amount) } : {}),\n ...(renewsAt ? { renewsAt: new Date(renewsAt).toISOString() } : {}),\n ...(notes.trim() ? { notes: notes.trim() } : {}),\n };\n create.mutate(input, {\n onSuccess: (rel) => navigateTo(`/relationships/${rel.id}`),\n onError: (err) =>\n setFormError(err instanceof Error ? err.message : 'Could not create relationship.'),\n });\n };\n\n if (providersQ.isError) {\n return (\n <PageLayout title=\"Add relationship\">\n <ErrorView\n message=\"Could not load the provider registry.\"\n onRetry={() => void providersQ.refetch()}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Add relationship\"\n description=\"Track a new recurring digital relationship\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships')}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n }\n >\n <PagePurpose className=\"mb-6\">\n Manually add a subscription, membership, license or contract. Link it to a provider and plan\n from the registry, set the price and renewal date, and Subscriber Bot will track its\n lifecycle from here.\n </PagePurpose>\n\n <form onSubmit={handleSubmit} className=\"max-w-2xl space-y-5\">\n {formError && (\n <div className=\"rounded-lg border border-status-error-text/30 bg-status-error-bg-subtle px-4 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n\n <div>\n <label htmlFor=\"rel-label\" className={labelCls}>\n Label <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"rel-label\"\n type=\"text\"\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n placeholder=\"e.g. Netflix Premium, AWS Production\"\n className={inputCls}\n required\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"rel-kind\" className={labelCls}>\n Kind\n </label>\n <select\n id=\"rel-kind\"\n value={kind}\n onChange={(e) => setKind(e.target.value as SubRelationshipKind)}\n className={inputCls}\n >\n {KINDS.map((k) => (\n <option key={k} value={k}>\n {k.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label htmlFor=\"rel-provider\" className={labelCls}>\n Provider\n </label>\n <select\n id=\"rel-provider\"\n value={providerId}\n onChange={(e) => {\n setProviderId(e.target.value);\n setPlanId('');\n }}\n className={inputCls}\n >\n <option value=\"\">— None —</option>\n {providers.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n {providerId && plans.length > 0 && (\n <div>\n <label htmlFor=\"rel-plan\" className={labelCls}>\n Plan\n </label>\n <select\n id=\"rel-plan\"\n value={planId}\n onChange={(e) => setPlanId(e.target.value)}\n className={inputCls}\n >\n <option value=\"\">— None —</option>\n {plans.map((pl) => (\n <option key={pl.id} value={pl.id}>\n {pl.name}\n </option>\n ))}\n </select>\n </div>\n )}\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label htmlFor=\"rel-amount\" className={labelCls}>\n Amount\n </label>\n <input\n id=\"rel-amount\"\n type=\"number\"\n min=\"0\"\n step=\"0.01\"\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n placeholder=\"0.00\"\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"rel-currency\" className={labelCls}>\n Currency\n </label>\n <input\n id=\"rel-currency\"\n type=\"text\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value.toUpperCase())}\n placeholder=\"USD\"\n maxLength={3}\n className={inputCls}\n />\n </div>\n <div>\n <label htmlFor=\"rel-interval\" className={labelCls}>\n Billing interval\n </label>\n <select\n id=\"rel-interval\"\n value={interval}\n onChange={(e) => setInterval(e.target.value as SubBillingInterval)}\n className={inputCls}\n >\n {INTERVALS.map((i) => (\n <option key={i} value={i}>\n {i.replace(/_/g, ' ')}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"rel-renews\" className={labelCls}>\n Renews on\n </label>\n <input\n id=\"rel-renews\"\n type=\"date\"\n value={renewsAt}\n onChange={(e) => setRenewsAt(e.target.value)}\n className={inputCls}\n />\n </div>\n <div className=\"flex items-end\">\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={autoRenew}\n onChange={(e) => setAutoRenew(e.target.checked)}\n className=\"size-4 rounded border-border-subtle\"\n />\n Auto-renews\n </label>\n </div>\n </div>\n\n <div>\n <label htmlFor=\"rel-notes\" className={labelCls}>\n Notes\n </label>\n <textarea\n id=\"rel-notes\"\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n rows={3}\n placeholder=\"Anything worth remembering about this relationship…\"\n className={inputCls}\n />\n </div>\n\n <div className=\"flex gap-3\">\n <button\n type=\"submit\"\n disabled={create.isPending}\n className=\"rounded-lg bg-action-primary-bg px-5 py-2.5 text-sm font-medium text-action-primary-text hover:opacity-90 disabled:opacity-50\"\n >\n {create.isPending ? 'Creating…' : 'Create relationship'}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships')}\n className=\"rounded-lg border border-border-subtle px-5 py-2.5 text-sm font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n </div>\n </form>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,248 @@
1
+ import { useSubscriberBot as e } from "../../context/SubscriberBotContext.js";
2
+ import { cn as t } from "../../utils/cn.js";
3
+ import { PageLayout as n } from "../../components/PageLayout.js";
4
+ import { formatCurrency as r, formatDate as i, formatEnumLabel as a, formatIntervalSuffix as o } from "../../utils/formatters.js";
5
+ import { StatusBadge as s } from "../../components/StatusBadge.js";
6
+ import { EmptyState as c } from "../../components/EmptyState.js";
7
+ import { ErrorView as l, LoadingView as u } from "../../components/StateViews.js";
8
+ import { useRelationships as d } from "../../hooks/useSubscriberBotApi.js";
9
+ import { useMemo as f, useState as p } from "react";
10
+ import { Plus as m, Repeat2 as h, Search as g } from "lucide-react";
11
+ import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
12
+ import { EmphasisPanel as b, PagePurpose as x } from "@burdenoff/fe-libs/ui";
13
+ //#region src/subscriberbot/pages/relationships/RelationshipsListPage.tsx
14
+ var S = 20, C = [
15
+ {
16
+ value: "",
17
+ label: "All statuses"
18
+ },
19
+ {
20
+ value: "ACTIVE",
21
+ label: "Active"
22
+ },
23
+ {
24
+ value: "TRIAL",
25
+ label: "Trial"
26
+ },
27
+ {
28
+ value: "PAUSED",
29
+ label: "Paused"
30
+ },
31
+ {
32
+ value: "PAST_DUE",
33
+ label: "Past due"
34
+ },
35
+ {
36
+ value: "LAPSED",
37
+ label: "Lapsed"
38
+ },
39
+ {
40
+ value: "EXPIRED",
41
+ label: "Expired"
42
+ },
43
+ {
44
+ value: "CANCELLED",
45
+ label: "Cancelled"
46
+ }
47
+ ], w = [
48
+ {
49
+ value: "",
50
+ label: "All kinds"
51
+ },
52
+ {
53
+ value: "SUBSCRIPTION",
54
+ label: "Subscription"
55
+ },
56
+ {
57
+ value: "MEMBERSHIP",
58
+ label: "Membership"
59
+ },
60
+ {
61
+ value: "LICENSE",
62
+ label: "License"
63
+ },
64
+ {
65
+ value: "CONTRACT",
66
+ label: "Contract"
67
+ },
68
+ {
69
+ value: "NEWSLETTER",
70
+ label: "Newsletter"
71
+ },
72
+ {
73
+ value: "RECURRING_PAYMENT",
74
+ label: "Recurring payment"
75
+ },
76
+ {
77
+ value: "ENROLLMENT",
78
+ label: "Enrollment"
79
+ },
80
+ {
81
+ value: "TRIAL",
82
+ label: "Trial"
83
+ }
84
+ ];
85
+ function T() {
86
+ let { navigateTo: T } = e(), [E, D] = p(0), [O, k] = p(""), [A, j] = p(""), [M, N] = p(""), { data: P, isLoading: F, isError: I, refetch: L, isFetching: R } = d({
87
+ status: O || void 0,
88
+ kind: A || void 0,
89
+ pagination: {
90
+ limit: S,
91
+ offset: E * S
92
+ }
93
+ }), z = f(() => P?.items ?? [], [P]), B = P?.pageInfo.hasMore ?? !1, V = f(() => {
94
+ if (!M.trim()) return z;
95
+ let e = M.toLowerCase();
96
+ return z.filter((t) => t.label.toLowerCase().includes(e) || (t.provider?.name?.toLowerCase().includes(e) ?? !1));
97
+ }, [z, M]);
98
+ return /* @__PURE__ */ y(n, {
99
+ title: "Relationships",
100
+ description: "Every subscription, membership, license and contract you track",
101
+ actions: /* @__PURE__ */ y("button", {
102
+ type: "button",
103
+ onClick: () => T("/relationships/new"),
104
+ className: "inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90",
105
+ children: [/* @__PURE__ */ v(m, { className: "h-4 w-4" }), " Add relationship"]
106
+ }),
107
+ children: [
108
+ /* @__PURE__ */ v(x, {
109
+ className: "mb-4",
110
+ children: "This is the fundamental object of Subscriber Bot. Filter by lifecycle and kind, open any row to manage its plan, payment, entitlements, assets and renewals, or advance its lifecycle."
111
+ }),
112
+ /* @__PURE__ */ v(b, { children: /* @__PURE__ */ y("div", {
113
+ className: "relative",
114
+ children: [/* @__PURE__ */ v(g, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ v("input", {
115
+ type: "text",
116
+ placeholder: "Search relationships on this page…",
117
+ value: M,
118
+ onChange: (e) => N(e.target.value),
119
+ className: "w-full rounded-lg border border-border-subtle bg-bg-surface py-2.5 pl-10 pr-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
120
+ })]
121
+ }) }),
122
+ /* @__PURE__ */ y("div", {
123
+ className: "mt-4 flex flex-wrap items-center gap-3",
124
+ children: [/* @__PURE__ */ v("select", {
125
+ value: O,
126
+ onChange: (e) => {
127
+ k(e.target.value), D(0);
128
+ },
129
+ className: "rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
130
+ children: C.map((e) => /* @__PURE__ */ v("option", {
131
+ value: e.value,
132
+ children: e.label
133
+ }, e.value))
134
+ }), /* @__PURE__ */ v("select", {
135
+ value: A,
136
+ onChange: (e) => {
137
+ j(e.target.value), D(0);
138
+ },
139
+ className: "rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
140
+ children: w.map((e) => /* @__PURE__ */ v("option", {
141
+ value: e.value,
142
+ children: e.label
143
+ }, e.value))
144
+ })]
145
+ }),
146
+ F ? /* @__PURE__ */ v(u, { label: "Loading relationships…" }) : I ? /* @__PURE__ */ v(l, {
147
+ message: "Could not load relationships.",
148
+ onRetry: () => void L()
149
+ }) : V.length === 0 ? /* @__PURE__ */ v("div", {
150
+ className: "mt-6",
151
+ children: /* @__PURE__ */ v(c, {
152
+ illustration: "empty-data",
153
+ icon: /* @__PURE__ */ v(h, { className: "h-12 w-12" }),
154
+ title: "No relationships found",
155
+ description: M ? `No matches for "${M}" on this page.` : "Add your first recurring relationship to start tracking it.",
156
+ action: /* @__PURE__ */ v("button", {
157
+ type: "button",
158
+ onClick: () => T("/relationships/new"),
159
+ className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90",
160
+ children: "Add relationship"
161
+ })
162
+ })
163
+ }) : /* @__PURE__ */ y(_, { children: [/* @__PURE__ */ y("div", {
164
+ className: "mt-6 overflow-hidden rounded-lg border border-border-subtle bg-bg-surface",
165
+ children: [/* @__PURE__ */ y("div", {
166
+ className: "hidden grid-cols-[2fr_1fr_1fr_1fr_1fr] gap-4 border-b border-border-subtle bg-bg-sunken px-4 py-2.5 text-xs font-medium text-text-secondary sm:grid",
167
+ children: [
168
+ /* @__PURE__ */ v("span", { children: "Relationship" }),
169
+ /* @__PURE__ */ v("span", { children: "Kind" }),
170
+ /* @__PURE__ */ v("span", { children: "Status" }),
171
+ /* @__PURE__ */ v("span", {
172
+ className: "text-right",
173
+ children: "Amount"
174
+ }),
175
+ /* @__PURE__ */ v("span", {
176
+ className: "text-right",
177
+ children: "Renews"
178
+ })
179
+ ]
180
+ }), /* @__PURE__ */ v("div", {
181
+ className: "divide-y divide-border-subtle",
182
+ children: V.map((e) => /* @__PURE__ */ y("button", {
183
+ type: "button",
184
+ onClick: () => T(`/relationships/${e.id}`),
185
+ className: "grid w-full grid-cols-1 gap-2 px-4 py-3 text-left hover:bg-bg-sunken sm:grid-cols-[2fr_1fr_1fr_1fr_1fr] sm:items-center sm:gap-4",
186
+ children: [
187
+ /* @__PURE__ */ y("div", {
188
+ className: "min-w-0",
189
+ children: [/* @__PURE__ */ v("p", {
190
+ className: "truncate text-sm font-medium text-text-primary",
191
+ children: e.label
192
+ }), e.provider?.name && /* @__PURE__ */ v("p", {
193
+ className: "truncate text-xs text-text-secondary",
194
+ children: e.provider.name
195
+ })]
196
+ }),
197
+ /* @__PURE__ */ v("span", {
198
+ className: "text-xs text-text-secondary",
199
+ children: a(e.kind)
200
+ }),
201
+ /* @__PURE__ */ v("span", { children: /* @__PURE__ */ v(s, { status: e.status }) }),
202
+ /* @__PURE__ */ y("span", {
203
+ className: "text-sm font-semibold tabular-nums text-text-primary sm:text-right",
204
+ children: [r(e.amount, e.currency), /* @__PURE__ */ v("span", {
205
+ className: "text-xs font-normal text-text-secondary",
206
+ children: o(e.interval)
207
+ })]
208
+ }),
209
+ /* @__PURE__ */ v("span", {
210
+ className: "text-xs text-text-secondary sm:text-right",
211
+ children: e.renewsAt ? i(e.renewsAt) : "—"
212
+ })
213
+ ]
214
+ }, e.id))
215
+ })]
216
+ }), /* @__PURE__ */ y("div", {
217
+ className: "mt-4 flex items-center justify-between",
218
+ children: [/* @__PURE__ */ y("p", {
219
+ className: "text-xs text-text-secondary",
220
+ children: [
221
+ "Page ",
222
+ E + 1,
223
+ R ? " · refreshing…" : ""
224
+ ]
225
+ }), /* @__PURE__ */ y("div", {
226
+ className: "flex gap-2",
227
+ children: [/* @__PURE__ */ v("button", {
228
+ type: "button",
229
+ disabled: E === 0,
230
+ onClick: () => D((e) => Math.max(0, e - 1)),
231
+ className: t("rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium", E === 0 ? "cursor-not-allowed text-text-secondary opacity-50" : "text-text-primary hover:bg-bg-sunken"),
232
+ children: "Previous"
233
+ }), /* @__PURE__ */ v("button", {
234
+ type: "button",
235
+ disabled: !B,
236
+ onClick: () => D((e) => e + 1),
237
+ className: t("rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium", B ? "text-text-primary hover:bg-bg-sunken" : "cursor-not-allowed text-text-secondary opacity-50"),
238
+ children: "Next"
239
+ })]
240
+ })]
241
+ })] })
242
+ ]
243
+ });
244
+ }
245
+ //#endregion
246
+ export { T as RelationshipsListPage };
247
+
248
+ //# sourceMappingURL=RelationshipsListPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RelationshipsListPage.js","names":[],"sources":["../../../../src/subscriberbot/pages/relationships/RelationshipsListPage.tsx"],"sourcesContent":["/**\n * Relationships list\n * @module subscriberbot/pages/relationships\n *\n * The core CRUDL list — every recurring relationship (subscription /\n * membership / license / contract) in one paginated, filterable view.\n * Data comes from `subListRelationships` (a SubRelationshipConnection).\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, Search, Repeat2 } from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { useSubscriberBot } from '../../context/SubscriberBotContext';\nimport { useRelationships } from '../../hooks/useSubscriberBotApi';\nimport { cn } from '../../utils/cn';\nimport { formatCurrency, formatDate, formatEnumLabel, formatIntervalSuffix } from '../../utils/formatters';\nimport type { SubRelationshipKind, SubRelationshipStatus } from '../../types';\n\nconst PAGE_SIZE = 20;\n\nconst STATUS_OPTIONS: Array<{ value: '' | SubRelationshipStatus; label: string }> = [\n { value: '', label: 'All statuses' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'TRIAL', label: 'Trial' },\n { value: 'PAUSED', label: 'Paused' },\n { value: 'PAST_DUE', label: 'Past due' },\n { value: 'LAPSED', label: 'Lapsed' },\n { value: 'EXPIRED', label: 'Expired' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\nconst KIND_OPTIONS: Array<{ value: '' | SubRelationshipKind; label: string }> = [\n { value: '', label: 'All kinds' },\n { value: 'SUBSCRIPTION', label: 'Subscription' },\n { value: 'MEMBERSHIP', label: 'Membership' },\n { value: 'LICENSE', label: 'License' },\n { value: 'CONTRACT', label: 'Contract' },\n { value: 'NEWSLETTER', label: 'Newsletter' },\n { value: 'RECURRING_PAYMENT', label: 'Recurring payment' },\n { value: 'ENROLLMENT', label: 'Enrollment' },\n { value: 'TRIAL', label: 'Trial' },\n];\n\nexport function RelationshipsListPage() {\n const { navigateTo } = useSubscriberBot();\n const [page, setPage] = useState(0);\n const [statusFilter, setStatusFilter] = useState<'' | SubRelationshipStatus>('');\n const [kindFilter, setKindFilter] = useState<'' | SubRelationshipKind>('');\n const [search, setSearch] = useState('');\n\n const { data, isLoading, isError, refetch, isFetching } = useRelationships({\n status: statusFilter || undefined,\n kind: kindFilter || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = useMemo(() => data?.items ?? [], [data]);\n const hasMore = data?.pageInfo.hasMore ?? false;\n\n // Client-side label search over the current page (server has no search arg).\n const visible = useMemo(() => {\n if (!search.trim()) return items;\n const q = search.toLowerCase();\n return items.filter(\n (r) =>\n r.label.toLowerCase().includes(q) ||\n (r.provider?.name?.toLowerCase().includes(q) ?? false)\n );\n }, [items, search]);\n\n return (\n <PageLayout\n title=\"Relationships\"\n description=\"Every subscription, membership, license and contract you track\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships/new')}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n <Plus className=\"h-4 w-4\" /> Add relationship\n </button>\n }\n >\n <PagePurpose className=\"mb-4\">\n This is the fundamental object of Subscriber Bot. Filter by lifecycle and kind, open any row\n to manage its plan, payment, entitlements, assets and renewals, or advance its lifecycle.\n </PagePurpose>\n\n <EmphasisPanel>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"text\"\n placeholder=\"Search relationships on this page…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface py-2.5 pl-10 pr-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </EmphasisPanel>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-3\">\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as '' | SubRelationshipStatus);\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {STATUS_OPTIONS.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n <select\n value={kindFilter}\n onChange={(e) => {\n setKindFilter(e.target.value as '' | SubRelationshipKind);\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {KIND_OPTIONS.map((o) => (\n <option key={o.value} value={o.value}>\n {o.label}\n </option>\n ))}\n </select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading relationships…\" />\n ) : isError ? (\n <ErrorView message=\"Could not load relationships.\" onRetry={() => void refetch()} />\n ) : visible.length === 0 ? (\n <div className=\"mt-6\">\n <EmptyState\n illustration=\"empty-data\"\n icon={<Repeat2 className=\"h-12 w-12\" />}\n title=\"No relationships found\"\n description={\n search\n ? `No matches for \"${search}\" on this page.`\n : 'Add your first recurring relationship to start tracking it.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/relationships/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90\"\n >\n Add relationship\n </button>\n }\n />\n </div>\n ) : (\n <>\n <div className=\"mt-6 overflow-hidden rounded-lg border border-border-subtle bg-bg-surface\">\n <div className=\"hidden grid-cols-[2fr_1fr_1fr_1fr_1fr] gap-4 border-b border-border-subtle bg-bg-sunken px-4 py-2.5 text-xs font-medium text-text-secondary sm:grid\">\n <span>Relationship</span>\n <span>Kind</span>\n <span>Status</span>\n <span className=\"text-right\">Amount</span>\n <span className=\"text-right\">Renews</span>\n </div>\n <div className=\"divide-y divide-border-subtle\">\n {visible.map((r) => (\n <button\n key={r.id}\n type=\"button\"\n onClick={() => navigateTo(`/relationships/${r.id}`)}\n className=\"grid w-full grid-cols-1 gap-2 px-4 py-3 text-left hover:bg-bg-sunken sm:grid-cols-[2fr_1fr_1fr_1fr_1fr] sm:items-center sm:gap-4\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{r.label}</p>\n {r.provider?.name && (\n <p className=\"truncate text-xs text-text-secondary\">{r.provider.name}</p>\n )}\n </div>\n <span className=\"text-xs text-text-secondary\">{formatEnumLabel(r.kind)}</span>\n <span>\n <StatusBadge status={r.status} />\n </span>\n <span className=\"text-sm font-semibold tabular-nums text-text-primary sm:text-right\">\n {formatCurrency(r.amount, r.currency)}\n <span className=\"text-xs font-normal text-text-secondary\">\n {formatIntervalSuffix(r.interval)}\n </span>\n </span>\n <span className=\"text-xs text-text-secondary sm:text-right\">\n {r.renewsAt ? formatDate(r.renewsAt) : '—'}\n </span>\n </button>\n ))}\n </div>\n </div>\n\n <div className=\"mt-4 flex items-center justify-between\">\n <p className=\"text-xs text-text-secondary\">\n Page {page + 1}\n {isFetching ? ' · refreshing…' : ''}\n </p>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n disabled={page === 0}\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n page === 0\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n Previous\n </button>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setPage((p) => p + 1)}\n className={cn(\n 'rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium',\n !hasMore\n ? 'cursor-not-allowed text-text-secondary opacity-50'\n : 'text-text-primary hover:bg-bg-sunken'\n )}\n >\n Next\n </button>\n </div>\n </div>\n </>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,51 +1,69 @@
1
1
  //#region src/subscriberbot/utils/formatters.ts
2
- function e(e, t) {
3
- return new Intl.NumberFormat("en-IN", {
4
- style: "currency",
5
- currency: "INR",
6
- minimumFractionDigits: 2,
7
- maximumFractionDigits: 2,
8
- ...t
9
- }).format(e);
2
+ function e(e, t = "USD", n) {
3
+ try {
4
+ return new Intl.NumberFormat(void 0, {
5
+ style: "currency",
6
+ currency: t || "USD",
7
+ minimumFractionDigits: 2,
8
+ maximumFractionDigits: 2,
9
+ ...n
10
+ }).format(e);
11
+ } catch {
12
+ return `${e.toFixed(2)} ${t}`;
13
+ }
10
14
  }
11
15
  function t(e, t) {
16
+ if (!e) return "—";
17
+ let n = typeof e == "string" ? new Date(e) : e;
18
+ return Number.isNaN(n.getTime()) ? "—" : new Intl.DateTimeFormat(void 0, {
19
+ day: "numeric",
20
+ month: "short",
21
+ year: "numeric",
22
+ ...t
23
+ }).format(n);
24
+ }
25
+ function n(e, t) {
26
+ if (!e) return "—";
12
27
  let n = typeof e == "string" ? new Date(e) : e;
13
- return new Intl.DateTimeFormat("en-IN", {
28
+ return Number.isNaN(n.getTime()) ? "—" : new Intl.DateTimeFormat(void 0, {
14
29
  day: "numeric",
15
30
  month: "short",
16
31
  year: "numeric",
17
32
  hour: "numeric",
18
33
  minute: "2-digit",
19
- hour12: !0,
20
34
  ...t
21
35
  }).format(n);
22
36
  }
23
- function n(e) {
24
- let t = typeof e == "string" ? new Date(e) : e, n = (/* @__PURE__ */ new Date()).getTime() - t.getTime(), r = Math.floor(n / 1e3), i = Math.floor(r / 60), a = Math.floor(i / 60), o = Math.floor(a / 24), s = Math.floor(o / 7), c = Math.floor(o / 30);
25
- if (r < 60) return "just now";
26
- if (i < 60) return `${i} min${i === 1 ? "" : "s"} ago`;
27
- if (a < 24) return `${a} hour${a === 1 ? "" : "s"} ago`;
28
- if (o < 7) return `${o} day${o === 1 ? "" : "s"} ago`;
29
- if (s < 4) return `${s} week${s === 1 ? "" : "s"} ago`;
30
- if (c < 12) return `${c} month${c === 1 ? "" : "s"} ago`;
31
- let l = Math.floor(o / 365);
32
- return `${l} year${l === 1 ? "" : "s"} ago`;
37
+ function r(e) {
38
+ if (!e) return "—";
39
+ let t = typeof e == "string" ? new Date(e) : e;
40
+ if (Number.isNaN(t.getTime())) return "—";
41
+ let n = /* @__PURE__ */ new Date(), r = t.getTime() - n.getTime(), i = r >= 0, a = Math.floor(Math.abs(r) / 1e3), o = Math.floor(a / 60), s = Math.floor(o / 60), c = Math.floor(s / 24), l = Math.floor(c / 7), u = Math.floor(c / 30), d = Math.floor(c / 365), f = (e, t) => {
42
+ let n = `${e} ${t}${e === 1 ? "" : "s"}`;
43
+ return i ? `in ${n}` : `${n} ago`;
44
+ };
45
+ return a < 60 ? i ? "soon" : "just now" : o < 60 ? f(o, "min") : s < 24 ? f(s, "hour") : c < 7 ? f(c, "day") : l < 4 ? f(l, "week") : u < 12 ? f(u, "month") : f(d, "year");
33
46
  }
34
- function r(e, t) {
35
- return new Intl.NumberFormat("en-IN", t).format(e);
36
- }
37
- function i(e) {
38
- return e < 1 ? `${Math.round(e * 1e3)} m` : e < 10 ? `${e.toFixed(1)} km` : `${Math.round(e)} km`;
47
+ function i(e, t) {
48
+ return new Intl.NumberFormat(void 0, t).format(e);
39
49
  }
40
50
  function a(e) {
41
- if (e < 1) return "instant";
42
- if (e < 60) return `${e} min`;
43
- let t = Math.floor(e / 60), n = e % 60;
44
- if (t < 24) return n === 0 ? `${t}h` : `${t}h ${n}min`;
45
- let r = Math.floor(t / 24), i = t % 24;
46
- return i === 0 ? `${r} day${r === 1 ? "" : "s"}` : `${r} day${r === 1 ? "" : "s"} ${i}h`;
51
+ return e.toLowerCase().replace(/_/g, " ").replace(/\b\w/g, (e) => e.toUpperCase());
52
+ }
53
+ function o(e) {
54
+ switch (e) {
55
+ case "DAILY": return "/day";
56
+ case "WEEKLY": return "/wk";
57
+ case "MONTHLY": return "/mo";
58
+ case "QUARTERLY": return "/qtr";
59
+ case "YEARLY": return "/yr";
60
+ case "LIFETIME": return " lifetime";
61
+ case "USAGE_BASED": return " usage";
62
+ case "FREE": return "";
63
+ default: return "";
64
+ }
47
65
  }
48
66
  //#endregion
49
- export { e as formatCurrency, t as formatDate, i as formatDistance, a as formatDuration, r as formatNumber, n as formatRelativeTime };
67
+ export { e as formatCurrency, t as formatDate, n as formatDateTime, a as formatEnumLabel, o as formatIntervalSuffix, i as formatNumber, r as formatRelativeTime };
50
68
 
51
69
  //# sourceMappingURL=formatters.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"formatters.js","names":[],"sources":["../../../src/subscriberbot/utils/formatters.ts"],"sourcesContent":["/**\n * Formatting utilities for the SubscriberBot microfrontend\n * @module subscriberbot/utils/formatters\n */\n\n/**\n * Format a number as Indian Rupees (INR) currency\n *\n * @param amount - The numeric amount to format\n * @param options - Optional Intl.NumberFormat options to override defaults\n * @returns Formatted currency string (e.g., \"₹1,200.00\")\n *\n * @example\n * formatCurrency(1200) // => \"₹1,200.00\"\n * formatCurrency(0) // => \"₹0.00\"\n * formatCurrency(1200, { minimumFractionDigits: 0 }) // => \"₹1,200\"\n */\nexport function formatCurrency(\n amount: number,\n options?: Partial<Intl.NumberFormatOptions>\n): string {\n return new Intl.NumberFormat('en-IN', {\n style: 'currency',\n currency: 'INR',\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n ...options,\n }).format(amount);\n}\n\n/**\n * Format a date string or Date object into a human-readable format\n *\n * @param date - ISO date string or Date object\n * @param options - Optional Intl.DateTimeFormat options\n * @returns Formatted date string (e.g., \"15 Jan 2024, 4:00 PM\")\n *\n * @example\n * formatDate('2024-01-15T10:30:00Z')\n * // => \"15 Jan 2024, 4:00 PM\" (in IST)\n */\nexport function formatDate(\n date: string | Date,\n options?: Partial<Intl.DateTimeFormatOptions>\n): string {\n const d = typeof date === 'string' ? new Date(date) : date;\n return new Intl.DateTimeFormat('en-IN', {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n hour12: true,\n ...options,\n }).format(d);\n}\n\n/**\n * Format a date into a relative time string (e.g., \"2 hours ago\")\n *\n * @param date - ISO date string or Date object\n * @returns Human-readable relative time string\n *\n * @example\n * formatRelativeTime('2024-01-15T10:30:00Z') // => \"3 days ago\"\n * formatRelativeTime(new Date()) // => \"just now\"\n */\nexport function formatRelativeTime(date: string | Date): string {\n const d = typeof date === 'string' ? new Date(date) : date;\n const now = new Date();\n const diffMs = now.getTime() - d.getTime();\n const diffSeconds = Math.floor(diffMs / 1000);\n const diffMinutes = Math.floor(diffSeconds / 60);\n const diffHours = Math.floor(diffMinutes / 60);\n const diffDays = Math.floor(diffHours / 24);\n const diffWeeks = Math.floor(diffDays / 7);\n const diffMonths = Math.floor(diffDays / 30);\n\n if (diffSeconds < 60) return 'just now';\n if (diffMinutes < 60) return `${diffMinutes} min${diffMinutes !== 1 ? 's' : ''} ago`;\n if (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ago`;\n if (diffDays < 7) return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`;\n if (diffWeeks < 4) return `${diffWeeks} week${diffWeeks !== 1 ? 's' : ''} ago`;\n if (diffMonths < 12) return `${diffMonths} month${diffMonths !== 1 ? 's' : ''} ago`;\n\n const diffYears = Math.floor(diffDays / 365);\n return `${diffYears} year${diffYears !== 1 ? 's' : ''} ago`;\n}\n\n/**\n * Format a number with Indian locale grouping (lakhs, crores)\n *\n * @param value - The number to format\n * @param options - Optional Intl.NumberFormat options\n * @returns Formatted number string (e.g., \"12,345\" or \"1,24,500\")\n *\n * @example\n * formatNumber(1245) // => \"1,245\"\n * formatNumber(124500) // => \"1,24,500\"\n */\nexport function formatNumber(value: number, options?: Partial<Intl.NumberFormatOptions>): string {\n return new Intl.NumberFormat('en-IN', options).format(value);\n}\n\n/**\n * Format a distance value in kilometers\n *\n * @param km - Distance in kilometers\n * @returns Formatted distance string\n *\n * @example\n * formatDistance(0.5) // => \"500 m\"\n * formatDistance(1.2) // => \"1.2 km\"\n * formatDistance(15) // => \"15 km\"\n */\nexport function formatDistance(km: number): string {\n if (km < 1) {\n return `${Math.round(km * 1000)} m`;\n }\n if (km < 10) {\n return `${km.toFixed(1)} km`;\n }\n return `${Math.round(km)} km`;\n}\n\n/**\n * Format a duration in minutes into a human-readable string\n *\n * @param minutes - Duration in minutes\n * @returns Formatted duration string\n *\n * @example\n * formatDuration(5) // => \"5 min\"\n * formatDuration(90) // => \"1h 30min\"\n * formatDuration(1440) // => \"1 day\"\n * formatDuration(4320) // => \"3 days\"\n */\nexport function formatDuration(minutes: number): string {\n if (minutes < 1) return 'instant';\n if (minutes < 60) return `${minutes} min`;\n\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n\n if (hours < 24) {\n if (remainingMinutes === 0) {\n return `${hours}h`;\n }\n return `${hours}h ${remainingMinutes}min`;\n }\n\n const days = Math.floor(hours / 24);\n const remainingHours = hours % 24;\n\n if (remainingHours === 0) {\n return `${days} day${days !== 1 ? 's' : ''}`;\n }\n return `${days} day${days !== 1 ? 's' : ''} ${remainingHours}h`;\n}\n"],"mappings":";AAiBA,SAAgB,EACd,GACA,GACQ;AACR,QAAO,IAAI,KAAK,aAAa,SAAS;EACpC,OAAO;EACP,UAAU;EACV,uBAAuB;EACvB,uBAAuB;EACvB,GAAG;EACJ,CAAC,CAAC,OAAO,EAAO;;AAcnB,SAAgB,EACd,GACA,GACQ;CACR,IAAM,IAAI,OAAO,KAAS,WAAW,IAAI,KAAK,EAAK,GAAG;AACtD,QAAO,IAAI,KAAK,eAAe,SAAS;EACtC,KAAK;EACL,OAAO;EACP,MAAM;EACN,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,GAAG;EACJ,CAAC,CAAC,OAAO,EAAE;;AAad,SAAgB,EAAmB,GAA6B;CAC9D,IAAM,IAAI,OAAO,KAAS,WAAW,IAAI,KAAK,EAAK,GAAG,GAEhD,qBADM,IAAI,MAAM,EACH,SAAS,GAAG,EAAE,SAAS,EACpC,IAAc,KAAK,MAAM,IAAS,IAAK,EACvC,IAAc,KAAK,MAAM,IAAc,GAAG,EAC1C,IAAY,KAAK,MAAM,IAAc,GAAG,EACxC,IAAW,KAAK,MAAM,IAAY,GAAG,EACrC,IAAY,KAAK,MAAM,IAAW,EAAE,EACpC,IAAa,KAAK,MAAM,IAAW,GAAG;AAE5C,KAAI,IAAc,GAAI,QAAO;AAC7B,KAAI,IAAc,GAAI,QAAO,GAAG,EAAY,MAAM,MAAgB,IAAU,KAAN,IAAS;AAC/E,KAAI,IAAY,GAAI,QAAO,GAAG,EAAU,OAAO,MAAc,IAAU,KAAN,IAAS;AAC1E,KAAI,IAAW,EAAG,QAAO,GAAG,EAAS,MAAM,MAAa,IAAU,KAAN,IAAS;AACrE,KAAI,IAAY,EAAG,QAAO,GAAG,EAAU,OAAO,MAAc,IAAU,KAAN,IAAS;AACzE,KAAI,IAAa,GAAI,QAAO,GAAG,EAAW,QAAQ,MAAe,IAAU,KAAN,IAAS;CAE9E,IAAM,IAAY,KAAK,MAAM,IAAW,IAAI;AAC5C,QAAO,GAAG,EAAU,OAAO,MAAc,IAAU,KAAN,IAAS;;AAcxD,SAAgB,EAAa,GAAe,GAAqD;AAC/F,QAAO,IAAI,KAAK,aAAa,SAAS,EAAQ,CAAC,OAAO,EAAM;;AAc9D,SAAgB,EAAe,GAAoB;AAOjD,QANI,IAAK,IACA,GAAG,KAAK,MAAM,IAAK,IAAK,CAAC,MAE9B,IAAK,KACA,GAAG,EAAG,QAAQ,EAAE,CAAC,OAEnB,GAAG,KAAK,MAAM,EAAG,CAAC;;AAe3B,SAAgB,EAAe,GAAyB;AACtD,KAAI,IAAU,EAAG,QAAO;AACxB,KAAI,IAAU,GAAI,QAAO,GAAG,EAAQ;CAEpC,IAAM,IAAQ,KAAK,MAAM,IAAU,GAAG,EAChC,IAAmB,IAAU;AAEnC,KAAI,IAAQ,GAIV,QAHI,MAAqB,IAChB,GAAG,EAAM,KAEX,GAAG,EAAM,IAAI,EAAiB;CAGvC,IAAM,IAAO,KAAK,MAAM,IAAQ,GAAG,EAC7B,IAAiB,IAAQ;AAK/B,QAHI,MAAmB,IACd,GAAG,EAAK,MAAM,MAAS,IAAU,KAAN,QAE7B,GAAG,EAAK,MAAM,MAAS,IAAU,KAAN,IAAS,GAAG,EAAe"}
1
+ {"version":3,"file":"formatters.js","names":[],"sources":["../../../src/subscriberbot/utils/formatters.ts"],"sourcesContent":["/**\n * Formatting utilities for the Subscriber Bot microfrontend\n * @module subscriberbot/utils/formatters\n *\n * Subscriber Bot is a global product — relationships are billed in many\n * currencies. Formatting is therefore currency-aware (each Relationship /\n * Invoice / Plan carries its own ISO currency code).\n */\n\n/**\n * Format an amount as currency using the relationship/invoice's own ISO code.\n *\n * @param amount - The numeric amount to format\n * @param currency - ISO 4217 currency code (e.g. \"USD\", \"EUR\", \"INR\")\n * @param options - Optional Intl.NumberFormat overrides\n * @returns Formatted currency string (e.g. \"$12.00\")\n *\n * @example\n * formatCurrency(12, 'USD') // => \"$12.00\"\n * formatCurrency(999, 'INR', { minimumFractionDigits: 0 }) // => \"₹999\"\n */\nexport function formatCurrency(\n amount: number,\n currency = 'USD',\n options?: Partial<Intl.NumberFormatOptions>\n): string {\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency: currency || 'USD',\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n ...options,\n }).format(amount);\n } catch {\n // Unknown/invalid currency code → fall back to a plain number + code.\n return `${amount.toFixed(2)} ${currency}`;\n }\n}\n\n/**\n * Format a date string or Date object into a human-readable date.\n *\n * @param date - ISO date string or Date object\n * @param options - Optional Intl.DateTimeFormat options\n * @returns Formatted date string (e.g. \"15 Jan 2026\")\n */\nexport function formatDate(\n date: string | Date | null | undefined,\n options?: Partial<Intl.DateTimeFormatOptions>\n): string {\n if (!date) return '—';\n const d = typeof date === 'string' ? new Date(date) : date;\n if (Number.isNaN(d.getTime())) return '—';\n return new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n ...options,\n }).format(d);\n}\n\n/**\n * Format a date-time into a date + time string.\n */\nexport function formatDateTime(\n date: string | Date | null | undefined,\n options?: Partial<Intl.DateTimeFormatOptions>\n): string {\n if (!date) return '—';\n const d = typeof date === 'string' ? new Date(date) : date;\n if (Number.isNaN(d.getTime())) return '—';\n return new Intl.DateTimeFormat(undefined, {\n day: 'numeric',\n month: 'short',\n year: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n ...options,\n }).format(d);\n}\n\n/**\n * Format a date into a relative time string (e.g. \"2 hours ago\", \"in 3 days\").\n *\n * @param date - ISO date string or Date object\n * @returns Human-readable relative time string\n */\nexport function formatRelativeTime(date: string | Date | null | undefined): string {\n if (!date) return '—';\n const d = typeof date === 'string' ? new Date(date) : date;\n if (Number.isNaN(d.getTime())) return '—';\n\n const now = new Date();\n const diffMs = d.getTime() - now.getTime();\n const future = diffMs >= 0;\n const absSeconds = Math.floor(Math.abs(diffMs) / 1000);\n const minutes = Math.floor(absSeconds / 60);\n const hours = Math.floor(minutes / 60);\n const days = Math.floor(hours / 24);\n const weeks = Math.floor(days / 7);\n const months = Math.floor(days / 30);\n const years = Math.floor(days / 365);\n\n const phrase = (value: number, unit: string): string => {\n const label = `${value} ${unit}${value !== 1 ? 's' : ''}`;\n return future ? `in ${label}` : `${label} ago`;\n };\n\n if (absSeconds < 60) return future ? 'soon' : 'just now';\n if (minutes < 60) return phrase(minutes, 'min');\n if (hours < 24) return phrase(hours, 'hour');\n if (days < 7) return phrase(days, 'day');\n if (weeks < 4) return phrase(weeks, 'week');\n if (months < 12) return phrase(months, 'month');\n return phrase(years, 'year');\n}\n\n/**\n * Format a number with locale grouping.\n */\nexport function formatNumber(value: number, options?: Partial<Intl.NumberFormatOptions>): string {\n return new Intl.NumberFormat(undefined, options).format(value);\n}\n\n/**\n * Turn an ENUM_LIKE_TOKEN into a human \"Enum Like Token\" label.\n */\nexport function formatEnumLabel(value: string): string {\n return value\n .toLowerCase()\n .replace(/_/g, ' ')\n .replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\n/**\n * Format a billing interval enum into a short suffix (e.g. \"MONTHLY\" → \"/mo\").\n */\nexport function formatIntervalSuffix(interval: string): string {\n switch (interval) {\n case 'DAILY':\n return '/day';\n case 'WEEKLY':\n return '/wk';\n case 'MONTHLY':\n return '/mo';\n case 'QUARTERLY':\n return '/qtr';\n case 'YEARLY':\n return '/yr';\n case 'LIFETIME':\n return ' lifetime';\n case 'USAGE_BASED':\n return ' usage';\n case 'FREE':\n return '';\n default:\n return '';\n }\n}\n"],"mappings":";AAqBA,SAAgB,EACd,GACA,IAAW,OACX,GACQ;AACR,KAAI;AACF,SAAO,IAAI,KAAK,aAAa,KAAA,GAAW;GACtC,OAAO;GACP,UAAU,KAAY;GACtB,uBAAuB;GACvB,uBAAuB;GACvB,GAAG;GACJ,CAAC,CAAC,OAAO,EAAO;SACX;AAEN,SAAO,GAAG,EAAO,QAAQ,EAAE,CAAC,GAAG;;;AAWnC,SAAgB,EACd,GACA,GACQ;AACR,KAAI,CAAC,EAAM,QAAO;CAClB,IAAM,IAAI,OAAO,KAAS,WAAW,IAAI,KAAK,EAAK,GAAG;AAEtD,QADI,OAAO,MAAM,EAAE,SAAS,CAAC,GAAS,MAC/B,IAAI,KAAK,eAAe,KAAA,GAAW;EACxC,KAAK;EACL,OAAO;EACP,MAAM;EACN,GAAG;EACJ,CAAC,CAAC,OAAO,EAAE;;AAMd,SAAgB,EACd,GACA,GACQ;AACR,KAAI,CAAC,EAAM,QAAO;CAClB,IAAM,IAAI,OAAO,KAAS,WAAW,IAAI,KAAK,EAAK,GAAG;AAEtD,QADI,OAAO,MAAM,EAAE,SAAS,CAAC,GAAS,MAC/B,IAAI,KAAK,eAAe,KAAA,GAAW;EACxC,KAAK;EACL,OAAO;EACP,MAAM;EACN,MAAM;EACN,QAAQ;EACR,GAAG;EACJ,CAAC,CAAC,OAAO,EAAE;;AASd,SAAgB,EAAmB,GAAgD;AACjF,KAAI,CAAC,EAAM,QAAO;CAClB,IAAM,IAAI,OAAO,KAAS,WAAW,IAAI,KAAK,EAAK,GAAG;AACtD,KAAI,OAAO,MAAM,EAAE,SAAS,CAAC,CAAE,QAAO;CAEtC,IAAM,oBAAM,IAAI,MAAM,EAChB,IAAS,EAAE,SAAS,GAAG,EAAI,SAAS,EACpC,IAAS,KAAU,GACnB,IAAa,KAAK,MAAM,KAAK,IAAI,EAAO,GAAG,IAAK,EAChD,IAAU,KAAK,MAAM,IAAa,GAAG,EACrC,IAAQ,KAAK,MAAM,IAAU,GAAG,EAChC,IAAO,KAAK,MAAM,IAAQ,GAAG,EAC7B,IAAQ,KAAK,MAAM,IAAO,EAAE,EAC5B,IAAS,KAAK,MAAM,IAAO,GAAG,EAC9B,IAAQ,KAAK,MAAM,IAAO,IAAI,EAE9B,KAAU,GAAe,MAAyB;EACtD,IAAM,IAAQ,GAAG,EAAM,GAAG,IAAO,MAAU,IAAU,KAAN;AAC/C,SAAO,IAAS,MAAM,MAAU,GAAG,EAAM;;AAS3C,QANI,IAAa,KAAW,IAAS,SAAS,aAC1C,IAAU,KAAW,EAAO,GAAS,MAAM,GAC3C,IAAQ,KAAW,EAAO,GAAO,OAAO,GACxC,IAAO,IAAU,EAAO,GAAM,MAAM,GACpC,IAAQ,IAAU,EAAO,GAAO,OAAO,GACvC,IAAS,KAAW,EAAO,GAAQ,QAAQ,GACxC,EAAO,GAAO,OAAO;;AAM9B,SAAgB,EAAa,GAAe,GAAqD;AAC/F,QAAO,IAAI,KAAK,aAAa,KAAA,GAAW,EAAQ,CAAC,OAAO,EAAM;;AAMhE,SAAgB,EAAgB,GAAuB;AACrD,QAAO,EACJ,aAAa,CACb,QAAQ,MAAM,IAAI,CAClB,QAAQ,UAAU,MAAS,EAAK,aAAa,CAAC;;AAMnD,SAAgB,EAAqB,GAA0B;AAC7D,SAAQ,GAAR;EACE,KAAK,QACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,UACH,QAAO;EACT,KAAK,YACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,WACH,QAAO;EACT,KAAK,cACH,QAAO;EACT,KAAK,OACH,QAAO;EACT,QACE,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-subscriberbot",
3
- "version": "2026.626.2",
3
+ "version": "2026.628.1",
4
4
  "description": "Burdenoff SubscriberBot Microfrontend",
5
5
  "type": "module",
6
6
  "files": [
@@ -1,97 +0,0 @@
1
- import { cn as e } from "../utils/cn.js";
2
- import { StatusBadge as t } from "./StatusBadge.js";
3
- import { AlertTriangle as n, Shield as r, TrendingUp as i } from "lucide-react";
4
- import { jsx as a, jsxs as o } from "react/jsx-runtime";
5
- import { GlassCard as s } from "@burdenoff/fe-libs/ui";
6
- //#region src/subscriberbot/components/InvestmentCard.tsx
7
- var c = {
8
- low: {
9
- label: "Low Risk",
10
- icon: r,
11
- color: "text-status-success-text"
12
- },
13
- medium: {
14
- label: "Medium Risk",
15
- icon: n,
16
- color: "text-status-warning-text"
17
- },
18
- high: {
19
- label: "High Risk",
20
- icon: n,
21
- color: "text-status-error-text"
22
- }
23
- }, l = ({ id: n, cartName: r, targetAmount: l, raisedAmount: u, currency: d = "INR", expectedReturns: f, riskLevel: p, status: m, onClick: h, className: g }) => {
24
- let _ = l > 0 ? Math.min(u / l * 100, 100) : 0, v = c[p], y = v.icon, b = (e) => new Intl.NumberFormat(void 0, {
25
- style: "currency",
26
- currency: d,
27
- maximumFractionDigits: 0
28
- }).format(e);
29
- return /* @__PURE__ */ o(s, {
30
- role: "button",
31
- tabIndex: 0,
32
- onClick: () => h?.(n),
33
- onKeyDown: (e) => {
34
- (e.key === "Enter" || e.key === " ") && (e.preventDefault(), h?.(n));
35
- },
36
- className: e("p-4 cursor-pointer", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]", g),
37
- children: [
38
- /* @__PURE__ */ o("div", {
39
- className: "flex items-start justify-between",
40
- children: [/* @__PURE__ */ a("h3", {
41
- className: "text-base font-semibold text-text-primary truncate",
42
- children: r
43
- }), /* @__PURE__ */ a(t, { status: m })]
44
- }),
45
- /* @__PURE__ */ o("div", {
46
- className: "mt-4",
47
- children: [
48
- /* @__PURE__ */ o("div", {
49
- className: "flex items-center justify-between text-sm",
50
- children: [/* @__PURE__ */ a("span", {
51
- className: "text-text-secondary",
52
- children: "Raised"
53
- }), /* @__PURE__ */ o("span", {
54
- className: "font-medium text-text-primary tabular-nums",
55
- children: [Math.round(_), "%"]
56
- })]
57
- }),
58
- /* @__PURE__ */ a("div", {
59
- className: "mt-1.5 h-2 w-full rounded-full bg-bg-sunken overflow-hidden",
60
- children: /* @__PURE__ */ a("div", {
61
- className: "h-full rounded-full bg-action-primary-bg transition-all duration-300",
62
- style: { width: `${_}%` }
63
- })
64
- }),
65
- /* @__PURE__ */ o("div", {
66
- className: "mt-1.5 flex items-center justify-between text-xs text-text-secondary tabular-nums",
67
- children: [/* @__PURE__ */ a("span", { children: b(u) }), /* @__PURE__ */ a("span", { children: b(l) })]
68
- })
69
- ]
70
- }),
71
- /* @__PURE__ */ o("div", {
72
- className: "mt-4 flex items-center justify-between",
73
- children: [/* @__PURE__ */ o("div", {
74
- className: "flex items-center gap-1.5 text-sm",
75
- children: [
76
- /* @__PURE__ */ a(i, { className: "h-3.5 w-3.5 text-status-success-text" }),
77
- /* @__PURE__ */ a("span", {
78
- className: "font-medium text-text-primary",
79
- children: f
80
- }),
81
- /* @__PURE__ */ a("span", {
82
- className: "text-text-secondary",
83
- children: "returns"
84
- })
85
- ]
86
- }), /* @__PURE__ */ o("div", {
87
- className: e("flex items-center gap-1 text-xs font-medium", v.color),
88
- children: [/* @__PURE__ */ a(y, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ a("span", { children: v.label })]
89
- })]
90
- })
91
- ]
92
- });
93
- };
94
- //#endregion
95
- export { l as InvestmentCard };
96
-
97
- //# sourceMappingURL=InvestmentCard.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"InvestmentCard.js","names":[],"sources":["../../../src/subscriberbot/components/InvestmentCard.tsx"],"sourcesContent":["/**\n * Investment Card Component\n * @module subscriberbot/components\n *\n * Displays an investment opportunity with cart name, target/raised amounts,\n * progress bar, expected returns, risk level, and status.\n */\n\nimport type { FC } from 'react';\nimport { TrendingUp, AlertTriangle, Shield } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { StatusBadge } from './StatusBadge';\nimport type { InvestmentStatus, RiskLevel } from '../types';\n\ninterface InvestmentCardProps {\n id: string;\n cartName: string;\n targetAmount: number;\n raisedAmount: number;\n currency?: string;\n expectedReturns: string;\n riskLevel: RiskLevel;\n status: InvestmentStatus;\n onClick?: (id: string) => void;\n className?: string;\n}\n\nconst riskConfig: Record<RiskLevel, { label: string; icon: typeof Shield; color: string }> = {\n low: {\n label: 'Low Risk',\n icon: Shield,\n color: 'text-status-success-text',\n },\n medium: {\n label: 'Medium Risk',\n icon: AlertTriangle,\n color: 'text-status-warning-text',\n },\n high: {\n label: 'High Risk',\n icon: AlertTriangle,\n color: 'text-status-error-text',\n },\n};\n\nexport const InvestmentCard: FC<InvestmentCardProps> = ({\n id,\n cartName,\n targetAmount,\n raisedAmount,\n currency = 'INR',\n expectedReturns,\n riskLevel,\n status,\n onClick,\n className,\n}) => {\n const progress = targetAmount > 0 ? Math.min((raisedAmount / targetAmount) * 100, 100) : 0;\n const risk = riskConfig[riskLevel];\n const RiskIcon = risk.icon;\n\n const formatAmount = (amount: number) =>\n new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency,\n maximumFractionDigits: 0,\n }).format(amount);\n\n return (\n <GlassCard\n role=\"button\"\n tabIndex={0}\n onClick={() => onClick?.(id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.(id);\n }\n }}\n className={cn(\n 'p-4 cursor-pointer',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n className\n )}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between\">\n <h3 className=\"text-base font-semibold text-text-primary truncate\">{cartName}</h3>\n <StatusBadge status={status} />\n </div>\n\n {/* Progress Bar */}\n <div className=\"mt-4\">\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">Raised</span>\n <span className=\"font-medium text-text-primary tabular-nums\">\n {Math.round(progress)}%\n </span>\n </div>\n <div className=\"mt-1.5 h-2 w-full rounded-full bg-bg-sunken overflow-hidden\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg transition-all duration-300\"\n style={{ width: `${progress}%` }}\n />\n </div>\n <div className=\"mt-1.5 flex items-center justify-between text-xs text-text-secondary tabular-nums\">\n <span>{formatAmount(raisedAmount)}</span>\n <span>{formatAmount(targetAmount)}</span>\n </div>\n </div>\n\n {/* Details */}\n <div className=\"mt-4 flex items-center justify-between\">\n <div className=\"flex items-center gap-1.5 text-sm\">\n <TrendingUp className=\"h-3.5 w-3.5 text-status-success-text\" />\n <span className=\"font-medium text-text-primary\">{expectedReturns}</span>\n <span className=\"text-text-secondary\">returns</span>\n </div>\n <div className={cn('flex items-center gap-1 text-xs font-medium', risk.color)}>\n <RiskIcon className=\"h-3.5 w-3.5\" />\n <span>{risk.label}</span>\n </div>\n </div>\n </GlassCard>\n );\n};\n"],"mappings":";;;;;;AA4BA,IAAM,IAAuF;CAC3F,KAAK;EACH,OAAO;EACP,MAAM;EACN,OAAO;EACR;CACD,QAAQ;EACN,OAAO;EACP,MAAM;EACN,OAAO;EACR;CACD,MAAM;EACJ,OAAO;EACP,MAAM;EACN,OAAO;EACR;CACF,EAEY,KAA2C,EACtD,OACA,aACA,iBACA,iBACA,cAAW,OACX,oBACA,cACA,WACA,YACA,mBACI;CACJ,IAAM,IAAW,IAAe,IAAI,KAAK,IAAK,IAAe,IAAgB,KAAK,IAAI,GAAG,GACnF,IAAO,EAAW,IAClB,IAAW,EAAK,MAEhB,KAAgB,MACpB,IAAI,KAAK,aAAa,KAAA,GAAW;EAC/B,OAAO;EACP;EACA,uBAAuB;EACxB,CAAC,CAAC,OAAO,EAAO;AAEnB,QACE,kBAAC,GAAD;EACE,MAAK;EACL,UAAU;EACV,eAAe,IAAU,EAAG;EAC5B,YAAY,MAAM;AAChB,IAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EAClB,IAAU,EAAG;;EAGjB,WAAW,EACT,sBACA,gGACA,EACD;YAdH;GAiBE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAAsD;KAAc,CAAA,EAClF,kBAAC,GAAD,EAAqB,WAAU,CAAA,CAC3B;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBAAsB;OAAa,CAAA,EACnD,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CACG,KAAK,MAAM,EAAS,EAAC,IACjB;SACH;;KACN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,OAAD;OACE,WAAU;OACV,OAAO,EAAE,OAAO,GAAG,EAAS,IAAI;OAChC,CAAA;MACE,CAAA;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD,EAAA,UAAO,EAAa,EAAa,EAAQ,CAAA,EACzC,kBAAC,QAAD,EAAA,UAAO,EAAa,EAAa,EAAQ,CAAA,CACrC;;KACF;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD,EAAY,WAAU,wCAAyC,CAAA;MAC/D,kBAAC,QAAD;OAAM,WAAU;iBAAiC;OAAuB,CAAA;MACxE,kBAAC,QAAD;OAAM,WAAU;iBAAsB;OAAc,CAAA;MAChD;QACN,kBAAC,OAAD;KAAK,WAAW,EAAG,+CAA+C,EAAK,MAAM;eAA7E,CACE,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA,EACpC,kBAAC,QAAD,EAAA,UAAO,EAAK,OAAa,CAAA,CACrB;OACF;;GACI"}