diff --git a/ui/web/src/components/shared/confirm-delete-dialog.tsx b/ui/web/src/components/shared/confirm-delete-dialog.tsx
new file mode 100644
index 00000000..0d1a3346
--- /dev/null
+++ b/ui/web/src/components/shared/confirm-delete-dialog.tsx
@@ -0,0 +1,75 @@
+import { useState, useEffect } from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+
+interface ConfirmDeleteDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ title: string;
+ description: string;
+ confirmValue: string;
+ confirmLabel?: string;
+ onConfirm: () => void;
+ loading?: boolean;
+}
+
+export function ConfirmDeleteDialog({
+ open,
+ onOpenChange,
+ title,
+ description,
+ confirmValue,
+ confirmLabel = "Delete",
+ onConfirm,
+ loading,
+}: ConfirmDeleteDialogProps) {
+ const [inputValue, setInputValue] = useState("");
+
+ useEffect(() => {
+ if (!open) setInputValue("");
+ }, [open]);
+
+ const isMatch = inputValue.toLowerCase() === confirmValue.toLowerCase();
+
+ return (
+
+ );
+}
diff --git a/ui/web/src/pages/agents/agent-detail/agent-detail-page.tsx b/ui/web/src/pages/agents/agent-detail/agent-detail-page.tsx
index 53488368..a7ba76f1 100644
--- a/ui/web/src/pages/agents/agent-detail/agent-detail-page.tsx
+++ b/ui/web/src/pages/agents/agent-detail/agent-detail-page.tsx
@@ -11,10 +11,9 @@ import { AgentFilesTab } from "./agent-files-tab";
import { AgentSharesTab } from "./agent-shares-tab";
import { AgentLinksTab } from "./agent-links-tab";
import { AgentSkillsTab } from "./agent-skills-tab";
-import { AgentDangerTab } from "./agent-danger-tab";
import { SummoningModal } from "../summoning-modal";
-import { ConfirmDialog } from "@/components/shared/confirm-dialog";
-import { DeferredSpinner } from "@/components/shared/loading-skeleton";
+import { ConfirmDeleteDialog } from "@/components/shared/confirm-delete-dialog";
+import { DetailPageSkeleton } from "@/components/shared/loading-skeleton";
interface AgentDetailPageProps {
agentId: string;
@@ -38,7 +37,7 @@ function agentSubtitle(agent: { display_name?: string; agent_key: string; id: st
}
export function AgentDetailPage({ agentId, onBack }: AgentDetailPageProps) {
- const { agent, files, loading, updateAgent, getFile, setFile, regenerateAgent, resummonAgent, deleteAgent, refresh } =
+ const { agent, files, loading, updateAgent, getFile, setFile, regenerateAgent, resummonAgent, refresh } =
useAgentDetail(agentId);
const { deleteAgent: deleteAgentById } = useAgents();
const [summoningOpen, setSummoningOpen] = useState(false);
@@ -62,14 +61,7 @@ export function AgentDetailPage({ agentId, onBack }: AgentDetailPageProps) {
};
if (loading || !agent) {
- return (
-
- );
+ return ;
}
const title = agentDisplayName(agent);
@@ -131,7 +123,6 @@ export function AgentDetailPage({ agentId, onBack }: AgentDetailPageProps) {
Shares
Links
Skills
- Danger
@@ -165,9 +156,6 @@ export function AgentDetailPage({ agentId, onBack }: AgentDetailPageProps) {
-
-
-
@@ -180,13 +168,13 @@ export function AgentDetailPage({ agentId, onBack }: AgentDetailPageProps) {
onResummon={async () => { await resummonAgent(); }}
/>
- {
await deleteAgentById(agentId);
setDeleteOpen(false);
diff --git a/ui/web/src/pages/channels/channels-page.tsx b/ui/web/src/pages/channels/channels-page.tsx
index d3a8a708..149f361d 100644
--- a/ui/web/src/pages/channels/channels-page.tsx
+++ b/ui/web/src/pages/channels/channels-page.tsx
@@ -8,7 +8,7 @@ import { EmptyState } from "@/components/shared/empty-state";
import { TableSkeleton } from "@/components/shared/loading-skeleton";
import { SearchInput } from "@/components/shared/search-input";
import { Pagination } from "@/components/shared/pagination";
-import { ConfirmDialog } from "@/components/shared/confirm-dialog";
+import { ConfirmDeleteDialog } from "@/components/shared/confirm-delete-dialog";
import { useChannels } from "./hooks/use-channels";
import { useChannelInstances, type ChannelInstanceData, type ChannelInstanceInput } from "./hooks/use-channel-instances";
import { ChannelInstanceFormDialog } from "./channel-instance-form-dialog";
@@ -264,13 +264,13 @@ export function ChannelsPage() {
onUpdate={handleUpdate}
/>
- !v && setDeleteTarget(null)}
title="Delete Channel Instance"
- description={`Are you sure you want to delete "${deleteTarget?.display_name || deleteTarget?.name}"?`}
+ description={`Are you sure you want to delete "${deleteTarget?.display_name || deleteTarget?.name}"? This action cannot be undone.`}
+ confirmValue={deleteTarget?.display_name || deleteTarget?.name || ""}
confirmLabel="Delete"
- variant="destructive"
onConfirm={handleDelete}
loading={deleteLoading}
/>
diff --git a/ui/web/src/pages/mcp/mcp-page.tsx b/ui/web/src/pages/mcp/mcp-page.tsx
index a244e1c3..c19e3990 100644
--- a/ui/web/src/pages/mcp/mcp-page.tsx
+++ b/ui/web/src/pages/mcp/mcp-page.tsx
@@ -7,7 +7,7 @@ import { EmptyState } from "@/components/shared/empty-state";
import { SearchInput } from "@/components/shared/search-input";
import { Pagination } from "@/components/shared/pagination";
import { TableSkeleton } from "@/components/shared/loading-skeleton";
-import { ConfirmDialog } from "@/components/shared/confirm-dialog";
+import { ConfirmDeleteDialog } from "@/components/shared/confirm-delete-dialog";
import { useMCP, type MCPServerData, type MCPServerInput } from "./hooks/use-mcp";
import { MCPFormDialog } from "./mcp-form-dialog";
import { MCPGrantsDialog } from "./mcp-grants-dialog";
@@ -226,13 +226,13 @@ export function MCPPage() {
/>
)}
- !open && setDeleteTarget(null)}
title="Delete MCP Server"
description={`Are you sure you want to delete "${deleteTarget?.display_name || deleteTarget?.name}"? This will also remove all associated grants.`}
+ confirmValue={deleteTarget?.display_name || deleteTarget?.name || ""}
confirmLabel="Delete"
- variant="destructive"
onConfirm={handleDelete}
loading={deleteLoading}
/>
diff --git a/ui/web/src/pages/providers/providers-page.tsx b/ui/web/src/pages/providers/providers-page.tsx
index b7755fd5..04583872 100644
--- a/ui/web/src/pages/providers/providers-page.tsx
+++ b/ui/web/src/pages/providers/providers-page.tsx
@@ -7,7 +7,7 @@ import { EmptyState } from "@/components/shared/empty-state";
import { SearchInput } from "@/components/shared/search-input";
import { Pagination } from "@/components/shared/pagination";
import { TableSkeleton } from "@/components/shared/loading-skeleton";
-import { ConfirmDialog } from "@/components/shared/confirm-dialog";
+import { ConfirmDeleteDialog } from "@/components/shared/confirm-delete-dialog";
import { useProviders, type ProviderData, type ProviderInput } from "./hooks/use-providers";
import { ProviderFormDialog } from "./provider-form-dialog";
import { useMinLoading } from "@/hooks/use-min-loading";
@@ -202,13 +202,13 @@ export function ProvidersPage() {
existingProviders={providers}
/>
- !v && setDeleteTarget(null)}
title="Delete Provider"
- description={`Are you sure you want to delete "${deleteTarget?.display_name || deleteTarget?.name}"?`}
+ description={`Are you sure you want to delete "${deleteTarget?.display_name || deleteTarget?.name}"? This action cannot be undone.`}
+ confirmValue={deleteTarget?.display_name || deleteTarget?.name || ""}
confirmLabel="Delete"
- variant="destructive"
onConfirm={handleDelete}
loading={deleteLoading}
/>
diff --git a/ui/web/src/pages/skills/skills-page.tsx b/ui/web/src/pages/skills/skills-page.tsx
index 0c48959f..d36e5b1a 100644
--- a/ui/web/src/pages/skills/skills-page.tsx
+++ b/ui/web/src/pages/skills/skills-page.tsx
@@ -7,7 +7,7 @@ import { EmptyState } from "@/components/shared/empty-state";
import { SearchInput } from "@/components/shared/search-input";
import { Pagination } from "@/components/shared/pagination";
import { TableSkeleton } from "@/components/shared/loading-skeleton";
-import { ConfirmDialog } from "@/components/shared/confirm-dialog";
+import { ConfirmDeleteDialog } from "@/components/shared/confirm-delete-dialog";
import { useSkills, type SkillInfo } from "./hooks/use-skills";
import { SkillDetailDialog } from "./skill-detail-dialog";
import { SkillUploadDialog } from "./skill-upload-dialog";
@@ -176,13 +176,13 @@ export function SkillsPage() {
onUpload={handleUpload}
/>
- !open && setDeleteTarget(null)}
title="Delete Skill"
description={`Are you sure you want to delete "${deleteTarget?.name}"? This action cannot be undone.`}
+ confirmValue={deleteTarget?.name || ""}
confirmLabel="Delete"
- variant="destructive"
onConfirm={handleDelete}
loading={deleteLoading}
/>
diff --git a/ui/web/src/pages/teams/team-detail-page.tsx b/ui/web/src/pages/teams/team-detail-page.tsx
index 48f026d5..855fc746 100644
--- a/ui/web/src/pages/teams/team-detail-page.tsx
+++ b/ui/web/src/pages/teams/team-detail-page.tsx
@@ -3,8 +3,8 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { ArrowLeft, Users, Trash2 } from "lucide-react";
-import { DeferredSpinner } from "@/components/shared/loading-skeleton";
-import { ConfirmDialog } from "@/components/shared/confirm-dialog";
+import { DetailPageSkeleton } from "@/components/shared/loading-skeleton";
+import { ConfirmDeleteDialog } from "@/components/shared/confirm-delete-dialog";
import { useTeams } from "./hooks/use-teams";
import { TeamMembersTab } from "./team-members-tab";
import { TeamTasksTab } from "./team-tasks-tab";
@@ -65,14 +65,7 @@ export function TeamDetailPage({ teamId, onBack }: TeamDetailPageProps) {
}, [teamId, removeMember, reload]);
if (loading || !team) {
- return (
-
- );
+ return ;
}
return (
@@ -116,7 +109,7 @@ export function TeamDetailPage({ teamId, onBack }: TeamDetailPageProps) {
{/* Tabs */}
-
+
Members
@@ -148,13 +141,13 @@ export function TeamDetailPage({ teamId, onBack }: TeamDetailPageProps) {
-
{
await deleteTeam(teamId);
setDeleteOpen(false);