From 8ae6fed101ba6955c30fa534c5b37ed9e8a6b14b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 08:12:19 +0000 Subject: [PATCH] feat(ui): migrate SearchToolView + SSOSettings small components to shadcn - SearchToolView (SearchTools section): @tremor Button/Card/Grid/Text/ Title + antd Button + @heroicons ArrowLeftIcon \u2192 shadcn Button/ Card + plain Tailwind grid + lucide ArrowLeft. Replace 'left-2 z-10' positioning hack with simple inline copy buttons. - RedactableField (SSOSettings): antd Button \u2192 shadcn Button. Raw text-gray-* tokens replaced with text-muted-foreground. - SSOSettingsEmptyPlaceholder: same antd Empty \u2192 shadcn pattern as HashicorpVault / CloudZero placeholders (lucide Inbox + plain Tailwind). Test updates: - SearchToolView.test.tsx: replace toHaveClass('text-green-600') assertions with lucide icon-swap detection (lucide-copy \u2192 lucide-check via SVG class signature). Visual confirmation is now conveyed by icon change, not raw color class. Gates: TS \u2713 | Lint \u2713 | Vitest 19/19 SearchToolView + 101/101 SSO \u2713 | Build \u2713. Co-authored-by: yuneng-jiang --- .../SearchTools/SearchToolView.test.tsx | 13 +- .../components/SearchTools/SearchToolView.tsx | 128 +++++++++++------- .../SSOSettings/RedactableField.tsx | 22 +-- .../SSOSettingsEmptyPlaceholder.tsx | 34 ++--- 4 files changed, 117 insertions(+), 80 deletions(-) diff --git a/ui/litellm-dashboard/src/components/SearchTools/SearchToolView.test.tsx b/ui/litellm-dashboard/src/components/SearchTools/SearchToolView.test.tsx index bb04a04a992..04f22bf1adc 100644 --- a/ui/litellm-dashboard/src/components/SearchTools/SearchToolView.test.tsx +++ b/ui/litellm-dashboard/src/components/SearchTools/SearchToolView.test.tsx @@ -226,13 +226,18 @@ describe("SearchToolView", () => { const initialSvg = nameCopyButton!.querySelector("svg"); expect(initialSvg).toBeInTheDocument(); + // Pre-click: button has a copy icon (lucide CopyIcon). + const initialIconClass = initialSvg!.getAttribute("class") || ""; await user.click(nameCopyButton!); + // Post-click: button should swap to the check icon. Detect by waiting for + // the SVG class signature to change (lucide-copy \u2192 lucide-check). await waitFor(() => { const updatedSvg = nameCopyButton!.querySelector("svg"); expect(updatedSvg).toBeInTheDocument(); - expect(nameCopyButton).toHaveClass("text-green-600"); + const updatedIconClass = updatedSvg!.getAttribute("class") || ""; + expect(updatedIconClass).not.toBe(initialIconClass); }); }); @@ -257,7 +262,11 @@ describe("SearchToolView", () => { expect(copyToClipboard).toHaveBeenCalledWith("Test Search Tool"); }, { timeout: 3000 }); - expect(nameCopyButton).not.toHaveClass("text-green-600"); + // Post phase-1 the visual confirmation is conveyed via icon swap, + // not a text-green-600 class. Failed copy: icon does not swap. + const svg = nameCopyButton!.querySelector("svg"); + const cls = svg?.getAttribute("class") || ""; + expect(cls).toContain("lucide-copy"); }); it("should render SearchToolTester when accessToken is provided", () => { diff --git a/ui/litellm-dashboard/src/components/SearchTools/SearchToolView.tsx b/ui/litellm-dashboard/src/components/SearchTools/SearchToolView.tsx index ad88acd127f..da041528c30 100644 --- a/ui/litellm-dashboard/src/components/SearchTools/SearchToolView.tsx +++ b/ui/litellm-dashboard/src/components/SearchTools/SearchToolView.tsx @@ -1,8 +1,7 @@ import { copyToClipboard as utilCopyToClipboard } from "@/utils/dataUtils"; -import { ArrowLeftIcon } from "@heroicons/react/outline"; -import { Button, Card, Grid, Text, Title } from "@tremor/react"; -import { Button as AntdButton } from "antd"; -import { CheckIcon, CopyIcon } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { ArrowLeft, CheckIcon, CopyIcon } from "lucide-react"; import React, { useState } from "react"; import { SearchToolTester } from "./SearchToolTester"; import { AvailableSearchProvider, SearchTool } from "./types"; @@ -18,13 +17,15 @@ interface SearchToolViewProps { export const SearchToolView: React.FC = ({ searchTool, onBack, - isEditing, accessToken, availableProviders, }) => { const [copiedStates, setCopiedStates] = useState>({}); - const copyToClipboard = async (text: string | null | undefined, key: string) => { + const copyToClipboard = async ( + text: string | null | undefined, + key: string, + ) => { const success = await utilCopyToClipboard(text); if (success) { setCopiedStates((prev) => ({ ...prev, [key]: true })); @@ -35,7 +36,9 @@ export const SearchToolView: React.FC = ({ }; const getProviderDisplayName = (providerName: string) => { - const provider = availableProviders.find(p => p.provider_name === providerName); + const provider = availableProviders.find( + (p) => p.provider_name === providerName, + ); return provider?.ui_friendly_name || providerName; }; @@ -43,73 +46,98 @@ export const SearchToolView: React.FC = ({
- -
- {searchTool.search_tool_name} - : } - onClick={() => copyToClipboard(searchTool.search_tool_name, "search-tool-name")} - className={`left-2 z-10 transition-all duration-200 ${copiedStates["search-tool-name"] - ? "text-green-600 bg-green-50 border-green-200" - : "text-gray-500 hover:text-gray-700 hover:bg-gray-100" - }`} - /> +
+

+ {searchTool.search_tool_name} +

+
-
- {searchTool.search_tool_id} - : } - onClick={() => copyToClipboard(searchTool.search_tool_id, "search-tool-id")} - className={`left-2 z-10 transition-all duration-200 ${copiedStates["search-tool-id"] - ? "text-green-600 bg-green-50 border-green-200" - : "text-gray-500 hover:text-gray-700 hover:bg-gray-100" - }`} - /> +
+ + {searchTool.search_tool_id} + +
- - - Provider +
+ +

