@burdenoff/microfe-billing 2026.515.6 → 2026.516.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/OrderSummary.js +4 -4
- package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.d.ts.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +2 -2
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +2 -2
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js +2 -2
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaPage.js +1 -1
- package/dist/billing/modules/quota/pages/QuotaPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.d.ts.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +101 -44
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.d.ts.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +105 -46
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +3 -3
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +3 -3
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/dist/billing/shared/types/graphql.d.ts +2 -0
- package/dist/billing/shared/types/graphql.d.ts.map +1 -1
- package/dist/billing/shared/types/graphql.js.map +1 -1
- package/package.json +1 -1
|
@@ -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-[200px] 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=\"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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuotaTemplateCreatePage.d.ts","sourceRoot":"","sources":["../../../../../src/billing/modules/quota/pages/QuotaTemplateCreatePage.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAY,KAAK,EAAE,EAAkB,MAAM,OAAO,CAAC;AAQ1D,eAAO,MAAM,uBAAuB,EAAE,
|
|
1
|
+
{"version":3,"file":"QuotaTemplateCreatePage.d.ts","sourceRoot":"","sources":["../../../../../src/billing/modules/quota/pages/QuotaTemplateCreatePage.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAY,KAAK,EAAE,EAAkB,MAAM,OAAO,CAAC;AAQ1D,eAAO,MAAM,uBAAuB,EAAE,EAsTrC,CAAC"}
|
|
@@ -14,7 +14,7 @@ var d = () => {
|
|
|
14
14
|
let { t: d } = u(), f = (e, t) => {
|
|
15
15
|
let n = d(e);
|
|
16
16
|
return n === e ? t : n;
|
|
17
|
-
}, p = a(), m = i(), { createQuota: h, isCreating: g } = o(), [_, v] = s(""), [y, b] = s("
|
|
17
|
+
}, p = a(), m = i(), { createQuota: h, isCreating: g } = o(), [_, v] = s(""), [y, b] = s(""), [x, S] = s(""), [C, w] = s("numeric"), [T, E] = s(""), [D, O] = s(""), [k, A] = s(r.PRODUCT_SPECIFIC), [j, M] = s(!1), [N, P] = s(n.NEVER), [F, I] = s(null), [L, R] = s(!1);
|
|
18
18
|
return m.canManageQuotas ? /* @__PURE__ */ l("div", {
|
|
19
19
|
className: "space-y-6 p-6",
|
|
20
20
|
children: [/* @__PURE__ */ c(t, {
|
|
@@ -22,45 +22,49 @@ var d = () => {
|
|
|
22
22
|
description: "Set up a new quota template"
|
|
23
23
|
}), /* @__PURE__ */ l("form", {
|
|
24
24
|
onSubmit: async (e) => {
|
|
25
|
-
if (e.preventDefault(),
|
|
26
|
-
|
|
25
|
+
if (e.preventDefault(), I(null), R(!1), !_.trim()) {
|
|
26
|
+
I("Quota name is required");
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
|
-
if (!
|
|
30
|
-
|
|
29
|
+
if (!T || isNaN(Number(T)) || Number(T) < 0) {
|
|
30
|
+
I("Valid limit value is required");
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
-
if (!
|
|
34
|
-
|
|
33
|
+
if (!D.trim()) {
|
|
34
|
+
I("Unit is required (e.g., bots, GB, seats)");
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
37
|
let t = {
|
|
38
38
|
name: _.trim(),
|
|
39
39
|
limits: {
|
|
40
|
-
type:
|
|
41
|
-
value: Number(
|
|
42
|
-
unit:
|
|
40
|
+
type: C,
|
|
41
|
+
value: Number(T),
|
|
42
|
+
unit: D.trim()
|
|
43
43
|
},
|
|
44
|
-
quotaType:
|
|
45
|
-
reusable:
|
|
46
|
-
resetPeriod:
|
|
44
|
+
quotaType: k,
|
|
45
|
+
reusable: j,
|
|
46
|
+
resetPeriod: N,
|
|
47
|
+
context: {
|
|
48
|
+
displayName: y.trim() || _.trim(),
|
|
49
|
+
description: x.trim()
|
|
50
|
+
}
|
|
47
51
|
};
|
|
48
52
|
try {
|
|
49
|
-
await h(t),
|
|
53
|
+
await h(t), R(!0), setTimeout(() => p("/quotas"), 1500);
|
|
50
54
|
} catch (e) {
|
|
51
|
-
|
|
55
|
+
I(e instanceof Error ? e.message : "Failed to create quota");
|
|
52
56
|
}
|
|
53
57
|
},
|
|
54
58
|
className: "max-w-2xl space-y-6",
|
|
55
59
|
children: [
|
|
56
|
-
|
|
60
|
+
F && /* @__PURE__ */ c("div", {
|
|
57
61
|
className: "p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg",
|
|
58
62
|
children: /* @__PURE__ */ c("p", {
|
|
59
63
|
className: "text-sm text-status-error-text",
|
|
60
|
-
children:
|
|
64
|
+
children: F
|
|
61
65
|
})
|
|
62
66
|
}),
|
|
63
|
-
|
|
67
|
+
L && /* @__PURE__ */ c("div", {
|
|
64
68
|
className: "p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg",
|
|
65
69
|
children: /* @__PURE__ */ c("p", {
|
|
66
70
|
className: "text-sm text-status-success-text",
|
|
@@ -78,19 +82,72 @@ var d = () => {
|
|
|
78
82
|
}), /* @__PURE__ */ l("div", {
|
|
79
83
|
className: "p-4 space-y-4",
|
|
80
84
|
children: [
|
|
81
|
-
/* @__PURE__ */ l("div", { children: [
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
/* @__PURE__ */ l("div", { children: [
|
|
86
|
+
/* @__PURE__ */ c("label", {
|
|
87
|
+
htmlFor: "name",
|
|
88
|
+
className: "block text-sm font-medium text-foreground mb-1",
|
|
89
|
+
children: "Quota Key *"
|
|
90
|
+
}),
|
|
91
|
+
/* @__PURE__ */ c("input", {
|
|
92
|
+
id: "name",
|
|
93
|
+
type: "text",
|
|
94
|
+
value: _,
|
|
95
|
+
onChange: (e) => v(e.target.value),
|
|
96
|
+
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",
|
|
97
|
+
placeholder: "e.g., sessions-started, tunnels-created",
|
|
98
|
+
required: !0
|
|
99
|
+
}),
|
|
100
|
+
/* @__PURE__ */ l("p", {
|
|
101
|
+
className: "text-xs text-muted-foreground mt-1",
|
|
102
|
+
children: [
|
|
103
|
+
"Internal machine key — must match the ",
|
|
104
|
+
/* @__PURE__ */ c("code", { children: "quotaName" }),
|
|
105
|
+
" used in",
|
|
106
|
+
" ",
|
|
107
|
+
/* @__PURE__ */ c("code", { children: "@usage" }),
|
|
108
|
+
" directives. Never shown to users."
|
|
109
|
+
]
|
|
110
|
+
})
|
|
111
|
+
] }),
|
|
112
|
+
/* @__PURE__ */ l("div", { children: [
|
|
113
|
+
/* @__PURE__ */ c("label", {
|
|
114
|
+
htmlFor: "displayName",
|
|
115
|
+
className: "block text-sm font-medium text-foreground mb-1",
|
|
116
|
+
children: "Display Name *"
|
|
117
|
+
}),
|
|
118
|
+
/* @__PURE__ */ c("input", {
|
|
119
|
+
id: "displayName",
|
|
120
|
+
type: "text",
|
|
121
|
+
value: y,
|
|
122
|
+
onChange: (e) => b(e.target.value),
|
|
123
|
+
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",
|
|
124
|
+
placeholder: "e.g., Tunnels, Session Starts, Webhooks",
|
|
125
|
+
required: !0
|
|
126
|
+
}),
|
|
127
|
+
/* @__PURE__ */ c("p", {
|
|
128
|
+
className: "text-xs text-muted-foreground mt-1",
|
|
129
|
+
children: "Human-readable name shown to users in billing and usage UI."
|
|
130
|
+
})
|
|
131
|
+
] }),
|
|
132
|
+
/* @__PURE__ */ l("div", { children: [
|
|
133
|
+
/* @__PURE__ */ c("label", {
|
|
134
|
+
htmlFor: "description",
|
|
135
|
+
className: "block text-sm font-medium text-foreground mb-1",
|
|
136
|
+
children: "Description"
|
|
137
|
+
}),
|
|
138
|
+
/* @__PURE__ */ c("textarea", {
|
|
139
|
+
id: "description",
|
|
140
|
+
value: x,
|
|
141
|
+
onChange: (e) => S(e.target.value),
|
|
142
|
+
rows: 2,
|
|
143
|
+
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",
|
|
144
|
+
placeholder: "e.g., Active tunnels created through VibeControls (slot freed on delete)"
|
|
145
|
+
}),
|
|
146
|
+
/* @__PURE__ */ c("p", {
|
|
147
|
+
className: "text-xs text-muted-foreground mt-1",
|
|
148
|
+
children: "Short explanation shown in billing and usage detail views."
|
|
149
|
+
})
|
|
150
|
+
] }),
|
|
94
151
|
/* @__PURE__ */ l("div", { children: [
|
|
95
152
|
/* @__PURE__ */ c("label", {
|
|
96
153
|
htmlFor: "quotaType",
|
|
@@ -99,8 +156,8 @@ var d = () => {
|
|
|
99
156
|
}),
|
|
100
157
|
/* @__PURE__ */ l("select", {
|
|
101
158
|
id: "quotaType",
|
|
102
|
-
value:
|
|
103
|
-
onChange: (e) =>
|
|
159
|
+
value: k,
|
|
160
|
+
onChange: (e) => A(e.target.value),
|
|
104
161
|
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",
|
|
105
162
|
children: [/* @__PURE__ */ c("option", {
|
|
106
163
|
value: r.PRODUCT_SPECIFIC,
|
|
@@ -112,7 +169,7 @@ var d = () => {
|
|
|
112
169
|
}),
|
|
113
170
|
/* @__PURE__ */ c("p", {
|
|
114
171
|
className: "text-xs text-muted-foreground mt-1",
|
|
115
|
-
children:
|
|
172
|
+
children: k === r.POOLED ? "Shared across all products" : "Specific to individual products"
|
|
116
173
|
})
|
|
117
174
|
] }),
|
|
118
175
|
/* @__PURE__ */ l("div", {
|
|
@@ -120,8 +177,8 @@ var d = () => {
|
|
|
120
177
|
children: [/* @__PURE__ */ c("input", {
|
|
121
178
|
id: "reusable",
|
|
122
179
|
type: "checkbox",
|
|
123
|
-
checked:
|
|
124
|
-
onChange: (e) =>
|
|
180
|
+
checked: j,
|
|
181
|
+
onChange: (e) => M(e.target.checked),
|
|
125
182
|
className: "w-4 h-4 rounded border-border text-primary focus:ring-primary"
|
|
126
183
|
}), /* @__PURE__ */ l("div", { children: [/* @__PURE__ */ c("label", {
|
|
127
184
|
htmlFor: "reusable",
|
|
@@ -140,8 +197,8 @@ var d = () => {
|
|
|
140
197
|
}),
|
|
141
198
|
/* @__PURE__ */ l("select", {
|
|
142
199
|
id: "resetPeriod",
|
|
143
|
-
value:
|
|
144
|
-
onChange: (e) =>
|
|
200
|
+
value: N,
|
|
201
|
+
onChange: (e) => P(e.target.value),
|
|
145
202
|
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",
|
|
146
203
|
children: [
|
|
147
204
|
/* @__PURE__ */ c("option", {
|
|
@@ -183,8 +240,8 @@ var d = () => {
|
|
|
183
240
|
children: "Limit Type *"
|
|
184
241
|
}), /* @__PURE__ */ l("select", {
|
|
185
242
|
id: "limitType",
|
|
186
|
-
value:
|
|
187
|
-
onChange: (e) =>
|
|
243
|
+
value: C,
|
|
244
|
+
onChange: (e) => w(e.target.value),
|
|
188
245
|
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",
|
|
189
246
|
children: [
|
|
190
247
|
/* @__PURE__ */ c("option", {
|
|
@@ -215,8 +272,8 @@ var d = () => {
|
|
|
215
272
|
id: "limitValue",
|
|
216
273
|
type: "number",
|
|
217
274
|
min: "0",
|
|
218
|
-
value:
|
|
219
|
-
onChange: (e) =>
|
|
275
|
+
value: T,
|
|
276
|
+
onChange: (e) => E(e.target.value),
|
|
220
277
|
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",
|
|
221
278
|
placeholder: "1000",
|
|
222
279
|
required: !0
|
|
@@ -235,8 +292,8 @@ var d = () => {
|
|
|
235
292
|
/* @__PURE__ */ c("input", {
|
|
236
293
|
id: "unit",
|
|
237
294
|
type: "text",
|
|
238
|
-
value:
|
|
239
|
-
onChange: (e) =>
|
|
295
|
+
value: D,
|
|
296
|
+
onChange: (e) => O(e.target.value),
|
|
240
297
|
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",
|
|
241
298
|
placeholder: "e.g., bots, GB, MB, seats, workflow runs",
|
|
242
299
|
required: !0
|
|
@@ -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 [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 };\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 {/* Name */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-foreground mb-1\">\n Quota Name *\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\"\n placeholder=\"e.g., API Calls, Storage, Seats\"\n required\n />\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":"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"}
|
|
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 htmlFor=\"displayName\" className=\"block text-sm font-medium text-foreground mb-1\">\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 htmlFor=\"description\" className=\"block text-sm font-medium text-foreground mb-1\">\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":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuotaTemplateEditPage.d.ts","sourceRoot":"","sources":["../../../../../src/billing/modules/quota/pages/QuotaTemplateEditPage.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAuB,KAAK,EAAE,EAAkB,MAAM,OAAO,CAAC;AAUrE,eAAO,MAAM,qBAAqB,EAAE,
|
|
1
|
+
{"version":3,"file":"QuotaTemplateEditPage.d.ts","sourceRoot":"","sources":["../../../../../src/billing/modules/quota/pages/QuotaTemplateEditPage.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAuB,KAAK,EAAE,EAAkB,MAAM,OAAO,CAAC;AAUrE,eAAO,MAAM,qBAAqB,EAAE,EAkYnC,CAAC"}
|