diff --git a/frontend2/.env.development b/frontend2/.env.development index d1d6ea9d8..4bfb5fa98 100644 --- a/frontend2/.env.development +++ b/frontend2/.env.development @@ -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 diff --git a/frontend2/public/index.html b/frontend2/public/index.html index 219d6e899..9b7aafa38 100644 --- a/frontend2/public/index.html +++ b/frontend2/public/index.html @@ -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`. --> - React App + Battlecode diff --git a/frontend2/src/App.tsx b/frontend2/src/App.tsx index 937d4e7a1..e311eeda9 100644 --- a/frontend2/src/App.tsx +++ b/frontend2/src/App.tsx @@ -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"; @@ -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({ @@ -164,7 +166,7 @@ const router = createBrowserRouter([ loader: submissionsLoader(queryClient), }, { - path: "team", + path: "my_team", element: , loader: myTeamLoader(queryClient), }, @@ -208,6 +210,11 @@ const router = createBrowserRouter([ element: , loader: tournamentLoader(queryClient), }, + { + path: "team/:teamId", + element: , + loader: teamProfileLoader(queryClient), + }, { path: "*", element: , diff --git a/frontend2/src/api/loaders/teamProfileLoader.ts b/frontend2/src/api/loaders/teamProfileLoader.ts new file mode 100644 index 000000000..8a2c96eaf --- /dev/null +++ b/frontend2/src/api/loaders/teamProfileLoader.ts @@ -0,0 +1,28 @@ +import type { QueryClient } from "@tanstack/react-query"; +import type { LoaderFunction } from "react-router-dom"; +import { matchListFactory } from "../compete/competeFactories"; +import { buildKey } from "../helpers"; +import { + otherTeamInfoFactory, + searchTeamsFactory, +} 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; + }; diff --git a/frontend2/src/components/sidebar/index.tsx b/frontend2/src/components/sidebar/index.tsx index 0ab560fe5..6cd8f8912 100644 --- a/frontend2/src/components/sidebar/index.tsx +++ b/frontend2/src/components/sidebar/index.tsx @@ -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", diff --git a/frontend2/src/components/tables/RankingsTable.tsx b/frontend2/src/components/tables/RankingsTable.tsx index 1c61052e2..b9776fa0e 100644 --- a/frontend2/src/components/tables/RankingsTable.tsx +++ b/frontend2/src/components/tables/RankingsTable.tsx @@ -61,7 +61,7 @@ const RankingsTable: React.FC = ({ key: "team", value: (team) => ( {team.name} diff --git a/frontend2/src/components/team/MemberList.tsx b/frontend2/src/components/team/MemberList.tsx index 3649b13b4..a2fd2c6d6 100644 --- a/frontend2/src/components/team/MemberList.tsx +++ b/frontend2/src/components/team/MemberList.tsx @@ -41,9 +41,10 @@ const MemberList: React.FC = ({ members, className = "" }) => { return (
{/* display current user first */} - {currentUser !== undefined && ( - - )} + {currentUser !== undefined && + members.find((user) => user.id === currentUser.id) !== undefined && ( + + )} {members.map( (member) => member.id !== currentUser?.id && ( diff --git a/frontend2/src/views/TeamProfile.tsx b/frontend2/src/views/TeamProfile.tsx new file mode 100644 index 000000000..a17be3955 --- /dev/null +++ b/frontend2/src/views/TeamProfile.tsx @@ -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"; +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 ( +
+ {!team.isSuccess ? ( + + ) : ( + + )} +
+ ); + }, [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 ; + } + + return ( +
+ Team Profile +
+
+ +
+
+ +
+ {team.data.name} +
+ {team.data.status === Status526Enum.S && ( +
+ (staff team) +
+ )} +
+
+
+
Team quote
+
+ {isNilOrEmptyStr(team.data.profile?.quote) ? ( + + This team does not have a quote + + ) : ( + team.data.profile?.quote + )} +
+
+
+
Team biography
+
+ {isNilOrEmptyStr(team.data.profile?.biography) ? ( + + This team does not have a biography + + ) : ( + team.data.profile?.biography + )} +
+
+
+
Eligibility
+
+ {eligibles.length !== 0 ? ( + eligibles?.map((criterion) => ( +
+ {criterion.title} + {" "} + {criterion.icon} +
+ )) + ) : ( + + This team did not select any eligibility criteria. + + )} +
+
+
+
+
+ + {/* The members list that displays when on a smaller screen */} + + {membersList} + +
+ + {/* The members list that displays to the right side when on a big screen. */} + + {membersList} + +
+
+ ); +}; + +const TeamNotFound: React.FC = () => { + const { teamId } = useParams(); + return
Team {teamId} was not found.
; +}; + +export default TeamProfile;