From 072fdca64131f272d562b0a35563f488258170e5 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Fri, 1 May 2026 17:08:24 +0300 Subject: [PATCH] Replace migrate group solution --- .../match-page/MatchActionPickBanTab.tsx | 1 - .../components/RematchVotePanel.tsx | 30 ++++++++++++++----- .../components/SendouQMatchActionTab.tsx | 10 +++++-- .../components/SendouQMatchTabs.tsx | 17 ++++------- .../loaders/q.match.$id.server.ts | 10 ------- app/features/sendouq/q-constants.ts | 2 +- .../loaders/u.$identifier.seasons.server.ts | 5 +++- locales/da/q.json | 2 ++ locales/de/q.json | 2 ++ locales/en/q.json | 2 ++ locales/es-ES/q.json | 2 ++ locales/es-US/q.json | 2 ++ locales/fr-CA/q.json | 2 ++ locales/fr-EU/q.json | 2 ++ locales/he/q.json | 2 ++ locales/it/q.json | 2 ++ locales/ja/q.json | 2 ++ locales/ko/q.json | 2 ++ locales/nl/q.json | 2 ++ locales/pl/q.json | 2 ++ locales/pt-BR/q.json | 2 ++ locales/ru/q.json | 2 ++ locales/zh/q.json | 2 ++ 23 files changed, 72 insertions(+), 35 deletions(-) diff --git a/app/components/match-page/MatchActionPickBanTab.tsx b/app/components/match-page/MatchActionPickBanTab.tsx index 8bc49883c..32f1116ce 100644 --- a/app/components/match-page/MatchActionPickBanTab.tsx +++ b/app/components/match-page/MatchActionPickBanTab.tsx @@ -266,7 +266,6 @@ function ModeOnlyGrid({ ); } -// xxx: maybe we should just have a shared custom component for stage image + label function StageTile({ option, type, diff --git a/app/features/sendouq-match/components/RematchVotePanel.tsx b/app/features/sendouq-match/components/RematchVotePanel.tsx index 88c042774..2358a7e4f 100644 --- a/app/features/sendouq-match/components/RematchVotePanel.tsx +++ b/app/features/sendouq-match/components/RematchVotePanel.tsx @@ -1,9 +1,10 @@ -import { Check, Clock, X } from "lucide-react"; +import { Check, Clock, RotateCcw, X } from "lucide-react"; import { useTranslation } from "react-i18next"; -import type { FetcherWithComponents } from "react-router"; +import { type FetcherWithComponents, Link } from "react-router"; import { Avatar } from "~/components/Avatar"; import { SendouButton } from "~/components/elements/Button"; import { FormWithConfirm } from "~/components/FormWithConfirm"; +import { SENDOUQ_LOOKING_PAGE } from "~/utils/urls"; import * as RejoinVote from "../core/RejoinVote"; import styles from "./RematchVotePanel.module.css"; @@ -37,10 +38,18 @@ export function RematchVotePanel({ members.map((m) => m.id), ).length; + const voteResolved = RejoinVote.result(votes).type === "RESOLVED"; + const viewerVotedYes = + RejoinVote.userContinueStatus(votes, viewerUserId) === true; + const viewerVotedNo = + RejoinVote.userContinueStatus(votes, viewerUserId) === false; + return (
- {t("q:match.rematch.prompt", { count: currentRoundSize })} + {voteResolved + ? t("q:match.rematch.resolved", { count: currentRoundSize }) + : t("q:match.rematch.prompt", { count: currentRoundSize })}
- {RejoinVote.userContinueStatus(votes, viewerUserId) === false ? null : ( + {voteResolved && viewerVotedYes ? ( +
+ + }> + {t("q:match.rematch.backToQueue")} + + +
+ ) : viewerVotedNo ? null : (
fetcher.submit( { diff --git a/app/features/sendouq-match/components/SendouQMatchActionTab.tsx b/app/features/sendouq-match/components/SendouQMatchActionTab.tsx index 37b0efca6..0aaba68d0 100644 --- a/app/features/sendouq-match/components/SendouQMatchActionTab.tsx +++ b/app/features/sendouq-match/components/SendouQMatchActionTab.tsx @@ -210,10 +210,14 @@ function RequeueTab({ reporterSide !== null && reporterSide !== viewerSide; + const showTimeline = !data.match.isLocked; + return ( {isStaffOnly || !viewerGroup || !user ? ( - + showTimeline ? ( + + ) : null ) : (
{viewerGroup.matchmade ? ( @@ -234,7 +238,9 @@ function RequeueTab({ ) : null} {isOnReporterTeam ?
: null} - + {showTimeline ? ( + + ) : null} {isOnConfirmerTeam ? : null} {isOnReporterTeam ? : null} diff --git a/app/features/sendouq-match/components/SendouQMatchTabs.tsx b/app/features/sendouq-match/components/SendouQMatchTabs.tsx index da29bd057..954572eee 100644 --- a/app/features/sendouq-match/components/SendouQMatchTabs.tsx +++ b/app/features/sendouq-match/components/SendouQMatchTabs.tsx @@ -4,18 +4,17 @@ import { MatchJoinTab } from "~/components/match-page/MatchJoinTab"; import { MatchResultTab } from "~/components/match-page/MatchResultTab"; import { MatchRosterTab } from "~/components/match-page/MatchRosterTab"; import { MatchTabs } from "~/components/match-page/MatchTabs"; -import { Redirect } from "~/components/Redirect"; import { useUser } from "~/features/auth/core/user"; import { resolveActiveRoomLink, useConfirmRoom, } from "~/features/chat/room-link-utils"; -import { DISPLAY_VOTE_RESULT_SECONDS } from "~/features/sendouq/q-constants"; +import { ACTION_TAB_AFTER_LOCKED_SECONDS } from "~/features/sendouq/q-constants"; import { resolveRoomPass } from "~/features/tournament-match/tournament-match-utils"; import { useHasRole } from "~/modules/permissions/hooks"; import { databaseTimestampNow } from "~/utils/dates"; import { safeNumberParse } from "~/utils/number"; -import { SENDOUQ_LOOKING_PAGE, sendouQMatchPage, teamPage } from "~/utils/urls"; +import { sendouQMatchPage, teamPage } from "~/utils/urls"; import { resolveTimelineMaps, resolveTimelineSpChanges, @@ -57,17 +56,11 @@ export function SendouQMatchTabs({ data }: { data: SendouQMatchLoaderData }) { const isCanceled = data.match.isCanceled; const isParticipant = Boolean(userSide); - const migrated = data.migratedToGroupId != null && isParticipant; - // xxx: hmm is this really correct? - if (migrated) { - return ; - } - - const lockedVoteVisible = + const lockedActionTabVisible = data.match.confirmedAt !== null && databaseTimestampNow() < - data.match.confirmedAt + DISPLAY_VOTE_RESULT_SECONDS; + data.match.confirmedAt + ACTION_TAB_AFTER_LOCKED_SECONDS; const matchInProgress = !isLocked && !awaitingConfirmation && currentMap; @@ -76,7 +69,7 @@ export function SendouQMatchTabs({ data }: { data: SendouQMatchLoaderData }) { !isCanceled && (matchInProgress || awaitingConfirmation || - (isLocked && lockedVoteVisible)); + (isLocked && lockedActionTabVisible)); const hasReportedMaps = data.match.mapList.some( (m) => m.winnerGroupId !== null, diff --git a/app/features/sendouq-match/loaders/q.match.$id.server.ts b/app/features/sendouq-match/loaders/q.match.$id.server.ts index f14cd15c2..afef68ca7 100644 --- a/app/features/sendouq-match/loaders/q.match.$id.server.ts +++ b/app/features/sendouq-match/loaders/q.match.$id.server.ts @@ -40,18 +40,8 @@ export const loader = async ({ params }: LoaderFunctionArgs) => { const match = SendouQ.mapMatch(matchUnmapped, user, privateNotes); - const ownCurrentGroup = user ? SendouQ.findOwnGroup(user.id) : undefined; - const migratedToGroupId = - ownCurrentGroup && - ownCurrentGroup.id !== match.groupAlpha.id && - ownCurrentGroup.id !== match.groupBravo.id - ? ownCurrentGroup.id - : null; - return { match, - // xxx: i don't think this is the correct idea - migratedToGroupId, roomLinks, anyUserPrefersNoSplatnet, reportedWeapons, diff --git a/app/features/sendouq/q-constants.ts b/app/features/sendouq/q-constants.ts index 06d9c936f..da39334e0 100644 --- a/app/features/sendouq/q-constants.ts +++ b/app/features/sendouq/q-constants.ts @@ -14,7 +14,7 @@ export const FULL_GROUP_SIZE = 4; export const SENDOUQ_BEST_OF = 7; -export const DISPLAY_VOTE_RESULT_SECONDS = 3 * 60 * 60; +export const ACTION_TAB_AFTER_LOCKED_SECONDS = 24 * 60 * 60; // 24 hours export const JOIN_CODE_SEARCH_PARAM_KEY = "join"; diff --git a/app/features/user-page/loaders/u.$identifier.seasons.server.ts b/app/features/user-page/loaders/u.$identifier.seasons.server.ts index 1262dae74..30f5af0d4 100644 --- a/app/features/user-page/loaders/u.$identifier.seasons.server.ts +++ b/app/features/user-page/loaders/u.$identifier.seasons.server.ts @@ -91,7 +91,10 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => { : null, weapons: info === "weapons" - ? await ReportedWeaponRepository.seasonReportedWeaponsByUserId({ season, userId: user.id }) + ? await ReportedWeaponRepository.seasonReportedWeaponsByUserId({ + season, + userId: user.id, + }) : null, players: info === "enemies" || info === "mates" diff --git a/locales/da/q.json b/locales/da/q.json index 6b21aa8a3..e6bb3f1eb 100644 --- a/locales/da/q.json +++ b/locales/da/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "", "tiers.currentCriteria": "", "tiers.info.p1": "", diff --git a/locales/de/q.json b/locales/de/q.json index 8e10b6c3a..6ea07e582 100644 --- a/locales/de/q.json +++ b/locales/de/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "", "tiers.currentCriteria": "", "tiers.info.p1": "", diff --git a/locales/en/q.json b/locales/en/q.json index a4906bf5e..7dfd2929a 100644 --- a/locales/en/q.json +++ b/locales/en/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "Confirm score", "match.confirmScore.wrongHint": "Wrong score? Ask the other team to undo their report and adjust it.", "match.rematch.prompt": "Continue queueing with the group of {{count}}?", + "match.rematch.resolved": "New group of {{count}} formed", "match.rematch.vote.yes": "Yes, continue", "match.rematch.vote.no": "No, I'm done", "match.rematch.vote.noConfirm": "Vote no? You can't change your vote afterwards.", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "Nobody wanted to continue", "match.rematch.waitingCaptain": "Waiting for the captain to choose whether to re-queue", "match.rematch.rejoinQueue": "Rejoin queue", + "match.rematch.backToQueue": "Back to queue", "preparing.joinQ": "Join the queue", "tiers.currentCriteria": "Current criteria", "tiers.info.p1": "For example, Leviathan is the top 5% of players. Diamond is the 85th percentile etc.", diff --git a/locales/es-ES/q.json b/locales/es-ES/q.json index b90da20fd..302f39287 100644 --- a/locales/es-ES/q.json +++ b/locales/es-ES/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "Unirte a la fila", "tiers.currentCriteria": "Criterios actuales", "tiers.info.p1": "Por ejemplo, Leviathan se encuentra entre el 5% de los mejores jugadores. Diamond es el percentil 85, etc.", diff --git a/locales/es-US/q.json b/locales/es-US/q.json index cbaef239a..65db3340a 100644 --- a/locales/es-US/q.json +++ b/locales/es-US/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "Unirte a la fila", "tiers.currentCriteria": "Criterios actuales", "tiers.info.p1": "Por ejemplo, Leviathan se encuentra entre el 5% de los mejores jugadores. Diamond es el percentil 85, etc.", diff --git a/locales/fr-CA/q.json b/locales/fr-CA/q.json index 35ac4b796..74b69f994 100644 --- a/locales/fr-CA/q.json +++ b/locales/fr-CA/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "", "tiers.currentCriteria": "", "tiers.info.p1": "", diff --git a/locales/fr-EU/q.json b/locales/fr-EU/q.json index a82599275..0b13be931 100644 --- a/locales/fr-EU/q.json +++ b/locales/fr-EU/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "Rejoindre la queue", "tiers.currentCriteria": "Critères actuels", "tiers.info.p1": "Par exemple, Les Léviathans font partie des 5 % des meilleurs joueurs. Le diamant est le top 15%, etc.", diff --git a/locales/he/q.json b/locales/he/q.json index 3b52b9dff..8d30d099d 100644 --- a/locales/he/q.json +++ b/locales/he/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "", "tiers.currentCriteria": "", "tiers.info.p1": "", diff --git a/locales/it/q.json b/locales/it/q.json index baeb32b15..895fa4a64 100644 --- a/locales/it/q.json +++ b/locales/it/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "Unisciti alla coda", "tiers.currentCriteria": "Criterio corrente", "tiers.info.p1": "Per esempio Leviathan è la top 5% dei giocatori. Diamante è l' 85esimo percentile etc.", diff --git a/locales/ja/q.json b/locales/ja/q.json index 375fa1ecc..836971da4 100644 --- a/locales/ja/q.json +++ b/locales/ja/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "列に入る", "tiers.currentCriteria": "現在の基準", "tiers.info.p1": "例として、Leviathanはプレイヤーの上位5%、Diamondは上位15%", diff --git a/locales/ko/q.json b/locales/ko/q.json index 8e10b6c3a..6ea07e582 100644 --- a/locales/ko/q.json +++ b/locales/ko/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "", "tiers.currentCriteria": "", "tiers.info.p1": "", diff --git a/locales/nl/q.json b/locales/nl/q.json index 8e10b6c3a..6ea07e582 100644 --- a/locales/nl/q.json +++ b/locales/nl/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "", "tiers.currentCriteria": "", "tiers.info.p1": "", diff --git a/locales/pl/q.json b/locales/pl/q.json index 8e10b6c3a..6ea07e582 100644 --- a/locales/pl/q.json +++ b/locales/pl/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "", "tiers.currentCriteria": "", "tiers.info.p1": "", diff --git a/locales/pt-BR/q.json b/locales/pt-BR/q.json index 8884bb8a6..fbb62f292 100644 --- a/locales/pt-BR/q.json +++ b/locales/pt-BR/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "Entrar na fila", "tiers.currentCriteria": "Critérios atuais", "tiers.info.p1": "Por exemplo, Leviathan é o top 5% dos jogadores. Diamond é top 15% e etc.", diff --git a/locales/ru/q.json b/locales/ru/q.json index 23262c10f..ede75207b 100644 --- a/locales/ru/q.json +++ b/locales/ru/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "Присоединиться к очереди", "tiers.currentCriteria": "Текущие критерии", "tiers.info.p1": "Например, Leviathan - топ 5% игроков, Diamond - 85 процентиль и т.д.", diff --git a/locales/zh/q.json b/locales/zh/q.json index 48c4877d9..f55f486a7 100644 --- a/locales/zh/q.json +++ b/locales/zh/q.json @@ -210,6 +210,7 @@ "match.confirmScore": "", "match.confirmScore.wrongHint": "", "match.rematch.prompt": "", + "match.rematch.resolved": "", "match.rematch.vote.yes": "", "match.rematch.vote.no": "", "match.rematch.vote.noConfirm": "", @@ -217,6 +218,7 @@ "match.rematch.fizzled": "", "match.rematch.waitingCaptain": "", "match.rematch.rejoinQueue": "", + "match.rematch.backToQueue": "", "preparing.joinQ": "开始匹配", "tiers.currentCriteria": "当前规则", "tiers.info.p1": "比如说,Leviathan是前5%的玩家,Diamond是前15%的玩家。",