diff --git a/web/src/features/skill/skill-card.tsx b/web/src/features/skill/skill-card.tsx index 7f2b5872..f9c5fb86 100644 --- a/web/src/features/skill/skill-card.tsx +++ b/web/src/features/skill/skill-card.tsx @@ -1,7 +1,9 @@ +import { useState } from 'react' import { useTranslation } from 'react-i18next' import type { SkillSummary } from '@/api/types' import { useAuth } from '@/features/auth/use-auth' -import { useStar } from '@/features/social/use-star' +import { useStar, useToggleStar } from '@/features/social/use-star' +import { ConfirmDialog } from '@/shared/components/confirm-dialog' import { Card } from '@/shared/ui/card' import { NamespaceBadge } from '@/shared/components/namespace-badge' import { Bookmark } from 'lucide-react' @@ -15,74 +17,101 @@ interface SkillCardProps { export function SkillCard({ skill, onClick, highlightStarred = true }: SkillCardProps) { const { t } = useTranslation() const { isAuthenticated } = useAuth() + const [confirmOpen, setConfirmOpen] = useState(false) const { data: starStatus } = useStar(skill.id, highlightStarred && isAuthenticated) + const toggleStarMutation = useToggleStar(skill.id) const showStarredBadge = highlightStarred && isAuthenticated && starStatus?.starred + const handleStarredBadgeClick = (event: React.MouseEvent) => { + event.stopPropagation() + setConfirmOpen(true) + } + + const handleConfirmUnstar = async () => { + if (!starStatus?.starred) { + return + } + await toggleStarMutation.mutateAsync(starStatus.starred) + } + return ( - - {/* Hover gradient border effect */} -
+ <> + + {/* Hover gradient border effect */} +
-
-
-
-

- {skill.displayName} -

+
+
+
+

+ {skill.displayName} +

+
+
+ {showStarredBadge ? ( + + ) : null} + +
-
- {showStarredBadge ? ( - - - {t('skillCard.starred')} + + {skill.summary && ( +

+ {skill.summary} +

+ )} + +
+ {skill.latestVersion && ( + + v{skill.latestVersion} - ) : null} - + )} + + + + + {skill.downloadCount} + + + + {skill.starCount} + + {skill.ratingAvg !== undefined && skill.ratingCount > 0 && ( + + + + + {skill.ratingAvg.toFixed(1)} + + )}
+ - {skill.summary && ( -

- {skill.summary} -

- )} - -
- {skill.latestVersion && ( - - v{skill.latestVersion} - - )} - - - - - {skill.downloadCount} - - - - {skill.starCount} - - {skill.ratingAvg !== undefined && skill.ratingCount > 0 && ( - - - - - {skill.ratingAvg.toFixed(1)} - - )} -
-
- + + ) } diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 1f4cf42b..69fa62af 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -416,7 +416,11 @@ "star": "Star" }, "skillCard": { - "starred": "Starred" + "starred": "Starred", + "starredAction": "Click to unstar", + "unstarTitle": "Remove from starred", + "unstarDescription": "Are you sure you want to remove \"{{name}}\" from your starred skills?", + "unstarConfirm": "Remove" }, "copyButton": { "copied": "Copied", diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index 9e71b83e..e00d28b0 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -416,7 +416,11 @@ "star": "收藏" }, "skillCard": { - "starred": "已收藏" + "starred": "已收藏", + "starredAction": "点击取消收藏", + "unstarTitle": "取消收藏", + "unstarDescription": "确定要取消收藏技能“{{name}}”吗?", + "unstarConfirm": "取消收藏" }, "copyButton": { "copied": "已复制",