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": "",