"use client"; import { useState, useEffect, useCallback, useRef, useMemo } from "react"; import ProviderIcon from "@/shared/components/ProviderIcon"; import QuotaTable from "./QuotaTable"; import Toggle from "@/shared/components/Toggle"; import { parseQuotaData, calculatePercentage } from "./utils"; import Card from "@/shared/components/Card"; import { EditConnectionModal } from "@/shared/components"; import { USAGE_SUPPORTED_PROVIDERS } from "@/shared/constants/providers"; function getConnectionLabel(connection) { const isEmail = (value) => typeof value === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value); if (isEmail(connection.email)) return connection.email; if (isEmail(connection.name)) return connection.name; return connection.name; } function getConnectionQuotaRemaining(connection, quotaData) { const quota = quotaData[connection.id]?.quotas?.[0]; if (!quota) return Number.POSITIVE_INFINITY; if (typeof quota.remaining === "number") return quota.remaining; return Number.POSITIVE_INFINITY; } function sortVisibleConnections( connections, quotaData, expiringFirst, providerFilter, quotaSortMode, ) { if (providerFilter === "codex" && quotaSortMode !== "default") { return [...connections].sort((a, b) => { const remainingA = getConnectionQuotaRemaining(a, quotaData); const remainingB = getConnectionQuotaRemaining(b, quotaData); const remainingDiff = quotaSortMode === "remaining-asc" ? remainingA - remainingB : remainingB - remainingA; if (remainingDiff !== 0) return remainingDiff; return (getConnectionLabel(a) || "").localeCompare( getConnectionLabel(b) || "", ); }); } if (!expiringFirst) return connections; const getEarliestResetTime = (connection) => { const resetTimes = (quotaData[connection.id]?.quotas || []) .map((quota) => quota.resetAt ? new Date(quota.resetAt).getTime() : Number.POSITIVE_INFINITY, ) .filter((time) => Number.isFinite(time)); return resetTimes.length > 0 ? Math.min(...resetTimes) : Number.POSITIVE_INFINITY; }; return [...connections].sort((a, b) => { const expiryDiff = getEarliestResetTime(a) - getEarliestResetTime(b); if (expiryDiff !== 0) return expiryDiff; return ( (a.provider || "").localeCompare(b.provider || "") || (getConnectionLabel(a) || "").localeCompare(getConnectionLabel(b) || "") ); }); } function buildLoadingState(connections) { const nextLoadingState = {}; connections.forEach((connection) => { nextLoadingState[connection.id] = true; }); return nextLoadingState; } function filterQuotaStateByConnections(state, connections) { const visibleIds = new Set(connections.map((connection) => connection.id)); return Object.fromEntries( Object.entries(state).filter(([id]) => visibleIds.has(id)), ); } function getConnectionsPageRange(pagination) { if (!pagination.total) { return { start: 0, end: 0 }; } const start = (pagination.page - 1) * pagination.pageSize + 1; const end = Math.min(pagination.page * pagination.pageSize, pagination.total); return { start, end }; } function getConnectionsEmptyMessage(totals, providerFilter, accountFilter) { if (!totals.eligibleConnections) { return { icon: "cloud_off", title: "No Providers Connected", description: "Connect to providers with OAuth to track your API quota limits and usage.", }; } if (!totals.providerFilteredConnections) { return { icon: "filter_alt_off", title: "No Accounts Match Current Filters", description: providerFilter === "all" ? "Try changing the account status filter to see more quota trackers." : `No ${accountFilter === "inactive" ? "turned off" : accountFilter === "active" ? "active" : "matching"} accounts found for ${providerFilter}.`, }; } return { icon: "filter_alt_off", title: "No Accounts On This Page", description: "Try moving to another page or refreshing the current filters.", }; } function sortRequestFromExpiringFirst(expiringFirst) { return expiringFirst ? "expiring" : "priority"; } function getPageSizeLabel(pageSize, isCustomPageSize) { return isCustomPageSize ? `Custom: ${pageSize} / page` : `${pageSize} / page`; } function getConnectionsPaginationSummary(pagination) { const { start, end } = getConnectionsPageRange(pagination); return `Showing ${start}-${end} of ${pagination.total}`; } function getSafePagination(pagination, fallbackPageSize) { return ( pagination || { page: 1, pageSize: fallbackPageSize, total: 0, totalPages: 1, } ); } function getSafeTotals(totals, fallbackTotal = 0) { return ( totals || { eligibleConnections: fallbackTotal, providerFilteredConnections: fallbackTotal, } ); } function shouldResetPage(previousValue, nextValue) { return previousValue !== nextValue; } function getPaginationPageValue(dataPagination, fallbackPage) { return dataPagination?.page || fallbackPage; } function getProviderOptions(dataProviderOptions) { return dataProviderOptions || []; } async function reconcileConnectionsPage(fetchConnections, targetPage) { const nextConnections = await fetchConnections(targetPage); return nextConnections; } const QUOTA_CACHE_KEY = "quotaCacheData"; function getQuotaCache() { if (typeof window === "undefined") return {}; try { const cached = window.localStorage.getItem(QUOTA_CACHE_KEY); return cached ? JSON.parse(cached) : {}; } catch (error) { console.error("Error reading quota cache:", error); return {}; } } function setQuotaCache(connectionId, quotaEntry) { if (typeof window === "undefined") return; try { const cache = getQuotaCache(); cache[connectionId] = { ...quotaEntry, cachedAt: new Date().toISOString(), }; window.localStorage.setItem(QUOTA_CACHE_KEY, JSON.stringify(cache)); } catch (error) { console.error("Error writing quota cache:", error); } } const REFRESH_INTERVAL_MS = 60000; // 60 seconds const DEPLETED_QUOTA_THRESHOLD = 5; // percent const AUTO_REFRESH_STORAGE_KEY = "quotaAutoRefresh"; const ACCOUNT_FILTER_OPTIONS = [ { value: "all", label: "All accounts" }, { value: "active", label: "Active" }, { value: "inactive", label: "Turned off" }, ]; const QUOTA_SORT_OPTIONS = [ { value: "default", label: "Default quota order" }, { value: "remaining-asc", label: "% quota: low to high" }, { value: "remaining-desc", label: "% quota: high to low" }, ]; const CONNECTIONS_PAGE_SIZE = 20; const ACCOUNT_PAGE_SIZE_OPTIONS = [10, 20, 50, 100]; const ACCOUNT_PAGE_SIZE_MAX = 500; export default function ProviderLimits() { const [connections, setConnections] = useState([]); const [quotaData, setQuotaData] = useState({}); const [loading, setLoading] = useState({}); const [errors, setErrors] = useState({}); const [autoRefresh, setAutoRefresh] = useState(true); const [lastUpdated, setLastUpdated] = useState(null); const [hasHydratedAutoRefresh, setHasHydratedAutoRefresh] = useState(false); const [refreshingAll, setRefreshingAll] = useState(false); const [countdown, setCountdown] = useState(60); const [connectionsLoading, setConnectionsLoading] = useState(true); const [deletingId, setDeletingId] = useState(null); const [togglingId, setTogglingId] = useState(null); const [showEditModal, setShowEditModal] = useState(false); const [selectedConnection, setSelectedConnection] = useState(null); const [proxyPools, setProxyPools] = useState([]); const [providerFilter, setProviderFilter] = useState("all"); const [providerOptions, setProviderOptions] = useState([]); const [accountFilter, setAccountFilter] = useState("all"); const [quotaSortMode, setQuotaSortMode] = useState("default"); const [expiringFirst, setExpiringFirst] = useState(false); const [providerMenuOpen, setProviderMenuOpen] = useState(false); const [bulkToggling, setBulkToggling] = useState(false); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(CONNECTIONS_PAGE_SIZE); const [customPageSizeInput, setCustomPageSizeInput] = useState( String(CONNECTIONS_PAGE_SIZE), ); const [pagination, setPagination] = useState({ page: 1, pageSize: CONNECTIONS_PAGE_SIZE, total: 0, totalPages: 1, }); const [totals, setTotals] = useState({ eligibleConnections: 0, providerFilteredConnections: 0, }); const intervalRef = useRef(null); const countdownRef = useRef(null); const fetchConnections = useCallback( async (targetPage = page) => { try { const params = new URLSearchParams({ page: String(targetPage), pageSize: String(pageSize), accountStatus: accountFilter, sort: "priority", }); if (providerFilter !== "all") { params.set("provider", providerFilter); } const response = await fetch( `/api/providers/client?${params.toString()}`, ); if (!response.ok) throw new Error("Failed to fetch connections"); const data = await response.json(); const connectionList = data.connections || []; const nextPagination = getSafePagination(data.pagination, pageSize); const nextTotals = getSafeTotals(data.totals, connectionList.length); setConnections(connectionList); setProviderOptions(getProviderOptions(data.providerOptions)); setPagination(nextPagination); setTotals(nextTotals); setPage(getPaginationPageValue(data.pagination, targetPage)); return connectionList; } catch (error) { console.error("Error fetching connections:", error); setConnections([]); setProviderOptions([]); setPagination({ page: 1, pageSize, total: 0, totalPages: 1 }); setTotals({ eligibleConnections: 0, providerFilteredConnections: 0 }); return []; } }, [accountFilter, expiringFirst, page, pageSize, providerFilter], ); // Fetch quota for a specific connection const fetchQuota = useCallback(async (connectionId, provider) => { setLoading((prev) => ({ ...prev, [connectionId]: true })); setErrors((prev) => ({ ...prev, [connectionId]: null })); try { console.log( `[ProviderLimits] Fetching quota for ${provider} (${connectionId})`, ); const response = await fetch(`/api/usage/${connectionId}`); if (!response.ok) { const errorData = await response.json().catch(() => ({})); const errorMsg = errorData.error || response.statusText; // Handle different error types gracefully if (response.status === 404) { // Connection not found - skip silently console.warn( `[ProviderLimits] Connection not found for ${provider}, skipping`, ); return; } if (response.status === 401) { // Auth error - show message instead of throwing console.warn( `[ProviderLimits] Auth error for ${provider}:`, errorMsg, ); const quotaEntry = { quotas: [], message: errorMsg, }; setQuotaData((prev) => ({ ...prev, [connectionId]: quotaEntry, })); setQuotaCache(connectionId, quotaEntry); return; } throw new Error(`HTTP ${response.status}: ${errorMsg}`); } const data = await response.json(); console.log(`[ProviderLimits] Got quota for ${provider}:`, data); // Parse quota data using provider-specific parser const parsedQuotas = parseQuotaData(provider, data); const quotaEntry = { quotas: parsedQuotas, plan: data.plan || null, message: data.message || null, raw: data, }; setQuotaData((prev) => ({ ...prev, [connectionId]: quotaEntry, })); setQuotaCache(connectionId, quotaEntry); } catch (error) { console.error( `[ProviderLimits] Error fetching quota for ${provider} (${connectionId}):`, error, ); setErrors((prev) => ({ ...prev, [connectionId]: error.message || "Failed to fetch quota", })); } finally { setLoading((prev) => ({ ...prev, [connectionId]: false })); } }, []); // Refresh quota for a specific provider const refreshProvider = useCallback( async (connectionId, provider) => { await fetchQuota(connectionId, provider); setLastUpdated(new Date()); }, [fetchQuota], ); const handleDeleteConnection = useCallback( async (id) => { if (!confirm("Delete this connection?")) return; setDeletingId(id); try { const res = await fetch(`/api/providers/${id}`, { method: "DELETE" }); if (res.ok) { setQuotaData((prev) => { const next = { ...prev }; delete next[id]; return next; }); setLoading((prev) => { const next = { ...prev }; delete next[id]; return next; }); setErrors((prev) => { const next = { ...prev }; delete next[id]; return next; }); if (typeof window !== "undefined") { try { const cache = getQuotaCache(); if (cache[id]) { delete cache[id]; window.localStorage.setItem( QUOTA_CACHE_KEY, JSON.stringify(cache), ); } } catch (e) { console.error("Error deleting cache entry:", e); } } await reconcileConnectionsPage(fetchConnections, page); } } catch (error) { console.error("Error deleting connection:", error); } finally { setDeletingId(null); } }, [fetchConnections, page], ); const handleToggleConnectionActive = useCallback( async (id, isActive) => { setTogglingId(id); try { const res = await fetch(`/api/providers/${id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ isActive }), }); if (res.ok) { setQuotaData((prev) => { const next = { ...prev }; return next; }); await reconcileConnectionsPage(fetchConnections, page); } } catch (error) { console.error("Error updating connection status:", error); } finally { setTogglingId(null); } }, [fetchConnections, page], ); const handleUpdateConnection = useCallback( async (formData) => { if (!selectedConnection?.id) return; const connectionId = selectedConnection.id; const provider = selectedConnection.provider; try { const res = await fetch(`/api/providers/${connectionId}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(formData), }); if (res.ok) { await fetchConnections(); setShowEditModal(false); setSelectedConnection(null); if (USAGE_SUPPORTED_PROVIDERS.includes(provider)) { await fetchQuota(connectionId, provider); } } } catch (error) { console.error("Error saving connection:", error); } }, [selectedConnection, fetchConnections, fetchQuota], ); useEffect(() => { let cancelled = false; fetch("/api/proxy-pools?isActive=true", { cache: "no-store" }) .then((res) => res.json()) .then((data) => { if (!cancelled && data?.proxyPools) { setProxyPools(data.proxyPools); } }) .catch(() => {}); return () => { cancelled = true; }; }, []); const refreshAll = useCallback(async () => { if (refreshingAll) return; setRefreshingAll(true); setCountdown(60); try { const visibleConnections = await fetchConnections(page); setLoading(buildLoadingState(visibleConnections)); setErrors((prev) => filterQuotaStateByConnections(prev, visibleConnections), ); setQuotaData((prev) => filterQuotaStateByConnections(prev, visibleConnections), ); await Promise.all( visibleConnections.map((conn) => fetchQuota(conn.id, conn.provider)), ); setLastUpdated(new Date()); } catch (error) { console.error("Error refreshing all providers:", error); } finally { setRefreshingAll(false); } }, [refreshingAll, fetchConnections, fetchQuota, page]); useEffect(() => { const initializeData = async () => { setConnectionsLoading(true); const visibleConnections = await fetchConnections(page); setConnectionsLoading(false); // Always fetch fresh quota on mount, no cache display setLoading(buildLoadingState(visibleConnections)); setErrors((prev) => filterQuotaStateByConnections(prev, visibleConnections), ); setQuotaData((prev) => filterQuotaStateByConnections(prev, visibleConnections), ); await Promise.all( visibleConnections.map((conn) => fetchQuota(conn.id, conn.provider)), ); setLastUpdated(new Date()); }; initializeData(); }, [fetchConnections, fetchQuota, page]); useEffect(() => { if (typeof window === "undefined") return; const stored = window.localStorage.getItem(AUTO_REFRESH_STORAGE_KEY); setAutoRefresh(stored === null ? true : stored === "true"); setHasHydratedAutoRefresh(true); }, []); // Persist auto-refresh preference useEffect(() => { if (typeof window === "undefined" || !hasHydratedAutoRefresh) return; window.localStorage.setItem(AUTO_REFRESH_STORAGE_KEY, String(autoRefresh)); }, [autoRefresh, hasHydratedAutoRefresh]); // Auto-refresh interval useEffect(() => { if (!hasHydratedAutoRefresh || !autoRefresh) { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } if (countdownRef.current) { clearInterval(countdownRef.current); countdownRef.current = null; } return; } // Main refresh interval intervalRef.current = setInterval(() => { refreshAll(); }, REFRESH_INTERVAL_MS); // Countdown interval countdownRef.current = setInterval(() => { setCountdown((prev) => { if (prev <= 1) return 60; return prev - 1; }); }, 1000); return () => { if (intervalRef.current) clearInterval(intervalRef.current); if (countdownRef.current) clearInterval(countdownRef.current); }; }, [autoRefresh, refreshAll, hasHydratedAutoRefresh]); // Pause auto-refresh when tab is hidden (Page Visibility API) useEffect(() => { const handleVisibilityChange = () => { if (document.hidden) { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } if (countdownRef.current) { clearInterval(countdownRef.current); countdownRef.current = null; } } else if (autoRefresh && hasHydratedAutoRefresh) { // Resume auto-refresh when tab becomes visible intervalRef.current = setInterval(refreshAll, REFRESH_INTERVAL_MS); countdownRef.current = setInterval(() => { setCountdown((prev) => (prev <= 1 ? 60 : prev - 1)); }, 1000); } }; document.addEventListener("visibilitychange", handleVisibilityChange); return () => { document.removeEventListener("visibilitychange", handleVisibilityChange); }; }, [autoRefresh, refreshAll, hasHydratedAutoRefresh]); const sortedConnections = useMemo( () => sortVisibleConnections( connections, quotaData, expiringFirst, providerFilter, quotaSortMode, ), [connections, quotaData, expiringFirst, providerFilter, quotaSortMode], ); // Connection is depleted when any quota entry hit the threshold const isConnectionDepleted = (conn) => { const quotas = quotaData[conn.id]?.quotas; if (!quotas?.length) return false; return quotas.some((q) => { if (!q.total || q.total <= 0) return false; return calculatePercentage(q.used, q.total) <= DEPLETED_QUOTA_THRESHOLD; }); }; const bulkSetActive = useCallback( async (targetIds, isActive) => { if (!targetIds.length || bulkToggling) return; setBulkToggling(true); try { await Promise.all( targetIds.map((id) => fetch(`/api/providers/${id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ isActive }), }), ), ); await reconcileConnectionsPage(fetchConnections, page); } catch (error) { console.error("Error bulk toggling connections:", error); } finally { setBulkToggling(false); } }, [bulkToggling, fetchConnections, page], ); const handleDisableDepleted = () => { const ids = sortedConnections .filter((c) => (c.isActive ?? true) && isConnectionDepleted(c)) .map((c) => c.id); bulkSetActive(ids, false); }; const handleEnableAvailable = () => { const ids = sortedConnections .filter((c) => !(c.isActive ?? true) && !isConnectionDepleted(c)) .map((c) => c.id); bulkSetActive(ids, true); }; const selectedProviderLabel = providerFilter === "all" ? "All providers" : providerFilter; const hasEligibleConnections = totals.eligibleConnections > 0; const hasVisibleConnections = sortedConnections.length > 0; const emptyState = getConnectionsEmptyMessage( totals, providerFilter, accountFilter, ); const connectionsPageSummary = getConnectionsPaginationSummary(pagination); const isCustomPageSize = !ACCOUNT_PAGE_SIZE_OPTIONS.includes(pageSize); const pageSizeLabel = getPageSizeLabel(pageSize, isCustomPageSize); if (!connectionsLoading && !hasEligibleConnections) { return (
cloud_off

