diff --git a/src/components/servers/TestConnectionPanel.test.tsx b/src/components/servers/TestConnectionPanel.test.tsx
index 582ae3c..dbd28e2 100644
--- a/src/components/servers/TestConnectionPanel.test.tsx
+++ b/src/components/servers/TestConnectionPanel.test.tsx
@@ -1,8 +1,9 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
-import { render, screen, waitFor } from "@testing-library/react";
+import { screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { http, HttpResponse } from "msw";
import { server } from "@/test/mocks/server";
+import { renderWithProviders as render } from "@/test/test-utils";
import { TestConnectionPanel } from "./TestConnectionPanel";
const TEST_ENDPOINT = "*/v1/mcp-servers/test";
diff --git a/src/components/servers/TestConnectionPanel.tsx b/src/components/servers/TestConnectionPanel.tsx
index 41f1514..56cfa8a 100644
--- a/src/components/servers/TestConnectionPanel.tsx
+++ b/src/components/servers/TestConnectionPanel.tsx
@@ -1,15 +1,16 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { z } from "zod";
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
-import { CircleCheck, CircleAlert, Copy, Info, Loader2 } from "lucide-react";
+import { CircleCheck, CircleAlert, Info, Loader2 } from "lucide-react";
+import { useIntl } from "react-intl";
import { Button } from "../ui/button";
+import { CopyButton } from "../ui/copy-button";
import { Input } from "../ui/input";
import { Label } from "../ui/label";
import { RadioGroup } from "../ui/radio-group";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
import { Textarea } from "../ui/textarea";
import { JsonHighlighter } from "../ui/json-highlighter";
-import { copyToClipboard } from "@/lib/clipboard";
import { serversApi } from "@/api/servers";
import type { GatewayTestRequest, GatewayTestResponse } from "@/generated/types";
import { parseApiError } from "@/lib/errorUtils";
@@ -123,6 +124,7 @@ function FieldLabel({
}
export function TestConnectionPanel({ serverUrl }: TestConnectionPanelProps) {
+ const intl = useIntl();
const [status, setStatus] = useState
("idle");
const [method, setMethod] = useState("Get");
const [url, setUrl] = useState(serverUrl);
@@ -439,16 +441,11 @@ export function TestConnectionPanel({ serverUrl }: TestConnectionPanelProps) {
aria-live="polite"
>
{responseBodyText && (
-
+ />
)}
diff --git a/src/components/tokens/TokenCreatedDialog.tsx b/src/components/tokens/TokenCreatedDialog.tsx
index 71b9119..a59b42c 100644
--- a/src/components/tokens/TokenCreatedDialog.tsx
+++ b/src/components/tokens/TokenCreatedDialog.tsx
@@ -1,5 +1,4 @@
-import type { RefObject } from "react";
-import { Copy } from "lucide-react";
+import { useRef, type RefObject } from "react";
import { useIntl } from "react-intl";
import {
@@ -10,7 +9,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
-import { copyToClipboard } from "@/lib/clipboard";
+import { CopyButton } from "@/components/ui/copy-button";
interface TokenCreatedDialogProps {
/** The raw access token to reveal, or null when the dialog is closed. */
@@ -34,6 +33,7 @@ interface TokenCreatedDialogProps {
export function TokenCreatedDialog({ token, onClose, returnFocusRef }: TokenCreatedDialogProps) {
const intl = useIntl();
const isOpen = token !== null;
+ const closeButtonRef = useRef
(null);
return (
-
+
diff --git a/src/components/tools/ToolForm.test.tsx b/src/components/tools/ToolForm.test.tsx
index 12f56f6..64a06fe 100644
--- a/src/components/tools/ToolForm.test.tsx
+++ b/src/components/tools/ToolForm.test.tsx
@@ -6,6 +6,7 @@ import { http, HttpResponse } from "msw";
import { server } from "@/test/mocks/server";
import { I18nProvider } from "@/i18n";
import { AuthProvider } from "@/auth/AuthContext";
+import { TooltipProvider } from "@/components/ui/tooltip";
import { ToolForm } from "./ToolForm";
import type { Tool } from "@/types/tool";
@@ -42,7 +43,9 @@ function renderForm(props: Partial> = {})
return render(
-
+
+
+
,
);
@@ -243,7 +246,9 @@ describe("ToolForm", () => {
render(
-
+
+
+
,
);
@@ -655,19 +660,15 @@ describe("ToolForm", () => {
}),
});
- // Find the second copy button (Output schema)
- const copyButtons = screen.getAllByRole("button", { name: /Copy output schema/i });
- const outputCopyBtn =
- copyButtons.find((btn) => btn.getAttribute("aria-label")?.includes("Copy output schema")) ||
- copyButtons[0];
+ const outputCopyBtn = screen.getByRole("button", { name: /Copy output schema/i });
- await user.click(outputCopyBtn!);
+ await user.click(outputCopyBtn);
expect(writeTextMock).toHaveBeenCalledWith('{\n "type": "string"\n}');
- // Wait for Check icon (aria-label="Copied!") to appear
+ // Wait for the copied confirmation to appear
await waitFor(() => {
- expect(screen.getByText(/copied/i)).toBeInTheDocument();
+ expect(screen.getByRole("status")).toHaveTextContent("Copied!");
});
// Restore clipboard
diff --git a/src/components/tools/ToolForm.tsx b/src/components/tools/ToolForm.tsx
index a8a8f81..9dd9216 100644
--- a/src/components/tools/ToolForm.tsx
+++ b/src/components/tools/ToolForm.tsx
@@ -1,8 +1,9 @@
import { useState, useEffect } from "react";
import { useIntl } from "react-intl";
-import { ChevronDown, Copy, RefreshCw, Wrench, Zap } from "lucide-react";
+import { ChevronDown, RefreshCw, Wrench, Zap } from "lucide-react";
import { BackButton } from "@/components/ui/back-button";
import { Button } from "@/components/ui/button";
+import { CopyButton } from "@/components/ui/copy-button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { ToolAdvancedSettings } from "@/components/tools/ToolAdvancedSettings";
@@ -63,8 +64,6 @@ interface ToolFormProps {
export function ToolForm({ isOpen, onToggle, onSuccess, tool }: ToolFormProps) {
const intl = useIntl();
- const [copiedInput, setCopiedInput] = useState(false);
- const [copiedOutput, setCopiedOutput] = useState(false);
const [showOverwriteConfirm, setShowOverwriteConfirm] = useState(false);
const isEditMode = Boolean(tool);
@@ -146,13 +145,6 @@ export function ToolForm({ isOpen, onToggle, onSuccess, tool }: ToolFormProps) {
setCustomHeaders,
]);
- const handleCopy = (text: string, setCopied: (v: boolean) => void) => {
- void navigator.clipboard.writeText(text).then(() => {
- setCopied(true);
- setTimeout(() => setCopied(false), 1500);
- });
- };
-
const handleCancel = () => {
onToggle();
};
@@ -422,21 +414,12 @@ export function ToolForm({ isOpen, onToggle, onSuccess, tool }: ToolFormProps) {
placeholder={'{\n "type": "object",\n "properties": {}\n}'}
spellCheck={false}
/>
-
+ />
@@ -456,21 +439,12 @@ export function ToolForm({ isOpen, onToggle, onSuccess, tool }: ToolFormProps) {
placeholder={'{\n "type": "object",\n "properties": {}\n}'}
spellCheck={false}
/>
-