From 99a06ef8040ef2336d34ca7eac399dd80b25c389 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sat, 30 May 2026 15:19:31 +0300 Subject: [PATCH] Admin tabs --- .../tournament/routes/to.$id.admin.tsx | 160 +++++++++++++----- e2e/tournament-bracket.spec.ts | 7 + e2e/tournament-staff.spec.ts | 5 +- e2e/tournament-streams.spec.ts | 3 + locales/da/tournament.json | 4 + locales/de/tournament.json | 4 + locales/en/tournament.json | 4 + locales/es-ES/tournament.json | 4 + locales/es-US/tournament.json | 4 + locales/fr-CA/tournament.json | 4 + locales/fr-EU/tournament.json | 4 + locales/he/tournament.json | 4 + locales/it/tournament.json | 4 + locales/ja/tournament.json | 4 + locales/ko/tournament.json | 4 + locales/nl/tournament.json | 4 + locales/pl/tournament.json | 4 + locales/pt-BR/tournament.json | 4 + locales/ru/tournament.json | 4 + locales/zh/tournament.json | 4 + 20 files changed, 192 insertions(+), 47 deletions(-) diff --git a/app/features/tournament/routes/to.$id.admin.tsx b/app/features/tournament/routes/to.$id.admin.tsx index 297d015c8..a0710e029 100644 --- a/app/features/tournament/routes/to.$id.admin.tsx +++ b/app/features/tournament/routes/to.$id.admin.tsx @@ -1,5 +1,5 @@ import clsx from "clsx"; -import { Trash } from "lucide-react"; +import { Trash, Trophy, Tv, UserCog, Users } from "lucide-react"; import * as React from "react"; import { useTranslation } from "react-i18next"; import { useFetcher } from "react-router"; @@ -7,6 +7,12 @@ import { Avatar } from "~/components/Avatar"; import { Divider } from "~/components/Divider"; import { LinkButton, SendouButton } from "~/components/elements/Button"; import { SendouDialog } from "~/components/elements/Dialog"; +import { + SendouTab, + SendouTabList, + SendouTabPanel, + SendouTabs, +} from "~/components/elements/Tabs"; import { UserSearch } from "~/components/elements/UserSearch"; import { FormMessage } from "~/components/FormMessage"; import { FormWithConfirm } from "~/components/FormWithConfirm"; @@ -20,6 +26,7 @@ import { useUser } from "~/features/auth/core/user"; import * as Progression from "~/features/tournament-bracket/core/Progression"; import type { TournamentData } from "~/features/tournament-bracket/core/Tournament.server"; import { USER } from "~/features/user-page/user-page-constants"; +import { useSearchParamState } from "~/hooks/useSearchParamState"; import { databaseTimestampToDate } from "~/utils/dates"; import invariant from "~/utils/invariant"; import { assertUnreachable } from "~/utils/types"; @@ -35,8 +42,10 @@ import adminStyles from "./to.$id.admin.module.css"; export { action } from "../actions/to.$id.admin.server"; +type AdminTab = "teams" | "staff" | "stream" | "brackets"; + export default function TournamentAdminPage() { - const { t } = useTranslation(["calendar"]); + const { t } = useTranslation(["tournament", "calendar"]); const tournament = useTournament(); const [editingProgression, setEditingProgression] = React.useState(false); @@ -47,6 +56,39 @@ export default function TournamentAdminPage() { setEditingProgression(false); }, [tournament]); + const showReopen = Boolean( + DANGEROUS_CAN_ACCESS_DEV_CONTROLS && + tournament.ctx.isFinalized && + tournament.isAdmin(user), + ); + const showEditBrackets = + tournament.isAdmin(user) && + tournament.hasStarted && + !tournament.ctx.isFinalized; + const showStaffTab = tournament.isAdmin(user); + const showBracketsTab = + !tournament.isLeagueSignup || showEditBrackets || showReopen; + + const isVisibleTab = (tab: string): tab is AdminTab => { + switch (tab) { + case "teams": + case "stream": + return true; + case "staff": + return showStaffTab; + case "brackets": + return showBracketsTab; + default: + return false; + } + }; + + const [tab, setTab] = useSearchParamState({ + defaultValue: "teams", + name: "tab", + revive: (value) => (isVisibleTab(value) ? value : null), + }); + if ( !tournament.isOrganizer(user) || (tournament.ctx.isFinalized && !DANGEROUS_CAN_ACCESS_DEV_CONTROLS) @@ -86,51 +128,77 @@ export default function TournamentAdminPage() { ) : null} ) : null} - {tournament.isAdmin(user) && - tournament.hasStarted && - !tournament.ctx.isFinalized ? ( -
- setEditingProgression(true)} - size="small" - variant="outlined" - data-testid="edit-event-info-button" - > - Edit brackets - - {editingProgression ? ( - setEditingProgression(false)} - /> + setTab(key as AdminTab)} + > + + }> + {t("tournament:admin.tab.teams")} + + {showStaffTab ? ( + }> + {t("tournament:admin.tab.staff")} + ) : null} -
- ) : null} - Team actions - - {tournament.isAdmin(user) ? ( - <> - Staff - - - ) : null} - Cast Twitch Accounts - - Participant list download - - {!tournament.isLeagueSignup ? ( - <> - Bracket reset - - - ) : null} - {DANGEROUS_CAN_ACCESS_DEV_CONTROLS && - tournament.ctx.isFinalized && - tournament.isAdmin(user) ? ( - <> - Reopen tournament (dev only) - - - ) : null} + }> + {t("tournament:admin.tab.stream")} + + {showBracketsTab ? ( + }> + {t("tournament:admin.tab.brackets")} + + ) : null} + + + + Participant list download + + + {showStaffTab ? ( + + + + ) : null} + + + + {showBracketsTab ? ( + + {showEditBrackets ? ( +
+ setEditingProgression(true)} + size="small" + variant="outlined" + data-testid="edit-event-info-button" + > + Edit brackets + + {editingProgression ? ( + setEditingProgression(false)} + /> + ) : null} +
+ ) : null} + {!tournament.isLeagueSignup ? ( + <> + Bracket reset + + + ) : null} + {showReopen ? ( + <> + Reopen tournament (dev only) + + + ) : null} +
+ ) : null} + ); } diff --git a/e2e/tournament-bracket.spec.ts b/e2e/tournament-bracket.spec.ts index 75011d852..45a0bd059 100644 --- a/e2e/tournament-bracket.spec.ts +++ b/e2e/tournament-bracket.spec.ts @@ -376,6 +376,7 @@ test.describe("Tournament bracket", () => { await submit(page); } + await page.getByRole("tab", { name: "Brackets" }).click(); await page.getByTestId("edit-event-info-button").click(); for (let i = 0; i < 3; i++) { await page.getByTestId("delete-bracket-button").last().click(); @@ -436,6 +437,7 @@ test.describe("Tournament bracket", () => { url: tournamentAdminPage(tournamentId), }); + await page.getByRole("tab", { name: "Brackets" }).click(); await page.getByTestId("edit-event-info-button").click(); await page.getByTestId("delete-bracket-button").last().click(); await page.getByTestId("placements-input").last().fill("3,4"); @@ -498,6 +500,7 @@ test.describe("Tournament bracket", () => { url: tournamentAdminPage(tournamentId), }); + await page.getByRole("tab", { name: "Brackets" }).click(); await page.getByTestId("edit-event-info-button").click(); await page.getByTestId("delete-bracket-button").last().click(); @@ -596,6 +599,7 @@ test.describe("Tournament bracket", () => { url: tournamentAdminPage(tournamentId), }); + await page.getByRole("tab", { name: "Brackets" }).click(); await page.getByTestId("edit-event-info-button").click(); await page.getByRole("button", { name: "Clear" }).click(); @@ -738,6 +742,7 @@ test.describe("Tournament bracket", () => { await submit(page); } + await page.getByRole("tab", { name: "Stream" }).click(); await page.getByLabel("Twitch accounts").fill("test"); await submit(page, "save-cast-twitch-accounts-button"); @@ -802,11 +807,13 @@ test.describe("Tournament bracket", () => { await reportResult(page, { mapsToReport: 2 }); await page.getByTestId("admin-tab").click(); + await page.getByRole("tab", { name: "Brackets" }).click(); await page .getByLabel('Type bracket name ("Main bracket") to confirm') .fill("Main bracket"); await submit(page, "reset-bracket-button"); + await page.getByRole("tab", { name: "Teams" }).click(); await page.getByLabel("Action").selectOption("CHECK_IN"); await page.getByLabel("Team", { exact: true }).selectOption("1"); await submit(page); diff --git a/e2e/tournament-staff.spec.ts b/e2e/tournament-staff.spec.ts index 9f89a4ee0..ba923afa6 100644 --- a/e2e/tournament-staff.spec.ts +++ b/e2e/tournament-staff.spec.ts @@ -31,6 +31,7 @@ test.describe("Tournament staff", () => { url: tournamentAdminPage(TOURNAMENT_ID), }); + await page.getByRole("tab", { name: "Staff" }).click(); await selectUser({ page, userName: "N-ZAP", @@ -65,6 +66,7 @@ test.describe("Tournament staff", () => { url: tournamentAdminPage(TOURNAMENT_ID), }); + await page.getByRole("tab", { name: "Staff" }).click(); await selectUser({ page, userName: "N-ZAP", @@ -83,7 +85,7 @@ test.describe("Tournament staff", () => { await isNotVisible(page.getByTestId("add-staff-button")); await page.getByLabel("Action").selectOption("CHECK_IN"); - await page.getByLabel("Team").selectOption("101"); + await page.getByLabel("Team", { exact: true }).selectOption("101"); await submit(page); await navigate({ @@ -118,6 +120,7 @@ test.describe("Tournament staff", () => { url: tournamentAdminPage(TOURNAMENT_ID), }); + await page.getByRole("tab", { name: "Staff" }).click(); await selectUser({ page, userName: "N-ZAP", diff --git a/e2e/tournament-streams.spec.ts b/e2e/tournament-streams.spec.ts index 1ee23f59f..3e61090f2 100644 --- a/e2e/tournament-streams.spec.ts +++ b/e2e/tournament-streams.spec.ts @@ -31,6 +31,7 @@ test.describe("Tournament streams", () => { url: tournamentAdminPage(tournamentId), }); + await page.getByRole("tab", { name: "Stream" }).click(); await page .getByLabel("Twitch accounts") .fill("test_cast_stream,another_cast"); @@ -46,6 +47,7 @@ test.describe("Tournament streams", () => { url: tournamentAdminPage(tournamentId), }); + await page.getByRole("tab", { name: "Stream" }).click(); await expect(page.getByLabel("Twitch accounts")).toHaveValue( "test_cast_stream,another_cast", ); @@ -114,6 +116,7 @@ test.describe("Tournament streams", () => { page, url: tournamentAdminPage(tournamentId), }); + await page.getByRole("tab", { name: "Stream" }).click(); await page.getByLabel("Twitch accounts").fill("test_cast_stream"); await submit(page, "save-cast-twitch-accounts-button"); diff --git a/locales/da/tournament.json b/locales/da/tournament.json index faab77bea..8c4b7161e 100644 --- a/locales/da/tournament.json +++ b/locales/da/tournament.json @@ -76,6 +76,10 @@ "generator.error": "Ændringer, som du har lavet, blev ikke gemt, da turneringen nu er begyndt. ", "teams.mapsPickedStatus": "Status for banevalg", "admin.download": "Hent liste over deltagere", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "Ændr holdkaptajn", "admin.actions.CHANGE_TEAM_NAME": "Ændr holdnavn", "admin.actions.CHECK_IN": "Tjek ind", diff --git a/locales/de/tournament.json b/locales/de/tournament.json index befe72a12..1cae8380d 100644 --- a/locales/de/tournament.json +++ b/locales/de/tournament.json @@ -76,6 +76,10 @@ "generator.error": "Die Änderungen wurden nicht gespeichert, weil das Turnier bereits begonnen hat", "teams.mapsPickedStatus": "Status ausgewählter Arenen", "admin.download": "Teilnehmerliste herunterladen", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "Kapitän ändern", "admin.actions.CHANGE_TEAM_NAME": "Teamnamen ändern", "admin.actions.CHECK_IN": "Einchecken", diff --git a/locales/en/tournament.json b/locales/en/tournament.json index 0f322f105..83d57d179 100644 --- a/locales/en/tournament.json +++ b/locales/en/tournament.json @@ -76,6 +76,10 @@ "generator.error": "Changes you made weren't saved since the tournament started", "teams.mapsPickedStatus": "Maps picked status", "admin.download": "Download participants", + "admin.tab.teams": "Teams", + "admin.tab.staff": "Staff", + "admin.tab.stream": "Stream", + "admin.tab.brackets": "Brackets", "admin.actions.CHANGE_TEAM_OWNER": "Change captain", "admin.actions.CHANGE_TEAM_NAME": "Change team name", "admin.actions.CHECK_IN": "Check in", diff --git a/locales/es-ES/tournament.json b/locales/es-ES/tournament.json index 79a9fb183..dd4da6c16 100644 --- a/locales/es-ES/tournament.json +++ b/locales/es-ES/tournament.json @@ -77,6 +77,10 @@ "generator.error": "Los cambios no fueron guardados ya que el torneo comenzó", "teams.mapsPickedStatus": "Estado de mapas elegidos", "admin.download": "Descarga participantes", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "Cambiar capitán", "admin.actions.CHANGE_TEAM_NAME": "Cambiar nombre de equipo", "admin.actions.CHECK_IN": "Check in", diff --git a/locales/es-US/tournament.json b/locales/es-US/tournament.json index 358ee632a..ce7ca09ec 100644 --- a/locales/es-US/tournament.json +++ b/locales/es-US/tournament.json @@ -77,6 +77,10 @@ "generator.error": "Cambios no fueron guardados ya que el torneo comenzó", "teams.mapsPickedStatus": "Estado de escenarios elegidos", "admin.download": "Descarga participantes", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "Cambiar capitán", "admin.actions.CHANGE_TEAM_NAME": "Cambiar nombre de equipo", "admin.actions.CHECK_IN": "Check in", diff --git a/locales/fr-CA/tournament.json b/locales/fr-CA/tournament.json index 6a11fdaf5..780fa3ede 100644 --- a/locales/fr-CA/tournament.json +++ b/locales/fr-CA/tournament.json @@ -77,6 +77,10 @@ "generator.error": "Les modifications apportées n'ont pas été enregistrées depuis le début du tournoi", "teams.mapsPickedStatus": "Statut des stages sélectionnés", "admin.download": "Télécharger les participants", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "Changer le capitaine", "admin.actions.CHANGE_TEAM_NAME": "", "admin.actions.CHECK_IN": "S'enregister", diff --git a/locales/fr-EU/tournament.json b/locales/fr-EU/tournament.json index b847efde9..c8f6566f4 100644 --- a/locales/fr-EU/tournament.json +++ b/locales/fr-EU/tournament.json @@ -77,6 +77,10 @@ "generator.error": "Les modifications apportées n'ont pas été enregistrées depuis le début du tournoi", "teams.mapsPickedStatus": "Statut des stages sélectionnés", "admin.download": "Télécharger les participants", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "Changer le capitaine", "admin.actions.CHANGE_TEAM_NAME": "Changer le nom de l'équipe ", "admin.actions.CHECK_IN": "S'enregister", diff --git a/locales/he/tournament.json b/locales/he/tournament.json index 41ebe9030..0ae0d9415 100644 --- a/locales/he/tournament.json +++ b/locales/he/tournament.json @@ -77,6 +77,10 @@ "generator.error": "שינויים שביצעת לא נשמרו מאז תחילת הטורניר", "teams.mapsPickedStatus": "סטטוס בחירת המפות", "admin.download": "הורדת משתתפים", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "שינוי קפטן", "admin.actions.CHANGE_TEAM_NAME": "", "admin.actions.CHECK_IN": "קבלה", diff --git a/locales/it/tournament.json b/locales/it/tournament.json index 33ce3f02a..ab19cbfba 100644 --- a/locales/it/tournament.json +++ b/locales/it/tournament.json @@ -77,6 +77,10 @@ "generator.error": "I cambiamenti che hai fatto non sono stati salvati visto che il torneo è già cominciato.", "teams.mapsPickedStatus": "Stato scenari selezionati", "admin.download": "Download partecipanti", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "Cambia capitano", "admin.actions.CHANGE_TEAM_NAME": "Cambia nome team", "admin.actions.CHECK_IN": "Check-in", diff --git a/locales/ja/tournament.json b/locales/ja/tournament.json index 39a9adb1d..9494b4de9 100644 --- a/locales/ja/tournament.json +++ b/locales/ja/tournament.json @@ -74,6 +74,10 @@ "generator.error": "トーナメントが開始済みなので変更は保存されませんでした。", "teams.mapsPickedStatus": "マップの選択状況", "admin.download": "参加者リストをダウンロード", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "キャプテンを変更", "admin.actions.CHANGE_TEAM_NAME": "チーム名変更", "admin.actions.CHECK_IN": "チェックイン", diff --git a/locales/ko/tournament.json b/locales/ko/tournament.json index f0210f163..b135b8ef8 100644 --- a/locales/ko/tournament.json +++ b/locales/ko/tournament.json @@ -74,6 +74,10 @@ "generator.error": "", "teams.mapsPickedStatus": "", "admin.download": "", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "", "admin.actions.CHANGE_TEAM_NAME": "", "admin.actions.CHECK_IN": "", diff --git a/locales/nl/tournament.json b/locales/nl/tournament.json index 9becc6d6f..59da297ad 100644 --- a/locales/nl/tournament.json +++ b/locales/nl/tournament.json @@ -76,6 +76,10 @@ "generator.error": "", "teams.mapsPickedStatus": "", "admin.download": "", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "", "admin.actions.CHANGE_TEAM_NAME": "", "admin.actions.CHECK_IN": "", diff --git a/locales/pl/tournament.json b/locales/pl/tournament.json index 2d02cca11..860b59cdf 100644 --- a/locales/pl/tournament.json +++ b/locales/pl/tournament.json @@ -78,6 +78,10 @@ "generator.error": "Zmiany nie zostały zapisane z powodu rozpoczęcia turnieju", "teams.mapsPickedStatus": "Status wybranych map", "admin.download": "Zapisz uczestników", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "", "admin.actions.CHANGE_TEAM_NAME": "", "admin.actions.CHECK_IN": "", diff --git a/locales/pt-BR/tournament.json b/locales/pt-BR/tournament.json index d9d4e1af8..aa79f4980 100644 --- a/locales/pt-BR/tournament.json +++ b/locales/pt-BR/tournament.json @@ -77,6 +77,10 @@ "generator.error": "Mudanças que você fez não foram salvas uma vez que o torneio já começou", "teams.mapsPickedStatus": "Status dos mapas escolhidos", "admin.download": "Download dos participantes", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "Mudar capitão", "admin.actions.CHANGE_TEAM_NAME": "Mudar nome do time", "admin.actions.CHECK_IN": "Check-in", diff --git a/locales/ru/tournament.json b/locales/ru/tournament.json index 15833e6cf..136539f99 100644 --- a/locales/ru/tournament.json +++ b/locales/ru/tournament.json @@ -78,6 +78,10 @@ "generator.error": "Ваши изменения не были сохранены так как турнир начался", "teams.mapsPickedStatus": "Статус выбранных карт", "admin.download": "Скачать участников", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "Изменить капитана", "admin.actions.CHANGE_TEAM_NAME": "Изменить имя команды", "admin.actions.CHECK_IN": "Зарегистрировать", diff --git a/locales/zh/tournament.json b/locales/zh/tournament.json index 32e90e0bf..2dc34acb3 100644 --- a/locales/zh/tournament.json +++ b/locales/zh/tournament.json @@ -74,6 +74,10 @@ "generator.error": "由于比赛已经开始,您所更改的内容无法保存", "teams.mapsPickedStatus": "地图的选择情况", "admin.download": "下载参赛者", + "admin.tab.teams": "", + "admin.tab.staff": "", + "admin.tab.stream": "", + "admin.tab.brackets": "", "admin.actions.CHANGE_TEAM_OWNER": "变更队长", "admin.actions.CHANGE_TEAM_NAME": "变更队名", "admin.actions.CHECK_IN": "登入",