diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js index 0090b7c6..45785c6f 100644 --- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js +++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js @@ -1,6 +1,7 @@ "use client"; import { useState, useEffect, useCallback, useRef, useMemo } from "react"; +import { useSearchParams, useRouter, usePathname } from "next/navigation"; import ProviderIcon from "@/shared/components/ProviderIcon"; import QuotaTable from "./QuotaTable"; import Toggle from "@/shared/components/Toggle"; @@ -152,6 +153,9 @@ function formatTimeRemaining(value) { export default function ProviderLimits() { const { copied, copy } = useCopyToClipboard(); + const searchParams = useSearchParams(); + const router = useRouter(); + const pathname = usePathname(); const [connections, setConnections] = useState([]); const [quotaData, setQuotaData] = useState({}); const [loading, setLoading] = useState({}); @@ -171,7 +175,26 @@ export default function ProviderLimits() { const [showEditModal, setShowEditModal] = useState(false); const [selectedConnection, setSelectedConnection] = useState(null); const [proxyPools, setProxyPools] = useState([]); - const [providerFilter, setProviderFilter] = useState("all"); + // Initialize providerFilter from URL ?provider= param so the page + // can be bookmarked / deep-linked to a specific provider (#4217). + const [providerFilter, _setProviderFilter] = useState( + () => searchParams?.get("provider") || "all" + ); + // Wrapper: keeps URL in sync with the selected provider so the view can be + // bookmarked. Replaces the URL without adding to browser history. + const setProviderFilter = useCallback((value) => { + _setProviderFilter(value); + try { + const params = new URLSearchParams(searchParams?.toString() || ""); + if (value === "all") { + params.delete("provider"); + } else { + params.set("provider", value); + } + const newUrl = params.toString() ? `${pathname}?${params.toString()}` : pathname; + router.replace(newUrl, { scroll: false }); + } catch { /* non-fatal: URL sync is best-effort */ } + }, [pathname, router, searchParams]); const [providerOptions, setProviderOptions] = useState([]); const [accountFilter, setAccountFilter] = useState("all"); const [quotaSortMode, setQuotaSortMode] = useState("default"); diff --git a/tests/unit/quota-tracker-url-param.test.js b/tests/unit/quota-tracker-url-param.test.js new file mode 100644 index 00000000..9baa5605 --- /dev/null +++ b/tests/unit/quota-tracker-url-param.test.js @@ -0,0 +1,53 @@ +/** + * Tests for #4217 — Quota Tracker URL ?provider= parameter support. + * + * Before this fix, visiting /dashboard/quota?provider=codex ignored the query + * parameter and always defaulted to "All providers". The dropdown selection + * updated only local state and did not update the URL. + * + * Fix: ProviderLimits initializes providerFilter from useSearchParams and + * wraps setProviderFilter to call router.replace when the filter changes. + * + * Because ProviderLimits is a JSX file we cannot import it in Vitest without + * a full Next.js setup. We verify the source text instead. + */ + +import { describe, it, expect } from "vitest"; +import fs from "fs"; +import path from "path"; + +const src = fs.readFileSync( + path.resolve("../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js"), + "utf-8" +); + +describe("ProviderLimits — URL ?provider= sync (#4217)", () => { + it("imports useSearchParams from next/navigation", () => { + expect(src).toContain("useSearchParams"); + expect(src).toContain("next/navigation"); + }); + + it("imports useRouter from next/navigation", () => { + expect(src).toContain("useRouter"); + }); + + it("imports usePathname from next/navigation", () => { + expect(src).toContain("usePathname"); + }); + + it("initializes providerFilter from searchParams.get('provider')", () => { + expect(src).toMatch(/searchParams.*get.*provider/); + }); + + it("calls router.replace when provider filter changes", () => { + expect(src).toContain("router.replace"); + }); + + it("removes ?provider from URL when 'all' is selected", () => { + expect(src).toContain("params.delete"); + }); + + it("sets ?provider in URL when a specific provider is selected", () => { + expect(src).toContain('params.set("provider"'); + }); +}); \ No newline at end of file