@burdenoff/microfe-billing 2026.614.1 → 2026.623.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/BillingAdminRoutes.js +7 -6
- package/dist/billing/BillingAdminRoutes.js.map +1 -1
- package/dist/billing/BillingRoot.js +1 -1
- package/dist/billing/BillingRoot.js.map +1 -1
- package/dist/billing/modules/addons/components/FloatingCartDrawer.js +22 -22
- package/dist/billing/modules/addons/components/FloatingCartDrawer.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonCreatePage.js +31 -31
- package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonDetailPage.js +39 -39
- package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +29 -29
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js +91 -91
- package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js +40 -40
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +115 -115
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +75 -72
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +126 -126
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/UserRefundsPage.js +45 -45
- package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js +25 -25
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js +31 -35
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/checkout/components/CreditsAmountInput.js +9 -9
- package/dist/billing/modules/checkout/components/CreditsAmountInput.js.map +1 -1
- package/dist/billing/modules/checkout/components/OrderSummary.js +31 -31
- package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
- package/dist/billing/modules/checkout/components/PromoCodeInput.js +8 -8
- package/dist/billing/modules/checkout/components/PromoCodeInput.js.map +1 -1
- package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js +5 -5
- package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
- package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js +9 -9
- package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js.map +1 -1
- package/dist/billing/modules/checkout/components/StripePaymentForm.js +21 -21
- package/dist/billing/modules/checkout/components/StripePaymentForm.js.map +1 -1
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js +11 -11
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js +31 -31
- package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js +23 -23
- package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +91 -91
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js +62 -62
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponsListPage.js +72 -60
- package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js +66 -66
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/EditCouponPage.js +46 -46
- package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js +19 -19
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +37 -37
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +90 -90
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditsPage.js +41 -41
- package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js +31 -31
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js +35 -35
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js +24 -24
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js +99 -99
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js +23 -23
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +22 -22
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +58 -58
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js +126 -126
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +83 -79
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +16 -16
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +40 -40
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js +35 -35
- package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanDetailPage.js +57 -57
- package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +35 -35
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +57 -57
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansListPage.js +52 -52
- package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +40 -39
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js +22 -19
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +34 -34
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js +47 -47
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +39 -39
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +52 -52
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js +23 -23
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +259 -246
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +88 -85
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +51 -51
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +206 -196
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/dist/billing/shared/components/ServerError.js +4 -4
- package/dist/billing/shared/components/ServerError.js.map +1 -1
- package/dist/billing/shared/components/StatCard.js +11 -33
- package/dist/billing/shared/components/StatCard.js.map +1 -1
- package/dist/billing/shared/ui/Card.js +15 -0
- package/dist/billing/shared/ui/Card.js.map +1 -0
- package/dist/billing/shared/ui/MetricCard.js +44 -0
- package/dist/billing/shared/ui/MetricCard.js.map +1 -0
- package/dist/billing/shared/ui/ProgressBar.js +30 -0
- package/dist/billing/shared/ui/ProgressBar.js.map +1 -0
- package/dist/billing/shared/ui/SectionHeader.js +2 -0
- package/dist/billing/shared/ui/StatusPill.js +28 -0
- package/dist/billing/shared/ui/StatusPill.js.map +1 -0
- package/dist/billing/shared/ui/index.js +5 -0
- package/dist/billing/shared/utils/tokens.js +13 -30
- package/dist/billing/shared/utils/tokens.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UnifiedPaymentMethodSelector.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/UnifiedPaymentMethodSelector.tsx"],"sourcesContent":["/**\n * Unified Payment Method Selector\n * Combines payment providers and saved payment methods into one clear interface\n */\n\nimport { type FC, useState } from 'react';\nimport { Coins, CreditCard, Wallet, ChevronDown } from 'lucide-react';\nimport type { SavedPaymentMethod } from '../types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface UnifiedPaymentMethodSelectorProps {\n // Saved cards\n savedPaymentMethods: SavedPaymentMethod[];\n selectedPaymentMethodId: string | null;\n onSelectSavedCard: (id: string | null) => void;\n\n // Provider selection (for new cards)\n selectedProvider: 'CREDITS' | 'STRIPE' | 'RAZORPAY' | 'AUTO';\n onSelectProvider: (provider: 'CREDITS' | 'STRIPE' | 'RAZORPAY') => void;\n\n // Provider availability\n stripeEnabled: boolean;\n razorpayEnabled: boolean;\n canPayWithCredits: boolean;\n availableCredits: number;\n totalAmount: number;\n billingCountry?: string;\n\n // Save card option\n saveCard: boolean;\n onSaveCardChange: (save: boolean) => void;\n\n // Auto-renewal (when enabled, card must be saved - checkbox hidden)\n autoRenewEnabled?: boolean;\n\n isLoading?: boolean;\n}\n\nconst getCardIcon = (type: string) => {\n if (type === 'WALLET') return <Wallet className=\"size-4\" />;\n return <CreditCard className=\"size-4\" />;\n};\n\nconst getCardDisplay = (pm: SavedPaymentMethod): string => {\n const type =\n pm.type === 'CREDIT_CARD' ? 'Credit Card' : pm.type === 'DEBIT_CARD' ? 'Debit Card' : pm.type;\n const last4 = pm.last4 ? ` •••• ${pm.last4}` : '';\n const brand = pm.brand ? ` (${pm.brand})` : '';\n return `${type}${last4}${brand}`;\n};\n\nexport const UnifiedPaymentMethodSelector: FC<UnifiedPaymentMethodSelectorProps> = ({\n savedPaymentMethods,\n selectedPaymentMethodId,\n onSelectSavedCard,\n selectedProvider,\n onSelectProvider,\n stripeEnabled,\n razorpayEnabled,\n canPayWithCredits,\n availableCredits,\n totalAmount,\n billingCountry,\n saveCard,\n onSaveCardChange,\n autoRenewEnabled = false,\n isLoading = false,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const [showAllSavedCards, setShowAllSavedCards] = useState(false);\n const hasEnoughCredits = availableCredits >= totalAmount;\n const isIndia = billingCountry === 'IN';\n\n // Get default card or first card for collapsible view\n const defaultSavedCard = savedPaymentMethods.find((pm) => pm.isDefault) || savedPaymentMethods[0];\n const otherSavedCards = savedPaymentMethods.filter((pm) => pm.id !== defaultSavedCard?.id);\n\n const handleSelect = (type: 'saved' | 'new', value: string) => {\n if (type === 'saved') {\n // When selecting saved card, set it and auto-determine provider\n const selectedCard = savedPaymentMethods.find((pm) => pm.id === value);\n if (selectedCard) {\n onSelectSavedCard(value);\n // Auto-set provider based on card type\n if (selectedCard.razorpayPaymentMethodId) {\n onSelectProvider('RAZORPAY');\n } else if (selectedCard.stripePaymentMethodId) {\n onSelectProvider('STRIPE');\n }\n }\n } else {\n // When selecting new card payment, clear saved card selection\n onSelectSavedCard(null);\n onSelectProvider(value as 'CREDITS' | 'STRIPE' | 'RAZORPAY');\n }\n };\n\n const isSelected = (type: 'saved' | 'new', value: string): boolean => {\n if (type === 'saved') {\n return selectedPaymentMethodId === value;\n } else {\n return !selectedPaymentMethodId && selectedProvider === value;\n }\n };\n\n return (\n <div className=\"space-y-3\">\n <h3 className=\"text-sm font-medium text-foreground\">\n {tr('billing.checkout.paymentMethod', 'Payment Method')}\n </h3>\n\n {isLoading ? (\n <div className=\"space-y-2\">\n <div className=\"h-16 rounded-lg border border-border bg-muted animate-pulse\" />\n <div className=\"h-16 rounded-lg border border-border bg-muted animate-pulse\" />\n </div>\n ) : (\n <div className=\"space-y-2\">\n {/* Credits Option */}\n {canPayWithCredits && (\n <button\n type=\"button\"\n onClick={() => handleSelect('new', 'CREDITS')}\n disabled={!hasEnoughCredits}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('new', 'CREDITS')\n ? 'border-primary bg-primary/5 ring-1 ring-primary'\n : 'border-border hover:border-primary/50 hover:bg-muted/50'\n }\n ${!hasEnoughCredits && 'opacity-50 cursor-not-allowed'}\n `}\n >\n <Coins\n className={`size-5 shrink-0 ${isSelected('new', 'CREDITS') ? 'text-primary' : 'text-muted-foreground'}`}\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-foreground\">Pay with Credits</div>\n <div className=\"text-xs text-muted-foreground\">\n {hasEnoughCredits\n ? `Available: ${availableCredits.toLocaleString()} credits`\n : `Insufficient credits (need ${totalAmount.toLocaleString()})`}\n </div>\n </div>\n {hasEnoughCredits && (\n <span className=\"text-xs px-2 py-0.5 rounded bg-success/10 text-success\">\n Instant\n </span>\n )}\n </button>\n )}\n\n {/* Saved Cards Section */}\n {savedPaymentMethods.length > 0 && (\n <>\n <div className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide pt-2\">\n Saved Cards\n </div>\n\n {/* Default/First Card - Always Shown */}\n {defaultSavedCard && (\n <button\n type=\"button\"\n onClick={() => handleSelect('saved', defaultSavedCard.id)}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('saved', defaultSavedCard.id)\n ? 'border-primary bg-primary/5 ring-1 ring-primary'\n : 'border-border hover:border-primary/50 hover:bg-muted/50'\n }\n `}\n >\n {getCardIcon(defaultSavedCard.type)}\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-foreground\">\n {getCardDisplay(defaultSavedCard)}\n </div>\n {defaultSavedCard.expiryMonth && defaultSavedCard.expiryYear && (\n <div className=\"text-xs text-muted-foreground\">\n Expires {defaultSavedCard.expiryMonth.toString().padStart(2, '0')}/\n {defaultSavedCard.expiryYear}\n </div>\n )}\n </div>\n {defaultSavedCard.isDefault && (\n <span className=\"text-xs px-2 py-0.5 rounded bg-primary/10 text-primary\">\n Default\n </span>\n )}\n </button>\n )}\n\n {/* Other Cards - Collapsible */}\n {otherSavedCards.length > 0 && (\n <>\n {showAllSavedCards ? (\n <>\n {otherSavedCards.map((pm) => (\n <button\n key={pm.id}\n type=\"button\"\n onClick={() => handleSelect('saved', pm.id)}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('saved', pm.id)\n ? 'border-primary bg-primary/5 ring-1 ring-primary'\n : 'border-border hover:border-primary/50 hover:bg-muted/50'\n }\n `}\n >\n {getCardIcon(pm.type)}\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-foreground\">\n {getCardDisplay(pm)}\n </div>\n {pm.expiryMonth && pm.expiryYear && (\n <div className=\"text-xs text-muted-foreground\">\n Expires {pm.expiryMonth.toString().padStart(2, '0')}/{pm.expiryYear}\n </div>\n )}\n </div>\n </button>\n ))}\n <button\n type=\"button\"\n onClick={() => setShowAllSavedCards(false)}\n className=\"text-xs text-primary hover:underline flex items-center gap-1 pl-1\"\n >\n <ChevronDown className=\"size-3 rotate-180\" />\n Hide other cards\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n onClick={() => setShowAllSavedCards(true)}\n className=\"text-xs text-primary hover:underline flex items-center gap-1 pl-1\"\n >\n <ChevronDown className=\"size-3\" />\n Show {otherSavedCards.length} more card\n {otherSavedCards.length > 1 ? 's' : ''}\n </button>\n )}\n </>\n )}\n </>\n )}\n\n {/* New Card Options */}\n <div className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide pt-2\">\n {savedPaymentMethods.length > 0\n ? 'Or use new card'\n : canPayWithCredits\n ? 'Or pay with card'\n : 'Pay with Card'}\n </div>\n\n {/* Razorpay (India) */}\n {razorpayEnabled && isIndia && (\n <button\n type=\"button\"\n onClick={() => handleSelect('new', 'RAZORPAY')}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('new', 'RAZORPAY')\n ? 'border-primary bg-primary/5 ring-1 ring-primary'\n : 'border-border hover:border-primary/50 hover:bg-muted/50'\n }\n `}\n >\n <CreditCard\n className={`size-5 shrink-0 ${isSelected('new', 'RAZORPAY') ? 'text-primary' : 'text-muted-foreground'}`}\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-foreground\">UPI / Cards / Netbanking</div>\n <div className=\"text-xs text-muted-foreground\">Razorpay</div>\n </div>\n <span className=\"text-xs px-2 py-0.5 rounded bg-accent-blue-subtle text-accent-blue\">\n Recommended\n </span>\n </button>\n )}\n\n {/* Stripe (International) */}\n {stripeEnabled && (\n <button\n type=\"button\"\n onClick={() => handleSelect('new', 'STRIPE')}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('new', 'STRIPE')\n ? 'border-primary bg-primary/5 ring-1 ring-primary'\n : 'border-border hover:border-primary/50 hover:bg-muted/50'\n }\n `}\n >\n <CreditCard\n className={`size-5 shrink-0 ${isSelected('new', 'STRIPE') ? 'text-primary' : 'text-muted-foreground'}`}\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-foreground\">Credit / Debit Cards</div>\n <div className=\"text-xs text-muted-foreground\">Stripe - Multi-currency</div>\n </div>\n {!isIndia && (\n <span className=\"text-xs px-2 py-0.5 rounded bg-accent-blue-subtle text-accent-blue\">\n Recommended\n </span>\n )}\n </button>\n )}\n\n {/* Save Card Checkbox - Only show when using new card (not saved card, not credits) */}\n {!selectedPaymentMethodId &&\n selectedProvider !== 'CREDITS' &&\n selectedProvider !== 'AUTO' &&\n !autoRenewEnabled && (\n <div className=\"mt-4 pt-4 border-t border-border\">\n <label className=\"flex items-start gap-3 cursor-pointer group\">\n <input\n type=\"checkbox\"\n checked={saveCard}\n onChange={(e) => onSaveCardChange(e.target.checked)}\n className=\"mt-0.5 size-4 rounded border-border text-primary focus:ring-primary focus:ring-offset-0\"\n />\n <div className=\"flex-1\">\n <div className=\"text-sm font-medium text-foreground group-hover:text-primary transition-colors\">\n Save card for future use\n </div>\n <div className=\"text-xs text-muted-foreground mt-0.5\">\n Securely save this payment method for faster checkout next time\n </div>\n </div>\n </label>\n </div>\n )}\n\n {/* Auto-renewal notice - Show when auto-renewal is enabled */}\n {!selectedPaymentMethodId &&\n selectedProvider !== 'CREDITS' &&\n selectedProvider !== 'AUTO' &&\n autoRenewEnabled && (\n <div className=\"mt-4 pt-4 border-t border-border\">\n <div className=\"flex items-start gap-2 text-xs text-muted-foreground bg-muted/50 p-3 rounded-lg\">\n <svg\n className=\"size-4 mt-0.5 flex-shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <div>Your payment method will be securely saved for auto-renewal</div>\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"UnifiedPaymentMethodSelector.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/UnifiedPaymentMethodSelector.tsx"],"sourcesContent":["/**\n * Unified Payment Method Selector\n * Combines payment providers and saved payment methods into one clear interface\n */\n\nimport { type FC, useState } from 'react';\nimport { Coins, CreditCard, Wallet, ChevronDown } from 'lucide-react';\nimport type { SavedPaymentMethod } from '../types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface UnifiedPaymentMethodSelectorProps {\n // Saved cards\n savedPaymentMethods: SavedPaymentMethod[];\n selectedPaymentMethodId: string | null;\n onSelectSavedCard: (id: string | null) => void;\n\n // Provider selection (for new cards)\n selectedProvider: 'CREDITS' | 'STRIPE' | 'RAZORPAY' | 'AUTO';\n onSelectProvider: (provider: 'CREDITS' | 'STRIPE' | 'RAZORPAY') => void;\n\n // Provider availability\n stripeEnabled: boolean;\n razorpayEnabled: boolean;\n canPayWithCredits: boolean;\n availableCredits: number;\n totalAmount: number;\n billingCountry?: string;\n\n // Save card option\n saveCard: boolean;\n onSaveCardChange: (save: boolean) => void;\n\n // Auto-renewal (when enabled, card must be saved - checkbox hidden)\n autoRenewEnabled?: boolean;\n\n isLoading?: boolean;\n}\n\nconst getCardIcon = (type: string) => {\n if (type === 'WALLET') return <Wallet className=\"size-4\" />;\n return <CreditCard className=\"size-4\" />;\n};\n\nconst getCardDisplay = (pm: SavedPaymentMethod): string => {\n const type =\n pm.type === 'CREDIT_CARD' ? 'Credit Card' : pm.type === 'DEBIT_CARD' ? 'Debit Card' : pm.type;\n const last4 = pm.last4 ? ` •••• ${pm.last4}` : '';\n const brand = pm.brand ? ` (${pm.brand})` : '';\n return `${type}${last4}${brand}`;\n};\n\nexport const UnifiedPaymentMethodSelector: FC<UnifiedPaymentMethodSelectorProps> = ({\n savedPaymentMethods,\n selectedPaymentMethodId,\n onSelectSavedCard,\n selectedProvider,\n onSelectProvider,\n stripeEnabled,\n razorpayEnabled,\n canPayWithCredits,\n availableCredits,\n totalAmount,\n billingCountry,\n saveCard,\n onSaveCardChange,\n autoRenewEnabled = false,\n isLoading = false,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const [showAllSavedCards, setShowAllSavedCards] = useState(false);\n const hasEnoughCredits = availableCredits >= totalAmount;\n const isIndia = billingCountry === 'IN';\n\n // Get default card or first card for collapsible view\n const defaultSavedCard = savedPaymentMethods.find((pm) => pm.isDefault) || savedPaymentMethods[0];\n const otherSavedCards = savedPaymentMethods.filter((pm) => pm.id !== defaultSavedCard?.id);\n\n const handleSelect = (type: 'saved' | 'new', value: string) => {\n if (type === 'saved') {\n // When selecting saved card, set it and auto-determine provider\n const selectedCard = savedPaymentMethods.find((pm) => pm.id === value);\n if (selectedCard) {\n onSelectSavedCard(value);\n // Auto-set provider based on card type\n if (selectedCard.razorpayPaymentMethodId) {\n onSelectProvider('RAZORPAY');\n } else if (selectedCard.stripePaymentMethodId) {\n onSelectProvider('STRIPE');\n }\n }\n } else {\n // When selecting new card payment, clear saved card selection\n onSelectSavedCard(null);\n onSelectProvider(value as 'CREDITS' | 'STRIPE' | 'RAZORPAY');\n }\n };\n\n const isSelected = (type: 'saved' | 'new', value: string): boolean => {\n if (type === 'saved') {\n return selectedPaymentMethodId === value;\n } else {\n return !selectedPaymentMethodId && selectedProvider === value;\n }\n };\n\n return (\n <div className=\"space-y-3\">\n <h3 className=\"text-sm font-medium text-text-primary\">\n {tr('billing.checkout.paymentMethod', 'Payment Method')}\n </h3>\n\n {isLoading ? (\n <div className=\"space-y-2\">\n <div className=\"h-16 rounded-lg border border-border-subtle bg-bg-sunken animate-pulse\" />\n <div className=\"h-16 rounded-lg border border-border-subtle bg-bg-sunken animate-pulse\" />\n </div>\n ) : (\n <div className=\"space-y-2\">\n {/* Credits Option */}\n {canPayWithCredits && (\n <button\n type=\"button\"\n onClick={() => handleSelect('new', 'CREDITS')}\n disabled={!hasEnoughCredits}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('new', 'CREDITS')\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n ${!hasEnoughCredits && 'opacity-50 cursor-not-allowed'}\n `}\n >\n <Coins\n className={`size-5 shrink-0 ${isSelected('new', 'CREDITS') ? 'text-text-link' : 'text-text-secondary'}`}\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">Pay with Credits</div>\n <div className=\"text-xs text-text-secondary\">\n {hasEnoughCredits\n ? `Available: ${availableCredits.toLocaleString()} credits`\n : `Insufficient credits (need ${totalAmount.toLocaleString()})`}\n </div>\n </div>\n {hasEnoughCredits && (\n <span className=\"text-xs px-2 py-0.5 rounded bg-success/10 text-success\">\n Instant\n </span>\n )}\n </button>\n )}\n\n {/* Saved Cards Section */}\n {savedPaymentMethods.length > 0 && (\n <>\n <div className=\"text-xs font-medium text-text-secondary uppercase tracking-wide pt-2\">\n Saved Cards\n </div>\n\n {/* Default/First Card - Always Shown */}\n {defaultSavedCard && (\n <button\n type=\"button\"\n onClick={() => handleSelect('saved', defaultSavedCard.id)}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('saved', defaultSavedCard.id)\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n `}\n >\n {getCardIcon(defaultSavedCard.type)}\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">\n {getCardDisplay(defaultSavedCard)}\n </div>\n {defaultSavedCard.expiryMonth && defaultSavedCard.expiryYear && (\n <div className=\"text-xs text-text-secondary\">\n Expires {defaultSavedCard.expiryMonth.toString().padStart(2, '0')}/\n {defaultSavedCard.expiryYear}\n </div>\n )}\n </div>\n {defaultSavedCard.isDefault && (\n <span className=\"text-xs px-2 py-0.5 rounded bg-[var(--color-accent-soft)] text-text-link\">\n Default\n </span>\n )}\n </button>\n )}\n\n {/* Other Cards - Collapsible */}\n {otherSavedCards.length > 0 && (\n <>\n {showAllSavedCards ? (\n <>\n {otherSavedCards.map((pm) => (\n <button\n key={pm.id}\n type=\"button\"\n onClick={() => handleSelect('saved', pm.id)}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('saved', pm.id)\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n `}\n >\n {getCardIcon(pm.type)}\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">\n {getCardDisplay(pm)}\n </div>\n {pm.expiryMonth && pm.expiryYear && (\n <div className=\"text-xs text-text-secondary\">\n Expires {pm.expiryMonth.toString().padStart(2, '0')}/{pm.expiryYear}\n </div>\n )}\n </div>\n </button>\n ))}\n <button\n type=\"button\"\n onClick={() => setShowAllSavedCards(false)}\n className=\"text-xs text-text-link hover:underline flex items-center gap-1 pl-1\"\n >\n <ChevronDown className=\"size-3 rotate-180\" />\n Hide other cards\n </button>\n </>\n ) : (\n <button\n type=\"button\"\n onClick={() => setShowAllSavedCards(true)}\n className=\"text-xs text-text-link hover:underline flex items-center gap-1 pl-1\"\n >\n <ChevronDown className=\"size-3\" />\n Show {otherSavedCards.length} more card\n {otherSavedCards.length > 1 ? 's' : ''}\n </button>\n )}\n </>\n )}\n </>\n )}\n\n {/* New Card Options */}\n <div className=\"text-xs font-medium text-text-secondary uppercase tracking-wide pt-2\">\n {savedPaymentMethods.length > 0\n ? 'Or use new card'\n : canPayWithCredits\n ? 'Or pay with card'\n : 'Pay with Card'}\n </div>\n\n {/* Razorpay (India) */}\n {razorpayEnabled && isIndia && (\n <button\n type=\"button\"\n onClick={() => handleSelect('new', 'RAZORPAY')}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('new', 'RAZORPAY')\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n `}\n >\n <CreditCard\n className={`size-5 shrink-0 ${isSelected('new', 'RAZORPAY') ? 'text-text-link' : 'text-text-secondary'}`}\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">\n UPI / Cards / Netbanking\n </div>\n <div className=\"text-xs text-text-secondary\">Razorpay</div>\n </div>\n <span className=\"text-xs px-2 py-0.5 rounded bg-accent-blue-subtle text-accent-blue\">\n Recommended\n </span>\n </button>\n )}\n\n {/* Stripe (International) */}\n {stripeEnabled && (\n <button\n type=\"button\"\n onClick={() => handleSelect('new', 'STRIPE')}\n className={`\n w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left\n ${\n isSelected('new', 'STRIPE')\n ? 'border-border-strong bg-[var(--color-accent-soft)] ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken'\n }\n `}\n >\n <CreditCard\n className={`size-5 shrink-0 ${isSelected('new', 'STRIPE') ? 'text-text-link' : 'text-text-secondary'}`}\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium text-text-primary\">Credit / Debit Cards</div>\n <div className=\"text-xs text-text-secondary\">Stripe - Multi-currency</div>\n </div>\n {!isIndia && (\n <span className=\"text-xs px-2 py-0.5 rounded bg-accent-blue-subtle text-accent-blue\">\n Recommended\n </span>\n )}\n </button>\n )}\n\n {/* Save Card Checkbox - Only show when using new card (not saved card, not credits) */}\n {!selectedPaymentMethodId &&\n selectedProvider !== 'CREDITS' &&\n selectedProvider !== 'AUTO' &&\n !autoRenewEnabled && (\n <div className=\"mt-4 pt-4 border-t border-border-subtle\">\n <label className=\"flex items-start gap-3 cursor-pointer group\">\n <input\n type=\"checkbox\"\n checked={saveCard}\n onChange={(e) => onSaveCardChange(e.target.checked)}\n className=\"mt-0.5 size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)] focus:ring-offset-0\"\n />\n <div className=\"flex-1\">\n <div className=\"text-sm font-medium text-text-primary group-hover:text-text-link transition-colors\">\n Save card for future use\n </div>\n <div className=\"text-xs text-text-secondary mt-0.5\">\n Securely save this payment method for faster checkout next time\n </div>\n </div>\n </label>\n </div>\n )}\n\n {/* Auto-renewal notice - Show when auto-renewal is enabled */}\n {!selectedPaymentMethodId &&\n selectedProvider !== 'CREDITS' &&\n selectedProvider !== 'AUTO' &&\n autoRenewEnabled && (\n <div className=\"mt-4 pt-4 border-t border-border-subtle\">\n <div className=\"flex items-start gap-2 text-xs text-text-secondary bg-bg-sunken p-3 rounded-lg\">\n <svg\n className=\"size-4 mt-0.5 flex-shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <div>Your payment method will be securely saved for auto-renewal</div>\n </div>\n </div>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -30,20 +30,20 @@ var p = ({ billingAccountId: d, subscriptionId: f, quotas: p, workspaces: m, cur
|
|
|
30
30
|
g
|
|
31
31
|
]);
|
|
32
32
|
return b ? /* @__PURE__ */ l("div", {
|
|
33
|
-
className: "fixed inset-0 bg-
|
|
33
|
+
className: "fixed inset-0 bg-bg-overlay backdrop-blur-sm z-50 flex items-center justify-center p-4",
|
|
34
34
|
children: /* @__PURE__ */ u("div", {
|
|
35
|
-
className: "bg-
|
|
35
|
+
className: "bg-bg-surface border border-border-subtle rounded-card shadow-[var(--shadow-elevation-4)] max-w-sm w-full p-6 text-center space-y-4",
|
|
36
36
|
children: [
|
|
37
37
|
/* @__PURE__ */ l("div", {
|
|
38
38
|
className: `inline-flex p-3 rounded-full ${e.success.bg} ${e.success.text}`,
|
|
39
39
|
children: /* @__PURE__ */ l(a, { className: "size-8" })
|
|
40
40
|
}),
|
|
41
41
|
/* @__PURE__ */ l("h2", {
|
|
42
|
-
className: "text-lg font-semibold text-
|
|
42
|
+
className: "text-lg font-semibold text-text-primary",
|
|
43
43
|
children: "Quota assigned!"
|
|
44
44
|
}),
|
|
45
45
|
/* @__PURE__ */ u("p", {
|
|
46
|
-
className: "text-sm text-
|
|
46
|
+
className: "text-sm text-text-secondary",
|
|
47
47
|
children: [
|
|
48
48
|
p.length,
|
|
49
49
|
" quota",
|
|
@@ -51,7 +51,7 @@ var p = ({ billingAccountId: d, subscriptionId: f, quotas: p, workspaces: m, cur
|
|
|
51
51
|
" been assigned to",
|
|
52
52
|
" ",
|
|
53
53
|
/* @__PURE__ */ l("span", {
|
|
54
|
-
className: "font-medium text-
|
|
54
|
+
className: "font-medium text-text-primary",
|
|
55
55
|
children: m.find((e) => e.id === v)?.name ?? v
|
|
56
56
|
}),
|
|
57
57
|
"."
|
|
@@ -60,22 +60,22 @@ var p = ({ billingAccountId: d, subscriptionId: f, quotas: p, workspaces: m, cur
|
|
|
60
60
|
]
|
|
61
61
|
})
|
|
62
62
|
}) : /* @__PURE__ */ l("div", {
|
|
63
|
-
className: "fixed inset-0 bg-
|
|
63
|
+
className: "fixed inset-0 bg-bg-overlay backdrop-blur-sm z-50 flex items-center justify-center p-4",
|
|
64
64
|
children: /* @__PURE__ */ u("div", {
|
|
65
|
-
className: "bg-
|
|
65
|
+
className: "bg-bg-surface border border-border-subtle rounded-card shadow-[var(--shadow-elevation-4)] max-w-md w-full",
|
|
66
66
|
children: [
|
|
67
67
|
/* @__PURE__ */ u("div", {
|
|
68
|
-
className: "flex items-center justify-between px-6 py-4 border-b border-border",
|
|
68
|
+
className: "flex items-center justify-between px-6 py-4 border-b border-border-subtle",
|
|
69
69
|
children: [/* @__PURE__ */ u("div", {
|
|
70
70
|
className: "flex items-center gap-2",
|
|
71
|
-
children: [/* @__PURE__ */ l(o, { className: "size-5 text-
|
|
72
|
-
className: "text-base font-semibold text-
|
|
71
|
+
children: [/* @__PURE__ */ l(o, { className: "size-5 text-text-link" }), /* @__PURE__ */ l("h2", {
|
|
72
|
+
className: "text-base font-semibold text-text-primary",
|
|
73
73
|
children: "Assign to a Workspace"
|
|
74
74
|
})]
|
|
75
75
|
}), /* @__PURE__ */ l("button", {
|
|
76
76
|
type: "button",
|
|
77
77
|
onClick: _,
|
|
78
|
-
className: "p-1 rounded hover:bg-
|
|
78
|
+
className: "p-1 rounded hover:bg-bg-sunken transition-colors text-text-secondary",
|
|
79
79
|
"aria-label": "Skip",
|
|
80
80
|
children: /* @__PURE__ */ l(c, { className: "size-4" })
|
|
81
81
|
})]
|
|
@@ -84,7 +84,7 @@ var p = ({ billingAccountId: d, subscriptionId: f, quotas: p, workspaces: m, cur
|
|
|
84
84
|
className: "px-6 py-5 space-y-5",
|
|
85
85
|
children: [
|
|
86
86
|
/* @__PURE__ */ u("p", {
|
|
87
|
-
className: "text-sm text-
|
|
87
|
+
className: "text-sm text-text-secondary",
|
|
88
88
|
children: [
|
|
89
89
|
"Select which workspace should receive the quota",
|
|
90
90
|
p.length > 1 ? "s" : "",
|
|
@@ -92,17 +92,17 @@ var p = ({ billingAccountId: d, subscriptionId: f, quotas: p, workspaces: m, cur
|
|
|
92
92
|
]
|
|
93
93
|
}),
|
|
94
94
|
/* @__PURE__ */ u("div", {
|
|
95
|
-
className: "bg-
|
|
95
|
+
className: "bg-bg-sunken rounded-lg p-3 space-y-2",
|
|
96
96
|
children: [/* @__PURE__ */ l("p", {
|
|
97
|
-
className: "text-xs font-medium text-
|
|
97
|
+
className: "text-xs font-medium text-text-secondary uppercase tracking-wide",
|
|
98
98
|
children: "Included quotas"
|
|
99
99
|
}), p.map((e) => /* @__PURE__ */ u("div", {
|
|
100
100
|
className: "flex items-center justify-between text-sm",
|
|
101
101
|
children: [/* @__PURE__ */ l("span", {
|
|
102
|
-
className: "text-
|
|
102
|
+
className: "text-text-primary",
|
|
103
103
|
children: e.quotaName
|
|
104
104
|
}), /* @__PURE__ */ l("span", {
|
|
105
|
-
className: "text-
|
|
105
|
+
className: "text-text-secondary",
|
|
106
106
|
children: e.noLimit ? "Unlimited" : e.limit == null ? "—" : `${e.limit} max`
|
|
107
107
|
})]
|
|
108
108
|
}, e.quotaName))]
|
|
@@ -111,25 +111,25 @@ var p = ({ billingAccountId: d, subscriptionId: f, quotas: p, workspaces: m, cur
|
|
|
111
111
|
className: "space-y-1.5",
|
|
112
112
|
children: [/* @__PURE__ */ l("label", {
|
|
113
113
|
htmlFor: "ws-selector",
|
|
114
|
-
className: "block text-sm font-medium text-
|
|
114
|
+
className: "block text-sm font-medium text-text-primary",
|
|
115
115
|
children: "Workspace"
|
|
116
116
|
}), /* @__PURE__ */ l("select", {
|
|
117
117
|
id: "ws-selector",
|
|
118
118
|
value: v,
|
|
119
119
|
onChange: (e) => y(e.target.value),
|
|
120
|
-
className: "w-full px-3 py-2 text-sm border border-border rounded-
|
|
120
|
+
className: "w-full px-3 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
121
121
|
children: m.map((e) => /* @__PURE__ */ l("option", {
|
|
122
122
|
value: e.id,
|
|
123
123
|
children: e.name
|
|
124
124
|
}, e.id))
|
|
125
125
|
})]
|
|
126
126
|
}) : /* @__PURE__ */ u("div", {
|
|
127
|
-
className: "bg-
|
|
127
|
+
className: "bg-bg-sunken rounded-lg p-3 text-sm text-text-secondary",
|
|
128
128
|
children: [
|
|
129
129
|
"Will be assigned to",
|
|
130
130
|
" ",
|
|
131
131
|
/* @__PURE__ */ l("span", {
|
|
132
|
-
className: "font-medium text-
|
|
132
|
+
className: "font-medium text-text-primary",
|
|
133
133
|
children: m[0]?.name ?? h
|
|
134
134
|
})
|
|
135
135
|
]
|
|
@@ -141,17 +141,17 @@ var p = ({ billingAccountId: d, subscriptionId: f, quotas: p, workspaces: m, cur
|
|
|
141
141
|
]
|
|
142
142
|
}),
|
|
143
143
|
/* @__PURE__ */ u("div", {
|
|
144
|
-
className: "flex items-center justify-end gap-3 px-6 py-4 border-t border-border",
|
|
144
|
+
className: "flex items-center justify-end gap-3 px-6 py-4 border-t border-border-subtle",
|
|
145
145
|
children: [/* @__PURE__ */ l("button", {
|
|
146
146
|
type: "button",
|
|
147
147
|
onClick: _,
|
|
148
|
-
className: "px-4 py-2 text-sm border border-border rounded-
|
|
148
|
+
className: "px-4 py-2 text-sm border border-border-subtle rounded-button hover:bg-bg-sunken transition-colors text-text-secondary",
|
|
149
149
|
children: "Skip for now"
|
|
150
150
|
}), /* @__PURE__ */ u("button", {
|
|
151
151
|
type: "button",
|
|
152
152
|
onClick: () => void E(),
|
|
153
153
|
disabled: !v || T,
|
|
154
|
-
className: "flex items-center gap-2 px-4 py-2 text-sm font-medium bg-primary text-primary-
|
|
154
|
+
className: "flex items-center gap-2 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50",
|
|
155
155
|
children: [T && /* @__PURE__ */ l(s, { className: "size-3.5 animate-spin" }), "Assign quota"]
|
|
156
156
|
})]
|
|
157
157
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceQuotaAssignmentModal.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.tsx"],"sourcesContent":["/**\n * WorkspaceQuotaAssignmentModal\n *\n * Shown after a successful plan / add-on purchase to let the user assign\n * workspace-level quotas (agents, sandboxes, sessions, runtime, etc.) to a\n * specific workspace.\n *\n * Spec rules:\n * - Free plan or single workspace → auto-assign; no modal shown.\n * - Multiple workspaces → require selection before activating the quota.\n *\n * This component handles both cases: the parent calls autoAssignOrShow() and\n * will either receive an immediate assignment result (auto-assign path) or\n * render this modal for user selection.\n */\n\nimport { type FC, useState, useCallback } from 'react';\nimport { CheckCircle, Layers, AlertTriangle, Loader2, X } from 'lucide-react';\nimport { useActivateSubscriptionForWorkspace } from '../../usage/hooks/useUsage';\nimport { statusTokens } from '../../../shared/utils/tokens';\n\nexport interface WorkspaceOption {\n id: string;\n name: string;\n}\n\nexport interface WorkspaceQuotaToAssign {\n quotaName: string;\n productId: string;\n limit: number | null;\n noLimit: boolean;\n resetPeriod: string;\n quotaMode: 'dedicated' | 'pooled';\n}\n\ninterface WorkspaceQuotaAssignmentModalProps {\n billingAccountId: string;\n subscriptionId: string;\n quotas: WorkspaceQuotaToAssign[];\n workspaces: WorkspaceOption[];\n /**\n * Current workspace — used as the pre-selected value.\n * Pass the workspace from the app shell token context.\n */\n currentWorkspaceId: string;\n onAssigned: (workspaceId: string) => void;\n onSkip: () => void;\n}\n\n/**\n * Derives which quotas in a plan are workspace-scoped (not global).\n * Global quotas: org_count, workspace_count, project_count.\n * All others are workspace-scoped.\n */\nconst GLOBAL_QUOTA_NAMES = new Set(['org_count', 'workspace_count', 'project_count']);\n\nexport function isWorkspaceScopedQuota(quotaName: string): boolean {\n return !GLOBAL_QUOTA_NAMES.has(quotaName);\n}\n\nexport const WorkspaceQuotaAssignmentModal: FC<WorkspaceQuotaAssignmentModalProps> = ({\n billingAccountId,\n subscriptionId,\n quotas,\n workspaces,\n currentWorkspaceId,\n onAssigned,\n onSkip,\n}) => {\n const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(currentWorkspaceId);\n const [isDone, setIsDone] = useState(false);\n const [assignError, setAssignError] = useState<string | null>(null);\n\n const { activate, isLoading } = useActivateSubscriptionForWorkspace();\n\n const handleAssign = useCallback(async () => {\n if (!selectedWorkspaceId) return;\n setAssignError(null);\n\n try {\n await activate(subscriptionId, billingAccountId, selectedWorkspaceId);\n setIsDone(true);\n onAssigned(selectedWorkspaceId);\n } catch (err) {\n setAssignError(err instanceof Error ? err.message : 'Failed to assign quota to workspace.');\n }\n }, [selectedWorkspaceId, billingAccountId, subscriptionId, activate, onAssigned]);\n\n if (isDone) {\n return (\n <div className=\"fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center p-4\">\n <div className=\"bg-card border border-border rounded-xl shadow-lg max-w-sm w-full p-6 text-center space-y-4\">\n <div\n className={`inline-flex p-3 rounded-full ${statusTokens.success.bg} ${statusTokens.success.text}`}\n >\n <CheckCircle className=\"size-8\" />\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">Quota assigned!</h2>\n <p className=\"text-sm text-muted-foreground\">\n {quotas.length} quota{quotas.length !== 1 ? 's have' : ' has'} been assigned to{' '}\n <span className=\"font-medium text-foreground\">\n {workspaces.find((w) => w.id === selectedWorkspaceId)?.name ?? selectedWorkspaceId}\n </span>\n .\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center p-4\">\n <div className=\"bg-card border border-border rounded-xl shadow-lg max-w-md w-full\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border\">\n <div className=\"flex items-center gap-2\">\n <Layers className=\"size-5 text-primary\" />\n <h2 className=\"text-base font-semibold text-foreground\">Assign to a Workspace</h2>\n </div>\n <button\n type=\"button\"\n onClick={onSkip}\n className=\"p-1 rounded hover:bg-muted/50 transition-colors text-muted-foreground\"\n aria-label=\"Skip\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n {/* Body */}\n <div className=\"px-6 py-5 space-y-5\">\n <p className=\"text-sm text-muted-foreground\">\n Select which workspace should receive the quota{quotas.length > 1 ? 's' : ''} included\n in your purchase.\n </p>\n\n {/* Quotas list */}\n <div className=\"bg-muted/30 rounded-lg p-3 space-y-2\">\n <p className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n Included quotas\n </p>\n {quotas.map((q) => (\n <div key={q.quotaName} className=\"flex items-center justify-between text-sm\">\n <span className=\"text-foreground\">{q.quotaName}</span>\n <span className=\"text-muted-foreground\">\n {q.noLimit ? 'Unlimited' : q.limit != null ? `${q.limit} max` : '—'}\n </span>\n </div>\n ))}\n </div>\n\n {/* Workspace selector */}\n {workspaces.length > 1 ? (\n <div className=\"space-y-1.5\">\n <label htmlFor=\"ws-selector\" className=\"block text-sm font-medium text-foreground\">\n Workspace\n </label>\n <select\n id=\"ws-selector\"\n value={selectedWorkspaceId}\n onChange={(e) => setSelectedWorkspaceId(e.target.value)}\n className=\"w-full px-3 py-2 text-sm border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {workspaces.map((ws) => (\n <option key={ws.id} value={ws.id}>\n {ws.name}\n </option>\n ))}\n </select>\n </div>\n ) : (\n <div className=\"bg-muted/30 rounded-lg p-3 text-sm text-muted-foreground\">\n Will be assigned to{' '}\n <span className=\"font-medium text-foreground\">\n {workspaces[0]?.name ?? currentWorkspaceId}\n </span>\n </div>\n )}\n\n {/* Error */}\n {assignError && (\n <div\n className={`flex items-start gap-2 text-sm ${statusTokens.error.text} ${statusTokens.error.bg} rounded-lg p-3`}\n >\n <AlertTriangle className=\"size-4 shrink-0 mt-0.5\" />\n <span>{assignError}</span>\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 px-6 py-4 border-t border-border\">\n <button\n type=\"button\"\n onClick={onSkip}\n className=\"px-4 py-2 text-sm border border-border rounded-md hover:bg-muted/50 transition-colors text-muted-foreground\"\n >\n Skip for now\n </button>\n <button\n type=\"button\"\n onClick={() => void handleAssign()}\n disabled={!selectedWorkspaceId || isLoading}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors disabled:opacity-50\"\n >\n {isLoading && <Loader2 className=\"size-3.5 animate-spin\" />}\n Assign quota\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WorkspaceQuotaAssignmentModal.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.tsx"],"sourcesContent":["/**\n * WorkspaceQuotaAssignmentModal\n *\n * Shown after a successful plan / add-on purchase to let the user assign\n * workspace-level quotas (agents, sandboxes, sessions, runtime, etc.) to a\n * specific workspace.\n *\n * Spec rules:\n * - Free plan or single workspace → auto-assign; no modal shown.\n * - Multiple workspaces → require selection before activating the quota.\n *\n * This component handles both cases: the parent calls autoAssignOrShow() and\n * will either receive an immediate assignment result (auto-assign path) or\n * render this modal for user selection.\n */\n\nimport { type FC, useState, useCallback } from 'react';\nimport { CheckCircle, Layers, AlertTriangle, Loader2, X } from 'lucide-react';\nimport { useActivateSubscriptionForWorkspace } from '../../usage/hooks/useUsage';\nimport { statusTokens } from '../../../shared/utils/tokens';\n\nexport interface WorkspaceOption {\n id: string;\n name: string;\n}\n\nexport interface WorkspaceQuotaToAssign {\n quotaName: string;\n productId: string;\n limit: number | null;\n noLimit: boolean;\n resetPeriod: string;\n quotaMode: 'dedicated' | 'pooled';\n}\n\ninterface WorkspaceQuotaAssignmentModalProps {\n billingAccountId: string;\n subscriptionId: string;\n quotas: WorkspaceQuotaToAssign[];\n workspaces: WorkspaceOption[];\n /**\n * Current workspace — used as the pre-selected value.\n * Pass the workspace from the app shell token context.\n */\n currentWorkspaceId: string;\n onAssigned: (workspaceId: string) => void;\n onSkip: () => void;\n}\n\n/**\n * Derives which quotas in a plan are workspace-scoped (not global).\n * Global quotas: org_count, workspace_count, project_count.\n * All others are workspace-scoped.\n */\nconst GLOBAL_QUOTA_NAMES = new Set(['org_count', 'workspace_count', 'project_count']);\n\nexport function isWorkspaceScopedQuota(quotaName: string): boolean {\n return !GLOBAL_QUOTA_NAMES.has(quotaName);\n}\n\nexport const WorkspaceQuotaAssignmentModal: FC<WorkspaceQuotaAssignmentModalProps> = ({\n billingAccountId,\n subscriptionId,\n quotas,\n workspaces,\n currentWorkspaceId,\n onAssigned,\n onSkip,\n}) => {\n const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(currentWorkspaceId);\n const [isDone, setIsDone] = useState(false);\n const [assignError, setAssignError] = useState<string | null>(null);\n\n const { activate, isLoading } = useActivateSubscriptionForWorkspace();\n\n const handleAssign = useCallback(async () => {\n if (!selectedWorkspaceId) return;\n setAssignError(null);\n\n try {\n await activate(subscriptionId, billingAccountId, selectedWorkspaceId);\n setIsDone(true);\n onAssigned(selectedWorkspaceId);\n } catch (err) {\n setAssignError(err instanceof Error ? err.message : 'Failed to assign quota to workspace.');\n }\n }, [selectedWorkspaceId, billingAccountId, subscriptionId, activate, onAssigned]);\n\n if (isDone) {\n return (\n <div className=\"fixed inset-0 bg-bg-overlay backdrop-blur-sm z-50 flex items-center justify-center p-4\">\n <div className=\"bg-bg-surface border border-border-subtle rounded-card shadow-[var(--shadow-elevation-4)] max-w-sm w-full p-6 text-center space-y-4\">\n <div\n className={`inline-flex p-3 rounded-full ${statusTokens.success.bg} ${statusTokens.success.text}`}\n >\n <CheckCircle className=\"size-8\" />\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Quota assigned!</h2>\n <p className=\"text-sm text-text-secondary\">\n {quotas.length} quota{quotas.length !== 1 ? 's have' : ' has'} been assigned to{' '}\n <span className=\"font-medium text-text-primary\">\n {workspaces.find((w) => w.id === selectedWorkspaceId)?.name ?? selectedWorkspaceId}\n </span>\n .\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"fixed inset-0 bg-bg-overlay backdrop-blur-sm z-50 flex items-center justify-center p-4\">\n <div className=\"bg-bg-surface border border-border-subtle rounded-card shadow-[var(--shadow-elevation-4)] max-w-md w-full\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-border-subtle\">\n <div className=\"flex items-center gap-2\">\n <Layers className=\"size-5 text-text-link\" />\n <h2 className=\"text-base font-semibold text-text-primary\">Assign to a Workspace</h2>\n </div>\n <button\n type=\"button\"\n onClick={onSkip}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors text-text-secondary\"\n aria-label=\"Skip\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n {/* Body */}\n <div className=\"px-6 py-5 space-y-5\">\n <p className=\"text-sm text-text-secondary\">\n Select which workspace should receive the quota{quotas.length > 1 ? 's' : ''} included\n in your purchase.\n </p>\n\n {/* Quotas list */}\n <div className=\"bg-bg-sunken rounded-lg p-3 space-y-2\">\n <p className=\"text-xs font-medium text-text-secondary uppercase tracking-wide\">\n Included quotas\n </p>\n {quotas.map((q) => (\n <div key={q.quotaName} className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-primary\">{q.quotaName}</span>\n <span className=\"text-text-secondary\">\n {q.noLimit ? 'Unlimited' : q.limit != null ? `${q.limit} max` : '—'}\n </span>\n </div>\n ))}\n </div>\n\n {/* Workspace selector */}\n {workspaces.length > 1 ? (\n <div className=\"space-y-1.5\">\n <label htmlFor=\"ws-selector\" className=\"block text-sm font-medium text-text-primary\">\n Workspace\n </label>\n <select\n id=\"ws-selector\"\n value={selectedWorkspaceId}\n onChange={(e) => setSelectedWorkspaceId(e.target.value)}\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n {workspaces.map((ws) => (\n <option key={ws.id} value={ws.id}>\n {ws.name}\n </option>\n ))}\n </select>\n </div>\n ) : (\n <div className=\"bg-bg-sunken rounded-lg p-3 text-sm text-text-secondary\">\n Will be assigned to{' '}\n <span className=\"font-medium text-text-primary\">\n {workspaces[0]?.name ?? currentWorkspaceId}\n </span>\n </div>\n )}\n\n {/* Error */}\n {assignError && (\n <div\n className={`flex items-start gap-2 text-sm ${statusTokens.error.text} ${statusTokens.error.bg} rounded-lg p-3`}\n >\n <AlertTriangle className=\"size-4 shrink-0 mt-0.5\" />\n <span>{assignError}</span>\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 px-6 py-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={onSkip}\n className=\"px-4 py-2 text-sm border border-border-subtle rounded-button hover:bg-bg-sunken transition-colors text-text-secondary\"\n >\n Skip for now\n </button>\n <button\n type=\"button\"\n onClick={() => void handleAssign()}\n disabled={!selectedWorkspaceId || isLoading}\n className=\"flex items-center gap-2 px-4 py-2 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-button hover:bg-action-primary-bgHover transition-colors disabled:opacity-50\"\n >\n {isLoading && <Loader2 className=\"size-3.5 animate-spin\" />}\n Assign quota\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|