mirror of
https://github.com/vernu/textbee.git
synced 2026-07-30 17:07:46 -04:00
The home page was four all-time counters that could not answer either question a user actually opens the dashboard for: how much quota is left, and did my recent sends work. Removed fabricated data: - Every stat card rendered a green TrendingUp arrow whenever a value existed. Nothing computed a trend. The stats endpoint returns running totals with no time window, so there was nothing to compare against. - "Since last year" on the sent and received counts. getStatsForUser sums device counters with no date filter, so these are all-time totals. - "Active Devices" with a "Connected now" caption, over a number that counts every device including disabled ones. Enabled count is now derived from the device list we already fetch. - "Active keys" over a count that included revoked keys; now uses the active API key list. Added, all from fields the API already returns: - Usage cards leading the page: today and this month against their limits, with progress bars, an amber near-limit state past 80% and an upgrade link. Unlimited plans (-1) show usage with no meter, since there is no ceiling to measure against. - Recent activity: the last 5 messages for the connected device. Messages are only exposed per device, so the card names the device rather than implying account-wide coverage. - All-time totals compacted from four large cards into one honest strip. lib/usage.ts extracts the daily/monthly derivation that subscription-info already did inline, so the dashboard and billing page cannot disagree about remaining quota. Unit tested including the -1 sentinel, custom limit overrides, and an over-100 percentage (possible when a limit is lowered mid-period). No backend changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 lines
1.9 KiB
TypeScript
68 lines
1.9 KiB
TypeScript
import type { Subscription } from '@/lib/api/types'
|
|
|
|
// The backend uses -1 to mean "no limit".
|
|
export const UNLIMITED = -1
|
|
|
|
export type UsageWindow = {
|
|
used: number
|
|
limit: number | undefined
|
|
remaining: number
|
|
// 0-100, clamped. Meaningless when unlimited.
|
|
percentage: number
|
|
unlimited: boolean
|
|
// True once the user is close enough to the limit to warrant a nudge.
|
|
nearLimit: boolean
|
|
atLimit: boolean
|
|
}
|
|
|
|
export const NEAR_LIMIT_PERCENT = 80
|
|
|
|
function buildWindow(
|
|
used: number | undefined,
|
|
limit: number | undefined,
|
|
remaining: number | undefined,
|
|
percentage: number | undefined
|
|
): UsageWindow {
|
|
const unlimited = limit === UNLIMITED
|
|
// The backend reports percentage directly, but it is only meaningful for a
|
|
// real limit and can exceed 100 when a limit was lowered mid-period.
|
|
const pct = unlimited ? 0 : Math.min(100, Math.max(0, percentage ?? 0))
|
|
|
|
return {
|
|
used: used ?? 0,
|
|
limit,
|
|
remaining: remaining ?? 0,
|
|
percentage: pct,
|
|
unlimited,
|
|
nearLimit: !unlimited && pct >= NEAR_LIMIT_PERCENT && pct < 100,
|
|
atLimit: !unlimited && pct >= 100,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Derive the daily and monthly send windows from a subscription.
|
|
*
|
|
* Shared by the dashboard usage cards and the billing page so the two can
|
|
* never disagree about how much quota is left. `usage` values win over `plan`
|
|
* values because they already account for per-account custom overrides.
|
|
*/
|
|
export function deriveUsage(subscription: Subscription | undefined) {
|
|
const plan = subscription?.plan
|
|
const usage = subscription?.usage
|
|
|
|
return {
|
|
daily: buildWindow(
|
|
usage?.processedSmsToday,
|
|
usage?.dailyLimit ?? plan?.dailyLimit,
|
|
usage?.dailyRemaining,
|
|
usage?.dailyUsagePercentage
|
|
),
|
|
monthly: buildWindow(
|
|
usage?.processedSmsLastMonth,
|
|
usage?.monthlyLimit ?? plan?.monthlyLimit,
|
|
usage?.monthlyRemaining,
|
|
usage?.monthlyUsagePercentage
|
|
),
|
|
}
|
|
}
|