From a17980c744f9945ba0f48f1a26d8d63f5d05ee91 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 31 Dec 2025 10:42:34 -0800 Subject: [PATCH] feat: hide new badges --- .../hooks/useDisableShowNewBadge.ts | 35 ++++ .../UsagePage/components/UsagePageView.tsx | 14 +- .../common_components/NewBadge.test.tsx | 52 ++++++ .../components/common_components/NewBadge.tsx | 17 +- .../src/components/navbar.tsx | 40 ++++- .../src/utils/localStorageUtils.test.ts | 157 ++++++++++++++++++ .../src/utils/localStorageUtils.ts | 33 ++++ 7 files changed, 331 insertions(+), 17 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowNewBadge.ts create mode 100644 ui/litellm-dashboard/src/components/common_components/NewBadge.test.tsx create mode 100644 ui/litellm-dashboard/src/utils/localStorageUtils.test.ts create mode 100644 ui/litellm-dashboard/src/utils/localStorageUtils.ts diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowNewBadge.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowNewBadge.ts new file mode 100644 index 00000000000..d0a618e27ba --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowNewBadge.ts @@ -0,0 +1,35 @@ +// hooks/useDisableShowNewBadge.ts +import { useSyncExternalStore } from "react"; +import { getLocalStorageItem } from "@/utils/localStorageUtils"; +import { LOCAL_STORAGE_EVENT } from "@/utils/localStorageUtils"; + +function subscribe(callback: () => void) { + const onStorage = (e: StorageEvent) => { + if (e.key === "disableShowNewBadge") { + callback(); + } + }; + + const onCustom = (e: Event) => { + const { key } = (e as CustomEvent).detail; + if (key === "disableShowNewBadge") { + callback(); + } + }; + + window.addEventListener("storage", onStorage); + window.addEventListener(LOCAL_STORAGE_EVENT, onCustom); + + return () => { + window.removeEventListener("storage", onStorage); + window.removeEventListener(LOCAL_STORAGE_EVENT, onCustom); + }; +} + +function getSnapshot() { + return getLocalStorageItem("disableShowNewBadge") === "true"; +} + +export function useDisableShowNewBadge() { + return useSyncExternalStore(subscribe, getSnapshot); +} diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx index 920955138b9..a7107814113 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/UsagePageView.tsx @@ -27,7 +27,7 @@ import { Text, Title, } from "@tremor/react"; -import { Alert, Badge } from "antd"; +import { Alert } from "antd"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { useAgents } from "@/app/(dashboard)/hooks/agents/useAgents"; @@ -419,13 +419,11 @@ const UsagePage: React.FC = ({ teams, organizations }) => {
- - setUsageView(value)} - isAdmin={all_admin_roles.includes(userRole || "")} - /> - + setUsageView(value)} + isAdmin={all_admin_roles.includes(userRole || "")} + />
{/* Your Usage Panel */} diff --git a/ui/litellm-dashboard/src/components/common_components/NewBadge.test.tsx b/ui/litellm-dashboard/src/components/common_components/NewBadge.test.tsx new file mode 100644 index 00000000000..a24b52db6b8 --- /dev/null +++ b/ui/litellm-dashboard/src/components/common_components/NewBadge.test.tsx @@ -0,0 +1,52 @@ +import { render, screen } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach } from "vitest"; +import NewBadge from "./NewBadge"; + +// Mock the hook directly +vi.mock("@/app/(dashboard)/hooks/useDisableShowNewBadge", () => ({ + useDisableShowNewBadge: vi.fn(), +})); + +import { useDisableShowNewBadge } from "@/app/(dashboard)/hooks/useDisableShowNewBadge"; + +const mockUseDisableShowNewBadge = vi.mocked(useDisableShowNewBadge); + +describe("NewBadge", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should render the badge when disableShowNewBadge is false", () => { + mockUseDisableShowNewBadge.mockReturnValue(false); + + render(Test Content); + + expect(screen.getByText("New")).toBeInTheDocument(); + expect(screen.getByText("Test Content")).toBeInTheDocument(); + }); + + it("should render the badge when disableShowNewBadge is not set", () => { + mockUseDisableShowNewBadge.mockReturnValue(false); + + render(); + + expect(screen.getByText("New")).toBeInTheDocument(); + }); + + it("should render only children when disableShowNewBadge is true", () => { + mockUseDisableShowNewBadge.mockReturnValue(true); + + render(Test Content); + + expect(screen.queryByText("New")).not.toBeInTheDocument(); + expect(screen.getByText("Test Content")).toBeInTheDocument(); + }); + + it("should render nothing when disableShowNewBadge is true and no children", () => { + mockUseDisableShowNewBadge.mockReturnValue(true); + + const { container } = render(); + + expect(container.firstChild).toBeNull(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/common_components/NewBadge.tsx b/ui/litellm-dashboard/src/components/common_components/NewBadge.tsx index 2a3d1a51248..97cdea8cfbb 100644 --- a/ui/litellm-dashboard/src/components/common_components/NewBadge.tsx +++ b/ui/litellm-dashboard/src/components/common_components/NewBadge.tsx @@ -1,5 +1,18 @@ import { Badge } from "antd"; +import { useDisableShowNewBadge } from "@/app/(dashboard)/hooks/useDisableShowNewBadge"; -export default function NewBadge() { - return ; +export default function NewBadge({ children }: { children?: React.ReactNode }) { + const disableShowNewBadge = useDisableShowNewBadge(); + + if (disableShowNewBadge) { + return children ? <>{children} : null; + } + + return children ? ( + + {children} + + ) : ( + + ); } diff --git a/ui/litellm-dashboard/src/components/navbar.tsx b/ui/litellm-dashboard/src/components/navbar.tsx index e032649ae29..23b31ec4821 100644 --- a/ui/litellm-dashboard/src/components/navbar.tsx +++ b/ui/litellm-dashboard/src/components/navbar.tsx @@ -1,7 +1,7 @@ import Link from "next/link"; import React, { useState, useEffect } from "react"; import type { MenuProps } from "antd"; -import { Dropdown, Tooltip } from "antd"; +import { Dropdown, Switch, Tooltip } from "antd"; import { getProxyBaseUrl } from "@/components/networking"; import { UserOutlined, @@ -13,8 +13,10 @@ import { MenuUnfoldOutlined, } from "@ant-design/icons"; import { clearTokenCookies } from "@/utils/cookieUtils"; +import { getLocalStorageItem, setLocalStorageItem, removeLocalStorageItem } from "@/utils/localStorageUtils"; import { fetchProxySettings } from "@/utils/proxyUtils"; import { useTheme } from "@/contexts/ThemeContext"; +import { emitLocalStorageChange } from "@/utils/localStorageUtils"; interface NavbarProps { userID: string | null; @@ -44,6 +46,7 @@ const Navbar: React.FC = ({ const baseUrl = getProxyBaseUrl(); const [logoutUrl, setLogoutUrl] = useState(""); const [version, setVersion] = useState(""); + const [disableShowNewBadge, setDisableShowNewBadge] = useState(false); const { logoUrl } = useTheme(); // Simple logo URL: use custom logo if available, otherwise default @@ -79,6 +82,11 @@ const Navbar: React.FC = ({ initializeProxySettings(); }, [accessToken]); + useEffect(() => { + const storedValue = getLocalStorageItem("disableShowNewBadge"); + setDisableShowNewBadge(storedValue === "true"); + }, []); + useEffect(() => { setLogoutUrl(proxySettings?.PROXY_LOGOUT_URL || ""); }, [proxySettings]); @@ -129,6 +137,28 @@ const Navbar: React.FC = ({ {userEmail || "Unknown"}
+
e.stopPropagation()} + > + Hide New Feature Indicators + { + setDisableShowNewBadge(checked); + if (checked) { + setLocalStorageItem("disableShowNewBadge", "true"); + emitLocalStorageChange("disableShowNewBadge"); + } else { + removeLocalStorageItem("disableShowNewBadge"); + emitLocalStorageChange("disableShowNewBadge"); + } + }} + aria-label="Toggle hide new feature indicators" + /> +
), @@ -148,11 +178,7 @@ const Navbar: React.FC = ({