From a4475d2145b6c56cd1e29674fa516ad4df78d0bb Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 13 Oct 2024 21:14:06 -0300 Subject: [PATCH] feat: achievement section for user not logged in --- src/locales/en/translation.json | 6 +- src/locales/pt-BR/translation.json | 6 +- src/locales/pt-PT/translation.json | 3 +- .../achievements/get-game-achievement-data.ts | 7 +- .../game-details/game-details.context.tsx | 37 ++++++-- .../sidebar-section/sidebar-section.tsx | 1 + .../pages/game-details/sidebar/sidebar.tsx | 90 +++++++++++++++++-- 7 files changed, 131 insertions(+), 19 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 6d2ea93b..53760b42 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -131,7 +131,8 @@ "executable_path_in_use": "Executable already in use by \"{{game}}\"", "warning": "Warning:", "hydra_needs_to_remain_open": "for this download, Hydra needs to remain open util its conclusion. In case Hydra closes before the conclusion, you will lose your progress.", - "achievements": "Achievements {{unlockedCount}}/{{achievementsCount}}", + "achievements": "Achievements", + "achievements_count": "Achievements {{unlockedCount}}/{{achievementsCount}}", "cloud_save": "Cloud save", "cloud_save_description": "Save your progress in the cloud and continue playing on any device", "backups": "Backups", @@ -146,7 +147,8 @@ "backup_uploaded": "Backup uploaded", "backup_deleted": "Backup deleted", "backup_restored": "Backup restored", - "see_all_achievements": "See all achievements" + "see_all_achievements": "See all achievements", + "sign_in_to_see_achievements": "Sign in to see achievements" }, "activation": { "title": "Activate Hydra", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index f5d91d46..72a941bc 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -127,7 +127,8 @@ "executable_path_in_use": "Executável em uso por \"{{game}}\"", "warning": "Aviso:", "hydra_needs_to_remain_open": "para este download, o Hydra precisa ficar aberto até a conclusão. Caso o Hydra encerre antes da conclusão, perderá seu progresso.", - "achievements": "Conquistas ({{unlockedCount}}/{{achievementsCount}})", + "achievements": "Conquistas", + "achievements_count": "Conquistas ({{unlockedCount}}/{{achievementsCount}})", "cloud_save": "Salvamento em nuvem", "cloud_save_description": "Matenha seu progresso na nuvem e continue de onde parou em qualquer dispositivo", "backups": "Backups", @@ -142,7 +143,8 @@ "backup_uploaded": "Backup criado", "backup_deleted": "Backup apagado", "backup_restored": "Backup restaurado", - "see_all_achievements": "Ver todas as conquistas" + "see_all_achievements": "Ver todas as conquistas", + "sign_in_to_see_achievements": "Faça login para ver as conquistas" }, "activation": { "title": "Ativação", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index a65ace2f..01fa3140 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -116,7 +116,8 @@ "executable_path_in_use": "Executável em uso por \"{{game}}\"", "warning": "Aviso:", "hydra_needs_to_remain_open": "para este download, o Hydra precisa ficar aberto até a conclusão. Caso o Hydra encerre antes da conclusão, perderá seu progresso.", - "achievements": "Conquistas ({{unlockedCount}}/{{achievementsCount}})", + "achievements": "Conquistas", + "achievements_count": "Conquistas ({{unlockedCount}}/{{achievementsCount}})", "see_all_achievements": "Ver todas as conquistas" }, "activation": { diff --git a/src/main/services/achievements/get-game-achievement-data.ts b/src/main/services/achievements/get-game-achievement-data.ts index 0cbadb54..b94cbe7f 100644 --- a/src/main/services/achievements/get-game-achievement-data.ts +++ b/src/main/services/achievements/get-game-achievement-data.ts @@ -4,6 +4,7 @@ import { } from "@main/repository"; import { HydraApi } from "../hydra-api"; import { AchievementData } from "@types"; +import { UserNotLoggedInError } from "@shared"; export const getGameAchievementData = async ( objectId: string, @@ -30,7 +31,11 @@ export const getGameAchievementData = async ( return achievements; }) - .catch(() => { + .catch((err) => { + if (err instanceof UserNotLoggedInError) { + throw err; + } + return gameAchievementRepository .findOne({ where: { objectId, shop }, diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index a08f4a55..5c5a0014 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -3,12 +3,18 @@ import { useCallback, useContext, useEffect, + useRef, useState, } from "react"; import { setHeaderTitle } from "@renderer/features"; import { getSteamLanguage } from "@renderer/helpers"; -import { useAppDispatch, useAppSelector, useDownload } from "@renderer/hooks"; +import { + useAppDispatch, + useAppSelector, + useDownload, + useUserDetails, +} from "@renderer/hooks"; import type { Game, @@ -67,6 +73,7 @@ export function GameDetailsContextProvider({ const [achievements, setAchievements] = useState([]); const [game, setGame] = useState(null); const [hasNSFWContentBlocked, setHasNSFWContentBlocked] = useState(false); + const abortControllerRef = useRef(null); const [stats, setStats] = useState(null); @@ -93,6 +100,7 @@ export function GameDetailsContextProvider({ const dispatch = useAppDispatch(); const { lastPacket } = useDownload(); + const { userDetails } = useUserDetails(); const userPreferences = useAppSelector( (state) => state.userPreferences.value @@ -111,6 +119,10 @@ export function GameDetailsContextProvider({ }, [updateGame, isGameDownloading, lastPacket?.game.status]); useEffect(() => { + if (abortControllerRef.current) abortControllerRef.current.abort(); + const abortController = new AbortController(); + abortControllerRef.current = abortController; + window.electron .getGameShopDetails( objectId!, @@ -118,6 +130,8 @@ export function GameDetailsContextProvider({ getSteamLanguage(i18n.language) ) .then((result) => { + if (abortController.signal.aborted) return; + setShopDetails(result); if ( @@ -133,21 +147,29 @@ export function GameDetailsContextProvider({ }); window.electron.getGameStats(objectId, shop as GameShop).then((result) => { + if (abortController.signal.aborted) return; setStats(result); }); window.electron .getGameAchievements(objectId, shop as GameShop) .then((achievements) => { - // TODO: race condition + if (abortController.signal.aborted) return; + if (!userDetails) return; setAchievements(achievements); }) - .catch(() => { - // TODO: handle user not logged in error - }); + .catch(() => {}); updateGame(); - }, [updateGame, dispatch, gameTitle, objectId, shop, i18n.language]); + }, [ + updateGame, + dispatch, + gameTitle, + objectId, + shop, + i18n.language, + userDetails, + ]); useEffect(() => { setShopDetails(null); @@ -180,6 +202,7 @@ export function GameDetailsContextProvider({ objectId, shop, (achievements) => { + if (!userDetails) return; setAchievements(achievements); } ); @@ -187,7 +210,7 @@ export function GameDetailsContextProvider({ return () => { unsubscribe(); }; - }, [objectId, shop]); + }, [objectId, shop, userDetails]); const getDownloadsPath = async () => { if (userPreferences?.downloadsPath) return userPreferences.downloadsPath; diff --git a/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.tsx b/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.tsx index 9ed48c9b..da9d078f 100644 --- a/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.tsx +++ b/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.tsx @@ -29,6 +29,7 @@ export function SidebarSection({ title, children }: SidebarSectionProps) { maxHeight: isOpen ? `${content.current?.scrollHeight}px` : "0", overflow: "hidden", transition: "max-height 0.4s cubic-bezier(0, 1, 0, 1)", + position: "relative", }} > {children} diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index d04afbc6..f72fcadc 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -1,16 +1,49 @@ import { useContext, useEffect, useState } from "react"; -import type { HowLongToBeatCategory, SteamAppDetails } from "@types"; +import type { + HowLongToBeatCategory, + SteamAppDetails, + UserAchievement, +} from "@types"; import { useTranslation } from "react-i18next"; import { Button, Link } from "@renderer/components"; import * as styles from "./sidebar.css"; import { gameDetailsContext } from "@renderer/context"; -import { useDate, useFormat } from "@renderer/hooks"; -import { DownloadIcon, PeopleIcon } from "@primer/octicons-react"; +import { useDate, useFormat, useUserDetails } from "@renderer/hooks"; +import { DownloadIcon, LockIcon, PeopleIcon } from "@primer/octicons-react"; import { HowLongToBeatSection } from "./how-long-to-beat-section"; import { howLongToBeatEntriesTable } from "@renderer/dexie"; import { SidebarSection } from "../sidebar-section/sidebar-section"; import { buildGameAchievementPath } from "@renderer/helpers"; +import { SPACING_UNIT } from "@renderer/theme.css"; + +const fakeAchievements: UserAchievement[] = [ + { + displayName: "Timber!!", + name: "", + hidden: false, + description: "Chop down your first tree.", + icon: "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/105600/0fbb33098c9da39d1d4771d8209afface9c46e81.jpg", + unlocked: true, + unlockTime: Date.now(), + }, + { + displayName: "Supreme Helper Minion!", + name: "", + hidden: false, + icon: "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/105600/0a6ff6a36670c96ceb4d30cf6fd69d2fdf55f38e.jpg", + unlocked: false, + unlockTime: null, + }, + { + displayName: "Feast of Midas", + name: "", + hidden: false, + icon: "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/apps/105600/2d10311274fe7c92ab25cc29afdca86b019ad472.jpg", + unlocked: false, + unlockTime: null, + }, +]; export function Sidebar() { const [howLongToBeat, setHowLongToBeat] = useState<{ @@ -18,6 +51,8 @@ export function Sidebar() { data: HowLongToBeatCategory[] | null; }>({ isLoading: true, data: null }); + const { userDetails } = useUserDetails(); + const [activeRequirement, setActiveRequirement] = useState("minimum"); @@ -68,9 +103,53 @@ export function Sidebar() { return (