From b214f2828871b0716d1fcc1602eb3e9545ed3b7b Mon Sep 17 00:00:00 2001 From: Aleksandr Kiliushin Date: Sun, 18 Jun 2023 11:59:32 +0400 Subject: [PATCH 1/4] n99: chore: Install zod --- package-lock.json | 40 +++++++++++++++++++++++++++------------- package.json | 3 ++- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index f90c1ac..7646234 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,8 @@ "react-router-dom": "6.13.0", "react-use": "17.4.0", "recharts": "2.7.1", - "yup": "1.2.0" + "yup": "1.2.0", + "zod": "3.21.4" }, "devDependencies": { "@babel/core": "7.22.5", @@ -6256,9 +6257,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001503", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001503.tgz", - "integrity": "sha512-Sf9NiF+wZxPfzv8Z3iS0rXM1Do+iOy2Lxvib38glFX+08TCYYYGR5fRJXk4d77C4AYwhUjgYgMsMudbh2TqCKw==", + "version": "1.0.30001504", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001504.tgz", + "integrity": "sha512-5uo7eoOp2mKbWyfMXnGO9rJWOGU8duvzEiYITW+wivukL7yHH4gX9yuRaobu6El4jPxo6jKZfG+N6fB621GD/Q==", "dev": true, "funding": [ { @@ -7597,9 +7598,9 @@ } }, "node_modules/dotenv": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.0.tgz", - "integrity": "sha512-tHB+hmf8MRCkT3VVivGiG8kq9HiGTmQ3FzOKgztfpJQH1IWuZTOvKSJmHNnQPowecAmkCJhLrxdPhOr06LLqIQ==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", "dev": true, "engines": { "node": ">=12" @@ -14994,6 +14995,14 @@ "dependencies": { "zen-observable": "0.8.15" } + }, + "node_modules/zod": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", + "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } }, "dependencies": { @@ -19586,9 +19595,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001503", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001503.tgz", - "integrity": "sha512-Sf9NiF+wZxPfzv8Z3iS0rXM1Do+iOy2Lxvib38glFX+08TCYYYGR5fRJXk4d77C4AYwhUjgYgMsMudbh2TqCKw==", + "version": "1.0.30001504", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001504.tgz", + "integrity": "sha512-5uo7eoOp2mKbWyfMXnGO9rJWOGU8duvzEiYITW+wivukL7yHH4gX9yuRaobu6El4jPxo6jKZfG+N6fB621GD/Q==", "dev": true }, "capital-case": { @@ -20594,9 +20603,9 @@ } }, "dotenv": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.0.tgz", - "integrity": "sha512-tHB+hmf8MRCkT3VVivGiG8kq9HiGTmQ3FzOKgztfpJQH1IWuZTOvKSJmHNnQPowecAmkCJhLrxdPhOr06LLqIQ==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", "dev": true }, "dset": { @@ -26119,6 +26128,11 @@ "requires": { "zen-observable": "0.8.15" } + }, + "zod": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz", + "integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==" } } } diff --git a/package.json b/package.json index 36429ae..b37a6c2 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "react-router-dom": "6.13.0", "react-use": "17.4.0", "recharts": "2.7.1", - "yup": "1.2.0" + "yup": "1.2.0", + "zod": "3.21.4" }, "devDependencies": { "@babel/core": "7.22.5", From 068db4337188df95006c4b77af525738029bf507 Mon Sep 17 00:00:00 2001 From: Aleksandr Kiliushin Date: Sun, 18 Jun 2023 12:06:09 +0400 Subject: [PATCH 2/4] n99: refactor: Refactor imports --- cypress/component/Dialog.cy.tsx | 3 ++- cypress/e2e/budget-board-creating.cy.ts | 2 +- src/components/Navbar/helpers.tsx | 3 ++- src/utils/getChildByDisplayName.ts | 10 ++-------- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/cypress/component/Dialog.cy.tsx b/cypress/component/Dialog.cy.tsx index e86e421..ad73b83 100644 --- a/cypress/component/Dialog.cy.tsx +++ b/cypress/component/Dialog.cy.tsx @@ -1,9 +1,10 @@ import { Button, Typography } from "@mui/material" +import { FC } from "react" import { useToggle } from "react-use" import { Dialog } from "#components/Dialog" -const SampleComponentWithDialog: React.FC = () => { +const SampleComponentWithDialog: FC = () => { const [isMyDialogOpen, toggleIsMyDialogOpen] = useToggle(false) return ( diff --git a/cypress/e2e/budget-board-creating.cy.ts b/cypress/e2e/budget-board-creating.cy.ts index bfba3fa..e5f1809 100644 --- a/cypress/e2e/budget-board-creating.cy.ts +++ b/cypress/e2e/budget-board-creating.cy.ts @@ -13,7 +13,7 @@ describe("Budget board creating", () => { cy.contains('"clever-budgetiers" budget board already exists.').should("be.visible") }) - it("board craeted successfully", () => { + it("board created successfully", () => { cy.authorize(testUsers.johnDoe.id) cy.visit("/boards") diff --git a/src/components/Navbar/helpers.tsx b/src/components/Navbar/helpers.tsx index 43454a9..d22e5ea 100644 --- a/src/components/Navbar/helpers.tsx +++ b/src/components/Navbar/helpers.tsx @@ -1,7 +1,8 @@ import { Dashboard as DashboardIcon, Person as PersonIcon } from "@mui/icons-material" +import { ReactElement } from "react" type TSection = { - icon: React.ReactElement + icon: ReactElement id: string path: string } diff --git a/src/utils/getChildByDisplayName.ts b/src/utils/getChildByDisplayName.ts index 5976d1b..50a5aee 100644 --- a/src/utils/getChildByDisplayName.ts +++ b/src/utils/getChildByDisplayName.ts @@ -1,12 +1,6 @@ -import { Children, isValidElement } from "react" +import { Children, ReactNode, isValidElement } from "react" -export const getChildByDisplayName = ({ - children, - displayName, -}: { - children: React.ReactNode - displayName: string -}) => { +export const getChildByDisplayName = ({ children, displayName }: { children: ReactNode; displayName: string }) => { return Children.map(children, (child) => { if (!isValidElement(child)) return null // eslint-disable-next-line @typescript-eslint/no-explicit-any From 333903fd6c4ea223439d5107e6deb902e1cecb3e Mon Sep 17 00:00:00 2001 From: Aleksandr Kiliushin Date: Sun, 18 Jun 2023 12:15:33 +0400 Subject: [PATCH 3/4] n99: fix: Fix RecordFormDialog currencySlug default value, unskip tests --- cypress/e2e/budget-board-settings.cy.ts | 8 ++--- cypress/e2e/budget-records.cy.ts | 4 +-- .../RecordFormDialog/form-helpers.ts | 34 +++++++++---------- .../BoardRecords/RecordFormDialog/index.tsx | 28 ++++++++------- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/cypress/e2e/budget-board-settings.cy.ts b/cypress/e2e/budget-board-settings.cy.ts index 05ff113..0ebf04b 100644 --- a/cypress/e2e/budget-board-settings.cy.ts +++ b/cypress/e2e/budget-board-settings.cy.ts @@ -48,8 +48,7 @@ describe("Budget board settings", () => { it("board default currency is edited correctly", () => { cy.authorize(testUsers.johnDoe.id) - cy.visit("/boards/1/records") - cy.get("[aria-label='Add record']").click() + cy.visit("/boards/1/records/add") cy.get("#mui-component-select-currencySlug").should("contain", "GEL ₾") cy.visit("/boards/1/settings") @@ -61,14 +60,13 @@ describe("Budget board settings", () => { cy.get("td").contains("GEL ₾").should("not.exist") cy.get("td").contains("USD $").should("be.visible") - cy.visit("/boards/1/records") - cy.get("[aria-label='Add record']").click() + cy.visit("/boards/1/records/add") cy.get("#mui-component-select-currencySlug").should("contain", "USD $") }) }) }) - describe.only("Budget categories settings", () => { + describe("Budget categories settings", () => { it("budget board settings are fetched and rendered correctly", () => { cy.authorize(testUsers.johnDoe.id) cy.visit("/boards/1/settings") diff --git a/cypress/e2e/budget-records.cy.ts b/cypress/e2e/budget-records.cy.ts index a34512a..22ee390 100644 --- a/cypress/e2e/budget-records.cy.ts +++ b/cypress/e2e/budget-records.cy.ts @@ -41,7 +41,7 @@ describe("Budget records", () => { cy.get("[role='progressbar'][aria-label='Loading more records']").should("not.exist") }) - it.only("is created correctly", () => { + it("is created correctly", () => { cy.authorize(testUsers.johnDoe.id) cy.visit("/boards/1/records") @@ -51,7 +51,7 @@ describe("Budget records", () => { cy.get("[role='option']").contains("education").click() cy.get("#mui-component-select-currencySlug").click() cy.get("[role='option']").contains("USD $").click() - cy.get("p").contains("amount must be a positive number").should("be.visible") + cy.get("p").contains("Number must be greater than 0").should("be.visible") cy.get("input[name='amount']").clear().type("4.53") // TODO: Enter a date. cy.get("button").contains("Add").click() diff --git a/src/views/boards/BoardRecords/RecordFormDialog/form-helpers.ts b/src/views/boards/BoardRecords/RecordFormDialog/form-helpers.ts index 422dfae..8cdaf88 100644 --- a/src/views/boards/BoardRecords/RecordFormDialog/form-helpers.ts +++ b/src/views/boards/BoardRecords/RecordFormDialog/form-helpers.ts @@ -1,6 +1,4 @@ -import * as Yup from "yup" - -import { BudgetRecord } from "#api/types" +import { z } from "zod" export enum FieldName { Amount = "amount", @@ -10,18 +8,20 @@ export enum FieldName { Date = "date", } -export type TFormValues = { - [FieldName.Amount]: BudgetRecord["amount"] | null - [FieldName.CategoryId]: BudgetRecord["category"]["id"] | null - [FieldName.Comment]: BudgetRecord["comment"] - [FieldName.CurrencySlug]: BudgetRecord["currency"]["slug"] - [FieldName.Date]: BudgetRecord["date"] -} - -export const validationSchema = Yup.object({ - [FieldName.Amount]: Yup.number().required().positive(), - [FieldName.CategoryId]: Yup.number().required(), - [FieldName.Comment]: Yup.string(), - [FieldName.CurrencySlug]: Yup.string().required(), - [FieldName.Date]: Yup.string().required(), +export const validationSchema = z.object({ + [FieldName.Amount]: z.number().positive(), + [FieldName.CategoryId]: z.number(), + [FieldName.Comment]: z.string(), + [FieldName.CurrencySlug]: z.string().nonempty(), + [FieldName.Date]: z.string(), }) + +export type TFormValidValues = z.infer + +export type TFormDefaultValues = { + [FieldName.Amount]: number | null + [FieldName.CategoryId]: number | null + [FieldName.Comment]: string + [FieldName.CurrencySlug]: string | null + [FieldName.Date]: string +} diff --git a/src/views/boards/BoardRecords/RecordFormDialog/index.tsx b/src/views/boards/BoardRecords/RecordFormDialog/index.tsx index bd16541..59d69bd 100644 --- a/src/views/boards/BoardRecords/RecordFormDialog/index.tsx +++ b/src/views/boards/BoardRecords/RecordFormDialog/index.tsx @@ -1,7 +1,7 @@ -import { yupResolver } from "@hookform/resolvers/yup" +import { zodResolver } from "@hookform/resolvers/zod" import { Box, Button, FormControl, InputLabel, MenuItem, Select, TextField, Typography } from "@mui/material" import { format as formatDate } from "date-fns" -import { FC } from "react" +import { FC, useEffect } from "react" import { useForm } from "react-hook-form" import { Link, useNavigate, useParams } from "react-router-dom" @@ -19,7 +19,7 @@ import { Dialog } from "#components/Dialog" import { RowGroup } from "#components/RowGroup" import { theme } from "#styles/theme" -import { FieldName, TFormValues, validationSchema } from "./form-helpers" +import { FieldName, TFormDefaultValues, TFormValidValues, validationSchema } from "./form-helpers" const budgetCategoryIndicatorColorByBudgetCategoryType = new Map([ [1, theme.palette.error.main], @@ -56,7 +56,7 @@ export const RecordFormDialog: FC = ({ record }) => { const getAuthorizedUserResult = useGetUserQuery({ variables: { id: 0 } }) const authorizedUser = getAuthorizedUserResult.data?.user - const defaultValues = record + const defaultValues: TFormDefaultValues = record ? { amount: record.amount, categoryId: record.category.id, @@ -68,18 +68,23 @@ export const RecordFormDialog: FC = ({ record }) => { amount: null, categoryId: null, comment: "", - currencySlug: board?.defaultCurrency?.slug ?? "", + currencySlug: null, date: formatDate(new Date(), "yyyy-MM-dd"), } - const { formState, handleSubmit, register } = useForm({ + const { formState, handleSubmit, register, watch, setValue } = useForm({ defaultValues, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore TODO: Migrate to zod. - resolver: yupResolver(validationSchema), mode: "onChange", + resolver: zodResolver(validationSchema), }) + useEffect(() => { + // Is it possible for a board to have default currency nul | undefined? + if (board?.defaultCurrency && watch("currencySlug") === null) { + setValue(FieldName.CurrencySlug, board.defaultCurrency.slug) + } + }, [board, setValue, watch]) + const getCurrenciesResult = useGetCurrenciesQuery() const getBoardBudgetCategoriesResult = useGetBudgetCategoriesQuery({ variables: { boardsIds: [Number(params.boardId)], orderingByType: "ASC" }, @@ -108,9 +113,6 @@ export const RecordFormDialog: FC = ({ record }) => { const closeDialogHref = `/boards/${params.boardId}/records${location.search}` const submitRecordForm = handleSubmit((formValues) => { - if (formValues.amount === null) return - if (formValues.categoryId === null) return - if (record === undefined) { createBudgetRecord({ variables: { @@ -160,8 +162,8 @@ export const RecordFormDialog: FC = ({ record }) => { Currency