import { useState, useEffect, useCallback, useRef } from "react";
import { X, RefreshCw, Activity } from "lucide-react";
import type { ProviderUsage, UsageWindow } from "../api";
import { useUsageData } from "../hooks/useUsageData";
interface UsageIndicatorProps {
isOpen: boolean;
onClose: () => void;
}
/**
* Get color class for usage percentage
* - >90%: high (red/error color)
* - >70%: medium (yellow/triage color)
* - <=70%: low (green/success color)
*/
function getUsageColorClass(percentUsed: number): string {
if (percentUsed > 90) return "usage-progress-fill--high";
if (percentUsed > 70) return "usage-progress-fill--medium";
return "usage-progress-fill--low";
}
/**
* Single usage window row with progress bar
*/
function UsageWindowRow({ window }: { window: UsageWindow }) {
const colorClass = getUsageColorClass(window.percentUsed);
return (
{window.label}
{window.percentUsed}% used
{window.percentLeft}% left
{window.resetText && (
{window.resetText}
)}
);
}
/**
* Provider card showing status and usage windows
*/
function ProviderCard({ provider }: { provider: ProviderUsage }) {
const getStatusBadge = () => {
switch (provider.status) {
case "ok":
return (
Connected
);
case "error":
return (
Error
);
case "no-auth":
default:
return (
Not configured
);
}
};
return (
{provider.icon}
{provider.name}
{getStatusBadge()}
{provider.error && (
{provider.error}
)}
{(provider.plan || provider.email) && (
{provider.plan && {provider.plan}}
{provider.email && {provider.email}}
)}
{provider.windows.length > 0 ? (
{provider.windows.map((window, index) => (
))}
) : provider.status === "ok" ? (
No usage data available
) : null}
);
}
/**
* Loading skeleton for usage providers
*/
function UsageSkeleton() {
return (
{[1, 2, 3].map((i) => (
))}
);
}
/**
* Usage Indicator Modal
*
* Displays AI provider subscription usage across multiple providers.
* Shows hourly and weekly usage windows with percentage bars,
* reset timers, and pace indicators.
*/
export function UsageIndicator({ isOpen, onClose }: UsageIndicatorProps) {
const { providers, loading, error, lastUpdated, refresh } = useUsageData({
autoRefresh: isOpen, // Only poll when modal is open
});
const [isRefreshing, setIsRefreshing] = useState(false);
const contentRef = useRef(null);
// Handle manual refresh
const handleRefresh = useCallback(async () => {
setIsRefreshing(true);
await refresh();
setIsRefreshing(false);
}, [refresh]);
// Close on Escape key
useEffect(() => {
if (!isOpen) return;
const handleKey = (e: KeyboardEvent) => {
if (e.key === "Escape") {
onClose();
}
};
document.addEventListener("keydown", handleKey);
return () => document.removeEventListener("keydown", handleKey);
}, [isOpen, onClose]);
// Close on overlay click
const handleOverlayClick = useCallback(
(e: React.MouseEvent) => {
if (e.target === e.currentTarget) {
onClose();
}
},
[onClose]
);
if (!isOpen) return null;
return (
{loading && providers.length === 0 ? (
) : error && providers.length === 0 ? (
Failed to load usage data
{error}
) : providers.length === 0 ? (
No AI providers configured
Configure authentication in Settings to see usage data.
) : (
{providers.map((provider) => (
))}
)}
{lastUpdated && (
Last updated: {lastUpdated.toLocaleTimeString()}
)}
);
}