From a655db05a46ccd917bb473c4048d4e169bae80d1 Mon Sep 17 00:00:00 2001 From: "Kalle (Sendou)" <38327916+Sendouc@users.noreply.github.com> Date: Sun, 24 Jan 2021 19:18:40 +0200 Subject: [PATCH] join ladder team backend frontend --- components/play/RegisterHeader.tsx | 147 ++++++++++++++++++ hooks/play.ts | 5 + lib/constants.ts | 1 + pages/api/play/teams/index.ts | 68 ++++++++ pages/api/play/teams/join.ts | 53 +++++++ pages/api/play/teams/leave.ts | 35 +++++ pages/play/index.tsx | 23 +++ pages/play/join.tsx | 49 ++++++ prisma/queries/getAllLadderRegisteredTeams.ts | 34 ++++ 9 files changed, 415 insertions(+) create mode 100644 components/play/RegisterHeader.tsx create mode 100644 hooks/play.ts create mode 100644 pages/api/play/teams/index.ts create mode 100644 pages/api/play/teams/join.ts create mode 100644 pages/api/play/teams/leave.ts create mode 100644 pages/play/index.tsx create mode 100644 pages/play/join.tsx create mode 100644 prisma/queries/getAllLadderRegisteredTeams.ts diff --git a/components/play/RegisterHeader.tsx b/components/play/RegisterHeader.tsx new file mode 100644 index 000000000..0b59de62b --- /dev/null +++ b/components/play/RegisterHeader.tsx @@ -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 = ({}) => { + 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 ( + + {ownTeam ? ( + + + {ownTeam.roster.length >= 4 ? ( + Team fully registered + ) : ( + Add players to complete registration + )} + + + {ownTeam.inviteCode && ( + + + + )} + + {ownTeam.roster.map((member) => ( + + ))} + + + {ownTeam.ownerId === user.id ? ( + + ) : ( + + )} + + + + ) : ( + + )} + + ); +}; + +export default RegisterHeader; diff --git a/hooks/play.ts b/hooks/play.ts new file mode 100644 index 000000000..a15378c9d --- /dev/null +++ b/hooks/play.ts @@ -0,0 +1,5 @@ +import { GetAllLadderRegisteredTeamsData } from "prisma/queries/getAllLadderRegisteredTeams"; +import useSWR from "swr"; + +export const useLadderTeams = () => + useSWR("/api/play/teams"); diff --git a/lib/constants.ts b/lib/constants.ts index 9d913e0ac..298a7dca6 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -8,3 +8,4 @@ export const SALMON_RUN_ADMIN_DISCORD_IDS = [ ]; export const TEAM_ROSTER_LIMIT = 10; +export const LADDER_ROSTER_LIMIT = 4; diff --git a/pages/api/play/teams/index.ts b/pages/api/play/teams/index.ts new file mode 100644 index 000000000..9f6815705 --- /dev/null +++ b/pages/api/play/teams/index.ts @@ -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; diff --git a/pages/api/play/teams/join.ts b/pages/api/play/teams/join.ts new file mode 100644 index 000000000..b25edd534 --- /dev/null +++ b/pages/api/play/teams/join.ts @@ -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; diff --git a/pages/api/play/teams/leave.ts b/pages/api/play/teams/leave.ts new file mode 100644 index 000000000..eb1e0b103 --- /dev/null +++ b/pages/api/play/teams/leave.ts @@ -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; diff --git a/pages/play/index.tsx b/pages/play/index.tsx new file mode 100644 index 000000000..9c7c5d353 --- /dev/null +++ b/pages/play/index.tsx @@ -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 ( + + + + Next event: testing + + + + ); +}; + +export default PlayPage; diff --git a/pages/play/join.tsx b/pages/play/join.tsx new file mode 100644 index 000000000..f4c99fe0a --- /dev/null +++ b/pages/play/join.tsx @@ -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 {i18n._(statusMessage[joiningStatus])}; +}; + +export default JoinLadderTeamPage; diff --git a/prisma/queries/getAllLadderRegisteredTeams.ts b/prisma/queries/getAllLadderRegisteredTeams.ts new file mode 100644 index 000000000..761428029 --- /dev/null +++ b/prisma/queries/getAllLadderRegisteredTeams.ts @@ -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, + })); +};