@burdenoff/microfe-billing 2026.623.3 → 2026.624.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 (43) hide show
  1. package/dist/billing/BillingUserRoutes.js +3 -3
  2. package/dist/billing/BillingUserRoutes.js.map +1 -1
  3. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +34 -34
  4. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
  5. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +94 -94
  6. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
  7. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +162 -187
  8. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
  9. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +146 -153
  10. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
  11. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +10 -10
  12. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
  13. package/dist/billing/modules/billing/pages/TransactionsPage.js +62 -62
  14. package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
  15. package/dist/billing/modules/coupons/pages/CouponDetailPage.js +164 -179
  16. package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
  17. package/dist/billing/modules/coupons/pages/CreateCouponPage.js +8 -8
  18. package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
  19. package/dist/billing/modules/earnings/pages/EarningsPage.js +243 -305
  20. package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
  21. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +73 -78
  22. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
  23. package/dist/billing/modules/earnings/pages/PayoutsPage.js +173 -179
  24. package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
  25. package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +4 -4
  26. package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
  27. package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +4 -4
  28. package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
  29. package/dist/billing/modules/settings/pages/SettingsPage.js +181 -173
  30. package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
  31. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +111 -99
  32. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
  33. package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js +68 -83
  34. package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
  35. package/dist/billing/shared/components/AccessDenied.js +4 -4
  36. package/dist/billing/shared/components/AccessDenied.js.map +1 -1
  37. package/dist/billing/shared/components/ActorIdentity.js +8 -8
  38. package/dist/billing/shared/components/ActorIdentity.js.map +1 -1
  39. package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js +11 -11
  40. package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js.map +1 -1
  41. package/dist/billing/shared/ui/MetricCard.js +1 -1
  42. package/dist/billing/shared/ui/MetricCard.js.map +1 -1
  43. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"PayoutsPage.js","names":[],"sources":["../../../../../src/billing/modules/earnings/pages/PayoutsPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type FC } from 'react';\nimport { Landmark, RefreshCw, Send } from 'lucide-react';\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { AccessDenied, EmptyState, PageHeader, ServerError } from '../../../shared/components';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\nimport { useGetDefaultBillingAccountSettingsQuery } from '../../../../generated/global-operations';\nimport {\n addPayoutAccount,\n archivePayoutAccount,\n fetchPayoutAccounts,\n fetchPayoutRequests,\n requestPayout,\n setDefaultPayoutAccount,\n updatePayoutAccount,\n type PayoutAccount,\n type PayoutRequest,\n} from '../api';\n\nexport const PayoutsPage: FC = () => {\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const { apiGatewayUrl, authToken, orgId } = useBilling();\n const { data: billingAccountData } = useGetDefaultBillingAccountSettingsQuery({});\n const billingAccountId = billingAccountData?.getDefaultBillingAccount?.id;\n\n const [accounts, setAccounts] = useState<PayoutAccount[]>([]);\n const [requests, setRequests] = useState<PayoutRequest[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const [accountForm, setAccountForm] = useState({\n label: '',\n type: 'BANK_ACCOUNT',\n providerPreference: 'AUTO',\n country: 'US',\n currency: 'USD',\n accountHolderName: '',\n bankName: '',\n accountNumber: '',\n routingNumber: '',\n ifsc: '',\n upiId: '',\n });\n const [requestAmount, setRequestAmount] = useState('');\n const [selectedAccountId, setSelectedAccountId] = useState<string>('');\n const [editingAccountId, setEditingAccountId] = useState<string | null>(null);\n\n const requestContext = useMemo(\n () => ({\n apiGatewayUrl,\n authToken,\n orgId,\n }),\n [apiGatewayUrl, authToken, orgId]\n );\n\n const loadData = async () => {\n if (!billingAccountId) return;\n setLoading(true);\n setError(null);\n try {\n const [loadedAccounts, loadedRequests] = await Promise.all([\n fetchPayoutAccounts(billingAccountId, requestContext),\n fetchPayoutRequests(billingAccountId, requestContext),\n ]);\n setAccounts(loadedAccounts);\n setRequests(loadedRequests);\n if (!selectedAccountId && loadedAccounts[0]?.id) {\n setSelectedAccountId(loadedAccounts[0].id);\n }\n } catch (loadError) {\n setError(loadError instanceof Error ? loadError : new Error('Failed to load payout data'));\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [billingAccountId, requestContext]);\n\n if (!permissions.canManagePaymentMethods) {\n return <AccessDenied message=\"You don't have permission to manage payout accounts.\" />;\n }\n\n if (error && isServerError(error)) {\n return (\n <ServerError\n title=\"Payout data unavailable\"\n message={error.message}\n onRetry={() => void loadData()}\n showRetry\n />\n );\n }\n\n const handleAddAccount = async () => {\n if (!billingAccountId) return;\n const payload = {\n billingAccountId,\n ...accountForm,\n };\n\n if (editingAccountId) {\n await updatePayoutAccount(editingAccountId, payload, requestContext);\n emit('billing.payout_account.updated', {\n route: '/billing/payouts',\n entityId: editingAccountId,\n source: 'payouts',\n });\n } else {\n await addPayoutAccount(payload, requestContext);\n emit('billing.payout_account.added', {\n route: '/billing/payouts',\n entityId: billingAccountId,\n source: 'payouts',\n });\n }\n\n setAccountForm({\n label: '',\n type: 'BANK_ACCOUNT',\n providerPreference: 'AUTO',\n country: 'US',\n currency: 'USD',\n accountHolderName: '',\n bankName: '',\n accountNumber: '',\n routingNumber: '',\n ifsc: '',\n upiId: '',\n });\n setEditingAccountId(null);\n await loadData();\n };\n\n const handleSetDefault = async (accountId: string) => {\n await setDefaultPayoutAccount(accountId, requestContext);\n await loadData();\n };\n\n const handleArchive = async (accountId: string) => {\n if (!window.confirm('Archive this payout account?')) return;\n await archivePayoutAccount(accountId, requestContext);\n emit('billing.payout_account.archived', {\n route: '/billing/payouts',\n entityId: accountId,\n source: 'payouts',\n });\n await loadData();\n };\n\n const handleEditAccount = (account: PayoutAccount) => {\n setEditingAccountId(account.id);\n setAccountForm((current) => ({\n ...current,\n label: account.label,\n type: account.type,\n providerPreference: account.providerPreference,\n country: account.country || current.country,\n currency: account.currency,\n accountHolderName: account.displayName || current.accountHolderName,\n bankName: '',\n accountNumber: '',\n routingNumber: '',\n ifsc: '',\n upiId: '',\n }));\n };\n\n const handleRequestPayout = async () => {\n if (!billingAccountId || !selectedAccountId || !requestAmount) return;\n await requestPayout(\n {\n billingAccountId,\n payoutAccountId: selectedAccountId,\n amount: Number(requestAmount),\n currency: accounts.find((account) => account.id === selectedAccountId)?.currency || 'USD',\n },\n requestContext\n );\n emit('billing.payout.requested', {\n route: '/billing/payouts',\n entityId: selectedAccountId,\n source: 'payouts',\n amount: Number(requestAmount),\n currency: accounts.find((account) => account.id === selectedAccountId)?.currency || 'USD',\n });\n setRequestAmount('');\n await loadData();\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Payouts\"\n description=\"Register payout accounts, request withdrawals, and track disbursement status.\"\n />\n\n {error && !isServerError(error) ? (\n <div className=\"rounded-lg border border-border bg-card p-4 text-sm text-destructive\">\n {error.message}\n </div>\n ) : null}\n\n <div className=\"grid gap-6 xl:grid-cols-[1.05fr,1.95fr]\">\n <section className=\"rounded-lg border border-border bg-card p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-foreground\">\n {editingAccountId ? 'Edit payout account' : 'Add payout account'}\n </h2>\n <div className=\"space-y-3\">\n <input\n value={accountForm.label}\n onChange={(event) =>\n setAccountForm((current) => ({ ...current, label: event.target.value }))\n }\n placeholder=\"Label\"\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground\"\n />\n <div className=\"grid gap-3 md:grid-cols-2\">\n <select\n value={accountForm.type}\n onChange={(event) =>\n setAccountForm((current) => ({ ...current, type: event.target.value }))\n }\n className=\"rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground\"\n >\n <option value=\"BANK_ACCOUNT\">Bank account</option>\n <option value=\"UPI\">UPI</option>\n <option value=\"WALLET\">Wallet</option>\n </select>\n <select\n value={accountForm.providerPreference}\n onChange={(event) =>\n setAccountForm((current) => ({\n ...current,\n providerPreference: event.target.value,\n }))\n }\n className=\"rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground\"\n >\n <option value=\"AUTO\">Auto</option>\n <option value=\"STRIPE\">Stripe</option>\n <option value=\"RAZORPAY\">Razorpay</option>\n <option value=\"MANUAL\">Manual</option>\n </select>\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <input\n value={accountForm.country}\n onChange={(event) =>\n setAccountForm((current) => ({\n ...current,\n country: event.target.value.toUpperCase(),\n }))\n }\n placeholder=\"Country\"\n className=\"rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground\"\n />\n <input\n value={accountForm.currency}\n onChange={(event) =>\n setAccountForm((current) => ({\n ...current,\n currency: event.target.value.toUpperCase(),\n }))\n }\n placeholder=\"Currency\"\n className=\"rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground\"\n />\n </div>\n <input\n value={accountForm.accountHolderName}\n onChange={(event) =>\n setAccountForm((current) => ({ ...current, accountHolderName: event.target.value }))\n }\n placeholder=\"Account holder name\"\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground\"\n />\n {accountForm.type === 'BANK_ACCOUNT' ? (\n <>\n <input\n value={accountForm.bankName}\n onChange={(event) =>\n setAccountForm((current) => ({ ...current, bankName: event.target.value }))\n }\n placeholder=\"Bank name\"\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground\"\n />\n <input\n value={accountForm.accountNumber}\n onChange={(event) =>\n setAccountForm((current) => ({ ...current, accountNumber: event.target.value }))\n }\n placeholder=\"Account number\"\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground\"\n />\n <input\n value={\n accountForm.country === 'IN' ? accountForm.ifsc : accountForm.routingNumber\n }\n onChange={(event) =>\n setAccountForm((current) =>\n current.country === 'IN'\n ? { ...current, ifsc: event.target.value }\n : { ...current, routingNumber: event.target.value }\n )\n }\n placeholder={accountForm.country === 'IN' ? 'IFSC' : 'Routing number'}\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground\"\n />\n </>\n ) : accountForm.type === 'UPI' ? (\n <input\n value={accountForm.upiId}\n onChange={(event) =>\n setAccountForm((current) => ({ ...current, upiId: event.target.value }))\n }\n placeholder=\"UPI ID\"\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground\"\n />\n ) : null}\n </div>\n <button\n type=\"button\"\n onClick={() => void handleAddAccount()}\n className=\"mt-4 inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground\"\n >\n <Landmark className=\"size-4\" />\n {editingAccountId ? 'Update payout account' : 'Save payout account'}\n </button>\n {editingAccountId ? (\n <button\n type=\"button\"\n onClick={() => {\n setEditingAccountId(null);\n setAccountForm({\n label: '',\n type: 'BANK_ACCOUNT',\n providerPreference: 'AUTO',\n country: 'US',\n currency: 'USD',\n accountHolderName: '',\n bankName: '',\n accountNumber: '',\n routingNumber: '',\n ifsc: '',\n upiId: '',\n });\n }}\n className=\"mt-4 ml-3 inline-flex items-center gap-2 rounded-md border border-border px-4 py-2 text-sm font-medium text-foreground\"\n >\n Cancel edit\n </button>\n ) : null}\n </section>\n\n <section className=\"space-y-4\">\n <div className=\"rounded-lg border border-border bg-card p-4\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-foreground\">Request payout</h2>\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-medium text-foreground\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n </div>\n {accounts.length === 0 && !loading ? (\n <EmptyState\n icon={<Landmark className=\"size-8\" />}\n title=\"No payout accounts yet\"\n description=\"Add a payout account before requesting a withdrawal.\"\n />\n ) : (\n <div className=\"space-y-3\">\n <select\n value={selectedAccountId}\n onChange={(event) => setSelectedAccountId(event.target.value)}\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground\"\n >\n {accounts.map((account) => (\n <option key={account.id} value={account.id}>\n {account.label} · {account.currency} · {account.status}\n </option>\n ))}\n </select>\n <input\n value={requestAmount}\n onChange={(event) => setRequestAmount(event.target.value)}\n placeholder=\"Amount\"\n className=\"w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground\"\n />\n <button\n type=\"button\"\n onClick={() => void handleRequestPayout()}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground\"\n >\n <Send className=\"size-4\" />\n Request payout\n </button>\n </div>\n )}\n\n {accounts.length > 0 ? (\n <div className=\"mt-4 space-y-3 rounded-md border border-border bg-background/60 p-3\">\n <h3 className=\"text-sm font-semibold text-foreground\">Saved payout accounts</h3>\n {accounts.map((account) => (\n <div\n key={account.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-md border border-border/70 bg-card px-3 py-2 text-sm\"\n >\n <div>\n <p className=\"font-medium text-foreground\">\n {account.label} {account.isDefault ? '• Default' : ''}\n </p>\n <p className=\"text-muted-foreground\">\n {account.providerPreference} · {account.currency} · {account.status}\n </p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={() => handleEditAccount(account)}\n className=\"rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground\"\n >\n Edit\n </button>\n {!account.isDefault ? (\n <button\n type=\"button\"\n onClick={() => void handleSetDefault(account.id)}\n className=\"rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground\"\n >\n Make default\n </button>\n ) : null}\n <button\n type=\"button\"\n onClick={() => void handleArchive(account.id)}\n className=\"rounded-md border border-border px-3 py-1.5 text-xs font-medium text-destructive\"\n >\n Archive\n </button>\n </div>\n </div>\n ))}\n </div>\n ) : null}\n </div>\n\n <div className=\"rounded-lg border border-border bg-card p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-foreground\">Payout requests</h2>\n {requests.length === 0 && !loading ? (\n <EmptyState\n icon={<Send className=\"size-8\" />}\n title=\"No payout requests yet\"\n description=\"Your payout requests and processing updates will appear here.\"\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border text-left text-muted-foreground\">\n <th className=\"px-3 py-2\">Amount</th>\n <th className=\"px-3 py-2\">Provider</th>\n <th className=\"px-3 py-2\">Status</th>\n <th className=\"px-3 py-2\">Account</th>\n <th className=\"px-3 py-2\">Created</th>\n </tr>\n </thead>\n <tbody>\n {requests.map((request) => (\n <tr key={request.id} className=\"border-b border-border/70 text-foreground\">\n <td className=\"px-3 py-2 font-medium\">\n {formatCurrency(request.requestedAmount, request.currency)}\n </td>\n <td className=\"px-3 py-2\">{request.provider}</td>\n <td className=\"px-3 py-2\">{request.status}</td>\n <td className=\"px-3 py-2\">{request.payoutAccount?.label || '-'}</td>\n <td className=\"px-3 py-2\">{formatDateTime(request.createdAt)}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </div>\n </section>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"PayoutsPage.js","names":[],"sources":["../../../../../src/billing/modules/earnings/pages/PayoutsPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState, type FC } from 'react';\nimport { Landmark, RefreshCw, Send } from 'lucide-react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { AccessDenied, EmptyState, PageHeader, ServerError } from '../../../shared/components';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\nimport { useGetDefaultBillingAccountSettingsQuery } from '../../../../generated/global-operations';\nimport {\n addPayoutAccount,\n archivePayoutAccount,\n fetchPayoutAccounts,\n fetchPayoutRequests,\n requestPayout,\n setDefaultPayoutAccount,\n updatePayoutAccount,\n type PayoutAccount,\n type PayoutRequest,\n} from '../api';\n\nconst payoutRequestColumns: ResponsiveTableColumn<PayoutRequest>[] = [\n {\n key: 'amount',\n header: 'Amount',\n priority: 'primary',\n cell: (request) => (\n <span className=\"tabular-nums\">\n {formatCurrency(request.requestedAmount, request.currency)}\n </span>\n ),\n },\n { key: 'provider', header: 'Provider', cell: (request) => request.provider },\n { key: 'status', header: 'Status', cell: (request) => request.status },\n {\n key: 'account',\n header: 'Account',\n cell: (request) => request.payoutAccount?.label || '-',\n },\n {\n key: 'created',\n header: 'Created',\n cell: (request) => formatDateTime(request.createdAt),\n },\n];\n\nexport const PayoutsPage: FC = () => {\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const { apiGatewayUrl, authToken, orgId } = useBilling();\n const { data: billingAccountData } = useGetDefaultBillingAccountSettingsQuery({});\n const billingAccountId = billingAccountData?.getDefaultBillingAccount?.id;\n\n const [accounts, setAccounts] = useState<PayoutAccount[]>([]);\n const [requests, setRequests] = useState<PayoutRequest[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const [accountForm, setAccountForm] = useState({\n label: '',\n type: 'BANK_ACCOUNT',\n providerPreference: 'AUTO',\n country: 'US',\n currency: 'USD',\n accountHolderName: '',\n bankName: '',\n accountNumber: '',\n routingNumber: '',\n ifsc: '',\n upiId: '',\n });\n const [requestAmount, setRequestAmount] = useState('');\n const [selectedAccountId, setSelectedAccountId] = useState<string>('');\n const [editingAccountId, setEditingAccountId] = useState<string | null>(null);\n const [archiveAccountId, setArchiveAccountId] = useState<string | null>(null);\n\n const requestContext = useMemo(\n () => ({\n apiGatewayUrl,\n authToken,\n orgId,\n }),\n [apiGatewayUrl, authToken, orgId]\n );\n\n const loadData = async () => {\n if (!billingAccountId) return;\n setLoading(true);\n setError(null);\n try {\n const [loadedAccounts, loadedRequests] = await Promise.all([\n fetchPayoutAccounts(billingAccountId, requestContext),\n fetchPayoutRequests(billingAccountId, requestContext),\n ]);\n setAccounts(loadedAccounts);\n setRequests(loadedRequests);\n if (!selectedAccountId && loadedAccounts[0]?.id) {\n setSelectedAccountId(loadedAccounts[0].id);\n }\n } catch (loadError) {\n setError(loadError instanceof Error ? loadError : new Error('Failed to load payout data'));\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [billingAccountId, requestContext]);\n\n if (!permissions.canManagePaymentMethods) {\n return <AccessDenied message=\"You don't have permission to manage payout accounts.\" />;\n }\n\n if (error && isServerError(error)) {\n return (\n <ServerError\n title=\"Payout data unavailable\"\n message={error.message}\n onRetry={() => void loadData()}\n showRetry\n />\n );\n }\n\n const handleAddAccount = async () => {\n if (!billingAccountId) return;\n const payload = {\n billingAccountId,\n ...accountForm,\n };\n\n if (editingAccountId) {\n await updatePayoutAccount(editingAccountId, payload, requestContext);\n emit('billing.payout_account.updated', {\n route: '/billing/payouts',\n entityId: editingAccountId,\n source: 'payouts',\n });\n } else {\n await addPayoutAccount(payload, requestContext);\n emit('billing.payout_account.added', {\n route: '/billing/payouts',\n entityId: billingAccountId,\n source: 'payouts',\n });\n }\n\n setAccountForm({\n label: '',\n type: 'BANK_ACCOUNT',\n providerPreference: 'AUTO',\n country: 'US',\n currency: 'USD',\n accountHolderName: '',\n bankName: '',\n accountNumber: '',\n routingNumber: '',\n ifsc: '',\n upiId: '',\n });\n setEditingAccountId(null);\n await loadData();\n };\n\n const handleSetDefault = async (accountId: string) => {\n await setDefaultPayoutAccount(accountId, requestContext);\n await loadData();\n };\n\n const handleArchive = async (accountId: string) => {\n await archivePayoutAccount(accountId, requestContext);\n emit('billing.payout_account.archived', {\n route: '/billing/payouts',\n entityId: accountId,\n source: 'payouts',\n });\n await loadData();\n };\n\n const handleEditAccount = (account: PayoutAccount) => {\n setEditingAccountId(account.id);\n setAccountForm((current) => ({\n ...current,\n label: account.label,\n type: account.type,\n providerPreference: account.providerPreference,\n country: account.country || current.country,\n currency: account.currency,\n accountHolderName: account.displayName || current.accountHolderName,\n bankName: '',\n accountNumber: '',\n routingNumber: '',\n ifsc: '',\n upiId: '',\n }));\n };\n\n const handleRequestPayout = async () => {\n if (!billingAccountId || !selectedAccountId || !requestAmount) return;\n await requestPayout(\n {\n billingAccountId,\n payoutAccountId: selectedAccountId,\n amount: Number(requestAmount),\n currency: accounts.find((account) => account.id === selectedAccountId)?.currency || 'USD',\n },\n requestContext\n );\n emit('billing.payout.requested', {\n route: '/billing/payouts',\n entityId: selectedAccountId,\n source: 'payouts',\n amount: Number(requestAmount),\n currency: accounts.find((account) => account.id === selectedAccountId)?.currency || 'USD',\n });\n setRequestAmount('');\n await loadData();\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Payouts\"\n description=\"Register payout accounts, request withdrawals, and track disbursement status.\"\n />\n\n {error && !isServerError(error) ? (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm text-status-error-text\">\n {error.message}\n </div>\n ) : null}\n\n <div className=\"grid gap-6 xl:grid-cols-[1.05fr,1.95fr]\">\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n {editingAccountId ? 'Edit payout account' : 'Add payout account'}\n </h2>\n <div className=\"space-y-3\">\n <input\n value={accountForm.label}\n onChange={(event) =>\n setAccountForm((current) => ({ ...current, label: event.target.value }))\n }\n placeholder=\"Label\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <div className=\"grid gap-3 md:grid-cols-2\">\n <select\n value={accountForm.type}\n onChange={(event) =>\n setAccountForm((current) => ({ ...current, type: event.target.value }))\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"BANK_ACCOUNT\">Bank account</option>\n <option value=\"UPI\">UPI</option>\n <option value=\"WALLET\">Wallet</option>\n </select>\n <select\n value={accountForm.providerPreference}\n onChange={(event) =>\n setAccountForm((current) => ({\n ...current,\n providerPreference: event.target.value,\n }))\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n <option value=\"AUTO\">Auto</option>\n <option value=\"STRIPE\">Stripe</option>\n <option value=\"RAZORPAY\">Razorpay</option>\n <option value=\"MANUAL\">Manual</option>\n </select>\n </div>\n <div className=\"grid gap-3 md:grid-cols-2\">\n <input\n value={accountForm.country}\n onChange={(event) =>\n setAccountForm((current) => ({\n ...current,\n country: event.target.value.toUpperCase(),\n }))\n }\n placeholder=\"Country\"\n className=\"rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <input\n value={accountForm.currency}\n onChange={(event) =>\n setAccountForm((current) => ({\n ...current,\n currency: event.target.value.toUpperCase(),\n }))\n }\n placeholder=\"Currency\"\n className=\"rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </div>\n <input\n value={accountForm.accountHolderName}\n onChange={(event) =>\n setAccountForm((current) => ({ ...current, accountHolderName: event.target.value }))\n }\n placeholder=\"Account holder name\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n {accountForm.type === 'BANK_ACCOUNT' ? (\n <>\n <input\n value={accountForm.bankName}\n onChange={(event) =>\n setAccountForm((current) => ({ ...current, bankName: event.target.value }))\n }\n placeholder=\"Bank name\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <input\n value={accountForm.accountNumber}\n onChange={(event) =>\n setAccountForm((current) => ({ ...current, accountNumber: event.target.value }))\n }\n placeholder=\"Account number\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <input\n value={\n accountForm.country === 'IN' ? accountForm.ifsc : accountForm.routingNumber\n }\n onChange={(event) =>\n setAccountForm((current) =>\n current.country === 'IN'\n ? { ...current, ifsc: event.target.value }\n : { ...current, routingNumber: event.target.value }\n )\n }\n placeholder={accountForm.country === 'IN' ? 'IFSC' : 'Routing number'}\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </>\n ) : accountForm.type === 'UPI' ? (\n <input\n value={accountForm.upiId}\n onChange={(event) =>\n setAccountForm((current) => ({ ...current, upiId: event.target.value }))\n }\n placeholder=\"UPI ID\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n ) : null}\n </div>\n <button\n type=\"button\"\n onClick={() => void handleAddAccount()}\n className=\"mt-4 inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n <Landmark className=\"size-4\" />\n {editingAccountId ? 'Update payout account' : 'Save payout account'}\n </button>\n {editingAccountId ? (\n <button\n type=\"button\"\n onClick={() => {\n setEditingAccountId(null);\n setAccountForm({\n label: '',\n type: 'BANK_ACCOUNT',\n providerPreference: 'AUTO',\n country: 'US',\n currency: 'USD',\n accountHolderName: '',\n bankName: '',\n accountNumber: '',\n routingNumber: '',\n ifsc: '',\n upiId: '',\n });\n }}\n className=\"mt-4 ml-3 inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary\"\n >\n Cancel edit\n </button>\n ) : null}\n </section>\n\n <section className=\"space-y-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Request payout</h2>\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n </div>\n {accounts.length === 0 && !loading ? (\n <EmptyState\n icon={<Landmark className=\"size-8\" />}\n title=\"No payout accounts yet\"\n description=\"Add a payout account before requesting a withdrawal.\"\n />\n ) : (\n <div className=\"space-y-3\">\n <select\n value={selectedAccountId}\n onChange={(event) => setSelectedAccountId(event.target.value)}\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n >\n {accounts.map((account) => (\n <option key={account.id} value={account.id}>\n {account.label} · {account.currency} · {account.status}\n </option>\n ))}\n </select>\n <input\n value={requestAmount}\n onChange={(event) => setRequestAmount(event.target.value)}\n placeholder=\"Amount\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n <button\n type=\"button\"\n onClick={() => void handleRequestPayout()}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text\"\n >\n <Send className=\"size-4\" />\n Request payout\n </button>\n </div>\n )}\n\n {accounts.length > 0 ? (\n <div className=\"mt-4 space-y-3 rounded-md border border-border-subtle bg-bg-surface/60 p-3\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Saved payout accounts</h3>\n {accounts.map((account) => (\n <div\n key={account.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-md border border-border-subtle/70 bg-bg-surface px-3 py-2 text-sm\"\n >\n <div>\n <p className=\"font-medium text-text-primary\">\n {account.label} {account.isDefault ? '• Default' : ''}\n </p>\n <p className=\"text-text-secondary\">\n {account.providerPreference} · {account.currency} · {account.status}\n </p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={() => handleEditAccount(account)}\n className=\"rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary\"\n >\n Edit\n </button>\n {!account.isDefault ? (\n <button\n type=\"button\"\n onClick={() => void handleSetDefault(account.id)}\n className=\"rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary\"\n >\n Make default\n </button>\n ) : null}\n <button\n type=\"button\"\n onClick={() => setArchiveAccountId(account.id)}\n className=\"rounded-md border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-error-text\"\n >\n Archive\n </button>\n </div>\n </div>\n ))}\n </div>\n ) : null}\n </div>\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Payout requests</h2>\n {requests.length === 0 && !loading ? (\n <EmptyState\n icon={<Send className=\"size-8\" />}\n title=\"No payout requests yet\"\n description=\"Your payout requests and processing updates will appear here.\"\n />\n ) : (\n <ResponsiveTable\n data={requests}\n rowKey={(request) => request.id}\n columns={payoutRequestColumns}\n />\n )}\n </div>\n </section>\n </div>\n\n <AlertDialog\n open={archiveAccountId !== null}\n onOpenChange={(open) => {\n if (!open) setArchiveAccountId(null);\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Archive payout account?</AlertDialogTitle>\n <AlertDialogDescription>\n Archive this payout account? You can add a new one at any time.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={() => {\n const id = archiveAccountId;\n setArchiveAccountId(null);\n if (id) void handleArchive(id);\n }}\n >\n Archive\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -143,9 +143,9 @@ var u = () => {
