@burdenoff/microfe-billing 2026.917.3 → 2026.917.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js +1 -1
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js +2 -2
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +2 -2
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +13 -9
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -113,7 +113,7 @@ var h = ({ subscription: h, onViewDetails: g, onUpgrade: _, onManage: v, onPurch
|
|
|
113
113
|
/* @__PURE__ */ p("div", {
|
|
114
114
|
className: "p-4 sm:p-6",
|
|
115
115
|
children: [/* @__PURE__ */ p("div", {
|
|
116
|
-
className: "grid grid-cols-
|
|
116
|
+
className: "grid grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4",
|
|
117
117
|
children: [
|
|
118
118
|
/* @__PURE__ */ p("div", {
|
|
119
119
|
className: "space-y-1.5",
|
|
@@ -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 /** 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-seam rounded-card bg-bg-surface shadow-elevation-1 p-5 ${className}`}\n >\n <div className=\"flex flex-col items-center text-center py-4\">\n <div className=\"size-16 rounded-full bg-[var(--color-accent-soft)] flex items-center justify-center mb-4\">\n <Zap className=\"size-8 text-text-link\" />\n </div>\n <h3 className=\"text-lg font-semibold text-text-primary mb-2\">\n {tr('billing.overview.noActiveSubscription', 'No Active Subscription')}\n </h3>\n <p className=\"text-sm text-text-secondary 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-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\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-seam rounded-card bg-bg-surface bg-accent-wash shadow-[var(--shadow-pop)] 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-subtle\">\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-action-primary-bg flex items-center justify-center flex-shrink-0\">\n <Crown className=\"size-6 text-action-primary-text\" />\n </div>\n <div>\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\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-text-secondary 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-text-primary border border-border-subtle rounded-button hover:bg-bg-sunken 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-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover 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-text-secondary uppercase tracking-wide\">\n Started\n </p>\n <p className=\"text-sm font-medium text-text-primary flex items-center gap-2\">\n <Calendar className=\"size-4 text-text-secondary\" />\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-text-secondary 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-text-primary'}`}\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-text-secondary uppercase tracking-wide\">\n Auto Renewal\n </p>\n <p className=\"text-sm font-medium text-text-primary flex items-center gap-2\">\n <RefreshCw\n className={`size-4 ${subscription.autoRenewal ? statusTokens.success.icon : 'text-text-secondary'}`}\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-text-secondary uppercase tracking-wide\">\n Payment Via\n </p>\n <p className=\"text-sm font-medium text-text-primary flex items-center gap-2\">\n {subscription.paymentGateway === 'CREDITS' ? (\n <Coins className={`size-4 ${statusTokens.warning.icon}`} />\n ) : (\n <CreditCard className=\"size-4 text-text-secondary\" />\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-bg-sunken border border-border-subtle\">\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-text-primary\">\n {tr('billing.overview.nextBillingDate', 'Next billing date')}\n </p>\n <p className=\"text-xs text-text-secondary\">You'll be charged automatically</p>\n </div>\n <p className=\"text-sm font-semibold text-text-primary\">\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-subtle bg-bg-sunken\">\n <button\n type=\"button\"\n onClick={onViewDetails}\n className=\"w-full flex items-center justify-center gap-2 text-sm font-medium text-text-secondary hover:text-text-primary 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-seam rounded-card bg-bg-surface shadow-elevation-1 p-5 ${className}`}\n >\n <div className=\"flex flex-col items-center text-center py-4\">\n <div className=\"size-16 rounded-full bg-[var(--color-accent-soft)] flex items-center justify-center mb-4\">\n <Zap className=\"size-8 text-text-link\" />\n </div>\n <h3 className=\"text-lg font-semibold text-text-primary mb-2\">\n {tr('billing.overview.noActiveSubscription', 'No Active Subscription')}\n </h3>\n <p className=\"text-sm text-text-secondary 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-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\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-seam rounded-card bg-bg-surface bg-accent-wash shadow-[var(--shadow-pop)] 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-subtle\">\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-action-primary-bg flex items-center justify-center flex-shrink-0\">\n <Crown className=\"size-6 text-action-primary-text\" />\n </div>\n <div>\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\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-text-secondary 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-text-primary border border-border-subtle rounded-button hover:bg-bg-sunken 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-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover 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-2 lg:grid-cols-4 gap-3 sm:gap-4\">\n {/* Start Date */}\n <div className=\"space-y-1.5\">\n <p className=\"text-xs font-medium text-text-secondary uppercase tracking-wide\">\n Started\n </p>\n <p className=\"text-sm font-medium text-text-primary flex items-center gap-2\">\n <Calendar className=\"size-4 text-text-secondary\" />\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-text-secondary 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-text-primary'}`}\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-text-secondary uppercase tracking-wide\">\n Auto Renewal\n </p>\n <p className=\"text-sm font-medium text-text-primary flex items-center gap-2\">\n <RefreshCw\n className={`size-4 ${subscription.autoRenewal ? statusTokens.success.icon : 'text-text-secondary'}`}\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-text-secondary uppercase tracking-wide\">\n Payment Via\n </p>\n <p className=\"text-sm font-medium text-text-primary flex items-center gap-2\">\n {subscription.paymentGateway === 'CREDITS' ? (\n <Coins className={`size-4 ${statusTokens.warning.icon}`} />\n ) : (\n <CreditCard className=\"size-4 text-text-secondary\" />\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-bg-sunken border border-border-subtle\">\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-text-primary\">\n {tr('billing.overview.nextBillingDate', 'Next billing date')}\n </p>\n <p className=\"text-xs text-text-secondary\">You'll be charged automatically</p>\n </div>\n <p className=\"text-sm font-semibold text-text-primary\">\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-subtle bg-bg-sunken\">\n <button\n type=\"button\"\n onClick={onViewDetails}\n className=\"w-full flex items-center justify-center gap-2 text-sm font-medium text-text-secondary hover:text-text-primary 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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"}
|
|
@@ -173,7 +173,7 @@ var h = ({ brand: e, className: t }) => {
|
|
|
173
173
|
className: `space-y-4 ${l}`,
|
|
174
174
|
"data-tour": u,
|
|
175
175
|
children: [/* @__PURE__ */ p("header", {
|
|
176
|
-
className: "flex items-center justify-between",
|
|
176
|
+
className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3",
|
|
177
177
|
children: [/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("h3", {
|
|
178
178
|
className: "text-lg font-semibold text-text-primary",
|
|
179
179
|
children: h("billing.overview.paymentMethods", "Payment Methods")
|
|
@@ -183,7 +183,7 @@ var h = ({ brand: e, className: t }) => {
|
|
|
183
183
|
})] }), /* @__PURE__ */ p("button", {
|
|
184
184
|
type: "button",
|
|
185
185
|
onClick: n,
|
|
186
|
-
className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover transition-colors",
|
|
186
|
+
className: "inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover transition-colors sm:flex-shrink-0",
|
|
187
187
|
children: [/* @__PURE__ */ f(c, { className: "size-4" }), "Add Method"]
|
|
188
188
|
})]
|
|
189
189
|
}), s ? /* @__PURE__ */ f("div", {
|
|
@@ -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 /** 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-text-secondary',\n };\n\n const color = brandColors[brandLower] || brandColors.default;\n\n return (\n <div className={`w-10 h-6 rounded bg-bg-sunken 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-border-strong bg-[var(--color-accent-soft)]' : 'border-border-subtle bg-bg-surface'}\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-text-primary\">\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-[var(--color-accent-soft)] text-text-link\">\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-text-secondary\">\n {getExpiryText() && (\n <>\n <span className={isExpired() ? 'text-status-error-text' : ''}>\n Expires {getExpiryText()}\n </span>\n {isExpired() && <span className=\"text-status-error-text 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-button hover:bg-bg-sunken text-text-secondary hover:text-text-primary 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-card border border-border-seam bg-bg-elevated shadow-elevation-3 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-text-primary hover:bg-bg-sunken 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-status-error-text hover:bg-status-error-bg-subtle 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-button border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"rounded-button bg-action-danger-bg px-3 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-danger-bgHover 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-text-primary\">\n {tr('billing.overview.paymentMethods', 'Payment Methods')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\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-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover 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-text-secondary\" />\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-subtle rounded-lg\">\n <div className=\"size-12 rounded-full bg-bg-sunken flex items-center justify-center mb-4\">\n <CreditCard className=\"size-6 text-text-secondary\" />\n </div>\n <h4 className=\"text-base font-medium text-text-primary\">No payment methods</h4>\n <p className=\"text-sm text-text-secondary 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-text-secondary',\n };\n\n const color = brandColors[brandLower] || brandColors.default;\n\n return (\n <div className={`w-10 h-6 rounded bg-bg-sunken 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-border-strong bg-[var(--color-accent-soft)]' : 'border-border-subtle bg-bg-surface'}\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-text-primary\">\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-[var(--color-accent-soft)] text-text-link\">\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-text-secondary\">\n {getExpiryText() && (\n <>\n <span className={isExpired() ? 'text-status-error-text' : ''}>\n Expires {getExpiryText()}\n </span>\n {isExpired() && <span className=\"text-status-error-text 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-button hover:bg-bg-sunken text-text-secondary hover:text-text-primary 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-card border border-border-seam bg-bg-elevated shadow-elevation-3 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-text-primary hover:bg-bg-sunken 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-status-error-text hover:bg-status-error-bg-subtle 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-button border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"rounded-button bg-action-danger-bg px-3 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-danger-bgHover 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 flex-col sm:flex-row sm:items-center sm:justify-between gap-3\">\n <div>\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.overview.paymentMethods', 'Payment Methods')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\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 justify-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover transition-colors sm:flex-shrink-0\"\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-text-secondary\" />\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-subtle rounded-lg\">\n <div className=\"size-12 rounded-full bg-bg-sunken flex items-center justify-center mb-4\">\n <CreditCard className=\"size-6 text-text-secondary\" />\n </div>\n <h4 className=\"text-base font-medium text-text-primary\">No payment methods</h4>\n <p className=\"text-sm text-text-secondary 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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"}
|
|
@@ -150,7 +150,7 @@ var v = ({ addon: e, onView: r, onPurchase: a, onAddToCart: o, isInCart: d = !1,
|
|
|
150
150
|
"data-tour": x,
|
|
151
151
|
children: [
|
|
152
152
|
/* @__PURE__ */ h("header", {
|
|
153
|
-
className: "flex items-center justify-between",
|
|
153
|
+
className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3",
|
|
154
154
|
children: [/* @__PURE__ */ h("div", {
|
|
155
155
|
className: "flex items-center gap-2",
|
|
156
156
|
children: [/* @__PURE__ */ m(d, { className: "size-5 text-status-warning-text" }), /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("h3", {
|
|
@@ -163,7 +163,7 @@ var v = ({ addon: e, onView: r, onPurchase: a, onAddToCart: o, isInCart: d = !1,
|
|
|
163
163
|
}), /* @__PURE__ */ h("button", {
|
|
164
164
|
type: "button",
|
|
165
165
|
onClick: s,
|
|
166
|
-
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-text-link hover:text-text-link transition-colors",
|
|
166
|
+
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-text-link hover:text-text-link transition-colors sm:flex-shrink-0",
|
|
167
167
|
children: ["View All", /* @__PURE__ */ m(a, { className: "size-4" })]
|
|
168
168
|
})]
|
|
169
169
|
}),
|
|
@@ -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';\nimport { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\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 limitValue = getPlanFeatureQuantity(feature);\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-seam rounded-card bg-bg-surface shadow-[var(--shadow-pop)] overflow-hidden hover:shadow-elevation-2 [@media(hover:none)]: transition-[box-shadow,transform] duration-200 ease-[var(--motion-easing-out)]\">\n {/* Header */}\n <header className=\"p-4 border-b border-border-subtle\">\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-text-primary\">{addon.name}</h4>\n <p className=\"text-sm text-text-secondary\">\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-text-secondary\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-text-primary\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 2 && (\n <li className=\"text-xs text-text-secondary\">\n +{combinedQuotas.length - 2} more quotas\n </li>\n )}\n </ul>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n Enhance your subscription with additional features\n </p>\n )}\n </div>\n\n {/* Footer */}\n <footer className=\"p-4 border-t border-border-subtle bg-bg-sunken 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-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-button 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-status-error-text hover:bg-status-error-bg-subtle rounded-button 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-text-primary hover:bg-bg-sunken rounded-button transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-8 text-center font-medium text-text-primary\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(cartQuantity + 1)}\n className=\"p-2 text-text-primary hover:bg-bg-sunken rounded-button 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-action-primary-text hover:bg-status-success-bg/90'\n : 'text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bgHover'\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-text-primary\">\n {tr('billing.overview.recommendedAddons', 'Recommended Add-ons')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\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-text-secondary\" />\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-text-primary\">Recommended Add-ons</h3>\n <p className=\"text-sm text-text-secondary\">\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-text-link hover:text-text-link 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-text-secondary hover:text-text-primary 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';\nimport { getPlanFeatureQuantity } from '../../../shared/utils/planFeatureQuantity';\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 limitValue = getPlanFeatureQuantity(feature);\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-seam rounded-card bg-bg-surface shadow-[var(--shadow-pop)] overflow-hidden hover:shadow-elevation-2 [@media(hover:none)]: transition-[box-shadow,transform] duration-200 ease-[var(--motion-easing-out)]\">\n {/* Header */}\n <header className=\"p-4 border-b border-border-subtle\">\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-text-primary\">{addon.name}</h4>\n <p className=\"text-sm text-text-secondary\">\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-text-secondary\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-text-primary\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 2 && (\n <li className=\"text-xs text-text-secondary\">\n +{combinedQuotas.length - 2} more quotas\n </li>\n )}\n </ul>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n Enhance your subscription with additional features\n </p>\n )}\n </div>\n\n {/* Footer */}\n <footer className=\"p-4 border-t border-border-subtle bg-bg-sunken 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-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-button 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-status-error-text hover:bg-status-error-bg-subtle rounded-button 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-text-primary hover:bg-bg-sunken rounded-button transition-colors\"\n >\n <Minus className=\"size-4\" />\n </button>\n <span className=\"w-8 text-center font-medium text-text-primary\">{cartQuantity}</span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity(cartQuantity + 1)}\n className=\"p-2 text-text-primary hover:bg-bg-sunken rounded-button 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-action-primary-text hover:bg-status-success-bg/90'\n : 'text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bgHover'\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-text-primary\">\n {tr('billing.overview.recommendedAddons', 'Recommended Add-ons')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\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-text-secondary\" />\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 flex-col sm:flex-row sm:items-center sm:justify-between gap-3\">\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-text-primary\">Recommended Add-ons</h3>\n <p className=\"text-sm text-text-secondary\">\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-text-link hover:text-text-link transition-colors sm:flex-shrink-0\"\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-text-secondary hover:text-text-primary 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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"}
|
|
@@ -134,10 +134,10 @@ var y = () => {
|
|
|
134
134
|
})]
|
|
135
135
|
})
|
|
136
136
|
}) : !B && F.length === 0 && !I ? /* @__PURE__ */ g("div", {
|
|
137
|
-
className: "px-6 lg:px-8 py-6",
|
|
137
|
+
className: "px-4 sm:px-6 lg:px-8 py-4 sm:py-6",
|
|
138
138
|
children: [
|
|
139
139
|
/* @__PURE__ */ g("header", {
|
|
140
|
-
className: "relative overflow-hidden mb-8 rounded-card",
|
|
140
|
+
className: "relative overflow-hidden mb-6 sm:mb-8 rounded-card",
|
|
141
141
|
children: [
|
|
142
142
|
/* @__PURE__ */ h(_, { intensity: "subtle" }),
|
|
143
143
|
/* @__PURE__ */ h("h1", {
|
|
@@ -172,7 +172,7 @@ var y = () => {
|
|
|
172
172
|
})
|
|
173
173
|
]
|
|
174
174
|
}) : /* @__PURE__ */ g("div", {
|
|
175
|
-
className: "px-6 lg:px-8 py-6 space-y-
|
|
175
|
+
className: "px-4 sm:px-6 lg:px-8 py-4 sm:py-6 space-y-4 sm:space-y-8",
|
|
176
176
|
children: [
|
|
177
177
|
/* @__PURE__ */ g("header", {
|
|
178
178
|
className: "relative overflow-hidden rounded-card flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4",
|
|
@@ -260,25 +260,29 @@ var y = () => {
|
|
|
260
260
|
})] })]
|
|
261
261
|
}),
|
|
262
262
|
I && /* @__PURE__ */ g("div", {
|
|
263
|
-
className: "grid grid-cols-
|
|
263
|
+
className: "grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4",
|
|
264
264
|
"data-tour": "billing-overview-stats-row",
|
|
265
265
|
children: [
|
|
266
266
|
/* @__PURE__ */ h(r, {
|
|
267
|
+
className: "p-3 sm:p-5",
|
|
267
268
|
label: y("billing.overview.stats.creditBalance", "Credit Balance"),
|
|
268
269
|
value: `${(I.creditAmount || 0).toLocaleString()} Credits`,
|
|
269
270
|
icon: /* @__PURE__ */ h(p, { className: "size-5" })
|
|
270
271
|
}),
|
|
271
272
|
/* @__PURE__ */ h(r, {
|
|
273
|
+
className: "p-3 sm:p-5",
|
|
272
274
|
label: y("billing.overview.stats.activePlans", "Active Plans"),
|
|
273
275
|
value: R.length > 0 ? R.length === 1 ? R[0]?.plan?.name || "1 Plan" : `${R.length} ${y("billing.overview.stats.plansSuffix", "Plans")}` : y("billing.overview.stats.none", "None"),
|
|
274
276
|
icon: /* @__PURE__ */ h(f, { className: "size-5" })
|
|
275
277
|
}),
|
|
276
278
|
/* @__PURE__ */ h(r, {
|
|
279
|
+
className: "p-3 sm:p-5",
|
|
277
280
|
label: y("billing.overview.stats.paymentMethods", "Payment Methods"),
|
|
278
281
|
value: L.length,
|
|
279
282
|
icon: /* @__PURE__ */ h(l, { className: "size-5" })
|
|
280
283
|
}),
|
|
281
284
|
/* @__PURE__ */ h(r, {
|
|
285
|
+
className: "p-3 sm:p-5",
|
|
282
286
|
label: y("billing.overview.stats.paymentCurrency", "Payment Currency"),
|
|
283
287
|
value: I.currency || "USD",
|
|
284
288
|
icon: /* @__PURE__ */ h(l, { className: "size-5" })
|
|
@@ -286,9 +290,9 @@ var y = () => {
|
|
|
286
290
|
]
|
|
287
291
|
}),
|
|
288
292
|
/* @__PURE__ */ g("div", {
|
|
289
|
-
className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
|
|
293
|
+
className: "grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6",
|
|
290
294
|
children: [/* @__PURE__ */ g("div", {
|
|
291
|
-
className: "lg:col-span-2 space-y-6",
|
|
295
|
+
className: "lg:col-span-2 space-y-4 sm:space-y-6",
|
|
292
296
|
children: [
|
|
293
297
|
I && /* @__PURE__ */ h(ce, {
|
|
294
298
|
dataTour: "billing-overview-billing-account-card",
|
|
@@ -389,9 +393,9 @@ var y = () => {
|
|
|
389
393
|
})
|
|
390
394
|
]
|
|
391
395
|
}), /* @__PURE__ */ g("div", {
|
|
392
|
-
className: "space-y-6",
|
|
396
|
+
className: "space-y-4 sm:space-y-6",
|
|
393
397
|
children: [/* @__PURE__ */ g("section", {
|
|
394
|
-
className: "border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5",
|
|
398
|
+
className: "border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-4 sm:p-5",
|
|
395
399
|
"data-tour": "billing-overview-quick-actions",
|
|
396
400
|
children: [/* @__PURE__ */ h("h3", {
|
|
397
401
|
className: "font-semibold text-text-primary mb-4",
|
|
@@ -443,7 +447,7 @@ var y = () => {
|
|
|
443
447
|
}), !z && /* @__PURE__ */ g(Te, {
|
|
444
448
|
treatment: "glass",
|
|
445
449
|
glow: !0,
|
|
446
|
-
className: "p-5",
|
|
450
|
+
className: "p-4 sm:p-5",
|
|
447
451
|
children: [/* @__PURE__ */ g("div", {
|
|
448
452
|
className: "flex items-start gap-3 mb-4",
|
|
449
453
|
children: [/* @__PURE__ */ h("div", {
|
|
@@ -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 { 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 { useTr } from '../../../shared/hooks/useTr';\nimport {\n Button,\n CTAOverflowMenu,\n GlassCard,\n AuroraBackground,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\nexport const OverviewPage: FC = () => {\n const tr = useTr();\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={tr('billing.serverError.title', 'Server Unavailable')}\n message={tr(\n 'billing.overview.serverUnavailableMessage',\n 'Unable to load billing overview. The server might be down or experiencing issues.'\n )}\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-text-link\" />\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.overview.loadingInfo', 'Loading billing information...')}\n </p>\n </div>\n </div>\n );\n }\n\n // No billing accounts state. Checked against `billingAccounts` AND\n // `currentAccount` (not just the list) because getDefaultBillingAccount\n // lazy-creates a default account for a brand-new org — on that exact\n // first request, getBillingAccountsByOrg's plain read can still observe\n // zero rows in the same response that returned a real, freshly-created\n // currentAccount. A valid currentAccount always means there's a real\n // billing account regardless of what the (possibly momentarily stale)\n // list says.\n if (!isLoading && billingAccounts.length === 0 && !currentAccount) {\n return (\n <div className=\"px-6 lg:px-8 py-6\">\n <header className=\"relative overflow-hidden mb-8 rounded-card\">\n <AuroraBackground intensity=\"subtle\" />\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n <GradientText>{tr('billing.overview.title', 'Billing Overview')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.emptySetupDescription',\n 'Set up billing to manage subscriptions, payments, and invoices'\n )}\n </p>\n </header>\n <IllustratedEmptyState\n illustration=\"onboarding\"\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-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\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=\"px-6 lg:px-8 py-6 space-y-6 sm:space-y-8\">\n {/* Header */}\n <header className=\"relative overflow-hidden rounded-card flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4\">\n <AuroraBackground intensity=\"subtle\" />\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n <GradientText>{tr('billing.overview.title', 'Billing Overview')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.description',\n 'Manage your billing accounts, subscriptions, and payment methods'\n )}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.overview.purpose',\n 'Your billing home base — see your current plan, credit balance and payment methods at a glance, and jump to transactions, usage, credits or new plans. Start here to understand what you are paying for and manage it.'\n )}\n </PagePurpose>\n </div>\n <CTAOverflowMenu\n primary={\n permissions.canManageBillingAccount && billingAccounts.length > 0\n ? {\n label: tr('billing.overview.newAccount', 'New Account'),\n onSelect: () => setIsCreateModalOpen(true),\n icon: <Plus className=\"size-4\" />,\n }\n : undefined\n }\n actions={[\n {\n label: tr('billing.overview.refresh', 'Refresh'),\n onSelect: () => {\n void handleRefresh();\n },\n icon: <RefreshCw className={`size-4 ${isLoading ? 'animate-spin' : ''}`} />,\n },\n ]}\n />\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-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Building2 className=\"size-4 text-text-link\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {currentAccount?.name || tr('billing.overview.selectAccount', 'Select Account')}\n </p>\n <p className=\"text-xs text-text-secondary\">{currentAccount?.email}</p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-text-muted 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-bg-elevated border border-border-seam rounded-card shadow-elevation-3 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-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <Building2 className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">{account.email}</p>\n </div>\n </div>\n {currentAccount?.id === account.id && (\n <Check className=\"size-4 text-text-link 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-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Crown className=\"size-4 text-text-link\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {selectedSubscription?.plan?.name ||\n tr('billing.overview.selectSubscription', 'Select Subscription')}\n {selectedSubscription?.plan?.product?.name && (\n <span className=\"text-text-secondary font-normal\">\n {' '}\n · {selectedSubscription.plan.product.name}\n </span>\n )}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {activeSubscriptions.length}{' '}\n {tr('billing.overview.activeSubscriptions', 'active subscriptions')}\n </p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-text-muted 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-bg-elevated border border-border-seam rounded-card shadow-elevation-3 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-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <Crown className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {subscription.plan?.name ||\n tr('billing.overview.subscription', 'Subscription')}\n </p>\n <p className=\"text-xs text-text-secondary 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-text-link 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-seam rounded-card bg-bg-surface shadow-elevation-1 p-5\"\n data-tour=\"billing-overview-quick-actions\"\n >\n <h3 className=\"font-semibold text-text-primary mb-4\">\n {tr('billing.overview.quickActions', 'Quick Actions')}\n </h3>\n <div className=\"space-y-1.5\">\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-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-status-info-bg-subtle flex items-center justify-center\">\n <Receipt className=\"size-4 text-status-info-text\" />\n </div>\n {tr('billing.overview.viewTransactions', '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-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-status-warning-bg-subtle flex items-center justify-center\">\n <Wallet className=\"size-4 text-status-warning-text\" />\n </div>\n {tr('billing.overview.creditHistory', '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-text-primary hover:bg-bg-sunken rounded-button transition-colors\"\n >\n <div className=\"size-9 rounded-lg bg-status-success-bg-subtle flex items-center justify-center\">\n <Coins className=\"size-4 text-status-success-text\" />\n </div>\n {tr('billing.overview.purchaseCredits', '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-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Activity className=\"size-4 text-text-link\" />\n </div>\n {tr('billing.overview.usageAnalytics', 'Usage Analytics')}\n </button>\n </div>\n </section>\n\n {/* Get Started (if no subscription) — primary emphasis zone */}\n {!hasActiveSubscription && (\n <GlassCard treatment=\"glass\" glow className=\"p-5\">\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"size-10 rounded-lg bg-bg-surface flex items-center justify-center flex-shrink-0 shadow-elevation-1\">\n <Sparkles className=\"size-5 text-text-link\" />\n </div>\n <div>\n <h3 className=\"font-semibold text-text-primary\">\n {tr('billing.overview.getStarted', 'Get Started')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.getStartedDescription',\n 'Choose a plan that fits your needs to unlock all features'\n )}\n </p>\n </div>\n </div>\n <Button type=\"button\" onClick={() => navigateTo('/plans')} className=\"w-full\">\n {tr('billing.overview.browsePlans', 'Browse Plans')}\n <ArrowRight className=\"size-4\" />\n </Button>\n </GlassCard>\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":"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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 { 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 { useTr } from '../../../shared/hooks/useTr';\nimport {\n Button,\n CTAOverflowMenu,\n GlassCard,\n AuroraBackground,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\nexport const OverviewPage: FC = () => {\n const tr = useTr();\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={tr('billing.serverError.title', 'Server Unavailable')}\n message={tr(\n 'billing.overview.serverUnavailableMessage',\n 'Unable to load billing overview. The server might be down or experiencing issues.'\n )}\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-text-link\" />\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.overview.loadingInfo', 'Loading billing information...')}\n </p>\n </div>\n </div>\n );\n }\n\n // No billing accounts state. Checked against `billingAccounts` AND\n // `currentAccount` (not just the list) because getDefaultBillingAccount\n // lazy-creates a default account for a brand-new org — on that exact\n // first request, getBillingAccountsByOrg's plain read can still observe\n // zero rows in the same response that returned a real, freshly-created\n // currentAccount. A valid currentAccount always means there's a real\n // billing account regardless of what the (possibly momentarily stale)\n // list says.\n if (!isLoading && billingAccounts.length === 0 && !currentAccount) {\n return (\n <div className=\"px-4 sm:px-6 lg:px-8 py-4 sm:py-6\">\n <header className=\"relative overflow-hidden mb-6 sm:mb-8 rounded-card\">\n <AuroraBackground intensity=\"subtle\" />\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n <GradientText>{tr('billing.overview.title', 'Billing Overview')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.emptySetupDescription',\n 'Set up billing to manage subscriptions, payments, and invoices'\n )}\n </p>\n </header>\n <IllustratedEmptyState\n illustration=\"onboarding\"\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-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\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=\"px-4 sm:px-6 lg:px-8 py-4 sm:py-6 space-y-4 sm:space-y-8\">\n {/* Header */}\n <header className=\"relative overflow-hidden rounded-card flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4\">\n <AuroraBackground intensity=\"subtle\" />\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n <GradientText>{tr('billing.overview.title', 'Billing Overview')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.description',\n 'Manage your billing accounts, subscriptions, and payment methods'\n )}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.overview.purpose',\n 'Your billing home base — see your current plan, credit balance and payment methods at a glance, and jump to transactions, usage, credits or new plans. Start here to understand what you are paying for and manage it.'\n )}\n </PagePurpose>\n </div>\n <CTAOverflowMenu\n primary={\n permissions.canManageBillingAccount && billingAccounts.length > 0\n ? {\n label: tr('billing.overview.newAccount', 'New Account'),\n onSelect: () => setIsCreateModalOpen(true),\n icon: <Plus className=\"size-4\" />,\n }\n : undefined\n }\n actions={[\n {\n label: tr('billing.overview.refresh', 'Refresh'),\n onSelect: () => {\n void handleRefresh();\n },\n icon: <RefreshCw className={`size-4 ${isLoading ? 'animate-spin' : ''}`} />,\n },\n ]}\n />\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-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Building2 className=\"size-4 text-text-link\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {currentAccount?.name || tr('billing.overview.selectAccount', 'Select Account')}\n </p>\n <p className=\"text-xs text-text-secondary\">{currentAccount?.email}</p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-text-muted 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-bg-elevated border border-border-seam rounded-card shadow-elevation-3 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-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <Building2 className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">{account.email}</p>\n </div>\n </div>\n {currentAccount?.id === account.id && (\n <Check className=\"size-4 text-text-link 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-2 sm:grid-cols-4 gap-3 sm:gap-4\"\n data-tour=\"billing-overview-stats-row\"\n >\n <StatCard\n className=\"p-3 sm:p-5\"\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 className=\"p-3 sm:p-5\"\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 className=\"p-3 sm:p-5\"\n label={tr('billing.overview.stats.paymentMethods', 'Payment Methods')}\n value={paymentMethods.length}\n icon={<Building2 className=\"size-5\" />}\n />\n <StatCard\n className=\"p-3 sm:p-5\"\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-4 sm:gap-6\">\n {/* Left Column - Billing Account & Subscription */}\n <div className=\"lg:col-span-2 space-y-4 sm: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-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Crown className=\"size-4 text-text-link\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {selectedSubscription?.plan?.name ||\n tr('billing.overview.selectSubscription', 'Select Subscription')}\n {selectedSubscription?.plan?.product?.name && (\n <span className=\"text-text-secondary font-normal\">\n {' '}\n · {selectedSubscription.plan.product.name}\n </span>\n )}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {activeSubscriptions.length}{' '}\n {tr('billing.overview.activeSubscriptions', 'active subscriptions')}\n </p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-text-muted 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-bg-elevated border border-border-seam rounded-card shadow-elevation-3 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-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <Crown className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {subscription.plan?.name ||\n tr('billing.overview.subscription', 'Subscription')}\n </p>\n <p className=\"text-xs text-text-secondary 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-text-link 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-4 sm:space-y-6\">\n {/* Quick Actions */}\n <section\n className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-4 sm:p-5\"\n data-tour=\"billing-overview-quick-actions\"\n >\n <h3 className=\"font-semibold text-text-primary mb-4\">\n {tr('billing.overview.quickActions', 'Quick Actions')}\n </h3>\n <div className=\"space-y-1.5\">\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-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-status-info-bg-subtle flex items-center justify-center\">\n <Receipt className=\"size-4 text-status-info-text\" />\n </div>\n {tr('billing.overview.viewTransactions', '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-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-status-warning-bg-subtle flex items-center justify-center\">\n <Wallet className=\"size-4 text-status-warning-text\" />\n </div>\n {tr('billing.overview.creditHistory', '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-text-primary hover:bg-bg-sunken rounded-button transition-colors\"\n >\n <div className=\"size-9 rounded-lg bg-status-success-bg-subtle flex items-center justify-center\">\n <Coins className=\"size-4 text-status-success-text\" />\n </div>\n {tr('billing.overview.purchaseCredits', '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-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Activity className=\"size-4 text-text-link\" />\n </div>\n {tr('billing.overview.usageAnalytics', 'Usage Analytics')}\n </button>\n </div>\n </section>\n\n {/* Get Started (if no subscription) — primary emphasis zone */}\n {!hasActiveSubscription && (\n <GlassCard treatment=\"glass\" glow className=\"p-4 sm:p-5\">\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"size-10 rounded-lg bg-bg-surface flex items-center justify-center flex-shrink-0 shadow-elevation-1\">\n <Sparkles className=\"size-5 text-text-link\" />\n </div>\n <div>\n <h3 className=\"font-semibold text-text-primary\">\n {tr('billing.overview.getStarted', 'Get Started')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.getStartedDescription',\n 'Choose a plan that fits your needs to unlock all features'\n )}\n </p>\n </div>\n </div>\n <Button type=\"button\" onClick={() => navigateTo('/plans')} className=\"w-full\">\n {tr('billing.overview.browsePlans', 'Browse Plans')}\n <ArrowRight className=\"size-4\" />\n </Button>\n </GlassCard>\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":"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"}
|