feat(quota): sync ?provider= URL param with provider filter for bookmarkable deep links (#4395)

This commit is contained in:
semihisikman authored and decolua committed 2026-09-28 19:07:51 +07:00
1 parent 92c7bdd5bc
commit 65826d95f2
2 files changed
+77 -1

No files matched your search

@@ -1,6 +1,7 @@
"use client"; "use client";
import { useState, useEffect, useCallback, useRef, useMemo } from "react"; import { useState, useEffect, useCallback, useRef, useMemo } from "react";
import { useSearchParams, useRouter, usePathname } from "next/navigation";
import ProviderIcon from "@/shared/components/ProviderIcon"; import ProviderIcon from "@/shared/components/ProviderIcon";
import QuotaTable from "./QuotaTable"; import QuotaTable from "./QuotaTable";
import Toggle from "@/shared/components/Toggle"; import Toggle from "@/shared/components/Toggle";
@@ -152,6 +153,9 @@ function formatTimeRemaining(value) {
export default function ProviderLimits() { export default function ProviderLimits() {
const { copied, copy } = useCopyToClipboard(); const { copied, copy } = useCopyToClipboard();
const searchParams = useSearchParams();
const router = useRouter();
const pathname = usePathname();
const [connections, setConnections] = useState([]); const [connections, setConnections] = useState([]);
const [quotaData, setQuotaData] = useState({}); const [quotaData, setQuotaData] = useState({});
const [loading, setLoading] = useState({}); const [loading, setLoading] = useState({});
@@ -171,7 +175,26 @@ export default function ProviderLimits() {
const [showEditModal, setShowEditModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false);
const [selectedConnection, setSelectedConnection] = useState(null); const [selectedConnection, setSelectedConnection] = useState(null);
const [proxyPools, setProxyPools] = useState([]); 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 [providerOptions, setProviderOptions] = useState([]);
const [accountFilter, setAccountFilter] = useState("all"); const [accountFilter, setAccountFilter] = useState("all");
const [quotaSortMode, setQuotaSortMode] = useState("default"); const [quotaSortMode, setQuotaSortMode] = useState("default");
@@ -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"');
});
});