diff --git a/client/src/components/layout/header/HeaderMenu.tsx b/client/src/components/layout/header/HeaderMenu.tsx
index 99a3eb891..db2903aa1 100644
--- a/client/src/components/layout/header/HeaderMenu.tsx
+++ b/client/src/components/layout/header/HeaderMenu.tsx
@@ -1,10 +1,11 @@
import { AllLUGPTStatsContext } from "context/AllLUGPTStatsContext";
import { UserSettingsContext } from "context/UserSettingsContext";
import React, { useContext, useEffect } from "react";
-import { UserDocument, UserGameStats } from "tachi-common";
+import { UserGameStats } from "tachi-common";
import useApiQuery from "components/util/query/useApiQuery";
import Nav from "react-bootstrap/Nav";
import { SetState } from "types/react";
+import { UserContext } from "context/UserContext";
import GlobalInfoDropdown from "./GlobalInfoDropdown";
import ImportScoresDropdown from "./ImportScoresDropdown";
import UtilsDropdown from "./UtilsDropdown";
@@ -14,21 +15,24 @@ const toggleClassNames = "w-100 justify-content-between";
const menuClassNames = "shadow-none shadow-lg-lg";
export function HeaderMenu({
- user,
dropdownMenuStyle,
setState,
}: {
- user: UserDocument | null;
dropdownMenuStyle?: React.CSSProperties;
setState?: SetState
;
}) {
+ const { user } = useContext(UserContext);
const { ugs, setUGS } = useContext(AllLUGPTStatsContext);
const { settings } = useContext(UserSettingsContext);
- const { data, error } = useApiQuery("/users/me/game-stats", undefined, [
- user?.id,
- "game_stats",
- ]);
+ const { data, error } = useApiQuery(
+ // We should generate a valid url just in case the skip somehow fails
+ `/users/${user?.id ?? "me"}/game-stats`,
+ undefined,
+ undefined,
+ // We should skip if a user isn't logged in.
+ !user
+ );
useEffect(() => {
if (error) {
diff --git a/client/src/components/tables/dropdowns/PBDropdown.tsx b/client/src/components/tables/dropdowns/PBDropdown.tsx
index 819524bf9..360d01a50 100644
--- a/client/src/components/tables/dropdowns/PBDropdown.tsx
+++ b/client/src/components/tables/dropdowns/PBDropdown.tsx
@@ -181,9 +181,11 @@ export default function PBDropdown({
{targetData && ` (${targetData.goals.length})`}
)}
-
- Rivals
-
+ {currentUser && (
+
+ Rivals
+
+ )}
Debug Info
diff --git a/client/src/components/user/UGPTProfiles.tsx b/client/src/components/user/UGPTProfiles.tsx
new file mode 100644
index 000000000..c3d8b11dd
--- /dev/null
+++ b/client/src/components/user/UGPTProfiles.tsx
@@ -0,0 +1,130 @@
+import { GetSortedGPTs } from "util/site";
+import Muted from "components/util/Muted";
+import ReferToUser from "components/util/ReferToUser";
+import React, { memo, useContext } from "react";
+import { Col, Row } from "react-bootstrap";
+import { FormatGame, UserDocument, UserGameStats } from "tachi-common";
+import { UGSWithRankingData } from "types/api-returns";
+import LinkButton from "components/util/LinkButton";
+import Card from "components/layout/page/Card";
+import { UserContext } from "context/UserContext";
+import { AllLUGPTStatsContext } from "context/AllLUGPTStatsContext";
+import useApiQuery from "components/util/query/useApiQuery";
+import LoadingWrapper from "components/util/LoadingWrapper";
+import UGPTRatingsTable from "./UGPTStatsOverview";
+import RankingData from "./UGPTRankingData";
+
+interface GamesInfoProps {
+ ugsList: UserGameStats[];
+ reqUser: UserDocument;
+}
+
+interface GamesInfoUnitProps {
+ ugs: UGSWithRankingData;
+ reqUser: UserDocument;
+}
+
+export default function UGPTProfiles({ reqUser }: { reqUser?: UserDocument }) {
+ const { user } = useContext(UserContext);
+
+ return (
+
+ {/*
+ If a user is logged in and the component hasn't been provided reqUser or this is the logged in user's stats,
+ we can just grab the user's stats that have already been loaded into context on load.
+ */}
+ {user && (!reqUser || reqUser.id === user.id) ? (
+
+ ) : reqUser ? (
+
+ ) : (
+ <>User not provided; can't show games for nobody!>
+ )}
+
+ );
+}
+
+const ContextualGamesInfo = memo(({ user }: { user: UserDocument }) => {
+ const { ugs } = useContext(AllLUGPTStatsContext);
+
+ return ;
+});
+
+function QueryGamesInfo({ reqUser }: { reqUser: UserDocument }) {
+ const { data, error } = useApiQuery(
+ `/users/${reqUser.id}/game-stats`,
+ undefined,
+ undefined,
+ !reqUser
+ );
+
+ if (error) {
+ throw new Error("An error occurred fetching User Game Stats.", { cause: error });
+ }
+
+ return (
+
+
+
+ );
+}
+
+function GamesInfo({ ugsList, reqUser }: GamesInfoProps) {
+ if (ugsList.length === 0) {
+ return (
+
+
+ not played anything.
+
+
+ );
+ }
+
+ const gpts = GetSortedGPTs();
+
+ const ugsMap = new Map();
+
+ for (const ugs of ugsList) {
+ ugsMap.set(`${ugs.game}:${ugs.playtype}`, ugs);
+ }
+
+ return (
+ <>
+ {gpts.map(({ game, playtype }) => {
+ const e = ugsMap.get(`${game}:${playtype}`);
+
+ if (!e) {
+ return null;
+ }
+
+ return ;
+ })}
+ >
+ );
+}
+
+function GamesInfoUnit({ ugs, reqUser }: GamesInfoUnitProps) {
+ return (
+
+
+
+ View Game Profile
+
+
+ }
+ header={FormatGame(ugs.game, ugs.playtype)}
+ >
+