can enter sens to db

This commit is contained in:
Kalle (Sendou) 2020-10-25 15:15:08 +02:00
parent a05749374f
commit 32d1314b5d
5 changed files with 170 additions and 75 deletions

View File

@ -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<Props & CircularProgressProps> = ({
currentLength,
maxLength,
...props
}) => {
const value = Math.floor((currentLength / maxLength) * 100);
return (
<CircularProgress
size="20px"
value={value}
thickness="16px"
color={getColor(value)}
{...props}
/>
);
};
export default LimitProgress;

View File

@ -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 } },

View File

@ -28,10 +28,10 @@ const FooterContent: React.FC = () => {
</Flex>
<Flex alignItems="center" flexWrap="wrap" justifyContent="center">
<a href="https://discord.gg/sendou">
<DiscordIcon h="30px" w="30px" m="1em" />
<DiscordIcon h="50px" w="50px" m="1em" />
</a>
<a href="https://github.com/Sendouc/sendou.ink">
<Box as={FaGithub} size="30px" m="1em" />
<Box as={FaGithub} size="50px" m="1em" />
</a>
</Flex>
</Flex>

View File

@ -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<typeof profileSchemaFrontend>;
const ProfileModal: React.FC<Props> = ({
isOpen,
onClose,
@ -52,21 +81,19 @@ const ProfileModal: React.FC<Props> = ({
}) => {
const { t } = useTranslation();
const { handleSubmit, errors, register, watch } = useForm<
UpdateUserProfileInput
>({
resolver: zodResolver(profileSchema),
defaultValues: existingProfile ?? undefined,
const { handleSubmit, errors, register, watch } = useForm<FormData>({
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<Props> = ({
},
});
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<Props> = ({
<Box as={FaGamepad} display="inline-block" mr={2} mb={1} />
{t("users;Stick sensitivity")}
</FormLabel>
<NumberInput size="lg" step={0.5} min={-5} max={5}>
<NumberInputField ref={register} name="sensStick" />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
<Select ref={register} name="sensStick">
{sensOptions.map((sens) => (
<option key={sens} value={sens}>
{sens}
</option>
))}
</Select>
<FormLabel htmlFor="sensMotion" mt={4}>
<Box as={FaGamepad} display="inline-block" mr={2} mb={1} />
{t("users;Motion sensitivity")}
</FormLabel>
<NumberInput size="lg" step={0.5} min={-5} max={5}>
<NumberInputField ref={register} name="sensMotion" />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
<Select ref={register} name="sensMotion">
{sensOptions.map((sens) => (
<option key={sens} value={sens}>
{sens}
</option>
))}
</Select>
<FormControl isInvalid={!!errors.bio}>
<FormLabel htmlFor="bio" mt={4}>
@ -238,6 +276,11 @@ const ProfileModal: React.FC<Props> = ({
resize="vertical"
/>
<FormHelperText>
<LimitProgress
currentLength={watchBio!.length}
maxLength={10000}
mr={3}
/>
{t("users;markdownPrompt")}{" "}
<a href="/markdown" target="_blank" rel="noreferrer noopener">
https://sendou.ink/markdown

View File

@ -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(),
});