@burdenoff/microfe-billing 2026.915.2 → 2026.916.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
- package/dist/billing/modules/earnings/api.js.map +1 -1
- package/dist/billing/modules/earnings/pages/DeveloperRevenueOrgDetailPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/EarningsPage.js +117 -111
- package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +3 -2
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/generated/global-operations.js +1 -0
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PayoutAdminPage.js","names":[],"sources":["../../../../../src/billing/modules/earnings/pages/PayoutAdminPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState, type FC } from 'react';\nimport {\n AlertTriangle,\n Banknote,\n CheckCircle2,\n ChevronDown,\n ChevronUp,\n Clock,\n Coins,\n CreditCard,\n RefreshCw,\n ReceiptText,\n XCircle,\n} from 'lucide-react';\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport {\n approvePayoutRequest,\n fetchAdminPayoutRequests,\n fetchAdminPayoutSummary,\n markPayoutPaid,\n rejectPayoutRequest,\n type PayoutAdminSummary,\n type PayoutRequest,\n} from '../api';\n\nconst REQUESTS_PAGE_SIZE = 50;\n\ntype Tab = 'pending' | 'approved' | 'history';\ntype Kind = PayoutRequest['requestKind'];\n\nconst KIND_TABS: { id: Kind; label: string; description: string }[] = [\n {\n id: 'CASH_PAYOUT',\n label: 'Cash Payouts',\n description: 'Real money leaves the platform — verify bank/UPI details, then mark as paid.',\n },\n {\n id: 'CREDIT_CONVERSION',\n label: 'Revenue Share Credits',\n description:\n 'A developer requesting their revenue share as earned credits. Approving converts the amount straight into credits on their billing account — no bank transfer, nothing further to do.',\n },\n];\n\ninterface ActionDialogState {\n type: 'approve' | 'reject' | 'mark_paid';\n requestId: string;\n amount: number;\n currency: string;\n accountLabel: string;\n}\n\nconst STATUS_LABEL: Record<string, string> = {\n REQUESTED: 'Requested',\n APPROVED: 'Approved',\n REJECTED: 'Rejected',\n PROCESSING: 'Processing',\n PAID: 'Paid',\n FAILED: 'Failed',\n CANCELED: 'Canceled',\n};\n\nconst STATUS_COLORS: Record<string, string> = {\n REQUESTED: 'bg-status-warning-bg-subtle text-status-warning-text',\n APPROVED: 'bg-status-info-bg-subtle text-status-info-text',\n REJECTED: 'bg-status-error-bg-subtle text-status-error-text',\n PROCESSING: 'bg-status-info-bg-subtle text-status-info-text',\n PAID: 'bg-status-success-bg-subtle text-status-success-text',\n FAILED: 'bg-status-error-bg-subtle text-status-error-text',\n CANCELED: 'bg-bg-sunken text-text-muted',\n};\n\nfunction StatusBadge({ status }: { status: string }) {\n return (\n <span\n className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${STATUS_COLORS[status] ?? 'bg-bg-sunken text-text-muted'}`}\n >\n {STATUS_LABEL[status] ?? status}\n </span>\n );\n}\n\nfunction AccountDetails({ account }: { account: NonNullable<PayoutRequest['payoutAccount']> }) {\n const [expanded, setExpanded] = useState(false);\n const details = account.accountDetails as Record<string, string | null> | null;\n\n return (\n <div className=\"rounded-md border border-border-subtle bg-bg-sunken/40 p-3 text-sm\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2 text-text-primary font-medium\">\n <CreditCard className=\"size-4 text-text-muted\" />\n {account.displayName ?? account.label}\n <span className=\"text-xs text-text-muted font-normal\">\n ({account.type === 'UPI' ? 'UPI' : 'Bank'} · {account.country ?? 'Unknown'} ·{' '}\n {account.currency})\n </span>\n </div>\n {details && (\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className=\"text-text-muted hover:text-text-primary\"\n >\n {expanded ? <ChevronUp className=\"size-4\" /> : <ChevronDown className=\"size-4\" />}\n </button>\n )}\n </div>\n {expanded && details && (\n <div className=\"mt-2 grid gap-1 text-xs\">\n {details.accountHolderName && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Account holder</span>\n <span className=\"text-text-primary\">{details.accountHolderName}</span>\n </div>\n )}\n {details.bankName && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Bank</span>\n <span className=\"text-text-primary\">{details.bankName}</span>\n </div>\n )}\n {account.accountLast4 && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Account (last 4)</span>\n <span className=\"text-text-primary font-mono\">••••{account.accountLast4}</span>\n </div>\n )}\n {details.ifsc && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">IFSC</span>\n <span className=\"text-text-primary font-mono\">{details.ifsc}</span>\n </div>\n )}\n {details.routingNumberLast4 && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Routing (last 4)</span>\n <span className=\"text-text-primary font-mono\">••••{details.routingNumberLast4}</span>\n </div>\n )}\n {details.upiIdMasked && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">UPI ID</span>\n <span className=\"text-text-primary font-mono\">{details.upiIdMasked}</span>\n </div>\n )}\n {details.swift && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">SWIFT</span>\n <span className=\"text-text-primary font-mono\">{details.swift}</span>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\nfunction RequestCard({\n request,\n onApprove,\n onReject,\n onMarkPaid,\n}: {\n request: PayoutRequest;\n onApprove: (r: PayoutRequest) => void;\n onReject: (r: PayoutRequest) => void;\n onMarkPaid: (r: PayoutRequest) => void;\n}) {\n const canApprove = request.status === 'REQUESTED' || request.status === 'REJECTED';\n const canReject = request.status === 'REQUESTED' || request.status === 'APPROVED';\n // A credit conversion is never manually marked paid — approving IS completion\n // (it converts straight to earned credits, no bank transfer to confirm). This\n // should never actually be reachable for that kind, but guard it anyway.\n const canMarkPaid =\n request.requestKind === 'CASH_PAYOUT' &&\n (request.status === 'APPROVED' || request.status === 'PROCESSING');\n\n return (\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-5 space-y-4 shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:shadow-[var(--shadow-elevation-2)]\">\n {/* Header row */}\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xl font-semibold text-text-primary\">\n {formatCurrency(request.requestedAmount, request.currency)}\n </span>\n <StatusBadge status={request.status} />\n </div>\n <div className=\"text-xs text-text-muted space-x-2\">\n <span>Requested {formatDateTime(request.createdAt)}</span>\n {request.requesterUserId && (\n <>\n <span>·</span>\n <span>by {request.requesterUserId}</span>\n </>\n )}\n <span>·</span>\n <span className=\"font-mono text-xs\">{request.id.slice(0, 8)}…</span>\n </div>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {canApprove && (\n <button\n type=\"button\"\n onClick={() => onApprove(request)}\n className=\"inline-flex items-center gap-1.5 rounded-button bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text\"\n >\n <CheckCircle2 className=\"size-3.5\" />\n Approve\n </button>\n )}\n {canMarkPaid && (\n <button\n type=\"button\"\n onClick={() => onMarkPaid(request)}\n className=\"inline-flex items-center gap-1.5 rounded-button bg-status-success-bg-subtle border border-status-success-border px-3 py-1.5 text-xs font-medium text-status-success-text transition-colors hover:bg-status-success-bg-subtle/70\"\n >\n <Banknote className=\"size-3.5\" />\n Mark as Paid\n </button>\n )}\n {canReject && (\n <button\n type=\"button\"\n onClick={() => onReject(request)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-error-text\"\n >\n <XCircle className=\"size-3.5\" />\n Reject\n </button>\n )}\n </div>\n </div>\n\n {/* Payout account — irrelevant for a revenue-share request even when one\n happens to be on file, since approving never sends money to it. */}\n {request.requestKind === 'CREDIT_CONVERSION' ? (\n <div className=\"flex items-center gap-2 rounded-md border border-border-subtle bg-bg-sunken/40 p-3 text-sm text-text-muted\">\n <Coins className=\"size-4 shrink-0\" />\n Approving converts this straight into earned credits — no bank transfer needed.\n </div>\n ) : (\n request.payoutAccount && <AccountDetails account={request.payoutAccount} />\n )}\n\n {/* User notes */}\n {request.userNotes && (\n <div className=\"rounded-md bg-bg-sunken/40 border border-border-subtle px-3 py-2 text-sm\">\n <span className=\"text-xs font-medium text-text-muted uppercase tracking-wide\">\n User note\n </span>\n <p className=\"mt-1 text-text-primary\">{request.userNotes}</p>\n </div>\n )}\n\n {/* Internal notes / failure / ref */}\n {(request.internalNotes ?? request.failureReason ?? request.externalReference) && (\n <div className=\"grid gap-1 text-xs text-text-muted\">\n {request.externalReference && (\n <div className=\"flex gap-2\">\n <ReceiptText className=\"size-3.5 mt-px shrink-0\" />\n <span>\n Ref:{' '}\n <span className=\"font-mono text-text-primary\">{request.externalReference}</span>\n </span>\n </div>\n )}\n {request.internalNotes && (\n <div className=\"flex gap-2\">\n <span className=\"shrink-0\">Internal note:</span>\n <span className=\"text-text-primary\">{request.internalNotes}</span>\n </div>\n )}\n {request.failureReason && (\n <div className=\"flex gap-2 text-status-error-text\">\n <AlertTriangle className=\"size-3.5 mt-px shrink-0\" />\n <span>{request.failureReason}</span>\n </div>\n )}\n </div>\n )}\n\n {/* Timestamps */}\n {(request.paidAt ?? request.processedAt) && (\n <div className=\"flex flex-wrap gap-4 text-xs text-text-muted\">\n {request.processedAt && (\n <span className=\"flex items-center gap-1\">\n <Clock className=\"size-3\" />\n Processed {formatDateTime(request.processedAt)}\n </span>\n )}\n {request.paidAt && (\n <span className=\"flex items-center gap-1\">\n <CheckCircle2 className=\"size-3\" />\n Paid {formatDateTime(request.paidAt)}\n </span>\n )}\n </div>\n )}\n </div>\n );\n}\n\nfunction SummaryBar({ summary }: { summary: PayoutAdminSummary }) {\n return (\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"text-xs text-text-muted uppercase tracking-wide\">Pending payout</div>\n <div className=\"mt-1 text-lg font-semibold text-text-primary\">\n {formatCurrency(summary.totalRequestedAmount, 'USD')}\n </div>\n <div className=\"text-xs text-text-muted mt-0.5\">{summary.pendingCount} requests</div>\n </div>\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"text-xs text-text-muted uppercase tracking-wide\">Processing</div>\n <div className=\"mt-1 text-lg font-semibold text-text-primary\">\n {formatCurrency(summary.totalProcessingAmount, 'USD')}\n </div>\n </div>\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"text-xs text-text-muted uppercase tracking-wide\">Total paid</div>\n <div className=\"mt-1 text-lg font-semibold text-text-primary\">\n {formatCurrency(summary.totalPaidAmount, 'USD')}\n </div>\n {summary.failedCount > 0 && (\n <div className=\"text-xs text-status-error-text mt-0.5\">{summary.failedCount} failed</div>\n )}\n </div>\n </div>\n );\n}\n\nfunction ActionDialog({\n dialog,\n onClose,\n onConfirm,\n}: {\n dialog: ActionDialogState;\n onClose: () => void;\n onConfirm: (data: {\n notes?: string;\n reason?: string;\n externalReference?: string;\n }) => Promise<void>;\n}) {\n const [notes, setNotes] = useState('');\n const [reason, setReason] = useState('');\n const [txnRef, setTxnRef] = useState('');\n const [submitting, setSubmitting] = useState(false);\n\n const handleSubmit = async () => {\n if (dialog.type === 'reject' && !reason.trim()) return;\n setSubmitting(true);\n await onConfirm({\n notes: notes.trim() || undefined,\n reason: reason.trim() || undefined,\n externalReference: txnRef.trim() || undefined,\n });\n setSubmitting(false);\n };\n\n const titles = {\n approve: 'Approve withdrawal request',\n reject: 'Reject withdrawal request',\n mark_paid: 'Mark as paid',\n };\n\n return (\n <ResponsiveDialog\n open\n onOpenChange={(open) => {\n if (!open && !submitting) onClose();\n }}\n >\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>{titles[dialog.type]}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {formatCurrency(dialog.amount, dialog.currency)} · {dialog.accountLabel}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n {dialog.type === 'mark_paid' && (\n <div className=\"space-y-3\">\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Transaction reference / UTR number\n </label>\n <input\n type=\"text\"\n value={txnRef}\n onChange={(e) => setTxnRef(e.target.value)}\n placeholder=\"e.g. UTR123456789012, TXN_abc123\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Enter the bank reference number so the user can verify the transfer.\n </p>\n </div>\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Internal notes (optional)\n </label>\n <textarea\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n rows={2}\n placeholder=\"Any internal notes about this payment…\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n </div>\n )}\n\n {dialog.type === 'approve' && (\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Internal notes (optional)\n </label>\n <textarea\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n rows={3}\n placeholder=\"e.g. Verified bank details, approved for transfer…\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n )}\n\n {dialog.type === 'reject' && (\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Reason for rejection <span className=\"text-status-error-text\">*</span>\n </label>\n <textarea\n value={reason}\n onChange={(e) => setReason(e.target.value)}\n rows={3}\n placeholder=\"e.g. Invalid bank details provided, please re-submit with correct IFSC…\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n )}\n\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={onClose}\n disabled={submitting}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void handleSubmit()}\n disabled={submitting || (dialog.type === 'reject' && !reason.trim())}\n className={`rounded-md px-4 py-2 text-sm font-medium disabled:opacity-50 ${\n dialog.type === 'reject'\n ? 'bg-action-danger-bg text-action-danger-text'\n : 'bg-action-primary-bg text-action-primary-text'\n }`}\n >\n {submitting\n ? 'Saving…'\n : dialog.type === 'approve'\n ? 'Approve'\n : dialog.type === 'reject'\n ? 'Reject'\n : 'Confirm Payment'}\n </button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nexport const PayoutAdminPage: FC = () => {\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const { apiGatewayUrl, authToken, orgId } = useBilling();\n\n const [summary, setSummary] = useState<PayoutAdminSummary | null>(null);\n const [requests, setRequests] = useState<PayoutRequest[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [hasMore, setHasMore] = useState(false);\n const [loading, setLoading] = useState(true);\n const [loadingMore, setLoadingMore] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [activeKind, setActiveKind] = useState<Kind>('CASH_PAYOUT');\n const [activeTab, setActiveTab] = useState<Tab>('pending');\n const [dialog, setDialog] = useState<ActionDialogState | null>(null);\n\n const requestContext = useMemo(\n () => ({ apiGatewayUrl, authToken, orgId }),\n [apiGatewayUrl, authToken, orgId]\n );\n\n const loadData = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const [loadedSummary, loadedRequests] = await Promise.all([\n fetchAdminPayoutSummary(requestContext),\n fetchAdminPayoutRequests(requestContext, { limit: REQUESTS_PAGE_SIZE, offset: 0 }),\n ]);\n setSummary(loadedSummary);\n setRequests(loadedRequests.items);\n setTotalCount(loadedRequests.totalCount);\n setHasMore(loadedRequests.hasMore);\n } catch (loadError) {\n setError(loadError instanceof Error ? loadError : new Error('Failed to load payout queue'));\n } finally {\n setLoading(false);\n }\n }, [requestContext]);\n\n const loadMore = async () => {\n if (loadingMore || !hasMore) return;\n\n setLoadingMore(true);\n setError(null);\n try {\n const loadedRequests = await fetchAdminPayoutRequests(requestContext, {\n limit: REQUESTS_PAGE_SIZE,\n offset: requests.length,\n });\n setRequests((current) => [...current, ...loadedRequests.items]);\n setTotalCount(loadedRequests.totalCount);\n setHasMore(loadedRequests.hasMore);\n } catch (loadError) {\n setError(\n loadError instanceof Error ? loadError : new Error('Failed to load more payout requests')\n );\n } finally {\n setLoadingMore(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [loadData]);\n\n if (!permissions.isBillingAdmin) {\n return <AccessDenied message=\"You don't have permission to manage payouts.\" />;\n }\n\n if (error && isServerError(error)) {\n return (\n <ServerError\n title=\"Payout queue unavailable\"\n message={error.message}\n onRetry={() => void loadData()}\n showRetry\n />\n );\n }\n\n // Split by kind FIRST (BOFF-7192) — a $35 revenue-share credit conversion\n // must never sit in the same list as a real bank payout, so every count and\n // sub-tab below is scoped to whichever kind tab is active.\n const kindRequests = requests.filter((r) => r.requestKind === activeKind);\n\n const pending = kindRequests.filter((r) => r.status === 'REQUESTED');\n const approved = kindRequests.filter(\n (r) => r.status === 'APPROVED' || r.status === 'PROCESSING'\n );\n const history = kindRequests.filter((r) =>\n ['PAID', 'REJECTED', 'FAILED', 'CANCELED'].includes(r.status)\n );\n\n const kindTabItems = KIND_TABS.map((kind) => ({\n ...kind,\n count: requests.filter((r) => r.requestKind === kind.id).length,\n }));\n\n const tabItems: { id: Tab; label: string; count: number }[] = [\n { id: 'pending', label: 'Pending', count: pending.length },\n { id: 'approved', label: 'Approved', count: approved.length },\n { id: 'history', label: 'History', count: history.length },\n ];\n\n const visibleRequests =\n activeTab === 'pending' ? pending : activeTab === 'approved' ? approved : history;\n\n const openDialog = (type: ActionDialogState['type'], request: PayoutRequest) => {\n setDialog({\n type,\n requestId: request.id,\n amount: request.requestedAmount,\n currency: request.currency,\n accountLabel:\n request.payoutAccount?.displayName ?? request.payoutAccount?.label ?? 'Unknown account',\n });\n };\n\n const handleConfirm = async (data: {\n notes?: string;\n reason?: string;\n externalReference?: string;\n }) => {\n if (!dialog) return;\n\n try {\n if (dialog.type === 'approve') {\n await approvePayoutRequest(dialog.requestId, data.notes ?? null, requestContext);\n emit('billing.payout.approved', {\n route: '/billing/payouts',\n entityId: dialog.requestId,\n source: 'payout-admin',\n });\n } else if (dialog.type === 'reject') {\n await rejectPayoutRequest(dialog.requestId, data.reason ?? '', requestContext);\n emit('billing.payout.rejected', {\n route: '/billing/payouts',\n entityId: dialog.requestId,\n source: 'payout-admin',\n reason: data.reason,\n });\n } else if (dialog.type === 'mark_paid') {\n await markPayoutPaid(\n dialog.requestId,\n data.externalReference ?? null,\n data.notes ?? null,\n requestContext\n );\n emit('billing.payout.processed', {\n route: '/billing/payouts',\n entityId: dialog.requestId,\n source: 'payout-admin',\n });\n }\n setDialog(null);\n await loadData();\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Action failed'));\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Payout queue\"\n description=\"Review withdrawal requests, verify payout details, and mark them as paid once funds are transferred.\"\n actions={\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n }\n />\n\n <PagePurpose>\n This queue holds two different kinds of request, split into the tabs below so a revenue-share\n credit conversion never gets mistaken for a real bank transfer. <strong>Cash Payouts</strong>{' '}\n are real money leaving the platform — developers withdrawing purchased or earned credits from\n billing to their own bank/UPI account. <strong>Revenue Share Credits</strong> are a developer\n requesting their store revenue share — approving one just converts the amount into earned\n credits on their billing account, no transfer involved.\n </PagePurpose>\n\n {summary && !loading && <SummaryBar summary={summary} />}\n\n {!loading && totalCount > 0 && (\n <div className=\"text-sm text-text-muted\">\n Showing {requests.length} of {totalCount} payout requests.\n </div>\n )}\n\n {loading && (\n <div className=\"grid gap-3 sm:grid-cols-3\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-20 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n )}\n\n {error && !isServerError(error) && (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-sm text-status-error-text\">\n {error.message}\n </div>\n )}\n\n {/* Kind tabs — Cash Payouts vs Revenue Share Credits (BOFF-7192). Kept\n visually distinct from the status sub-tabs below: this is the\n primary split, since approving each kind does something completely\n different (real money vs. an internal credit conversion). */}\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {kindTabItems.map((kind) => {\n const Icon = kind.id === 'CASH_PAYOUT' ? Banknote : Coins;\n const isActive = activeKind === kind.id;\n return (\n <button\n key={kind.id}\n type=\"button\"\n onClick={() => {\n setActiveKind(kind.id);\n setActiveTab('pending');\n }}\n className={`flex items-start gap-3 rounded-card border p-4 text-left transition-colors ${\n isActive\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)]'\n : 'border-border-subtle bg-bg-surface hover:border-border-strong'\n }`}\n >\n <Icon\n className={`size-5 mt-0.5 shrink-0 ${isActive ? 'text-text-link' : 'text-text-muted'}`}\n />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span\n className={`text-sm font-semibold ${isActive ? 'text-text-link' : 'text-text-primary'}`}\n >\n {kind.label}\n </span>\n {kind.count > 0 && (\n <span\n className={`rounded-full px-1.5 py-0.5 text-xs ${\n isActive\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {kind.count}\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-muted mt-0.5\">{kind.description}</p>\n </div>\n </button>\n );\n })}\n </div>\n\n {/* Tabs */}\n <div className=\"border-b border-border-subtle\">\n <div className=\"flex gap-0\">\n {tabItems.map((tab) => (\n <button\n key={tab.id}\n type=\"button\"\n onClick={() => setActiveTab(tab.id)}\n className={`flex items-center gap-2 border-b-2 px-4 py-2.5 text-sm font-medium transition-colors ${\n activeTab === tab.id\n ? 'border-action-primary-bg text-text-primary'\n : 'border-transparent text-text-muted hover:text-text-primary'\n }`}\n >\n {tab.label}\n {tab.count > 0 && (\n <span\n className={`rounded-full px-1.5 py-0.5 text-xs ${\n activeTab === tab.id\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {tab.count}\n </span>\n )}\n </button>\n ))}\n </div>\n </div>\n\n {/* How to pay — shown only on the Approved tab for real cash payouts */}\n {activeKind === 'CASH_PAYOUT' && activeTab === 'approved' && approved.length > 0 && (\n <div className=\"rounded-card border border-status-info-border bg-status-info-bg-subtle px-4 py-3 text-sm text-status-info-text\">\n <strong>How to pay:</strong> Click the account row to expand bank/UPI details → transfer\n the exact amount via your bank → click <strong>Mark as Paid</strong> and enter the UTR /\n transaction reference number.\n </div>\n )}\n\n {/* Request cards */}\n {loading ? (\n <div className=\"space-y-4\">\n {[1, 2].map((i) => (\n <div\n key={i}\n className=\"h-40 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n ) : visibleRequests.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={\n activeTab === 'pending'\n ? 'No pending requests'\n : activeTab === 'approved'\n ? 'No approved requests awaiting payment'\n : 'No history yet'\n }\n description={\n activeTab === 'pending'\n ? 'New requests will appear here.'\n : activeTab === 'approved'\n ? activeKind === 'CREDIT_CONVERSION'\n ? \"Revenue share requests skip this tab — approving one completes it immediately, there's no separate payment step.\"\n : 'Approve pending requests first, then send payment and mark as paid.'\n : 'Completed and rejected requests will appear here.'\n }\n />\n ) : (\n <div className=\"space-y-4\">\n {visibleRequests.map((request) => (\n <RequestCard\n key={request.id}\n request={request}\n onApprove={(r) => openDialog('approve', r)}\n onReject={(r) => openDialog('reject', r)}\n onMarkPaid={(r) => openDialog('mark_paid', r)}\n />\n ))}\n\n {hasMore && (\n <div className=\"flex justify-center pt-2\">\n <button\n type=\"button\"\n onClick={() => void loadMore()}\n disabled={loadingMore}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary disabled:opacity-50\"\n >\n {loadingMore ? 'Loading…' : 'Load more requests'}\n </button>\n </div>\n )}\n </div>\n )}\n\n {dialog && (\n <ActionDialog dialog={dialog} onClose={() => setDialog(null)} onConfirm={handleConfirm} />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PayoutAdminPage.js","names":[],"sources":["../../../../../src/billing/modules/earnings/pages/PayoutAdminPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState, type FC } from 'react';\nimport {\n AlertTriangle,\n Banknote,\n CheckCircle2,\n ChevronDown,\n ChevronUp,\n Clock,\n Coins,\n CreditCard,\n RefreshCw,\n ReceiptText,\n XCircle,\n} from 'lucide-react';\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { PagePurpose, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport {\n approvePayoutRequest,\n fetchAdminPayoutRequests,\n fetchAdminPayoutSummary,\n markPayoutPaid,\n rejectPayoutRequest,\n type PayoutAdminSummary,\n type PayoutRequest,\n} from '../api';\n\nconst REQUESTS_PAGE_SIZE = 50;\n\ntype Tab = 'pending' | 'approved' | 'history';\ntype Kind = PayoutRequest['requestKind'];\n\nconst KIND_TABS: { id: Kind; label: string; description: string }[] = [\n {\n id: 'CASH_PAYOUT',\n label: 'Cash Payouts',\n description: 'Real money leaves the platform — verify bank/UPI details, then mark as paid.',\n },\n {\n id: 'CREDIT_CONVERSION',\n label: 'Revenue Share Credits',\n description:\n 'A developer requesting their revenue share as earned credits. Approving converts the amount straight into credits on their billing account — no bank transfer, nothing further to do.',\n },\n];\n\ninterface ActionDialogState {\n type: 'approve' | 'reject' | 'mark_paid';\n requestId: string;\n amount: number;\n currency: string;\n accountLabel: string;\n}\n\nconst STATUS_LABEL: Record<string, string> = {\n REQUESTED: 'Requested',\n APPROVED: 'Approved',\n REJECTED: 'Rejected',\n PROCESSING: 'Processing',\n PAID: 'Paid',\n FAILED: 'Failed',\n CANCELED: 'Canceled',\n};\n\nconst STATUS_COLORS: Record<string, string> = {\n REQUESTED: 'bg-status-warning-bg-subtle text-status-warning-text',\n APPROVED: 'bg-status-info-bg-subtle text-status-info-text',\n REJECTED: 'bg-status-error-bg-subtle text-status-error-text',\n PROCESSING: 'bg-status-info-bg-subtle text-status-info-text',\n PAID: 'bg-status-success-bg-subtle text-status-success-text',\n FAILED: 'bg-status-error-bg-subtle text-status-error-text',\n CANCELED: 'bg-bg-sunken text-text-muted',\n};\n\nfunction StatusBadge({ status }: { status: string }) {\n return (\n <span\n className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${STATUS_COLORS[status] ?? 'bg-bg-sunken text-text-muted'}`}\n >\n {STATUS_LABEL[status] ?? status}\n </span>\n );\n}\n\nfunction AccountDetails({ account }: { account: NonNullable<PayoutRequest['payoutAccount']> }) {\n const [expanded, setExpanded] = useState(false);\n const details = account.accountDetails as Record<string, string | null> | null;\n\n return (\n <div className=\"rounded-md border border-border-subtle bg-bg-sunken/40 p-3 text-sm\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2 text-text-primary font-medium\">\n <CreditCard className=\"size-4 text-text-muted\" />\n {account.displayName ?? account.label}\n <span className=\"text-xs text-text-muted font-normal\">\n ({account.type === 'UPI' ? 'UPI' : 'Bank'} · {account.country ?? 'Unknown'} ·{' '}\n {account.currency})\n </span>\n </div>\n {details && (\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className=\"text-text-muted hover:text-text-primary\"\n >\n {expanded ? <ChevronUp className=\"size-4\" /> : <ChevronDown className=\"size-4\" />}\n </button>\n )}\n </div>\n {expanded && details && (\n <div className=\"mt-2 grid gap-1 text-xs\">\n {details.accountHolderName && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Account holder</span>\n <span className=\"text-text-primary\">{details.accountHolderName}</span>\n </div>\n )}\n {details.bankName && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Bank</span>\n <span className=\"text-text-primary\">{details.bankName}</span>\n </div>\n )}\n {account.accountLast4 && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Account (last 4)</span>\n <span className=\"text-text-primary font-mono\">••••{account.accountLast4}</span>\n </div>\n )}\n {details.ifsc && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">IFSC</span>\n <span className=\"text-text-primary font-mono\">{details.ifsc}</span>\n </div>\n )}\n {details.routingNumberLast4 && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">Routing (last 4)</span>\n <span className=\"text-text-primary font-mono\">••••{details.routingNumberLast4}</span>\n </div>\n )}\n {details.upiIdMasked && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">UPI ID</span>\n <span className=\"text-text-primary font-mono\">{details.upiIdMasked}</span>\n </div>\n )}\n {details.swift && (\n <div className=\"flex gap-2\">\n <span className=\"text-text-muted w-32 shrink-0\">SWIFT</span>\n <span className=\"text-text-primary font-mono\">{details.swift}</span>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n\nfunction RequestCard({\n request,\n onApprove,\n onReject,\n onMarkPaid,\n}: {\n request: PayoutRequest;\n onApprove: (r: PayoutRequest) => void;\n onReject: (r: PayoutRequest) => void;\n onMarkPaid: (r: PayoutRequest) => void;\n}) {\n const canApprove = request.status === 'REQUESTED' || request.status === 'REJECTED';\n const canReject = request.status === 'REQUESTED' || request.status === 'APPROVED';\n // A credit conversion is never manually marked paid — approving IS completion\n // (it converts straight to earned credits, no bank transfer to confirm). This\n // should never actually be reachable for that kind, but guard it anyway.\n const canMarkPaid =\n request.requestKind === 'CASH_PAYOUT' &&\n (request.status === 'APPROVED' || request.status === 'PROCESSING');\n\n return (\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-5 space-y-4 shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:shadow-[var(--shadow-elevation-2)]\">\n {/* Header row */}\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xl font-semibold text-text-primary\">\n {formatCurrency(request.requestedAmount, request.currency)}\n </span>\n <StatusBadge status={request.status} />\n </div>\n <div className=\"text-xs text-text-muted space-x-2\">\n <span>Requested {formatDateTime(request.createdAt)}</span>\n {request.requesterUserId && (\n <>\n <span>·</span>\n <span>by {request.requesterUserId}</span>\n </>\n )}\n <span>·</span>\n <span className=\"font-mono text-xs\">{request.id.slice(0, 8)}…</span>\n </div>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {canApprove && (\n <button\n type=\"button\"\n onClick={() => onApprove(request)}\n className=\"inline-flex items-center gap-1.5 rounded-button bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text\"\n >\n <CheckCircle2 className=\"size-3.5\" />\n Approve\n </button>\n )}\n {canMarkPaid && (\n <button\n type=\"button\"\n onClick={() => onMarkPaid(request)}\n className=\"inline-flex items-center gap-1.5 rounded-button bg-status-success-bg-subtle border border-status-success-border px-3 py-1.5 text-xs font-medium text-status-success-text transition-colors hover:bg-status-success-bg-subtle/70\"\n >\n <Banknote className=\"size-3.5\" />\n Mark as Paid\n </button>\n )}\n {canReject && (\n <button\n type=\"button\"\n onClick={() => onReject(request)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-error-text\"\n >\n <XCircle className=\"size-3.5\" />\n Reject\n </button>\n )}\n </div>\n </div>\n\n {/* Payout account — irrelevant for a revenue-share request even when one\n happens to be on file, since approving never sends money to it. */}\n {request.requestKind === 'CREDIT_CONVERSION' ? (\n <div className=\"flex items-center gap-2 rounded-md border border-border-subtle bg-bg-sunken/40 p-3 text-sm text-text-muted\">\n <Coins className=\"size-4 shrink-0\" />\n Approving converts this straight into earned credits — no bank transfer needed.\n </div>\n ) : (\n request.payoutAccount && <AccountDetails account={request.payoutAccount} />\n )}\n\n {/* User notes */}\n {request.userNotes && (\n <div className=\"rounded-md bg-bg-sunken/40 border border-border-subtle px-3 py-2 text-sm\">\n <span className=\"text-xs font-medium text-text-muted uppercase tracking-wide\">\n User note\n </span>\n <p className=\"mt-1 text-text-primary\">{request.userNotes}</p>\n </div>\n )}\n\n {/* Internal notes / failure / ref */}\n {(request.internalNotes ?? request.failureReason ?? request.externalReference) && (\n <div className=\"grid gap-1 text-xs text-text-muted\">\n {request.externalReference && (\n <div className=\"flex gap-2\">\n <ReceiptText className=\"size-3.5 mt-px shrink-0\" />\n <span>\n Ref:{' '}\n <span className=\"font-mono text-text-primary\">{request.externalReference}</span>\n </span>\n </div>\n )}\n {request.internalNotes && (\n <div className=\"flex gap-2\">\n <span className=\"shrink-0\">Internal note:</span>\n <span className=\"text-text-primary\">{request.internalNotes}</span>\n </div>\n )}\n {request.failureReason && (\n <div className=\"flex gap-2 text-status-error-text\">\n <AlertTriangle className=\"size-3.5 mt-px shrink-0\" />\n <span>{request.failureReason}</span>\n </div>\n )}\n </div>\n )}\n\n {/* Timestamps */}\n {(request.paidAt ?? request.processedAt) && (\n <div className=\"flex flex-wrap gap-4 text-xs text-text-muted\">\n {request.processedAt && (\n <span className=\"flex items-center gap-1\">\n <Clock className=\"size-3\" />\n Processed {formatDateTime(request.processedAt)}\n </span>\n )}\n {request.paidAt && (\n <span className=\"flex items-center gap-1\">\n <CheckCircle2 className=\"size-3\" />\n Paid {formatDateTime(request.paidAt)}\n </span>\n )}\n </div>\n )}\n </div>\n );\n}\n\nfunction SummaryBar({ summary }: { summary: PayoutAdminSummary }) {\n return (\n <div className=\"grid gap-3 sm:grid-cols-3\">\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"text-xs text-text-muted uppercase tracking-wide\">Pending payout</div>\n <div className=\"mt-1 text-lg font-semibold text-text-primary\">\n {formatCurrency(summary.totalRequestedAmount, 'USD')}\n </div>\n <div className=\"text-xs text-text-muted mt-0.5\">{summary.pendingCount} requests</div>\n </div>\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"text-xs text-text-muted uppercase tracking-wide\">Processing</div>\n <div className=\"mt-1 text-lg font-semibold text-text-primary\">\n {formatCurrency(summary.totalProcessingAmount, 'USD')}\n </div>\n </div>\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"text-xs text-text-muted uppercase tracking-wide\">Total paid</div>\n <div className=\"mt-1 text-lg font-semibold text-text-primary\">\n {formatCurrency(summary.totalPaidAmount, 'USD')}\n </div>\n {summary.failedCount > 0 && (\n <div className=\"text-xs text-status-error-text mt-0.5\">{summary.failedCount} failed</div>\n )}\n </div>\n </div>\n );\n}\n\nfunction ActionDialog({\n dialog,\n onClose,\n onConfirm,\n}: {\n dialog: ActionDialogState;\n onClose: () => void;\n onConfirm: (data: {\n notes?: string;\n reason?: string;\n externalReference?: string;\n }) => Promise<void>;\n}) {\n const [notes, setNotes] = useState('');\n const [reason, setReason] = useState('');\n const [txnRef, setTxnRef] = useState('');\n const [submitting, setSubmitting] = useState(false);\n\n const handleSubmit = async () => {\n if (dialog.type === 'reject' && !reason.trim()) return;\n setSubmitting(true);\n await onConfirm({\n notes: notes.trim() || undefined,\n reason: reason.trim() || undefined,\n externalReference: txnRef.trim() || undefined,\n });\n setSubmitting(false);\n };\n\n const titles = {\n approve: 'Approve withdrawal request',\n reject: 'Reject withdrawal request',\n mark_paid: 'Mark as paid',\n };\n\n return (\n <ResponsiveDialog\n open\n onOpenChange={(open) => {\n if (!open && !submitting) onClose();\n }}\n >\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>{titles[dialog.type]}</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {formatCurrency(dialog.amount, dialog.currency)} · {dialog.accountLabel}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n {dialog.type === 'mark_paid' && (\n <div className=\"space-y-3\">\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Transaction reference / UTR number\n </label>\n <input\n type=\"text\"\n value={txnRef}\n onChange={(e) => setTxnRef(e.target.value)}\n placeholder=\"e.g. UTR123456789012, TXN_abc123\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Enter the bank reference number so the user can verify the transfer.\n </p>\n </div>\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Internal notes (optional)\n </label>\n <textarea\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n rows={2}\n placeholder=\"Any internal notes about this payment…\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n </div>\n )}\n\n {dialog.type === 'approve' && (\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Internal notes (optional)\n </label>\n <textarea\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n rows={3}\n placeholder=\"e.g. Verified bank details, approved for transfer…\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n )}\n\n {dialog.type === 'reject' && (\n <div>\n <label className=\"text-xs font-medium text-text-primary block mb-1\">\n Reason for rejection <span className=\"text-status-error-text\">*</span>\n </label>\n <textarea\n value={reason}\n onChange={(e) => setReason(e.target.value)}\n rows={3}\n placeholder=\"e.g. Invalid bank details provided, please re-submit with correct IFSC…\"\n className=\"w-full rounded-input border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n )}\n\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={onClose}\n disabled={submitting}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void handleSubmit()}\n disabled={submitting || (dialog.type === 'reject' && !reason.trim())}\n className={`rounded-md px-4 py-2 text-sm font-medium disabled:opacity-50 ${\n dialog.type === 'reject'\n ? 'bg-action-danger-bg text-action-danger-text'\n : 'bg-action-primary-bg text-action-primary-text'\n }`}\n >\n {submitting\n ? 'Saving…'\n : dialog.type === 'approve'\n ? 'Approve'\n : dialog.type === 'reject'\n ? 'Reject'\n : 'Confirm Payment'}\n </button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nexport const PayoutAdminPage: FC = () => {\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const { apiGatewayUrl, authToken, orgId } = useBilling();\n\n const [summary, setSummary] = useState<PayoutAdminSummary | null>(null);\n const [requests, setRequests] = useState<PayoutRequest[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [hasMore, setHasMore] = useState(false);\n const [loading, setLoading] = useState(true);\n const [loadingMore, setLoadingMore] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [activeKind, setActiveKind] = useState<Kind>('CASH_PAYOUT');\n const [activeTab, setActiveTab] = useState<Tab>('pending');\n const [dialog, setDialog] = useState<ActionDialogState | null>(null);\n\n const requestContext = useMemo(\n () => ({ apiGatewayUrl, authToken, orgId }),\n [apiGatewayUrl, authToken, orgId]\n );\n\n const loadData = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const [loadedSummary, loadedRequests] = await Promise.all([\n fetchAdminPayoutSummary(requestContext),\n fetchAdminPayoutRequests(requestContext, { limit: REQUESTS_PAGE_SIZE, offset: 0 }),\n ]);\n setSummary(loadedSummary);\n setRequests(loadedRequests.items);\n setTotalCount(loadedRequests.totalCount);\n setHasMore(loadedRequests.hasMore);\n } catch (loadError) {\n setError(loadError instanceof Error ? loadError : new Error('Failed to load payout queue'));\n } finally {\n setLoading(false);\n }\n }, [requestContext]);\n\n const loadMore = async () => {\n if (loadingMore || !hasMore) return;\n\n setLoadingMore(true);\n setError(null);\n try {\n const loadedRequests = await fetchAdminPayoutRequests(requestContext, {\n limit: REQUESTS_PAGE_SIZE,\n offset: requests.length,\n });\n setRequests((current) => [...current, ...loadedRequests.items]);\n setTotalCount(loadedRequests.totalCount);\n setHasMore(loadedRequests.hasMore);\n } catch (loadError) {\n setError(\n loadError instanceof Error ? loadError : new Error('Failed to load more payout requests')\n );\n } finally {\n setLoadingMore(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [loadData]);\n\n if (!permissions.isBillingAdmin) {\n return <AccessDenied message=\"You don't have permission to manage payouts.\" />;\n }\n\n if (error && isServerError(error)) {\n return (\n <ServerError\n title=\"Payout queue unavailable\"\n message={error.message}\n onRetry={() => void loadData()}\n showRetry\n />\n );\n }\n\n // Split by kind FIRST (BOFF-7192) — a $35 revenue-share credit conversion\n // must never sit in the same list as a real bank payout, so every count and\n // sub-tab below is scoped to whichever kind tab is active.\n const kindRequests = requests.filter((r) => r.requestKind === activeKind);\n\n const pending = kindRequests.filter((r) => r.status === 'REQUESTED');\n const approved = kindRequests.filter((r) => r.status === 'APPROVED' || r.status === 'PROCESSING');\n const history = kindRequests.filter((r) =>\n ['PAID', 'REJECTED', 'FAILED', 'CANCELED'].includes(r.status)\n );\n\n const kindTabItems = KIND_TABS.map((kind) => ({\n ...kind,\n count: requests.filter((r) => r.requestKind === kind.id).length,\n }));\n\n const tabItems: { id: Tab; label: string; count: number }[] = [\n { id: 'pending', label: 'Pending', count: pending.length },\n { id: 'approved', label: 'Approved', count: approved.length },\n { id: 'history', label: 'History', count: history.length },\n ];\n\n const visibleRequests =\n activeTab === 'pending' ? pending : activeTab === 'approved' ? approved : history;\n\n const openDialog = (type: ActionDialogState['type'], request: PayoutRequest) => {\n setDialog({\n type,\n requestId: request.id,\n amount: request.requestedAmount,\n currency: request.currency,\n accountLabel:\n request.payoutAccount?.displayName ?? request.payoutAccount?.label ?? 'Unknown account',\n });\n };\n\n const handleConfirm = async (data: {\n notes?: string;\n reason?: string;\n externalReference?: string;\n }) => {\n if (!dialog) return;\n\n try {\n if (dialog.type === 'approve') {\n await approvePayoutRequest(dialog.requestId, data.notes ?? null, requestContext);\n emit('billing.payout.approved', {\n route: '/billing/payouts',\n entityId: dialog.requestId,\n source: 'payout-admin',\n });\n } else if (dialog.type === 'reject') {\n await rejectPayoutRequest(dialog.requestId, data.reason ?? '', requestContext);\n emit('billing.payout.rejected', {\n route: '/billing/payouts',\n entityId: dialog.requestId,\n source: 'payout-admin',\n reason: data.reason,\n });\n } else if (dialog.type === 'mark_paid') {\n await markPayoutPaid(\n dialog.requestId,\n data.externalReference ?? null,\n data.notes ?? null,\n requestContext\n );\n emit('billing.payout.processed', {\n route: '/billing/payouts',\n entityId: dialog.requestId,\n source: 'payout-admin',\n });\n }\n setDialog(null);\n await loadData();\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Action failed'));\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Payout queue\"\n description=\"Review withdrawal requests, verify payout details, and mark them as paid once funds are transferred.\"\n actions={\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n }\n />\n\n <PagePurpose>\n This queue holds two different kinds of request, split into the tabs below so a\n revenue-share credit conversion never gets mistaken for a real bank transfer.{' '}\n <strong>Cash Payouts</strong> are real money leaving the platform — developers withdrawing\n purchased or earned credits from billing to their own bank/UPI account.{' '}\n <strong>Revenue Share Credits</strong> are a developer requesting their store revenue share\n — approving one just converts the amount into earned credits on their billing account, no\n transfer involved.\n </PagePurpose>\n\n {summary && !loading && <SummaryBar summary={summary} />}\n\n {!loading && totalCount > 0 && (\n <div className=\"text-sm text-text-muted\">\n Showing {requests.length} of {totalCount} payout requests.\n </div>\n )}\n\n {loading && (\n <div className=\"grid gap-3 sm:grid-cols-3\">\n {[1, 2, 3].map((i) => (\n <div\n key={i}\n className=\"h-20 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n )}\n\n {error && !isServerError(error) && (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-sm text-status-error-text\">\n {error.message}\n </div>\n )}\n\n {/* Kind tabs — Cash Payouts vs Revenue Share Credits (BOFF-7192). Kept\n visually distinct from the status sub-tabs below: this is the\n primary split, since approving each kind does something completely\n different (real money vs. an internal credit conversion). */}\n <div className=\"grid gap-3 sm:grid-cols-2\">\n {kindTabItems.map((kind) => {\n const Icon = kind.id === 'CASH_PAYOUT' ? Banknote : Coins;\n const isActive = activeKind === kind.id;\n return (\n <button\n key={kind.id}\n type=\"button\"\n onClick={() => {\n setActiveKind(kind.id);\n setActiveTab('pending');\n }}\n className={`flex items-start gap-3 rounded-card border p-4 text-left transition-colors ${\n isActive\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)]'\n : 'border-border-subtle bg-bg-surface hover:border-border-strong'\n }`}\n >\n <Icon\n className={`size-5 mt-0.5 shrink-0 ${isActive ? 'text-text-link' : 'text-text-muted'}`}\n />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span\n className={`text-sm font-semibold ${isActive ? 'text-text-link' : 'text-text-primary'}`}\n >\n {kind.label}\n </span>\n {kind.count > 0 && (\n <span\n className={`rounded-full px-1.5 py-0.5 text-xs ${\n isActive\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {kind.count}\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-muted mt-0.5\">{kind.description}</p>\n </div>\n </button>\n );\n })}\n </div>\n\n {/* Tabs */}\n <div className=\"border-b border-border-subtle\">\n <div className=\"flex gap-0\">\n {tabItems.map((tab) => (\n <button\n key={tab.id}\n type=\"button\"\n onClick={() => setActiveTab(tab.id)}\n className={`flex items-center gap-2 border-b-2 px-4 py-2.5 text-sm font-medium transition-colors ${\n activeTab === tab.id\n ? 'border-action-primary-bg text-text-primary'\n : 'border-transparent text-text-muted hover:text-text-primary'\n }`}\n >\n {tab.label}\n {tab.count > 0 && (\n <span\n className={`rounded-full px-1.5 py-0.5 text-xs ${\n activeTab === tab.id\n ? 'bg-action-primary-bg text-action-primary-text'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {tab.count}\n </span>\n )}\n </button>\n ))}\n </div>\n </div>\n\n {/* How to pay — shown only on the Approved tab for real cash payouts */}\n {activeKind === 'CASH_PAYOUT' && activeTab === 'approved' && approved.length > 0 && (\n <div className=\"rounded-card border border-status-info-border bg-status-info-bg-subtle px-4 py-3 text-sm text-status-info-text\">\n <strong>How to pay:</strong> Click the account row to expand bank/UPI details → transfer\n the exact amount via your bank → click <strong>Mark as Paid</strong> and enter the UTR /\n transaction reference number.\n </div>\n )}\n\n {/* Request cards */}\n {loading ? (\n <div className=\"space-y-4\">\n {[1, 2].map((i) => (\n <div\n key={i}\n className=\"h-40 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n ) : visibleRequests.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={\n activeTab === 'pending'\n ? 'No pending requests'\n : activeTab === 'approved'\n ? 'No approved requests awaiting payment'\n : 'No history yet'\n }\n description={\n activeTab === 'pending'\n ? 'New requests will appear here.'\n : activeTab === 'approved'\n ? activeKind === 'CREDIT_CONVERSION'\n ? \"Revenue share requests skip this tab — approving one completes it immediately, there's no separate payment step.\"\n : 'Approve pending requests first, then send payment and mark as paid.'\n : 'Completed and rejected requests will appear here.'\n }\n />\n ) : (\n <div className=\"space-y-4\">\n {visibleRequests.map((request) => (\n <RequestCard\n key={request.id}\n request={request}\n onApprove={(r) => openDialog('approve', r)}\n onReject={(r) => openDialog('reject', r)}\n onMarkPaid={(r) => openDialog('mark_paid', r)}\n />\n ))}\n\n {hasMore && (\n <div className=\"flex justify-center pt-2\">\n <button\n type=\"button\"\n onClick={() => void loadMore()}\n disabled={loadingMore}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary disabled:opacity-50\"\n >\n {loadingMore ? 'Loading…' : 'Load more requests'}\n </button>\n </div>\n )}\n </div>\n )}\n\n {dialog && (\n <ActionDialog dialog={dialog} onClose={() => setDialog(null)} onConfirm={handleConfirm} />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -2777,6 +2777,7 @@ var Dn = t`
|
|
|
2777
2777
|
`, Ln = t`
|
|
2778
2778
|
query BillingDeveloperPortalRevenue($organizationId: ID!) {
|
|
2779
2779
|
developerOrgRevenueSummary(organizationId: $organizationId, period: "monthly") {
|
|
2780
|
+
currency
|
|
2780
2781
|
totalGrossRevenue
|
|
2781
2782
|
totalNetRevenue
|
|
2782
2783
|
totalPlatformFees
|