diff --git a/.env.example b/.env.example index 3f914eb3..8ea7af55 100644 --- a/.env.example +++ b/.env.example @@ -3,3 +3,5 @@ MAIN_VITE_AUTH_URL= MAIN_VITE_WS_URL= RENDERER_VITE_REAL_DEBRID_REFERRAL_ID= RENDERER_VITE_TORBOX_REFERRAL_CODE= +VITE_GG_DEALS_API_URL=https://api.gg.deals/v1/prices/by-steam-app-id +VITE_GG_DEALS_API_KEY= \ No newline at end of file diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json old mode 100644 new mode 100755 index 66e0fee9..57c11d8a --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -241,7 +241,21 @@ "backup_frozen": "Backup pinned", "backup_unfrozen": "Backup unpinned", "backup_freeze_failed": "Failed to freeze backup", - "backup_freeze_failed_description": "You must leave at least one free slot for automatic backups" + "backup_freeze_failed_description": "You must leave at least one free slot for automatic backups", + "game_details": "Game Details", + "currency_symbol": "$", + "currency_country": "us", + "prices": "Prices", + "no_prices_found": "No prices found", + "view_all_prices": "Click to view all prices", + "retail_price": "Retail price", + "keyshop_price": "Keyshop price", + "historical_retail": "Historical retail", + "historical_keyshop": "Historical keyshop", + "supported_languages": "Supported languages", + "language": "Language", + "caption": "Caption", + "audio": "Audio" }, "activation": { "title": "Activate Hydra", @@ -287,6 +301,7 @@ "change": "Update", "notifications": "Notifications", "enable_download_notifications": "When a download is complete", + "gg_deals_api_key_description": "gg deals api key. Used to show the lowest price. (https://gg.deals/api/)", "enable_repack_list_notifications": "When a new repack is added", "real_debrid_api_token_label": "Real-Debrid API token", "quit_app_instead_hiding": "Don't hide Hydra when closing", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json old mode 100644 new mode 100755 index e8917d44..08228c2a --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -218,7 +218,21 @@ "backup_frozen": "Backup fixado", "backup_unfrozen": "Backup removido dos fixados", "backup_freeze_failed": "Falha ao fixar backup", - "backup_freeze_failed_description": "Você deve deixar pelo menos um espaço livre para backups automáticos" + "backup_freeze_failed_description": "Você deve deixar pelo menos um espaço livre para backups automáticos", + "game_details": "Detalhes do Jogo", + "currency_symbol": "R$", + "currency_country": "br", + "prices": "Preços", + "no_prices_found": "Nenhum preço encontrado", + "view_all_prices": "Clique para ver todos os preços", + "retail_price": "Preço de lojas oficiais", + "keyshop_price": "Preço em keyshops", + "historical_retail": "Preço histórico de lojas oficiais", + "historical_keyshop": "Preço histórico em keyshops", + "supported_languages": "Idiomas suportados", + "language": "Idioma", + "caption": "Legenda", + "audio": "Áudio" }, "activation": { "title": "Ativação", @@ -264,6 +278,7 @@ "change": "Explorar...", "notifications": "Notificações", "enable_download_notifications": "Quando um download for concluído", + "gg_deals_api_key_description": "gg deals api key. Usado para mostrar o menor preço. (https://gg.deals/api/)", "enable_repack_list_notifications": "Quando a lista de repacks for atualizada", "real_debrid_api_token_label": "Token de API do Real-Debrid", "quit_app_instead_hiding": "Encerrar o Hydra em vez de apenas minimizá-lo ao fechar", diff --git a/src/renderer/src/pages/game-details/sidebar/game-language-section.tsx b/src/renderer/src/pages/game-details/sidebar/game-language-section.tsx new file mode 100755 index 00000000..f67e4dfa --- /dev/null +++ b/src/renderer/src/pages/game-details/sidebar/game-language-section.tsx @@ -0,0 +1,66 @@ +import { useContext } from "react"; +import { useTranslation } from "react-i18next"; +import { gameDetailsContext } from "@renderer/context/game-details/game-details.context"; +import { SidebarSection } from "../sidebar-section/sidebar-section"; + +export function GameLanguageSection() { + const { t } = useTranslation("game_details"); + const { shopDetails, objectId } = useContext(gameDetailsContext); + + const getLanguages = () => { + let languages = shopDetails?.supported_languages; + if (!languages) return []; + languages = languages?.split("
")[0]; + const arrayIdiomas = languages?.split(","); + const listLanguages: { + language: string; + caption: string; + audio: string; + }[] = []; + arrayIdiomas?.forEach((lang) => { + const objectLanguage = { + language: lang.replace("*", ""), + caption: "✔", + audio: lang.includes("*") ? "✔" : "", + }; + listLanguages.push(objectLanguage); + }); + return listLanguages; + }; + + return ( + +
+

