From e5f7e9addc4f4dbb7e41502f3e4169901ad033a8 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 4 Mar 2025 19:44:35 +0000 Subject: [PATCH] feat: adding dynamic badges --- package.json | 1 + src/locales/en/translation.json | 3 -- src/locales/pt-BR/translation.json | 3 -- src/locales/ru/translation.json | 3 -- src/main/events/index.ts | 1 + src/main/events/misc/get-badges.ts | 23 ++++++++++ src/preload/index.ts | 1 + .../src/assets/icons/badge-theme-creator.svg | 29 ------------ .../src/components/avatar/avatar.scss | 1 + src/renderer/src/components/avatar/avatar.tsx | 9 +++- .../user-profile/user-profile.context.tsx | 14 +++++- src/renderer/src/declaration.d.ts | 2 + src/renderer/src/main.tsx | 1 + .../achievements/achievements-content.scss | 2 +- .../profile/profile-hero/profile-hero.scss | 11 +++-- .../profile/profile-hero/profile-hero.tsx | 44 ++++++++++++++----- .../profile/profile-hero/user-badges.tsx | 40 ----------------- src/types/index.ts | 10 ++++- yarn.lock | 23 +++++++++- 19 files changed, 118 insertions(+), 103 deletions(-) create mode 100644 src/main/events/misc/get-badges.ts delete mode 100644 src/renderer/src/assets/icons/badge-theme-creator.svg delete mode 100644 src/renderer/src/pages/profile/profile-hero/user-badges.tsx diff --git a/package.json b/package.json index 136f3e2f..3d2e56fd 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "react-loading-skeleton": "^3.4.0", "react-redux": "^9.1.1", "react-router-dom": "^6.22.3", + "react-tooltip": "^5.28.0", "sound-play": "^1.1.0", "sudo-prompt": "^9.2.1", "tar": "^7.4.3", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index bf91cc40..f1e85019 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -444,9 +444,6 @@ "show_achievements_on_profile": "Show your achievements on your profile", "show_points_on_profile": "Show your earned points on your profile" }, - "badge": { - "badge_description_theme_creator": "Awarded to those who created a custom theme" - }, "achievement": { "achievement_unlocked": "Achievement unlocked", "user_achievements": "{{displayName}}'s Achievements", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 3556dbd1..0cefd188 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -440,9 +440,6 @@ "show_achievements_on_profile": "Exiba suas conquistas no perfil", "show_points_on_profile": "Exiba seus pontos ganhos no perfil" }, - "badge": { - "badge_description_theme_creator": "Concedido àqueles que criaram um tema customizado" - }, "achievement": { "achievement_unlocked": "Conquista desbloqueada", "your_achievements": "Suas Conquistas", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 494b8d06..c2aa40ce 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -440,9 +440,6 @@ "show_achievements_on_profile": "Покажите свои достижения в профиле", "show_points_on_profile": "Показывать заработанные очки в своем профиле" }, - "badge": { - "badge_description_theme_creator": "Награждается тот, кто создал пользовательскую тему" - }, "achievement": { "achievement_unlocked": "Достижение разблокировано", "user_achievements": "Достижения {{displayName}}", diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 572cba0f..8b8f4ebc 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -87,6 +87,7 @@ import "./themes/get-custom-theme-by-id"; import "./themes/get-active-custom-theme"; import "./themes/close-editor-window"; import "./themes/toggle-custom-theme"; +import "./misc/get-badges"; import { isPortableVersion } from "@main/helpers"; ipcMain.handle("ping", () => "pong"); diff --git a/src/main/events/misc/get-badges.ts b/src/main/events/misc/get-badges.ts new file mode 100644 index 00000000..b235acac --- /dev/null +++ b/src/main/events/misc/get-badges.ts @@ -0,0 +1,23 @@ +import { Badge } from "@types"; +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import { levelKeys } from "@main/level"; +import { db } from "@main/level"; + +const getBadges = async (_event: Electron.IpcMainInvokeEvent) => { + const language = await db + .get(levelKeys.language, { + valueEncoding: "utf-8", + }) + .then((language) => language || "en"); + + const params = new URLSearchParams({ + locale: language, + }); + + return HydraApi.get(`/badges?${params.toString()}`, null, { + needsAuth: false, + }); +}; + +registerEvent("getBadges", getBadges); diff --git a/src/preload/index.ts b/src/preload/index.ts index 5b3498ac..7b94cda1 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -266,6 +266,7 @@ contextBridge.exposeInMainWorld("electron", { showItemInFolder: (path: string) => ipcRenderer.invoke("showItemInFolder", path), getFeatures: () => ipcRenderer.invoke("getFeatures"), + getBadges: () => ipcRenderer.invoke("getBadges"), platform: process.platform, /* Auto update */ diff --git a/src/renderer/src/assets/icons/badge-theme-creator.svg b/src/renderer/src/assets/icons/badge-theme-creator.svg deleted file mode 100644 index 0793a7e9..00000000 --- a/src/renderer/src/assets/icons/badge-theme-creator.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/renderer/src/components/avatar/avatar.scss b/src/renderer/src/components/avatar/avatar.scss index 57ad6ebf..c45c28b1 100644 --- a/src/renderer/src/components/avatar/avatar.scss +++ b/src/renderer/src/components/avatar/avatar.scss @@ -10,6 +10,7 @@ cursor: pointer; color: globals.$muted-color; position: relative; + overflow: hidden; &__image { height: 100%; diff --git a/src/renderer/src/components/avatar/avatar.tsx b/src/renderer/src/components/avatar/avatar.tsx index 9d68998a..31047b77 100644 --- a/src/renderer/src/components/avatar/avatar.tsx +++ b/src/renderer/src/components/avatar/avatar.tsx @@ -18,7 +18,14 @@ export function Avatar({ size, alt, src, ...props }: AvatarProps) { return (
{src ? ( - {alt} + {alt} ) : ( )} 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 ce831981..9b9d16b4 100644 --- a/src/renderer/src/context/user-profile/user-profile.context.tsx +++ b/src/renderer/src/context/user-profile/user-profile.context.tsx @@ -1,6 +1,6 @@ import { darkenColor } from "@renderer/helpers"; import { useAppSelector, useToast } from "@renderer/hooks"; -import type { UserProfile, UserStats } from "@types"; +import type { Badge, UserProfile, UserStats } from "@types"; import { average } from "color.js"; import { createContext, useCallback, useEffect, useState } from "react"; @@ -16,6 +16,7 @@ export interface UserProfileContext { getUserProfile: () => Promise; setSelectedBackgroundImage: React.Dispatch>; backgroundImage: string; + badges: Badge[]; } export const DEFAULT_USER_PROFILE_BACKGROUND = "#151515B3"; @@ -28,6 +29,7 @@ export const userProfileContext = createContext({ getUserProfile: async () => {}, setSelectedBackgroundImage: () => {}, backgroundImage: "", + badges: [], }); const { Provider } = userProfileContext; @@ -47,6 +49,7 @@ export function UserProfileContextProvider({ const [userStats, setUserStats] = useState(null); const [userProfile, setUserProfile] = useState(null); + const [badges, setBadges] = useState([]); const [heroBackground, setHeroBackground] = useState( DEFAULT_USER_PROFILE_BACKGROUND ); @@ -101,12 +104,18 @@ export function UserProfileContextProvider({ }); }, [navigate, getUserStats, showErrorToast, userId, t]); + const getBadges = useCallback(async () => { + const badges = await window.electron.getBadges(); + setBadges(badges); + }, []); + useEffect(() => { setUserProfile(null); setHeroBackground(DEFAULT_USER_PROFILE_BACKGROUND); getUserProfile(); - }, [getUserProfile]); + getBadges(); + }, [getUserProfile, getBadges]); return ( {children} diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 1b588d5c..4700fbb1 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -30,6 +30,7 @@ import type { GameRunning, TorBoxUser, Theme, + Badge, } from "@types"; import type { AxiosProgressEvent } from "axios"; import type disk from "diskusage"; @@ -217,6 +218,7 @@ declare global { ) => Promise; showItemInFolder: (path: string) => Promise; getFeatures: () => Promise; + getBadges: () => Promise; platform: NodeJS.Platform; /* Auto update */ diff --git a/src/renderer/src/main.tsx b/src/renderer/src/main.tsx index eb890d18..bb0a3e03 100644 --- a/src/renderer/src/main.tsx +++ b/src/renderer/src/main.tsx @@ -11,6 +11,7 @@ import "@fontsource/noto-sans/500.css"; import "@fontsource/noto-sans/700.css"; import "react-loading-skeleton/dist/skeleton.css"; +import "react-tooltip/dist/react-tooltip.css"; import { App } from "./app"; diff --git a/src/renderer/src/pages/achievements/achievements-content.scss b/src/renderer/src/pages/achievements/achievements-content.scss index 8ee1773f..a378f490 100644 --- a/src/renderer/src/pages/achievements/achievements-content.scss +++ b/src/renderer/src/pages/achievements/achievements-content.scss @@ -31,7 +31,7 @@ $logo-max-width: 200px; display: flex; justify-content: center; width: 100%; - gap: globals.$spacing-unit / 2; + gap: calc(globals.$spacing-unit / 2); color: globals.$body-color; cursor: pointer; diff --git a/src/renderer/src/pages/profile/profile-hero/profile-hero.scss b/src/renderer/src/pages/profile/profile-hero/profile-hero.scss index fd21cf1d..834cb0cf 100644 --- a/src/renderer/src/pages/profile/profile-hero/profile-hero.scss +++ b/src/renderer/src/pages/profile/profile-hero/profile-hero.scss @@ -27,6 +27,11 @@ } } + &__badges { + display: flex; + gap: calc(globals.$spacing-unit / 2); + } + &__user-information { display: flex; padding: calc(globals.$spacing-unit * 7) calc(globals.$spacing-unit * 3); @@ -82,12 +87,6 @@ text-shadow: 0 0 5px rgb(0 0 0 / 40%); } - &__display-name-badges-container { - display: flex; - gap: globals.$spacing-unit; - align-items: center; - } - &__current-game { &-wrapper { display: flex; diff --git a/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx b/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx index 66799c47..fc354d01 100644 --- a/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx +++ b/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx @@ -24,8 +24,8 @@ import type { FriendRequestAction } from "@types"; import { EditProfileModal } from "../edit-profile-modal/edit-profile-modal"; import Skeleton from "react-loading-skeleton"; import { UploadBackgroundImageButton } from "../upload-background-image-button/upload-background-image-button"; +import { Tooltip } from "react-tooltip"; import "./profile-hero.scss"; -import { UserBadges } from "./user-badges"; type FriendAction = | FriendRequestAction @@ -35,8 +35,14 @@ export function ProfileHero() { const [showEditProfileModal, setShowEditProfileModal] = useState(false); const [isPerformingAction, setIsPerformingAction] = useState(false); - const { isMe, getUserProfile, userProfile, heroBackground, backgroundImage } = - useContext(userProfileContext); + const { + isMe, + badges, + getUserProfile, + userProfile, + heroBackground, + backgroundImage, + } = useContext(userProfileContext); const { signOut, updateFriendRequestState, @@ -261,14 +267,6 @@ export function ProfileHero() { return ( <> - {/* */} - setShowEditProfileModal(false)} @@ -312,7 +310,29 @@ export function ProfileHero() {

{userProfile?.displayName}

- + +
+ {userProfile.badges.map((badgeName) => { + const badge = badges.find((b) => b.name === badgeName); + + if (!badge) return null; + + return ( + {badge.name} + ); + })} + + +
) : ( diff --git a/src/renderer/src/pages/profile/profile-hero/user-badges.tsx b/src/renderer/src/pages/profile/profile-hero/user-badges.tsx deleted file mode 100644 index ed5d0571..00000000 --- a/src/renderer/src/pages/profile/profile-hero/user-badges.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import BadgeThemeCreator from "@renderer/assets/icons/badge-theme-creator.svg?react"; -import "./profile-hero.scss"; -import { useContext } from "react"; -import { userProfileContext } from "@renderer/context"; -import { UserBadge } from "@types"; -import { useTranslation } from "react-i18next"; - -export function UserBadges() { - const { t } = useTranslation("badge"); - const { userProfile } = useContext(userProfileContext); - - if (!userProfile?.badges?.length) return null; - - const getBadgeIcon = (badge: UserBadge) => { - if (badge === "THEME_CREATOR") { - return ; - } - - return null; - }; - - return ( -
- {userProfile.badges.map((badge) => { - const badgeIcon = getBadgeIcon(badge); - - if (!badgeIcon) return null; - return ( -
- {badgeIcon} -
- ); - })} -
- ); -} diff --git a/src/types/index.ts b/src/types/index.ts index e17a694a..a8726b6b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -130,7 +130,13 @@ export interface UserProfileCurrentGame extends Omit { export type ProfileVisibility = "PUBLIC" | "PRIVATE" | "FRIENDS"; -export type UserBadge = "THEME_CREATOR"; +export interface Badge { + name: string; + description: string; + badge: { + url: string; + }; +} export interface UserDetails { id: string; @@ -166,7 +172,7 @@ export interface UserProfile { quirks: { backupsPerGameLimit: number; }; - badges: UserBadge[]; + badges: string[]; } export interface UpdateProfileRequest { diff --git a/yarn.lock b/yarn.lock index 58d0cc9a..22b895e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1663,6 +1663,14 @@ "@floating-ui/core" "^1.6.0" "@floating-ui/utils" "^0.2.8" +"@floating-ui/dom@^1.6.1": + version "1.6.13" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.13.tgz#a8a938532aea27a95121ec16e667a7cbe8c59e34" + integrity sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w== + dependencies: + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.9" + "@floating-ui/react-dom@^2.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31" @@ -1675,6 +1683,11 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62" integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig== +"@floating-ui/utils@^0.2.9": + version "0.2.9" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429" + integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg== + "@fontsource/noto-sans@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@fontsource/noto-sans/-/noto-sans-5.1.0.tgz#54a5edd1b2b8c8e17bec6a85d4ee3a53b4b89c1f" @@ -4279,7 +4292,7 @@ classic-level@^2.0.0: napi-macros "^2.2.2" node-gyp-build "^4.3.0" -classnames@^2.2.1, classnames@^2.2.6, classnames@^2.5.1: +classnames@^2.2.1, classnames@^2.2.6, classnames@^2.3.0, classnames@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== @@ -8093,6 +8106,14 @@ react-style-singleton@^2.2.1: invariant "^2.2.4" tslib "^2.0.0" +react-tooltip@^5.28.0: + version "5.28.0" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.28.0.tgz#c7b5343ab2d740a428494a3d8315515af1f26f46" + integrity sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg== + dependencies: + "@floating-ui/dom" "^1.6.1" + classnames "^2.3.0" + react@^18.2.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"