Unregister teams from tournament

This commit is contained in:
Kalle
2022-04-01 18:34:20 +03:00
parent c70e362917
commit 00ae9d575c
5 changed files with 124 additions and 39 deletions

View File

@@ -0,0 +1,18 @@
export function TrashIcon({ className }: { className?: string }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
className={className}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
);
}

View File

@@ -57,3 +57,15 @@ export function checkOut(id: string) {
},
});
}
export function unregister(id: string) {
// TODO: this should use cascades instead
return db.$transaction([
db.tournamentTeamMember.deleteMany({ where: { teamId: id } }),
db.tournamentTeam.delete({
where: {
id,
},
}),
]);
}

View File

@@ -1,3 +1,4 @@
import { useRef } from "react";
import {
ActionFunction,
Form,
@@ -5,38 +6,49 @@ import {
useMatches,
useTransition,
} from "remix";
import invariant from "tiny-invariant";
import { z } from "zod";
import { Button } from "~/components/Button";
import { Catcher } from "~/components/Catcher";
import { TrashIcon } from "~/components/icons/Trash";
import { TOURNAMENT_TEAM_ROSTER_MIN_SIZE } from "~/constants";
import { checkInHasStarted } from "~/core/tournament/utils";
import {
isTournamentAdmin,
tournamentHasNotStarted,
} from "~/core/tournament/validators";
import * as TournamentTeam from "~/models/TournamentTeam.server";
import {
checkIn,
checkOut,
FindTournamentByNameForUrlI,
} from "~/services/tournament";
import manageStylesUrl from "~/styles/tournament-manage.css";
import { parseRequestFormData, requireUser, Unpacked } from "~/utils";
import { parseRequestFormData, requireUser, Unpacked, validate } from "~/utils";
// TODO: for consistency upper case this schema and all others like in /validators
const manageSchema = z.union([
z.object({
_action: z.literal("CHECK_IN"),
teamId: z.string().uuid(),
}),
z.object({
_action: z.literal("CHECK_OUT"),
teamId: z.string().uuid(),
}),
]);
const manageActionSchema = z.object({
_action: z.enum(["CHECK_OUT", "CHECK_IN", "UNREGISTER"]),
teamId: z.string().uuid(),
});
export const action: ActionFunction = async ({ context, request }) => {
const data = await parseRequestFormData({
request,
schema: manageSchema,
schema: manageActionSchema,
});
const user = requireUser(context);
const tournamentTeam = await TournamentTeam.findById(data.teamId);
validate(tournamentTeam, "Invalid team id");
validate(
isTournamentAdmin({
userId: user.id,
organization: tournamentTeam?.tournament.organizer,
}),
"Not tournament admin"
);
// TODO: validate tournament has not started
switch (data._action) {
case "CHECK_IN": {
await checkIn({ teamId: data.teamId, userId: user.id });
@@ -46,8 +58,12 @@ export const action: ActionFunction = async ({ context, request }) => {
await checkOut({ teamId: data.teamId, userId: user.id });
break;
}
case "UNREGISTER": {
await TournamentTeam.unregister(data.teamId);
break;
}
default: {
const exhaustive: never = data;
const exhaustive: never = data._action;
throw new Response(`Unknown action: ${JSON.stringify(exhaustive)}`, {
status: 400,
});
@@ -88,7 +104,8 @@ function RowContents({
team: Unpacked<FindTournamentByNameForUrlI["teams"]>;
}) {
const [, parentRoute] = useMatches();
const { checkInStartTime } = parentRoute.data as FindTournamentByNameForUrlI;
const tournament = parentRoute.data as FindTournamentByNameForUrlI;
const unregisterFormRef = useRef<HTMLFormElement>(null);
const playersLacking = (() => {
if (team.members.length >= TOURNAMENT_TEAM_ROSTER_MIN_SIZE) return;
@@ -96,32 +113,61 @@ function RowContents({
return TOURNAMENT_TEAM_ROSTER_MIN_SIZE - team.members.length;
})();
const handleUnregisterButtonClick = () => {
invariant(unregisterFormRef.current, "!unregisterFormRef.current");
if (window.confirm(`Delete ${team.name} from the tournament? (No undo)`)) {
unregisterFormRef.current.submit();
}
};
return (
<div className="tournament__manage__teams-list-row">
<div>{team.name}</div>
<div>
{new Date(team.createdAt).toLocaleString("en-US", {
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
})}
</div>
<div>
{!checkInHasStarted(checkInStartTime) ? null : team.checkedInTime ? (
<CheckOutButton teamId={team.id} />
) : !playersLacking ? (
<CheckInButton teamId={team.id} />
) : (
<div className="text-xs">
<i>
{playersLacking} more {playersLacking > 1 ? "players" : "player"}{" "}
required
</i>
</div>
<>
<Form className="hidden" ref={unregisterFormRef} method="post">
<input type="hidden" name="_action" value="UNREGISTER" />
<input type="hidden" name="teamId" value={team.id} />
</Form>
<div className="tournament__manage__teams-list-row">
{tournamentHasNotStarted(tournament) && (
<Button
type="button"
name="_action"
value="UNREGISTER"
variant="minimal-destructive"
title="Delete team from the tournament"
aria-label="Delete team from the tournament"
onClick={handleUnregisterButtonClick}
>
<TrashIcon className="tournament__manage__trash-icon" />
</Button>
)}
<div>{team.name}</div>
<div>
{new Date(team.createdAt).toLocaleString("en-US", {
month: "numeric",
day: "numeric",
hour: "numeric",
minute: "numeric",
})}
</div>
<div>
{!checkInHasStarted(
tournament.checkInStartTime
) ? null : team.checkedInTime ? (
<CheckOutButton teamId={team.id} />
) : !playersLacking ? (
<CheckInButton teamId={team.id} />
) : (
<div className="text-xs">
<i>
{playersLacking} more{" "}
{playersLacking > 1 ? "players" : "player"} required
</i>
</div>
)}
</div>
</div>
</div>
</>
);
}

View File

@@ -764,6 +764,10 @@ hr {
visibility: hidden;
}
.hidden {
display: none;
}
.width-full {
width: 100%;
}

View File

@@ -12,11 +12,16 @@
border-radius: var(--rounded);
column-gap: var(--s-1);
font-size: var(--fonts-sm);
grid-template-columns: 4fr 3fr 3fr;
grid-template-columns: 0.5fr 4fr 3fr 3fr;
list-style: none;
row-gap: var(--s-1-5);
}
.tournament__manage__trash-icon {
width: 1rem;
margin-block-end: 1px;
}
/* TODO: this does not work correctly when variation = check-in */
.tournament__manage__teams-list-row:nth-child(even) {
background-color: var(--bg-lighter-transparent);