From 762cb5e2156e427df0e4e2326c55caa63999a0ef Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Fri, 5 Aug 2022 00:35:10 +0300 Subject: [PATCH] Implement parsing report scores action --- app/constants.ts | 3 + app/routes/calendar/$id/report-winners.tsx | 131 +++++++++++++++++---- app/utils/remix.ts | 21 +++- app/utils/zod.ts | 6 + 4 files changed, 135 insertions(+), 26 deletions(-) diff --git a/app/constants.ts b/app/constants.ts index d7a1f37ca..45a9c66ec 100644 --- a/app/constants.ts +++ b/app/constants.ts @@ -22,6 +22,9 @@ export const CALENDAR_EVENT_TAGS = Object.keys( export const CALENDAR_EVENT_RESULT = { MAX_PARTICIPANTS_COUNT: 1000, MAX_PLAYERS_LENGTH: 8, + MAX_TEAM_NAME_LENGTH: 100, + MAX_TEAM_PLACEMENT: 256, + MAX_PLAYER_NAME_LENGTH: 100, } as const; export const PLUS_TIERS = [1, 2, 3]; diff --git a/app/routes/calendar/$id/report-winners.tsx b/app/routes/calendar/$id/report-winners.tsx index d6ecc0661..3fc44a16e 100644 --- a/app/routes/calendar/$id/report-winners.tsx +++ b/app/routes/calendar/$id/report-winners.tsx @@ -1,5 +1,5 @@ -import type { ActionFunction, LoaderArgs } from "@remix-run/node"; -import { Form } from "@remix-run/react"; +import { type ActionFunction, type LoaderArgs, json } from "@remix-run/node"; +import { Form, useLoaderData } from "@remix-run/react"; import { z } from "zod"; import { Label } from "~/components/Label"; import { Main } from "~/components/Main"; @@ -7,8 +7,14 @@ import { CALENDAR_EVENT_RESULT } from "~/constants"; import { db } from "~/db"; import { requireUser } from "~/modules/auth"; import { canReportCalendarEventWinners } from "~/permissions"; -import { notFoundIfFalsy, validate } from "~/utils/remix"; -import { actualNumber, id } from "~/utils/zod"; +import { notFoundIfFalsy, parseRequestFormData, validate } from "~/utils/remix"; +import { + actualNumber, + id, + processMany, + safeJSONParse, + toArray, +} from "~/utils/zod"; import * as React from "react"; import type { User } from "~/db/types"; import { Button } from "~/components/Button"; @@ -16,10 +22,63 @@ import clsx from "clsx"; import { UserCombobox } from "~/components/Combobox"; import { FormMessage } from "~/components/FormMessage"; +const reportWinnersActionSchema = z.object({ + participantsCount: z.preprocess( + actualNumber, + z + .number() + .int() + .positive() + .max(CALENDAR_EVENT_RESULT.MAX_PARTICIPANTS_COUNT) + ), + team: z + .array( + z.preprocess( + processMany(safeJSONParse, toArray), + z.object({ + teamName: z + .string() + .min(1) + .max(CALENDAR_EVENT_RESULT.MAX_TEAM_NAME_LENGTH), + placement: z.preprocess( + actualNumber, + z + .number() + .int() + .positive() + .max(CALENDAR_EVENT_RESULT.MAX_TEAM_PLACEMENT) + ), + players: z + .array( + z.union([ + z + .string() + .min(1) + .max(CALENDAR_EVENT_RESULT.MAX_PLAYER_NAME_LENGTH), + z.object({ id }), + ]) + ) + .nonempty() + .max(CALENDAR_EVENT_RESULT.MAX_PLAYERS_LENGTH), + }) + ) + ) + // don't allow repeating same team name + .refine( + (val) => val.length === new Set(val.map((team) => team.teamName)).size + ), +}); + export const action: ActionFunction = async ({ request }) => { - const formData = await request.formData(); - // eslint-disable-next-line no-console - console.log({ formData }); + // xxx: return nice errors to frontend to render + // try { + // const data = await parseRequestFormData({ + // request, + // schema: reportWinnersActionSchema, + // }); + // } catch (e) { + // return null; + // } return null; }; @@ -44,26 +103,18 @@ export const loader = async ({ request, params }: LoaderArgs) => { 401 ); - return null; + return json({ + name: event.name, + }); }; -interface TeamResults { - teamName: string; - placement: string; - players: Array< - | { - id: User["id"]; - } - | string - >; -} - export default function ReportWinnersPage() { + const data = useLoaderData(); + return (
- {/* xxx: use real name */} -

Reporting results of TEST Tournament

+

Reporting results of {data.name}

You choose how many results to report. It can be just the winning @@ -123,6 +174,7 @@ function TeamInputs() { : undefined } hidden={hidden} + initialPlacement={String(i + 1)} /> {!hidden &&
} @@ -140,19 +192,32 @@ function TeamInputs() { const NEW_PLAYER = { id: 0 } as const; +interface TeamResults { + teamName: string; + placement: string; + players: Array< + | { + id: User["id"]; + } + | string + >; +} + function Team({ onRemoveTeam, hidden, + initialPlacement, }: { onRemoveTeam?: () => void; hidden: boolean; + initialPlacement: string; }) { const teamNameId = React.useId(); const placementId = React.useId(); + const [results, setResults] = React.useState({ teamName: "", - // xxx: could use i + 1 - placement: "1", + placement: initialPlacement, players: [NEW_PLAYER, NEW_PLAYER, NEW_PLAYER, NEW_PLAYER], }); @@ -167,15 +232,28 @@ function Team({ return (