diff --git a/ui/litellm-dashboard/src/components/organization/organization_view.tsx b/ui/litellm-dashboard/src/components/organization/organization_view.tsx index b7421966e61..2d9d010160f 100644 --- a/ui/litellm-dashboard/src/components/organization/organization_view.tsx +++ b/ui/litellm-dashboard/src/components/organization/organization_view.tsx @@ -27,6 +27,7 @@ import ObjectPermissionsView from "../object_permissions_view"; import { CopyableBadge } from "../shared/copyable_badge"; import NumericalInput from "../shared/numerical_input"; import MemberModal from "../team/EditMembership"; +import { TooltipProvider } from "../ui/tooltip"; import VectorStoreSelector from "../vector_store_management/VectorStoreSelector"; interface OrganizationInfoProps { @@ -302,11 +303,13 @@ const OrganizationInfoView: React.FC = ({ Teams -
- {orgData.teams?.map((team, index) => ( - - ))} -
+ +
+ {orgData.teams?.map((team, index) => ( + + ))} +
+
({ copyToClipboardMock: vi.fn() })); +const writeText = vi.fn().mockResolvedValue(undefined); -vi.mock("@/utils/dataUtils", async (importOriginal) => ({ - ...(await importOriginal()), - copyToClipboard: copyToClipboardMock, -})); +beforeEach(() => { + writeText.mockClear(); + Object.defineProperty(navigator, "clipboard", { + configurable: true, + value: { writeText }, + }); +}); describe("CopyableBadge", () => { it("renders the value and truncates it", () => { @@ -19,14 +21,12 @@ describe("CopyableBadge", () => { expect(label.className).toContain("max-w-[220px]"); }); - it("copies the full value when clicked", async () => { - copyToClipboardMock.mockResolvedValue(true); - const user = userEvent.setup(); - render(); + it("copies the full value when the copy button is clicked", async () => { + render(); - await user.click(screen.getByTestId("team-badge")); + fireEvent.click(screen.getByRole("button", { name: "Copy team-alias-long-name" })); - expect(copyToClipboardMock).toHaveBeenCalledWith("team-alias-long-name"); + await waitFor(() => expect(writeText).toHaveBeenCalledWith("team-alias-long-name")); }); it("exposes a copy affordance via accessible label", () => { diff --git a/ui/litellm-dashboard/src/components/shared/copyable_badge.tsx b/ui/litellm-dashboard/src/components/shared/copyable_badge.tsx index 463b23462d3..004325b2c79 100644 --- a/ui/litellm-dashboard/src/components/shared/copyable_badge.tsx +++ b/ui/litellm-dashboard/src/components/shared/copyable_badge.tsx @@ -1,12 +1,10 @@ "use client"; import { Tag } from "antd"; -import { Check, Copy } from "lucide-react"; -import * as React from "react"; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import CopyButton from "@/components/shared/CopyButton"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { cn } from "@/lib/cva.config"; -import { copyToClipboard } from "@/utils/dataUtils"; interface CopyableBadgeProps { value: string; @@ -21,37 +19,15 @@ export function CopyableBadge({ maxWidthClassName = "max-w-[220px]", dataTestId, }: CopyableBadgeProps) { - const [copied, setCopied] = React.useState(false); - - const handleCopy = async () => { - const success = await copyToClipboard(value); - if (success) { - setCopied(true); - setTimeout(() => setCopied(false), 2000); - } - }; - return ( - + - - - {value} - + + {value} - - - {copied ? : } - {value} - {copied ? "Copied" : "Click to copy"} - - + {value} - + + ); }