From ac5a9e071d48c18eb70141f8a77f120e7ea2cb3b Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Mon, 6 Jul 2026 20:21:22 +0300 Subject: [PATCH] Fix match being locked giving no feedback to users before server error --- .../tournament-bracket/core/Tournament.ts | 7 ++++++- .../actions/to.$id.matches.$mid.server.ts | 6 ++++++ .../components/TournamentMatchBanner.tsx | 11 +++++++++++ .../tournament-match/match-page-context.tsx | 11 +++++++++++ .../routes/to.$id.matches.$mid.test.ts | 15 +++++++++++++++ locales/da/tournament.json | 2 ++ locales/de/tournament.json | 2 ++ locales/en/tournament.json | 2 ++ locales/es-ES/tournament.json | 2 ++ locales/es-US/tournament.json | 2 ++ locales/fr-CA/tournament.json | 2 ++ locales/fr-EU/tournament.json | 2 ++ locales/he/tournament.json | 2 ++ locales/it/tournament.json | 2 ++ locales/ja/tournament.json | 2 ++ locales/ko/tournament.json | 2 ++ locales/nl/tournament.json | 2 ++ locales/pl/tournament.json | 2 ++ locales/pt-BR/tournament.json | 2 ++ locales/ru/tournament.json | 2 ++ locales/zh/tournament.json | 2 ++ 21 files changed, 81 insertions(+), 1 deletion(-) diff --git a/app/features/tournament-bracket/core/Tournament.ts b/app/features/tournament-bracket/core/Tournament.ts index bc7330a69..b819dd12e 100644 --- a/app/features/tournament-bracket/core/Tournament.ts +++ b/app/features/tournament-bracket/core/Tournament.ts @@ -15,7 +15,7 @@ import { } from "~/features/tournament/tournament-utils"; import type * as Progression from "~/features/tournament-bracket/core/Progression"; import type { TournamentManagerDataSet } from "~/modules/brackets-manager/types"; -import type { Match, Stage } from "~/modules/brackets-model"; +import { type Match, type Stage, Status } from "~/modules/brackets-model"; import type { ModeShort } from "~/modules/in-game-lists/types"; import { isAdmin } from "~/modules/permissions/utils"; import { @@ -798,6 +798,11 @@ export class Tournament { // match didn't start yet if (!match.opponent1 || !match.opponent2) return false; + // waiting for teams to finish their previous matches + if (match.status === Status.Locked || match.status === Status.Waiting) { + return false; + } + const matchIsOver = match.opponent1.result === "win" || match.opponent2.result === "win"; diff --git a/app/features/tournament-match/actions/to.$id.matches.$mid.server.ts b/app/features/tournament-match/actions/to.$id.matches.$mid.server.ts index fbc9ad550..3981cc8dc 100644 --- a/app/features/tournament-match/actions/to.$id.matches.$mid.server.ts +++ b/app/features/tournament-match/actions/to.$id.matches.$mid.server.ts @@ -77,6 +77,12 @@ export const action: ActionFunction = async ({ params, request }) => { (p) => p.id === user?.id, ); + errorToastIfFalsy( + match.status !== TournamentMatchStatus.Locked && + match.status !== TournamentMatchStatus.Waiting, + "Match is locked, waiting for teams to finish their previous matches", + ); + errorToastIfFalsy( canReportTournamentScore({ match, diff --git a/app/features/tournament-match/components/TournamentMatchBanner.tsx b/app/features/tournament-match/components/TournamentMatchBanner.tsx index a25400dbd..91c757ce4 100644 --- a/app/features/tournament-match/components/TournamentMatchBanner.tsx +++ b/app/features/tournament-match/components/TournamentMatchBanner.tsx @@ -55,6 +55,7 @@ export function TournamentMatchBanner({ currentMap, teamsMissingActiveRoster, matchIsLocked, + waitingForPreviousMatch, joinPool, joinPass, teams, @@ -156,6 +157,16 @@ export function TournamentMatchBanner({ header={t("tournament:match.waitingForTeams.header")} subtitle={t("tournament:match.waitingForTeams.subtitle")} /> + ) : waitingForPreviousMatch ? ( + } + header={t("tournament:match.waitingForPrevious.header")} + subtitle={t("tournament:match.waitingForPrevious.subtitle")} + screenLegal={screenLegal} + joinPool={joinPool} + joinPass={joinPass} + host={host} + /> ) : teamsMissingActiveRoster.length > 0 ? ( } diff --git a/app/features/tournament-match/match-page-context.tsx b/app/features/tournament-match/match-page-context.tsx index 72e5c586e..427557fd4 100644 --- a/app/features/tournament-match/match-page-context.tsx +++ b/app/features/tournament-match/match-page-context.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import { TAB_KEYS } from "~/components/match-page/MatchTabs"; import { resolveRoomPass } from "~/components/match-page/utils"; +import { TournamentMatchStatus } from "~/db/tables"; import { useUser } from "~/features/auth/core/user"; import { useTournament } from "~/features/tournament/routes/to.$id"; import { isLeagueRoundLocked } from "~/features/tournament/tournament-utils"; @@ -32,6 +33,7 @@ type MatchPageContextValue = { turnOfResult: ReturnType; isPickBanStep: boolean; matchIsLocked: boolean; + waitingForPreviousMatch: boolean; joinPool: string | null; joinPass: string | null; }; @@ -99,6 +101,10 @@ export function MatchPageProvider({ scores, }); + const waitingForPreviousMatch = + data.match.status === TournamentMatchStatus.Locked || + data.match.status === TournamentMatchStatus.Waiting; + const joinInfo = resolveJoinInfo({ tournament, data, teams }); const tabs = resolveVisibleTabs({ @@ -117,6 +123,7 @@ export function MatchPageProvider({ tournament.isOrganizerOrStreamer(user) && !tournament.ctx.isFinalized, leagueRoundLocked: isLeagueRoundLocked(tournament, data.match.roundId), lockedForCast, + waitingForPreviousMatch, }); return ( @@ -132,6 +139,7 @@ export function MatchPageProvider({ turnOfResult, isPickBanStep, matchIsLocked: lockedForCast, + waitingForPreviousMatch, joinPool: joinInfo?.pool ?? null, joinPass: joinInfo?.pass ?? null, }} @@ -160,6 +168,7 @@ function resolveVisibleTabs({ isAdminEligible, leagueRoundLocked, lockedForCast, + waitingForPreviousMatch, }: { canReportScore: boolean; canReportWeapons: boolean; @@ -171,11 +180,13 @@ function resolveVisibleTabs({ isAdminEligible: boolean; leagueRoundLocked: boolean; lockedForCast: boolean; + waitingForPreviousMatch: boolean; }): MatchTabKey[] { const tabs: MatchTabKey[] = [TAB_KEYS.ROSTERS]; if ( !leagueRoundLocked && + !waitingForPreviousMatch && (isPickBanStep || (canReportScore && hasCurrentMap && diff --git a/app/features/tournament-match/routes/to.$id.matches.$mid.test.ts b/app/features/tournament-match/routes/to.$id.matches.$mid.test.ts index 399275df3..503f3dfd7 100644 --- a/app/features/tournament-match/routes/to.$id.matches.$mid.test.ts +++ b/app/features/tournament-match/routes/to.$id.matches.$mid.test.ts @@ -246,6 +246,21 @@ describe("Tournament match page", () => { }); }); + describe("locked match", () => { + it("should return error when reporting score for a match waiting on previous matches", async () => { + await setActiveRosterAction(); + await db + .updateTable("TournamentMatch") + .set({ status: 0 }) + .where("id", "=", 1) + .execute(); + + const res = await reportScoreAction({ position: 0 }); + + assertResponseErrored(res, "Match is locked"); + }); + }); + describe("BYE matches", () => { it("should 404 when accessing a BYE match", async () => { await db diff --git a/locales/da/tournament.json b/locales/da/tournament.json index fe88e8fe8..b338abba1 100644 --- a/locales/da/tournament.json +++ b/locales/da/tournament.json @@ -171,6 +171,8 @@ "match.locked.subtitle": "", "match.waitingForTeams.header": "", "match.waitingForTeams.subtitle": "", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "", "match.action.undoLastScore": "Annuller sidste score", "match.action.reopenMatch": "Genåbn kamp", diff --git a/locales/de/tournament.json b/locales/de/tournament.json index e45efa7a4..960756aa7 100644 --- a/locales/de/tournament.json +++ b/locales/de/tournament.json @@ -171,6 +171,8 @@ "match.locked.subtitle": "", "match.waitingForTeams.header": "", "match.waitingForTeams.subtitle": "", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "", "match.action.undoLastScore": "Letztes Ergebnis widerrufen", "match.action.reopenMatch": "Match erneut öffnen", diff --git a/locales/en/tournament.json b/locales/en/tournament.json index e61902699..46d2622b5 100644 --- a/locales/en/tournament.json +++ b/locales/en/tournament.json @@ -171,6 +171,8 @@ "match.locked.subtitle": "Please wait for staff to unlock", "match.waitingForTeams.header": "Waiting for teams", "match.waitingForTeams.subtitle": "Teams will be resolved from earlier matches", + "match.waitingForPrevious.header": "Previous match ongoing", + "match.waitingForPrevious.subtitle": "Match will be reportable when both teams are ready to play", "match.tbd": "TBD", "match.action.undoLastScore": "Undo last score", "match.action.reopenMatch": "Reopen match", diff --git a/locales/es-ES/tournament.json b/locales/es-ES/tournament.json index f4c6e30bb..de3f5da55 100644 --- a/locales/es-ES/tournament.json +++ b/locales/es-ES/tournament.json @@ -173,6 +173,8 @@ "match.locked.subtitle": "", "match.waitingForTeams.header": "", "match.waitingForTeams.subtitle": "", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "", "match.action.undoLastScore": "Anular resultado previo", "match.action.reopenMatch": "Reabrir partido", diff --git a/locales/es-US/tournament.json b/locales/es-US/tournament.json index 1954fa13a..e3089b74f 100644 --- a/locales/es-US/tournament.json +++ b/locales/es-US/tournament.json @@ -173,6 +173,8 @@ "match.locked.subtitle": "", "match.waitingForTeams.header": "", "match.waitingForTeams.subtitle": "", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "", "match.action.undoLastScore": "Anular resultado previo", "match.action.reopenMatch": "Reabrir partido", diff --git a/locales/fr-CA/tournament.json b/locales/fr-CA/tournament.json index 16e9b27a9..59739fe0d 100644 --- a/locales/fr-CA/tournament.json +++ b/locales/fr-CA/tournament.json @@ -173,6 +173,8 @@ "match.locked.subtitle": "", "match.waitingForTeams.header": "", "match.waitingForTeams.subtitle": "", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "", "match.action.undoLastScore": "Annuler le dernier score", "match.action.reopenMatch": "Rouvrir le match", diff --git a/locales/fr-EU/tournament.json b/locales/fr-EU/tournament.json index a95bc342b..257b63120 100644 --- a/locales/fr-EU/tournament.json +++ b/locales/fr-EU/tournament.json @@ -173,6 +173,8 @@ "match.locked.subtitle": "", "match.waitingForTeams.header": "", "match.waitingForTeams.subtitle": "", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "", "match.action.undoLastScore": "Annuler le dernier score", "match.action.reopenMatch": "Rouvrir le match", diff --git a/locales/he/tournament.json b/locales/he/tournament.json index 0763a5957..a0df9ba61 100644 --- a/locales/he/tournament.json +++ b/locales/he/tournament.json @@ -173,6 +173,8 @@ "match.locked.subtitle": "", "match.waitingForTeams.header": "", "match.waitingForTeams.subtitle": "", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "", "match.action.undoLastScore": "בטלו את התוצאה האחרונה", "match.action.reopenMatch": "פתיחה מחדש של הקרב", diff --git a/locales/it/tournament.json b/locales/it/tournament.json index 697ad843d..125294514 100644 --- a/locales/it/tournament.json +++ b/locales/it/tournament.json @@ -173,6 +173,8 @@ "match.locked.subtitle": "", "match.waitingForTeams.header": "", "match.waitingForTeams.subtitle": "", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "", "match.action.undoLastScore": "Annulla ultimo punteggio", "match.action.reopenMatch": "Riapri match", diff --git a/locales/ja/tournament.json b/locales/ja/tournament.json index 0c3694d80..89f7c9eea 100644 --- a/locales/ja/tournament.json +++ b/locales/ja/tournament.json @@ -167,6 +167,8 @@ "match.locked.subtitle": "", "match.waitingForTeams.header": "", "match.waitingForTeams.subtitle": "", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "", "match.action.undoLastScore": "最後のスコアをやりなおす", "match.action.reopenMatch": "対戦を再度開く", diff --git a/locales/ko/tournament.json b/locales/ko/tournament.json index 022754298..4ee278b26 100644 --- a/locales/ko/tournament.json +++ b/locales/ko/tournament.json @@ -167,6 +167,8 @@ "match.locked.subtitle": "", "match.waitingForTeams.header": "", "match.waitingForTeams.subtitle": "", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "", "match.action.undoLastScore": "", "match.action.reopenMatch": "", diff --git a/locales/nl/tournament.json b/locales/nl/tournament.json index 141826d9f..cdfffbd05 100644 --- a/locales/nl/tournament.json +++ b/locales/nl/tournament.json @@ -171,6 +171,8 @@ "match.locked.subtitle": "", "match.waitingForTeams.header": "", "match.waitingForTeams.subtitle": "", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "", "match.action.undoLastScore": "", "match.action.reopenMatch": "", diff --git a/locales/pl/tournament.json b/locales/pl/tournament.json index 1e6b39be9..c5bd99d1e 100644 --- a/locales/pl/tournament.json +++ b/locales/pl/tournament.json @@ -175,6 +175,8 @@ "match.locked.subtitle": "", "match.waitingForTeams.header": "", "match.waitingForTeams.subtitle": "", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "", "match.action.undoLastScore": "", "match.action.reopenMatch": "", diff --git a/locales/pt-BR/tournament.json b/locales/pt-BR/tournament.json index 66e0e53f2..825462b2f 100644 --- a/locales/pt-BR/tournament.json +++ b/locales/pt-BR/tournament.json @@ -173,6 +173,8 @@ "match.locked.subtitle": "", "match.waitingForTeams.header": "", "match.waitingForTeams.subtitle": "", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "", "match.action.undoLastScore": "Desfazer última pontuação", "match.action.reopenMatch": "Reabrir partida", diff --git a/locales/ru/tournament.json b/locales/ru/tournament.json index 7d00ebebd..207457e1a 100644 --- a/locales/ru/tournament.json +++ b/locales/ru/tournament.json @@ -175,6 +175,8 @@ "match.locked.subtitle": "", "match.waitingForTeams.header": "", "match.waitingForTeams.subtitle": "", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "", "match.action.undoLastScore": "Отменить последний результат", "match.action.reopenMatch": "Открыть матч заново", diff --git a/locales/zh/tournament.json b/locales/zh/tournament.json index b98d1ed2b..43d220467 100644 --- a/locales/zh/tournament.json +++ b/locales/zh/tournament.json @@ -169,6 +169,8 @@ "match.locked.subtitle": "请等待工作人员解锁", "match.waitingForTeams.header": "正在等待队伍", "match.waitingForTeams.subtitle": "队伍将由先前的对局结果决定", + "match.waitingForPrevious.header": "", + "match.waitingForPrevious.subtitle": "", "match.tbd": "待定", "match.action.undoLastScore": "撤销上一局比分", "match.action.reopenMatch": "重新开启对局",