143
143
  /* @__PURE__ */ c("div", { children: [
144
144
  /* @__PURE__ */ c("label", {
145
145
  htmlFor: "workspaceId",
146
- className: "block text-sm font-medium text-foreground mb-1",
146
+ className: "block text-sm font-medium text-text-primary mb-1",
147
147
  children: ["Workspace ID ", /* @__PURE__ */ s("span", {
148
- className: "text-muted-foreground font-normal",
148
+ className: "text-text-secondary font-normal",
149
149
  children: "(optional)"
150
150
  })]
151
151
  }),
@@ -154,11 +154,11 @@ var u = () => {
154
154
  type: "text",
155
155
  value: b,
156
156
  onChange: (e) => x(e.target.value),
157
- className: "w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary font-mono text-sm",
157
+ className: "w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-primary font-mono text-sm",
158
158
  placeholder: "e.g. ws_01jk3n2… (leave blank to auto-resolve)"
159
159
  }),
160
160
  /* @__PURE__ */ s("p", {
161
- className: "text-xs text-muted-foreground mt-1",
161
+ className: "text-xs text-text-secondary mt-1",
162
162
  children: "If blank, the first active workspace for this org is used automatically. Set explicitly when the org has multiple workspaces."
163
163
  })
164
164
  ] }),
