From 2e08e17056b0293e2ed61ab0ab7ec208835db647 Mon Sep 17 00:00:00 2001 From: Pumnia Mykhailo <74021315+GvoFor@users.noreply.github.com> Date: Mon, 2 Sep 2024 10:57:10 +0300 Subject: [PATCH] feat: add an ability to create a project gf-87 (#115) * fix: filename gf-87 * feat: improve Button component gf-87 * feat: improve Input component gf-87 * feat: add an ability to create new project gf-87 * fix: disable button while name is empty gf-87 * refactor: move empty string length const into consts folder gf-87 * refactor: cleanup Input component and its styles gf-87 * refactor: rename Button disabled prop and fix its styles gf-87 * fix: correct the order of projects returned by backend gf-87 * fix: close modal only on success gf-87 Also: - add success toast notification - fix data rerendering issue * fix: wrap modal open/close functions with useCallback gf-87 Without useCallback this functions recalculates when modal opens, which leads to issues when use these functions as dependencies in other hooks * refactor: rename 'success message' to 'notification message' gf-87 * refactor: rename 'projectsStatus' back to 'dataStatus' gf-87 * fix: button styles gf-87 * refactor: rename EMPTY_STRING_LENGTH constant and make it common gf-87 * refactor: rid of DESCRIPTION_ROWS_COUNT constant gf-87 * refactor: rename 'rows' to 'rowsCount' gf-87 * refactor: move EMPTY_LENGTH to shared package gf-87 * refactor: create SortType enum gf-87 * refactor: rename 'event' to 'event_' gf-87 * fix: make create button disabled only when there is errors in form gf-87 * fix: remove button disalability gf-87 * refactor: make button variant prop inline gf-87 * refactor: move SortType to shared gf-87 * fix: rid of ProjectCreateResponseDto gf-87 --------- Co-authored-by: Yelyzaveta Veis <64521658+liza-veis@users.noreply.github.com> --- apps/backend/src/libs/enums/enums.ts | 1 + .../modules/projects/project.repository.ts | 6 +- apps/frontend/src/assets/css/variables.css | 1 + .../src/libs/components/button/button.tsx | 21 ++++++- .../libs/components/button/styles.module.css | 18 ++++++ .../src/libs/components/input/input.tsx | 43 ++++++++++----- .../libs/components/input/styles.module.css | 15 +++++ apps/frontend/src/libs/constants/constants.ts | 1 + apps/frontend/src/libs/enums/enums.ts | 2 +- ...e.enum.ts => notification-message.enum.ts} | 1 + .../use-modal-state/use-modal-state.hook.ts | 10 ++-- .../src/modules/projects/libs/types/types.ts | 1 + .../validation-schemas/validation-schemas.ts | 1 + .../src/modules/projects/projects-api.ts | 17 ++++++ .../frontend/src/modules/projects/projects.ts | 2 + .../src/modules/projects/slices/actions.ts | 18 +++++- .../modules/projects/slices/project.slice.ts | 14 ++++- .../src/modules/projects/slices/projects.ts | 3 +- .../pages/projects/components/components.ts | 1 + .../libs/constants/constants.ts | 1 + ...default-project-create-payload.constant.ts | 8 +++ .../project-create-form.tsx | 55 +++++++++++++++++++ .../project-create-form/styles.module.css | 12 ++++ apps/frontend/src/pages/projects/projects.tsx | 42 ++++++++++++-- .../src/pages/projects/styles.module.css | 5 ++ packages/shared/src/index.ts | 2 + .../shared/src/libs/constants/constants.ts | 1 + .../libs/constants/empty-length.constant.ts | 3 + packages/shared/src/libs/enums/enums.ts | 1 + .../shared/src/libs/enums/sort-type.enum.ts | 6 ++ .../projects/libs/exceptions/exceptions.ts | 2 +- ...xception.ts => project-error.exception.ts} | 0 32 files changed, 280 insertions(+), 34 deletions(-) rename apps/frontend/src/libs/enums/{notification-massage.enum.ts => notification-message.enum.ts} (70%) create mode 100644 apps/frontend/src/modules/projects/libs/validation-schemas/validation-schemas.ts create mode 100644 apps/frontend/src/pages/projects/components/project-create-form/libs/constants/constants.ts create mode 100644 apps/frontend/src/pages/projects/components/project-create-form/libs/constants/default-project-create-payload.constant.ts create mode 100644 apps/frontend/src/pages/projects/components/project-create-form/project-create-form.tsx create mode 100644 apps/frontend/src/pages/projects/components/project-create-form/styles.module.css create mode 100644 packages/shared/src/libs/constants/constants.ts create mode 100644 packages/shared/src/libs/constants/empty-length.constant.ts create mode 100644 packages/shared/src/libs/enums/sort-type.enum.ts rename packages/shared/src/modules/projects/libs/exceptions/{user-error.exception.ts => project-error.exception.ts} (100%) diff --git a/apps/backend/src/libs/enums/enums.ts b/apps/backend/src/libs/enums/enums.ts index d2783b5bc..3715b486c 100644 --- a/apps/backend/src/libs/enums/enums.ts +++ b/apps/backend/src/libs/enums/enums.ts @@ -1,3 +1,4 @@ +export { SortType } from "@git-fit/shared"; export { APIPath, AppEnvironment, diff --git a/apps/backend/src/modules/projects/project.repository.ts b/apps/backend/src/modules/projects/project.repository.ts index 3bd525726..e6aafc9bc 100644 --- a/apps/backend/src/modules/projects/project.repository.ts +++ b/apps/backend/src/modules/projects/project.repository.ts @@ -1,3 +1,4 @@ +import { SortType } from "~/libs/enums/enums.js"; import { type Repository } from "~/libs/types/types.js"; import { ProjectEntity } from "./project.entity.js"; @@ -36,7 +37,10 @@ class ProjectRepository implements Repository { } public async findAll(): Promise { - const projects = await this.projectModel.query().execute(); + const projects = await this.projectModel + .query() + .orderBy("created_at", SortType.DESCENDING) + .execute(); return projects.map((project) => ProjectEntity.initialize(project)); } diff --git a/apps/frontend/src/assets/css/variables.css b/apps/frontend/src/assets/css/variables.css index da9ddc1b4..e4f169378 100644 --- a/apps/frontend/src/assets/css/variables.css +++ b/apps/frontend/src/assets/css/variables.css @@ -19,6 +19,7 @@ --color-success: #0d9c00; --color-warning: #d3bf06; --color-danger: #ca4925; + --color-danger-hover: #b83b19; /* Shadow */ --box-shadow: 0px 0px 25px 0px #00000080; diff --git a/apps/frontend/src/libs/components/button/button.tsx b/apps/frontend/src/libs/components/button/button.tsx index bd78260d8..020419bcc 100644 --- a/apps/frontend/src/libs/components/button/button.tsx +++ b/apps/frontend/src/libs/components/button/button.tsx @@ -1,4 +1,5 @@ import { NavLink } from "~/libs/components/components.js"; +import { getValidClassNames } from "~/libs/helpers/helpers.js"; import styles from "./styles.module.css"; @@ -6,25 +7,39 @@ type Properties = { href?: string | undefined; isDisabled?: boolean; label: string; + onClick?: () => void; type?: "button" | "submit"; + variant?: "danger" | "default" | "outlined"; }; const Button = ({ href, - isDisabled = false, + isDisabled, label, + onClick, type = "button", + variant = "default", }: Properties): JSX.Element => { + const buttonClassName = getValidClassNames( + styles["button"], + styles[`button-${variant}`], + ); + if (href) { return ( - + {label} ); } return ( - ); diff --git a/apps/frontend/src/libs/components/button/styles.module.css b/apps/frontend/src/libs/components/button/styles.module.css index 95f2ae424..9aa7efa8f 100644 --- a/apps/frontend/src/libs/components/button/styles.module.css +++ b/apps/frontend/src/libs/components/button/styles.module.css @@ -1,4 +1,5 @@ .button { + min-height: 44px; padding: 10px 16px; font-family: Inter, sans-serif; font-size: 16px; @@ -21,3 +22,20 @@ .button:hover:not(:disabled) { background-color: var(--color-brand-primary-hover); } + +.button-danger { + background-color: var(--color-danger); +} + +.button-danger:hover:not(:disabled) { + background-color: var(--color-danger-hover); +} + +.button-outlined { + background-color: transparent; + border: 1px solid var(--color-brand-primary); +} + +.button-outlined:hover:not(:disabled) { + background-color: var(--color-background-hover); +} diff --git a/apps/frontend/src/libs/components/input/input.tsx b/apps/frontend/src/libs/components/input/input.tsx index af41313cd..fdabf53f0 100644 --- a/apps/frontend/src/libs/components/input/input.tsx +++ b/apps/frontend/src/libs/components/input/input.tsx @@ -20,6 +20,7 @@ type Properties = { name: FieldPath; placeholder?: string; rightIcon?: JSX.Element; + rowsCount?: number; type?: "email" | "password" | "text"; }; @@ -33,6 +34,7 @@ const Input = ({ name, placeholder = "", rightIcon, + rowsCount, type = "text", }: Properties): JSX.Element => { const { field } = useFormController({ control, name }); @@ -41,11 +43,7 @@ const Input = ({ const hasError = Boolean(error); const hasLeftIcon = Boolean(leftIcon); const hasRightIcon = Boolean(rightIcon); - const inputClassNames = getValidClassNames( - styles["input-field"], - hasLeftIcon && styles["with-left-icon"], - hasRightIcon && styles["with-right-icon"], - ); + const isTextArea = Boolean(rowsCount); return (