From 02b4d89921718b17cdc151fb40e243a995e5a47d Mon Sep 17 00:00:00 2001 From: Oksamies Date: Thu, 16 Nov 2023 00:47:54 +0200 Subject: [PATCH] CreateTeamForm PR fixes Add usage of Zod Minor type fixes refs TS-1942, TS-1829 --- packages/cyberstorm/package.json | 4 +- .../CreateTeamForm/CreateTeamForm.module.css | 10 +- .../Forms/CreateTeamForm/CreateTeamForm.tsx | 115 ++++++++---------- .../src/components/TextInput/TextInput.tsx | 12 +- .../src/components/Toast/Provider.tsx | 25 +++- yarn.lock | 5 + 6 files changed, 93 insertions(+), 78 deletions(-) diff --git a/packages/cyberstorm/package.json b/packages/cyberstorm/package.json index 361b2e629..bd3269127 100644 --- a/packages/cyberstorm/package.json +++ b/packages/cyberstorm/package.json @@ -24,6 +24,7 @@ "@fortawesome/pro-solid-svg-icons": "6.2.0", "@fortawesome/pro-thin-svg-icons": "6.2.0", "@fortawesome/react-fontawesome": "^0.2.0", + "@hookform/resolvers": "^3.3.2", "@radix-ui/react-checkbox": "^1.0.1", "@radix-ui/react-dialog": "^1.0.2", "@radix-ui/react-dropdown-menu": "^2.0.1", @@ -42,7 +43,8 @@ "react-markdown": "^8.0.7", "remark-gfm": "^3.0.1", "styled-components": "^6.0.0-rc.5", - "use-debounce": "^9.0.4" + "use-debounce": "^9.0.4", + "zod": "^3.22.2" }, "devDependencies": { "typescript-plugin-css-modules": "^3.4.0" diff --git a/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.module.css b/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.module.css index 4e75e219d..ca5e6cf70 100644 --- a/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.module.css +++ b/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.module.css @@ -1,16 +1,16 @@ -.createTeamDialog { +.root { display: flex; flex-direction: column; - gap: var(--space--24); + gap: 2rem; } -.createTeamForm { +.dialog { display: flex; flex-direction: column; - gap: 2rem; + gap: var(--space--24); } -.createTeamDialogText { +.dialogText { color: var(--color-text--secondary); font-weight: var(--font-weight-medium); font-size: var(--font-size--l); diff --git a/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.tsx b/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.tsx index 9787fabfe..d36f9da04 100644 --- a/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.tsx +++ b/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.tsx @@ -1,73 +1,44 @@ "use client"; -import styles from "./CreateTeamForm.module.css"; +import { faArrowsRotate } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useController, useForm, Form } from "react-hook-form"; import * as Button from "../../Button"; import { TextInput } from "../../TextInput/TextInput"; -import { useForm, FieldErrors, useController } from "react-hook-form"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faArrowsRotate } from "@fortawesome/free-solid-svg-icons"; +import { useToast } from "../../Toast/Provider"; +import styles from "./CreateTeamForm.module.css"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { isRecord } from "../../../utils/type_guards"; -import { useContext } from "react"; -import { ToastContext } from "../../Toast/ToastContext"; -import { - faCircleCheck, - faCircleExclamation, - faOctagonExclamation, -} from "@fortawesome/pro-solid-svg-icons"; +interface errorResponse { + error: { message: string }; +} + +function isErrorResponse(response: unknown): response is errorResponse { + return ( + isRecord(response) && + isRecord(response.error) && + typeof response.error.message === "string" + ); +} -/** - * Form for creating a team - */ export function CreateTeamForm() { - const useToast = () => useContext(ToastContext); const toast = useToast(); - async function onValid(data: { teamName: string }) { - // TODO: Add sending to TS API endpoint - toast?.addToast( - "info", - , - "Creating team" - ); - await new Promise((resolve) => { - setTimeout(resolve, 1000); - }); - // TODO: Add a toast on response return based on the response - toast?.addToast( - "success", - , - "Team created" - ); - console.log(JSON.stringify(data)); + interface formFields { + teamName: string; } - async function onInvalid( - errors: FieldErrors<{ - teamName: string; - }> - ) { - if (errors.teamName) { - toast?.addToast( - "danger", - , - errors.teamName.message, - 30000 - ); - } else { - toast?.addToast( - "danger", - , - "Unknown error", - 30000 - ); - } - } + const schema = z.object({ + teamName: z.string({ required_error: "Team name is required" }), + }); const { control, - handleSubmit, formState: { isSubmitting }, - } = useForm<{ teamName: string }>({ + } = useForm({ mode: "onSubmit", + resolver: zodResolver(schema), }); const teamName = useController({ @@ -79,12 +50,34 @@ export function CreateTeamForm() { }); return ( -
{ + toast.addToast({ variant: "info", message: "Creating team" }); + }} + onSuccess={(response) => { + console.log(response); + toast.addToast({ variant: "success", message: "Team created" }); + }} + onError={(response) => { + if (isErrorResponse(response)) { + toast.addToast({ + variant: "danger", + message: response.error.message, + duration: 30000, + }); + } else { + // TODO: Add sentry error here + console.log("TODO: Sentry error logging missing!"); + } + }} + validateStatus={(status) => status === 200} + className={styles.root} > -
-
+
+
Enter the name of the team you wish to create. Team names can contain the characters a-z A-Z 0-9 _ and must not start or end with an _
@@ -111,7 +104,7 @@ export function CreateTeamForm() { )}
- + ); } diff --git a/packages/cyberstorm/src/components/TextInput/TextInput.tsx b/packages/cyberstorm/src/components/TextInput/TextInput.tsx index 4c62c89d4..aa0cb7fa5 100644 --- a/packages/cyberstorm/src/components/TextInput/TextInput.tsx +++ b/packages/cyberstorm/src/components/TextInput/TextInput.tsx @@ -1,11 +1,12 @@ "use client"; -import React, { ReactNode } from "react"; +import React from "react"; import styles from "./TextInput.module.css"; import { Icon } from "../Icon/Icon"; import { classnames } from "../../utils/utils"; -export interface TextInputProps { - children?: ReactNode; +export interface TextInputProps + extends React.ComponentPropsWithRef<"input">, + React.PropsWithChildren { placeHolder?: string; leftIcon?: JSX.Element; id?: string; @@ -14,9 +15,8 @@ export interface TextInputProps { value?: string; name?: string; color?: string; - ref?: (instance: any) => void; - onBlur?: (val: any) => void; - onChange?: (val: any) => void; + onBlur?: React.FocusEventHandler; + onChange?: React.ChangeEventHandler; enterHook?: (value: string) => string | void; } diff --git a/packages/cyberstorm/src/components/Toast/Provider.tsx b/packages/cyberstorm/src/components/Toast/Provider.tsx index 1f10b8b5b..ac0a8a6c3 100644 --- a/packages/cyberstorm/src/components/Toast/Provider.tsx +++ b/packages/cyberstorm/src/components/Toast/Provider.tsx @@ -1,6 +1,11 @@ -import { createContext, PropsWithChildren, useReducer } from "react"; -import { v4 as uuid } from "uuid"; import * as RadixToast from "@radix-ui/react-toast"; +import { + createContext, + PropsWithChildren, + useContext, + useReducer, +} from "react"; +import { v4 as uuid } from "uuid"; import Toast from "."; import { ToastProps } from "./Toast"; @@ -37,16 +42,16 @@ const toastReducer = ( }; interface ContextInterface { - addToast: (props: Omit) => void; + addToast: ({ ...props }: Omit) => void; remove: (id: string) => void; } -const ToastContext = createContext(null); +export const ToastContext = createContext(null); export function Provider(props: { toastDuration: number } & PropsWithChildren) { const [state, dispatch] = useReducer(toastReducer, initState); - const addToast = (props: Omit) => { + const addToast = ({ ...props }: Omit) => { const id = uuid(); dispatch({ type: "add", toast: { id, ...props } }); }; @@ -69,3 +74,13 @@ export function Provider(props: { toastDuration: number } & PropsWithChildren) { ); } + +export const useToast = (): ContextInterface => { + const contextState = useContext(ToastContext); + + if (contextState === null) { + throw new Error("useToast must be used within a Toast.Provider tag"); + } + + return contextState; +}; diff --git a/yarn.lock b/yarn.lock index 39c1087d1..872fe68da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2588,6 +2588,11 @@ dependencies: prop-types "^15.8.1" +"@hookform/resolvers@^3.3.2": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.3.2.tgz#5c40f06fe8137390b071d961c66d27ee8f76f3bc" + integrity sha512-Tw+GGPnBp+5DOsSg4ek3LCPgkBOuOgS5DsDV7qsWNH9LZc433kgsWICjlsh2J9p04H2K66hsXPPb9qn9ILdUtA== + "@humanwhocodes/config-array@^0.11.8": version "0.11.8" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9"