Skip to content

Commit

Permalink
refactor(client): unify user profile components (#1156)
Browse files Browse the repository at this point in the history
* disable query instead of throwing an error

* stop header from fetching user stats when no user

* unify user profiles components

for logged in users viewing their own profiles, it now simply reads the UGS in context

for viewing other users' games, it now uses react query instead of making a fetch every time the component is created

* fix rivals button being shown to logged out users
  • Loading branch information
saintnoodle committed Aug 27, 2024
1 parent 35b94ef commit 2a07244
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 163 deletions.
51 changes: 4 additions & 47 deletions client/src/app/pages/dashboard/DashboardPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { APIFetchV1 } from "util/api";
import { CreateGoalMap } from "util/data";
import { RFA } from "util/misc";
import { NumericSOV } from "util/sorts";
Expand All @@ -9,7 +8,6 @@ import { DashboardHeader } from "components/dashboard/DashboardHeader";
import useSetSubheader from "components/layout/header/useSetSubheader";
import SessionCard from "components/sessions/SessionCard";
import ApiError from "components/util/ApiError";
import AsyncLoader from "components/util/AsyncLoader";
import Divider from "components/util/Divider";
import GoalLink from "components/util/GoalLink";
import LinkButton from "components/util/LinkButton";
Expand All @@ -21,13 +19,12 @@ import { TachiConfig } from "lib/config";
import React, { useContext, useMemo } from "react";
import Alert from "react-bootstrap/Alert";
import Stack from "react-bootstrap/Stack";
import Row from "react-bootstrap/Row";
import { Link, Route, Switch } from "react-router-dom";
import { GetGameConfig, UserDocument } from "tachi-common";
import { UGSWithRankingData, UserRecentSummary } from "types/api-returns";
import { UserDocument } from "tachi-common";
import { UserRecentSummary } from "types/api-returns";
import SessionCalendar from "components/sessions/SessionCalendar";
import { WindowContext } from "context/WindowContext";
import { GameStatContainer } from "./users/UserGamesPage";
import UGPTProfiles from "components/user/UGPTProfiles";
import SupportBanner from "./misc/SupportBanner";

export function DashboardPage() {
Expand Down Expand Up @@ -70,7 +67,7 @@ function DashboardLoggedIn({ user }: { user: UserDocument }) {
/>
</Route>
<Route exact path="/profiles">
<UserGameStatsInfo user={user} />
<UGPTProfiles />
</Route>
<Route exact path="/global-activity">
<Activity url="/activity" />
Expand Down Expand Up @@ -188,46 +185,6 @@ function RecentInfo({ user }: { user: UserDocument }) {
);
}

function UserGameStatsInfo({ user }: { user: UserDocument }) {
return (
<Row xs={{ cols: 1 }} lg={{ cols: 2 }}>
<AsyncLoader
promiseFn={async () => {
const res = await APIFetchV1<UGSWithRankingData[]>(
`/users/${user.id}/game-stats`
);

if (!res.success) {
throw new Error(res.description);
}

return res.body.sort((a, b) => {
if (a.game === b.game) {
const gameConfig = GetGameConfig(a.game);

return (
gameConfig.playtypes.indexOf(a.playtype) -
gameConfig.playtypes.indexOf(b.playtype)
);
}

const i1 = TachiConfig.GAMES.indexOf(a.game);
const i2 = TachiConfig.GAMES.indexOf(b.game);

return i1 - i2;
});
}}
>
{(ugs) =>
ugs.map((e) => (
<GameStatContainer ugs={e} reqUser={user} key={`${e.game}:${e.playtype}`} />
))
}
</AsyncLoader>
</Row>
);
}

function DashboardNotLoggedIn() {
const {
breakpoint: { isMd },
Expand Down
101 changes: 4 additions & 97 deletions client/src/app/pages/dashboard/users/UserGamesPage.tsx
Original file line number Diff line number Diff line change
@@ -1,107 +1,14 @@
import { APIFetchV1 } from "util/api";
import { GetSortedGPTs } from "util/site";
import useSetSubheader from "components/layout/header/useSetSubheader";
import Card from "components/layout/page/Card";
import RankingData from "components/user/UGPTRankingData";
import UGPTRatingsTable from "components/user/UGPTStatsOverview";
import AsyncLoader from "components/util/AsyncLoader";
import LinkButton from "components/util/LinkButton";
import Muted from "components/util/Muted";
import ReferToUser from "components/util/ReferToUser";
import React from "react";
import { FormatGame, UserDocument, UserGameStats } from "tachi-common";
import { UGSWithRankingData } from "types/api-returns";
import Col from "react-bootstrap/Col";
import Row from "react-bootstrap/Row";
import UGPTProfiles from "components/user/UGPTProfiles";
import { UserDocument } from "tachi-common";

interface Props {
reqUser: UserDocument;
}

export default function UserGamesPage({ reqUser }: Props) {
export default function UserGamesPage({ reqUser }: { reqUser: UserDocument }) {
useSetSubheader(
["Users", reqUser.username, "Games"],
[reqUser],
`${reqUser.username}'s Game Profiles`
);

return (
<Row xs={{ cols: 1 }} lg={{ cols: 2 }}>
<AsyncLoader
promiseFn={async () => {
const res = await APIFetchV1<UGSWithRankingData[]>(
`/users/${reqUser.id}/game-stats`
);

if (!res.success) {
throw new Error(res.description);
}

return res.body;
}}
>
{(ugs) =>
ugs.length ? (
<GamesInfo ugs={ugs} reqUser={reqUser} />
) : (
<div className="col-12 text-center">
<Muted>
<ReferToUser reqUser={reqUser} /> not played anything.
</Muted>
</div>
)
}
</AsyncLoader>
</Row>
);
}

function GamesInfo({ ugs, reqUser }: { ugs: UserGameStats[]; reqUser: UserDocument }) {
const gpts = GetSortedGPTs();

const ugsMap = new Map();

for (const u of ugs) {
ugsMap.set(`${u.game}:${u.playtype}`, u);
}

return (
<>
{gpts.map(({ game, playtype }, i) => {
const e = ugsMap.get(`${game}:${playtype}`);

if (!e) {
return <React.Fragment key={`${game}:${playtype}`}></React.Fragment>;
}

return <GameStatContainer key={`${game}:${playtype}`} ugs={e} reqUser={reqUser} />;
})}
</>
);
}

export function GameStatContainer({ ugs, reqUser }: { ugs: UGSWithRankingData } & Props) {
return (
<Col className="p-2 flex-grow-1">
<Card
className="h-100"
footer={
<div className="d-flex justify-content-end">
<LinkButton to={`/u/${reqUser.username}/games/${ugs.game}/${ugs.playtype}`}>
View Game Profile
</LinkButton>
</div>
}
header={FormatGame(ugs.game, ugs.playtype)}
>
<UGPTRatingsTable ugs={ugs} />
<RankingData
game={ugs.game}
playtype={ugs.playtype}
rankingData={ugs.__rankingData}
userID={ugs.userID}
/>
</Card>
</Col>
);
return <UGPTProfiles reqUser={reqUser} />;
}
6 changes: 1 addition & 5 deletions client/src/components/layout/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,7 @@ export default function Header({ styles }: { styles: LayoutStyles }) {
</Link>
</Offcanvas.Header>
<Offcanvas.Body className="d-flex flex-column">
<HeaderMenu
user={user}
dropdownMenuStyle={dropdownMenuStyle}
setState={setState}
/>
<HeaderMenu dropdownMenuStyle={dropdownMenuStyle} setState={setState} />
</Offcanvas.Body>
{user && (
<div className="d-flex bottom-0 pb-2 px-4 d-lg-none">
Expand Down
18 changes: 11 additions & 7 deletions client/src/components/layout/header/HeaderMenu.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<boolean>;
}) {
const { user } = useContext(UserContext);
const { ugs, setUGS } = useContext(AllLUGPTStatsContext);
const { settings } = useContext(UserSettingsContext);

const { data, error } = useApiQuery<UserGameStats[]>("/users/me/game-stats", undefined, [
user?.id,
"game_stats",
]);
const { data, error } = useApiQuery<UserGameStats[]>(
// 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) {
Expand Down
8 changes: 5 additions & 3 deletions client/src/components/tables/dropdowns/PBDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,11 @@ export default function PBDropdown({
{targetData && ` (${targetData.goals.length})`}
</SelectButton>
)}
<SelectButton setValue={setView} value={view} id="rivals">
<Icon type="users" /> Rivals
</SelectButton>
{currentUser && (
<SelectButton setValue={setView} value={view} id="rivals">
<Icon type="users" /> Rivals
</SelectButton>
)}
<HasDevModeOn>
<SelectButton setValue={setView} value={view} id="debug">
<Icon type="bug" /> Debug Info
Expand Down
Loading

0 comments on commit 2a07244

Please sign in to comment.