@burdenoff/microfe-billing 2026.529.1 → 2026.530.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js +27 -25
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js +9 -8
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js +7 -6
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +9 -7
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +7 -0
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,24 +4,25 @@ import { AlertTriangle as r, ArrowRight as i, Calendar as a, Clock as o, Coins a
|
|
|
4
4
|
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
5
5
|
import { useI18n as m } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
6
6
|
//#region src/billing/modules/dashboard/components/ActiveSubscriptionCard.tsx
|
|
7
|
-
var h = ({ subscription: h, onViewDetails: g, onUpgrade: _, onManage: v, onPurchasePlan: y, className: b = "" }) => {
|
|
8
|
-
let { t:
|
|
9
|
-
let n =
|
|
7
|
+
var h = ({ subscription: h, onViewDetails: g, onUpgrade: _, onManage: v, onPurchasePlan: y, className: b = "", dataTour: x }) => {
|
|
8
|
+
let { t: S } = m(), C = (e, t) => {
|
|
9
|
+
let n = S(e);
|
|
10
10
|
return n === e ? t : n;
|
|
11
|
-
},
|
|
11
|
+
}, w = (e) => e ? new Date(e).toLocaleDateString("en-US", {
|
|
12
12
|
year: "numeric",
|
|
13
13
|
month: "long",
|
|
14
14
|
day: "numeric"
|
|
15
|
-
}) : "N/A",
|
|
15
|
+
}) : "N/A", T = (e, t) => new Intl.NumberFormat("en-US", {
|
|
16
16
|
style: "currency",
|
|
17
17
|
currency: t || "USD",
|
|
18
18
|
minimumFractionDigits: 2
|
|
19
|
-
}).format(e),
|
|
19
|
+
}).format(e), E = n(() => {
|
|
20
20
|
if (!h?.endDate) return null;
|
|
21
21
|
let e = new Date(h.endDate), t = /* @__PURE__ */ new Date();
|
|
22
22
|
return Math.ceil((e.getTime() - t.getTime()) / (1e3 * 60 * 60 * 24));
|
|
23
|
-
}, [h?.endDate]),
|
|
23
|
+
}, [h?.endDate]), D = E !== null && E <= 7 && E > 0, O = E !== null && E <= 0, k = () => O ? `${t.error.text} ${t.error.bg}` : D ? `${t.warning.text} ${t.warning.bg}` : `${t.success.text} ${t.success.bg}`, A = () => O ? "Expired" : D ? "Expiring Soon" : "Active";
|
|
24
24
|
if (!h) return /* @__PURE__ */ f("section", {
|
|
25
|
+
"data-tour": x,
|
|
25
26
|
className: `border border-border rounded-lg bg-card p-6 ${b}`,
|
|
26
27
|
children: /* @__PURE__ */ p("div", {
|
|
27
28
|
className: "flex flex-col items-center text-center py-4",
|
|
@@ -32,7 +33,7 @@ var h = ({ subscription: h, onViewDetails: g, onUpgrade: _, onManage: v, onPurch
|
|
|
32
33
|
}),
|
|
33
34
|
/* @__PURE__ */ f("h3", {
|
|
34
35
|
className: "text-lg font-semibold text-foreground mb-2",
|
|
35
|
-
children:
|
|
36
|
+
children: C("billing.overview.noActiveSubscription", "No Active Subscription")
|
|
36
37
|
}),
|
|
37
38
|
/* @__PURE__ */ f("p", {
|
|
38
39
|
className: "text-sm text-muted-foreground mb-6 max-w-sm",
|
|
@@ -47,11 +48,12 @@ var h = ({ subscription: h, onViewDetails: g, onUpgrade: _, onManage: v, onPurch
|
|
|
47
48
|
]
|
|
48
49
|
})
|
|
49
50
|
});
|
|
50
|
-
let
|
|
51
|
+
let j = h.plan;
|
|
51
52
|
return /* @__PURE__ */ p("section", {
|
|
53
|
+
"data-tour": x,
|
|
52
54
|
className: `border border-border rounded-lg bg-card overflow-hidden ${b}`,
|
|
53
55
|
children: [
|
|
54
|
-
|
|
56
|
+
D && /* @__PURE__ */ f("div", {
|
|
55
57
|
className: `px-4 py-2 ${e.warning.container} border-b`,
|
|
56
58
|
children: /* @__PURE__ */ p("div", {
|
|
57
59
|
className: `flex items-center gap-2 ${e.warning.text}`,
|
|
@@ -59,9 +61,9 @@ var h = ({ subscription: h, onViewDetails: g, onUpgrade: _, onManage: v, onPurch
|
|
|
59
61
|
className: "text-sm font-medium",
|
|
60
62
|
children: [
|
|
61
63
|
"Your subscription expires in ",
|
|
62
|
-
|
|
64
|
+
E,
|
|
63
65
|
" day",
|
|
64
|
-
|
|
66
|
+
E === 1 ? "" : "s"
|
|
65
67
|
]
|
|
66
68
|
})]
|
|
67
69
|
})
|
|
@@ -79,17 +81,17 @@ var h = ({ subscription: h, onViewDetails: g, onUpgrade: _, onManage: v, onPurch
|
|
|
79
81
|
className: "flex items-center gap-2 flex-wrap",
|
|
80
82
|
children: [/* @__PURE__ */ f("h3", {
|
|
81
83
|
className: "text-lg font-semibold text-foreground",
|
|
82
|
-
children:
|
|
84
|
+
children: j?.name || "Subscription"
|
|
83
85
|
}), /* @__PURE__ */ f("span", {
|
|
84
|
-
className: `inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${
|
|
85
|
-
children:
|
|
86
|
+
className: `inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${k()}`,
|
|
87
|
+
children: A()
|
|
86
88
|
})]
|
|
87
89
|
}), /* @__PURE__ */ p("p", {
|
|
88
90
|
className: "text-sm text-muted-foreground mt-1",
|
|
89
91
|
children: [
|
|
90
|
-
|
|
92
|
+
j && T(j.price, j.currency),
|
|
91
93
|
" / ",
|
|
92
|
-
|
|
94
|
+
j?.duration || "month"
|
|
93
95
|
]
|
|
94
96
|
})] })]
|
|
95
97
|
}), /* @__PURE__ */ p("div", {
|
|
@@ -99,7 +101,7 @@ var h = ({ subscription: h, onViewDetails: g, onUpgrade: _, onManage: v, onPurch
|
|
|
99
101
|
onClick: v,
|
|
100
102
|
className: "px-4 py-2 text-sm font-medium text-foreground border border-border rounded-lg hover:bg-muted transition-colors",
|
|
101
103
|
children: "Manage"
|
|
102
|
-
}), _ && !
|
|
104
|
+
}), _ && !O && /* @__PURE__ */ f("button", {
|
|
103
105
|
type: "button",
|
|
104
106
|
onClick: _,
|
|
105
107
|
className: "px-4 py-2 text-sm font-medium text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors",
|
|
@@ -120,17 +122,17 @@ var h = ({ subscription: h, onViewDetails: g, onUpgrade: _, onManage: v, onPurch
|
|
|
120
122
|
children: "Started"
|
|
121
123
|
}), /* @__PURE__ */ p("p", {
|
|
122
124
|
className: "text-sm font-medium text-foreground flex items-center gap-2",
|
|
123
|
-
children: [/* @__PURE__ */ f(a, { className: "size-4 text-muted-foreground" }),
|
|
125
|
+
children: [/* @__PURE__ */ f(a, { className: "size-4 text-muted-foreground" }), w(h.startDate)]
|
|
124
126
|
})]
|
|
125
127
|
}),
|
|
126
128
|
/* @__PURE__ */ p("div", {
|
|
127
129
|
className: "space-y-1.5",
|
|
128
130
|
children: [/* @__PURE__ */ f("p", {
|
|
129
131
|
className: "text-xs font-medium text-muted-foreground uppercase tracking-wide",
|
|
130
|
-
children:
|
|
132
|
+
children: O ? "Expired" : "Ends"
|
|
131
133
|
}), /* @__PURE__ */ p("p", {
|
|
132
|
-
className: `text-sm font-medium flex items-center gap-2 ${
|
|
133
|
-
children: [/* @__PURE__ */ f(o, { className: "size-4" }),
|
|
134
|
+
className: `text-sm font-medium flex items-center gap-2 ${O ? t.error.text : D ? t.warning.text : "text-foreground"}`,
|
|
135
|
+
children: [/* @__PURE__ */ f(o, { className: "size-4" }), w(h.endDate)]
|
|
134
136
|
})]
|
|
135
137
|
}),
|
|
136
138
|
/* @__PURE__ */ p("div", {
|
|
@@ -154,19 +156,19 @@ var h = ({ subscription: h, onViewDetails: g, onUpgrade: _, onManage: v, onPurch
|
|
|
154
156
|
})]
|
|
155
157
|
})
|
|
156
158
|
]
|
|
157
|
-
}), h.nextBillingDate && h.autoRenewal && !
|
|
159
|
+
}), h.nextBillingDate && h.autoRenewal && !O && /* @__PURE__ */ f("div", {
|
|
158
160
|
className: "mt-6 p-4 rounded-lg bg-muted/50 border border-border",
|
|
159
161
|
children: /* @__PURE__ */ p("div", {
|
|
160
162
|
className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2",
|
|
161
163
|
children: [/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("p", {
|
|
162
164
|
className: "text-sm font-medium text-foreground",
|
|
163
|
-
children:
|
|
165
|
+
children: C("billing.overview.nextBillingDate", "Next billing date")
|
|
164
166
|
}), /* @__PURE__ */ f("p", {
|
|
165
167
|
className: "text-xs text-muted-foreground",
|
|
166
168
|
children: "You'll be charged automatically"
|
|
167
169
|
})] }), /* @__PURE__ */ f("p", {
|
|
168
170
|
className: "text-sm font-semibold text-foreground",
|
|
169
|
-
children:
|
|
171
|
+
children: w(h.nextBillingDate)
|
|
170
172
|
})]
|
|
171
173
|
})
|
|
172
174
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActiveSubscriptionCard.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/components/ActiveSubscriptionCard.tsx"],"sourcesContent":["/**\n * Active Subscription Card Component\n * Displays the active subscription details with renewal info\n */\n\nimport { type FC, useMemo } from 'react';\nimport {\n Crown,\n Calendar,\n ArrowRight,\n RefreshCw,\n Clock,\n CreditCard,\n Zap,\n AlertTriangle,\n Coins,\n} from 'lucide-react';\nimport type { BillingSubscription } from '../../../shared/types';\nimport { statusTokens, alertTokens } from '../../../shared/utils/tokens';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface ActiveSubscriptionCardProps {\n subscription: BillingSubscription | null;\n onViewDetails?: () => void;\n onUpgrade?: () => void;\n onManage?: () => void;\n onPurchasePlan?: () => void;\n className?: string;\n}\n\nexport const ActiveSubscriptionCard: FC<ActiveSubscriptionCardProps> = ({\n subscription,\n onViewDetails,\n onUpgrade,\n onManage,\n onPurchasePlan,\n className = '',\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const formatDate = (dateString: string | undefined) => {\n if (!dateString) return 'N/A';\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n });\n };\n\n const formatCurrency = (amount: number, currency: string) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency || 'USD',\n minimumFractionDigits: 2,\n }).format(amount);\n };\n\n const daysUntilEnd = useMemo(() => {\n if (!subscription?.endDate) return null;\n const end = new Date(subscription.endDate);\n const now = new Date();\n const diff = Math.ceil((end.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));\n return diff;\n }, [subscription?.endDate]);\n\n const isExpiringSoon = daysUntilEnd !== null && daysUntilEnd <= 7 && daysUntilEnd > 0;\n const isExpired = daysUntilEnd !== null && daysUntilEnd <= 0;\n\n const getStatusColor = () => {\n if (isExpired) return `${statusTokens.error.text} ${statusTokens.error.bg}`;\n if (isExpiringSoon) return `${statusTokens.warning.text} ${statusTokens.warning.bg}`;\n return `${statusTokens.success.text} ${statusTokens.success.bg}`;\n };\n\n const getStatusText = () => {\n if (isExpired) return 'Expired';\n if (isExpiringSoon) return 'Expiring Soon';\n return 'Active';\n };\n\n // No subscription state\n if (!subscription) {\n return (\n <section className={`border border-border rounded-lg bg-card p-6 ${className}`}>\n <div className=\"flex flex-col items-center text-center py-4\">\n <div className=\"size-16 rounded-full bg-gradient-to-br from-primary/20 to-primary/5 flex items-center justify-center mb-4\">\n <Zap className=\"size-8 text-primary\" />\n </div>\n <h3 className=\"text-lg font-semibold text-foreground mb-2\">\n {tr('billing.overview.noActiveSubscription', 'No Active Subscription')}\n </h3>\n <p className=\"text-sm text-muted-foreground mb-6 max-w-sm\">\n Subscribe to a plan to unlock premium features and get the most out of your account\n </p>\n {onPurchasePlan && (\n <button\n type=\"button\"\n onClick={onPurchasePlan}\n className=\"inline-flex items-center gap-2 px-6 py-2.5 text-sm font-medium text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors\"\n >\n Browse Plans\n <ArrowRight className=\"size-4\" />\n </button>\n )}\n </div>\n </section>\n );\n }\n\n const plan = subscription.plan;\n\n return (\n <section className={`border border-border rounded-lg bg-card overflow-hidden ${className}`}>\n {/* Expiring Soon Banner */}\n {isExpiringSoon && (\n <div className={`px-4 py-2 ${alertTokens.warning.container} border-b`}>\n <div className={`flex items-center gap-2 ${alertTokens.warning.text}`}>\n <AlertTriangle className=\"size-4\" />\n <span className=\"text-sm font-medium\">\n Your subscription expires in {daysUntilEnd} day{daysUntilEnd !== 1 ? 's' : ''}\n </span>\n </div>\n </div>\n )}\n\n {/* Header */}\n <header className=\"p-4 sm:p-6 border-b border-border\">\n <div className=\"flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4\">\n <div className=\"flex items-start gap-4\">\n <div className=\"size-12 rounded-lg bg-gradient-to-br from-primary to-primary/60 flex items-center justify-center flex-shrink-0\">\n <Crown className=\"size-6 text-primary-foreground\" />\n </div>\n <div>\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"text-lg font-semibold text-foreground\">\n {plan?.name || 'Subscription'}\n </h3>\n <span\n className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${getStatusColor()}`}\n >\n {getStatusText()}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {plan && formatCurrency(plan.price, plan.currency)} / {plan?.duration || 'month'}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {onManage && (\n <button\n type=\"button\"\n onClick={onManage}\n className=\"px-4 py-2 text-sm font-medium text-foreground border border-border rounded-lg hover:bg-muted transition-colors\"\n >\n Manage\n </button>\n )}\n {onUpgrade && !isExpired && (\n <button\n type=\"button\"\n onClick={onUpgrade}\n className=\"px-4 py-2 text-sm font-medium text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors\"\n >\n Upgrade\n </button>\n )}\n </div>\n </div>\n </header>\n\n {/* Body */}\n <div className=\"p-4 sm:p-6\">\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n {/* Start Date */}\n <div className=\"space-y-1.5\">\n <p className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n Started\n </p>\n <p className=\"text-sm font-medium text-foreground flex items-center gap-2\">\n <Calendar className=\"size-4 text-muted-foreground\" />\n {formatDate(subscription.startDate)}\n </p>\n </div>\n\n {/* End Date */}\n <div className=\"space-y-1.5\">\n <p className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n {isExpired ? 'Expired' : 'Ends'}\n </p>\n <p\n className={`text-sm font-medium flex items-center gap-2 ${isExpired ? statusTokens.error.text : isExpiringSoon ? statusTokens.warning.text : 'text-foreground'}`}\n >\n <Clock className=\"size-4\" />\n {formatDate(subscription.endDate)}\n </p>\n </div>\n\n {/* Auto Renewal */}\n <div className=\"space-y-1.5\">\n <p className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n Auto Renewal\n </p>\n <p className=\"text-sm font-medium text-foreground flex items-center gap-2\">\n <RefreshCw\n className={`size-4 ${subscription.autoRenewal ? statusTokens.success.icon : 'text-muted-foreground'}`}\n />\n {subscription.autoRenewal ? 'Enabled' : 'Disabled'}\n </p>\n </div>\n\n {/* Payment Gateway */}\n <div className=\"space-y-1.5\">\n <p className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n Payment Via\n </p>\n <p className=\"text-sm font-medium text-foreground flex items-center gap-2\">\n {subscription.paymentGateway === 'CREDITS' ? (\n <Coins className={`size-4 ${statusTokens.warning.icon}`} />\n ) : (\n <CreditCard className=\"size-4 text-muted-foreground\" />\n )}\n {subscription.paymentGateway === 'CREDITS'\n ? 'Credits'\n : subscription.paymentGateway === 'STRIPE'\n ? 'Stripe'\n : subscription.paymentGateway === 'RAZORPAY'\n ? 'Razorpay'\n : subscription.paymentGateway || 'N/A'}\n </p>\n </div>\n </div>\n\n {/* Next Billing */}\n {subscription.nextBillingDate && subscription.autoRenewal && !isExpired && (\n <div className=\"mt-6 p-4 rounded-lg bg-muted/50 border border-border\">\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2\">\n <div>\n <p className=\"text-sm font-medium text-foreground\">\n {tr('billing.overview.nextBillingDate', 'Next billing date')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n You'll be charged automatically\n </p>\n </div>\n <p className=\"text-sm font-semibold text-foreground\">\n {formatDate(subscription.nextBillingDate)}\n </p>\n </div>\n </div>\n )}\n </div>\n\n {/* Footer */}\n {onViewDetails && (\n <footer className=\"px-4 sm:px-6 py-3 border-t border-border bg-muted/30\">\n <button\n type=\"button\"\n onClick={onViewDetails}\n className=\"w-full flex items-center justify-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors\"\n >\n View Subscription Details\n <ArrowRight className=\"size-4\" />\n </button>\n </footer>\n )}\n </section>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ActiveSubscriptionCard.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/components/ActiveSubscriptionCard.tsx"],"sourcesContent":["/**\n * Active Subscription Card Component\n * Displays the active subscription details with renewal info\n */\n\nimport { type FC, useMemo } from 'react';\nimport {\n Crown,\n Calendar,\n ArrowRight,\n RefreshCw,\n Clock,\n CreditCard,\n Zap,\n AlertTriangle,\n Coins,\n} from 'lucide-react';\nimport type { BillingSubscription } from '../../../shared/types';\nimport { statusTokens, alertTokens } from '../../../shared/utils/tokens';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface ActiveSubscriptionCardProps {\n subscription: BillingSubscription | null;\n onViewDetails?: () => void;\n onUpgrade?: () => void;\n onManage?: () => void;\n onPurchasePlan?: () => void;\n className?: string;\n /** Optional tour anchor applied to the section root (for onboarding tours). */\n dataTour?: string;\n}\n\nexport const ActiveSubscriptionCard: FC<ActiveSubscriptionCardProps> = ({\n subscription,\n onViewDetails,\n onUpgrade,\n onManage,\n onPurchasePlan,\n className = '',\n dataTour,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const formatDate = (dateString: string | undefined) => {\n if (!dateString) return 'N/A';\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n });\n };\n\n const formatCurrency = (amount: number, currency: string) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency || 'USD',\n minimumFractionDigits: 2,\n }).format(amount);\n };\n\n const daysUntilEnd = useMemo(() => {\n if (!subscription?.endDate) return null;\n const end = new Date(subscription.endDate);\n const now = new Date();\n const diff = Math.ceil((end.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));\n return diff;\n }, [subscription?.endDate]);\n\n const isExpiringSoon = daysUntilEnd !== null && daysUntilEnd <= 7 && daysUntilEnd > 0;\n const isExpired = daysUntilEnd !== null && daysUntilEnd <= 0;\n\n const getStatusColor = () => {\n if (isExpired) return `${statusTokens.error.text} ${statusTokens.error.bg}`;\n if (isExpiringSoon) return `${statusTokens.warning.text} ${statusTokens.warning.bg}`;\n return `${statusTokens.success.text} ${statusTokens.success.bg}`;\n };\n\n const getStatusText = () => {\n if (isExpired) return 'Expired';\n if (isExpiringSoon) return 'Expiring Soon';\n return 'Active';\n };\n\n // No subscription state\n if (!subscription) {\n return (\n <section\n data-tour={dataTour}\n className={`border border-border rounded-lg bg-card p-6 ${className}`}\n >\n <div className=\"flex flex-col items-center text-center py-4\">\n <div className=\"size-16 rounded-full bg-gradient-to-br from-primary/20 to-primary/5 flex items-center justify-center mb-4\">\n <Zap className=\"size-8 text-primary\" />\n </div>\n <h3 className=\"text-lg font-semibold text-foreground mb-2\">\n {tr('billing.overview.noActiveSubscription', 'No Active Subscription')}\n </h3>\n <p className=\"text-sm text-muted-foreground mb-6 max-w-sm\">\n Subscribe to a plan to unlock premium features and get the most out of your account\n </p>\n {onPurchasePlan && (\n <button\n type=\"button\"\n onClick={onPurchasePlan}\n className=\"inline-flex items-center gap-2 px-6 py-2.5 text-sm font-medium text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors\"\n >\n Browse Plans\n <ArrowRight className=\"size-4\" />\n </button>\n )}\n </div>\n </section>\n );\n }\n\n const plan = subscription.plan;\n\n return (\n <section\n data-tour={dataTour}\n className={`border border-border rounded-lg bg-card overflow-hidden ${className}`}\n >\n {/* Expiring Soon Banner */}\n {isExpiringSoon && (\n <div className={`px-4 py-2 ${alertTokens.warning.container} border-b`}>\n <div className={`flex items-center gap-2 ${alertTokens.warning.text}`}>\n <AlertTriangle className=\"size-4\" />\n <span className=\"text-sm font-medium\">\n Your subscription expires in {daysUntilEnd} day{daysUntilEnd !== 1 ? 's' : ''}\n </span>\n </div>\n </div>\n )}\n\n {/* Header */}\n <header className=\"p-4 sm:p-6 border-b border-border\">\n <div className=\"flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4\">\n <div className=\"flex items-start gap-4\">\n <div className=\"size-12 rounded-lg bg-gradient-to-br from-primary to-primary/60 flex items-center justify-center flex-shrink-0\">\n <Crown className=\"size-6 text-primary-foreground\" />\n </div>\n <div>\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"text-lg font-semibold text-foreground\">\n {plan?.name || 'Subscription'}\n </h3>\n <span\n className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${getStatusColor()}`}\n >\n {getStatusText()}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {plan && formatCurrency(plan.price, plan.currency)} / {plan?.duration || 'month'}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {onManage && (\n <button\n type=\"button\"\n onClick={onManage}\n className=\"px-4 py-2 text-sm font-medium text-foreground border border-border rounded-lg hover:bg-muted transition-colors\"\n >\n Manage\n </button>\n )}\n {onUpgrade && !isExpired && (\n <button\n type=\"button\"\n onClick={onUpgrade}\n className=\"px-4 py-2 text-sm font-medium text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors\"\n >\n Upgrade\n </button>\n )}\n </div>\n </div>\n </header>\n\n {/* Body */}\n <div className=\"p-4 sm:p-6\">\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n {/* Start Date */}\n <div className=\"space-y-1.5\">\n <p className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n Started\n </p>\n <p className=\"text-sm font-medium text-foreground flex items-center gap-2\">\n <Calendar className=\"size-4 text-muted-foreground\" />\n {formatDate(subscription.startDate)}\n </p>\n </div>\n\n {/* End Date */}\n <div className=\"space-y-1.5\">\n <p className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n {isExpired ? 'Expired' : 'Ends'}\n </p>\n <p\n className={`text-sm font-medium flex items-center gap-2 ${isExpired ? statusTokens.error.text : isExpiringSoon ? statusTokens.warning.text : 'text-foreground'}`}\n >\n <Clock className=\"size-4\" />\n {formatDate(subscription.endDate)}\n </p>\n </div>\n\n {/* Auto Renewal */}\n <div className=\"space-y-1.5\">\n <p className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n Auto Renewal\n </p>\n <p className=\"text-sm font-medium text-foreground flex items-center gap-2\">\n <RefreshCw\n className={`size-4 ${subscription.autoRenewal ? statusTokens.success.icon : 'text-muted-foreground'}`}\n />\n {subscription.autoRenewal ? 'Enabled' : 'Disabled'}\n </p>\n </div>\n\n {/* Payment Gateway */}\n <div className=\"space-y-1.5\">\n <p className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n Payment Via\n </p>\n <p className=\"text-sm font-medium text-foreground flex items-center gap-2\">\n {subscription.paymentGateway === 'CREDITS' ? (\n <Coins className={`size-4 ${statusTokens.warning.icon}`} />\n ) : (\n <CreditCard className=\"size-4 text-muted-foreground\" />\n )}\n {subscription.paymentGateway === 'CREDITS'\n ? 'Credits'\n : subscription.paymentGateway === 'STRIPE'\n ? 'Stripe'\n : subscription.paymentGateway === 'RAZORPAY'\n ? 'Razorpay'\n : subscription.paymentGateway || 'N/A'}\n </p>\n </div>\n </div>\n\n {/* Next Billing */}\n {subscription.nextBillingDate && subscription.autoRenewal && !isExpired && (\n <div className=\"mt-6 p-4 rounded-lg bg-muted/50 border border-border\">\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2\">\n <div>\n <p className=\"text-sm font-medium text-foreground\">\n {tr('billing.overview.nextBillingDate', 'Next billing date')}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n You'll be charged automatically\n </p>\n </div>\n <p className=\"text-sm font-semibold text-foreground\">\n {formatDate(subscription.nextBillingDate)}\n </p>\n </div>\n </div>\n )}\n </div>\n\n {/* Footer */}\n {onViewDetails && (\n <footer className=\"px-4 sm:px-6 py-3 border-t border-border bg-muted/30\">\n <button\n type=\"button\"\n onClick={onViewDetails}\n className=\"w-full flex items-center justify-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors\"\n >\n View Subscription Details\n <ArrowRight className=\"size-4\" />\n </button>\n </footer>\n )}\n </section>\n );\n};\n"],"mappings":"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"}
|
|
@@ -4,21 +4,22 @@ import { Building2 as n, Calendar as r, Check as i, ChevronRight as a, Coins as
|
|
|
4
4
|
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
5
5
|
import { useI18n as f } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
6
6
|
//#region src/billing/modules/dashboard/components/BillingAccountCard.tsx
|
|
7
|
-
var p = ({ account: p, isDefault: m = !1, isSelected: h = !1, onSelect: g, onEdit: _, onViewTransactions: v, onViewCreditTransactions: y, className: b = "" }) => {
|
|
8
|
-
let { t:
|
|
9
|
-
let n =
|
|
7
|
+
var p = ({ account: p, isDefault: m = !1, isSelected: h = !1, onSelect: g, onEdit: _, onViewTransactions: v, onViewCreditTransactions: y, className: b = "", dataTour: x }) => {
|
|
8
|
+
let { t: S } = f(), C = (e, t) => {
|
|
9
|
+
let n = S(e);
|
|
10
10
|
return n === e ? t : n;
|
|
11
|
-
}, [
|
|
11
|
+
}, [w, T] = t(!1);
|
|
12
12
|
return /* @__PURE__ */ d("article", {
|
|
13
|
+
"data-tour": x,
|
|
13
14
|
className: `
|
|
14
15
|
relative border rounded-lg bg-card transition-all duration-200
|
|
15
16
|
${h ? "border-primary ring-2 ring-primary/20" : "border-border"}
|
|
16
|
-
${
|
|
17
|
+
${w ? "shadow-md" : "shadow-sm"}
|
|
17
18
|
${g ? "cursor-pointer" : ""}
|
|
18
19
|
${b}
|
|
19
20
|
`,
|
|
20
|
-
onMouseEnter: () =>
|
|
21
|
-
onMouseLeave: () =>
|
|
21
|
+
onMouseEnter: () => T(!0),
|
|
22
|
+
onMouseLeave: () => T(!1),
|
|
22
23
|
onClick: g,
|
|
23
24
|
role: g ? "button" : "article",
|
|
24
25
|
tabIndex: g ? 0 : void 0,
|
|
@@ -46,7 +47,7 @@ var p = ({ account: p, isDefault: m = !1, isSelected: h = !1, onSelect: g, onEdi
|
|
|
46
47
|
}),
|
|
47
48
|
m && /* @__PURE__ */ u("span", {
|
|
48
49
|
className: "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-primary/10 text-primary",
|
|
49
|
-
children:
|
|
50
|
+
children: C("billing.overview.default", "Default")
|
|
50
51
|
}),
|
|
51
52
|
h && /* @__PURE__ */ u("span", {
|
|
52
53
|
className: "inline-flex items-center justify-center size-5 rounded-full bg-primary",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BillingAccountCard.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/components/BillingAccountCard.tsx"],"sourcesContent":["/**\n * Billing Account Card Component\n * Displays billing account details with actions\n */\n\nimport { type FC, useState } from 'react';\nimport {\n Building2,\n Mail,\n CreditCard,\n Edit2,\n ChevronRight,\n Coins,\n Check,\n Calendar,\n} from 'lucide-react';\nimport type { BillingAccount } from '../../../shared/types';\nimport { statusTokens } from '../../../shared/utils/tokens';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface BillingAccountCardProps {\n account: BillingAccount;\n isDefault?: boolean;\n isSelected?: boolean;\n onSelect?: () => void;\n onEdit?: () => void;\n onViewTransactions?: () => void;\n onViewCreditTransactions?: () => void;\n className?: string;\n}\n\nexport const BillingAccountCard: FC<BillingAccountCardProps> = ({\n account,\n isDefault = false,\n isSelected = false,\n onSelect,\n onEdit,\n onViewTransactions,\n onViewCreditTransactions,\n className = '',\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const [isHovered, setIsHovered] = useState(false);\n\n const formatDate = (dateString: string) => {\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n };\n\n return (\n <article\n className={`\n relative border rounded-lg bg-card transition-all duration-200\n ${isSelected ? 'border-primary ring-2 ring-primary/20' : 'border-border'}\n ${isHovered ? 'shadow-md' : 'shadow-sm'}\n ${onSelect ? 'cursor-pointer' : ''}\n ${className}\n `}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n onClick={onSelect}\n role={onSelect ? 'button' : 'article'}\n tabIndex={onSelect ? 0 : undefined}\n onKeyDown={(e) => {\n if (onSelect && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n onSelect();\n }\n }}\n >\n {/* Header */}\n <header className=\"p-4 border-b border-border\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-10 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0\">\n <Building2 className=\"size-5 text-primary\" />\n </div>\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-foreground truncate\">{account.name}</h3>\n {isDefault && (\n <span className=\"inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-primary/10 text-primary\">\n {tr('billing.overview.default', 'Default')}\n </span>\n )}\n {isSelected && (\n <span className=\"inline-flex items-center justify-center size-5 rounded-full bg-primary\">\n <Check className=\"size-3 text-primary-foreground\" />\n </span>\n )}\n </div>\n <p className=\"text-sm text-muted-foreground truncate flex items-center gap-1\">\n <Mail className=\"size-3.5\" />\n {account.email}\n </p>\n </div>\n </div>\n {onEdit && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onEdit();\n }}\n className=\"p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors\"\n aria-label=\"Edit billing account\"\n >\n <Edit2 className=\"size-4\" />\n </button>\n )}\n </div>\n </header>\n\n {/* Body */}\n <div className=\"p-4 space-y-4\">\n {/* Stats Grid */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-1\">\n <p className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n Credit Balance\n </p>\n <p className=\"text-lg font-semibold text-foreground flex items-center gap-1.5\">\n <Coins className={`size-4 ${statusTokens.warning.icon}`} />\n {(account.creditAmount || 0).toLocaleString()} Credits\n </p>\n </div>\n <div className=\"space-y-1\">\n <p className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n Payment Currency\n </p>\n <p className=\"text-lg font-semibold text-foreground flex items-center gap-1.5\">\n <CreditCard className=\"size-4 text-primary\" />\n {account.currency || 'USD'}\n </p>\n </div>\n </div>\n\n {/* Meta Info */}\n <div className=\"pt-3 border-t border-border\">\n <p className=\"text-xs text-muted-foreground flex items-center gap-1\">\n <Calendar className=\"size-3.5\" />\n Created {formatDate(account.createdAt)}\n </p>\n </div>\n </div>\n\n {/* Footer Actions */}\n {(onViewTransactions || onViewCreditTransactions) && (\n <footer className=\"px-4 py-3 border-t border-border bg-muted/30 rounded-b-lg\">\n <div className=\"flex items-center gap-2\">\n {onViewTransactions && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onViewTransactions();\n }}\n className=\"flex-1 flex items-center justify-center gap-1.5 px-3 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors\"\n >\n Transactions\n <ChevronRight className=\"size-4\" />\n </button>\n )}\n {onViewCreditTransactions && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onViewCreditTransactions();\n }}\n className=\"flex-1 flex items-center justify-center gap-1.5 px-3 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors\"\n >\n Credit History\n <ChevronRight className=\"size-4\" />\n </button>\n )}\n </div>\n </footer>\n )}\n\n {/* Status indicator */}\n {!account.isActive && (\n <div className=\"absolute inset-0 bg-background/60 backdrop-blur-sm rounded-lg flex items-center justify-center\">\n <span className=\"px-3 py-1.5 rounded-full bg-destructive/10 text-destructive text-sm font-medium\">\n Inactive\n </span>\n </div>\n )}\n </article>\n );\n};\n"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"BillingAccountCard.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/components/BillingAccountCard.tsx"],"sourcesContent":["/**\n * Billing Account Card Component\n * Displays billing account details with actions\n */\n\nimport { type FC, useState } from 'react';\nimport {\n Building2,\n Mail,\n CreditCard,\n Edit2,\n ChevronRight,\n Coins,\n Check,\n Calendar,\n} from 'lucide-react';\nimport type { BillingAccount } from '../../../shared/types';\nimport { statusTokens } from '../../../shared/utils/tokens';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface BillingAccountCardProps {\n account: BillingAccount;\n isDefault?: boolean;\n isSelected?: boolean;\n onSelect?: () => void;\n onEdit?: () => void;\n onViewTransactions?: () => void;\n onViewCreditTransactions?: () => void;\n className?: string;\n /** Optional tour anchor applied to the card root (for onboarding tours). */\n dataTour?: string;\n}\n\nexport const BillingAccountCard: FC<BillingAccountCardProps> = ({\n account,\n isDefault = false,\n isSelected = false,\n onSelect,\n onEdit,\n onViewTransactions,\n onViewCreditTransactions,\n className = '',\n dataTour,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const [isHovered, setIsHovered] = useState(false);\n\n const formatDate = (dateString: string) => {\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n };\n\n return (\n <article\n data-tour={dataTour}\n className={`\n relative border rounded-lg bg-card transition-all duration-200\n ${isSelected ? 'border-primary ring-2 ring-primary/20' : 'border-border'}\n ${isHovered ? 'shadow-md' : 'shadow-sm'}\n ${onSelect ? 'cursor-pointer' : ''}\n ${className}\n `}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n onClick={onSelect}\n role={onSelect ? 'button' : 'article'}\n tabIndex={onSelect ? 0 : undefined}\n onKeyDown={(e) => {\n if (onSelect && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n onSelect();\n }\n }}\n >\n {/* Header */}\n <header className=\"p-4 border-b border-border\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-10 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0\">\n <Building2 className=\"size-5 text-primary\" />\n </div>\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-foreground truncate\">{account.name}</h3>\n {isDefault && (\n <span className=\"inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-primary/10 text-primary\">\n {tr('billing.overview.default', 'Default')}\n </span>\n )}\n {isSelected && (\n <span className=\"inline-flex items-center justify-center size-5 rounded-full bg-primary\">\n <Check className=\"size-3 text-primary-foreground\" />\n </span>\n )}\n </div>\n <p className=\"text-sm text-muted-foreground truncate flex items-center gap-1\">\n <Mail className=\"size-3.5\" />\n {account.email}\n </p>\n </div>\n </div>\n {onEdit && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onEdit();\n }}\n className=\"p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors\"\n aria-label=\"Edit billing account\"\n >\n <Edit2 className=\"size-4\" />\n </button>\n )}\n </div>\n </header>\n\n {/* Body */}\n <div className=\"p-4 space-y-4\">\n {/* Stats Grid */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-1\">\n <p className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n Credit Balance\n </p>\n <p className=\"text-lg font-semibold text-foreground flex items-center gap-1.5\">\n <Coins className={`size-4 ${statusTokens.warning.icon}`} />\n {(account.creditAmount || 0).toLocaleString()} Credits\n </p>\n </div>\n <div className=\"space-y-1\">\n <p className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n Payment Currency\n </p>\n <p className=\"text-lg font-semibold text-foreground flex items-center gap-1.5\">\n <CreditCard className=\"size-4 text-primary\" />\n {account.currency || 'USD'}\n </p>\n </div>\n </div>\n\n {/* Meta Info */}\n <div className=\"pt-3 border-t border-border\">\n <p className=\"text-xs text-muted-foreground flex items-center gap-1\">\n <Calendar className=\"size-3.5\" />\n Created {formatDate(account.createdAt)}\n </p>\n </div>\n </div>\n\n {/* Footer Actions */}\n {(onViewTransactions || onViewCreditTransactions) && (\n <footer className=\"px-4 py-3 border-t border-border bg-muted/30 rounded-b-lg\">\n <div className=\"flex items-center gap-2\">\n {onViewTransactions && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onViewTransactions();\n }}\n className=\"flex-1 flex items-center justify-center gap-1.5 px-3 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors\"\n >\n Transactions\n <ChevronRight className=\"size-4\" />\n </button>\n )}\n {onViewCreditTransactions && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n onViewCreditTransactions();\n }}\n className=\"flex-1 flex items-center justify-center gap-1.5 px-3 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors\"\n >\n Credit History\n <ChevronRight className=\"size-4\" />\n </button>\n )}\n </div>\n </footer>\n )}\n\n {/* Status indicator */}\n {!account.isActive && (\n <div className=\"absolute inset-0 bg-background/60 backdrop-blur-sm rounded-lg flex items-center justify-center\">\n <span className=\"px-3 py-1.5 rounded-full bg-destructive/10 text-destructive text-sm font-medium\">\n Inactive\n </span>\n </div>\n )}\n </article>\n );\n};\n"],"mappings":"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"}
|
|
@@ -164,18 +164,19 @@ var h = ({ brand: e, className: t }) => {
|
|
|
164
164
|
})]
|
|
165
165
|
})]
|
|
166
166
|
});
|
|
167
|
-
}, _ = ({ paymentMethods: e, billingAccountId: t, onAddPaymentMethod: n, onSetDefault: r, onDelete: i, isLoading: s = !1, className: l = "" }) => {
|
|
168
|
-
let { t:
|
|
169
|
-
let n =
|
|
167
|
+
}, _ = ({ paymentMethods: e, billingAccountId: t, onAddPaymentMethod: n, onSetDefault: r, onDelete: i, isLoading: s = !1, className: l = "", dataTour: u }) => {
|
|
168
|
+
let { t: d } = m(), h = (e, t) => {
|
|
169
|
+
let n = d(e);
|
|
170
170
|
return n === e ? t : n;
|
|
171
|
-
},
|
|
171
|
+
}, _ = e.length > 0;
|
|
172
172
|
return /* @__PURE__ */ p("section", {
|
|
173
173
|
className: `space-y-4 ${l}`,
|
|
174
|
+
"data-tour": u,
|
|
174
175
|
children: [/* @__PURE__ */ p("header", {
|
|
175
176
|
className: "flex items-center justify-between",
|
|
176
177
|
children: [/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("h3", {
|
|
177
178
|
className: "text-lg font-semibold text-foreground",
|
|
178
|
-
children:
|
|
179
|
+
children: h("billing.overview.paymentMethods", "Payment Methods")
|
|
179
180
|
}), /* @__PURE__ */ f("p", {
|
|
180
181
|
className: "text-sm text-muted-foreground",
|
|
181
182
|
children: "Manage your payment methods for subscriptions and purchases"
|
|
@@ -188,7 +189,7 @@ var h = ({ brand: e, className: t }) => {
|
|
|
188
189
|
}), s ? /* @__PURE__ */ f("div", {
|
|
189
190
|
className: "flex items-center justify-center py-8",
|
|
190
191
|
children: /* @__PURE__ */ f(o, { className: "size-6 animate-spin text-muted-foreground" })
|
|
191
|
-
}) :
|
|
192
|
+
}) : _ ? /* @__PURE__ */ f("div", {
|
|
192
193
|
className: "space-y-3 overflow-visible",
|
|
193
194
|
children: e.filter((e) => e != null).map((e) => /* @__PURE__ */ f(g, {
|
|
194
195
|
method: e,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PaymentMethodsSection.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/components/PaymentMethodsSection.tsx"],"sourcesContent":["/**\n * Payment Methods Section Component\n * Displays and manages payment methods\n * Note: Provider config is fetched in the modal, not here\n */\n\nimport { type FC, useState } from 'react';\nimport { CreditCard, Plus, MoreVertical, Trash2, Star, Loader2, CheckCircle2 } from 'lucide-react';\nimport type { PaymentMethod, PaymentMethodType } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\n\ninterface PaymentMethodsSectionProps {\n paymentMethods: PaymentMethod[];\n billingAccountId: string;\n onAddPaymentMethod: () => void;\n onSetDefault: (id: string) => Promise<void>;\n onDelete: (id: string) => Promise<void>;\n isLoading?: boolean;\n className?: string;\n}\n\n// Card brand icons mapping\nconst CardBrandIcon: FC<{ brand?: string; className?: string }> = ({ brand, className }) => {\n const brandLower = brand?.toLowerCase() || '';\n\n // In a real app, you'd use actual brand SVGs/images\n const brandColors: Record<string, string> = {\n visa: 'text-accent-blue',\n mastercard: 'text-accent-orange',\n amex: 'text-accent-blue',\n discover: 'text-accent-orange',\n default: 'text-muted-foreground',\n };\n\n const color = brandColors[brandLower] || brandColors.default;\n\n return (\n <div className={`w-10 h-6 rounded bg-muted flex items-center justify-center ${className}`}>\n <CreditCard className={`size-5 ${color}`} />\n </div>\n );\n};\n\nconst PaymentMethodCard: FC<{\n method: PaymentMethod;\n onSetDefault: (id: string) => Promise<void>;\n onDelete: (id: string) => Promise<void>;\n}> = ({ method, onSetDefault, onDelete }) => {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [isSettingDefault, setIsSettingDefault] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [isConfirmingDelete, setIsConfirmingDelete] = useState(false);\n\n const handleSetDefault = async () => {\n setIsMenuOpen(false);\n setIsSettingDefault(true);\n try {\n await onSetDefault(method.id);\n } finally {\n setIsSettingDefault(false);\n }\n };\n\n const handleDeleteRequest = async () => {\n setIsMenuOpen(false);\n void nativeImpact('medium');\n const confirmed = await nativeConfirm({\n title: 'Delete payment method',\n message: 'Are you sure you want to remove this payment method?',\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n if (confirmed === true) {\n setIsDeleting(true);\n try {\n await onDelete(method.id);\n void nativeNotify('success');\n } catch {\n void nativeNotify('error');\n } finally {\n setIsDeleting(false);\n }\n return;\n }\n if (confirmed === null) {\n setIsConfirmingDelete(true);\n }\n };\n\n const handleDelete = async () => {\n setIsDeleting(true);\n try {\n await onDelete(method.id);\n void nativeNotify('success');\n } catch {\n void nativeNotify('error');\n } finally {\n setIsDeleting(false);\n setIsConfirmingDelete(false);\n }\n };\n\n const getMethodTypeLabel = (type: PaymentMethodType) => {\n const labels: Record<PaymentMethodType, string> = {\n CREDIT_CARD: 'Credit Card',\n DEBIT_CARD: 'Debit Card',\n BANK_TRANSFER: 'UPI',\n PAYPAL: 'PayPal',\n CRYPTO: 'Crypto',\n WALLET: 'Wallet',\n NETBANKING: 'Netbanking',\n };\n return labels[type] || type;\n };\n\n const getExpiryText = () => {\n if (method.expiryMonth && method.expiryYear) {\n const monthStr = String(method.expiryMonth).padStart(2, '0');\n const yearStr = String(method.expiryYear).slice(-2);\n return `${monthStr}/${yearStr}`;\n }\n return null;\n };\n\n const isExpired = () => {\n if (!method.expiryMonth || !method.expiryYear) return false;\n const now = new Date();\n const expiry = new Date(method.expiryYear, method.expiryMonth, 0);\n return expiry < now;\n };\n\n const getProviderBadge = () => {\n if (method.stripePaymentMethodId) {\n return (\n <span className=\"inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-accent-violet-subtle text-accent-violet\">\n Stripe\n </span>\n );\n }\n if (method.razorpayPaymentMethodId) {\n return (\n <span className=\"inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-status-info-bg-subtle text-status-info-text\">\n Razorpay\n </span>\n );\n }\n return null;\n };\n\n return (\n <div\n className={`\n relative p-4 border rounded-lg transition-all overflow-visible\n ${method.isDefault ? 'border-primary bg-primary/5' : 'border-border bg-card'}\n ${isDeleting ? 'opacity-50' : ''}\n `}\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <CardBrandIcon brand={method.brand} />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <p className=\"font-medium text-foreground\">\n {method.brand || getMethodTypeLabel(method.type)} •••• {method.last4 || '****'}\n </p>\n {method.isDefault && (\n <span className=\"inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-medium bg-primary/10 text-primary\">\n <Star className=\"size-2.5 fill-current\" />\n Default\n </span>\n )}\n {getProviderBadge()}\n </div>\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n {getExpiryText() && (\n <>\n <span className={isExpired() ? 'text-destructive' : ''}>\n Expires {getExpiryText()}\n </span>\n {isExpired() && <span className=\"text-destructive text-xs\">(Expired)</span>}\n </>\n )}\n {method.isVerified && (\n <span className=\"flex items-center gap-1 text-status-success-text\">\n <CheckCircle2 className=\"size-3\" />\n Verified\n </span>\n )}\n </div>\n </div>\n </div>\n\n {/* Actions Menu */}\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={() => setIsMenuOpen(!isMenuOpen)}\n className=\"p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors\"\n disabled={isSettingDefault || isDeleting}\n aria-label=\"Payment method options\"\n >\n {isSettingDefault || isDeleting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <MoreVertical className=\"size-4\" />\n )}\n </button>\n\n {isMenuOpen && (\n <>\n <div className=\"fixed inset-0 z-10\" onClick={() => setIsMenuOpen(false)} />\n <div className=\"absolute top-full right-0 mt-1 min-w-[140px] rounded-lg border border-border bg-popover shadow-lg z-20 overflow-hidden whitespace-nowrap\">\n {!method.isDefault && (\n <button\n type=\"button\"\n onClick={handleSetDefault}\n className=\"w-full flex items-center gap-2 px-3 py-2.5 text-sm text-foreground hover:bg-muted transition-colors text-left\"\n >\n <Star className=\"size-4 flex-shrink-0\" />\n Set as default\n </button>\n )}\n <button\n type=\"button\"\n onClick={handleDeleteRequest}\n className=\"w-full flex items-center gap-2 px-3 py-2.5 text-sm text-destructive hover:bg-destructive/10 transition-colors text-left\"\n >\n <Trash2 className=\"size-4 flex-shrink-0\" />\n Remove\n </button>\n </div>\n </>\n )}\n </div>\n </div>\n\n {isConfirmingDelete && (\n <div className=\"mt-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3\">\n <p className=\"text-sm font-medium text-status-error-text\">\n Remove this payment method from your billing account?\n </p>\n <div className=\"mt-3 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => setIsConfirmingDelete(false)}\n className=\"rounded-lg border border-border bg-background px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"rounded-lg bg-destructive px-3 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-destructive/90 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isDeleting ? 'Removing…' : 'Remove'}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n\nexport const PaymentMethodsSection: FC<PaymentMethodsSectionProps> = ({\n paymentMethods,\n billingAccountId: _billingAccountId,\n onAddPaymentMethod,\n onSetDefault,\n onDelete,\n isLoading = false,\n className = '',\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n // billingAccountId is available for future use\n void _billingAccountId;\n\n const hasPaymentMethods = paymentMethods.length > 0;\n\n return (\n <section className={`space-y-4 ${className}`}>\n {/* Header */}\n <header className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.overview.paymentMethods', 'Payment Methods')}\n </h3>\n <p className=\"text-sm text-muted-foreground\">\n Manage your payment methods for subscriptions and purchases\n </p>\n </div>\n <button\n type=\"button\"\n onClick={onAddPaymentMethod}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors\"\n >\n <Plus className=\"size-4\" />\n Add Method\n </button>\n </header>\n\n {/* Content */}\n {isLoading ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"size-6 animate-spin text-muted-foreground\" />\n </div>\n ) : hasPaymentMethods ? (\n <div className=\"space-y-3 overflow-visible\">\n {paymentMethods\n .filter((method): method is PaymentMethod => method !== null && method !== undefined)\n .map((method) => (\n <PaymentMethodCard\n key={method.id}\n method={method}\n onSetDefault={onSetDefault}\n onDelete={onDelete}\n />\n ))}\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center py-8 border border-dashed border-border rounded-lg\">\n <div className=\"size-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <CreditCard className=\"size-6 text-muted-foreground\" />\n </div>\n <h4 className=\"text-base font-medium text-foreground\">No payment methods</h4>\n <p className=\"text-sm text-muted-foreground mt-1 text-center max-w-sm\">\n Add a payment method to enable automatic payments for your subscriptions\n </p>\n </div>\n )}\n </section>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PaymentMethodsSection.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/components/PaymentMethodsSection.tsx"],"sourcesContent":["/**\n * Payment Methods Section Component\n * Displays and manages payment methods\n * Note: Provider config is fetched in the modal, not here\n */\n\nimport { type FC, useState } from 'react';\nimport { CreditCard, Plus, MoreVertical, Trash2, Star, Loader2, CheckCircle2 } from 'lucide-react';\nimport type { PaymentMethod, PaymentMethodType } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../../../../utils/nativeBridge';\n\ninterface PaymentMethodsSectionProps {\n paymentMethods: PaymentMethod[];\n billingAccountId: string;\n onAddPaymentMethod: () => void;\n onSetDefault: (id: string) => Promise<void>;\n onDelete: (id: string) => Promise<void>;\n isLoading?: boolean;\n className?: string;\n /** Optional tour anchor applied to the section root (for onboarding tours). */\n dataTour?: string;\n}\n\n// Card brand icons mapping\nconst CardBrandIcon: FC<{ brand?: string; className?: string }> = ({ brand, className }) => {\n const brandLower = brand?.toLowerCase() || '';\n\n // In a real app, you'd use actual brand SVGs/images\n const brandColors: Record<string, string> = {\n visa: 'text-accent-blue',\n mastercard: 'text-accent-orange',\n amex: 'text-accent-blue',\n discover: 'text-accent-orange',\n default: 'text-muted-foreground',\n };\n\n const color = brandColors[brandLower] || brandColors.default;\n\n return (\n <div className={`w-10 h-6 rounded bg-muted flex items-center justify-center ${className}`}>\n <CreditCard className={`size-5 ${color}`} />\n </div>\n );\n};\n\nconst PaymentMethodCard: FC<{\n method: PaymentMethod;\n onSetDefault: (id: string) => Promise<void>;\n onDelete: (id: string) => Promise<void>;\n}> = ({ method, onSetDefault, onDelete }) => {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [isSettingDefault, setIsSettingDefault] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [isConfirmingDelete, setIsConfirmingDelete] = useState(false);\n\n const handleSetDefault = async () => {\n setIsMenuOpen(false);\n setIsSettingDefault(true);\n try {\n await onSetDefault(method.id);\n } finally {\n setIsSettingDefault(false);\n }\n };\n\n const handleDeleteRequest = async () => {\n setIsMenuOpen(false);\n void nativeImpact('medium');\n const confirmed = await nativeConfirm({\n title: 'Delete payment method',\n message: 'Are you sure you want to remove this payment method?',\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n if (confirmed === true) {\n setIsDeleting(true);\n try {\n await onDelete(method.id);\n void nativeNotify('success');\n } catch {\n void nativeNotify('error');\n } finally {\n setIsDeleting(false);\n }\n return;\n }\n if (confirmed === null) {\n setIsConfirmingDelete(true);\n }\n };\n\n const handleDelete = async () => {\n setIsDeleting(true);\n try {\n await onDelete(method.id);\n void nativeNotify('success');\n } catch {\n void nativeNotify('error');\n } finally {\n setIsDeleting(false);\n setIsConfirmingDelete(false);\n }\n };\n\n const getMethodTypeLabel = (type: PaymentMethodType) => {\n const labels: Record<PaymentMethodType, string> = {\n CREDIT_CARD: 'Credit Card',\n DEBIT_CARD: 'Debit Card',\n BANK_TRANSFER: 'UPI',\n PAYPAL: 'PayPal',\n CRYPTO: 'Crypto',\n WALLET: 'Wallet',\n NETBANKING: 'Netbanking',\n };\n return labels[type] || type;\n };\n\n const getExpiryText = () => {\n if (method.expiryMonth && method.expiryYear) {\n const monthStr = String(method.expiryMonth).padStart(2, '0');\n const yearStr = String(method.expiryYear).slice(-2);\n return `${monthStr}/${yearStr}`;\n }\n return null;\n };\n\n const isExpired = () => {\n if (!method.expiryMonth || !method.expiryYear) return false;\n const now = new Date();\n const expiry = new Date(method.expiryYear, method.expiryMonth, 0);\n return expiry < now;\n };\n\n const getProviderBadge = () => {\n if (method.stripePaymentMethodId) {\n return (\n <span className=\"inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-accent-violet-subtle text-accent-violet\">\n Stripe\n </span>\n );\n }\n if (method.razorpayPaymentMethodId) {\n return (\n <span className=\"inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-status-info-bg-subtle text-status-info-text\">\n Razorpay\n </span>\n );\n }\n return null;\n };\n\n return (\n <div\n className={`\n relative p-4 border rounded-lg transition-all overflow-visible\n ${method.isDefault ? 'border-primary bg-primary/5' : 'border-border bg-card'}\n ${isDeleting ? 'opacity-50' : ''}\n `}\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <CardBrandIcon brand={method.brand} />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <p className=\"font-medium text-foreground\">\n {method.brand || getMethodTypeLabel(method.type)} •••• {method.last4 || '****'}\n </p>\n {method.isDefault && (\n <span className=\"inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-medium bg-primary/10 text-primary\">\n <Star className=\"size-2.5 fill-current\" />\n Default\n </span>\n )}\n {getProviderBadge()}\n </div>\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n {getExpiryText() && (\n <>\n <span className={isExpired() ? 'text-destructive' : ''}>\n Expires {getExpiryText()}\n </span>\n {isExpired() && <span className=\"text-destructive text-xs\">(Expired)</span>}\n </>\n )}\n {method.isVerified && (\n <span className=\"flex items-center gap-1 text-status-success-text\">\n <CheckCircle2 className=\"size-3\" />\n Verified\n </span>\n )}\n </div>\n </div>\n </div>\n\n {/* Actions Menu */}\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={() => setIsMenuOpen(!isMenuOpen)}\n className=\"p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground transition-colors\"\n disabled={isSettingDefault || isDeleting}\n aria-label=\"Payment method options\"\n >\n {isSettingDefault || isDeleting ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <MoreVertical className=\"size-4\" />\n )}\n </button>\n\n {isMenuOpen && (\n <>\n <div className=\"fixed inset-0 z-10\" onClick={() => setIsMenuOpen(false)} />\n <div className=\"absolute top-full right-0 mt-1 min-w-[140px] rounded-lg border border-border bg-popover shadow-lg z-20 overflow-hidden whitespace-nowrap\">\n {!method.isDefault && (\n <button\n type=\"button\"\n onClick={handleSetDefault}\n className=\"w-full flex items-center gap-2 px-3 py-2.5 text-sm text-foreground hover:bg-muted transition-colors text-left\"\n >\n <Star className=\"size-4 flex-shrink-0\" />\n Set as default\n </button>\n )}\n <button\n type=\"button\"\n onClick={handleDeleteRequest}\n className=\"w-full flex items-center gap-2 px-3 py-2.5 text-sm text-destructive hover:bg-destructive/10 transition-colors text-left\"\n >\n <Trash2 className=\"size-4 flex-shrink-0\" />\n Remove\n </button>\n </div>\n </>\n )}\n </div>\n </div>\n\n {isConfirmingDelete && (\n <div className=\"mt-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3\">\n <p className=\"text-sm font-medium text-status-error-text\">\n Remove this payment method from your billing account?\n </p>\n <div className=\"mt-3 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => setIsConfirmingDelete(false)}\n className=\"rounded-lg border border-border bg-background px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"rounded-lg bg-destructive px-3 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-destructive/90 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isDeleting ? 'Removing…' : 'Remove'}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n\nexport const PaymentMethodsSection: FC<PaymentMethodsSectionProps> = ({\n paymentMethods,\n billingAccountId: _billingAccountId,\n onAddPaymentMethod,\n onSetDefault,\n onDelete,\n isLoading = false,\n className = '',\n dataTour,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n // billingAccountId is available for future use\n void _billingAccountId;\n\n const hasPaymentMethods = paymentMethods.length > 0;\n\n return (\n <section className={`space-y-4 ${className}`} data-tour={dataTour}>\n {/* Header */}\n <header className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.overview.paymentMethods', 'Payment Methods')}\n </h3>\n <p className=\"text-sm text-muted-foreground\">\n Manage your payment methods for subscriptions and purchases\n </p>\n </div>\n <button\n type=\"button\"\n onClick={onAddPaymentMethod}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors\"\n >\n <Plus className=\"size-4\" />\n Add Method\n </button>\n </header>\n\n {/* Content */}\n {isLoading ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"size-6 animate-spin text-muted-foreground\" />\n </div>\n ) : hasPaymentMethods ? (\n <div className=\"space-y-3 overflow-visible\">\n {paymentMethods\n .filter((method): method is PaymentMethod => method !== null && method !== undefined)\n .map((method) => (\n <PaymentMethodCard\n key={method.id}\n method={method}\n onSetDefault={onSetDefault}\n onDelete={onDelete}\n />\n ))}\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center py-8 border border-dashed border-border rounded-lg\">\n <div className=\"size-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <CreditCard className=\"size-6 text-muted-foreground\" />\n </div>\n <h4 className=\"text-base font-medium text-foreground\">No payment methods</h4>\n <p className=\"text-sm text-muted-foreground mt-1 text-center max-w-sm\">\n Add a payment method to enable automatic payments for your subscriptions\n </p>\n </div>\n )}\n </section>\n );\n};\n"],"mappings":"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"}
|
|
@@ -124,18 +124,19 @@ var _ = ({ addon: n, onView: i, onPurchase: a, onAddToCart: u, isInCart: h = !1,
|
|
|
124
124
|
})
|
|
125
125
|
]
|
|
126
126
|
});
|
|
127
|
-
}, v = ({ addons: e, hasSubscription: t, onViewAddon: r, onPurchaseAddon: o, onViewAllAddons: s, onAddToCart: c, isInCart: l, getCartQuantity: d, onUpdateQuantity: f, onRemoveFromCart: g, maxAddons: v = 3, isLoading: y = !1, className: b = "" }) => {
|
|
128
|
-
let { t:
|
|
129
|
-
let n =
|
|
127
|
+
}, v = ({ addons: e, hasSubscription: t, onViewAddon: r, onPurchaseAddon: o, onViewAllAddons: s, onAddToCart: c, isInCart: l, getCartQuantity: d, onUpdateQuantity: f, onRemoveFromCart: g, maxAddons: v = 3, isLoading: y = !1, className: b = "", dataTour: x }) => {
|
|
128
|
+
let { t: S } = h(), C = (e, t) => {
|
|
129
|
+
let n = S(e);
|
|
130
130
|
return n === e ? t : n;
|
|
131
131
|
};
|
|
132
132
|
if (!t) return null;
|
|
133
|
-
let
|
|
133
|
+
let w = e.slice(0, v);
|
|
134
134
|
return y ? /* @__PURE__ */ m("section", {
|
|
135
135
|
className: `space-y-4 ${b}`,
|
|
136
|
+
"data-tour": x,
|
|
136
137
|
children: [/* @__PURE__ */ m("header", { children: [/* @__PURE__ */ p("h3", {
|
|
137
138
|
className: "text-lg font-semibold text-foreground",
|
|
138
|
-
children:
|
|
139
|
+
children: C("billing.overview.recommendedAddons", "Recommended Add-ons")
|
|
139
140
|
}), /* @__PURE__ */ p("p", {
|
|
140
141
|
className: "text-sm text-muted-foreground",
|
|
141
142
|
children: "Enhance your subscription with these popular add-ons"
|
|
@@ -143,8 +144,9 @@ var _ = ({ addon: n, onView: i, onPurchase: a, onAddToCart: u, isInCart: h = !1,
|
|
|
143
144
|
className: "flex items-center justify-center py-12",
|
|
144
145
|
children: /* @__PURE__ */ p(a, { className: "size-6 animate-spin text-muted-foreground" })
|
|
145
146
|
})]
|
|
146
|
-
}) :
|
|
147
|
+
}) : w.length === 0 ? null : /* @__PURE__ */ m("section", {
|
|
147
148
|
className: `space-y-4 ${b}`,
|
|
149
|
+
"data-tour": x,
|
|
148
150
|
children: [
|
|
149
151
|
/* @__PURE__ */ m("header", {
|
|
150
152
|
className: "flex items-center justify-between",
|
|
@@ -166,7 +168,7 @@ var _ = ({ addon: n, onView: i, onPurchase: a, onAddToCart: u, isInCart: h = !1,
|
|
|
166
168
|
}),
|
|
167
169
|
/* @__PURE__ */ p("div", {
|
|
168
170
|
className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",
|
|
169
|
-
children:
|
|
171
|
+
children: w.map((e) => /* @__PURE__ */ p(_, {
|
|
170
172
|
addon: e,
|
|
171
173
|
onView: () => r(e.id),
|
|
172
174
|
onPurchase: () => o(e.id),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecommendedAddonsSection.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/components/RecommendedAddonsSection.tsx"],"sourcesContent":["/**\n * Recommended Addons Section Component\n * Displays recommended addons that users might want to purchase\n * Supports add to cart functionality\n */\n\nimport { type FC, useState, useMemo } from 'react';\nimport {\n Package,\n ArrowRight,\n Sparkles,\n ChevronRight,\n Loader2,\n ShoppingCart,\n Check,\n Plus,\n Minus,\n Trash2,\n} from 'lucide-react';\nimport type { Addon, PlanDuration, SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\n/**\n * Combines duplicate quotas by quota ID and aggregates their values\n * e.g., 10 bots + 10 bots = 20 bots (shown as one feature)\n */\nfunction combineQuotas(features: SubscriptionFeatures[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n\n for (const feature of features) {\n if (!feature.quota) continue;\n\n const quotaId = feature.quota.id;\n const limits = feature.quota.limits as { value?: number } | null;\n const limitValue = limits?.value ?? 0;\n\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n\n return Array.from(quotaMap.values());\n}\n\ninterface RecommendedAddonsSectionProps {\n addons: Addon[];\n hasSubscription: boolean;\n onViewAddon: (addonId: string) => void;\n onPurchaseAddon: (addonId: string) => void;\n onViewAllAddons: () => void;\n /** Handler for adding addon to cart */\n onAddToCart?: (addon: Addon) => void;\n /** Check if addon is in cart */\n isInCart?: (addonId: string) => boolean;\n /** Get cart quantity for addon */\n getCartQuantity?: (addonId: string) => number;\n /** Update quantity in cart */\n onUpdateQuantity?: (addonId: string, quantity: number) => void;\n /** Remove from cart */\n onRemoveFromCart?: (addonId: string) => void;\n /** Maximum number of addons to show (default: 3) */\n maxAddons?: number;\n isLoading?: boolean;\n className?: string;\n}\n\nconst AddonCard: FC<{\n addon: Addon;\n onView: () => void;\n onPurchase: () => void;\n onAddToCart?: () => void;\n isInCart?: boolean;\n cartQuantity?: number;\n onUpdateQuantity?: (quantity: number) => void;\n onRemoveFromCart?: () => void;\n}> = ({\n addon,\n onView,\n onPurchase,\n onAddToCart,\n isInCart = false,\n cartQuantity = 0,\n onUpdateQuantity,\n onRemoveFromCart,\n}) => {\n const [justAdded, setJustAdded] = useState(false);\n\n const handleAddToCart = () => {\n if (onAddToCart) {\n onAddToCart();\n setJustAdded(true);\n setTimeout(() => setJustAdded(false), 2000);\n } else {\n onPurchase();\n }\n };\n\n const formatCurrency = (amount: number, currency: string) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency || 'USD',\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n }).format(amount);\n };\n\n const getDurationLabel = (duration: PlanDuration) => {\n return duration === 'yearly' ? 'year' : 'month';\n };\n\n // Combine duplicate quotas (e.g., 10 bots + 10 bots = 20 bots)\n const combinedQuotas = useMemo(() => {\n const features = addon.features || [];\n return combineQuotas(features as SubscriptionFeatures[]);\n }, [addon.features]);\n\n return (\n <article className=\"flex flex-col border border-border rounded-lg bg-card overflow-hidden hover:shadow-md transition-shadow\">\n {/* Header */}\n <header className=\"p-4 border-b border-border\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-status-success-bg-subtle flex items-center justify-center\">\n <Package className=\"size-5 text-status-success-text\" />\n </div>\n <div>\n <h4 className=\"font-semibold text-foreground\">{addon.name}</h4>\n <p className=\"text-sm text-muted-foreground\">\n {formatCurrency(addon.price, addon.currency)}/{getDurationLabel(addon.duration)}\n </p>\n </div>\n </div>\n </div>\n </header>\n\n {/* Body */}\n <div className=\"p-4 flex-1\">\n {combinedQuotas.length > 0 ? (\n <ul className=\"space-y-2\">\n {combinedQuotas.slice(0, 2).map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <span className=\"size-1.5 rounded-full bg-status-success-bg\" />\n <span className=\"text-muted-foreground\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-foreground\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 2 && (\n <li className=\"text-xs text-muted-foreground\">\n +{combinedQuotas.length - 2} more quotas\n </li>\n )}\n </ul>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n Enhance your subscription with additional features\n </p>\n )}\n </div>\n\n {/* Footer */}\n <footer className=\"p-4 border-t border-border bg-muted/30 flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={onView}\n className=\"flex-1 px-3 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors text-center\"\n >\n View Details\n </button>\n {isInCart && onUpdateQuantity ? (\n // Quantity controls when in cart\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={onRemoveFromCart}\n className=\"p-2 text-destructive hover:bg-destructive/10 rounded-lg transition-colors\"\n title=\"Remove from cart\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(cartQuantity - 1)}\n className=\"p-2 text-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-8 text-center font-medium text-foreground\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(cartQuantity + 1)}\n className=\"p-2 text-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n ) : (\n // Add to cart button\n <button\n type=\"button\"\n onClick={handleAddToCart}\n disabled={justAdded}\n className={`flex-1 px-3 py-2 text-sm font-medium rounded-lg transition-colors text-center flex items-center justify-center gap-1.5 ${\n justAdded\n ? 'bg-status-success-bg text-primary-foreground hover:bg-status-success-bg/90'\n : 'text-primary-foreground bg-primary hover:bg-primary/90'\n }`}\n >\n {justAdded ? (\n <>\n <Check className=\"size-4\" />\n Added\n </>\n ) : (\n <>\n <ShoppingCart className=\"size-4\" />\n Add to Cart\n </>\n )}\n </button>\n )}\n </footer>\n </article>\n );\n};\n\nexport const RecommendedAddonsSection: FC<RecommendedAddonsSectionProps> = ({\n addons,\n hasSubscription,\n onViewAddon,\n onPurchaseAddon,\n onViewAllAddons,\n onAddToCart,\n isInCart,\n getCartQuantity,\n onUpdateQuantity,\n onRemoveFromCart,\n maxAddons = 3,\n isLoading = false,\n className = '',\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n // Only show if user has a subscription\n if (!hasSubscription) {\n return null;\n }\n\n // Limit addons to maxAddons\n const displayAddons = addons.slice(0, maxAddons);\n\n if (isLoading) {\n return (\n <section className={`space-y-4 ${className}`}>\n <header>\n <h3 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.overview.recommendedAddons', 'Recommended Add-ons')}\n </h3>\n <p className=\"text-sm text-muted-foreground\">\n Enhance your subscription with these popular add-ons\n </p>\n </header>\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"size-6 animate-spin text-muted-foreground\" />\n </div>\n </section>\n );\n }\n\n if (displayAddons.length === 0) {\n return null;\n }\n\n return (\n <section className={`space-y-4 ${className}`}>\n {/* Header */}\n <header className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Sparkles className=\"size-5 text-status-warning-text\" />\n <div>\n <h3 className=\"text-lg font-semibold text-foreground\">Recommended Add-ons</h3>\n <p className=\"text-sm text-muted-foreground\">\n Enhance your subscription with these popular add-ons\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onViewAllAddons}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-primary hover:text-primary/80 transition-colors\"\n >\n View All\n <ChevronRight className=\"size-4\" />\n </button>\n </header>\n\n {/* Addons Grid - Limited to maxAddons */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {displayAddons.map((addon) => (\n <AddonCard\n key={addon.id}\n addon={addon}\n onView={() => onViewAddon(addon.id)}\n onPurchase={() => onPurchaseAddon(addon.id)}\n onAddToCart={onAddToCart ? () => onAddToCart(addon) : undefined}\n isInCart={isInCart ? isInCart(addon.id) : false}\n cartQuantity={getCartQuantity ? getCartQuantity(addon.id) : 0}\n onUpdateQuantity={\n onUpdateQuantity ? (qty) => onUpdateQuantity(addon.id, qty) : undefined\n }\n onRemoveFromCart={onRemoveFromCart ? () => onRemoveFromCart(addon.id) : undefined}\n />\n ))}\n </div>\n\n {/* See More Link - show if there are more addons */}\n {addons.length > maxAddons && (\n <div className=\"flex justify-center pt-2\">\n <button\n type=\"button\"\n onClick={onViewAllAddons}\n className=\"inline-flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors\"\n >\n View all {addons.length} add-ons\n <ArrowRight className=\"size-4\" />\n </button>\n </div>\n )}\n </section>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RecommendedAddonsSection.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/components/RecommendedAddonsSection.tsx"],"sourcesContent":["/**\n * Recommended Addons Section Component\n * Displays recommended addons that users might want to purchase\n * Supports add to cart functionality\n */\n\nimport { type FC, useState, useMemo } from 'react';\nimport {\n Package,\n ArrowRight,\n Sparkles,\n ChevronRight,\n Loader2,\n ShoppingCart,\n Check,\n Plus,\n Minus,\n Trash2,\n} from 'lucide-react';\nimport type { Addon, PlanDuration, SubscriptionFeatures } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\n/**\n * Combines duplicate quotas by quota ID and aggregates their values\n * e.g., 10 bots + 10 bots = 20 bots (shown as one feature)\n */\nfunction combineQuotas(features: SubscriptionFeatures[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n\n for (const feature of features) {\n if (!feature.quota) continue;\n\n const quotaId = feature.quota.id;\n const limits = feature.quota.limits as { value?: number } | null;\n const limitValue = limits?.value ?? 0;\n\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n\n return Array.from(quotaMap.values());\n}\n\ninterface RecommendedAddonsSectionProps {\n addons: Addon[];\n hasSubscription: boolean;\n onViewAddon: (addonId: string) => void;\n onPurchaseAddon: (addonId: string) => void;\n onViewAllAddons: () => void;\n /** Handler for adding addon to cart */\n onAddToCart?: (addon: Addon) => void;\n /** Check if addon is in cart */\n isInCart?: (addonId: string) => boolean;\n /** Get cart quantity for addon */\n getCartQuantity?: (addonId: string) => number;\n /** Update quantity in cart */\n onUpdateQuantity?: (addonId: string, quantity: number) => void;\n /** Remove from cart */\n onRemoveFromCart?: (addonId: string) => void;\n /** Maximum number of addons to show (default: 3) */\n maxAddons?: number;\n isLoading?: boolean;\n className?: string;\n /** Optional tour anchor applied to the section root (for onboarding tours). */\n dataTour?: string;\n}\n\nconst AddonCard: FC<{\n addon: Addon;\n onView: () => void;\n onPurchase: () => void;\n onAddToCart?: () => void;\n isInCart?: boolean;\n cartQuantity?: number;\n onUpdateQuantity?: (quantity: number) => void;\n onRemoveFromCart?: () => void;\n}> = ({\n addon,\n onView,\n onPurchase,\n onAddToCart,\n isInCart = false,\n cartQuantity = 0,\n onUpdateQuantity,\n onRemoveFromCart,\n}) => {\n const [justAdded, setJustAdded] = useState(false);\n\n const handleAddToCart = () => {\n if (onAddToCart) {\n onAddToCart();\n setJustAdded(true);\n setTimeout(() => setJustAdded(false), 2000);\n } else {\n onPurchase();\n }\n };\n\n const formatCurrency = (amount: number, currency: string) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: currency || 'USD',\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n }).format(amount);\n };\n\n const getDurationLabel = (duration: PlanDuration) => {\n return duration === 'yearly' ? 'year' : 'month';\n };\n\n // Combine duplicate quotas (e.g., 10 bots + 10 bots = 20 bots)\n const combinedQuotas = useMemo(() => {\n const features = addon.features || [];\n return combineQuotas(features as SubscriptionFeatures[]);\n }, [addon.features]);\n\n return (\n <article className=\"flex flex-col border border-border rounded-lg bg-card overflow-hidden hover:shadow-md transition-shadow\">\n {/* Header */}\n <header className=\"p-4 border-b border-border\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-status-success-bg-subtle flex items-center justify-center\">\n <Package className=\"size-5 text-status-success-text\" />\n </div>\n <div>\n <h4 className=\"font-semibold text-foreground\">{addon.name}</h4>\n <p className=\"text-sm text-muted-foreground\">\n {formatCurrency(addon.price, addon.currency)}/{getDurationLabel(addon.duration)}\n </p>\n </div>\n </div>\n </div>\n </header>\n\n {/* Body */}\n <div className=\"p-4 flex-1\">\n {combinedQuotas.length > 0 ? (\n <ul className=\"space-y-2\">\n {combinedQuotas.slice(0, 2).map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <span className=\"size-1.5 rounded-full bg-status-success-bg\" />\n <span className=\"text-muted-foreground\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-foreground\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 2 && (\n <li className=\"text-xs text-muted-foreground\">\n +{combinedQuotas.length - 2} more quotas\n </li>\n )}\n </ul>\n ) : (\n <p className=\"text-sm text-muted-foreground\">\n Enhance your subscription with additional features\n </p>\n )}\n </div>\n\n {/* Footer */}\n <footer className=\"p-4 border-t border-border bg-muted/30 flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={onView}\n className=\"flex-1 px-3 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted rounded-lg transition-colors text-center\"\n >\n View Details\n </button>\n {isInCart && onUpdateQuantity ? (\n // Quantity controls when in cart\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n onClick={onRemoveFromCart}\n className=\"p-2 text-destructive hover:bg-destructive/10 rounded-lg transition-colors\"\n title=\"Remove from cart\"\n >\n <Trash2 className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(cartQuantity - 1)}\n className=\"p-2 text-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-8 text-center font-medium text-foreground\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(cartQuantity + 1)}\n className=\"p-2 text-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n ) : (\n // Add to cart button\n <button\n type=\"button\"\n onClick={handleAddToCart}\n disabled={justAdded}\n className={`flex-1 px-3 py-2 text-sm font-medium rounded-lg transition-colors text-center flex items-center justify-center gap-1.5 ${\n justAdded\n ? 'bg-status-success-bg text-primary-foreground hover:bg-status-success-bg/90'\n : 'text-primary-foreground bg-primary hover:bg-primary/90'\n }`}\n >\n {justAdded ? (\n <>\n <Check className=\"size-4\" />\n Added\n </>\n ) : (\n <>\n <ShoppingCart className=\"size-4\" />\n Add to Cart\n </>\n )}\n </button>\n )}\n </footer>\n </article>\n );\n};\n\nexport const RecommendedAddonsSection: FC<RecommendedAddonsSectionProps> = ({\n addons,\n hasSubscription,\n onViewAddon,\n onPurchaseAddon,\n onViewAllAddons,\n onAddToCart,\n isInCart,\n getCartQuantity,\n onUpdateQuantity,\n onRemoveFromCart,\n maxAddons = 3,\n isLoading = false,\n className = '',\n dataTour,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n // Only show if user has a subscription\n if (!hasSubscription) {\n return null;\n }\n\n // Limit addons to maxAddons\n const displayAddons = addons.slice(0, maxAddons);\n\n if (isLoading) {\n return (\n <section className={`space-y-4 ${className}`} data-tour={dataTour}>\n <header>\n <h3 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.overview.recommendedAddons', 'Recommended Add-ons')}\n </h3>\n <p className=\"text-sm text-muted-foreground\">\n Enhance your subscription with these popular add-ons\n </p>\n </header>\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"size-6 animate-spin text-muted-foreground\" />\n </div>\n </section>\n );\n }\n\n if (displayAddons.length === 0) {\n return null;\n }\n\n return (\n <section className={`space-y-4 ${className}`} data-tour={dataTour}>\n {/* Header */}\n <header className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Sparkles className=\"size-5 text-status-warning-text\" />\n <div>\n <h3 className=\"text-lg font-semibold text-foreground\">Recommended Add-ons</h3>\n <p className=\"text-sm text-muted-foreground\">\n Enhance your subscription with these popular add-ons\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onViewAllAddons}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-primary hover:text-primary/80 transition-colors\"\n >\n View All\n <ChevronRight className=\"size-4\" />\n </button>\n </header>\n\n {/* Addons Grid - Limited to maxAddons */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {displayAddons.map((addon) => (\n <AddonCard\n key={addon.id}\n addon={addon}\n onView={() => onViewAddon(addon.id)}\n onPurchase={() => onPurchaseAddon(addon.id)}\n onAddToCart={onAddToCart ? () => onAddToCart(addon) : undefined}\n isInCart={isInCart ? isInCart(addon.id) : false}\n cartQuantity={getCartQuantity ? getCartQuantity(addon.id) : 0}\n onUpdateQuantity={\n onUpdateQuantity ? (qty) => onUpdateQuantity(addon.id, qty) : undefined\n }\n onRemoveFromCart={onRemoveFromCart ? () => onRemoveFromCart(addon.id) : undefined}\n />\n ))}\n </div>\n\n {/* See More Link - show if there are more addons */}\n {addons.length > maxAddons && (\n <div className=\"flex justify-center pt-2\">\n <button\n type=\"button\"\n onClick={onViewAllAddons}\n className=\"inline-flex items-center gap-2 text-sm font-medium text-muted-foreground hover:text-foreground transition-colors\"\n >\n View all {addons.length} add-ons\n <ArrowRight className=\"size-4\" />\n </button>\n </div>\n )}\n </section>\n );\n};\n"],"mappings":"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"}
|
|
@@ -202,6 +202,7 @@ var g = () => {
|
|
|
202
202
|
}),
|
|
203
203
|
N.length > 1 && /* @__PURE__ */ h("div", {
|
|
204
204
|
className: "relative",
|
|
205
|
+
"data-tour": "billing-overview-account-selector",
|
|
205
206
|
children: [/* @__PURE__ */ h("button", {
|
|
206
207
|
type: "button",
|
|
207
208
|
onClick: () => k(!O),
|
|
@@ -252,6 +253,7 @@ var g = () => {
|
|
|
252
253
|
}),
|
|
253
254
|
P && /* @__PURE__ */ h("div", {
|
|
254
255
|
className: "grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4",
|
|
256
|
+
"data-tour": "billing-overview-stats-row",
|
|
255
257
|
children: [
|
|
256
258
|
/* @__PURE__ */ m(n, {
|
|
257
259
|
label: _("billing.overview.stats.creditBalance", "Credit Balance"),
|
|
@@ -281,6 +283,7 @@ var g = () => {
|
|
|
281
283
|
className: "lg:col-span-2 space-y-6",
|
|
282
284
|
children: [
|
|
283
285
|
P && /* @__PURE__ */ m(le, {
|
|
286
|
+
dataTour: "billing-overview-billing-account-card",
|
|
284
287
|
account: P,
|
|
285
288
|
isDefault: P.isDefault ?? !1,
|
|
286
289
|
onEdit: () => P && Q(`/settings?billingAccountId=${P.id}`),
|
|
@@ -359,6 +362,7 @@ var g = () => {
|
|
|
359
362
|
}, e.id))
|
|
360
363
|
})] })]
|
|
361
364
|
}), /* @__PURE__ */ m(de, {
|
|
365
|
+
dataTour: "billing-overview-active-subscription-card",
|
|
362
366
|
subscription: Z,
|
|
363
367
|
onViewDetails: () => Z && Q(i(`/subscriptions/${Z.id}`, Z.billingAccountId ?? P?.id)),
|
|
364
368
|
onUpgrade: () => Z && Q(i(`/subscriptions/${Z.id}/upgrade`, Z.billingAccountId ?? P?.id)),
|
|
@@ -367,6 +371,7 @@ var g = () => {
|
|
|
367
371
|
})]
|
|
368
372
|
}),
|
|
369
373
|
P && /* @__PURE__ */ m(ue, {
|
|
374
|
+
dataTour: "billing-overview-payment-methods-section",
|
|
370
375
|
paymentMethods: F,
|
|
371
376
|
billingAccountId: P.id,
|
|
372
377
|
onAddPaymentMethod: Fe,
|
|
@@ -379,6 +384,7 @@ var g = () => {
|
|
|
379
384
|
className: "space-y-6",
|
|
380
385
|
children: [/* @__PURE__ */ h("section", {
|
|
381
386
|
className: "border border-border rounded-lg bg-card p-4",
|
|
387
|
+
"data-tour": "billing-overview-quick-actions",
|
|
382
388
|
children: [/* @__PURE__ */ m("h3", {
|
|
383
389
|
className: "font-semibold text-foreground mb-4",
|
|
384
390
|
children: "Quick Actions"
|
|
@@ -450,6 +456,7 @@ var g = () => {
|
|
|
450
456
|
})]
|
|
451
457
|
}),
|
|
452
458
|
/* @__PURE__ */ m(fe, {
|
|
459
|
+
dataTour: "billing-overview-recommended-addons",
|
|
453
460
|
addons: Ae,
|
|
454
461
|
hasSubscription: L,
|
|
455
462
|
onViewAddon: (e) => Q(`/addons/${e}`),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OverviewPage.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/pages/OverviewPage.tsx"],"sourcesContent":["/**\n * Billing Overview Page\n * Main dashboard for billing showing accounts, subscriptions, payment methods, and addons\n */\n\nimport { type FC, useState, useCallback, useEffect } from 'react';\nimport {\n Plus,\n Building2,\n RefreshCw,\n Loader2,\n Wallet,\n ChevronDown,\n Check,\n Activity,\n Receipt,\n ArrowRight,\n Sparkles,\n Crown,\n Coins,\n} from 'lucide-react';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { StatCard } from '../../../shared/components/StatCard';\nimport { EmptyState } from '../../../shared/components/EmptyState';\nimport { ServerError } from '../../../shared/components/ServerError';\nimport { AccessDenied } from '../../../shared/components/AccessDenied';\nimport { isServerError } from '../../../shared/utils';\nimport { useBillingOverviewData, useDashboardMutations } from '../hooks';\nimport {\n BillingAccountCard,\n PaymentMethodsSection,\n ActiveSubscriptionCard,\n RecommendedAddonsSection,\n CreateBillingAccountModal,\n AddPaymentMethodModal,\n} from '../components';\nimport { useAddonCartStore } from '../../addons/store/addonCartStore';\nimport { FloatingCartDrawer } from '../../addons/components/FloatingCartDrawer';\nimport {\n getStoredBillingAccountId,\n setStoredBillingAccountId,\n} from '../../../hooks/useBillingAccountSelection';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport type {\n BillingAccount,\n CreateBillingAccountInput,\n SetBillingAddressInput,\n Addon,\n} from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport const OverviewPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { navigate, basePath, orgId, productId } = useBilling();\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n\n const [selectedAccountId, setSelectedAccountIdState] = useState<string | null>(() =>\n getStoredBillingAccountId(orgId)\n );\n\n const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);\n const [createError, setCreateError] = useState<string | null>(null);\n const [isAccountSelectorOpen, setIsAccountSelectorOpen] = useState(false);\n const [isAddPaymentModalOpen, setIsAddPaymentModalOpen] = useState(false);\n const [selectedSubscriptionIndex, setSelectedSubscriptionIndex] = useState(0);\n const [isSubscriptionSelectorOpen, setIsSubscriptionSelectorOpen] = useState(false);\n\n const {\n billingAccounts,\n currentAccount,\n paymentMethods,\n activeSubscriptions,\n hasActiveSubscription,\n recommendedAddons,\n isLoading,\n error,\n refetchAll,\n } = useBillingOverviewData(selectedAccountId, productId);\n\n const currentAccountId = currentAccount?.id;\n const isLoadingAddons = isLoading;\n\n const setSelectedAccountId = useCallback(\n (accountId: string | null) => {\n setSelectedAccountIdState(accountId);\n setStoredBillingAccountId(orgId, accountId);\n },\n [orgId]\n );\n\n // Addon cart store for add to cart functionality\n const { addToCart, isInCart, getCartItem, updateQuantity, removeFromCart } = useAddonCartStore();\n\n const {\n createBillingAccount,\n setBillingAddress,\n setDefaultPaymentMethod,\n deletePaymentMethod,\n isCreatingAccount,\n } = useDashboardMutations();\n\n // Reset subscription selector when account changes or subscriptions change\n useEffect(() => {\n setSelectedSubscriptionIndex(0);\n setIsSubscriptionSelectorOpen(false);\n }, [currentAccountId, activeSubscriptions.length]);\n\n useEffect(() => {\n if (selectedAccountId && billingAccounts.length > 0) {\n const accountExists = billingAccounts.some((account) => account.id === selectedAccountId);\n if (!accountExists) {\n setSelectedAccountId(null);\n }\n }\n }, [billingAccounts, selectedAccountId, setSelectedAccountId]);\n\n useEffect(() => {\n if (!selectedAccountId && billingAccounts.length > 0) {\n const defaultAccount =\n billingAccounts.find((account) => account.isDefault) ?? billingAccounts[0];\n setSelectedAccountId(defaultAccount.id);\n }\n }, [billingAccounts, selectedAccountId, setSelectedAccountId]);\n\n // Derive selected subscription from active subscriptions\n const selectedSubscription =\n activeSubscriptions[selectedSubscriptionIndex] || activeSubscriptions[0] || null;\n\n // Helper for navigation - properly joins basePath and path\n const navigateTo = useCallback(\n (path: string) => {\n // Handle path joining to avoid double slashes (e.g., / + /plans = //plans)\n let fullPath = path;\n if (basePath && basePath !== '/') {\n // Remove trailing slash from basePath and leading slash from path if needed\n const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n const suffix = path.startsWith('/') ? path : `/${path}`;\n fullPath = `${base}${suffix}`;\n }\n navigate(fullPath);\n },\n [navigate, basePath]\n );\n\n // Handlers\n const handleCreateAccount = useCallback(\n async (\n input: CreateBillingAccountInput,\n address?: Omit<SetBillingAddressInput, 'billingAccountId'>\n ) => {\n setCreateError(null);\n try {\n const newAccount = await createBillingAccount(input);\n\n // If address data provided, set the billing address\n if (address && newAccount.id) {\n await setBillingAddress({\n billingAccountId: newAccount.id,\n ...address,\n });\n }\n\n setIsCreateModalOpen(false);\n setSelectedAccountId(newAccount.id);\n emit('billing.billing_account.created', {\n route: '/billing/overview',\n entityId: newAccount.id,\n source: 'overview',\n });\n await refetchAll();\n } catch (err) {\n setCreateError(err instanceof Error ? err.message : 'Failed to create billing account');\n }\n },\n [createBillingAccount, emit, refetchAll, setBillingAddress, setSelectedAccountId]\n );\n\n const handleAddPaymentMethod = useCallback(() => {\n setIsAddPaymentModalOpen(true);\n }, []);\n\n // Helper to refetch the current account data\n const refetchCurrentAccount = useCallback(async () => {\n await refetchAll();\n }, [refetchAll]);\n\n const handlePaymentMethodAdded = useCallback(async () => {\n emit('billing.payment_method.added', {\n route: '/billing/overview',\n entityId: currentAccountId,\n source: 'overview',\n });\n await refetchCurrentAccount();\n }, [currentAccountId, emit, refetchCurrentAccount]);\n\n const handleSetDefaultPaymentMethod = useCallback(\n async (id: string) => {\n if (!currentAccountId) return;\n await setDefaultPaymentMethod(id, currentAccountId);\n emit('billing.payment_method.default_set', {\n route: '/billing/overview',\n entityId: id,\n source: 'overview',\n });\n await refetchCurrentAccount();\n },\n [currentAccountId, emit, setDefaultPaymentMethod, refetchCurrentAccount]\n );\n\n const handleDeletePaymentMethod = useCallback(\n async (id: string) => {\n if (!currentAccountId) return;\n await deletePaymentMethod(id, currentAccountId);\n emit('billing.payment_method.deleted', {\n route: '/billing/overview',\n entityId: id,\n source: 'overview',\n });\n await refetchCurrentAccount();\n },\n [currentAccountId, deletePaymentMethod, emit, refetchCurrentAccount]\n );\n\n const handleSelectAccount = useCallback(\n (account: BillingAccount) => {\n setSelectedAccountId(account.id);\n setIsAccountSelectorOpen(false);\n },\n [setSelectedAccountId]\n );\n\n const handleRefresh = useCallback(async () => {\n await refetchAll();\n }, [refetchAll]);\n\n // Handler for adding addon to cart\n const handleAddToCart = useCallback(\n (addon: Addon) => {\n addToCart(addon);\n },\n [addToCart]\n );\n\n // Handler to check if addon is in cart\n const handleIsInCart = useCallback(\n (addonId: string) => {\n return isInCart(addonId);\n },\n [isInCart]\n );\n\n // Handler to get cart quantity for addon\n const handleGetCartQuantity = useCallback(\n (addonId: string) => {\n const item = getCartItem(addonId);\n return item?.quantity || 0;\n },\n [getCartItem]\n );\n\n // Handler to update quantity in cart\n const handleUpdateQuantity = useCallback(\n (addonId: string, quantity: number) => {\n if (quantity <= 0) {\n removeFromCart(addonId);\n } else {\n updateQuantity(addonId, quantity);\n }\n },\n [updateQuantity, removeFromCart]\n );\n\n // Handler to remove from cart\n const handleRemoveFromCart = useCallback(\n (addonId: string) => {\n removeFromCart(addonId);\n },\n [removeFromCart]\n );\n\n // Permission check - must be after all hooks are defined\n if (!permissions.canViewBillingAccount) {\n return <AccessDenied message=\"You don't have permission to view billing information.\" />;\n }\n\n // Error state\n if (error && isServerError(error) && !isLoading) {\n return (\n <div className=\"p-6\">\n <ServerError\n title=\"Server Unavailable\"\n message=\"Unable to load billing overview. The server might be down or experiencing issues.\"\n onRetry={handleRefresh}\n showRetry\n />\n </div>\n );\n }\n\n // Loading state\n if (isLoading && !currentAccount) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"flex flex-col items-center gap-3\">\n <Loader2 className=\"size-8 animate-spin text-primary\" />\n <p className=\"text-sm text-muted-foreground\">\n {tr('billing.overview.loadingInfo', 'Loading billing information...')}\n </p>\n </div>\n </div>\n );\n }\n\n // No billing accounts state\n if (!isLoading && billingAccounts.length === 0) {\n return (\n <div className=\"p-6\">\n <header className=\"mb-8\">\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.overview.title', 'Billing Overview')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr(\n 'billing.overview.emptySetupDescription',\n 'Set up billing to manage subscriptions, payments, and invoices'\n )}\n </p>\n </header>\n <EmptyState\n icon={<Building2 className=\"size-6 text-muted-foreground\" />}\n title={tr('billing.overview.emptyTitle', 'No billing account')}\n description={tr(\n 'billing.overview.emptyDescription',\n 'Create a billing account to start managing your subscriptions and payments'\n )}\n action={\n permissions.canManageBillingAccount ? (\n <button\n type=\"button\"\n onClick={() => setIsCreateModalOpen(true)}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('billing.overview.createBillingAccount', 'Create Billing Account')}\n </button>\n ) : undefined\n }\n />\n\n <CreateBillingAccountModal\n isOpen={isCreateModalOpen}\n onClose={() => {\n setIsCreateModalOpen(false);\n setCreateError(null);\n }}\n onSubmit={handleCreateAccount}\n isSubmitting={isCreatingAccount}\n error={createError}\n />\n </div>\n );\n }\n\n return (\n <div className=\"p-4 sm:p-6 space-y-6 sm:space-y-8\">\n {/* Header */}\n <header className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.overview.title', 'Billing Overview')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr(\n 'billing.overview.description',\n 'Manage your billing accounts, subscriptions, and payment methods'\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleRefresh}\n className=\"p-2 rounded-lg border border-border hover:bg-muted text-muted-foreground hover:text-foreground transition-colors\"\n aria-label={tr('billing.overview.refresh', 'Refresh')}\n >\n <RefreshCw className={`size-4 ${isLoading ? 'animate-spin' : ''}`} />\n </button>\n {permissions.canManageBillingAccount && billingAccounts.length > 0 && (\n <button\n type=\"button\"\n onClick={() => setIsCreateModalOpen(true)}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors\"\n >\n <Plus className=\"size-4\" />\n <span className=\"hidden sm:inline\">\n {tr('billing.overview.newAccount', 'New Account')}\n </span>\n </button>\n )}\n </div>\n </header>\n\n {/* Account Selector (if multiple accounts) */}\n {billingAccounts.length > 1 && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={() => setIsAccountSelectorOpen(!isAccountSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border rounded-lg bg-card hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 rounded-lg bg-primary/10 flex items-center justify-center\">\n <Building2 className=\"size-4 text-primary\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-foreground\">\n {currentAccount?.name || tr('billing.overview.selectAccount', 'Select Account')}\n </p>\n <p className=\"text-xs text-muted-foreground\">{currentAccount?.email}</p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-muted-foreground transition-transform ${isAccountSelectorOpen ? 'rotate-180' : ''}`}\n />\n </button>\n\n {isAccountSelectorOpen && (\n <>\n <div className=\"fixed inset-0 z-10\" onClick={() => setIsAccountSelectorOpen(false)} />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-popover border border-border rounded-lg shadow-lg z-20 py-1 max-h-60 overflow-y-auto\">\n {billingAccounts.map((account) => (\n <button\n type=\"button\"\n key={account.id}\n onClick={() => handleSelectAccount(account)}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-muted transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-8 rounded-lg bg-muted flex items-center justify-center flex-shrink-0\">\n <Building2 className=\"size-4 text-muted-foreground\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-foreground truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">{account.email}</p>\n </div>\n </div>\n {currentAccount?.id === account.id && (\n <Check className=\"size-4 text-primary flex-shrink-0\" />\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Stats Row */}\n {currentAccount && (\n <div className=\"grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4\">\n <StatCard\n label={tr('billing.overview.stats.creditBalance', 'Credit Balance')}\n value={`${(currentAccount.creditAmount || 0).toLocaleString()} Credits`}\n icon={<Wallet className=\"size-5\" />}\n />\n <StatCard\n label={tr('billing.overview.stats.activePlans', 'Active Plans')}\n value={\n activeSubscriptions.length > 0\n ? activeSubscriptions.length === 1\n ? activeSubscriptions[0]?.plan?.name || '1 Plan'\n : `${activeSubscriptions.length} ${tr('billing.overview.stats.plansSuffix', 'Plans')}`\n : tr('billing.overview.stats.none', 'None')\n }\n icon={<RefreshCw className=\"size-5\" />}\n />\n <StatCard\n label={tr('billing.overview.stats.paymentMethods', 'Payment Methods')}\n value={paymentMethods.length}\n icon={<Building2 className=\"size-5\" />}\n />\n <StatCard\n label={tr('billing.overview.stats.paymentCurrency', 'Payment Currency')}\n value={currentAccount.currency || 'USD'}\n icon={<Building2 className=\"size-5\" />}\n />\n </div>\n )}\n\n {/* Main Content Grid */}\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Left Column - Billing Account & Subscription */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Billing Account Card */}\n {currentAccount && (\n <BillingAccountCard\n account={currentAccount}\n isDefault={currentAccount.isDefault ?? false}\n onEdit={() =>\n currentAccount && navigateTo(`/settings?billingAccountId=${currentAccount.id}`)\n }\n onViewTransactions={() => navigateTo(`/transactions/${currentAccount.id}`)}\n onViewCreditTransactions={() =>\n navigateTo(`/creditTransactions/${currentAccount.id}`)\n }\n />\n )}\n\n {/* Active Subscriptions */}\n <div className=\"space-y-3\">\n {/* Subscription Selector (if multiple subscriptions) */}\n {activeSubscriptions.length > 1 && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={() => setIsSubscriptionSelectorOpen(!isSubscriptionSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border rounded-lg bg-card hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 rounded-lg bg-primary/10 flex items-center justify-center\">\n <Crown className=\"size-4 text-primary\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-foreground\">\n {selectedSubscription?.plan?.name ||\n tr('billing.overview.selectSubscription', 'Select Subscription')}\n {selectedSubscription?.plan?.product?.name && (\n <span className=\"text-muted-foreground font-normal\">\n {' '}\n · {selectedSubscription.plan.product.name}\n </span>\n )}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {activeSubscriptions.length}{' '}\n {tr('billing.overview.activeSubscriptions', 'active subscriptions')}\n </p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-muted-foreground transition-transform ${isSubscriptionSelectorOpen ? 'rotate-180' : ''}`}\n />\n </button>\n\n {isSubscriptionSelectorOpen && (\n <>\n <div\n className=\"fixed inset-0 z-10\"\n onClick={() => setIsSubscriptionSelectorOpen(false)}\n />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-popover border border-border rounded-lg shadow-lg z-20 py-1 max-h-60 overflow-y-auto\">\n {activeSubscriptions.map((subscription, index) => (\n <button\n type=\"button\"\n key={subscription.id}\n onClick={() => {\n setSelectedSubscriptionIndex(index);\n setIsSubscriptionSelectorOpen(false);\n }}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-muted transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-8 rounded-lg bg-muted flex items-center justify-center flex-shrink-0\">\n <Crown className=\"size-4 text-muted-foreground\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-foreground truncate\">\n {subscription.plan?.name ||\n tr('billing.overview.subscription', 'Subscription')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">\n {subscription.plan?.product?.name ||\n tr('billing.overview.unknownProduct', 'Unknown Product')}{' '}\n · {tr('billing.overview.ends', 'Ends')}{' '}\n {new Date(subscription.endDate).toLocaleDateString()}\n </p>\n </div>\n </div>\n {selectedSubscriptionIndex === index && (\n <Check className=\"size-4 text-primary flex-shrink-0\" />\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Single Subscription Card */}\n <ActiveSubscriptionCard\n subscription={selectedSubscription}\n onViewDetails={() =>\n selectedSubscription &&\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${selectedSubscription.id}`,\n selectedSubscription.billingAccountId ?? currentAccount?.id\n )\n )\n }\n onUpgrade={() =>\n selectedSubscription &&\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${selectedSubscription.id}/upgrade`,\n selectedSubscription.billingAccountId ?? currentAccount?.id\n )\n )\n }\n onManage={() =>\n currentAccount && navigateTo(`/subscriptions?billingAccountId=${currentAccount.id}`)\n }\n onPurchasePlan={() => navigateTo('/plans')}\n />\n </div>\n\n {/* Payment Methods */}\n {currentAccount && (\n <PaymentMethodsSection\n paymentMethods={paymentMethods}\n billingAccountId={currentAccount.id}\n onAddPaymentMethod={handleAddPaymentMethod}\n onSetDefault={handleSetDefaultPaymentMethod}\n onDelete={handleDeletePaymentMethod}\n isLoading={isLoading}\n />\n )}\n </div>\n\n {/* Right Column - Quick Actions & Info */}\n <div className=\"space-y-6\">\n {/* Quick Actions */}\n <section className=\"border border-border rounded-lg bg-card p-4\">\n <h3 className=\"font-semibold text-foreground mb-4\">Quick Actions</h3>\n <div className=\"space-y-2\">\n <button\n type=\"button\"\n onClick={() => currentAccount && navigateTo(`/transactions/${currentAccount.id}`)}\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-foreground hover:bg-muted rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-8 rounded bg-status-info-bg-subtle flex items-center justify-center\">\n <Receipt className=\"size-4 text-accent-blue\" />\n </div>\n View Transactions\n </button>\n <button\n type=\"button\"\n onClick={() =>\n currentAccount && navigateTo(`/creditTransactions/${currentAccount.id}`)\n }\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-foreground hover:bg-muted rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-8 rounded bg-status-warning-bg-subtle flex items-center justify-center\">\n <Wallet className=\"size-4 text-status-warning-text\" />\n </div>\n Credit History\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/checkout/credits')}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <div className=\"size-8 rounded bg-status-success-bg-subtle flex items-center justify-center\">\n <Coins className=\"size-4 text-status-success-text\" />\n </div>\n Purchase Credits\n </button>\n <button\n type=\"button\"\n onClick={() =>\n currentAccount && navigateTo(`/usage?billingAccountId=${currentAccount.id}`)\n }\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-foreground hover:bg-muted rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-8 rounded bg-accent-violet-subtle flex items-center justify-center\">\n <Activity className=\"size-4 text-accent-violet\" />\n </div>\n Usage Analytics\n </button>\n </div>\n </section>\n\n {/* Get Started (if no subscription) */}\n {!hasActiveSubscription && (\n <section className=\"border border-border rounded-lg bg-gradient-to-br from-primary/5 to-primary/10 p-4\">\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"size-10 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0\">\n <Sparkles className=\"size-5 text-primary\" />\n </div>\n <div>\n <h3 className=\"font-semibold text-foreground\">Get Started</h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n Choose a plan that fits your needs to unlock all features\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"w-full flex items-center justify-center gap-2 px-4 py-2.5 text-sm font-medium text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors\"\n >\n Browse Plans\n <ArrowRight className=\"size-4\" />\n </button>\n </section>\n )}\n </div>\n </div>\n\n {/* Recommended Addons (only if has subscription) - Limited to 3 with cart support */}\n <RecommendedAddonsSection\n addons={recommendedAddons}\n hasSubscription={hasActiveSubscription}\n onViewAddon={(addonId) => navigateTo(`/addons/${addonId}`)}\n onPurchaseAddon={(addonId) => navigateTo(`/addons/${addonId}?action=purchase`)}\n onViewAllAddons={() => navigateTo('/addons')}\n onAddToCart={handleAddToCart}\n isInCart={handleIsInCart}\n getCartQuantity={handleGetCartQuantity}\n onUpdateQuantity={handleUpdateQuantity}\n onRemoveFromCart={handleRemoveFromCart}\n maxAddons={3}\n isLoading={isLoadingAddons}\n />\n\n {/* Floating Cart Drawer - shows when items are in cart */}\n <FloatingCartDrawer />\n\n {/* Create Billing Account Modal */}\n <CreateBillingAccountModal\n isOpen={isCreateModalOpen}\n onClose={() => {\n setIsCreateModalOpen(false);\n setCreateError(null);\n }}\n onSubmit={handleCreateAccount}\n isSubmitting={isCreatingAccount}\n error={createError}\n />\n\n {/* Add Payment Method Modal */}\n {currentAccount && (\n <AddPaymentMethodModal\n isOpen={isAddPaymentModalOpen}\n onClose={() => setIsAddPaymentModalOpen(false)}\n billingAccountId={currentAccount.id}\n currency={currentAccount.currency}\n country={currentAccount.billingAddresses?.[0]?.country}\n onSuccess={handlePaymentMethodAdded}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OverviewPage.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/pages/OverviewPage.tsx"],"sourcesContent":["/**\n * Billing Overview Page\n * Main dashboard for billing showing accounts, subscriptions, payment methods, and addons\n */\n\nimport { type FC, useState, useCallback, useEffect } from 'react';\nimport {\n Plus,\n Building2,\n RefreshCw,\n Loader2,\n Wallet,\n ChevronDown,\n Check,\n Activity,\n Receipt,\n ArrowRight,\n Sparkles,\n Crown,\n Coins,\n} from 'lucide-react';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { StatCard } from '../../../shared/components/StatCard';\nimport { EmptyState } from '../../../shared/components/EmptyState';\nimport { ServerError } from '../../../shared/components/ServerError';\nimport { AccessDenied } from '../../../shared/components/AccessDenied';\nimport { isServerError } from '../../../shared/utils';\nimport { useBillingOverviewData, useDashboardMutations } from '../hooks';\nimport {\n BillingAccountCard,\n PaymentMethodsSection,\n ActiveSubscriptionCard,\n RecommendedAddonsSection,\n CreateBillingAccountModal,\n AddPaymentMethodModal,\n} from '../components';\nimport { useAddonCartStore } from '../../addons/store/addonCartStore';\nimport { FloatingCartDrawer } from '../../addons/components/FloatingCartDrawer';\nimport {\n getStoredBillingAccountId,\n setStoredBillingAccountId,\n} from '../../../hooks/useBillingAccountSelection';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport type {\n BillingAccount,\n CreateBillingAccountInput,\n SetBillingAddressInput,\n Addon,\n} from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport const OverviewPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { navigate, basePath, orgId, productId } = useBilling();\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n\n const [selectedAccountId, setSelectedAccountIdState] = useState<string | null>(() =>\n getStoredBillingAccountId(orgId)\n );\n\n const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);\n const [createError, setCreateError] = useState<string | null>(null);\n const [isAccountSelectorOpen, setIsAccountSelectorOpen] = useState(false);\n const [isAddPaymentModalOpen, setIsAddPaymentModalOpen] = useState(false);\n const [selectedSubscriptionIndex, setSelectedSubscriptionIndex] = useState(0);\n const [isSubscriptionSelectorOpen, setIsSubscriptionSelectorOpen] = useState(false);\n\n const {\n billingAccounts,\n currentAccount,\n paymentMethods,\n activeSubscriptions,\n hasActiveSubscription,\n recommendedAddons,\n isLoading,\n error,\n refetchAll,\n } = useBillingOverviewData(selectedAccountId, productId);\n\n const currentAccountId = currentAccount?.id;\n const isLoadingAddons = isLoading;\n\n const setSelectedAccountId = useCallback(\n (accountId: string | null) => {\n setSelectedAccountIdState(accountId);\n setStoredBillingAccountId(orgId, accountId);\n },\n [orgId]\n );\n\n // Addon cart store for add to cart functionality\n const { addToCart, isInCart, getCartItem, updateQuantity, removeFromCart } = useAddonCartStore();\n\n const {\n createBillingAccount,\n setBillingAddress,\n setDefaultPaymentMethod,\n deletePaymentMethod,\n isCreatingAccount,\n } = useDashboardMutations();\n\n // Reset subscription selector when account changes or subscriptions change\n useEffect(() => {\n setSelectedSubscriptionIndex(0);\n setIsSubscriptionSelectorOpen(false);\n }, [currentAccountId, activeSubscriptions.length]);\n\n useEffect(() => {\n if (selectedAccountId && billingAccounts.length > 0) {\n const accountExists = billingAccounts.some((account) => account.id === selectedAccountId);\n if (!accountExists) {\n setSelectedAccountId(null);\n }\n }\n }, [billingAccounts, selectedAccountId, setSelectedAccountId]);\n\n useEffect(() => {\n if (!selectedAccountId && billingAccounts.length > 0) {\n const defaultAccount =\n billingAccounts.find((account) => account.isDefault) ?? billingAccounts[0];\n setSelectedAccountId(defaultAccount.id);\n }\n }, [billingAccounts, selectedAccountId, setSelectedAccountId]);\n\n // Derive selected subscription from active subscriptions\n const selectedSubscription =\n activeSubscriptions[selectedSubscriptionIndex] || activeSubscriptions[0] || null;\n\n // Helper for navigation - properly joins basePath and path\n const navigateTo = useCallback(\n (path: string) => {\n // Handle path joining to avoid double slashes (e.g., / + /plans = //plans)\n let fullPath = path;\n if (basePath && basePath !== '/') {\n // Remove trailing slash from basePath and leading slash from path if needed\n const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n const suffix = path.startsWith('/') ? path : `/${path}`;\n fullPath = `${base}${suffix}`;\n }\n navigate(fullPath);\n },\n [navigate, basePath]\n );\n\n // Handlers\n const handleCreateAccount = useCallback(\n async (\n input: CreateBillingAccountInput,\n address?: Omit<SetBillingAddressInput, 'billingAccountId'>\n ) => {\n setCreateError(null);\n try {\n const newAccount = await createBillingAccount(input);\n\n // If address data provided, set the billing address\n if (address && newAccount.id) {\n await setBillingAddress({\n billingAccountId: newAccount.id,\n ...address,\n });\n }\n\n setIsCreateModalOpen(false);\n setSelectedAccountId(newAccount.id);\n emit('billing.billing_account.created', {\n route: '/billing/overview',\n entityId: newAccount.id,\n source: 'overview',\n });\n await refetchAll();\n } catch (err) {\n setCreateError(err instanceof Error ? err.message : 'Failed to create billing account');\n }\n },\n [createBillingAccount, emit, refetchAll, setBillingAddress, setSelectedAccountId]\n );\n\n const handleAddPaymentMethod = useCallback(() => {\n setIsAddPaymentModalOpen(true);\n }, []);\n\n // Helper to refetch the current account data\n const refetchCurrentAccount = useCallback(async () => {\n await refetchAll();\n }, [refetchAll]);\n\n const handlePaymentMethodAdded = useCallback(async () => {\n emit('billing.payment_method.added', {\n route: '/billing/overview',\n entityId: currentAccountId,\n source: 'overview',\n });\n await refetchCurrentAccount();\n }, [currentAccountId, emit, refetchCurrentAccount]);\n\n const handleSetDefaultPaymentMethod = useCallback(\n async (id: string) => {\n if (!currentAccountId) return;\n await setDefaultPaymentMethod(id, currentAccountId);\n emit('billing.payment_method.default_set', {\n route: '/billing/overview',\n entityId: id,\n source: 'overview',\n });\n await refetchCurrentAccount();\n },\n [currentAccountId, emit, setDefaultPaymentMethod, refetchCurrentAccount]\n );\n\n const handleDeletePaymentMethod = useCallback(\n async (id: string) => {\n if (!currentAccountId) return;\n await deletePaymentMethod(id, currentAccountId);\n emit('billing.payment_method.deleted', {\n route: '/billing/overview',\n entityId: id,\n source: 'overview',\n });\n await refetchCurrentAccount();\n },\n [currentAccountId, deletePaymentMethod, emit, refetchCurrentAccount]\n );\n\n const handleSelectAccount = useCallback(\n (account: BillingAccount) => {\n setSelectedAccountId(account.id);\n setIsAccountSelectorOpen(false);\n },\n [setSelectedAccountId]\n );\n\n const handleRefresh = useCallback(async () => {\n await refetchAll();\n }, [refetchAll]);\n\n // Handler for adding addon to cart\n const handleAddToCart = useCallback(\n (addon: Addon) => {\n addToCart(addon);\n },\n [addToCart]\n );\n\n // Handler to check if addon is in cart\n const handleIsInCart = useCallback(\n (addonId: string) => {\n return isInCart(addonId);\n },\n [isInCart]\n );\n\n // Handler to get cart quantity for addon\n const handleGetCartQuantity = useCallback(\n (addonId: string) => {\n const item = getCartItem(addonId);\n return item?.quantity || 0;\n },\n [getCartItem]\n );\n\n // Handler to update quantity in cart\n const handleUpdateQuantity = useCallback(\n (addonId: string, quantity: number) => {\n if (quantity <= 0) {\n removeFromCart(addonId);\n } else {\n updateQuantity(addonId, quantity);\n }\n },\n [updateQuantity, removeFromCart]\n );\n\n // Handler to remove from cart\n const handleRemoveFromCart = useCallback(\n (addonId: string) => {\n removeFromCart(addonId);\n },\n [removeFromCart]\n );\n\n // Permission check - must be after all hooks are defined\n if (!permissions.canViewBillingAccount) {\n return <AccessDenied message=\"You don't have permission to view billing information.\" />;\n }\n\n // Error state\n if (error && isServerError(error) && !isLoading) {\n return (\n <div className=\"p-6\">\n <ServerError\n title=\"Server Unavailable\"\n message=\"Unable to load billing overview. The server might be down or experiencing issues.\"\n onRetry={handleRefresh}\n showRetry\n />\n </div>\n );\n }\n\n // Loading state\n if (isLoading && !currentAccount) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"flex flex-col items-center gap-3\">\n <Loader2 className=\"size-8 animate-spin text-primary\" />\n <p className=\"text-sm text-muted-foreground\">\n {tr('billing.overview.loadingInfo', 'Loading billing information...')}\n </p>\n </div>\n </div>\n );\n }\n\n // No billing accounts state\n if (!isLoading && billingAccounts.length === 0) {\n return (\n <div className=\"p-6\">\n <header className=\"mb-8\">\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.overview.title', 'Billing Overview')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr(\n 'billing.overview.emptySetupDescription',\n 'Set up billing to manage subscriptions, payments, and invoices'\n )}\n </p>\n </header>\n <EmptyState\n icon={<Building2 className=\"size-6 text-muted-foreground\" />}\n title={tr('billing.overview.emptyTitle', 'No billing account')}\n description={tr(\n 'billing.overview.emptyDescription',\n 'Create a billing account to start managing your subscriptions and payments'\n )}\n action={\n permissions.canManageBillingAccount ? (\n <button\n type=\"button\"\n onClick={() => setIsCreateModalOpen(true)}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('billing.overview.createBillingAccount', 'Create Billing Account')}\n </button>\n ) : undefined\n }\n />\n\n <CreateBillingAccountModal\n isOpen={isCreateModalOpen}\n onClose={() => {\n setIsCreateModalOpen(false);\n setCreateError(null);\n }}\n onSubmit={handleCreateAccount}\n isSubmitting={isCreatingAccount}\n error={createError}\n />\n </div>\n );\n }\n\n return (\n <div className=\"p-4 sm:p-6 space-y-6 sm:space-y-8\">\n {/* Header */}\n <header className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.overview.title', 'Billing Overview')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr(\n 'billing.overview.description',\n 'Manage your billing accounts, subscriptions, and payment methods'\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleRefresh}\n className=\"p-2 rounded-lg border border-border hover:bg-muted text-muted-foreground hover:text-foreground transition-colors\"\n aria-label={tr('billing.overview.refresh', 'Refresh')}\n >\n <RefreshCw className={`size-4 ${isLoading ? 'animate-spin' : ''}`} />\n </button>\n {permissions.canManageBillingAccount && billingAccounts.length > 0 && (\n <button\n type=\"button\"\n onClick={() => setIsCreateModalOpen(true)}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors\"\n >\n <Plus className=\"size-4\" />\n <span className=\"hidden sm:inline\">\n {tr('billing.overview.newAccount', 'New Account')}\n </span>\n </button>\n )}\n </div>\n </header>\n\n {/* Account Selector (if multiple accounts) */}\n {billingAccounts.length > 1 && (\n <div className=\"relative\" data-tour=\"billing-overview-account-selector\">\n <button\n type=\"button\"\n onClick={() => setIsAccountSelectorOpen(!isAccountSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border rounded-lg bg-card hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 rounded-lg bg-primary/10 flex items-center justify-center\">\n <Building2 className=\"size-4 text-primary\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-foreground\">\n {currentAccount?.name || tr('billing.overview.selectAccount', 'Select Account')}\n </p>\n <p className=\"text-xs text-muted-foreground\">{currentAccount?.email}</p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-muted-foreground transition-transform ${isAccountSelectorOpen ? 'rotate-180' : ''}`}\n />\n </button>\n\n {isAccountSelectorOpen && (\n <>\n <div className=\"fixed inset-0 z-10\" onClick={() => setIsAccountSelectorOpen(false)} />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-popover border border-border rounded-lg shadow-lg z-20 py-1 max-h-60 overflow-y-auto\">\n {billingAccounts.map((account) => (\n <button\n type=\"button\"\n key={account.id}\n onClick={() => handleSelectAccount(account)}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-muted transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-8 rounded-lg bg-muted flex items-center justify-center flex-shrink-0\">\n <Building2 className=\"size-4 text-muted-foreground\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-foreground truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">{account.email}</p>\n </div>\n </div>\n {currentAccount?.id === account.id && (\n <Check className=\"size-4 text-primary flex-shrink-0\" />\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Stats Row */}\n {currentAccount && (\n <div\n className=\"grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4\"\n data-tour=\"billing-overview-stats-row\"\n >\n <StatCard\n label={tr('billing.overview.stats.creditBalance', 'Credit Balance')}\n value={`${(currentAccount.creditAmount || 0).toLocaleString()} Credits`}\n icon={<Wallet className=\"size-5\" />}\n />\n <StatCard\n label={tr('billing.overview.stats.activePlans', 'Active Plans')}\n value={\n activeSubscriptions.length > 0\n ? activeSubscriptions.length === 1\n ? activeSubscriptions[0]?.plan?.name || '1 Plan'\n : `${activeSubscriptions.length} ${tr('billing.overview.stats.plansSuffix', 'Plans')}`\n : tr('billing.overview.stats.none', 'None')\n }\n icon={<RefreshCw className=\"size-5\" />}\n />\n <StatCard\n label={tr('billing.overview.stats.paymentMethods', 'Payment Methods')}\n value={paymentMethods.length}\n icon={<Building2 className=\"size-5\" />}\n />\n <StatCard\n label={tr('billing.overview.stats.paymentCurrency', 'Payment Currency')}\n value={currentAccount.currency || 'USD'}\n icon={<Building2 className=\"size-5\" />}\n />\n </div>\n )}\n\n {/* Main Content Grid */}\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Left Column - Billing Account & Subscription */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Billing Account Card */}\n {currentAccount && (\n <BillingAccountCard\n dataTour=\"billing-overview-billing-account-card\"\n account={currentAccount}\n isDefault={currentAccount.isDefault ?? false}\n onEdit={() =>\n currentAccount && navigateTo(`/settings?billingAccountId=${currentAccount.id}`)\n }\n onViewTransactions={() => navigateTo(`/transactions/${currentAccount.id}`)}\n onViewCreditTransactions={() =>\n navigateTo(`/creditTransactions/${currentAccount.id}`)\n }\n />\n )}\n\n {/* Active Subscriptions */}\n <div className=\"space-y-3\">\n {/* Subscription Selector (if multiple subscriptions) */}\n {activeSubscriptions.length > 1 && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={() => setIsSubscriptionSelectorOpen(!isSubscriptionSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border rounded-lg bg-card hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-8 rounded-lg bg-primary/10 flex items-center justify-center\">\n <Crown className=\"size-4 text-primary\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-foreground\">\n {selectedSubscription?.plan?.name ||\n tr('billing.overview.selectSubscription', 'Select Subscription')}\n {selectedSubscription?.plan?.product?.name && (\n <span className=\"text-muted-foreground font-normal\">\n {' '}\n · {selectedSubscription.plan.product.name}\n </span>\n )}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {activeSubscriptions.length}{' '}\n {tr('billing.overview.activeSubscriptions', 'active subscriptions')}\n </p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-muted-foreground transition-transform ${isSubscriptionSelectorOpen ? 'rotate-180' : ''}`}\n />\n </button>\n\n {isSubscriptionSelectorOpen && (\n <>\n <div\n className=\"fixed inset-0 z-10\"\n onClick={() => setIsSubscriptionSelectorOpen(false)}\n />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-popover border border-border rounded-lg shadow-lg z-20 py-1 max-h-60 overflow-y-auto\">\n {activeSubscriptions.map((subscription, index) => (\n <button\n type=\"button\"\n key={subscription.id}\n onClick={() => {\n setSelectedSubscriptionIndex(index);\n setIsSubscriptionSelectorOpen(false);\n }}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-muted transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-8 rounded-lg bg-muted flex items-center justify-center flex-shrink-0\">\n <Crown className=\"size-4 text-muted-foreground\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-foreground truncate\">\n {subscription.plan?.name ||\n tr('billing.overview.subscription', 'Subscription')}\n </p>\n <p className=\"text-xs text-muted-foreground truncate\">\n {subscription.plan?.product?.name ||\n tr('billing.overview.unknownProduct', 'Unknown Product')}{' '}\n · {tr('billing.overview.ends', 'Ends')}{' '}\n {new Date(subscription.endDate).toLocaleDateString()}\n </p>\n </div>\n </div>\n {selectedSubscriptionIndex === index && (\n <Check className=\"size-4 text-primary flex-shrink-0\" />\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Single Subscription Card */}\n <ActiveSubscriptionCard\n dataTour=\"billing-overview-active-subscription-card\"\n subscription={selectedSubscription}\n onViewDetails={() =>\n selectedSubscription &&\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${selectedSubscription.id}`,\n selectedSubscription.billingAccountId ?? currentAccount?.id\n )\n )\n }\n onUpgrade={() =>\n selectedSubscription &&\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${selectedSubscription.id}/upgrade`,\n selectedSubscription.billingAccountId ?? currentAccount?.id\n )\n )\n }\n onManage={() =>\n currentAccount && navigateTo(`/subscriptions?billingAccountId=${currentAccount.id}`)\n }\n onPurchasePlan={() => navigateTo('/plans')}\n />\n </div>\n\n {/* Payment Methods */}\n {currentAccount && (\n <PaymentMethodsSection\n dataTour=\"billing-overview-payment-methods-section\"\n paymentMethods={paymentMethods}\n billingAccountId={currentAccount.id}\n onAddPaymentMethod={handleAddPaymentMethod}\n onSetDefault={handleSetDefaultPaymentMethod}\n onDelete={handleDeletePaymentMethod}\n isLoading={isLoading}\n />\n )}\n </div>\n\n {/* Right Column - Quick Actions & Info */}\n <div className=\"space-y-6\">\n {/* Quick Actions */}\n <section\n className=\"border border-border rounded-lg bg-card p-4\"\n data-tour=\"billing-overview-quick-actions\"\n >\n <h3 className=\"font-semibold text-foreground mb-4\">Quick Actions</h3>\n <div className=\"space-y-2\">\n <button\n type=\"button\"\n onClick={() => currentAccount && navigateTo(`/transactions/${currentAccount.id}`)}\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-foreground hover:bg-muted rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-8 rounded bg-status-info-bg-subtle flex items-center justify-center\">\n <Receipt className=\"size-4 text-accent-blue\" />\n </div>\n View Transactions\n </button>\n <button\n type=\"button\"\n onClick={() =>\n currentAccount && navigateTo(`/creditTransactions/${currentAccount.id}`)\n }\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-foreground hover:bg-muted rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-8 rounded bg-status-warning-bg-subtle flex items-center justify-center\">\n <Wallet className=\"size-4 text-status-warning-text\" />\n </div>\n Credit History\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/checkout/credits')}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-foreground hover:bg-muted rounded-lg transition-colors\"\n >\n <div className=\"size-8 rounded bg-status-success-bg-subtle flex items-center justify-center\">\n <Coins className=\"size-4 text-status-success-text\" />\n </div>\n Purchase Credits\n </button>\n <button\n type=\"button\"\n onClick={() =>\n currentAccount && navigateTo(`/usage?billingAccountId=${currentAccount.id}`)\n }\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-foreground hover:bg-muted rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-8 rounded bg-accent-violet-subtle flex items-center justify-center\">\n <Activity className=\"size-4 text-accent-violet\" />\n </div>\n Usage Analytics\n </button>\n </div>\n </section>\n\n {/* Get Started (if no subscription) */}\n {!hasActiveSubscription && (\n <section className=\"border border-border rounded-lg bg-gradient-to-br from-primary/5 to-primary/10 p-4\">\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"size-10 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0\">\n <Sparkles className=\"size-5 text-primary\" />\n </div>\n <div>\n <h3 className=\"font-semibold text-foreground\">Get Started</h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n Choose a plan that fits your needs to unlock all features\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"w-full flex items-center justify-center gap-2 px-4 py-2.5 text-sm font-medium text-primary-foreground bg-primary rounded-lg hover:bg-primary/90 transition-colors\"\n >\n Browse Plans\n <ArrowRight className=\"size-4\" />\n </button>\n </section>\n )}\n </div>\n </div>\n\n {/* Recommended Addons (only if has subscription) - Limited to 3 with cart support */}\n <RecommendedAddonsSection\n dataTour=\"billing-overview-recommended-addons\"\n addons={recommendedAddons}\n hasSubscription={hasActiveSubscription}\n onViewAddon={(addonId) => navigateTo(`/addons/${addonId}`)}\n onPurchaseAddon={(addonId) => navigateTo(`/addons/${addonId}?action=purchase`)}\n onViewAllAddons={() => navigateTo('/addons')}\n onAddToCart={handleAddToCart}\n isInCart={handleIsInCart}\n getCartQuantity={handleGetCartQuantity}\n onUpdateQuantity={handleUpdateQuantity}\n onRemoveFromCart={handleRemoveFromCart}\n maxAddons={3}\n isLoading={isLoadingAddons}\n />\n\n {/* Floating Cart Drawer - shows when items are in cart */}\n <FloatingCartDrawer />\n\n {/* Create Billing Account Modal */}\n <CreateBillingAccountModal\n isOpen={isCreateModalOpen}\n onClose={() => {\n setIsCreateModalOpen(false);\n setCreateError(null);\n }}\n onSubmit={handleCreateAccount}\n isSubmitting={isCreatingAccount}\n error={createError}\n />\n\n {/* Add Payment Method Modal */}\n {currentAccount && (\n <AddPaymentMethodModal\n isOpen={isAddPaymentModalOpen}\n onClose={() => setIsAddPaymentModalOpen(false)}\n billingAccountId={currentAccount.id}\n currency={currentAccount.currency}\n country={currentAccount.billingAddresses?.[0]?.country}\n onSuccess={handlePaymentMethodAdded}\n />\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,IAAa,UAAyB;CACpC,IAAM,EAAE,SAAM,IAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEnC,EAAE,aAAU,aAAU,UAAO,kBAAc,GAAY,EACvD,IAAc,IAAuB,EACrC,EAAE,YAAS,IAAwB,EAEnC,CAAC,GAAmB,MAA6B,QACrD,GAA0B,EAAM,CACjC,EAEK,CAAC,GAAmB,KAAwB,EAAS,GAAM,EAC3D,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAuB,KAA4B,EAAS,GAAM,EACnE,CAAC,IAAuB,KAA4B,EAAS,GAAM,EACnE,CAAC,IAA2B,MAAgC,EAAS,EAAE,EACvE,CAAC,GAA4B,KAAiC,EAAS,GAAM,EAE7E,EACJ,oBACA,mBACA,mBACA,wBACA,0BACA,uBACA,cACA,UACA,kBACE,GAAuB,GAAmB,GAAU,EAElD,IAAmB,GAAgB,IACnC,KAAkB,GAElB,IAAuB,GAC1B,MAA6B;AAE5B,EADA,GAA0B,EAAU,EACpC,GAA0B,GAAO,EAAU;IAE7C,CAAC,EAAM,CACR,EAGK,EAAE,cAAW,aAAU,gBAAa,mBAAgB,sBAAmB,IAAmB,EAE1F,EACJ,yBACA,sBACA,4BACA,yBACA,0BACE,IAAuB;AAiB3B,CAdA,QAAgB;AAEd,EADA,GAA6B,EAAE,EAC/B,EAA8B,GAAM;IACnC,CAAC,GAAkB,EAAoB,OAAO,CAAC,EAElD,QAAgB;AACd,EAAI,KAAqB,EAAgB,SAAS,MAC1B,EAAgB,MAAM,MAAY,EAAQ,OAAO,EAAkB,IAEvF,EAAqB,KAAK;IAG7B;EAAC;EAAiB;EAAmB;EAAqB,CAAC,EAE9D,QAAgB;AACd,EAAI,CAAC,KAAqB,EAAgB,SAAS,KAGjD,GADE,EAAgB,MAAM,MAAY,EAAQ,UAAU,IAAI,EAAgB,IACtC,GAAG;IAExC;EAAC;EAAiB;EAAmB;EAAqB,CAAC;CAG9D,IAAM,IACJ,EAAoB,OAA8B,EAAoB,MAAM,MAGxE,IAAa,GAChB,MAAiB;EAEhB,IAAI,IAAW;AAOf,EANI,KAAY,MAAa,QAI3B,IAAW,GAFE,EAAS,SAAS,IAAI,GAAG,EAAS,MAAM,GAAG,GAAG,GAAG,IAC/C,EAAK,WAAW,IAAI,GAAG,IAAO,IAAI,QAGnD,EAAS,EAAS;IAEpB,CAAC,GAAU,EAAS,CACrB,EAGK,KAAsB,EAC1B,OACE,GACA,MACG;AACH,IAAe,KAAK;AACpB,MAAI;GACF,IAAM,IAAa,MAAM,EAAqB,EAAM;AAiBpD,GAdI,KAAW,EAAW,MACxB,MAAM,EAAkB;IACtB,kBAAkB,EAAW;IAC7B,GAAG;IACJ,CAAC,EAGJ,EAAqB,GAAM,EAC3B,EAAqB,EAAW,GAAG,EACnC,EAAK,mCAAmC;IACtC,OAAO;IACP,UAAU,EAAW;IACrB,QAAQ;IACT,CAAC,EACF,MAAM,GAAY;WACX,GAAK;AACZ,KAAe,aAAe,QAAQ,EAAI,UAAU,mCAAmC;;IAG3F;EAAC;EAAsB;EAAM;EAAY;EAAmB;EAAqB,CAClF,EAEK,KAAyB,QAAkB;AAC/C,IAAyB,GAAK;IAC7B,EAAE,CAAC,EAGA,IAAwB,EAAY,YAAY;AACpD,QAAM,GAAY;IACjB,CAAC,EAAW,CAAC,EAEV,KAA2B,EAAY,YAAY;AAMvD,EALA,EAAK,gCAAgC;GACnC,OAAO;GACP,UAAU;GACV,QAAQ;GACT,CAAC,EACF,MAAM,GAAuB;IAC5B;EAAC;EAAkB;EAAM;EAAsB,CAAC,EAE7C,KAAgC,EACpC,OAAO,MAAe;AACf,QACL,MAAM,EAAwB,GAAI,EAAiB,EACnD,EAAK,sCAAsC;GACzC,OAAO;GACP,UAAU;GACV,QAAQ;GACT,CAAC,EACF,MAAM,GAAuB;IAE/B;EAAC;EAAkB;EAAM;EAAyB;EAAsB,CACzE,EAEK,KAA4B,EAChC,OAAO,MAAe;AACf,QACL,MAAM,GAAoB,GAAI,EAAiB,EAC/C,EAAK,kCAAkC;GACrC,OAAO;GACP,UAAU;GACV,QAAQ;GACT,CAAC,EACF,MAAM,GAAuB;IAE/B;EAAC;EAAkB;EAAqB;EAAM;EAAsB,CACrE,EAEK,KAAsB,GACzB,MAA4B;AAE3B,EADA,EAAqB,EAAQ,GAAG,EAChC,EAAyB,GAAM;IAEjC,CAAC,EAAqB,CACvB,EAEK,KAAgB,EAAY,YAAY;AAC5C,QAAM,GAAY;IACjB,CAAC,EAAW,CAAC,EAGV,KAAkB,GACrB,MAAiB;AAChB,IAAU,EAAM;IAElB,CAAC,EAAU,CACZ,EAGK,KAAiB,GACpB,MACQ,EAAS,EAAQ,EAE1B,CAAC,EAAS,CACX,EAGK,KAAwB,GAC3B,MACc,EAAY,EAAQ,EACpB,YAAY,GAE3B,CAAC,EAAY,CACd,EAGK,KAAuB,GAC1B,GAAiB,MAAqB;AACrC,EAAI,KAAY,IACd,EAAe,EAAQ,GAEvB,EAAe,GAAS,EAAS;IAGrC,CAAC,GAAgB,EAAe,CACjC,EAGK,KAAuB,GAC1B,MAAoB;AACnB,IAAe,EAAQ;IAEzB,CAAC,EAAe,CACjB;AAqFD,QAlFK,EAAY,wBAKb,KAAS,GAAc,EAAM,IAAI,CAAC,IAElC,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,IAAD;GACE,OAAM;GACN,SAAQ;GACR,SAAS;GACT,WAAA;GACA,CAAA;EACE,CAAA,GAKN,KAAa,CAAC,IAEd,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,IAAD,EAAS,WAAU,oCAAqC,CAAA,EACxD,kBAAC,KAAD;IAAG,WAAU;cACV,EAAG,gCAAgC,iCAAiC;IACnE,CAAA,CACA;;EACF,CAAA,GAKN,CAAC,KAAa,EAAgB,WAAW,IAEzC,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,UAAD;IAAQ,WAAU;cAAlB,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,0BAA0B,mBAAmB;KAC9C,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,0CACA,iEACD;KACC,CAAA,CACG;;GACT,kBAAC,IAAD;IACE,MAAM,kBAAC,GAAD,EAAW,WAAU,gCAAiC,CAAA;IAC5D,OAAO,EAAG,+BAA+B,qBAAqB;IAC9D,aAAa,EACX,qCACA,6EACD;IACD,QACE,EAAY,0BACV,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAqB,GAAK;KACzC,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,EAAG,yCAAyC,yBAAyB,CAC/D;SACP,KAAA;IAEN,CAAA;GAEF,kBAAC,GAAD;IACE,QAAQ;IACR,eAAe;AAEb,KADA,EAAqB,GAAM,EAC3B,EAAe,KAAK;;IAEtB,UAAU;IACV,cAAc;IACd,OAAO;IACP,CAAA;GACE;MAKR,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,UAAD;IAAQ,WAAU;cAAlB,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,0BAA0B,mBAAmB;KAC9C,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,gCACA,mEACD;KACC,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAY,EAAG,4BAA4B,UAAU;gBAErD,kBAAC,GAAD,EAAW,WAAW,UAAU,IAAY,iBAAiB,MAAQ,CAAA;MAC9D,CAAA,EACR,EAAY,2BAA2B,EAAgB,SAAS,KAC/D,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAqB,GAAK;MACzC,WAAU;gBAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAC3B,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAG,+BAA+B,cAAc;OAC5C,CAAA,CACA;QAEP;OACC;;GAGR,EAAgB,SAAS,KACxB,kBAAC,OAAD;IAAK,WAAU;IAAW,aAAU;cAApC,CACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAyB,CAAC,EAAsB;KAC/D,WAAU;eAHZ,CAKE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAW,WAAU,uBAAwB,CAAA;OACzC,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,GAAgB,QAAQ,EAAG,kCAAkC,iBAAiB;QAC7E,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBAAiC,GAAgB;QAAU,CAAA,CACpE;SACF;SACN,kBAAC,IAAD,EACE,WAAW,qDAAqD,IAAwB,eAAe,MACvG,CAAA,CACK;QAER,KACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;KAAqB,eAAe,EAAyB,GAAM;KAAI,CAAA,EACtF,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAgB,KAAK,MACpB,kBAAC,UAAD;MACE,MAAK;MAEL,eAAe,GAAoB,EAAQ;MAC3C,WAAU;gBAJZ,CAME,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD,EAAW,WAAU,gCAAiC,CAAA;QAClD,CAAA,EACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAQ;SACP,CAAA,EACJ,kBAAC,KAAD;SAAG,WAAU;mBAA0C,EAAQ;SAAU,CAAA,CACrE;UACF;UACL,GAAgB,OAAO,EAAQ,MAC9B,kBAAC,IAAD,EAAO,WAAU,qCAAsC,CAAA,CAElD;QAlBF,EAAQ,GAkBN,CACT;KACE,CAAA,CACL,EAAA,CAAA,CAED;;GAIP,KACC,kBAAC,OAAD;IACE,WAAU;IACV,aAAU;cAFZ;KAIE,kBAAC,GAAD;MACE,OAAO,EAAG,wCAAwC,iBAAiB;MACnE,OAAO,IAAI,EAAe,gBAAgB,GAAG,gBAAgB,CAAC;MAC9D,MAAM,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA;MACnC,CAAA;KACF,kBAAC,GAAD;MACE,OAAO,EAAG,sCAAsC,eAAe;MAC/D,OACE,EAAoB,SAAS,IACzB,EAAoB,WAAW,IAC7B,EAAoB,IAAI,MAAM,QAAQ,WACtC,GAAG,EAAoB,OAAO,GAAG,EAAG,sCAAsC,QAAQ,KACpF,EAAG,+BAA+B,OAAO;MAE/C,MAAM,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;MACtC,CAAA;KACF,kBAAC,GAAD;MACE,OAAO,EAAG,yCAAyC,kBAAkB;MACrE,OAAO,EAAe;MACtB,MAAM,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;MACtC,CAAA;KACF,kBAAC,GAAD;MACE,OAAO,EAAG,0CAA0C,mBAAmB;MACvE,OAAO,EAAe,YAAY;MAClC,MAAM,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;MACtC,CAAA;KACE;;GAIR,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,OAAD;KAAK,WAAU;eAAf;MAEG,KACC,kBAAC,IAAD;OACE,UAAS;OACT,SAAS;OACT,WAAW,EAAe,aAAa;OACvC,cACE,KAAkB,EAAW,8BAA8B,EAAe,KAAK;OAEjF,0BAA0B,EAAW,iBAAiB,EAAe,KAAK;OAC1E,gCACE,EAAW,uBAAuB,EAAe,KAAK;OAExD,CAAA;MAIJ,kBAAC,OAAD;OAAK,WAAU;iBAAf,CAEG,EAAoB,SAAS,KAC5B,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAA8B,CAAC,EAA2B;SACzE,WAAU;mBAHZ,CAKE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WAAK,WAAU;qBACb,kBAAC,GAAD,EAAO,WAAU,uBAAwB,CAAA;WACrC,CAAA,EACN,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,KAAD;YAAG,WAAU;sBAAb,CACG,GAAsB,MAAM,QAC3B,EAAG,uCAAuC,sBAAsB,EACjE,GAAsB,MAAM,SAAS,QACpC,kBAAC,QAAD;aAAM,WAAU;uBAAhB;cACG;cAAI;cACF,EAAqB,KAAK,QAAQ;cAChC;eAEP;eACJ,kBAAC,KAAD;YAAG,WAAU;sBAAb;aACG,EAAoB;aAAQ;aAC5B,EAAG,wCAAwC,uBAAuB;aACjE;cACA;aACF;aACN,kBAAC,IAAD,EACE,WAAW,qDAAqD,IAA6B,eAAe,MAC5G,CAAA,CACK;YAER,KACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;SACE,WAAU;SACV,eAAe,EAA8B,GAAM;SACnD,CAAA,EACF,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAoB,KAAK,GAAc,MACtC,kBAAC,UAAD;UACE,MAAK;UAEL,eAAe;AAEb,WADA,GAA6B,EAAM,EACnC,EAA8B,GAAM;;UAEtC,WAAU;oBAPZ,CASE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,OAAD;YAAK,WAAU;sBACb,kBAAC,GAAD,EAAO,WAAU,gCAAiC,CAAA;YAC9C,CAAA,EACN,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,KAAD;aAAG,WAAU;uBACV,EAAa,MAAM,QAClB,EAAG,iCAAiC,eAAe;aACnD,CAAA,EACJ,kBAAC,KAAD;aAAG,WAAU;uBAAb;cACG,EAAa,MAAM,SAAS,QAC3B,EAAG,mCAAmC,kBAAkB;cAAE;cAAI;cAC7D,EAAG,yBAAyB,OAAO;cAAE;cACvC,IAAI,KAAK,EAAa,QAAQ,CAAC,oBAAoB;cAClD;eACA;cACF;cACL,OAA8B,KAC7B,kBAAC,IAAD,EAAO,WAAU,qCAAsC,CAAA,CAElD;YA3BF,EAAa,GA2BX,CACT;SACE,CAAA,CACL,EAAA,CAAA,CAED;WAIR,kBAAC,IAAD;QACE,UAAS;QACT,cAAc;QACd,qBACE,KACA,EACE,EACE,kBAAkB,EAAqB,MACvC,EAAqB,oBAAoB,GAAgB,GAC1D,CACF;QAEH,iBACE,KACA,EACE,EACE,kBAAkB,EAAqB,GAAG,WAC1C,EAAqB,oBAAoB,GAAgB,GAC1D,CACF;QAEH,gBACE,KAAkB,EAAW,mCAAmC,EAAe,KAAK;QAEtF,sBAAsB,EAAW,SAAS;QAC1C,CAAA,CACE;;MAGL,KACC,kBAAC,IAAD;OACE,UAAS;OACO;OAChB,kBAAkB,EAAe;OACjC,oBAAoB;OACpB,cAAc;OACd,UAAU;OACC;OACX,CAAA;MAEA;QAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,kBAAC,WAAD;MACE,WAAU;MACV,aAAU;gBAFZ,CAIE,kBAAC,MAAD;OAAI,WAAU;iBAAqC;OAAkB,CAAA,EACrE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,KAAkB,EAAW,iBAAiB,EAAe,KAAK;SACjF,UAAU,CAAC;SACX,WAAU;mBAJZ,CAME,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,IAAD,EAAS,WAAU,2BAA4B,CAAA;UAC3C,CAAA,EAAA,oBAEC;;QACT,kBAAC,UAAD;SACE,MAAK;SACL,eACE,KAAkB,EAAW,uBAAuB,EAAe,KAAK;SAE1E,UAAU,CAAC;SACX,WAAU;mBANZ,CAQE,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,GAAD,EAAQ,WAAU,mCAAoC,CAAA;UAClD,CAAA,EAAA,iBAEC;;QACT,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAW,oBAAoB;SAC9C,WAAU;mBAHZ,CAKE,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,IAAD,EAAO,WAAU,mCAAoC,CAAA;UACjD,CAAA,EAAA,mBAEC;;QACT,kBAAC,UAAD;SACE,MAAK;SACL,eACE,KAAkB,EAAW,2BAA2B,EAAe,KAAK;SAE9E,UAAU,CAAC;SACX,WAAU;mBANZ,CAQE,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,IAAD,EAAU,WAAU,6BAA8B,CAAA;UAC9C,CAAA,EAAA,kBAEC;;QACL;SACE;SAGT,CAAC,KACA,kBAAC,WAAD;MAAS,WAAU;gBAAnB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,IAAD,EAAU,WAAU,uBAAwB,CAAA;QACxC,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;QAAI,WAAU;kBAAgC;QAAgB,CAAA,EAC9D,kBAAC,KAAD;QAAG,WAAU;kBAAqC;QAE9C,CAAA,CACA,EAAA,CAAA,CACF;UACN,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAW,SAAS;OACnC,WAAU;iBAHZ,CAIC,gBAEC,kBAAC,IAAD,EAAY,WAAU,UAAW,CAAA,CAC1B;SACD;QAER;OACF;;GAGN,kBAAC,IAAD;IACE,UAAS;IACT,QAAQ;IACR,iBAAiB;IACjB,cAAc,MAAY,EAAW,WAAW,IAAU;IAC1D,kBAAkB,MAAY,EAAW,WAAW,EAAQ,kBAAkB;IAC9E,uBAAuB,EAAW,UAAU;IAC5C,aAAa;IACb,UAAU;IACV,iBAAiB;IACjB,kBAAkB;IAClB,kBAAkB;IAClB,WAAW;IACX,WAAW;IACX,CAAA;GAGF,kBAAC,IAAD,EAAsB,CAAA;GAGtB,kBAAC,GAAD;IACE,QAAQ;IACR,eAAe;AAEb,KADA,EAAqB,GAAM,EAC3B,EAAe,KAAK;;IAEtB,UAAU;IACV,cAAc;IACd,OAAO;IACP,CAAA;GAGD,KACC,kBAAC,IAAD;IACE,QAAQ;IACR,eAAe,EAAyB,GAAM;IAC9C,kBAAkB,EAAe;IACjC,UAAU,EAAe;IACzB,SAAS,EAAe,mBAAmB,IAAI;IAC/C,WAAW;IACX,CAAA;GAEA;MAreC,kBAAC,GAAD,EAAc,SAAQ,0DAA2D,CAAA"}
|