mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
feat(skills): add skill deletion and improve UI/UX
- Add skill deletion functionality with confirmation dialog - Protect builtin skills from deletion - Optimize skill card layout (badge and enable button near menu) - Change enable button to icon-only with theme color - Improve card selection and hover states - Fix skill hub installation state tracking (per-skill) - Add proper i18n for delete feature (en/zh) - Add loading states to delete confirmation dialog - Remove manual refetch calls (use query invalidation)
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
|||||||
Power,
|
Power,
|
||||||
PowerOff,
|
PowerOff,
|
||||||
User,
|
User,
|
||||||
|
Trash2,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Card } from '@/components/ui/Card';
|
import { Card } from '@/components/ui/Card';
|
||||||
@@ -28,6 +29,7 @@ export interface SkillCardProps {
|
|||||||
onToggle?: (skill: Skill, enabled: boolean) => void;
|
onToggle?: (skill: Skill, enabled: boolean) => void;
|
||||||
onClick?: (skill: Skill) => void;
|
onClick?: (skill: Skill) => void;
|
||||||
onConfigure?: (skill: Skill) => void;
|
onConfigure?: (skill: Skill) => void;
|
||||||
|
onDelete?: (skill: Skill) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
showActions?: boolean;
|
showActions?: boolean;
|
||||||
@@ -70,6 +72,7 @@ export function SkillCard({
|
|||||||
onToggle,
|
onToggle,
|
||||||
onClick,
|
onClick,
|
||||||
onConfigure,
|
onConfigure,
|
||||||
|
onDelete,
|
||||||
className,
|
className,
|
||||||
compact = false,
|
compact = false,
|
||||||
showActions = true,
|
showActions = true,
|
||||||
@@ -95,6 +98,12 @@ export function SkillCard({
|
|||||||
onConfigure?.(skill);
|
onConfigure?.(skill);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDelete = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsMenuOpen(false);
|
||||||
|
onDelete?.(skill);
|
||||||
|
};
|
||||||
|
|
||||||
if (compact) {
|
if (compact) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -172,20 +181,15 @@ export function SkillCard({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className={cn(
|
className="h-8 w-8 p-0 hover:bg-primary/10"
|
||||||
"h-8 w-8 p-0",
|
|
||||||
skill.enabled
|
|
||||||
? "bg-primary hover:bg-primary/90"
|
|
||||||
: "hover:bg-muted"
|
|
||||||
)}
|
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
disabled={isToggling}
|
disabled={isToggling}
|
||||||
title={skill.enabled ? formatMessage({ id: 'skills.state.enabled' }) : formatMessage({ id: 'skills.state.disabled' })}
|
title={skill.enabled ? formatMessage({ id: 'skills.state.enabled' }) : formatMessage({ id: 'skills.state.disabled' })}
|
||||||
>
|
>
|
||||||
{skill.enabled ? (
|
{skill.enabled ? (
|
||||||
<Power className="w-4 h-4 text-white" />
|
<Power className="w-4 h-4 text-primary" />
|
||||||
) : (
|
) : (
|
||||||
<PowerOff className="w-4 h-4 text-foreground" />
|
<PowerOff className="w-4 h-4 text-muted-foreground" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
{showActions && (
|
{showActions && (
|
||||||
@@ -222,6 +226,12 @@ export function SkillCard({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
{onDelete && skill.source !== 'builtin' && (
|
||||||
|
<DropdownMenuItem onClick={handleDelete} className="text-destructive focus:text-destructive">
|
||||||
|
<Trash2 className="w-4 h-4 mr-2" />
|
||||||
|
{formatMessage({ id: 'skills.actions.delete' })}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
)}
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -215,15 +215,53 @@ export function useToggleSkill(): UseToggleSkillReturn {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook for deleting a skill
|
||||||
|
*/
|
||||||
|
export function useDeleteSkill() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
skillName,
|
||||||
|
location,
|
||||||
|
projectPath,
|
||||||
|
cliType,
|
||||||
|
}: {
|
||||||
|
skillName: string;
|
||||||
|
location: 'project' | 'user';
|
||||||
|
projectPath?: string;
|
||||||
|
cliType: 'claude' | 'codex';
|
||||||
|
}) => {
|
||||||
|
const { deleteSkill } = await import('@/lib/api');
|
||||||
|
return deleteSkill(skillName, location, projectPath, cliType);
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
// Invalidate skills queries to refresh the list
|
||||||
|
queryClient.invalidateQueries({ queryKey: skillsKeys.all });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
deleteSkill: (skillName: string, location: 'project' | 'user', projectPath?: string, cliType: 'claude' | 'codex' = 'claude') =>
|
||||||
|
mutation.mutateAsync({ skillName, location, projectPath, cliType }),
|
||||||
|
isDeleting: mutation.isPending,
|
||||||
|
error: mutation.error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combined hook for all skill mutations
|
* Combined hook for all skill mutations
|
||||||
*/
|
*/
|
||||||
export function useSkillMutations() {
|
export function useSkillMutations() {
|
||||||
const toggle = useToggleSkill();
|
const toggle = useToggleSkill();
|
||||||
|
const deleteSkillHook = useDeleteSkill();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
toggleSkill: toggle.toggleSkill,
|
toggleSkill: toggle.toggleSkill,
|
||||||
isToggling: toggle.isToggling,
|
isToggling: toggle.isToggling,
|
||||||
isMutating: toggle.isToggling,
|
deleteSkill: deleteSkillHook.deleteSkill,
|
||||||
|
isDeleting: deleteSkillHook.isDeleting,
|
||||||
|
isMutating: toggle.isToggling || deleteSkillHook.isDeleting,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1265,6 +1265,25 @@ export async function fetchSkillDetail(
|
|||||||
return fetchApi<{ skill: Skill }>(url);
|
return fetchApi<{ skill: Skill }>(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a skill
|
||||||
|
* @param skillName - Name of the skill to delete
|
||||||
|
* @param location - Location of the skill (project or user)
|
||||||
|
* @param projectPath - Optional project path
|
||||||
|
* @param cliType - CLI type (claude or codex)
|
||||||
|
*/
|
||||||
|
export async function deleteSkill(
|
||||||
|
skillName: string,
|
||||||
|
location: 'project' | 'user',
|
||||||
|
projectPath?: string,
|
||||||
|
cliType: 'claude' | 'codex' = 'claude'
|
||||||
|
): Promise<{ success: boolean }> {
|
||||||
|
return fetchApi<{ success: boolean }>(`/api/skills/${encodeURIComponent(skillName)}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
body: JSON.stringify({ location, projectPath, cliType }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate a skill folder for import
|
* Validate a skill folder for import
|
||||||
*/
|
*/
|
||||||
@@ -1298,6 +1317,42 @@ export async function createSkill(params: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a skill file content
|
||||||
|
*/
|
||||||
|
export async function readSkillFile(params: {
|
||||||
|
skillName: string;
|
||||||
|
fileName: string;
|
||||||
|
location: 'project' | 'user';
|
||||||
|
projectPath?: string;
|
||||||
|
cliType?: 'claude' | 'codex';
|
||||||
|
}): Promise<{ content: string; fileName: string; path: string }> {
|
||||||
|
const { skillName, fileName, location, projectPath, cliType = 'claude' } = params;
|
||||||
|
const encodedSkillName = encodeURIComponent(skillName);
|
||||||
|
const url = `/api/skills/${encodedSkillName}/file?filename=${encodeURIComponent(fileName)}&location=${location}&cliType=${cliType}${projectPath ? `&path=${encodeURIComponent(projectPath)}` : ''}`;
|
||||||
|
return fetchApi(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a skill file content
|
||||||
|
*/
|
||||||
|
export async function writeSkillFile(params: {
|
||||||
|
skillName: string;
|
||||||
|
fileName: string;
|
||||||
|
content: string;
|
||||||
|
location: 'project' | 'user';
|
||||||
|
projectPath?: string;
|
||||||
|
cliType?: 'claude' | 'codex';
|
||||||
|
}): Promise<{ success: boolean; fileName: string; path: string }> {
|
||||||
|
const { skillName, fileName, content, location, projectPath, cliType = 'claude' } = params;
|
||||||
|
const encodedSkillName = encodeURIComponent(skillName);
|
||||||
|
const url = `/api/skills/${encodedSkillName}/file`;
|
||||||
|
return fetchApi(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ content, fileName, location, projectPath, cliType }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Commands API ==========
|
// ========== Commands API ==========
|
||||||
|
|
||||||
export interface Command {
|
export interface Command {
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
"title": "Disable Skill?",
|
"title": "Disable Skill?",
|
||||||
"message": "Are you sure you want to disable \"{name}\"?"
|
"message": "Are you sure you want to disable \"{name}\"?"
|
||||||
},
|
},
|
||||||
|
"deleteConfirm": {
|
||||||
|
"title": "Delete Skill?",
|
||||||
|
"message": "Are you sure you want to delete \"{name}\"? This action cannot be undone."
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"success": "Skill \"{name}\" has been deleted",
|
||||||
|
"error": "Failed to delete skill: {error}"
|
||||||
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"project": "Project",
|
"project": "Project",
|
||||||
"user": "Global",
|
"user": "Global",
|
||||||
@@ -25,8 +33,10 @@
|
|||||||
"disable": "Disable",
|
"disable": "Disable",
|
||||||
"toggle": "Toggle",
|
"toggle": "Toggle",
|
||||||
"install": "Install Skill",
|
"install": "Install Skill",
|
||||||
|
"delete": "Delete",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"confirmDisable": "Disable"
|
"confirmDisable": "Disable",
|
||||||
|
"confirmDelete": "Delete"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
"title": "禁用技能?",
|
"title": "禁用技能?",
|
||||||
"message": "确定要禁用 \"{name}\" 吗?"
|
"message": "确定要禁用 \"{name}\" 吗?"
|
||||||
},
|
},
|
||||||
|
"deleteConfirm": {
|
||||||
|
"title": "删除技能?",
|
||||||
|
"message": "确定要删除 \"{name}\" 吗? 此操作无法撤销。"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"success": "技能 \"{name}\" 已删除",
|
||||||
|
"error": "删除技能失败: {error}"
|
||||||
|
},
|
||||||
"location": {
|
"location": {
|
||||||
"project": "项目",
|
"project": "项目",
|
||||||
"user": "全局",
|
"user": "全局",
|
||||||
@@ -25,8 +33,10 @@
|
|||||||
"disable": "禁用",
|
"disable": "禁用",
|
||||||
"toggle": "切换",
|
"toggle": "切换",
|
||||||
"install": "安装技能",
|
"install": "安装技能",
|
||||||
|
"delete": "删除",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"confirmDisable": "禁用"
|
"confirmDisable": "禁用",
|
||||||
|
"confirmDelete": "删除"
|
||||||
},
|
},
|
||||||
"state": {
|
"state": {
|
||||||
"enabled": "已启用",
|
"enabled": "已启用",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { useState, useMemo, useCallback } from 'react';
|
import { useState, useMemo, useCallback } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import {
|
import {
|
||||||
Sparkles,
|
Sparkles,
|
||||||
Search,
|
Search,
|
||||||
@@ -70,11 +71,12 @@ interface SkillGridProps {
|
|||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
onToggle: (skill: Skill, enabled: boolean) => void;
|
onToggle: (skill: Skill, enabled: boolean) => void;
|
||||||
onClick: (skill: Skill) => void;
|
onClick: (skill: Skill) => void;
|
||||||
|
onDelete?: (skill: Skill) => void;
|
||||||
isToggling: boolean;
|
isToggling: boolean;
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SkillGrid({ skills, isLoading, onToggle, onClick, isToggling, compact }: SkillGridProps) {
|
function SkillGrid({ skills, isLoading, onToggle, onClick, onDelete, isToggling, compact }: SkillGridProps) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
@@ -113,6 +115,7 @@ function SkillGrid({ skills, isLoading, onToggle, onClick, isToggling, compact }
|
|||||||
skill={skill}
|
skill={skill}
|
||||||
onToggle={onToggle}
|
onToggle={onToggle}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
onDelete={onDelete}
|
||||||
isToggling={isToggling}
|
isToggling={isToggling}
|
||||||
compact={compact}
|
compact={compact}
|
||||||
/>
|
/>
|
||||||
@@ -140,12 +143,14 @@ export function SkillsManagerPage() {
|
|||||||
const [viewMode, setViewMode] = useState<'grid' | 'compact'>('grid');
|
const [viewMode, setViewMode] = useState<'grid' | 'compact'>('grid');
|
||||||
const [showDisabledSection, setShowDisabledSection] = useState(false);
|
const [showDisabledSection, setShowDisabledSection] = useState(false);
|
||||||
const [confirmDisable, setConfirmDisable] = useState<{ skill: Skill; enable: boolean } | null>(null);
|
const [confirmDisable, setConfirmDisable] = useState<{ skill: Skill; enable: boolean } | null>(null);
|
||||||
|
const [confirmDelete, setConfirmDelete] = useState<Skill | null>(null);
|
||||||
const [locationFilter, setLocationFilter] = useState<'project' | 'user' | 'hub'>(initialLocationFilter);
|
const [locationFilter, setLocationFilter] = useState<'project' | 'user' | 'hub'>(initialLocationFilter);
|
||||||
|
|
||||||
// Skill Hub state
|
// Skill Hub state
|
||||||
const [hubTab, setHubTab] = useState<'remote' | 'local' | 'installed'>('remote');
|
const [hubTab, setHubTab] = useState<'remote' | 'local' | 'installed'>('remote');
|
||||||
const [hubSearchQuery, setHubSearchQuery] = useState('');
|
const [hubSearchQuery, setHubSearchQuery] = useState('');
|
||||||
const [hubCategoryFilter, setHubCategoryFilter] = useState<string | null>(null);
|
const [hubCategoryFilter, setHubCategoryFilter] = useState<string | null>(null);
|
||||||
|
const [installingSkillId, setInstallingSkillId] = useState<string | null>(null);
|
||||||
|
|
||||||
// Skill create dialog state
|
// Skill create dialog state
|
||||||
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
|
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
|
||||||
@@ -179,7 +184,7 @@ export function SkillsManagerPage() {
|
|||||||
cliType: cliMode,
|
cliType: cliMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { toggleSkill, isToggling } = useSkillMutations();
|
const { toggleSkill, deleteSkill, isToggling, isDeleting } = useSkillMutations();
|
||||||
|
|
||||||
// Skill Hub hooks
|
// Skill Hub hooks
|
||||||
const {
|
const {
|
||||||
@@ -245,21 +250,40 @@ export function SkillsManagerPage() {
|
|||||||
// Hub skill handlers
|
// Hub skill handlers
|
||||||
const handleHubInstall = async (skill: RemoteSkill | LocalSkill, cliType: CliType) => {
|
const handleHubInstall = async (skill: RemoteSkill | LocalSkill, cliType: CliType) => {
|
||||||
const source: SkillSource = 'downloadUrl' in skill ? 'remote' : 'local';
|
const source: SkillSource = 'downloadUrl' in skill ? 'remote' : 'local';
|
||||||
await installSkillMutation.mutateAsync({
|
setInstallingSkillId(skill.id);
|
||||||
skillId: skill.id,
|
try {
|
||||||
cliType,
|
await installSkillMutation.mutateAsync({
|
||||||
source,
|
skillId: skill.id,
|
||||||
downloadUrl: 'downloadUrl' in skill ? skill.downloadUrl : undefined,
|
cliType,
|
||||||
});
|
source,
|
||||||
|
downloadUrl: 'downloadUrl' in skill ? skill.downloadUrl : undefined,
|
||||||
|
});
|
||||||
|
// Show success toast
|
||||||
|
toast.success(formatMessage({ id: 'skill-hub.install.success' }, { name: skill.name }));
|
||||||
|
} catch (error) {
|
||||||
|
// Show error toast
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
toast.error(formatMessage({ id: 'skill-hub.install.error' }, { error: errorMessage }));
|
||||||
|
} finally {
|
||||||
|
setInstallingSkillId(null);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHubUninstall = async (skill: RemoteSkill | LocalSkill, cliType: CliType) => {
|
const handleHubUninstall = async (skill: RemoteSkill | LocalSkill, cliType: CliType) => {
|
||||||
const installedInfo = installedMap.get(skill.id);
|
const installedInfo = installedMap.get(skill.id);
|
||||||
if (installedInfo) {
|
if (installedInfo) {
|
||||||
await uninstallSkillMutation.mutateAsync({
|
try {
|
||||||
skillId: installedInfo.id,
|
await uninstallSkillMutation.mutateAsync({
|
||||||
cliType,
|
skillId: installedInfo.id,
|
||||||
});
|
cliType,
|
||||||
|
});
|
||||||
|
// Show success toast
|
||||||
|
toast.success(formatMessage({ id: 'skill-hub.uninstall.success' }, { name: skill.name }));
|
||||||
|
} catch (error) {
|
||||||
|
// Show error toast
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
toast.error(formatMessage({ id: 'skill-hub.uninstall.error' }, { error: errorMessage }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -317,6 +341,27 @@ export function SkillsManagerPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDelete = (skill: Skill) => {
|
||||||
|
setConfirmDelete(skill);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmDelete = async () => {
|
||||||
|
if (confirmDelete) {
|
||||||
|
const location = confirmDelete.location || 'project';
|
||||||
|
const skillIdentifier = confirmDelete.folderName || confirmDelete.name;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteSkill(skillIdentifier, location, projectPath, cliMode);
|
||||||
|
toast.success(formatMessage({ id: 'skills.delete.success' }, { name: confirmDelete.name }));
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
toast.error(formatMessage({ id: 'skills.delete.error' }, { error: errorMessage }));
|
||||||
|
} finally {
|
||||||
|
setConfirmDelete(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Skill detail panel handlers
|
// Skill detail panel handlers
|
||||||
const handleSkillClick = useCallback(async (skill: Skill) => {
|
const handleSkillClick = useCallback(async (skill: Skill) => {
|
||||||
setIsDetailLoading(true);
|
setIsDetailLoading(true);
|
||||||
@@ -610,7 +655,7 @@ export function SkillsManagerPage() {
|
|||||||
source={skillSource}
|
source={skillSource}
|
||||||
onInstall={handleHubInstall}
|
onInstall={handleHubInstall}
|
||||||
onUninstall={handleHubUninstall}
|
onUninstall={handleHubUninstall}
|
||||||
isInstalling={installSkillMutation.isPending}
|
isInstalling={installingSkillId === skill.id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -712,6 +757,7 @@ export function SkillsManagerPage() {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onToggle={handleToggleWithConfirm}
|
onToggle={handleToggleWithConfirm}
|
||||||
onClick={handleSkillClick}
|
onClick={handleSkillClick}
|
||||||
|
onDelete={handleDelete}
|
||||||
isToggling={isToggling || !!confirmDisable}
|
isToggling={isToggling || !!confirmDisable}
|
||||||
compact={viewMode === 'compact'}
|
compact={viewMode === 'compact'}
|
||||||
/>
|
/>
|
||||||
@@ -735,6 +781,7 @@ export function SkillsManagerPage() {
|
|||||||
isLoading={false}
|
isLoading={false}
|
||||||
onToggle={handleToggleWithConfirm}
|
onToggle={handleToggleWithConfirm}
|
||||||
onClick={handleSkillClick}
|
onClick={handleSkillClick}
|
||||||
|
onDelete={handleDelete}
|
||||||
isToggling={isToggling || !!confirmDisable}
|
isToggling={isToggling || !!confirmDisable}
|
||||||
compact={true}
|
compact={true}
|
||||||
/>
|
/>
|
||||||
@@ -765,6 +812,31 @@ export function SkillsManagerPage() {
|
|||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|
||||||
|
{/* Delete Confirmation Dialog */}
|
||||||
|
<AlertDialog open={!!confirmDelete} onOpenChange={(open) => !open && setConfirmDelete(null)}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{formatMessage({ id: 'skills.deleteConfirm.title' })}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{formatMessage(
|
||||||
|
{ id: 'skills.deleteConfirm.message' },
|
||||||
|
{ name: confirmDelete?.name || '' }
|
||||||
|
)}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel disabled={isDeleting}>{formatMessage({ id: 'skills.actions.cancel' })}</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={handleConfirmDelete}
|
||||||
|
disabled={isDeleting}
|
||||||
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||||
|
>
|
||||||
|
{isDeleting ? formatMessage({ id: 'common.deleting' }) : formatMessage({ id: 'skills.actions.confirmDelete' })}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
|
||||||
{/* Skill Detail Panel */}
|
{/* Skill Detail Panel */}
|
||||||
<SkillDetailPanel
|
<SkillDetailPanel
|
||||||
skill={selectedSkill}
|
skill={selectedSkill}
|
||||||
|
|||||||
Reference in New Issue
Block a user