mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-09 12:13:10 -05:00
can enter sens to db
This commit is contained in:
parent
a05749374f
commit
32d1314b5d
33
components/LimitProgress.tsx
Normal file
33
components/LimitProgress.tsx
Normal 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;
|
||||
|
|
@ -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 } },
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user