diff --git a/docs/docs/contributor/frontend/advanced/best-practices.mdx b/docs/docs/contributor/frontend/advanced/best-practices.mdx index 0c495dfec..5444d0dba 100644 --- a/docs/docs/contributor/frontend/advanced/best-practices.mdx +++ b/docs/docs/contributor/frontend/advanced/best-practices.mdx @@ -303,6 +303,27 @@ import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator'; import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';= ``` +## Schema Validation + +[Zod](https://github.com/colinhacks/zod) is used as the schema validator for untyped objects: + +```js +const validationSchema = z + .object({ + exist: z.boolean(), + email: z + .string() + .email('Email must be a valid email'), + password: z + .string() + .regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'), + }) + .required(); + +type Form = z.infer; +``` + + ## Breaking Changes Prioritize thorough manual testing before proceeding to guarantee that modifications haven’t caused disruptions elsewhere, given that tests have not yet been extensively integrated. diff --git a/front/package.json b/front/package.json index c8d81d69e..da7855801 100644 --- a/front/package.json +++ b/front/package.json @@ -64,7 +64,6 @@ "uuid": "^9.0.0", "web-vitals": "^2.1.4", "xlsx-ugnis": "^0.19.3", - "yup": "^1.2.0", "zod": "^3.22.2" }, "scripts": { diff --git a/front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx b/front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx index 0d34f7eea..d863a7772 100644 --- a/front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx +++ b/front/src/modules/auth/sign-in-up/hooks/useSignInUp.tsx @@ -1,9 +1,9 @@ import { useCallback, useEffect, useState } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; import { useNavigate, useParams } from 'react-router-dom'; -import { yupResolver } from '@hookform/resolvers/yup'; +import { zodResolver } from '@hookform/resolvers/zod'; import { useRecoilState, useRecoilValue } from 'recoil'; -import * as Yup from 'yup'; +import { z } from 'zod'; import { authProvidersState } from '@/client-config/states/authProvidersState'; import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState'; @@ -28,19 +28,18 @@ export enum SignInUpStep { Email = 'email', Password = 'password', } -const validationSchema = Yup.object() - .shape({ - exist: Yup.boolean().required(), - email: Yup.string() - .email('Email must be a valid email') - .required('Email must be a valid email'), - password: Yup.string() - .matches(PASSWORD_REGEX, 'Password must contain at least 8 characters') - .required(), + +const validationSchema = z + .object({ + exist: z.boolean(), + email: z.string().email('Email must be a valid email'), + password: z + .string() + .regex(PASSWORD_REGEX, 'Password must contain at least 8 characters'), }) .required(); -type Form = Yup.InferType; +type Form = z.infer; export const useSignInUp = () => { const navigate = useNavigate(); @@ -72,7 +71,7 @@ export const useSignInUp = () => { defaultValues: { exist: false, }, - resolver: yupResolver(validationSchema), + resolver: zodResolver(validationSchema), }); useEffect(() => { diff --git a/front/src/modules/ui/data/field/types/guards/isFieldBooleanValue.ts b/front/src/modules/ui/data/field/types/guards/isFieldBooleanValue.ts index 70dfe4c0c..f3637a465 100644 --- a/front/src/modules/ui/data/field/types/guards/isFieldBooleanValue.ts +++ b/front/src/modules/ui/data/field/types/guards/isFieldBooleanValue.ts @@ -2,7 +2,7 @@ import { isBoolean } from '@sniptt/guards'; import { FieldBooleanValue } from '../FieldMetadata'; -// TODO: add yup +// TODO: add zod export const isFieldBooleanValue = ( fieldValue: unknown, ): fieldValue is FieldBooleanValue => isBoolean(fieldValue); diff --git a/front/src/modules/ui/data/field/types/guards/isFieldChipValue.ts b/front/src/modules/ui/data/field/types/guards/isFieldChipValue.ts index 1be6f1737..d276b36af 100644 --- a/front/src/modules/ui/data/field/types/guards/isFieldChipValue.ts +++ b/front/src/modules/ui/data/field/types/guards/isFieldChipValue.ts @@ -2,7 +2,7 @@ import { isString } from '@sniptt/guards'; import { FieldChipValue } from '../FieldMetadata'; -// TODO: add yup +// TODO: add zod export const isFieldChipValue = ( fieldValue: unknown, ): fieldValue is FieldChipValue => isString(fieldValue); diff --git a/front/src/modules/ui/data/field/types/guards/isFieldDateValue.ts b/front/src/modules/ui/data/field/types/guards/isFieldDateValue.ts index c84f9264b..0611a5fc7 100644 --- a/front/src/modules/ui/data/field/types/guards/isFieldDateValue.ts +++ b/front/src/modules/ui/data/field/types/guards/isFieldDateValue.ts @@ -2,7 +2,7 @@ import { isNull, isString } from '@sniptt/guards'; import { FieldDateValue } from '../FieldMetadata'; -// TODO: add yup +// TODO: add zod export const isFieldDateValue = ( fieldValue: unknown, ): fieldValue is FieldDateValue => isNull(fieldValue) || isString(fieldValue); diff --git a/front/src/modules/ui/data/field/types/guards/isFieldDoubleTextValue.ts b/front/src/modules/ui/data/field/types/guards/isFieldDoubleTextValue.ts index cd38e733f..53eeee541 100644 --- a/front/src/modules/ui/data/field/types/guards/isFieldDoubleTextValue.ts +++ b/front/src/modules/ui/data/field/types/guards/isFieldDoubleTextValue.ts @@ -1,7 +1,7 @@ import { FieldDoubleTextValue } from '../FieldMetadata'; import { DoubleTextTypeResolver } from '../resolvers/DoubleTextTypeResolver'; -// TODO: add yup +// TODO: add zod export const isFieldDoubleTextValue = ( fieldValue: unknown, ): fieldValue is FieldDoubleTextValue => diff --git a/front/src/modules/ui/data/field/types/guards/isFieldEmailValue.ts b/front/src/modules/ui/data/field/types/guards/isFieldEmailValue.ts index 0594b8ee4..2ce954057 100644 --- a/front/src/modules/ui/data/field/types/guards/isFieldEmailValue.ts +++ b/front/src/modules/ui/data/field/types/guards/isFieldEmailValue.ts @@ -2,7 +2,7 @@ import { isString } from '@sniptt/guards'; import { FieldEmailValue } from '../FieldMetadata'; -// TODO: add yup +// TODO: add zod export const isFieldEmailValue = ( fieldValue: unknown, ): fieldValue is FieldEmailValue => isString(fieldValue); diff --git a/front/src/modules/ui/data/field/types/guards/isFieldMoneyValue.ts b/front/src/modules/ui/data/field/types/guards/isFieldMoneyValue.ts index 6dad1dae3..9db82cf2d 100644 --- a/front/src/modules/ui/data/field/types/guards/isFieldMoneyValue.ts +++ b/front/src/modules/ui/data/field/types/guards/isFieldMoneyValue.ts @@ -2,7 +2,7 @@ import { isNull, isNumber } from '@sniptt/guards'; import { FieldMoneyValue } from '../FieldMetadata'; -// TODO: add yup +// TODO: add zod export const isFieldMoneyValue = ( fieldValue: unknown, ): fieldValue is FieldMoneyValue => isNull(fieldValue) || isNumber(fieldValue); diff --git a/front/src/modules/ui/data/field/types/guards/isFieldNumberValue.ts b/front/src/modules/ui/data/field/types/guards/isFieldNumberValue.ts index 4ab45262d..0d0b2395e 100644 --- a/front/src/modules/ui/data/field/types/guards/isFieldNumberValue.ts +++ b/front/src/modules/ui/data/field/types/guards/isFieldNumberValue.ts @@ -2,7 +2,7 @@ import { isNull, isNumber } from '@sniptt/guards'; import { FieldNumberValue } from '../FieldMetadata'; -// TODO: add yup +// TODO: add zod export const isFieldNumberValue = ( fieldValue: unknown, ): fieldValue is FieldNumberValue => isNull(fieldValue) || isNumber(fieldValue); diff --git a/front/src/modules/ui/data/field/types/guards/isFieldPhoneValue.ts b/front/src/modules/ui/data/field/types/guards/isFieldPhoneValue.ts index e8634d77b..4bb79a9e6 100644 --- a/front/src/modules/ui/data/field/types/guards/isFieldPhoneValue.ts +++ b/front/src/modules/ui/data/field/types/guards/isFieldPhoneValue.ts @@ -2,7 +2,7 @@ import { isString } from '@sniptt/guards'; import { FieldPhoneValue } from '../FieldMetadata'; -// TODO: add yup +// TODO: add zod export const isFieldPhoneValue = ( fieldValue: unknown, ): fieldValue is FieldPhoneValue => isString(fieldValue); diff --git a/front/src/modules/ui/data/field/types/guards/isFieldProbabilityValue.ts b/front/src/modules/ui/data/field/types/guards/isFieldProbabilityValue.ts index 134a0e9e5..1c989fda9 100644 --- a/front/src/modules/ui/data/field/types/guards/isFieldProbabilityValue.ts +++ b/front/src/modules/ui/data/field/types/guards/isFieldProbabilityValue.ts @@ -2,7 +2,7 @@ import { isNumber } from '@sniptt/guards'; import { FieldProbabilityValue } from '../FieldMetadata'; -// TODO: add yup +// TODO: add zod export const isFieldProbabilityValue = ( fieldValue: unknown, ): fieldValue is FieldProbabilityValue => isNumber(fieldValue); diff --git a/front/src/modules/ui/data/field/types/guards/isFieldRelationValue.ts b/front/src/modules/ui/data/field/types/guards/isFieldRelationValue.ts index afdbc5fec..879437f91 100644 --- a/front/src/modules/ui/data/field/types/guards/isFieldRelationValue.ts +++ b/front/src/modules/ui/data/field/types/guards/isFieldRelationValue.ts @@ -2,7 +2,7 @@ import { isNull, isObject, isUndefined } from '@sniptt/guards'; import { FieldRelationValue } from '../FieldMetadata'; -// TODO: add yup +// TODO: add zod export const isFieldRelationValue = ( fieldValue: unknown, ): fieldValue is FieldRelationValue => diff --git a/front/src/modules/ui/data/field/types/guards/isFieldTextValue.ts b/front/src/modules/ui/data/field/types/guards/isFieldTextValue.ts index 77efd6973..b1001bd88 100644 --- a/front/src/modules/ui/data/field/types/guards/isFieldTextValue.ts +++ b/front/src/modules/ui/data/field/types/guards/isFieldTextValue.ts @@ -2,7 +2,7 @@ import { isString } from '@sniptt/guards'; import { FieldTextValue } from '../FieldMetadata'; -// TODO: add yup +// TODO: add zod export const isFieldTextValue = ( fieldValue: unknown, ): fieldValue is FieldTextValue => isString(fieldValue); diff --git a/front/src/modules/ui/data/field/types/guards/isFieldURLV2Value.ts b/front/src/modules/ui/data/field/types/guards/isFieldURLV2Value.ts index 2fe1de753..713d60f40 100644 --- a/front/src/modules/ui/data/field/types/guards/isFieldURLV2Value.ts +++ b/front/src/modules/ui/data/field/types/guards/isFieldURLV2Value.ts @@ -7,7 +7,7 @@ const urlV2Schema = z.object({ text: z.string(), }); -// TODO: add yup +// TODO: add zod export const isFieldURLV2Value = ( fieldValue: unknown, ): fieldValue is FieldURLV2Value => urlV2Schema.safeParse(fieldValue).success; diff --git a/front/src/modules/ui/data/field/types/guards/isFieldURLValue.ts b/front/src/modules/ui/data/field/types/guards/isFieldURLValue.ts index a4a50120e..dede3f5fb 100644 --- a/front/src/modules/ui/data/field/types/guards/isFieldURLValue.ts +++ b/front/src/modules/ui/data/field/types/guards/isFieldURLValue.ts @@ -2,7 +2,7 @@ import { isString } from '@sniptt/guards'; import { FieldURLValue } from '../FieldMetadata'; -// TODO: add yup +// TODO: add zod export const isFieldURLValue = ( fieldValue: unknown, ): fieldValue is FieldURLValue => isString(fieldValue); diff --git a/front/src/pages/auth/CreateProfile.tsx b/front/src/pages/auth/CreateProfile.tsx index 0e4d1b635..63df14b0d 100644 --- a/front/src/pages/auth/CreateProfile.tsx +++ b/front/src/pages/auth/CreateProfile.tsx @@ -3,10 +3,10 @@ import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; import { getOperationName } from '@apollo/client/utilities'; import styled from '@emotion/styled'; -import { yupResolver } from '@hookform/resolvers/yup'; +import { zodResolver } from '@hookform/resolvers/zod'; import { useRecoilState } from 'recoil'; import { Key } from 'ts-key-enum'; -import * as Yup from 'yup'; +import { z } from 'zod'; import { SubTitle } from '@/auth/components/SubTitle'; import { Title } from '@/auth/components/Title'; @@ -42,14 +42,14 @@ const StyledComboInputContainer = styled.div` } `; -const validationSchema = Yup.object() - .shape({ - firstName: Yup.string().required('First name can not be empty'), - lastName: Yup.string().required('Last name can not be empty'), +const validationSchema = z + .object({ + firstName: z.string().min(1, { message: 'First name can not be empty' }), + lastName: z.string().min(1, { message: 'Last name can not be empty' }), }) .required(); -type Form = Yup.InferType; +type Form = z.infer; export const CreateProfile = () => { const navigate = useNavigate(); @@ -72,7 +72,7 @@ export const CreateProfile = () => { firstName: currentUser?.firstName ?? '', lastName: currentUser?.lastName ?? '', }, - resolver: yupResolver(validationSchema), + resolver: zodResolver(validationSchema), }); const onSubmit: SubmitHandler
= useCallback( diff --git a/front/src/pages/auth/CreateWorkspace.tsx b/front/src/pages/auth/CreateWorkspace.tsx index 80a26d5bb..78810b22f 100644 --- a/front/src/pages/auth/CreateWorkspace.tsx +++ b/front/src/pages/auth/CreateWorkspace.tsx @@ -3,8 +3,8 @@ import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; import { getOperationName } from '@apollo/client/utilities'; import styled from '@emotion/styled'; -import { yupResolver } from '@hookform/resolvers/yup'; -import * as Yup from 'yup'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; import { SubTitle } from '@/auth/components/SubTitle'; import { Title } from '@/auth/components/Title'; @@ -31,13 +31,13 @@ const StyledButtonContainer = styled.div` width: 200px; `; -const validationSchema = Yup.object() - .shape({ - name: Yup.string().required('Name can not be empty'), +const validationSchema = z + .object({ + name: z.string().min(1, { message: 'Name can not be empty' }), }) .required(); -type Form = Yup.InferType; +type Form = z.infer; export const CreateWorkspace = () => { const navigate = useNavigate(); @@ -57,7 +57,7 @@ export const CreateWorkspace = () => { defaultValues: { name: '', }, - resolver: yupResolver(validationSchema), + resolver: zodResolver(validationSchema), }); const onSubmit: SubmitHandler = useCallback( diff --git a/front/yarn.lock b/front/yarn.lock index 716f84da1..849c23cae 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -15975,11 +15975,6 @@ prop-types@^15.6.1, prop-types@^15.7.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -property-expr@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.5.tgz#278bdb15308ae16af3e3b9640024524f4dc02cb4" - integrity sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA== - property-information@^6.0.0: version "6.2.0" resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.2.0.tgz#b74f522c31c097b5149e3c3cb8d7f3defd986a1d" @@ -18399,11 +18394,6 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== -tiny-case@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03" - integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q== - tiny-invariant@^1.0.6, tiny-invariant@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" @@ -18462,11 +18452,6 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -toposort@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" - integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== - tough-cookie@^4.0.0: version "4.1.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" @@ -19996,16 +19981,6 @@ yocto-queue@^1.0.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== -yup@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/yup/-/yup-1.2.0.tgz#9e51af0c63bdfc9be0fdc6c10aa0710899d8aff6" - integrity sha512-PPqYKSAXjpRCgLgLKVGPA33v5c/WgEx3wi6NFjIiegz90zSwyMpvTFp/uGcVnnbx6to28pgnzp/q8ih3QRjLMQ== - dependencies: - property-expr "^2.0.5" - tiny-case "^1.0.3" - toposort "^2.0.2" - type-fest "^2.19.0" - zen-observable-ts@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz#6c6d9ea3d3a842812c6e9519209365a122ba8b58"