No Providers Connected

Connect to providers with OAuth to track your API quota limits and usage.

); } if (!connectionsLoading && !hasVisibleConnections) { return (
{emptyState.icon}

{emptyState.title}

{emptyState.description}

); } return (
{/* Header Controls */}
{providerMenuOpen && ( <>
{providerOptions.map((provider) => ( ))}
)}
{providerFilter === "codex" && ( )} {/* Bulk: disable depleted */} {/* Bulk: enable available */} {/* Auto-refresh toggle */} {/* Refresh all button */}
{/* Provider cards: 2 columns, compact */} {expiringFirst && (
Expiring-first currently reorders accounts inside the current page. Cross-page ordering still follows backend pagination.
)}
{sortedConnections.map((conn) => { const quota = quotaData[conn.id]; const isLoading = loading[conn.id]; const error = errors[conn.id]; // Use table layout for all providers const isInactive = conn.isActive === false; const rowBusy = deletingId === conn.id || togglingId === conn.id; return (

{conn.provider}

{getConnectionLabel(conn) ? (

{getConnectionLabel(conn)}

) : null}
handleToggleConnectionActive(conn.id, nextActive) } />
{isLoading ? (
progress_activity
) : error ? (
error

{error}

) : quota?.message ? (

{quota.message}

) : ( )}
); })}
{connectionsPageSummary}
setCustomPageSizeInput(event.target.value)} onBlur={() => { const parsedValue = Number.parseInt(customPageSizeInput, 10); if (!Number.isFinite(parsedValue)) { setCustomPageSizeInput(String(pageSize)); return; } const nextPageSize = Math.min(ACCOUNT_PAGE_SIZE_MAX, Math.max(1, parsedValue)); setPage(1); setPageSize(nextPageSize); setCustomPageSizeInput(String(nextPageSize)); }} onKeyDown={(event) => { if (event.key !== "Enter") return; const parsedValue = Number.parseInt(customPageSizeInput, 10); if (!Number.isFinite(parsedValue)) { setCustomPageSizeInput(String(pageSize)); return; } const nextPageSize = Math.min(ACCOUNT_PAGE_SIZE_MAX, Math.max(1, parsedValue)); setPage(1); setPageSize(nextPageSize); setCustomPageSizeInput(String(nextPageSize)); }} className="h-8 w-20 rounded-lg border border-black/10 bg-black/[0.02] px-2 text-xs text-text-primary outline-none transition-colors hover:bg-black/5 dark:border-white/10 dark:bg-white/[0.03] dark:hover:bg-white/10" aria-label="Custom accounts per page" placeholder="Custom" /> Page {pagination.page} / {pagination.totalPages}
{ setShowEditModal(false); setSelectedConnection(null); }} />
); }