/** * Claude Usage Popover * * Displays Claude API usage statistics using React Query for data fetching. */ import { useState, useMemo } from 'react'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Button } from '@/components/ui/button'; import { RefreshCw, AlertTriangle, CheckCircle, XCircle, Clock, ExternalLink } from 'lucide-react'; import { Spinner } from '@/components/ui/spinner'; import { cn } from '@/lib/utils'; import { useSetupStore } from '@/store/setup-store'; import { useClaudeUsage } from '@/hooks/queries'; export function ClaudeUsagePopover() { const claudeAuthStatus = useSetupStore((state) => state.claudeAuthStatus); const [open, setOpen] = useState(false); // Check if CLI is verified/authenticated const isCliVerified = claudeAuthStatus?.authenticated && claudeAuthStatus?.method === 'cli_authenticated'; // Use React Query for usage data const { data: claudeUsage, isLoading, isFetching, error, dataUpdatedAt, refetch, } = useClaudeUsage(isCliVerified); // Check if data is stale (older than 2 minutes) const isStale = useMemo(() => { return !dataUpdatedAt || Date.now() - dataUpdatedAt > 2 * 60 * 1000; }, [dataUpdatedAt]); // Derived status color/icon helper const getStatusInfo = (percentage: number) => { if (percentage >= 75) return { color: 'text-red-500', icon: XCircle, bg: 'bg-red-500' }; if (percentage >= 50) return { color: 'text-orange-500', icon: AlertTriangle, bg: 'bg-orange-500' }; return { color: 'text-green-500', icon: CheckCircle, bg: 'bg-green-500' }; }; // Helper component for the progress bar const ProgressBar = ({ percentage, colorClass }: { percentage: number; colorClass: string }) => (
); const UsageCard = ({ title, subtitle, percentage, resetText, isPrimary = false, stale = false, }: { title: string; subtitle: string; percentage: number; resetText?: string; isPrimary?: boolean; stale?: boolean; }) => { const isValidPercentage = typeof percentage === 'number' && !isNaN(percentage) && isFinite(percentage); const safePercentage = isValidPercentage ? percentage : 0; const status = getStatusInfo(safePercentage); const StatusIcon = status.icon; return (

{title}

{subtitle}

{isValidPercentage ? (
{Math.round(safePercentage)}%
) : ( N/A )}
{resetText && (

{title === 'Session Usage' && } {resetText}

)}
); }; // Header Button const maxPercentage = claudeUsage ? Math.max(claudeUsage.sessionPercentage || 0, claudeUsage.weeklyPercentage || 0) : 0; const getProgressBarColor = (percentage: number) => { if (percentage >= 80) return 'bg-red-500'; if (percentage >= 50) return 'bg-yellow-500'; return 'bg-green-500'; }; const trigger = ( ); return ( {trigger} {/* Header */}
Claude Usage
{error && ( )}
{/* Content */}
{error ? (

{error instanceof Error ? error.message : 'Failed to fetch usage'}

Make sure Claude CLI is installed and authenticated via{' '} claude login

) : isLoading || !claudeUsage ? (

Loading usage data...

) : ( <> {/* Primary Card */} {/* Secondary Cards Grid */}
{/* Extra Usage / Cost */} {claudeUsage.costLimit && claudeUsage.costLimit > 0 && ( 0 ? ((claudeUsage.costUsed ?? 0) / claudeUsage.costLimit) * 100 : 0 } stale={isStale} /> )} )}
{/* Footer */}
Claude Status Updates every minute
); }