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 (

Usage

{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()} )}
); }