diff --git a/TODO.md b/TODO.md index 09ea95f19..d75eed6f5 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,7 @@ ### Before production - [x] Can log in -- [ ] Can update builds +- [x] Can update builds - [ ] Build Analyzer - [ ] Import /analyzer - [ ] Builds link to /analyzer diff --git a/components/builds/BuildCard.tsx b/components/builds/BuildCard.tsx index 3d92a9f52..e823b5343 100644 --- a/components/builds/BuildCard.tsx +++ b/components/builds/BuildCard.tsx @@ -18,7 +18,7 @@ import { useMyTheme } from "lib/useMyTheme"; import Image from "next/image"; import Link from "next/link"; import { useState } from "react"; -import { FiBarChart2, FiInfo, FiTarget } from "react-icons/fi"; +import { FiBarChart2, FiEdit, FiInfo, FiTarget } from "react-icons/fi"; import Gears from "./Gears"; import ViewAP from "./ViewAP"; import ViewSlots from "./ViewSlots"; @@ -26,17 +26,15 @@ import ViewSlots from "./ViewSlots"; interface BuildCardProps { // TODO: don't select unnecessary fields build: PartialBy, "user">; - canModify?: boolean; - //setBuildBeingEdited?: (build: Build) => void; otherBuildCount?: number; onShowAllByUser?: () => void; + onEdit?: (build: BuildCardProps["build"]) => void; showWeapon?: boolean; } const BuildCard: React.FC = ({ build, - canModify, - //setBuildBeingEdited, + onEdit, otherBuildCount, onShowAllByUser, showWeapon, @@ -159,20 +157,17 @@ const BuildCard: React.FC = ({ )} - {/* {canModify && ( + {onEdit && ( setBuildBeingEdited(build)) - } - aria-label="Show description" + onClick={() => onEdit(build)} + aria-label="Edit build" fontSize="20px" icon={} ml="0.5em" /> - )} */} + )} diff --git a/components/u/BuildModal.tsx b/components/u/BuildModal.tsx index 6156fff1b..a7bcd8b28 100644 --- a/components/u/BuildModal.tsx +++ b/components/u/BuildModal.tsx @@ -93,7 +93,11 @@ const BuildModal: React.FC = ({ onClose, build }) => { } } - const success = await sendData("POST", "/api/builds", mutationData); + const success = await sendData( + build ? "PUT" : "POST", + "/api/builds", + build ? { ...mutationData, id: build.id } : mutationData + ); setSending(false); if (!success) return; diff --git a/lib/validators/build.ts b/lib/validators/build.ts index 45361756c..a8d592f6a 100644 --- a/lib/validators/build.ts +++ b/lib/validators/build.ts @@ -73,6 +73,7 @@ export const buildSchema = z.object({ .refine((val) => !val || shoesGear.includes(val as any)) .optional() .nullable(), + id: z.number().optional(), }); function isAbilityArray(arr: any[], type: "HEAD" | "CLOTHING" | "SHOES") { diff --git a/pages/api/builds/index.ts b/pages/api/builds/index.ts index cf900abfb..cd35fa9f6 100644 --- a/pages/api/builds/index.ts +++ b/pages/api/builds/index.ts @@ -8,11 +8,7 @@ import DBClient from "prisma/client"; const prisma = DBClient.getInstance().prisma; -const buildHandler = async (req: NextApiRequest, res: NextApiResponse) => { - if (req.method !== "POST") { - return res.status(405).end(); - } - +const postHandler = async (req: NextApiRequest, res: NextApiResponse) => { const user = await getMySession(req); if (!user) return res.status(401).end(); @@ -33,23 +29,6 @@ const buildHandler = async (req: NextApiRequest, res: NextApiResponse) => { .json({ message: "You have too many builds posted already" }); } - const getAbilityPoints = () => { - const result: Partial> = {}; - - const getPointsFromAbilityArray = (ability: Ability, index: number) => { - const existingAmount = result[ability] ?? 0; - const apsToAdd = index === 0 ? 10 : 3; - - result[ability] = existingAmount + apsToAdd; - }; - - parsed.data.headAbilities.forEach(getPointsFromAbilityArray); - parsed.data.clothingAbilities.forEach(getPointsFromAbilityArray); - parsed.data.shoesAbilities.forEach(getPointsFromAbilityArray); - - return result; - }; - const hasTop500WithTheWeapon = async () => { const playerData = await prisma.player.findFirst({ where: { userId: user.id }, @@ -70,7 +49,11 @@ const buildHandler = async (req: NextApiRequest, res: NextApiResponse) => { await prisma.build.create({ data: { ...parsed.data, - abilityPoints: getAbilityPoints(), + abilityPoints: getAbilityPoints( + parsed.data.headAbilities, + parsed.data.clothingAbilities, + parsed.data.shoesAbilities + ), jpn: postsJpBuilds, top500: await hasTop500WithTheWeapon(), user: { @@ -84,4 +67,79 @@ const buildHandler = async (req: NextApiRequest, res: NextApiResponse) => { res.status(200).end(); }; +const updateHandler = async (req: NextApiRequest, res: NextApiResponse) => { + const user = await getMySession(req); + if (!user) return res.status(401).end(); + + const parsed = buildSchema.safeParse(req.body); + + if (!parsed.success) { + return res.status(400).end(); + } + + const id = parsed.data.id; + + const existingBuild = await prisma.build.findUnique({ + where: { id }, + }); + + if (!existingBuild || existingBuild.weapon !== parsed.data.weapon) { + return res.status(400).end(); + } + + if (existingBuild.userId !== user.id) { + return res.status(403).end(); + } + + delete parsed.data.id; + + await prisma.build.update({ + where: { id }, + data: { + ...parsed.data, + abilityPoints: getAbilityPoints( + parsed.data.headAbilities, + parsed.data.clothingAbilities, + parsed.data.shoesAbilities + ), + }, + }); + + res.status(200).end(); +}; + +const buildHandler = async (req: NextApiRequest, res: NextApiResponse) => { + switch (req.method) { + case "POST": + await postHandler(req, res); + break; + case "PUT": + await updateHandler(req, res); + break; + default: + return res.status(405).end(); + } +}; + +function getAbilityPoints( + headAbilities: Ability[], + clothingAbilities: Ability[], + shoesAbilities: Ability[] +) { + const result: Partial> = {}; + + const getPointsFromAbilityArray = (ability: Ability, index: number) => { + const existingAmount = result[ability] ?? 0; + const apsToAdd = index === 0 ? 10 : 3; + + result[ability] = existingAmount + apsToAdd; + }; + + headAbilities.forEach(getPointsFromAbilityArray); + clothingAbilities.forEach(getPointsFromAbilityArray); + shoesAbilities.forEach(getPointsFromAbilityArray); + + return result; +} + export default buildHandler; diff --git a/pages/u/[identifier].tsx b/pages/u/[identifier].tsx index 3ee96ca8c..453268072 100644 --- a/pages/u/[identifier].tsx +++ b/pages/u/[identifier].tsx @@ -1,7 +1,7 @@ import { Button, Divider, Select } from "@chakra-ui/react"; import { t, Trans } from "@lingui/macro"; import { useLingui } from "@lingui/react"; -import { RankedMode } from "@prisma/client"; +import { Build, RankedMode } from "@prisma/client"; import BuildCard from "components/builds/BuildCard"; import Breadcrumbs from "components/common/Breadcrumbs"; import Markdown from "components/common/Markdown"; @@ -33,7 +33,7 @@ interface Props { const ProfilePage = (props: Props) => { const [showProfileModal, setShowProfileModal] = useState(false); - const [showBuildModal, setShowBuildModal] = useState(false); + const [buildToEdit, setBuildToEdit] = useState(false); const [loggedInUser] = useUser(); const { data: user } = useSWR( @@ -64,8 +64,11 @@ const ProfilePage = (props: Props) => { {showProfileModal && ( setShowProfileModal(false)} user={user} /> )} - {showBuildModal && ( - setShowBuildModal(false)} /> + {buildToEdit && ( + setBuildToEdit(false)} + build={typeof buildToEdit === "boolean" ? undefined : buildToEdit} + /> )} { )} {canPostBuilds() && ( - )} {builds.map((build) => ( - + setBuildToEdit(build)} + /> ))}