mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-11 21:29:09 -05:00
Wire up tournament join tab
This commit is contained in:
parent
aec410c348
commit
646cc04899
|
|
@ -101,6 +101,10 @@ export const matchSchema = z.union([
|
|||
_action: _action("END_SET"),
|
||||
winnerTeamId: z.preprocess(nullLiteraltoNull, id.nullable()),
|
||||
}),
|
||||
// xxx: one central API for confirm room?
|
||||
z.object({
|
||||
_action: _action("CONFIRM_ROOM"),
|
||||
}),
|
||||
]);
|
||||
|
||||
export const bracketIdx = z.coerce.number().int().min(0).max(100);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { sql } from "~/db/sql";
|
|||
import { TournamentMatchStatus } from "~/db/tables";
|
||||
import { requireUser } from "~/features/auth/core/user.server";
|
||||
import * as ChatSystemMessage from "~/features/chat/ChatSystemMessage.server";
|
||||
import * as RoomLinkRepository from "~/features/chat/RoomLinkRepository.server";
|
||||
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
|
||||
import * as TournamentTeamRepository from "~/features/tournament/TournamentTeamRepository.server";
|
||||
import { endDroppedTeamMatches } from "~/features/tournament/tournament-utils.server";
|
||||
|
|
@ -728,6 +729,10 @@ export const action: ActionFunction = async ({ params, request }) => {
|
|||
|
||||
break;
|
||||
}
|
||||
case "CONFIRM_ROOM": {
|
||||
await RoomLinkRepository.refreshTimestamp(user.id);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertUnreachable(data);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { differenceInMinutes } from "date-fns";
|
||||
import { useFetcher } from "react-router";
|
||||
import { MatchJoinTab } from "~/components/match-page/MatchJoinTab";
|
||||
import { MatchResultTab } from "~/components/match-page/MatchResultTab";
|
||||
|
|
@ -6,9 +7,14 @@ import { MatchTabs } from "~/components/match-page/MatchTabs";
|
|||
import type { TimelineMap } from "~/components/match-page/MatchTimeline";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import { useTournament } from "~/features/tournament/routes/to.$id";
|
||||
import { tournamentTeamToActiveRosterUserIds } from "~/features/tournament-bracket/tournament-bracket-utils";
|
||||
import {
|
||||
groupNumberToLetters,
|
||||
tournamentTeamToActiveRosterUserIds,
|
||||
} from "~/features/tournament-bracket/tournament-bracket-utils";
|
||||
import { databaseTimestampToDate } from "~/utils/dates";
|
||||
import { tournamentTeamPage } from "~/utils/urls";
|
||||
import type { TournamentMatchLoaderData } from "../loaders/to.$id.matches.$mid.server";
|
||||
import { resolveHostingTeam, resolveRoomPass } from "../tournament-match-utils";
|
||||
import { TournamentMatchActionTab } from "./TournamentMatchActionTab";
|
||||
|
||||
export function TournamentMatchTabs({
|
||||
|
|
@ -62,7 +68,7 @@ export function TournamentMatchTabs({
|
|||
maps={resolveTimelineMaps(data, opponentOneId, opponentTwoId)}
|
||||
/>
|
||||
) : null}
|
||||
{tabs.includes("join") ? <TournamentMatchJoinTab /> : null}
|
||||
{tabs.includes("join") ? <TournamentMatchJoinTab data={data} /> : null}
|
||||
<TournamentMatchRosterTab data={data} />
|
||||
{tabs.includes("action") && currentMap ? (
|
||||
<TournamentMatchActionTab
|
||||
|
|
@ -144,13 +150,77 @@ function resolveTimelineMaps(
|
|||
});
|
||||
}
|
||||
|
||||
function TournamentMatchJoinTab() {
|
||||
function TournamentMatchJoinTab({ data }: { data: TournamentMatchLoaderData }) {
|
||||
const tournament = useTournament();
|
||||
const user = useUser();
|
||||
const confirmFetcher = useFetcher();
|
||||
|
||||
const teamOne = tournament.teamById(data.match.opponentOne!.id!)!;
|
||||
const teamTwo = tournament.teamById(data.match.opponentTwo!.id!)!;
|
||||
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,
|
||||
});
|
||||
|
||||
// xxx: maybe some shared util?
|
||||
const freshnessCutoff = data.match.startedAt ?? 0;
|
||||
const validRoomLink = data.roomLinks.find(
|
||||
(rl) => rl.refreshedAt >= freshnessCutoff,
|
||||
);
|
||||
const ownStaleRoomLink = validRoomLink
|
||||
? undefined
|
||||
: data.roomLinks.find((rl) => rl.userId === user?.id);
|
||||
const activeRoomLink = validRoomLink ?? ownStaleRoomLink;
|
||||
const isStale = activeRoomLink ? !validRoomLink : undefined;
|
||||
const staleMinutesAgo = ownStaleRoomLink
|
||||
? differenceInMinutes(
|
||||
new Date(),
|
||||
databaseTimestampToDate(ownStaleRoomLink.refreshedAt),
|
||||
)
|
||||
: 0;
|
||||
const roomLinkUsername = activeRoomLink
|
||||
? data.match.players.find((p) => p.id === activeRoomLink.userId)?.username
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<MatchJoinTab
|
||||
joinLink="https://app.nintendo.net/private_battle/abc123"
|
||||
pool="SQ7"
|
||||
pass="8430"
|
||||
showNoSplatnetAlert
|
||||
joinLink={activeRoomLink?.url}
|
||||
hostedBy={roomLinkUsername ?? hostingTeam.name}
|
||||
isStale={isStale}
|
||||
staleMinutesAgo={staleMinutesAgo}
|
||||
refreshedAt={
|
||||
validRoomLink
|
||||
? databaseTimestampToDate(validRoomLink.refreshedAt)
|
||||
: undefined
|
||||
}
|
||||
onConfirmRoom={() => {
|
||||
confirmFetcher.submit({ _action: "CONFIRM_ROOM" }, { method: "post" });
|
||||
}}
|
||||
isConfirming={confirmFetcher.state !== "idle"}
|
||||
pool={`${poolCode.prefix}${poolCode.suffix}`}
|
||||
pass={resolveRoomPass(hostingTeam.id)}
|
||||
showNoSplatnetAlert={data.anyUserPrefersNoSplatnet}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type { LoaderFunctionArgs } from "react-router";
|
|||
import { getUser } from "~/features/auth/core/user.server";
|
||||
import * as ChatSystemMessage from "~/features/chat/ChatSystemMessage.server";
|
||||
import { chatAccessible } from "~/features/chat/chat-utils";
|
||||
import * as RoomLinkRepository from "~/features/chat/RoomLinkRepository.server";
|
||||
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
|
||||
import * as TournamentTeamRepository from "~/features/tournament/TournamentTeamRepository.server";
|
||||
import * as PickBan from "~/features/tournament-bracket/core/PickBan";
|
||||
|
|
@ -176,6 +177,17 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
|||
const visibleChatCode =
|
||||
shouldSeeChat && !chatCodeExpired ? match.chatCode : undefined;
|
||||
|
||||
// xxx: optimization, can be skipped if user can't join anyway
|
||||
const [roomLinks, anyUserPrefersNoSplatnet] = matchIsOver
|
||||
? ([[], false] as const)
|
||||
: await Promise.all([
|
||||
RoomLinkRepository.findByUserIds(
|
||||
match.players.map((p) => p.id),
|
||||
3,
|
||||
),
|
||||
UserRepository.anyUserPrefersNoSplatnet(match.players.map((p) => p.id)),
|
||||
]);
|
||||
|
||||
return {
|
||||
match: shouldSeeChat ? match : { ...match, chatCode: undefined },
|
||||
results,
|
||||
|
|
@ -184,6 +196,8 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
|||
endedEarly,
|
||||
noScreen,
|
||||
chatCode: visibleChatCode,
|
||||
roomLinks,
|
||||
anyUserPrefersNoSplatnet,
|
||||
pickBanEventCount: pickBanEvents.length,
|
||||
pickBanEvents: pickBanEvents.map((e) => ({
|
||||
type: e.type,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user