diff --git a/packages/ui/src/components/sections/agents/AgentsSidebar.tsx b/packages/ui/src/components/sections/agents/AgentsSidebar.tsx index 0b7c1f4a..d5e11faf 100644 --- a/packages/ui/src/components/sections/agents/AgentsSidebar.tsx +++ b/packages/ui/src/components/sections/agents/AgentsSidebar.tsx @@ -102,6 +102,9 @@ const rulesetToPermissionConfig = (ruleset: unknown): AgentDraft['permission'] = export const AgentsSidebar: React.FC = ({ onItemSelect }) => { const [renameDialogAgent, setRenameDialogAgent] = React.useState(null); const [renameNewName, setRenameNewName] = React.useState(''); + const [confirmActionAgent, setConfirmActionAgent] = React.useState(null); + const [confirmActionType, setConfirmActionType] = React.useState<'delete' | 'reset' | null>(null); + const [isConfirmActionPending, setIsConfirmActionPending] = React.useState(false); const { selectedAgentName, @@ -150,14 +153,8 @@ export const AgentsSidebar: React.FC = ({ onItemSelect }) => return; } - if (window.confirm(`Are you sure you want to delete agent "${agent.name}"?`)) { - const success = await deleteAgent(agent.name); - if (success) { - toast.success(`Agent "${agent.name}" deleted successfully`); - } else { - toast.error('Failed to delete agent'); - } - } + setConfirmActionAgent(agent); + setConfirmActionType('delete'); }; const handleResetAgent = async (agent: Agent) => { @@ -165,14 +162,37 @@ export const AgentsSidebar: React.FC = ({ onItemSelect }) => return; } - if (window.confirm(`Are you sure you want to reset agent "${agent.name}" to its default configuration?`)) { - const success = await deleteAgent(agent.name); - if (success) { - toast.success(`Agent "${agent.name}" reset to default`); - } else { - toast.error('Failed to reset agent'); - } + setConfirmActionAgent(agent); + setConfirmActionType('reset'); + }; + + const closeConfirmActionDialog = () => { + setConfirmActionAgent(null); + setConfirmActionType(null); + }; + + const handleConfirmAction = async () => { + if (!confirmActionAgent || !confirmActionType) { + return; } + + setIsConfirmActionPending(true); + const success = await deleteAgent(confirmActionAgent.name); + + if (success) { + if (confirmActionType === 'delete') { + toast.success(`Agent "${confirmActionAgent.name}" deleted successfully`); + } else { + toast.success(`Agent "${confirmActionAgent.name}" reset to default`); + } + closeConfirmActionDialog(); + } else if (confirmActionType === 'delete') { + toast.error('Failed to delete agent'); + } else { + toast.error('Failed to reset agent'); + } + + setIsConfirmActionPending(false); }; const handleDuplicateAgent = (agent: Agent) => { @@ -367,6 +387,39 @@ export const AgentsSidebar: React.FC = ({ onItemSelect }) => )} + { + if (!open && !isConfirmActionPending) { + closeConfirmActionDialog(); + } + }} + > + + + {confirmActionType === 'delete' ? 'Delete Agent' : 'Reset Agent'} + + {confirmActionType === 'delete' + ? `Are you sure you want to delete agent "${confirmActionAgent?.name}"?` + : `Are you sure you want to reset agent "${confirmActionAgent?.name}" to its default configuration?`} + + + + + + {confirmActionType === 'delete' ? 'Delete' : 'Reset'} + + + + + {/* Rename Dialog */} !open && setRenameDialogAgent(null)}> diff --git a/packages/ui/src/components/sections/commands/CommandsSidebar.tsx b/packages/ui/src/components/sections/commands/CommandsSidebar.tsx index e0cca1f0..b57025da 100644 --- a/packages/ui/src/components/sections/commands/CommandsSidebar.tsx +++ b/packages/ui/src/components/sections/commands/CommandsSidebar.tsx @@ -32,6 +32,9 @@ interface CommandsSidebarProps { export const CommandsSidebar: React.FC = ({ onItemSelect }) => { const [renameDialogCommand, setRenameDialogCommand] = React.useState(null); const [renameNewName, setRenameNewName] = React.useState(''); + const [confirmActionCommand, setConfirmActionCommand] = React.useState(null); + const [confirmActionType, setConfirmActionType] = React.useState<'delete' | 'reset' | null>(null); + const [isConfirmActionPending, setIsConfirmActionPending] = React.useState(false); const { selectedCommandName, @@ -80,14 +83,8 @@ export const CommandsSidebar: React.FC = ({ onItemSelect } return; } - if (window.confirm(`Are you sure you want to delete command "${command.name}"?`)) { - const success = await deleteCommand(command.name); - if (success) { - toast.success(`Command "${command.name}" deleted successfully`); - } else { - toast.error('Failed to delete command'); - } - } + setConfirmActionCommand(command); + setConfirmActionType('delete'); }; const handleResetCommand = async (command: Command) => { @@ -95,14 +92,37 @@ export const CommandsSidebar: React.FC = ({ onItemSelect } return; } - if (window.confirm(`Are you sure you want to reset command "${command.name}" to its default configuration?`)) { - const success = await deleteCommand(command.name); - if (success) { - toast.success(`Command "${command.name}" reset to default`); - } else { - toast.error('Failed to reset command'); - } + setConfirmActionCommand(command); + setConfirmActionType('reset'); + }; + + const closeConfirmActionDialog = () => { + setConfirmActionCommand(null); + setConfirmActionType(null); + }; + + const handleConfirmAction = async () => { + if (!confirmActionCommand || !confirmActionType) { + return; } + + setIsConfirmActionPending(true); + const success = await deleteCommand(confirmActionCommand.name); + + if (success) { + if (confirmActionType === 'delete') { + toast.success(`Command "${confirmActionCommand.name}" deleted successfully`); + } else { + toast.success(`Command "${confirmActionCommand.name}" reset to default`); + } + closeConfirmActionDialog(); + } else if (confirmActionType === 'delete') { + toast.error('Failed to delete command'); + } else { + toast.error('Failed to reset command'); + } + + setIsConfirmActionPending(false); }; const handleDuplicateCommand = (command: Command) => { @@ -264,6 +284,39 @@ export const CommandsSidebar: React.FC = ({ onItemSelect } )} + { + if (!open && !isConfirmActionPending) { + closeConfirmActionDialog(); + } + }} + > + + + {confirmActionType === 'delete' ? 'Delete Command' : 'Reset Command'} + + {confirmActionType === 'delete' + ? `Are you sure you want to delete command "${confirmActionCommand?.name}"?` + : `Are you sure you want to reset command "${confirmActionCommand?.name}" to its default configuration?`} + + + + + + {confirmActionType === 'delete' ? 'Delete' : 'Reset'} + + + + + {/* Rename Dialog */} !open && setRenameDialogCommand(null)}> diff --git a/packages/ui/src/components/sections/git-identities/GitIdentitiesPage.tsx b/packages/ui/src/components/sections/git-identities/GitIdentitiesPage.tsx index 1ffe905d..1dc27d9d 100644 --- a/packages/ui/src/components/sections/git-identities/GitIdentitiesPage.tsx +++ b/packages/ui/src/components/sections/git-identities/GitIdentitiesPage.tsx @@ -19,6 +19,14 @@ import { import { cn } from '@/lib/utils'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; const PROFILE_COLORS = [ { key: 'keyword', label: 'Green', cssVar: 'var(--syntax-keyword)' }, @@ -70,6 +78,8 @@ export const GitIdentitiesPage: React.FC = () => { const [color, setColor] = React.useState('keyword'); const [icon, setIcon] = React.useState('branch'); const [isSaving, setIsSaving] = React.useState(false); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = React.useState(false); + const [isDeleting, setIsDeleting] = React.useState(false); React.useEffect(() => { if (importData) { @@ -152,23 +162,31 @@ export const GitIdentitiesPage: React.FC = () => { } }; - const handleDelete = async () => { + const handleDelete = () => { if (!selectedProfileId || isNewProfile) return; - if (!confirm('Are you sure you want to delete this profile?')) { + setIsDeleteDialogOpen(true); + }; + + const handleConfirmDelete = async () => { + if (!selectedProfileId || isNewProfile) { return; } + setIsDeleting(true); try { const success = await deleteProfile(selectedProfileId); if (success) { toast.success('Profile deleted successfully'); + setIsDeleteDialogOpen(false); } else { toast.error('Failed to delete profile'); } } catch (error) { console.error('Error deleting profile:', error); toast.error('An error occurred while deleting'); + } finally { + setIsDeleting(false); } }; @@ -483,6 +501,32 @@ export const GitIdentitiesPage: React.FC = () => { )} + + { + if (!isDeleting) { + setIsDeleteDialogOpen(open); + } + }} + > + + + Delete Profile + + Are you sure you want to delete profile "{selectedProfile?.name || name || 'this profile'}"? + + + + + + + + ); diff --git a/packages/ui/src/components/sections/git-identities/GitIdentitiesSidebar.tsx b/packages/ui/src/components/sections/git-identities/GitIdentitiesSidebar.tsx index abc1d4ab..eeb4c7f6 100644 --- a/packages/ui/src/components/sections/git-identities/GitIdentitiesSidebar.tsx +++ b/packages/ui/src/components/sections/git-identities/GitIdentitiesSidebar.tsx @@ -1,6 +1,14 @@ import React from 'react'; import { Button } from '@/components/ui/button'; import { toast } from '@/components/ui'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; import { DropdownMenu, DropdownMenuContent, @@ -49,6 +57,9 @@ interface GitIdentitiesSidebarProps { } export const GitIdentitiesSidebar: React.FC = ({ onItemSelect }) => { + const [deleteDialogProfile, setDeleteDialogProfile] = React.useState(null); + const [isDeletePending, setIsDeletePending] = React.useState(false); + const { selectedProfileId, defaultGitIdentityId, @@ -99,14 +110,23 @@ export const GitIdentitiesSidebar: React.FC = ({ onIt }; const handleDeleteProfile = async (profile: GitIdentityProfile) => { - if (window.confirm(`Are you sure you want to delete profile "${profile.name}"?`)) { - const success = await deleteProfile(profile.id); - if (success) { - toast.success(`Profile "${profile.name}" deleted successfully`); - } else { - toast.error('Failed to delete profile'); - } + setDeleteDialogProfile(profile); + }; + + const handleConfirmDeleteProfile = async () => { + if (!deleteDialogProfile) { + return; } + + setIsDeletePending(true); + const success = await deleteProfile(deleteDialogProfile.id); + if (success) { + toast.success(`Profile "${deleteDialogProfile.name}" deleted successfully`); + setDeleteDialogProfile(null); + } else { + toast.error('Failed to delete profile'); + } + setIsDeletePending(false); }; const handleToggleDefault = async (profileId: string) => { @@ -216,6 +236,32 @@ export const GitIdentitiesSidebar: React.FC = ({ onIt )} + + { + if (!open && !isDeletePending) { + setDeleteDialogProfile(null); + } + }} + > + + + Delete Profile + + Are you sure you want to delete profile "{deleteDialogProfile?.name}"? + + + + + + + + ); }; diff --git a/packages/ui/src/components/sections/skills/SkillsPage.tsx b/packages/ui/src/components/sections/skills/SkillsPage.tsx index 58cd617f..ad12ead1 100644 --- a/packages/ui/src/components/sections/skills/SkillsPage.tsx +++ b/packages/ui/src/components/sections/skills/SkillsPage.tsx @@ -91,6 +91,8 @@ export const SkillsPage: React.FC = () => { const [editingFilePath, setEditingFilePath] = React.useState(null); // null = adding, string = editing const [isLoadingFile, setIsLoadingFile] = React.useState(false); const [originalFileContent, setOriginalFileContent] = React.useState(''); // Track original for change detection + const [deleteFilePath, setDeleteFilePath] = React.useState(null); + const [isDeletingFile, setIsDeletingFile] = React.useState(false); // Detect if skill-level fields have changed const hasSkillChanges = isNewSkill @@ -306,7 +308,7 @@ export const SkillsPage: React.FC = () => { } }; - const handleDeleteFile = async (filePath: string) => { + const handleDeleteFile = (filePath: string) => { // For new skills, remove from pending files if (isNewSkill) { setPendingFiles(prev => prev.filter(f => f.path !== filePath)); @@ -315,23 +317,34 @@ export const SkillsPage: React.FC = () => { } // For existing skills, delete from disk - if (!selectedSkillName) return; - - if (window.confirm(`Are you sure you want to delete "${filePath}"?`)) { - const { deleteSupportingFile } = useSkillsStore.getState(); - const success = await deleteSupportingFile(selectedSkillName, filePath); - - if (success) { - toast.success(`File "${filePath}" deleted`); - // Refresh skill details - const detail = await getSkillDetail(selectedSkillName); - if (detail) { - setSupportingFiles(detail.sources.md.supportingFiles || []); - } - } else { - toast.error('Failed to delete file'); - } + if (!selectedSkillName) { + return; } + + setDeleteFilePath(filePath); + }; + + const handleConfirmDeleteFile = async () => { + if (!deleteFilePath || !selectedSkillName) { + return; + } + + setIsDeletingFile(true); + const { deleteSupportingFile } = useSkillsStore.getState(); + const success = await deleteSupportingFile(selectedSkillName, deleteFilePath); + + if (success) { + toast.success(`File "${deleteFilePath}" deleted`); + const detail = await getSkillDetail(selectedSkillName); + if (detail) { + setSupportingFiles(detail.sources.md.supportingFiles || []); + } + setDeleteFilePath(null); + } else { + toast.error('Failed to delete file'); + } + + setIsDeletingFile(false); }; if (isNewSkill && mode === 'external') { @@ -554,6 +567,37 @@ export const SkillsPage: React.FC = () => { {/* Add/Edit File Dialog */} + { + if (!open && !isDeletingFile) { + setDeleteFilePath(null); + } + }} + > + + + Delete Supporting File + + Are you sure you want to delete "{deleteFilePath}"? + + + + + + Delete + + + + + { setIsFileDialogOpen(open); if (!open) setEditingFilePath(null); diff --git a/packages/ui/src/components/sections/skills/SkillsSidebar.tsx b/packages/ui/src/components/sections/skills/SkillsSidebar.tsx index 90a90cec..608819fa 100644 --- a/packages/ui/src/components/sections/skills/SkillsSidebar.tsx +++ b/packages/ui/src/components/sections/skills/SkillsSidebar.tsx @@ -32,6 +32,8 @@ interface SkillsSidebarProps { export const SkillsSidebar: React.FC = ({ onItemSelect }) => { const [renameDialogSkill, setRenameDialogSkill] = React.useState(null); const [renameNewName, setRenameNewName] = React.useState(''); + const [deleteDialogSkill, setDeleteDialogSkill] = React.useState(null); + const [isDeletePending, setIsDeletePending] = React.useState(false); const { selectedSkillName, @@ -76,14 +78,23 @@ export const SkillsSidebar: React.FC = ({ onItemSelect }) => }; const handleDeleteSkill = async (skill: DiscoveredSkill) => { - if (window.confirm(`Are you sure you want to delete skill "${skill.name}"?`)) { - const success = await deleteSkill(skill.name); - if (success) { - toast.success(`Skill "${skill.name}" deleted successfully`); - } else { - toast.error('Failed to delete skill'); - } + setDeleteDialogSkill(skill); + }; + + const handleConfirmDeleteSkill = async () => { + if (!deleteDialogSkill) { + return; } + + setIsDeletePending(true); + const success = await deleteSkill(deleteDialogSkill.name); + if (success) { + toast.success(`Skill "${deleteDialogSkill.name}" deleted successfully`); + setDeleteDialogSkill(null); + } else { + toast.error('Failed to delete skill'); + } + setIsDeletePending(false); }; const handleDuplicateSkill = async (skill: DiscoveredSkill) => { @@ -256,6 +267,37 @@ export const SkillsSidebar: React.FC = ({ onItemSelect }) => )} + { + if (!open && !isDeletePending) { + setDeleteDialogSkill(null); + } + }} + > + + + Delete Skill + + Are you sure you want to delete skill "{deleteDialogSkill?.name}"? + + + + + + Delete + + + + + {/* Rename Dialog */} !open && setRenameDialogSkill(null)}> diff --git a/packages/ui/src/components/sections/skills/catalog/SkillsCatalogPage.tsx b/packages/ui/src/components/sections/skills/catalog/SkillsCatalogPage.tsx index 76fb934c..0736be73 100644 --- a/packages/ui/src/components/sections/skills/catalog/SkillsCatalogPage.tsx +++ b/packages/ui/src/components/sections/skills/catalog/SkillsCatalogPage.tsx @@ -4,6 +4,14 @@ import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay'; import { AnimatedTabs } from '@/components/ui/animated-tabs'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; import { Select, SelectContent, @@ -75,6 +83,7 @@ export const SkillsCatalogPage: React.FC = ({ mode, onMo const [installDialogOpen, setInstallDialogOpen] = React.useState(false); const [installItem, setInstallItem] = React.useState(null); const [isRemovingCatalog, setIsRemovingCatalog] = React.useState(false); + const [isRemoveCatalogDialogOpen, setIsRemoveCatalogDialogOpen] = React.useState(false); React.useEffect(() => { void loadCatalog(); @@ -118,10 +127,6 @@ export const SkillsCatalogPage: React.FC = ({ mode, onMo return; } - if (!window.confirm('Remove this catalog?')) { - return; - } - setIsRemovingCatalog(true); try { const settings = await loadSettings(); @@ -129,6 +134,7 @@ export const SkillsCatalogPage: React.FC = ({ mode, onMo const updated = catalogs.filter((c) => c.id !== selectedSourceId); await updateDesktopSettings({ skillCatalogs: updated }); await loadCatalog({ refresh: true }); + setIsRemoveCatalogDialogOpen(false); } finally { setIsRemovingCatalog(false); } @@ -198,7 +204,7 @@ export const SkillsCatalogPage: React.FC = ({ mode, onMo + + + + );