@@ -1 +1 @@
1
- {"version":3,"file":"PlanAssignFreePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanAssignFreePage.tsx"],"sourcesContent":["/**\n * Plans Module - Assign Plan Free (Admin only)\n *\n * Internal tool for assigning any plan to a billing account at no charge.\n * Protected by PLAN_ASSIGN_SECRET — only team members who know the secret\n * can use this page. Never expose to end users.\n *\n * Route: /billing/plans/assign-free\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useAssignPlanFreeMutation } from '../../../../generated/global-operations';\nimport { usePlans } from '../hooks';\n\nexport const PlanAssignFreePage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n\n const [secret, setSecret] = useState('');\n const [billingAccountId, setBillingAccountId] = useState('');\n const [planId, setPlanId] = useState('');\n const [workspaceId, setWorkspaceId] = useState('');\n const [result, setResult] = useState<{ success: boolean; message: string } | null>(null);\n\n // Fetch all plans (including non-purchasable internal ones) for the dropdown\n const { plans, isLoading: isPlansLoading } = usePlans({ includeFree: true });\n\n const [assignPlanFree, { loading: isAssigning }] = useAssignPlanFreeMutation();\n\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.assignFree.noPermission',\n \"You don't have permission to access this page.\"\n )}\n />\n );\n }\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setResult(null);\n\n if (!secret.trim() || !billingAccountId.trim() || !planId) {\n setResult({ success: false, message: 'All fields are required.' });\n return;\n }\n\n try {\n const { data } = await assignPlanFree({\n variables: {\n secret: secret.trim(),\n billingAccountId: billingAccountId.trim(),\n planId,\n ...(workspaceId.trim() ? { workspaceId: workspaceId.trim() } : {}),\n },\n });\n\n if (data?.assignPlanFree) {\n const sub = data.assignPlanFree;\n setResult({\n success: true,\n message: `Plan assigned. Subscription ID: ${sub.id} · Status: ${sub.status} · Valid: ${new Date(sub.startDate).toLocaleDateString()} → ${new Date(sub.endDate).toLocaleDateString()}`,\n });\n // Clear sensitive fields\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setWorkspaceId('');\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Failed to assign plan.';\n setResult({ success: false, message: msg });\n }\n };\n\n const selectedPlan = plans.find((p) => p.id === planId);\n\n return (\n <div className=\"space-y-6 p-6 max-w-2xl\">\n <PageHeader\n title={tr('billing.assignFree.title', 'Assign Plan for Free')}\n description={tr(\n 'billing.assignFree.description',\n 'Internally assign any plan to a billing account at no charge. Requires the team secret.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-3 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n ← Back to Plans\n </button>\n }\n />\n\n {/* Warning banner */}\n <div className=\"flex items-start gap-3 p-4 rounded-lg border border-status-warning-border bg-status-warning-bg/10\">\n <svg\n className=\"size-5 text-status-warning-text shrink-0 mt-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.962-.833-2.732 0L3.072 16.5c-.77.833.192 2.5 1.732 2.5z\"\n />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-status-warning-text\">Internal admin tool</p>\n <p className=\"text-xs text-status-warning-text/80 mt-0.5\">\n This bypasses all payment flows. The plan is assigned immediately with no charge. Use\n only for enterprise deals or internal accounts.\n </p>\n </div>\n </div>\n\n <form\n onSubmit={handleSubmit}\n className=\"border border-border-subtle rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\"\n >\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">Assignment Details</h2>\n </div>\n\n <div className=\"p-4 space-y-4\">\n {/* Secret */}\n <div>\n <label htmlFor=\"secret\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Team Secret <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"secret\"\n type=\"password\"\n value={secret}\n onChange={(e) => setSecret(e.target.value)}\n autoComplete=\"off\"\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] font-mono\"\n placeholder=\"Enter PLAN_ASSIGN_SECRET\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n The secret key shared within the team. Never expose this to users.\n </p>\n </div>\n\n {/* Billing Account ID */}\n <div>\n <label\n htmlFor=\"billingAccountId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Billing Account ID <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"billingAccountId\"\n type=\"text\"\n value={billingAccountId}\n onChange={(e) => setBillingAccountId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] font-mono text-sm\"\n placeholder=\"e.g. ba_01jk3n2...\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Find this in the user's billing account settings or via the billing accounts list.\n </p>\n </div>\n\n {/* Workspace ID (optional — auto-resolved from org if omitted) */}\n <div>\n <label htmlFor=\"workspaceId\" className=\"block text-sm font-medium text-foreground mb-1\">\n Workspace ID <span className=\"text-muted-foreground font-normal\">(optional)</span>\n </label>\n <input\n id=\"workspaceId\"\n type=\"text\"\n value={workspaceId}\n onChange={(e) => setWorkspaceId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary font-mono text-sm\"\n placeholder=\"e.g. ws_01jk3n2… (leave blank to auto-resolve)\"\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n If blank, the first active workspace for this org is used automatically. Set\n explicitly when the org has multiple workspaces.\n </p>\n </div>\n\n {/* Plan selector */}\n <div>\n <label htmlFor=\"planId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Plan <span className=\"text-status-error-text\">*</span>\n </label>\n {isPlansLoading ? (\n <div className=\"h-10 bg-bg-sunken animate-pulse rounded-md\" />\n ) : (\n <select\n id=\"planId\"\n value={planId}\n onChange={(e) => setPlanId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n required\n >\n <option value=\"\" disabled>\n Select a plan…\n </option>\n {plans.map((plan) => (\n <option key={plan.id} value={plan.id}>\n {plan.name}\n {!plan.isPurchasable ? ' (internal)' : ''}\n {plan.price != null && plan.price > 0\n ? ` — ${plan.currency ?? 'USD'} ${plan.price}`\n : ''}\n </option>\n ))}\n </select>\n )}\n {selectedPlan && (\n <p className=\"text-xs text-text-muted mt-1\">\n {selectedPlan.isPurchasable ? 'Public plan' : 'Internal / non-purchasable plan'}\n {selectedPlan.productID ? ` · Product: ${selectedPlan.productID}` : ''}\n </p>\n )}\n </div>\n </div>\n\n {/* Result banner */}\n {result && (\n <div\n className={`mx-4 mb-4 p-3 rounded-lg border text-sm ${\n result.success\n ? 'border-status-success-border bg-status-success-bg/10 text-status-success-text'\n : 'border-status-error-border bg-status-error-bg/10 text-status-error-text'\n }`}\n >\n {result.message}\n </div>\n )}\n\n <div className=\"px-4 pb-4 flex items-center gap-3\">\n <button\n type=\"submit\"\n disabled={isAssigning || !secret || !billingAccountId || !planId}\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 {isAssigning ? 'Assigning…' : 'Assign Plan for Free'}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setWorkspaceId('');\n setResult(null);\n }}\n className=\"px-4 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n Clear\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"PlanAssignFreePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanAssignFreePage.tsx"],"sourcesContent":["/**\n * Plans Module - Assign Plan Free (Admin only)\n *\n * Internal tool for assigning any plan to a billing account at no charge.\n * Protected by PLAN_ASSIGN_SECRET — only team members who know the secret\n * can use this page. Never expose to end users.\n *\n * Route: /billing/plans/assign-free\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useAssignPlanFreeMutation } from '../../../../generated/global-operations';\nimport { usePlans } from '../hooks';\n\nexport const PlanAssignFreePage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n\n const [secret, setSecret] = useState('');\n const [billingAccountId, setBillingAccountId] = useState('');\n const [planId, setPlanId] = useState('');\n const [workspaceId, setWorkspaceId] = useState('');\n const [result, setResult] = useState<{ success: boolean; message: string } | null>(null);\n\n // Fetch all plans (including non-purchasable internal ones) for the dropdown\n const { plans, isLoading: isPlansLoading } = usePlans({ includeFree: true });\n\n const [assignPlanFree, { loading: isAssigning }] = useAssignPlanFreeMutation();\n\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.assignFree.noPermission',\n \"You don't have permission to access this page.\"\n )}\n />\n );\n }\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setResult(null);\n\n if (!secret.trim() || !billingAccountId.trim() || !planId) {\n setResult({ success: false, message: 'All fields are required.' });\n return;\n }\n\n try {\n const { data } = await assignPlanFree({\n variables: {\n secret: secret.trim(),\n billingAccountId: billingAccountId.trim(),\n planId,\n ...(workspaceId.trim() ? { workspaceId: workspaceId.trim() } : {}),\n },\n });\n\n if (data?.assignPlanFree) {\n const sub = data.assignPlanFree;\n setResult({\n success: true,\n message: `Plan assigned. Subscription ID: ${sub.id} · Status: ${sub.status} · Valid: ${new Date(sub.startDate).toLocaleDateString()} → ${new Date(sub.endDate).toLocaleDateString()}`,\n });\n // Clear sensitive fields\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setWorkspaceId('');\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : 'Failed to assign plan.';\n setResult({ success: false, message: msg });\n }\n };\n\n const selectedPlan = plans.find((p) => p.id === planId);\n\n return (\n <div className=\"space-y-6 p-6 max-w-2xl\">\n <PageHeader\n title={tr('billing.assignFree.title', 'Assign Plan for Free')}\n description={tr(\n 'billing.assignFree.description',\n 'Internally assign any plan to a billing account at no charge. Requires the team secret.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-3 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n ← Back to Plans\n </button>\n }\n />\n\n {/* Warning banner */}\n <div className=\"flex items-start gap-3 p-4 rounded-lg border border-status-warning-border bg-status-warning-bg/10\">\n <svg\n className=\"size-5 text-status-warning-text shrink-0 mt-0.5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.962-.833-2.732 0L3.072 16.5c-.77.833.192 2.5 1.732 2.5z\"\n />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-status-warning-text\">Internal admin tool</p>\n <p className=\"text-xs text-status-warning-text/80 mt-0.5\">\n This bypasses all payment flows. The plan is assigned immediately with no charge. Use\n only for enterprise deals or internal accounts.\n </p>\n </div>\n </div>\n\n <form\n onSubmit={handleSubmit}\n className=\"border border-border-subtle rounded-card bg-bg-surface overflow-hidden shadow-[var(--shadow-elevation-1)]\"\n >\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken/30\">\n <h2 className=\"font-semibold text-text-primary\">Assignment Details</h2>\n </div>\n\n <div className=\"p-4 space-y-4\">\n {/* Secret */}\n <div>\n <label htmlFor=\"secret\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Team Secret <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"secret\"\n type=\"password\"\n value={secret}\n onChange={(e) => setSecret(e.target.value)}\n autoComplete=\"off\"\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] font-mono\"\n placeholder=\"Enter PLAN_ASSIGN_SECRET\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n The secret key shared within the team. Never expose this to users.\n </p>\n </div>\n\n {/* Billing Account ID */}\n <div>\n <label\n htmlFor=\"billingAccountId\"\n className=\"block text-sm font-medium text-text-primary mb-1\"\n >\n Billing Account ID <span className=\"text-status-error-text\">*</span>\n </label>\n <input\n id=\"billingAccountId\"\n type=\"text\"\n value={billingAccountId}\n onChange={(e) => setBillingAccountId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] font-mono text-sm\"\n placeholder=\"e.g. ba_01jk3n2...\"\n required\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Find this in the user's billing account settings or via the billing accounts list.\n </p>\n </div>\n\n {/* Workspace ID (optional — auto-resolved from org if omitted) */}\n <div>\n <label htmlFor=\"workspaceId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Workspace ID <span className=\"text-text-secondary font-normal\">(optional)</span>\n </label>\n <input\n id=\"workspaceId\"\n type=\"text\"\n value={workspaceId}\n onChange={(e) => setWorkspaceId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-primary font-mono text-sm\"\n placeholder=\"e.g. ws_01jk3n2… (leave blank to auto-resolve)\"\n />\n <p className=\"text-xs text-text-secondary mt-1\">\n If blank, the first active workspace for this org is used automatically. Set\n explicitly when the org has multiple workspaces.\n </p>\n </div>\n\n {/* Plan selector */}\n <div>\n <label htmlFor=\"planId\" className=\"block text-sm font-medium text-text-primary mb-1\">\n Plan <span className=\"text-status-error-text\">*</span>\n </label>\n {isPlansLoading ? (\n <div className=\"h-10 bg-bg-sunken animate-pulse rounded-md\" />\n ) : (\n <select\n id=\"planId\"\n value={planId}\n onChange={(e) => setPlanId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n required\n >\n <option value=\"\" disabled>\n Select a plan…\n </option>\n {plans.map((plan) => (\n <option key={plan.id} value={plan.id}>\n {plan.name}\n {!plan.isPurchasable ? ' (internal)' : ''}\n {plan.price != null && plan.price > 0\n ? ` — ${plan.currency ?? 'USD'} ${plan.price}`\n : ''}\n </option>\n ))}\n </select>\n )}\n {selectedPlan && (\n <p className=\"text-xs text-text-muted mt-1\">\n {selectedPlan.isPurchasable ? 'Public plan' : 'Internal / non-purchasable plan'}\n {selectedPlan.productID ? ` · Product: ${selectedPlan.productID}` : ''}\n </p>\n )}\n </div>\n </div>\n\n {/* Result banner */}\n {result && (\n <div\n className={`mx-4 mb-4 p-3 rounded-lg border text-sm ${\n result.success\n ? 'border-status-success-border bg-status-success-bg/10 text-status-success-text'\n : 'border-status-error-border bg-status-error-bg/10 text-status-error-text'\n }`}\n >\n {result.message}\n </div>\n )}\n\n <div className=\"px-4 pb-4 flex items-center gap-3\">\n <button\n type=\"submit\"\n disabled={isAssigning || !secret || !billingAccountId || !planId}\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 {isAssigning ? 'Assigning…' : 'Assign Plan for Free'}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setSecret('');\n setBillingAccountId('');\n setPlanId('');\n setWorkspaceId('');\n setResult(null);\n }}\n className=\"px-4 py-2 text-sm border border-border-subtle rounded-md text-text-primary hover:bg-bg-sunken transition-colors\"\n >\n Clear\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -95,7 +95,7 @@ var f = [
95
95
  value: x.name,
