From 3c296fe7216e68c9876ed7f1485b7c82acb763b9 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Tue, 23 Dec 2025 15:43:52 +0200 Subject: [PATCH] feat: add all badges modal and enhance badges display in profile --- src/main/services/hydra-api.ts | 2 +- .../profile-content/all-badges-modal.scss | 87 ++++++++++++++++ .../profile-content/all-badges-modal.tsx | 58 +++++++++++ .../profile/profile-content/badges-box.scss | 64 ++++++++++-- .../profile/profile-content/badges-box.tsx | 98 ++++++++++++------- src/types/index.ts | 1 + 6 files changed, 263 insertions(+), 47 deletions(-) create mode 100644 src/renderer/src/pages/profile/profile-content/all-badges-modal.scss create mode 100644 src/renderer/src/pages/profile/profile-content/all-badges-modal.tsx diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index fa712105..596b0635 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -30,7 +30,7 @@ export class HydraApi { private static instance: AxiosInstance; private static readonly EXPIRATION_OFFSET_IN_MS = 1000 * 60 * 5; // 5 minutes - private static readonly ADD_LOG_INTERCEPTOR = false; + private static readonly ADD_LOG_INTERCEPTOR = true; private static secondsToMilliseconds(seconds: number) { return seconds * 1000; diff --git a/src/renderer/src/pages/profile/profile-content/all-badges-modal.scss b/src/renderer/src/pages/profile/profile-content/all-badges-modal.scss new file mode 100644 index 00000000..83f8f6ef --- /dev/null +++ b/src/renderer/src/pages/profile/profile-content/all-badges-modal.scss @@ -0,0 +1,87 @@ +@use "../../../scss/globals.scss"; + +.all-badges-modal { + display: flex; + flex-direction: column; + gap: globals.$spacing-unit; + max-height: 400px; + margin-top: calc(globals.$spacing-unit * -1); + + &__title { + display: flex; + align-items: center; + gap: globals.$spacing-unit; + } + + &__count { + background-color: rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.7); + padding: 4px 8px; + border-radius: 6px; + font-size: 12px; + font-weight: 600; + min-width: 24px; + text-align: center; + } + + &__list { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 2); + overflow-y: auto; + padding-right: globals.$spacing-unit; + } + + &__item { + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit * 2); + padding: calc(globals.$spacing-unit * 1.5); + background-color: rgba(255, 255, 255, 0.05); + border-radius: 8px; + transition: background-color ease 0.2s; + + &:hover { + background-color: rgba(255, 255, 255, 0.1); + } + } + + &__item-icon { + flex-shrink: 0; + width: 48px; + height: 48px; + border-radius: 8px; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + background-color: globals.$background-color; + + img { + width: 32px; + height: 32px; + object-fit: contain; + } + } + + &__item-content { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 0.5); + flex: 1; + min-width: 0; + } + + &__item-title { + font-size: globals.$body-font-size; + font-weight: 600; + color: globals.$body-color; + margin: 0; + } + + &__item-description { + font-size: 0.75rem; + color: rgba(255, 255, 255, 0.6); + margin: 0; + } +} diff --git a/src/renderer/src/pages/profile/profile-content/all-badges-modal.tsx b/src/renderer/src/pages/profile/profile-content/all-badges-modal.tsx new file mode 100644 index 00000000..8eb50051 --- /dev/null +++ b/src/renderer/src/pages/profile/profile-content/all-badges-modal.tsx @@ -0,0 +1,58 @@ +import { useContext } from "react"; +import { useTranslation } from "react-i18next"; +import { Modal } from "@renderer/components"; +import { userProfileContext } from "@renderer/context"; +import "./all-badges-modal.scss"; + +interface AllBadgesModalProps { + visible: boolean; + onClose: () => void; +} + +export function AllBadgesModal({ + visible, + onClose, +}: Readonly) { + const { t } = useTranslation("user_profile"); + const { userProfile, badges } = useContext(userProfileContext); + + const userBadges = userProfile?.badges + .map((badgeName) => badges.find((b) => b.name === badgeName)) + .filter((badge) => badge !== undefined); + + const modalTitle = ( +
+ {t("badges")} + {userBadges && userBadges.length > 0 && ( + {userBadges.length} + )} +
+ ); + + return ( + +
+
+ {userBadges?.map((badge) => ( +
+
+ {badge.name} +
+
+

{badge.title}

+

+ {badge.description} +

+
+
+ ))} +
+
+
+ ); +} diff --git a/src/renderer/src/pages/profile/profile-content/badges-box.scss b/src/renderer/src/pages/profile/profile-content/badges-box.scss index cd68e338..01720b75 100644 --- a/src/renderer/src/pages/profile/profile-content/badges-box.scss +++ b/src/renderer/src/pages/profile/profile-content/badges-box.scss @@ -17,28 +17,76 @@ &__list { display: flex; - flex-wrap: wrap; + flex-direction: column; gap: calc(globals.$spacing-unit * 2); } &__item { display: flex; align-items: center; - justify-content: center; - width: 48px; - height: 48px; + gap: calc(globals.$spacing-unit * 2); + width: 100%; + padding: calc(globals.$spacing-unit * 1.5); background-color: rgba(255, 255, 255, 0.05); border-radius: 8px; - cursor: pointer; - transition: all ease 0.2s; + transition: background-color ease 0.2s; &:hover { background-color: rgba(255, 255, 255, 0.1); - transform: scale(1.05); } + } + + &__item-icon { + flex-shrink: 0; + width: 48px; + height: 48px; + border-radius: 8px; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + background-color: globals.$background-color; img { - border-radius: 4px; + width: 32px; + height: 32px; + object-fit: contain; + } + } + + &__item-content { + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 0.5); + flex: 1; + min-width: 0; + } + + &__item-title { + font-size: globals.$body-font-size; + font-weight: 600; + color: globals.$body-color; + margin: 0; + } + + &__item-description { + font-size: 0.75rem; + color: rgba(255, 255, 255, 0.6); + margin: 0; + } + + &__view-all { + background: none; + border: none; + color: globals.$body-color; + font-size: globals.$small-font-size; + cursor: pointer; + text-decoration: underline; + padding: 0; + transition: color ease 0.2s; + + &:hover { + color: globals.$muted-color; } } } diff --git a/src/renderer/src/pages/profile/profile-content/badges-box.tsx b/src/renderer/src/pages/profile/profile-content/badges-box.tsx index 94c68195..5b35ad3a 100644 --- a/src/renderer/src/pages/profile/profile-content/badges-box.tsx +++ b/src/renderer/src/pages/profile/profile-content/badges-box.tsx @@ -1,56 +1,78 @@ import { userProfileContext } from "@renderer/context"; import { useFormat } from "@renderer/hooks"; -import { useContext } from "react"; +import { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Tooltip } from "react-tooltip"; +import { AllBadgesModal } from "./all-badges-modal"; import "./badges-box.scss"; +const MAX_VISIBLE_BADGES = 4; + export function BadgesBox() { const { userProfile, badges } = useContext(userProfileContext); const { t } = useTranslation("user_profile"); const { numberFormatter } = useFormat(); + const [showAllBadgesModal, setShowAllBadgesModal] = useState(false); if (!userProfile?.badges.length) return null; + const visibleBadges = userProfile.badges.slice(0, MAX_VISIBLE_BADGES); + const hasMoreBadges = userProfile.badges.length > MAX_VISIBLE_BADGES; + return ( -
-
-
-

{t("badges")}

- - {numberFormatter.format(userProfile.badges.length)} - + <> +
+
+
+

{t("badges")}

+ + {numberFormatter.format(userProfile.badges.length)} + +
+ {hasMoreBadges && ( + + )} +
+ +
+
+ {visibleBadges.map((badgeName) => { + const badge = badges.find((b) => b.name === badgeName); + + if (!badge) return null; + + return ( +
+
+ {badge.name} +
+
+

{badge.title}

+

+ {badge.description} +

+
+
+ ); + })} +
-
-
- {userProfile.badges.map((badgeName) => { - const badge = badges.find((b) => b.name === badgeName); - - if (!badge) return null; - - return ( -
- {badge.name} -
- ); - })} -
- - -
-
+ setShowAllBadgesModal(false)} + /> + ); } diff --git a/src/types/index.ts b/src/types/index.ts index d326aeef..87eeb027 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -170,6 +170,7 @@ export type ProfileVisibility = "PUBLIC" | "PRIVATE" | "FRIENDS"; export interface Badge { name: string; + title: string; description: string; badge: { url: string;