react hook form with validation

This commit is contained in:
Kalle (Sendou) 2020-10-21 21:22:04 +03:00
parent 06ac7cb840
commit c242533e7b
12 changed files with 561 additions and 32 deletions

View File

@ -1,22 +1,14 @@
import { PrismaClient } from "@prisma/client";
import { NextApiRequest } from "next";
import { getSession } from "next-auth/client";
const prisma = new PrismaClient({ log: ["query"] });
export interface Context {
prisma: PrismaClient;
// FIXME: type
session: any;
req?: NextApiRequest;
}
export const createContext = async (req: NextApiRequest): Promise<Context> => {
const session = await getSession({ req });
console.log({ session });
return {
prisma,
session,
};
};
export const createContext = ({ req }: { req: NextApiRequest }): Context => ({
prisma,
req,
});

View File

@ -5,6 +5,7 @@ import {
queryType,
stringArg,
} from "@nexus/schema";
import { getSession } from "next-auth/client";
export const User = objectType({
name: "User",
@ -107,8 +108,11 @@ export const Mutation = mutationType({
authorize: async (_root, _args, ctx) => {
return true;
},
resolve: async (_root, args, ctx, a) => {
// FIXME: validate
resolve: async (_root, args, ctx) => {
const session = await getSession({ req: ctx.req });
console.log({ session });
// FIXME: set custom url to lowerCase
return await ctx.prisma.profile.upsert({
create: { user: { connect: { id: 4 } }, ...args.profile },
// FIXME: doing it like this makes removing values impossible?

View File

@ -0,0 +1,141 @@
export const weaponsWithHero = [
"Sploosh-o-matic",
"Neo Sploosh-o-matic",
"Sploosh-o-matic 7",
"Splattershot Jr.",
"Custom Splattershot Jr.",
"Kensa Splattershot Jr.",
"Splash-o-matic",
"Neo Splash-o-matic",
"Aerospray MG",
"Aerospray RG",
"Aerospray PG",
"Splattershot",
"Hero Shot Replica",
"Tentatek Splattershot",
"Octo Shot Replica",
"Kensa Splattershot",
".52 Gal",
".52 Gal Deco",
"Kensa .52 Gal",
"N-ZAP '85",
"N-ZAP '89",
"N-ZAP '83",
"Splattershot Pro",
"Forge Splattershot Pro",
"Kensa Splattershot Pro",
".96 Gal",
".96 Gal Deco",
"Jet Squelcher",
"Custom Jet Squelcher",
"L-3 Nozzlenose",
"L-3 Nozzlenose D",
"Kensa L-3 Nozzlenose",
"H-3 Nozzlenose",
"H-3 Nozzlenose D",
"Cherry H-3 Nozzlenose",
"Squeezer",
"Foil Squeezer",
"Luna Blaster",
"Luna Blaster Neo",
"Kensa Luna Blaster",
"Blaster",
"Hero Blaster Replica",
"Custom Blaster",
"Range Blaster",
"Custom Range Blaster",
"Grim Range Blaster",
"Rapid Blaster",
"Rapid Blaster Deco",
"Kensa Rapid Blaster",
"Rapid Blaster Pro",
"Rapid Blaster Pro Deco",
"Clash Blaster",
"Clash Blaster Neo",
"Carbon Roller",
"Carbon Roller Deco",
"Splat Roller",
"Hero Roller Replica",
"Krak-On Splat Roller",
"Kensa Splat Roller",
"Dynamo Roller",
"Gold Dynamo Roller",
"Kensa Dynamo Roller",
"Flingza Roller",
"Foil Flingza Roller",
"Inkbrush",
"Inkbrush Nouveau",
"Permanent Inkbrush",
"Octobrush",
"Herobrush Replica",
"Octobrush Nouveau",
"Kensa Octobrush",
"Classic Squiffer",
"New Squiffer",
"Fresh Squiffer",
"Splat Charger",
"Hero Charger Replica",
"Firefin Splat Charger",
"Kensa Charger",
"Splatterscope",
"Firefin Splatterscope",
"Kensa Splatterscope",
"E-liter 4K",
"Custom E-liter 4K",
"E-liter 4K Scope",
"Custom E-liter 4K Scope",
"Bamboozler 14 Mk I",
"Bamboozler 14 Mk II",
"Bamboozler 14 Mk III",
"Goo Tuber",
"Custom Goo Tuber",
"Slosher",
"Hero Slosher Replica",
"Slosher Deco",
"Soda Slosher",
"Tri-Slosher",
"Tri-Slosher Nouveau",
"Sloshing Machine",
"Sloshing Machine Neo",
"Kensa Sloshing Machine",
"Bloblobber",
"Bloblobber Deco",
"Explosher",
"Custom Explosher",
"Mini Splatling",
"Zink Mini Splatling",
"Kensa Mini Splatling",
"Heavy Splatling",
"Hero Splatling Replica",
"Heavy Splatling Deco",
"Heavy Splatling Remix",
"Hydra Splatling",
"Custom Hydra Splatling",
"Ballpoint Splatling",
"Ballpoint Splatling Nouveau",
"Nautilus 47",
"Nautilus 79",
"Dapple Dualies",
"Dapple Dualies Nouveau",
"Clear Dapple Dualies",
"Splat Dualies",
"Hero Dualie Replicas",
"Enperry Splat Dualies",
"Kensa Splat Dualies",
"Glooga Dualies",
"Glooga Dualies Deco",
"Kensa Glooga Dualies",
"Dualie Squelchers",
"Custom Dualie Squelchers",
"Dark Tetra Dualies",
"Light Tetra Dualies",
"Splat Brella",
"Hero Brella Replica",
"Sorella Brella",
"Tenta Brella",
"Tenta Sorella Brella",
"Tenta Camo Brella",
"Undercover Brella",
"Undercover Sorella Brella",
"Kensa Undercover Brella",
] as const;

View File

@ -1514,6 +1514,9 @@ const translations = {
"Best placement / power": "Best placement / power",
noPlacementsPromptNew:
"If you have reached Top 500 in a <1>finished</1> X Rank season you can have it displayed here! Simply contact Sendou#0043 on Discord with your in-game nick. Once set up new results are added automatically and no further action is needed.",
"Twitter name": "Twitter name",
"Twitch name": "Twitch name",
"YouTube channel ID": "YouTube channel ID",
},
freeagents: {
loginPrompt: "Log in to make your own free agent post and start matching!",

View File

@ -1441,7 +1441,10 @@
"Highest power": "Highest power",
"Number of placements": "Number of placements",
"Best placement / power": "Best placement / power",
"noPlacementsPromptNew": "If you have reached Top 500 in a <1>finished</1> X Rank season you can have it displayed here! Simply contact Sendou#0043 on Discord with your in-game nick. Once set up new results are added automatically and no further action is needed."
"noPlacementsPromptNew": "If you have reached Top 500 in a <1>finished</1> X Rank season you can have it displayed here! Simply contact Sendou#0043 on Discord with your in-game nick. Once set up new results are added automatically and no further action is needed.",
"Twitter name": "Twitter name",
"Twitch name": "Twitch name",
"YouTube channel ID": "YouTube channel ID"
},
"freeagents": {
"loginPrompt": "Log in to make your own free agent post and start matching!",

84
package-lock.json generated
View File

@ -11,6 +11,7 @@
"@apollo/client": "^3.2.5",
"@chakra-ui/core": "^1.0.0-rc.5",
"@chakra-ui/theme-tools": "^1.0.0-rc.5",
"@hookform/resolvers": "^1.0.0",
"@nexus/schema": "^0.16.0",
"@prisma/client": "^2.9.0",
"apollo-server-micro": "^2.18.2",
@ -22,9 +23,12 @@
"nexus-plugin-prisma": "^0.22.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-hook-form": "^6.9.5",
"react-icons": "^3.11.0",
"react-markdown": "^5.0.0",
"react-string-replace": "^0.4.4"
"react-string-replace": "^0.4.4",
"validator": "^13.1.17",
"zod": "^1.11.9"
},
"devDependencies": {
"@graphql-codegen/cli": "1.18.0",
@ -32,8 +36,9 @@
"@graphql-codegen/typescript-operations": "1.17.8",
"@graphql-codegen/typescript-react-apollo": "2.0.7",
"@prisma/cli": "^2.9.0",
"@types/next-auth": "^3.1.13",
"@types/next-auth": "^3.1.14",
"@types/react": "^16.9.53",
"@types/validator": "^13.1.0",
"cypress": "^5.4.0",
"prettier": "^2.1.2",
"ts-node": "^9.0.0",
@ -3497,6 +3502,14 @@
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.1.0.tgz",
"integrity": "sha512-i9YbZPN3QgfighY/1X1Pu118VUz2Fmmhd6b2n0/O8YVgGGfw0FbUYoA97k7FkpGJ+pLCFEDLUmAPPV4D1kpeFw=="
},
"node_modules/@hookform/resolvers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-1.0.0.tgz",
"integrity": "sha512-YzBq6ZFw/uWGa3rXBNSHqnsE4hDXLrzdboDxPRKGjYHVzs1dBxjvELftP8iTmRPqP32VjnbVfUktX1CQ6Y7sog==",
"peerDependencies": {
"react-hook-form": ">=6.6.0"
}
},
"node_modules/@next/env": {
"version": "9.5.5",
"resolved": "https://registry.npmjs.org/@next/env/-/env-9.5.5.tgz",
@ -4129,9 +4142,9 @@
"integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q=="
},
"node_modules/@types/next-auth": {
"version": "3.1.13",
"resolved": "https://registry.npmjs.org/@types/next-auth/-/next-auth-3.1.13.tgz",
"integrity": "sha512-1HKyYcrPFOEuTmKmqDVbXA/1em1OrE65m+HaZP8/uC0Ups1xbUwCMeuJYn8p83MGNJI0JGF0D5vm6ZWdK2bxZw==",
"version": "3.1.14",
"resolved": "https://registry.npmjs.org/@types/next-auth/-/next-auth-3.1.14.tgz",
"integrity": "sha512-XL70zlxHCmhAkHg4P6lE+gf9WUeM65UZsjwGUDGDPNQTrB3euwm7Z25WlLxG0Z0rsdZmBsXw5kiS+dmuZ/xqlA==",
"dev": true,
"dependencies": {
"@types/node": "*",
@ -4227,6 +4240,12 @@
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz",
"integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ=="
},
"node_modules/@types/validator": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.1.0.tgz",
"integrity": "sha512-gHUHI6pJaANIO2r6WcbT7+WMgbL9GZooR4tWpuBOETpDIqFNxwaJluE+6rj6VGYe8k6OkfhbHz2Fkm8kl06Igw==",
"dev": true
},
"node_modules/@types/warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz",
@ -12729,6 +12748,14 @@
"react": "^16.8.0"
}
},
"node_modules/react-hook-form": {
"version": "6.9.5",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-6.9.5.tgz",
"integrity": "sha512-kkUm6b4u1Iy3wCdHA0fal23sjGQMg8BBRAT3KBG9PEdls5e+7OX2Df20oBPgpXxo/GaIwk2Lh5DeVv5OeVjKsg==",
"peerDependencies": {
"react": "^16.8.0"
}
},
"node_modules/react-icons": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-3.11.0.tgz",
@ -15590,6 +15617,14 @@
"integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=",
"dev": true
},
"node_modules/validator": {
"version": "13.1.17",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.1.17.tgz",
"integrity": "sha512-zL5QBoemJ3jYFb2/j38y7ljhwYGXVLUp8H6W1nVxadnAOvUOytec+L7BHh1oBQ82/TzWXHd+GSaxUWp4lROkLg==",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
@ -16651,6 +16686,11 @@
"tslib": "^1.9.3",
"zen-observable": "^0.8.0"
}
},
"node_modules/zod": {
"version": "1.11.9",
"resolved": "https://registry.npmjs.org/zod/-/zod-1.11.9.tgz",
"integrity": "sha512-qZjs9DkvPYHOiOUdAtNcxOC0u5cv7tx9DCmlNZN0MxWeFvgqyr3XkXFqUlaSpmTiZ4A4YVkB2s1Zw2ENJ9/fSg=="
}
},
"dependencies": {
@ -19529,6 +19569,12 @@
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.1.0.tgz",
"integrity": "sha512-i9YbZPN3QgfighY/1X1Pu118VUz2Fmmhd6b2n0/O8YVgGGfw0FbUYoA97k7FkpGJ+pLCFEDLUmAPPV4D1kpeFw=="
},
"@hookform/resolvers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-1.0.0.tgz",
"integrity": "sha512-YzBq6ZFw/uWGa3rXBNSHqnsE4hDXLrzdboDxPRKGjYHVzs1dBxjvELftP8iTmRPqP32VjnbVfUktX1CQ6Y7sog==",
"requires": {}
},
"@next/env": {
"version": "9.5.5",
"resolved": "https://registry.npmjs.org/@next/env/-/env-9.5.5.tgz",
@ -20063,9 +20109,9 @@
"integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q=="
},
"@types/next-auth": {
"version": "3.1.13",
"resolved": "https://registry.npmjs.org/@types/next-auth/-/next-auth-3.1.13.tgz",
"integrity": "sha512-1HKyYcrPFOEuTmKmqDVbXA/1em1OrE65m+HaZP8/uC0Ups1xbUwCMeuJYn8p83MGNJI0JGF0D5vm6ZWdK2bxZw==",
"version": "3.1.14",
"resolved": "https://registry.npmjs.org/@types/next-auth/-/next-auth-3.1.14.tgz",
"integrity": "sha512-XL70zlxHCmhAkHg4P6lE+gf9WUeM65UZsjwGUDGDPNQTrB3euwm7Z25WlLxG0Z0rsdZmBsXw5kiS+dmuZ/xqlA==",
"dev": true,
"requires": {
"@types/node": "*",
@ -20161,6 +20207,12 @@
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz",
"integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ=="
},
"@types/validator": {
"version": "13.1.0",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.1.0.tgz",
"integrity": "sha512-gHUHI6pJaANIO2r6WcbT7+WMgbL9GZooR4tWpuBOETpDIqFNxwaJluE+6rj6VGYe8k6OkfhbHz2Fkm8kl06Igw==",
"dev": true
},
"@types/warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz",
@ -27011,6 +27063,12 @@
"use-sidecar": "^1.0.1"
}
},
"react-hook-form": {
"version": "6.9.5",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-6.9.5.tgz",
"integrity": "sha512-kkUm6b4u1Iy3wCdHA0fal23sjGQMg8BBRAT3KBG9PEdls5e+7OX2Df20oBPgpXxo/GaIwk2Lh5DeVv5OeVjKsg==",
"requires": {}
},
"react-icons": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-3.11.0.tgz",
@ -29238,6 +29296,11 @@
"integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=",
"dev": true
},
"validator": {
"version": "13.1.17",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.1.17.tgz",
"integrity": "sha512-zL5QBoemJ3jYFb2/j38y7ljhwYGXVLUp8H6W1nVxadnAOvUOytec+L7BHh1oBQ82/TzWXHd+GSaxUWp4lROkLg=="
},
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
@ -30093,6 +30156,11 @@
"tslib": "^1.9.3",
"zen-observable": "^0.8.0"
}
},
"zod": {
"version": "1.11.9",
"resolved": "https://registry.npmjs.org/zod/-/zod-1.11.9.tgz",
"integrity": "sha512-qZjs9DkvPYHOiOUdAtNcxOC0u5cv7tx9DCmlNZN0MxWeFvgqyr3XkXFqUlaSpmTiZ4A4YVkB2s1Zw2ENJ9/fSg=="
}
}
}

View File

@ -20,6 +20,7 @@
"@apollo/client": "^3.2.5",
"@chakra-ui/core": "^1.0.0-rc.5",
"@chakra-ui/theme-tools": "^1.0.0-rc.5",
"@hookform/resolvers": "^1.0.0",
"@nexus/schema": "^0.16.0",
"@prisma/client": "^2.9.0",
"apollo-server-micro": "^2.18.2",
@ -31,9 +32,12 @@
"nexus-plugin-prisma": "^0.22.0",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-hook-form": "^6.9.5",
"react-icons": "^3.11.0",
"react-markdown": "^5.0.0",
"react-string-replace": "^0.4.4"
"react-string-replace": "^0.4.4",
"validator": "^13.1.17",
"zod": "^1.11.9"
},
"devDependencies": {
"@graphql-codegen/cli": "1.18.0",
@ -41,8 +45,9 @@
"@graphql-codegen/typescript-operations": "1.17.8",
"@graphql-codegen/typescript-react-apollo": "2.0.7",
"@prisma/cli": "^2.9.0",
"@types/next-auth": "^3.1.13",
"@types/next-auth": "^3.1.14",
"@types/react": "^16.9.53",
"@types/validator": "^13.1.0",
"cypress": "^5.4.0",
"prettier": "^2.1.2",
"ts-node": "^9.0.0",

View File

@ -10,8 +10,6 @@ export const config = {
export default new ApolloServer({
schema,
context: ({ req }) => {
return createContext(req);
},
context: createContext,
tracing: process.env.NODE_ENV === "development",
}).createHandler({ path: "/api/graphql" });

View File

@ -31,8 +31,6 @@ const TopNav = () => {
if (loading) return <Box />;
console.log({ user });
if (!user) {
return (
<Button

View File

@ -0,0 +1,135 @@
import {
Button,
FormControl,
FormErrorMessage,
FormLabel,
Input,
InputGroup,
InputLeftAddon,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
} from "@chakra-ui/core";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslation } from "lib/useMockT";
import { useForm } from "react-hook-form";
import { ProfileSchema } from "validators/Profile";
interface Props {
isOpen: boolean;
onClose: () => void;
}
const ProfileModal: React.FC<Props> = ({ isOpen, onClose }) => {
const { t } = useTranslation();
const { handleSubmit, errors, register, formState } = useForm({
resolver: zodResolver(ProfileSchema),
});
console.log({ formState: formState.errors });
const onSubmit = (data) => {
Object.keys(data).forEach((key) => {
if (data[key] === "") {
data[key] = null;
}
});
console.log(data);
};
console.log({ errors });
return (
<Modal isOpen={isOpen} onClose={onClose} size="xl">
<ModalOverlay>
<ModalContent>
<ModalHeader>{t("users;Editing profile")}</ModalHeader>
<ModalCloseButton />
<form onSubmit={handleSubmit(onSubmit)}>
<ModalBody pb={6}>
<FormControl isInvalid={!!errors.customUrlPath}>
<FormLabel htmlFor="customUrlPath">
{t("users;Custom URL")}
</FormLabel>
<InputGroup>
<InputLeftAddon children="https://sendou.ink/u/" />
<Input
name="customUrlPath"
ref={register}
placeholder={t("users;Custom URL")}
/>
</InputGroup>
<FormErrorMessage>
{errors.customUrlPath?.message}
</FormErrorMessage>
</FormControl>
<FormControl isInvalid={!!errors.twitterName}>
<FormLabel htmlFor="twitterName" mt={4}>
{t("users;Twitter name")}
</FormLabel>
<InputGroup>
<InputLeftAddon children="https://twitter.com/" />
<Input
name="twitterName"
ref={register}
placeholder={t("users;Twitter name")}
/>
</InputGroup>
<FormErrorMessage>
{errors.twitterName?.message}
</FormErrorMessage>
</FormControl>
<FormControl isInvalid={!!errors.twitchName}>
<FormLabel htmlFor="twitchName" mt={4}>
{t("users;Twitch name")}
</FormLabel>
<InputGroup>
<InputLeftAddon children="https://twitch.tv/" />
<Input
name="twitchName"
ref={register}
placeholder={t("users;Twitch name")}
/>
</InputGroup>
<FormErrorMessage>
{errors.twitchName?.message}
</FormErrorMessage>
</FormControl>
<FormControl isInvalid={!!errors.youtubeId}>
<FormLabel htmlFor="youtubeId" mt={4}>
{t("users;YouTube channel ID")}
</FormLabel>
<InputGroup>
<InputLeftAddon children="https://youtube.com/channel/" />
<Input
name="youtubeId"
ref={register}
placeholder={t("users;YouTube channel ID")}
/>
</InputGroup>
<FormErrorMessage>{errors.youtubeId?.message}</FormErrorMessage>
</FormControl>
</ModalBody>
<ModalFooter>
<Button colorScheme="blue" mr={3} type="submit">
Save
</Button>
<Button onClick={onClose}>Cancel</Button>
</ModalFooter>
</form>
</ModalContent>
</ModalOverlay>
</Modal>
);
};
export default ProfileModal;

View File

@ -1,18 +1,27 @@
import { Box } from "@chakra-ui/core";
import { Box, Button } from "@chakra-ui/core";
import Markdown from "components/Markdown";
import MyHead from "components/MyHead";
import { GetUserByIdentifierQuery } from "generated/graphql";
import { useTranslation } from "lib/useMockT";
import { useState } from "react";
import AvatarWithInfo from "./components/AvatarWithInfo";
import ProfileModal from "./components/ProfileModal";
interface Props {
user: NonNullable<GetUserByIdentifierQuery["getUserByIdentifier"]>;
}
const Profile: React.FC<Props> = ({ user }) => {
const { t } = useTranslation();
const [showModal, setShowModal] = useState(false);
return (
<>
<MyHead title={user.fullUsername} />
<AvatarWithInfo user={user} />
<Button onClick={() => setShowModal(true)}>
{t("users;Edit profile")}
</Button>
<ProfileModal isOpen={showModal} onClose={() => setShowModal(false)} />
{user.profile?.bio && (
<Box my="2em">
<Markdown value={user.profile.bio} />

173
validators/Profile.ts Normal file
View File

@ -0,0 +1,173 @@
//import isISO31661Alpha2 from "validator/es/lib/isISO31661Alpha2";
import * as z from "zod";
export const ProfileSchema = z.object({
bio: z.string().max(10000).optional(),
country: z
.string()
//.refine((val) => isISO31661Alpha2(val))
.optional(),
customUrlPath: z.string().max(32).optional(),
sensMotion: z
.number()
.min(5)
.max(5)
.refine((val) => (val * 10) % 5 === 0)
.optional(),
sensStick: z
.number()
.min(5)
.max(5)
.refine((val) => (val * 10) % 5 === 0)
.optional(),
twitchName: z.string().max(25).optional(),
twitterName: z.string().max(15).optional(),
youtubeId: z.string().optional(),
// weaponsWithHero
weaponPool: z
.array(
z.enum([
"Sploosh-o-matic",
"Neo Sploosh-o-matic",
"Sploosh-o-matic 7",
"Splattershot Jr.",
"Custom Splattershot Jr.",
"Kensa Splattershot Jr.",
"Splash-o-matic",
"Neo Splash-o-matic",
"Aerospray MG",
"Aerospray RG",
"Aerospray PG",
"Splattershot",
"Hero Shot Replica",
"Tentatek Splattershot",
"Octo Shot Replica",
"Kensa Splattershot",
".52 Gal",
".52 Gal Deco",
"Kensa .52 Gal",
"N-ZAP '85",
"N-ZAP '89",
"N-ZAP '83",
"Splattershot Pro",
"Forge Splattershot Pro",
"Kensa Splattershot Pro",
".96 Gal",
".96 Gal Deco",
"Jet Squelcher",
"Custom Jet Squelcher",
"L-3 Nozzlenose",
"L-3 Nozzlenose D",
"Kensa L-3 Nozzlenose",
"H-3 Nozzlenose",
"H-3 Nozzlenose D",
"Cherry H-3 Nozzlenose",
"Squeezer",
"Foil Squeezer",
"Luna Blaster",
"Luna Blaster Neo",
"Kensa Luna Blaster",
"Blaster",
"Hero Blaster Replica",
"Custom Blaster",
"Range Blaster",
"Custom Range Blaster",
"Grim Range Blaster",
"Rapid Blaster",
"Rapid Blaster Deco",
"Kensa Rapid Blaster",
"Rapid Blaster Pro",
"Rapid Blaster Pro Deco",
"Clash Blaster",
"Clash Blaster Neo",
"Carbon Roller",
"Carbon Roller Deco",
"Splat Roller",
"Hero Roller Replica",
"Krak-On Splat Roller",
"Kensa Splat Roller",
"Dynamo Roller",
"Gold Dynamo Roller",
"Kensa Dynamo Roller",
"Flingza Roller",
"Foil Flingza Roller",
"Inkbrush",
"Inkbrush Nouveau",
"Permanent Inkbrush",
"Octobrush",
"Herobrush Replica",
"Octobrush Nouveau",
"Kensa Octobrush",
"Classic Squiffer",
"New Squiffer",
"Fresh Squiffer",
"Splat Charger",
"Hero Charger Replica",
"Firefin Splat Charger",
"Kensa Charger",
"Splatterscope",
"Firefin Splatterscope",
"Kensa Splatterscope",
"E-liter 4K",
"Custom E-liter 4K",
"E-liter 4K Scope",
"Custom E-liter 4K Scope",
"Bamboozler 14 Mk I",
"Bamboozler 14 Mk II",
"Bamboozler 14 Mk III",
"Goo Tuber",
"Custom Goo Tuber",
"Slosher",
"Hero Slosher Replica",
"Slosher Deco",
"Soda Slosher",
"Tri-Slosher",
"Tri-Slosher Nouveau",
"Sloshing Machine",
"Sloshing Machine Neo",
"Kensa Sloshing Machine",
"Bloblobber",
"Bloblobber Deco",
"Explosher",
"Custom Explosher",
"Mini Splatling",
"Zink Mini Splatling",
"Kensa Mini Splatling",
"Heavy Splatling",
"Hero Splatling Replica",
"Heavy Splatling Deco",
"Heavy Splatling Remix",
"Hydra Splatling",
"Custom Hydra Splatling",
"Ballpoint Splatling",
"Ballpoint Splatling Nouveau",
"Nautilus 47",
"Nautilus 79",
"Dapple Dualies",
"Dapple Dualies Nouveau",
"Clear Dapple Dualies",
"Splat Dualies",
"Hero Dualie Replicas",
"Enperry Splat Dualies",
"Kensa Splat Dualies",
"Glooga Dualies",
"Glooga Dualies Deco",
"Kensa Glooga Dualies",
"Dualie Squelchers",
"Custom Dualie Squelchers",
"Dark Tetra Dualies",
"Light Tetra Dualies",
"Splat Brella",
"Hero Brella Replica",
"Sorella Brella",
"Tenta Brella",
"Tenta Sorella Brella",
"Tenta Camo Brella",
"Undercover Brella",
"Undercover Sorella Brella",
"Kensa Undercover Brella",
])
)
.max(5)
.optional(),
});