@burdenoff/microfe-billing 2026.625.1 → 2026.625.3
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/billing/pages/TransactionsPage.js +26 -37
- package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +41 -40
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +234 -225
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansListPage.js +1 -1
- package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +1 -0
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +1 -0
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/dist/billing/shared/ui/Card.js +1 -1
- package/dist/billing/shared/ui/Card.js.map +1 -1
- package/dist/billing/shared/ui/StatusPill.js +1 -1
- package/dist/billing/shared/ui/StatusPill.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreditTransactionsPage.js","names":[],"sources":["../../../../../src/billing/modules/credits/pages/CreditTransactionsPage.tsx"],"sourcesContent":["/**\n * Credits Module - Credit Transactions Page\n * Displays credit transaction history for a specific billing account\n */\n\nimport { useState, type FC } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n Coins,\n CreditCard,\n Mail,\n ChevronLeft,\n ChevronRight,\n Plus,\n Minus,\n CheckCircle,\n XCircle,\n FileText,\n Calendar,\n} from 'lucide-react';\nimport { useCreditTransactions } from '../hooks/useCredits';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { AccessDenied, PageHeader, EmptyState, ServerError } from '../../../shared/components';\nimport {\n formatDateTime,\n formatRelativeTime,\n formatCreditTransactionType,\n isServerError,\n} from '../../../shared/utils';\nimport {\n getCreditTransactionStatusColor,\n getStatusBadgeClasses,\n} from '../../../shared/utils/status';\nimport type { CreditTransaction, CreditTransactionType } from '../../../shared/types';\nimport { useGetBillingAccountQuery } from '../../../../generated/global-operations';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ntype FilterType = 'all' | CreditTransactionType;\n\nexport const CreditTransactionsPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { billingAccountId } = useParams<{ billingAccountId: string }>();\n const permissions = useBillingPermissions();\n const navigateTo = useBillingNavigate();\n\n const [days, setDays] = useState(30);\n const [filterType, setFilterType] = useState<FilterType>('all');\n const [page, setPage] = useState(1);\n const pageSize = 20;\n\n // Fetch billing account details\n const { data: accountData, loading: isLoadingAccount } = useGetBillingAccountQuery({\n variables: { id: billingAccountId! },\n skip: !billingAccountId,\n });\n\n const billingAccount = accountData?.getBillingAccount;\n\n // Fetch credit transactions\n const {\n transactions,\n pagination,\n isLoading: isLoadingTransactions,\n error,\n refetch,\n } = useCreditTransactions({\n billingAccountId: billingAccountId || '',\n days,\n page,\n pageSize,\n });\n\n // Permission check\n if (!permissions.canViewCredits) {\n return <AccessDenied message=\"You don't have permission to view credit history.\" />;\n }\n\n // Show error if no billing account ID provided\n if (!billingAccountId) {\n return (\n <div className=\"p-6\">\n <EmptyState\n icon={\n <svg\n className=\"size-6 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n }\n title=\"No billing account selected\"\n description=\"Please select a billing account from the overview page to view credit history.\"\n />\n </div>\n );\n }\n\n // Show server error if backend is down\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title=\"Server Unavailable\"\n message=\"Unable to load credit transactions. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n const isLoading = isLoadingAccount || isLoadingTransactions;\n\n // Filter transactions\n const filteredTransactions =\n filterType === 'all' ? transactions : transactions.filter((t) => t.type === filterType);\n\n // Calculate total pages\n const totalPages = Math.ceil(pagination.totalCount / pageSize);\n\n // Pagination handlers\n const goToNextPage = () => {\n if (pagination.hasMore) {\n setPage((p) => p + 1);\n }\n };\n\n const goToPreviousPage = () => {\n if (page > 1) {\n setPage((p) => p - 1);\n }\n };\n\n if (isLoading && !billingAccount) {\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/overview')}\n className=\"inline-flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('billing.credits.backToOverview', 'Back to Overview')}\n </button>\n\n <PageHeader\n title=\"Credit History\"\n description={\n billingAccount\n ? `Credit transactions for ${billingAccount.name}`\n : 'View your credit transaction history'\n }\n />\n\n {/* Billing Account Info Card */}\n {billingAccount && (\n <div className=\"border border-border-subtle rounded-lg p-4 bg-bg-surface\">\n <div className=\"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4\">\n {/* Account Name & Status */}\n <div className=\"flex items-start gap-2\">\n <CreditCard className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Account</p>\n <p className=\"text-sm font-medium\">{billingAccount.name}</p>\n {billingAccount.isActive ? (\n <span className=\"inline-flex items-center gap-1 mt-1 px-1.5 py-0.5 text-xs font-medium rounded bg-status-success-bg-subtle text-status-success-text\">\n <CheckCircle className=\"size-3\" />\n Active\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1 mt-1 px-1.5 py-0.5 text-xs font-medium rounded bg-status-error-bg-subtle text-status-error-text\">\n <XCircle className=\"size-3\" />\n Inactive\n </span>\n )}\n </div>\n </div>\n\n {/* Email */}\n <div className=\"flex items-start gap-2\">\n <Mail className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Email</p>\n <p className=\"text-sm font-medium break-all\">\n {billingAccount.billingEmail || billingAccount.email}\n </p>\n </div>\n </div>\n\n {/* Credit Balance */}\n <div className=\"flex items-start gap-2\">\n <Coins className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Credit Balance</p>\n <p className=\"text-sm font-semibold text-text-primary\">\n {(billingAccount.creditAmount || 0).toLocaleString()} Credits\n </p>\n </div>\n </div>\n\n {/* Tax ID */}\n <div className=\"flex items-start gap-2\">\n <FileText className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Tax ID</p>\n <p className=\"text-sm font-medium\">{billingAccount.taxId || '-'}</p>\n </div>\n </div>\n\n {/* Account Created */}\n <div className=\"flex items-start gap-2\">\n <Calendar className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Member Since</p>\n <p className=\"text-sm font-medium\">\n {billingAccount.createdAt ? formatDateTime(billingAccount.createdAt) : '-'}\n </p>\n </div>\n </div>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle\">\n {/* Time Range */}\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken\">\n {[7, 30, 90].map((d) => (\n <button\n type=\"button\"\n key={d}\n onClick={() => {\n setDays(d);\n setPage(1); // Reset to first page on filter change\n }}\n className={`px-3 py-1.5 text-sm font-medium rounded-button transition-colors ${\n days === d\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {d}d\n </button>\n ))}\n </div>\n\n {/* Transaction Type Filter */}\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken\">\n {(['all', 'purchase', 'usage', 'adjustment'] as FilterType[]).map((type) => (\n <button\n type=\"button\"\n key={type}\n onClick={() => {\n setFilterType(type);\n setPage(1); // Reset to first page on filter change\n }}\n className={`px-3 py-1.5 text-sm font-medium rounded-button transition-colors capitalize ${\n filterType === type\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {type === 'all' ? 'All' : formatCreditTransactionType(type as CreditTransactionType)}\n </button>\n ))}\n </div>\n\n {/* Transaction count */}\n <div className=\"ml-auto text-sm text-text-secondary\">\n {pagination.totalCount} transaction{pagination.totalCount !== 1 ? 's' : ''} in last {days}{' '}\n days\n </div>\n </div>\n\n {/* Transactions List */}\n {filteredTransactions.length === 0 ? (\n <EmptyState\n icon={<Coins className=\"size-6 text-text-secondary\" />}\n title=\"No credit transactions found\"\n description={\n filterType !== 'all'\n ? 'No transactions match your filter.'\n : 'Your credit transaction history will appear here.'\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {filteredTransactions.map((transaction) => (\n <CreditTransactionRow key={transaction.id} transaction={transaction} />\n ))}\n </div>\n )}\n\n {/* Pagination Controls */}\n {pagination.totalCount > 0 && (\n <div className=\"flex items-center justify-between border-t border-border-subtle pt-4\">\n <p className=\"text-sm text-text-secondary\">\n Showing {(page - 1) * pageSize + 1} to{' '}\n {Math.min(page * pageSize, pagination.totalCount)} of {pagination.totalCount}{' '}\n transactions\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={goToPreviousPage}\n disabled={page <= 1}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n <ChevronLeft className=\"size-4\" />\n Previous\n </button>\n <span className=\"text-sm text-text-secondary px-2\">\n Page {page} of {totalPages || 1}\n </span>\n <button\n type=\"button\"\n onClick={goToNextPage}\n disabled={!pagination.hasMore}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n Next\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Credit Transaction Row Component\n// ============================================================================\n\ninterface CreditTransactionRowProps {\n transaction: CreditTransaction;\n}\n\nconst CreditTransactionRow: FC<CreditTransactionRowProps> = ({ transaction }) => {\n const statusColor = getCreditTransactionStatusColor(transaction.status);\n\n // FIX: Check the actual amount value, not just the transaction type\n // Negative amounts are debits (show red with \"-\"), positive amounts are credits (show green with \"+\")\n const amount = Number(transaction.amount);\n const isCredit = amount > 0;\n const absAmount = Math.abs(amount);\n\n const getTransactionIcon = () => {\n // For adjustments, show different icon based on amount sign\n if (transaction.type === 'adjustment') {\n return isCredit ? (\n <Plus className=\"size-4 text-status-success-text\" />\n ) : (\n <Minus className=\"size-4 text-status-error-text\" />\n );\n }\n\n switch (transaction.type) {\n case 'purchase':\n return <Plus className=\"size-4 text-status-success-text\" />;\n case 'usage':\n return <Minus className=\"size-4 text-status-warning-text\" />;\n default:\n return <Coins className=\"size-4 text-text-secondary\" />;\n }\n };\n\n const getIconBackground = () => {\n // For adjustments, show different background based on amount sign\n if (transaction.type === 'adjustment') {\n return isCredit ? 'bg-status-success-bg-subtle' : 'bg-status-error-bg-subtle';\n }\n\n switch (transaction.type) {\n case 'purchase':\n return 'bg-status-success-bg-subtle';\n case 'usage':\n return 'bg-status-warning-bg-subtle';\n default:\n return 'bg-bg-sunken';\n }\n };\n\n return (\n <div className=\"flex items-center gap-4 p-4 border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 hover:shadow-sm transition-shadow\">\n {/* Icon */}\n <div\n className={`size-10 rounded-full flex items-center justify-center flex-shrink-0 ${getIconBackground()}`}\n >\n {getTransactionIcon()}\n </div>\n\n {/* Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <p className=\"font-medium text-text-primary capitalize\">\n {formatCreditTransactionType(transaction.type)}\n </p>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(statusColor)}`}\n >\n {transaction.status}\n </span>\n {transaction.entityType && (\n <span className=\"px-2 py-0.5 text-xs font-medium rounded bg-bg-sunken text-text-secondary capitalize\">\n {transaction.entityType}\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {formatRelativeTime(transaction.createdAt)} • {formatDateTime(transaction.createdAt)}\n </p>\n {transaction.productId && transaction.productId !== 'default' && (\n <p className=\"text-xs text-text-secondary mt-1\">Product: {transaction.productId}</p>\n )}\n </div>\n\n {/* Amount */}\n <div className=\"text-right flex-shrink-0\">\n <p\n className={`font-semibold text-lg ${isCredit ? 'text-status-success-text' : 'text-status-error-text'}`}\n >\n {isCredit ? '+' : '-'}\n {absAmount.toLocaleString()} credits\n </p>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CreditTransactionsPage.js","names":[],"sources":["../../../../../src/billing/modules/credits/pages/CreditTransactionsPage.tsx"],"sourcesContent":["/**\n * Credits Module - Credit Transactions Page\n * Displays credit transaction history for a specific billing account\n */\n\nimport { useState, type FC } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n Coins,\n CreditCard,\n Mail,\n ChevronLeft,\n ChevronRight,\n Plus,\n Minus,\n CheckCircle,\n XCircle,\n FileText,\n Calendar,\n} from 'lucide-react';\nimport { useCreditTransactions } from '../hooks/useCredits';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { AccessDenied, PageHeader, EmptyState, ServerError } from '../../../shared/components';\nimport {\n formatDateTime,\n formatRelativeTime,\n formatCreditTransactionType,\n isServerError,\n} from '../../../shared/utils';\nimport {\n getCreditTransactionStatusColor,\n getStatusBadgeClasses,\n} from '../../../shared/utils/status';\nimport type { CreditTransaction, CreditTransactionType } from '../../../shared/types';\nimport { useGetBillingAccountQuery } from '../../../../generated/global-operations';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\n\ntype FilterType = 'all' | CreditTransactionType;\n\nexport const CreditTransactionsPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { billingAccountId } = useParams<{ billingAccountId: string }>();\n const permissions = useBillingPermissions();\n const navigateTo = useBillingNavigate();\n\n const [days, setDays] = useState(30);\n const [filterType, setFilterType] = useState<FilterType>('all');\n const [page, setPage] = useState(1);\n const pageSize = 20;\n\n // Fetch billing account details\n const { data: accountData, loading: isLoadingAccount } = useGetBillingAccountQuery({\n variables: { id: billingAccountId! },\n skip: !billingAccountId,\n });\n\n const billingAccount = accountData?.getBillingAccount;\n\n // Fetch credit transactions\n const {\n transactions,\n pagination,\n isLoading: isLoadingTransactions,\n error,\n refetch,\n } = useCreditTransactions({\n billingAccountId: billingAccountId || '',\n days,\n page,\n pageSize,\n });\n\n // Permission check\n if (!permissions.canViewCredits) {\n return <AccessDenied message=\"You don't have permission to view credit history.\" />;\n }\n\n // Show error if no billing account ID provided\n if (!billingAccountId) {\n return (\n <div className=\"p-6\">\n <EmptyState\n icon={\n <svg\n className=\"size-6 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n }\n title=\"No billing account selected\"\n description=\"Please select a billing account from the overview page to view credit history.\"\n />\n </div>\n );\n }\n\n // Show server error if backend is down\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title=\"Server Unavailable\"\n message=\"Unable to load credit transactions. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n const isLoading = isLoadingAccount || isLoadingTransactions;\n\n // Filter transactions\n const filteredTransactions =\n filterType === 'all' ? transactions : transactions.filter((t) => t.type === filterType);\n\n // Calculate total pages\n const totalPages = Math.ceil(pagination.totalCount / pageSize);\n\n // Pagination handlers\n const goToNextPage = () => {\n if (pagination.hasMore) {\n setPage((p) => p + 1);\n }\n };\n\n const goToPreviousPage = () => {\n if (page > 1) {\n setPage((p) => p - 1);\n }\n };\n\n if (isLoading && !billingAccount) {\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 px-6 lg:px-8 py-6\">\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/overview')}\n className=\"inline-flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('billing.credits.backToOverview', 'Back to Overview')}\n </button>\n\n <PageHeader\n title=\"Credit History\"\n description={\n billingAccount\n ? `Credit transactions for ${billingAccount.name}`\n : 'View your credit transaction history'\n }\n />\n\n {/* Billing Account Info Card */}\n {billingAccount && (\n <div className=\"border border-border-subtle rounded-lg p-4 bg-bg-surface\">\n <div className=\"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4\">\n {/* Account Name & Status */}\n <div className=\"flex items-start gap-2\">\n <CreditCard className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Account</p>\n <p className=\"text-sm font-medium\">{billingAccount.name}</p>\n {billingAccount.isActive ? (\n <span className=\"inline-flex items-center gap-1 mt-1 px-1.5 py-0.5 text-xs font-medium rounded bg-status-success-bg-subtle text-status-success-text\">\n <CheckCircle className=\"size-3\" />\n Active\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1 mt-1 px-1.5 py-0.5 text-xs font-medium rounded bg-status-error-bg-subtle text-status-error-text\">\n <XCircle className=\"size-3\" />\n Inactive\n </span>\n )}\n </div>\n </div>\n\n {/* Email */}\n <div className=\"flex items-start gap-2\">\n <Mail className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Email</p>\n <p className=\"text-sm font-medium break-all\">\n {billingAccount.billingEmail || billingAccount.email}\n </p>\n </div>\n </div>\n\n {/* Credit Balance */}\n <div className=\"flex items-start gap-2\">\n <Coins className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Credit Balance</p>\n <p className=\"text-sm font-semibold text-text-primary\">\n {(billingAccount.creditAmount || 0).toLocaleString()} Credits\n </p>\n </div>\n </div>\n\n {/* Tax ID */}\n <div className=\"flex items-start gap-2\">\n <FileText className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Tax ID</p>\n <p className=\"text-sm font-medium\">{billingAccount.taxId || '-'}</p>\n </div>\n </div>\n\n {/* Account Created */}\n <div className=\"flex items-start gap-2\">\n <Calendar className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Member Since</p>\n <p className=\"text-sm font-medium\">\n {billingAccount.createdAt ? formatDateTime(billingAccount.createdAt) : '-'}\n </p>\n </div>\n </div>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border-subtle\">\n {/* Time Range */}\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken\">\n {[7, 30, 90].map((d) => (\n <button\n type=\"button\"\n key={d}\n onClick={() => {\n setDays(d);\n setPage(1); // Reset to first page on filter change\n }}\n className={`px-3 py-1.5 text-sm font-medium rounded-button transition-colors ${\n days === d\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {d}d\n </button>\n ))}\n </div>\n\n {/* Transaction Type Filter */}\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken\">\n {(['all', 'purchase', 'usage', 'adjustment'] as FilterType[]).map((type) => (\n <button\n type=\"button\"\n key={type}\n onClick={() => {\n setFilterType(type);\n setPage(1); // Reset to first page on filter change\n }}\n className={`px-3 py-1.5 text-sm font-medium rounded-button transition-colors capitalize ${\n filterType === type\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {type === 'all' ? 'All' : formatCreditTransactionType(type as CreditTransactionType)}\n </button>\n ))}\n </div>\n\n {/* Transaction count */}\n <div className=\"ml-auto text-sm text-text-secondary\">\n {pagination.totalCount} transaction{pagination.totalCount !== 1 ? 's' : ''} in last {days}{' '}\n days\n </div>\n </div>\n\n {/* Transactions List */}\n {filteredTransactions.length === 0 ? (\n <IllustratedEmptyState\n illustration={filterType !== 'all' ? 'empty-search' : 'empty-data'}\n title=\"No credit transactions found\"\n description={\n filterType !== 'all'\n ? 'No transactions match your filter.'\n : 'Your credit transaction history will appear here.'\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {filteredTransactions.map((transaction) => (\n <CreditTransactionRow key={transaction.id} transaction={transaction} />\n ))}\n </div>\n )}\n\n {/* Pagination Controls */}\n {pagination.totalCount > 0 && (\n <div className=\"flex items-center justify-between border-t border-border-subtle pt-4\">\n <p className=\"text-sm text-text-secondary\">\n Showing {(page - 1) * pageSize + 1} to{' '}\n {Math.min(page * pageSize, pagination.totalCount)} of {pagination.totalCount}{' '}\n transactions\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={goToPreviousPage}\n disabled={page <= 1}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n <ChevronLeft className=\"size-4\" />\n Previous\n </button>\n <span className=\"text-sm text-text-secondary px-2\">\n Page {page} of {totalPages || 1}\n </span>\n <button\n type=\"button\"\n onClick={goToNextPage}\n disabled={!pagination.hasMore}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n Next\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Credit Transaction Row Component\n// ============================================================================\n\ninterface CreditTransactionRowProps {\n transaction: CreditTransaction;\n}\n\nconst CreditTransactionRow: FC<CreditTransactionRowProps> = ({ transaction }) => {\n const statusColor = getCreditTransactionStatusColor(transaction.status);\n\n // FIX: Check the actual amount value, not just the transaction type\n // Negative amounts are debits (show red with \"-\"), positive amounts are credits (show green with \"+\")\n const amount = Number(transaction.amount);\n const isCredit = amount > 0;\n const absAmount = Math.abs(amount);\n\n const getTransactionIcon = () => {\n // For adjustments, show different icon based on amount sign\n if (transaction.type === 'adjustment') {\n return isCredit ? (\n <Plus className=\"size-4 text-status-success-text\" />\n ) : (\n <Minus className=\"size-4 text-status-error-text\" />\n );\n }\n\n switch (transaction.type) {\n case 'purchase':\n return <Plus className=\"size-4 text-status-success-text\" />;\n case 'usage':\n return <Minus className=\"size-4 text-status-warning-text\" />;\n default:\n return <Coins className=\"size-4 text-text-secondary\" />;\n }\n };\n\n const getIconBackground = () => {\n // For adjustments, show different background based on amount sign\n if (transaction.type === 'adjustment') {\n return isCredit ? 'bg-status-success-bg-subtle' : 'bg-status-error-bg-subtle';\n }\n\n switch (transaction.type) {\n case 'purchase':\n return 'bg-status-success-bg-subtle';\n case 'usage':\n return 'bg-status-warning-bg-subtle';\n default:\n return 'bg-bg-sunken';\n }\n };\n\n return (\n <div className=\"flex items-center gap-4 p-4 border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 hover:shadow-sm transition-shadow\">\n {/* Icon */}\n <div\n className={`size-10 rounded-full flex items-center justify-center flex-shrink-0 ${getIconBackground()}`}\n >\n {getTransactionIcon()}\n </div>\n\n {/* Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <p className=\"font-medium text-text-primary capitalize\">\n {formatCreditTransactionType(transaction.type)}\n </p>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(statusColor)}`}\n >\n {transaction.status}\n </span>\n {transaction.entityType && (\n <span className=\"px-2 py-0.5 text-xs font-medium rounded bg-bg-sunken text-text-secondary capitalize\">\n {transaction.entityType}\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n {formatRelativeTime(transaction.createdAt)} • {formatDateTime(transaction.createdAt)}\n </p>\n {transaction.productId && transaction.productId !== 'default' && (\n <p className=\"text-xs text-text-secondary mt-1\">Product: {transaction.productId}</p>\n )}\n </div>\n\n {/* Amount */}\n <div className=\"text-right flex-shrink-0\">\n <p\n className={`font-semibold text-lg ${isCredit ? 'text-status-success-text' : 'text-status-error-text'}`}\n >\n {isCredit ? '+' : '-'}\n {absAmount.toLocaleString()} credits\n </p>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -27,7 +27,7 @@ var v = ({ addon: e, onView: r, onPurchase: a, onAddToCart: o, isInCart: d = !1,
|
|
|
27
27
|
maximumFractionDigits: 2
|
|
28
28
|
}).format(e), w = (e) => e === "yearly" ? "year" : "month", T = t(() => _(e.features || []), [e.features]);
|
|
29
29
|
return /* @__PURE__ */ h("article", {
|
|
30
|
-
className: "flex flex-col border border-border-seam rounded-card bg-bg-surface shadow-[var(--shadow-pop)] overflow-hidden
|
|
30
|
+
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)]",
|
|
31
31
|
children: [
|
|
32
32
|
/* @__PURE__ */ m("header", {
|
|
33
33
|
className: "p-4 border-b border-border-subtle",
|
|
@@ -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 motion-safe:hover:translate-y-[var(--pop-lift-y)] hover:shadow-elevation-2 [@media(hover:none)]:hover:translate-y-0 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 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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"}
|