mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-26 17:27:09 -05:00
* Got something going * Style overwrites * width != height * More playing with lines * Migrations * Start bracket initial * Unhardcode stage generation params * Link to match page * Matches page initial * Support directly adding seed to map list generator * Add docs * Maps in matches page * Add invariant about tie breaker map pool * Fix PICNIC lacking tie breaker maps * Only link in bracket when tournament has started * Styled tournament roster inputs * Prefer IGN in tournament match page * ModeProgressIndicator * Some conditional rendering * Match action initial + better error display * Persist bestOf in DB * Resolve best of ahead of time * Move brackets-manager to core * Score reporting works * Clear winner on score report * ModeProgressIndicator: highlight winners * Fix inconsistent input * Better text when submitting match * mapCountPlayedInSetWithCertainty that works * UNDO_REPORT_SCORE implemented * Permission check when starting tournament * Remove IGN from upsert * View match results page * Source in DB * Match page waiting for teams * Move tournament bracket to feature folder * REOPEN_MATCH initial * Handle proper resetting of match * Inline bracket-manager * Syncify * Transactions * Handle match is locked gracefully * Match page auto refresh * Fix match refresh called "globally" * Bracket autoupdate * Move fillWithNullTillPowerOfTwo to utils with testing * Fix map lists not visible after tournament started * Optimize match events * Show UI while in progress to members * Fix start tournament alert not being responsive * Teams can check in * Fix map list 400 * xxx -> TODO * Seeds page * Remove map icons for team page * Don't display link to seeds after tournament has started * Admin actions initial * Change captain admin action * Make all hooks ts * Admin actions functioning * Fix validate error not displaying in CatchBoundary * Adjust validate args order * Remove admin loader * Make delete team button menancing * Only include checked in teams to bracket * Optimize to.id route loads * Working show map list generator toggle * Update full tournaments flow * Make full tournaments work with many start times * Handle undefined in crud * Dynamic stage banner * Handle default strat if map list generation fails * Fix crash on brackets if less than 2 teams * Add commented out test for reference * Add TODO * Add players from team during register * TrustRelationship * Prefers not to host feature * Last before merge * Rename some vars * More renames
148 lines
4.4 KiB
TypeScript
148 lines
4.4 KiB
TypeScript
import type {
|
|
LinksFunction,
|
|
LoaderArgs,
|
|
V2_MetaFunction,
|
|
SerializeFrom,
|
|
} from "@remix-run/node";
|
|
import {
|
|
type ShouldRevalidateFunction,
|
|
Outlet,
|
|
useLoaderData,
|
|
useLocation,
|
|
} from "@remix-run/react";
|
|
import { Main } from "~/components/Main";
|
|
import { SubNav, SubNavLink } from "~/components/SubNav";
|
|
import { db } from "~/db";
|
|
import { useTranslation } from "~/hooks/useTranslation";
|
|
import { useUser } from "~/modules/auth";
|
|
import { getUserId } from "~/modules/auth/user.server";
|
|
import { canAdminTournament } from "~/permissions";
|
|
import { notFoundIfFalsy, type SendouRouteHandle } from "~/utils/remix";
|
|
import { makeTitle } from "~/utils/strings";
|
|
import type { Unpacked } from "~/utils/types";
|
|
import { findByIdentifier } from "../queries/findByIdentifier.server";
|
|
import type { FindTeamsByTournamentId } from "../queries/findTeamsByTournamentId.server";
|
|
import { findTeamsByTournamentId } from "../queries/findTeamsByTournamentId.server";
|
|
import { tournamentIdFromParams } from "../tournament-utils";
|
|
import styles from "../tournament.css";
|
|
import hasTournamentStarted from "../queries/hasTournamentStarted.server";
|
|
|
|
export const shouldRevalidate: ShouldRevalidateFunction = (args) => {
|
|
const wasMutation = args.formMethod === "post";
|
|
const wasOnMatchPage = args.formAction?.includes("matches");
|
|
|
|
if (wasMutation && wasOnMatchPage) {
|
|
return false;
|
|
}
|
|
|
|
const wasRevalidation = !args.formMethod;
|
|
const wasOnBracketPage = args.currentUrl.href.includes("brackets");
|
|
|
|
if (wasRevalidation && wasOnBracketPage) {
|
|
return false;
|
|
}
|
|
|
|
return args.defaultShouldRevalidate;
|
|
};
|
|
|
|
export const meta: V2_MetaFunction = (args) => {
|
|
const data = args.data as SerializeFrom<typeof loader>;
|
|
|
|
if (!data) return [];
|
|
|
|
return [{ title: makeTitle(data.event.name) }];
|
|
};
|
|
|
|
export const links: LinksFunction = () => {
|
|
return [{ rel: "stylesheet", href: styles }];
|
|
};
|
|
|
|
export const handle: SendouRouteHandle = {
|
|
i18n: ["tournament"],
|
|
};
|
|
|
|
export type TournamentLoaderTeam = Unpacked<TournamentLoaderData["teams"]>;
|
|
export type TournamentLoaderData = SerializeFrom<typeof loader>;
|
|
|
|
export const loader = async ({ params, request }: LoaderArgs) => {
|
|
const user = await getUserId(request);
|
|
const tournamentId = tournamentIdFromParams(params);
|
|
const event = notFoundIfFalsy(findByIdentifier(tournamentId));
|
|
|
|
const mapListGeneratorAvailable =
|
|
canAdminTournament({ user, event }) || event.showMapListGenerator;
|
|
|
|
const teams = findTeamsByTournamentId(tournamentId);
|
|
|
|
const ownedTeamId = teams.find((team) =>
|
|
team.members.some((member) => member.userId === user?.id && member.isOwner)
|
|
)?.id;
|
|
|
|
const hasStarted = hasTournamentStarted(tournamentId);
|
|
|
|
return {
|
|
event,
|
|
tieBreakerMapPool: db.calendarEvents.findTieBreakerMapPoolByEventId(
|
|
event.eventId
|
|
),
|
|
ownedTeamId,
|
|
teams: censorMapPools(teams),
|
|
mapListGeneratorAvailable,
|
|
hasStarted,
|
|
};
|
|
|
|
function censorMapPools(
|
|
teams: FindTeamsByTournamentId
|
|
): FindTeamsByTournamentId {
|
|
if (hasStarted || mapListGeneratorAvailable) return teams;
|
|
|
|
return teams.map((team) =>
|
|
team.id === ownedTeamId
|
|
? team
|
|
: {
|
|
...team,
|
|
mapPool: undefined,
|
|
}
|
|
);
|
|
}
|
|
};
|
|
|
|
// TODO: icons to nav could be nice
|
|
export default function TournamentLayout() {
|
|
const { t } = useTranslation(["tournament"]);
|
|
const user = useUser();
|
|
const data = useLoaderData<typeof loader>();
|
|
const location = useLocation();
|
|
|
|
const onBracketsPage = location.pathname.includes("brackets");
|
|
|
|
return (
|
|
<Main bigger={onBracketsPage}>
|
|
<SubNav>
|
|
{!data.hasStarted ? (
|
|
<SubNavLink to="register" data-testid="register-tab">
|
|
{t("tournament:tabs.register")}
|
|
</SubNavLink>
|
|
) : null}
|
|
<SubNavLink to="brackets" data-testid="brackets-tab">
|
|
Brackets
|
|
</SubNavLink>
|
|
{data.mapListGeneratorAvailable ? (
|
|
<SubNavLink to="maps">{t("tournament:tabs.maps")}</SubNavLink>
|
|
) : null}
|
|
<SubNavLink to="teams">
|
|
{t("tournament:tabs.teams", { count: data.teams.length })}
|
|
</SubNavLink>
|
|
{canAdminTournament({ user, event: data.event }) &&
|
|
!data.hasStarted && <SubNavLink to="seeds">Seeds</SubNavLink>}
|
|
{canAdminTournament({ user, event: data.event }) && (
|
|
<SubNavLink to="admin" data-testid="admin-tab">
|
|
{t("tournament:tabs.admin")}
|
|
</SubNavLink>
|
|
)}
|
|
</SubNav>
|
|
<Outlet context={data} />
|
|
</Main>
|
|
);
|
|
}
|