@burdenoff/microfe-billing 2026.623.2 → 2026.624.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/BillingUserRoutes.js +3 -3
- package/dist/billing/BillingUserRoutes.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +34 -34
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +94 -94
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +40 -40
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +10 -10
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/TransactionsPage.js +62 -62
- package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js +7 -7
- package/dist/billing/modules/coupons/pages/CreateCouponPage.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/earnings/pages/EarningsPage.js +21 -21
- package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +4 -4
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutsPage.js +33 -33
- package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +4 -4
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +146 -142
- 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/promotions/pages/CreatePromotionPage.js +4 -4
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/SettingsPage.js +57 -57
- package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +33 -33
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +1 -1
- 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/components/AccessDenied.js +4 -4
- package/dist/billing/shared/components/AccessDenied.js.map +1 -1
- package/dist/billing/shared/components/ActorIdentity.js +8 -8
- package/dist/billing/shared/components/ActorIdentity.js.map +1 -1
- package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js +11 -11
- package/dist/billing/shared/components/CurrencyPriceOverridesEditor.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/MetricCard.js +1 -1
- package/dist/billing/shared/ui/MetricCard.js.map +1 -1
- package/package.json +1 -1
|
@@ -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-subtle rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden hover:border-border-strong hover:shadow-elevation-2 hover:-translate-y-0.5 transition-all duration-200\">\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-subtle rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden hover:border-border-strong hover:shadow-elevation-2 transition-all duration-200\">\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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"}
|
|
@@ -123,7 +123,7 @@ var w = () => {
|
|
|
123
123
|
2,
|
|
124
124
|
3,
|
|
125
125
|
4
|
|
126
|
-
].map((e) => /* @__PURE__ */ S("div", { className: "h-28 animate-pulse rounded-lg border border-border bg-
|
|
126
|
+
].map((e) => /* @__PURE__ */ S("div", { className: "h-28 animate-pulse rounded-lg border border-border-subtle bg-bg-surface" }, e))
|
|
127
127
|
}) : Q ? R?.summary ? /* @__PURE__ */ C("div", {
|
|
128
128
|
className: "space-y-6",
|
|
129
129
|
children: [
|
|
@@ -136,16 +136,16 @@ var w = () => {
|
|
|
136
136
|
}, e.title))
|
|
137
137
|
}),
|
|
138
138
|
/* @__PURE__ */ C("section", {
|
|
139
|
-
className: "rounded-lg border border-border bg-
|
|
139
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
|
|
140
140
|
children: [/* @__PURE__ */ C("div", {
|
|
141
141
|
className: "mb-4 flex items-center justify-between",
|
|
142
142
|
children: [/* @__PURE__ */ S("h2", {
|
|
143
|
-
className: "text-lg font-semibold text-
|
|
143
|
+
className: "text-lg font-semibold text-text-primary",
|
|
144
144
|
children: "Recent sales"
|
|
145
145
|
}), /* @__PURE__ */ C("button", {
|
|
146
146
|
type: "button",
|
|
147
147
|
onClick: () => void q(),
|
|
148
|
-
className: "inline-flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-
|
|
148
|
+
className: "inline-flex items-center gap-2 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary",
|
|
149
149
|
children: [/* @__PURE__ */ S(y, { className: "size-4" }), "Refresh"]
|
|
150
150
|
})]
|
|
151
151
|
}), R.sales.length === 0 ? /* @__PURE__ */ S(i, {
|
|
@@ -157,7 +157,7 @@ var w = () => {
|
|
|
157
157
|
children: /* @__PURE__ */ C("table", {
|
|
158
158
|
className: "min-w-full text-sm",
|
|
159
159
|
children: [/* @__PURE__ */ S("thead", { children: /* @__PURE__ */ C("tr", {
|
|
160
|
-
className: "border-b border-border text-left text-
|
|
160
|
+
className: "border-b border-border-subtle text-left text-text-secondary",
|
|
161
161
|
children: [
|
|
162
162
|
/* @__PURE__ */ S("th", {
|
|
163
163
|
className: "px-3 py-2",
|
|
@@ -185,7 +185,7 @@ var w = () => {
|
|
|
185
185
|
})
|
|
186
186
|
]
|
|
187
187
|
}) }), /* @__PURE__ */ S("tbody", { children: R.sales.map((e) => /* @__PURE__ */ C("tr", {
|
|
188
|
-
className: "border-b border-border/70 text-
|
|
188
|
+
className: "border-b border-border-subtle/70 text-text-primary",
|
|
189
189
|
children: [
|
|
190
190
|
/* @__PURE__ */ S("td", {
|
|
191
191
|
className: "px-3 py-2 font-medium",
|
|
@@ -217,9 +217,9 @@ var w = () => {
|
|
|
217
217
|
})]
|
|
218
218
|
}),
|
|
219
219
|
/* @__PURE__ */ C("section", {
|
|
220
|
-
className: "rounded-lg border border-border bg-
|
|
220
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
|
|
221
221
|
children: [/* @__PURE__ */ S("h2", {
|
|
222
|
-
className: "mb-4 text-lg font-semibold text-
|
|
222
|
+
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
223
223
|
children: "Payouts"
|
|
224
224
|
}), R.payouts.length === 0 ? /* @__PURE__ */ S(i, {
|
|
225
225
|
icon: /* @__PURE__ */ S(b, { className: "size-8" }),
|
|
@@ -230,7 +230,7 @@ var w = () => {
|
|
|
230
230
|
children: /* @__PURE__ */ C("table", {
|
|
231
231
|
className: "min-w-full text-sm",
|
|
232
232
|
children: [/* @__PURE__ */ S("thead", { children: /* @__PURE__ */ C("tr", {
|
|
233
|
-
className: "border-b border-border text-left text-
|
|
233
|
+
className: "border-b border-border-subtle text-left text-text-secondary",
|
|
234
234
|
children: [
|
|
235
235
|
/* @__PURE__ */ S("th", {
|
|
236
236
|
className: "px-3 py-2",
|
|
@@ -254,7 +254,7 @@ var w = () => {
|
|
|
254
254
|
})
|
|
255
255
|
]
|
|
256
256
|
}) }), /* @__PURE__ */ S("tbody", { children: R.payouts.map((e) => /* @__PURE__ */ C("tr", {
|
|
257
|
-
className: "border-b border-border/70 text-
|
|
257
|
+
className: "border-b border-border-subtle/70 text-text-primary",
|
|
258
258
|
children: [
|
|
259
259
|
/* @__PURE__ */ S("td", {
|
|
260
260
|
className: "px-3 py-2 font-medium",
|
|
@@ -298,18 +298,18 @@ var w = () => {
|
|
|
298
298
|
}), X.length > 0 ? /* @__PURE__ */ S("div", {
|
|
299
299
|
className: "grid gap-4 md:grid-cols-2 xl:grid-cols-3",
|
|
300
300
|
children: X.map((e) => /* @__PURE__ */ C("div", {
|
|
301
|
-
className: "rounded-lg border border-border bg-
|
|
301
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
|
|
302
302
|
children: [
|
|
303
303
|
/* @__PURE__ */ S("p", {
|
|
304
|
-
className: "text-sm text-
|
|
304
|
+
className: "text-sm text-text-secondary",
|
|
305
305
|
children: e.title
|
|
306
306
|
}),
|
|
307
307
|
/* @__PURE__ */ S("p", {
|
|
308
|
-
className: "mt-2 text-xl font-semibold text-
|
|
308
|
+
className: "mt-2 text-xl font-semibold text-text-primary",
|
|
309
309
|
children: e.value
|
|
310
310
|
}),
|
|
311
311
|
/* @__PURE__ */ S("p", {
|
|
312
|
-
className: "mt-1 text-xs text-
|
|
312
|
+
className: "mt-1 text-xs text-text-secondary",
|
|
313
313
|
children: e.helperText
|
|
314
314
|
})
|
|
315
315
|
]
|
|
@@ -317,22 +317,22 @@ var w = () => {
|
|
|
317
317
|
}) : null]
|
|
318
318
|
}) : null,
|
|
319
319
|
W && !c(W) ? /* @__PURE__ */ S("div", {
|
|
320
|
-
className: "rounded-lg border border-border bg-
|
|
320
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm text-status-error-text",
|
|
321
321
|
children: W.message
|
|
322
322
|
}) : null,
|
|
323
323
|
/* @__PURE__ */ C("section", {
|
|
324
|
-
className: "rounded-lg border border-border bg-
|
|
324
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface p-4",
|
|
325
325
|
children: [/* @__PURE__ */ C("div", {
|
|
326
326
|
className: "mb-4 flex items-center justify-between",
|
|
327
327
|
children: [/* @__PURE__ */ S("h2", {
|
|
328
|
-
className: "text-lg font-semibold text-
|
|
328
|
+
className: "text-lg font-semibold text-text-primary",
|
|
329
329
|
children: "Ledger"
|
|
330
330
|
}), /* @__PURE__ */ C("div", {
|
|
331
331
|
className: "flex items-center gap-3",
|
|
332
332
|
children: [/* @__PURE__ */ C("select", {
|
|
333
333
|
value: B,
|
|
334
334
|
onChange: (e) => V(e.target.value),
|
|
335
|
-
className: "rounded-md border border-border bg-
|
|
335
|
+
className: "rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary",
|
|
336
336
|
children: [
|
|
337
337
|
/* @__PURE__ */ S("option", {
|
|
338
338
|
value: "ALL",
|
|
@@ -354,7 +354,7 @@ var w = () => {
|
|
|
354
354
|
}), /* @__PURE__ */ C("button", {
|
|
355
355
|
type: "button",
|
|
356
356
|
onClick: () => void q(),
|
|
357
|
-
className: "inline-flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-
|
|
357
|
+
className: "inline-flex items-center gap-2 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary",
|
|
358
358
|
children: [/* @__PURE__ */ S(y, { className: "size-4" }), "Refresh"]
|
|
359
359
|
})]
|
|
360
360
|
})]
|
|
@@ -367,7 +367,7 @@ var w = () => {
|
|
|
367
367
|
children: /* @__PURE__ */ C("table", {
|
|
368
368
|
className: "min-w-full text-sm",
|
|
369
369
|
children: [/* @__PURE__ */ S("thead", { children: /* @__PURE__ */ C("tr", {
|
|
370
|
-
className: "border-b border-border text-left text-
|
|
370
|
+
className: "border-b border-border-subtle text-left text-text-secondary",
|
|
371
371
|
children: [
|
|
372
372
|
/* @__PURE__ */ S("th", {
|
|
373
373
|
className: "px-3 py-2",
|
|
@@ -395,7 +395,7 @@ var w = () => {
|
|
|
395
395
|
})
|
|
396
396
|
]
|
|
397
397
|
}) }), /* @__PURE__ */ S("tbody", { children: Y.map((e) => /* @__PURE__ */ C("tr", {
|
|
398
|
-
className: "border-b border-border/70 text-
|
|
398
|
+
className: "border-b border-border-subtle/70 text-text-primary",
|
|
399
399
|
children: [
|
|
400
400
|
/* @__PURE__ */ S("td", {
|
|
401
401
|
className: "px-3 py-2 font-medium",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EarningsPage.js","names":[],"sources":["../../../../../src/billing/modules/earnings/pages/EarningsPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type FC } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Coins, HandCoins, RefreshCw, Wallet } from 'lucide-react';\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport {\n AccessDenied,\n EmptyState,\n PageHeader,\n ServerError,\n StatCard,\n} from '../../../shared/components';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\nimport { useGetDefaultBillingAccountSettingsQuery } from '../../../../generated/global-operations';\nimport {\n fetchEarningsEntries,\n fetchEarningsSummary,\n fetchDeveloperPortalRevenueDashboard,\n type DeveloperPortalRevenueDashboard,\n type EarningsEntry,\n type EarningsSummary,\n} from '../api';\n\ntype EarningsSourceFilter = 'ALL' | 'AFFILIATE' | 'STORE' | 'OTHER';\n\nexport const EarningsPage: FC = () => {\n const [searchParams] = useSearchParams();\n const permissions = useBillingPermissions();\n const { apiGatewayUrl, authToken, orgId } = useBilling();\n const developerOrgId = searchParams.get('developerOrgId');\n const developerOrgName = searchParams.get('developerOrgName');\n const { data: billingAccountData, loading: billingAccountLoading } =\n useGetDefaultBillingAccountSettingsQuery({});\n\n const billingAccountId = billingAccountData?.getDefaultBillingAccount?.id;\n const [summary, setSummary] = useState<EarningsSummary | null>(null);\n const [entries, setEntries] = useState<EarningsEntry[]>([]);\n const [developerPortalRevenue, setDeveloperPortalRevenue] =\n useState<DeveloperPortalRevenueDashboard | null>(null);\n const [sourceFilter, setSourceFilter] = useState<EarningsSourceFilter>('ALL');\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const requestContext = useMemo(\n () => ({\n apiGatewayUrl,\n authToken,\n orgId,\n }),\n [apiGatewayUrl, authToken, orgId]\n );\n\n const loadData = async () => {\n setLoading(true);\n setError(null);\n try {\n if (developerOrgId) {\n const loadedRevenue = await fetchDeveloperPortalRevenueDashboard(\n developerOrgId,\n requestContext\n );\n setDeveloperPortalRevenue(loadedRevenue);\n setSummary(null);\n setEntries([]);\n return;\n }\n\n if (!billingAccountId) {\n setSummary(null);\n setEntries([]);\n setDeveloperPortalRevenue(null);\n return;\n }\n\n const [loadedSummary, loadedEntries] = await Promise.all([\n fetchEarningsSummary(billingAccountId, requestContext),\n fetchEarningsEntries(billingAccountId, requestContext),\n ]);\n setSummary(loadedSummary);\n setEntries(loadedEntries);\n setDeveloperPortalRevenue(null);\n } catch (loadError) {\n setError(loadError instanceof Error ? loadError : new Error('Failed to load earnings'));\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [billingAccountId, developerOrgId, requestContext]);\n\n if (!permissions.canViewBillingAccount) {\n return <AccessDenied message=\"You don't have permission to view earnings.\" />;\n }\n\n if (!developerOrgId && !billingAccountId && !billingAccountLoading) {\n return (\n <EmptyState\n icon={<Wallet className=\"size-8\" />}\n title=\"No billing account found\"\n description=\"Create a billing account first to view earnings.\"\n />\n );\n }\n\n if (error && isServerError(error)) {\n return (\n <ServerError\n title=\"Earnings unavailable\"\n message={error.message}\n onRetry={() => void loadData()}\n showRetry\n />\n );\n }\n\n const cards = summary\n ? [\n {\n title: 'Available',\n value: formatCurrency(summary.availableAmount, 'USD'),\n icon: <HandCoins className=\"size-5\" />,\n },\n {\n title: 'Reserved',\n value: formatCurrency(summary.reservedAmount, 'USD'),\n icon: <Coins className=\"size-5\" />,\n },\n {\n title: 'Paid out',\n value: formatCurrency(summary.paidAmount, 'USD'),\n icon: <Wallet className=\"size-5\" />,\n },\n {\n title: 'Lifetime',\n value: formatCurrency(summary.totalLifetimeAmount, 'USD'),\n icon: <RefreshCw className=\"size-5\" />,\n },\n ]\n : [];\n\n const filteredEntries =\n sourceFilter === 'ALL'\n ? entries\n : entries.filter((entry) => entry.sourceCategory === sourceFilter);\n\n const categoryCards =\n summary?.categoryBreakdown?.map((item) => ({\n title: `${item.category} earnings`,\n value: formatCurrency(item.amount, 'USD'),\n helperText: `${item.count} entries`,\n })) ?? [];\n\n const developerPortalCards = developerPortalRevenue?.summary\n ? [\n {\n title: 'Gross revenue',\n value: formatCurrency(developerPortalRevenue.summary.totalGrossRevenue, 'USD'),\n icon: <HandCoins className=\"size-5\" />,\n },\n {\n title: 'Net revenue',\n value: formatCurrency(developerPortalRevenue.summary.totalNetRevenue, 'USD'),\n icon: <Wallet className=\"size-5\" />,\n },\n {\n title: 'Pending balance',\n value: formatCurrency(developerPortalRevenue.summary.pendingBalance, 'USD'),\n icon: <Coins className=\"size-5\" />,\n },\n {\n title: 'Platform fees',\n value: formatCurrency(developerPortalRevenue.summary.totalPlatformFees, 'USD'),\n icon: <RefreshCw className=\"size-5\" />,\n },\n ]\n : [];\n\n const isDeveloperPortalMode = !!developerOrgId;\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={isDeveloperPortalMode ? 'Marketplace earnings' : 'Earnings'}\n description={\n isDeveloperPortalMode\n ? `Developer portal revenue, sales, and payouts${developerOrgName ? ` for ${developerOrgName}` : ''}.`\n : 'Track affiliate, store, and other earning sources in one ledger.'\n }\n />\n\n {loading ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {[1, 2, 3, 4].map((item) => (\n <div\n key={item}\n className=\"h-28 animate-pulse rounded-lg border border-border bg-card\"\n />\n ))}\n </div>\n ) : isDeveloperPortalMode ? (\n developerPortalRevenue?.summary ? (\n <div className=\"space-y-6\">\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {developerPortalCards.map((card) => (\n <StatCard key={card.title} label={card.title} value={card.value} icon={card.icon} />\n ))}\n </div>\n\n <section className=\"rounded-lg border border-border bg-card p-4\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-foreground\">Recent sales</h2>\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-foreground\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n </div>\n\n {developerPortalRevenue.sales.length === 0 ? (\n <EmptyState\n icon={<Coins className=\"size-8\" />}\n title=\"No sales yet\"\n description=\"Sales will appear here when customers start purchasing your apps.\"\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border text-left text-muted-foreground\">\n <th className=\"px-3 py-2\">Application</th>\n <th className=\"px-3 py-2\">Pricing</th>\n <th className=\"px-3 py-2\">Gross</th>\n <th className=\"px-3 py-2\">Net</th>\n <th className=\"px-3 py-2\">Status</th>\n <th className=\"px-3 py-2\">Purchased</th>\n </tr>\n </thead>\n <tbody>\n {developerPortalRevenue.sales.map((sale) => (\n <tr key={sale.id} className=\"border-b border-border/70 text-foreground\">\n <td className=\"px-3 py-2 font-medium\">\n {sale.application?.name || sale.application?.slug || 'Unknown app'}\n </td>\n <td className=\"px-3 py-2\">{sale.pricingModel}</td>\n <td className=\"px-3 py-2\">\n {formatCurrency(sale.grossAmount, sale.currency || 'USD')}\n </td>\n <td className=\"px-3 py-2\">\n {formatCurrency(sale.netAmount, sale.currency || 'USD')}\n </td>\n <td className=\"px-3 py-2\">{sale.status}</td>\n <td className=\"px-3 py-2\">{formatDateTime(sale.purchasedAt)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </section>\n\n <section className=\"rounded-lg border border-border bg-card p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-foreground\">Payouts</h2>\n\n {developerPortalRevenue.payouts.length === 0 ? (\n <EmptyState\n icon={<Wallet className=\"size-8\" />}\n title=\"No payouts yet\"\n description=\"Scheduled and completed payouts will appear here.\"\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border text-left text-muted-foreground\">\n <th className=\"px-3 py-2\">Period</th>\n <th className=\"px-3 py-2\">Amount</th>\n <th className=\"px-3 py-2\">Status</th>\n <th className=\"px-3 py-2\">Scheduled</th>\n <th className=\"px-3 py-2\">Completed</th>\n </tr>\n </thead>\n <tbody>\n {developerPortalRevenue.payouts.map((payout) => (\n <tr key={payout.id} className=\"border-b border-border/70 text-foreground\">\n <td className=\"px-3 py-2 font-medium\">{payout.periodLabel || '-'}</td>\n <td className=\"px-3 py-2\">\n {formatCurrency(payout.netAmount, payout.currency || 'USD')}\n </td>\n <td className=\"px-3 py-2\">{payout.status}</td>\n <td className=\"px-3 py-2\">\n {payout.scheduledAt ? formatDateTime(payout.scheduledAt) : '-'}\n </td>\n <td className=\"px-3 py-2\">\n {payout.completedAt ? formatDateTime(payout.completedAt) : '-'}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </section>\n </div>\n ) : (\n <EmptyState\n icon={<Coins className=\"size-8\" />}\n title=\"No developer portal revenue yet\"\n description=\"Marketplace revenue will appear here once your developer organization records sales.\"\n />\n )\n ) : summary ? (\n <div className=\"space-y-4\">\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {cards.map((card) => (\n <StatCard key={card.title} label={card.title} value={card.value} icon={card.icon} />\n ))}\n </div>\n {categoryCards.length > 0 ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-3\">\n {categoryCards.map((card) => (\n <div key={card.title} className=\"rounded-lg border border-border bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">{card.title}</p>\n <p className=\"mt-2 text-xl font-semibold text-foreground\">{card.value}</p>\n <p className=\"mt-1 text-xs text-muted-foreground\">{card.helperText}</p>\n </div>\n ))}\n </div>\n ) : null}\n </div>\n ) : null}\n\n {error && !isServerError(error) ? (\n <div className=\"rounded-lg border border-border bg-card p-4 text-sm text-destructive\">\n {error.message}\n </div>\n ) : null}\n\n <section className=\"rounded-lg border border-border bg-card p-4\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-foreground\">Ledger</h2>\n <div className=\"flex items-center gap-3\">\n <select\n value={sourceFilter}\n onChange={(event) => setSourceFilter(event.target.value as EarningsSourceFilter)}\n className=\"rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground\"\n >\n <option value=\"ALL\">All sources</option>\n <option value=\"AFFILIATE\">Affiliate</option>\n <option value=\"STORE\">Store</option>\n <option value=\"OTHER\">Other</option>\n </select>\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-foreground\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n </div>\n </div>\n\n {filteredEntries.length === 0 && !loading ? (\n <EmptyState\n icon={<Coins className=\"size-8\" />}\n title=\"No earnings yet\"\n description=\"Eligible affiliate rewards and other earnings will appear here.\"\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border text-left text-muted-foreground\">\n <th className=\"px-3 py-2\">Category</th>\n <th className=\"px-3 py-2\">Description</th>\n <th className=\"px-3 py-2\">Unit</th>\n <th className=\"px-3 py-2\">Amount</th>\n <th className=\"px-3 py-2\">Status</th>\n <th className=\"px-3 py-2\">Created</th>\n </tr>\n </thead>\n <tbody>\n {filteredEntries.map((entry) => (\n <tr key={entry.id} className=\"border-b border-border/70 text-foreground\">\n <td className=\"px-3 py-2 font-medium\">{entry.sourceCategory}</td>\n <td className=\"px-3 py-2\">\n {entry.description || entry.sourceSubcategory || '-'}\n </td>\n <td className=\"px-3 py-2\">{entry.unitType}</td>\n <td className=\"px-3 py-2\">\n {entry.unitType === 'CREDITS'\n ? `${entry.amount.toLocaleString()} credits`\n : formatCurrency(entry.amount, entry.currency || 'USD')}\n </td>\n <td className=\"px-3 py-2\">{entry.status}</td>\n <td className=\"px-3 py-2\">{formatDateTime(entry.createdAt)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </section>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EarningsPage.js","names":[],"sources":["../../../../../src/billing/modules/earnings/pages/EarningsPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type FC } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Coins, HandCoins, RefreshCw, Wallet } from 'lucide-react';\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport {\n AccessDenied,\n EmptyState,\n PageHeader,\n ServerError,\n StatCard,\n} from '../../../shared/components';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\nimport { useGetDefaultBillingAccountSettingsQuery } from '../../../../generated/global-operations';\nimport {\n fetchEarningsEntries,\n fetchEarningsSummary,\n fetchDeveloperPortalRevenueDashboard,\n type DeveloperPortalRevenueDashboard,\n type EarningsEntry,\n type EarningsSummary,\n} from '../api';\n\ntype EarningsSourceFilter = 'ALL' | 'AFFILIATE' | 'STORE' | 'OTHER';\n\nexport const EarningsPage: FC = () => {\n const [searchParams] = useSearchParams();\n const permissions = useBillingPermissions();\n const { apiGatewayUrl, authToken, orgId } = useBilling();\n const developerOrgId = searchParams.get('developerOrgId');\n const developerOrgName = searchParams.get('developerOrgName');\n const { data: billingAccountData, loading: billingAccountLoading } =\n useGetDefaultBillingAccountSettingsQuery({});\n\n const billingAccountId = billingAccountData?.getDefaultBillingAccount?.id;\n const [summary, setSummary] = useState<EarningsSummary | null>(null);\n const [entries, setEntries] = useState<EarningsEntry[]>([]);\n const [developerPortalRevenue, setDeveloperPortalRevenue] =\n useState<DeveloperPortalRevenueDashboard | null>(null);\n const [sourceFilter, setSourceFilter] = useState<EarningsSourceFilter>('ALL');\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const requestContext = useMemo(\n () => ({\n apiGatewayUrl,\n authToken,\n orgId,\n }),\n [apiGatewayUrl, authToken, orgId]\n );\n\n const loadData = async () => {\n setLoading(true);\n setError(null);\n try {\n if (developerOrgId) {\n const loadedRevenue = await fetchDeveloperPortalRevenueDashboard(\n developerOrgId,\n requestContext\n );\n setDeveloperPortalRevenue(loadedRevenue);\n setSummary(null);\n setEntries([]);\n return;\n }\n\n if (!billingAccountId) {\n setSummary(null);\n setEntries([]);\n setDeveloperPortalRevenue(null);\n return;\n }\n\n const [loadedSummary, loadedEntries] = await Promise.all([\n fetchEarningsSummary(billingAccountId, requestContext),\n fetchEarningsEntries(billingAccountId, requestContext),\n ]);\n setSummary(loadedSummary);\n setEntries(loadedEntries);\n setDeveloperPortalRevenue(null);\n } catch (loadError) {\n setError(loadError instanceof Error ? loadError : new Error('Failed to load earnings'));\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [billingAccountId, developerOrgId, requestContext]);\n\n if (!permissions.canViewBillingAccount) {\n return <AccessDenied message=\"You don't have permission to view earnings.\" />;\n }\n\n if (!developerOrgId && !billingAccountId && !billingAccountLoading) {\n return (\n <EmptyState\n icon={<Wallet className=\"size-8\" />}\n title=\"No billing account found\"\n description=\"Create a billing account first to view earnings.\"\n />\n );\n }\n\n if (error && isServerError(error)) {\n return (\n <ServerError\n title=\"Earnings unavailable\"\n message={error.message}\n onRetry={() => void loadData()}\n showRetry\n />\n );\n }\n\n const cards = summary\n ? [\n {\n title: 'Available',\n value: formatCurrency(summary.availableAmount, 'USD'),\n icon: <HandCoins className=\"size-5\" />,\n },\n {\n title: 'Reserved',\n value: formatCurrency(summary.reservedAmount, 'USD'),\n icon: <Coins className=\"size-5\" />,\n },\n {\n title: 'Paid out',\n value: formatCurrency(summary.paidAmount, 'USD'),\n icon: <Wallet className=\"size-5\" />,\n },\n {\n title: 'Lifetime',\n value: formatCurrency(summary.totalLifetimeAmount, 'USD'),\n icon: <RefreshCw className=\"size-5\" />,\n },\n ]\n : [];\n\n const filteredEntries =\n sourceFilter === 'ALL'\n ? entries\n : entries.filter((entry) => entry.sourceCategory === sourceFilter);\n\n const categoryCards =\n summary?.categoryBreakdown?.map((item) => ({\n title: `${item.category} earnings`,\n value: formatCurrency(item.amount, 'USD'),\n helperText: `${item.count} entries`,\n })) ?? [];\n\n const developerPortalCards = developerPortalRevenue?.summary\n ? [\n {\n title: 'Gross revenue',\n value: formatCurrency(developerPortalRevenue.summary.totalGrossRevenue, 'USD'),\n icon: <HandCoins className=\"size-5\" />,\n },\n {\n title: 'Net revenue',\n value: formatCurrency(developerPortalRevenue.summary.totalNetRevenue, 'USD'),\n icon: <Wallet className=\"size-5\" />,\n },\n {\n title: 'Pending balance',\n value: formatCurrency(developerPortalRevenue.summary.pendingBalance, 'USD'),\n icon: <Coins className=\"size-5\" />,\n },\n {\n title: 'Platform fees',\n value: formatCurrency(developerPortalRevenue.summary.totalPlatformFees, 'USD'),\n icon: <RefreshCw className=\"size-5\" />,\n },\n ]\n : [];\n\n const isDeveloperPortalMode = !!developerOrgId;\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={isDeveloperPortalMode ? 'Marketplace earnings' : 'Earnings'}\n description={\n isDeveloperPortalMode\n ? `Developer portal revenue, sales, and payouts${developerOrgName ? ` for ${developerOrgName}` : ''}.`\n : 'Track affiliate, store, and other earning sources in one ledger.'\n }\n />\n\n {loading ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {[1, 2, 3, 4].map((item) => (\n <div\n key={item}\n className=\"h-28 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n ) : isDeveloperPortalMode ? (\n developerPortalRevenue?.summary ? (\n <div className=\"space-y-6\">\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {developerPortalCards.map((card) => (\n <StatCard key={card.title} label={card.title} value={card.value} icon={card.icon} />\n ))}\n </div>\n\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Recent sales</h2>\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n </div>\n\n {developerPortalRevenue.sales.length === 0 ? (\n <EmptyState\n icon={<Coins className=\"size-8\" />}\n title=\"No sales yet\"\n description=\"Sales will appear here when customers start purchasing your apps.\"\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle text-left text-text-secondary\">\n <th className=\"px-3 py-2\">Application</th>\n <th className=\"px-3 py-2\">Pricing</th>\n <th className=\"px-3 py-2\">Gross</th>\n <th className=\"px-3 py-2\">Net</th>\n <th className=\"px-3 py-2\">Status</th>\n <th className=\"px-3 py-2\">Purchased</th>\n </tr>\n </thead>\n <tbody>\n {developerPortalRevenue.sales.map((sale) => (\n <tr key={sale.id} className=\"border-b border-border-subtle/70 text-text-primary\">\n <td className=\"px-3 py-2 font-medium\">\n {sale.application?.name || sale.application?.slug || 'Unknown app'}\n </td>\n <td className=\"px-3 py-2\">{sale.pricingModel}</td>\n <td className=\"px-3 py-2\">\n {formatCurrency(sale.grossAmount, sale.currency || 'USD')}\n </td>\n <td className=\"px-3 py-2\">\n {formatCurrency(sale.netAmount, sale.currency || 'USD')}\n </td>\n <td className=\"px-3 py-2\">{sale.status}</td>\n <td className=\"px-3 py-2\">{formatDateTime(sale.purchasedAt)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </section>\n\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Payouts</h2>\n\n {developerPortalRevenue.payouts.length === 0 ? (\n <EmptyState\n icon={<Wallet className=\"size-8\" />}\n title=\"No payouts yet\"\n description=\"Scheduled and completed payouts will appear here.\"\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle text-left text-text-secondary\">\n <th className=\"px-3 py-2\">Period</th>\n <th className=\"px-3 py-2\">Amount</th>\n <th className=\"px-3 py-2\">Status</th>\n <th className=\"px-3 py-2\">Scheduled</th>\n <th className=\"px-3 py-2\">Completed</th>\n </tr>\n </thead>\n <tbody>\n {developerPortalRevenue.payouts.map((payout) => (\n <tr key={payout.id} className=\"border-b border-border-subtle/70 text-text-primary\">\n <td className=\"px-3 py-2 font-medium\">{payout.periodLabel || '-'}</td>\n <td className=\"px-3 py-2\">\n {formatCurrency(payout.netAmount, payout.currency || 'USD')}\n </td>\n <td className=\"px-3 py-2\">{payout.status}</td>\n <td className=\"px-3 py-2\">\n {payout.scheduledAt ? formatDateTime(payout.scheduledAt) : '-'}\n </td>\n <td className=\"px-3 py-2\">\n {payout.completedAt ? formatDateTime(payout.completedAt) : '-'}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </section>\n </div>\n ) : (\n <EmptyState\n icon={<Coins className=\"size-8\" />}\n title=\"No developer portal revenue yet\"\n description=\"Marketplace revenue will appear here once your developer organization records sales.\"\n />\n )\n ) : summary ? (\n <div className=\"space-y-4\">\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {cards.map((card) => (\n <StatCard key={card.title} label={card.title} value={card.value} icon={card.icon} />\n ))}\n </div>\n {categoryCards.length > 0 ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-3\">\n {categoryCards.map((card) => (\n <div key={card.title} className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <p className=\"text-sm text-text-secondary\">{card.title}</p>\n <p className=\"mt-2 text-xl font-semibold text-text-primary\">{card.value}</p>\n <p className=\"mt-1 text-xs text-text-secondary\">{card.helperText}</p>\n </div>\n ))}\n </div>\n ) : null}\n </div>\n ) : null}\n\n {error && !isServerError(error) ? (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm text-status-error-text\">\n {error.message}\n </div>\n ) : null}\n\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Ledger</h2>\n <div className=\"flex items-center gap-3\">\n <select\n value={sourceFilter}\n onChange={(event) => setSourceFilter(event.target.value as EarningsSourceFilter)}\n className=\"rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"ALL\">All sources</option>\n <option value=\"AFFILIATE\">Affiliate</option>\n <option value=\"STORE\">Store</option>\n <option value=\"OTHER\">Other</option>\n </select>\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n </div>\n </div>\n\n {filteredEntries.length === 0 && !loading ? (\n <EmptyState\n icon={<Coins className=\"size-8\" />}\n title=\"No earnings yet\"\n description=\"Eligible affiliate rewards and other earnings will appear here.\"\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle text-left text-text-secondary\">\n <th className=\"px-3 py-2\">Category</th>\n <th className=\"px-3 py-2\">Description</th>\n <th className=\"px-3 py-2\">Unit</th>\n <th className=\"px-3 py-2\">Amount</th>\n <th className=\"px-3 py-2\">Status</th>\n <th className=\"px-3 py-2\">Created</th>\n </tr>\n </thead>\n <tbody>\n {filteredEntries.map((entry) => (\n <tr key={entry.id} className=\"border-b border-border-subtle/70 text-text-primary\">\n <td className=\"px-3 py-2 font-medium\">{entry.sourceCategory}</td>\n <td className=\"px-3 py-2\">\n {entry.description || entry.sourceSubcategory || '-'}\n </td>\n <td className=\"px-3 py-2\">{entry.unitType}</td>\n <td className=\"px-3 py-2\">\n {entry.unitType === 'CREDITS'\n ? `${entry.amount.toLocaleString()} credits`\n : formatCurrency(entry.amount, entry.currency || 'USD')}\n </td>\n <td className=\"px-3 py-2\">{entry.status}</td>\n <td className=\"px-3 py-2\">{formatDateTime(entry.createdAt)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </section>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -349,7 +349,7 @@ function L({ dialog: e, onClose: t, onConfirm: n }) {
|
|
|
349
349
|
value: c,
|
|
350
350
|
onChange: (e) => l(e.target.value),
|
|
351
351
|
placeholder: "e.g. UTR123456789012, TXN_abc123",
|
|
352
|
-
className: "w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-ring"
|
|
352
|
+
className: "w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
|
|
353
353
|
}),
|
|
354
354
|
/* @__PURE__ */ k("p", {
|
|
355
355
|
className: "text-xs text-text-muted mt-1",
|
|
@@ -363,7 +363,7 @@ function L({ dialog: e, onClose: t, onConfirm: n }) {
|
|
|
363
363
|
onChange: (e) => i(e.target.value),
|
|
364
364
|
rows: 2,
|
|
365
365
|
placeholder: "Any internal notes about this payment…",
|
|
366
|
-
className: "w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-ring resize-none"
|
|
366
|
+
className: "w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none"
|
|
367
367
|
})] })]
|
|
368
368
|
}),
|
|
369
369
|
e.type === "approve" && /* @__PURE__ */ A("div", { children: [/* @__PURE__ */ k("label", {
|
|
@@ -374,7 +374,7 @@ function L({ dialog: e, onClose: t, onConfirm: n }) {
|
|
|
374
374
|
onChange: (e) => i(e.target.value),
|
|
375
375
|
rows: 3,
|
|
376
376
|
placeholder: "e.g. Verified bank details, approved for transfer…",
|
|
377
|
-
className: "w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-ring resize-none"
|
|
377
|
+
className: "w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none"
|
|
378
378
|
})] }),
|
|
379
379
|
e.type === "reject" && /* @__PURE__ */ A("div", { children: [/* @__PURE__ */ A("label", {
|
|
380
380
|
className: "text-xs font-medium text-text-primary block mb-1",
|
|
@@ -387,7 +387,7 @@ function L({ dialog: e, onClose: t, onConfirm: n }) {
|
|
|
387
387
|
onChange: (e) => s(e.target.value),
|
|
388
388
|
rows: 3,
|
|
389
389
|
placeholder: "e.g. Invalid bank details provided, please re-submit with correct IFSC…",
|
|
390
|
-
className: "w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-ring resize-none"
|
|
390
|
+
className: "w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none"
|
|
391
391
|
})] }),
|
|
392
392
|
/* @__PURE__ */ A("div", {
|
|
393
393
|
className: "flex justify-end gap-2 pt-2",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PayoutAdminPage.js","names":[],"sources":["../../../../../src/billing/modules/earnings/pages/PayoutAdminPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type FC } from 'react';\nimport {\n AlertTriangle,\n Banknote,\n CheckCircle2,\n ChevronDown,\n ChevronUp,\n Clock,\n CreditCard,\n RefreshCw,\n ReceiptText,\n XCircle,\n} from 'lucide-react';\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { AccessDenied, EmptyState, PageHeader, ServerError } from '../../../shared/components';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\nimport {\n approvePayoutRequest,\n fetchAdminPayoutRequests,\n fetchAdminPayoutSummary,\n markPayoutPaid,\n rejectPayoutRequest,\n type PayoutAdminSummary,\n type PayoutRequest,\n} from '../api';\n\ntype Tab = 'pending' | 'approved' | 'history';\n\ninterface ActionDialogState {\n type: 'approve' | 'reject' | 'mark_paid';\n requestId: string;\n amount: number;\n currency: string;\n accountLabel: string;\n}\n\nconst STATUS_LABEL: Record<string, string> = {\n REQUESTED: 'Requested',\n APPROVED: 'Approved',\n REJECTED: 'Rejected',\n PROCESSING: 'Processing',\n PAID: 'Paid',\n FAILED: 'Failed',\n CANCELED: 'Canceled',\n};\n\nconst STATUS_COLORS: Record<string, string> = {\n REQUESTED: 'bg-status-warning-bg-subtle text-status-warning-text',\n APPROVED: 'bg-status-info-bg-subtle text-status-info-text',\n REJECTED: 'bg-status-error-bg-subtle text-status-error-text',\n PROCESSING: 'bg-status-info-bg-subtle text-status-info-text',\n PAID: 'bg-status-success-bg-subtle text-status-success-text',\n FAILED: 'bg-status-error-bg-subtle text-status-error-text',\n CANCELED: 'bg-bg-sunken text-text-muted',\n};\n\nfunction StatusBadge({ status }: { status: string }) {\n return (\n <span\n className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${STATUS_COLORS[status] ?? 'bg-bg-sunken text-text-muted'}`}\n >\n {STATUS_LABEL[status] ?? status}\n </span>\n );\n}\n\nfunction AccountDetails({ account }: { account: NonNullable<PayoutRequest['payoutAccount']> }) {\n const [expanded, setExpanded] = useState(false);\n const details = account.accountDetails as Record<string, string | null> | null;\n\n return (\n <div className=\"rounded-md border border-border-subtle bg-bg-sunken/40 p-3 text-sm\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2 text-text-primary font-medium\">\n <CreditCard className=\"size-4 text-text-muted\" />\n {account.displayName ?? account.label}\n <span className=\"text-xs text-text-muted font-normal\">\n ({account.type === 'UPI' ? 'UPI' : 'Bank'} · {account.country ?? 'Unknown'} ·{' '}\n {account.currency})\n </span>\n </div>\n {details && (\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className=\"text-text-muted hover:text-text-primary\"\n >\n {expanded ? <ChevronUp className=\"size-4\" /> : <ChevronDown className=\"size-4\" />}\n </button>\n )}\n </div>\n {expanded && details && (\n <div className=\"mt-2 grid gap-1 text-xs\">\n {details.accountHolderName && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Account holder</span>\n <span className=\"text-text-primary\">{details.accountHolderName}</span>\n </div>\n )}\n {details.bankName && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Bank</span>\n <span className=\"text-text-primary\">{details.bankName}</span>\n </div>\n )}\n {account.accountLast4 && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Account (last 4)</span>\n <span className=\"text-text-primary font-mono\">••••{account.accountLast4}</span>\n </div>\n )}\n {details.ifsc && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">IFSC</span>\n <span className=\"text-text-primary font-mono\">{details.ifsc}</span>\n </div>\n )}\n {details.routingNumberLast4 && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Routing (last 4)</span>\n <span className=\"text-text-primary font-mono\">••••{details.routingNumberLast4}</span>\n </div>\n )}\n {details.upiIdMasked && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">UPI ID</span>\n <span className=\"text-text-primary font-mono\">{details.upiIdMasked}</span>\n </div>\n )}\n {details.swift && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">SWIFT</span>\n <span className=\"text-text-primary font-mono\">{details.swift}</span>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\nfunction RequestCard({\n request,\n onApprove,\n onReject,\n onMarkPaid,\n}: {\n request: PayoutRequest;\n onApprove: (r: PayoutRequest) => void;\n onReject: (r: PayoutRequest) => void;\n onMarkPaid: (r: PayoutRequest) => void;\n}) {\n const canApprove = request.status === 'REQUESTED' || request.status === 'REJECTED';\n const canReject = request.status === 'REQUESTED' || request.status === 'APPROVED';\n const canMarkPaid = request.status === 'APPROVED' || request.status === 'PROCESSING';\n\n return (\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-5 space-y-4 shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:shadow-[var(--shadow-elevation-2)]\">\n {/* Header row */}\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xl font-semibold text-text-primary\">\n {formatCurrency(request.requestedAmount, request.currency)}\n </span>\n <StatusBadge status={request.status} />\n </div>\n <div className=\"text-xs text-text-muted space-x-2\">\n <span>Requested {formatDateTime(request.createdAt)}</span>\n {request.requesterUserId && (\n <>\n <span>·</span>\n <span>by {request.requesterUserId}</span>\n </>\n )}\n <span>·</span>\n <span className=\"font-mono text-xs\">{request.id.slice(0, 8)}…</span>\n </div>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {canApprove && (\n <button\n type=\"button\"\n onClick={() => onApprove(request)}\n className=\"inline-flex items-center gap-1.5 rounded-button bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text\"\n >\n <CheckCircle2 className=\"size-3.5\" />\n Approve\n </button>\n )}\n {canMarkPaid && (\n <button\n type=\"button\"\n onClick={() => onMarkPaid(request)}\n className=\"inline-flex items-center gap-1.5 rounded-button bg-status-success-bg-subtle border border-status-success-border px-3 py-1.5 text-xs font-medium text-status-success-text transition-colors hover:bg-status-success-bg-subtle/70\"\n >\n <Banknote className=\"size-3.5\" />\n Mark as Paid\n </button>\n )}\n {canReject && (\n <button\n type=\"button\"\n onClick={() => onReject(request)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-error-text\"\n >\n <XCircle className=\"size-3.5\" />\n Reject\n </button>\n )}\n </div>\n </div>\n\n {/* Payout account */}\n {request.payoutAccount && <AccountDetails account={request.payoutAccount} />}\n\n {/* User notes */}\n {request.userNotes && (\n <div className=\"rounded-md bg-bg-sunken/40 border border-border-subtle px-3 py-2 text-sm\">\n <span className=\"text-xs font-medium text-text-muted uppercase tracking-wide\">\n User note\n </span>\n <p className=\"mt-1 text-text-primary\">{request.userNotes}</p>\n </div>\n )}\n\n {/* Internal notes / failure / ref */}\n {(request.internalNotes ?? request.failureReason ?? request.externalReference) && (\n <div className=\"grid gap-1 text-xs text-text-muted\">\n {request.externalReference && (\n <div className=\"flex gap-2\">\n <ReceiptText className=\"size-3.5 mt-px shrink-0\" />\n <span>\n Ref:{' '}\n <span className=\"font-mono text-text-primary\">{request.externalReference}</span>\n </span>\n </div>\n )}\n {request.internalNotes && (\n <div className=\"flex gap-2\">\n <span className=\"shrink-0\">Internal note:</span>\n <span className=\"text-text-primary\">{request.internalNotes}</span>\n </div>\n )}\n {request.failureReason && (\n <div className=\"flex gap-2 text-status-error-text\">\n <AlertTriangle className=\"size-3.5 mt-px shrink-0\" />\n <span>{request.failureReason}</span>\n </div>\n )}\n </div>\n )}\n\n {/* Timestamps */}\n {(request.paidAt ?? request.processedAt) && (\n <div className=\"flex flex-wrap gap-4 text-xs text-text-muted\">\n {request.processedAt && (\n <span className=\"flex items-center gap-1\">\n <Clock className=\"size-3\" />\n Processed {formatDateTime(request.processedAt)}\n </span>\n )}\n {request.paidAt && (\n <span className=\"flex items-center gap-1\">\n <CheckCircle2 className=\"size-3\" />\n Paid {formatDateTime(request.paidAt)}\n </span>\n )}\n </div>\n )}\n </div>\n );\n}\n\nfunction SummaryBar({ summary }: { summary: PayoutAdminSummary }) {\n return (\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-4 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"text-xs text-text-muted uppercase tracking-wide\">Pending payout</div>\n <div className=\"mt-1 text-lg font-semibold text-text-primary\">\n {formatCurrency(summary.totalRequestedAmount, 'USD')}\n </div>\n <div className=\"text-xs text-text-muted mt-0.5\">{summary.pendingCount} requests</div>\n </div>\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-4 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"text-xs text-text-muted uppercase tracking-wide\">Processing</div>\n <div className=\"mt-1 text-lg font-semibold text-text-primary\">\n {formatCurrency(summary.totalProcessingAmount, 'USD')}\n </div>\n </div>\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-4 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"text-xs text-text-muted uppercase tracking-wide\">Total paid</div>\n <div className=\"mt-1 text-lg font-semibold text-text-primary\">\n {formatCurrency(summary.totalPaidAmount, 'USD')}\n </div>\n {summary.failedCount > 0 && (\n <div className=\"text-xs text-status-error-text mt-0.5\">{summary.failedCount} failed</div>\n )}\n </div>\n </div>\n );\n}\n\nfunction ActionDialog({\n dialog,\n onClose,\n onConfirm,\n}: {\n dialog: ActionDialogState;\n onClose: () => void;\n onConfirm: (data: {\n notes?: string;\n reason?: string;\n externalReference?: string;\n }) => Promise<void>;\n}) {\n const [notes, setNotes] = useState('');\n const [reason, setReason] = useState('');\n const [txnRef, setTxnRef] = useState('');\n const [submitting, setSubmitting] = useState(false);\n\n const handleSubmit = async () => {\n if (dialog.type === 'reject' && !reason.trim()) return;\n setSubmitting(true);\n await onConfirm({\n notes: notes.trim() || undefined,\n reason: reason.trim() || undefined,\n externalReference: txnRef.trim() || undefined,\n });\n setSubmitting(false);\n };\n\n const titles = {\n approve: 'Approve withdrawal request',\n reject: 'Reject withdrawal request',\n mark_paid: 'Mark as paid',\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\">\n <div className=\"w-full max-w-md rounded-card border border-border-subtle bg-bg-elevated p-6 shadow-[var(--shadow-elevation-4)] space-y-4\">\n <div>\n <h3 className=\"text-base font-semibold text-text-primary\">{titles[dialog.type]}</h3>\n <p className=\"text-sm text-text-muted mt-1\">\n {formatCurrency(dialog.amount, dialog.currency)} · {dialog.accountLabel}\n </p>\n </div>\n\n {dialog.type === 'mark_paid' && (\n <div className=\"space-y-3\">\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Transaction reference / UTR number\n </label>\n <input\n type=\"text\"\n value={txnRef}\n onChange={(e) => setTxnRef(e.target.value)}\n placeholder=\"e.g. UTR123456789012, TXN_abc123\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-ring\"\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Enter the bank reference number so the user can verify the transfer.\n </p>\n </div>\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Internal notes (optional)\n </label>\n <textarea\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n rows={2}\n placeholder=\"Any internal notes about this payment…\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-ring resize-none\"\n />\n </div>\n </div>\n )}\n\n {dialog.type === 'approve' && (\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Internal notes (optional)\n </label>\n <textarea\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n rows={3}\n placeholder=\"e.g. Verified bank details, approved for transfer…\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-ring resize-none\"\n />\n </div>\n )}\n\n {dialog.type === 'reject' && (\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Reason for rejection <span className=\"text-status-error-text\">*</span>\n </label>\n <textarea\n value={reason}\n onChange={(e) => setReason(e.target.value)}\n rows={3}\n placeholder=\"e.g. Invalid bank details provided, please re-submit with correct IFSC…\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-ring resize-none\"\n />\n </div>\n )}\n\n <div className=\"flex justify-end gap-2 pt-2\">\n <button\n type=\"button\"\n onClick={onClose}\n disabled={submitting}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void handleSubmit()}\n disabled={submitting || (dialog.type === 'reject' && !reason.trim())}\n className={`rounded-md px-4 py-2 text-sm font-medium disabled:opacity-50 ${\n dialog.type === 'reject'\n ? 'bg-action-danger-bg text-action-danger-text'\n : 'bg-action-primary-bg text-action-primary-text'\n }`}\n >\n {submitting\n ? 'Saving…'\n : dialog.type === 'approve'\n ? 'Approve'\n : dialog.type === 'reject'\n ? 'Reject'\n : 'Confirm Payment'}\n </button>\n </div>\n </div>\n </div>\n );\n}\n\nexport const PayoutAdminPage: FC = () => {\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const { apiGatewayUrl, authToken, orgId } = useBilling();\n\n const [summary, setSummary] = useState<PayoutAdminSummary | null>(null);\n const [requests, setRequests] = useState<PayoutRequest[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const [activeTab, setActiveTab] = useState<Tab>('pending');\n const [dialog, setDialog] = useState<ActionDialogState | null>(null);\n\n const requestContext = useMemo(\n () => ({ apiGatewayUrl, authToken, orgId }),\n [apiGatewayUrl, authToken, orgId]\n );\n\n const loadData = async () => {\n setLoading(true);\n setError(null);\n try {\n const [loadedSummary, loadedRequests] = await Promise.all([\n fetchAdminPayoutSummary(requestContext),\n fetchAdminPayoutRequests(requestContext),\n ]);\n setSummary(loadedSummary);\n setRequests(loadedRequests);\n } catch (loadError) {\n setError(loadError instanceof Error ? loadError : new Error('Failed to load payout queue'));\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [requestContext]);\n\n if (!permissions.isBillingAdmin) {\n return <AccessDenied message=\"You don't have permission to manage payouts.\" />;\n }\n\n if (error && isServerError(error)) {\n return (\n <ServerError\n title=\"Payout queue unavailable\"\n message={error.message}\n onRetry={() => void loadData()}\n showRetry\n />\n );\n }\n\n const pending = requests.filter((r) => r.status === 'REQUESTED');\n const approved = requests.filter((r) => r.status === 'APPROVED' || r.status === 'PROCESSING');\n const history = requests.filter((r) =>\n ['PAID', 'REJECTED', 'FAILED', 'CANCELED'].includes(r.status)\n );\n\n const tabItems: { id: Tab; label: string; count: number }[] = [\n { id: 'pending', label: 'Pending', count: pending.length },\n { id: 'approved', label: 'Approved', count: approved.length },\n { id: 'history', label: 'History', count: history.length },\n ];\n\n const visibleRequests =\n activeTab === 'pending' ? pending : activeTab === 'approved' ? approved : history;\n\n const openDialog = (type: ActionDialogState['type'], request: PayoutRequest) => {\n setDialog({\n type,\n requestId: request.id,\n amount: request.requestedAmount,\n currency: request.currency,\n accountLabel:\n request.payoutAccount?.displayName ?? request.payoutAccount?.label ?? 'Unknown account',\n });\n };\n\n const handleConfirm = async (data: {\n notes?: string;\n reason?: string;\n externalReference?: string;\n }) => {\n if (!dialog) return;\n\n try {\n if (dialog.type === 'approve') {\n await approvePayoutRequest(dialog.requestId, data.notes ?? null, requestContext);\n emit('billing.payout.approved', {\n route: '/billing/payouts',\n entityId: dialog.requestId,\n source: 'payout-admin',\n });\n } else if (dialog.type === 'reject') {\n await rejectPayoutRequest(dialog.requestId, data.reason ?? '', requestContext);\n emit('billing.payout.rejected', {\n route: '/billing/payouts',\n entityId: dialog.requestId,\n source: 'payout-admin',\n reason: data.reason,\n });\n } else if (dialog.type === 'mark_paid') {\n await markPayoutPaid(\n dialog.requestId,\n data.externalReference ?? null,\n data.notes ?? null,\n requestContext\n );\n emit('billing.payout.processed', {\n route: '/billing/payouts',\n entityId: dialog.requestId,\n source: 'payout-admin',\n });\n }\n setDialog(null);\n await loadData();\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Action failed'));\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Payout queue\"\n description=\"Review withdrawal requests, verify bank details, and mark payments as paid once transferred.\"\n actions={\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n }\n />\n\n {summary && !loading && <SummaryBar summary={summary} />}\n\n {loading && (\n <div className=\"grid gap-3 sm:grid-cols-3\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-20 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n )}\n\n {error && !isServerError(error) && (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-sm text-status-error-text\">\n {error.message}\n </div>\n )}\n\n {/* Tabs */}\n <div className=\"border-b border-border-subtle\">\n <div className=\"flex gap-0\">\n {tabItems.map((tab) => (\n <button\n key={tab.id}\n type=\"button\"\n onClick={() => setActiveTab(tab.id)}\n className={`flex items-center gap-2 border-b-2 px-4 py-2.5 text-sm font-medium transition-colors ${\n activeTab === tab.id\n ? 'border-action-primary-bg text-text-primary'\n : 'border-transparent text-text-muted hover:text-text-primary'\n }`}\n >\n {tab.label}\n {tab.count > 0 && (\n <span\n className={`rounded-full px-1.5 py-0.5 text-xs ${\n activeTab === tab.id\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {tab.count}\n </span>\n )}\n </button>\n ))}\n </div>\n </div>\n\n {/* How to pay — shown only on the Approved tab */}\n {activeTab === 'approved' && approved.length > 0 && (\n <div className=\"rounded-card border border-status-info-border bg-status-info-bg-subtle px-4 py-3 text-sm text-status-info-text\">\n <strong>How to pay:</strong> Click the account row to expand bank/UPI details → transfer\n the exact amount via your bank → click <strong>Mark as Paid</strong> and enter the UTR /\n transaction reference number.\n </div>\n )}\n\n {/* Request cards */}\n {loading ? (\n <div className=\"space-y-4\">\n {[1, 2].map((i) => (\n <div\n key={i}\n className=\"h-40 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n ) : visibleRequests.length === 0 ? (\n <EmptyState\n icon={<CheckCircle2 className=\"size-8\" />}\n title={\n activeTab === 'pending'\n ? 'No pending requests'\n : activeTab === 'approved'\n ? 'No approved requests awaiting payment'\n : 'No history yet'\n }\n description={\n activeTab === 'pending'\n ? 'New withdrawal requests will appear here.'\n : activeTab === 'approved'\n ? 'Approve pending requests first, then send payment and mark as paid.'\n : 'Completed and rejected requests will appear here.'\n }\n />\n ) : (\n <div className=\"space-y-4\">\n {visibleRequests.map((request) => (\n <RequestCard\n key={request.id}\n request={request}\n onApprove={(r) => openDialog('approve', r)}\n onReject={(r) => openDialog('reject', r)}\n onMarkPaid={(r) => openDialog('mark_paid', r)}\n />\n ))}\n </div>\n )}\n\n {dialog && (\n <ActionDialog dialog={dialog} onClose={() => setDialog(null)} onConfirm={handleConfirm} />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PayoutAdminPage.js","names":[],"sources":["../../../../../src/billing/modules/earnings/pages/PayoutAdminPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type FC } from 'react';\nimport {\n AlertTriangle,\n Banknote,\n CheckCircle2,\n ChevronDown,\n ChevronUp,\n Clock,\n CreditCard,\n RefreshCw,\n ReceiptText,\n XCircle,\n} from 'lucide-react';\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { AccessDenied, EmptyState, PageHeader, ServerError } from '../../../shared/components';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\nimport {\n approvePayoutRequest,\n fetchAdminPayoutRequests,\n fetchAdminPayoutSummary,\n markPayoutPaid,\n rejectPayoutRequest,\n type PayoutAdminSummary,\n type PayoutRequest,\n} from '../api';\n\ntype Tab = 'pending' | 'approved' | 'history';\n\ninterface ActionDialogState {\n type: 'approve' | 'reject' | 'mark_paid';\n requestId: string;\n amount: number;\n currency: string;\n accountLabel: string;\n}\n\nconst STATUS_LABEL: Record<string, string> = {\n REQUESTED: 'Requested',\n APPROVED: 'Approved',\n REJECTED: 'Rejected',\n PROCESSING: 'Processing',\n PAID: 'Paid',\n FAILED: 'Failed',\n CANCELED: 'Canceled',\n};\n\nconst STATUS_COLORS: Record<string, string> = {\n REQUESTED: 'bg-status-warning-bg-subtle text-status-warning-text',\n APPROVED: 'bg-status-info-bg-subtle text-status-info-text',\n REJECTED: 'bg-status-error-bg-subtle text-status-error-text',\n PROCESSING: 'bg-status-info-bg-subtle text-status-info-text',\n PAID: 'bg-status-success-bg-subtle text-status-success-text',\n FAILED: 'bg-status-error-bg-subtle text-status-error-text',\n CANCELED: 'bg-bg-sunken text-text-muted',\n};\n\nfunction StatusBadge({ status }: { status: string }) {\n return (\n <span\n className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${STATUS_COLORS[status] ?? 'bg-bg-sunken text-text-muted'}`}\n >\n {STATUS_LABEL[status] ?? status}\n </span>\n );\n}\n\nfunction AccountDetails({ account }: { account: NonNullable<PayoutRequest['payoutAccount']> }) {\n const [expanded, setExpanded] = useState(false);\n const details = account.accountDetails as Record<string, string | null> | null;\n\n return (\n <div className=\"rounded-md border border-border-subtle bg-bg-sunken/40 p-3 text-sm\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2 text-text-primary font-medium\">\n <CreditCard className=\"size-4 text-text-muted\" />\n {account.displayName ?? account.label}\n <span className=\"text-xs text-text-muted font-normal\">\n ({account.type === 'UPI' ? 'UPI' : 'Bank'} · {account.country ?? 'Unknown'} ·{' '}\n {account.currency})\n </span>\n </div>\n {details && (\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className=\"text-text-muted hover:text-text-primary\"\n >\n {expanded ? <ChevronUp className=\"size-4\" /> : <ChevronDown className=\"size-4\" />}\n </button>\n )}\n </div>\n {expanded && details && (\n <div className=\"mt-2 grid gap-1 text-xs\">\n {details.accountHolderName && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Account holder</span>\n <span className=\"text-text-primary\">{details.accountHolderName}</span>\n </div>\n )}\n {details.bankName && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Bank</span>\n <span className=\"text-text-primary\">{details.bankName}</span>\n </div>\n )}\n {account.accountLast4 && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Account (last 4)</span>\n <span className=\"text-text-primary font-mono\">••••{account.accountLast4}</span>\n </div>\n )}\n {details.ifsc && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">IFSC</span>\n <span className=\"text-text-primary font-mono\">{details.ifsc}</span>\n </div>\n )}\n {details.routingNumberLast4 && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Routing (last 4)</span>\n <span className=\"text-text-primary font-mono\">••••{details.routingNumberLast4}</span>\n </div>\n )}\n {details.upiIdMasked && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">UPI ID</span>\n <span className=\"text-text-primary font-mono\">{details.upiIdMasked}</span>\n </div>\n )}\n {details.swift && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">SWIFT</span>\n <span className=\"text-text-primary font-mono\">{details.swift}</span>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\nfunction RequestCard({\n request,\n onApprove,\n onReject,\n onMarkPaid,\n}: {\n request: PayoutRequest;\n onApprove: (r: PayoutRequest) => void;\n onReject: (r: PayoutRequest) => void;\n onMarkPaid: (r: PayoutRequest) => void;\n}) {\n const canApprove = request.status === 'REQUESTED' || request.status === 'REJECTED';\n const canReject = request.status === 'REQUESTED' || request.status === 'APPROVED';\n const canMarkPaid = request.status === 'APPROVED' || request.status === 'PROCESSING';\n\n return (\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-5 space-y-4 shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:shadow-[var(--shadow-elevation-2)]\">\n {/* Header row */}\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xl font-semibold text-text-primary\">\n {formatCurrency(request.requestedAmount, request.currency)}\n </span>\n <StatusBadge status={request.status} />\n </div>\n <div className=\"text-xs text-text-muted space-x-2\">\n <span>Requested {formatDateTime(request.createdAt)}</span>\n {request.requesterUserId && (\n <>\n <span>·</span>\n <span>by {request.requesterUserId}</span>\n </>\n )}\n <span>·</span>\n <span className=\"font-mono text-xs\">{request.id.slice(0, 8)}…</span>\n </div>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {canApprove && (\n <button\n type=\"button\"\n onClick={() => onApprove(request)}\n className=\"inline-flex items-center gap-1.5 rounded-button bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text\"\n >\n <CheckCircle2 className=\"size-3.5\" />\n Approve\n </button>\n )}\n {canMarkPaid && (\n <button\n type=\"button\"\n onClick={() => onMarkPaid(request)}\n className=\"inline-flex items-center gap-1.5 rounded-button bg-status-success-bg-subtle border border-status-success-border px-3 py-1.5 text-xs font-medium text-status-success-text transition-colors hover:bg-status-success-bg-subtle/70\"\n >\n <Banknote className=\"size-3.5\" />\n Mark as Paid\n </button>\n )}\n {canReject && (\n <button\n type=\"button\"\n onClick={() => onReject(request)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-error-text\"\n >\n <XCircle className=\"size-3.5\" />\n Reject\n </button>\n )}\n </div>\n </div>\n\n {/* Payout account */}\n {request.payoutAccount && <AccountDetails account={request.payoutAccount} />}\n\n {/* User notes */}\n {request.userNotes && (\n <div className=\"rounded-md bg-bg-sunken/40 border border-border-subtle px-3 py-2 text-sm\">\n <span className=\"text-xs font-medium text-text-muted uppercase tracking-wide\">\n User note\n </span>\n <p className=\"mt-1 text-text-primary\">{request.userNotes}</p>\n </div>\n )}\n\n {/* Internal notes / failure / ref */}\n {(request.internalNotes ?? request.failureReason ?? request.externalReference) && (\n <div className=\"grid gap-1 text-xs text-text-muted\">\n {request.externalReference && (\n <div className=\"flex gap-2\">\n <ReceiptText className=\"size-3.5 mt-px shrink-0\" />\n <span>\n Ref:{' '}\n <span className=\"font-mono text-text-primary\">{request.externalReference}</span>\n </span>\n </div>\n )}\n {request.internalNotes && (\n <div className=\"flex gap-2\">\n <span className=\"shrink-0\">Internal note:</span>\n <span className=\"text-text-primary\">{request.internalNotes}</span>\n </div>\n )}\n {request.failureReason && (\n <div className=\"flex gap-2 text-status-error-text\">\n <AlertTriangle className=\"size-3.5 mt-px shrink-0\" />\n <span>{request.failureReason}</span>\n </div>\n )}\n </div>\n )}\n\n {/* Timestamps */}\n {(request.paidAt ?? request.processedAt) && (\n <div className=\"flex flex-wrap gap-4 text-xs text-text-muted\">\n {request.processedAt && (\n <span className=\"flex items-center gap-1\">\n <Clock className=\"size-3\" />\n Processed {formatDateTime(request.processedAt)}\n </span>\n )}\n {request.paidAt && (\n <span className=\"flex items-center gap-1\">\n <CheckCircle2 className=\"size-3\" />\n Paid {formatDateTime(request.paidAt)}\n </span>\n )}\n </div>\n )}\n </div>\n );\n}\n\nfunction SummaryBar({ summary }: { summary: PayoutAdminSummary }) {\n return (\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-4 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"text-xs text-text-muted uppercase tracking-wide\">Pending payout</div>\n <div className=\"mt-1 text-lg font-semibold text-text-primary\">\n {formatCurrency(summary.totalRequestedAmount, 'USD')}\n </div>\n <div className=\"text-xs text-text-muted mt-0.5\">{summary.pendingCount} requests</div>\n </div>\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-4 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"text-xs text-text-muted uppercase tracking-wide\">Processing</div>\n <div className=\"mt-1 text-lg font-semibold text-text-primary\">\n {formatCurrency(summary.totalProcessingAmount, 'USD')}\n </div>\n </div>\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-4 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"text-xs text-text-muted uppercase tracking-wide\">Total paid</div>\n <div className=\"mt-1 text-lg font-semibold text-text-primary\">\n {formatCurrency(summary.totalPaidAmount, 'USD')}\n </div>\n {summary.failedCount > 0 && (\n <div className=\"text-xs text-status-error-text mt-0.5\">{summary.failedCount} failed</div>\n )}\n </div>\n </div>\n );\n}\n\nfunction ActionDialog({\n dialog,\n onClose,\n onConfirm,\n}: {\n dialog: ActionDialogState;\n onClose: () => void;\n onConfirm: (data: {\n notes?: string;\n reason?: string;\n externalReference?: string;\n }) => Promise<void>;\n}) {\n const [notes, setNotes] = useState('');\n const [reason, setReason] = useState('');\n const [txnRef, setTxnRef] = useState('');\n const [submitting, setSubmitting] = useState(false);\n\n const handleSubmit = async () => {\n if (dialog.type === 'reject' && !reason.trim()) return;\n setSubmitting(true);\n await onConfirm({\n notes: notes.trim() || undefined,\n reason: reason.trim() || undefined,\n externalReference: txnRef.trim() || undefined,\n });\n setSubmitting(false);\n };\n\n const titles = {\n approve: 'Approve withdrawal request',\n reject: 'Reject withdrawal request',\n mark_paid: 'Mark as paid',\n };\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\">\n <div className=\"w-full max-w-md rounded-card border border-border-subtle bg-bg-elevated p-6 shadow-[var(--shadow-elevation-4)] space-y-4\">\n <div>\n <h3 className=\"text-base font-semibold text-text-primary\">{titles[dialog.type]}</h3>\n <p className=\"text-sm text-text-muted mt-1\">\n {formatCurrency(dialog.amount, dialog.currency)} · {dialog.accountLabel}\n </p>\n </div>\n\n {dialog.type === 'mark_paid' && (\n <div className=\"space-y-3\">\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Transaction reference / UTR number\n </label>\n <input\n type=\"text\"\n value={txnRef}\n onChange={(e) => setTxnRef(e.target.value)}\n placeholder=\"e.g. UTR123456789012, TXN_abc123\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Enter the bank reference number so the user can verify the transfer.\n </p>\n </div>\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Internal notes (optional)\n </label>\n <textarea\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n rows={2}\n placeholder=\"Any internal notes about this payment…\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n </div>\n )}\n\n {dialog.type === 'approve' && (\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Internal notes (optional)\n </label>\n <textarea\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n rows={3}\n placeholder=\"e.g. Verified bank details, approved for transfer…\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n )}\n\n {dialog.type === 'reject' && (\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Reason for rejection <span className=\"text-status-error-text\">*</span>\n </label>\n <textarea\n value={reason}\n onChange={(e) => setReason(e.target.value)}\n rows={3}\n placeholder=\"e.g. Invalid bank details provided, please re-submit with correct IFSC…\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n )}\n\n <div className=\"flex justify-end gap-2 pt-2\">\n <button\n type=\"button\"\n onClick={onClose}\n disabled={submitting}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void handleSubmit()}\n disabled={submitting || (dialog.type === 'reject' && !reason.trim())}\n className={`rounded-md px-4 py-2 text-sm font-medium disabled:opacity-50 ${\n dialog.type === 'reject'\n ? 'bg-action-danger-bg text-action-danger-text'\n : 'bg-action-primary-bg text-action-primary-text'\n }`}\n >\n {submitting\n ? 'Saving…'\n : dialog.type === 'approve'\n ? 'Approve'\n : dialog.type === 'reject'\n ? 'Reject'\n : 'Confirm Payment'}\n </button>\n </div>\n </div>\n </div>\n );\n}\n\nexport const PayoutAdminPage: FC = () => {\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const { apiGatewayUrl, authToken, orgId } = useBilling();\n\n const [summary, setSummary] = useState<PayoutAdminSummary | null>(null);\n const [requests, setRequests] = useState<PayoutRequest[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const [activeTab, setActiveTab] = useState<Tab>('pending');\n const [dialog, setDialog] = useState<ActionDialogState | null>(null);\n\n const requestContext = useMemo(\n () => ({ apiGatewayUrl, authToken, orgId }),\n [apiGatewayUrl, authToken, orgId]\n );\n\n const loadData = async () => {\n setLoading(true);\n setError(null);\n try {\n const [loadedSummary, loadedRequests] = await Promise.all([\n fetchAdminPayoutSummary(requestContext),\n fetchAdminPayoutRequests(requestContext),\n ]);\n setSummary(loadedSummary);\n setRequests(loadedRequests);\n } catch (loadError) {\n setError(loadError instanceof Error ? loadError : new Error('Failed to load payout queue'));\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [requestContext]);\n\n if (!permissions.isBillingAdmin) {\n return <AccessDenied message=\"You don't have permission to manage payouts.\" />;\n }\n\n if (error && isServerError(error)) {\n return (\n <ServerError\n title=\"Payout queue unavailable\"\n message={error.message}\n onRetry={() => void loadData()}\n showRetry\n />\n );\n }\n\n const pending = requests.filter((r) => r.status === 'REQUESTED');\n const approved = requests.filter((r) => r.status === 'APPROVED' || r.status === 'PROCESSING');\n const history = requests.filter((r) =>\n ['PAID', 'REJECTED', 'FAILED', 'CANCELED'].includes(r.status)\n );\n\n const tabItems: { id: Tab; label: string; count: number }[] = [\n { id: 'pending', label: 'Pending', count: pending.length },\n { id: 'approved', label: 'Approved', count: approved.length },\n { id: 'history', label: 'History', count: history.length },\n ];\n\n const visibleRequests =\n activeTab === 'pending' ? pending : activeTab === 'approved' ? approved : history;\n\n const openDialog = (type: ActionDialogState['type'], request: PayoutRequest) => {\n setDialog({\n type,\n requestId: request.id,\n amount: request.requestedAmount,\n currency: request.currency,\n accountLabel:\n request.payoutAccount?.displayName ?? request.payoutAccount?.label ?? 'Unknown account',\n });\n };\n\n const handleConfirm = async (data: {\n notes?: string;\n reason?: string;\n externalReference?: string;\n }) => {\n if (!dialog) return;\n\n try {\n if (dialog.type === 'approve') {\n await approvePayoutRequest(dialog.requestId, data.notes ?? null, requestContext);\n emit('billing.payout.approved', {\n route: '/billing/payouts',\n entityId: dialog.requestId,\n source: 'payout-admin',\n });\n } else if (dialog.type === 'reject') {\n await rejectPayoutRequest(dialog.requestId, data.reason ?? '', requestContext);\n emit('billing.payout.rejected', {\n route: '/billing/payouts',\n entityId: dialog.requestId,\n source: 'payout-admin',\n reason: data.reason,\n });\n } else if (dialog.type === 'mark_paid') {\n await markPayoutPaid(\n dialog.requestId,\n data.externalReference ?? null,\n data.notes ?? null,\n requestContext\n );\n emit('billing.payout.processed', {\n route: '/billing/payouts',\n entityId: dialog.requestId,\n source: 'payout-admin',\n });\n }\n setDialog(null);\n await loadData();\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Action failed'));\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Payout queue\"\n description=\"Review withdrawal requests, verify bank details, and mark payments as paid once transferred.\"\n actions={\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n }\n />\n\n {summary && !loading && <SummaryBar summary={summary} />}\n\n {loading && (\n <div className=\"grid gap-3 sm:grid-cols-3\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-20 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n )}\n\n {error && !isServerError(error) && (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-sm text-status-error-text\">\n {error.message}\n </div>\n )}\n\n {/* Tabs */}\n <div className=\"border-b border-border-subtle\">\n <div className=\"flex gap-0\">\n {tabItems.map((tab) => (\n <button\n key={tab.id}\n type=\"button\"\n onClick={() => setActiveTab(tab.id)}\n className={`flex items-center gap-2 border-b-2 px-4 py-2.5 text-sm font-medium transition-colors ${\n activeTab === tab.id\n ? 'border-action-primary-bg text-text-primary'\n : 'border-transparent text-text-muted hover:text-text-primary'\n }`}\n >\n {tab.label}\n {tab.count > 0 && (\n <span\n className={`rounded-full px-1.5 py-0.5 text-xs ${\n activeTab === tab.id\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {tab.count}\n </span>\n )}\n </button>\n ))}\n </div>\n </div>\n\n {/* How to pay — shown only on the Approved tab */}\n {activeTab === 'approved' && approved.length > 0 && (\n <div className=\"rounded-card border border-status-info-border bg-status-info-bg-subtle px-4 py-3 text-sm text-status-info-text\">\n <strong>How to pay:</strong> Click the account row to expand bank/UPI details → transfer\n the exact amount via your bank → click <strong>Mark as Paid</strong> and enter the UTR /\n transaction reference number.\n </div>\n )}\n\n {/* Request cards */}\n {loading ? (\n <div className=\"space-y-4\">\n {[1, 2].map((i) => (\n <div\n key={i}\n className=\"h-40 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n ) : visibleRequests.length === 0 ? (\n <EmptyState\n icon={<CheckCircle2 className=\"size-8\" />}\n title={\n activeTab === 'pending'\n ? 'No pending requests'\n : activeTab === 'approved'\n ? 'No approved requests awaiting payment'\n : 'No history yet'\n }\n description={\n activeTab === 'pending'\n ? 'New withdrawal requests will appear here.'\n : activeTab === 'approved'\n ? 'Approve pending requests first, then send payment and mark as paid.'\n : 'Completed and rejected requests will appear here.'\n }\n />\n ) : (\n <div className=\"space-y-4\">\n {visibleRequests.map((request) => (\n <RequestCard\n key={request.id}\n request={request}\n onApprove={(r) => openDialog('approve', r)}\n onReject={(r) => openDialog('reject', r)}\n onMarkPaid={(r) => openDialog('mark_paid', r)}\n />\n ))}\n </div>\n )}\n\n {dialog && (\n <ActionDialog dialog={dialog} onClose={() => setDialog(null)} onConfirm={handleConfirm} />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|