Skip to content

Commit bc3d47e

Browse files
committed
feat: deleting achievement souvenirs
1 parent e1c60c6 commit bc3d47e

File tree

5 files changed

+106
-15
lines changed

5 files changed

+106
-15
lines changed

src/locales/en/translation.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,9 @@
713713
"karma_description": "Earned from positive likes on reviews",
714714
"user_reviews": "Reviews",
715715
"delete_review": "Delete Review",
716-
"loading_reviews": "Loading reviews..."
716+
"loading_reviews": "Loading reviews...",
717+
"souvenir_deleted_successfully": "Souvenir deleted successfully",
718+
"souvenir_deletion_failed": "Failed to delete souvenir"
717719
},
718720
"library": {
719721
"library": "Library",

src/renderer/src/pages/profile/profile-content/profile-content.scss

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,41 @@
326326
}
327327
}
328328

329+
&__image-delete-button {
330+
position: absolute;
331+
top: 8px;
332+
right: 8px;
333+
width: 32px;
334+
height: 32px;
335+
display: flex;
336+
align-items: center;
337+
justify-content: center;
338+
background: rgba(244, 67, 54, 0.9);
339+
border: 1px solid rgba(244, 67, 54, 0.5);
340+
border-radius: 6px;
341+
color: white;
342+
cursor: pointer;
343+
transition: all 0.2s ease;
344+
z-index: 3;
345+
opacity: 0;
346+
347+
&:hover {
348+
background: rgba(244, 67, 54, 1);
349+
border-color: rgba(244, 67, 54, 0.7);
350+
transform: scale(1.1);
351+
}
352+
353+
&:disabled {
354+
opacity: 0.5;
355+
cursor: not-allowed;
356+
transform: none;
357+
}
358+
}
359+
360+
&__image-achievement-image-wrapper:hover &__image-delete-button {
361+
opacity: 1;
362+
}
363+
329364
// Show overlay on keyboard focus for accessibility
330365
&__image-button:focus-visible + &__image-achievement-image-overlay {
331366
opacity: 1;

src/renderer/src/pages/profile/profile-content/profile-content.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export function ProfileContent() {
8888
userStats,
8989
libraryGames,
9090
pinnedGames,
91+
getUserProfile,
9192
getUserLibraryGames,
9293
loadMoreLibraryGames,
9394
hasMoreLibraryGames,
@@ -459,6 +460,8 @@ export function ProfileContent() {
459460
<SouvenirsTab
460461
achievements={userProfile?.achievements || []}
461462
onImageClick={handleImageClick}
463+
isMe={isMe}
464+
onAchievementDeleted={getUserProfile}
462465
/>
463466
)}
464467
</AnimatePresence>

src/renderer/src/pages/profile/profile-content/souvenirs-tab.tsx

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,55 @@
11
import { motion } from "framer-motion";
22
import { useTranslation } from "react-i18next";
3-
import { SearchIcon } from "@primer/octicons-react";
3+
import { SearchIcon, XIcon } from "@primer/octicons-react";
4+
import { useState } from "react";
5+
import type { ProfileAchievement } from "@types";
6+
import { useToast } from "@renderer/hooks";
7+
import { logger } from "@renderer/logger";
48
import "./profile-content.scss";
59

6-
interface Achievement {
7-
name: string;
8-
imageUrl: string;
9-
achievementIcon: string | null;
10-
gameTitle: string;
11-
gameIconUrl: string | null;
12-
}
13-
1410
interface SouvenirsTabProps {
15-
achievements: Achievement[];
11+
achievements: ProfileAchievement[];
1612
onImageClick: (imageUrl: string, achievementName: string) => void;
13+
isMe: boolean;
14+
onAchievementDeleted: () => void;
1715
}
1816

1917
export function SouvenirsTab({
2018
achievements,
2119
onImageClick,
20+
isMe,
21+
onAchievementDeleted,
2222
}: Readonly<SouvenirsTabProps>) {
2323
const { t } = useTranslation("user_profile");
24+
const { showSuccessToast, showErrorToast } = useToast();
25+
const [deletingIds, setDeletingIds] = useState<Set<string>>(new Set());
26+
27+
const handleDeleteAchievement = async (achievement: ProfileAchievement) => {
28+
if (deletingIds.has(achievement.id)) return;
29+
30+
setDeletingIds((prev) => new Set(prev).add(achievement.id));
31+
32+
try {
33+
await window.electron.hydraApi.delete(
34+
`/profile/games/achievements/${achievement.gameId}/${achievement.name}/image`
35+
);
36+
37+
showSuccessToast(
38+
t("souvenir_deleted_successfully", "Souvenir deleted successfully")
39+
);
40+
onAchievementDeleted();
41+
} catch (error) {
42+
logger.error("Failed to delete souvenir:", error);
43+
showErrorToast(
44+
t("souvenir_deletion_failed", "Failed to delete souvenir")
45+
);
46+
setDeletingIds((prev) => {
47+
const next = new Set(prev);
48+
next.delete(achievement.id);
49+
return next;
50+
});
51+
}
52+
};
2453

2554
return (
2655
<motion.div
@@ -50,9 +79,12 @@ export function SouvenirsTab({
5079
type="button"
5180
className="profile-content__image-button"
5281
onClick={() =>
53-
onImageClick(achievement.imageUrl, achievement.name)
82+
onImageClick(
83+
achievement.imageUrl,
84+
achievement.displayName
85+
)
5486
}
55-
aria-label={`View ${achievement.name} screenshot in fullscreen`}
87+
aria-label={`View ${achievement.displayName} screenshot in fullscreen`}
5688
style={{
5789
cursor: "pointer",
5890
padding: 0,
@@ -62,14 +94,30 @@ export function SouvenirsTab({
6294
>
6395
<img
6496
src={achievement.imageUrl}
65-
alt={achievement.name}
97+
alt={achievement.displayName}
6698
className="profile-content__image-achievement-image"
6799
loading="lazy"
68100
/>
69101
</button>
70102
<div className="profile-content__image-achievement-image-overlay">
71103
<SearchIcon size={20} />
72104
</div>
105+
{isMe && (
106+
<button
107+
type="button"
108+
className="profile-content__image-delete-button"
109+
onClick={() => handleDeleteAchievement(achievement)}
110+
aria-label={`Delete ${achievement.displayName} souvenir`}
111+
disabled={deletingIds.has(achievement.id)}
112+
style={{
113+
cursor: deletingIds.has(achievement.id)
114+
? "not-allowed"
115+
: "pointer",
116+
}}
117+
>
118+
<XIcon size={16} />
119+
</button>
120+
)}
73121
</div>
74122
</div>
75123

@@ -84,7 +132,7 @@ export function SouvenirsTab({
84132
/>
85133
)}
86134
<span className="profile-content__image-achievement-name">
87-
{achievement.name}
135+
{achievement.displayName}
88136
</span>
89137
</div>
90138

src/types/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,15 @@ export interface UserDetails {
191191
}
192192

193193
export interface ProfileAchievement {
194+
id: string;
194195
name: string;
196+
displayName: string;
195197
imageUrl: string;
196198
unlockTime: number;
197199
gameTitle: string;
198200
gameIconUrl: string | null;
199201
achievementIcon: string | null;
202+
gameId: string;
200203
}
201204

202205
export interface UserProfile {

0 commit comments

Comments
 (0)