Provider

- {getProviderDisplayName(searchTool.litellm_params.search_provider)} +

+ {getProviderDisplayName( + searchTool.litellm_params.search_provider, + )} +

- - API Key + +

API Key

- {searchTool.litellm_params.api_key ? "****" : "Not set"} +

+ {searchTool.litellm_params.api_key ? "****" : "Not set"} +

- - Created At + +

Created At

- - {searchTool.created_at ? new Date(searchTool.created_at).toLocaleString() : "Unknown"} - +

+ {searchTool.created_at + ? new Date(searchTool.created_at).toLocaleString() + : "Unknown"} +

- +
{searchTool.search_tool_info?.description && ( - - Description + +

Description

- {searchTool.search_tool_info.description} +

{searchTool.search_tool_info.description}

)} - {/* Search Tool Tester */}
{accessToken && ( = ({
); }; - - diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/RedactableField.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/RedactableField.tsx index 44fef5cc7f8..4ea215f8867 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/RedactableField.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/RedactableField.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { Button } from "antd"; +import { Button } from "@/components/ui/button"; import { Eye, EyeOff } from "lucide-react"; export default function RedactableField({ @@ -13,7 +13,7 @@ export default function RedactableField({ return (
- + {value ? ( isHidden ? ( "•".repeat(value.length) @@ -21,17 +21,23 @@ export default function RedactableField({ value ) ) : ( - Not configured + Not configured )} {value && ( )}
); diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/SSOSettingsEmptyPlaceholder.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/SSOSettingsEmptyPlaceholder.tsx index fc315493a54..270207860bc 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/SSOSettingsEmptyPlaceholder.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/SSOSettingsEmptyPlaceholder.tsx @@ -1,30 +1,26 @@ -import { Empty, Typography, Button } from "antd"; - -const { Title, Paragraph } = Typography; +import { Button } from "@/components/ui/button"; +import { Inbox } from "lucide-react"; interface SSOSettingsEmptyPlaceholderProps { onAdd: () => void; } -export default function SSOSettingsEmptyPlaceholder({ onAdd }: SSOSettingsEmptyPlaceholderProps) { +export default function SSOSettingsEmptyPlaceholder({ + onAdd, +}: SSOSettingsEmptyPlaceholderProps) { return ( -
- - No SSO Configuration Found - - Configure Single Sign-On (SSO) to enable seamless authentication for your team members using your identity - provider. - -
- } - > - - +
); }