From 8b70e1b2c6a2d2873f314b3920f243b6a8f9dbeb Mon Sep 17 00:00:00 2001
From: Kalle <38327916+Sendouc@users.noreply.github.com>
Date: Sun, 3 May 2026 11:30:35 +0300
Subject: [PATCH] Better UI when waiting on a team
---
.../components/TournamentMatchBanner.tsx | 9 +++++-
.../components/TournamentMatchTabs.tsx | 32 ++++++++++++-------
.../loaders/to.$id.matches.$mid.server.ts | 2 ++
.../routes/to.$id.matches.$mid.tsx | 1 -
locales/da/tournament.json | 3 ++
locales/de/tournament.json | 3 ++
locales/en/tournament.json | 3 ++
locales/es-ES/tournament.json | 3 ++
locales/es-US/tournament.json | 3 ++
locales/fr-CA/tournament.json | 3 ++
locales/fr-EU/tournament.json | 3 ++
locales/he/tournament.json | 3 ++
locales/it/tournament.json | 3 ++
locales/ja/tournament.json | 3 ++
locales/ko/tournament.json | 3 ++
locales/nl/tournament.json | 3 ++
locales/pl/tournament.json | 3 ++
locales/pt-BR/tournament.json | 3 ++
locales/ru/tournament.json | 3 ++
locales/zh/tournament.json | 3 ++
20 files changed, 79 insertions(+), 13 deletions(-)
diff --git a/app/features/tournament-match/components/TournamentMatchBanner.tsx b/app/features/tournament-match/components/TournamentMatchBanner.tsx
index 1ea030109..c6fabbbf3 100644
--- a/app/features/tournament-match/components/TournamentMatchBanner.tsx
+++ b/app/features/tournament-match/components/TournamentMatchBanner.tsx
@@ -1,5 +1,5 @@
import { differenceInMinutes } from "date-fns";
-import { Lock, MousePointerClick, Users, X } from "lucide-react";
+import { Hourglass, Lock, MousePointerClick, Users, X } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Avatar } from "~/components/Avatar";
import { SendouButton } from "~/components/elements/Button";
@@ -40,6 +40,7 @@ export function TournamentMatchBanner({
const opponentOne = data.match.opponentOne;
const opponentTwo = data.match.opponentTwo;
+ const isMissingTeam = !opponentOne?.id || !opponentTwo?.id;
const leagueRoundLocked = isLeagueRoundLocked(tournament, data.match.roundId);
const leagueRoundStartDate = leagueRoundLocked
@@ -81,6 +82,12 @@ export function TournamentMatchBanner({
: undefined
}
/>
+ ) : isMissingTeam ? (
+ }
+ header={t("tournament:match.waitingForTeams.header")}
+ subtitle={t("tournament:match.waitingForTeams.subtitle")}
+ />
) : teamsMissingActiveRoster.length > 0 ? (
}
diff --git a/app/features/tournament-match/components/TournamentMatchTabs.tsx b/app/features/tournament-match/components/TournamentMatchTabs.tsx
index 78535df90..e32598015 100644
--- a/app/features/tournament-match/components/TournamentMatchTabs.tsx
+++ b/app/features/tournament-match/components/TournamentMatchTabs.tsx
@@ -1,3 +1,4 @@
+import { useTranslation } from "react-i18next";
import { useFetcher } from "react-router";
import { MatchJoinTab } from "~/components/match-page/MatchJoinTab";
import { MatchResultTab } from "~/components/match-page/MatchResultTab";
@@ -42,12 +43,16 @@ export function TournamentMatchTabs({
isPickBanStep,
} = useMatch();
- // Preview matches (participants TBD) only render the admin tab so organizers
- // can pre-cast or pre-prepare; everything else needs both teams.
+ // When waiting on team(s) only a subset of tabs can be rendered
if (!teamOne || !teamTwo) {
- return tabs.includes(TAB_KEYS.ADMIN) ? (
-
-
+ return tabs.length > 0 ? (
+
+ {tabs.includes(TAB_KEYS.ROSTERS) ? (
+
+ ) : null}
+ {tabs.includes(TAB_KEYS.ADMIN) ? (
+
+ ) : null}
) : null;
}
@@ -369,28 +374,33 @@ function TournamentMatchRosterTab({
}: {
data: TournamentMatchLoaderData;
}) {
+ const { t } = useTranslation(["tournament"]);
const tournament = useTournament();
const user = useUser();
const fetcher = useFetcher();
const {
teams: [teamOne, teamTwo],
} = useMatch();
- if (!teamOne || !teamTwo) return null;
+
+ const tbdTeam = { defaultName: t("tournament:match.tbd"), members: [] };
return (
);
diff --git a/app/features/tournament-match/loaders/to.$id.matches.$mid.server.ts b/app/features/tournament-match/loaders/to.$id.matches.$mid.server.ts
index c69dc67f2..d568e10bc 100644
--- a/app/features/tournament-match/loaders/to.$id.matches.$mid.server.ts
+++ b/app/features/tournament-match/loaders/to.$id.matches.$mid.server.ts
@@ -207,6 +207,8 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
const isParticipant = match.players.some((p) => p.id === user?.id);
const canJoin =
!matchIsOver &&
+ match.opponentOne?.id != null &&
+ match.opponentTwo?.id != null &&
(isParticipant || tournament.isOrganizerOrStreamer(user)) &&
!isLeagueRoundLocked(tournament, match.roundId);
diff --git a/app/features/tournament-match/routes/to.$id.matches.$mid.tsx b/app/features/tournament-match/routes/to.$id.matches.$mid.tsx
index da19aae15..8214d3a30 100644
--- a/app/features/tournament-match/routes/to.$id.matches.$mid.tsx
+++ b/app/features/tournament-match/routes/to.$id.matches.$mid.tsx
@@ -15,7 +15,6 @@ export const handle: SendouRouteHandle = {
i18n: ["q"],
};
-// xxx: check page when both teams are not resolved yet
export default function TournamentMatchPage() {
const data = useLoaderData();
diff --git a/locales/da/tournament.json b/locales/da/tournament.json
index 6b5adb486..bb1a74f25 100644
--- a/locales/da/tournament.json
+++ b/locales/da/tournament.json
@@ -117,6 +117,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "Annuller sidste score",
"match.action.reopenMatch": "Genåbn kamp",
"match.action.endSet": "",
diff --git a/locales/de/tournament.json b/locales/de/tournament.json
index f14255cde..c6a29d72d 100644
--- a/locales/de/tournament.json
+++ b/locales/de/tournament.json
@@ -117,6 +117,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "Letztes Ergebnis widerrufen",
"match.action.reopenMatch": "Match erneut öffnen",
"match.action.endSet": "",
diff --git a/locales/en/tournament.json b/locales/en/tournament.json
index f02cc4e55..1b264d584 100644
--- a/locales/en/tournament.json
+++ b/locales/en/tournament.json
@@ -117,6 +117,9 @@
"match.activeRosterMissing.subtitle": "Waiting on {{teams}}",
"match.leagueLocked.header": "Waiting for league round to start",
"match.leagueLocked.subtitle": "Round playable from {{date}} onwards",
+ "match.waitingForTeams.header": "Waiting for teams",
+ "match.waitingForTeams.subtitle": "Teams will be resolved from earlier matches",
+ "match.tbd": "TBD",
"match.action.undoLastScore": "Undo last score",
"match.action.reopenMatch": "Reopen match",
"match.action.endSet": "End set",
diff --git a/locales/es-ES/tournament.json b/locales/es-ES/tournament.json
index 8abe2659a..7d027d0dc 100644
--- a/locales/es-ES/tournament.json
+++ b/locales/es-ES/tournament.json
@@ -119,6 +119,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "Anular resultado previo",
"match.action.reopenMatch": "Reabrir partido",
"match.action.endSet": "Finalizar set",
diff --git a/locales/es-US/tournament.json b/locales/es-US/tournament.json
index 30216170b..0a750ff5e 100644
--- a/locales/es-US/tournament.json
+++ b/locales/es-US/tournament.json
@@ -119,6 +119,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "Anular resultado previo",
"match.action.reopenMatch": "Reabrir partido",
"match.action.endSet": "",
diff --git a/locales/fr-CA/tournament.json b/locales/fr-CA/tournament.json
index 3f1034285..8a9b01633 100644
--- a/locales/fr-CA/tournament.json
+++ b/locales/fr-CA/tournament.json
@@ -119,6 +119,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "Annuler le dernier score",
"match.action.reopenMatch": "Rouvrir le match",
"match.action.endSet": "",
diff --git a/locales/fr-EU/tournament.json b/locales/fr-EU/tournament.json
index c16fce253..47dd8aa6a 100644
--- a/locales/fr-EU/tournament.json
+++ b/locales/fr-EU/tournament.json
@@ -119,6 +119,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "Annuler le dernier score",
"match.action.reopenMatch": "Rouvrir le match",
"match.action.endSet": "",
diff --git a/locales/he/tournament.json b/locales/he/tournament.json
index a5a27b9a6..ac5631000 100644
--- a/locales/he/tournament.json
+++ b/locales/he/tournament.json
@@ -119,6 +119,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "בטלו את התוצאה האחרונה",
"match.action.reopenMatch": "פתיחה מחדש של הקרב",
"match.action.endSet": "",
diff --git a/locales/it/tournament.json b/locales/it/tournament.json
index 4593a09d3..7b3f65085 100644
--- a/locales/it/tournament.json
+++ b/locales/it/tournament.json
@@ -119,6 +119,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "Annulla ultimo punteggio",
"match.action.reopenMatch": "Riapri match",
"match.action.endSet": "",
diff --git a/locales/ja/tournament.json b/locales/ja/tournament.json
index a04d7f67e..76121d332 100644
--- a/locales/ja/tournament.json
+++ b/locales/ja/tournament.json
@@ -113,6 +113,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "最後のスコアをやりなおす",
"match.action.reopenMatch": "対戦を再度開く",
"match.action.endSet": "",
diff --git a/locales/ko/tournament.json b/locales/ko/tournament.json
index 2f2e317b5..9e6591090 100644
--- a/locales/ko/tournament.json
+++ b/locales/ko/tournament.json
@@ -113,6 +113,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "",
"match.action.reopenMatch": "",
"match.action.endSet": "",
diff --git a/locales/nl/tournament.json b/locales/nl/tournament.json
index 20f2b79be..67479e4b8 100644
--- a/locales/nl/tournament.json
+++ b/locales/nl/tournament.json
@@ -117,6 +117,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "",
"match.action.reopenMatch": "",
"match.action.endSet": "",
diff --git a/locales/pl/tournament.json b/locales/pl/tournament.json
index 73a30b3f2..75af15a15 100644
--- a/locales/pl/tournament.json
+++ b/locales/pl/tournament.json
@@ -121,6 +121,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "",
"match.action.reopenMatch": "",
"match.action.endSet": "",
diff --git a/locales/pt-BR/tournament.json b/locales/pt-BR/tournament.json
index 4da957b12..6d2b5cc9c 100644
--- a/locales/pt-BR/tournament.json
+++ b/locales/pt-BR/tournament.json
@@ -119,6 +119,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "Desfazer última pontuação",
"match.action.reopenMatch": "Reabrir partida",
"match.action.endSet": "",
diff --git a/locales/ru/tournament.json b/locales/ru/tournament.json
index bdbb99469..bba79ad5e 100644
--- a/locales/ru/tournament.json
+++ b/locales/ru/tournament.json
@@ -121,6 +121,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "Отменить последний результат",
"match.action.reopenMatch": "Открыть матч заново",
"match.action.endSet": "",
diff --git a/locales/zh/tournament.json b/locales/zh/tournament.json
index 259b24f0d..d08158ca1 100644
--- a/locales/zh/tournament.json
+++ b/locales/zh/tournament.json
@@ -113,6 +113,9 @@
"match.activeRosterMissing.subtitle": "",
"match.leagueLocked.header": "",
"match.leagueLocked.subtitle": "",
+ "match.waitingForTeams.header": "",
+ "match.waitingForTeams.subtitle": "",
+ "match.tbd": "",
"match.action.undoLastScore": "撤销上次比分",
"match.action.reopenMatch": "重新开始对战",
"match.action.endSet": "",