join ladder team backend frontend

This commit is contained in:
Kalle (Sendou)
2021-01-24 19:18:40 +02:00
parent 1e98701c18
commit a655db05a4
9 changed files with 415 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
import {
Alert,
AlertDescription,
AlertTitle,
Box,
Button,
HStack,
Input,
useToast,
} from "@chakra-ui/react";
import { t, Trans } from "@lingui/macro";
import UserAvatar from "components/common/UserAvatar";
import { useLadderTeams } from "hooks/play";
import { getToastOptions } from "lib/getToastOptions";
import { sendData } from "lib/postData";
import useUser from "lib/useUser";
import { useState } from "react";
import { FiTrash } from "react-icons/fi";
interface Props {}
const RegisterHeader: React.FC<Props> = ({}) => {
const toast = useToast();
const [user] = useUser();
const { data, mutate } = useLadderTeams();
const [sending, setSending] = useState(false);
const createNewTeam = async () => {
setSending(true);
const success = await sendData("POST", "/api/play/teams");
setSending(false);
if (!success) return;
mutate();
toast(getToastOptions(t`Team created`, "success"));
};
const deleteTeam = async () => {
if (!window.confirm(t`Delete registration?`)) return;
setSending(true);
const success = await sendData("DELETE", "/api/play/teams");
setSending(false);
if (!success) return;
mutate();
toast(getToastOptions(t`Registration canceled`, "success"));
};
const leaveTeam = async () => {
if (!window.confirm(t`Leave team?`)) return;
setSending(true);
const success = await sendData("POST", "/api/play/teams/leave");
setSending(false);
if (!success) return;
mutate();
toast(getToastOptions(t`Team left`, "success"));
};
if (!user) return null;
const ownTeam = data?.find((team) =>
team.roster.some((member) => member.id === user.id)
);
const ownTeamFullyRegisted = !!ownTeam && ownTeam.roster.length >= 4;
return (
<Box>
{ownTeam ? (
<Alert
status={ownTeamFullyRegisted ? "success" : "warning"}
variant="subtle"
flexDirection="column"
alignItems="center"
justifyContent="center"
textAlign="center"
p={6}
mt={4}
>
<AlertTitle mb={1} fontSize="lg">
{ownTeam.roster.length >= 4 ? (
<Trans>Team fully registered</Trans>
) : (
<Trans>Add players to complete registration</Trans>
)}
</AlertTitle>
<AlertDescription>
{ownTeam.inviteCode && (
<Box>
<Input
name="code"
value={`https://sendou.ink/play/join?code=${ownTeam.inviteCode}`}
readOnly
/>
</Box>
)}
<HStack my={2} justify="center">
{ownTeam.roster.map((member) => (
<UserAvatar user={member} />
))}
</HStack>
<Box mt={4}>
{ownTeam.ownerId === user.id ? (
<Button
size="sm"
variant="outline"
leftIcon={<FiTrash />}
colorScheme="red"
onClick={deleteTeam}
isLoading={sending}
>
<Trans>Delete registration</Trans>
</Button>
) : (
<Button
size="sm"
variant="outline"
colorScheme="red"
onClick={leaveTeam}
isLoading={sending}
>
<Trans>Leave team</Trans>
</Button>
)}
</Box>
</AlertDescription>
</Alert>
) : (
<Button
size="sm"
variant="outline"
onClick={createNewTeam}
isLoading={sending}
>
<Trans>Register new team</Trans>
</Button>
)}
</Box>
);
};
export default RegisterHeader;

5
hooks/play.ts Normal file
View File

@@ -0,0 +1,5 @@
import { GetAllLadderRegisteredTeamsData } from "prisma/queries/getAllLadderRegisteredTeams";
import useSWR from "swr";
export const useLadderTeams = () =>
useSWR<GetAllLadderRegisteredTeamsData>("/api/play/teams");

View File

@@ -8,3 +8,4 @@ export const SALMON_RUN_ADMIN_DISCORD_IDS = [
];
export const TEAM_ROSTER_LIMIT = 10;
export const LADDER_ROSTER_LIMIT = 4;

View File

@@ -0,0 +1,68 @@
import { getMySession } from "lib/getMySession";
import { NextApiRequest, NextApiResponse } from "next";
import prisma from "prisma/client";
import { getAllLadderRegisteredTeams } from "prisma/queries/getAllLadderRegisteredTeams";
import { v4 as uuidv4 } from "uuid";
const teamsHandler = async (req: NextApiRequest, res: NextApiResponse) => {
switch (req.method) {
case "GET":
await getHandler(req, res);
break;
case "POST":
await postHandler(req, res);
break;
case "DELETE":
await deleteHandler(req, res);
break;
default:
res.status(405).end();
}
};
async function getHandler(req: NextApiRequest, res: NextApiResponse) {
const user = await getMySession(req);
const teams = await getAllLadderRegisteredTeams(user?.id);
res.status(200).json(teams);
}
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
const user = await getMySession(req);
if (!user) return res.status(401).end();
const userFromDb = await prisma.user.findUnique({
where: { id: user.id },
select: { ladderTeamId: true },
});
if (!userFromDb || userFromDb.ladderTeamId) {
return res.status(400).end();
}
await prisma.ladderRegisteredTeam.create({
data: {
inviteCode: uuidv4(),
ownerId: user.id,
roster: { connect: { id: user.id } },
},
});
res.status(200).end();
}
async function deleteHandler(req: NextApiRequest, res: NextApiResponse) {
const user = await getMySession(req);
if (!user) return res.status(401).end();
const team = await prisma.ladderRegisteredTeam.findUnique({
where: { ownerId: user.id },
});
if (!team) return res.status(400).end();
await prisma.ladderRegisteredTeam.delete({ where: { id: team.id } });
res.status(200).end();
}
export default teamsHandler;

