Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ import CodeOfConduct from "views/CodeOfConduct";
import Client from "views/Client";
import AdminTournament from "views/AdminTournament";
import { adminTournamentLoader } from "api/loaders/adminTournamentLoader";
import MatchProfile from "views/MatchProfile";
import { matchProfileLoader } from "api/loaders/matchProfileLoader";

const queryClient = new QueryClient({
queryCache: new QueryCache({
Expand Down Expand Up @@ -178,6 +180,11 @@ const router = createBrowserRouter([
path: "client",
element: <Client />,
},
{
path: "match/:matchId",
element: <MatchProfile />,
loader: matchProfileLoader(queryClient),
},
],
},
// Pages that should always be visible
Expand Down
68 changes: 43 additions & 25 deletions frontend/src/api/compete/competeApi.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
import {
CompeteApi,
type TournamentSubmission,
type PaginatedSubmissionList,
type PaginatedScrimmageRequestList,
type PaginatedMatchList,
type CompeteSubmissionCreateRequest,
type CompeteSubmissionDownloadRetrieveRequest,
type CompeteSubmissionListRequest,
type Submission,
type CompeteRequestAcceptCreateRequest,
type CompeteRequestRejectCreateRequest,
type CompeteRequestInboxListRequest,
type CompeteRequestOutboxListRequest,
type CompeteRequestCreateRequest,
type ScrimmageRequest,
type CompeteMatchScrimmageListRequest,
type CompeteMatchTournamentListRequest,
type CompeteMatchListRequest,
type CompeteSubmissionTournamentListRequest,
type CompeteRequestDestroyRequest,
type CompeteMatchHistoricalRatingTopNListRequest,
type HistoricalRating,
type CompeteMatchScrimmagingRecordRetrieveRequest,
type ScrimmageRecord,
type CompeteMatchHistoricalRatingRetrieveRequest,
CompeteApi,
type TournamentSubmission,
type PaginatedSubmissionList,
type PaginatedScrimmageRequestList,
type PaginatedMatchList,
type CompeteSubmissionRetrieveRequest,
type CompeteSubmissionCreateRequest,
type CompeteSubmissionDownloadRetrieveRequest,
type CompeteSubmissionListRequest,
type Submission,
type CompeteRequestAcceptCreateRequest,
type CompeteRequestRejectCreateRequest,
type CompeteRequestInboxListRequest,
type CompeteRequestOutboxListRequest,
type CompeteRequestCreateRequest,
type ScrimmageRequest,
type Match,
type CompeteMatchRetrieveRequest,
type CompeteMatchScrimmageListRequest,
type CompeteMatchTournamentListRequest,
type CompeteMatchListRequest,
type CompeteSubmissionTournamentListRequest,
type CompeteRequestDestroyRequest,
type CompeteMatchHistoricalRatingTopNListRequest,
type HistoricalRating,
type CompeteMatchScrimmagingRecordRetrieveRequest,
type ScrimmageRecord,
type CompeteMatchHistoricalRatingRetrieveRequest,
} from "../_autogen";
import { DEFAULT_API_CONFIGURATION, downloadFile } from "../helpers";

Expand Down Expand Up @@ -73,6 +76,11 @@ export const downloadSubmission = async ({
await downloadFile(url, `battlecode_source_${id}.zip`);
};

export const getSubmissionInfo = async ({
episodeId,
id,
}: CompeteSubmissionRetrieveRequest): Promise<Submission> => await API.competeSubmissionRetrieve({ episodeId, id });

/**
* Get a paginated list of all of the current user's team's submissions.
* @param episodeId The current episode's ID.
Expand All @@ -93,6 +101,7 @@ export const getAllUserTournamentSubmissions = async ({
}: CompeteSubmissionTournamentListRequest): Promise<TournamentSubmission[]> =>
await API.competeSubmissionTournamentList({ episodeId });


/**
* Accept a scrimmage invitation.
* @param episodeId The current episode's ID.
Expand Down Expand Up @@ -203,6 +212,15 @@ export const getTournamentMatchesList = async ({
tournamentId,
});

export const getMatchInfo = async ({
episodeId,
id,
}: CompeteMatchRetrieveRequest): Promise<Match> =>
await API.competeMatchRetrieve({
episodeId,
id,
});

/**
* Get all of the matches played in the given episode. Includes both tournament
* matches and scrimmages.
Expand Down
23 changes: 21 additions & 2 deletions frontend/src/api/compete/competeFactories.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {
CompeteMatchHistoricalRatingTopNListRequest,
CompeteMatchHistoricalRatingRetrieveRequest,
CompeteMatchRetrieveRequest,
CompeteMatchListRequest,
CompeteMatchScrimmageListRequest,
CompeteMatchScrimmagingRecordRetrieveRequest,
Expand All @@ -10,16 +11,18 @@ import type {
CompeteSubmissionListRequest,
CompeteSubmissionTournamentListRequest,
HistoricalRating,
Match,
PaginatedMatchList,
PaginatedScrimmageRequestList,
PaginatedSubmissionList,
TournamentSubmission,
ScrimmageRecord,
ScrimmageRecord, CompeteSubmissionRetrieveRequest, Submission
} from "../_autogen";
import type { PaginatedQueryFactory, QueryFactory } from "../apiTypes";
import { competeQueryKeys } from "./competeKeys";
import {
getAllUserTournamentSubmissions,
getMatchInfo,
getMatchesList,
getRatingTopNList,
getRatingHistory,
Expand All @@ -28,10 +31,18 @@ import {
getSubmissionsList,
getTournamentMatchesList,
getUserScrimmagesInboxList,
getUserScrimmagesOutboxList,
getUserScrimmagesOutboxList, getSubmissionInfo
} from "./competeApi";
import { prefetchNextPage } from "../helpers";

export const submissionInfoFactory: QueryFactory<
CompeteSubmissionRetrieveRequest,
Submission
> = {
queryKey: competeQueryKeys.subInfo,
queryFn: async ({ episodeId, id }) => await getSubmissionInfo({ episodeId, id }),
} as const;

export const subsListFactory: PaginatedQueryFactory<
CompeteSubmissionListRequest,
PaginatedSubmissionList
Expand Down Expand Up @@ -161,6 +172,14 @@ export const teamScrimmageListFactory: PaginatedQueryFactory<
},
} as const;

export const matchInfoFactory: QueryFactory<
CompeteMatchRetrieveRequest,
Match
> = {
queryKey: competeQueryKeys.matchInfo,
queryFn: async ({ episodeId, id }) => await getMatchInfo({ episodeId, id }),
} as const;

export const matchListFactory: PaginatedQueryFactory<
CompeteMatchListRequest,
PaginatedMatchList
Expand Down
15 changes: 14 additions & 1 deletion frontend/src/api/compete/competeKeys.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import type {
CompeteMatchHistoricalRatingTopNListRequest,
CompeteMatchHistoricalRatingRetrieveRequest,
CompeteMatchRetrieveRequest,
CompeteMatchListRequest,
CompeteMatchScrimmageListRequest,
CompeteMatchScrimmagingRecordRetrieveRequest,
CompeteMatchTournamentListRequest,
CompeteRequestInboxListRequest,
CompeteRequestOutboxListRequest,
CompeteSubmissionListRequest,
CompeteSubmissionTournamentListRequest,
CompeteSubmissionTournamentListRequest, CompeteSubmissionRetrieveRequest
} from "../_autogen";
import type { QueryKeyBuilder } from "../apiTypes";

interface CompeteKeys {
// --- SUBMISSIONS --- //
subBase: QueryKeyBuilder<{ episodeId: string }>;
subInfo: QueryKeyBuilder<CompeteSubmissionRetrieveRequest>;
subList: QueryKeyBuilder<CompeteSubmissionListRequest>;
tourneySubs: QueryKeyBuilder<CompeteSubmissionTournamentListRequest>;
// --- SCRIMMAGES --- //
Expand All @@ -25,6 +27,7 @@ interface CompeteKeys {
scrimsOtherList: QueryKeyBuilder<CompeteMatchScrimmageListRequest>;
// --- MATCHES --- //
matchBase: QueryKeyBuilder<{ episodeId: string }>;
matchInfo: QueryKeyBuilder<CompeteMatchRetrieveRequest>;
matchList: QueryKeyBuilder<CompeteMatchListRequest>;
tourneyMatchList: QueryKeyBuilder<CompeteMatchTournamentListRequest>;
// --- PERFORMANCE --- //
Expand All @@ -43,6 +46,11 @@ export const competeQueryKeys: CompeteKeys = {
["compete", episodeId, "submissions"] as const,
},

subInfo: {
key: ({ episodeId, id }: CompeteSubmissionRetrieveRequest) =>
[...competeQueryKeys.subBase.key({ episodeId }), "info", id] as const,
},

subList: {
key: ({ episodeId, page = 1 }: CompeteSubmissionListRequest) =>
[...competeQueryKeys.subBase.key({ episodeId }), "list", page] as const,
Expand Down Expand Up @@ -103,6 +111,11 @@ export const competeQueryKeys: CompeteKeys = {
["compete", episodeId, "matches"] as const,
},

matchInfo: {
key: ({ episodeId, id }: CompeteMatchRetrieveRequest) =>
[...competeQueryKeys.matchBase.key({ episodeId }), "info", id] as const,
},

matchList: {
key: ({ episodeId, page = 1 }: CompeteMatchListRequest) =>
[...competeQueryKeys.matchBase.key({ episodeId }), "list", page] as const,
Expand Down
24 changes: 23 additions & 1 deletion frontend/src/api/compete/useCompete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { competeMutationKeys, competeQueryKeys } from "./competeKeys";
import type {
CompeteMatchHistoricalRatingTopNListRequest,
CompeteMatchHistoricalRatingRetrieveRequest,
CompeteMatchRetrieveRequest,
CompeteMatchListRequest,
CompeteMatchScrimmageListRequest,
CompeteMatchScrimmagingRecordRetrieveRequest,
Expand All @@ -20,10 +21,12 @@ import type {
CompeteRequestOutboxListRequest,
CompeteRequestRejectCreateRequest,
CompeteSubmissionCreateRequest,
CompeteSubmissionRetrieveRequest,
CompeteSubmissionListRequest,
CompeteSubmissionTournamentListRequest,
CompeteSubmissionDownloadRetrieveRequest,
HistoricalRating,
Match,
PaginatedMatchList,
PaginatedScrimmageRequestList,
PaginatedSubmissionList,
Expand All @@ -44,6 +47,7 @@ import {
import toast from "react-hot-toast";
import { buildKey } from "../helpers";
import {
matchInfoFactory,
matchListFactory,
ratingHistoryTopNFactory,
userRatingHistoryFactory,
Expand All @@ -55,13 +59,22 @@ import {
tournamentMatchListFactory,
tournamentSubsListFactory,
userScrimmageListFactory,
ratingHistoryFactory,
ratingHistoryFactory, submissionInfoFactory
} from "./competeFactories";
import { MILLIS_SECOND, SECONDS_MINUTE } from "utils/utilTypes";

// ---------- QUERY HOOKS ---------- //
const STATISTICS_WAIT_MINUTES = 5;

export const useSubmissionInfo = (
{ episodeId, id }: CompeteSubmissionRetrieveRequest,
): UseQueryResult<Submission> =>
useQuery({
queryKey: buildKey(submissionInfoFactory.queryKey, { episodeId, id }),
queryFn: async () =>
await submissionInfoFactory.queryFn({ episodeId, id })
});

/**
* For retrieving a list of the currently logged in user's submissions.
*/
Expand Down Expand Up @@ -163,6 +176,15 @@ export const useTeamScrimmageList = (
),
});

export const useMatchInfo = (
{ episodeId, id }: CompeteMatchRetrieveRequest,
): UseQueryResult<Match> =>
useQuery({
queryKey: buildKey(matchInfoFactory.queryKey, { episodeId, id }),
queryFn: async () =>
await matchInfoFactory.queryFn({ episodeId, id }),
});

/**
* For retrieving a paginated list of the matches in a given episode.
*/
Expand Down
25 changes: 25 additions & 0 deletions frontend/src/api/loaders/matchProfileLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { QueryClient } from "@tanstack/react-query";
import { matchInfoFactory } from "api/compete/competeFactories";
import { safeEnsureQueryData } from "api/helpers";
import type { LoaderFunction } from "react-router-dom";
import { isPresent } from "utils/utilTypes";

export const matchProfileLoader =
(queryClient: QueryClient): LoaderFunction =>
({ params }) => {
const { episodeId, id } = params;

if (!isPresent(id) || !isPresent(episodeId)) return null;

// Load match info
safeEnsureQueryData(
{
episodeId,
id,
},
matchInfoFactory,
queryClient,
);

return null;
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useEpisodeId } from "contexts/EpisodeContext";
import { useUserTeam } from "api/team/useTeam";
import { useUserScrimmageList } from "api/compete/useCompete";
import { useQueryClient } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import MatchReplayButton from "components/MatchReplayButton";

interface ScrimHistoryTableProps {
Expand All @@ -24,6 +25,7 @@ const ScrimHistoryTable: React.FC<ScrimHistoryTableProps> = ({
}) => {
const { episodeId } = useEpisodeId();
const queryClient = useQueryClient();
const navigate = useNavigate();
const episodeData = useEpisodeInfo({ id: episodeId });
const userTeamData = useUserTeam({ episodeId });
const scrimsData = useUserScrimmageList(
Expand Down Expand Up @@ -113,6 +115,9 @@ const ScrimHistoryTable: React.FC<ScrimHistoryTableProps> = ({
value: (match) => dateTime(match.created).localFullString,
},
]}
onRowClick={(match) => {
navigate(`/${episodeId}/match/${match.id.toString()}`);
}}
/>
</Fragment>
);
Expand Down
Loading
Loading