mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-01 07:37:55 -05:00
Default to rosters tab on match page + show pool/qr code indicator
Closes #3053
This commit is contained in:
@@ -48,8 +48,7 @@
|
||||
.notice {
|
||||
position: absolute;
|
||||
bottom: var(--s-2);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
right: var(--s-2);
|
||||
display: flex;
|
||||
gap: var(--s-0-5);
|
||||
align-items: center;
|
||||
@@ -62,6 +61,23 @@
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.joinBadge {
|
||||
position: absolute;
|
||||
bottom: var(--s-2);
|
||||
left: var(--s-2);
|
||||
display: flex;
|
||||
gap: var(--s-1);
|
||||
align-items: center;
|
||||
color: var(--color-text-high);
|
||||
background-color: var(--color-bg-high);
|
||||
padding: var(--s-0-5) var(--s-1-5);
|
||||
border-radius: var(--radius-field);
|
||||
font-size: var(--font-2xs);
|
||||
font-weight: var(--weight-semi);
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.infoBadge {
|
||||
display: flex;
|
||||
gap: var(--s-1-5);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import clsx from "clsx";
|
||||
import { Check, X } from "lucide-react";
|
||||
import { Check, QrCode, X } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSearchParams } from "react-router";
|
||||
import { SendouButton } from "~/components/elements/Button";
|
||||
import { SendouPopover } from "~/components/elements/Popover";
|
||||
import type { ModeShort, StageId } from "~/modules/in-game-lists/types";
|
||||
@@ -20,6 +21,8 @@ interface MatchBannerProps {
|
||||
stageId: StageId;
|
||||
mode: ModeShort;
|
||||
screenLegal?: boolean;
|
||||
joinPool?: string | null;
|
||||
joinViaQr?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -27,6 +30,8 @@ export function MatchBanner({
|
||||
stageId,
|
||||
mode,
|
||||
screenLegal,
|
||||
joinPool,
|
||||
joinViaQr,
|
||||
children,
|
||||
}: MatchBannerProps) {
|
||||
const { t } = useTranslation(["game-misc"]);
|
||||
@@ -45,6 +50,7 @@ export function MatchBanner({
|
||||
</div>
|
||||
<div className={clsx(styles.info, styles.thickText)}>{children}</div>
|
||||
|
||||
{joinPool ? <JoinPoolBadge pool={joinPool} viaQr={joinViaQr} /> : null}
|
||||
{screenLegal !== undefined ? (
|
||||
<ScreenNotice screenLegal={screenLegal} />
|
||||
) : null}
|
||||
@@ -75,6 +81,8 @@ interface IconBannerProps {
|
||||
header: string;
|
||||
subtitle?: string;
|
||||
screenLegal?: boolean;
|
||||
joinPool?: string | null;
|
||||
joinViaQr?: boolean;
|
||||
topRight?: React.ReactNode;
|
||||
testId?: string;
|
||||
}
|
||||
@@ -84,6 +92,8 @@ export function IconBanner({
|
||||
header,
|
||||
subtitle,
|
||||
screenLegal,
|
||||
joinPool,
|
||||
joinViaQr,
|
||||
topRight,
|
||||
testId,
|
||||
}: IconBannerProps) {
|
||||
@@ -94,6 +104,7 @@ export function IconBanner({
|
||||
{subtitle ? (
|
||||
<div className={styles.iconBannerSubtitle}>{subtitle}</div>
|
||||
) : null}
|
||||
{joinPool ? <JoinPoolBadge pool={joinPool} viaQr={joinViaQr} /> : null}
|
||||
{screenLegal !== undefined ? (
|
||||
<ScreenNotice screenLegal={screenLegal} />
|
||||
) : null}
|
||||
@@ -104,6 +115,31 @@ export function IconBanner({
|
||||
);
|
||||
}
|
||||
|
||||
function JoinPoolBadge({ pool, viaQr }: { pool: string; viaQr?: boolean }) {
|
||||
const { t } = useTranslation(["q"]);
|
||||
const [, setSearchParams] = useSearchParams();
|
||||
|
||||
return (
|
||||
<SendouButton
|
||||
variant="minimal"
|
||||
className={styles.joinBadge}
|
||||
onPress={() =>
|
||||
setSearchParams(
|
||||
{ tab: "join" },
|
||||
{
|
||||
preventScrollReset: true,
|
||||
defaultShouldRevalidate: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
aria-label={t("q:match.pool")}
|
||||
testId="join-pool-badge"
|
||||
>
|
||||
{viaQr ? <QrCode size={18} /> : pool}
|
||||
</SendouButton>
|
||||
);
|
||||
}
|
||||
|
||||
function ScreenNotice({ screenLegal }: { screenLegal: boolean }) {
|
||||
const { t } = useTranslation(["weapons", "q"]);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { sub } from "date-fns";
|
||||
import { Ban, Swords } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useLoaderData } from "react-router";
|
||||
@@ -6,16 +7,22 @@ import {
|
||||
IconBanner,
|
||||
MatchBannerContainer,
|
||||
} from "~/components/match-page/MatchBanner";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import { resolveActiveRoomLink } from "~/features/chat/room-link-utils";
|
||||
import { MapPool } from "~/features/map-list-generator/core/map-pool";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import { logger } from "~/utils/logger";
|
||||
import type { SerializeFrom } from "~/utils/remix";
|
||||
import { mapsPageWithMapPool, navIconUrl } from "~/utils/urls";
|
||||
import * as Scrim from "../core/Scrim";
|
||||
import type { loader } from "../loaders/scrims.$id.server";
|
||||
import { SCRIM } from "../scrims-constants";
|
||||
import type { ScrimPost } from "../scrims-types";
|
||||
|
||||
export function ScrimMatchBanner() {
|
||||
const { t } = useTranslation(["scrims"]);
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const user = useUser();
|
||||
|
||||
const screenLegal = !data.anyUserPrefersNoScreen;
|
||||
|
||||
@@ -37,6 +44,17 @@ export function ScrimMatchBanner() {
|
||||
|
||||
const hasMaps = data.post.maps || data.tournamentMapPool;
|
||||
|
||||
const acceptedRequest = data.post.requests[0];
|
||||
const activeRoomLink = resolveActiveRoomLink({
|
||||
roomLinks: data.roomLinks,
|
||||
freshnessCutoff: dateToDatabaseTimestamp(
|
||||
sub(new Date(), { minutes: SCRIM.ROOM_LINK_FRESHNESS_MINUTES }),
|
||||
),
|
||||
viewerUserId: user?.id,
|
||||
members: [...data.post.users, ...acceptedRequest.users],
|
||||
});
|
||||
const joinViaQr = Boolean(activeRoomLink.joinLink) && !activeRoomLink.isStale;
|
||||
|
||||
return (
|
||||
<MatchBannerContainer>
|
||||
<IconBanner
|
||||
@@ -44,6 +62,8 @@ export function ScrimMatchBanner() {
|
||||
header={t("scrims:banner.freeForm.header")}
|
||||
subtitle={t("scrims:banner.freeForm.subtitle")}
|
||||
screenLegal={screenLegal}
|
||||
joinPool={Scrim.resolvePoolCode(data.post.id)}
|
||||
joinViaQr={joinViaQr}
|
||||
topRight={
|
||||
hasMaps ? (
|
||||
<MapsLink
|
||||
|
||||
@@ -14,10 +14,9 @@ import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import { teamPage } from "~/utils/urls";
|
||||
import * as Scrim from "../core/Scrim";
|
||||
import type { loader } from "../loaders/scrims.$id.server";
|
||||
import { SCRIM } from "../scrims-constants";
|
||||
import type { ScrimPost } from "../scrims-types";
|
||||
|
||||
const SCRIM_ROOM_LINK_FRESHNESS_MINUTES = 30;
|
||||
|
||||
export function ScrimMatchTabs() {
|
||||
const { t } = useTranslation(["q"]);
|
||||
const user = useUser();
|
||||
@@ -30,14 +29,14 @@ export function ScrimMatchTabs() {
|
||||
const activeRoomLink = resolveActiveRoomLink({
|
||||
roomLinks: data.roomLinks,
|
||||
freshnessCutoff: dateToDatabaseTimestamp(
|
||||
sub(new Date(), { minutes: SCRIM_ROOM_LINK_FRESHNESS_MINUTES }),
|
||||
sub(new Date(), { minutes: SCRIM.ROOM_LINK_FRESHNESS_MINUTES }),
|
||||
),
|
||||
viewerUserId: user?.id,
|
||||
members: allMembers,
|
||||
});
|
||||
|
||||
return (
|
||||
<MatchTabs tabs={[TAB_KEYS.JOIN, TAB_KEYS.ROSTERS]}>
|
||||
<MatchTabs tabs={[TAB_KEYS.ROSTERS, TAB_KEYS.JOIN]}>
|
||||
<MatchJoinTab
|
||||
{...activeRoomLink}
|
||||
onConfirmRoom={onConfirmRoom}
|
||||
|
||||
@@ -19,4 +19,5 @@ export const SCRIM = {
|
||||
CANCEL_REASON_MAX_LENGTH: 500,
|
||||
REQUEST_MESSAGE_MAX_LENGTH: 200,
|
||||
MAX_TIME_RANGE_MS: 3 * 60 * 60 * 1000, // 3 hours
|
||||
ROOM_LINK_FRESHNESS_MINUTES: 30,
|
||||
};
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
import bannerStyles from "~/components/match-page/MatchBanner.module.css";
|
||||
import { MatchBannerBottomRow } from "~/components/match-page/MatchBannerBottomRow";
|
||||
import { MatchBannerTopRow } from "~/components/match-page/MatchBannerTopRow";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import { resolveActiveRoomLink } from "~/features/chat/room-link-utils";
|
||||
import { SENDOUQ_BEST_OF } from "~/features/sendouq/q-constants";
|
||||
import { useAutoRerender } from "~/hooks/useAutoRerender";
|
||||
import { databaseTimestampToDate } from "~/utils/dates";
|
||||
@@ -23,6 +25,7 @@ import type { SendouQMatchLoaderData } from "../loaders/q.match.$id.server";
|
||||
|
||||
export function SendouQMatchBanner({ data }: { data: SendouQMatchLoaderData }) {
|
||||
const { t } = useTranslation(["q"]);
|
||||
const user = useUser();
|
||||
|
||||
const cancelRequested = Boolean(data.match.cancelRequestedByUserId);
|
||||
const cancelRequesterSide = SendouQMatch.resolveGroupMemberOf({
|
||||
@@ -82,6 +85,18 @@ export function SendouQMatchBanner({ data }: { data: SendouQMatchLoaderData }) {
|
||||
const currentMap = data.match.currentMap;
|
||||
invariant(currentMap);
|
||||
|
||||
const joinPool = `SQ${String(data.match.id).at(-1)}`;
|
||||
const activeRoomLink = resolveActiveRoomLink({
|
||||
roomLinks: data.roomLinks,
|
||||
freshnessCutoff: data.match.createdAt,
|
||||
viewerUserId: user?.id,
|
||||
members: [
|
||||
...data.match.groupAlpha.members,
|
||||
...data.match.groupBravo.members,
|
||||
],
|
||||
});
|
||||
const joinViaQr = Boolean(activeRoomLink.joinLink) && !activeRoomLink.isStale;
|
||||
|
||||
return (
|
||||
<MatchBannerContainer>
|
||||
<SendouQMatchBannerTopRow data={data} awaitingConfirmation={false} />
|
||||
@@ -92,6 +107,8 @@ export function SendouQMatchBanner({ data }: { data: SendouQMatchLoaderData }) {
|
||||
subtitle={t("q:match.cancelRequested.subtitle", {
|
||||
teamName: cancelRequesterName,
|
||||
})}
|
||||
joinPool={joinPool}
|
||||
joinViaQr={joinViaQr}
|
||||
/>
|
||||
) : (
|
||||
<MatchBanner
|
||||
@@ -100,6 +117,8 @@ export function SendouQMatchBanner({ data }: { data: SendouQMatchLoaderData }) {
|
||||
screenLegal={
|
||||
!data.match.groupAlpha.noScreen && !data.match.groupBravo.noScreen
|
||||
}
|
||||
joinPool={joinPool}
|
||||
joinViaQr={joinViaQr}
|
||||
>
|
||||
<CurrentMapVotesBadge voters={currentMap.voters} />
|
||||
</MatchBanner>
|
||||
|
||||
@@ -75,15 +75,10 @@ export function SendouQMatchTabs({ data }: { data: SendouQMatchLoaderData }) {
|
||||
(m) => m.winnerGroupId !== null,
|
||||
);
|
||||
|
||||
const tabs: Array<"join" | "rosters" | "action" | "result"> = [];
|
||||
if (isLocked) {
|
||||
tabs.push("result", "rosters");
|
||||
} else {
|
||||
if (isParticipant) tabs.push("join");
|
||||
tabs.push("rosters");
|
||||
}
|
||||
const tabs: Array<"join" | "rosters" | "action" | "result"> = ["rosters"];
|
||||
if (!isLocked && isParticipant) tabs.push("join");
|
||||
if (showActionTab) tabs.push("action");
|
||||
if (!isLocked && hasReportedMaps) tabs.push("result");
|
||||
if (isLocked || hasReportedMaps) tabs.push("result");
|
||||
|
||||
const allMembers = [
|
||||
...data.match.groupAlpha.members,
|
||||
|
||||
@@ -36,7 +36,15 @@ export function TournamentMatchBanner({
|
||||
const { t } = useTranslation(["tournament"]);
|
||||
const { formatDate } = useTimeFormat();
|
||||
const tournament = useTournament();
|
||||
const { currentMap, teamsMissingActiveRoster, matchIsLocked } = useMatch();
|
||||
const {
|
||||
currentMap,
|
||||
teamsMissingActiveRoster,
|
||||
matchIsLocked,
|
||||
joinPool,
|
||||
activeRoomLink,
|
||||
} = useMatch();
|
||||
const joinViaQr =
|
||||
Boolean(activeRoomLink?.joinLink) && !activeRoomLink?.isStale;
|
||||
|
||||
const opponentOne = data.match.opponentOne;
|
||||
const opponentTwo = data.match.opponentTwo;
|
||||
@@ -107,6 +115,8 @@ export function TournamentMatchBanner({
|
||||
teams: teamsMissingActiveRoster.join(" & "),
|
||||
})}
|
||||
screenLegal={screenLegal}
|
||||
joinPool={joinPool}
|
||||
joinViaQr={joinViaQr}
|
||||
testId="active-roster-needed-text"
|
||||
/>
|
||||
) : data.matchIsOver ? (
|
||||
@@ -119,12 +129,16 @@ export function TournamentMatchBanner({
|
||||
header={pickBanBanner.header}
|
||||
subtitle={pickBanBanner.subtitle}
|
||||
screenLegal={screenLegal}
|
||||
joinPool={joinPool}
|
||||
joinViaQr={joinViaQr}
|
||||
/>
|
||||
) : currentMap ? (
|
||||
<MatchBanner
|
||||
stageId={currentMap.stageId}
|
||||
mode={currentMap.mode}
|
||||
screenLegal={screenLegal}
|
||||
joinPool={joinPool}
|
||||
joinViaQr={joinViaQr}
|
||||
>
|
||||
<CurrentMapPickInfo
|
||||
source={currentMap.source}
|
||||
|
||||
@@ -10,16 +10,10 @@ import type {
|
||||
} from "~/components/match-page/MatchTimeline";
|
||||
import { resolveRoomPass } from "~/components/match-page/utils";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import {
|
||||
resolveActiveRoomLink,
|
||||
useConfirmRoom,
|
||||
} from "~/features/chat/room-link-utils";
|
||||
import { useConfirmRoom } from "~/features/chat/room-link-utils";
|
||||
import { useTournament } from "~/features/tournament/routes/to.$id";
|
||||
import * as PickBan from "~/features/tournament-bracket/core/PickBan";
|
||||
import {
|
||||
groupNumberToLetters,
|
||||
tournamentTeamToActiveRosterUserIds,
|
||||
} from "~/features/tournament-bracket/tournament-bracket-utils";
|
||||
import { tournamentTeamToActiveRosterUserIds } from "~/features/tournament-bracket/tournament-bracket-utils";
|
||||
import { databaseTimestampToJavascriptTimestamp } from "~/utils/dates";
|
||||
import { tournamentTeamPage } from "~/utils/urls";
|
||||
import type { TournamentMatchLoaderData } from "../loaders/to.$id.matches.$mid.server";
|
||||
@@ -318,54 +312,23 @@ function slotOfEvent({
|
||||
}
|
||||
|
||||
function TournamentMatchJoinTab({ data }: { data: TournamentMatchLoaderData }) {
|
||||
const tournament = useTournament();
|
||||
const user = useUser();
|
||||
const { onConfirmRoom, isConfirming } = useConfirmRoom();
|
||||
const {
|
||||
teams: [teamOne, teamTwo],
|
||||
joinPool,
|
||||
activeRoomLink,
|
||||
} = useMatch();
|
||||
if (!teamOne || !teamTwo) return null;
|
||||
if (!teamOne || !teamTwo || !joinPool || !activeRoomLink) return null;
|
||||
|
||||
const hostingTeam = resolveHostingTeam([teamOne, teamTwo]);
|
||||
|
||||
const hasRoundRobin = tournament.brackets.some(
|
||||
(b) => b.type === "round_robin",
|
||||
);
|
||||
const bracketIdx = tournament.brackets.findIndex((b) =>
|
||||
b.data.match.some((m) => m.id === data.match.id),
|
||||
);
|
||||
const bracket = tournament.brackets[bracketIdx];
|
||||
const bracketMatch = bracket?.data.match.find((m) => m.id === data.match.id);
|
||||
const group = bracket?.data.group.find(
|
||||
(g) => g.id === bracketMatch?.group_id,
|
||||
);
|
||||
|
||||
const poolCode = tournament.resolvePoolCode({
|
||||
hostingTeamId: hostingTeam.id,
|
||||
groupLetters:
|
||||
group && bracket?.type === "round_robin"
|
||||
? groupNumberToLetters(group.number)
|
||||
: undefined,
|
||||
bracketNumber:
|
||||
hasRoundRobin && bracket?.type !== "round_robin"
|
||||
? bracketIdx + 1
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const activeRoomLink = resolveActiveRoomLink({
|
||||
roomLinks: data.roomLinks,
|
||||
freshnessCutoff: data.match.startedAt ?? 0,
|
||||
viewerUserId: user?.id,
|
||||
members: data.match.players,
|
||||
});
|
||||
|
||||
return (
|
||||
<MatchJoinTab
|
||||
{...activeRoomLink}
|
||||
hostedBy={activeRoomLink.hostedBy ?? hostingTeam.name}
|
||||
onConfirmRoom={onConfirmRoom}
|
||||
isConfirming={isConfirming}
|
||||
pool={`${poolCode.prefix}${poolCode.suffix}`}
|
||||
pool={joinPool}
|
||||
pass={resolveRoomPass(hostingTeam.id)}
|
||||
showNoSplatnetAlert={data.anyUserPrefersNoSplatnet}
|
||||
/>
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import * as React from "react";
|
||||
import { TAB_KEYS } from "~/components/match-page/MatchTabs";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import { resolveActiveRoomLink } from "~/features/chat/room-link-utils";
|
||||
import { useTournament } from "~/features/tournament/routes/to.$id";
|
||||
import { isLeagueRoundLocked } from "~/features/tournament/tournament-utils";
|
||||
import * as PickBan from "~/features/tournament-bracket/core/PickBan";
|
||||
import type { Tournament } from "~/features/tournament-bracket/core/Tournament";
|
||||
import { tournamentTeamToActiveRosterUserIds } from "~/features/tournament-bracket/tournament-bracket-utils";
|
||||
import {
|
||||
groupNumberToLetters,
|
||||
tournamentTeamToActiveRosterUserIds,
|
||||
} from "~/features/tournament-bracket/tournament-bracket-utils";
|
||||
import type { TournamentMatchLoaderData } from "./loaders/to.$id.matches.$mid.server";
|
||||
import { matchIsLocked } from "./tournament-match-utils";
|
||||
import { matchIsLocked, resolveHostingTeam } from "./tournament-match-utils";
|
||||
|
||||
type ActiveRoomLink = ReturnType<typeof resolveActiveRoomLink>;
|
||||
|
||||
export type MatchPageTeam = NonNullable<ReturnType<Tournament["teamById"]>>;
|
||||
|
||||
@@ -28,6 +34,8 @@ type MatchPageContextValue = {
|
||||
turnOfResult: ReturnType<typeof PickBan.turnOf>;
|
||||
isPickBanStep: boolean;
|
||||
matchIsLocked: boolean;
|
||||
joinPool: string | null;
|
||||
activeRoomLink: ActiveRoomLink | null;
|
||||
};
|
||||
|
||||
const MatchPageContext = React.createContext<MatchPageContextValue | null>(
|
||||
@@ -93,6 +101,17 @@ export function MatchPageProvider({
|
||||
scores,
|
||||
});
|
||||
|
||||
const joinPool = resolveJoinPool({ tournament, data, teams });
|
||||
|
||||
const activeRoomLink = data.canJoin
|
||||
? resolveActiveRoomLink({
|
||||
roomLinks: data.roomLinks,
|
||||
freshnessCutoff: data.match.startedAt ?? 0,
|
||||
viewerUserId: user?.id,
|
||||
members: data.match.players,
|
||||
})
|
||||
: null;
|
||||
|
||||
const tabs = resolveVisibleTabs({
|
||||
matchIsOver: data.matchIsOver,
|
||||
canReportScore: tournament.canReportScore({
|
||||
@@ -126,6 +145,8 @@ export function MatchPageProvider({
|
||||
turnOfResult,
|
||||
isPickBanStep,
|
||||
matchIsLocked: lockedForCast,
|
||||
joinPool,
|
||||
activeRoomLink,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
@@ -168,15 +189,11 @@ function resolveVisibleTabs({
|
||||
leagueRoundLocked: boolean;
|
||||
lockedForCast: boolean;
|
||||
}): MatchTabKey[] {
|
||||
const tabs: MatchTabKey[] = [];
|
||||
const tabs: MatchTabKey[] = [TAB_KEYS.ROSTERS];
|
||||
|
||||
if (matchIsOver) {
|
||||
tabs.push(TAB_KEYS.RESULT);
|
||||
}
|
||||
if (canJoin) {
|
||||
tabs.push(TAB_KEYS.JOIN);
|
||||
}
|
||||
tabs.push(TAB_KEYS.ROSTERS);
|
||||
if (
|
||||
!leagueRoundLocked &&
|
||||
(isPickBanStep ||
|
||||
@@ -191,13 +208,56 @@ function resolveVisibleTabs({
|
||||
if (isAdminEligible) {
|
||||
tabs.push(TAB_KEYS.ADMIN);
|
||||
}
|
||||
if (!matchIsOver && (hasReportedMaps || hasPickBanEvents)) {
|
||||
if (matchIsOver || hasReportedMaps || hasPickBanEvents) {
|
||||
tabs.push(TAB_KEYS.RESULT);
|
||||
}
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
function resolveJoinPool({
|
||||
tournament,
|
||||
data,
|
||||
teams,
|
||||
}: {
|
||||
tournament: ReturnType<typeof useTournament>;
|
||||
data: TournamentMatchLoaderData;
|
||||
teams: [MatchPageTeam | null, MatchPageTeam | null];
|
||||
}): string | null {
|
||||
if (!data.canJoin) return null;
|
||||
|
||||
const [teamOne, teamTwo] = teams;
|
||||
if (!teamOne || !teamTwo) return null;
|
||||
|
||||
const hostingTeam = resolveHostingTeam([teamOne, teamTwo]);
|
||||
|
||||
const hasRoundRobin = tournament.brackets.some(
|
||||
(b) => b.type === "round_robin",
|
||||
);
|
||||
const bracketIdx = tournament.brackets.findIndex((b) =>
|
||||
b.data.match.some((m) => m.id === data.match.id),
|
||||
);
|
||||
const bracket = tournament.brackets[bracketIdx];
|
||||
const bracketMatch = bracket?.data.match.find((m) => m.id === data.match.id);
|
||||
const group = bracket?.data.group.find(
|
||||
(g) => g.id === bracketMatch?.group_id,
|
||||
);
|
||||
|
||||
const poolCode = tournament.resolvePoolCode({
|
||||
hostingTeamId: hostingTeam.id,
|
||||
groupLetters:
|
||||
group && bracket?.type === "round_robin"
|
||||
? groupNumberToLetters(group.number)
|
||||
: undefined,
|
||||
bracketNumber:
|
||||
hasRoundRobin && bracket?.type !== "round_robin"
|
||||
? bracketIdx + 1
|
||||
: undefined,
|
||||
});
|
||||
|
||||
return `${poolCode.prefix}${poolCode.suffix}`;
|
||||
}
|
||||
|
||||
function resolveTeamsMissingActiveRoster(
|
||||
teams: [MatchPageTeam | null, MatchPageTeam | null],
|
||||
minMembersPerTeam: number,
|
||||
|
||||
Reference in New Issue
Block a user