/** * Rate Limit Indicator Component * Action 5.4.1.2: Display rate limit status to users */ import { useEffect, useState } from 'react'; import { useRateLimitStore } from '@/stores/rateLimit'; import { AlertTriangle, Clock } from 'lucide-react'; import { cn } from '@/lib/utils'; /** * RateLimitIndicator - Displays current rate limit status * * Shows rate limit information when: * - User is rate limited (isLimited = true) * - Remaining requests are low (< 20% of limit) * * @example * ```tsx * * ``` */ export function RateLimitIndicator() { const { limit, remaining, reset, isLimited } = useRateLimitStore(); const [timeUntilReset, setTimeUntilReset] = useState(null); // Calculate time until reset useEffect(() => { if (!reset) { setTimeUntilReset(null); return; } const updateTimer = () => { const now = Math.floor(Date.now() / 1000); const secondsUntilReset = reset - now; setTimeUntilReset(secondsUntilReset > 0 ? secondsUntilReset : 0); }; // Update immediately updateTimer(); // Update every second const interval = setInterval(updateTimer, 1000); return () => clearInterval(interval); }, [reset]); // Calculate percentage remaining const percentageRemaining = limit !== null && remaining !== null && limit > 0 ? (remaining / limit) * 100 : null; // Show indicator if: // 1. User is rate limited, OR // 2. Remaining is low (< 20% of limit) and we have data const shouldShow = isLimited || (limit !== null && remaining !== null && percentageRemaining !== null && percentageRemaining < 20); if (!shouldShow || limit === null) { return null; } // Format time until reset const formatTimeUntilReset = (seconds: number): string => { if (seconds <= 0) return '0s'; if (seconds < 60) return `${seconds}s`; const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; if (minutes < 60) { return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`; } const hours = Math.floor(minutes / 60); const remainingMinutes = minutes % 60; return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`; }; // Determine severity and styling const isCritical = isLimited || (remaining !== null && remaining <= 0); return (
{isLimited ? ( <> Rate limit exceeded {timeUntilReset !== null && ( {formatTimeUntilReset(timeUntilReset)} )} ) : ( <> {remaining !== null ? `${remaining}/${limit} requests` : `${limit} requests`} {timeUntilReset !== null && ( resets in {formatTimeUntilReset(timeUntilReset)} )} )}
); }