From 0268829946468ad04bd027d93d6209f219cf677f Mon Sep 17 00:00:00 2001 From: Moyasee Date: Fri, 12 Dec 2025 13:53:12 +0200 Subject: [PATCH 1/8] feat: add Wrapped 2025 view in profile --- src/locales/en/translation.json | 6 +- src/main/services/window-manager.ts | 8 +- .../profile-content/profile-content.tsx | 15 ++- .../profile/profile-content/profile-tabs.tsx | 17 ++- .../profile/profile-content/wrapped-tab.scss | 73 ++++++++++++ .../profile/profile-content/wrapped-tab.tsx | 104 ++++++++++++++++++ 6 files changed, 214 insertions(+), 9 deletions(-) create mode 100644 src/renderer/src/pages/profile/profile-content/wrapped-tab.scss create mode 100644 src/renderer/src/pages/profile/profile-content/wrapped-tab.tsx diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index ed8c7d4e..bc6f45ee 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -715,7 +715,11 @@ "karma_description": "Earned from positive likes on reviews", "user_reviews": "Reviews", "delete_review": "Delete Review", - "loading_reviews": "Loading reviews..." + "loading_reviews": "Loading reviews...", + "wrapped_2025": "Wrapped 2025", + "view_wrapped_title": "View {{displayName}}'s Wrapped 2025?", + "view_wrapped_yes": "Yes", + "view_wrapped_no": "No" }, "library": { "library": "Library", diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 04c77619..26d13228 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -36,9 +36,9 @@ export class WindowManager { private static initialConfigInitializationMainWindow: Electron.BrowserWindowConstructorOptions = { width: 1200, - height: 720, + height: 860, minWidth: 1024, - minHeight: 540, + minHeight: 860, backgroundColor: "#1c1c1c", titleBarStyle: process.platform === "linux" ? "default" : "hidden", icon, @@ -106,7 +106,7 @@ export class WindowManager { valueEncoding: "json", } ); - return data ?? { isMaximized: false, height: 720, width: 1200 }; + return data ?? { isMaximized: false, height: 860, width: 1200 }; } private static updateInitialConfig( @@ -224,7 +224,7 @@ export class WindowManager { ? { x: undefined, y: undefined, - height: this.initialConfigInitializationMainWindow.height ?? 720, + height: this.initialConfigInitializationMainWindow.height ?? 860, width: this.initialConfigInitializationMainWindow.width ?? 1200, isMaximized: true, } diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.tsx b/src/renderer/src/pages/profile/profile-content/profile-content.tsx index 8176bace..a117c12a 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.tsx +++ b/src/renderer/src/pages/profile/profile-content/profile-content.tsx @@ -21,9 +21,10 @@ import { UserKarmaBox } from "./user-karma-box"; import { DeleteReviewModal } from "@renderer/pages/game-details/modals/delete-review-modal"; import { GAME_STATS_ANIMATION_DURATION_IN_MS } from "./profile-animations"; import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants"; -import { ProfileTabs } from "./profile-tabs"; +import { ProfileTabs, type ProfileTabType } from "./profile-tabs"; import { LibraryTab } from "./library-tab"; import { ReviewsTab } from "./reviews-tab"; +import { WrappedConfirmModal } from "./wrapped-tab"; import { AnimatePresence } from "framer-motion"; import "./profile-content.scss"; @@ -95,7 +96,7 @@ export function ProfileContent() { const [sortBy, setSortBy] = useState("playedRecently"); const statsAnimation = useRef(-1); - const [activeTab, setActiveTab] = useState<"library" | "reviews">("library"); + const [activeTab, setActiveTab] = useState("library"); // User reviews state const [reviews, setReviews] = useState([]); @@ -104,6 +105,7 @@ export function ProfileContent() { const [votingReviews, setVotingReviews] = useState>(new Set()); const [deleteModalVisible, setDeleteModalVisible] = useState(false); const [reviewToDelete, setReviewToDelete] = useState(null); + const [wrappedModalVisible, setWrappedModalVisible] = useState(false); const dispatch = useAppDispatch(); @@ -386,6 +388,7 @@ export function ProfileContent() { activeTab={activeTab} reviewsTotalCount={reviewsTotalCount} onTabChange={setActiveTab} + onWrappedClick={() => setWrappedModalVisible(true)} />
@@ -439,6 +442,13 @@ export function ProfileContent() { onClose={handleDeleteCancel} onConfirm={handleDeleteConfirm} /> + + setWrappedModalVisible(false)} + /> ); }, [ @@ -460,6 +470,7 @@ export function ProfileContent() { isLoadingReviews, votingReviews, deleteModalVisible, + wrappedModalVisible, ]); return ( diff --git a/src/renderer/src/pages/profile/profile-content/profile-tabs.tsx b/src/renderer/src/pages/profile/profile-content/profile-tabs.tsx index bc76f40c..9eac8843 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-tabs.tsx +++ b/src/renderer/src/pages/profile/profile-content/profile-tabs.tsx @@ -2,16 +2,20 @@ import { motion } from "framer-motion"; import { useTranslation } from "react-i18next"; import "./profile-content.scss"; +export type ProfileTabType = "library" | "reviews"; + interface ProfileTabsProps { - activeTab: "library" | "reviews"; + activeTab: ProfileTabType; reviewsTotalCount: number; - onTabChange: (tab: "library" | "reviews") => void; + onTabChange: (tab: ProfileTabType) => void; + onWrappedClick: () => void; } export function ProfileTabs({ activeTab, reviewsTotalCount, onTabChange, + onWrappedClick, }: Readonly) { const { t } = useTranslation("user_profile"); @@ -62,6 +66,15 @@ export function ProfileTabs({ /> )}
+
+ +
); } diff --git a/src/renderer/src/pages/profile/profile-content/wrapped-tab.scss b/src/renderer/src/pages/profile/profile-content/wrapped-tab.scss new file mode 100644 index 00000000..6669586d --- /dev/null +++ b/src/renderer/src/pages/profile/profile-content/wrapped-tab.scss @@ -0,0 +1,73 @@ +@use "../../../scss/globals.scss"; + +.wrapped-fullscreen-modal { + position: fixed; + inset: 0; + z-index: 999; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + margin: 0; + border: none; + background: transparent; + width: 100%; + height: 100%; + + &__backdrop { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.9); + border: none; + z-index: 1; + } + + &__container { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + padding: calc(globals.$spacing-unit * 2); + pointer-events: none; + z-index: 2; + } + + &__close-button { + position: absolute; + top: calc(globals.$spacing-unit * 5); + right: calc(globals.$spacing-unit * 5); + background: rgba(255, 255, 255, 0.1); + border: none; + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: white; + transition: background 0.2s ease; + z-index: 10; + pointer-events: auto; + + &:hover { + background: rgba(255, 255, 255, 0.2); + } + } + + &__content { + border-radius: 8px; + overflow: hidden; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 8px 48px rgba(0, 0, 0, 0.5); + pointer-events: auto; + } + + &__iframe { + width: 100%; + height: 100%; + border: none; + } +} diff --git a/src/renderer/src/pages/profile/profile-content/wrapped-tab.tsx b/src/renderer/src/pages/profile/profile-content/wrapped-tab.tsx new file mode 100644 index 00000000..1716fcc0 --- /dev/null +++ b/src/renderer/src/pages/profile/profile-content/wrapped-tab.tsx @@ -0,0 +1,104 @@ +import { useState, useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import { XIcon } from "@primer/octicons-react"; +import { ConfirmationModal } from "@renderer/components"; +import "./wrapped-tab.scss"; + +interface WrappedModalProps { + userId: string; + displayName: string; + isOpen: boolean; + onClose: () => void; +} + +interface ScaleConfig { + scale: number; + width: number; + height: number; +} + +const SCALE_CONFIGS: Record = { + 0.25: { scale: 0.25, width: 270, height: 480 }, + 0.3: { scale: 0.3, width: 324, height: 576 }, + 0.5: { scale: 0.5, width: 540, height: 960 }, +}; + +const getScaleConfigForHeight = (height: number): ScaleConfig => { + if (height >= 1000) return SCALE_CONFIGS[0.5]; + if (height >= 650) return SCALE_CONFIGS[0.3]; + return SCALE_CONFIGS[0.25]; +}; + +export function WrappedConfirmModal({ + userId, + displayName, + isOpen, + onClose, +}: Readonly) { + const { t } = useTranslation("user_profile"); + const [showFullscreen, setShowFullscreen] = useState(false); + const [config, setConfig] = useState(SCALE_CONFIGS[0.5]); + + useEffect(() => { + if (!showFullscreen) return; + + const updateConfig = () => { + setConfig(getScaleConfigForHeight(window.innerHeight)); + }; + + updateConfig(); + window.addEventListener("resize", updateConfig); + return () => window.removeEventListener("resize", updateConfig); + }, [showFullscreen]); + + const handleConfirm = () => { + onClose(); + setShowFullscreen(true); + }; + + return ( + <> + + + {showFullscreen && ( + + + +
+