mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-12 06:06:28 -05:00
Add viewable rosters when not reporting score
This commit is contained in:
@@ -24,7 +24,13 @@ const stm = sql.prepare(/* sql */ `
|
||||
'tournamentTeamId',
|
||||
"TournamentTeamMember"."tournamentTeamId",
|
||||
'inGameName',
|
||||
"User"."inGameName"
|
||||
"User"."inGameName",
|
||||
'discordId',
|
||||
"User"."discordId",
|
||||
'customUrl',
|
||||
"User"."customUrl",
|
||||
'discordAvatar',
|
||||
"User"."discordAvatar"
|
||||
)
|
||||
) as "players"
|
||||
from "TournamentMatch"
|
||||
@@ -58,6 +64,9 @@ export const findMatchById = (id: number) => {
|
||||
discordName: User["discordName"];
|
||||
tournamentTeamId: TournamentTeamMember["tournamentTeamId"];
|
||||
inGameName: User["inGameName"];
|
||||
discordId: User["discordId"];
|
||||
customUrl: User["customUrl"];
|
||||
discordAvatar: User["discordAvatar"];
|
||||
}>,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
LoaderArgs,
|
||||
} from "@remix-run/node";
|
||||
import {
|
||||
Link,
|
||||
useLoaderData,
|
||||
useOutletContext,
|
||||
useRevalidator,
|
||||
@@ -28,6 +29,8 @@ import { assertUnreachable } from "~/utils/types";
|
||||
import {
|
||||
tournamentBracketsPage,
|
||||
tournamentMatchSubscribePage,
|
||||
tournamentTeamPage,
|
||||
userPage,
|
||||
} from "~/utils/urls";
|
||||
import { findByIdentifier } from "../../tournament/queries/findByIdentifier.server";
|
||||
import { findTeamsByTournamentId } from "../../tournament/queries/findTeamsByTournamentId.server";
|
||||
@@ -47,6 +50,8 @@ import {
|
||||
matchSubscriptionKey,
|
||||
} from "../tournament-bracket-utils";
|
||||
import bracketStyles from "../tournament-bracket.css";
|
||||
import clsx from "clsx";
|
||||
import { Avatar } from "~/components/Avatar";
|
||||
|
||||
export const links: LinksFunction = () => [
|
||||
{
|
||||
@@ -323,6 +328,7 @@ export const loader = ({ params }: LoaderArgs) => {
|
||||
};
|
||||
|
||||
export default function TournamentMatchPage() {
|
||||
const user = useUser();
|
||||
const visibility = useVisibilityChange();
|
||||
const { revalidate } = useRevalidator();
|
||||
const parentRouteData = useOutletContext<TournamentLoaderData>();
|
||||
@@ -332,16 +338,33 @@ export default function TournamentMatchPage() {
|
||||
data.match.opponentOne?.result === "win" ||
|
||||
data.match.opponentTwo?.result === "win";
|
||||
|
||||
const matchHasTwoTeams = Boolean(
|
||||
data.match.opponentOne?.id && data.match.opponentTwo?.id
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (visibility !== "visible" || matchIsOver) return;
|
||||
|
||||
revalidate();
|
||||
}, [visibility, revalidate, matchIsOver]);
|
||||
|
||||
const isMemberOfATeam = data.match.players.some((p) => p.id === user?.id);
|
||||
|
||||
const type = canReportTournamentScore({
|
||||
event: parentRouteData.event,
|
||||
match: data.match,
|
||||
ownedTeamId: parentRouteData.ownTeam?.id,
|
||||
user,
|
||||
})
|
||||
? "EDIT"
|
||||
: isMemberOfATeam
|
||||
? "MEMBER"
|
||||
: "OTHER";
|
||||
|
||||
const showRosterPeek = () => {
|
||||
if (matchIsOver) return false;
|
||||
|
||||
if (!data.match.opponentOne?.id || !data.match.opponentTwo?.id) return true;
|
||||
|
||||
return type !== "EDIT";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="stack lg">
|
||||
{!matchIsOver && visibility !== "hidden" ? <AutoRefresher /> : null}
|
||||
@@ -359,17 +382,18 @@ export default function TournamentMatchPage() {
|
||||
Back to bracket
|
||||
</LinkButton>
|
||||
</div>
|
||||
{!matchHasTwoTeams ? (
|
||||
<div className="text-lg text-lighter font-semi-bold text-center">
|
||||
Waiting for teams
|
||||
</div>
|
||||
) : null}
|
||||
{matchIsOver ? <ResultsSection /> : null}
|
||||
{!matchIsOver &&
|
||||
typeof data.match.opponentOne?.id === "number" &&
|
||||
typeof data.match.opponentTwo?.id === "number" ? (
|
||||
<MapListSection
|
||||
teams={[data.match.opponentOne.id, data.match.opponentTwo.id]}
|
||||
type={type}
|
||||
/>
|
||||
) : null}
|
||||
{showRosterPeek() ? (
|
||||
<Rosters
|
||||
teams={[data.match.opponentOne?.id, data.match.opponentTwo?.id]}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -403,8 +427,13 @@ function useAutoRefresh() {
|
||||
}, [lastEventId, revalidate]);
|
||||
}
|
||||
|
||||
function MapListSection({ teams }: { teams: [id: number, id: number] }) {
|
||||
const user = useUser();
|
||||
function MapListSection({
|
||||
teams,
|
||||
type,
|
||||
}: {
|
||||
teams: [id: number, id: number];
|
||||
type: "EDIT" | "MEMBER" | "OTHER";
|
||||
}) {
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const parentRouteData = useOutletContext<TournamentLoaderData>();
|
||||
|
||||
@@ -416,27 +445,12 @@ function MapListSection({ teams }: { teams: [id: number, id: number] }) {
|
||||
invariant(data.currentMap, "No map found for this score");
|
||||
invariant(data.modes, "No modes found for this map list");
|
||||
|
||||
const isMemberOfATeam =
|
||||
teamOne.members.some((m) => m.userId === user?.id) ||
|
||||
teamTwo.members.some((m) => m.userId === user?.id);
|
||||
|
||||
return (
|
||||
<ScoreReporter
|
||||
currentStageWithMode={data.currentMap}
|
||||
teams={[teamOne, teamTwo]}
|
||||
modes={data.modes}
|
||||
type={
|
||||
canReportTournamentScore({
|
||||
event: parentRouteData.event,
|
||||
match: data.match,
|
||||
ownedTeamId: parentRouteData.ownTeam?.id,
|
||||
user,
|
||||
})
|
||||
? "EDIT"
|
||||
: isMemberOfATeam
|
||||
? "MEMBER"
|
||||
: "OTHER"
|
||||
}
|
||||
type={type}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -482,3 +496,100 @@ function ResultsSection() {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function Rosters({
|
||||
teams,
|
||||
}: {
|
||||
teams: [id: number | null | undefined, id: number | null | undefined];
|
||||
}) {
|
||||
const data = useLoaderData<typeof loader>();
|
||||
const parentRouteData = useOutletContext<TournamentLoaderData>();
|
||||
|
||||
const teamOne = parentRouteData.teams.find((team) => team.id === teams[0]);
|
||||
const teamTwo = parentRouteData.teams.find((team) => team.id === teams[1]);
|
||||
const teamOnePlayers = data.match.players.filter(
|
||||
(p) => p.tournamentTeamId === teamOne?.id
|
||||
);
|
||||
const teamTwoPlayers = data.match.players.filter(
|
||||
(p) => p.tournamentTeamId === teamTwo?.id
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="tournament-bracket__rosters">
|
||||
<div>
|
||||
<div className="stack xs horizontal items-center text-lighter">
|
||||
<div className="tournament-bracket__team-one-dot" />
|
||||
Team 1
|
||||
</div>
|
||||
<h2
|
||||
className={clsx("text-sm", {
|
||||
"text-lighter": !teamOne,
|
||||
})}
|
||||
>
|
||||
{teamOne ? (
|
||||
<Link
|
||||
to={tournamentTeamPage({
|
||||
eventId: parentRouteData.event.id,
|
||||
tournamentTeamId: teamOne.id,
|
||||
})}
|
||||
className="text-main-forced font-bold"
|
||||
>
|
||||
{teamOne.name}
|
||||
</Link>
|
||||
) : (
|
||||
"Waiting on team"
|
||||
)}
|
||||
</h2>
|
||||
{teamOnePlayers.length > 0 ? (
|
||||
<ul className="stack xs mt-2">
|
||||
{teamOnePlayers.map((p) => {
|
||||
return (
|
||||
<li key={p.id}>
|
||||
<Link to={userPage(p)} className="stack horizontal sm">
|
||||
<Avatar user={p} size="xxs" />
|
||||
{p.discordName}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
<div className="stack xs horizontal items-center text-lighter">
|
||||
<div className="tournament-bracket__team-two-dot" />
|
||||
Team 2
|
||||
</div>
|
||||
<h2 className={clsx("text-sm", { "text-lighter": !teamTwo })}>
|
||||
{teamTwo ? (
|
||||
<Link
|
||||
to={tournamentTeamPage({
|
||||
eventId: parentRouteData.event.id,
|
||||
tournamentTeamId: teamTwo.id,
|
||||
})}
|
||||
className="text-main-forced font-bold"
|
||||
>
|
||||
{teamTwo.name}
|
||||
</Link>
|
||||
) : (
|
||||
"Waiting on team"
|
||||
)}
|
||||
</h2>
|
||||
{teamTwoPlayers.length > 0 ? (
|
||||
<ul className="stack xs mt-2">
|
||||
{teamTwoPlayers.map((p) => {
|
||||
return (
|
||||
<li key={p.id}>
|
||||
<Link to={userPage(p)} className="stack horizontal sm">
|
||||
<Avatar user={p} size="xxs" />
|
||||
{p.discordName}
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -209,6 +209,27 @@
|
||||
font-weight: var(--bold);
|
||||
}
|
||||
|
||||
.tournament-bracket__rosters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--s-8);
|
||||
font-size: var(--fonts-xs);
|
||||
font-weight: var(--semi-bold);
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tournament-bracket__rosters ul {
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 640px) {
|
||||
.tournament-bracket__rosters {
|
||||
justify-content: space-evenly;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.tournament-bracket__mode-progress {
|
||||
display: flex;
|
||||
margin-bottom: var(--s-4);
|
||||
|
||||
Reference in New Issue
Block a user