@burdenoff/microfe-billing 2026.526.1 → 2026.528.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/BillingAdminRoutes.js +1 -1
- package/dist/billing/BillingAdminRoutes.js.map +1 -1
- package/dist/billing/BillingUserRoutes.js +1 -1
- package/dist/billing/BillingUserRoutes.js.map +1 -1
- package/dist/billing/modules/addons/components/FloatingCartDrawer.js +15 -8
- package/dist/billing/modules/addons/components/FloatingCartDrawer.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +13 -6
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonCreatePage.js +1 -1
- package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonDetailPage.js +8 -4
- package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +3 -2
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +44 -23
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js +60 -31
- package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +9 -9
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js +8 -8
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +16 -10
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +24 -16
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +5 -1
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +46 -29
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/TransactionsPage.js +34 -22
- package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js +10 -10
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/CreditsAmountInput.js +3 -3
- package/dist/billing/modules/checkout/components/CreditsAmountInput.js.map +1 -1
- package/dist/billing/modules/checkout/components/NativeCheckoutView.js +7 -0
- package/dist/billing/modules/checkout/components/NativeCheckoutView.js.map +1 -1
- package/dist/billing/modules/checkout/components/OrderSummary.js +13 -13
- package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
- package/dist/billing/modules/checkout/components/PromoCodeInput.js +5 -5
- package/dist/billing/modules/checkout/components/PromoCodeInput.js.map +1 -1
- package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js +5 -5
- package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
- package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js +4 -4
- package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js.map +1 -1
- package/dist/billing/modules/checkout/components/StripePaymentForm.js +11 -11
- package/dist/billing/modules/checkout/components/StripePaymentForm.js.map +1 -1
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js +3 -3
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js +8 -8
- package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +61 -52
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js +22 -15
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponsListPage.js +30 -21
- package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js +9 -8
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/EditCouponPage.js +11 -8
- package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +22 -17
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +15 -8
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditsPage.js +17 -11
- package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js +15 -11
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js +21 -17
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js +14 -11
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js +32 -26
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js +14 -10
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +18 -11
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +44 -32
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js +35 -26
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/EarningsPage.js +15 -15
- package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +11 -11
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutsPage.js +5 -5
- package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +9 -4
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanDetailPage.js +13 -8
- package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +3 -2
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +14 -7
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansListPage.js +23 -9
- package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaPage.js +11 -7
- package/dist/billing/modules/quota/pages/QuotaPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +3 -3
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js +6 -2
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +5 -4
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +26 -12
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/SettingsPage.js +356 -349
- package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +157 -123
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js +13 -8
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +15 -7
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +30 -20
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +25 -16
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +50 -34
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/dist/billing/shared/components/AccessDenied.js +2 -2
- package/dist/billing/shared/components/AccessDenied.js.map +1 -1
- package/dist/billing/shared/components/ActorIdentity.js +67 -0
- package/dist/billing/shared/components/ActorIdentity.js.map +1 -0
- package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js +6 -2
- package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js.map +1 -1
- package/dist/billing/shared/components/EmptyState.js +7 -21
- package/dist/billing/shared/components/EmptyState.js.map +1 -1
- package/dist/billing/shared/components/PageHeader.js +7 -19
- package/dist/billing/shared/components/PageHeader.js.map +1 -1
- package/dist/billing/shared/components/ServerError.js +5 -4
- package/dist/billing/shared/components/ServerError.js.map +1 -1
- package/dist/billing/shared/components/index.js +1 -0
- package/dist/billing/shared/hooks/index.js +1 -0
- package/dist/billing/shared/hooks/useActorProfiles.js +113 -0
- package/dist/billing/shared/hooks/useActorProfiles.js.map +1 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlansListPage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlansListPage.tsx"],"sourcesContent":["/**\n * Plans Module - Plans List Page\n * Displays all available pricing plans\n */\n\nimport { useState, useMemo, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlans, usePlanMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n isServerError,\n} from '../../../shared/utils';\nimport type { Plan, PlanDuration, PlanFeature } from '../../../shared/types';\nimport { PricingModel } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\n\ntype ViewMode = 'grid' | 'list';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\n/**\n * Combines duplicate quotas by quota ID and aggregates their values\n */\nfunction combineQuotas(features: PlanFeature[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n\n for (const feature of features) {\n if (!feature.quota) continue;\n\n const quotaId = feature.quota.id;\n const limits = feature.quota.limits as { value?: number } | null;\n const limitValue = limits?.value ?? 0;\n\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\n return Array.from(quotaMap.values());\n}\n\nexport const PlansListPage: 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 permissions = useBillingPermissions();\n const { plans, isLoading, error, refetch } = usePlans();\n const { togglePlan, isToggling } = usePlanMutations();\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showInactive, setShowInactive] = useState(false);\n const [billingInterval, setBillingInterval] = useState<PlanDuration | 'all'>('all');\n\n // Permission check\n if (!permissions.canViewPlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.plans.noViewPermission',\n \"You don't have permission to view pricing plans.\"\n )}\n />\n );\n }\n\n // Filter plans\n const filteredPlans = plans.filter((plan) => {\n if (!showInactive && !plan.isActive) return false;\n if (billingInterval !== 'all' && plan.duration !== billingInterval) return false;\n return true;\n });\n\n const handleTogglePlan = async (plan: Plan) => {\n if (!permissions.canManagePlans) return;\n try {\n await togglePlan(plan.id, !plan.isActive);\n await refetch();\n } catch (err) {\n console.error('Failed to toggle plan:', err);\n }\n };\n\n const handleSelectPlan = (plan: Plan) => {\n navigateTo(`/checkout/${plan.id}`);\n };\n\n const handleViewPlan = (plan: Plan) => {\n navigateTo(`/plans/${plan.id}`);\n };\n\n // Loading state\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, 4].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 // Error state - show server error component for 500 errors\n if (error) {\n if (isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.plans.serverUnavailableMessage',\n 'Unable to load pricing plans. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n // Generic error for non-server errors\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"w-12 h-12 mx-auto rounded-full bg-destructive/10 flex items-center justify-center\">\n <svg\n className=\"w-6 h-6 text-destructive\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.plans.failedToLoad', 'Failed to load plans')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">{error.message}</p>\n <button\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.plans.title', 'Pricing Plans')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.plans.subtitle', 'Choose the plan that best fits your needs')}\n </p>\n </div>\n\n {permissions.canManagePlans && (\n <button\n onClick={() => navigateTo('/plans/new')}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 4v16m8-8H4\"\n />\n </svg>\n Create Plan\n </button>\n )}\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border\">\n {/* Billing Interval Toggle */}\n <div className=\"inline-flex rounded-lg border border-border p-1 bg-muted/50\">\n <button\n onClick={() => setBillingInterval('all')}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'all'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n All\n </button>\n <button\n onClick={() => setBillingInterval('monthly' as PlanDuration)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'monthly'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n Monthly\n </button>\n <button\n onClick={() => setBillingInterval('yearly' as PlanDuration)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'yearly'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n Yearly\n </button>\n </div>\n\n {/* Show Inactive Toggle */}\n {permissions.canManagePlans && (\n <label className=\"flex items-center gap-2 text-sm cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={showInactive}\n onChange={(e) => setShowInactive(e.target.checked)}\n className=\"w-4 h-4 rounded border-border text-primary focus:ring-primary\"\n />\n <span className=\"text-muted-foreground\">\n {tr('billing.plans.showInactive', 'Show inactive plans')}\n </span>\n </label>\n )}\n\n {/* View Mode Toggle */}\n <div className=\"ml-auto inline-flex rounded-lg border border-border p-1 bg-muted/50\">\n <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=\"w-4 h-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 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=\"w-4 h-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 Grid/List */}\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=\"w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <svg\n className=\"w-6 h-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=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">\n {tr('billing.plans.noPlansFound', 'No plans found')}\n </h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {showInactive\n ? tr('billing.plans.noPlansFiltered', 'No plans match your current filters.')\n : tr('billing.plans.noActivePlans', 'There are no active plans available.')}\n </p>\n {permissions.canManagePlans && (\n <button\n onClick={() => navigateTo('/plans/new')}\n className=\"mt-4 px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n {tr('billing.plans.createFirstPlan', 'Create your first plan')}\n </button>\n )}\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 <PlanCard\n key={plan.id}\n plan={plan}\n onSelect={handleSelectPlan}\n onView={handleViewPlan}\n onToggle={handleTogglePlan}\n canSelect={permissions.canCreateSubscription}\n canManage={permissions.canManagePlans}\n isToggling={isToggling}\n />\n ))}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredPlans.map((plan) => (\n <PlanRow\n key={plan.id}\n plan={plan}\n onSelect={handleSelectPlan}\n onView={handleViewPlan}\n onToggle={handleTogglePlan}\n canSelect={permissions.canCreateSubscription}\n canManage={permissions.canManagePlans}\n isToggling={isToggling}\n />\n ))}\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Plan Card Component\n// ============================================================================\n\ninterface PlanCardProps {\n plan: Plan;\n onSelect: (plan: Plan) => void;\n onView: (plan: Plan) => void;\n onToggle: (plan: Plan) => void;\n canSelect: boolean;\n canManage: boolean;\n isToggling: boolean;\n}\n\nconst PlanCard: FC<PlanCardProps> = ({\n plan,\n onSelect,\n onView,\n onToggle,\n canSelect,\n canManage,\n isToggling,\n}) => {\n const displayCurrency = useDefaultBillingCurrency();\n const combinedQuotas = useMemo(() => {\n const features = plan.features || [];\n return combineQuotas(features);\n }, [plan.features]);\n\n return (\n <div\n className={`relative border rounded-lg p-6 transition-all hover:shadow-md flex flex-col h-full ${\n plan.isActive\n ? 'border-border bg-card'\n : 'border-dashed border-muted-foreground/30 bg-muted/30'\n }`}\n >\n {/* Status Badge */}\n {!plan.isActive && (\n <span className=\"absolute top-4 right-4 px-2 py-1 text-xs font-medium bg-muted text-muted-foreground rounded\">\n Inactive\n </span>\n )}\n\n {/* Plan Name */}\n <div className=\"mb-4\">\n <h3 className=\"text-xl 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 {/* Price */}\n <div className=\"flex items-baseline gap-1 mb-1\">\n <span className=\"text-3xl font-bold text-foreground\">\n {(() => {\n const base =\n plan.pricingModel === PricingModel.PER_SEAT && plan.pricePerSeat\n ? { price: plan.pricePerSeat, currency: plan.currency }\n : { price: plan.price, currency: plan.currency };\n const { price, currency } = getDisplayPrice(\n base.price,\n base.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\">/ {formatPlanDuration(plan.duration)}</span>\n </div>\n {plan.pricingModel === PricingModel.PER_SEAT && (\n <p className=\"text-xs text-muted-foreground mb-4\">\n per seat\n {plan.minSeats ? ` · min ${plan.minSeats} seat${plan.minSeats !== 1 ? 's' : ''}` : ''}\n </p>\n )}\n {plan.pricingModel !== PricingModel.PER_SEAT && <div className=\"mb-6\" />}\n\n {/* Features - Combined Quotas */}\n {combinedQuotas.length > 0 && (\n <ul className=\"space-y-2 mb-6\">\n {combinedQuotas.slice(0, 5).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=\"w-5 h-5 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\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-foreground\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 5 && (\n <li className=\"text-sm text-muted-foreground pl-7\">\n +{combinedQuotas.length - 5} more quotas\n </li>\n )}\n </ul>\n )}\n\n {/* Actions - pushed to bottom with mt-auto */}\n <div className=\"space-y-2 pt-4 border-t border-border mt-auto\">\n {canSelect && plan.isActive && (\n <button\n onClick={() => onSelect(plan)}\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 Select Plan\n </button>\n )}\n <div className=\"flex gap-2\">\n <button\n onClick={() => onView(plan)}\n className=\"flex-1 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 {canManage && (\n <button\n onClick={() => onToggle(plan)}\n disabled={isToggling}\n className={`px-4 py-2 text-sm font-medium border border-border rounded-md transition-colors ${\n plan.isActive\n ? 'text-destructive hover:bg-destructive/10'\n : 'text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n >\n {plan.isActive ? 'Deactivate' : 'Activate'}\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Plan Row Component (List View)\n// ============================================================================\n\ninterface PlanRowProps {\n plan: Plan;\n onSelect: (plan: Plan) => void;\n onView: (plan: Plan) => void;\n onToggle: (plan: Plan) => void;\n canSelect: boolean;\n canManage: boolean;\n isToggling: boolean;\n}\n\nconst PlanRow: FC<PlanRowProps> = ({\n plan,\n onSelect,\n onView,\n onToggle,\n canSelect,\n canManage,\n isToggling,\n}) => {\n const displayCurrency = useDefaultBillingCurrency();\n const combinedQuotas = useMemo(() => {\n const features = plan.features || [];\n return combineQuotas(features);\n }, [plan.features]);\n\n return (\n <div\n className={`flex items-center gap-4 p-4 border rounded-lg transition-all hover:shadow-sm ${\n plan.isActive\n ? 'border-border bg-card'\n : 'border-dashed border-muted-foreground/30 bg-muted/30'\n }`}\n >\n {/* Plan Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-foreground truncate\">{plan.name}</h3>\n {!plan.isActive && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-muted text-muted-foreground rounded\">\n Inactive\n </span>\n )}\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {combinedQuotas.length} quota{combinedQuotas.length !== 1 ? 's' : ''} included\n </p>\n </div>\n\n {/* Price */}\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 {/* Actions */}\n <div className=\"flex items-center gap-2\">\n {canSelect && plan.isActive && (\n <button\n onClick={() => onSelect(plan)}\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 Select\n </button>\n )}\n <button\n onClick={() => onView(plan)}\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 {canManage && (\n <button\n onClick={() => onToggle(plan)}\n disabled={isToggling}\n className={`px-3 py-1.5 text-sm font-medium border border-border rounded-md transition-colors ${\n plan.isActive\n ? 'text-destructive hover:bg-destructive/10'\n : 'text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n >\n {plan.isActive ? 'Deactivate' : 'Activate'}\n </button>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlansListPage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlansListPage.tsx"],"sourcesContent":["/**\n * Plans Module - Plans List Page\n * Displays all available pricing plans\n */\n\nimport { useState, useMemo, type FC } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlans, usePlanMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, ServerError } from '../../../shared/components';\nimport {\n formatCurrency,\n formatPlanDuration,\n formatPlanDurationLabel,\n isServerError,\n} from '../../../shared/utils';\nimport type { Plan, PlanDuration, PlanFeature } from '../../../shared/types';\nimport { PricingModel } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n useDefaultBillingCurrency,\n getDisplayPrice,\n} from '../../../hooks/useDefaultBillingCurrency';\n\ntype ViewMode = 'grid' | 'list';\n\n/**\n * Combined quota info for display\n */\ninterface CombinedQuota {\n quotaId: string;\n name: string;\n totalValue: number;\n}\n\n/**\n * Combines duplicate quotas by quota ID and aggregates their values\n */\nfunction combineQuotas(features: PlanFeature[]): CombinedQuota[] {\n const quotaMap = new Map<string, CombinedQuota>();\n\n for (const feature of features) {\n if (!feature.quota) continue;\n\n const quotaId = feature.quota.id;\n const limits = feature.quota.limits as { value?: number } | null;\n const limitValue = limits?.value ?? 0;\n\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\n return Array.from(quotaMap.values());\n}\n\nexport const PlansListPage: 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 permissions = useBillingPermissions();\n const { plans, isLoading, error, refetch } = usePlans();\n const { togglePlan, isToggling } = usePlanMutations();\n\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showInactive, setShowInactive] = useState(false);\n const [billingInterval, setBillingInterval] = useState<PlanDuration | 'all'>('all');\n\n // Permission check\n if (!permissions.canViewPlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.plans.noViewPermission',\n \"You don't have permission to view pricing plans.\"\n )}\n />\n );\n }\n\n // Filter plans\n const filteredPlans = plans.filter((plan) => {\n if (!showInactive && !plan.isActive) return false;\n if (billingInterval !== 'all' && plan.duration !== billingInterval) return false;\n return true;\n });\n\n const handleTogglePlan = async (plan: Plan) => {\n if (!permissions.canManagePlans) return;\n try {\n await togglePlan(plan.id, !plan.isActive);\n await refetch();\n } catch (err) {\n console.error('Failed to toggle plan:', err);\n }\n };\n\n const handleSelectPlan = (plan: Plan) => {\n navigateTo(`/checkout/${plan.id}`);\n };\n\n const handleViewPlan = (plan: Plan) => {\n navigateTo(`/plans/${plan.id}`);\n };\n\n // Loading state\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, 4].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 // Error state - show server error component for 500 errors\n if (error) {\n if (isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.plans.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.plans.serverUnavailableMessage',\n 'Unable to load pricing plans. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n // Generic error for non-server errors\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"size-12 mx-auto rounded-full bg-destructive/10 flex items-center justify-center\">\n <svg\n className=\"size-6 text-destructive\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.plans.failedToLoad', 'Failed to load plans')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.plans.title', 'Pricing Plans')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {tr('billing.plans.subtitle', 'Choose the plan that best fits your needs')}\n </p>\n </div>\n\n {permissions.canManagePlans && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans/new')}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\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=\"M12 4v16m8-8H4\"\n />\n </svg>\n Create Plan\n </button>\n )}\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border\">\n {/* Billing Interval Toggle */}\n <div className=\"inline-flex rounded-lg border border-border p-1 bg-muted/50\">\n <button\n type=\"button\"\n onClick={() => setBillingInterval('all')}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'all'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n All\n </button>\n <button\n type=\"button\"\n onClick={() => setBillingInterval('monthly' as PlanDuration)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'monthly'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n Monthly\n </button>\n <button\n type=\"button\"\n onClick={() => setBillingInterval('yearly' as PlanDuration)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n billingInterval === 'yearly'\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n Yearly\n </button>\n </div>\n\n {/* Show Inactive Toggle */}\n {permissions.canManagePlans && (\n <label className=\"flex items-center gap-2 text-sm cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={showInactive}\n onChange={(e) => setShowInactive(e.target.checked)}\n className=\"size-4 rounded border-border text-primary focus:ring-primary\"\n />\n <span className=\"text-muted-foreground\">\n {tr('billing.plans.showInactive', 'Show inactive plans')}\n </span>\n </label>\n )}\n\n {/* View Mode Toggle */}\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 Grid/List */}\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=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">\n {tr('billing.plans.noPlansFound', 'No plans found')}\n </h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {showInactive\n ? tr('billing.plans.noPlansFiltered', 'No plans match your current filters.')\n : tr('billing.plans.noActivePlans', 'There are no active plans available.')}\n </p>\n {permissions.canManagePlans && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans/new')}\n className=\"mt-4 px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n {tr('billing.plans.createFirstPlan', 'Create your first plan')}\n </button>\n )}\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 <PlanCard\n key={plan.id}\n plan={plan}\n onSelect={handleSelectPlan}\n onView={handleViewPlan}\n onToggle={handleTogglePlan}\n canSelect={permissions.canCreateSubscription}\n canManage={permissions.canManagePlans}\n isToggling={isToggling}\n />\n ))}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {filteredPlans.map((plan) => (\n <PlanRow\n key={plan.id}\n plan={plan}\n onSelect={handleSelectPlan}\n onView={handleViewPlan}\n onToggle={handleTogglePlan}\n canSelect={permissions.canCreateSubscription}\n canManage={permissions.canManagePlans}\n isToggling={isToggling}\n />\n ))}\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Plan Card Component\n// ============================================================================\n\ninterface PlanCardProps {\n plan: Plan;\n onSelect: (plan: Plan) => void;\n onView: (plan: Plan) => void;\n onToggle: (plan: Plan) => void;\n canSelect: boolean;\n canManage: boolean;\n isToggling: boolean;\n}\n\nconst PlanCard: FC<PlanCardProps> = ({\n plan,\n onSelect,\n onView,\n onToggle,\n canSelect,\n canManage,\n isToggling,\n}) => {\n const displayCurrency = useDefaultBillingCurrency();\n const combinedQuotas = useMemo(() => {\n const features = plan.features || [];\n return combineQuotas(features);\n }, [plan.features]);\n\n return (\n <div\n className={`relative border rounded-lg p-6 transition-all hover:shadow-md flex flex-col h-full ${\n plan.isActive\n ? 'border-border bg-card'\n : 'border-dashed border-muted-foreground/30 bg-muted/30'\n }`}\n >\n {/* Status Badge */}\n {!plan.isActive && (\n <span className=\"absolute top-4 right-4 px-2 py-1 text-xs font-medium bg-muted text-muted-foreground rounded\">\n Inactive\n </span>\n )}\n\n {/* Plan Name */}\n <div className=\"mb-4\">\n <h3 className=\"text-xl 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 {/* Price */}\n <div className=\"flex items-baseline gap-1 mb-1\">\n <span className=\"text-3xl font-bold text-foreground\">\n {(() => {\n const base =\n plan.pricingModel === PricingModel.PER_SEAT && plan.pricePerSeat\n ? { price: plan.pricePerSeat, currency: plan.currency }\n : { price: plan.price, currency: plan.currency };\n const { price, currency } = getDisplayPrice(\n base.price,\n base.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\">/ {formatPlanDuration(plan.duration)}</span>\n </div>\n {plan.pricingModel === PricingModel.PER_SEAT && (\n <p className=\"text-xs text-muted-foreground mb-4\">\n per seat\n {plan.minSeats ? ` · min ${plan.minSeats} seat${plan.minSeats !== 1 ? 's' : ''}` : ''}\n </p>\n )}\n {plan.pricingModel !== PricingModel.PER_SEAT && <div className=\"mb-6\" />}\n\n {/* Features - Combined Quotas */}\n {combinedQuotas.length > 0 && (\n <ul className=\"space-y-2 mb-6\">\n {combinedQuotas.slice(0, 5).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-5 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\">{quota.name}</span>\n </div>\n <span className=\"font-medium text-foreground\">\n {quota.totalValue.toLocaleString()}\n </span>\n </li>\n ))}\n {combinedQuotas.length > 5 && (\n <li className=\"text-sm text-muted-foreground pl-7\">\n +{combinedQuotas.length - 5} more quotas\n </li>\n )}\n </ul>\n )}\n\n {/* Actions - pushed to bottom with mt-auto */}\n <div className=\"space-y-2 pt-4 border-t border-border mt-auto\">\n {canSelect && plan.isActive && (\n <button\n type=\"button\"\n onClick={() => onSelect(plan)}\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 Select Plan\n </button>\n )}\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => onView(plan)}\n className=\"flex-1 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 {canManage && (\n <button\n type=\"button\"\n onClick={() => onToggle(plan)}\n disabled={isToggling}\n className={`px-4 py-2 text-sm font-medium border border-border rounded-md transition-colors ${\n plan.isActive\n ? 'text-destructive hover:bg-destructive/10'\n : 'text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n >\n {plan.isActive ? 'Deactivate' : 'Activate'}\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Plan Row Component (List View)\n// ============================================================================\n\ninterface PlanRowProps {\n plan: Plan;\n onSelect: (plan: Plan) => void;\n onView: (plan: Plan) => void;\n onToggle: (plan: Plan) => void;\n canSelect: boolean;\n canManage: boolean;\n isToggling: boolean;\n}\n\nconst PlanRow: FC<PlanRowProps> = ({\n plan,\n onSelect,\n onView,\n onToggle,\n canSelect,\n canManage,\n isToggling,\n}) => {\n const displayCurrency = useDefaultBillingCurrency();\n const combinedQuotas = useMemo(() => {\n const features = plan.features || [];\n return combineQuotas(features);\n }, [plan.features]);\n\n return (\n <div\n className={`flex items-center gap-4 p-4 border rounded-lg transition-all hover:shadow-sm ${\n plan.isActive\n ? 'border-border bg-card'\n : 'border-dashed border-muted-foreground/30 bg-muted/30'\n }`}\n >\n {/* Plan Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-foreground truncate\">{plan.name}</h3>\n {!plan.isActive && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-muted text-muted-foreground rounded\">\n Inactive\n </span>\n )}\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {combinedQuotas.length} quota{combinedQuotas.length !== 1 ? 's' : ''} included\n </p>\n </div>\n\n {/* Price */}\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 {/* Actions */}\n <div className=\"flex items-center gap-2\">\n {canSelect && plan.isActive && (\n <button\n type=\"button\"\n onClick={() => onSelect(plan)}\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 Select\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => onView(plan)}\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 {canManage && (\n <button\n type=\"button\"\n onClick={() => onToggle(plan)}\n disabled={isToggling}\n className={`px-3 py-1.5 text-sm font-medium border border-border rounded-md transition-colors ${\n plan.isActive\n ? 'text-destructive hover:bg-destructive/10'\n : 'text-status-success-text hover:bg-status-success-bg-subtle'\n }`}\n >\n {plan.isActive ? 'Deactivate' : 'Activate'}\n </button>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -25,9 +25,9 @@ var g = () => {
|
|
|
25
25
|
className: "text-center space-y-2",
|
|
26
26
|
children: [
|
|
27
27
|
/* @__PURE__ */ p("div", {
|
|
28
|
-
className: "
|
|
28
|
+
className: "size-12 mx-auto rounded-full bg-muted flex items-center justify-center",
|
|
29
29
|
children: /* @__PURE__ */ p("svg", {
|
|
30
|
-
className: "
|
|
30
|
+
className: "size-6 text-muted-foreground",
|
|
31
31
|
fill: "none",
|
|
32
32
|
viewBox: "0 0 24 24",
|
|
33
33
|
stroke: "currentColor",
|
|
@@ -71,9 +71,9 @@ var g = () => {
|
|
|
71
71
|
className: "text-center space-y-4",
|
|
72
72
|
children: [
|
|
73
73
|
/* @__PURE__ */ p("div", {
|
|
74
|
-
className: "
|
|
74
|
+
className: "size-12 mx-auto rounded-full bg-destructive/10 flex items-center justify-center",
|
|
75
75
|
children: /* @__PURE__ */ p("svg", {
|
|
76
|
-
className: "
|
|
76
|
+
className: "size-6 text-destructive",
|
|
77
77
|
fill: "none",
|
|
78
78
|
viewBox: "0 0 24 24",
|
|
79
79
|
stroke: "currentColor",
|
|
@@ -94,6 +94,7 @@ var g = () => {
|
|
|
94
94
|
children: E.message
|
|
95
95
|
}),
|
|
96
96
|
/* @__PURE__ */ p("button", {
|
|
97
|
+
type: "button",
|
|
97
98
|
onClick: () => D(),
|
|
98
99
|
className: "px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
|
|
99
100
|
children: "Try Again"
|
|
@@ -171,6 +172,7 @@ var g = () => {
|
|
|
171
172
|
"CRITICAL",
|
|
172
173
|
"EXHAUSTED"
|
|
173
174
|
].map((e) => /* @__PURE__ */ p("button", {
|
|
175
|
+
type: "button",
|
|
174
176
|
onClick: () => s(e),
|
|
175
177
|
className: `px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${o === e ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
|
|
176
178
|
children: e === "all" ? "All" : r(e)
|
|
@@ -178,7 +180,7 @@ var g = () => {
|
|
|
178
180
|
}), /* @__PURE__ */ m("div", {
|
|
179
181
|
className: "relative flex-1 min-w-0 w-full sm:max-w-md",
|
|
180
182
|
children: [/* @__PURE__ */ p("svg", {
|
|
181
|
-
className: "absolute left-3 top-1/2 -translate-y-1/2
|
|
183
|
+
className: "absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground",
|
|
182
184
|
fill: "none",
|
|
183
185
|
viewBox: "0 0 24 24",
|
|
184
186
|
stroke: "currentColor",
|
|
@@ -201,9 +203,9 @@ var g = () => {
|
|
|
201
203
|
className: "flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg",
|
|
202
204
|
children: [
|
|
203
205
|
/* @__PURE__ */ p("div", {
|
|
204
|
-
className: "
|
|
206
|
+
className: "size-12 rounded-full bg-muted flex items-center justify-center mb-4",
|
|
205
207
|
children: /* @__PURE__ */ p("svg", {
|
|
206
|
-
className: "
|
|
208
|
+
className: "size-6 text-muted-foreground",
|
|
207
209
|
fill: "none",
|
|
208
210
|
viewBox: "0 0 24 24",
|
|
209
211
|
stroke: "currentColor",
|
|
@@ -230,6 +232,7 @@ var g = () => {
|
|
|
230
232
|
className: "flex items-center justify-center gap-2 pt-4",
|
|
231
233
|
children: [
|
|
232
234
|
/* @__PURE__ */ p("button", {
|
|
235
|
+
type: "button",
|
|
233
236
|
onClick: () => b((e) => Math.max(1, e - 1)),
|
|
234
237
|
disabled: y === 1,
|
|
235
238
|
className: "px-3 py-1.5 text-sm font-medium border border-border rounded-md disabled:opacity-50",
|
|
@@ -245,6 +248,7 @@ var g = () => {
|
|
|
245
248
|
]
|
|
246
249
|
}),
|
|
247
250
|
/* @__PURE__ */ p("button", {
|
|
251
|
+
type: "button",
|
|
248
252
|
onClick: () => b((e) => Math.min(w.totalPages, e + 1)),
|
|
249
253
|
disabled: y === w.totalPages,
|
|
250
254
|
className: "px-3 py-1.5 text-sm font-medium border border-border rounded-md disabled:opacity-50",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuotaPage.js","names":[],"sources":["../../../../../src/billing/modules/quota/pages/QuotaPage.tsx"],"sourcesContent":["/**\n * Quota Module - Quota Page\n * Displays quota assignments and usage summary\n */\n\nimport { useState, type FC } from 'react';\nimport {\n useBillingAccountQuotasSummary,\n useQuotaAssignmentsWithAnalytics,\n} from '../hooks/useQuota';\nimport { useDefaultBillingAccount } from '../../subscriptions/hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n formatPercentage,\n formatNumber,\n formatDate,\n formatQuotaStatus,\n} from '../../../shared/utils/format';\nimport {\n getQuotaStatusColor,\n getStatusBadgeClasses,\n getQuotaProgressColor,\n getQuotaProgressBgColor,\n} from '../../../shared/utils/status';\nimport type { QuotaAssignmentWithAnalytics, QuotaStatus } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ntype FilterStatus = 'all' | QuotaStatus;\n\nexport const QuotaPage: 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 permissions = useBillingPermissions();\n const { billingAccount, isLoading: isLoadingAccount } = useDefaultBillingAccount();\n\n const [filterStatus, setFilterStatus] = useState<FilterStatus>('all');\n const [searchQuery, setSearchQuery] = useState('');\n const [page, setPage] = useState(1);\n\n const { summary, isLoading: isLoadingSummary } = useBillingAccountQuotasSummary(\n billingAccount?.id\n );\n\n const {\n quotaAssignments,\n pagination,\n isLoading: isLoadingQuotas,\n error,\n refetch,\n } = useQuotaAssignmentsWithAnalytics({\n billingAccountId: billingAccount?.id || '',\n page,\n pageSize: 10,\n searchQuery: searchQuery || undefined,\n status: filterStatus === 'all' ? undefined : [filterStatus],\n });\n\n // Permission check\n if (!permissions.canViewUsage) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <div className=\"w-12 h-12 mx-auto rounded-full bg-muted flex items-center justify-center\">\n <svg\n className=\"w-6 h-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=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">Access Denied</h2>\n <p className=\"text-sm text-muted-foreground max-w-sm\">\n You don't have permission to view quotas.\n </p>\n </div>\n </div>\n );\n }\n\n const isLoading = isLoadingAccount || isLoadingSummary || isLoadingQuotas;\n\n // Loading state\n if (isLoading && !billingAccount) {\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 sm:grid-cols-4 gap-4\">\n {[1, 2, 3, 4].map((i) => (\n <div key={i} className=\"border border-border rounded-lg p-4\">\n <div className=\"h-4 w-20 bg-muted animate-pulse rounded mb-2\" />\n <div className=\"h-8 w-16 bg-muted animate-pulse rounded\" />\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"w-12 h-12 mx-auto rounded-full bg-destructive/10 flex items-center justify-center\">\n <svg\n className=\"w-6 h-6 text-destructive\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.quota.failedToLoad', 'Failed to load quotas')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">{error.message}</p>\n <button\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n const allQuotas = [...(summary?.pooledQuotas || []), ...(summary?.subscriptionQuotas || [])];\n const healthyCount = allQuotas.filter((q) => q.usagePercentage < 75).length;\n const warningCount = allQuotas.filter(\n (q) => q.usagePercentage >= 75 && q.usagePercentage < 90\n ).length;\n const criticalCount = allQuotas.filter(\n (q) => q.usagePercentage >= 90 && q.usagePercentage < 100\n ).length;\n const exhaustedCount = allQuotas.filter((q) => q.usagePercentage >= 100).length;\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.quota.title', 'Quotas')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n Monitor your quota allocations and usage\n </p>\n </div>\n </div>\n\n {/* Summary Cards */}\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <div className=\"border border-border rounded-lg bg-status-success-bg-subtle p-4\">\n <p className=\"text-sm text-status-success-text\">\n {tr('billing.quota.healthy', 'Healthy')}\n </p>\n <p className=\"text-2xl font-bold text-status-success-text mt-1\">{healthyCount}</p>\n </div>\n <div className=\"border border-border rounded-lg bg-status-warning-bg-subtle p-4\">\n <p className=\"text-sm text-status-warning-text\">Warning</p>\n <p className=\"text-2xl font-bold text-status-warning-text mt-1\">{warningCount}</p>\n </div>\n <div className=\"border border-border rounded-lg bg-status-error-bg-subtle p-4\">\n <p className=\"text-sm text-status-error-text\">Critical</p>\n <p className=\"text-2xl font-bold text-status-error-text mt-1\">{criticalCount}</p>\n </div>\n <div className=\"border border-border rounded-lg bg-bg-canvas p-4\">\n <p className=\"text-sm text-text-secondary\">Exhausted</p>\n <p className=\"text-2xl font-bold text-text-secondary mt-1\">{exhaustedCount}</p>\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border\">\n {/* Status Filter */}\n <div className=\"inline-flex rounded-lg border border-border p-1 bg-muted/50 flex-wrap\">\n {(['all', 'HEALTHY', 'WARNING', 'CRITICAL', 'EXHAUSTED'] as FilterStatus[]).map(\n (status) => (\n <button\n key={status}\n onClick={() => setFilterStatus(status)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n filterStatus === status\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n {status === 'all' ? 'All' : formatQuotaStatus(status as QuotaStatus)}\n </button>\n )\n )}\n </div>\n\n {/* Search */}\n <div className=\"relative flex-1 min-w-0 w-full sm:max-w-md\">\n <svg\n className=\"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 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=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"\n />\n </svg>\n <input\n type=\"text\"\n placeholder=\"Search quotas...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 text-sm border border-border rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\"\n />\n </div>\n </div>\n\n {/* Quotas List */}\n {quotaAssignments.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 border border-dashed border-border rounded-lg\">\n <div className=\"w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-4\">\n <svg\n className=\"w-6 h-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=\"M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">No quotas found</h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {filterStatus !== 'all' || searchQuery\n ? 'No quotas match your filters.'\n : 'No quota assignments available.'}\n </p>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {quotaAssignments.map((quota) => (\n <QuotaCard key={quota.id} quota={quota} />\n ))}\n\n {/* Pagination */}\n {pagination.totalPages > 1 && (\n <div className=\"flex items-center justify-center gap-2 pt-4\">\n <button\n onClick={() => setPage((p) => Math.max(1, p - 1))}\n disabled={page === 1}\n className=\"px-3 py-1.5 text-sm font-medium border border-border rounded-md disabled:opacity-50\"\n >\n Previous\n </button>\n <span className=\"text-sm text-muted-foreground\">\n Page {page} of {pagination.totalPages}\n </span>\n <button\n onClick={() => setPage((p) => Math.min(pagination.totalPages, p + 1))}\n disabled={page === pagination.totalPages}\n className=\"px-3 py-1.5 text-sm font-medium border border-border rounded-md disabled:opacity-50\"\n >\n Next\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Quota Card Component\n// ============================================================================\n\ninterface QuotaCardProps {\n quota: QuotaAssignmentWithAnalytics;\n}\n\nconst QuotaCard: FC<QuotaCardProps> = ({ quota }) => {\n const statusColor = getQuotaStatusColor(quota.status);\n const progressColor = getQuotaProgressColor(quota.usagePercentage);\n const progressBgColor = getQuotaProgressBgColor(quota.usagePercentage);\n const limits = quota.limits as { value?: number } | null;\n const limit = limits?.value || 0;\n\n return (\n <div className=\"border border-border rounded-lg bg-card p-6 hover:shadow-sm transition-shadow\">\n <div className=\"flex flex-col sm:flex-row sm:items-start gap-4\">\n {/* Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-foreground\">{quota.name}</h3>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(statusColor)}`}\n >\n {formatQuotaStatus(quota.status)}\n </span>\n <span className=\"px-2 py-0.5 text-xs font-medium bg-muted text-muted-foreground rounded\">\n {quota.quotaType}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">\n Expires: {formatDate(quota.endtime, 'short')}\n </p>\n </div>\n\n {/* Usage Stats */}\n <div className=\"text-right\">\n <p className=\"text-2xl font-bold text-foreground\">\n {formatNumber(quota.currentUsageSum)} / {limit > 0 ? formatNumber(limit) : '∞'}\n </p>\n <p className=\"text-sm text-muted-foreground\">\n {formatPercentage(quota.usagePercentage)} used\n </p>\n </div>\n </div>\n\n {/* Progress Bar */}\n <div className=\"mt-4\">\n <div className={`h-2 rounded-full overflow-hidden ${progressBgColor}`}>\n <div\n className={`h-full transition-all duration-500 ${progressColor}`}\n style={{ width: `${Math.min(100, quota.usagePercentage)}%` }}\n />\n </div>\n <div className=\"flex items-center justify-between mt-2 text-xs text-muted-foreground\">\n <span>{formatNumber(quota.limitLeft)} remaining</span>\n <span>{quota.totalUsageCount} usage records</span>\n </div>\n </div>\n\n {/* Analytics Preview */}\n {quota.analytics?.trends && (\n <div className=\"mt-4 pt-4 border-t border-border flex flex-wrap gap-4 text-sm\">\n {quota.analytics.trends.averageDailyUsage !== undefined && (\n <div>\n <span className=\"text-muted-foreground\">Avg. Daily: </span>\n <span className=\"font-medium text-foreground\">\n {formatNumber(quota.analytics.trends.averageDailyUsage, 1)}\n </span>\n </div>\n )}\n {quota.analytics.trends.growthRate !== undefined && (\n <div>\n <span className=\"text-muted-foreground\">Growth: </span>\n <span\n className={`font-medium ${quota.analytics.trends.growthRate > 0 ? 'text-status-error-text' : 'text-status-success-text'}`}\n >\n {quota.analytics.trends.growthRate > 0 ? '+' : ''}\n {formatPercentage(quota.analytics.trends.growthRate)}\n </span>\n </div>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"QuotaPage.js","names":[],"sources":["../../../../../src/billing/modules/quota/pages/QuotaPage.tsx"],"sourcesContent":["/**\n * Quota Module - Quota Page\n * Displays quota assignments and usage summary\n */\n\nimport { useState, type FC } from 'react';\nimport {\n useBillingAccountQuotasSummary,\n useQuotaAssignmentsWithAnalytics,\n} from '../hooks/useQuota';\nimport { useDefaultBillingAccount } from '../../subscriptions/hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n formatPercentage,\n formatNumber,\n formatDate,\n formatQuotaStatus,\n} from '../../../shared/utils/format';\nimport {\n getQuotaStatusColor,\n getStatusBadgeClasses,\n getQuotaProgressColor,\n getQuotaProgressBgColor,\n} from '../../../shared/utils/status';\nimport type { QuotaAssignmentWithAnalytics, QuotaStatus } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ntype FilterStatus = 'all' | QuotaStatus;\n\nexport const QuotaPage: 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 permissions = useBillingPermissions();\n const { billingAccount, isLoading: isLoadingAccount } = useDefaultBillingAccount();\n\n const [filterStatus, setFilterStatus] = useState<FilterStatus>('all');\n const [searchQuery, setSearchQuery] = useState('');\n const [page, setPage] = useState(1);\n\n const { summary, isLoading: isLoadingSummary } = useBillingAccountQuotasSummary(\n billingAccount?.id\n );\n\n const {\n quotaAssignments,\n pagination,\n isLoading: isLoadingQuotas,\n error,\n refetch,\n } = useQuotaAssignmentsWithAnalytics({\n billingAccountId: billingAccount?.id || '',\n page,\n pageSize: 10,\n searchQuery: searchQuery || undefined,\n status: filterStatus === 'all' ? undefined : [filterStatus],\n });\n\n // Permission check\n if (!permissions.canViewUsage) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <div className=\"size-12 mx-auto rounded-full bg-muted flex items-center justify-center\">\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=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">Access Denied</h2>\n <p className=\"text-sm text-muted-foreground max-w-sm\">\n You don't have permission to view quotas.\n </p>\n </div>\n </div>\n );\n }\n\n const isLoading = isLoadingAccount || isLoadingSummary || isLoadingQuotas;\n\n // Loading state\n if (isLoading && !billingAccount) {\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 sm:grid-cols-4 gap-4\">\n {[1, 2, 3, 4].map((i) => (\n <div key={i} className=\"border border-border rounded-lg p-4\">\n <div className=\"h-4 w-20 bg-muted animate-pulse rounded mb-2\" />\n <div className=\"h-8 w-16 bg-muted animate-pulse rounded\" />\n </div>\n ))}\n </div>\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <div className=\"size-12 mx-auto rounded-full bg-destructive/10 flex items-center justify-center\">\n <svg\n className=\"size-6 text-destructive\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-foreground\">\n {tr('billing.quota.failedToLoad', 'Failed to load quotas')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">{error.message}</p>\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors\"\n >\n Try Again\n </button>\n </div>\n </div>\n );\n }\n\n const allQuotas = [...(summary?.pooledQuotas || []), ...(summary?.subscriptionQuotas || [])];\n const healthyCount = allQuotas.filter((q) => q.usagePercentage < 75).length;\n const warningCount = allQuotas.filter(\n (q) => q.usagePercentage >= 75 && q.usagePercentage < 90\n ).length;\n const criticalCount = allQuotas.filter(\n (q) => q.usagePercentage >= 90 && q.usagePercentage < 100\n ).length;\n const exhaustedCount = allQuotas.filter((q) => q.usagePercentage >= 100).length;\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">\n {tr('billing.quota.title', 'Quotas')}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n Monitor your quota allocations and usage\n </p>\n </div>\n </div>\n\n {/* Summary Cards */}\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <div className=\"border border-border rounded-lg bg-status-success-bg-subtle p-4\">\n <p className=\"text-sm text-status-success-text\">\n {tr('billing.quota.healthy', 'Healthy')}\n </p>\n <p className=\"text-2xl font-bold text-status-success-text mt-1\">{healthyCount}</p>\n </div>\n <div className=\"border border-border rounded-lg bg-status-warning-bg-subtle p-4\">\n <p className=\"text-sm text-status-warning-text\">Warning</p>\n <p className=\"text-2xl font-bold text-status-warning-text mt-1\">{warningCount}</p>\n </div>\n <div className=\"border border-border rounded-lg bg-status-error-bg-subtle p-4\">\n <p className=\"text-sm text-status-error-text\">Critical</p>\n <p className=\"text-2xl font-bold text-status-error-text mt-1\">{criticalCount}</p>\n </div>\n <div className=\"border border-border rounded-lg bg-bg-canvas p-4\">\n <p className=\"text-sm text-text-secondary\">Exhausted</p>\n <p className=\"text-2xl font-bold text-text-secondary mt-1\">{exhaustedCount}</p>\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-wrap items-center gap-4 pb-4 border-b border-border\">\n {/* Status Filter */}\n <div className=\"inline-flex rounded-lg border border-border p-1 bg-muted/50 flex-wrap\">\n {(['all', 'HEALTHY', 'WARNING', 'CRITICAL', 'EXHAUSTED'] as FilterStatus[]).map(\n (status) => (\n <button\n type=\"button\"\n key={status}\n onClick={() => setFilterStatus(status)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n filterStatus === status\n ? 'bg-background text-foreground shadow-sm'\n : 'text-muted-foreground hover:text-foreground'\n }`}\n >\n {status === 'all' ? 'All' : formatQuotaStatus(status as QuotaStatus)}\n </button>\n )\n )}\n </div>\n\n {/* Search */}\n <div className=\"relative flex-1 min-w-0 w-full sm:max-w-md\">\n <svg\n className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 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=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"\n />\n </svg>\n <input\n type=\"text\"\n placeholder=\"Search quotas...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 text-sm border border-border rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\"\n />\n </div>\n </div>\n\n {/* Quotas List */}\n {quotaAssignments.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=\"M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-foreground\">No quotas found</h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {filterStatus !== 'all' || searchQuery\n ? 'No quotas match your filters.'\n : 'No quota assignments available.'}\n </p>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {quotaAssignments.map((quota) => (\n <QuotaCard key={quota.id} quota={quota} />\n ))}\n\n {/* Pagination */}\n {pagination.totalPages > 1 && (\n <div className=\"flex items-center justify-center gap-2 pt-4\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(1, p - 1))}\n disabled={page === 1}\n className=\"px-3 py-1.5 text-sm font-medium border border-border rounded-md disabled:opacity-50\"\n >\n Previous\n </button>\n <span className=\"text-sm text-muted-foreground\">\n Page {page} of {pagination.totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.min(pagination.totalPages, p + 1))}\n disabled={page === pagination.totalPages}\n className=\"px-3 py-1.5 text-sm font-medium border border-border rounded-md disabled:opacity-50\"\n >\n Next\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Quota Card Component\n// ============================================================================\n\ninterface QuotaCardProps {\n quota: QuotaAssignmentWithAnalytics;\n}\n\nconst QuotaCard: FC<QuotaCardProps> = ({ quota }) => {\n const statusColor = getQuotaStatusColor(quota.status);\n const progressColor = getQuotaProgressColor(quota.usagePercentage);\n const progressBgColor = getQuotaProgressBgColor(quota.usagePercentage);\n const limits = quota.limits as { value?: number } | null;\n const limit = limits?.value || 0;\n\n return (\n <div className=\"border border-border rounded-lg bg-card p-6 hover:shadow-sm transition-shadow\">\n <div className=\"flex flex-col sm:flex-row sm:items-start gap-4\">\n {/* Info */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"font-semibold text-foreground\">{quota.name}</h3>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(statusColor)}`}\n >\n {formatQuotaStatus(quota.status)}\n </span>\n <span className=\"px-2 py-0.5 text-xs font-medium bg-muted text-muted-foreground rounded\">\n {quota.quotaType}\n </span>\n </div>\n <p className=\"text-sm text-muted-foreground mt-1\">\n Expires: {formatDate(quota.endtime, 'short')}\n </p>\n </div>\n\n {/* Usage Stats */}\n <div className=\"text-right\">\n <p className=\"text-2xl font-bold text-foreground\">\n {formatNumber(quota.currentUsageSum)} / {limit > 0 ? formatNumber(limit) : '∞'}\n </p>\n <p className=\"text-sm text-muted-foreground\">\n {formatPercentage(quota.usagePercentage)} used\n </p>\n </div>\n </div>\n\n {/* Progress Bar */}\n <div className=\"mt-4\">\n <div className={`h-2 rounded-full overflow-hidden ${progressBgColor}`}>\n <div\n className={`h-full transition-all duration-500 ${progressColor}`}\n style={{ width: `${Math.min(100, quota.usagePercentage)}%` }}\n />\n </div>\n <div className=\"flex items-center justify-between mt-2 text-xs text-muted-foreground\">\n <span>{formatNumber(quota.limitLeft)} remaining</span>\n <span>{quota.totalUsageCount} usage records</span>\n </div>\n </div>\n\n {/* Analytics Preview */}\n {quota.analytics?.trends && (\n <div className=\"mt-4 pt-4 border-t border-border flex flex-wrap gap-4 text-sm\">\n {quota.analytics.trends.averageDailyUsage !== undefined && (\n <div>\n <span className=\"text-muted-foreground\">Avg. Daily: </span>\n <span className=\"font-medium text-foreground\">\n {formatNumber(quota.analytics.trends.averageDailyUsage, 1)}\n </span>\n </div>\n )}\n {quota.analytics.trends.growthRate !== undefined && (\n <div>\n <span className=\"text-muted-foreground\">Growth: </span>\n <span\n className={`font-medium ${quota.analytics.trends.growthRate > 0 ? 'text-status-error-text' : 'text-status-success-text'}`}\n >\n {quota.analytics.trends.growthRate > 0 ? '+' : ''}\n {formatPercentage(quota.analytics.trends.growthRate)}\n </span>\n </div>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;AA6BA,IAAa,UAAsB;CACjC,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEnC,IAAc,GAAuB,EACrC,EAAE,mBAAgB,WAAW,MAAqB,GAA0B,EAE5E,CAAC,GAAc,KAAmB,EAAuB,MAAM,EAC/D,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAM,KAAW,EAAS,EAAE,EAE7B,EAAE,YAAS,WAAW,MAAqB,EAC/C,GAAgB,GACjB,EAEK,EACJ,qBACA,eACA,WAAW,GACX,UACA,eACE,EAAiC;EACnC,kBAAkB,GAAgB,MAAM;EACxC;EACA,UAAU;EACV,aAAa,KAAe,KAAA;EAC5B,QAAQ,MAAiB,QAAQ,KAAA,IAAY,CAAC,EAAa;EAC5D,CAAC;AAGF,KAAI,CAAC,EAAY,aACf,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;gBAEP,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;OACF,CAAA;MACE,CAAA;KACF,CAAA;IACN,kBAAC,MAAD;KAAI,WAAU;eAAwC;KAAkB,CAAA;IACxE,kBAAC,KAAD;KAAG,WAAU;eAAyC;KAElD,CAAA;IACA;;EACF,CAAA;AAOV,MAHkB,KAAoB,KAAoB,MAGzC,CAAC,EAChB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD,EAAK,WAAU,2CAA4C,CAAA,EAC3D,kBAAC,OAAD;GAAK,WAAU;aACZ;IAAC;IAAG;IAAG;IAAG;IAAE,CAAC,KAAK,MACjB,kBAAC,OAAD;IAAa,WAAU;cAAvB,CACE,kBAAC,OAAD,EAAK,WAAU,gDAAiD,CAAA,EAChE,kBAAC,OAAD,EAAK,WAAU,2CAA4C,CAAA,CACvD;MAHI,EAGJ,CACN;GACE,CAAA,CACF;;AAKV,KAAI,EACF,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;gBAEP,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;OACF,CAAA;MACE,CAAA;KACF,CAAA;IACN,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,8BAA8B,wBAAwB;KACvD,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eAAiC,EAAM;KAAY,CAAA;IAChE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,GAAS;KACxB,WAAU;eACX;KAEQ,CAAA;IACL;;EACF,CAAA;CAIV,IAAM,IAAY,CAAC,GAAI,GAAS,gBAAgB,EAAE,EAAG,GAAI,GAAS,sBAAsB,EAAE,CAAE,EACtF,IAAe,EAAU,QAAQ,MAAM,EAAE,kBAAkB,GAAG,CAAC,QAC/D,IAAe,EAAU,QAC5B,MAAM,EAAE,mBAAmB,MAAM,EAAE,kBAAkB,GACvD,CAAC,QACI,IAAgB,EAAU,QAC7B,MAAM,EAAE,mBAAmB,MAAM,EAAE,kBAAkB,IACvD,CAAC,QACI,IAAiB,EAAU,QAAQ,MAAM,EAAE,mBAAmB,IAAI,CAAC;AAEzE,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,uBAAuB,SAAS;KACjC,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eAAqC;KAE9C,CAAA,CACA,EAAA,CAAA;IACF,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAG,yBAAyB,UAAU;OACrC,CAAA,EACJ,kBAAC,KAAD;OAAG,WAAU;iBAAoD;OAAiB,CAAA,CAC9E;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAmC;OAAW,CAAA,EAC3D,kBAAC,KAAD;OAAG,WAAU;iBAAoD;OAAiB,CAAA,CAC9E;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAiC;OAAY,CAAA,EAC1D,kBAAC,KAAD;OAAG,WAAU;iBAAkD;OAAkB,CAAA,CAC7E;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAa,CAAA,EACxD,kBAAC,KAAD;OAAG,WAAU;iBAA+C;OAAmB,CAAA,CAC3E;;KACF;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,OAAD;KAAK,WAAU;eACX;MAAC;MAAO;MAAW;MAAW;MAAY;MAAY,CAAoB,KACzE,MACC,kBAAC,UAAD;MACE,MAAK;MAEL,eAAe,EAAgB,EAAO;MACtC,WAAW,gEACT,MAAiB,IACb,4CACA;gBAGL,MAAW,QAAQ,QAAQ,EAAkB,EAAsB;MAC7D,EATF,EASE,CAEZ;KACG,CAAA,EAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;gBAEP,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;OACF,CAAA;MACE,CAAA,EACN,kBAAC,SAAD;MACE,MAAK;MACL,aAAY;MACZ,OAAO;MACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;MAC/C,WAAU;MACV,CAAA,CACE;OACF;;GAGL,EAAiB,WAAW,IAC3B,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,OAAD;OACE,WAAU;OACV,MAAK;OACL,SAAQ;OACR,QAAO;iBAEP,kBAAC,QAAD;QACE,eAAc;QACd,gBAAe;QACf,aAAa;QACb,GAAE;QACF,CAAA;OACE,CAAA;MACF,CAAA;KACN,kBAAC,MAAD;MAAI,WAAU;gBAAsC;MAAoB,CAAA;KACxE,kBAAC,KAAD;MAAG,WAAU;gBACV,MAAiB,SAAS,IACvB,kCACA;MACF,CAAA;KACA;QAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAiB,KAAK,MACrB,kBAAC,GAAD,EAAiC,UAAS,EAA1B,EAAM,GAAoB,CAC1C,EAGD,EAAW,aAAa,KACvB,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAS,MAAM,KAAK,IAAI,GAAG,IAAI,EAAE,CAAC;OACjD,UAAU,MAAS;OACnB,WAAU;iBACX;OAEQ,CAAA;MACT,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QAAgD;QACxC;QAAK;QAAK,EAAW;QACtB;;MACP,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAS,MAAM,KAAK,IAAI,EAAW,YAAY,IAAI,EAAE,CAAC;OACrE,UAAU,MAAS,EAAW;OAC9B,WAAU;iBACX;OAEQ,CAAA;MACL;OAEJ;;GAEJ;;GAYJ,KAAiC,EAAE,eAAY;CACnD,IAAM,IAAc,EAAoB,EAAM,OAAO,EAC/C,IAAgB,EAAsB,EAAM,gBAAgB,EAC5D,IAAkB,EAAwB,EAAM,gBAAgB,EAEhE,IADS,EAAM,QACC,SAAS;AAE/B,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,MAAD;QAAI,WAAU;kBAAiC,EAAM;QAAU,CAAA;OAC/D,kBAAC,QAAD;QACE,WAAW,2CAA2C,EAAsB,EAAY;kBAEvF,EAAkB,EAAM,OAAO;QAC3B,CAAA;OACP,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAM;QACF,CAAA;OACH;SACN,kBAAC,KAAD;MAAG,WAAU;gBAAb,CAAkD,aACtC,EAAW,EAAM,SAAS,QAAQ,CAC1C;QACA;QAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAb;OACG,EAAa,EAAM,gBAAgB;OAAC;OAAI,IAAQ,IAAI,EAAa,EAAM,GAAG;OACzE;SACJ,kBAAC,KAAD;MAAG,WAAU;gBAAb,CACG,EAAiB,EAAM,gBAAgB,EAAC,QACvC;QACA;OACF;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAW,oCAAoC;eAClD,kBAAC,OAAD;MACE,WAAW,sCAAsC;MACjD,OAAO,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,EAAM,gBAAgB,CAAC,IAAI;MAC5D,CAAA;KACE,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAa,EAAM,UAAU,EAAC,aAAiB,EAAA,CAAA,EACtD,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAM,iBAAgB,iBAAqB,EAAA,CAAA,CAC9C;OACF;;GAGL,EAAM,WAAW,UAChB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAM,UAAU,OAAO,sBAAsB,KAAA,KAC5C,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,QAAD;KAAM,WAAU;eAAwB;KAAmB,CAAA,EAC3D,kBAAC,QAAD;KAAM,WAAU;eACb,EAAa,EAAM,UAAU,OAAO,mBAAmB,EAAE;KACrD,CAAA,CACH,EAAA,CAAA,EAEP,EAAM,UAAU,OAAO,eAAe,KAAA,KACrC,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,QAAD;KAAM,WAAU;eAAwB;KAAe,CAAA,EACvD,kBAAC,QAAD;KACE,WAAW,eAAe,EAAM,UAAU,OAAO,aAAa,IAAI,2BAA2B;eAD/F,CAGG,EAAM,UAAU,OAAO,aAAa,IAAI,MAAM,IAC9C,EAAiB,EAAM,UAAU,OAAO,WAAW,CAC/C;OACH,EAAA,CAAA,CAEJ;;GAEJ"}
|
|
@@ -68,7 +68,7 @@ var d = () => {
|
|
|
68
68
|
className: "p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg",
|
|
69
69
|
children: /* @__PURE__ */ c("p", {
|
|
70
70
|
className: "text-sm text-status-success-text",
|
|
71
|
-
children: "Quota created successfully! Redirecting
|
|
71
|
+
children: "Quota created successfully! Redirecting…"
|
|
72
72
|
})
|
|
73
73
|
}),
|
|
74
74
|
/* @__PURE__ */ l("div", {
|
|
@@ -100,7 +100,7 @@ var d = () => {
|
|
|
100
100
|
/* @__PURE__ */ l("p", {
|
|
101
101
|
className: "text-xs text-muted-foreground mt-1",
|
|
102
102
|
children: [
|
|
103
|
-
"Internal machine key
|
|
103
|
+
"Internal machine key, must match the ",
|
|
104
104
|
/* @__PURE__ */ c("code", { children: "quotaName" }),
|
|
105
105
|
" used in",
|
|
106
106
|
" ",
|
|
@@ -179,7 +179,7 @@ var d = () => {
|
|
|
179
179
|
type: "checkbox",
|
|
180
180
|
checked: j,
|
|
181
181
|
onChange: (e) => M(e.target.checked),
|
|
182
|
-
className: "
|
|
182
|
+
className: "size-4 rounded border-border text-primary focus:ring-primary"
|
|
183
183
|
}), /* @__PURE__ */ l("div", { children: [/* @__PURE__ */ c("label", {
|
|
184
184
|
htmlFor: "reusable",
|
|
185
185
|
className: "text-sm font-medium text-foreground",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuotaTemplateCreatePage.js","names":[],"sources":["../../../../../src/billing/modules/quota/pages/QuotaTemplateCreatePage.tsx"],"sourcesContent":["/**\n * Quota Module - Quota Template Create Page\n * Form to create a new quota template\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useQuotaTemplateMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { QuotaType, QuotaResetPeriod, type CreateQuotaInput } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport const QuotaTemplateCreatePage: 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 permissions = useBillingPermissions();\n const { createQuota, isCreating } = useQuotaTemplateMutations();\n\n // Form state\n const [name, setName] = useState('');\n const [displayName, setDisplayName] = useState('');\n const [description, setDescription] = useState('');\n const [limitType, setLimitType] = useState('numeric');\n const [limitValue, setLimitValue] = useState('');\n const [unit, setUnit] = useState('');\n const [quotaType, setQuotaType] = useState<QuotaType>(QuotaType.PRODUCT_SPECIFIC);\n const [reusable, setReusable] = useState(false);\n const [resetPeriod, setResetPeriod] = useState<QuotaResetPeriod>(QuotaResetPeriod.NEVER);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n\n // Permission check\n if (!permissions.canManageQuotas) {\n return <AccessDenied message=\"You don't have permission to create quotas.\" />;\n }\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccess(false);\n\n // Validation\n if (!name.trim()) {\n setError('Quota name is required');\n return;\n }\n if (!limitValue || isNaN(Number(limitValue)) || Number(limitValue) < 0) {\n setError('Valid limit value is required');\n return;\n }\n if (!unit.trim()) {\n setError('Unit is required (e.g., bots, GB, seats)');\n return;\n }\n\n const input: CreateQuotaInput = {\n name: name.trim(),\n limits: {\n type: limitType,\n value: Number(limitValue),\n unit: unit.trim(),\n },\n quotaType,\n reusable,\n resetPeriod,\n context: {\n displayName: displayName.trim() || name.trim(),\n description: description.trim(),\n },\n };\n\n try {\n await createQuota(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo('/quotas'), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create quota');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader title=\"Create New Quota\" description=\"Set up a new quota template\" />\n\n <form onSubmit={handleSubmit} className=\"max-w-2xl space-y-6\">\n {/* Error/Success Messages */}\n {error && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n {success && (\n <div className=\"p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-success-text\">\n Quota created successfully! Redirecting...\n </p>\n </div>\n )}\n\n {/* Basic Info */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">Basic Information</h2>\n </div>\n <div className=\"p-4 space-y-4\">\n {/* Machine Key (name) */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-foreground mb-1\">\n Quota Key *\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary font-mono text-sm\"\n placeholder=\"e.g., sessions-started, tunnels-created\"\n required\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n Internal machine key — must match the <code>quotaName</code> used in{' '}\n <code>@usage</code> directives. Never shown to users.\n </p>\n </div>\n\n {/* Display Name */}\n <div>\n <label\n htmlFor=\"displayName\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n Display Name *\n </label>\n <input\n id=\"displayName\"\n type=\"text\"\n value={displayName}\n onChange={(e) => setDisplayName(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder=\"e.g., Tunnels, Session Starts, Webhooks\"\n required\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n Human-readable name shown to users in billing and usage UI.\n </p>\n </div>\n\n {/* Description */}\n <div>\n <label\n htmlFor=\"description\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n Description\n </label>\n <textarea\n id=\"description\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary resize-none\"\n placeholder=\"e.g., Active tunnels created through VibeControls (slot freed on delete)\"\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n Short explanation shown in billing and usage detail views.\n </p>\n </div>\n\n {/* Quota Type */}\n <div>\n <label htmlFor=\"quotaType\" className=\"block text-sm font-medium text-foreground mb-1\">\n Quota Type *\n </label>\n <select\n id=\"quotaType\"\n value={quotaType}\n onChange={(e) => setQuotaType(e.target.value as QuotaType)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value={QuotaType.PRODUCT_SPECIFIC}>Product Specific</option>\n <option value={QuotaType.POOLED}>Pooled</option>\n </select>\n <p className=\"text-xs text-muted-foreground mt-1\">\n {quotaType === QuotaType.POOLED\n ? 'Shared across all products'\n : 'Specific to individual products'}\n </p>\n </div>\n\n {/* Reusable */}\n <div className=\"flex items-center gap-3\">\n <input\n id=\"reusable\"\n type=\"checkbox\"\n checked={reusable}\n onChange={(e) => setReusable(e.target.checked)}\n className=\"w-4 h-4 rounded border-border text-primary focus:ring-primary\"\n />\n <div>\n <label htmlFor=\"reusable\" className=\"text-sm font-medium text-foreground\">\n Reusable Quota\n </label>\n <p className=\"text-xs text-muted-foreground\">\n Enable for quotas like seats or bots that can be freed up\n </p>\n </div>\n </div>\n\n {/* Reset Period */}\n <div>\n <label\n htmlFor=\"resetPeriod\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n Reset Period\n </label>\n <select\n id=\"resetPeriod\"\n value={resetPeriod}\n onChange={(e) => setResetPeriod(e.target.value as QuotaResetPeriod)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value={QuotaResetPeriod.NEVER}>Never (one-time limit)</option>\n <option value={QuotaResetPeriod.DAILY}>Daily (resets every day)</option>\n <option value={QuotaResetPeriod.MONTHLY}>Monthly (resets every month)</option>\n </select>\n <p className=\"text-xs text-muted-foreground mt-1\">\n How often the quota usage counter is reset to zero\n </p>\n </div>\n </div>\n </div>\n\n {/* Limits Configuration */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">Limits Configuration</h2>\n </div>\n <div className=\"p-4 space-y-4\">\n {/* Limit Type */}\n <div>\n <label htmlFor=\"limitType\" className=\"block text-sm font-medium text-foreground mb-1\">\n Limit Type *\n </label>\n <select\n id=\"limitType\"\n value={limitType}\n onChange={(e) => setLimitType(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"numeric\">Numeric (count)</option>\n <option value=\"storage\">Storage (bytes)</option>\n <option value=\"duration\">Duration (seconds)</option>\n <option value=\"percentage\">Percentage</option>\n </select>\n </div>\n\n {/* Limit Value */}\n <div>\n <label\n htmlFor=\"limitValue\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n Limit Value *\n </label>\n <input\n id=\"limitValue\"\n type=\"number\"\n min=\"0\"\n value={limitValue}\n onChange={(e) => setLimitValue(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder=\"1000\"\n required\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n The maximum limit value for this quota\n </p>\n </div>\n\n {/* Unit */}\n <div>\n <label htmlFor=\"unit\" className=\"block text-sm font-medium text-foreground mb-1\">\n Unit *\n </label>\n <input\n id=\"unit\"\n type=\"text\"\n value={unit}\n onChange={(e) => setUnit(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder=\"e.g., bots, GB, MB, seats, workflow runs\"\n required\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n The unit of measurement displayed with usage values (e.g., "2/6 bots")\n </p>\n </div>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-4\">\n <button\n type=\"submit\"\n disabled={isCreating}\n className=\"px-6 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors disabled:opacity-50\"\n >\n {isCreating\n ? tr('billing.quota.creating', 'Creating...')\n : tr('billing.quota.createQuota', 'Create Quota')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/quotas')}\n className=\"px-6 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n Cancel\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;AAaA,IAAa,UAAoC;CAC/C,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEnC,IAAa,GAAoB,EACjC,IAAc,GAAuB,EACrC,EAAE,gBAAa,kBAAe,GAA2B,EAGzD,CAAC,GAAM,KAAW,EAAS,GAAG,EAC9B,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAW,KAAgB,EAAS,UAAU,EAC/C,CAAC,GAAY,KAAiB,EAAS,GAAG,EAC1C,CAAC,GAAM,KAAW,EAAS,GAAG,EAC9B,CAAC,GAAW,KAAgB,EAAoB,EAAU,iBAAiB,EAC3E,CAAC,GAAU,KAAe,EAAS,GAAM,EACzC,CAAC,GAAa,KAAkB,EAA2B,EAAiB,MAAM,EAClF,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAS,KAAc,EAAS,GAAM;AAoD7C,QAjDK,EAAY,kBAkDf,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD;GAAY,OAAM;GAAmB,aAAY;GAAgC,CAAA,EAEjF,kBAAC,QAAD;GAAM,UAjDW,OAAO,MAAiB;AAM3C,QALA,EAAE,gBAAgB,EAClB,EAAS,KAAK,EACd,EAAW,GAAM,EAGb,CAAC,EAAK,MAAM,EAAE;AAChB,OAAS,yBAAyB;AAClC;;AAEF,QAAI,CAAC,KAAc,MAAM,OAAO,EAAW,CAAC,IAAI,OAAO,EAAW,GAAG,GAAG;AACtE,OAAS,gCAAgC;AACzC;;AAEF,QAAI,CAAC,EAAK,MAAM,EAAE;AAChB,OAAS,2CAA2C;AACpD;;IAGF,IAAM,IAA0B;KAC9B,MAAM,EAAK,MAAM;KACjB,QAAQ;MACN,MAAM;MACN,OAAO,OAAO,EAAW;MACzB,MAAM,EAAK,MAAM;MAClB;KACD;KACA;KACA;KACA,SAAS;MACP,aAAa,EAAY,MAAM,IAAI,EAAK,MAAM;MAC9C,aAAa,EAAY,MAAM;MAChC;KACF;AAED,QAAI;AAIF,KAHA,MAAM,EAAY,EAAM,EACxB,EAAW,GAAK,EAEhB,iBAAiB,EAAW,UAAU,EAAE,KAAK;aACtC,GAAK;AACZ,OAAS,aAAe,QAAQ,EAAI,UAAU,yBAAyB;;;GAQzC,WAAU;aAAxC;IAEG,KACC,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,KAAD;MAAG,WAAU;gBAAkC;MAAU,CAAA;KACrD,CAAA;IAEP,KACC,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,KAAD;MAAG,WAAU;gBAAmC;MAE5C,CAAA;KACA,CAAA;IAIR,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,MAAD;OAAI,WAAU;iBAAgC;OAAsB,CAAA;MAChE,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OAEE,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SAAO,SAAQ;SAAO,WAAU;mBAAiD;SAEzE,CAAA;QACR,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAQ,EAAE,OAAO,MAAM;SACxC,WAAU;SACV,aAAY;SACZ,UAAA;SACA,CAAA;QACF,kBAAC,KAAD;SAAG,WAAU;mBAAb;UAAkD;UACV,kBAAC,QAAD,EAAA,UAAM,aAAgB,CAAA;;UAAS;UACrE,kBAAC,QAAD,EAAA,UAAM,UAAa,CAAA;;UACjB;;QACA,EAAA,CAAA;OAGN,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SACE,SAAQ;SACR,WAAU;mBACX;SAEO,CAAA;QACR,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;SAC/C,WAAU;SACV,aAAY;SACZ,UAAA;SACA,CAAA;QACF,kBAAC,KAAD;SAAG,WAAU;mBAAqC;SAE9C,CAAA;QACA,EAAA,CAAA;OAGN,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SACE,SAAQ;SACR,WAAU;mBACX;SAEO,CAAA;QACR,kBAAC,YAAD;SACE,IAAG;SACH,OAAO;SACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;SAC/C,MAAM;SACN,WAAU;SACV,aAAY;SACZ,CAAA;QACF,kBAAC,KAAD;SAAG,WAAU;mBAAqC;SAE9C,CAAA;QACA,EAAA,CAAA;OAGN,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SAAO,SAAQ;SAAY,WAAU;mBAAiD;SAE9E,CAAA;QACR,kBAAC,UAAD;SACE,IAAG;SACH,OAAO;SACP,WAAW,MAAM,EAAa,EAAE,OAAO,MAAmB;SAC1D,WAAU;mBAJZ,CAME,kBAAC,UAAD;UAAQ,OAAO,EAAU;oBAAkB;UAAyB,CAAA,EACpE,kBAAC,UAAD;UAAQ,OAAO,EAAU;oBAAQ;UAAe,CAAA,CACzC;;QACT,kBAAC,KAAD;SAAG,WAAU;mBACV,MAAc,EAAU,SACrB,+BACA;SACF,CAAA;QACA,EAAA,CAAA;OAGN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,SAAS;SACT,WAAW,MAAM,EAAY,EAAE,OAAO,QAAQ;SAC9C,WAAU;SACV,CAAA,EACF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;SAAO,SAAQ;SAAW,WAAU;mBAAsC;SAElE,CAAA,EACR,kBAAC,KAAD;SAAG,WAAU;mBAAgC;SAEzC,CAAA,CACA,EAAA,CAAA,CACF;;OAGN,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SACE,SAAQ;SACR,WAAU;mBACX;SAEO,CAAA;QACR,kBAAC,UAAD;SACE,IAAG;SACH,OAAO;SACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAA0B;SACnE,WAAU;mBAJZ;UAME,kBAAC,UAAD;WAAQ,OAAO,EAAiB;qBAAO;WAA+B,CAAA;UACtE,kBAAC,UAAD;WAAQ,OAAO,EAAiB;qBAAO;WAAiC,CAAA;UACxE,kBAAC,UAAD;WAAQ,OAAO,EAAiB;qBAAS;WAAqC,CAAA;UACvE;;QACT,kBAAC,KAAD;SAAG,WAAU;mBAAqC;SAE9C,CAAA;QACA,EAAA,CAAA;OACF;QACF;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,MAAD;OAAI,WAAU;iBAAgC;OAAyB,CAAA;MACnE,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OAEE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,SAAQ;QAAY,WAAU;kBAAiD;QAE9E,CAAA,EACR,kBAAC,UAAD;QACE,IAAG;QACH,OAAO;QACP,WAAW,MAAM,EAAa,EAAE,OAAO,MAAM;QAC7C,WAAU;kBAJZ;SAME,kBAAC,UAAD;UAAQ,OAAM;oBAAU;UAAwB,CAAA;SAChD,kBAAC,UAAD;UAAQ,OAAM;oBAAU;UAAwB,CAAA;SAChD,kBAAC,UAAD;UAAQ,OAAM;oBAAW;UAA2B,CAAA;SACpD,kBAAC,UAAD;UAAQ,OAAM;oBAAa;UAAmB,CAAA;SACvC;UACL,EAAA,CAAA;OAGN,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SACE,SAAQ;SACR,WAAU;mBACX;SAEO,CAAA;QACR,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,KAAI;SACJ,OAAO;SACP,WAAW,MAAM,EAAc,EAAE,OAAO,MAAM;SAC9C,WAAU;SACV,aAAY;SACZ,UAAA;SACA,CAAA;QACF,kBAAC,KAAD;SAAG,WAAU;mBAAqC;SAE9C,CAAA;QACA,EAAA,CAAA;OAGN,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,SAAD;SAAO,SAAQ;SAAO,WAAU;mBAAiD;SAEzE,CAAA;QACR,kBAAC,SAAD;SACE,IAAG;SACH,MAAK;SACL,OAAO;SACP,WAAW,MAAM,EAAQ,EAAE,OAAO,MAAM;SACxC,WAAU;SACV,aAAY;SACZ,UAAA;SACA,CAAA;QACF,kBAAC,KAAD;SAAG,WAAU;mBAAqC;SAE9C,CAAA;QACA,EAAA,CAAA;OACF;QACF;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,UAAU;MACV,WAAU;gBAET,IACG,EAAG,0BAA0B,cAAc,GAC3C,EAAG,6BAA6B,eAAe;MAC5C,CAAA,EACT,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAW,UAAU;MACpC,WAAU;gBACX;MAEQ,CAAA,CACL;;IACD;KACH;MAjSC,kBAAC,GAAD,EAAc,SAAQ,+CAAgD,CAAA"}
|
|
1
|
+
{"version":3,"file":"QuotaTemplateCreatePage.js","names":[],"sources":["../../../../../src/billing/modules/quota/pages/QuotaTemplateCreatePage.tsx"],"sourcesContent":["/**\n * Quota Module - Quota Template Create Page\n * Form to create a new quota template\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useQuotaTemplateMutations } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { QuotaType, QuotaResetPeriod, type CreateQuotaInput } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport const QuotaTemplateCreatePage: 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 permissions = useBillingPermissions();\n const { createQuota, isCreating } = useQuotaTemplateMutations();\n\n // Form state\n const [name, setName] = useState('');\n const [displayName, setDisplayName] = useState('');\n const [description, setDescription] = useState('');\n const [limitType, setLimitType] = useState('numeric');\n const [limitValue, setLimitValue] = useState('');\n const [unit, setUnit] = useState('');\n const [quotaType, setQuotaType] = useState<QuotaType>(QuotaType.PRODUCT_SPECIFIC);\n const [reusable, setReusable] = useState(false);\n const [resetPeriod, setResetPeriod] = useState<QuotaResetPeriod>(QuotaResetPeriod.NEVER);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n\n // Permission check\n if (!permissions.canManageQuotas) {\n return <AccessDenied message=\"You don't have permission to create quotas.\" />;\n }\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccess(false);\n\n // Validation\n if (!name.trim()) {\n setError('Quota name is required');\n return;\n }\n if (!limitValue || isNaN(Number(limitValue)) || Number(limitValue) < 0) {\n setError('Valid limit value is required');\n return;\n }\n if (!unit.trim()) {\n setError('Unit is required (e.g., bots, GB, seats)');\n return;\n }\n\n const input: CreateQuotaInput = {\n name: name.trim(),\n limits: {\n type: limitType,\n value: Number(limitValue),\n unit: unit.trim(),\n },\n quotaType,\n reusable,\n resetPeriod,\n context: {\n displayName: displayName.trim() || name.trim(),\n description: description.trim(),\n },\n };\n\n try {\n await createQuota(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo('/quotas'), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create quota');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader title=\"Create New Quota\" description=\"Set up a new quota template\" />\n\n <form onSubmit={handleSubmit} className=\"max-w-2xl space-y-6\">\n {/* Error/Success Messages */}\n {error && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n {success && (\n <div className=\"p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-success-text\">\n Quota created successfully! Redirecting…\n </p>\n </div>\n )}\n\n {/* Basic Info */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">Basic Information</h2>\n </div>\n <div className=\"p-4 space-y-4\">\n {/* Machine Key (name) */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-foreground mb-1\">\n Quota Key *\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary font-mono text-sm\"\n placeholder=\"e.g., sessions-started, tunnels-created\"\n required\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n Internal machine key, must match the <code>quotaName</code> used in{' '}\n <code>@usage</code> directives. Never shown to users.\n </p>\n </div>\n\n {/* Display Name */}\n <div>\n <label\n htmlFor=\"displayName\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n Display Name *\n </label>\n <input\n id=\"displayName\"\n type=\"text\"\n value={displayName}\n onChange={(e) => setDisplayName(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder=\"e.g., Tunnels, Session Starts, Webhooks\"\n required\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n Human-readable name shown to users in billing and usage UI.\n </p>\n </div>\n\n {/* Description */}\n <div>\n <label\n htmlFor=\"description\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n Description\n </label>\n <textarea\n id=\"description\"\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n rows={2}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary resize-none\"\n placeholder=\"e.g., Active tunnels created through VibeControls (slot freed on delete)\"\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n Short explanation shown in billing and usage detail views.\n </p>\n </div>\n\n {/* Quota Type */}\n <div>\n <label htmlFor=\"quotaType\" className=\"block text-sm font-medium text-foreground mb-1\">\n Quota Type *\n </label>\n <select\n id=\"quotaType\"\n value={quotaType}\n onChange={(e) => setQuotaType(e.target.value as QuotaType)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value={QuotaType.PRODUCT_SPECIFIC}>Product Specific</option>\n <option value={QuotaType.POOLED}>Pooled</option>\n </select>\n <p className=\"text-xs text-muted-foreground mt-1\">\n {quotaType === QuotaType.POOLED\n ? 'Shared across all products'\n : 'Specific to individual products'}\n </p>\n </div>\n\n {/* Reusable */}\n <div className=\"flex items-center gap-3\">\n <input\n id=\"reusable\"\n type=\"checkbox\"\n checked={reusable}\n onChange={(e) => setReusable(e.target.checked)}\n className=\"size-4 rounded border-border text-primary focus:ring-primary\"\n />\n <div>\n <label htmlFor=\"reusable\" className=\"text-sm font-medium text-foreground\">\n Reusable Quota\n </label>\n <p className=\"text-xs text-muted-foreground\">\n Enable for quotas like seats or bots that can be freed up\n </p>\n </div>\n </div>\n\n {/* Reset Period */}\n <div>\n <label\n htmlFor=\"resetPeriod\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n Reset Period\n </label>\n <select\n id=\"resetPeriod\"\n value={resetPeriod}\n onChange={(e) => setResetPeriod(e.target.value as QuotaResetPeriod)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value={QuotaResetPeriod.NEVER}>Never (one-time limit)</option>\n <option value={QuotaResetPeriod.DAILY}>Daily (resets every day)</option>\n <option value={QuotaResetPeriod.MONTHLY}>Monthly (resets every month)</option>\n </select>\n <p className=\"text-xs text-muted-foreground mt-1\">\n How often the quota usage counter is reset to zero\n </p>\n </div>\n </div>\n </div>\n\n {/* Limits Configuration */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <div className=\"p-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">Limits Configuration</h2>\n </div>\n <div className=\"p-4 space-y-4\">\n {/* Limit Type */}\n <div>\n <label htmlFor=\"limitType\" className=\"block text-sm font-medium text-foreground mb-1\">\n Limit Type *\n </label>\n <select\n id=\"limitType\"\n value={limitType}\n onChange={(e) => setLimitType(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"numeric\">Numeric (count)</option>\n <option value=\"storage\">Storage (bytes)</option>\n <option value=\"duration\">Duration (seconds)</option>\n <option value=\"percentage\">Percentage</option>\n </select>\n </div>\n\n {/* Limit Value */}\n <div>\n <label\n htmlFor=\"limitValue\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n Limit Value *\n </label>\n <input\n id=\"limitValue\"\n type=\"number\"\n min=\"0\"\n value={limitValue}\n onChange={(e) => setLimitValue(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder=\"1000\"\n required\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n The maximum limit value for this quota\n </p>\n </div>\n\n {/* Unit */}\n <div>\n <label htmlFor=\"unit\" className=\"block text-sm font-medium text-foreground mb-1\">\n Unit *\n </label>\n <input\n id=\"unit\"\n type=\"text\"\n value={unit}\n onChange={(e) => setUnit(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder=\"e.g., bots, GB, MB, seats, workflow runs\"\n required\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n The unit of measurement displayed with usage values (e.g., "2/6 bots")\n </p>\n </div>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-4\">\n <button\n type=\"submit\"\n disabled={isCreating}\n className=\"px-6 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors disabled:opacity-50\"\n >\n {isCreating\n ? tr('billing.quota.creating', 'Creating...')\n : tr('billing.quota.createQuota', 'Create Quota')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/quotas')}\n className=\"px-6 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n Cancel\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -48,9 +48,9 @@ var p = () => {
|
|
|
48
48
|
className: "text-center space-y-4",
|
|
49
49
|
children: [
|
|
50
50
|
/* @__PURE__ */ u("div", {
|
|
51
|
-
className: "
|
|
51
|
+
className: "size-12 mx-auto rounded-full bg-muted flex items-center justify-center",
|
|
52
52
|
children: /* @__PURE__ */ u("svg", {
|
|
53
|
-
className: "
|
|
53
|
+
className: "size-6 text-muted-foreground",
|
|
54
54
|
fill: "none",
|
|
55
55
|
viewBox: "0 0 24 24",
|
|
56
56
|
stroke: "currentColor",
|
|
@@ -71,6 +71,7 @@ var p = () => {
|
|
|
71
71
|
children: "The quota you're looking for doesn't exist or has been removed."
|
|
72
72
|
}),
|
|
73
73
|
/* @__PURE__ */ u("button", {
|
|
74
|
+
type: "button",
|
|
74
75
|
onClick: () => g("/quotas"),
|
|
75
76
|
className: "px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
|
|
76
77
|
children: "Back to Quotas"
|
|
@@ -245,10 +246,12 @@ var p = () => {
|
|
|
245
246
|
}), /* @__PURE__ */ d("div", {
|
|
246
247
|
className: "p-4 space-y-2",
|
|
247
248
|
children: [/* @__PURE__ */ u("button", {
|
|
249
|
+
type: "button",
|
|
248
250
|
onClick: () => g(`/quotas/${v.id}/edit`),
|
|
249
251
|
className: "w-full px-4 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors",
|
|
250
252
|
children: "Edit Quota"
|
|
251
253
|
}), /* @__PURE__ */ u("button", {
|
|
254
|
+
type: "button",
|
|
252
255
|
onClick: w,
|
|
253
256
|
disabled: C,
|
|
254
257
|
className: `w-full px-4 py-2 text-sm font-medium border border-border rounded-md transition-colors disabled:opacity-50 ${v.isActive ? "text-destructive hover:bg-destructive/10" : "text-status-success-text hover:bg-status-success-bg-subtle"}`,
|
|
@@ -257,6 +260,7 @@ var p = () => {
|
|
|
257
260
|
})]
|
|
258
261
|
}),
|
|
259
262
|
/* @__PURE__ */ u("button", {
|
|
263
|
+
type: "button",
|
|
260
264
|
onClick: () => g("/quotas"),
|
|
261
265
|
className: "w-full px-4 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors",
|
|
262
266
|
children: "Back to Quotas"
|