Skip to content

Commit 8586d63

Browse files
Add shadcn alert to match project design
1 parent 539de0f commit 8586d63

File tree

9 files changed

+611
-32
lines changed

9 files changed

+611
-32
lines changed

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@radix-ui/react-select": "^2.1.2",
3333
"@radix-ui/react-slot": "^1.1.0",
3434
"@radix-ui/react-tabs": "^1.1.1",
35+
"@radix-ui/react-toast": "^1.2.6",
3536
"@types/prismjs": "^1.26.5",
3637
"class-variance-authority": "^0.7.0",
3738
"clsx": "^2.1.1",
@@ -42,7 +43,6 @@
4243
"react": "^18.3.1",
4344
"react-dom": "^18.3.1",
4445
"react-simple-code-editor": "^0.14.1",
45-
"react-toastify": "^10.0.6",
4646
"serve-handler": "^6.1.6",
4747
"tailwind-merge": "^2.5.3",
4848
"tailwindcss-animate": "^1.0.7",

client/src/App.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import {
3333
MessageSquare,
3434
} from "lucide-react";
3535

36-
import { toast } from "react-toastify";
3736
import { z } from "zod";
3837
import "./App.css";
3938
import ConsoleTab from "./components/ConsoleTab";
@@ -47,13 +46,14 @@ import Sidebar from "./components/Sidebar";
4746
import ToolsTab from "./components/ToolsTab";
4847
import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants";
4948
import { InspectorConfig } from "./lib/configurationTypes";
50-
49+
import { useToast } from "@/hooks/use-toast";
5150
const params = new URLSearchParams(window.location.search);
5251
const PROXY_PORT = params.get("proxyPort") ?? "6277";
5352
const PROXY_SERVER_URL = `http://${window.location.hostname}:${PROXY_PORT}`;
5453
const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1";
5554

5655
const App = () => {
56+
const { toast } = useToast();
5757
// Handle OAuth callback route
5858
const [resources, setResources] = useState<Resource[]>([]);
5959
const [resourceTemplates, setResourceTemplates] = useState<
@@ -208,11 +208,14 @@ const App = () => {
208208
newUrl.searchParams.delete("serverUrl");
209209
window.history.replaceState({}, "", newUrl.toString());
210210
// Show success toast for OAuth
211-
toast.success("Successfully authenticated with OAuth");
211+
toast({
212+
title: "Success",
213+
description: "Successfully authenticated with OAuth",
214+
});
212215
// Connect to the server
213216
connectMcpServer();
214217
}
215-
}, [connectMcpServer]);
218+
}, [connectMcpServer, toast]);
216219

