Join team action

This commit is contained in:
Kalle (Sendou) 2021-12-04 15:12:05 +02:00
parent 0a56f72d8b
commit 87e53abf42
6 changed files with 106 additions and 45 deletions

View File

@ -48,3 +48,6 @@ export const navItems = [
export const ADMIN_TEST_UUID = "846e12eb-d373-4002-a0c3-e23077e1c88c";
export const ADMIN_TEST_DISCORD_ID = "79237403620945920";
export const ADMIN_TEST_AVATAR = "fcfd65a3bea598905abb9ca25296816b";
export const TOURNAMENT_TEAM_ROSTER_MIN_SIZE = 4;
export const TOURNAMENT_TEAM_ROSTER_MAX_SIZE = 6;

View File

@ -6,16 +6,37 @@ import {
useNavigate,
useMatches,
} from "remix";
import type { LoaderFunction, LinksFunction } from "remix";
import { findTournamentWithInviteCodes } from "~/services/tournament";
import type { LoaderFunction, LinksFunction, ActionFunction } from "remix";
import {
FindTournamentByNameForUrlI,
findTournamentWithInviteCodes,
joinTeam,
} from "~/services/tournament";
import styles from "~/styles/tournament-join-team.css";
import invariant from "tiny-invariant";
import { getUser } from "~/utils";
import { getUser, requireUser } from "~/utils";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
};
export const action: ActionFunction = async ({ request, context, params }) => {
const formData = await request.formData();
const inviteCode = formData.get("inviteCode");
const tournamentId = formData.get("tournamentId");
invariant(typeof inviteCode === "string", "Invalid type for inviteCode.");
invariant(
typeof tournamentId === "string",
"Invalid type for tournament id."
);
const user = requireUser(context);
await joinTeam({ inviteCode, userId: user.id, tournamentId: tournamentId });
return redirect(`/to/${params.organization}/${params.tournament}/teams`);
};
const INVITE_CODE_LENGTH = 36;
type ResponseObject =
@ -24,7 +45,7 @@ type ResponseObject =
| { status: "LOG_IN" }
| { status: "ALREADY_JOINED"; teamName: string }
| { status: "INVALID" }
| { status: "OK"; teamName: string; inviterName: string };
| { status: "OK"; teamName: string; inviterName: string; inviteCode: string };
const typedJson = (args: ResponseObject) => json(args);
@ -87,6 +108,7 @@ export const loader: LoaderFunction = async ({ request, params, context }) => {
return typedJson({
status: "OK",
teamName: teamInvitedTo.name,
inviteCode,
inviterName: teamInvitedTo.members.find(({ captain }) => captain)!.member
.discordName,
});
@ -105,6 +127,7 @@ export default function JoinTeamPage() {
function Contents({ data }: { data: ResponseObject }) {
const navigate = useNavigate();
const [, parentRoute] = useMatches();
const parentRouteData = parentRoute.data as FindTournamentByNameForUrlI;
switch (data.status) {
case "NO_CODE":
@ -140,6 +163,12 @@ function Contents({ data }: { data: ResponseObject }) {
{data.inviterName} invited you to join {data.teamName} for this
tournament. Accept invite?
<Form method="post">
<input
type="hidden"
name="tournamentId"
value={parentRouteData.id}
/>
<input type="hidden" name="inviteCode" value={data.inviteCode} />
<div className="tournament__join-team__buttons">
<button type="submit">Join</button>
<button

View File

@ -2,6 +2,10 @@ import * as React from "react";
import { LinksFunction, useMatches } from "remix";
import { Alert } from "~/components/Alert";
import { TeamRoster } from "~/components/tournament/TeamRoster";
import {
TOURNAMENT_TEAM_ROSTER_MAX_SIZE,
TOURNAMENT_TEAM_ROSTER_MIN_SIZE,
} from "~/constants";
import { FindTournamentByNameForUrlI } from "~/services/tournament";
import styles from "~/styles/tournament-manage-roster.css";
@ -43,7 +47,8 @@ export default function ManageRosterPage() {
<div className="tournament__manage-roster">
{ownTeam.members.length < 4 && (
<Alert type="warning">
You need at least 4 players in your roster to play (max 6)
You need at least {TOURNAMENT_TEAM_ROSTER_MIN_SIZE} players in your
roster to play (max {TOURNAMENT_TEAM_ROSTER_MAX_SIZE})
</Alert>
)}
<TeamRoster team={ownTeam} />

View File

@ -1,5 +1,4 @@
import { Prisma } from ".prisma/client";
import * as React from "react";
import {
ActionFunction,
Form,

View File

@ -1,4 +1,4 @@
import { useMatches, LinksFunction } from "remix";
import { useMatches } from "remix";
import type { FindTournamentByNameForUrlI } from "~/services/tournament";
import { TeamRoster } from "~/components/tournament/TeamRoster";
@ -8,48 +8,11 @@ export default function TeamsTab() {
if (!teams.length) return null;
const sortedTeams = teams
// TODO: user id here
.sort(sortOwnTeamsAndFullTeamsFirst(""))
.map((team) => {
return {
...team,
members: team.members.sort(sortCaptainFirst),
};
});
return (
<div className="teams-tab">
{sortedTeams.map((team) => (
{teams.map((team) => (
<TeamRoster team={team} key={team.id} />
))}
</div>
);
}
function sortCaptainFirst(a: { captain: boolean }, b: { captain: boolean }) {
return Number(b.captain) - Number(a.captain);
}
function sortOwnTeamsAndFullTeamsFirst(userId?: string) {
return function (
a: { members: { member: { id: string } }[] },
b: { members: { member: { id: string } }[] }
) {
if (userId) {
const aSortValue = Number(
a.members.some(({ member }) => userId === member.id)
);
const bSortValue = Number(
b.members.some(({ member }) => userId === member.id)
);
if (aSortValue !== bSortValue) return bSortValue - aSortValue;
}
const aSortValue = a.members.length >= 4 ? 1 : 0;
const bSortValue = b.members.length >= 4 ? 1 : 0;
return bSortValue - aSortValue;
};
}

View File

@ -1,5 +1,9 @@
import { Prisma } from ".prisma/client";
import { json } from "remix";
import {
TOURNAMENT_TEAM_ROSTER_MAX_SIZE,
TOURNAMENT_TEAM_ROSTER_MIN_SIZE,
} from "~/constants";
import { Serialized } from "~/utils";
import { db } from "~/utils/db.server";
@ -87,6 +91,31 @@ export async function findTournamentByNameForUrl({
})),
};
if (userId) {
result.teams.sort((teamA, teamB) => {
// show team the user is member of first
let aSortValue = Number(
teamB.members.some(({ member }) => member.id === userId)
);
let bSortValue = Number(
teamA.members.some(({ member }) => member.id === userId)
);
if (aSortValue !== bSortValue) return aSortValue - bSortValue;
// TODO: show stronger teams first
// otherwise let's show full teams first
aSortValue = Number(
teamB.members.length >= TOURNAMENT_TEAM_ROSTER_MIN_SIZE
);
bSortValue = Number(
teamA.members.length >= TOURNAMENT_TEAM_ROSTER_MIN_SIZE
);
console.log({ aSortValue, bSortValue });
return aSortValue - bSortValue;
});
}
result.organizer.twitter = twitterToUrl(result.organizer.twitter);
result.organizer.discordInvite = discordInviteToUrl(
result.organizer.discordInvite
@ -182,3 +211,36 @@ export function createTournamentTeam({
},
});
}
export async function joinTeam({
tournamentId,
inviteCode,
userId,
}: {
tournamentId: string;
inviteCode: string;
userId: string;
}) {
const tournament = await db.tournament.findUnique({
where: { id: tournamentId },
include: { teams: { include: { members: true } } },
});
if (!tournament) throw json("Invalid tournament id", { status: 400 });
const tournamentTeamToJoin = tournament.teams.find(
(team) => team.inviteCode === inviteCode
);
if (!tournamentTeamToJoin) throw json("Invalid invite code", { status: 400 });
if (tournamentTeamToJoin.members.length >= TOURNAMENT_TEAM_ROSTER_MAX_SIZE) {
throw json("Team is already full", { status: 400 });
}
return db.tournamentTeamMember.create({
data: {
tournamentId,
teamId: tournamentTeamToJoin.id,
memberId: userId,
},
});
}