mirror of
				https://github.com/hydralauncher/hydra.git
				synced 2025-03-09 15:40:26 +00:00 
			
		
		
		
	feat: create achievements page
This commit is contained in:
		
							parent
							
								
									6d4f957e2b
								
							
						
					
					
						commit
						39af661720
					
				
					 8 changed files with 76 additions and 20 deletions
				
			
		| 
						 | 
				
			
			@ -1,13 +1,19 @@
 | 
			
		|||
import type { GameAchievement, GameShop } from "@types";
 | 
			
		||||
import type { GameAchievement, GameShop, UnlockedAchievement } from "@types";
 | 
			
		||||
import { registerEvent } from "../register-event";
 | 
			
		||||
import { gameAchievementRepository } from "@main/repository";
 | 
			
		||||
import {
 | 
			
		||||
  gameAchievementRepository,
 | 
			
		||||
  userAuthRepository,
 | 
			
		||||
} from "@main/repository";
 | 
			
		||||
import { getGameAchievementData } from "@main/services/achievements/get-game-achievement-data";
 | 
			
		||||
import { HydraApi } from "@main/services";
 | 
			
		||||
 | 
			
		||||
const getGameAchievements = async (
 | 
			
		||||
  _event: Electron.IpcMainInvokeEvent,
 | 
			
		||||
const getAchievements = async (
 | 
			
		||||
  shop: string,
 | 
			
		||||
  objectId: string,
 | 
			
		||||
  shop: GameShop
 | 
			
		||||
): Promise<GameAchievement[]> => {
 | 
			
		||||
  userId?: string
 | 
			
		||||
) => {
 | 
			
		||||
  const userAuth = await userAuthRepository.findOne({ where: { userId } });
 | 
			
		||||
 | 
			
		||||
  const cachedAchievements = await gameAchievementRepository.findOne({
 | 
			
		||||
    where: { objectId, shop },
 | 
			
		||||
  });
 | 
			
		||||
| 
						 | 
				
			
			@ -16,9 +22,33 @@ const getGameAchievements = async (
 | 
			
		|||
    ? JSON.parse(cachedAchievements.achievements)
 | 
			
		||||
    : await getGameAchievementData(objectId, shop);
 | 
			
		||||
 | 
			
		||||
  if (!userId || userAuth) {
 | 
			
		||||
    const unlockedAchievements = JSON.parse(
 | 
			
		||||
      cachedAchievements?.unlockedAchievements || "[]"
 | 
			
		||||
  ) as { name: string; unlockTime: number }[];
 | 
			
		||||
    ) as UnlockedAchievement[];
 | 
			
		||||
 | 
			
		||||
    return { achievementsData, unlockedAchievements };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const unlockedAchievements = await HydraApi.get<UnlockedAchievement[]>(
 | 
			
		||||
    `/users/${userId}/games/achievements`,
 | 
			
		||||
    { shop, objectId, language: "en" }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return { achievementsData, unlockedAchievements };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getGameAchievements = async (
 | 
			
		||||
  _event: Electron.IpcMainInvokeEvent,
 | 
			
		||||
  objectId: string,
 | 
			
		||||
  shop: GameShop,
 | 
			
		||||
  userId?: string
 | 
			
		||||
): Promise<GameAchievement[]> => {
 | 
			
		||||
  const { achievementsData, unlockedAchievements } = await getAchievements(
 | 
			
		||||
    shop,
 | 
			
		||||
    objectId,
 | 
			
		||||
    userId
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return achievementsData
 | 
			
		||||
    .map((achievementData) => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,8 +49,8 @@ contextBridge.exposeInMainWorld("electron", {
 | 
			
		|||
  getGameStats: (objectId: string, shop: GameShop) =>
 | 
			
		||||
    ipcRenderer.invoke("getGameStats", objectId, shop),
 | 
			
		||||
  getTrendingGames: () => ipcRenderer.invoke("getTrendingGames"),
 | 
			
		||||
  getGameAchievements: (objectId: string, shop: GameShop) =>
 | 
			
		||||
    ipcRenderer.invoke("getGameAchievements", objectId, shop),
 | 
			
		||||
  getGameAchievements: (objectId: string, shop: GameShop, userId?: string) =>
 | 
			
		||||
    ipcRenderer.invoke("getGameAchievements", objectId, shop, userId),
 | 
			
		||||
  onAchievementUnlocked: (
 | 
			
		||||
    cb: (
 | 
			
		||||
      objectId: string,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										3
									
								
								src/renderer/src/declaration.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								src/renderer/src/declaration.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -68,7 +68,8 @@ declare global {
 | 
			
		|||
    getTrendingGames: () => Promise<TrendingGame[]>;
 | 
			
		||||
    getGameAchievements: (
 | 
			
		||||
      objectId: string,
 | 
			
		||||
      shop: GameShop
 | 
			
		||||
      shop: GameShop,
 | 
			
		||||
      userId?: string
 | 
			
		||||
    ) => Promise<GameAchievement[]>;
 | 
			
		||||
    onAchievementUnlocked: (
 | 
			
		||||
      cb: (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,10 +28,11 @@ import {
 | 
			
		|||
import { store } from "./store";
 | 
			
		||||
 | 
			
		||||
import resources from "@locales";
 | 
			
		||||
import { Achievement } from "./pages/achievement/achievement";
 | 
			
		||||
import { AchievementNotification } from "./pages/achievement/notification/achievement-notification";
 | 
			
		||||
 | 
			
		||||
import "./workers";
 | 
			
		||||
import { RepacksContextProvider } from "./context";
 | 
			
		||||
import { Achievement } from "./pages/achievement/achievements";
 | 
			
		||||
 | 
			
		||||
Sentry.init({});
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -69,8 +70,12 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
 | 
			
		|||
              <Route path="/search" Component={SearchResults} />
 | 
			
		||||
              <Route path="/settings" Component={Settings} />
 | 
			
		||||
              <Route path="/profile/:userId" Component={Profile} />
 | 
			
		||||
              <Route path="/achievements" Component={Achievement} />
 | 
			
		||||
            </Route>
 | 
			
		||||
            <Route path="/achievement-notification" Component={Achievement} />
 | 
			
		||||
            <Route
 | 
			
		||||
              path="/achievement-notification"
 | 
			
		||||
              Component={AchievementNotification}
 | 
			
		||||
            />
 | 
			
		||||
          </Routes>
 | 
			
		||||
        </HashRouter>
 | 
			
		||||
      </RepacksContextProvider>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										11
									
								
								src/renderer/src/pages/achievement/achievements.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/renderer/src/pages/achievement/achievements.tsx
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
import { useSearchParams } from "react-router-dom";
 | 
			
		||||
 | 
			
		||||
export function Achievement() {
 | 
			
		||||
  const [searchParams] = useSearchParams();
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <h1>Achievement</h1>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
import { recipe } from "@vanilla-extract/recipes";
 | 
			
		||||
import { vars } from "../../theme.css";
 | 
			
		||||
import { vars } from "../../../theme.css";
 | 
			
		||||
import { keyframes, style } from "@vanilla-extract/css";
 | 
			
		||||
 | 
			
		||||
const animationIn = keyframes({
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
 | 
			
		||||
import achievementSound from "@renderer/assets/audio/achievement.wav";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import * as styles from "./achievement.css";
 | 
			
		||||
import * as styles from "./achievement-notification.css";
 | 
			
		||||
 | 
			
		||||
interface AchievementInfo {
 | 
			
		||||
  displayName: string;
 | 
			
		||||
| 
						 | 
				
			
			@ -10,7 +10,7 @@ interface AchievementInfo {
 | 
			
		|||
 | 
			
		||||
const NOTIFICATION_TIMEOUT = 4000;
 | 
			
		||||
 | 
			
		||||
export function Achievement() {
 | 
			
		||||
export function AchievementNotification() {
 | 
			
		||||
  const { t } = useTranslation("achievement");
 | 
			
		||||
 | 
			
		||||
  const [isClosing, setIsClosing] = useState(false);
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
import { useContext, useState } from "react";
 | 
			
		||||
import type { HowLongToBeatCategory, SteamAppDetails } from "@types";
 | 
			
		||||
import { useTranslation } from "react-i18next";
 | 
			
		||||
import { Button } from "@renderer/components";
 | 
			
		||||
import { Button, Link } from "@renderer/components";
 | 
			
		||||
 | 
			
		||||
import * as styles from "./sidebar.css";
 | 
			
		||||
import { gameDetailsContext } from "@renderer/context";
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +18,7 @@ export function Sidebar() {
 | 
			
		|||
  const [activeRequirement, setActiveRequirement] =
 | 
			
		||||
    useState<keyof SteamAppDetails["pc_requirements"]>("minimum");
 | 
			
		||||
 | 
			
		||||
  const { gameTitle, shopDetails, stats, achievements } =
 | 
			
		||||
  const { gameTitle, shopDetails, stats, achievements, shop, objectID } =
 | 
			
		||||
    useContext(gameDetailsContext);
 | 
			
		||||
 | 
			
		||||
  const { t } = useTranslation("game_details");
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +26,11 @@ export function Sidebar() {
 | 
			
		|||
 | 
			
		||||
  const { numberFormatter } = useFormat();
 | 
			
		||||
 | 
			
		||||
  const buildGameAchievementPath = () => {
 | 
			
		||||
    const urlParams = new URLSearchParams({ objectId: objectID!, shop });
 | 
			
		||||
    return `/achievements?${urlParams.toString()}`;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  // useEffect(() => {
 | 
			
		||||
  //   if (objectID) {
 | 
			
		||||
  //     setHowLongToBeat({ isLoading: true, data: null });
 | 
			
		||||
| 
						 | 
				
			
			@ -61,6 +66,10 @@ export function Sidebar() {
 | 
			
		|||
                {achievements.length})
 | 
			
		||||
              </span>
 | 
			
		||||
            </h3>
 | 
			
		||||
            <span>
 | 
			
		||||
              <Link to={buildGameAchievementPath()}>Ver todas</Link>
 | 
			
		||||
              <a></a>
 | 
			
		||||
            </span>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div
 | 
			
		||||
            style={{
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +79,7 @@ export function Sidebar() {
 | 
			
		|||
              padding: `${SPACING_UNIT * 2}px`,
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            {achievements.map((achievement, index) => (
 | 
			
		||||
            {achievements.slice(0, 6).map((achievement, index) => (
 | 
			
		||||
              <div
 | 
			
		||||
                key={index}
 | 
			
		||||
                style={{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue