mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-07-18 16:43:56 -05:00
Can unregister from tournament before start
This commit is contained in:
parent
695894b1de
commit
93a7ae9c1e
|
|
@ -1,11 +1,15 @@
|
|||
import { Form, useTransition } from "@remix-run/react";
|
||||
import { Form, useMatches, useTransition } from "@remix-run/react";
|
||||
import { tournamentHasNotStarted } from "~/core/tournament/validators";
|
||||
import { useUser } from "~/hooks/common";
|
||||
import { FindTournamentByNameForUrlI } from "~/services/tournament";
|
||||
import { Avatar } from "../Avatar";
|
||||
import { Button } from "../Button";
|
||||
import { SubmitButton } from "../SubmitButton";
|
||||
|
||||
export function TeamRoster({
|
||||
team,
|
||||
deleteMode: deleteMode,
|
||||
showUnregister = false,
|
||||
deleteMode = false,
|
||||
}: {
|
||||
team: {
|
||||
id: string;
|
||||
|
|
@ -21,19 +25,51 @@ export function TeamRoster({
|
|||
}[];
|
||||
};
|
||||
deleteMode?: boolean;
|
||||
showUnregister?: boolean;
|
||||
}) {
|
||||
const [, parentRoute] = useMatches();
|
||||
const tournament = parentRoute.data as FindTournamentByNameForUrlI;
|
||||
const user = useUser();
|
||||
|
||||
const showDeleteButtons = (userToDeleteId: string) => {
|
||||
// TODO:
|
||||
const tournamentHasStarted = false;
|
||||
return (
|
||||
tournamentHasNotStarted(tournament) &&
|
||||
deleteMode &&
|
||||
userToDeleteId !== user?.id
|
||||
);
|
||||
};
|
||||
|
||||
return !tournamentHasStarted && deleteMode && userToDeleteId !== user?.id;
|
||||
const showUnregisterButton = () => {
|
||||
return tournamentHasNotStarted(tournament) && showUnregister;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="teams-tab__team-container">
|
||||
<div className="teams-tab__team-name">{team.name}</div>
|
||||
<div className="teams-tab__team-name">
|
||||
{team.name}
|
||||
{showUnregisterButton() ? (
|
||||
<Form method="post" className="flex justify-center">
|
||||
<input type="hidden" name="_action" value="UNREGISTER" />
|
||||
<input type="hidden" name="teamId" value={team.id} />
|
||||
<SubmitButton
|
||||
actionType="UNREGISTER"
|
||||
tiny
|
||||
variant="minimal-destructive"
|
||||
loadingText="Unregistering..."
|
||||
onClick={(e) => {
|
||||
if (
|
||||
!confirm(`Unregister ${team.name} from ${tournament.name}?`)
|
||||
) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
data-cy="unregister-button"
|
||||
>
|
||||
Unregister
|
||||
</SubmitButton>
|
||||
</Form>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="teams-tab__members-container">
|
||||
{team.members
|
||||
.sort((a, b) => Number(b.captain) - Number(a.captain))
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
redirect,
|
||||
} from "@remix-run/node";
|
||||
import { useActionData, useLoaderData, useLocation } from "@remix-run/react";
|
||||
import invariant from "tiny-invariant";
|
||||
import { z } from "zod";
|
||||
import { AddPlayers } from "~/components/AddPlayers";
|
||||
import { Alert } from "~/components/Alert";
|
||||
|
|
@ -16,6 +17,7 @@ import { TOURNAMENT_TEAM_ROSTER_MAX_SIZE } from "~/constants";
|
|||
import {
|
||||
isCaptainOfTheTeam,
|
||||
teamHasNotCheckedIn,
|
||||
tournamentHasNotStarted,
|
||||
tournamentTeamIsNotFull,
|
||||
} from "~/core/tournament/validators";
|
||||
import * as Tournament from "~/models/Tournament.server";
|
||||
|
|
@ -42,6 +44,10 @@ const actionSchema = z.union([
|
|||
userId: z.string().uuid(),
|
||||
teamId: z.string().uuid(),
|
||||
}),
|
||||
z.object({
|
||||
_action: z.literal("UNREGISTER"),
|
||||
teamId: z.string().uuid(),
|
||||
}),
|
||||
]);
|
||||
|
||||
type ActionData = {
|
||||
|
|
@ -59,18 +65,14 @@ export const action: ActionFunction = async ({
|
|||
});
|
||||
const user = requireUser(context);
|
||||
|
||||
const tournamentTeam = await TournamentTeam.findById(data.teamId);
|
||||
validate(tournamentTeam, "Invalid tournament team id");
|
||||
validate(isCaptainOfTheTeam(user, tournamentTeam), "Not captain of the team");
|
||||
|
||||
switch (data._action) {
|
||||
case "ADD_PLAYER": {
|
||||
try {
|
||||
const tournamentTeam = await TournamentTeam.findById(data.teamId);
|
||||
|
||||
// TODO: Validate if tournament already started / concluded (depending on if tournament allows mid-event roster additions)
|
||||
validate(tournamentTeam, "Invalid tournament team id");
|
||||
validate(tournamentTeamIsNotFull(tournamentTeam), "Team is full");
|
||||
validate(
|
||||
isCaptainOfTheTeam(user, tournamentTeam),
|
||||
"Not captain of the team"
|
||||
);
|
||||
|
||||
await TournamentTeamMember.joinTeam({
|
||||
tournamentId: tournamentTeam.tournament.id,
|
||||
|
|
@ -91,14 +93,7 @@ export const action: ActionFunction = async ({
|
|||
return { ok: "ADD_PLAYER" };
|
||||
}
|
||||
case "DELETE_PLAYER": {
|
||||
const tournamentTeam = await TournamentTeam.findById(data.teamId);
|
||||
|
||||
validate(tournamentTeam, "Invalid team id");
|
||||
validate(data.userId !== user.id, "Can't remove self");
|
||||
validate(
|
||||
isCaptainOfTheTeam(user, tournamentTeam),
|
||||
"Not captain of the team"
|
||||
);
|
||||
validate(
|
||||
teamHasNotCheckedIn(tournamentTeam),
|
||||
"Can't remove players after checking in"
|
||||
|
|
@ -111,6 +106,16 @@ export const action: ActionFunction = async ({
|
|||
|
||||
return { ok: "DELETE_PLAYER" };
|
||||
}
|
||||
case "UNREGISTER": {
|
||||
const tournament = await Tournament.findById(tournamentTeam.tournamentId);
|
||||
invariant(tournament, "!tournament");
|
||||
|
||||
validate(tournamentHasNotStarted(tournament), "Tournament is ongoing");
|
||||
|
||||
await TournamentTeam.unregister(data.teamId);
|
||||
|
||||
return { ok: "UNREGISTER" };
|
||||
}
|
||||
default: {
|
||||
const exhaustive: never = data;
|
||||
throw new Response(`Unknown action: ${JSON.stringify(exhaustive)}`, {
|
||||
|
|
@ -173,7 +178,11 @@ export default function ManageTeamPage() {
|
|||
</Alert>
|
||||
)}
|
||||
<div className="tournament__manage-team__roster-container">
|
||||
<TeamRoster team={ownTeam} deleteMode={!ownTeam.checkedInTime} />
|
||||
<TeamRoster
|
||||
team={ownTeam}
|
||||
deleteMode={!ownTeam.checkedInTime}
|
||||
showUnregister
|
||||
/>
|
||||
</div>
|
||||
{ownTeam.members.length < TOURNAMENT_TEAM_ROSTER_MAX_SIZE && (
|
||||
<AddPlayers
|
||||
|
|
|
|||
|
|
@ -242,10 +242,12 @@
|
|||
|
||||
.teams-tab__team-name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: center;
|
||||
border-block-end: 2px solid var(--theme);
|
||||
font-size: var(--fonts-lg);
|
||||
font-weight: var(--extra-bold);
|
||||
gap: var(--s-2);
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,14 +7,17 @@ describe("Before tournament starts", () => {
|
|||
).as("tournaments");
|
||||
});
|
||||
|
||||
it("Registers a new team", () => {
|
||||
function registerToTournament(teamName: string) {
|
||||
cy.logIn("sendou");
|
||||
cy.visit("/to/sendou/in-the-zone-x");
|
||||
cy.title().should("include", "In The Zone X");
|
||||
cy.getCy("register-button").click();
|
||||
cy.getCy("team-name-input").type("Team Olive");
|
||||
cy.getCy("team-name-input").type(teamName);
|
||||
cy.getCy("register-submit-button").click();
|
||||
cy.contains("Team name already taken");
|
||||
}
|
||||
|
||||
it("Registers a new team", () => {
|
||||
registerToTournament("Team Olive");
|
||||
|
||||
cy.wait("@tournaments");
|
||||
cy.getCy("team-name-input").clear().type("Team Olive V2");
|
||||
|
|
@ -33,4 +36,12 @@ describe("Before tournament starts", () => {
|
|||
"map-pool__stage-image-disabled"
|
||||
);
|
||||
});
|
||||
|
||||
it("Can unregister from tournament", () => {
|
||||
registerToTournament("Team Olive V2");
|
||||
|
||||
cy.on("window:confirm", () => true);
|
||||
cy.getCy("unregister-button").click();
|
||||
cy.getCy("register-button");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user