From bc3d47ed0ef66af9ebe9d5609514b532cd21c1b9 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Tue, 18 Nov 2025 18:26:22 +0200 Subject: [PATCH] feat: deleting achievement souvenirs --- src/locales/en/translation.json | 4 +- .../profile-content/profile-content.scss | 35 +++++++++ .../profile-content/profile-content.tsx | 3 + .../profile/profile-content/souvenirs-tab.tsx | 76 +++++++++++++++---- src/types/index.ts | 3 + 5 files changed, 106 insertions(+), 15 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index e874fab4..6da94356 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -713,7 +713,9 @@ "karma_description": "Earned from positive likes on reviews", "user_reviews": "Reviews", "delete_review": "Delete Review", - "loading_reviews": "Loading reviews..." + "loading_reviews": "Loading reviews...", + "souvenir_deleted_successfully": "Souvenir deleted successfully", + "souvenir_deletion_failed": "Failed to delete souvenir" }, "library": { "library": "Library", diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.scss b/src/renderer/src/pages/profile/profile-content/profile-content.scss index 0c8ea559..adb00cfc 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.scss +++ b/src/renderer/src/pages/profile/profile-content/profile-content.scss @@ -326,6 +326,41 @@ } } + &__image-delete-button { + position: absolute; + top: 8px; + right: 8px; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(244, 67, 54, 0.9); + border: 1px solid rgba(244, 67, 54, 0.5); + border-radius: 6px; + color: white; + cursor: pointer; + transition: all 0.2s ease; + z-index: 3; + opacity: 0; + + &:hover { + background: rgba(244, 67, 54, 1); + border-color: rgba(244, 67, 54, 0.7); + transform: scale(1.1); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; + } + } + + &__image-achievement-image-wrapper:hover &__image-delete-button { + opacity: 1; + } + // Show overlay on keyboard focus for accessibility &__image-button:focus-visible + &__image-achievement-image-overlay { opacity: 1; diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.tsx b/src/renderer/src/pages/profile/profile-content/profile-content.tsx index 5d907e4a..905f3b4c 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.tsx +++ b/src/renderer/src/pages/profile/profile-content/profile-content.tsx @@ -88,6 +88,7 @@ export function ProfileContent() { userStats, libraryGames, pinnedGames, + getUserProfile, getUserLibraryGames, loadMoreLibraryGames, hasMoreLibraryGames, @@ -459,6 +460,8 @@ export function ProfileContent() { )} diff --git a/src/renderer/src/pages/profile/profile-content/souvenirs-tab.tsx b/src/renderer/src/pages/profile/profile-content/souvenirs-tab.tsx index fa8442c8..a88b653e 100644 --- a/src/renderer/src/pages/profile/profile-content/souvenirs-tab.tsx +++ b/src/renderer/src/pages/profile/profile-content/souvenirs-tab.tsx @@ -1,26 +1,55 @@ import { motion } from "framer-motion"; import { useTranslation } from "react-i18next"; -import { SearchIcon } from "@primer/octicons-react"; +import { SearchIcon, XIcon } from "@primer/octicons-react"; +import { useState } from "react"; +import type { ProfileAchievement } from "@types"; +import { useToast } from "@renderer/hooks"; +import { logger } from "@renderer/logger"; import "./profile-content.scss"; -interface Achievement { - name: string; - imageUrl: string; - achievementIcon: string | null; - gameTitle: string; - gameIconUrl: string | null; -} - interface SouvenirsTabProps { - achievements: Achievement[]; + achievements: ProfileAchievement[]; onImageClick: (imageUrl: string, achievementName: string) => void; + isMe: boolean; + onAchievementDeleted: () => void; } export function SouvenirsTab({ achievements, onImageClick, + isMe, + onAchievementDeleted, }: Readonly) { const { t } = useTranslation("user_profile"); + const { showSuccessToast, showErrorToast } = useToast(); + const [deletingIds, setDeletingIds] = useState>(new Set()); + + const handleDeleteAchievement = async (achievement: ProfileAchievement) => { + if (deletingIds.has(achievement.id)) return; + + setDeletingIds((prev) => new Set(prev).add(achievement.id)); + + try { + await window.electron.hydraApi.delete( + `/profile/games/achievements/${achievement.gameId}/${achievement.name}/image` + ); + + showSuccessToast( + t("souvenir_deleted_successfully", "Souvenir deleted successfully") + ); + onAchievementDeleted(); + } catch (error) { + logger.error("Failed to delete souvenir:", error); + showErrorToast( + t("souvenir_deletion_failed", "Failed to delete souvenir") + ); + setDeletingIds((prev) => { + const next = new Set(prev); + next.delete(achievement.id); + return next; + }); + } + }; return ( - onImageClick(achievement.imageUrl, achievement.name) + onImageClick( + achievement.imageUrl, + achievement.displayName + ) } - aria-label={`View ${achievement.name} screenshot in fullscreen`} + aria-label={`View ${achievement.displayName} screenshot in fullscreen`} style={{ cursor: "pointer", padding: 0, @@ -62,7 +94,7 @@ export function SouvenirsTab({ > {achievement.name} @@ -70,6 +102,22 @@ export function SouvenirsTab({
+ {isMe && ( + + )} @@ -84,7 +132,7 @@ export function SouvenirsTab({ /> )} - {achievement.name} + {achievement.displayName} diff --git a/src/types/index.ts b/src/types/index.ts index 91b5fe64..b632fcfb 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -191,12 +191,15 @@ export interface UserDetails { } export interface ProfileAchievement { + id: string; name: string; + displayName: string; imageUrl: string; unlockTime: number; gameTitle: string; gameIconUrl: string | null; achievementIcon: string | null; + gameId: string; } export interface UserProfile {