diff --git a/app/components/tournament/EliminationBracketMatch.tsx b/app/components/tournament/EliminationBracketMatch.tsx
index bf8e04e52..971d8aa57 100644
--- a/app/components/tournament/EliminationBracketMatch.tsx
+++ b/app/components/tournament/EliminationBracketMatch.tsx
@@ -35,7 +35,7 @@ export function EliminationBracketMatch({
return (
{children}
diff --git a/app/core/tournament/bracket.ts b/app/core/tournament/bracket.ts
index aec5e5b27..fe0b7670a 100644
--- a/app/core/tournament/bracket.ts
+++ b/app/core/tournament/bracket.ts
@@ -120,6 +120,41 @@ export function getRoundNames(bracket: Bracket): EliminationBracket {
};
}
+/** Returns round name (e.g. "Winners' Round 1") from positions
+ *
+ * @param position Position of the match to get the name for
+ * @param allPositions All positions of matches in the bracket. Positive integers are winners and negative losers.
+ *
+ */
+export function getRoundNameByPositions(
+ position: number,
+ allPositions: number[]
+): string {
+ const winnersRoundCount = allPositions.reduce(
+ (sum, pos) => (pos > 0 ? 1 : 0) + sum,
+ 0
+ );
+ const losersRoundCount = allPositions.reduce(
+ (sum, pos) => (pos < 0 ? 1 : 0) + sum,
+ 0
+ );
+
+ const allRoundNames = {
+ winners: winnersRoundNames(winnersRoundCount, losersRoundCount === 0),
+ losers: losersRoundNames(losersRoundCount),
+ };
+
+ const result = (() => {
+ const index = Math.abs(position) - 1;
+ if (position > 0) return allRoundNames.winners[index];
+
+ return allRoundNames.losers[index];
+ })();
+ invariant(result, "No round name found");
+
+ return result;
+}
+
export function countRounds(bracket: Bracket): EliminationBracket {
const isDE = bracket.losers.length > 0;
let winners = 1 + Number(isDE);
diff --git a/app/db/tournament/queries/findMatchModalInfoByNumber.ts b/app/db/tournament/queries/findMatchModalInfoByNumber.ts
new file mode 100644
index 000000000..a6bbc698a
--- /dev/null
+++ b/app/db/tournament/queries/findMatchModalInfoByNumber.ts
@@ -0,0 +1,46 @@
+import { Prisma } from "@prisma/client";
+import { getRoundNameByPositions } from "~/core/tournament/bracket";
+import { db } from "~/utils/db.server";
+
+export type FindMatchModalInfoByNumber = Prisma.PromiseReturnType<
+ typeof findMatchModalInfoByNumber
+>;
+
+export async function findMatchModalInfoByNumber({
+ bracketId,
+ matchNumber,
+}: {
+ bracketId: string;
+ matchNumber: number;
+}) {
+ const tournamentRounds = await db.tournamentRound.findMany({
+ where: { bracketId },
+ include: {
+ matches: {
+ include: { results: true, participants: { include: { team: true } } },
+ },
+ stages: true,
+ },
+ });
+
+ const tournamentRound = tournamentRounds.find((round) =>
+ round.matches.find((match) => match.position === matchNumber)
+ );
+ const match = tournamentRound?.matches.find(
+ (match) => match.position === matchNumber
+ );
+
+ if (!tournamentRound || !match) return;
+
+ const teamsOrdered = match.participants.sort((a, b) =>
+ b.order.localeCompare(a.order)
+ );
+
+ return {
+ title: `${teamsOrdered[0].team.name} vs. ${teamsOrdered[1].team.name}`,
+ roundName: getRoundNameByPositions(
+ tournamentRound.position,
+ tournamentRounds.map((round) => round.position)
+ ),
+ };
+}
diff --git a/app/routes/to/$organization.$tournament/bracket.$bid/match.$mid.tsx b/app/routes/to/$organization.$tournament/bracket.$bid/match.$mid.tsx
deleted file mode 100644
index 9cb108548..000000000
--- a/app/routes/to/$organization.$tournament/bracket.$bid/match.$mid.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { json, LoaderFunction, useLoaderData, useLocation } from "remix";
-import invariant from "tiny-invariant";
-import Modal from "~/components/Modal";
-import { matchById } from "~/services/bracket";
-import type { MatchByIdI } from "~/services/bracket";
-import { Serialized } from "~/utils";
-
-const typedJson = (args: MatchByIdI) => json(args);
-
-export const loader: LoaderFunction = async ({ params }) => {
- invariant(typeof params.mid === "string", "Expected params.mid to be string");
-
- const match = await matchById(params.mid);
- return typedJson(match);
-};
-
-export default function MatchModal() {
- const data = useLoaderData>();
- const location = useLocation();
-
- const teamVersusTitle = (): string => {
- const teamsOrdered = data.participants.sort((a, b) =>
- b.order.localeCompare(a.order)
- );
-
- return `${teamsOrdered[0].team.name} vs. ${teamsOrdered[1].team.name}`;
- };
-
- return (
-
- asdasd
-
- );
-}
diff --git a/app/routes/to/$organization.$tournament/bracket.$bid/match.$num.tsx b/app/routes/to/$organization.$tournament/bracket.$bid/match.$num.tsx
new file mode 100644
index 000000000..f00aa4d33
--- /dev/null
+++ b/app/routes/to/$organization.$tournament/bracket.$bid/match.$num.tsx
@@ -0,0 +1,31 @@
+import { json, LoaderFunction, useLoaderData, useLocation } from "remix";
+import { z } from "zod";
+import Modal from "~/components/Modal";
+import {
+ FindMatchModalInfoByNumber,
+ findMatchModalInfoByNumber,
+} from "~/db/tournament/queries/findMatchModalInfoByNumber";
+
+const typedJson = (args: NonNullable) => json(args);
+
+export const loader: LoaderFunction = async ({ params }) => {
+ const { bid: bracketId, num: matchNumber } = z
+ .object({ bid: z.string(), num: z.preprocess(Number, z.number()) })
+ .parse(params);
+
+ const match = await findMatchModalInfoByNumber({ bracketId, matchNumber });
+ if (!match) throw new Response("No match found", { status: 400 });
+
+ return typedJson(match);
+};
+
+export default function MatchModal() {
+ const data = useLoaderData>();
+ const location = useLocation();
+
+ return (
+
+ {data.roundName}
+
+ );
+}