Skip to content

Commit

Permalink
Team Profile page (for other teams) (#803)
Browse files Browse the repository at this point in the history
  • Loading branch information
acrantel authored Sep 25, 2024
1 parent 4a78925 commit 277d2e9
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 8 deletions.
2 changes: 1 addition & 1 deletion frontend2/.env.development
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
REACT_APP_THIS_URL=http://localhost:3000
REACT_APP_BACKEND_URL=http://localhost:8000
REACT_APP_BACKEND_URL=http://api.staging.battlecode.org/
REACT_APP_REPLAY_URL=https://play.battlecode.org
2 changes: 1 addition & 1 deletion frontend2/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>Battlecode</title>
</head>
<body class="h-full">
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
9 changes: 8 additions & 1 deletion frontend2/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { submissionsLoader } from "./api/loaders/submissionsLoader";
import { myTeamLoader } from "./api/loaders/myTeamLoader";
import { scrimmagingLoader } from "./api/loaders/scrimmagingLoader";
import { rankingsLoader } from "./api/loaders/rankingsLoader";
import { teamProfileLoader } from "./api/loaders/teamProfileLoader";
import { episodeInfoFactory } from "./api/episode/episodeFactories";
import { buildKey } from "./api/helpers";
import { queueLoader } from "./api/loaders/queueLoader";
Expand All @@ -49,6 +50,7 @@ import { homeLoader } from "./api/loaders/homeLoader";
import ErrorBoundary from "./views/ErrorBoundary";
import { searchTeamsFactory } from "api/team/teamFactories";
import PageNotFound from "views/PageNotFound";
import TeamProfile from "views/TeamProfile";

const queryClient = new QueryClient({
queryCache: new QueryCache({
Expand Down Expand Up @@ -164,7 +166,7 @@ const router = createBrowserRouter([
loader: submissionsLoader(queryClient),
},
{
path: "team",
path: "my_team",
element: <MyTeam />,
loader: myTeamLoader(queryClient),
},
Expand Down Expand Up @@ -208,6 +210,11 @@ const router = createBrowserRouter([
element: <TournamentPage />,
loader: tournamentLoader(queryClient),
},
{
path: "team/:teamId",
element: <TeamProfile />,
loader: teamProfileLoader(queryClient),
},
{
path: "*",
element: <PageNotFound />,
Expand Down
28 changes: 28 additions & 0 deletions frontend2/src/api/loaders/teamProfileLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { QueryClient } from "@tanstack/react-query";
import type { LoaderFunction } from "react-router-dom";
import { matchListFactory } from "../compete/competeFactories";

Check warning on line 3 in frontend2/src/api/loaders/teamProfileLoader.ts

View workflow job for this annotation

GitHub Actions / Frontend2 linting / unit tests

'matchListFactory' is defined but never used
import { buildKey } from "../helpers";
import {
otherTeamInfoFactory,
searchTeamsFactory,

Check warning on line 7 in frontend2/src/api/loaders/teamProfileLoader.ts

View workflow job for this annotation

GitHub Actions / Frontend2 linting / unit tests

'searchTeamsFactory' is defined but never used
} from "../team/teamFactories";

// loader for other team's public profile pages
export const teamProfileLoader =
(queryClient: QueryClient): LoaderFunction =>
({ params }) => {
const episodeId = params.episodeId;
const teamId = params.teamId;
if (teamId === undefined || episodeId === undefined) return null;

// All teams
void queryClient.ensureQueryData({
queryKey: buildKey(otherTeamInfoFactory.queryKey, {
episodeId,
id: teamId,
}),
queryFn: async () =>
await otherTeamInfoFactory.queryFn({ episodeId, id: teamId }),
});
return null;
};
2 changes: 1 addition & 1 deletion frontend2/src/components/sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const SIDEBAR_ITEM_DATA: Array<{
{
iconName: "user_group",
text: "My Team",
linkTo: "team",
linkTo: "my_team",
},
{
iconName: "arrow_up_tray",
Expand Down
2 changes: 1 addition & 1 deletion frontend2/src/components/tables/RankingsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const RankingsTable: React.FC<RankingsTableProps> = ({
key: "team",
value: (team) => (
<NavLink
to={`${episodeId}/team/${team.id}`}
to={`/${episodeId}/team/${team.id}`}
className="hover:underline"
>
{team.name}
Expand Down
7 changes: 4 additions & 3 deletions frontend2/src/components/team/MemberList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ const MemberList: React.FC<MemberListProps> = ({ members, className = "" }) => {
return (
<div className={`flex flex-col gap-6 ${className}`}>
{/* display current user first */}
{currentUser !== undefined && (
<UserRow isCurrentUser user={currentUser} />
)}
{currentUser !== undefined &&
members.find((user) => user.id === currentUser.id) !== undefined && (
<UserRow isCurrentUser user={currentUser} />
)}
{members.map(
(member) =>
member.id !== currentUser?.id && (
Expand Down
138 changes: 138 additions & 0 deletions frontend2/src/views/TeamProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React, { useMemo } from "react";
import { useEpisodeId } from "contexts/EpisodeContext";
import { useParams } from "react-router-dom";
import { useQueryClient } from "@tanstack/react-query";

Check warning on line 4 in frontend2/src/views/TeamProfile.tsx

View workflow job for this annotation

GitHub Actions / Frontend2 linting / unit tests

'useQueryClient' is defined but never used
import { useTeam } from "api/team/useTeam";
import { isNil } from "lodash";
import SectionCard from "components/SectionCard";
import { PageTitle } from "components/elements/BattlecodeStyle";
import MemberList from "components/team/MemberList";
import Loading from "components/Loading";
import { Status526Enum } from "api/_autogen";
import { useEpisodeInfo } from "api/episode/useEpisode";

const isNilOrEmptyStr = (str: string | undefined | null): boolean => {
return isNil(str) || str === "";
};

// rendered at /:episodeId/team/:teamId
const TeamProfile: React.FC = () => {
const { episodeId } = useEpisodeId();
const { teamId } = useParams();
const episode = useEpisodeInfo({ id: episodeId });
const team = useTeam({ episodeId, id: teamId ?? "" });
const membersList = useMemo(() => {
return (
<div className="flex flex-col gap-8">
{!team.isSuccess ? (
<Loading />
) : (
<MemberList members={team.data.members} />
)}
</div>
);
}, [team]);
const eligibles = useMemo(
() =>
episode.data?.eligibility_criteria.filter(
(criterion) =>
team.data?.profile?.eligible_for?.find(
(id) => id === criterion.id,
) !== undefined,
) ?? [],
[episode, team],
);
if (isNil(team.data)) {
return <TeamNotFound />;
}

return (
<div className="p-6">
<PageTitle>Team Profile</PageTitle>
<div className="flex flex-col gap-8 xl:flex-row">
<div className="flex flex-1 flex-col gap-8 xl:max-w-4xl">
<SectionCard title="Profile" className="">
<div className="flex flex-col md:flex-row md:gap-8">
<div className="flex flex-col items-center p-4">
<img
className="h-24 w-24 rounded-full bg-gray-400 md:h-48 md:w-48"
src={team.data.profile?.avatar_url}
// TODO: open add avatar modal on click! With hover effect!
/>
<div className="mt-6 text-center text-xl font-semibold">
{team.data.name}
</div>
{team.data.status === Status526Enum.S && (
<div className="text-sm font-light text-gray-500">
(staff team)
</div>
)}
</div>
<div className="flex flex-1 flex-col gap-4">
<div>
<div className="font-medium">Team quote</div>
<div className="text-sm text-gray-800">
{isNilOrEmptyStr(team.data.profile?.quote) ? (
<span className="italic text-gray-500">
This team does not have a quote
</span>
) : (
team.data.profile?.quote
)}
</div>
</div>
<div>
<div className="font-medium">Team biography</div>
<div className="text-sm text-gray-800">
{isNilOrEmptyStr(team.data.profile?.biography) ? (
<span className="italic text-gray-500">
This team does not have a biography
</span>
) : (
team.data.profile?.biography
)}
</div>
</div>
<div>
<div className="font-medium">Eligibility</div>
<div className="text-sm text-gray-800">
{eligibles.length !== 0 ? (
eligibles?.map((criterion) => (
<div key={criterion.id}>
{criterion.title}
{" "}
{criterion.icon}
</div>
))
) : (
<span className="italic text-gray-500">
This team did not select any eligibility criteria.
</span>
)}
</div>
</div>
</div>
</div>
</SectionCard>
<SectionCard title="Rating"></SectionCard>
{/* The members list that displays when on a smaller screen */}
<SectionCard className="shrink xl:hidden" title="Members">
{membersList}
</SectionCard>
</div>

{/* The members list that displays to the right side when on a big screen. */}
<SectionCard className="hidden w-1/3 shrink xl:block" title="Members">
{membersList}
</SectionCard>
</div>
</div>
);
};

const TeamNotFound: React.FC = () => {
const { teamId } = useParams();
return <div>Team {teamId} was not found.</div>;
};

export default TeamProfile;

0 comments on commit 277d2e9

Please sign in to comment.