@burdenoff/microfe-billing 2026.821.1 → 2026.825.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.
Files changed (145) hide show
  1. package/dist/billing/BillingAdminRoutes.js.map +1 -1
  2. package/dist/billing/BillingRoot.js.map +1 -1
  3. package/dist/billing/BillingUserRoutes.js.map +1 -1
  4. package/dist/billing/hooks/useBillingAccountSelection.js.map +1 -1
  5. package/dist/billing/hooks/useBillingEventEmitter.js.map +1 -1
  6. package/dist/billing/hooks/useBillingNavigate.js.map +1 -1
  7. package/dist/billing/hooks/useBillingPermissions.js.map +1 -1
  8. package/dist/billing/hooks/useDefaultBillingCurrency.js.map +1 -1
  9. package/dist/billing/modules/addons/components/FloatingCartDrawer.js.map +1 -1
  10. package/dist/billing/modules/addons/hooks/useAddons.js.map +1 -1
  11. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
  12. package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
  13. package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
  14. package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
  15. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
  16. package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
  17. package/dist/billing/modules/addons/store/addonCartStore.js.map +1 -1
  18. package/dist/billing/modules/affiliate/api.js.map +1 -1
  19. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +107 -107
  20. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
  21. package/dist/billing/modules/analytics/pages/AnalyticsPage.js +80 -81
  22. package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
  23. package/dist/billing/modules/billing/hooks/useInvoices.js.map +1 -1
  24. package/dist/billing/modules/billing/hooks/useRefunds.js.map +1 -1
  25. package/dist/billing/modules/billing/hooks/useTransactions.js.map +1 -1
  26. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
  27. package/dist/billing/modules/billing/pages/InvoicesListPage.js +1 -1
  28. package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
  29. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
  30. package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
  31. package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
  32. package/dist/billing/modules/billing/pages/UserRefundsPage.js +127 -127
  33. package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
  34. package/dist/billing/modules/checkout/components/BillingAccountSelector.js.map +1 -1
  35. package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -1
  36. package/dist/billing/modules/checkout/components/CreditsAmountInput.js.map +1 -1
  37. package/dist/billing/modules/checkout/components/NativeCheckoutView.js.map +1 -1
  38. package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
  39. package/dist/billing/modules/checkout/components/PromoCodeInput.js.map +1 -1
  40. package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
  41. package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js.map +1 -1
  42. package/dist/billing/modules/checkout/components/StoreShippingSection.js.map +1 -1
  43. package/dist/billing/modules/checkout/components/StripePaymentForm.js.map +1 -1
  44. package/dist/billing/modules/checkout/components/SubscriptionSelector.js.map +1 -1
  45. package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js.map +1 -1
  46. package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js.map +1 -1
  47. package/dist/billing/modules/checkout/hooks/useCheckout.js +0 -1
  48. package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
  49. package/dist/billing/modules/checkout/hooks/useNativeCheckout.js.map +1 -1
  50. package/dist/billing/modules/checkout/hooks/usePaymentProviders.js.map +1 -1
  51. package/dist/billing/modules/checkout/hooks/usePaymentStatusPolling.js.map +1 -1
  52. package/dist/billing/modules/checkout/hooks/useStoreShipping.js.map +1 -1
  53. package/dist/billing/modules/checkout/pages/CheckoutPage.js +412 -395
  54. package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
  55. package/dist/billing/modules/checkout/utils/storeOrder.js.map +1 -1
  56. package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
  57. package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
  58. package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
  59. package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
  60. package/dist/billing/modules/credits/hooks/useCredits.js.map +1 -1
  61. package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
  62. package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
  63. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
  64. package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
  65. package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
  66. package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
  67. package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
  68. package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
  69. package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
  70. package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
  71. package/dist/billing/modules/dashboard/hooks/useBillingOverviewData.js.map +1 -1
  72. package/dist/billing/modules/dashboard/hooks/useDashboard.js.map +1 -1
  73. package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
  74. package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
  75. package/dist/billing/modules/earnings/api.js.map +1 -1
  76. package/dist/billing/modules/earnings/pages/DeveloperRevenueAdminPage.js +145 -145
  77. package/dist/billing/modules/earnings/pages/DeveloperRevenueAdminPage.js.map +1 -1
  78. package/dist/billing/modules/earnings/pages/EarningsPage.js +133 -133
  79. package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
  80. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +7 -9
  81. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
  82. package/dist/billing/modules/earnings/pages/PayoutsPage.js +95 -95
  83. package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
  84. package/dist/billing/modules/plans/constants/featuredQuotas.js.map +1 -1
  85. package/dist/billing/modules/plans/hooks/usePlans.js.map +1 -1
  86. package/dist/billing/modules/plans/hooks/useQuotas.js.map +1 -1
  87. package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
  88. package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
  89. package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
  90. package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
  91. package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
  92. package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
  93. package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
  94. package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
  95. package/dist/billing/modules/promotions/pages/PromotionsListPage.js.map +1 -1
  96. package/dist/billing/modules/quota/hooks/useQuotaTemplates.js.map +1 -1
  97. package/dist/billing/modules/quota/pages/QuotaAssignFreePage.js.map +1 -1
  98. package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
  99. package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
  100. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
  101. package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
  102. package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
  103. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
  104. package/dist/billing/modules/store/pages/StoreCheckoutPage.js +5 -6
  105. package/dist/billing/modules/store/pages/StoreCheckoutPage.js.map +1 -1
  106. package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
  107. package/dist/billing/modules/subscriptions/hooks/useSubscriptions.js.map +1 -1
  108. package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
  109. package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
  110. package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
  111. package/dist/billing/modules/usage/hooks/useUsage.js +14 -9
  112. package/dist/billing/modules/usage/hooks/useUsage.js.map +1 -1
  113. package/dist/billing/modules/usage/pages/UsagePage.js +481 -453
  114. package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
  115. package/dist/billing/providers/BillingProvider.js.map +1 -1
  116. package/dist/billing/shared/components/AccessDenied.js.map +1 -1
  117. package/dist/billing/shared/components/ActorIdentity.js.map +1 -1
  118. package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js.map +1 -1
  119. package/dist/billing/shared/components/EmptyState.js.map +1 -1
  120. package/dist/billing/shared/components/PageHeader.js.map +1 -1
  121. package/dist/billing/shared/components/ServerError.js.map +1 -1
  122. package/dist/billing/shared/components/StatCard.js.map +1 -1
  123. package/dist/billing/shared/hooks/useActorProfiles.js.map +1 -1
  124. package/dist/billing/shared/types/graphql.js.map +1 -1
  125. package/dist/billing/shared/ui/Card.js.map +1 -1
  126. package/dist/billing/shared/ui/MetricCard.js.map +1 -1
  127. package/dist/billing/shared/ui/ProgressBar.js.map +1 -1
  128. package/dist/billing/shared/ui/StatusPill.js.map +1 -1
  129. package/dist/billing/shared/utils/directBillingGraphql.js.map +1 -1
  130. package/dist/billing/shared/utils/error.js.map +1 -1
  131. package/dist/billing/shared/utils/format.js.map +1 -1
  132. package/dist/billing/shared/utils/gatewayGraphql.js.map +1 -1
  133. package/dist/billing/shared/utils/navigation.js.map +1 -1
  134. package/dist/billing/shared/utils/paymentTheme.js.map +1 -1
  135. package/dist/billing/shared/utils/planFeatureQuantity.js.map +1 -1
  136. package/dist/billing/shared/utils/status.js.map +1 -1
  137. package/dist/billing/shared/utils/tokens.js.map +1 -1
  138. package/dist/generated/global-operations.js +5 -1
  139. package/dist/generated/global-operations.js.map +1 -1
  140. package/dist/generated/global-types.js.map +1 -1
  141. package/dist/generated/wspace-operations.js +1 -1
  142. package/dist/generated/wspace-operations.js.map +1 -1
  143. package/dist/node_modules/@capacitor/core/dist/index.js +1 -1
  144. package/dist/node_modules/@capacitor/core/dist/index.js.map +1 -1
  145. package/package.json +13 -10
