@burdenoff/microfe-billing 2026.601.6 → 2026.604.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.
@@ -1 +1 @@
1
- {"version":3,"file":"PlansBrowsePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlansBrowsePage.tsx"],"sourcesContent":["/**\n * Plans Module - Plans Browse Page (User-facing)\n * Browse available plans and subscribe. No create/edit/toggle actions.\n */\n\nimport { useState, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { usePlans } from '../hooks';\nimport { ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n getPlanFeatureQuantity,\n isServerError,\n} from '../../../shared/utils';\nimport { PlanDuration } from '../../../shared/types';\nimport type { Plan, PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\nimport {\n FEATURED_QUOTA_NAMES,\n FEATURED_QUOTA_LABELS,\n formatFeaturedQuotaValue,\n} from '../constants/featuredQuotas';\n\ntype ViewMode = 'grid' | 'list';\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\nfunction combineQuotas(features: PlanFeature[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n for (const feature of features) {\n if (!feature.quota) continue;\n const quotaId = feature.quota.id;\n const limitValue = getPlanFeatureQuantity(feature);\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\n/** Returns only the featured quotas, in the canonical display order. */\nfunction getFeaturedQuotas(features: PlanFeature[]): CombinedQuota[] {\n const all = combineQuotas(features);\n const byName = new Map(all.map((q) => [q.name, q]));\n return FEATURED_QUOTA_NAMES.flatMap((name) => {\n const q = byName.get(name);\n return q ? [q] : [];\n });\n}\n\nexport const PlansBrowsePage: 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 const navigateTo = useBillingNavigate();\n const { productId } = useBilling();\n const { plans, isLoading, error, refetch } = usePlans({\n includeFree: false,\n productId: productId ?? undefined,\n });\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [billingInterval, setBillingInterval] = useState<PlanDuration>(PlanDuration.MONTHLY);\n\n // Only show active plans to users\n const filteredPlans = plans.filter((plan) => plan.isActive && plan.duration === billingInterval);\n\n if (isLoading && plans.length === 0) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-muted animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-24 bg-muted animate-pulse rounded\" />\n <div className=\"h-8 w-32 bg-muted animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3].map((j) => (\n <div key={j} className=\"h-4 w-full bg-muted animate-pulse rounded\" />\n ))}\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr('billing.plans.unableToLoadPlans', 'Unable to load plans.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.plans.browsePlansTitle', 'Plans')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.plans.browsePlansSubtitle', 'Choose a plan that fits your needs')}\n </p>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border\">\n <div className=\"inline-flex rounded-lg border border-border p-1 bg-muted/50\">\n {([PlanDuration.MONTHLY, PlanDuration.YEARLY] as const).map((interval) => (\n <button\n type=\"button\"\n key={interval}\n onClick={() => setBillingInterval(interval)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === interval\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n {interval === PlanDuration.MONTHLY ? 'Monthly' : 'Yearly'}\n </button>\n ))}\n </div>\n\n <div className=\"ml-auto inline-flex rounded-lg border border-border p-1 bg-muted/50\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'grid'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n title=\"Grid view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'list'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n title=\"List view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6h16M4 12h16M4 18h16\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Plans */}\n {filteredPlans.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg\">\n <div className=\"size-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <svg\n className=\"size-6 text-muted-foreground\"\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=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">\n {tr('billing.plans.noPlansAvailable', 'No plans available')}\n </h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.plans.checkBackLater', 'Check back later for new plans.')}\n </p>\n </div>\n ) : viewMode === 'grid' ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {filteredPlans.map((plan) => (\n <BrowsePlanCard\n key={plan.id}\n plan={plan}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n <EnterpriseCard />\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredPlans.map((plan) => (\n <BrowsePlanRow\n key={plan.id}\n plan={plan}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n <EnterpriseRow />\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Static Enterprise \"Contact Us\" card — no backend plan record\n// ============================================================================\n\nconst ENTERPRISE_HIGHLIGHTS = [\n 'Unlimited Agents & Sessions',\n 'Dedicated single-tenant infra',\n 'Customer-managed encryption keys',\n 'SSO / SCIM provisioning',\n 'Custom SLA & support contract',\n 'Volume-based custom pricing',\n 'Audit log with custom retention',\n 'Priority onboarding & migration',\n];\n\nconst EnterpriseCard: FC = () => (\n <div className=\"border border-action-primary-bg/40 bg-action-primary-bg/5 rounded-lg p-6 flex flex-col h-full\">\n <div className=\"mb-4\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-lg font-bold text-foreground\">Enterprise</h3>\n <span className=\"text-xs font-medium px-2 py-0.5 rounded-full bg-action-primary-bg/15 text-action-primary-bg\">\n Contact Us\n </span>\n </div>\n <span className=\"text-sm text-muted-foreground\">Custom contract</span>\n </div>\n\n <div className=\"flex items-baseline gap-1 mb-4\">\n <span className=\"text-2xl font-bold text-foreground\">Custom pricing</span>\n </div>\n\n <ul className=\"space-y-2 mb-4 flex-1\">\n {ENTERPRISE_HIGHLIGHTS.map((item) => (\n <li key={item} className=\"flex items-center gap-2 text-sm\">\n <svg\n className=\"size-4 text-status-success-text shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M5 13l4 4L19 7\" />\n </svg>\n <span className=\"text-foreground\">{item}</span>\n </li>\n ))}\n </ul>\n\n <div className=\"space-y-2 pt-4 border-t border-border mt-auto\">\n <a\n href=\"mailto:contact@burdenoff.com?subject=Enterprise Plan Inquiry\"\n className=\"block w-full px-4 py-2 text-sm font-medium text-center bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n Contact Us\n </a>\n </div>\n </div>\n);\n\nconst EnterpriseRow: FC = () => (\n <div className=\"flex items-center gap-4 p-4 border border-action-primary-bg/40 rounded-lg bg-action-primary-bg/5 transition-all hover:shadow-sm\">\n <div className=\"size-10 rounded-lg bg-action-primary-bg/10 flex items-center justify-center shrink-0\">\n <svg\n className=\"size-5 text-action-primary-bg\"\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=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-2 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-foreground\">Enterprise</h3>\n <span className=\"text-xs font-medium px-2 py-0.5 rounded-full bg-action-primary-bg/15 text-action-primary-bg shrink-0\">\n Contact Us\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground\">Dedicated infra · SSO/SCIM · Custom SLA</p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-foreground\">Custom pricing</div>\n <div className=\"text-sm text-muted-foreground\">contract-based</div>\n </div>\n\n <a\n href=\"mailto:contact@burdenoff.com?subject=Enterprise Plan Inquiry\"\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors shrink-0\"\n >\n Contact Us\n </a>\n </div>\n);\n\n// ============================================================================\n// Browse Plan Card (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanCard: FC<{ plan: Plan; onView: () => void; onSubscribe: () => void }> = ({\n plan,\n onView,\n onSubscribe,\n}) => {\n const featuredQuotas = getFeaturedQuotas(plan.features || []);\n const allQuotas = combineQuotas(plan.features || []);\n const extraCount = allQuotas.length - featuredQuotas.length;\n const displayCurrency = useDefaultBillingCurrency();\n\n return (\n <div className=\"border border-border rounded-lg bg-card p-6 transition-all hover:shadow-md flex flex-col h-full\">\n <div className=\"mb-4\">\n <h3 className=\"text-lg font-bold text-foreground\">{plan.name}</h3>\n <span className=\"text-sm text-muted-foreground\">\n {formatPlanDurationLabel(plan.duration)}\n </span>\n </div>\n\n <div className=\"flex items-baseline gap-1 mb-4\">\n <span className=\"text-3xl font-bold text-foreground\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </span>\n <span className=\"text-muted-foreground text-sm\">/ {formatPlanDuration(plan.duration)}</span>\n </div>\n\n {featuredQuotas.length > 0 && (\n <ul className=\"space-y-2 mb-4 flex-1\">\n {featuredQuotas.map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-4 text-status-success-text shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n <span className=\"text-foreground\">\n {FEATURED_QUOTA_LABELS[quota.name] ?? quota.name}\n </span>\n </div>\n <span className=\"font-medium text-foreground tabular-nums\">\n {formatFeaturedQuotaValue(quota.name, quota.totalValue)}\n </span>\n </li>\n ))}\n {extraCount > 0 && (\n <li className=\"text-sm text-muted-foreground pl-6\">+{extraCount} more — see details</li>\n )}\n </ul>\n )}\n\n <div className=\"space-y-2 pt-4 border-t border-border mt-auto\">\n <button\n type=\"button\"\n onClick={onSubscribe}\n className=\"w-full px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Subscribe\n </button>\n <button\n type=\"button\"\n onClick={onView}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n View Details\n </button>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Browse Plan Row (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanRow: FC<{ plan: Plan; onView: () => void; onSubscribe: () => void }> = ({\n plan,\n onView,\n onSubscribe,\n}) => {\n const featuredQuotas = getFeaturedQuotas(plan.features || []);\n const displayCurrency = useDefaultBillingCurrency();\n\n return (\n <div className=\"flex items-center gap-4 p-4 border border-border rounded-lg bg-card transition-all hover:shadow-sm\">\n <div className=\"size-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0\">\n <svg className=\"size-5 text-primary\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <h3 className=\"font-semibold text-foreground truncate\">{plan.name}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {featuredQuotas.length} key feature{featuredQuotas.length !== 1 ? 's' : ''} included\n </p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-foreground\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </div>\n <div className=\"text-sm text-muted-foreground\">per {formatPlanDuration(plan.duration)}</div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={onSubscribe}\n className=\"px-3 py-1.5 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Subscribe\n </button>\n <button\n type=\"button\"\n onClick={onView}\n className=\"px-3 py-1.5 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n View\n </button>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"PlansBrowsePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlansBrowsePage.tsx"],"sourcesContent":["/**\n * Plans Module - Plans Browse Page (User-facing)\n * Browse available plans and subscribe. No create/edit/toggle actions.\n */\n\nimport { useState, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { usePlans } from '../hooks';\nimport { ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n getPlanFeatureQuantity,\n isServerError,\n} from '../../../shared/utils';\nimport { PlanDuration } from '../../../shared/types';\nimport type { Plan, PlanFeature } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\nimport {\n FEATURED_QUOTA_NAMES,\n FEATURED_QUOTA_LABELS,\n formatFeaturedQuotaValue,\n} from '../constants/featuredQuotas';\n\ntype ViewMode = 'grid' | 'list';\n\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\nfunction combineQuotas(features: PlanFeature[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n for (const feature of features) {\n if (!feature.quota) continue;\n const quotaId = feature.quota.id;\n const limitValue = getPlanFeatureQuantity(feature);\n const existing = quotaMap.get(quotaId);\n if (existing) {\n existing.totalValue += limitValue;\n } else {\n quotaMap.set(quotaId, {\n quotaId,\n name: feature.quota.name ?? 'Unknown Quota',\n totalValue: limitValue,\n });\n }\n }\n return Array.from(quotaMap.values());\n}\n\n/** Returns only the featured quotas, in the canonical display order. */\nfunction getFeaturedQuotas(features: PlanFeature[]): CombinedQuota[] {\n const all = combineQuotas(features);\n const byName = new Map(all.map((q) => [q.name, q]));\n return FEATURED_QUOTA_NAMES.flatMap((name) => {\n const q = byName.get(name);\n return q ? [q] : [];\n });\n}\n\nexport const PlansBrowsePage: 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 const navigateTo = useBillingNavigate();\n const { productId } = useBilling();\n const { plans, isLoading, error, refetch } = usePlans({\n includeFree: false,\n productId: productId ?? undefined,\n });\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [billingInterval, setBillingInterval] = useState<PlanDuration>(PlanDuration.MONTHLY);\n\n // Only show active, publicly purchasable plans to users\n const filteredPlans = plans.filter(\n (plan) => plan.isActive && plan.isPurchasable !== false && plan.duration === billingInterval\n );\n\n if (isLoading && plans.length === 0) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-muted animate-pulse rounded\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-6\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"border border-border rounded-lg p-6 space-y-4\">\n <div className=\"h-6 w-24 bg-muted animate-pulse rounded\" />\n <div className=\"h-8 w-32 bg-muted animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3].map((j) => (\n <div key={j} className=\"h-4 w-full bg-muted animate-pulse rounded\" />\n ))}\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr('billing.plans.unableToLoadPlans', 'Unable to load plans.')}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.plans.browsePlansTitle', 'Plans')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.plans.browsePlansSubtitle', 'Choose a plan that fits your needs')}\n </p>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border\">\n <div className=\"inline-flex rounded-lg border border-border p-1 bg-muted/50\">\n {([PlanDuration.MONTHLY, PlanDuration.YEARLY] as const).map((interval) => (\n <button\n type=\"button\"\n key={interval}\n onClick={() => setBillingInterval(interval)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === interval\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n {interval === PlanDuration.MONTHLY ? 'Monthly' : 'Yearly'}\n </button>\n ))}\n </div>\n\n <div className=\"ml-auto inline-flex rounded-lg border border-border p-1 bg-muted/50\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'grid'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n title=\"Grid view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 rounded-md transition-colors ${\n viewMode === 'list'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n title=\"List view\"\n >\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 6h16M4 12h16M4 18h16\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n {/* Plans */}\n {filteredPlans.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg\">\n <div className=\"size-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <svg\n className=\"size-6 text-muted-foreground\"\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=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">\n {tr('billing.plans.noPlansAvailable', 'No plans available')}\n </h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.plans.checkBackLater', 'Check back later for new plans.')}\n </p>\n </div>\n ) : viewMode === 'grid' ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n {filteredPlans.map((plan) => (\n <BrowsePlanCard\n key={plan.id}\n plan={plan}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n <EnterpriseCard />\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredPlans.map((plan) => (\n <BrowsePlanRow\n key={plan.id}\n plan={plan}\n onView={() => navigateTo(`/plans/${plan.id}`)}\n onSubscribe={() => navigateTo(`/checkout/${plan.id}`)}\n />\n ))}\n <EnterpriseRow />\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Static Enterprise \"Contact Us\" card — no backend plan record\n// ============================================================================\n\nconst ENTERPRISE_HIGHLIGHTS = [\n 'Unlimited Agents & Sessions',\n 'Dedicated single-tenant infra',\n 'Customer-managed encryption keys',\n 'SSO / SCIM provisioning',\n 'Custom SLA & support contract',\n 'Volume-based custom pricing',\n 'Audit log with custom retention',\n 'Priority onboarding & migration',\n];\n\nconst EnterpriseCard: FC = () => (\n <div className=\"border border-action-primary-bg/40 bg-action-primary-bg/5 rounded-lg p-6 flex flex-col h-full\">\n <div className=\"mb-4\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"text-lg font-bold text-foreground\">Enterprise</h3>\n <span className=\"text-xs font-medium px-2 py-0.5 rounded-full bg-action-primary-bg/15 text-action-primary-bg\">\n Contact Us\n </span>\n </div>\n <span className=\"text-sm text-muted-foreground\">Custom contract</span>\n </div>\n\n <div className=\"flex items-baseline gap-1 mb-4\">\n <span className=\"text-2xl font-bold text-foreground\">Custom pricing</span>\n </div>\n\n <ul className=\"space-y-2 mb-4 flex-1\">\n {ENTERPRISE_HIGHLIGHTS.map((item) => (\n <li key={item} className=\"flex items-center gap-2 text-sm\">\n <svg\n className=\"size-4 text-status-success-text shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M5 13l4 4L19 7\" />\n </svg>\n <span className=\"text-foreground\">{item}</span>\n </li>\n ))}\n </ul>\n\n <div className=\"space-y-2 pt-4 border-t border-border mt-auto\">\n <a\n href=\"mailto:contact@burdenoff.com?subject=Enterprise Plan Inquiry\"\n className=\"block w-full px-4 py-2 text-sm font-medium text-center bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n Contact Us\n </a>\n </div>\n </div>\n);\n\nconst EnterpriseRow: FC = () => (\n <div className=\"flex items-center gap-4 p-4 border border-action-primary-bg/40 rounded-lg bg-action-primary-bg/5 transition-all hover:shadow-sm\">\n <div className=\"size-10 rounded-lg bg-action-primary-bg/10 flex items-center justify-center shrink-0\">\n <svg\n className=\"size-5 text-action-primary-bg\"\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=\"M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-2 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-foreground\">Enterprise</h3>\n <span className=\"text-xs font-medium px-2 py-0.5 rounded-full bg-action-primary-bg/15 text-action-primary-bg shrink-0\">\n Contact Us\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground\">Dedicated infra · SSO/SCIM · Custom SLA</p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-foreground\">Custom pricing</div>\n <div className=\"text-sm text-muted-foreground\">contract-based</div>\n </div>\n\n <a\n href=\"mailto:contact@burdenoff.com?subject=Enterprise Plan Inquiry\"\n className=\"px-3 py-1.5 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors shrink-0\"\n >\n Contact Us\n </a>\n </div>\n);\n\n// ============================================================================\n// Browse Plan Card (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanCard: FC<{ plan: Plan; onView: () => void; onSubscribe: () => void }> = ({\n plan,\n onView,\n onSubscribe,\n}) => {\n const featuredQuotas = getFeaturedQuotas(plan.features || []);\n const allQuotas = combineQuotas(plan.features || []);\n const extraCount = allQuotas.length - featuredQuotas.length;\n const displayCurrency = useDefaultBillingCurrency();\n\n return (\n <div className=\"border border-border rounded-lg bg-card p-6 transition-all hover:shadow-md flex flex-col h-full\">\n <div className=\"mb-4\">\n <h3 className=\"text-lg font-bold text-foreground\">{plan.name}</h3>\n <span className=\"text-sm text-muted-foreground\">\n {formatPlanDurationLabel(plan.duration)}\n </span>\n </div>\n\n <div className=\"flex items-baseline gap-1 mb-4\">\n <span className=\"text-3xl font-bold text-foreground\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </span>\n <span className=\"text-muted-foreground text-sm\">/ {formatPlanDuration(plan.duration)}</span>\n </div>\n\n {featuredQuotas.length > 0 && (\n <ul className=\"space-y-2 mb-4 flex-1\">\n {featuredQuotas.map((quota) => (\n <li key={quota.quotaId} className=\"flex items-center justify-between text-sm\">\n <div className=\"flex items-center gap-2\">\n <svg\n className=\"size-4 text-status-success-text shrink-0\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M5 13l4 4L19 7\"\n />\n </svg>\n <span className=\"text-foreground\">\n {FEATURED_QUOTA_LABELS[quota.name] ?? quota.name}\n </span>\n </div>\n <span className=\"font-medium text-foreground tabular-nums\">\n {formatFeaturedQuotaValue(quota.name, quota.totalValue)}\n </span>\n </li>\n ))}\n {extraCount > 0 && (\n <li className=\"text-sm text-muted-foreground pl-6\">+{extraCount} more — see details</li>\n )}\n </ul>\n )}\n\n <div className=\"space-y-2 pt-4 border-t border-border mt-auto\">\n <button\n type=\"button\"\n onClick={onSubscribe}\n className=\"w-full px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Subscribe\n </button>\n <button\n type=\"button\"\n onClick={onView}\n className=\"w-full px-4 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n View Details\n </button>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Browse Plan Row (no edit/toggle)\n// ============================================================================\n\nconst BrowsePlanRow: FC<{ plan: Plan; onView: () => void; onSubscribe: () => void }> = ({\n plan,\n onView,\n onSubscribe,\n}) => {\n const featuredQuotas = getFeaturedQuotas(plan.features || []);\n const displayCurrency = useDefaultBillingCurrency();\n\n return (\n <div className=\"flex items-center gap-4 p-4 border border-border rounded-lg bg-card transition-all hover:shadow-sm\">\n <div className=\"size-10 rounded-lg bg-primary/10 flex items-center justify-center shrink-0\">\n <svg className=\"size-5 text-primary\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10\"\n />\n </svg>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <h3 className=\"font-semibold text-foreground truncate\">{plan.name}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {featuredQuotas.length} key feature{featuredQuotas.length !== 1 ? 's' : ''} included\n </p>\n </div>\n\n <div className=\"text-right\">\n <div className=\"font-semibold text-foreground\">\n {(() => {\n const { price, currency } = getDisplayPrice(\n plan.price,\n plan.currency,\n plan.currencyPrices as Record<string, number> | null,\n displayCurrency\n );\n return formatCurrency(price, currency);\n })()}\n </div>\n <div className=\"text-sm text-muted-foreground\">per {formatPlanDuration(plan.duration)}</div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={onSubscribe}\n className=\"px-3 py-1.5 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Subscribe\n </button>\n <button\n type=\"button\"\n onClick={onView}\n className=\"px-3 py-1.5 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n View\n </button>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -2,9 +2,88 @@ import { AnalyticsPeriod as e, QuotaStatus as t } from "../../../shared/types/gr
2
2
  import { useGetBillingAccountQuotasSummaryForUsageQuery as n, useGetBillingAccountsForUsageQuery as r, useGetPastSubscriptionsWithQuotasQuery as i, useGetSubscriptionsWithQuotasQuery as a, useGetUsageDashboardQuery as o } from "../../../../generated/global-operations.js";
3
3
  import { useGetUsageRecordsQuery as s } from "../../../../generated/wspace-operations.js";
4
4
  import { useCallback as c, useEffect as l, useMemo as u, useState as d } from "react";
5
- import { gql as f, useGatewayClient as p, useQuery as m } from "@burdenoff/fe-libs/shared/graphql";
5
+ import { gql as f, useGatewayClient as p, useMutation as m, useQuery as h } from "@burdenoff/fe-libs/shared/graphql";
6
6
  //#region src/billing/modules/usage/hooks/useUsage.ts
7
- var h = f`
7
+ var g = f`
8
+ query WorkspaceQuotaOverview($workspaceId: ID!) {
9
+ workspaceQuotaOverview(workspaceId: $workspaceId) {
10
+ assignment {
11
+ id
12
+ workspaceId
13
+ quotaName
14
+ productId
15
+ quotaMode
16
+ billingAccountId
17
+ subscriptionId
18
+ limit
19
+ used
20
+ noLimit
21
+ resetPeriod
22
+ lastResetAt
23
+ isActive
24
+ createdAt
25
+ }
26
+ liveStatus {
27
+ workspaceId
28
+ quotaName
29
+ productId
30
+ currentValue
31
+ limit
32
+ noLimit
33
+ scrapedAt
34
+ }
35
+ effectiveUsed
36
+ remaining
37
+ usagePercentage
38
+ }
39
+ }
40
+ `, _ = f`
41
+ mutation AssignWorkspaceQuota($input: AssignWorkspaceQuotaInput!) {
42
+ assignWorkspaceQuota(input: $input) {
43
+ id
44
+ workspaceId
45
+ quotaName
46
+ productId
47
+ quotaMode
48
+ billingAccountId
49
+ subscriptionId
50
+ limit
51
+ used
52
+ noLimit
53
+ resetPeriod
54
+ isActive
55
+ createdAt
56
+ }
57
+ }
58
+ `;
59
+ f`
60
+ mutation UnassignWorkspaceQuota($id: ID!, $workspaceId: ID!) {
61
+ unassignWorkspaceQuota(id: $id, workspaceId: $workspaceId)
62
+ }
63
+ `;
64
+ function v(e) {
65
+ let { data: t, loading: n, error: r, refetch: i } = h(g, {
66
+ client: p("workspace"),
67
+ variables: { workspaceId: e },
68
+ skip: !e,
69
+ fetchPolicy: "cache-and-network"
70
+ });
71
+ return {
72
+ overview: t?.workspaceQuotaOverview ?? [],
73
+ isLoading: n,
74
+ error: r ?? null,
75
+ refetch: i
76
+ };
77
+ }
78
+ function y() {
79
+ let [e, { loading: t, error: n }] = m(_, { client: p("workspace") });
80
+ return {
81
+ assign: async (t) => (await e({ variables: { input: t } })).data?.assignWorkspaceQuota ?? null,
82
+ isLoading: t,
83
+ error: n ?? null
84
+ };
85
+ }
86
+ var b = f`
8
87
  query WorkspaceQuotaStatuses($workspaceId: ID!) {
9
88
  workspaceQuotaStatuses(workspaceId: $workspaceId) {
10
89
  workspaceId
@@ -16,7 +95,7 @@ var h = f`
16
95
  scrapedAt
17
96
  }
18
97
  }
19
- `, g = f`
98
+ `, x = f`
20
99
  query WorkspaceQuotaHistory($workspaceId: ID!, $quotaName: String!, $limit: Int) {
21
100
  workspaceQuotaHistory(workspaceId: $workspaceId, quotaName: $quotaName, limit: $limit) {
22
101
  id
@@ -30,8 +109,8 @@ var h = f`
30
109
  }
31
110
  }
32
111
  `;
33
- function _(e) {
34
- let { data: t, loading: n, error: r } = m(h, {
112
+ function S(e) {
113
+ let { data: t, loading: n, error: r } = h(b, {
35
114
  client: p("workspace"),
36
115
  variables: { workspaceId: e },
37
116
  skip: !e,
@@ -50,8 +129,8 @@ function _(e) {
50
129
  error: r ?? null
51
130
  };
52
131
  }
53
- function v(e, t, n = 48) {
54
- let { data: r, loading: i, error: a } = m(g, {
132
+ function C(e, t, n = 48) {
133
+ let { data: r, loading: i, error: a } = h(x, {
55
134
  client: p("workspace"),
56
135
  variables: {
57
136
  workspaceId: e,
@@ -67,7 +146,7 @@ function v(e, t, n = 48) {
67
146
  error: a ?? null
68
147
  };
69
148
  }
70
- function y({ billingAccountId: t, period: n = e.LAST_30_DAYS }) {
149
+ function w({ billingAccountId: t, period: n = e.LAST_30_DAYS }) {
71
150
  let { data: r, loading: i, error: a, refetch: s } = o({
72
151
  variables: {
73
152
  billingAccountId: t,
@@ -83,19 +162,19 @@ function y({ billingAccountId: t, period: n = e.LAST_30_DAYS }) {
83
162
  refetch: s
84
163
  };
85
164
  }
86
- function b(e, n, r = !1) {
165
+ function T(e, n, r = !1) {
87
166
  return r ? t.HEALTHY : n <= 0 ? t.EXHAUSTED : e >= 90 ? t.CRITICAL : e >= 75 ? t.WARNING : t.HEALTHY;
88
167
  }
89
- var x = {
168
+ var E = {
90
169
  "agents-registered": "vibecontrols.agents.count",
91
170
  "vibes-created": "vibecontrols.vibes.count",
92
171
  "catalogs-created": "vibecontrols.catalogs.count",
93
172
  "tunnels-created": "vibecontrols.tunnels.count",
94
173
  "sessions-started": "vibecontrols.sessions.starts"
95
174
  };
96
- function S(e, t) {
175
+ function D(e, t) {
97
176
  return t.size === 0 ? e : e.map((e) => {
98
- let n = x[e.name] ?? e.name, r = t.get(n);
177
+ let n = E[e.name] ?? e.name, r = t.get(n);
99
178
  if (!r) return e;
100
179
  let i = r.currentValue, { totalLimit: a, noLimit: o } = e, s = o || a === 0 ? 0 : i / a * 100, c = o ? 0 : Math.max(0, a - i), l = i, u = [...e.quotaAssignments].sort((e, t) => (e.endtime ? new Date(e.endtime).getTime() : Infinity) - (t.endtime ? new Date(t.endtime).getTime() : Infinity)).map((e) => {
101
180
  let t = Math.min(l, e.limit || 0);
@@ -109,12 +188,12 @@ function S(e, t) {
109
188
  totalUsed: i,
110
189
  usagePercentage: s,
111
190
  isCollectorBased: !0,
112
- status: b(s, c, o),
191
+ status: T(s, c, o),
113
192
  quotaAssignments: u
114
193
  };
115
194
  });
116
195
  }
117
- function C(e) {
196
+ function O(e) {
118
197
  let t = /* @__PURE__ */ new Map();
119
198
  for (let n of e) {
120
199
  let e = t.get(n.name) || [];
@@ -122,7 +201,7 @@ function C(e) {
122
201
  }
123
202
  let n = [];
124
203
  for (let [e, r] of t) {
125
- let t = r.reduce((e, t) => e + (t.limit || 0), 0), i = r.reduce((e, t) => e + (t.used || 0), 0), a = r.reduce((e, t) => e + (t.limitLeft || 0), 0), o = t === 0 && r.every((e) => e.noLimit || (e.limit || 0) === 0), s = o || t === 0 ? 0 : i / t * 100, c = b(s, a, o), l = r[0]?.reusable || !1, u = r[0]?.unit || "", d = r[0]?.context, f = typeof d?.displayName == "string" ? d.displayName : void 0, p = typeof d?.description == "string" ? d.description : void 0;
204
+ let t = r.reduce((e, t) => e + (t.limit || 0), 0), i = r.reduce((e, t) => e + (t.used || 0), 0), a = r.reduce((e, t) => e + (t.limitLeft || 0), 0), o = t === 0 && r.every((e) => e.noLimit || (e.limit || 0) === 0), s = o || t === 0 ? 0 : i / t * 100, c = T(s, a, o), l = r[0]?.reusable || !1, u = r[0]?.unit || "", d = r[0]?.context, f = typeof d?.displayName == "string" ? d.displayName : void 0, p = typeof d?.description == "string" ? d.description : void 0;
126
205
  n.push({
127
206
  name: e,
128
207
  displayName: f,
@@ -151,7 +230,7 @@ function C(e) {
151
230
  return n === 0 ? t.usagePercentage - e.usagePercentage : n;
152
231
  });
153
232
  }
154
- function w() {
233
+ function k() {
155
234
  let { data: e, loading: t, error: n } = r({ fetchPolicy: "cache-and-network" });
156
235
  return {
157
236
  billingAccounts: e?.getBillingAccountsByOrg ?? [],
@@ -159,7 +238,7 @@ function w() {
159
238
  error: n ?? null
160
239
  };
161
240
  }
162
- function T(e, t) {
241
+ function A(e, t) {
163
242
  let { data: r, loading: i, error: a, refetch: o } = n({
164
243
  variables: {
165
244
  billingAccountId: e,
@@ -172,15 +251,15 @@ function T(e, t) {
172
251
  pooledQuotas: u(() => {
173
252
  if (!r?.getBillingAccountQuotasSummary) return [];
174
253
  let e = r.getBillingAccountQuotasSummary;
175
- return C(e.pooledQuotas || []);
254
+ return O(e.pooledQuotas || []);
176
255
  }, [r]),
177
256
  isLoading: i,
178
257
  error: a ?? null,
179
258
  refetch: o
180
259
  };
181
260
  }
182
- var E = 10;
183
- function D(e, t = {}) {
261
+ var j = 10;
262
+ function M(e, t = {}) {
184
263
  let { searchQuery: n, viewMode: r = "active" } = t, o = r === "active" || r === "all", s = r === "past" || r === "all", { data: f, loading: p, error: m, refetch: h } = a({
185
264
  variables: {
186
265
  billingAccountId: e,
@@ -193,11 +272,11 @@ function D(e, t = {}) {
193
272
  billingAccountId: e,
194
273
  searchQuery: n || null,
195
274
  page: 1,
196
- pageSize: E
275
+ pageSize: j
197
276
  },
198
277
  skip: !e || !s,
199
278
  fetchPolicy: "cache-and-network"
200
- }), [b, x] = d(!1), [S, w] = d(1), T = c((e) => {
279
+ }), [b, x] = d(!1), [S, C] = d(1), w = c((e) => {
201
280
  let t = (e.quotas || []).map((t) => {
202
281
  let n = t.limits?.value || 0, r = t.currentUsageSum || 0, i = Math.max(0, n - r), a = n > 0 ? r / n * 100 : 0, o = t.limits?.unit || "";
203
282
  return {
@@ -222,44 +301,44 @@ function D(e, t = {}) {
222
301
  id: e.id,
223
302
  status: e.status,
224
303
  createdAt: e.createdAt,
225
- aggregatedQuotas: C(t)
304
+ aggregatedQuotas: O(t)
226
305
  };
227
- }, []), D = u(() => f?.getSubscriptionUsageOverview ? f.getSubscriptionUsageOverview.map(T) : [], [f, T]), O = u(() => g?.getSubscriptionUsageOverview ? (g.getSubscriptionUsageOverview.subscriptions || g.getSubscriptionUsageOverview).map(T) : [], [g, T]), k = u(() => {
306
+ }, []), T = u(() => f?.getSubscriptionUsageOverview ? f.getSubscriptionUsageOverview.map(w) : [], [f, w]), E = u(() => g?.getSubscriptionUsageOverview ? (g.getSubscriptionUsageOverview.subscriptions || g.getSubscriptionUsageOverview).map(w) : [], [g, w]), D = u(() => {
228
307
  let e = g?.getSubscriptionUsageOverview;
229
308
  return e ? e.totalCount === void 0 ? {
230
- totalCount: O.length,
231
- hasMore: O.length >= E * S
309
+ totalCount: E.length,
310
+ hasMore: E.length >= j * S
232
311
  } : {
233
312
  totalCount: e.totalCount,
234
- hasMore: O.length < e.totalCount
313
+ hasMore: E.length < e.totalCount
235
314
  } : {
236
315
  totalCount: 0,
237
316
  hasMore: !1
238
317
  };
239
318
  }, [
240
319
  g,
241
- O.length,
320
+ E.length,
242
321
  S
243
322
  ]);
244
323
  l(() => {
245
- w(1);
324
+ C(1);
246
325
  }, [n, r]);
247
- let A = c(async () => {
248
- if (!y || b || !k.hasMore) return;
326
+ let k = c(async () => {
327
+ if (!y || b || !D.hasMore) return;
249
328
  x(!0);
250
329
  let e = S + 1;
251
330
  try {
252
331
  await y({
253
332
  variables: {
254
333
  page: e,
255
- pageSize: E
334
+ pageSize: j
256
335
  },
257
336
  updateQuery: (e, { fetchMoreResult: t }) => {
258
337
  if (!t) return e;
259
338
  let n = e?.getSubscriptionUsageOverview ?? [], r = t?.getSubscriptionUsageOverview ?? [];
260
339
  return { getSubscriptionUsageOverview: [...n, ...r] };
261
340
  }
262
- }), w(e);
341
+ }), C(e);
263
342
  } catch (e) {
264
343
  console.error("Failed to load more past subscriptions:", e);
265
344
  } finally {
@@ -268,10 +347,10 @@ function D(e, t = {}) {
268
347
  }, [
269
348
  y,
270
349
  b,
271
- k.hasMore,
350
+ D.hasMore,
272
351
  S
273
- ]), j = c(() => {
274
- w(1), o && h(), s && v();
352
+ ]), A = c(() => {
353
+ C(1), o && h(), s && v();
275
354
  }, [
276
355
  o,
277
356
  s,
@@ -279,19 +358,19 @@ function D(e, t = {}) {
279
358
  v
280
359
  ]);
281
360
  return {
282
- activeSubscriptions: D,
283
- pastSubscriptions: O,
361
+ activeSubscriptions: T,
362
+ pastSubscriptions: E,
284
363
  isLoading: o ? p : !1,
285
364
  isPastSubscriptionsLoading: s ? _ : !1,
286
365
  error: m ?? null,
287
- refetch: j,
288
- loadMorePastSubscriptions: A,
289
- hasMorePastSubscriptions: k.hasMore,
366
+ refetch: A,
367
+ loadMorePastSubscriptions: k,
368
+ hasMorePastSubscriptions: D.hasMore,
290
369
  isLoadingMorePast: b,
291
- pastSubscriptionsCount: k.totalCount
370
+ pastSubscriptionsCount: D.totalCount
292
371
  };
293
372
  }
294
- function O(e, t, n, r) {
373
+ function N(e, t, n, r) {
295
374
  return {
296
375
  filters: {
297
376
  quotaAssignmentId: e,
@@ -303,7 +382,7 @@ function O(e, t, n, r) {
303
382
  }
304
383
  };
305
384
  }
306
- function k(e) {
385
+ function P(e) {
307
386
  return {
308
387
  id: e.id,
309
388
  amount: e.amount,
@@ -315,15 +394,15 @@ function k(e) {
315
394
  lastUpdated: e.createdAt
316
395
  };
317
396
  }
318
- function A(e, t = 20, n = null, r) {
397
+ function F(e, t = 20, n = null, r) {
319
398
  let { data: i, loading: a, error: o, refetch: c } = s({
320
399
  client: p("workspace"),
321
- variables: O(e, t, n, r),
400
+ variables: N(e, t, n, r),
322
401
  skip: !e || !r,
323
402
  fetchPolicy: "cache-and-network"
324
403
  });
325
404
  return {
326
- usages: (i?.getUsageRecords?.items ?? []).map(k),
405
+ usages: (i?.getUsageRecords?.items ?? []).map(P),
327
406
  hasNextPage: i?.getUsageRecords?.hasNextPage ?? !1,
328
407
  nextCursor: i?.getUsageRecords?.nextCursor ?? null,
329
408
  isLoading: a,
@@ -331,19 +410,19 @@ function A(e, t = 20, n = null, r) {
331
410
  refetch: c
332
411
  };
333
412
  }
334
- function j({ billingAccountId: t, workspaceId: n, period: r = e.LAST_30_DAYS, searchQuery: i, viewMode: a = "active" }) {
335
- let o = y({
413
+ function I({ billingAccountId: t, workspaceId: n, period: r = e.LAST_30_DAYS, searchQuery: i, viewMode: a = "active" }) {
414
+ let o = w({
336
415
  billingAccountId: t,
337
416
  period: r
338
- }), s = T(t), { statusMap: l } = _(n), d = D(t, {
417
+ }), s = A(t), { statusMap: l } = S(n), d = M(t, {
339
418
  searchQuery: i,
340
419
  viewMode: a
341
- }), f = u(() => S(s.pooledQuotas, l), [s.pooledQuotas, l]), p = u(() => d.activeSubscriptions.map((e) => ({
420
+ }), f = u(() => D(s.pooledQuotas, l), [s.pooledQuotas, l]), p = u(() => d.activeSubscriptions.map((e) => ({
342
421
  ...e,
343
- aggregatedQuotas: S(e.aggregatedQuotas, l)
422
+ aggregatedQuotas: D(e.aggregatedQuotas, l)
344
423
  })), [d.activeSubscriptions, l]), m = u(() => d.pastSubscriptions.map((e) => ({
345
424
  ...e,
346
- aggregatedQuotas: S(e.aggregatedQuotas, l)
425
+ aggregatedQuotas: D(e.aggregatedQuotas, l)
347
426
  })), [d.pastSubscriptions, l]), h = c(() => {
348
427
  o.refetch(), s.refetch(), d.refetch();
349
428
  }, [
@@ -367,6 +446,6 @@ function j({ billingAccountId: t, workspaceId: n, period: r = e.LAST_30_DAYS, se
367
446
  };
368
447
  }
369
448
  //#endregion
370
- export { T as useBillingAccountQuotasSummary, w as useBillingAccountsForUsage, j as useEnhancedUsagePage, A as useQuotaUsageDetails, D as useSubscriptionsWithQuotas, y as useUsageDashboard, v as useWorkspaceQuotaHistory };
449
+ export { y as useAssignWorkspaceQuota, A as useBillingAccountQuotasSummary, k as useBillingAccountsForUsage, I as useEnhancedUsagePage, F as useQuotaUsageDetails, M as useSubscriptionsWithQuotas, w as useUsageDashboard, C as useWorkspaceQuotaHistory, v as useWorkspaceQuotaOverview };
371
450
 
372
451
  //# sourceMappingURL=useUsage.js.map