@bettercone/ui
ComponentsUsage

ApiUsageCard

Real-time API call tracking with usage limits and warnings

ApiUsageCard

The ApiUsageCard component displays real-time API usage tracking with visual progress indicators, warning thresholds, and upgrade prompts when approaching limits.

Plugin Required

This component requires the @bettercone/better-auth-plugin-usage-tracking plugin to be installed and configured in your Better Auth setup. See the UsageDashboard Plugin Installation guide.

This component is part of the UsageDashboard but can be used standalone for custom dashboards or widgets.

Installation

import { ApiUsageCard } from '@bettercone/ui';

Features

  • Real-Time Tracking - Live updates via Convex subscriptions
  • Visual Progress - Color-coded progress bars
  • Warning System - Alerts at 80% and 90% usage
  • Upgrade Prompts - Contextual upgrade suggestions
  • Number Formatting - Comma-separated thousands
  • Percentage Display - Clear usage percentage badge
  • Skeleton Loading - Smooth loading states
  • Type-Safe - Full TypeScript support

Usage

import { ApiUsageCard } from '@/components/usage/api-usage-card';

export default function UsagePage() {
  return (
    <div className="grid gap-6 md:grid-cols-2">
      <ApiUsageCard
        current={8500}
        limit={10000}
      />
    </div>
  );
}
import { ApiUsageCard } from '@/components/usage/api-usage-card';
import { useRouter } from 'next/navigation';

export default function UsagePage() {
  const router = useRouter();

  return (
    <ApiUsageCard
      current={9200}
      limit={10000}
      onUpgrade={() => router.push('/pricing')}
    />
  );
}
import { ApiUsageCard } from '@/components/usage/api-usage-card';

export default function UsagePage() {
  // Show warning at 70% instead of 80%
  return (
    <ApiUsageCard
      current={7500}
      limit={10000}
      warningThreshold={70}
      onUpgrade={() => router.push('/pricing')}
    />
  );
}
import { ApiUsageCard } from '@/components/usage/api-usage-card';

export default function DashboardWidget() {
  // No upgrade button, just display
  return (
    <ApiUsageCard
      current={5000}
      limit={10000}
    />
  );
}

Props

Prop

Type

Warning States

The component shows different visual states based on usage:

UsageBadgeProgress BarAction
0-79%Default (gray)BlueNone
80-89%Secondary (yellow)Orange"High Usage" warning
90-100%Destructive (red)Red"Upgrade Plan" button
// Warning logic
const usagePercentage = (current / limit) * 100;
const isWarning = usagePercentage >= 80;
const isNearLimit = usagePercentage >= 90;

Examples

Example 1: Usage Dashboard

import { ApiUsageCard, FeatureAccessCard } from '@bettercone/ui';
import { useRouter } from 'next/navigation';

export default function UsagePage() {
  const router = useRouter();
  
  const handleUpgrade = () => {
    router.push('/pricing?feature=api-upgrade');
  };

  return (
    <div className="container mx-auto py-8">
      <h1 className="text-3xl font-bold mb-8">Usage & Limits</h1>
      
      <div className="grid gap-6 lg:grid-cols-2">
        <ApiUsageCard
          current={85000}
          limit={100000}
          onUpgrade={handleUpgrade}
        />
                
          <FeatureAccessCard
            features={featureList}
            onUpgrade={handleUpgrade}
          />
      </div>
    </div>
  );
}

Example 2: With Real-Time Convex Data

import { ApiUsageCard } from '@/components/usage/api-usage-card';
import { useQuery } from 'convex/react';
import { api } from '@/convex/_generated/api';

export default function UsagePage() {
  const usageData = useQuery(api.usage.getCurrentUsage, {
    userId: session?.user?.id,
  });

  return (
    <ApiUsageCard
      current={usageData?.apiCalls}
      limit={usageData?.apiLimit}
      onUpgrade={() => router.push('/pricing')}
    />
  );
}

