mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-24 23:19:39 -05:00
Misc tournament things
This commit is contained in:
parent
ce60895e8f
commit
02abc0b99c
|
|
@ -1,4 +1,4 @@
|
|||
import { Form, useFetcher } from "@remix-run/react";
|
||||
import { useFetcher } from "@remix-run/react";
|
||||
import React from "react";
|
||||
import invariant from "tiny-invariant";
|
||||
import { useTranslation } from "~/hooks/useTranslation";
|
||||
|
|
@ -12,12 +12,14 @@ export function FormWithConfirm({
|
|||
dialogHeading,
|
||||
deleteButtonText,
|
||||
action,
|
||||
submitButtonTestId = "submit-button",
|
||||
}: {
|
||||
fields?: [name: string, value: string | number][];
|
||||
children: React.ReactNode;
|
||||
dialogHeading: string;
|
||||
deleteButtonText?: string;
|
||||
action?: string;
|
||||
submitButtonTestId?: string;
|
||||
}) {
|
||||
const fetcher = useFetcher();
|
||||
const { t } = useTranslation(["common"]);
|
||||
|
|
@ -50,7 +52,7 @@ export function FormWithConfirm({
|
|||
<SubmitButton
|
||||
form={id}
|
||||
variant="destructive"
|
||||
testId={dialogOpen ? "confirm-button" : undefined}
|
||||
testId={dialogOpen ? "confirm-button" : submitButtonTestId}
|
||||
>
|
||||
{deleteButtonText ?? t("common:actions.delete")}
|
||||
</SubmitButton>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { useTranslation } from "~/hooks/useTranslation";
|
||||
import type { MainWeaponId } from "~/modules/in-game-lists";
|
||||
import { mainWeaponImageUrl, outlinedMainWeaponImageUrl } from "~/utils/urls";
|
||||
import type { MainWeaponId, ModeShort } from "~/modules/in-game-lists";
|
||||
import {
|
||||
mainWeaponImageUrl,
|
||||
modeImageUrl,
|
||||
outlinedMainWeaponImageUrl,
|
||||
} from "~/utils/urls";
|
||||
|
||||
interface ImageProps {
|
||||
path: string;
|
||||
|
|
@ -10,6 +14,7 @@ interface ImageProps {
|
|||
containerClassName?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
size?: number;
|
||||
style?: React.CSSProperties;
|
||||
testId?: string;
|
||||
onClick?: () => void;
|
||||
|
|
@ -22,6 +27,7 @@ export function Image({
|
|||
className,
|
||||
width,
|
||||
height,
|
||||
size,
|
||||
style,
|
||||
testId,
|
||||
containerClassName,
|
||||
|
|
@ -40,8 +46,8 @@ export function Image({
|
|||
alt={alt}
|
||||
src={`${path}.png`}
|
||||
className={className}
|
||||
width={width}
|
||||
height={height}
|
||||
width={size ?? width}
|
||||
height={size ?? height}
|
||||
style={style}
|
||||
draggable="false"
|
||||
data-testid={testId}
|
||||
|
|
@ -77,3 +83,21 @@ export function WeaponImage({
|
|||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type ModeImageProps = {
|
||||
mode: ModeShort;
|
||||
} & Omit<ImageProps, "path" | "alt" | "title">;
|
||||
|
||||
export function ModeImage({ mode, testId, ...rest }: ModeImageProps) {
|
||||
const { t } = useTranslation(["game-misc"]);
|
||||
|
||||
return (
|
||||
<Image
|
||||
{...rest}
|
||||
alt={t(`game-misc:MODE_LONG_${mode}`)}
|
||||
title={t(`game-misc:MODE_LONG_${mode}`)}
|
||||
testId={testId}
|
||||
path={modeImageUrl(mode)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ export const loader = ({ params }: LoaderArgs) => {
|
|||
};
|
||||
};
|
||||
|
||||
// xxx: banner that shows for participants not mobile friendly
|
||||
export default function TournamentBracketsPage() {
|
||||
const user = useUser();
|
||||
const data = useLoaderData<typeof loader>();
|
||||
|
|
|
|||
|
|
@ -143,7 +143,6 @@ export const action: ActionFunction = async ({ request, params }) => {
|
|||
return null;
|
||||
};
|
||||
|
||||
// xxx: seed order for challonge download
|
||||
export default function TournamentAdminPage() {
|
||||
const { t } = useTranslation(["calendar"]);
|
||||
const data = useOutletContext<TournamentLoaderData>();
|
||||
|
|
@ -172,6 +171,7 @@ export default function TournamentAdminPage() {
|
|||
name: data.event.name,
|
||||
})}
|
||||
action={calendarEventPage(data.event.eventId)}
|
||||
submitButtonTestId="delete-submit-button"
|
||||
>
|
||||
<Button
|
||||
className="ml-auto"
|
||||
|
|
@ -385,6 +385,15 @@ function DownloadParticipants() {
|
|||
.join("\n");
|
||||
}
|
||||
|
||||
function simpleListInSeededOrder() {
|
||||
return data.teams
|
||||
.slice()
|
||||
.sort((a, b) => (a.seed ?? Infinity) - (b.seed ?? Infinity))
|
||||
.filter((team) => team.checkedInAt)
|
||||
.map((team) => team.name)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label>{t("tournament:admin.download")} (Discord format)</label>
|
||||
|
|
@ -411,6 +420,17 @@ function DownloadParticipants() {
|
|||
>
|
||||
Not checked in participants
|
||||
</Button>
|
||||
<Button
|
||||
size="tiny"
|
||||
onClick={() =>
|
||||
handleDownload({
|
||||
filename: "teams-in-seeded-order.txt",
|
||||
content: simpleListInSeededOrder(),
|
||||
})
|
||||
}
|
||||
>
|
||||
Simple list in seeded order
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,20 +1,28 @@
|
|||
import {
|
||||
redirect,
|
||||
type ActionFunction,
|
||||
type LoaderArgs,
|
||||
type SerializeFrom,
|
||||
redirect,
|
||||
} from "@remix-run/node";
|
||||
import { useFetcher, useLoaderData, useOutletContext } from "@remix-run/react";
|
||||
import clsx from "clsx";
|
||||
import * as React from "react";
|
||||
import { useCopyToClipboard } from "react-use";
|
||||
import invariant from "tiny-invariant";
|
||||
import { Alert } from "~/components/Alert";
|
||||
import { Avatar } from "~/components/Avatar";
|
||||
import { Button } from "~/components/Button";
|
||||
import { Image } from "~/components/Image";
|
||||
import { Divider } from "~/components/Divider";
|
||||
import { Image, ModeImage } from "~/components/Image";
|
||||
import { Input } from "~/components/Input";
|
||||
import { Label } from "~/components/Label";
|
||||
import { SubmitButton } from "~/components/SubmitButton";
|
||||
import { CheckmarkIcon } from "~/components/icons/Checkmark";
|
||||
import { ClockIcon } from "~/components/icons/Clock";
|
||||
import { CrossIcon } from "~/components/icons/Cross";
|
||||
import { UserIcon } from "~/components/icons/User";
|
||||
import { useAutoRerender } from "~/hooks/useAutoRerender";
|
||||
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||
import { useTranslation } from "~/hooks/useTranslation";
|
||||
import { useUser } from "~/modules/auth";
|
||||
import { getUser, requireUser } from "~/modules/auth/user.server";
|
||||
|
|
@ -26,12 +34,14 @@ import type {
|
|||
import { stageIds } from "~/modules/in-game-lists";
|
||||
import { rankedModesShort } from "~/modules/in-game-lists/modes";
|
||||
import { MapPool } from "~/modules/map-pool-serializer";
|
||||
import { databaseTimestampToDate } from "~/utils/dates";
|
||||
import {
|
||||
notFoundIfFalsy,
|
||||
parseRequestFormData,
|
||||
validate,
|
||||
type SendouRouteHandle,
|
||||
} from "~/utils/remix";
|
||||
import { booleanToInt } from "~/utils/sql";
|
||||
import { discordFullName } from "~/utils/strings";
|
||||
import type { Unpacked } from "~/utils/types";
|
||||
import { assertUnreachable } from "~/utils/types";
|
||||
|
|
@ -44,40 +54,31 @@ import {
|
|||
tournamentBracketsPage,
|
||||
tournamentJoinPage,
|
||||
} from "~/utils/urls";
|
||||
import { checkIn } from "../queries/checkIn.server";
|
||||
import { createTeam } from "../queries/createTeam.server";
|
||||
import deleteTeamMember from "../queries/deleteTeamMember.server";
|
||||
import { findByIdentifier } from "../queries/findByIdentifier.server";
|
||||
import { findOwnTeam } from "../queries/findOwnTeam.server";
|
||||
import { findTeamsByTournamentId } from "../queries/findTeamsByTournamentId.server";
|
||||
import type { TrustedPlayer } from "../queries/findTrustedPlayers.server";
|
||||
import { findTrustedPlayers } from "../queries/findTrustedPlayers.server";
|
||||
import hasTournamentStarted from "../queries/hasTournamentStarted.server";
|
||||
import { joinTeam } from "../queries/joinLeaveTeam.server";
|
||||
import { updateTeamInfo } from "../queries/updateTeamInfo.server";
|
||||
import { upsertCounterpickMaps } from "../queries/upsertCounterpickMaps.server";
|
||||
import { TOURNAMENT } from "../tournament-constants";
|
||||
import { useSelectCounterpickMapPoolState } from "../tournament-hooks";
|
||||
import { registerSchema } from "../tournament-schemas.server";
|
||||
import {
|
||||
isOneModeTournamentOf,
|
||||
HACKY_resolvePicture,
|
||||
tournamentIdFromParams,
|
||||
resolveOwnedTeam,
|
||||
HACKY_resolveCheckInTime,
|
||||
HACKY_resolvePicture,
|
||||
isOneModeTournamentOf,
|
||||
modesIncluded,
|
||||
resolveOwnedTeam,
|
||||
tournamentIdFromParams,
|
||||
validateCanCheckIn,
|
||||
} from "../tournament-utils";
|
||||
import type { TournamentLoaderData } from "./to.$id";
|
||||
import { createTeam } from "../queries/createTeam.server";
|
||||
import { ClockIcon } from "~/components/icons/Clock";
|
||||
import { databaseTimestampToDate } from "~/utils/dates";
|
||||
import { UserIcon } from "~/components/icons/User";
|
||||
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||
import hasTournamentStarted from "../queries/hasTournamentStarted.server";
|
||||
import { CheckmarkIcon } from "~/components/icons/Checkmark";
|
||||
import { CrossIcon } from "~/components/icons/Cross";
|
||||
import clsx from "clsx";
|
||||
import { checkIn } from "../queries/checkIn.server";
|
||||
import { useAutoRerender } from "~/hooks/useAutoRerender";
|
||||
import type { TrustedPlayer } from "../queries/findTrustedPlayers.server";
|
||||
import { findTrustedPlayers } from "../queries/findTrustedPlayers.server";
|
||||
import { Divider } from "~/components/Divider";
|
||||
import { joinTeam } from "../queries/joinLeaveTeam.server";
|
||||
import { booleanToInt } from "~/utils/sql";
|
||||
|
||||
export const handle: SendouRouteHandle = {
|
||||
breadcrumb: () => ({
|
||||
|
|
@ -262,6 +263,13 @@ export default function TournamentRegisterPage() {
|
|||
})
|
||||
: null}
|
||||
</div>
|
||||
<div className="stack horizontal sm mt-1">
|
||||
{modesIncluded(parentRouteData.event).map((mode) => (
|
||||
<div key={mode} className="tournament___info__mode-container">
|
||||
<ModeImage mode={mode} size={18} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ export const action: ActionFunction = async ({ request, params }) => {
|
|||
const hasStarted = hasTournamentStarted(tournamentId);
|
||||
|
||||
validate(canAdminTournament({ user, event: tournament }));
|
||||
validate(hasStarted, "Tournament has started");
|
||||
validate(!hasStarted, "Tournament has started");
|
||||
|
||||
updateTeamSeeds({ tournamentId, teamIds: data.seeds });
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ 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 { teamHasCheckedIn, tournamentIdFromParams } from "../tournament-utils";
|
||||
import styles from "../tournament.css";
|
||||
import hasTournamentStarted from "../queries/hasTournamentStarted.server";
|
||||
|
||||
|
|
@ -72,14 +72,16 @@ export const loader = async ({ params, request }: LoaderArgs) => {
|
|||
const mapListGeneratorAvailable =
|
||||
canAdminTournament({ user, event }) || event.showMapListGenerator;
|
||||
|
||||
const teams = findTeamsByTournamentId(tournamentId);
|
||||
const hasStarted = hasTournamentStarted(tournamentId);
|
||||
let teams = findTeamsByTournamentId(tournamentId);
|
||||
if (hasStarted) {
|
||||
teams = teams.filter(teamHasCheckedIn);
|
||||
}
|
||||
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -154,6 +154,12 @@
|
|||
padding: var(--s-1) 0;
|
||||
}
|
||||
|
||||
.tournament___info__mode-container {
|
||||
background-color: var(--bg-lighter);
|
||||
border-radius: 100%;
|
||||
padding: var(--s-2);
|
||||
}
|
||||
|
||||
.tournament__by {
|
||||
color: var(--text-lighter);
|
||||
font-size: var(--fonts-sm);
|
||||
|
|
|
|||
|
|
@ -1077,6 +1077,14 @@ dialog::backdrop {
|
|||
}
|
||||
|
||||
#nprogress .bar {
|
||||
background: var(--theme) !important;
|
||||
margin-top: 3rem !important;
|
||||
background: var(--theme) !important;
|
||||
}
|
||||
|
||||
#nprogress .spinner {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#nprogress .peg {
|
||||
display: none !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,6 +134,10 @@
|
|||
margin-block-start: var(--s-0);
|
||||
}
|
||||
|
||||
.mt-1 {
|
||||
margin-block-start: var(--s-1);
|
||||
}
|
||||
|
||||
.mt-2 {
|
||||
margin-block-start: var(--s-2);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user