{t("supported_languages")}

+ + + + + + + + + + {getLanguages().map((lang) => ( + + + + + + ))} + +
{t("language")}{t("caption")}{t("audio")}
{lang.language}{lang.caption}{lang.audio}
+
+
+ + Link Steam + +
+
+ ); +} diff --git a/src/renderer/src/pages/game-details/sidebar/game-prices-section.tsx b/src/renderer/src/pages/game-details/sidebar/game-prices-section.tsx new file mode 100755 index 00000000..0753cdad --- /dev/null +++ b/src/renderer/src/pages/game-details/sidebar/game-prices-section.tsx @@ -0,0 +1,86 @@ +import { useCallback, useContext, useEffect, useState } from "react"; +import { SidebarSection } from "../sidebar-section/sidebar-section"; +import { useTranslation } from "react-i18next"; +import { gameDetailsContext } from "@renderer/context/game-details/game-details.context"; +import { useAppSelector } from "@renderer/hooks"; + +export function GamePricesSection() { + const userPreferences = useAppSelector( + (state) => state.userPreferences.value + ); + const { t } = useTranslation("game_details"); + const [priceData, setPriceData] = useState(null); + const [isLoadingPrices, setIsLoadingPrices] = useState(false); + const { objectId } = useContext(gameDetailsContext); + + const fetchGamePrices = useCallback(async (steamAppId: string) => { + setIsLoadingPrices(true); + try { + const apiKey = + userPreferences?.ggDealsApiKey || import.meta.env.VITE_GG_DEALS_API_KEY; + if (!apiKey) { + setPriceData(null); + setIsLoadingPrices(false); + return; + } + const url = `${import.meta.env.VITE_GG_DEALS_API_URL}/?ids=${steamAppId}&key=${apiKey}®ion=br`; + const response = await fetch(url); + if (!response.ok) { + throw new Error("Network response was not ok"); + } + const data = await response.json(); + setPriceData(data.data?.[steamAppId] ?? null); + } catch (error) { + setPriceData(null); + } finally { + setIsLoadingPrices(false); + } + }, []); + + useEffect(() => { + if (objectId) { + fetchGamePrices(objectId.toString()); + } + }, [objectId, fetchGamePrices]); + + return ( + + {isLoadingPrices ? ( +
{t("loading")}
+ ) : priceData ? ( +
+
    +
  • + {t("retail_price")}: {t("currency_symbol")} + {priceData.prices.currentRetail} +
  • +
  • + {t("keyshop_price")}: {t("currency_symbol")} + {priceData.prices.currentKeyshops} +
  • +
  • + {t("historical_retail")}: {t("currency_symbol")} + {priceData.prices.historicalRetail} +
  • +
  • + {t("historical_keyshop")}: {t("currency_symbol")} + {priceData.prices.historicalKeyshops} +
  • +
  • + + {t("view_all_prices")} + +
  • +
+
+ ) : ( +
{t("no_prices_found")}
+ )} +
+ ); +} diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.scss b/src/renderer/src/pages/game-details/sidebar/sidebar.scss old mode 100644 new mode 100755 index 783449fd..84386f12 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.scss +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.scss @@ -194,3 +194,25 @@ .achievements-placeholder__blur { filter: blur(4px); } + +.table-languages { + width: 100%; + border-collapse: collapse; + text-align: left; + + th, + td { + padding: globals.$spacing-unit; + border-bottom: solid 1px globals.$border-color; + } + + th { + font-size: globals.$small-font-size; + color: globals.$muted-color; + font-weight: normal; + } + + td { + font-size: globals.$body-font-size; + } +} diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx old mode 100644 new mode 100755 index 7e107810..af72dc92 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -21,6 +21,8 @@ import { SidebarSection } from "../sidebar-section/sidebar-section"; import { buildGameAchievementPath } from "@renderer/helpers"; import { useSubscription } from "@renderer/hooks/use-subscription"; import "./sidebar.scss"; +import { GamePricesSection } from "./game-prices-section"; +import { GameLanguageSection } from "./game-language-section"; const achievementsPlaceholder: UserAchievement[] = [ { @@ -115,6 +117,9 @@ export function Sidebar() { return (