96
96
  onChange: (e) => C("name", e.target.value),
97
97
  placeholder: "e.g. June Launch Offer",
98
- className: `w-full px-3 py-2 rounded-input border ${v.name ? "border-destructive" : "border-border-subtle"} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`
98
+ className: `w-full px-3 py-2 rounded-input border ${v.name ? "border-status-error-border" : "border-border-subtle"} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`
99
99
  }),
100
100
  v.name && /* @__PURE__ */ u("p", {
101
101
  className: "text-xs text-status-error-text mt-1",
@@ -132,7 +132,7 @@ var f = [
132
132
  min: 1,
133
133
  max: 12,
134
134
  onChange: (e) => C("bonusMonths", parseInt(e.target.value) || 1),
135
- className: `w-32 px-3 py-2 rounded-input border ${v.bonusMonths ? "border-destructive" : "border-border-subtle"} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`
135
+ className: `w-32 px-3 py-2 rounded-input border ${v.bonusMonths ? "border-status-error-border" : "border-border-subtle"} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`
136
136
  }),
137
137
  v.bonusMonths && /* @__PURE__ */ u("p", {
138
138
  className: "text-xs text-status-error-text mt-1",
@@ -194,7 +194,7 @@ var f = [
194
194
  type: "date",
195
195
  value: x.startsAt,
196
196
  onChange: (e) => C("startsAt", e.target.value),
197
- className: `w-full px-3 py-2 rounded-input border ${v.startsAt ? "border-destructive" : "border-border-subtle"} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`
197
+ className: `w-full px-3 py-2 rounded-input border ${v.startsAt ? "border-status-error-border" : "border-border-subtle"} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`
198
198
  }),
199
199
  v.startsAt && /* @__PURE__ */ u("p", {
200
200
  className: "text-xs text-status-error-text mt-1",
@@ -210,7 +210,7 @@ var f = [
210
210
  value: x.expiresAt,
211
211
  onChange: (e) => C("expiresAt", e.target.value),
212
212
  min: x.startsAt,
213
- className: `w-full px-3 py-2 rounded-input border ${v.expiresAt ? "border-destructive" : "border-border-subtle"} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`
213
+ className: `w-full px-3 py-2 rounded-input border ${v.expiresAt ? "border-status-error-border" : "border-border-subtle"} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`
214
214
  }),
215
215
  v.expiresAt && /* @__PURE__ */ u("p", {
216
216
  className: "text-xs text-status-error-text mt-1",
@@ -1 +1 @@
1
- {"version":3,"file":"CreatePromotionPage.js","names":[],"sources":["../../../../../src/billing/modules/promotions/pages/CreatePromotionPage.tsx"],"sourcesContent":["import { type FC, useState } from 'react';\nimport { ArrowLeft, Save, Gift, Info } from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { useCreatePromotionMutation } from '../../../../generated/global-operations';\n\nconst KNOWN_PRODUCTS = ['vibecontrols', 'workspaces', 'botlit', 'fluidgrids', 'healthybowl'];\n\ninterface FormData {\n name: string;\n description: string;\n startsAt: string;\n expiresAt: string;\n bonusMonths: number;\n maxRedemptions: string;\n applicableProductIds: string[];\n applicablePlanIds: string;\n}\n\nexport const CreatePromotionPage: FC = () => {\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const [createPromotion] = useCreatePromotionMutation({ refetchQueries: ['ListPromotions'] });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>({});\n\n const today = new Date().toISOString().split('T')[0];\n const [form, setForm] = useState<FormData>({\n name: '',\n description: '',\n startsAt: today,\n expiresAt: '',\n bonusMonths: 1,\n maxRedemptions: '',\n applicableProductIds: [],\n applicablePlanIds: '',\n });\n\n if (!permissions.canManagePlans) {\n return <AccessDenied message=\"You don't have permission to create promotions.\" />;\n }\n\n const set = <K extends keyof FormData>(k: K, v: FormData[K]) =>\n setForm((prev) => ({ ...prev, [k]: v }));\n\n const toggleProduct = (pid: string) => {\n set(\n 'applicableProductIds',\n form.applicableProductIds.includes(pid)\n ? form.applicableProductIds.filter((p) => p !== pid)\n : [...form.applicableProductIds, pid]\n );\n };\n\n const validate = () => {\n const e: typeof errors = {};\n if (!form.name.trim()) e.name = 'Name is required';\n if (!form.startsAt) e.startsAt = 'Start date is required';\n if (!form.expiresAt) e.expiresAt = 'End date is required';\n if (form.expiresAt && form.expiresAt <= form.startsAt)\n e.expiresAt = 'End date must be after start date';\n if (form.bonusMonths < 1) e.bonusMonths = 'Must be at least 1';\n setErrors(e);\n return Object.keys(e).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate()) return;\n setIsSubmitting(true);\n try {\n const planIds = form.applicablePlanIds.split(',').flatMap((s) => {\n const trimmed = s.trim();\n return trimmed ? [trimmed] : [];\n });\n\n await createPromotion({\n variables: {\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n startsAt: new Date(form.startsAt).toISOString(),\n expiresAt: new Date(form.expiresAt).toISOString(),\n bonusMonths: form.bonusMonths,\n maxRedemptions: form.maxRedemptions ? parseInt(form.maxRedemptions) : undefined,\n applicableProductIds: form.applicableProductIds,\n applicablePlanIds: planIds,\n },\n },\n });\n navigateTo('/promotions');\n } catch (err) {\n setErrors({ name: err instanceof Error ? err.message : 'Failed to create promotion' });\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <div className=\"max-w-2xl mx-auto p-4 sm:p-6 space-y-6\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/promotions')}\n aria-label=\"Back to promotions\"\n className=\"p-2 rounded-button hover:bg-bg-sunken text-text-muted hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <PageHeader\n title=\"Create Promotion\"\n description=\"Offer bonus free renewal months to subscribers who sign up within the promotion window\"\n icon={<Gift className=\"size-6\" />}\n />\n </div>\n\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Basic info */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]\">\n <h3 className=\"font-semibold text-text-primary\">Promotion Details</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Name *</label>\n <input\n type=\"text\"\n value={form.name}\n onChange={(e) => set('name', e.target.value)}\n placeholder=\"e.g. June Launch Offer\"\n className={`w-full px-3 py-2 rounded-input border ${errors.name ? 'border-destructive' : 'border-border-subtle'} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.name && <p className=\"text-xs text-status-error-text mt-1\">{errors.name}</p>}\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Description</label>\n <textarea\n value={form.description}\n onChange={(e) => set('description', e.target.value)}\n placeholder=\"Subscribe in June and get 2 extra months free!\"\n rows={2}\n className=\"w-full px-3 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Bonus config */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]\">\n <h3 className=\"font-semibold text-text-primary\">Bonus Configuration</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Bonus Months *\n </label>\n <input\n type=\"number\"\n value={form.bonusMonths}\n min={1}\n max={12}\n onChange={(e) => set('bonusMonths', parseInt(e.target.value) || 1)}\n className={`w-32 px-3 py-2 rounded-input border ${errors.bonusMonths ? 'border-destructive' : 'border-border-subtle'} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.bonusMonths && (\n <p className=\"text-xs text-status-error-text mt-1\">{errors.bonusMonths}</p>\n )}\n <div className=\"mt-2 flex items-start gap-2 p-3 bg-[var(--color-accent-soft)] rounded-lg text-sm text-text-muted\">\n <Info className=\"size-4 shrink-0 mt-0.5 text-text-link\" />\n <span>\n Users who subscribe during this window get{' '}\n <strong className=\"text-text-primary\">\n {form.bonusMonths} free renewal{form.bonusMonths !== 1 ? 's' : ''}\n </strong>{' '}\n after their first paid period — their quota resets each month as normal.\n </span>\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Max Redemptions\n </label>\n <input\n type=\"number\"\n value={form.maxRedemptions}\n min={1}\n onChange={(e) => set('maxRedemptions', e.target.value)}\n placeholder=\"Unlimited\"\n className=\"w-48 px-3 py-2 rounded-input border border-border-subtle bg-bg-surface 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\">Leave empty for no cap</p>\n </div>\n </div>\n\n {/* Date window */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]\">\n <h3 className=\"font-semibold text-text-primary\">Active Window</h3>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Start Date *\n </label>\n <input\n type=\"date\"\n value={form.startsAt}\n onChange={(e) => set('startsAt', e.target.value)}\n className={`w-full px-3 py-2 rounded-input border ${errors.startsAt ? 'border-destructive' : 'border-border-subtle'} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.startsAt && (\n <p className=\"text-xs text-status-error-text mt-1\">{errors.startsAt}</p>\n )}\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">End Date *</label>\n <input\n type=\"date\"\n value={form.expiresAt}\n onChange={(e) => set('expiresAt', e.target.value)}\n min={form.startsAt}\n className={`w-full px-3 py-2 rounded-input border ${errors.expiresAt ? 'border-destructive' : 'border-border-subtle'} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.expiresAt && (\n <p className=\"text-xs text-status-error-text mt-1\">{errors.expiresAt}</p>\n )}\n </div>\n </div>\n <p className=\"text-xs text-text-muted\">\n Only purchases made between these dates will receive the bonus months\n </p>\n </div>\n\n {/* Scope */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]\">\n <h3 className=\"font-semibold text-text-primary\">Scope</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n Applicable Products\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {KNOWN_PRODUCTS.map((pid) => (\n <button\n key={pid}\n type=\"button\"\n onClick={() => toggleProduct(pid)}\n className={`px-3 py-1.5 rounded-lg border text-sm transition-colors ${\n form.applicableProductIds.includes(pid)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-link font-medium'\n : 'border-border-subtle hover:border-action-primary-bg/50'\n }`}\n >\n {pid}\n </button>\n ))}\n </div>\n <p className=\"text-xs text-text-muted mt-1\">\n {form.applicableProductIds.length === 0\n ? 'No selection = applies to all products'\n : `Applies to: ${form.applicableProductIds.join(', ')}`}\n </p>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Plan IDs (optional)\n </label>\n <input\n type=\"text\"\n value={form.applicablePlanIds}\n onChange={(e) => set('applicablePlanIds', e.target.value)}\n placeholder=\"plan-vibecontrols-pro-monthly, plan-vibecontrols-pro-yearly\"\n className=\"w-full px-3 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Comma-separated. Leave empty to apply to all plans\n </p>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex gap-3 justify-end\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/promotions')}\n className=\"px-5 py-2 rounded-button border border-border-subtle text-text-primary hover:bg-bg-sunken text-sm font-medium transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"px-5 py-2 rounded-button bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover flex items-center gap-2 text-sm font-medium transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:opacity-50\"\n >\n {isSubmitting ? (\n <div className=\"size-4 border-2 border-action-primary-text border-t-transparent rounded-full animate-spin\" />\n ) : (\n <Save className=\"size-4\" />\n )}\n Create Promotion\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"CreatePromotionPage.js","names":[],"sources":["../../../../../src/billing/modules/promotions/pages/CreatePromotionPage.tsx"],"sourcesContent":["import { type FC, useState } from 'react';\nimport { ArrowLeft, Save, Gift, Info } from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { useCreatePromotionMutation } from '../../../../generated/global-operations';\n\nconst KNOWN_PRODUCTS = ['vibecontrols', 'workspaces', 'botlit', 'fluidgrids', 'healthybowl'];\n\ninterface FormData {\n name: string;\n description: string;\n startsAt: string;\n expiresAt: string;\n bonusMonths: number;\n maxRedemptions: string;\n applicableProductIds: string[];\n applicablePlanIds: string;\n}\n\nexport const CreatePromotionPage: FC = () => {\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const [createPromotion] = useCreatePromotionMutation({ refetchQueries: ['ListPromotions'] });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>({});\n\n const today = new Date().toISOString().split('T')[0];\n const [form, setForm] = useState<FormData>({\n name: '',\n description: '',\n startsAt: today,\n expiresAt: '',\n bonusMonths: 1,\n maxRedemptions: '',\n applicableProductIds: [],\n applicablePlanIds: '',\n });\n\n if (!permissions.canManagePlans) {\n return <AccessDenied message=\"You don't have permission to create promotions.\" />;\n }\n\n const set = <K extends keyof FormData>(k: K, v: FormData[K]) =>\n setForm((prev) => ({ ...prev, [k]: v }));\n\n const toggleProduct = (pid: string) => {\n set(\n 'applicableProductIds',\n form.applicableProductIds.includes(pid)\n ? form.applicableProductIds.filter((p) => p !== pid)\n : [...form.applicableProductIds, pid]\n );\n };\n\n const validate = () => {\n const e: typeof errors = {};\n if (!form.name.trim()) e.name = 'Name is required';\n if (!form.startsAt) e.startsAt = 'Start date is required';\n if (!form.expiresAt) e.expiresAt = 'End date is required';\n if (form.expiresAt && form.expiresAt <= form.startsAt)\n e.expiresAt = 'End date must be after start date';\n if (form.bonusMonths < 1) e.bonusMonths = 'Must be at least 1';\n setErrors(e);\n return Object.keys(e).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate()) return;\n setIsSubmitting(true);\n try {\n const planIds = form.applicablePlanIds.split(',').flatMap((s) => {\n const trimmed = s.trim();\n return trimmed ? [trimmed] : [];\n });\n\n await createPromotion({\n variables: {\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n startsAt: new Date(form.startsAt).toISOString(),\n expiresAt: new Date(form.expiresAt).toISOString(),\n bonusMonths: form.bonusMonths,\n maxRedemptions: form.maxRedemptions ? parseInt(form.maxRedemptions) : undefined,\n applicableProductIds: form.applicableProductIds,\n applicablePlanIds: planIds,\n },\n },\n });\n navigateTo('/promotions');\n } catch (err) {\n setErrors({ name: err instanceof Error ? err.message : 'Failed to create promotion' });\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <div className=\"max-w-2xl mx-auto p-4 sm:p-6 space-y-6\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/promotions')}\n aria-label=\"Back to promotions\"\n className=\"p-2 rounded-button hover:bg-bg-sunken text-text-muted hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <PageHeader\n title=\"Create Promotion\"\n description=\"Offer bonus free renewal months to subscribers who sign up within the promotion window\"\n icon={<Gift className=\"size-6\" />}\n />\n </div>\n\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Basic info */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]\">\n <h3 className=\"font-semibold text-text-primary\">Promotion Details</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Name *</label>\n <input\n type=\"text\"\n value={form.name}\n onChange={(e) => set('name', e.target.value)}\n placeholder=\"e.g. June Launch Offer\"\n className={`w-full px-3 py-2 rounded-input border ${errors.name ? 'border-status-error-border' : 'border-border-subtle'} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.name && <p className=\"text-xs text-status-error-text mt-1\">{errors.name}</p>}\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Description</label>\n <textarea\n value={form.description}\n onChange={(e) => set('description', e.target.value)}\n placeholder=\"Subscribe in June and get 2 extra months free!\"\n rows={2}\n className=\"w-full px-3 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Bonus config */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]\">\n <h3 className=\"font-semibold text-text-primary\">Bonus Configuration</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Bonus Months *\n </label>\n <input\n type=\"number\"\n value={form.bonusMonths}\n min={1}\n max={12}\n onChange={(e) => set('bonusMonths', parseInt(e.target.value) || 1)}\n className={`w-32 px-3 py-2 rounded-input border ${errors.bonusMonths ? 'border-status-error-border' : 'border-border-subtle'} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.bonusMonths && (\n <p className=\"text-xs text-status-error-text mt-1\">{errors.bonusMonths}</p>\n )}\n <div className=\"mt-2 flex items-start gap-2 p-3 bg-[var(--color-accent-soft)] rounded-lg text-sm text-text-muted\">\n <Info className=\"size-4 shrink-0 mt-0.5 text-text-link\" />\n <span>\n Users who subscribe during this window get{' '}\n <strong className=\"text-text-primary\">\n {form.bonusMonths} free renewal{form.bonusMonths !== 1 ? 's' : ''}\n </strong>{' '}\n after their first paid period — their quota resets each month as normal.\n </span>\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Max Redemptions\n </label>\n <input\n type=\"number\"\n value={form.maxRedemptions}\n min={1}\n onChange={(e) => set('maxRedemptions', e.target.value)}\n placeholder=\"Unlimited\"\n className=\"w-48 px-3 py-2 rounded-input border border-border-subtle bg-bg-surface 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\">Leave empty for no cap</p>\n </div>\n </div>\n\n {/* Date window */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]\">\n <h3 className=\"font-semibold text-text-primary\">Active Window</h3>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Start Date *\n </label>\n <input\n type=\"date\"\n value={form.startsAt}\n onChange={(e) => set('startsAt', e.target.value)}\n className={`w-full px-3 py-2 rounded-input border ${errors.startsAt ? 'border-status-error-border' : 'border-border-subtle'} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.startsAt && (\n <p className=\"text-xs text-status-error-text mt-1\">{errors.startsAt}</p>\n )}\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">End Date *</label>\n <input\n type=\"date\"\n value={form.expiresAt}\n onChange={(e) => set('expiresAt', e.target.value)}\n min={form.startsAt}\n className={`w-full px-3 py-2 rounded-input border ${errors.expiresAt ? 'border-status-error-border' : 'border-border-subtle'} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.expiresAt && (\n <p className=\"text-xs text-status-error-text mt-1\">{errors.expiresAt}</p>\n )}\n </div>\n </div>\n <p className=\"text-xs text-text-muted\">\n Only purchases made between these dates will receive the bonus months\n </p>\n </div>\n\n {/* Scope */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]\">\n <h3 className=\"font-semibold text-text-primary\">Scope</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n Applicable Products\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {KNOWN_PRODUCTS.map((pid) => (\n <button\n key={pid}\n type=\"button\"\n onClick={() => toggleProduct(pid)}\n className={`px-3 py-1.5 rounded-lg border text-sm transition-colors ${\n form.applicableProductIds.includes(pid)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-link font-medium'\n : 'border-border-subtle hover:border-action-primary-bg/50'\n }`}\n >\n {pid}\n </button>\n ))}\n </div>\n <p className=\"text-xs text-text-muted mt-1\">\n {form.applicableProductIds.length === 0\n ? 'No selection = applies to all products'\n : `Applies to: ${form.applicableProductIds.join(', ')}`}\n </p>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Plan IDs (optional)\n </label>\n <input\n type=\"text\"\n value={form.applicablePlanIds}\n onChange={(e) => set('applicablePlanIds', e.target.value)}\n placeholder=\"plan-vibecontrols-pro-monthly, plan-vibecontrols-pro-yearly\"\n className=\"w-full px-3 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Comma-separated. Leave empty to apply to all plans\n </p>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex gap-3 justify-end\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/promotions')}\n className=\"px-5 py-2 rounded-button border border-border-subtle text-text-primary hover:bg-bg-sunken text-sm font-medium transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"px-5 py-2 rounded-button bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover flex items-center gap-2 text-sm font-medium transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:opacity-50\"\n >\n {isSubmitting ? (\n <div className=\"size-4 border-2 border-action-primary-text border-t-transparent rounded-full animate-spin\" />\n ) : (\n <Save className=\"size-4\" />\n )}\n Create Promotion\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}