diff --git a/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.module.css b/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.module.css index fdcf706f3..b5e1ab68e 100644 --- a/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.module.css +++ b/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.module.css @@ -30,6 +30,12 @@ animation: rotate 2s linear infinite; } +.errorMessage { + color: #f1385a; + font-weight: 500; + font-size: 0.75rem; +} + @keyframes rotate { 100% { transform: rotate(360deg); diff --git a/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.tsx b/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.tsx index bb90685f1..5c57284f5 100644 --- a/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.tsx +++ b/packages/cyberstorm/src/components/Forms/CreateTeamForm/CreateTeamForm.tsx @@ -1,23 +1,25 @@ "use client"; import { faArrowsRotate } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useController, useForm, Form } from "react-hook-form"; +import { useController, useForm } from "react-hook-form"; import * as Button from "../../Button"; import { TextInput } from "../../TextInput/TextInput"; import { useToast } from "../../Toast/Provider"; import styles from "./CreateTeamForm.module.css"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; -import { isErrorResponse } from "../../../utils/type_guards"; -import { useEffect } from "react"; +import { + FormErrorResponse, + isFormErrorResponse, +} from "../../../utils/type_guards"; export function CreateTeamForm() { const toast = useToast(); - // "Required field" error is different from value being too little + // "Required field" error only checks, if the field has been touched. (It's probably broken) // Check that the value is "> 0" const schema = z.object({ - teamName: z + name: z .string({ required_error: "Team name is required" }) .min(1, { message: "Team name is required" }), }); @@ -25,80 +27,88 @@ export function CreateTeamForm() { const { control, formState: { isSubmitting, errors }, + handleSubmit, + setError, } = useForm>({ mode: "onSubmit", resolver: zodResolver(schema), }); - const teamName = useController({ + const name = useController({ control: control, - name: "teamName", + name: "name", }); - // We have to do this because the RHF Form component is in beta and it - // doesn't have a callback prop like "onValidation" that could take in - // the generalized addToast error informing block. - useEffect(() => { - if (errors.teamName) { - console.log(errors); + const onSubmit = async (data: z.infer) => { + // SESSION TODO: Add sessionid here + const session = { sessionid: "74hmbkylkgqge1ne22tzuvzuz6u51tdg" }; + + const payload = JSON.stringify(data); + let response = undefined; + try { + // TODO: Change to dev env url + response = await fetch( + `http://thunderstore.temp/api/cyberstorm/teams/create/`, + { + method: "POST", + headers: { + authorization: `Session ${session.sessionid}`, + "Content-Type": "application/json", + }, + body: payload, + } + ); + } catch (e) { toast.addToast({ variant: "danger", - message: errors.teamName.message, + message: "There was a problem reaching the server", duration: 30000, }); } - }, [errors]); - return ( -
{ - toast.addToast({ variant: "info", message: "Creating team" }); - }} - onSuccess={(response) => { - console.log(response); + if (response) { + const responseJson = await response.json(); + if (response.ok) { 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!"); - toast.addToast({ - variant: "danger", - message: "Unhandled form response error", - duration: 30000, - }); - } - }} - validateStatus={(status) => status === 200} - className={styles.root} - > + } else if (isFormErrorResponse(responseJson)) { + const errors = responseJson as FormErrorResponse; + Object.keys(errors).forEach((key) => { + setError(key, { type: "manual", message: errors[key] }); + }); + } else { + // TODO: Add sentry error here + toast.addToast({ + variant: "danger", + message: "Skidaddle skidoodle the server gave you a noodle", + duration: 30000, + }); + } + } + }; + + return ( +
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 _
- +
+ + {errors.name?.message} +
-
+ ); } diff --git a/packages/cyberstorm/src/components/Layout/Settings/Profile/Profile.tsx b/packages/cyberstorm/src/components/Layout/Settings/Profile/Profile.tsx index 36976bcdb..7a0b08a0e 100644 --- a/packages/cyberstorm/src/components/Layout/Settings/Profile/Profile.tsx +++ b/packages/cyberstorm/src/components/Layout/Settings/Profile/Profile.tsx @@ -43,12 +43,12 @@ export function Profile(props: ProfileProps) { } + content={} /> } + content={} />
diff --git a/packages/cyberstorm/src/components/Layout/Teams/TeamSettings/TeamDetails/TeamDetails.tsx b/packages/cyberstorm/src/components/Layout/Teams/TeamSettings/TeamDetails/TeamDetails.tsx index f598ba02c..414084f0b 100644 --- a/packages/cyberstorm/src/components/Layout/Teams/TeamSettings/TeamDetails/TeamDetails.tsx +++ b/packages/cyberstorm/src/components/Layout/Teams/TeamSettings/TeamDetails/TeamDetails.tsx @@ -76,7 +76,7 @@ export function TeamDetails(props: Props) { } + content={} /> { - value?: string; leftIcon?: JSX.Element; rightIcon?: JSX.Element; color?: string; - enterHook?: (value: string) => string | void; + enterHook?: (value: string | number | readonly string[]) => string | void; } /** @@ -28,7 +27,7 @@ export const TextInput = React.forwardRef( } = props; const onEnter = (e: React.KeyboardEvent) => { if (enterHook && e.key === "Enter") { - enterHook(value.toLowerCase()); + enterHook(value); } }; @@ -47,7 +46,7 @@ export const TextInput = React.forwardRef( leftIcon ? styles.hasLeftIcon : null )} value={value} - onKeyDown={(e) => onEnter(e)} + onKeyDown={onEnter} data-color={color} /> {rightIcon ? ( diff --git a/packages/cyberstorm/src/components/Toast/Provider.tsx b/packages/cyberstorm/src/components/Toast/Provider.tsx index ba4547d8a..30ecf5226 100644 --- a/packages/cyberstorm/src/components/Toast/Provider.tsx +++ b/packages/cyberstorm/src/components/Toast/Provider.tsx @@ -46,7 +46,7 @@ interface ContextInterface { remove: (id: string) => void; } -export const ToastContext = createContext(null); +const ToastContext = createContext(null); export function Provider(props: { toastDuration: number } & PropsWithChildren) { const [state, dispatch] = useReducer(toastReducer, initState); diff --git a/packages/cyberstorm/src/utils/type_guards.ts b/packages/cyberstorm/src/utils/type_guards.ts index a9f56ac58..2b119a63f 100644 --- a/packages/cyberstorm/src/utils/type_guards.ts +++ b/packages/cyberstorm/src/utils/type_guards.ts @@ -4,14 +4,14 @@ export const isRecord = (obj: unknown): obj is Record => export const isStringArray = (arr: unknown): arr is string[] => Array.isArray(arr) && arr.every((s) => typeof s === "string"); -interface ErrorResponse { - error: { message: string }; +export interface FormErrorResponse { + [key: string]: string[]; } -export function isErrorResponse(response: unknown): response is ErrorResponse { +export function isFormErrorResponse( + response: unknown +): response is FormErrorResponse { return ( - isRecord(response) && - isRecord(response.error) && - typeof response.error.message === "string" + isRecord(response) && Object.values(response).every((v) => isStringArray(v)) ); }