From 32d1314b5db0931fe57be0878c8cf9dfdce2fed8 Mon Sep 17 00:00:00 2001 From: "Kalle (Sendou)" <38327916+Sendouc@users.noreply.github.com> Date: Sun, 25 Oct 2020 15:15:08 +0200 Subject: [PATCH] can enter sens to db --- components/LimitProgress.tsx | 33 ++++++ graphql/schema/user.ts | 51 +++++---- scenes/Layout/components/FooterContent.tsx | 4 +- scenes/Profile/components/ProfileModal.tsx | 119 ++++++++++++++------- validators/Profile.ts | 38 ++++--- 5 files changed, 170 insertions(+), 75 deletions(-) create mode 100644 components/LimitProgress.tsx diff --git a/components/LimitProgress.tsx b/components/LimitProgress.tsx new file mode 100644 index 000000000..a39684df8 --- /dev/null +++ b/components/LimitProgress.tsx @@ -0,0 +1,33 @@ +import { CircularProgress, CircularProgressProps } from "@chakra-ui/core"; + +interface Props { + currentLength: number; + maxLength: number; +} + +const getColor = (value: number) => { + if (value >= 100) return "red.500"; + + if (value >= 75) return "yellow.500"; + + return "theme.500"; +}; + +const LimitProgress: React.FC = ({ + currentLength, + maxLength, + ...props +}) => { + const value = Math.floor((currentLength / maxLength) * 100); + return ( + + ); +}; + +export default LimitProgress; diff --git a/graphql/schema/user.ts b/graphql/schema/user.ts index cd2e6ea1f..b9fc27e06 100644 --- a/graphql/schema/user.ts +++ b/graphql/schema/user.ts @@ -7,7 +7,7 @@ import { stringArg, } from "@nexus/schema"; import { getMySession } from "lib/getMySession"; -import { profileSchema } from "validators/profile"; +import { profileSchemaBackend } from "validators/profile"; export const User = objectType({ name: "User", @@ -111,12 +111,39 @@ export const Mutation = mutationType({ const user = await getMySession(ctx.req); if (!user) throw Error("Not logged in"); - profileSchema.parse(args.profile); + const argsForDb = { + ...args.profile, + // can't set array as undefined or null -> need to use [] instead due to how prisma does things + weaponPool: args.profile.weaponPool ? args.profile.weaponPool : [], + customUrlPath: + typeof args.profile.customUrlPath === "string" + ? args.profile.customUrlPath.toLowerCase() + : args.profile.customUrlPath, + twitterName: + typeof args.profile.twitterName === "string" + ? args.profile.twitterName.toLowerCase() + : args.profile.twitterName, + twitchName: + typeof args.profile.twitchName === "string" + ? args.profile.twitchName.toLowerCase() + : args.profile.twitchName, + // sens is saved as integers in the database + sensStick: + typeof args.profile.sensStick === "number" + ? args.profile.sensStick * 10 + : args.profile.sensStick, + sensMotion: + typeof args.profile.sensMotion === "number" + ? args.profile.sensMotion * 10 + : args.profile.sensMotion, + }; - if (args.profile.customUrlPath) { + profileSchemaBackend.parse(argsForDb); + + if (argsForDb.customUrlPath) { const profileWithSameCustomUrl = await ctx.prisma.profile.findOne({ where: { - customUrlPath: args.profile.customUrlPath, + customUrlPath: argsForDb.customUrlPath, }, }); @@ -127,22 +154,6 @@ export const Mutation = mutationType({ throw Error("Custom URL already taken"); } - const argsForDb = { - ...args.profile, - // can't set array as undefined or null -> need to use [] instead due to how prisma does things - weaponPool: args.profile?.weaponPool ? args.profile.weaponPool : [], - customUrlPath: args.profile?.customUrlPath - ? args.profile.customUrlPath.toLowerCase() - : null, - twitterName: args.profile?.twitterName - ? args.profile.twitterName.toLowerCase() - : null, - twitchName: args.profile?.twitchName - ? args.profile.twitchName.toLowerCase() - : null, - }; - - // FIXME: set custom url to lowerCase await ctx.prisma.profile.upsert({ create: { user: { connect: { id: user.id } }, diff --git a/scenes/Layout/components/FooterContent.tsx b/scenes/Layout/components/FooterContent.tsx index 5179b787e..90992f2c6 100644 --- a/scenes/Layout/components/FooterContent.tsx +++ b/scenes/Layout/components/FooterContent.tsx @@ -28,10 +28,10 @@ const FooterContent: React.FC = () => { - + - + diff --git a/scenes/Profile/components/ProfileModal.tsx b/scenes/Profile/components/ProfileModal.tsx index 76074a6b3..6850ee4a2 100644 --- a/scenes/Profile/components/ProfileModal.tsx +++ b/scenes/Profile/components/ProfileModal.tsx @@ -15,16 +15,12 @@ import { ModalFooter, ModalHeader, ModalOverlay, - NumberDecrementStepper, - NumberIncrementStepper, - NumberInput, - NumberInputField, - NumberInputStepper, Select, Textarea, useToast, } from "@chakra-ui/core"; import { zodResolver } from "@hookform/resolvers/zod"; +import LimitProgress from "components/LimitProgress"; import { countries } from "countries-list"; import { GetUserByIdentifierQuery, @@ -35,7 +31,38 @@ import { getToastOptions } from "lib/getToastOptions"; import { useTranslation } from "lib/useMockT"; import { useForm } from "react-hook-form"; import { FaGamepad, FaTwitch, FaTwitter, FaYoutube } from "react-icons/fa"; -import { profileSchema } from "validators/profile"; +import { profileSchemaFrontend } from "validators/profile"; +import * as z from "zod"; + +const sensOptions = [ + "-5", + "-4.5", + "-4", + "-3.5", + "-3", + "-2.5", + "-2", + "-1.5", + "-1", + "-0.5", + "0", + "+0.5", + "+1", + "+1.5", + "+2", + "+2.5", + "+3", + "+3.5", + "+4", + "+4.5", + "+5", +]; + +const sensToString = (sens: number | undefined | null) => { + if (sens === undefined || sens === null) return undefined; + + return sens > 0 ? `+${sens}` : `${sens}`; +}; interface Props { isOpen: boolean; @@ -45,6 +72,8 @@ interface Props { >["profile"]; } +type FormData = z.infer; + const ProfileModal: React.FC = ({ isOpen, onClose, @@ -52,21 +81,19 @@ const ProfileModal: React.FC = ({ }) => { const { t } = useTranslation(); - const { handleSubmit, errors, register, watch } = useForm< - UpdateUserProfileInput - >({ - resolver: zodResolver(profileSchema), - defaultValues: existingProfile ?? undefined, + const { handleSubmit, errors, register, watch } = useForm({ + resolver: zodResolver(profileSchemaFrontend), + defaultValues: existingProfile + ? { + ...existingProfile, + sensMotion: sensToString(existingProfile.sensMotion), + sensStick: sensToString(existingProfile.sensStick), + } + : undefined, }); // FIXME: bio length show - const watchBio = watch("bio", ""); - - const watchSens = watch("stickSens"); - - console.log({ watchSens, watchBio }); - - console.log({ errors }); + const watchBio = watch("bio", existingProfile?.bio ?? ""); const toast = useToast(); const [updateUserProfile] = useUpdateUserProfileMutation({ @@ -79,15 +106,26 @@ const ProfileModal: React.FC = ({ }, }); - const onSubmit = async (data: UpdateUserProfileInput) => { - Object.keys(data).forEach((key) => { - const typedKey = key as keyof typeof data; - if (data[typedKey] === "") { - data[typedKey] = null; + const onSubmit = async (formData: FormData) => { + const mutationData: UpdateUserProfileInput = { + ...formData, + sensStick: + typeof formData.sensStick === "string" + ? parseFloat(formData.sensStick) + : null, + sensMotion: + typeof formData.sensMotion === "string" + ? parseFloat(formData.sensMotion) + : null, + }; + Object.keys(mutationData).forEach((key) => { + const typedKey = key as keyof UpdateUserProfileInput; + if (formData[typedKey] === "" || formData[typedKey] === undefined) { + mutationData[typedKey] = null; } }); - await updateUserProfile({ variables: { profile: data } }); + await updateUserProfile({ variables: { profile: mutationData } }); }; return ( @@ -207,25 +245,25 @@ const ProfileModal: React.FC = ({ {t("users;Stick sensitivity")} - - - - - - - + {t("users;Motion sensitivity")} - - - - - - - + @@ -238,6 +276,11 @@ const ProfileModal: React.FC = ({ resize="vertical" /> + {t("users;markdownPrompt")}{" "} https://sendou.ink/markdown diff --git a/validators/Profile.ts b/validators/Profile.ts index f228b1d92..cde0ba883 100644 --- a/validators/Profile.ts +++ b/validators/Profile.ts @@ -2,7 +2,7 @@ import { countries } from "countries-list"; import { weaponsWithHero } from "lib/lists/weaponsWithHero"; import * as z from "zod"; -export const profileSchema = z.object({ +const profileRootSchema = z.object({ bio: z.string().max(10000).optional().nullable(), country: z .string() @@ -18,20 +18,6 @@ export const profileSchema = z.object({ ) .optional() .nullable(), - sensMotion: z - .number() - .min(-5) - .max(5) - .refine((val) => (val * 10) % 5 === 0) - .optional() - .nullable(), - sensStick: z - .number() - .min(-5) - .max(5) - .refine((val) => (val * 10) % 5 === 0) - .optional() - .nullable(), twitchName: z.string().max(25).optional().nullable(), twitterName: z.string().max(15).optional().nullable(), youtubeId: z.string().optional().nullable(), @@ -42,3 +28,25 @@ export const profileSchema = z.object({ .optional() .nullable(), }); + +export const profileSchemaFrontend = profileRootSchema.extend({ + sensMotion: z.string(), + sensStick: z.string(), +}); + +export const profileSchemaBackend = profileRootSchema.extend({ + sensMotion: z + .number() + .min(-50) + .max(50) + .refine((val) => val % 5 === 0) + .optional() + .nullable(), + sensStick: z + .number() + .min(-50) + .max(50) + .refine((val) => val % 5 === 0) + .optional() + .nullable(), +});