Example 3: Dashboard Widget

import { ApiUsageCard } from '@/components/usage/api-usage-card';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';

export default function DashboardPage() {
  return (
    <div className="grid gap-6 md:grid-cols-3">
      {/* Other widgets */}
      
      <Card>
        <CardHeader>
          <CardTitle>Quick Stats</CardTitle>
        </CardHeader>
        <CardContent>
          <ApiUsageCard
            current={45000}
            limit={100000}
          />
        </CardContent>
      </Card>
    </div>
  );
}

Example 4: With Alert Dialog

import { ApiUsageCard } from '@/components/usage/api-usage-card';
import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { useState } from 'react';

export default function UsagePage() {
  const [showUpgradeDialog, setShowUpgradeDialog] = useState(false);

  const handleUpgrade = () => {
    setShowUpgradeDialog(true);
  };

  return (
    <>
      <ApiUsageCard
        current={9500}
        limit={10000}
        onUpgrade={handleUpgrade}
      />
      
      <AlertDialog open={showUpgradeDialog} onOpenChange={setShowUpgradeDialog}>
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogTitle>Upgrade Your Plan?</AlertDialogTitle>
            <AlertDialogDescription>
              You're approaching your API limit. Upgrade now to avoid service interruption.
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>Cancel</AlertDialogCancel>
            <AlertDialogAction onClick={() => router.push('/pricing')}>
              View Plans
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </>
  );
}

Number Formatting

The component formats large numbers with commas for readability:

// Format: 1,234,567
current.toLocaleString() // 85,000
limit.toLocaleString()   // 100,000

// Display: "85,000 / 100,000 calls"

Progress Bar Colors

Progress bar color changes based on usage:

<Progress
  value={usagePercentage}
  className={
    isNearLimit ? "*:bg-destructive" :
    isWarning ? "*:bg-orange-500" :
    "" // default blue
  }
/>

Warning Messages

Context-aware warning messages:

  • 80-89% usage: "You're using more than 80% of your monthly API calls."
  • 90-100% usage: "You're approaching your API limit. Upgrade to avoid service interruption."

Loading States

When data is undefined, shows skeleton placeholder:

if (current === undefined || limit === undefined) {
  return <ApiUsageCardSkeleton />;
}

Rate Limiting Integration

Track API calls in your application:

// In your API route
import { api } from '@/convex/_generated/api';
import { fetchMutation } from 'convex/nextjs';

export async function POST(request: Request) {
  // Your API logic...
  
  // Track the call
  await fetchMutation(api.usage.trackApiCall, {
    userId: session.user.id,
    endpoint: request.url,
  });
  
  // Check if over limit
  const usage = await fetchQuery(api.usage.getCurrentUsage, {
    userId: session.user.id,
  });
  
  if (usage.apiCalls >= usage.apiLimit) {
    return Response.json(
      { error: 'API limit exceeded' },
      { status: 429 }
    );
  }
  
  return Response.json({ success: true });
}

Implement rate limiting at the API level to prevent service abuse when users exceed their limits.

Upgrade Flow

When upgrade button is clicked:

  1. User sees warning message
  2. Clicks "Upgrade Plan"
  3. Redirected to pricing page
  4. Can select higher tier plan
  5. Checkout completes
  6. Usage limits immediately updated

Plan Limits

Typical API limits by plan:

PlanMonthly API CallsResets
Free10,000Monthly
Pro100,000Monthly
TeamUnlimitedN/A

Reset Period

Usage resets monthly on subscription renewal:

// Next reset date
const resetDate = new Date(subscription.currentPeriodEnd);

// Display: "Resets on Nov 23, 2025"

Notes

  • Usage data updates in real-time via Convex
  • Progress bar smoothly animates between states
  • Skeleton shows while fetching initial data
  • Upgrade button only appears when onUpgrade is provided
  • Warning threshold customizable (default 80%)
  • Near-limit threshold fixed at 90%

Next Steps