From a29f2ba7414dfc4fe459ea5ddd4e9ee942f03016 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Thu, 25 Sep 2025 20:20:49 +0300 Subject: [PATCH] feat: added pin/unpin to the game card in profile --- .../user-profile/user-profile.context.tsx | 3 + .../profile-content/profile-content.tsx | 7 ++- .../user-library-game-card.scss | 35 ++++++++++-- .../user-library-game-card.tsx | 56 +++++++++++++++++-- 4 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/renderer/src/context/user-profile/user-profile.context.tsx b/src/renderer/src/context/user-profile/user-profile.context.tsx index 3b10b3e5..499b85ee 100644 --- a/src/renderer/src/context/user-profile/user-profile.context.tsx +++ b/src/renderer/src/context/user-profile/user-profile.context.tsx @@ -14,6 +14,7 @@ export interface UserProfileContext { isMe: boolean; userStats: UserStats | null; getUserProfile: () => Promise; + getUserLibraryGames: () => Promise; setSelectedBackgroundImage: React.Dispatch>; backgroundImage: string; badges: Badge[]; @@ -29,6 +30,7 @@ export const userProfileContext = createContext({ isMe: false, userStats: null, getUserProfile: async () => {}, + getUserLibraryGames: async () => {}, setSelectedBackgroundImage: () => {}, backgroundImage: "", badges: [], @@ -149,6 +151,7 @@ export function UserProfileContextProvider({ heroBackground, isMe, getUserProfile, + getUserLibraryGames, setSelectedBackgroundImage, backgroundImage: getBackgroundImageUrl(), userStats, 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 68e7fecf..3c27ae02 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.tsx +++ b/src/renderer/src/pages/profile/profile-content/profile-content.tsx @@ -84,7 +84,8 @@ export function ProfileContent() { const hasPinnedGames = pinnedGames.length > 0; const hasAnyGames = hasGames || hasPinnedGames; - const shouldShowRightContent = hasAnyGames || userProfile.friends.length > 0; + const shouldShowRightContent = + hasAnyGames || userProfile.friends.length > 0; return (
@@ -127,7 +128,9 @@ export function ProfileContent() {

{t("library")}

{userStats && ( - {numberFormatter.format(userStats.libraryCount)} + + {numberFormatter.format(userStats.libraryCount)} + )}
diff --git a/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss b/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss index ba3f5602..ab1f3456 100644 --- a/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss +++ b/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss @@ -65,18 +65,45 @@ padding: 8px; } - &__favorite-icon { + &__actions-container { position: absolute; top: 8px; right: 8px; - color: #ff6b6b; + display: flex; + gap: 6px; + z-index: 2; + } + + &__favorite-icon { + color: white; background-color: rgba(0, 0, 0, 0.7); border-radius: 50%; - padding: 4px; + padding: 6px; display: flex; align-items: center; justify-content: center; - z-index: 2; + } + + &__pin-button { + color: white; + background-color: rgba(0, 0, 0, 0.7); + border: none; + border-radius: 50%; + padding: 6px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: background-color 0.2s ease; + + &:hover { + background-color: rgba(0, 0, 0, 0.9); + } + + &:disabled { + opacity: 0.6; + cursor: not-allowed; + } } &__playtime { diff --git a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx index e64cd3bb..ca31b5de 100644 --- a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx +++ b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx @@ -1,6 +1,6 @@ import { UserGame } from "@types"; import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; -import { useFormat } from "@renderer/hooks"; +import { useFormat, useToast } from "@renderer/hooks"; import { useNavigate } from "react-router-dom"; import { useCallback, useContext, useState } from "react"; import { @@ -14,6 +14,8 @@ import { TrophyIcon, AlertFillIcon, HeartFillIcon, + PinIcon, + PinSlashIcon, } from "@primer/octicons-react"; import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants"; import { Tooltip } from "react-tooltip"; @@ -33,11 +35,14 @@ export function UserLibraryGameCard({ onMouseEnter, onMouseLeave, }: UserLibraryGameCardProps) { - const { userProfile } = useContext(userProfileContext); + const { userProfile, isMe, getUserLibraryGames } = useContext(userProfileContext); const { t } = useTranslation("user_profile"); + const { t: tGame } = useTranslation("game_details"); const { numberFormatter } = useFormat(); + const { showSuccessToast } = useToast(); const navigate = useNavigate(); const [isTooltipHovered, setIsTooltipHovered] = useState(false); + const [isPinning, setIsPinning] = useState(false); const getStatsItemCount = useCallback(() => { let statsCount = 1; @@ -89,6 +94,30 @@ export function UserLibraryGameCard({ [numberFormatter, t] ); + const toggleGamePinned = async () => { + setIsPinning(true); + + try { + if (game.isPinned) { + await window.electron.removeGameFromPinned(game.shop, game.objectId).then(() => { + showSuccessToast(tGame("game_removed_from_pinned")); + }); + } else { + await window.electron.addGameToPinned(game.shop, game.objectId).then(() => { + showSuccessToast(tGame("game_added_to_pinned")); + }); + } + + // Add a small delay to allow server synchronization before refreshing + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Refresh the library games to update the UI + await getUserLibraryGames(); + } finally { + setIsPinning(false); + } + }; + return ( <>
  • navigate(buildUserGameDetailsPath(game))} >
    - {game.isFavorite && ( -
    - + {(game.isFavorite || isMe) && ( +
    + {game.isFavorite && ( +
    + +
    + )} + {isMe && ( + + )}
    )}