Cycle Usage View
The cycle usage view gives org members a real-time look at credit consumption on the billing settings tab. It shows credits spent vs. included, a per-operation breakdown, and free-allowance progress — all without admin access.
API Endpoint
Section titled “API Endpoint”GET /api/billing/usage
Section titled “GET /api/billing/usage”Returns aggregated usage for the current billing cycle. No auth gate — any org member can call it (matches the get_org_subscription_api access pattern).
Handler: get_org_usage_api() in src/mods/billing/api/get_org_usage_api.rs
Response type: OrgUsageSummary
pub struct OrgUsageSummary { pub has_subscription: bool, pub cycle_start: String, // RFC-3339 timestamp pub cycle_end: String, // RFC-3339 timestamp pub credits_granted: i32, // tier.credits_per_seat × seat_count pub credits_spent: i32, // Total credits deducted this cycle pub credits_remaining: i32, // included_credits + purchased_credits pub by_operation: Vec<OpUsageRow>, // Sorted by highest spend first}
pub struct OpUsageRow { pub operation_type: OperationType, pub units: Decimal, // Raw billed units (native scale) pub free_units_used: Decimal, // Units absorbed by free allowance pub credits_spent: i32, // Credits charged for this op type pub tier_free_allowance: Option<i32>, // None = pay-per-use; Some(0) = not allowed}Query strategy:
- Load subscription + tier via
find_also_related()(single join) - Load
org_budgetwithselect_only()(2 columns:included_credits,purchased_credits) - Aggregate
operationtable withGROUP BY operation_typebounded by cycle dates - Sort results by
credits_spentdescending
Returns OrgUsageSummary::default() (with has_subscription: false) when no subscription exists.
Display Unit Helpers
Section titled “Display Unit Helpers”Two methods on OperationType convert stored units to customer-friendly display values:
impl OperationType { /// Customer-facing unit label (always plural) pub fn display_unit_label(&self) -> &'static str { // Voice/Recording/Transcription/Realtime → "minutes" // SMS → "segments", MMS → "messages" // AI text (all tiers) → "tokens" // Email → "emails" }
/// Multiplier from stored units to display units pub fn display_unit_multiplier(&self) -> i64 { // AI text: 1000 (stored as 1K-token blocks) // Everything else: 1 }}Example: An AI text operation stored as units = 12 (1K-token blocks) displays as 12,000 tokens.
UI Component
Section titled “UI Component”CycleUsageSection renders inside BillingContent, below the current-plan card. It only appears when a subscription exists.
File: src/mods/billing/components/cycle_usage_section_component.rs
Component hierarchy
Section titled “Component hierarchy”CycleUsageSection ← exported, no props, owns use_resource└─ CycleUsageContent ← receives OrgUsageSummary ├─ ProgressBar ← credits_spent / credits_granted ├─ Empty state card ← when by_operation is empty └─ OperationsBreakdown ← table of OpUsageRow items └─ OperationBreakdownRow ← one per operation typeState management
Section titled “State management”let usage = use_resource(|| async move { get_org_usage_api().await });
match &*usage.read() { None => /* ViewLoader spinner */, Some(Err(e)) => /* ResourceError */, Some(Ok(s)) if !s.has_subscription => /* nothing */, Some(Ok(s)) => /* CycleUsageContent */,}Progress bar
Section titled “Progress bar”The denominator handles edge cases:
let denominator = if credits_granted > 0 { credits_granted} else { credits_spent + credits_remaining // Fallback for legacy tiers};let progress = (credits_spent as f64 / denominator as f64).clamp(0.0, 1.0);Per-operation breakdown
Section titled “Per-operation breakdown”Each OperationBreakdownRow shows:
- Operation name — derived from
OperationTypedisplay - Native units — scaled via
display_unit_multiplier(), formatted withformat_with_commas() - Credits — raw credit count
- Share % —
(row.credits_spent / summary.credits_spent * 100).round() - Free allowance hint —
"12 of 100 free minutes used"whentier_free_allowance > 0
Responsive layout
Section titled “Responsive layout”- Desktop (≥640px): 4-column grid with header row (Operation, Usage, Credits, Share)
- Mobile (under 640px): Stacked layout, inline labels per row
Empty state
Section titled “Empty state”When the org has a subscription but zero operations this cycle, a dashed-border card displays: “No usage this cycle yet” with a hint that activity appears once calls, messages, or AI actions run.
Shared Utility
Section titled “Shared Utility”format_with_commas() in src/shared/utils/format_with_commas.rs adds thousands separators to numeric strings. Moved here from the billing module for reuse across the app.
Related Pages
Section titled “Related Pages”- Billing Module Overview — architecture and credit waterfall model
- Operation Metering — how
record_operation()writes the data this view reads - Stripe Integration — subscription lifecycle and webhook handling