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; isMe: boolean;
userStats: UserStats | null; userStats: UserStats | null;
getUserProfile: () => Promise<void>; getUserProfile: () => Promise<void>;
getUserLibraryGames: () => Promise<void>;
setSelectedBackgroundImage: React.Dispatch<React.SetStateAction<string>>; setSelectedBackgroundImage: React.Dispatch<React.SetStateAction<string>>;
backgroundImage: string; backgroundImage: string;
badges: Badge[]; badges: Badge[];
@@ -29,6 +30,7 @@ export const userProfileContext = createContext<UserProfileContext>({
isMe: false, isMe: false,
userStats: null, userStats: null,
getUserProfile: async () => {}, getUserProfile: async () => {},
getUserLibraryGames: async () => {},
setSelectedBackgroundImage: () => {}, setSelectedBackgroundImage: () => {},
backgroundImage: "", backgroundImage: "",
badges: [], badges: [],
@@ -149,6 +151,7 @@ export function UserProfileContextProvider({
heroBackground, heroBackground,
isMe, isMe,
getUserProfile, getUserProfile,
getUserLibraryGames,
setSelectedBackgroundImage, setSelectedBackgroundImage,
backgroundImage: getBackgroundImageUrl(), backgroundImage: getBackgroundImageUrl(),
userStats, userStats,

View File

@@ -84,7 +84,8 @@ export function ProfileContent() {
const hasPinnedGames = pinnedGames.length > 0; const hasPinnedGames = pinnedGames.length > 0;
const hasAnyGames = hasGames || hasPinnedGames; const hasAnyGames = hasGames || hasPinnedGames;
const shouldShowRightContent = hasAnyGames || userProfile.friends.length > 0; const shouldShowRightContent =
hasAnyGames || userProfile.friends.length > 0;
return ( return (
<section className="profile-content__section"> <section className="profile-content__section">
@@ -127,7 +128,9 @@ export function ProfileContent() {
<div className="profile-content__section-header"> <div className="profile-content__section-header">
<h2>{t("library")}</h2> <h2>{t("library")}</h2>
{userStats && ( {userStats && (
<span>{numberFormatter.format(userStats.libraryCount)}</span> <span>
{numberFormatter.format(userStats.libraryCount)}
</span>
)} )}
</div> </div>

View File

@@ -65,18 +65,45 @@
padding: 8px; padding: 8px;
} }
&__favorite-icon { &__actions-container {
position: absolute; position: absolute;
top: 8px; top: 8px;
right: 8px; right: 8px;
color: #ff6b6b; display: flex;
gap: 6px;
z-index: 2;
}
&__favorite-icon {
color: white;
background-color: rgba(0, 0, 0, 0.7); background-color: rgba(0, 0, 0, 0.7);
border-radius: 50%; border-radius: 50%;
padding: 4px; padding: 6px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: 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 { &__playtime {

View File

@@ -1,6 +1,6 @@
import { UserGame } from "@types"; import { UserGame } from "@types";
import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; 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 { useNavigate } from "react-router-dom";
import { useCallback, useContext, useState } from "react"; import { useCallback, useContext, useState } from "react";
import { import {
@@ -14,6 +14,8 @@ import {
TrophyIcon, TrophyIcon,
AlertFillIcon, AlertFillIcon,
HeartFillIcon, HeartFillIcon,
PinIcon,
PinSlashIcon,
} from "@primer/octicons-react"; } from "@primer/octicons-react";
import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants"; import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants";
import { Tooltip } from "react-tooltip"; import { Tooltip } from "react-tooltip";
@@ -33,11 +35,14 @@ export function UserLibraryGameCard({
onMouseEnter, onMouseEnter,
onMouseLeave, onMouseLeave,
}: UserLibraryGameCardProps) { }: UserLibraryGameCardProps) {
const { userProfile } = useContext(userProfileContext); const { userProfile, isMe, getUserLibraryGames } = useContext(userProfileContext);
const { t } = useTranslation("user_profile"); const { t } = useTranslation("user_profile");
const { t: tGame } = useTranslation("game_details");
const { numberFormatter } = useFormat(); const { numberFormatter } = useFormat();
const { showSuccessToast } = useToast();
const navigate = useNavigate(); const navigate = useNavigate();
const [isTooltipHovered, setIsTooltipHovered] = useState(false); const [isTooltipHovered, setIsTooltipHovered] = useState(false);
const [isPinning, setIsPinning] = useState(false);
const getStatsItemCount = useCallback(() => { const getStatsItemCount = useCallback(() => {
let statsCount = 1; let statsCount = 1;
@@ -89,6 +94,30 @@ export function UserLibraryGameCard({
[numberFormatter, t] [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 ( return (
<> <>
<li <li
@@ -103,9 +132,26 @@ export function UserLibraryGameCard({
onClick={() => navigate(buildUserGameDetailsPath(game))} onClick={() => navigate(buildUserGameDetailsPath(game))}
> >
<div className="user-library-game__overlay"> <div className="user-library-game__overlay">
{game.isFavorite && ( {(game.isFavorite || isMe) && (
<div className="user-library-game__favorite-icon"> <div className="user-library-game__actions-container">
<HeartFillIcon size={14} /> {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> </div>
)} )}
<small <small