mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 21:44:55 +03:00
feat: use new endpoint to get compared achievements
This commit is contained in:
parent
89bb099caa
commit
f0a2bf2f48
@ -50,6 +50,7 @@ import "./user/unblock-user";
|
||||
import "./user/get-user-friends";
|
||||
import "./user/get-user-stats";
|
||||
import "./user/report-user";
|
||||
import "./user/get-compared-unlocked-achievements";
|
||||
import "./profile/get-friend-requests";
|
||||
import "./profile/get-me";
|
||||
import "./profile/undo-friendship";
|
||||
|
44
src/main/events/user/get-compared-unlocked-achievements.ts
Normal file
44
src/main/events/user/get-compared-unlocked-achievements.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type { ComparedAchievements, GameShop } from "@types";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { userPreferencesRepository } from "@main/repository";
|
||||
import { HydraApi } from "@main/services";
|
||||
|
||||
const getComparedUnlockedAchievements = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
objectId: string,
|
||||
shop: GameShop,
|
||||
userId: string
|
||||
) => {
|
||||
const userPreferences = await userPreferencesRepository.findOne({
|
||||
where: { id: 1 },
|
||||
});
|
||||
|
||||
return HydraApi.get<ComparedAchievements>(
|
||||
`/users/${userId}/games/achievements/compare`,
|
||||
{
|
||||
shop,
|
||||
objectId,
|
||||
language: userPreferences?.language || "en",
|
||||
}
|
||||
).then((achievements) => {
|
||||
const sortedAchievements = achievements.achievements.sort((a, b) => {
|
||||
if (a.otherUserStat.unlocked && !b.otherUserStat.unlocked) return -1;
|
||||
if (!a.otherUserStat.unlocked && b.otherUserStat.unlocked) return 1;
|
||||
if (a.otherUserStat.unlocked && b.otherUserStat.unlocked) {
|
||||
return b.otherUserStat.unlockTime! - a.otherUserStat.unlockTime!;
|
||||
}
|
||||
|
||||
return Number(a.hidden) - Number(b.hidden);
|
||||
});
|
||||
|
||||
return {
|
||||
...achievements,
|
||||
achievements: sortedAchievements,
|
||||
} as ComparedAchievements;
|
||||
});
|
||||
};
|
||||
|
||||
registerEvent(
|
||||
"getComparedUnlockedAchievements",
|
||||
getComparedUnlockedAchievements
|
||||
);
|
@ -259,6 +259,17 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
getUserStats: (userId: string) => ipcRenderer.invoke("getUserStats", userId),
|
||||
reportUser: (userId: string, reason: string, description: string) =>
|
||||
ipcRenderer.invoke("reportUser", userId, reason, description),
|
||||
getComparedUnlockedAchievements: (
|
||||
objectId: string,
|
||||
shop: GameShop,
|
||||
userId: string
|
||||
) =>
|
||||
ipcRenderer.invoke(
|
||||
"getComparedUnlockedAchievements",
|
||||
objectId,
|
||||
shop,
|
||||
userId
|
||||
),
|
||||
|
||||
/* Auth */
|
||||
signOut: () => ipcRenderer.invoke("signOut"),
|
||||
|
6
src/renderer/src/declaration.d.ts
vendored
6
src/renderer/src/declaration.d.ts
vendored
@ -29,6 +29,7 @@ import type {
|
||||
GameArtifact,
|
||||
LudusaviBackup,
|
||||
UserAchievement,
|
||||
ComparedAchievements,
|
||||
} from "@types";
|
||||
import type { AxiosProgressEvent } from "axios";
|
||||
import type { DiskSpace } from "check-disk-space";
|
||||
@ -202,6 +203,11 @@ declare global {
|
||||
reason: string,
|
||||
description: string
|
||||
) => Promise<void>;
|
||||
getComparedUnlockedAchievements: (
|
||||
objectId: string,
|
||||
shop: GameShop,
|
||||
userId: string
|
||||
) => Promise<ComparedAchievements>;
|
||||
|
||||
/* Profile */
|
||||
getMe: () => Promise<UserDetails | null>;
|
||||
|
@ -36,15 +36,13 @@ export const buildGameDetailsPath = (
|
||||
|
||||
export const buildGameAchievementPath = (
|
||||
game: { shop: GameShop; objectId: string; title: string },
|
||||
user?: { userId: string; displayName: string; profileImageUrl: string | null }
|
||||
user?: { userId: string }
|
||||
) => {
|
||||
const searchParams = new URLSearchParams({
|
||||
title: game.title,
|
||||
shop: game.shop,
|
||||
objectId: game.objectId,
|
||||
userId: user?.userId || "",
|
||||
displayName: user?.displayName || "",
|
||||
profileImageUrl: user?.profileImageUrl || "",
|
||||
});
|
||||
|
||||
return `/achievements/?${searchParams.toString()}`;
|
||||
|
@ -1,40 +1,37 @@
|
||||
import { setHeaderTitle } from "@renderer/features";
|
||||
import { useAppDispatch, useDate, useUserDetails } from "@renderer/hooks";
|
||||
import { steamUrlBuilder } from "@shared";
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import * as styles from "./achievements.css";
|
||||
import {
|
||||
buildGameDetailsPath,
|
||||
formatDownloadProgress,
|
||||
} from "@renderer/helpers";
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
LockIcon,
|
||||
PersonIcon,
|
||||
TrophyIcon,
|
||||
} from "@primer/octicons-react";
|
||||
import { LockIcon, PersonIcon, TrophyIcon } from "@primer/octicons-react";
|
||||
import { SPACING_UNIT, vars } from "@renderer/theme.css";
|
||||
import { gameDetailsContext } from "@renderer/context";
|
||||
import { UserAchievement } from "@types";
|
||||
import { ComparedAchievements, UserAchievement } from "@types";
|
||||
import { average } from "color.js";
|
||||
import Color from "color";
|
||||
import { Link } from "@renderer/components";
|
||||
import { ComparedAchievementList } from "./compared-achievement-list";
|
||||
|
||||
interface UserInfo {
|
||||
userId: string;
|
||||
displayName: string;
|
||||
achievements: UserAchievement[];
|
||||
profileImageUrl: string | null;
|
||||
totalAchievementCount: number;
|
||||
unlockedAchievementCount: number;
|
||||
}
|
||||
|
||||
interface AchievementsContentProps {
|
||||
otherUser: UserInfo | null;
|
||||
comparedAchievements: ComparedAchievements | null;
|
||||
}
|
||||
|
||||
interface AchievementListProps {
|
||||
user: UserInfo;
|
||||
otherUser: UserInfo | null;
|
||||
achievements: UserAchievement[];
|
||||
}
|
||||
|
||||
interface AchievementSummaryProps {
|
||||
@ -46,11 +43,6 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
|
||||
const { t } = useTranslation("achievement");
|
||||
const { userDetails, hasActiveSubscription } = useUserDetails();
|
||||
|
||||
const userTotalAchievementCount = user.achievements.length;
|
||||
const userUnlockedAchievementCount = user.achievements.filter(
|
||||
(achievement) => achievement.unlocked
|
||||
).length;
|
||||
|
||||
const getProfileImage = (user: UserInfo) => {
|
||||
return (
|
||||
<div className={styles.profileAvatar}>
|
||||
@ -155,19 +147,19 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
|
||||
>
|
||||
<TrophyIcon size={13} />
|
||||
<span>
|
||||
{userUnlockedAchievementCount} / {userTotalAchievementCount}
|
||||
{user.unlockedAchievementCount} / {user.totalAchievementCount}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span>
|
||||
{formatDownloadProgress(
|
||||
userUnlockedAchievementCount / userTotalAchievementCount
|
||||
user.unlockedAchievementCount / user.totalAchievementCount
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<progress
|
||||
max={1}
|
||||
value={userUnlockedAchievementCount / userTotalAchievementCount}
|
||||
value={user.unlockedAchievementCount / user.totalAchievementCount}
|
||||
className={styles.achievementsProgressBar}
|
||||
/>
|
||||
</div>
|
||||
@ -175,132 +167,30 @@ function AchievementSummary({ user, isComparison }: AchievementSummaryProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function AchievementList({ user, otherUser }: AchievementListProps) {
|
||||
const achievements = user.achievements;
|
||||
const otherUserAchievements = otherUser?.achievements;
|
||||
|
||||
function AchievementList({ achievements }: AchievementListProps) {
|
||||
const { t } = useTranslation("achievement");
|
||||
const { formatDateTime } = useDate();
|
||||
|
||||
const { hasActiveSubscription } = useUserDetails();
|
||||
|
||||
if (!otherUserAchievements || otherUserAchievements.length === 0) {
|
||||
return (
|
||||
<ul className={styles.list}>
|
||||
{achievements.map((achievement, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={styles.listItem}
|
||||
style={{ display: "flex" }}
|
||||
>
|
||||
<img
|
||||
className={styles.listItemImage({
|
||||
unlocked: achievement.unlocked,
|
||||
})}
|
||||
src={achievement.icon}
|
||||
alt={achievement.displayName}
|
||||
loading="lazy"
|
||||
/>
|
||||
<div style={{ flex: 1 }}>
|
||||
<h4>{achievement.displayName}</h4>
|
||||
<p>{achievement.description}</p>
|
||||
</div>
|
||||
{achievement.unlockTime && (
|
||||
<div style={{ whiteSpace: "nowrap" }}>
|
||||
<small>{t("unlocked_at")}</small>
|
||||
<p>{formatDateTime(achievement.unlockTime)}</p>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className={styles.list}>
|
||||
{otherUserAchievements.map((otherUserAchievement, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={styles.listItem}
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: hasActiveSubscription
|
||||
? "3fr 1fr 1fr"
|
||||
: "3fr 2fr",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className={styles.listItemImage({
|
||||
unlocked: true,
|
||||
})}
|
||||
src={otherUserAchievement.icon}
|
||||
alt={otherUserAchievement.displayName}
|
||||
loading="lazy"
|
||||
/>
|
||||
<div>
|
||||
<h4>{otherUserAchievement.displayName}</h4>
|
||||
<p>{otherUserAchievement.description}</p>
|
||||
</div>
|
||||
{achievements.map((achievement, index) => (
|
||||
<li key={index} className={styles.listItem} style={{ display: "flex" }}>
|
||||
<img
|
||||
className={styles.listItemImage({
|
||||
unlocked: achievement.unlocked,
|
||||
})}
|
||||
src={achievement.icon}
|
||||
alt={achievement.displayName}
|
||||
loading="lazy"
|
||||
/>
|
||||
<div style={{ flex: 1 }}>
|
||||
<h4>{achievement.displayName}</h4>
|
||||
<p>{achievement.description}</p>
|
||||
</div>
|
||||
|
||||
{hasActiveSubscription ? (
|
||||
achievements[index].unlocked ? (
|
||||
<div
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<CheckCircleIcon />
|
||||
<small>{formatDateTime(achievements[index].unlockTime!)}</small>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
padding: `${SPACING_UNIT}px`,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<LockIcon />
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
|
||||
{otherUserAchievement.unlocked ? (
|
||||
<div
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<CheckCircleIcon />
|
||||
<small>{formatDateTime(otherUserAchievement.unlockTime!)}</small>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
padding: `${SPACING_UNIT}px`,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<LockIcon />
|
||||
{achievement.unlockTime && (
|
||||
<div style={{ whiteSpace: "nowrap" }}>
|
||||
<small>{t("unlocked_at")}</small>
|
||||
<p>{formatDateTime(achievement.unlockTime)}</p>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
@ -309,7 +199,10 @@ function AchievementList({ user, otherUser }: AchievementListProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export function AchievementsContent({ otherUser }: AchievementsContentProps) {
|
||||
export function AchievementsContent({
|
||||
otherUser,
|
||||
comparedAchievements,
|
||||
}: AchievementsContentProps) {
|
||||
const heroRef = useRef<HTMLDivElement | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [isHeaderStuck, setIsHeaderStuck] = useState(false);
|
||||
@ -317,20 +210,6 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) {
|
||||
const { gameTitle, objectId, shop, achievements, gameColor, setGameColor } =
|
||||
useContext(gameDetailsContext);
|
||||
|
||||
const sortedAchievements = useMemo(() => {
|
||||
if (!otherUser || otherUser.achievements.length === 0) return achievements!;
|
||||
|
||||
return achievements!.sort((a, b) => {
|
||||
const indexA = otherUser.achievements.findIndex(
|
||||
(achievement) => achievement.name === a.name
|
||||
);
|
||||
const indexB = otherUser.achievements.findIndex(
|
||||
(achievement) => achievement.name === b.name
|
||||
);
|
||||
return indexA - indexB;
|
||||
});
|
||||
}, [achievements, otherUser]);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { userDetails, hasActiveSubscription } = useUserDetails();
|
||||
@ -367,14 +246,17 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const getProfileImage = (user: UserInfo) => {
|
||||
const getProfileImage = (
|
||||
profileImageUrl: string | null,
|
||||
displayName: string
|
||||
) => {
|
||||
return (
|
||||
<div className={styles.profileAvatarSmall}>
|
||||
{user.profileImageUrl ? (
|
||||
{profileImageUrl ? (
|
||||
<img
|
||||
className={styles.profileAvatarSmall}
|
||||
src={user.profileImageUrl}
|
||||
alt={user.displayName}
|
||||
src={profileImageUrl}
|
||||
alt={displayName}
|
||||
/>
|
||||
) : (
|
||||
<PersonIcon size={24} />
|
||||
@ -434,7 +316,13 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) {
|
||||
user={{
|
||||
...userDetails,
|
||||
userId: userDetails.id,
|
||||
achievements: sortedAchievements,
|
||||
totalAchievementCount: comparedAchievements
|
||||
? comparedAchievements.ownerUser.totalAchievementCount
|
||||
: achievements!.length,
|
||||
unlockedAchievementCount: comparedAchievements
|
||||
? comparedAchievements.ownerUser.unlockedAchievementCount
|
||||
: achievements!.filter((achievement) => achievement.unlocked)
|
||||
.length,
|
||||
}}
|
||||
isComparison={otherUser !== null}
|
||||
/>
|
||||
@ -458,15 +346,17 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) {
|
||||
<div></div>
|
||||
{hasActiveSubscription && (
|
||||
<div style={{ display: "flex", justifyContent: "center" }}>
|
||||
{getProfileImage({
|
||||
...userDetails,
|
||||
userId: userDetails.id,
|
||||
achievements: sortedAchievements,
|
||||
})}
|
||||
{getProfileImage(
|
||||
userDetails.profileImageUrl,
|
||||
userDetails.displayName
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div style={{ display: "flex", justifyContent: "center" }}>
|
||||
{getProfileImage(otherUser)}
|
||||
{getProfileImage(
|
||||
otherUser.profileImageUrl,
|
||||
otherUser.displayName
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -480,14 +370,11 @@ export function AchievementsContent({ otherUser }: AchievementsContentProps) {
|
||||
backgroundColor: vars.color.background,
|
||||
}}
|
||||
>
|
||||
<AchievementList
|
||||
user={{
|
||||
...userDetails,
|
||||
userId: userDetails.id,
|
||||
achievements: sortedAchievements,
|
||||
}}
|
||||
otherUser={otherUser}
|
||||
/>
|
||||
{otherUser ? (
|
||||
<ComparedAchievementList achievements={comparedAchievements!} />
|
||||
) : (
|
||||
<AchievementList achievements={achievements!} />
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { setHeaderTitle } from "@renderer/features";
|
||||
import { useAppDispatch, useUserDetails } from "@renderer/hooks";
|
||||
import type { GameShop, UserAchievement } from "@types";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { ComparedAchievements, GameShop } from "@types";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { vars } from "@renderer/theme.css";
|
||||
import {
|
||||
@ -18,14 +18,11 @@ export default function Achievements() {
|
||||
const shop = searchParams.get("shop");
|
||||
const title = searchParams.get("title");
|
||||
const userId = searchParams.get("userId");
|
||||
const displayName = searchParams.get("displayName");
|
||||
const profileImageUrl = searchParams.get("profileImageUrl");
|
||||
|
||||
const { userDetails } = useUserDetails();
|
||||
|
||||
const [otherUserAchievements, setOtherUserAchievements] = useState<
|
||||
UserAchievement[] | null
|
||||
>(null);
|
||||
const [comparedAchievements, setComparedAchievements] =
|
||||
useState<ComparedAchievements | null>(null);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -36,31 +33,34 @@ export default function Achievements() {
|
||||
}, [dispatch, title]);
|
||||
|
||||
useEffect(() => {
|
||||
setOtherUserAchievements(null);
|
||||
setComparedAchievements(null);
|
||||
|
||||
if (userDetails?.id == userId) {
|
||||
setOtherUserAchievements([]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (objectId && shop && userId) {
|
||||
window.electron
|
||||
.getGameAchievements(objectId, shop as GameShop, userId)
|
||||
.then((achievements) => {
|
||||
setOtherUserAchievements(achievements);
|
||||
});
|
||||
.getComparedUnlockedAchievements(objectId, shop as GameShop, userId)
|
||||
.then(setComparedAchievements);
|
||||
}
|
||||
}, [objectId, shop, userId]);
|
||||
|
||||
const otherUserId = userDetails?.id === userId ? null : userId;
|
||||
|
||||
const otherUser = otherUserId
|
||||
? {
|
||||
userId: otherUserId,
|
||||
displayName: displayName || "",
|
||||
achievements: otherUserAchievements || [],
|
||||
profileImageUrl: profileImageUrl || "",
|
||||
}
|
||||
: null;
|
||||
const otherUser = useMemo(() => {
|
||||
if (!otherUserId || !comparedAchievements) return null;
|
||||
|
||||
return {
|
||||
userId: otherUserId,
|
||||
displayName: comparedAchievements.otherUser.displayName,
|
||||
profileImageUrl: comparedAchievements.otherUser.profileImageUrl,
|
||||
totalAchievementCount:
|
||||
comparedAchievements.otherUser.totalAchievementCount,
|
||||
unlockedAchievementCount:
|
||||
comparedAchievements.otherUser.unlockedAchievementCount,
|
||||
};
|
||||
}, [otherUserId, comparedAchievements]);
|
||||
|
||||
return (
|
||||
<GameDetailsContextProvider
|
||||
@ -70,17 +70,23 @@ export default function Achievements() {
|
||||
>
|
||||
<GameDetailsContextConsumer>
|
||||
{({ isLoading, achievements }) => {
|
||||
const showSkeleton =
|
||||
isLoading ||
|
||||
achievements === null ||
|
||||
(otherUserId && comparedAchievements === null);
|
||||
|
||||
return (
|
||||
<SkeletonTheme
|
||||
baseColor={vars.color.background}
|
||||
highlightColor="#444"
|
||||
>
|
||||
{isLoading ||
|
||||
achievements === null ||
|
||||
(otherUserId && otherUserAchievements === null) ? (
|
||||
{showSkeleton ? (
|
||||
<AchievementsSkeleton />
|
||||
) : (
|
||||
<AchievementsContent otherUser={otherUser} />
|
||||
<AchievementsContent
|
||||
otherUser={otherUser}
|
||||
comparedAchievements={comparedAchievements!}
|
||||
/>
|
||||
)}
|
||||
</SkeletonTheme>
|
||||
);
|
||||
|
@ -0,0 +1,110 @@
|
||||
import type { ComparedAchievements } from "@types";
|
||||
import * as styles from "./achievements.css";
|
||||
import { CheckCircleIcon, LockIcon } from "@primer/octicons-react";
|
||||
import { useDate } from "@renderer/hooks";
|
||||
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||
|
||||
export interface ComparedAchievementListProps {
|
||||
achievements: ComparedAchievements;
|
||||
}
|
||||
|
||||
export function ComparedAchievementList({
|
||||
achievements,
|
||||
}: ComparedAchievementListProps) {
|
||||
const { formatDateTime } = useDate();
|
||||
|
||||
return (
|
||||
<ul className={styles.list}>
|
||||
{achievements.achievements.map((achievement, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={styles.listItem}
|
||||
style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: achievement.onwerUserStat
|
||||
? "3fr 1fr 1fr"
|
||||
: "3fr 2fr",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className={styles.listItemImage({
|
||||
unlocked: true,
|
||||
})}
|
||||
src={achievement.icon}
|
||||
alt={achievement.displayName}
|
||||
loading="lazy"
|
||||
/>
|
||||
<div>
|
||||
<h4>{achievement.displayName}</h4>
|
||||
<p>{achievement.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{achievement.onwerUserStat ? (
|
||||
achievement.onwerUserStat.unlocked ? (
|
||||
<div
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<CheckCircleIcon />
|
||||
<small>
|
||||
{formatDateTime(achievement.onwerUserStat.unlockTime!)}
|
||||
</small>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
padding: `${SPACING_UNIT}px`,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<LockIcon />
|
||||
</div>
|
||||
)
|
||||
) : null}
|
||||
|
||||
{achievement.otherUserStat.unlocked ? (
|
||||
<div
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
gap: `${SPACING_UNIT}px`,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<CheckCircleIcon />
|
||||
<small>
|
||||
{formatDateTime(achievement.otherUserStat.unlockTime!)}
|
||||
</small>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
padding: `${SPACING_UNIT}px`,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<LockIcon />
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
@ -56,8 +56,6 @@ export function ProfileContent() {
|
||||
const userParams = userProfile
|
||||
? {
|
||||
userId: userProfile.id,
|
||||
displayName: userProfile.displayName,
|
||||
profileImageUrl: userProfile.profileImageUrl,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
@ -342,6 +342,33 @@ export interface GameArtifact {
|
||||
downloadCount: number;
|
||||
}
|
||||
|
||||
export interface ComparedAchievements {
|
||||
ownerUser: {
|
||||
totalAchievementCount: number;
|
||||
unlockedAchievementCount: number;
|
||||
};
|
||||
otherUser: {
|
||||
displayName: string;
|
||||
profileImageUrl: string;
|
||||
totalAchievementCount: number;
|
||||
unlockedAchievementCount: number;
|
||||
};
|
||||
achievements: {
|
||||
hidden: boolean;
|
||||
icon: string;
|
||||
displayName: string;
|
||||
description: string;
|
||||
onwerUserStat?: {
|
||||
unlocked: boolean;
|
||||
unlockTime: number;
|
||||
};
|
||||
otherUserStat: {
|
||||
unlocked: boolean;
|
||||
unlockTime: number;
|
||||
};
|
||||
}[];
|
||||
}
|
||||
|
||||
export * from "./steam.types";
|
||||
export * from "./real-debrid.types";
|
||||
export * from "./ludusavi.types";
|
||||
|
Loading…
Reference in New Issue
Block a user