feat: added pin/unpin to the game card in profile

This commit is contained in:
Moyasee
2025-09-25 20:20:49 +03:00
parent cad50649aa
commit a29f2ba741
4 changed files with 90 additions and 11 deletions

View File

@@ -14,6 +14,7 @@ export interface UserProfileContext {
isMe: boolean;
userStats: UserStats | null;
getUserProfile: () => Promise<void>;
getUserLibraryGames: () => Promise<void>;
setSelectedBackgroundImage: React.Dispatch<React.SetStateAction<string>>;
backgroundImage: string;
badges: Badge[];
@@ -29,6 +30,7 @@ export const userProfileContext = createContext<UserProfileContext>({
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,

View File

@@ -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 (
<section className="profile-content__section">
@@ -127,7 +128,9 @@ export function ProfileContent() {
<div className="profile-content__section-header">
<h2>{t("library")}</h2>
{userStats && (
<span>{numberFormatter.format(userStats.libraryCount)}</span>
<span>
{numberFormatter.format(userStats.libraryCount)}
</span>
)}
</div>

View File

@@ -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 {

View File

@@ -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 (
<>
<li
@@ -103,9 +132,26 @@ export function UserLibraryGameCard({
onClick={() => navigate(buildUserGameDetailsPath(game))}
>
<div className="user-library-game__overlay">
{game.isFavorite && (
<div className="user-library-game__favorite-icon">
<HeartFillIcon size={14} />
{(game.isFavorite || isMe) && (
<div className="user-library-game__actions-container">
{game.isFavorite && (
<div className="user-library-game__favorite-icon">
<HeartFillIcon size={12} />
</div>
)}
{isMe && (
<button
type="button"
className="user-library-game__pin-button"
onClick={(e) => {
e.stopPropagation();
toggleGamePinned();
}}
disabled={isPinning}
>
{game.isPinned ? <PinSlashIcon size={12} /> : <PinIcon size={12} />}
</button>
)}
</div>
)}
<small