diff --git a/lib/constants.ts b/lib/constants.ts index ca088280e..9798ab9c7 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -1,2 +1,7 @@ export const ADMIN_DISCORD_ID = "79237403620945920"; export const GANBA_DISCORD_ID = "312082701865713665"; +export const SALMON_RUN_ADMIN_DISCORD_IDS = [ + "81154649993785344", // Brian + "116999083796725761", // Marty + "78546869373906944", // Minaraii +]; diff --git a/lib/validators/salmonRunRecord.ts b/lib/validators/salmonRunRecord.ts new file mode 100644 index 000000000..539639b84 --- /dev/null +++ b/lib/validators/salmonRunRecord.ts @@ -0,0 +1,31 @@ +import * as z from "zod"; + +// https://stackoverflow.com/a/3809435 +const urlRegex = /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/; + +export const salmonRunRecordSchema = z.object({ + rotationId: z.number(), // check on db level + goldenEggCount: z.number().min(0).max(300), + category: z.string(), // check on db level + userIds: z.array(z.number()), + links: z + .string() + .refine((val) => { + const lines = linesFromTextareaValue(val); + if (lines.length === 0 || lines.length > 4) { + return false; + } + + return true; + }, "Include 1-4 links") + .refine((val) => { + return linesFromTextareaValue(val).every((link) => urlRegex.test(link)); + }, "One of the links is invalid"), +}); + +function linesFromTextareaValue(value: string) { + return value + .trim() + .split("\n") + .filter((val) => val !== ""); +} diff --git a/pages/sr/leaderboards/new.tsx b/pages/sr/leaderboards/new.tsx index 796bb1b22..5b86a79c7 100644 --- a/pages/sr/leaderboards/new.tsx +++ b/pages/sr/leaderboards/new.tsx @@ -2,6 +2,7 @@ import { Button, Flex, FormControl, + FormErrorMessage, FormHelperText, FormLabel, NumberDecrementStepper, @@ -12,23 +13,18 @@ import { Select, Textarea, } from "@chakra-ui/react"; +import { zodResolver } from "@hookform/resolvers/zod"; import { t, Trans } from "@lingui/macro"; import { useLingui } from "@lingui/react"; -import { SalmonRunRecordCategory } from "@prisma/client"; import Breadcrumbs from "components/common/Breadcrumbs"; import MyContainer from "components/common/MyContainer"; import UserSelector from "components/common/UserSelector"; import RotationSelector from "components/sr/RotationSelector"; +import { salmonRunRecordSchema } from "lib/validators/salmonRunRecord"; import Image from "next/image"; import { useState } from "react"; - -interface RecordFormData { - rotationId: number; - userIds: number[]; - category: SalmonRunRecordCategory; - goldenEggCount: number; - links: string; -} +import { Controller, useForm } from "react-hook-form"; +import * as z from "zod"; const salmonRunCategoryToNatural = { TOTAL: t`All waves`, @@ -52,10 +48,20 @@ const salmonRunCategoryToNatural = { LT_COHOCK: t`Cohock Charge`, } as const; +type FormData = z.infer; + const AddRecordModal = () => { const { i18n } = useLingui(); const [sending, setSending] = useState(false); - const [form, setForm] = useState>({ rotationId: 1 }); + const { handleSubmit, errors, register, control, watch } = useForm({ + resolver: zodResolver(salmonRunRecordSchema), + }); + + const watchRotationId = watch("rotationId", undefined); + + const onSubmit = async (data: FormData) => { + console.log("data", data); + }; return ( @@ -66,36 +72,22 @@ const AddRecordModal = () => { { name: t`New record` }, ]} /> -
- { - if (!rotationId) { - const newForm = { ...form }; - delete newForm.rotationId; - setForm(newForm); - return; - } - - setForm({ ...form, rotationId }); - }} + + ( + + )} /> - {form.rotationId && ( + {watchRotationId && ( <> Category - {Object.entries(salmonRunCategoryToNatural).map( ([key, value]) => (