|
4 | 4 | */ |
5 | 5 |
|
6 | 6 | import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants' |
| 7 | +import { USAGE_PILL_COLORS } from './consts' |
7 | 8 | import type { BillingStatus, SubscriptionData, UsageData } from './types' |
8 | 9 |
|
9 | 10 | const defaultUsage: UsageData = { |
@@ -36,9 +37,35 @@ export function getSubscriptionStatus(subscriptionData: SubscriptionData | null |
36 | 37 |
|
37 | 38 | /** |
38 | 39 | * Get usage data from subscription data |
| 40 | + * Validates and sanitizes all numeric values to prevent crashes from malformed data |
39 | 41 | */ |
40 | 42 | export function getUsage(subscriptionData: SubscriptionData | null | undefined): UsageData { |
41 | | - return subscriptionData?.usage ?? defaultUsage |
| 43 | + const usage = subscriptionData?.usage |
| 44 | + |
| 45 | + if (!usage) { |
| 46 | + return defaultUsage |
| 47 | + } |
| 48 | + |
| 49 | + return { |
| 50 | + current: |
| 51 | + typeof usage.current === 'number' && Number.isFinite(usage.current) ? usage.current : 0, |
| 52 | + limit: |
| 53 | + typeof usage.limit === 'number' && Number.isFinite(usage.limit) |
| 54 | + ? usage.limit |
| 55 | + : DEFAULT_FREE_CREDITS, |
| 56 | + percentUsed: |
| 57 | + typeof usage.percentUsed === 'number' && Number.isFinite(usage.percentUsed) |
| 58 | + ? usage.percentUsed |
| 59 | + : 0, |
| 60 | + isWarning: Boolean(usage.isWarning), |
| 61 | + isExceeded: Boolean(usage.isExceeded), |
| 62 | + billingPeriodStart: usage.billingPeriodStart ?? null, |
| 63 | + billingPeriodEnd: usage.billingPeriodEnd ?? null, |
| 64 | + lastPeriodCost: |
| 65 | + typeof usage.lastPeriodCost === 'number' && Number.isFinite(usage.lastPeriodCost) |
| 66 | + ? usage.lastPeriodCost |
| 67 | + : 0, |
| 68 | + } |
42 | 69 | } |
43 | 70 |
|
44 | 71 | /** |
@@ -100,3 +127,16 @@ export function canUpgrade(subscriptionData: SubscriptionData | null | undefined |
100 | 127 | const status = getSubscriptionStatus(subscriptionData) |
101 | 128 | return status.plan === 'free' || status.plan === 'pro' |
102 | 129 | } |
| 130 | + |
| 131 | +/** |
| 132 | + * Get the appropriate filled pill color based on usage thresholds. |
| 133 | + * |
| 134 | + * @param isCritical - Whether usage is at critical level (blocked or >= 90%) |
| 135 | + * @param isWarning - Whether usage is at warning level (>= 75% but < critical) |
| 136 | + * @returns CSS color value for filled pills |
| 137 | + */ |
| 138 | +export function getFilledPillColor(isCritical: boolean, isWarning: boolean): string { |
| 139 | + if (isCritical) return USAGE_PILL_COLORS.AT_LIMIT |
| 140 | + if (isWarning) return USAGE_PILL_COLORS.WARNING |
| 141 | + return USAGE_PILL_COLORS.FILLED |
| 142 | +} |
0 commit comments