217220
useEffect(() => {
218221
fetch(`${PROXY_SERVER_URL}/config`)

client/src/components/ToolsTab.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { Copy, Send, CheckCheck } from "lucide-react";
1717
import { useCallback, useEffect, useState } from "react";
1818
import ListPane from "./ListPane";
1919
import JsonView from "./JsonView";
20-
import { toast } from "react-toastify";
20+
import { useToast } from "@/hooks/use-toast";
2121

2222
const ToolsTab = ({
2323
tools,
@@ -39,6 +39,7 @@ const ToolsTab = ({
3939
nextCursor: ListToolsResult["nextCursor"];
4040
error: string | null;
4141
}) => {
42+
const { toast } = useToast();
4243
const [params, setParams] = useState<Record<string, unknown>>({});
4344
useEffect(() => {
4445
setParams({});
@@ -54,11 +55,13 @@ const ToolsTab = ({
5455
setCopied(false);
5556
}, 500);
5657
} catch (error) {
57-
toast.error(
58-
`There was an error coping result into the clipboard: ${error instanceof Error ? error.message : String(error)}`,
59-
);
58+
toast({
59+
title: "Error",
60+
description: `There was an error coping result into the clipboard: ${error instanceof Error ? error.message : String(error)}`,
61+
variant: "destructive",
62+
});
6063
}
61-
}, [toolResult]);
64+
}, [toast, toolResult]);
6265

6366
const renderToolResult = () => {
6467
if (!toolResult) return null;

client/src/components/ui/toast.tsx

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import * as React from "react";
2+
import * as ToastPrimitives from "@radix-ui/react-toast";
3+
import { cva, type VariantProps } from "class-variance-authority";
4+
import { cn } from "@/lib/utils";
5+
import { Cross2Icon } from "@radix-ui/react-icons";
6+
7+
const ToastProvider = ToastPrimitives.Provider;
8+
9+
const ToastViewport = React.forwardRef<
10+
React.ElementRef<typeof ToastPrimitives.Viewport>,
11+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
12+
>(({ className, ...props }, ref) => (
13+
<ToastPrimitives.Viewport
14+
ref={ref}
15+
className={cn(
16+
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
17+
className,
18+
)}
19+
{...props}
20+
/>
21+
));
22+
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
23+
24+
const toastVariants = cva(
25+
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
26+
{
27+
variants: {
28+
variant: {
29+
default: "border bg-background text-foreground",
30+
destructive:
31+
"destructive group border-destructive bg-destructive text-destructive-foreground",
32+
},
33+
},
34+
defaultVariants: {
35+
variant: "default",
36+
},
37+
},
38+
);
39+
40+
const Toast = React.forwardRef<
41+
React.ElementRef<typeof ToastPrimitives.Root>,
42+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
43+
VariantProps<typeof toastVariants>
44+
>(({ className, variant, ...props }, ref) => {
45+
return (
46+
<ToastPrimitives.Root
47+
ref={ref}
48+
className={cn(toastVariants({ variant }), className)}
49+
{...props}
50+
/>
51+
);
52+
});
53+
Toast.displayName = ToastPrimitives.Root.displayName;
54+
55+
const ToastAction = React.forwardRef<
56+
React.ElementRef<typeof ToastPrimitives.Action>,
57+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
58+
>(({ className, ...props }, ref) => (
59+
<ToastPrimitives.Action
60+
ref={ref}
61+
className={cn(
62+
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
63+
className,
64+
)}
65+
{...props}
66+
/>
67+
));
68+
ToastAction.displayName = ToastPrimitives.Action.displayName;
69+
70+
const ToastClose = React.forwardRef<
71+
React.ElementRef<typeof ToastPrimitives.Close>,
72+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
73+
>(({ className, ...props }, ref) => (
74+
<ToastPrimitives.Close
75+
ref={ref}
76+
className={cn(
77+
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
78+
className,
79+
)}
80+
toast-close=""
81+
{...props}
82+
>
83+
<Cross2Icon className="h-4 w-4" />
84+
</ToastPrimitives.Close>
85+
));
86+
ToastClose.displayName = ToastPrimitives.Close.displayName;
87+
88+
const ToastTitle = React.forwardRef<
89+
React.ElementRef<typeof ToastPrimitives.Title>,
90+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
91+
>(({ className, ...props }, ref) => (
92+
<ToastPrimitives.Title
93+
ref={ref}
94+
className={cn("text-sm font-semibold [&+div]:text-xs", className)}
95+
{...props}
96+
/>
97+
));
98+
ToastTitle.displayName = ToastPrimitives.Title.displayName;
99+
100+
const ToastDescription = React.forwardRef<
101+
React.ElementRef<typeof ToastPrimitives.Description>,
102+
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
103+
>(({ className, ...props }, ref) => (
104+
<ToastPrimitives.Description
105+
ref={ref}
106+
className={cn("text-sm opacity-90", className)}
107+
{...props}
108+
/>
109+
));
110+
ToastDescription.displayName = ToastPrimitives.Description.displayName;
111+
112+
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
113+
114+
type ToastActionElement = React.ReactElement<typeof ToastAction>;
115+
116+
export {
117+
type ToastProps,
118+
type ToastActionElement,
119+
ToastProvider,
120+
ToastViewport,
121+
Toast,
122+
ToastTitle,
123+
ToastDescription,
124+
ToastClose,
125+
ToastAction,
126+
};

client/src/components/ui/toaster.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useToast } from "@/hooks/use-toast";
2+
import {
3+
Toast,
4+
ToastClose,
5+
ToastDescription,
6+
ToastProvider,
7+
ToastTitle,
8+
ToastViewport,
9+
} from "@/components/ui/toast";
10+
11+
export function Toaster() {
12+
const { toasts } = useToast();
13+
14+
return (
15+
<ToastProvider>
16+
{toasts.map(function ({ id, title, description, action, ...props }) {
17+
return (
18+
<Toast key={id} {...props}>
19+
<div className="grid gap-1">
20+
{title && <ToastTitle>{title}</ToastTitle>}
21+
{description && (
22+
<ToastDescription>{description}</ToastDescription>
23+
)}
24+
</div>
25+
{action}
26+
<ToastClose />
27+
</Toast>
28+
);
29+
})}
30+
<ToastViewport />
31+
</ToastProvider>
32+
);
33+
}

0 commit comments

Comments
 (0)