feat: add confirm dialogs for delete/reset actions
This commit is contained in:
@@ -91,6 +91,8 @@ export const SkillsPage: React.FC = () => {
|
||||
const [editingFilePath, setEditingFilePath] = React.useState<string | null>(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<string | null>(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 = () => {
|
||||
</div>
|
||||
|
||||
{/* Add/Edit File Dialog */}
|
||||
<Dialog
|
||||
open={deleteFilePath !== null}
|
||||
onOpenChange={(open) => {
|
||||
if (!open && !isDeletingFile) {
|
||||
setDeleteFilePath(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete Supporting File</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete "{deleteFilePath}"?
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setDeleteFilePath(null)}
|
||||
disabled={isDeletingFile}
|
||||
className="text-foreground hover:bg-interactive-hover hover:text-foreground"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<ButtonLarge onClick={handleConfirmDeleteFile} disabled={isDeletingFile}>
|
||||
Delete
|
||||
</ButtonLarge>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={isFileDialogOpen} onOpenChange={(open) => {
|
||||
setIsFileDialogOpen(open);
|
||||
if (!open) setEditingFilePath(null);
|
||||
|
||||
@@ -32,6 +32,8 @@ interface SkillsSidebarProps {
|
||||
export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ onItemSelect }) => {
|
||||
const [renameDialogSkill, setRenameDialogSkill] = React.useState<DiscoveredSkill | null>(null);
|
||||
const [renameNewName, setRenameNewName] = React.useState('');
|
||||
const [deleteDialogSkill, setDeleteDialogSkill] = React.useState<DiscoveredSkill | null>(null);
|
||||
const [isDeletePending, setIsDeletePending] = React.useState(false);
|
||||
|
||||
const {
|
||||
selectedSkillName,
|
||||
@@ -76,14 +78,23 @@ export const SkillsSidebar: React.FC<SkillsSidebarProps> = ({ 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<SkillsSidebarProps> = ({ onItemSelect }) =>
|
||||
)}
|
||||
</ScrollableOverlay>
|
||||
|
||||
<Dialog
|
||||
open={deleteDialogSkill !== null}
|
||||
onOpenChange={(open) => {
|
||||
if (!open && !isDeletePending) {
|
||||
setDeleteDialogSkill(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete Skill</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete skill "{deleteDialogSkill?.name}"?
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setDeleteDialogSkill(null)}
|
||||
disabled={isDeletePending}
|
||||
className="text-foreground hover:bg-interactive-hover hover:text-foreground"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<ButtonLarge onClick={handleConfirmDeleteSkill} disabled={isDeletePending}>
|
||||
Delete
|
||||
</ButtonLarge>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Rename Dialog */}
|
||||
<Dialog open={renameDialogSkill !== null} onOpenChange={(open) => !open && setRenameDialogSkill(null)}>
|
||||
<DialogContent>
|
||||
|
||||
@@ -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<SkillsCatalogPageProps> = ({ mode, onMo
|
||||
const [installDialogOpen, setInstallDialogOpen] = React.useState(false);
|
||||
const [installItem, setInstallItem] = React.useState<SkillsCatalogItem | null>(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<SkillsCatalogPageProps> = ({ 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<SkillsCatalogPageProps> = ({ 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<SkillsCatalogPageProps> = ({ mode, onMo
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => void removeSelectedCatalog()}
|
||||
onClick={() => setIsRemoveCatalogDialogOpen(true)}
|
||||
disabled={isRemovingCatalog}
|
||||
className="gap-2"
|
||||
>
|
||||
@@ -337,6 +343,33 @@ export const SkillsCatalogPage: React.FC<SkillsCatalogPageProps> = ({ mode, onMo
|
||||
|
||||
<AddCatalogDialog open={addCatalogOpen} onOpenChange={setAddCatalogOpen} />
|
||||
<InstallSkillDialog open={installDialogOpen} onOpenChange={setInstallDialogOpen} item={installItem} />
|
||||
<Dialog
|
||||
open={isRemoveCatalogDialogOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (!isRemovingCatalog) {
|
||||
setIsRemoveCatalogDialogOpen(open);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Remove Catalog</DialogTitle>
|
||||
<DialogDescription>Are you sure you want to remove this catalog?</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => setIsRemoveCatalogDialogOpen(false)}
|
||||
disabled={isRemovingCatalog}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={() => void removeSelectedCatalog()} disabled={isRemovingCatalog}>
|
||||
Remove
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</ScrollableOverlay>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user