UsageDashboard
Complete usage tracking dashboard with API calls, storage, and feature access
UsageDashboard
The UsageDashboard is a composition component that provides a complete overview of organization usage metrics.
Plugin Required
This component requires the @bettercone/better-auth-plugin-usage-tracking plugin to be installed and configured in your Better Auth setup. See Plugin Installation below.
This is a composition component that combines ApiUsageCard and FeatureAccessCard. You provide the data from your backend.
Plugin Installation
First, install the usage tracking plugin:
pnpm add @bettercone/better-auth-plugin-usage-trackingThen configure it in your Better Auth setup:
import { betterAuth } from "better-auth";
import { usageTracking } from "@bettercone/better-auth-plugin-usage-tracking";
export const auth = betterAuth({
// ... other config
plugins: [
usageTracking(),
],
});Installation
import { UsageDashboard } from '@bettercone/ui';Features
- ✅ Backend-Agnostic - Works with any Better Auth backend (Convex, Prisma, Supabase, Drizzle)
- ✅ API Usage Tracking - Monitor API calls against plan limits
- ✅ Storage Monitoring - Track storage consumption with visual indicators
- ✅ Feature Access - Display available features by plan
- ✅ Flexible Layout - Grid or stack layout options
- ✅ Upgrade Prompts - Smart prompts when approaching limits via callbacks
- ✅ Progress Visualization - Clear progress bars and percentages
- ✅ Loading States - Built-in skeleton loading
- ✅ Type-Safe - Full TypeScript support
Usage
import { UsageDashboard } from '@bettercone/ui';
import { useRouter } from 'next/navigation';
export default function UsagePage() {
const router = useRouter();
return (
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold mb-6">Usage & Limits</h1>
<UsageDashboard
apiUsage={{ current: 8500, limit: 10000 }}
featureAccess={{
planId: "pro",
advancedAnalytics: true,
apiAccess: true,
customIntegrations: false
}}
apiUsageCardProps={{
onUpgrade: () => router.push('/pricing')
}}
featureAccessCardProps={{
onUpgrade: () => router.push('/pricing')
}}
/>
</div>
);
}import { UsageDashboard } from '@bettercone/ui';
import { useQuery } from 'convex/react';
import { api } from '@/convex/_generated/api';
import { authClient } from '@/lib/auth-client';
import { useRouter } from 'next/navigation';
export default function UsagePage() {
const router = useRouter();
const { data: session } = authClient.useSession();
const { data: organizations } = authClient.useListOrganizations();
const activeOrg = organizations?.[0];
const usageData = useQuery(
api.usage.getCurrentUsage,
session?.user?.id
? { userId: session.user.id, organizationId: activeOrg?.id }
: "skip"
);
const featureAccess = useQuery(
api.usage.getFeatureAccess,
session?.user?.id
? { userId: session.user.id, organizationId: activeOrg?.id }
: "skip"
);
return (
<div className="container mx-auto py-8 px-4">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Usage Metrics</h1>
<p className="text-muted-foreground">
Monitor your usage across all metrics
</p>
</div>
<div className="max-w-4xl mx-auto">
<UsageDashboard />
</div>
</div>
);
}import { UsageDashboard } from '@/components';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
export default function DashboardPage() {
return (
<div className="container mx-auto py-8">
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="usage">Usage</TabsTrigger>
<TabsTrigger value="billing">Billing</TabsTrigger>
</TabsList>
<TabsContent value="usage" className="mt-6">
<UsageDashboard />
</TabsContent>
</Tabs>
</div>
);
}import { UsageDashboard } from '@/components';
import { Suspense } from 'react';
import { Skeleton } from '@/components/ui/skeleton';
function UsageDashboardSkeleton() {
return (
<div className="space-y-6">
<Skeleton className="h-48 w-full" />
<Skeleton className="h-48 w-full" />
<Skeleton className="h-64 w-full" />
</div>
);
}
export default function UsagePage() {
return (
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold mb-6">Usage</h1>
<Suspense fallback={<UsageDashboardSkeleton />}>
<UsageDashboard />
</Suspense>
</div>
);
}Props
The UsageDashboard component has no required props - it automatically fetches and displays usage data for the authenticated user.
Prop
Type
The component automatically handles authentication, organization context, and data fetching internally.
Usage Metrics
1. API Usage
Tracks API calls made against your plan's limit:
- Current Usage: Number of API calls this billing period
- Limit: Maximum API calls allowed by plan
- Percentage: Visual progress indicator
- Reset Date: When the counter resets (monthly)
Example Plans:
- Free: 10,000 API calls/month
- Pro: 100,000 API calls/month
- Team: Unlimited
2. Storage Usage
Monitors file and data storage consumption:
- Current Storage: Used storage in GB/MB
- Limit: Maximum storage allowed by plan
- Percentage: Visual progress indicator
- File Count: Number of stored files (optional)
Example Plans:
- Free: 1 GB
- Pro: 10 GB
- Team: 100 GB
3. Feature Access
Displays available features by subscription plan:
- Available Features: Green checkmarks for enabled features
- Locked Features: Locked icons for premium features
- Upgrade Prompts: Contextual upgrade suggestions
Example Features:
- Advanced Analytics
- Priority Support
- Custom Domains
- API Access
- Team Collaboration
- White Labeling
Examples
Example 1: Protected Usage Page
'use client';
import { UsageDashboard } from '@/components';
import { SignedIn, SignedOut } from '@daveyplate/better-auth-ui';
import { Button } from '@/components/ui/button';
import Link from 'next/link';
export default function UsagePage() {
return (
<div className="container mx-auto py-8 px-4">
<SignedOut>
<div className="text-center py-12">
<h1 className="text-2xl font-bold mb-4">Sign in to view usage</h1>
<Button asChild>
<Link href="/auth/sign-in">Sign In</Link>
</Button>
</div>
</SignedOut>
<SignedIn>
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Usage & Limits</h1>
<p className="text-muted-foreground">
Track your API usage, storage, and available features
</p>
</div>
<UsageDashboard />
<div className="mt-8 p-6 bg-muted rounded-lg">
<h2 className="font-semibold mb-2">Need More Resources?</h2>
<p className="text-sm text-muted-foreground mb-4">
Upgrade your plan to get higher limits and access to premium features.
</p>
<Link
href="/pricing"
className="text-sm font-medium text-primary hover:underline"
>
View Pricing Plans →
</Link>
</div>
</SignedIn>
</div>
);
}Example 2: Dashboard Widget
import { UsageDashboard } from '@/components';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
export default function DashboardPage() {
return (
<div className="container mx-auto py-8">
<div className="grid gap-6 md:grid-cols-2">
{/* Other dashboard widgets */}
<Card>
<CardHeader>
<CardTitle>Quick Stats</CardTitle>
</CardHeader>
<CardContent>
{/* Stats content */}
</CardContent>
</Card>
{/* Usage widget */}
<Card className="md:col-span-2">
<CardHeader>
<CardTitle>Usage Metrics</CardTitle>
</CardHeader>
<CardContent>
<UsageDashboard />
</CardContent>
</Card>
</div>
</div>
);
}Example 3: Usage with Alerts
import { UsageDashboard } from '@/components';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { AlertTriangle } from 'lucide-react';
import { useQuery } from 'convex/react';
import { api } from '@/convex/_generated/api';
export default function UsagePage() {
// Check if user is near limits
const usageData = useQuery(api.usage.getCurrentUsage, {
userId: session?.user?.id,
});
const nearLimit = usageData && (
usageData.apiCalls / usageData.apiLimit > 0.8 ||
usageData.storageBytes / usageData.storageLimit > 0.8
);
return (
<div className="container mx-auto py-8 px-4">
<h1 className="text-3xl font-bold mb-6">Usage</h1>
{nearLimit && (
<Alert className="mb-6">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Approaching Limit</AlertTitle>
<AlertDescription>
You're using over 80% of your plan limits.
Consider upgrading to avoid service interruption.
</AlertDescription>
</Alert>
)}
<UsageDashboard />
</div>
);
}Example 4: Admin Usage Overview
import { UsageDashboard } from '@/components';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select';
import { useState } from 'react';
export default function AdminUsagePage() {
const [selectedOrg, setSelectedOrg] = useState<string>('all');
return (
<div className="container mx-auto py-8 px-4">
<div className="flex items-center justify-between mb-8">
<h1 className="text-3xl font-bold">Organization Usage</h1>
<Select value={selectedOrg} onValueChange={setSelectedOrg}>
<SelectTrigger className="w-64">
<SelectValue placeholder="Select organization" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Organizations</SelectItem>
<SelectItem value="org1">Acme Corp</SelectItem>
<SelectItem value="org2">TechStart Inc</SelectItem>
</SelectContent>
</Select>
</div>
<UsageDashboard />
</div>
);
}Component Composition
The UsageDashboard is composed of two main cards:
- ApiUsageCard - API call tracking
- FeatureAccessCard - Feature availability
You can use these cards individually for custom layouts:
import {
ApiUsageCard,
FeatureAccessCard
} from '@bettercone/ui';
export default function CustomUsagePage() {
return (
<div className="grid gap-6 lg:grid-cols-2">
{/* Side by side layout */}
<ApiUsageCard
current={8500}
limit={10000}
onUpgrade={() => router.push('/pricing')}
/>
<FeatureAccessCard
features={featureList}
onUpgrade={() => router.push('/pricing')}
/>
</div>
</div>
);
}Real-Time Updates
The component uses Convex for real-time data synchronization:
// Usage updates automatically when:
// 1. User makes API calls
// 2. Files are uploaded/deleted
// 3. Plan is upgraded/downgraded
// 4. Billing period resets
// No manual refresh needed!Usage data updates in real-time across all user sessions thanks to Convex's reactive queries.
Tracking Usage in Your App
To track API usage in your application:
// In your API route or server action
import { api } from '@/convex/_generated/api';
import { fetchMutation } from 'convex/nextjs';
export async function POST(request: Request) {
// Your API logic...
// Track the API call
await fetchMutation(api.usage.trackApiCall, {
userId: session.user.id,
endpoint: '/api/data',
organizationId: org?.id,
});
return Response.json({ success: true });
}Upgrade Flow
When users approach or exceed limits:
- Warning at 80%: Visual indicator changes to warning color
- Alert at 90%: Prominent upgrade prompt appears
- Blocked at 100%: API calls are rate-limited, upgrade required
// Automatic upgrade prompts
<UsageDashboard />
// Shows "Upgrade Plan" button when limits approachedPlan Limits Configuration
Configure plan limits in packages/convex/convex/subscriptionPlans.ts:
export const subscriptionPlans = [
{
id: "free",
name: "Free",
limits: {
apiCalls: 10000, // 10k API calls/month
storage: 1048576, // 1 GB in KB
features: ["basic"],
},
},
{
id: "pro",
name: "Pro",
limits: {
apiCalls: 100000, // 100k API calls/month
storage: 10485760, // 10 GB in KB
features: ["basic", "advanced", "analytics"],
},
},
// More plans...
];Notes
Rate Limiting: API usage tracking is essential for implementing rate limiting. Ensure all API endpoints track usage properly.
- Usage resets monthly on subscription renewal date
- Storage includes all uploaded files and database storage
- Feature access is determined by active subscription plan
- Organization-level usage aggregates all member activity
- Exceeded limits may result in service throttling
Related Components
- ApiUsageCard - API call tracking card
- FeatureAccessCard - Feature list card
- PricingDashboard - Upgrade plans
- BillingDashboard - Manage billing