@@ -1 +1 @@
1
- {"version":3,"file":"UserRefundsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/UserRefundsPage.tsx"],"sourcesContent":["/**\n * Billing Module - User Refunds Page\n * Shows the user's refund request history and allows submitting new requests.\n */\n\nimport { useState, useEffect, useRef, type FC, type FormEvent } from 'react';\nimport {\n PlusCircle,\n X,\n Clock,\n CheckCircle,\n XCircle,\n Loader2,\n Search,\n CreditCard,\n} from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { formatCurrency, formatDateTime } from '../../../shared/utils';\nimport { PageHeader } from '../../../shared/components';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport {\n useGetBillingAccountsCheckoutQuery,\n useGetTransactionLazyQuery,\n useGetRefundRecordsQuery,\n useCreateRefundRecordMutation,\n type GetRefundRecordsQuery,\n type GetTransactionQuery,\n} from '../../../../generated/global-operations';\nimport { RefundRecordStatus } from '../../../../generated/global-types';\n\ntype RefundItem = NonNullable<GetRefundRecordsQuery['getRefundRecords']['records']>[number];\ntype TxnLookup = NonNullable<GetTransactionQuery['getTransaction']>;\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n// ============================================================================\n// Status helpers\n// ============================================================================\n\nconst STATUS_CONFIG: Record<\n RefundRecordStatus,\n { label: string; className: string; icon: FC<{ className?: string }> }\n> = {\n [RefundRecordStatus.Pending]: {\n label: 'In Review',\n className: 'bg-status-warning-bg/20 text-status-warning-text border-status-warning-border',\n icon: ({ className }) => <Clock className={className} />,\n },\n [RefundRecordStatus.Processing]: {\n label: 'Processing',\n className: 'bg-action-primary-bg/15 text-action-primary-bg border-action-primary-bg/30',\n icon: ({ className }) => <Loader2 className={`${className} animate-spin`} />,\n },\n [RefundRecordStatus.Completed]: {\n label: 'Refunded',\n className: 'bg-status-success-bg/20 text-status-success-text border-status-success-border',\n icon: ({ className }) => <CheckCircle className={className} />,\n },\n [RefundRecordStatus.Failed]: {\n label: 'Failed',\n className: 'bg-status-error-bg/20 text-status-error-text border-status-error-border',\n icon: ({ className }) => <XCircle className={className} />,\n },\n [RefundRecordStatus.Skipped]: {\n label: 'Closed',\n className: 'bg-bg-sunken text-text-secondary border-border-subtle',\n icon: ({ className }) => <XCircle className={className} />,\n },\n};\n\nfunction StatusBadge({ status }: { status: string }) {\n const cfg = STATUS_CONFIG[status as RefundRecordStatus] ?? {\n label: status,\n className: 'bg-bg-sunken text-text-secondary border-border-subtle',\n icon: ({ className }: { className?: string }) => <Clock className={className} />,\n };\n const Icon = cfg.icon;\n return (\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full border text-xs font-medium ${cfg.className}`}\n >\n <Icon className=\"size-3\" />\n {cfg.label}\n </span>\n );\n}\n\n// ============================================================================\n// Main page\n// ============================================================================\n\nexport const UserRefundsPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const navigateTo = useBillingNavigate();\n\n // Form state\n const [showForm, setShowForm] = useState(false);\n const [txnIdInput, setTxnIdInput] = useState('');\n const [lookedUpTxn, setLookedUpTxn] = useState<TxnLookup | null>(null);\n const [txnNotFound, setTxnNotFound] = useState(false);\n const [reason, setReason] = useState('');\n const [amount, setAmount] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n const [formSuccess, setFormSuccess] = useState(false);\n\n // All billing accounts\n const { data: accountsData, loading: accountsLoading } = useGetBillingAccountsCheckoutQuery({\n fetchPolicy: 'cache-and-network',\n });\n type BillingAccount = NonNullable<typeof accountsData>['getBillingAccountsByOrg'][number];\n const accounts: BillingAccount[] = accountsData?.getBillingAccountsByOrg ?? [];\n\n // Default to the first account; user can switch\n const [selectedAccountId, setSelectedAccountId] = useState<string | undefined>(undefined);\n useEffect(() => {\n if (!selectedAccountId && accounts.length > 0) {\n setSelectedAccountId(accounts[0].id);\n }\n }, [accounts, selectedAccountId]);\n\n const selectedAccount = accounts.find((a) => a.id === selectedAccountId);\n\n // Transaction lookup by ID (lazy — fires when user enters a valid UUID)\n const [fetchTxn, { data: txnLookupData, loading: txnLookupLoading, error: txnLookupError }] =\n useGetTransactionLazyQuery({ fetchPolicy: 'network-only' });\n\n // Track the \"active\" lookup key so we only apply results for the latest request\n const lastLookupKey = useRef('');\n\n useEffect(() => {\n if (txnLookupLoading) return;\n const txn = txnLookupData?.getTransaction;\n if (txn) {\n setLookedUpTxn(txn);\n setTxnNotFound(false);\n setAmount(String(txn.amount));\n } else if (txnLookupData !== undefined || txnLookupError) {\n setLookedUpTxn(null);\n setTxnNotFound(true);\n setAmount('');\n }\n }, [txnLookupData, txnLookupLoading, txnLookupError]);\n\n const triggerLookup = (txnId: string, accountId: string) => {\n const key = `${txnId}::${accountId}`;\n lastLookupKey.current = key;\n setLookedUpTxn(null);\n setTxnNotFound(false);\n setAmount('');\n fetchTxn({ variables: { id: txnId, billingAccountId: accountId } });\n };\n\n const handleTxnIdChange = (value: string) => {\n setTxnIdInput(value);\n setLookedUpTxn(null);\n setTxnNotFound(false);\n setAmount('');\n if (UUID_RE.test(value.trim()) && selectedAccountId) {\n triggerLookup(value.trim(), selectedAccountId);\n }\n };\n\n // When account changes, re-fetch the transaction if one is entered\n const handleAccountChange = (accountId: string) => {\n setSelectedAccountId(accountId);\n setLookedUpTxn(null);\n setTxnNotFound(false);\n setAmount('');\n if (UUID_RE.test(txnIdInput.trim())) {\n triggerLookup(txnIdInput.trim(), accountId);\n }\n };\n\n // Refund history for selected account\n const {\n data: refundsData,\n loading: refundsLoading,\n refetch: refetchRefunds,\n } = useGetRefundRecordsQuery({\n variables: { billingAccountId: selectedAccountId, pageSize: 20, page: 1 },\n skip: !selectedAccountId,\n fetchPolicy: 'cache-and-network',\n });\n const refunds = refundsData?.getRefundRecords.records ?? [];\n\n const [createRefund, { loading: isSubmitting }] = useCreateRefundRecordMutation({\n refetchQueries: ['GetRefundRecords'],\n });\n\n const getGatewayInfo = (txn: TxnLookup | null) => {\n if (!txn) return null;\n if (txn.stripePaymentId) return { provider: 'STRIPE', gatewayPaymentId: txn.stripePaymentId };\n if (txn.razorpayPaymentId)\n return { provider: 'RAZORPAY', gatewayPaymentId: txn.razorpayPaymentId };\n return null;\n };\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setFormError(null);\n\n if (!lookedUpTxn || !reason.trim() || !amount) {\n setFormError('Enter a valid transaction ID, an amount, and a reason.');\n return;\n }\n const gatewayInfo = getGatewayInfo(lookedUpTxn);\n if (!gatewayInfo) {\n setFormError('No payment gateway info found for this transaction. Please contact support.');\n return;\n }\n const parsedAmount = parseFloat(amount);\n if (isNaN(parsedAmount) || parsedAmount <= 0) {\n setFormError('Please enter a valid amount greater than 0.');\n return;\n }\n if (parsedAmount > lookedUpTxn.amount) {\n setFormError(\n `Cannot exceed the original payment amount (${formatCurrency(lookedUpTxn.amount, lookedUpTxn.currency)}).`\n );\n return;\n }\n\n try {\n await createRefund({\n variables: {\n billingAccountId: selectedAccountId,\n provider: gatewayInfo.provider,\n gatewayPaymentId: gatewayInfo.gatewayPaymentId,\n amount: parsedAmount,\n currency: lookedUpTxn.currency ?? selectedAccount?.currency ?? 'USD',\n transactionId: lookedUpTxn.id,\n reason: reason.trim(),\n notes: `Requested by user via billing portal`,\n },\n });\n setFormSuccess(true);\n setShowForm(false);\n resetFormFields();\n await refetchRefunds();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to submit refund request.');\n }\n };\n\n const resetFormFields = () => {\n setTxnIdInput('');\n setLookedUpTxn(null);\n setTxnNotFound(false);\n setReason('');\n setAmount('');\n setFormError(null);\n };\n\n const resetForm = () => {\n setShowForm(false);\n setFormSuccess(false);\n resetFormFields();\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-3xl\">\n <PageHeader\n title={tr('billing.refunds.title', 'Refunds')}\n description={tr(\n 'billing.refunds.description',\n 'View your refund requests and submit new ones'\n )}\n actions={\n !showForm ? (\n <button\n type=\"button\"\n onClick={() => {\n setShowForm(true);\n setFormSuccess(false);\n }}\n className=\"inline-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\"\n >\n <PlusCircle className=\"size-4\" />\n {tr('billing.refunds.requestRefund', 'Request Refund')}\n </button>\n ) : undefined\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.refunds.userPurpose',\n 'Request money back for a payment and follow where each request stands. Paste the transaction ID of the charge you want refunded, tell us how much and why, and track every request here from \"In Review\" through to \"Refunded\".'\n )}\n </PagePurpose>\n\n {/* Billing account switcher */}\n {!accountsLoading && accounts.length > 1 && (\n <div className=\"flex items-center gap-3\">\n <CreditCard className=\"size-4 text-text-secondary shrink-0\" />\n <label htmlFor=\"accountSwitch\" className=\"text-sm text-text-secondary shrink-0\">\n {tr('billing.refunds.account', 'Billing account')}\n </label>\n <select\n id=\"accountSwitch\"\n value={selectedAccountId ?? ''}\n onChange={(e) => handleAccountChange(e.target.value)}\n className=\"flex-1 px-3 py-1.5 border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] text-sm\"\n >\n {accounts.map((a) => (\n <option key={a.id} value={a.id}>\n {a.name} ({a.currency}) — {a.email}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Success banner */}\n {formSuccess && (\n <div className=\"flex items-center justify-between p-4 rounded-lg border border-status-success-border bg-status-success-bg/10\">\n <div className=\"flex items-center gap-2\">\n <CheckCircle className=\"size-4 text-status-success-text\" />\n <p className=\"text-sm text-status-success-text font-medium\">\n {tr(\n 'billing.refunds.submitted',\n 'Refund request submitted. Our team will review it shortly.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => setFormSuccess(false)}\n className=\"text-status-success-text hover:opacity-70\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n )}\n\n {/* Request Refund Form */}\n {showForm && (\n <div className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle bg-bg-sunken\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.refunds.newRequest', 'New Refund Request')}\n </h2>\n <button\n type=\"button\"\n onClick={resetForm}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n\n <form onSubmit={handleSubmit} className=\"p-4 space-y-4\">\n {formError && (\n <div className=\"p-3 rounded-lg border border-status-error-border bg-status-error-bg/10\">\n <p className=\"text-sm text-status-error-text\">{formError}</p>\n </div>\n )}\n\n {/* Transaction ID lookup */}\n <div>\n <label htmlFor=\"txnId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('billing.refunds.transactionId', 'Transaction ID')} *\n </label>\n <div className=\"relative\">\n <input\n id=\"txnId\"\n type=\"text\"\n value={txnIdInput}\n onChange={(e) => handleTxnIdChange(e.target.value)}\n placeholder=\"Paste your transaction ID...\"\n className=\"w-full pl-3 pr-9 py-2 border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] text-sm font-mono\"\n />\n <div className=\"absolute right-2.5 top-1/2 -translate-y-1/2 text-text-secondary\">\n {txnLookupLoading ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Search className=\"size-4\" />\n )}\n </div>\n </div>\n\n {/* Transaction lookup result */}\n {lookedUpTxn && (\n <div className=\"mt-2 p-3 rounded-button border border-status-success-border bg-status-success-bg/10 text-sm space-y-0.5\">\n <div className=\"flex items-center gap-2\">\n <CheckCircle className=\"size-3.5 text-status-success-text shrink-0\" />\n <span className=\"font-medium text-text-primary\">\n {formatCurrency(lookedUpTxn.amount, lookedUpTxn.currency)}\n </span>\n <span className=\"text-text-secondary\">·</span>\n <span className=\"text-text-secondary\">\n {formatDateTime(lookedUpTxn.createdAt)}\n </span>\n {lookedUpTxn.stripePaymentId && (\n <span className=\"text-xs text-text-secondary\">Stripe</span>\n )}\n {lookedUpTxn.razorpayPaymentId && (\n <span className=\"text-xs text-text-secondary\">Razorpay</span>\n )}\n </div>\n {lookedUpTxn.description && (\n <p className=\"text-xs text-text-secondary pl-5\">{lookedUpTxn.description}</p>\n )}\n </div>\n )}\n {txnNotFound && txnIdInput.length > 0 && !txnLookupLoading && (\n <p className=\"mt-1 text-xs text-status-error-text\">\n {tr(\n 'billing.refunds.txnNotFound',\n 'Transaction not found in this billing account.'\n )}\n </p>\n )}\n {!UUID_RE.test(txnIdInput.trim()) && txnIdInput.length > 4 && (\n <p className=\"mt-1 text-xs text-text-secondary\">\n {tr(\n 'billing.refunds.txnIdHint',\n 'Transaction IDs look like: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'\n )}\n </p>\n )}\n </div>\n\n {/* Amount — only shown once transaction is found */}\n {lookedUpTxn && (\n <div>\n <label\n htmlFor=\"refundAmount\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.refunds.amount', 'Refund Amount')} *\n </label>\n <div className=\"flex items-center gap-2\">\n <input\n id=\"refundAmount\"\n type=\"number\"\n step=\"0.01\"\n min=\"0.01\"\n max={lookedUpTxn.amount}\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n required\n className=\"w-40 px-3 py-2 border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] text-sm\"\n placeholder=\"0.00\"\n />\n <span className=\"text-sm text-text-secondary\">\n {lookedUpTxn.currency ?? selectedAccount?.currency ?? 'USD'}\n </span>\n <span className=\"text-xs text-text-secondary\">\n max {formatCurrency(lookedUpTxn.amount, lookedUpTxn.currency)}\n </span>\n </div>\n </div>\n )}\n\n {/* Reason */}\n <div>\n <label htmlFor=\"reason\" className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('billing.refunds.reason', 'Reason for refund')} *\n </label>\n <textarea\n id=\"reason\"\n value={reason}\n onChange={(e) => setReason(e.target.value)}\n required\n rows={3}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] text-sm resize-none\"\n placeholder={tr(\n 'billing.refunds.reasonPlaceholder',\n 'Describe why you are requesting a refund...'\n )}\n />\n </div>\n\n <div className=\"flex items-center gap-3 pt-1\">\n <button\n type=\"submit\"\n disabled={isSubmitting || !lookedUpTxn || !reason.trim() || !amount}\n className=\"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 disabled:cursor-not-allowed\"\n >\n {isSubmitting\n ? tr('billing.refunds.submitting', 'Submitting...')\n : tr('billing.refunds.submitRequest', 'Submit Request')}\n </button>\n <button\n type=\"button\"\n onClick={resetForm}\n className=\"px-4 py-2 text-sm border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n {tr('billing.common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n </div>\n )}\n\n {/* Refund history */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.refunds.history', 'Refund History')}\n </h2>\n </div>\n\n {accountsLoading || refundsLoading ? (\n <div className=\"p-8 flex items-center justify-center\">\n <Loader2 className=\"size-5 animate-spin text-text-secondary\" />\n </div>\n ) : refunds.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('billing.refunds.noRefunds', 'No refund requests yet')}\n description={tr(\n 'billing.refunds.noRefundsHint',\n 'If you have an issue with a payment, click \"Request Refund\" to get started.'\n )}\n />\n ) : (\n <ul className=\"divide-y divide-border-subtle\">\n {refunds.map((r: RefundItem) => (\n <li\n key={r.id}\n className=\"flex items-start gap-4 p-4 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <span className=\"text-sm font-medium text-text-primary\">\n {formatCurrency(r.amount, r.currency)}\n </span>\n <StatusBadge status={r.status} />\n {r.purchaseDetail && (\n <span className=\"text-xs text-text-secondary truncate\">\n {r.purchaseDetail}\n </span>\n )}\n </div>\n {r.reason && (\n <p className=\"text-xs text-text-secondary mt-1 line-clamp-2\">{r.reason}</p>\n )}\n {r.errorMessage && r.status === RefundRecordStatus.Failed && (\n <p className=\"text-xs text-status-error-text mt-1\">{r.errorMessage}</p>\n )}\n </div>\n <div className=\"text-right shrink-0\">\n <p className=\"text-xs text-text-secondary\">{formatDateTime(r.createdAt)}</p>\n {r.processedAt && (\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Processed {formatDateTime(r.processedAt)}\n </p>\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'billing.refunds.footer',\n 'Refund requests are reviewed by our team within 3–5 business days. For urgent issues, contact support.'\n )}{' '}\n <button\n type=\"button\"\n onClick={() => navigateTo('/transactions/' + selectedAccountId)}\n className=\"underline hover:no-underline\"\n >\n {tr('billing.refunds.viewTransactions', 'View all transactions')}\n </button>\n </p>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"UserRefundsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/UserRefundsPage.tsx"],"sourcesContent":["/**\n * Billing Module - User Refunds Page\n * Shows the user's refund request history and allows submitting new requests.\n */\n\nimport { useState, useEffect, useMemo, useRef, type FC, type FormEvent } from 'react';\nimport {\n PlusCircle,\n X,\n Clock,\n CheckCircle,\n XCircle,\n Loader2,\n Search,\n CreditCard,\n} from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { formatCurrency, formatDateTime } from '../../../shared/utils';\nimport { PageHeader } from '../../../shared/components';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport {\n useGetBillingAccountsCheckoutQuery,\n useGetTransactionLazyQuery,\n useGetRefundRecordsQuery,\n useCreateRefundRecordMutation,\n type GetRefundRecordsQuery,\n type GetTransactionQuery,\n} from '../../../../generated/global-operations';\nimport { RefundRecordStatus } from '../../../../generated/global-types';\n\ntype RefundItem = NonNullable<GetRefundRecordsQuery['getRefundRecords']['records']>[number];\ntype TxnLookup = NonNullable<GetTransactionQuery['getTransaction']>;\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n// ============================================================================\n// Status helpers\n// ============================================================================\n\nconst STATUS_CONFIG: Record<\n RefundRecordStatus,\n { label: string; className: string; icon: FC<{ className?: string }> }\n> = {\n [RefundRecordStatus.Pending]: {\n label: 'In Review',\n className: 'bg-status-warning-bg/20 text-status-warning-text border-status-warning-border',\n icon: ({ className }) => <Clock className={className} />,\n },\n [RefundRecordStatus.Processing]: {\n label: 'Processing',\n className: 'bg-action-primary-bg/15 text-action-primary-bg border-action-primary-bg/30',\n icon: ({ className }) => <Loader2 className={`${className} animate-spin`} />,\n },\n [RefundRecordStatus.Completed]: {\n label: 'Refunded',\n className: 'bg-status-success-bg/20 text-status-success-text border-status-success-border',\n icon: ({ className }) => <CheckCircle className={className} />,\n },\n [RefundRecordStatus.Failed]: {\n label: 'Failed',\n className: 'bg-status-error-bg/20 text-status-error-text border-status-error-border',\n icon: ({ className }) => <XCircle className={className} />,\n },\n [RefundRecordStatus.Skipped]: {\n label: 'Closed',\n className: 'bg-bg-sunken text-text-secondary border-border-subtle',\n icon: ({ className }) => <XCircle className={className} />,\n },\n};\n\nfunction StatusBadge({ status }: { status: string }) {\n const cfg = STATUS_CONFIG[status as RefundRecordStatus] ?? {\n label: status,\n className: 'bg-bg-sunken text-text-secondary border-border-subtle',\n icon: ({ className }: { className?: string }) => <Clock className={className} />,\n };\n const Icon = cfg.icon;\n return (\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full border text-xs font-medium ${cfg.className}`}\n >\n <Icon className=\"size-3\" />\n {cfg.label}\n </span>\n );\n}\n\n// ============================================================================\n// Main page\n// ============================================================================\n\nexport const UserRefundsPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const navigateTo = useBillingNavigate();\n\n // Form state\n const [showForm, setShowForm] = useState(false);\n const [txnIdInput, setTxnIdInput] = useState('');\n const [lookedUpTxn, setLookedUpTxn] = useState<TxnLookup | null>(null);\n const [txnNotFound, setTxnNotFound] = useState(false);\n const [reason, setReason] = useState('');\n const [amount, setAmount] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n const [formSuccess, setFormSuccess] = useState(false);\n\n // All billing accounts\n const { data: accountsData, loading: accountsLoading } = useGetBillingAccountsCheckoutQuery({\n fetchPolicy: 'cache-and-network',\n });\n type BillingAccount = NonNullable<typeof accountsData>['getBillingAccountsByOrg'][number];\n const accounts = useMemo<BillingAccount[]>(\n () => accountsData?.getBillingAccountsByOrg ?? [],\n [accountsData]\n );\n\n // Default to the first account; user can switch\n const [selectedAccountId, setSelectedAccountId] = useState<string | undefined>(undefined);\n useEffect(() => {\n if (!selectedAccountId && accounts.length > 0) {\n setSelectedAccountId(accounts[0].id);\n }\n }, [accounts, selectedAccountId]);\n\n const selectedAccount = accounts.find((a) => a.id === selectedAccountId);\n\n // Transaction lookup by ID (lazy — fires when user enters a valid UUID)\n const [fetchTxn, { data: txnLookupData, loading: txnLookupLoading, error: txnLookupError }] =\n useGetTransactionLazyQuery({ fetchPolicy: 'network-only' });\n\n // Track the \"active\" lookup key so we only apply results for the latest request\n const lastLookupKey = useRef('');\n\n useEffect(() => {\n if (txnLookupLoading) return;\n const txn = txnLookupData?.getTransaction;\n if (txn) {\n setLookedUpTxn(txn);\n setTxnNotFound(false);\n setAmount(String(txn.amount));\n } else if (txnLookupData !== undefined || txnLookupError) {\n setLookedUpTxn(null);\n setTxnNotFound(true);\n setAmount('');\n }\n }, [txnLookupData, txnLookupLoading, txnLookupError]);\n\n const triggerLookup = (txnId: string, accountId: string) => {\n const key = `${txnId}::${accountId}`;\n lastLookupKey.current = key;\n setLookedUpTxn(null);\n setTxnNotFound(false);\n setAmount('');\n fetchTxn({ variables: { id: txnId, billingAccountId: accountId } });\n };\n\n const handleTxnIdChange = (value: string) => {\n setTxnIdInput(value);\n setLookedUpTxn(null);\n setTxnNotFound(false);\n setAmount('');\n if (UUID_RE.test(value.trim()) && selectedAccountId) {\n triggerLookup(value.trim(), selectedAccountId);\n }\n };\n\n // When account changes, re-fetch the transaction if one is entered\n const handleAccountChange = (accountId: string) => {\n setSelectedAccountId(accountId);\n setLookedUpTxn(null);\n setTxnNotFound(false);\n setAmount('');\n if (UUID_RE.test(txnIdInput.trim())) {\n triggerLookup(txnIdInput.trim(), accountId);\n }\n };\n\n // Refund history for selected account\n const {\n data: refundsData,\n loading: refundsLoading,\n refetch: refetchRefunds,\n } = useGetRefundRecordsQuery({\n variables: { billingAccountId: selectedAccountId, pageSize: 20, page: 1 },\n skip: !selectedAccountId,\n fetchPolicy: 'cache-and-network',\n });\n const refunds = refundsData?.getRefundRecords.records ?? [];\n\n const [createRefund, { loading: isSubmitting }] = useCreateRefundRecordMutation({\n refetchQueries: ['GetRefundRecords'],\n });\n\n const getGatewayInfo = (txn: TxnLookup | null) => {\n if (!txn) return null;\n if (txn.stripePaymentId) return { provider: 'STRIPE', gatewayPaymentId: txn.stripePaymentId };\n if (txn.razorpayPaymentId)\n return { provider: 'RAZORPAY', gatewayPaymentId: txn.razorpayPaymentId };\n return null;\n };\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setFormError(null);\n\n if (!lookedUpTxn || !reason.trim() || !amount) {\n setFormError('Enter a valid transaction ID, an amount, and a reason.');\n return;\n }\n const gatewayInfo = getGatewayInfo(lookedUpTxn);\n if (!gatewayInfo) {\n setFormError('No payment gateway info found for this transaction. Please contact support.');\n return;\n }\n const parsedAmount = parseFloat(amount);\n if (isNaN(parsedAmount) || parsedAmount <= 0) {\n setFormError('Please enter a valid amount greater than 0.');\n return;\n }\n if (parsedAmount > lookedUpTxn.amount) {\n setFormError(\n `Cannot exceed the original payment amount (${formatCurrency(lookedUpTxn.amount, lookedUpTxn.currency)}).`\n );\n return;\n }\n\n try {\n await createRefund({\n variables: {\n billingAccountId: selectedAccountId,\n provider: gatewayInfo.provider,\n gatewayPaymentId: gatewayInfo.gatewayPaymentId,\n amount: parsedAmount,\n currency: lookedUpTxn.currency ?? selectedAccount?.currency ?? 'USD',\n transactionId: lookedUpTxn.id,\n reason: reason.trim(),\n notes: `Requested by user via billing portal`,\n },\n });\n setFormSuccess(true);\n setShowForm(false);\n resetFormFields();\n await refetchRefunds();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to submit refund request.');\n }\n };\n\n const resetFormFields = () => {\n setTxnIdInput('');\n setLookedUpTxn(null);\n setTxnNotFound(false);\n setReason('');\n setAmount('');\n setFormError(null);\n };\n\n const resetForm = () => {\n setShowForm(false);\n setFormSuccess(false);\n resetFormFields();\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-3xl\">\n <PageHeader\n title={tr('billing.refunds.title', 'Refunds')}\n description={tr(\n 'billing.refunds.description',\n 'View your refund requests and submit new ones'\n )}\n actions={\n !showForm ? (\n <button\n type=\"button\"\n onClick={() => {\n setShowForm(true);\n setFormSuccess(false);\n }}\n className=\"inline-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\"\n >\n <PlusCircle className=\"size-4\" />\n {tr('billing.refunds.requestRefund', 'Request Refund')}\n </button>\n ) : undefined\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.refunds.userPurpose',\n 'Request money back for a payment and follow where each request stands. Paste the transaction ID of the charge you want refunded, tell us how much and why, and track every request here from \"In Review\" through to \"Refunded\".'\n )}\n </PagePurpose>\n\n {/* Billing account switcher */}\n {!accountsLoading && accounts.length > 1 && (\n <div className=\"flex items-center gap-3\">\n <CreditCard className=\"size-4 text-text-secondary shrink-0\" />\n <label htmlFor=\"accountSwitch\" className=\"text-sm text-text-secondary shrink-0\">\n {tr('billing.refunds.account', 'Billing account')}\n </label>\n <select\n id=\"accountSwitch\"\n value={selectedAccountId ?? ''}\n onChange={(e) => handleAccountChange(e.target.value)}\n className=\"flex-1 px-3 py-1.5 border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] text-sm\"\n >\n {accounts.map((a) => (\n <option key={a.id} value={a.id}>\n {a.name} ({a.currency}) — {a.email}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {/* Success banner */}\n {formSuccess && (\n <div className=\"flex items-center justify-between p-4 rounded-lg border border-status-success-border bg-status-success-bg/10\">\n <div className=\"flex items-center gap-2\">\n <CheckCircle className=\"size-4 text-status-success-text\" />\n <p className=\"text-sm text-status-success-text font-medium\">\n {tr(\n 'billing.refunds.submitted',\n 'Refund request submitted. Our team will review it shortly.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => setFormSuccess(false)}\n className=\"text-status-success-text hover:opacity-70\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n )}\n\n {/* Request Refund Form */}\n {showForm && (\n <div className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle bg-bg-sunken\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.refunds.newRequest', 'New Refund Request')}\n </h2>\n <button\n type=\"button\"\n onClick={resetForm}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n\n <form onSubmit={handleSubmit} className=\"p-4 space-y-4\">\n {formError && (\n <div className=\"p-3 rounded-lg border border-status-error-border bg-status-error-bg/10\">\n <p className=\"text-sm text-status-error-text\">{formError}</p>\n </div>\n )}\n\n {/* Transaction ID lookup */}\n <div>\n <label htmlFor=\"txnId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('billing.refunds.transactionId', 'Transaction ID')} *\n </label>\n <div className=\"relative\">\n <input\n id=\"txnId\"\n type=\"text\"\n value={txnIdInput}\n onChange={(e) => handleTxnIdChange(e.target.value)}\n placeholder=\"Paste your transaction ID...\"\n className=\"w-full pl-3 pr-9 py-2 border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] text-sm font-mono\"\n />\n <div className=\"absolute right-2.5 top-1/2 -translate-y-1/2 text-text-secondary\">\n {txnLookupLoading ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Search className=\"size-4\" />\n )}\n </div>\n </div>\n\n {/* Transaction lookup result */}\n {lookedUpTxn && (\n <div className=\"mt-2 p-3 rounded-button border border-status-success-border bg-status-success-bg/10 text-sm space-y-0.5\">\n <div className=\"flex items-center gap-2\">\n <CheckCircle className=\"size-3.5 text-status-success-text shrink-0\" />\n <span className=\"font-medium text-text-primary\">\n {formatCurrency(lookedUpTxn.amount, lookedUpTxn.currency)}\n </span>\n <span className=\"text-text-secondary\">·</span>\n <span className=\"text-text-secondary\">\n {formatDateTime(lookedUpTxn.createdAt)}\n </span>\n {lookedUpTxn.stripePaymentId && (\n <span className=\"text-xs text-text-secondary\">Stripe</span>\n )}\n {lookedUpTxn.razorpayPaymentId && (\n <span className=\"text-xs text-text-secondary\">Razorpay</span>\n )}\n </div>\n {lookedUpTxn.description && (\n <p className=\"text-xs text-text-secondary pl-5\">{lookedUpTxn.description}</p>\n )}\n </div>\n )}\n {txnNotFound && txnIdInput.length > 0 && !txnLookupLoading && (\n <p className=\"mt-1 text-xs text-status-error-text\">\n {tr(\n 'billing.refunds.txnNotFound',\n 'Transaction not found in this billing account.'\n )}\n </p>\n )}\n {!UUID_RE.test(txnIdInput.trim()) && txnIdInput.length > 4 && (\n <p className=\"mt-1 text-xs text-text-secondary\">\n {tr(\n 'billing.refunds.txnIdHint',\n 'Transaction IDs look like: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'\n )}\n </p>\n )}\n </div>\n\n {/* Amount — only shown once transaction is found */}\n {lookedUpTxn && (\n <div>\n <label\n htmlFor=\"refundAmount\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n {tr('billing.refunds.amount', 'Refund Amount')} *\n </label>\n <div className=\"flex items-center gap-2\">\n <input\n id=\"refundAmount\"\n type=\"number\"\n step=\"0.01\"\n min=\"0.01\"\n max={lookedUpTxn.amount}\n value={amount}\n onChange={(e) => setAmount(e.target.value)}\n required\n className=\"w-40 px-3 py-2 border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] text-sm\"\n placeholder=\"0.00\"\n />\n <span className=\"text-sm text-text-secondary\">\n {lookedUpTxn.currency ?? selectedAccount?.currency ?? 'USD'}\n </span>\n <span className=\"text-xs text-text-secondary\">\n max {formatCurrency(lookedUpTxn.amount, lookedUpTxn.currency)}\n </span>\n </div>\n </div>\n )}\n\n {/* Reason */}\n <div>\n <label htmlFor=\"reason\" className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('billing.refunds.reason', 'Reason for refund')} *\n </label>\n <textarea\n id=\"reason\"\n value={reason}\n onChange={(e) => setReason(e.target.value)}\n required\n rows={3}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-button bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] text-sm resize-none\"\n placeholder={tr(\n 'billing.refunds.reasonPlaceholder',\n 'Describe why you are requesting a refund...'\n )}\n />\n </div>\n\n <div className=\"flex items-center gap-3 pt-1\">\n <button\n type=\"submit\"\n disabled={isSubmitting || !lookedUpTxn || !reason.trim() || !amount}\n className=\"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 disabled:cursor-not-allowed\"\n >\n {isSubmitting\n ? tr('billing.refunds.submitting', 'Submitting...')\n : tr('billing.refunds.submitRequest', 'Submit Request')}\n </button>\n <button\n type=\"button\"\n onClick={resetForm}\n className=\"px-4 py-2 text-sm border border-border-subtle text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n {tr('billing.common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n </div>\n )}\n\n {/* Refund history */}\n <div className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 overflow-hidden\">\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken\">\n <h2 className=\"font-semibold text-text-primary\">\n {tr('billing.refunds.history', 'Refund History')}\n </h2>\n </div>\n\n {accountsLoading || refundsLoading ? (\n <div className=\"p-8 flex items-center justify-center\">\n <Loader2 className=\"size-5 animate-spin text-text-secondary\" />\n </div>\n ) : refunds.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('billing.refunds.noRefunds', 'No refund requests yet')}\n description={tr(\n 'billing.refunds.noRefundsHint',\n 'If you have an issue with a payment, click \"Request Refund\" to get started.'\n )}\n />\n ) : (\n <ul className=\"divide-y divide-border-subtle\">\n {refunds.map((r: RefundItem) => (\n <li\n key={r.id}\n className=\"flex items-start gap-4 p-4 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <span className=\"text-sm font-medium text-text-primary\">\n {formatCurrency(r.amount, r.currency)}\n </span>\n <StatusBadge status={r.status} />\n {r.purchaseDetail && (\n <span className=\"text-xs text-text-secondary truncate\">\n {r.purchaseDetail}\n </span>\n )}\n </div>\n {r.reason && (\n <p className=\"text-xs text-text-secondary mt-1 line-clamp-2\">{r.reason}</p>\n )}\n {r.errorMessage && r.status === RefundRecordStatus.Failed && (\n <p className=\"text-xs text-status-error-text mt-1\">{r.errorMessage}</p>\n )}\n </div>\n <div className=\"text-right shrink-0\">\n <p className=\"text-xs text-text-secondary\">{formatDateTime(r.createdAt)}</p>\n {r.processedAt && (\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Processed {formatDateTime(r.processedAt)}\n </p>\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'billing.refunds.footer',\n 'Refund requests are reviewed by our team within 3–5 business days. For urgent issues, contact support.'\n )}{' '}\n <button\n type=\"button\"\n onClick={() => navigateTo('/transactions/' + selectedAccountId)}\n className=\"underline hover:no-underline\"\n >\n {tr('billing.refunds.viewTransactions', 'View all transactions')}\n </button>\n </p>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BillingAccountSelector.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/BillingAccountSelector.tsx"],"sourcesContent":["/**\n * Billing Account Selector Component\n * Dropdown selector for choosing billing account in checkout\n */\n\nimport { type FC, useState, useRef, useEffect } from 'react';\nimport { Building2, CreditCard, ChevronDown, Check, Lock } from 'lucide-react';\nimport type { CheckoutBillingAccount } from '../types';\nimport { formatCurrency } from '../../../shared/utils/format';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface BillingAccountSelectorProps {\n accounts: CheckoutBillingAccount[];\n selectedAccountId: string | null;\n onSelect: (account: CheckoutBillingAccount) => void;\n isLoading?: boolean;\n disabled?: boolean;\n disabledMessage?: string;\n}\n\nexport const BillingAccountSelector: FC<BillingAccountSelectorProps> = ({\n accounts,\n selectedAccountId,\n onSelect,\n isLoading = false,\n disabled = false,\n disabledMessage,\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 [isOpen, setIsOpen] = useState(false);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n // Close dropdown when clicking outside\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {\n setIsOpen(false);\n }\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, []);\n\n // Close on escape key\n useEffect(() => {\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n setIsOpen(false);\n }\n };\n\n if (isOpen) {\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }\n }, [isOpen]);\n\n const selectedAccount = accounts.find((a) => a.id === selectedAccountId);\n\n if (isLoading) {\n return (\n <div className=\"space-y-2\">\n <div className=\"h-4 w-28 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-12 bg-bg-sunken animate-pulse rounded-lg\" />\n </div>\n );\n }\n\n if (accounts.length === 0) {\n return (\n <div className=\"rounded-lg border border-dashed border-border-subtle p-4 text-center\">\n <Building2 className=\"mx-auto size-6 text-text-secondary\" />\n <p className=\"mt-2 text-sm text-text-secondary\">No billing accounts found</p>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-2 relative\" ref={dropdownRef}>\n <div className=\"flex items-center justify-between\">\n <label className=\"text-sm font-medium text-text-primary\">\n {tr('billing.checkout.billingAccount', 'Billing Account')}\n </label>\n {disabled && (\n <span className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <Lock className=\"size-3\" />\n Locked\n </span>\n )}\n </div>\n\n {/* Dropdown trigger */}\n <button\n type=\"button\"\n onClick={() => !disabled && setIsOpen(!isOpen)}\n disabled={disabled}\n className={`\n w-full flex items-center justify-between gap-3 p-3 rounded-lg border\n transition-colors text-left\n ${\n disabled\n ? 'border-border-subtle bg-bg-sunken cursor-not-allowed opacity-80'\n : isOpen\n ? 'border-border-strong ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong'\n }\n `}\n >\n {selectedAccount ? (\n <div className=\"flex items-center gap-3 min-w-0 flex-1\">\n <div\n className={`flex size-8 shrink-0 items-center justify-center rounded-full ${\n disabled\n ? 'bg-bg-sunken text-text-secondary'\n : 'bg-[var(--color-accent-soft)] text-text-link'\n }`}\n >\n <Building2 className=\"size-4\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium text-text-primary truncate\">\n {selectedAccount.name}\n </span>\n {!selectedAccount.isActive && (\n <span className=\"text-xs px-1.5 py-0.5 rounded bg-status-error-bg-subtle text-status-error-text shrink-0\">\n Inactive\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary truncate\">{selectedAccount.email}</p>\n </div>\n <div className=\"shrink-0 text-right\">\n <div className=\"flex items-center gap-1 text-sm font-medium text-text-primary\">\n <CreditCard className=\"size-3.5 text-text-secondary\" />\n {formatCurrency(selectedAccount.creditAmount, 'CREDITS')}\n </div>\n </div>\n </div>\n ) : (\n <span className=\"text-text-secondary\">Select billing account</span>\n )}\n {disabled ? (\n <Lock className=\"size-4 shrink-0 text-text-secondary\" />\n ) : (\n <ChevronDown\n className={`size-4 shrink-0 text-text-secondary transition-transform ${\n isOpen ? 'rotate-180' : ''\n }`}\n />\n )}\n </button>\n\n {/* Disabled message */}\n {disabled && disabledMessage && (\n <p className=\"text-xs text-text-secondary\">{disabledMessage}</p>\n )}\n\n {/* Dropdown menu */}\n {isOpen && !disabled && (\n <div className=\"absolute left-0 right-0 z-50 mt-1 max-h-64 overflow-auto rounded-card border border-border-seam bg-bg-elevated shadow-elevation-3\">\n {accounts.map((account) => {\n const isSelected = account.id === selectedAccountId;\n return (\n <button\n key={account.id}\n type=\"button\"\n onClick={() => {\n onSelect(account);\n setIsOpen(false);\n }}\n className={`\n w-full flex items-center gap-3 p-3 text-left\n transition-colors\n ${isSelected ? 'bg-[var(--color-accent-soft)]' : 'hover:bg-bg-sunken'}\n `}\n >\n <div className=\"flex size-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-text-link\">\n <Building2 className=\"size-4\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium text-text-primary truncate\">{account.name}</span>\n {!account.isActive && (\n <span className=\"text-xs px-1.5 py-0.5 rounded bg-status-error-bg-subtle text-status-error-text shrink-0\">\n Inactive\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary truncate\">{account.email}</p>\n </div>\n <div className=\"shrink-0 flex items-center gap-2\">\n <div className=\"text-right\">\n <div className=\"flex items-center gap-1 text-sm font-medium text-text-primary\">\n <CreditCard className=\"size-3.5 text-text-secondary\" />\n {formatCurrency(account.creditAmount, 'CREDITS')}\n </div>\n </div>\n {isSelected && <Check className=\"size-4 text-text-link\" />}\n </div>\n </button>\n );\n })}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;AAoBA,IAAa,KAA2D,EACtE,aACA,sBACA,aACA,eAAY,IACZ,cAAW,IACX,yBACI;CACJ,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEnC,CAAC,GAAQ,KAAa,EAAS,GAAM,EACrC,IAAc,EAAuB,KAAK;AAehD,CAZA,QAAgB;EACd,IAAM,KAAsB,MAAsB;AAChD,GAAI,EAAY,WAAW,CAAC,EAAY,QAAQ,SAAS,EAAM,OAAe,IAC5E,EAAU,GAAM;;AAKpB,SADA,SAAS,iBAAiB,aAAa,EAAmB,QAC7C,SAAS,oBAAoB,aAAa,EAAmB;IACzE,EAAE,CAAC,EAGN,QAAgB;EACd,IAAM,KAAgB,MAAyB;AAC7C,GAAI,EAAM,QAAQ,YAChB,EAAU,GAAM;;AAIpB,MAAI,EAEF,QADA,SAAS,iBAAiB,WAAW,EAAa,QACrC,SAAS,oBAAoB,WAAW,EAAa;IAEnE,CAAC,EAAO,CAAC;CAEZ,IAAM,IAAkB,EAAS,MAAM,MAAM,EAAE,OAAO,EAAkB;AAoBxE,QAlBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD,EAAK,WAAU,+CAAgD,CAAA,EAC/D,kBAAC,OAAD,EAAK,WAAU,8CAA+C,CAAA,CAC1D;MAIN,EAAS,WAAW,IAEpB,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAW,WAAU,sCAAuC,CAAA,EAC5D,kBAAC,KAAD;GAAG,WAAU;aAAmC;GAA6B,CAAA,CACzE;MAKR,kBAAC,OAAD;EAAK,WAAU;EAAqB,KAAK;YAAzC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,SAAD;KAAO,WAAU;eACd,EAAG,mCAAmC,kBAAkB;KACnD,CAAA,EACP,KACC,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAAA,SAEtB;OAEL;;GAGN,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,CAAC,KAAY,EAAU,CAAC,EAAO;IACpC;IACV,WAAW;;;YAIP,IACI,oEACA,IACE,+DACA,kDACP;;cAbL,CAgBG,IACC,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OACE,WAAW,iEACT,IACI,qCACA;iBAGN,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;OAC5B,CAAA;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAgB;SACZ,CAAA,EACN,CAAC,EAAgB,YAChB,kBAAC,QAAD;SAAM,WAAU;mBAA0F;SAEnG,CAAA,CAEL;WACN,kBAAC,KAAD;QAAG,WAAU;kBAAwC,EAAgB;QAAU,CAAA,CAC3E;;MACN,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAY,WAAU,gCAAiC,CAAA,EACtD,EAAe,EAAgB,cAAc,UAAU,CACpD;;OACF,CAAA;MACF;SAEN,kBAAC,QAAD;KAAM,WAAU;eAAsB;KAA6B,CAAA,EAEpE,IACC,kBAAC,GAAD,EAAM,WAAU,uCAAwC,CAAA,GAExD,kBAAC,GAAD,EACE,WAAW,4DACT,IAAS,eAAe,MAE1B,CAAA,CAEG;;GAGR,KAAY,KACX,kBAAC,KAAD;IAAG,WAAU;cAA+B;IAAoB,CAAA;GAIjE,KAAU,CAAC,KACV,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAS,KAAK,MAAY;KACzB,IAAM,IAAa,EAAQ,OAAO;AAClC,YACE,kBAAC,UAAD;MAEE,MAAK;MACL,eAAe;AAEb,OADA,EAAS,EAAQ,EACjB,EAAU,GAAM;;MAElB,WAAW;;;oBAGP,IAAa,kCAAkC,qBAAqB;;gBAV1E;OAaE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA;QAC5B,CAAA;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBAA0C,EAAQ;UAAY,CAAA,EAC7E,CAAC,EAAQ,YACR,kBAAC,QAAD;UAAM,WAAU;oBAA0F;UAEnG,CAAA,CAEL;YACN,kBAAC,KAAD;SAAG,WAAU;mBAAwC,EAAQ;SAAU,CAAA,CACnE;;OACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD,EAAY,WAAU,gCAAiC,CAAA,EACtD,EAAe,EAAQ,cAAc,UAAU,CAC5C;;SACF,CAAA,EACL,KAAc,kBAAC,GAAD,EAAO,WAAU,yBAA0B,CAAA,CACtD;;OACC;QAnCF,EAAQ,GAmCN;MAEX;IACE,CAAA;GAEJ"}
1
+ {"version":3,"file":"BillingAccountSelector.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/BillingAccountSelector.tsx"],"sourcesContent":["/**\n * Billing Account Selector Component\n * Dropdown selector for choosing billing account in checkout\n */\n\nimport { type FC, useState, useRef, useEffect } from 'react';\nimport { Building2, CreditCard, ChevronDown, Check, Lock } from 'lucide-react';\nimport type { CheckoutBillingAccount } from '../types';\nimport { formatCurrency } from '../../../shared/utils/format';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface BillingAccountSelectorProps {\n accounts: CheckoutBillingAccount[];\n selectedAccountId: string | null;\n onSelect: (account: CheckoutBillingAccount) => void;\n isLoading?: boolean;\n disabled?: boolean;\n disabledMessage?: string;\n}\n\nexport const BillingAccountSelector: FC<BillingAccountSelectorProps> = ({\n accounts,\n selectedAccountId,\n onSelect,\n isLoading = false,\n disabled = false,\n disabledMessage,\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 [isOpen, setIsOpen] = useState(false);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n // Close dropdown when clicking outside\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {\n setIsOpen(false);\n }\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, []);\n\n // Close on escape key\n useEffect(() => {\n const handleEscape = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n setIsOpen(false);\n }\n };\n\n if (isOpen) {\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }\n }, [isOpen]);\n\n const selectedAccount = accounts.find((a) => a.id === selectedAccountId);\n\n if (isLoading) {\n return (\n <div className=\"space-y-2\">\n <div className=\"h-4 w-28 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"h-12 bg-bg-sunken animate-pulse rounded-lg\" />\n </div>\n );\n }\n\n if (accounts.length === 0) {\n return (\n <div className=\"rounded-lg border border-dashed border-border-subtle p-4 text-center\">\n <Building2 className=\"mx-auto size-6 text-text-secondary\" />\n <p className=\"mt-2 text-sm text-text-secondary\">No billing accounts found</p>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-2 relative\" ref={dropdownRef}>\n <div className=\"flex items-center justify-between\">\n <label className=\"text-sm font-medium text-text-primary\">\n {tr('billing.checkout.billingAccount', 'Billing Account')}\n </label>\n {disabled && (\n <span className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <Lock className=\"size-3\" />\n Locked\n </span>\n )}\n </div>\n\n {/* Dropdown trigger */}\n <button\n type=\"button\"\n onClick={() => !disabled && setIsOpen(!isOpen)}\n disabled={disabled}\n className={`\n w-full flex items-center justify-between gap-3 p-3 rounded-lg border\n transition-colors text-left\n ${\n disabled\n ? 'border-border-subtle bg-bg-sunken cursor-not-allowed opacity-80'\n : isOpen\n ? 'border-border-strong ring-1 ring-[var(--color-focus-ring)]'\n : 'border-border-subtle hover:border-border-strong'\n }\n `}\n >\n {selectedAccount ? (\n <div className=\"flex items-center gap-3 min-w-0 flex-1\">\n <div\n className={`flex size-8 shrink-0 items-center justify-center rounded-full ${\n disabled\n ? 'bg-bg-sunken text-text-secondary'\n : 'bg-[var(--color-accent-soft)] text-text-link'\n }`}\n >\n <Building2 className=\"size-4\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium text-text-primary truncate\">\n {selectedAccount.name}\n </span>\n {!selectedAccount.isActive && (\n <span className=\"text-xs px-1.5 py-0.5 rounded bg-status-error-bg-subtle text-status-error-text shrink-0\">\n Inactive\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary truncate\">{selectedAccount.email}</p>\n </div>\n <div className=\"shrink-0 text-right\">\n <div className=\"flex items-center gap-1 text-sm font-medium text-text-primary\">\n <CreditCard className=\"size-3.5 text-text-secondary\" />\n {formatCurrency(selectedAccount.creditAmount, 'CREDITS')}\n </div>\n </div>\n </div>\n ) : (\n <span className=\"text-text-secondary\">Select billing account</span>\n )}\n {disabled ? (\n <Lock className=\"size-4 shrink-0 text-text-secondary\" />\n ) : (\n <ChevronDown\n className={`size-4 shrink-0 text-text-secondary transition-transform ${\n isOpen ? 'rotate-180' : ''\n }`}\n />\n )}\n </button>\n\n {/* Disabled message */}\n {disabled && disabledMessage && (\n <p className=\"text-xs text-text-secondary\">{disabledMessage}</p>\n )}\n\n {/* Dropdown menu */}\n {isOpen && !disabled && (\n <div className=\"absolute left-0 right-0 z-50 mt-1 max-h-64 overflow-auto rounded-card border border-border-seam bg-bg-elevated shadow-elevation-3\">\n {accounts.map((account) => {\n const isSelected = account.id === selectedAccountId;\n return (\n <button\n key={account.id}\n type=\"button\"\n onClick={() => {\n onSelect(account);\n setIsOpen(false);\n }}\n className={`\n w-full flex items-center gap-3 p-3 text-left\n transition-colors\n ${isSelected ? 'bg-[var(--color-accent-soft)]' : 'hover:bg-bg-sunken'}\n `}\n >\n <div className=\"flex size-8 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)] text-text-link\">\n <Building2 className=\"size-4\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium text-text-primary truncate\">{account.name}</span>\n {!account.isActive && (\n <span className=\"text-xs px-1.5 py-0.5 rounded bg-status-error-bg-subtle text-status-error-text shrink-0\">\n Inactive\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary truncate\">{account.email}</p>\n </div>\n <div className=\"shrink-0 flex items-center gap-2\">\n <div className=\"text-right\">\n <div className=\"flex items-center gap-1 text-sm font-medium text-text-primary\">\n <CreditCard className=\"size-3.5 text-text-secondary\" />\n {formatCurrency(account.creditAmount, 'CREDITS')}\n </div>\n </div>\n {isSelected && <Check className=\"size-4 text-text-link\" />}\n </div>\n </button>\n );\n })}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;AAoBA,IAAa,KAA2D,EACtE,aACA,sBACA,aACA,eAAY,IACZ,cAAW,IACX,yBACI;CACJ,IAAM,EAAE,SAAM,EAAQ,GAChB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,CAAG;EACxB,OAAO,MAAe,IAAM,IAAW;CACzC,GACM,CAAC,GAAQ,KAAa,EAAS,EAAK,GACpC,IAAc,EAAuB,IAAI;CAe/C,AAZA,QAAgB;EACd,IAAM,KAAsB,MAAsB;GAChD,AAAI,EAAY,WAAW,CAAC,EAAY,QAAQ,SAAS,EAAM,MAAc,KAC3E,EAAU,EAAK;EAEnB;EAGA,OADA,SAAS,iBAAiB,aAAa,CAAkB,SAC5C,SAAS,oBAAoB,aAAa,CAAkB;CAC3E,GAAG,CAAC,CAAC,GAGL,QAAgB;EACd,IAAM,KAAgB,MAAyB;GAC7C,AAAI,EAAM,QAAQ,YAChB,EAAU,EAAK;EAEnB;EAEA,IAAI,GAEF,OADA,SAAS,iBAAiB,WAAW,CAAY,SACpC,SAAS,oBAAoB,WAAW,CAAY;CAErE,GAAG,CAAC,CAAM,CAAC;CAEX,IAAM,IAAkB,EAAS,MAAM,MAAM,EAAE,OAAO,CAAiB;CAoBvE,OAlBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD,EAAK,WAAU,8CAA+C,CAAA,GAC9D,kBAAC,OAAD,EAAK,WAAU,6CAA8C,CAAA,CAC1D;MAIL,EAAS,WAAW,IAEpB,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAW,WAAU,qCAAsC,CAAA,GAC3D,kBAAC,KAAD;GAAG,WAAU;aAAmC;EAA4B,CAAA,CACzE;MAKP,kBAAC,OAAD;EAAK,WAAU;EAAqB,KAAK;YAAzC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,SAAD;KAAO,WAAU;eACd,EAAG,mCAAmC,iBAAiB;IACnD,CAAA,GACN,KACC,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACE,kBAAC,GAAD,EAAM,WAAU,SAAU,CAAA,GAAC,QAEvB;MAEL;;GAGL,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,CAAC,KAAY,EAAU,CAAC,CAAM;IACnC;IACV,WAAW;;;YAIP,IACI,oEACA,IACE,+DACA,kDACP;;cAbL,CAgBG,IACC,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OACE,WAAW,iEACT,IACI,qCACA;iBAGN,kBAAC,GAAD,EAAW,WAAU,SAAU,CAAA;MAC5B,CAAA;MACL,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAgB;QACb,CAAA,GACL,CAAC,EAAgB,YAChB,kBAAC,QAAD;SAAM,WAAU;mBAA0F;QAEpG,CAAA,CAEL;WACL,kBAAC,KAAD;QAAG,WAAU;kBAAwC,EAAgB;OAAS,CAAA,CAC3E;;MACL,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAY,WAAU,+BAAgC,CAAA,GACrD,EAAe,EAAgB,cAAc,SAAS,CACpD;;MACF,CAAA;KACF;SAEL,kBAAC,QAAD;KAAM,WAAU;eAAsB;IAA4B,CAAA,GAEnE,IACC,kBAAC,GAAD,EAAM,WAAU,sCAAuC,CAAA,IAEvD,kBAAC,GAAD,EACE,WAAW,4DACT,IAAS,eAAe,KAE3B,CAAA,CAEG;;GAGP,KAAY,KACX,kBAAC,KAAD;IAAG,WAAU;cAA+B;GAAmB,CAAA;GAIhE,KAAU,CAAC,KACV,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAS,KAAK,MAAY;KACzB,IAAM,IAAa,EAAQ,OAAO;KAClC,OACE,kBAAC,UAAD;MAEE,MAAK;MACL,eAAe;OAEb,AADA,EAAS,CAAO,GAChB,EAAU,EAAK;MACjB;MACA,WAAW;;;oBAGP,IAAa,kCAAkC,qBAAqB;;gBAV1E;OAaE,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD,EAAW,WAAU,SAAU,CAAA;OAC5B,CAAA;OACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBAA0C,EAAQ;SAAW,CAAA,GAC5E,CAAC,EAAQ,YACR,kBAAC,QAAD;UAAM,WAAU;oBAA0F;SAEpG,CAAA,CAEL;YACL,kBAAC,KAAD;SAAG,WAAU;mBAAwC,EAAQ;QAAS,CAAA,CACnE;;OACL,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD,EAAY,WAAU,+BAAgC,CAAA,GACrD,EAAe,EAAQ,cAAc,SAAS,CAC5C;;QACF,CAAA,GACJ,KAAc,kBAAC,GAAD,EAAO,WAAU,wBAAyB,CAAA,CACtD;;MACC;QAnCD,EAAQ,EAmCP;IAEZ,CAAC;GACE,CAAA;EAEJ;;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"CompleteBillingAccountModal.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/CompleteBillingAccountModal.tsx"],"sourcesContent":["/**\n * Modal shown at checkout when the selected billing account is missing\n * a billing address or contact number — fields that can't be auto-filled\n * during account creation but are required by payment providers.\n */\n\nimport { type FC, useState } from 'react';\nimport { MapPin, Phone, Loader2, X } from 'lucide-react';\nimport {\n useSetBillingAddressMutation,\n useUpdateBillingAccountMutation,\n} from '../../../../generated/global-operations';\n\nconst COUNTRY_OPTIONS = [\n { value: 'IN', label: 'India' },\n { value: 'US', label: 'United States' },\n { value: 'GB', label: 'United Kingdom' },\n { value: 'AU', label: 'Australia' },\n { value: 'CA', label: 'Canada' },\n { value: 'SG', label: 'Singapore' },\n { value: 'DE', label: 'Germany' },\n { value: 'FR', label: 'France' },\n { value: 'AE', label: 'UAE' },\n];\n\ninterface FormState {\n contactNumber: string;\n line1: string;\n line2: string;\n city: string;\n state: string;\n postalCode: string;\n country: string;\n}\n\ninterface FormErrors {\n contactNumber?: string;\n line1?: string;\n city?: string;\n postalCode?: string;\n country?: string;\n}\n\ninterface Props {\n billingAccountId: string;\n /** Whether contactNumber is already filled on the account */\n hasPhone: boolean;\n /** Whether a billing address already exists (with line1 + postalCode) */\n hasAddress: boolean;\n onCompleted: () => void;\n onCancel: () => void;\n}\n\n/** Returns true when a billing account has everything required for checkout. */\nexport function isBillingAccountComplete(account: {\n contactNumber?: string | null;\n billingAddresses?: Array<{\n line1?: string | null;\n city?: string | null;\n postalCode?: string | null;\n country?: string | null;\n }> | null;\n}): boolean {\n const hasPhone = Boolean(account.contactNumber?.trim());\n const primaryAddress = account.billingAddresses?.[0];\n const hasAddress = Boolean(\n primaryAddress?.line1?.trim() &&\n primaryAddress?.city?.trim() &&\n primaryAddress?.postalCode?.trim() &&\n primaryAddress?.country?.trim()\n );\n return hasPhone && hasAddress;\n}\n\nexport const CompleteBillingAccountModal: FC<Props> = ({\n billingAccountId,\n hasPhone,\n hasAddress,\n onCompleted,\n onCancel,\n}) => {\n const [form, setForm] = useState<FormState>({\n contactNumber: '',\n line1: '',\n line2: '',\n city: '',\n state: '',\n postalCode: '',\n country: 'IN',\n });\n const [errors, setErrors] = useState<FormErrors>({});\n const [submitting, setSubmitting] = useState(false);\n const [serverError, setServerError] = useState<string | null>(null);\n\n const [setBillingAddress] = useSetBillingAddressMutation();\n const [updateBillingAccount] = useUpdateBillingAccountMutation();\n\n const set = (field: keyof FormState, value: string) =>\n setForm((prev) => ({ ...prev, [field]: value }));\n\n const validate = (): boolean => {\n const e: FormErrors = {};\n if (!hasPhone && !form.contactNumber.trim()) {\n e.contactNumber = 'Phone number is required for payment processing';\n }\n if (!hasAddress) {\n if (!form.line1.trim()) e.line1 = 'Street address is required';\n if (!form.city.trim()) e.city = 'City is required';\n if (!form.postalCode.trim()) e.postalCode = 'Postal code is required';\n if (!form.country.trim()) e.country = 'Country is required';\n }\n setErrors(e);\n return Object.keys(e).length === 0;\n };\n\n const handleSubmit = async () => {\n if (!validate()) return;\n setSubmitting(true);\n setServerError(null);\n\n try {\n const ops: Promise<unknown>[] = [];\n\n if (!hasPhone && form.contactNumber.trim()) {\n ops.push(\n updateBillingAccount({\n variables: {\n input: {\n id: billingAccountId,\n contactNumber: form.contactNumber.trim(),\n },\n },\n })\n );\n }\n\n if (!hasAddress) {\n ops.push(\n setBillingAddress({\n variables: {\n input: {\n billingAccountId,\n line1: form.line1.trim(),\n ...(form.line2.trim() ? { line2: form.line2.trim() } : {}),\n city: form.city.trim(),\n ...(form.state.trim() ? { state: form.state.trim() } : {}),\n postalCode: form.postalCode.trim(),\n country: form.country,\n },\n },\n })\n );\n }\n\n await Promise.all(ops);\n onCompleted();\n } catch {\n setServerError('Failed to save billing details. Please try again.');\n } finally {\n setSubmitting(false);\n }\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=\"bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] w-full max-w-lg max-h-[90vh] overflow-y-auto\">\n {/* Header */}\n <div className=\"flex items-start justify-between p-6 border-b border-border-subtle\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Complete billing details</h2>\n <p className=\"text-sm text-text-secondary mt-1\">\n We need a few more details before processing your payment.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"p-2 text-text-secondary hover:text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n <div className=\"p-6 space-y-6\">\n {serverError && (\n <div className=\"p-3 rounded-lg bg-status-error-bg-subtle border border-status-error-border text-sm text-status-error-text\">\n {serverError}\n </div>\n )}\n\n {/* Phone */}\n {!hasPhone && (\n <section className=\"space-y-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Phone className=\"size-4 text-text-secondary\" />\n Contact number\n </div>\n <div>\n <input\n type=\"tel\"\n value={form.contactNumber}\n onChange={(e) => set('contactNumber', e.target.value)}\n placeholder=\"+91 98765 43210\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.contactNumber ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.contactNumber && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.contactNumber}</p>\n )}\n </div>\n </section>\n )}\n\n {/* Address */}\n {!hasAddress && (\n <section className=\"space-y-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-text-primary\">\n <MapPin className=\"size-4 text-text-secondary\" />\n Billing address\n </div>\n\n <div className=\"space-y-3\">\n {/* Country */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Country</label>\n <select\n value={form.country}\n onChange={(e) => set('country', e.target.value)}\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.country ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n >\n {COUNTRY_OPTIONS.map((c) => (\n <option key={c.value} value={c.value}>\n {c.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Line 1 */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Street address</label>\n <input\n type=\"text\"\n value={form.line1}\n onChange={(e) => set('line1', e.target.value)}\n placeholder=\"123 Main St\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.line1 ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.line1 && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.line1}</p>\n )}\n </div>\n\n {/* Line 2 */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">\n Apartment, suite, etc. <span className=\"text-text-muted\">(optional)</span>\n </label>\n <input\n type=\"text\"\n value={form.line2}\n onChange={(e) => set('line2', e.target.value)}\n placeholder=\"Apt 4B\"\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n\n {/* City + State */}\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">City</label>\n <input\n type=\"text\"\n value={form.city}\n onChange={(e) => set('city', e.target.value)}\n placeholder=\"Mumbai\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.city ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.city && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.city}</p>\n )}\n </div>\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">\n State <span className=\"text-text-muted\">(optional)</span>\n </label>\n <input\n type=\"text\"\n value={form.state}\n onChange={(e) => set('state', e.target.value)}\n placeholder=\"Maharashtra\"\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Postal code */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Postal code</label>\n <input\n type=\"text\"\n value={form.postalCode}\n onChange={(e) => set('postalCode', e.target.value)}\n placeholder=\"400001\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.postalCode ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.postalCode && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.postalCode}</p>\n )}\n </div>\n </div>\n </section>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 p-6 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={submitting}\n className=\"px-4 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={submitting}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bgHover rounded-lg transition-colors disabled:opacity-50\"\n >\n {submitting && <Loader2 className=\"size-4 animate-spin\" />}\n Save and continue\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"CompleteBillingAccountModal.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/CompleteBillingAccountModal.tsx"],"sourcesContent":["/**\n * Modal shown at checkout when the selected billing account is missing\n * a billing address or contact number — fields that can't be auto-filled\n * during account creation but are required by payment providers.\n */\n\nimport { type FC, useState } from 'react';\nimport { MapPin, Phone, Loader2, X } from 'lucide-react';\nimport {\n useSetBillingAddressMutation,\n useUpdateBillingAccountMutation,\n} from '../../../../generated/global-operations';\n\nconst COUNTRY_OPTIONS = [\n { value: 'IN', label: 'India' },\n { value: 'US', label: 'United States' },\n { value: 'GB', label: 'United Kingdom' },\n { value: 'AU', label: 'Australia' },\n { value: 'CA', label: 'Canada' },\n { value: 'SG', label: 'Singapore' },\n { value: 'DE', label: 'Germany' },\n { value: 'FR', label: 'France' },\n { value: 'AE', label: 'UAE' },\n];\n\ninterface FormState {\n contactNumber: string;\n line1: string;\n line2: string;\n city: string;\n state: string;\n postalCode: string;\n country: string;\n}\n\ninterface FormErrors {\n contactNumber?: string;\n line1?: string;\n city?: string;\n postalCode?: string;\n country?: string;\n}\n\ninterface Props {\n billingAccountId: string;\n /** Whether contactNumber is already filled on the account */\n hasPhone: boolean;\n /** Whether a billing address already exists (with line1 + postalCode) */\n hasAddress: boolean;\n onCompleted: () => void;\n onCancel: () => void;\n}\n\n/** Returns true when a billing account has everything required for checkout. */\nexport function isBillingAccountComplete(account: {\n contactNumber?: string | null;\n billingAddresses?: Array<{\n line1?: string | null;\n city?: string | null;\n postalCode?: string | null;\n country?: string | null;\n }> | null;\n}): boolean {\n const hasPhone = Boolean(account.contactNumber?.trim());\n const primaryAddress = account.billingAddresses?.[0];\n const hasAddress = Boolean(\n primaryAddress?.line1?.trim() &&\n primaryAddress?.city?.trim() &&\n primaryAddress?.postalCode?.trim() &&\n primaryAddress?.country?.trim()\n );\n return hasPhone && hasAddress;\n}\n\nexport const CompleteBillingAccountModal: FC<Props> = ({\n billingAccountId,\n hasPhone,\n hasAddress,\n onCompleted,\n onCancel,\n}) => {\n const [form, setForm] = useState<FormState>({\n contactNumber: '',\n line1: '',\n line2: '',\n city: '',\n state: '',\n postalCode: '',\n country: 'IN',\n });\n const [errors, setErrors] = useState<FormErrors>({});\n const [submitting, setSubmitting] = useState(false);\n const [serverError, setServerError] = useState<string | null>(null);\n\n const [setBillingAddress] = useSetBillingAddressMutation();\n const [updateBillingAccount] = useUpdateBillingAccountMutation();\n\n const set = (field: keyof FormState, value: string) =>\n setForm((prev) => ({ ...prev, [field]: value }));\n\n const validate = (): boolean => {\n const e: FormErrors = {};\n if (!hasPhone && !form.contactNumber.trim()) {\n e.contactNumber = 'Phone number is required for payment processing';\n }\n if (!hasAddress) {\n if (!form.line1.trim()) e.line1 = 'Street address is required';\n if (!form.city.trim()) e.city = 'City is required';\n if (!form.postalCode.trim()) e.postalCode = 'Postal code is required';\n if (!form.country.trim()) e.country = 'Country is required';\n }\n setErrors(e);\n return Object.keys(e).length === 0;\n };\n\n const handleSubmit = async () => {\n if (!validate()) return;\n setSubmitting(true);\n setServerError(null);\n\n try {\n const ops: Promise<unknown>[] = [];\n\n if (!hasPhone && form.contactNumber.trim()) {\n ops.push(\n updateBillingAccount({\n variables: {\n input: {\n id: billingAccountId,\n contactNumber: form.contactNumber.trim(),\n },\n },\n })\n );\n }\n\n if (!hasAddress) {\n ops.push(\n setBillingAddress({\n variables: {\n input: {\n billingAccountId,\n line1: form.line1.trim(),\n ...(form.line2.trim() ? { line2: form.line2.trim() } : {}),\n city: form.city.trim(),\n ...(form.state.trim() ? { state: form.state.trim() } : {}),\n postalCode: form.postalCode.trim(),\n country: form.country,\n },\n },\n })\n );\n }\n\n await Promise.all(ops);\n onCompleted();\n } catch {\n setServerError('Failed to save billing details. Please try again.');\n } finally {\n setSubmitting(false);\n }\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=\"bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] w-full max-w-lg max-h-[90vh] overflow-y-auto\">\n {/* Header */}\n <div className=\"flex items-start justify-between p-6 border-b border-border-subtle\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Complete billing details</h2>\n <p className=\"text-sm text-text-secondary mt-1\">\n We need a few more details before processing your payment.\n </p>\n </div>\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"p-2 text-text-secondary hover:text-text-primary rounded-button hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n\n <div className=\"p-6 space-y-6\">\n {serverError && (\n <div className=\"p-3 rounded-lg bg-status-error-bg-subtle border border-status-error-border text-sm text-status-error-text\">\n {serverError}\n </div>\n )}\n\n {/* Phone */}\n {!hasPhone && (\n <section className=\"space-y-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-text-primary\">\n <Phone className=\"size-4 text-text-secondary\" />\n Contact number\n </div>\n <div>\n <input\n type=\"tel\"\n value={form.contactNumber}\n onChange={(e) => set('contactNumber', e.target.value)}\n placeholder=\"+91 98765 43210\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.contactNumber ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.contactNumber && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.contactNumber}</p>\n )}\n </div>\n </section>\n )}\n\n {/* Address */}\n {!hasAddress && (\n <section className=\"space-y-3\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-text-primary\">\n <MapPin className=\"size-4 text-text-secondary\" />\n Billing address\n </div>\n\n <div className=\"space-y-3\">\n {/* Country */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Country</label>\n <select\n value={form.country}\n onChange={(e) => set('country', e.target.value)}\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.country ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n >\n {COUNTRY_OPTIONS.map((c) => (\n <option key={c.value} value={c.value}>\n {c.label}\n </option>\n ))}\n </select>\n </div>\n\n {/* Line 1 */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Street address</label>\n <input\n type=\"text\"\n value={form.line1}\n onChange={(e) => set('line1', e.target.value)}\n placeholder=\"123 Main St\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.line1 ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.line1 && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.line1}</p>\n )}\n </div>\n\n {/* Line 2 */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">\n Apartment, suite, etc. <span className=\"text-text-muted\">(optional)</span>\n </label>\n <input\n type=\"text\"\n value={form.line2}\n onChange={(e) => set('line2', e.target.value)}\n placeholder=\"Apt 4B\"\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n\n {/* City + State */}\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">City</label>\n <input\n type=\"text\"\n value={form.city}\n onChange={(e) => set('city', e.target.value)}\n placeholder=\"Mumbai\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.city ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.city && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.city}</p>\n )}\n </div>\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">\n State <span className=\"text-text-muted\">(optional)</span>\n </label>\n <input\n type=\"text\"\n value={form.state}\n onChange={(e) => set('state', e.target.value)}\n placeholder=\"Maharashtra\"\n className=\"w-full px-3 py-2 text-sm border border-border-subtle rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Postal code */}\n <div>\n <label className=\"block text-xs text-text-secondary mb-1\">Postal code</label>\n <input\n type=\"text\"\n value={form.postalCode}\n onChange={(e) => set('postalCode', e.target.value)}\n placeholder=\"400001\"\n className={`w-full px-3 py-2 text-sm border rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n errors.postalCode ? 'border-status-error-border' : 'border-border-subtle'\n }`}\n />\n {errors.postalCode && (\n <p className=\"mt-1 text-xs text-status-error-text\">{errors.postalCode}</p>\n )}\n </div>\n </div>\n </section>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 p-6 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={submitting}\n className=\"px-4 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:bg-bg-sunken rounded-lg transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={submitting}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg hover:bg-action-primary-bgHover rounded-lg transition-colors disabled:opacity-50\"\n >\n {submitting && <Loader2 className=\"size-4 animate-spin\" />}\n Save and continue\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CreditsAmountInput.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/CreditsAmountInput.tsx"],"sourcesContent":["/**\n * Credits Amount Input Component\n * Compact input for specifying how many credits to purchase\n */\n\nimport { type FC, useState, useCallback } from 'react';\nimport { Coins, Minus, Plus } from 'lucide-react';\nimport { formatCurrency } from '../../../shared/utils/format';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface CreditsAmountInputProps {\n value: number;\n onChange: (value: number) => void;\n minAmount?: number;\n maxAmount?: number;\n costPerCredit?: number;\n currency?: string;\n disabled?: boolean;\n}\n\nconst QUICK_AMOUNTS = [100, 500, 1000, 5000];\n\nexport const CreditsAmountInput: FC<CreditsAmountInputProps> = ({\n value,\n onChange,\n minAmount = 1,\n maxAmount = 100000,\n costPerCredit = 0.01,\n currency = 'USD',\n disabled = 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 [inputValue, setInputValue] = useState(value.toString());\n\n const handleInputChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const raw = e.target.value.replace(/[^0-9]/g, '');\n setInputValue(raw);\n\n const parsed = parseInt(raw, 10);\n if (!isNaN(parsed) && parsed >= minAmount && parsed <= maxAmount) {\n onChange(parsed);\n }\n },\n [onChange, minAmount, maxAmount]\n );\n\n const handleBlur = useCallback(() => {\n let parsed = parseInt(inputValue, 10);\n if (isNaN(parsed) || parsed < minAmount) {\n parsed = minAmount;\n } else if (parsed > maxAmount) {\n parsed = maxAmount;\n }\n setInputValue(parsed.toString());\n onChange(parsed);\n }, [inputValue, onChange, minAmount, maxAmount]);\n\n const handleIncrement = useCallback(\n (amount: number) => {\n const newValue = Math.min(value + amount, maxAmount);\n setInputValue(newValue.toString());\n onChange(newValue);\n },\n [value, onChange, maxAmount]\n );\n\n const handleDecrement = useCallback(\n (amount: number) => {\n const newValue = Math.max(value - amount, minAmount);\n setInputValue(newValue.toString());\n onChange(newValue);\n },\n [value, onChange, minAmount]\n );\n\n const handleQuickAmount = useCallback(\n (amount: number) => {\n setInputValue(amount.toString());\n onChange(amount);\n },\n [onChange]\n );\n\n const totalCost = value * costPerCredit;\n\n return (\n <div className=\"space-y-3\">\n {/* Header - compact */}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Coins className=\"size-4 text-status-warning-text\" />\n <span className=\"font-medium text-text-primary\">\n {tr('billing.checkout.creditAmount', 'Credit Amount')}\n </span>\n </div>\n <span className=\"text-sm text-text-secondary\">\n {formatCurrency(costPerCredit, currency)}/credit\n </span>\n </div>\n\n {/* Input row - compact */}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => handleDecrement(100)}\n disabled={disabled || value <= minAmount}\n className=\"p-2 rounded-button border border-border-subtle hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed\"\n aria-label=\"Decrease by 100\"\n >\n <Minus className=\"size-4\" />\n </button>\n\n <div className=\"relative flex-1\">\n <input\n type=\"text\"\n inputMode=\"numeric\"\n aria-label={tr('billing.checkout.creditAmountInput', 'Credit amount')}\n value={inputValue}\n onChange={handleInputChange}\n onBlur={handleBlur}\n disabled={disabled}\n className=\"w-full px-4 py-2 text-center text-lg font-semibold\n rounded-button border border-border-subtle bg-bg-surface text-text-primary\n focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:border-border-strong\n disabled:opacity-50 disabled:cursor-not-allowed\"\n />\n </div>\n\n <button\n type=\"button\"\n onClick={() => handleIncrement(100)}\n disabled={disabled || value >= maxAmount}\n className=\"p-2 rounded-button border border-border-subtle hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed\"\n aria-label=\"Increase by 100\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n\n {/* Quick amount buttons - compact row */}\n <div className=\"flex gap-2\">\n {QUICK_AMOUNTS.map((amount) => (\n <button\n key={amount}\n type=\"button\"\n onClick={() => handleQuickAmount(amount)}\n disabled={disabled}\n className={`\n flex-1 px-2 py-1.5 rounded-button border text-sm font-medium transition-colors\n ${\n value === amount\n ? 'border-border-strong bg-[var(--color-accent-soft)] text-text-link'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken text-text-primary'\n }\n disabled:opacity-50 disabled:cursor-not-allowed\n `}\n >\n {amount.toLocaleString()}\n </button>\n ))}\n </div>\n\n {/* Cost summary - compact inline */}\n <div className=\"flex items-center justify-between p-3 rounded-button bg-status-warning-bg-subtle border border-border-subtle\">\n <span className=\"text-sm text-status-warning-text\">{value.toLocaleString()} credits</span>\n <span className=\"font-semibold text-text-primary\">\n {formatCurrency(totalCost, currency)}\n </span>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"CreditsAmountInput.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/CreditsAmountInput.tsx"],"sourcesContent":["/**\n * Credits Amount Input Component\n * Compact input for specifying how many credits to purchase\n */\n\nimport { type FC, useState, useCallback } from 'react';\nimport { Coins, Minus, Plus } from 'lucide-react';\nimport { formatCurrency } from '../../../shared/utils/format';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface CreditsAmountInputProps {\n value: number;\n onChange: (value: number) => void;\n minAmount?: number;\n maxAmount?: number;\n costPerCredit?: number;\n currency?: string;\n disabled?: boolean;\n}\n\nconst QUICK_AMOUNTS = [100, 500, 1000, 5000];\n\nexport const CreditsAmountInput: FC<CreditsAmountInputProps> = ({\n value,\n onChange,\n minAmount = 1,\n maxAmount = 100000,\n costPerCredit = 0.01,\n currency = 'USD',\n disabled = 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 [inputValue, setInputValue] = useState(value.toString());\n\n const handleInputChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const raw = e.target.value.replace(/[^0-9]/g, '');\n setInputValue(raw);\n\n const parsed = parseInt(raw, 10);\n if (!isNaN(parsed) && parsed >= minAmount && parsed <= maxAmount) {\n onChange(parsed);\n }\n },\n [onChange, minAmount, maxAmount]\n );\n\n const handleBlur = useCallback(() => {\n let parsed = parseInt(inputValue, 10);\n if (isNaN(parsed) || parsed < minAmount) {\n parsed = minAmount;\n } else if (parsed > maxAmount) {\n parsed = maxAmount;\n }\n setInputValue(parsed.toString());\n onChange(parsed);\n }, [inputValue, onChange, minAmount, maxAmount]);\n\n const handleIncrement = useCallback(\n (amount: number) => {\n const newValue = Math.min(value + amount, maxAmount);\n setInputValue(newValue.toString());\n onChange(newValue);\n },\n [value, onChange, maxAmount]\n );\n\n const handleDecrement = useCallback(\n (amount: number) => {\n const newValue = Math.max(value - amount, minAmount);\n setInputValue(newValue.toString());\n onChange(newValue);\n },\n [value, onChange, minAmount]\n );\n\n const handleQuickAmount = useCallback(\n (amount: number) => {\n setInputValue(amount.toString());\n onChange(amount);\n },\n [onChange]\n );\n\n const totalCost = value * costPerCredit;\n\n return (\n <div className=\"space-y-3\">\n {/* Header - compact */}\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Coins className=\"size-4 text-status-warning-text\" />\n <span className=\"font-medium text-text-primary\">\n {tr('billing.checkout.creditAmount', 'Credit Amount')}\n </span>\n </div>\n <span className=\"text-sm text-text-secondary\">\n {formatCurrency(costPerCredit, currency)}/credit\n </span>\n </div>\n\n {/* Input row - compact */}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => handleDecrement(100)}\n disabled={disabled || value <= minAmount}\n className=\"p-2 rounded-button border border-border-subtle hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed\"\n aria-label=\"Decrease by 100\"\n >\n <Minus className=\"size-4\" />\n </button>\n\n <div className=\"relative flex-1\">\n <input\n type=\"text\"\n inputMode=\"numeric\"\n aria-label={tr('billing.checkout.creditAmountInput', 'Credit amount')}\n value={inputValue}\n onChange={handleInputChange}\n onBlur={handleBlur}\n disabled={disabled}\n className=\"w-full px-4 py-2 text-center text-lg font-semibold\n rounded-button border border-border-subtle bg-bg-surface text-text-primary\n focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:border-border-strong\n disabled:opacity-50 disabled:cursor-not-allowed\"\n />\n </div>\n\n <button\n type=\"button\"\n onClick={() => handleIncrement(100)}\n disabled={disabled || value >= maxAmount}\n className=\"p-2 rounded-button border border-border-subtle hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed\"\n aria-label=\"Increase by 100\"\n >\n <Plus className=\"size-4\" />\n </button>\n </div>\n\n {/* Quick amount buttons - compact row */}\n <div className=\"flex gap-2\">\n {QUICK_AMOUNTS.map((amount) => (\n <button\n key={amount}\n type=\"button\"\n onClick={() => handleQuickAmount(amount)}\n disabled={disabled}\n className={`\n flex-1 px-2 py-1.5 rounded-button border text-sm font-medium transition-colors\n ${\n value === amount\n ? 'border-border-strong bg-[var(--color-accent-soft)] text-text-link'\n : 'border-border-subtle hover:border-border-strong hover:bg-bg-sunken text-text-primary'\n }\n disabled:opacity-50 disabled:cursor-not-allowed\n `}\n >\n {amount.toLocaleString()}\n </button>\n ))}\n </div>\n\n {/* Cost summary - compact inline */}\n <div className=\"flex items-center justify-between p-3 rounded-button bg-status-warning-bg-subtle border border-border-subtle\">\n <span className=\"text-sm text-status-warning-text\">{value.toLocaleString()} credits</span>\n <span className=\"font-semibold text-text-primary\">\n {formatCurrency(totalCost, currency)}\n </span>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;AAoBA,IAAM,IAAgB;CAAC;CAAK;CAAK;CAAM;AAAI,GAE9B,KAAmD,EAC9D,UACA,aACA,eAAY,GACZ,eAAY,KACZ,mBAAgB,KAChB,cAAW,OACX,cAAW,SACP;CACJ,IAAM,EAAE,SAAM,EAAQ,GAChB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,CAAG;EACxB,OAAO,MAAe,IAAM,IAAW;CACzC,GACM,CAAC,GAAY,KAAiB,EAAS,EAAM,SAAS,CAAC,GAEvD,IAAoB,GACvB,MAA2C;EAC1C,IAAM,IAAM,EAAE,OAAO,MAAM,QAAQ,WAAW,EAAE;EAChD,EAAc,CAAG;EAEjB,IAAM,IAAS,SAAS,GAAK,EAAE;EAC/B,AAAI,CAAC,MAAM,CAAM,KAAK,KAAU,KAAa,KAAU,KACrD,EAAS,CAAM;CAEnB,GACA;EAAC;EAAU;EAAW;CAAS,CACjC,GAEM,IAAa,QAAkB;EACnC,IAAI,IAAS,SAAS,GAAY,EAAE;EAOpC,AANI,MAAM,CAAM,KAAK,IAAS,IAC5B,IAAS,IACA,IAAS,MAClB,IAAS,IAEX,EAAc,EAAO,SAAS,CAAC,GAC/B,EAAS,CAAM;CACjB,GAAG;EAAC;EAAY;EAAU;EAAW;CAAS,CAAC,GAEzC,IAAkB,GACrB,MAAmB;EAClB,IAAM,IAAW,KAAK,IAAI,IAAQ,GAAQ,CAAS;EAEnD,AADA,EAAc,EAAS,SAAS,CAAC,GACjC,EAAS,CAAQ;CACnB,GACA;EAAC;EAAO;EAAU;CAAS,CAC7B,GAEM,IAAkB,GACrB,MAAmB;EAClB,IAAM,IAAW,KAAK,IAAI,IAAQ,GAAQ,CAAS;EAEnD,AADA,EAAc,EAAS,SAAS,CAAC,GACjC,EAAS,CAAQ;CACnB,GACA;EAAC;EAAO;EAAU;CAAS,CAC7B,GAEM,IAAoB,GACvB,MAAmB;EAElB,AADA,EAAc,EAAO,SAAS,CAAC,GAC/B,EAAS,CAAM;CACjB,GACA,CAAC,CAAQ,CACX,GAEM,IAAY,IAAQ;CAE1B,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAO,WAAU,kCAAmC,CAAA,GACpD,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAG,iCAAiC,eAAe;KAChD,CAAA,CACH;QACL,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACG,EAAe,GAAe,CAAQ,GAAE,SACrC;MACH;;GAGL,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAgB,GAAG;MAClC,UAAU,KAAY,KAAS;MAC/B,WAAU;MACV,cAAW;gBAEX,kBAAC,GAAD,EAAO,WAAU,SAAU,CAAA;KACrB,CAAA;KAER,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,SAAD;OACE,MAAK;OACL,WAAU;OACV,cAAY,EAAG,sCAAsC,eAAe;OACpE,OAAO;OACP,UAAU;OACV,QAAQ;OACE;OACV,WAAU;MAIX,CAAA;KACE,CAAA;KAEL,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAgB,GAAG;MAClC,UAAU,KAAY,KAAS;MAC/B,WAAU;MACV,cAAW;gBAEX,kBAAC,GAAD,EAAM,WAAU,SAAU,CAAA;KACpB,CAAA;IACL;;GAGL,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAc,KAAK,MAClB,kBAAC,UAAD;KAEE,MAAK;KACL,eAAe,EAAkB,CAAM;KAC7B;KACV,WAAW;;gBAGP,MAAU,IACN,sEACA,uFACL;;;eAIF,EAAO,eAAe;IACjB,GAfD,CAeC,CACT;GACE,CAAA;GAGL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAAoD,EAAM,eAAe,GAAE,UAAc;QACzF,kBAAC,QAAD;KAAM,WAAU;eACb,EAAe,GAAW,CAAQ;IAC/B,CAAA,CACH;;EACF;;AAET"}
@@ -1 +1 @@
1
- {"version":3,"file":"NativeCheckoutView.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/NativeCheckoutView.tsx"],"sourcesContent":["/**\n * Native IAP Checkout View\n *\n * Replaces the Stripe/Razorpay checkout UI when running inside a Capacitor\n * native app (iOS / Android). Shows an order summary then triggers the native\n * payment sheet on confirmation.\n *\n * After a successful purchase the backend validates the receipt/token with\n * Apple/Google servers and activates the subscription, credits, or add-on.\n */\n\nimport { type FC, useEffect, useState, useMemo } from 'react';\nimport { Capacitor } from '@capacitor/core';\nimport {\n ArrowLeft,\n Loader2,\n CheckCircle,\n XCircle,\n RotateCcw,\n Smartphone,\n Package,\n Coins,\n Blocks,\n ShoppingBag,\n CalendarDays,\n Info,\n ChevronRight,\n} from 'lucide-react';\nimport type { useNativeCheckout } from '../hooks/useNativeCheckout';\nimport type { Product } from '@capgo/native-purchases';\nimport type { PurchaseType } from '../types';\n\ninterface NativeCheckoutViewProps {\n purchaseType: PurchaseType;\n productId?: string;\n billingAccountId?: string;\n nativeCheckout: ReturnType<typeof useNativeCheckout>;\n onBack: () => void;\n}\n\nfunction getStoreName(): string {\n return Capacitor.getPlatform() === 'ios' ? 'App Store' : 'Google Play';\n}\n\nfunction purchaseTypeLabel(type: PurchaseType): string {\n switch (type) {\n case 'CREDITS':\n return 'Credits';\n case 'ADDON':\n return 'Add-on';\n case 'ORDER':\n return 'Order';\n default:\n return 'Subscription';\n }\n}\n\nfunction purchaseTypeIcon(type: PurchaseType) {\n switch (type) {\n case 'CREDITS':\n return <Coins className=\"size-6 text-[--color-primary]\" />;\n case 'ADDON':\n return <Blocks className=\"size-6 text-[--color-primary]\" />;\n case 'ORDER':\n return <ShoppingBag className=\"size-6 text-[--color-primary]\" />;\n default:\n return <Package className=\"size-6 text-[--color-primary]\" />;\n }\n}\n\ninterface SubscriptionPeriod {\n numberOfUnits: number;\n unit: number;\n}\n\n// unit: 0=day, 1=week, 2=month, 3=year (Apple/Google convention)\nfunction billingPeriodLabel(period: SubscriptionPeriod | null | undefined): string | null {\n if (!period) return null;\n switch (period.unit) {\n case 0:\n return period.numberOfUnits === 1\n ? 'Billed daily'\n : `Billed every ${period.numberOfUnits} days`;\n case 1:\n return period.numberOfUnits === 1\n ? 'Billed weekly'\n : `Billed every ${period.numberOfUnits} weeks`;\n case 2:\n return period.numberOfUnits === 1\n ? 'Billed monthly'\n : `Billed every ${period.numberOfUnits} months`;\n case 3:\n return period.numberOfUnits === 1\n ? 'Billed yearly'\n : `Billed every ${period.numberOfUnits} years`;\n default:\n return null;\n }\n}\n\nfunction successLabel(type: PurchaseType): string {\n switch (type) {\n case 'CREDITS':\n return 'Your credits will be added shortly. It may take a few seconds to reflect.';\n case 'ADDON':\n return 'Your add-on will be activated shortly. It may take a few seconds to reflect.';\n case 'ORDER':\n return 'Your order has been placed. It may take a few seconds to reflect.';\n default:\n return 'Your subscription will be activated shortly. It may take a few seconds to reflect.';\n }\n}\n\n// ─── Single-product summary card ──────────────────────────────────────────────\n\ninterface ProductSummaryProps {\n product: Product;\n purchaseType: PurchaseType;\n storeName: string;\n onPay: () => void;\n isProcessing: boolean;\n}\n\nconst ProductSummary: FC<ProductSummaryProps> = ({\n product,\n purchaseType,\n storeName,\n onPay,\n isProcessing,\n}) => {\n const period = billingPeriodLabel(\n product.subscriptionPeriod as SubscriptionPeriod | null | undefined\n );\n const hasIntro = !!product.introductoryPrice;\n\n return (\n <div className=\"flex flex-col gap-4 p-4\">\n {/* What you're getting */}\n <div className=\"rounded-2xl border border-[--color-border] bg-[--color-surface] overflow-hidden\">\n {/* Item row */}\n <div className=\"flex items-start gap-4 p-4\">\n <div className=\"flex size-11 shrink-0 items-center justify-center rounded-xl bg-[--color-primary-subtle]\">\n {purchaseTypeIcon(purchaseType)}\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-semibold text-[--color-text-primary] leading-snug\">\n {product.title}\n </p>\n {product.description ? (\n <p className=\"mt-1 text-xs text-[--color-text-secondary] leading-relaxed\">\n {product.description}\n </p>\n ) : null}\n </div>\n </div>\n\n {/* Divider */}\n <div className=\"h-px bg-[--color-border]\" />\n\n {/* Pricing row */}\n <div className=\"flex items-center justify-between px-4 py-3\">\n <div className=\"flex flex-col\">\n {hasIntro && product.introductoryPrice ? (\n <>\n <span className=\"text-xs text-[--color-text-secondary] line-through\">\n {product.priceString}\n </span>\n <span className=\"text-lg font-bold text-[--color-primary]\">\n {product.introductoryPrice.priceString}\n <span className=\"ml-1 text-xs font-normal text-[--color-text-secondary]\">\n intro offer\n </span>\n </span>\n </>\n ) : (\n <span className=\"text-lg font-bold text-[--color-primary]\">\n {product.priceString}\n </span>\n )}\n {period ? (\n <div className=\"mt-0.5 flex items-center gap-1\">\n <CalendarDays className=\"size-3 text-[--color-text-tertiary]\" />\n <span className=\"text-xs text-[--color-text-secondary]\">{period}</span>\n </div>\n ) : null}\n </div>\n <div className=\"flex items-center gap-1 rounded-full bg-[--color-surface-subtle] px-2.5 py-1\">\n <span className=\"text-xs font-medium text-[--color-text-secondary]\">\n {purchaseTypeLabel(purchaseType)}\n </span>\n </div>\n </div>\n </div>\n\n {/* Tax note */}\n <div className=\"flex items-start gap-2 rounded-xl bg-[--color-surface-subtle] px-3 py-2.5\">\n <Info className=\"mt-0.5 size-3.5 shrink-0 text-[--color-text-tertiary]\" />\n <p className=\"text-xs text-[--color-text-secondary] leading-relaxed\">\n Price includes all applicable taxes as determined by {storeName}. No additional charges.\n </p>\n </div>\n\n {/* Pay CTA */}\n <button\n type=\"button\"\n disabled={isProcessing}\n onClick={onPay}\n className=\"flex w-full items-center justify-center gap-2 rounded-xl bg-[--color-primary] px-6 py-4 text-sm font-semibold text-[--color-primary-foreground] transition-opacity disabled:opacity-60 active:opacity-80\"\n >\n {isProcessing ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n Processing…\n </>\n ) : (\n <>\n <Smartphone className=\"size-4\" />\n Pay with {storeName}\n <ChevronRight className=\"size-4 opacity-70\" />\n </>\n )}\n </button>\n\n {/* Legal note */}\n <p className=\"text-center text-[10px] leading-relaxed text-[--color-text-tertiary]\">\n By continuing, you agree to the {storeName} terms.{' '}\n {purchaseType === 'PLAN'\n ? 'Subscription renews automatically. Cancel anytime.'\n : 'This is a one-time purchase.'}\n </p>\n </div>\n );\n};\n\n// ─── Multi-product list ───────────────────────────────────────────────────────\n\ninterface ProductListProps {\n products: Product[];\n purchaseType: PurchaseType;\n onSelect: (product: Product) => void;\n isProcessing: boolean;\n}\n\nconst ProductList: FC<ProductListProps> = ({ products, purchaseType, onSelect, isProcessing }) => (\n <div className=\"flex flex-col gap-3 p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-[--color-text-tertiary]\">\n Select a {purchaseTypeLabel(purchaseType)}\n </p>\n {products.map((product) => {\n const period = billingPeriodLabel(\n product.subscriptionPeriod as SubscriptionPeriod | null | undefined\n );\n return (\n <button\n type=\"button\"\n key={product.identifier}\n disabled={isProcessing}\n onClick={() => onSelect(product)}\n className=\"flex items-center justify-between rounded-xl border border-[--color-border] bg-[--color-surface] p-4 text-left transition-colors hover:border-[--color-primary] hover:bg-[--color-surface-hover] disabled:opacity-60\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"flex size-9 shrink-0 items-center justify-center rounded-lg bg-[--color-primary-subtle]\">\n {purchaseTypeIcon(purchaseType)}\n </div>\n <div>\n <p className=\"text-sm font-semibold text-[--color-text-primary]\">{product.title}</p>\n {period ? (\n <p className=\"mt-0.5 text-xs text-[--color-text-secondary]\">{period}</p>\n ) : null}\n </div>\n </div>\n <div className=\"ml-4 shrink-0 text-right\">\n <p className=\"text-base font-bold text-[--color-primary]\">{product.priceString}</p>\n {isProcessing && (\n <Loader2 className=\"ml-auto mt-1 size-4 animate-spin text-[--color-text-tertiary]\" />\n )}\n </div>\n </button>\n );\n })}\n </div>\n);\n\n// ─── Main component ───────────────────────────────────────────────────────────\n\nexport const NativeCheckoutView: FC<NativeCheckoutViewProps> = ({\n purchaseType,\n productId,\n nativeCheckout,\n onBack,\n}) => {\n const storeName = useMemo(() => getStoreName(), []);\n const {\n products,\n checkoutState,\n error,\n isLoading,\n loadProducts,\n purchaseProduct,\n restorePurchases,\n } = nativeCheckout;\n\n // When user already selected a specific product, track it for the summary view\n const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);\n\n useEffect(() => {\n void loadProducts(productId ? [productId] : undefined);\n }, [loadProducts, productId]);\n\n // Auto-select when only one product loaded (specific product was pre-selected)\n useEffect(() => {\n if (products.length === 1 && products[0] && !selectedProduct) {\n setSelectedProduct(products[0]);\n }\n }, [products, selectedProduct]);\n\n const handlePay = (product: Product) => {\n setSelectedProduct(product);\n void purchaseProduct(product);\n };\n\n // ── Success ────────────────────────────────────────────────────────────────\n if (checkoutState === 'success') {\n return (\n <div className=\"flex flex-col items-center justify-center gap-6 py-16 px-6 text-center\">\n <CheckCircle className=\"size-16 text-[--color-success]\" />\n <div>\n <p className=\"text-xl font-semibold text-[--color-text-primary]\">Purchase Successful</p>\n <p className=\"mt-2 text-sm text-[--color-text-secondary]\">{successLabel(purchaseType)}</p>\n </div>\n <button\n type=\"button\"\n onClick={onBack}\n className=\"rounded-xl bg-[--color-primary] px-8 py-3 text-sm font-semibold text-[--color-primary-foreground]\"\n >\n Done\n </button>\n </div>\n );\n }\n\n // ── Error ──────────────────────────────────────────────────────────────────\n if (checkoutState === 'error') {\n return (\n <div className=\"flex flex-col items-center justify-center gap-6 py-16 px-6 text-center\">\n <XCircle className=\"size-16 text-[--color-destructive]\" />\n <div>\n <p className=\"text-xl font-semibold text-[--color-text-primary]\">Purchase Failed</p>\n <p className=\"mt-2 text-sm text-[--color-text-secondary]\">{error}</p>\n </div>\n <div className=\"flex gap-3\">\n <button\n type=\"button\"\n onClick={() => void loadProducts(productId ? [productId] : undefined)}\n className=\"rounded-xl border border-[--color-border] px-6 py-3 text-sm font-medium text-[--color-text-primary]\"\n >\n Try Again\n </button>\n <button\n type=\"button\"\n onClick={onBack}\n className=\"rounded-xl bg-[--color-primary] px-6 py-3 text-sm font-medium text-[--color-primary-foreground]\"\n >\n Cancel\n </button>\n </div>\n </div>\n );\n }\n\n const isProcessing = checkoutState === 'processing';\n\n return (\n <div className=\"flex flex-col gap-0\">\n {/* Header */}\n <div className=\"flex items-center gap-3 border-b border-[--color-border] px-4 py-3\">\n <button\n type=\"button\"\n onClick={onBack}\n className=\"rounded-lg p-2 text-[--color-text-secondary] hover:bg-[--color-surface-hover]\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <div>\n <p className=\"text-base font-semibold text-[--color-text-primary]\">\n {(selectedProduct ?? products.length === 1)\n ? 'Order Summary'\n : `Choose a ${purchaseTypeLabel(purchaseType)}`}\n </p>\n </div>\n </div>\n\n {/* Store badge */}\n <div className=\"flex items-center gap-2 border-b border-[--color-border] bg-[--color-surface-subtle] px-4 py-2\">\n <Smartphone className=\"size-3.5 text-[--color-text-tertiary]\" />\n <span className=\"text-xs text-[--color-text-secondary]\">\n Payments processed securely by <span className=\"font-medium\">{storeName}</span>\n </span>\n </div>\n\n {/* Content */}\n {isLoading && products.length === 0 ? (\n <div className=\"flex items-center justify-center py-20\">\n <Loader2 className=\"size-6 animate-spin text-[--color-text-tertiary]\" />\n </div>\n ) : products.length === 0 ? (\n <div className=\"py-16 text-center text-sm text-[--color-text-secondary] px-6\">\n No products available for this platform. Please try again later.\n </div>\n ) : (selectedProduct ?? products.length === 1) ? (\n // Single product — show order summary + pay CTA\n <ProductSummary\n product={(selectedProduct ?? products[0])!}\n purchaseType={purchaseType}\n storeName={storeName}\n onPay={() => handlePay((selectedProduct ?? products[0])!)}\n isProcessing={isProcessing}\n />\n ) : (\n // Multiple products — show list to pick from\n <ProductList\n products={products}\n purchaseType={purchaseType}\n onSelect={(p) => handlePay(p)}\n isProcessing={isProcessing}\n />\n )}\n\n {/* Restore purchases */}\n {!isProcessing && (\n <div className=\"px-4 pb-6 pt-2\">\n <button\n type=\"button\"\n onClick={() => void restorePurchases()}\n className=\"flex w-full items-center justify-center gap-1.5 text-xs text-[--color-text-tertiary] underline-offset-2 hover:underline\"\n >\n <RotateCcw className=\"size-3\" />\n Restore previous purchases\n </button>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"NativeCheckoutView.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/NativeCheckoutView.tsx"],"sourcesContent":["/**\n * Native IAP Checkout View\n *\n * Replaces the Stripe/Razorpay checkout UI when running inside a Capacitor\n * native app (iOS / Android). Shows an order summary then triggers the native\n * payment sheet on confirmation.\n *\n * After a successful purchase the backend validates the receipt/token with\n * Apple/Google servers and activates the subscription, credits, or add-on.\n */\n\nimport { type FC, useEffect, useState, useMemo } from 'react';\nimport { Capacitor } from '@capacitor/core';\nimport {\n ArrowLeft,\n Loader2,\n CheckCircle,\n XCircle,\n RotateCcw,\n Smartphone,\n Package,\n Coins,\n Blocks,\n ShoppingBag,\n CalendarDays,\n Info,\n ChevronRight,\n} from 'lucide-react';\nimport type { useNativeCheckout } from '../hooks/useNativeCheckout';\nimport type { Product } from '@capgo/native-purchases';\nimport type { PurchaseType } from '../types';\n\ninterface NativeCheckoutViewProps {\n purchaseType: PurchaseType;\n productId?: string;\n billingAccountId?: string;\n nativeCheckout: ReturnType<typeof useNativeCheckout>;\n onBack: () => void;\n}\n\nfunction getStoreName(): string {\n return Capacitor.getPlatform() === 'ios' ? 'App Store' : 'Google Play';\n}\n\nfunction purchaseTypeLabel(type: PurchaseType): string {\n switch (type) {\n case 'CREDITS':\n return 'Credits';\n case 'ADDON':\n return 'Add-on';\n case 'ORDER':\n return 'Order';\n default:\n return 'Subscription';\n }\n}\n\nfunction purchaseTypeIcon(type: PurchaseType) {\n switch (type) {\n case 'CREDITS':\n return <Coins className=\"size-6 text-[--color-primary]\" />;\n case 'ADDON':\n return <Blocks className=\"size-6 text-[--color-primary]\" />;\n case 'ORDER':\n return <ShoppingBag className=\"size-6 text-[--color-primary]\" />;\n default:\n return <Package className=\"size-6 text-[--color-primary]\" />;\n }\n}\n\ninterface SubscriptionPeriod {\n numberOfUnits: number;\n unit: number;\n}\n\n// unit: 0=day, 1=week, 2=month, 3=year (Apple/Google convention)\nfunction billingPeriodLabel(period: SubscriptionPeriod | null | undefined): string | null {\n if (!period) return null;\n switch (period.unit) {\n case 0:\n return period.numberOfUnits === 1\n ? 'Billed daily'\n : `Billed every ${period.numberOfUnits} days`;\n case 1:\n return period.numberOfUnits === 1\n ? 'Billed weekly'\n : `Billed every ${period.numberOfUnits} weeks`;\n case 2:\n return period.numberOfUnits === 1\n ? 'Billed monthly'\n : `Billed every ${period.numberOfUnits} months`;\n case 3:\n return period.numberOfUnits === 1\n ? 'Billed yearly'\n : `Billed every ${period.numberOfUnits} years`;\n default:\n return null;\n }\n}\n\nfunction successLabel(type: PurchaseType): string {\n switch (type) {\n case 'CREDITS':\n return 'Your credits will be added shortly. It may take a few seconds to reflect.';\n case 'ADDON':\n return 'Your add-on will be activated shortly. It may take a few seconds to reflect.';\n case 'ORDER':\n return 'Your order has been placed. It may take a few seconds to reflect.';\n default:\n return 'Your subscription will be activated shortly. It may take a few seconds to reflect.';\n }\n}\n\n// ─── Single-product summary card ──────────────────────────────────────────────\n\ninterface ProductSummaryProps {\n product: Product;\n purchaseType: PurchaseType;\n storeName: string;\n onPay: () => void;\n isProcessing: boolean;\n}\n\nconst ProductSummary: FC<ProductSummaryProps> = ({\n product,\n purchaseType,\n storeName,\n onPay,\n isProcessing,\n}) => {\n const period = billingPeriodLabel(\n product.subscriptionPeriod as SubscriptionPeriod | null | undefined\n );\n const hasIntro = !!product.introductoryPrice;\n\n return (\n <div className=\"flex flex-col gap-4 p-4\">\n {/* What you're getting */}\n <div className=\"rounded-2xl border border-[--color-border] bg-[--color-surface] overflow-hidden\">\n {/* Item row */}\n <div className=\"flex items-start gap-4 p-4\">\n <div className=\"flex size-11 shrink-0 items-center justify-center rounded-xl bg-[--color-primary-subtle]\">\n {purchaseTypeIcon(purchaseType)}\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-semibold text-[--color-text-primary] leading-snug\">\n {product.title}\n </p>\n {product.description ? (\n <p className=\"mt-1 text-xs text-[--color-text-secondary] leading-relaxed\">\n {product.description}\n </p>\n ) : null}\n </div>\n </div>\n\n {/* Divider */}\n <div className=\"h-px bg-[--color-border]\" />\n\n {/* Pricing row */}\n <div className=\"flex items-center justify-between px-4 py-3\">\n <div className=\"flex flex-col\">\n {hasIntro && product.introductoryPrice ? (\n <>\n <span className=\"text-xs text-[--color-text-secondary] line-through\">\n {product.priceString}\n </span>\n <span className=\"text-lg font-bold text-[--color-primary]\">\n {product.introductoryPrice.priceString}\n <span className=\"ml-1 text-xs font-normal text-[--color-text-secondary]\">\n intro offer\n </span>\n </span>\n </>\n ) : (\n <span className=\"text-lg font-bold text-[--color-primary]\">\n {product.priceString}\n </span>\n )}\n {period ? (\n <div className=\"mt-0.5 flex items-center gap-1\">\n <CalendarDays className=\"size-3 text-[--color-text-tertiary]\" />\n <span className=\"text-xs text-[--color-text-secondary]\">{period}</span>\n </div>\n ) : null}\n </div>\n <div className=\"flex items-center gap-1 rounded-full bg-[--color-surface-subtle] px-2.5 py-1\">\n <span className=\"text-xs font-medium text-[--color-text-secondary]\">\n {purchaseTypeLabel(purchaseType)}\n </span>\n </div>\n </div>\n </div>\n\n {/* Tax note */}\n <div className=\"flex items-start gap-2 rounded-xl bg-[--color-surface-subtle] px-3 py-2.5\">\n <Info className=\"mt-0.5 size-3.5 shrink-0 text-[--color-text-tertiary]\" />\n <p className=\"text-xs text-[--color-text-secondary] leading-relaxed\">\n Price includes all applicable taxes as determined by {storeName}. No additional charges.\n </p>\n </div>\n\n {/* Pay CTA */}\n <button\n type=\"button\"\n disabled={isProcessing}\n onClick={onPay}\n className=\"flex w-full items-center justify-center gap-2 rounded-xl bg-[--color-primary] px-6 py-4 text-sm font-semibold text-[--color-primary-foreground] transition-opacity disabled:opacity-60 active:opacity-80\"\n >\n {isProcessing ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n Processing…\n </>\n ) : (\n <>\n <Smartphone className=\"size-4\" />\n Pay with {storeName}\n <ChevronRight className=\"size-4 opacity-70\" />\n </>\n )}\n </button>\n\n {/* Legal note */}\n <p className=\"text-center text-[10px] leading-relaxed text-[--color-text-tertiary]\">\n By continuing, you agree to the {storeName} terms.{' '}\n {purchaseType === 'PLAN'\n ? 'Subscription renews automatically. Cancel anytime.'\n : 'This is a one-time purchase.'}\n </p>\n </div>\n );\n};\n\n// ─── Multi-product list ───────────────────────────────────────────────────────\n\ninterface ProductListProps {\n products: Product[];\n purchaseType: PurchaseType;\n onSelect: (product: Product) => void;\n isProcessing: boolean;\n}\n\nconst ProductList: FC<ProductListProps> = ({ products, purchaseType, onSelect, isProcessing }) => (\n <div className=\"flex flex-col gap-3 p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-[--color-text-tertiary]\">\n Select a {purchaseTypeLabel(purchaseType)}\n </p>\n {products.map((product) => {\n const period = billingPeriodLabel(\n product.subscriptionPeriod as SubscriptionPeriod | null | undefined\n );\n return (\n <button\n type=\"button\"\n key={product.identifier}\n disabled={isProcessing}\n onClick={() => onSelect(product)}\n className=\"flex items-center justify-between rounded-xl border border-[--color-border] bg-[--color-surface] p-4 text-left transition-colors hover:border-[--color-primary] hover:bg-[--color-surface-hover] disabled:opacity-60\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"flex size-9 shrink-0 items-center justify-center rounded-lg bg-[--color-primary-subtle]\">\n {purchaseTypeIcon(purchaseType)}\n </div>\n <div>\n <p className=\"text-sm font-semibold text-[--color-text-primary]\">{product.title}</p>\n {period ? (\n <p className=\"mt-0.5 text-xs text-[--color-text-secondary]\">{period}</p>\n ) : null}\n </div>\n </div>\n <div className=\"ml-4 shrink-0 text-right\">\n <p className=\"text-base font-bold text-[--color-primary]\">{product.priceString}</p>\n {isProcessing && (\n <Loader2 className=\"ml-auto mt-1 size-4 animate-spin text-[--color-text-tertiary]\" />\n )}\n </div>\n </button>\n );\n })}\n </div>\n);\n\n// ─── Main component ───────────────────────────────────────────────────────────\n\nexport const NativeCheckoutView: FC<NativeCheckoutViewProps> = ({\n purchaseType,\n productId,\n nativeCheckout,\n onBack,\n}) => {\n const storeName = useMemo(() => getStoreName(), []);\n const {\n products,\n checkoutState,\n error,\n isLoading,\n loadProducts,\n purchaseProduct,\n restorePurchases,\n } = nativeCheckout;\n\n // When user already selected a specific product, track it for the summary view\n const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);\n\n useEffect(() => {\n void loadProducts(productId ? [productId] : undefined);\n }, [loadProducts, productId]);\n\n // Auto-select when only one product loaded (specific product was pre-selected)\n useEffect(() => {\n if (products.length === 1 && products[0] && !selectedProduct) {\n setSelectedProduct(products[0]);\n }\n }, [products, selectedProduct]);\n\n const handlePay = (product: Product) => {\n setSelectedProduct(product);\n void purchaseProduct(product);\n };\n\n // ── Success ────────────────────────────────────────────────────────────────\n if (checkoutState === 'success') {\n return (\n <div className=\"flex flex-col items-center justify-center gap-6 py-16 px-6 text-center\">\n <CheckCircle className=\"size-16 text-[--color-success]\" />\n <div>\n <p className=\"text-xl font-semibold text-[--color-text-primary]\">Purchase Successful</p>\n <p className=\"mt-2 text-sm text-[--color-text-secondary]\">{successLabel(purchaseType)}</p>\n </div>\n <button\n type=\"button\"\n onClick={onBack}\n className=\"rounded-xl bg-[--color-primary] px-8 py-3 text-sm font-semibold text-[--color-primary-foreground]\"\n >\n Done\n </button>\n </div>\n );\n }\n\n // ── Error ──────────────────────────────────────────────────────────────────\n if (checkoutState === 'error') {\n return (\n <div className=\"flex flex-col items-center justify-center gap-6 py-16 px-6 text-center\">\n <XCircle className=\"size-16 text-[--color-destructive]\" />\n <div>\n <p className=\"text-xl font-semibold text-[--color-text-primary]\">Purchase Failed</p>\n <p className=\"mt-2 text-sm text-[--color-text-secondary]\">{error}</p>\n </div>\n <div className=\"flex gap-3\">\n <button\n type=\"button\"\n onClick={() => void loadProducts(productId ? [productId] : undefined)}\n className=\"rounded-xl border border-[--color-border] px-6 py-3 text-sm font-medium text-[--color-text-primary]\"\n >\n Try Again\n </button>\n <button\n type=\"button\"\n onClick={onBack}\n className=\"rounded-xl bg-[--color-primary] px-6 py-3 text-sm font-medium text-[--color-primary-foreground]\"\n >\n Cancel\n </button>\n </div>\n </div>\n );\n }\n\n const isProcessing = checkoutState === 'processing';\n\n return (\n <div className=\"flex flex-col gap-0\">\n {/* Header */}\n <div className=\"flex items-center gap-3 border-b border-[--color-border] px-4 py-3\">\n <button\n type=\"button\"\n onClick={onBack}\n className=\"rounded-lg p-2 text-[--color-text-secondary] hover:bg-[--color-surface-hover]\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <div>\n <p className=\"text-base font-semibold text-[--color-text-primary]\">\n {(selectedProduct ?? products.length === 1)\n ? 'Order Summary'\n : `Choose a ${purchaseTypeLabel(purchaseType)}`}\n </p>\n </div>\n </div>\n\n {/* Store badge */}\n <div className=\"flex items-center gap-2 border-b border-[--color-border] bg-[--color-surface-subtle] px-4 py-2\">\n <Smartphone className=\"size-3.5 text-[--color-text-tertiary]\" />\n <span className=\"text-xs text-[--color-text-secondary]\">\n Payments processed securely by <span className=\"font-medium\">{storeName}</span>\n </span>\n </div>\n\n {/* Content */}\n {isLoading && products.length === 0 ? (\n <div className=\"flex items-center justify-center py-20\">\n <Loader2 className=\"size-6 animate-spin text-[--color-text-tertiary]\" />\n </div>\n ) : products.length === 0 ? (\n <div className=\"py-16 text-center text-sm text-[--color-text-secondary] px-6\">\n No products available for this platform. Please try again later.\n </div>\n ) : (selectedProduct ?? products.length === 1) ? (\n // Single product — show order summary + pay CTA\n <ProductSummary\n product={(selectedProduct ?? products[0])!}\n purchaseType={purchaseType}\n storeName={storeName}\n onPay={() => handlePay((selectedProduct ?? products[0])!)}\n isProcessing={isProcessing}\n />\n ) : (\n // Multiple products — show list to pick from\n <ProductList\n products={products}\n purchaseType={purchaseType}\n onSelect={(p) => handlePay(p)}\n isProcessing={isProcessing}\n />\n )}\n\n {/* Restore purchases */}\n {!isProcessing && (\n <div className=\"px-4 pb-6 pt-2\">\n <button\n type=\"button\"\n onClick={() => void restorePurchases()}\n className=\"flex w-full items-center justify-center gap-1.5 text-xs text-[--color-text-tertiary] underline-offset-2 hover:underline\"\n >\n <RotateCcw className=\"size-3\" />\n Restore previous purchases\n </button>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}