@burdenoff/microfe-billing 2026.917.3 → 2026.918.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.
@@ -134,10 +134,10 @@ var y = () => {
134
134
  })]
135
135
  })
136
136
  }) : !B && F.length === 0 && !I ? /* @__PURE__ */ g("div", {
137
- className: "px-6 lg:px-8 py-6",
137
+ className: "px-4 sm:px-6 lg:px-8 py-4 sm:py-6",
138
138
  children: [
139
139
  /* @__PURE__ */ g("header", {
140
- className: "relative overflow-hidden mb-8 rounded-card",
140
+ className: "relative overflow-hidden mb-6 sm:mb-8 rounded-card",
141
141
  children: [
142
142
  /* @__PURE__ */ h(_, { intensity: "subtle" }),
143
143
  /* @__PURE__ */ h("h1", {
@@ -172,7 +172,7 @@ var y = () => {
172
172
  })
173
173
  ]
174
174
  }) : /* @__PURE__ */ g("div", {
175
- className: "px-6 lg:px-8 py-6 space-y-6 sm:space-y-8",
175
+ className: "px-4 sm:px-6 lg:px-8 py-4 sm:py-6 space-y-4 sm:space-y-8",
176
176
  children: [
177
177
  /* @__PURE__ */ g("header", {
178
178
  className: "relative overflow-hidden rounded-card flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4",
@@ -260,25 +260,29 @@ var y = () => {
260
260
  })] })]
261
261
  }),
262
262
  I && /* @__PURE__ */ g("div", {
263
- className: "grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4",
263
+ className: "grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4",
264
264
  "data-tour": "billing-overview-stats-row",
265
265
  children: [
266
266
  /* @__PURE__ */ h(r, {
267
+ className: "p-3 sm:p-5",
267
268
  label: y("billing.overview.stats.creditBalance", "Credit Balance"),
268
269
  value: `${(I.creditAmount || 0).toLocaleString()} Credits`,
269
270
  icon: /* @__PURE__ */ h(p, { className: "size-5" })
270
271
  }),
271
272
  /* @__PURE__ */ h(r, {
273
+ className: "p-3 sm:p-5",
272
274
  label: y("billing.overview.stats.activePlans", "Active Plans"),
273
275
  value: R.length > 0 ? R.length === 1 ? R[0]?.plan?.name || "1 Plan" : `${R.length} ${y("billing.overview.stats.plansSuffix", "Plans")}` : y("billing.overview.stats.none", "None"),
274
276
  icon: /* @__PURE__ */ h(f, { className: "size-5" })
275
277
  }),
276
278
  /* @__PURE__ */ h(r, {
279
+ className: "p-3 sm:p-5",
277
280
  label: y("billing.overview.stats.paymentMethods", "Payment Methods"),
278
281
  value: L.length,
279
282
  icon: /* @__PURE__ */ h(l, { className: "size-5" })
280
283
  }),
281
284
  /* @__PURE__ */ h(r, {
285
+ className: "p-3 sm:p-5",
282
286
  label: y("billing.overview.stats.paymentCurrency", "Payment Currency"),
283
287
  value: I.currency || "USD",
284
288
  icon: /* @__PURE__ */ h(l, { className: "size-5" })
@@ -286,9 +290,9 @@ var y = () => {
286
290
  ]
287
291
  }),
288
292
  /* @__PURE__ */ g("div", {
289
- className: "grid grid-cols-1 lg:grid-cols-3 gap-6",
293
+ className: "grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6",
290
294
  children: [/* @__PURE__ */ g("div", {
291
- className: "lg:col-span-2 space-y-6",
295
+ className: "lg:col-span-2 space-y-4 sm:space-y-6",
292
296
  children: [
293
297
  I && /* @__PURE__ */ h(ce, {
294
298
  dataTour: "billing-overview-billing-account-card",
@@ -389,9 +393,9 @@ var y = () => {
389
393
  })
390
394
  ]
391
395
  }), /* @__PURE__ */ g("div", {
392
- className: "space-y-6",
396
+ className: "space-y-4 sm:space-y-6",
393
397
  children: [/* @__PURE__ */ g("section", {
394
- className: "border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5",
398
+ className: "border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-4 sm:p-5",
395
399
  "data-tour": "billing-overview-quick-actions",
396
400
  children: [/* @__PURE__ */ h("h3", {
397
401
  className: "font-semibold text-text-primary mb-4",
@@ -443,7 +447,7 @@ var y = () => {
443
447
  }), !z && /* @__PURE__ */ g(Te, {
444
448
  treatment: "glass",
445
449
  glow: !0,
446
- className: "p-5",
450
+ className: "p-4 sm:p-5",
447
451
  children: [/* @__PURE__ */ g("div", {
448
452
  className: "flex items-start gap-3 mb-4",
449
453
  children: [/* @__PURE__ */ h("div", {
@@ -1 +1 @@
1
- {"version":3,"file":"OverviewPage.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/pages/OverviewPage.tsx"],"sourcesContent":["/**\n * Billing Overview Page\n * Main dashboard for billing showing accounts, subscriptions, payment methods, and addons\n */\n\nimport { type FC, useState, useCallback, useEffect } from 'react';\nimport {\n Plus,\n Building2,\n RefreshCw,\n Loader2,\n Wallet,\n ChevronDown,\n Check,\n Activity,\n Receipt,\n ArrowRight,\n Sparkles,\n Crown,\n Coins,\n} from 'lucide-react';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { StatCard } from '../../../shared/components/StatCard';\nimport { ServerError } from '../../../shared/components/ServerError';\nimport { AccessDenied } from '../../../shared/components/AccessDenied';\nimport { isServerError } from '../../../shared/utils';\nimport { useBillingOverviewData, useDashboardMutations } from '../hooks';\nimport {\n BillingAccountCard,\n PaymentMethodsSection,\n ActiveSubscriptionCard,\n RecommendedAddonsSection,\n CreateBillingAccountModal,\n AddPaymentMethodModal,\n} from '../components';\nimport { useAddonCartStore } from '../../addons/store/addonCartStore';\nimport { FloatingCartDrawer } from '../../addons/components/FloatingCartDrawer';\nimport {\n getStoredBillingAccountId,\n setStoredBillingAccountId,\n} from '../../../hooks/useBillingAccountSelection';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport type {\n BillingAccount,\n CreateBillingAccountInput,\n SetBillingAddressInput,\n Addon,\n} from '../../../shared/types';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport {\n Button,\n CTAOverflowMenu,\n GlassCard,\n AuroraBackground,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\nexport const OverviewPage: FC = () => {\n const tr = useTr();\n const { navigate, basePath, orgId, productId } = useBilling();\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n\n const [selectedAccountId, setSelectedAccountIdState] = useState<string | null>(() =>\n getStoredBillingAccountId(orgId)\n );\n\n const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);\n const [createError, setCreateError] = useState<string | null>(null);\n const [isAccountSelectorOpen, setIsAccountSelectorOpen] = useState(false);\n const [isAddPaymentModalOpen, setIsAddPaymentModalOpen] = useState(false);\n const [selectedSubscriptionIndex, setSelectedSubscriptionIndex] = useState(0);\n const [isSubscriptionSelectorOpen, setIsSubscriptionSelectorOpen] = useState(false);\n\n const {\n billingAccounts,\n currentAccount,\n paymentMethods,\n activeSubscriptions,\n hasActiveSubscription,\n recommendedAddons,\n isLoading,\n error,\n refetchAll,\n } = useBillingOverviewData(selectedAccountId, productId);\n\n const currentAccountId = currentAccount?.id;\n const isLoadingAddons = isLoading;\n\n const setSelectedAccountId = useCallback(\n (accountId: string | null) => {\n setSelectedAccountIdState(accountId);\n setStoredBillingAccountId(orgId, accountId);\n },\n [orgId]\n );\n\n // Addon cart store for add to cart functionality\n const { addToCart, isInCart, getCartItem, updateQuantity, removeFromCart } = useAddonCartStore();\n\n const {\n createBillingAccount,\n setBillingAddress,\n setDefaultPaymentMethod,\n deletePaymentMethod,\n isCreatingAccount,\n } = useDashboardMutations();\n\n // Reset subscription selector when account changes or subscriptions change\n useEffect(() => {\n setSelectedSubscriptionIndex(0);\n setIsSubscriptionSelectorOpen(false);\n }, [currentAccountId, activeSubscriptions.length]);\n\n useEffect(() => {\n if (selectedAccountId && billingAccounts.length > 0) {\n const accountExists = billingAccounts.some((account) => account.id === selectedAccountId);\n if (!accountExists) {\n setSelectedAccountId(null);\n }\n }\n }, [billingAccounts, selectedAccountId, setSelectedAccountId]);\n\n useEffect(() => {\n if (!selectedAccountId && billingAccounts.length > 0) {\n const defaultAccount =\n billingAccounts.find((account) => account.isDefault) ?? billingAccounts[0];\n setSelectedAccountId(defaultAccount.id);\n }\n }, [billingAccounts, selectedAccountId, setSelectedAccountId]);\n\n // Derive selected subscription from active subscriptions\n const selectedSubscription =\n activeSubscriptions[selectedSubscriptionIndex] || activeSubscriptions[0] || null;\n\n // Helper for navigation - properly joins basePath and path\n const navigateTo = useCallback(\n (path: string) => {\n // Handle path joining to avoid double slashes (e.g., / + /plans = //plans)\n let fullPath = path;\n if (basePath && basePath !== '/') {\n // Remove trailing slash from basePath and leading slash from path if needed\n const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n const suffix = path.startsWith('/') ? path : `/${path}`;\n fullPath = `${base}${suffix}`;\n }\n navigate(fullPath);\n },\n [navigate, basePath]\n );\n\n // Handlers\n const handleCreateAccount = useCallback(\n async (\n input: CreateBillingAccountInput,\n address?: Omit<SetBillingAddressInput, 'billingAccountId'>\n ) => {\n setCreateError(null);\n try {\n const newAccount = await createBillingAccount(input);\n\n // If address data provided, set the billing address\n if (address && newAccount.id) {\n await setBillingAddress({\n billingAccountId: newAccount.id,\n ...address,\n });\n }\n\n setIsCreateModalOpen(false);\n setSelectedAccountId(newAccount.id);\n emit('billing.billing_account.created', {\n route: '/billing/overview',\n entityId: newAccount.id,\n source: 'overview',\n });\n await refetchAll();\n } catch (err) {\n setCreateError(err instanceof Error ? err.message : 'Failed to create billing account');\n }\n },\n [createBillingAccount, emit, refetchAll, setBillingAddress, setSelectedAccountId]\n );\n\n const handleAddPaymentMethod = useCallback(() => {\n setIsAddPaymentModalOpen(true);\n }, []);\n\n // Helper to refetch the current account data\n const refetchCurrentAccount = useCallback(async () => {\n await refetchAll();\n }, [refetchAll]);\n\n const handlePaymentMethodAdded = useCallback(async () => {\n emit('billing.payment_method.added', {\n route: '/billing/overview',\n entityId: currentAccountId,\n source: 'overview',\n });\n await refetchCurrentAccount();\n }, [currentAccountId, emit, refetchCurrentAccount]);\n\n const handleSetDefaultPaymentMethod = useCallback(\n async (id: string) => {\n if (!currentAccountId) return;\n await setDefaultPaymentMethod(id, currentAccountId);\n emit('billing.payment_method.default_set', {\n route: '/billing/overview',\n entityId: id,\n source: 'overview',\n });\n await refetchCurrentAccount();\n },\n [currentAccountId, emit, setDefaultPaymentMethod, refetchCurrentAccount]\n );\n\n const handleDeletePaymentMethod = useCallback(\n async (id: string) => {\n if (!currentAccountId) return;\n await deletePaymentMethod(id, currentAccountId);\n emit('billing.payment_method.deleted', {\n route: '/billing/overview',\n entityId: id,\n source: 'overview',\n });\n await refetchCurrentAccount();\n },\n [currentAccountId, deletePaymentMethod, emit, refetchCurrentAccount]\n );\n\n const handleSelectAccount = useCallback(\n (account: BillingAccount) => {\n setSelectedAccountId(account.id);\n setIsAccountSelectorOpen(false);\n },\n [setSelectedAccountId]\n );\n\n const handleRefresh = useCallback(async () => {\n await refetchAll();\n }, [refetchAll]);\n\n // Handler for adding addon to cart\n const handleAddToCart = useCallback(\n (addon: Addon) => {\n addToCart(addon);\n },\n [addToCart]\n );\n\n // Handler to check if addon is in cart\n const handleIsInCart = useCallback(\n (addonId: string) => {\n return isInCart(addonId);\n },\n [isInCart]\n );\n\n // Handler to get cart quantity for addon\n const handleGetCartQuantity = useCallback(\n (addonId: string) => {\n const item = getCartItem(addonId);\n return item?.quantity || 0;\n },\n [getCartItem]\n );\n\n // Handler to update quantity in cart\n const handleUpdateQuantity = useCallback(\n (addonId: string, quantity: number) => {\n if (quantity <= 0) {\n removeFromCart(addonId);\n } else {\n updateQuantity(addonId, quantity);\n }\n },\n [updateQuantity, removeFromCart]\n );\n\n // Handler to remove from cart\n const handleRemoveFromCart = useCallback(\n (addonId: string) => {\n removeFromCart(addonId);\n },\n [removeFromCart]\n );\n\n // Permission check - must be after all hooks are defined\n if (!permissions.canViewBillingAccount) {\n return <AccessDenied message=\"You don't have permission to view billing information.\" />;\n }\n\n // Error state\n if (error && isServerError(error) && !isLoading) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.serverError.title', 'Server Unavailable')}\n message={tr(\n 'billing.overview.serverUnavailableMessage',\n 'Unable to load billing overview. The server might be down or experiencing issues.'\n )}\n onRetry={handleRefresh}\n showRetry\n />\n </div>\n );\n }\n\n // Loading state\n if (isLoading && !currentAccount) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"flex flex-col items-center gap-3\">\n <Loader2 className=\"size-8 animate-spin text-text-link\" />\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.overview.loadingInfo', 'Loading billing information...')}\n </p>\n </div>\n </div>\n );\n }\n\n // No billing accounts state. Checked against `billingAccounts` AND\n // `currentAccount` (not just the list) because getDefaultBillingAccount\n // lazy-creates a default account for a brand-new org — on that exact\n // first request, getBillingAccountsByOrg's plain read can still observe\n // zero rows in the same response that returned a real, freshly-created\n // currentAccount. A valid currentAccount always means there's a real\n // billing account regardless of what the (possibly momentarily stale)\n // list says.\n if (!isLoading && billingAccounts.length === 0 && !currentAccount) {\n return (\n <div className=\"px-6 lg:px-8 py-6\">\n <header className=\"relative overflow-hidden mb-8 rounded-card\">\n <AuroraBackground intensity=\"subtle\" />\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n <GradientText>{tr('billing.overview.title', 'Billing Overview')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.emptySetupDescription',\n 'Set up billing to manage subscriptions, payments, and invoices'\n )}\n </p>\n </header>\n <IllustratedEmptyState\n illustration=\"onboarding\"\n title={tr('billing.overview.emptyTitle', 'No billing account')}\n description={tr(\n 'billing.overview.emptyDescription',\n 'Create a billing account to start managing your subscriptions and payments'\n )}\n action={\n permissions.canManageBillingAccount ? (\n <button\n type=\"button\"\n onClick={() => setIsCreateModalOpen(true)}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n <Plus className=\"size-4\" />\n {tr('billing.overview.createBillingAccount', 'Create Billing Account')}\n </button>\n ) : undefined\n }\n />\n\n <CreateBillingAccountModal\n isOpen={isCreateModalOpen}\n onClose={() => {\n setIsCreateModalOpen(false);\n setCreateError(null);\n }}\n onSubmit={handleCreateAccount}\n isSubmitting={isCreatingAccount}\n error={createError}\n />\n </div>\n );\n }\n\n return (\n <div className=\"px-6 lg:px-8 py-6 space-y-6 sm:space-y-8\">\n {/* Header */}\n <header className=\"relative overflow-hidden rounded-card flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4\">\n <AuroraBackground intensity=\"subtle\" />\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n <GradientText>{tr('billing.overview.title', 'Billing Overview')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.description',\n 'Manage your billing accounts, subscriptions, and payment methods'\n )}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.overview.purpose',\n 'Your billing home base — see your current plan, credit balance and payment methods at a glance, and jump to transactions, usage, credits or new plans. Start here to understand what you are paying for and manage it.'\n )}\n </PagePurpose>\n </div>\n <CTAOverflowMenu\n primary={\n permissions.canManageBillingAccount && billingAccounts.length > 0\n ? {\n label: tr('billing.overview.newAccount', 'New Account'),\n onSelect: () => setIsCreateModalOpen(true),\n icon: <Plus className=\"size-4\" />,\n }\n : undefined\n }\n actions={[\n {\n label: tr('billing.overview.refresh', 'Refresh'),\n onSelect: () => {\n void handleRefresh();\n },\n icon: <RefreshCw className={`size-4 ${isLoading ? 'animate-spin' : ''}`} />,\n },\n ]}\n />\n </header>\n\n {/* Account Selector (if multiple accounts) */}\n {billingAccounts.length > 1 && (\n <div className=\"relative\" data-tour=\"billing-overview-account-selector\">\n <button\n type=\"button\"\n onClick={() => setIsAccountSelectorOpen(!isAccountSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Building2 className=\"size-4 text-text-link\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {currentAccount?.name || tr('billing.overview.selectAccount', 'Select Account')}\n </p>\n <p className=\"text-xs text-text-secondary\">{currentAccount?.email}</p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-text-muted transition-transform ${isAccountSelectorOpen ? 'rotate-180' : ''}`}\n />\n </button>\n\n {isAccountSelectorOpen && (\n <>\n <div className=\"fixed inset-0 z-10\" onClick={() => setIsAccountSelectorOpen(false)} />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto\">\n {billingAccounts.map((account) => (\n <button\n type=\"button\"\n key={account.id}\n onClick={() => handleSelectAccount(account)}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <Building2 className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">{account.email}</p>\n </div>\n </div>\n {currentAccount?.id === account.id && (\n <Check className=\"size-4 text-text-link flex-shrink-0\" />\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Stats Row */}\n {currentAccount && (\n <div\n className=\"grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4\"\n data-tour=\"billing-overview-stats-row\"\n >\n <StatCard\n label={tr('billing.overview.stats.creditBalance', 'Credit Balance')}\n value={`${(currentAccount.creditAmount || 0).toLocaleString()} Credits`}\n icon={<Wallet className=\"size-5\" />}\n />\n <StatCard\n label={tr('billing.overview.stats.activePlans', 'Active Plans')}\n value={\n activeSubscriptions.length > 0\n ? activeSubscriptions.length === 1\n ? activeSubscriptions[0]?.plan?.name || '1 Plan'\n : `${activeSubscriptions.length} ${tr('billing.overview.stats.plansSuffix', 'Plans')}`\n : tr('billing.overview.stats.none', 'None')\n }\n icon={<RefreshCw className=\"size-5\" />}\n />\n <StatCard\n label={tr('billing.overview.stats.paymentMethods', 'Payment Methods')}\n value={paymentMethods.length}\n icon={<Building2 className=\"size-5\" />}\n />\n <StatCard\n label={tr('billing.overview.stats.paymentCurrency', 'Payment Currency')}\n value={currentAccount.currency || 'USD'}\n icon={<Building2 className=\"size-5\" />}\n />\n </div>\n )}\n\n {/* Main Content Grid */}\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n {/* Left Column - Billing Account & Subscription */}\n <div className=\"lg:col-span-2 space-y-6\">\n {/* Billing Account Card */}\n {currentAccount && (\n <BillingAccountCard\n dataTour=\"billing-overview-billing-account-card\"\n account={currentAccount}\n isDefault={currentAccount.isDefault ?? false}\n onEdit={() =>\n currentAccount && navigateTo(`/settings?billingAccountId=${currentAccount.id}`)\n }\n onViewTransactions={() => navigateTo(`/transactions/${currentAccount.id}`)}\n onViewCreditTransactions={() =>\n navigateTo(`/creditTransactions/${currentAccount.id}`)\n }\n />\n )}\n\n {/* Active Subscriptions */}\n <div className=\"space-y-3\">\n {/* Subscription Selector (if multiple subscriptions) */}\n {activeSubscriptions.length > 1 && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={() => setIsSubscriptionSelectorOpen(!isSubscriptionSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Crown className=\"size-4 text-text-link\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {selectedSubscription?.plan?.name ||\n tr('billing.overview.selectSubscription', 'Select Subscription')}\n {selectedSubscription?.plan?.product?.name && (\n <span className=\"text-text-secondary font-normal\">\n {' '}\n · {selectedSubscription.plan.product.name}\n </span>\n )}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {activeSubscriptions.length}{' '}\n {tr('billing.overview.activeSubscriptions', 'active subscriptions')}\n </p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-text-muted transition-transform ${isSubscriptionSelectorOpen ? 'rotate-180' : ''}`}\n />\n </button>\n\n {isSubscriptionSelectorOpen && (\n <>\n <div\n className=\"fixed inset-0 z-10\"\n onClick={() => setIsSubscriptionSelectorOpen(false)}\n />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto\">\n {activeSubscriptions.map((subscription, index) => (\n <button\n type=\"button\"\n key={subscription.id}\n onClick={() => {\n setSelectedSubscriptionIndex(index);\n setIsSubscriptionSelectorOpen(false);\n }}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <Crown className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {subscription.plan?.name ||\n tr('billing.overview.subscription', 'Subscription')}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">\n {subscription.plan?.product?.name ||\n tr('billing.overview.unknownProduct', 'Unknown Product')}{' '}\n · {tr('billing.overview.ends', 'Ends')}{' '}\n {new Date(subscription.endDate).toLocaleDateString()}\n </p>\n </div>\n </div>\n {selectedSubscriptionIndex === index && (\n <Check className=\"size-4 text-text-link flex-shrink-0\" />\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Single Subscription Card */}\n <ActiveSubscriptionCard\n dataTour=\"billing-overview-active-subscription-card\"\n subscription={selectedSubscription}\n onViewDetails={() =>\n selectedSubscription &&\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${selectedSubscription.id}`,\n selectedSubscription.billingAccountId ?? currentAccount?.id\n )\n )\n }\n onUpgrade={() =>\n selectedSubscription &&\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${selectedSubscription.id}/upgrade`,\n selectedSubscription.billingAccountId ?? currentAccount?.id\n )\n )\n }\n onManage={() =>\n currentAccount && navigateTo(`/subscriptions?billingAccountId=${currentAccount.id}`)\n }\n onPurchasePlan={() => navigateTo('/plans')}\n />\n </div>\n\n {/* Payment Methods */}\n {currentAccount && (\n <PaymentMethodsSection\n dataTour=\"billing-overview-payment-methods-section\"\n paymentMethods={paymentMethods}\n billingAccountId={currentAccount.id}\n onAddPaymentMethod={handleAddPaymentMethod}\n onSetDefault={handleSetDefaultPaymentMethod}\n onDelete={handleDeletePaymentMethod}\n isLoading={isLoading}\n />\n )}\n </div>\n\n {/* Right Column - Quick Actions & Info */}\n <div className=\"space-y-6\">\n {/* Quick Actions */}\n <section\n className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-5\"\n data-tour=\"billing-overview-quick-actions\"\n >\n <h3 className=\"font-semibold text-text-primary mb-4\">\n {tr('billing.overview.quickActions', 'Quick Actions')}\n </h3>\n <div className=\"space-y-1.5\">\n <button\n type=\"button\"\n onClick={() => currentAccount && navigateTo(`/transactions/${currentAccount.id}`)}\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-status-info-bg-subtle flex items-center justify-center\">\n <Receipt className=\"size-4 text-status-info-text\" />\n </div>\n {tr('billing.overview.viewTransactions', 'View Transactions')}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n currentAccount && navigateTo(`/creditTransactions/${currentAccount.id}`)\n }\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-status-warning-bg-subtle flex items-center justify-center\">\n <Wallet className=\"size-4 text-status-warning-text\" />\n </div>\n {tr('billing.overview.creditHistory', 'Credit History')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/checkout/credits')}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors\"\n >\n <div className=\"size-9 rounded-lg bg-status-success-bg-subtle flex items-center justify-center\">\n <Coins className=\"size-4 text-status-success-text\" />\n </div>\n {tr('billing.overview.purchaseCredits', 'Purchase Credits')}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n currentAccount && navigateTo(`/usage?billingAccountId=${currentAccount.id}`)\n }\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Activity className=\"size-4 text-text-link\" />\n </div>\n {tr('billing.overview.usageAnalytics', 'Usage Analytics')}\n </button>\n </div>\n </section>\n\n {/* Get Started (if no subscription) — primary emphasis zone */}\n {!hasActiveSubscription && (\n <GlassCard treatment=\"glass\" glow className=\"p-5\">\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"size-10 rounded-lg bg-bg-surface flex items-center justify-center flex-shrink-0 shadow-elevation-1\">\n <Sparkles className=\"size-5 text-text-link\" />\n </div>\n <div>\n <h3 className=\"font-semibold text-text-primary\">\n {tr('billing.overview.getStarted', 'Get Started')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.getStartedDescription',\n 'Choose a plan that fits your needs to unlock all features'\n )}\n </p>\n </div>\n </div>\n <Button type=\"button\" onClick={() => navigateTo('/plans')} className=\"w-full\">\n {tr('billing.overview.browsePlans', 'Browse Plans')}\n <ArrowRight className=\"size-4\" />\n </Button>\n </GlassCard>\n )}\n </div>\n </div>\n\n {/* Recommended Addons (only if has subscription) - Limited to 3 with cart support */}\n <RecommendedAddonsSection\n dataTour=\"billing-overview-recommended-addons\"\n addons={recommendedAddons}\n hasSubscription={hasActiveSubscription}\n onViewAddon={(addonId) => navigateTo(`/addons/${addonId}`)}\n onPurchaseAddon={(addonId) => navigateTo(`/addons/${addonId}?action=purchase`)}\n onViewAllAddons={() => navigateTo('/addons')}\n onAddToCart={handleAddToCart}\n isInCart={handleIsInCart}\n getCartQuantity={handleGetCartQuantity}\n onUpdateQuantity={handleUpdateQuantity}\n onRemoveFromCart={handleRemoveFromCart}\n maxAddons={3}\n isLoading={isLoadingAddons}\n />\n\n {/* Floating Cart Drawer - shows when items are in cart */}\n <FloatingCartDrawer />\n\n {/* Create Billing Account Modal */}\n <CreateBillingAccountModal\n isOpen={isCreateModalOpen}\n onClose={() => {\n setIsCreateModalOpen(false);\n setCreateError(null);\n }}\n onSubmit={handleCreateAccount}\n isSubmitting={isCreatingAccount}\n error={createError}\n />\n\n {/* Add Payment Method Modal */}\n {currentAccount && (\n <AddPaymentMethodModal\n isOpen={isAddPaymentModalOpen}\n onClose={() => setIsAddPaymentModalOpen(false)}\n billingAccountId={currentAccount.id}\n currency={currentAccount.currency}\n country={currentAccount.billingAddresses?.[0]?.country}\n onSuccess={handlePaymentMethodAdded}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"OverviewPage.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/pages/OverviewPage.tsx"],"sourcesContent":["/**\n * Billing Overview Page\n * Main dashboard for billing showing accounts, subscriptions, payment methods, and addons\n */\n\nimport { type FC, useState, useCallback, useEffect } from 'react';\nimport {\n Plus,\n Building2,\n RefreshCw,\n Loader2,\n Wallet,\n ChevronDown,\n Check,\n Activity,\n Receipt,\n ArrowRight,\n Sparkles,\n Crown,\n Coins,\n} from 'lucide-react';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { StatCard } from '../../../shared/components/StatCard';\nimport { ServerError } from '../../../shared/components/ServerError';\nimport { AccessDenied } from '../../../shared/components/AccessDenied';\nimport { isServerError } from '../../../shared/utils';\nimport { useBillingOverviewData, useDashboardMutations } from '../hooks';\nimport {\n BillingAccountCard,\n PaymentMethodsSection,\n ActiveSubscriptionCard,\n RecommendedAddonsSection,\n CreateBillingAccountModal,\n AddPaymentMethodModal,\n} from '../components';\nimport { useAddonCartStore } from '../../addons/store/addonCartStore';\nimport { FloatingCartDrawer } from '../../addons/components/FloatingCartDrawer';\nimport {\n getStoredBillingAccountId,\n setStoredBillingAccountId,\n} from '../../../hooks/useBillingAccountSelection';\nimport { withBillingAccountId } from '../../../shared/utils/navigation';\nimport type {\n BillingAccount,\n CreateBillingAccountInput,\n SetBillingAddressInput,\n Addon,\n} from '../../../shared/types';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport {\n Button,\n CTAOverflowMenu,\n GlassCard,\n AuroraBackground,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\n\nexport const OverviewPage: FC = () => {\n const tr = useTr();\n const { navigate, basePath, orgId, productId } = useBilling();\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n\n const [selectedAccountId, setSelectedAccountIdState] = useState<string | null>(() =>\n getStoredBillingAccountId(orgId)\n );\n\n const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);\n const [createError, setCreateError] = useState<string | null>(null);\n const [isAccountSelectorOpen, setIsAccountSelectorOpen] = useState(false);\n const [isAddPaymentModalOpen, setIsAddPaymentModalOpen] = useState(false);\n const [selectedSubscriptionIndex, setSelectedSubscriptionIndex] = useState(0);\n const [isSubscriptionSelectorOpen, setIsSubscriptionSelectorOpen] = useState(false);\n\n const {\n billingAccounts,\n currentAccount,\n paymentMethods,\n activeSubscriptions,\n hasActiveSubscription,\n recommendedAddons,\n isLoading,\n error,\n refetchAll,\n } = useBillingOverviewData(selectedAccountId, productId);\n\n const currentAccountId = currentAccount?.id;\n const isLoadingAddons = isLoading;\n\n const setSelectedAccountId = useCallback(\n (accountId: string | null) => {\n setSelectedAccountIdState(accountId);\n setStoredBillingAccountId(orgId, accountId);\n },\n [orgId]\n );\n\n // Addon cart store for add to cart functionality\n const { addToCart, isInCart, getCartItem, updateQuantity, removeFromCart } = useAddonCartStore();\n\n const {\n createBillingAccount,\n setBillingAddress,\n setDefaultPaymentMethod,\n deletePaymentMethod,\n isCreatingAccount,\n } = useDashboardMutations();\n\n // Reset subscription selector when account changes or subscriptions change\n useEffect(() => {\n setSelectedSubscriptionIndex(0);\n setIsSubscriptionSelectorOpen(false);\n }, [currentAccountId, activeSubscriptions.length]);\n\n useEffect(() => {\n if (selectedAccountId && billingAccounts.length > 0) {\n const accountExists = billingAccounts.some((account) => account.id === selectedAccountId);\n if (!accountExists) {\n setSelectedAccountId(null);\n }\n }\n }, [billingAccounts, selectedAccountId, setSelectedAccountId]);\n\n useEffect(() => {\n if (!selectedAccountId && billingAccounts.length > 0) {\n const defaultAccount =\n billingAccounts.find((account) => account.isDefault) ?? billingAccounts[0];\n setSelectedAccountId(defaultAccount.id);\n }\n }, [billingAccounts, selectedAccountId, setSelectedAccountId]);\n\n // Derive selected subscription from active subscriptions\n const selectedSubscription =\n activeSubscriptions[selectedSubscriptionIndex] || activeSubscriptions[0] || null;\n\n // Helper for navigation - properly joins basePath and path\n const navigateTo = useCallback(\n (path: string) => {\n // Handle path joining to avoid double slashes (e.g., / + /plans = //plans)\n let fullPath = path;\n if (basePath && basePath !== '/') {\n // Remove trailing slash from basePath and leading slash from path if needed\n const base = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n const suffix = path.startsWith('/') ? path : `/${path}`;\n fullPath = `${base}${suffix}`;\n }\n navigate(fullPath);\n },\n [navigate, basePath]\n );\n\n // Handlers\n const handleCreateAccount = useCallback(\n async (\n input: CreateBillingAccountInput,\n address?: Omit<SetBillingAddressInput, 'billingAccountId'>\n ) => {\n setCreateError(null);\n try {\n const newAccount = await createBillingAccount(input);\n\n // If address data provided, set the billing address\n if (address && newAccount.id) {\n await setBillingAddress({\n billingAccountId: newAccount.id,\n ...address,\n });\n }\n\n setIsCreateModalOpen(false);\n setSelectedAccountId(newAccount.id);\n emit('billing.billing_account.created', {\n route: '/billing/overview',\n entityId: newAccount.id,\n source: 'overview',\n });\n await refetchAll();\n } catch (err) {\n setCreateError(err instanceof Error ? err.message : 'Failed to create billing account');\n }\n },\n [createBillingAccount, emit, refetchAll, setBillingAddress, setSelectedAccountId]\n );\n\n const handleAddPaymentMethod = useCallback(() => {\n setIsAddPaymentModalOpen(true);\n }, []);\n\n // Helper to refetch the current account data\n const refetchCurrentAccount = useCallback(async () => {\n await refetchAll();\n }, [refetchAll]);\n\n const handlePaymentMethodAdded = useCallback(async () => {\n emit('billing.payment_method.added', {\n route: '/billing/overview',\n entityId: currentAccountId,\n source: 'overview',\n });\n await refetchCurrentAccount();\n }, [currentAccountId, emit, refetchCurrentAccount]);\n\n const handleSetDefaultPaymentMethod = useCallback(\n async (id: string) => {\n if (!currentAccountId) return;\n await setDefaultPaymentMethod(id, currentAccountId);\n emit('billing.payment_method.default_set', {\n route: '/billing/overview',\n entityId: id,\n source: 'overview',\n });\n await refetchCurrentAccount();\n },\n [currentAccountId, emit, setDefaultPaymentMethod, refetchCurrentAccount]\n );\n\n const handleDeletePaymentMethod = useCallback(\n async (id: string) => {\n if (!currentAccountId) return;\n await deletePaymentMethod(id, currentAccountId);\n emit('billing.payment_method.deleted', {\n route: '/billing/overview',\n entityId: id,\n source: 'overview',\n });\n await refetchCurrentAccount();\n },\n [currentAccountId, deletePaymentMethod, emit, refetchCurrentAccount]\n );\n\n const handleSelectAccount = useCallback(\n (account: BillingAccount) => {\n setSelectedAccountId(account.id);\n setIsAccountSelectorOpen(false);\n },\n [setSelectedAccountId]\n );\n\n const handleRefresh = useCallback(async () => {\n await refetchAll();\n }, [refetchAll]);\n\n // Handler for adding addon to cart\n const handleAddToCart = useCallback(\n (addon: Addon) => {\n addToCart(addon);\n },\n [addToCart]\n );\n\n // Handler to check if addon is in cart\n const handleIsInCart = useCallback(\n (addonId: string) => {\n return isInCart(addonId);\n },\n [isInCart]\n );\n\n // Handler to get cart quantity for addon\n const handleGetCartQuantity = useCallback(\n (addonId: string) => {\n const item = getCartItem(addonId);\n return item?.quantity || 0;\n },\n [getCartItem]\n );\n\n // Handler to update quantity in cart\n const handleUpdateQuantity = useCallback(\n (addonId: string, quantity: number) => {\n if (quantity <= 0) {\n removeFromCart(addonId);\n } else {\n updateQuantity(addonId, quantity);\n }\n },\n [updateQuantity, removeFromCart]\n );\n\n // Handler to remove from cart\n const handleRemoveFromCart = useCallback(\n (addonId: string) => {\n removeFromCart(addonId);\n },\n [removeFromCart]\n );\n\n // Permission check - must be after all hooks are defined\n if (!permissions.canViewBillingAccount) {\n return <AccessDenied message=\"You don't have permission to view billing information.\" />;\n }\n\n // Error state\n if (error && isServerError(error) && !isLoading) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.serverError.title', 'Server Unavailable')}\n message={tr(\n 'billing.overview.serverUnavailableMessage',\n 'Unable to load billing overview. The server might be down or experiencing issues.'\n )}\n onRetry={handleRefresh}\n showRetry\n />\n </div>\n );\n }\n\n // Loading state\n if (isLoading && !currentAccount) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"flex flex-col items-center gap-3\">\n <Loader2 className=\"size-8 animate-spin text-text-link\" />\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.overview.loadingInfo', 'Loading billing information...')}\n </p>\n </div>\n </div>\n );\n }\n\n // No billing accounts state. Checked against `billingAccounts` AND\n // `currentAccount` (not just the list) because getDefaultBillingAccount\n // lazy-creates a default account for a brand-new org — on that exact\n // first request, getBillingAccountsByOrg's plain read can still observe\n // zero rows in the same response that returned a real, freshly-created\n // currentAccount. A valid currentAccount always means there's a real\n // billing account regardless of what the (possibly momentarily stale)\n // list says.\n if (!isLoading && billingAccounts.length === 0 && !currentAccount) {\n return (\n <div className=\"px-4 sm:px-6 lg:px-8 py-4 sm:py-6\">\n <header className=\"relative overflow-hidden mb-6 sm:mb-8 rounded-card\">\n <AuroraBackground intensity=\"subtle\" />\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n <GradientText>{tr('billing.overview.title', 'Billing Overview')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.emptySetupDescription',\n 'Set up billing to manage subscriptions, payments, and invoices'\n )}\n </p>\n </header>\n <IllustratedEmptyState\n illustration=\"onboarding\"\n title={tr('billing.overview.emptyTitle', 'No billing account')}\n description={tr(\n 'billing.overview.emptyDescription',\n 'Create a billing account to start managing your subscriptions and payments'\n )}\n action={\n permissions.canManageBillingAccount ? (\n <button\n type=\"button\"\n onClick={() => setIsCreateModalOpen(true)}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-button hover:bg-action-primary-bgHover transition-colors duration-200\"\n >\n <Plus className=\"size-4\" />\n {tr('billing.overview.createBillingAccount', 'Create Billing Account')}\n </button>\n ) : undefined\n }\n />\n\n <CreateBillingAccountModal\n isOpen={isCreateModalOpen}\n onClose={() => {\n setIsCreateModalOpen(false);\n setCreateError(null);\n }}\n onSubmit={handleCreateAccount}\n isSubmitting={isCreatingAccount}\n error={createError}\n />\n </div>\n );\n }\n\n return (\n <div className=\"px-4 sm:px-6 lg:px-8 py-4 sm:py-6 space-y-4 sm:space-y-8\">\n {/* Header */}\n <header className=\"relative overflow-hidden rounded-card flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4\">\n <AuroraBackground intensity=\"subtle\" />\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-text-primary\">\n <GradientText>{tr('billing.overview.title', 'Billing Overview')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.description',\n 'Manage your billing accounts, subscriptions, and payment methods'\n )}\n </p>\n <PagePurpose className=\"mt-3\">\n {tr(\n 'billing.overview.purpose',\n 'Your billing home base — see your current plan, credit balance and payment methods at a glance, and jump to transactions, usage, credits or new plans. Start here to understand what you are paying for and manage it.'\n )}\n </PagePurpose>\n </div>\n <CTAOverflowMenu\n primary={\n permissions.canManageBillingAccount && billingAccounts.length > 0\n ? {\n label: tr('billing.overview.newAccount', 'New Account'),\n onSelect: () => setIsCreateModalOpen(true),\n icon: <Plus className=\"size-4\" />,\n }\n : undefined\n }\n actions={[\n {\n label: tr('billing.overview.refresh', 'Refresh'),\n onSelect: () => {\n void handleRefresh();\n },\n icon: <RefreshCw className={`size-4 ${isLoading ? 'animate-spin' : ''}`} />,\n },\n ]}\n />\n </header>\n\n {/* Account Selector (if multiple accounts) */}\n {billingAccounts.length > 1 && (\n <div className=\"relative\" data-tour=\"billing-overview-account-selector\">\n <button\n type=\"button\"\n onClick={() => setIsAccountSelectorOpen(!isAccountSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Building2 className=\"size-4 text-text-link\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {currentAccount?.name || tr('billing.overview.selectAccount', 'Select Account')}\n </p>\n <p className=\"text-xs text-text-secondary\">{currentAccount?.email}</p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-text-muted transition-transform ${isAccountSelectorOpen ? 'rotate-180' : ''}`}\n />\n </button>\n\n {isAccountSelectorOpen && (\n <>\n <div className=\"fixed inset-0 z-10\" onClick={() => setIsAccountSelectorOpen(false)} />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto\">\n {billingAccounts.map((account) => (\n <button\n type=\"button\"\n key={account.id}\n onClick={() => handleSelectAccount(account)}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <Building2 className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {account.name}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">{account.email}</p>\n </div>\n </div>\n {currentAccount?.id === account.id && (\n <Check className=\"size-4 text-text-link flex-shrink-0\" />\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Stats Row */}\n {currentAccount && (\n <div\n className=\"grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4\"\n data-tour=\"billing-overview-stats-row\"\n >\n <StatCard\n className=\"p-3 sm:p-5\"\n label={tr('billing.overview.stats.creditBalance', 'Credit Balance')}\n value={`${(currentAccount.creditAmount || 0).toLocaleString()} Credits`}\n icon={<Wallet className=\"size-5\" />}\n />\n <StatCard\n className=\"p-3 sm:p-5\"\n label={tr('billing.overview.stats.activePlans', 'Active Plans')}\n value={\n activeSubscriptions.length > 0\n ? activeSubscriptions.length === 1\n ? activeSubscriptions[0]?.plan?.name || '1 Plan'\n : `${activeSubscriptions.length} ${tr('billing.overview.stats.plansSuffix', 'Plans')}`\n : tr('billing.overview.stats.none', 'None')\n }\n icon={<RefreshCw className=\"size-5\" />}\n />\n <StatCard\n className=\"p-3 sm:p-5\"\n label={tr('billing.overview.stats.paymentMethods', 'Payment Methods')}\n value={paymentMethods.length}\n icon={<Building2 className=\"size-5\" />}\n />\n <StatCard\n className=\"p-3 sm:p-5\"\n label={tr('billing.overview.stats.paymentCurrency', 'Payment Currency')}\n value={currentAccount.currency || 'USD'}\n icon={<Building2 className=\"size-5\" />}\n />\n </div>\n )}\n\n {/* Main Content Grid */}\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6\">\n {/* Left Column - Billing Account & Subscription */}\n <div className=\"lg:col-span-2 space-y-4 sm:space-y-6\">\n {/* Billing Account Card */}\n {currentAccount && (\n <BillingAccountCard\n dataTour=\"billing-overview-billing-account-card\"\n account={currentAccount}\n isDefault={currentAccount.isDefault ?? false}\n onEdit={() =>\n currentAccount && navigateTo(`/settings?billingAccountId=${currentAccount.id}`)\n }\n onViewTransactions={() => navigateTo(`/transactions/${currentAccount.id}`)}\n onViewCreditTransactions={() =>\n navigateTo(`/creditTransactions/${currentAccount.id}`)\n }\n />\n )}\n\n {/* Active Subscriptions */}\n <div className=\"space-y-3\">\n {/* Subscription Selector (if multiple subscriptions) */}\n {activeSubscriptions.length > 1 && (\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={() => setIsSubscriptionSelectorOpen(!isSubscriptionSelectorOpen)}\n className=\"w-full sm:w-auto flex items-center justify-between gap-3 px-4 py-3 border border-border-seam rounded-card bg-bg-surface hover:bg-bg-sunken hover:border-border-strong transition-all duration-200\"\n >\n <div className=\"flex items-center gap-3\">\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Crown className=\"size-4 text-text-link\" />\n </div>\n <div className=\"text-left\">\n <p className=\"text-sm font-medium text-text-primary\">\n {selectedSubscription?.plan?.name ||\n tr('billing.overview.selectSubscription', 'Select Subscription')}\n {selectedSubscription?.plan?.product?.name && (\n <span className=\"text-text-secondary font-normal\">\n {' '}\n · {selectedSubscription.plan.product.name}\n </span>\n )}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {activeSubscriptions.length}{' '}\n {tr('billing.overview.activeSubscriptions', 'active subscriptions')}\n </p>\n </div>\n </div>\n <ChevronDown\n className={`size-4 text-text-muted transition-transform ${isSubscriptionSelectorOpen ? 'rotate-180' : ''}`}\n />\n </button>\n\n {isSubscriptionSelectorOpen && (\n <>\n <div\n className=\"fixed inset-0 z-10\"\n onClick={() => setIsSubscriptionSelectorOpen(false)}\n />\n <div className=\"absolute top-full left-0 right-0 sm:right-auto mt-1 w-full sm:w-80 bg-bg-elevated border border-border-seam rounded-card shadow-elevation-3 z-20 py-1 max-h-60 overflow-y-auto\">\n {activeSubscriptions.map((subscription, index) => (\n <button\n type=\"button\"\n key={subscription.id}\n onClick={() => {\n setSelectedSubscriptionIndex(index);\n setIsSubscriptionSelectorOpen(false);\n }}\n className=\"w-full flex items-center justify-between gap-3 px-4 py-3 hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"size-9 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <Crown className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"text-left min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {subscription.plan?.name ||\n tr('billing.overview.subscription', 'Subscription')}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">\n {subscription.plan?.product?.name ||\n tr('billing.overview.unknownProduct', 'Unknown Product')}{' '}\n · {tr('billing.overview.ends', 'Ends')}{' '}\n {new Date(subscription.endDate).toLocaleDateString()}\n </p>\n </div>\n </div>\n {selectedSubscriptionIndex === index && (\n <Check className=\"size-4 text-text-link flex-shrink-0\" />\n )}\n </button>\n ))}\n </div>\n </>\n )}\n </div>\n )}\n\n {/* Single Subscription Card */}\n <ActiveSubscriptionCard\n dataTour=\"billing-overview-active-subscription-card\"\n subscription={selectedSubscription}\n onViewDetails={() =>\n selectedSubscription &&\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${selectedSubscription.id}`,\n selectedSubscription.billingAccountId ?? currentAccount?.id\n )\n )\n }\n onUpgrade={() =>\n selectedSubscription &&\n navigateTo(\n withBillingAccountId(\n `/subscriptions/${selectedSubscription.id}/upgrade`,\n selectedSubscription.billingAccountId ?? currentAccount?.id\n )\n )\n }\n onManage={() =>\n currentAccount && navigateTo(`/subscriptions?billingAccountId=${currentAccount.id}`)\n }\n onPurchasePlan={() => navigateTo('/plans')}\n />\n </div>\n\n {/* Payment Methods */}\n {currentAccount && (\n <PaymentMethodsSection\n dataTour=\"billing-overview-payment-methods-section\"\n paymentMethods={paymentMethods}\n billingAccountId={currentAccount.id}\n onAddPaymentMethod={handleAddPaymentMethod}\n onSetDefault={handleSetDefaultPaymentMethod}\n onDelete={handleDeletePaymentMethod}\n isLoading={isLoading}\n />\n )}\n </div>\n\n {/* Right Column - Quick Actions & Info */}\n <div className=\"space-y-4 sm:space-y-6\">\n {/* Quick Actions */}\n <section\n className=\"border border-border-seam rounded-card bg-bg-surface shadow-elevation-1 p-4 sm:p-5\"\n data-tour=\"billing-overview-quick-actions\"\n >\n <h3 className=\"font-semibold text-text-primary mb-4\">\n {tr('billing.overview.quickActions', 'Quick Actions')}\n </h3>\n <div className=\"space-y-1.5\">\n <button\n type=\"button\"\n onClick={() => currentAccount && navigateTo(`/transactions/${currentAccount.id}`)}\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-status-info-bg-subtle flex items-center justify-center\">\n <Receipt className=\"size-4 text-status-info-text\" />\n </div>\n {tr('billing.overview.viewTransactions', 'View Transactions')}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n currentAccount && navigateTo(`/creditTransactions/${currentAccount.id}`)\n }\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-status-warning-bg-subtle flex items-center justify-center\">\n <Wallet className=\"size-4 text-status-warning-text\" />\n </div>\n {tr('billing.overview.creditHistory', 'Credit History')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/checkout/credits')}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors\"\n >\n <div className=\"size-9 rounded-lg bg-status-success-bg-subtle flex items-center justify-center\">\n <Coins className=\"size-4 text-status-success-text\" />\n </div>\n {tr('billing.overview.purchaseCredits', 'Purchase Credits')}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n currentAccount && navigateTo(`/usage?billingAccountId=${currentAccount.id}`)\n }\n disabled={!currentAccount}\n className=\"w-full flex items-center gap-3 px-3 py-2.5 text-sm text-left text-text-primary hover:bg-bg-sunken rounded-button transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <div className=\"size-9 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <Activity className=\"size-4 text-text-link\" />\n </div>\n {tr('billing.overview.usageAnalytics', 'Usage Analytics')}\n </button>\n </div>\n </section>\n\n {/* Get Started (if no subscription) — primary emphasis zone */}\n {!hasActiveSubscription && (\n <GlassCard treatment=\"glass\" glow className=\"p-4 sm:p-5\">\n <div className=\"flex items-start gap-3 mb-4\">\n <div className=\"size-10 rounded-lg bg-bg-surface flex items-center justify-center flex-shrink-0 shadow-elevation-1\">\n <Sparkles className=\"size-5 text-text-link\" />\n </div>\n <div>\n <h3 className=\"font-semibold text-text-primary\">\n {tr('billing.overview.getStarted', 'Get Started')}\n </h3>\n <p className=\"text-sm text-text-secondary mt-1\">\n {tr(\n 'billing.overview.getStartedDescription',\n 'Choose a plan that fits your needs to unlock all features'\n )}\n </p>\n </div>\n </div>\n <Button type=\"button\" onClick={() => navigateTo('/plans')} className=\"w-full\">\n {tr('billing.overview.browsePlans', 'Browse Plans')}\n <ArrowRight className=\"size-4\" />\n </Button>\n </GlassCard>\n )}\n </div>\n </div>\n\n {/* Recommended Addons (only if has subscription) - Limited to 3 with cart support */}\n <RecommendedAddonsSection\n dataTour=\"billing-overview-recommended-addons\"\n addons={recommendedAddons}\n hasSubscription={hasActiveSubscription}\n onViewAddon={(addonId) => navigateTo(`/addons/${addonId}`)}\n onPurchaseAddon={(addonId) => navigateTo(`/addons/${addonId}?action=purchase`)}\n onViewAllAddons={() => navigateTo('/addons')}\n onAddToCart={handleAddToCart}\n isInCart={handleIsInCart}\n getCartQuantity={handleGetCartQuantity}\n onUpdateQuantity={handleUpdateQuantity}\n onRemoveFromCart={handleRemoveFromCart}\n maxAddons={3}\n isLoading={isLoadingAddons}\n />\n\n {/* Floating Cart Drawer - shows when items are in cart */}\n <FloatingCartDrawer />\n\n {/* Create Billing Account Modal */}\n <CreateBillingAccountModal\n isOpen={isCreateModalOpen}\n onClose={() => {\n setIsCreateModalOpen(false);\n setCreateError(null);\n }}\n onSubmit={handleCreateAccount}\n isSubmitting={isCreatingAccount}\n error={createError}\n />\n\n {/* Add Payment Method Modal */}\n {currentAccount && (\n <AddPaymentMethodModal\n isOpen={isAddPaymentModalOpen}\n onClose={() => setIsAddPaymentModalOpen(false)}\n billingAccountId={currentAccount.id}\n currency={currentAccount.currency}\n country={currentAccount.billingAddresses?.[0]?.country}\n onSuccess={handlePaymentMethodAdded}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
@@ -83,7 +83,7 @@ var C = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, w = "
83
83
  }
84
84
  };
85
85
  return P.canManageBillingAccount ? N ? /* @__PURE__ */ m("div", {
86
- className: "space-y-6",
86
+ className: "space-y-6 p-6",
87
87
  children: [
88
88
  /* @__PURE__ */ p(n, {
89
89
  title: "Team & Permissions",
@@ -307,7 +307,7 @@ var C = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, w = "
307
307
  })
308
308
  ]
309
309
  }) : /* @__PURE__ */ m("div", {
310
- className: "space-y-6",
310
+ className: "space-y-6 p-6",
311
311
  children: [/* @__PURE__ */ p(n, {
312
312
  title: "Team & Permissions",
313
313
  description: "Select a billing account to manage who can access it."
@@ -1 +1 @@
1
- {"version":3,"file":"TeamPermissionsPage.js","names":[],"sources":["../../../../../src/billing/modules/settings/pages/TeamPermissionsPage.tsx"],"sourcesContent":["/**\n * Settings Module - Team & Permissions Page\n *\n * Lets a Billing Admin manage billing-scope role assignments for a specific\n * billing account. Calls global-rbac-svc directly via the same gateway URL\n * used by the rest of the billing module.\n *\n * Scope: `scopeType: \"billing\"`, `scopeId: <billingAccountId>`\n * Roles cloned per billing account at creation time:\n * - Billing Admin (full access to this billing account)\n * - Billing Viewer (read-only access to this billing account)\n */\nimport { useCallback, useEffect, useMemo, useState, type FC } from 'react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { AccessDenied, ActorIdentity, PageHeader } from '../../../shared/components';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBillingAccountSelection } from '../../../hooks/useBillingAccountSelection';\nimport { useBillingAccounts } from '../../dashboard/hooks/useDashboard';\nimport { directBillingGraphqlRequest } from '../../../shared/utils';\nimport { useActorProfiles } from '../../../shared/hooks';\n\n// Basic UUID-v1-through-v5 shape — accept any 8-4-4-4-12 hex pattern.\n// We intentionally don't strictly validate the version nibble; the rbac\n// service is the authoritative ID validator.\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\ninterface Role {\n id: string;\n name: string;\n description?: string | null;\n priority: number;\n isSystem: boolean;\n}\n\ninterface ActorRoleAssignment {\n id: string;\n actorId: string;\n actorType: string;\n roleId: string;\n scopeId: string;\n isActive: boolean;\n expiresAt?: string | null;\n role?: Role;\n}\n\ninterface ActorRbacSummary {\n actorId: string;\n actorType: string;\n roleAssignments: ActorRoleAssignment[];\n effectivePermissionCount?: number;\n}\n\nconst ROLES_QUERY = `\n query BillingScopeRoles($scopeId: String!) {\n roles(scopeId: $scopeId) {\n id\n name\n description\n priority\n isSystem\n }\n }\n`;\n\nconst ACTORS_QUERY = `\n query BillingScopeActors($scopeId: String!) {\n actors(scopeId: $scopeId) {\n actorId\n actorType\n roleAssignments {\n id\n actorId\n actorType\n roleId\n scopeId\n isActive\n expiresAt\n role { id name priority }\n }\n }\n }\n`;\n\nconst ASSIGN_ROLE_MUTATION = `\n mutation AssignBillingRole($input: AssignRoleInput!) {\n assignRoleToActor(input: $input) {\n id\n actorId\n roleId\n scopeId\n }\n }\n`;\n\nconst REMOVE_ROLE_MUTATION = `\n mutation RemoveBillingRole($assignmentId: String!) {\n removeRoleFromActor(assignmentId: $assignmentId)\n }\n`;\n\nexport const TeamPermissionsPage: FC = () => {\n const { apiGatewayUrl, authToken, orgId } = useBilling();\n\n // Resolution priority: URL `?billingAccountId=` → persisted localStorage →\n // default/first available account. Pass `billingAccounts` so first-time\n // users with empty localStorage still land on the default account\n // (without it the hook's auto-select effect can't fire and the page\n // dead-ends on \"No billing account selected\").\n const { billingAccounts } = useBillingAccounts();\n const { selectedAccountId } = useBillingAccountSelection({\n orgId,\n billingAccounts,\n syncFromUrl: true,\n urlParamName: 'billingAccountId',\n });\n const billingAccountId = selectedAccountId ?? undefined;\n\n // Share the resolved account with `useBillingPermissions` so its\n // billing-scope permissions fetch observes the same ID. Without this,\n // a first-time user (no URL param, empty localStorage) would see the\n // page resolve to the default account here while the permissions hook\n // still sat at null — billing-scope-only admins would see AccessDenied\n // until a navigation/reload propagated the localStorage write.\n const permissions = useBillingPermissions({ billingAccountIdOverride: billingAccountId });\n\n const [roles, setRoles] = useState<Role[]>([]);\n const [actors, setActors] = useState<ActorRbacSummary[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Add-member form\n const [newActorId, setNewActorId] = useState('');\n const [newRoleId, setNewRoleId] = useState('');\n const [isAdding, setIsAdding] = useState(false);\n const [removeAssignmentId, setRemoveAssignmentId] = useState<string | null>(null);\n\n const gqlPost = useCallback(\n async <T,>(query: string, variables: Record<string, unknown>): Promise<T> => {\n return directBillingGraphqlRequest<T>({\n apiGatewayUrl,\n authToken,\n orgId,\n query,\n variables,\n });\n },\n [apiGatewayUrl, authToken, orgId]\n );\n\n const loadData = useCallback(async () => {\n if (!billingAccountId) return;\n setIsLoading(true);\n setError(null);\n try {\n const [rolesResp, actorsResp] = await Promise.all([\n gqlPost<{ roles: Role[] }>(ROLES_QUERY, { scopeId: billingAccountId }),\n gqlPost<{ actors: ActorRbacSummary[] }>(ACTORS_QUERY, {\n scopeId: billingAccountId,\n }),\n ]);\n setRoles(rolesResp.roles ?? []);\n setActors(actorsResp.actors ?? []);\n // Default the role-picker to \"Billing Viewer\" if available, else first role\n const viewer = (rolesResp.roles ?? []).find((r) => /viewer/i.test(r.name));\n setNewRoleId(viewer?.id ?? rolesResp.roles?.[0]?.id ?? '');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load team data');\n } finally {\n setIsLoading(false);\n }\n }, [billingAccountId, gqlPost]);\n\n useEffect(() => {\n void loadData();\n }, [loadData]);\n\n const sortedActors = useMemo(\n () =>\n [...actors].sort((a, b) => {\n const priA = Math.max(...a.roleAssignments.map((ra) => ra.role?.priority ?? 0), 0);\n const priB = Math.max(...b.roleAssignments.map((ra) => ra.role?.priority ?? 0), 0);\n return priB - priA;\n }),\n [actors]\n );\n\n const profileInputs = useMemo(\n () =>\n sortedActors.map((a) => ({\n actorId: a.actorId,\n actorType: a.actorType,\n })),\n [sortedActors]\n );\n const profileMap = useActorProfiles(profileInputs);\n\n const billingAccountName = useMemo(() => {\n if (!billingAccountId) return undefined;\n return billingAccounts.find((acct) => acct.id === billingAccountId)?.name ?? undefined;\n }, [billingAccountId, billingAccounts]);\n\n const handleAddMember = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!billingAccountId || !newActorId.trim() || !newRoleId) return;\n const trimmedActorId = newActorId.trim();\n if (!UUID_RE.test(trimmedActorId)) {\n setError('User ID must be a UUID (e.g. 1a2b3c4d-...). Get it from the user profile page.');\n return;\n }\n setIsAdding(true);\n setError(null);\n try {\n await gqlPost(ASSIGN_ROLE_MUTATION, {\n input: {\n actorId: trimmedActorId,\n actorType: 'USER',\n roleId: newRoleId,\n scopeId: billingAccountId,\n },\n });\n setNewActorId('');\n await loadData();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to assign role');\n } finally {\n setIsAdding(false);\n }\n };\n\n const handleRemoveAssignment = async (assignmentId: string) => {\n setError(null);\n try {\n await gqlPost(REMOVE_ROLE_MUTATION, { assignmentId });\n await loadData();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to remove role');\n }\n };\n\n if (!permissions.canManageBillingAccount) {\n return (\n <AccessDenied message=\"You don't have permission to manage team and permissions for this billing account.\" />\n );\n }\n\n if (!billingAccountId) {\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Team & Permissions\"\n description=\"Select a billing account to manage who can access it.\"\n />\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No billing account selected\"\n description=\"Pick one from the billing account selector to manage its members and roles.\"\n />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Team & Permissions\"\n description=\"Control who can manage this billing account and what they can do.\"\n />\n\n <PagePurpose>\n Decide who on your team can see and manage this billing account. Assign the Billing Admin\n role (full access) or Billing Viewer (read-only) to specific people, and revoke access when\n someone no longer needs it.\n </PagePurpose>\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm\">\n <span className=\"font-medium text-text-primary\">Billing account:</span>{' '}\n <span className=\"text-text-primary\">{billingAccountName ?? 'Unnamed account'}</span>\n {billingAccountId ? (\n <details className=\"mt-2 text-xs text-text-secondary\">\n <summary className=\"cursor-pointer\">Account ID</summary>\n <span className=\"mt-1 inline-block font-mono\">{billingAccountId}</span>\n </details>\n ) : null}\n </div>\n\n {error && (\n <div className=\"rounded-lg border border-status-error-border/30 bg-status-error-bg/5 p-4 text-sm text-status-error-text\">\n {error}\n </div>\n )}\n\n {/* Add member form */}\n <form\n onSubmit={(e) => void handleAddMember(e)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">Add member</h3>\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-3\">\n <div className=\"md:col-span-2\">\n <label htmlFor=\"newActorId\" className=\"block text-xs font-medium text-text-secondary\">\n User ID\n </label>\n <input\n id=\"newActorId\"\n type=\"text\"\n value={newActorId}\n onChange={(e) => setNewActorId(e.target.value)}\n placeholder=\"Paste the user ID from their profile page\"\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm\"\n required\n />\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n Find this on the user's profile page.\n </p>\n </div>\n <div>\n <label htmlFor=\"newRoleId\" className=\"block text-xs font-medium text-text-secondary\">\n Role\n </label>\n <select\n id=\"newRoleId\"\n value={newRoleId}\n onChange={(e) => setNewRoleId(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm\"\n required\n >\n {roles.map((r) => (\n <option key={r.id} value={r.id}>\n {r.name}\n </option>\n ))}\n </select>\n </div>\n </div>\n <button\n type=\"submit\"\n disabled={isAdding || !newActorId.trim() || !newRoleId}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text disabled:opacity-50\"\n >\n {isAdding ? 'Adding…' : 'Add member'}\n </button>\n </form>\n\n {/* Members table */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface\">\n <div className=\"border-b border-border-subtle px-4 py-3\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Members</h3>\n <p className=\"text-xs text-text-secondary\">\n Each member's role applies only to this billing account.\n </p>\n </div>\n {isLoading ? (\n <div className=\"p-6 text-sm text-text-secondary\">Loading…</div>\n ) : sortedActors.length === 0 ? (\n <div className=\"p-6 text-sm text-text-secondary\">No members assigned yet.</div>\n ) : (\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle text-left text-xs text-text-secondary\">\n <th className=\"px-4 py-2\">Member</th>\n <th className=\"px-4 py-2\">Type</th>\n <th className=\"px-4 py-2\">Roles</th>\n <th className=\"px-4 py-2 text-right\">Actions</th>\n </tr>\n </thead>\n <tbody>\n {sortedActors.map((actor) => (\n <tr key={actor.actorId} className=\"border-b border-border-subtle last:border-0\">\n <td className=\"px-4 py-3\">\n <ActorIdentity\n profile={\n profileMap[actor.actorId] ?? {\n actorId: actor.actorId,\n actorType: actor.actorType,\n displayName:\n actor.actorType.toLowerCase() === 'user'\n ? `User ${actor.actorId.slice(0, 8)}…`\n : actor.actorType,\n secondaryLabel: actor.actorId.slice(0, 12),\n resolved: false,\n }\n }\n variant=\"cell\"\n />\n </td>\n <td className=\"px-4 py-3 text-xs capitalize text-text-secondary\">\n {actor.actorType.toLowerCase()}\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex flex-wrap gap-1\">\n {actor.roleAssignments\n .filter((ra) => ra.isActive)\n .map((ra) => (\n <span\n key={ra.id}\n className=\"inline-flex items-center gap-1 rounded-full border border-border-subtle bg-bg-sunken px-2 py-0.5 text-xs\"\n >\n {ra.role?.name ?? ra.roleId}\n <button\n type=\"button\"\n onClick={() => setRemoveAssignmentId(ra.id)}\n className=\"text-text-secondary hover:text-status-error-text\"\n aria-label={`Remove ${ra.role?.name ?? 'role'}`}\n >\n ×\n </button>\n </span>\n ))}\n </div>\n </td>\n <td className=\"px-4 py-3 text-right\">\n <span className=\"text-xs text-text-secondary\">\n {actor.effectivePermissionCount ?? '—'} permissions\n </span>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n )}\n </div>\n\n {/* Roles reference */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Available roles</h3>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Roles are cloned from the system templates when a billing account is created.\n </p>\n <ul className=\"mt-3 space-y-2\">\n {roles.map((r) => (\n <li\n key={r.id}\n className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-bg-surface p-3\"\n >\n <div className=\"flex-1\">\n <div className=\"text-sm font-medium text-text-primary\">{r.name}</div>\n {r.description && (\n <p className=\"mt-1 text-xs text-text-secondary\">{r.description}</p>\n )}\n </div>\n <span className=\"text-xs text-text-secondary\">priority {r.priority}</span>\n </li>\n ))}\n </ul>\n </div>\n\n <AlertDialog\n open={removeAssignmentId !== null}\n onOpenChange={(open) => {\n if (!open) setRemoveAssignmentId(null);\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Remove role assignment?</AlertDialogTitle>\n <AlertDialogDescription>\n Remove this role assignment? The member will lose the access this role grants for this\n billing account.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={() => {\n const id = removeAssignmentId;\n setRemoveAssignmentId(null);\n if (id) void handleRemoveAssignment(id);\n }}\n >\n Remove\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"TeamPermissionsPage.js","names":[],"sources":["../../../../../src/billing/modules/settings/pages/TeamPermissionsPage.tsx"],"sourcesContent":["/**\n * Settings Module - Team & Permissions Page\n *\n * Lets a Billing Admin manage billing-scope role assignments for a specific\n * billing account. Calls global-rbac-svc directly via the same gateway URL\n * used by the rest of the billing module.\n *\n * Scope: `scopeType: \"billing\"`, `scopeId: <billingAccountId>`\n * Roles cloned per billing account at creation time:\n * - Billing Admin (full access to this billing account)\n * - Billing Viewer (read-only access to this billing account)\n */\nimport { useCallback, useEffect, useMemo, useState, type FC } from 'react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { AccessDenied, ActorIdentity, PageHeader } from '../../../shared/components';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBillingAccountSelection } from '../../../hooks/useBillingAccountSelection';\nimport { useBillingAccounts } from '../../dashboard/hooks/useDashboard';\nimport { directBillingGraphqlRequest } from '../../../shared/utils';\nimport { useActorProfiles } from '../../../shared/hooks';\n\n// Basic UUID-v1-through-v5 shape — accept any 8-4-4-4-12 hex pattern.\n// We intentionally don't strictly validate the version nibble; the rbac\n// service is the authoritative ID validator.\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\ninterface Role {\n id: string;\n name: string;\n description?: string | null;\n priority: number;\n isSystem: boolean;\n}\n\ninterface ActorRoleAssignment {\n id: string;\n actorId: string;\n actorType: string;\n roleId: string;\n scopeId: string;\n isActive: boolean;\n expiresAt?: string | null;\n role?: Role;\n}\n\ninterface ActorRbacSummary {\n actorId: string;\n actorType: string;\n roleAssignments: ActorRoleAssignment[];\n effectivePermissionCount?: number;\n}\n\nconst ROLES_QUERY = `\n query BillingScopeRoles($scopeId: String!) {\n roles(scopeId: $scopeId) {\n id\n name\n description\n priority\n isSystem\n }\n }\n`;\n\nconst ACTORS_QUERY = `\n query BillingScopeActors($scopeId: String!) {\n actors(scopeId: $scopeId) {\n actorId\n actorType\n roleAssignments {\n id\n actorId\n actorType\n roleId\n scopeId\n isActive\n expiresAt\n role { id name priority }\n }\n }\n }\n`;\n\nconst ASSIGN_ROLE_MUTATION = `\n mutation AssignBillingRole($input: AssignRoleInput!) {\n assignRoleToActor(input: $input) {\n id\n actorId\n roleId\n scopeId\n }\n }\n`;\n\nconst REMOVE_ROLE_MUTATION = `\n mutation RemoveBillingRole($assignmentId: String!) {\n removeRoleFromActor(assignmentId: $assignmentId)\n }\n`;\n\nexport const TeamPermissionsPage: FC = () => {\n const { apiGatewayUrl, authToken, orgId } = useBilling();\n\n // Resolution priority: URL `?billingAccountId=` → persisted localStorage →\n // default/first available account. Pass `billingAccounts` so first-time\n // users with empty localStorage still land on the default account\n // (without it the hook's auto-select effect can't fire and the page\n // dead-ends on \"No billing account selected\").\n const { billingAccounts } = useBillingAccounts();\n const { selectedAccountId } = useBillingAccountSelection({\n orgId,\n billingAccounts,\n syncFromUrl: true,\n urlParamName: 'billingAccountId',\n });\n const billingAccountId = selectedAccountId ?? undefined;\n\n // Share the resolved account with `useBillingPermissions` so its\n // billing-scope permissions fetch observes the same ID. Without this,\n // a first-time user (no URL param, empty localStorage) would see the\n // page resolve to the default account here while the permissions hook\n // still sat at null — billing-scope-only admins would see AccessDenied\n // until a navigation/reload propagated the localStorage write.\n const permissions = useBillingPermissions({ billingAccountIdOverride: billingAccountId });\n\n const [roles, setRoles] = useState<Role[]>([]);\n const [actors, setActors] = useState<ActorRbacSummary[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Add-member form\n const [newActorId, setNewActorId] = useState('');\n const [newRoleId, setNewRoleId] = useState('');\n const [isAdding, setIsAdding] = useState(false);\n const [removeAssignmentId, setRemoveAssignmentId] = useState<string | null>(null);\n\n const gqlPost = useCallback(\n async <T,>(query: string, variables: Record<string, unknown>): Promise<T> => {\n return directBillingGraphqlRequest<T>({\n apiGatewayUrl,\n authToken,\n orgId,\n query,\n variables,\n });\n },\n [apiGatewayUrl, authToken, orgId]\n );\n\n const loadData = useCallback(async () => {\n if (!billingAccountId) return;\n setIsLoading(true);\n setError(null);\n try {\n const [rolesResp, actorsResp] = await Promise.all([\n gqlPost<{ roles: Role[] }>(ROLES_QUERY, { scopeId: billingAccountId }),\n gqlPost<{ actors: ActorRbacSummary[] }>(ACTORS_QUERY, {\n scopeId: billingAccountId,\n }),\n ]);\n setRoles(rolesResp.roles ?? []);\n setActors(actorsResp.actors ?? []);\n // Default the role-picker to \"Billing Viewer\" if available, else first role\n const viewer = (rolesResp.roles ?? []).find((r) => /viewer/i.test(r.name));\n setNewRoleId(viewer?.id ?? rolesResp.roles?.[0]?.id ?? '');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load team data');\n } finally {\n setIsLoading(false);\n }\n }, [billingAccountId, gqlPost]);\n\n useEffect(() => {\n void loadData();\n }, [loadData]);\n\n const sortedActors = useMemo(\n () =>\n [...actors].sort((a, b) => {\n const priA = Math.max(...a.roleAssignments.map((ra) => ra.role?.priority ?? 0), 0);\n const priB = Math.max(...b.roleAssignments.map((ra) => ra.role?.priority ?? 0), 0);\n return priB - priA;\n }),\n [actors]\n );\n\n const profileInputs = useMemo(\n () =>\n sortedActors.map((a) => ({\n actorId: a.actorId,\n actorType: a.actorType,\n })),\n [sortedActors]\n );\n const profileMap = useActorProfiles(profileInputs);\n\n const billingAccountName = useMemo(() => {\n if (!billingAccountId) return undefined;\n return billingAccounts.find((acct) => acct.id === billingAccountId)?.name ?? undefined;\n }, [billingAccountId, billingAccounts]);\n\n const handleAddMember = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!billingAccountId || !newActorId.trim() || !newRoleId) return;\n const trimmedActorId = newActorId.trim();\n if (!UUID_RE.test(trimmedActorId)) {\n setError('User ID must be a UUID (e.g. 1a2b3c4d-...). Get it from the user profile page.');\n return;\n }\n setIsAdding(true);\n setError(null);\n try {\n await gqlPost(ASSIGN_ROLE_MUTATION, {\n input: {\n actorId: trimmedActorId,\n actorType: 'USER',\n roleId: newRoleId,\n scopeId: billingAccountId,\n },\n });\n setNewActorId('');\n await loadData();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to assign role');\n } finally {\n setIsAdding(false);\n }\n };\n\n const handleRemoveAssignment = async (assignmentId: string) => {\n setError(null);\n try {\n await gqlPost(REMOVE_ROLE_MUTATION, { assignmentId });\n await loadData();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to remove role');\n }\n };\n\n if (!permissions.canManageBillingAccount) {\n return (\n <AccessDenied message=\"You don't have permission to manage team and permissions for this billing account.\" />\n );\n }\n\n if (!billingAccountId) {\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Team & Permissions\"\n description=\"Select a billing account to manage who can access it.\"\n />\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No billing account selected\"\n description=\"Pick one from the billing account selector to manage its members and roles.\"\n />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Team & Permissions\"\n description=\"Control who can manage this billing account and what they can do.\"\n />\n\n <PagePurpose>\n Decide who on your team can see and manage this billing account. Assign the Billing Admin\n role (full access) or Billing Viewer (read-only) to specific people, and revoke access when\n someone no longer needs it.\n </PagePurpose>\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm\">\n <span className=\"font-medium text-text-primary\">Billing account:</span>{' '}\n <span className=\"text-text-primary\">{billingAccountName ?? 'Unnamed account'}</span>\n {billingAccountId ? (\n <details className=\"mt-2 text-xs text-text-secondary\">\n <summary className=\"cursor-pointer\">Account ID</summary>\n <span className=\"mt-1 inline-block font-mono\">{billingAccountId}</span>\n </details>\n ) : null}\n </div>\n\n {error && (\n <div className=\"rounded-lg border border-status-error-border/30 bg-status-error-bg/5 p-4 text-sm text-status-error-text\">\n {error}\n </div>\n )}\n\n {/* Add member form */}\n <form\n onSubmit={(e) => void handleAddMember(e)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 space-y-3\"\n >\n <h3 className=\"text-sm font-semibold text-text-primary\">Add member</h3>\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-3\">\n <div className=\"md:col-span-2\">\n <label htmlFor=\"newActorId\" className=\"block text-xs font-medium text-text-secondary\">\n User ID\n </label>\n <input\n id=\"newActorId\"\n type=\"text\"\n value={newActorId}\n onChange={(e) => setNewActorId(e.target.value)}\n placeholder=\"Paste the user ID from their profile page\"\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm\"\n required\n />\n <p className=\"mt-1 text-[11px] text-text-secondary\">\n Find this on the user's profile page.\n </p>\n </div>\n <div>\n <label htmlFor=\"newRoleId\" className=\"block text-xs font-medium text-text-secondary\">\n Role\n </label>\n <select\n id=\"newRoleId\"\n value={newRoleId}\n onChange={(e) => setNewRoleId(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm\"\n required\n >\n {roles.map((r) => (\n <option key={r.id} value={r.id}>\n {r.name}\n </option>\n ))}\n </select>\n </div>\n </div>\n <button\n type=\"submit\"\n disabled={isAdding || !newActorId.trim() || !newRoleId}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text disabled:opacity-50\"\n >\n {isAdding ? 'Adding…' : 'Add member'}\n </button>\n </form>\n\n {/* Members table */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface\">\n <div className=\"border-b border-border-subtle px-4 py-3\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Members</h3>\n <p className=\"text-xs text-text-secondary\">\n Each member's role applies only to this billing account.\n </p>\n </div>\n {isLoading ? (\n <div className=\"p-6 text-sm text-text-secondary\">Loading…</div>\n ) : sortedActors.length === 0 ? (\n <div className=\"p-6 text-sm text-text-secondary\">No members assigned yet.</div>\n ) : (\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle text-left text-xs text-text-secondary\">\n <th className=\"px-4 py-2\">Member</th>\n <th className=\"px-4 py-2\">Type</th>\n <th className=\"px-4 py-2\">Roles</th>\n <th className=\"px-4 py-2 text-right\">Actions</th>\n </tr>\n </thead>\n <tbody>\n {sortedActors.map((actor) => (\n <tr key={actor.actorId} className=\"border-b border-border-subtle last:border-0\">\n <td className=\"px-4 py-3\">\n <ActorIdentity\n profile={\n profileMap[actor.actorId] ?? {\n actorId: actor.actorId,\n actorType: actor.actorType,\n displayName:\n actor.actorType.toLowerCase() === 'user'\n ? `User ${actor.actorId.slice(0, 8)}…`\n : actor.actorType,\n secondaryLabel: actor.actorId.slice(0, 12),\n resolved: false,\n }\n }\n variant=\"cell\"\n />\n </td>\n <td className=\"px-4 py-3 text-xs capitalize text-text-secondary\">\n {actor.actorType.toLowerCase()}\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex flex-wrap gap-1\">\n {actor.roleAssignments\n .filter((ra) => ra.isActive)\n .map((ra) => (\n <span\n key={ra.id}\n className=\"inline-flex items-center gap-1 rounded-full border border-border-subtle bg-bg-sunken px-2 py-0.5 text-xs\"\n >\n {ra.role?.name ?? ra.roleId}\n <button\n type=\"button\"\n onClick={() => setRemoveAssignmentId(ra.id)}\n className=\"text-text-secondary hover:text-status-error-text\"\n aria-label={`Remove ${ra.role?.name ?? 'role'}`}\n >\n ×\n </button>\n </span>\n ))}\n </div>\n </td>\n <td className=\"px-4 py-3 text-right\">\n <span className=\"text-xs text-text-secondary\">\n {actor.effectivePermissionCount ?? '—'} permissions\n </span>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n )}\n </div>\n\n {/* Roles reference */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Available roles</h3>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Roles are cloned from the system templates when a billing account is created.\n </p>\n <ul className=\"mt-3 space-y-2\">\n {roles.map((r) => (\n <li\n key={r.id}\n className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-bg-surface p-3\"\n >\n <div className=\"flex-1\">\n <div className=\"text-sm font-medium text-text-primary\">{r.name}</div>\n {r.description && (\n <p className=\"mt-1 text-xs text-text-secondary\">{r.description}</p>\n )}\n </div>\n <span className=\"text-xs text-text-secondary\">priority {r.priority}</span>\n </li>\n ))}\n </ul>\n </div>\n\n <AlertDialog\n open={removeAssignmentId !== null}\n onOpenChange={(open) => {\n if (!open) setRemoveAssignmentId(null);\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Remove role assignment?</AlertDialogTitle>\n <AlertDialogDescription>\n Remove this role assignment? The member will lose the access this role grants for this\n billing account.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={() => {\n const id = removeAssignmentId;\n setRemoveAssignmentId(null);\n if (id) void handleRemoveAssignment(id);\n }}\n >\n Remove\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n};\n"],"mappings":"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"}