View File

@@ -0,0 +1,53 @@
import { LADDER_ROSTER_LIMIT } from "lib/constants";
import { getMySession } from "lib/getMySession";
import { NextApiRequest, NextApiResponse } from "next";
import prisma from "prisma/client";
const joinHandler = async (req: NextApiRequest, res: NextApiResponse) => {
switch (req.method) {
case "POST":
await postHandler(req, res);
break;
default:
res.status(405).end();
}
};
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
const user = await getMySession(req);
if (!user) return res.status(401).end();
const { code } = req.body;
if (typeof code !== "string") {
return res.status(400).end();
}
const userFromDb = await prisma.user.findUnique({ where: { id: user.id } });
if (userFromDb?.ladderTeamId) {
return res.status(400).json({ message: "Already in a team" });
}
const team = await prisma.ladderRegisteredTeam.findUnique({
where: { inviteCode: code },
include: { roster: true },
});
if (!team) {
return res.status(400).json({ message: "Invalid invite code" });
}
if (team.roster.length >= LADDER_ROSTER_LIMIT) {
return res.status(400).json({ message: "Team already has 4 members" });
}
await prisma.ladderRegisteredTeam.update({
where: { id: team.id },
data: { roster: { connect: { id: user.id } } },
});
res.status(200).end();
}
export default joinHandler;

View File

@@ -0,0 +1,35 @@
import { getMySession } from "lib/getMySession";
import { NextApiRequest, NextApiResponse } from "next";
import prisma from "prisma/client";
const playLeaveHandler = async (req: NextApiRequest, res: NextApiResponse) => {
switch (req.method) {
case "POST":
await postHandler(req, res);
break;
default:
res.status(405).end();
}
};
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
const user = await getMySession(req);
if (!user) return res.status(401).end();
const userFromDb = await prisma.user.findUnique({
where: { id: user.id },
include: { ladderTeam: true },
});
if (!userFromDb?.ladderTeam || userFromDb.ladderTeam.ownerId === user.id) {
return res.status(400).end();
}
await prisma.user.update({
where: { id: user.id },
data: { ladderTeam: { disconnect: true } },
});
res.status(200).end();
}
export default playLeaveHandler;

23
pages/play/index.tsx Normal file
View File

@@ -0,0 +1,23 @@
import { Box } from "@chakra-ui/react";
import { t } from "@lingui/macro";
import Breadcrumbs from "components/common/Breadcrumbs";
import MyContainer from "components/common/MyContainer";
import RegisterHeader from "components/play/RegisterHeader";
import { useLadderTeams } from "hooks/play";
const PlayPage = () => {
const { data } = useLadderTeams();
console.log({ data });
return (
<MyContainer>
<Breadcrumbs pages={[{ name: t`Play` }]} />
<Box fontSize="lg" fontWeight="bold">
Next event: testing
</Box>
<RegisterHeader />
</MyContainer>
);
};
export default PlayPage;

49
pages/play/join.tsx Normal file
View File

@@ -0,0 +1,49 @@
import { t } from "@lingui/macro";
import { useLingui } from "@lingui/react";
import MyContainer from "components/common/MyContainer";
import { useLadderTeams } from "hooks/play";
import { sendData } from "lib/postData";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
const statusMessage = {
LOADING: t`Please wait...`,
ERROR: t`There is an error that needs to be fixed before you can join the team.`,
REDIRECTING: t`Joined the team. Redirecting.`,
} as const;
const JoinLadderTeamPage = ({}) => {
const { i18n } = useLingui();
const router = useRouter();
const { mutate } = useLadderTeams();
const [joiningStatus, setJoiningStatus] = useState<
"LOADING" | "ERROR" | "REDIRECTING"
>("LOADING");
useEffect(() => {
const joinTeam = async () => {
const { code } = router.query;
if (typeof code !== "string" || typeof name !== "string") {
router.push("/404");
}
const success = await sendData("POST", "/api/play/teams/join", {
code,
});
if (!success) {
return setJoiningStatus("ERROR");
}
mutate();
setJoiningStatus("REDIRECTING");
router.push(`/play`);
};
joinTeam();
}, []);
return <MyContainer>{i18n._(statusMessage[joiningStatus])}</MyContainer>;
};
export default JoinLadderTeamPage;

View File

@@ -0,0 +1,34 @@
import { Prisma } from "@prisma/client";
import prisma from "prisma/client";
export type GetAllLadderRegisteredTeamsData = Prisma.PromiseReturnType<
typeof getAllLadderRegisteredTeams
>;
export const getAllLadderRegisteredTeams = async (userId?: number) => {
const teams = await prisma.ladderRegisteredTeam.findMany({
include: {
roster: {
select: {
id: true,
discordAvatar: true,
discordId: true,
discriminator: true,
username: true,
team: {
select: {
name: true,
nameForUrl: true,
twitterName: true,
},
},
},
},
},
});
return teams.map((team) => ({
...team,
inviteCode: team.ownerId === userId ? team.inviteCode : undefined,
}));
};