diff --git a/components/t/TeamProfileModal.tsx b/components/t/TeamProfileModal.tsx new file mode 100644 index 000000000..9c62ffad9 --- /dev/null +++ b/components/t/TeamProfileModal.tsx @@ -0,0 +1,160 @@ +import { + Box, + Button, + FormControl, + FormErrorMessage, + FormLabel, + Input, + InputGroup, + InputLeftAddon, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + useToast, +} from "@chakra-ui/react"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { t, Trans } from "@lingui/macro"; +import { useLingui } from "@lingui/react"; +import MarkdownTextarea from "components/common/MarkdownTextarea"; +import { getToastOptions } from "lib/getToastOptions"; +import { sendData } from "lib/postData"; +import { + teamSchema, + TEAM_BIO_CHARACTER_LIMIT, + TEAM_RECRUITING_POST_CHARACTER_LIMIT, +} from "lib/validators/team"; +import { GetTeamData } from "prisma/queries/getTeam"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { FaTwitter } from "react-icons/fa"; +import { FiEdit } from "react-icons/fi"; +import { mutate } from "swr"; +import * as z from "zod"; + +interface Props { + team: NonNullable; +} + +type FormData = z.infer; + +const TeamProfileModal: React.FC = ({ team }) => { + const { i18n } = useLingui(); + const toast = useToast(); + const [isOpen, setIsOpen] = useState(false); + const [sending, setSending] = useState(false); + + const { handleSubmit, errors, register, watch } = useForm({ + resolver: zodResolver(teamSchema), + defaultValues: team, + }); + + const watchBio = watch("bio", team.bio); + const watchRecruitingPost = watch("recruitingPost", team.recruitingPost); + + const onSubmit = async (formData: FormData) => { + setSending(true); + + const success = await sendData("PUT", "/api/teams", formData); + setSending(false); + if (!success) return; + + mutate(`/api/teams/${team.id}`); + + toast(getToastOptions(t`Team profile updated`, "success")); + + setIsOpen(false); + }; + + return ( + <> + + {isOpen && ( + setIsOpen(false)} + size="xl" + closeOnOverlayClick={false} + > + + + + Editing team profile + + +
+ + + + {" "} + Twitter name + + + + + + + {errors.twitterName?.message} + + + + + + + + + + + + +
+
+
+
+ )} + + ); +}; + +export default TeamProfileModal; diff --git a/lib/validators/team.ts b/lib/validators/team.ts index 60a4822da..d8c64147b 100644 --- a/lib/validators/team.ts +++ b/lib/validators/team.ts @@ -1,7 +1,7 @@ import * as z from "zod"; -const TEAM_BIO_CHARACTER_LIMIT = 7000; -const TEAM_RECRUITING_POST_CHARACTER_LIMIT = 2000; +export const TEAM_BIO_CHARACTER_LIMIT = 7000; +export const TEAM_RECRUITING_POST_CHARACTER_LIMIT = 2000; export const teamSchema = z.object({ twitterName: z.string().max(15).optional().nullable(), diff --git a/pages/api/teams/index.ts b/pages/api/teams/index.ts index 5ad6bfc54..591456f3d 100644 --- a/pages/api/teams/index.ts +++ b/pages/api/teams/index.ts @@ -1,5 +1,6 @@ import { getMySession } from "lib/getMySession"; import { makeNameUrlFriendly } from "lib/makeNameUrlFriendly"; +import { teamSchema } from "lib/validators/team"; import { NextApiRequest, NextApiResponse } from "next"; import prisma from "prisma/client"; import { v4 as uuidv4 } from "uuid"; @@ -12,6 +13,9 @@ const teamsHandler = async (req: NextApiRequest, res: NextApiResponse) => { case "DELETE": await deleteHandler(req, res); break; + case "PUT": + await putHandler(req, res); + break; default: res.status(405).end(); } @@ -77,4 +81,22 @@ async function deleteHandler(req: NextApiRequest, res: NextApiResponse) { res.status(200).end(); } +async function putHandler(req: NextApiRequest, res: NextApiResponse) { + const parsed = teamSchema.safeParse(req.body); + + if (!parsed.success) { + return res.status(400).end(); + } + + const user = await getMySession(req); + if (!user) return res.status(401).end(); + + const team = await prisma.team.findUnique({ where: { captainId: user.id } }); + if (!team) return res.status(400).end(); + + await prisma.team.update({ where: { id: team.id }, data: parsed.data }); + + res.status(200).end(); +} + export default teamsHandler; diff --git a/pages/t/[team].tsx b/pages/t/[team].tsx index a85675808..f3de1e51b 100644 --- a/pages/t/[team].tsx +++ b/pages/t/[team].tsx @@ -1,8 +1,23 @@ -import { Box, Button, Heading, useToast } from "@chakra-ui/react"; +import { + Box, + Button, + Divider, + Flex, + Heading, + useToast, + Wrap, + WrapItem, +} from "@chakra-ui/react"; import { t, Trans } from "@lingui/macro"; +import Markdown from "components/common/Markdown"; import MyContainer from "components/common/MyContainer"; +import Section from "components/common/Section"; +import SubTextCollapse from "components/common/SubTextCollapse"; +import UserAvatar from "components/common/UserAvatar"; +import WeaponImage from "components/common/WeaponImage"; import TeamManagementModal from "components/t/TeamManagementModal"; -import { getEmojiFlag } from "countries-list"; +import TeamProfileModal from "components/t/TeamProfileModal"; +import { countries, getEmojiFlag } from "countries-list"; import { getToastOptions } from "lib/getToastOptions"; import { sendData } from "lib/postData"; import useUser from "lib/useUser"; @@ -47,7 +62,7 @@ const TeamPage: React.FC = (props) => { {team.name} - + {/* {team.roster .reduce((acc: [string, number][], cur) => { if (!cur.profile?.country) return acc; @@ -62,12 +77,17 @@ const TeamPage: React.FC = (props) => { }, []) .sort((a, b) => b[1] - a[1]) .map(([country]) => getEmojiFlag(country))} - + */} {user?.id === team.captainId && ( - teamMember.id !== user.id)} - teamId={team.id} - /> + <> + teamMember.id !== user.id + )} + teamId={team.id} + /> + + )} {user && user.id !== team.captainId && @@ -82,6 +102,58 @@ const TeamPage: React.FC = (props) => { Leave team )} + + {team.bio && } + {team.recruitingPost && ( + + + + )} + {(team.bio || team.recruitingPost) && } + + {team.roster + .sort( + (a, b) => + Number(a.id === team.captainId) - Number(b.id === team.captainId) + ) + .map((user) => ( + +
+ + {`${user.username}#${user.discriminator}`} + {user.profile?.country && ( + + + {getEmojiFlag(user.profile.country)}{" "} + + { + Object.entries(countries).find( + ([key]) => key === user.profile!.country + )![1].name + } + + )} + {user.profile?.weaponPool.length && ( + + {user.profile.weaponPool.map((wpn) => ( + + + + ))} + + )} +
+
+ ))} +
); };