From 5cf9a86264c943e8fe36dd1957be12c4008fc866 Mon Sep 17 00:00:00 2001 From: Margarita Golubeva Date: Wed, 12 Jun 2024 02:17:16 +0300 Subject: [PATCH] refactor: separate count badge into a separate component --- .validate-branch-namerc.cjs | 3 +- src/app/App/model/AppModel.ts | 5 +- .../CountBadge/model/CountBadgeModel.ts | 32 ++++++++++++ .../CountBadge/view/CountBadgeView.ts | 49 +++++++++++++++++++ .../view/countBadgeView.module.scss | 23 +++++++++ .../ProductCard/view/ProductCardView.ts | 4 +- .../ProductSorts/view/ProductSortsView.ts | 2 +- src/shared/utils/messageTemplates.ts | 4 ++ src/widgets/Header/model/HeaderModel.ts | 13 ----- src/widgets/Header/view/HeaderView.ts | 42 ++-------------- .../Header/view/headerView.module.scss | 24 --------- 11 files changed, 119 insertions(+), 82 deletions(-) create mode 100644 src/entities/CountBadge/model/CountBadgeModel.ts create mode 100644 src/entities/CountBadge/view/CountBadgeView.ts create mode 100644 src/entities/CountBadge/view/countBadgeView.module.scss diff --git a/.validate-branch-namerc.cjs b/.validate-branch-namerc.cjs index 2c0fefe4..ffc17199 100644 --- a/.validate-branch-namerc.cjs +++ b/.validate-branch-namerc.cjs @@ -1,6 +1,5 @@ module.exports = { - pattern: - /^sprint-4|^(feat|fix|hotfix|chore|refactor|revert|docs|style|test|)\(RSS-ECOMM-\d_\d{2}\)\/[a-z]+[a-zA-Z0-9]*$/, + pattern: /^(feat|fix|hotfix|chore|refactor|revert|docs|style|test|)\(RSS-ECOMM-\d_\d{2}\)\/[a-z]+[a-zA-Z0-9]*$/, errorMsg: 'Please use correct branch name', }; diff --git a/src/app/App/model/AppModel.ts b/src/app/App/model/AppModel.ts index 21375b24..aed626f4 100644 --- a/src/app/App/model/AppModel.ts +++ b/src/app/App/model/AppModel.ts @@ -5,6 +5,7 @@ import RouterModel from '@/app/Router/model/RouterModel.ts'; import modal from '@/shared/Modal/model/ModalModel.ts'; import ScrollToTopModel from '@/shared/ScrollToTop/model/ScrollToTopModel.ts'; import { PAGE_ID } from '@/shared/constants/pages.ts'; +import { showErrorMessage } from '@/shared/utils/userMessage.ts'; import FooterModel from '@/widgets/Footer/model/FooterModel.ts'; import HeaderModel from '@/widgets/Header/model/HeaderModel.ts'; @@ -14,8 +15,8 @@ class AppModel { private appView: AppView = new AppView(); constructor() { - this.initialize().catch(() => { - throw new Error('AppModel initialization error'); + this.initialize().catch((error) => { + showErrorMessage(error); }); } diff --git a/src/entities/CountBadge/model/CountBadgeModel.ts b/src/entities/CountBadge/model/CountBadgeModel.ts new file mode 100644 index 00000000..b4908bc5 --- /dev/null +++ b/src/entities/CountBadge/model/CountBadgeModel.ts @@ -0,0 +1,32 @@ +import type { Cart } from '@/shared/types/cart.ts'; + +import getCartModel from '@/shared/API/cart/model/CartModel.ts'; + +import CountBadgeView from '../view/CountBadgeView.ts'; + +class CountBadgeModel { + private view = new CountBadgeView(); + + constructor() { + this.init(); + } + + private countChangeHandler(cart: Cart): boolean { + this.view.updateBadgeCount(cart.products.reduce((acc, item) => acc + item.quantity, 0)); + return true; + } + + private init(): void { + this.observeCartChange(); + } + + private observeCartChange(): boolean { + return getCartModel().observeCartChange(this.countChangeHandler.bind(this)); + } + + public getHTML(): HTMLDivElement { + return this.view.getHTML(); + } +} + +export default CountBadgeModel; diff --git a/src/entities/CountBadge/view/CountBadgeView.ts b/src/entities/CountBadge/view/CountBadgeView.ts new file mode 100644 index 00000000..81b78795 --- /dev/null +++ b/src/entities/CountBadge/view/CountBadgeView.ts @@ -0,0 +1,49 @@ +import createBaseElement from '@/shared/utils/createBaseElement.ts'; + +import styles from './countBadgeView.module.scss'; + +class CountBadgeView { + private countBadge: HTMLSpanElement; + + private countBadgeWrap: HTMLDivElement; + + constructor() { + this.countBadgeWrap = this.createHTML(); + this.countBadge = this.createBadge(); + } + + private createBadge(): HTMLSpanElement { + this.countBadge = createBaseElement({ + cssClasses: [styles.badge], + tag: 'span', + }); + this.countBadgeWrap.append(this.countBadge); + + return this.countBadge; + } + + private createHTML(): HTMLDivElement { + this.countBadgeWrap = createBaseElement({ + cssClasses: [styles.badgeWrap], + tag: 'div', + }); + + return this.countBadgeWrap; + } + + public getHTML(): HTMLDivElement { + return this.countBadgeWrap; + } + + public updateBadgeCount(count?: number): void { + if (!count) { + this.countBadgeWrap.classList.add(styles.hidden); + } else { + this.countBadgeWrap.classList.remove(styles.hidden); + } + + this.countBadge.textContent = count ? count.toString() : ''; + } +} + +export default CountBadgeView; diff --git a/src/entities/CountBadge/view/countBadgeView.module.scss b/src/entities/CountBadge/view/countBadgeView.module.scss new file mode 100644 index 00000000..ea80ade4 --- /dev/null +++ b/src/entities/CountBadge/view/countBadgeView.module.scss @@ -0,0 +1,23 @@ +.badgeWrap { + position: absolute; + right: -30%; + top: -20%; + display: flex; + align-items: center; + justify-content: center; + border: var(--one) solid var(--noble-gray-1000); + border-radius: 100%; + width: calc(var(--tiny-offset) * 2); + height: calc(var(--tiny-offset) * 2); + font: var(--regular-font); + background-color: var(--steam-green-800); +} + +.badge { + display: block; + color: var(--noble-gray-200); +} + +.hidden { + display: none; +} diff --git a/src/entities/ProductCard/view/ProductCardView.ts b/src/entities/ProductCard/view/ProductCardView.ts index 82d14327..79dc00df 100644 --- a/src/entities/ProductCard/view/ProductCardView.ts +++ b/src/entities/ProductCard/view/ProductCardView.ts @@ -14,6 +14,7 @@ import * as buildPath from '@/shared/utils/buildPathname.ts'; import createBaseElement from '@/shared/utils/createBaseElement.ts'; import createSVGUse from '@/shared/utils/createSVGUse.ts'; import getCurrentLanguage from '@/shared/utils/getCurrentLanguage.ts'; +import { discountText } from '@/shared/utils/messageTemplates.ts'; import styles from './productCardView.module.scss'; @@ -112,8 +113,7 @@ class ProductCardView { private createDiscountLabel(): HTMLSpanElement { const currentVariant = this.params.variant.find(({ size }) => size === this.currentSize) ?? this.params.variant[0]; - // TBD replace template - const innerContent = `${Math.round((1 - currentVariant.discount / currentVariant.price) * 100)}%`; + const innerContent = discountText(currentVariant); this.discountLabel = createBaseElement({ cssClasses: [styles.discountLabel], innerContent, diff --git a/src/features/ProductSorts/view/ProductSortsView.ts b/src/features/ProductSorts/view/ProductSortsView.ts index 090b8079..ee748260 100644 --- a/src/features/ProductSorts/view/ProductSortsView.ts +++ b/src/features/ProductSorts/view/ProductSortsView.ts @@ -134,7 +134,7 @@ class ProductSortsView { if (currentLink && initialField) { currentLink?.getHTML().classList.toggle(styles.pass, initialDirection === SortDirection.DESC); // TBD check hight - currentLink?.getHTML().classList.toggle(styles.hight, initialDirection === SortDirection.DESC); + // currentLink?.getHTML().classList.toggle(styles.hight, initialDirection === SortDirection.DESC); currentLink.getHTML().dataset.field = initialField; } diff --git a/src/shared/utils/messageTemplates.ts b/src/shared/utils/messageTemplates.ts index f7bb1987..e03edddb 100644 --- a/src/shared/utils/messageTemplates.ts +++ b/src/shared/utils/messageTemplates.ts @@ -1,3 +1,5 @@ +import type { Variant } from '../types/product.ts'; + import { LANGUAGE_CHOICE } from '../constants/common.ts'; import { SERVER_MESSAGE } from '../constants/messages.ts'; import { PAGE_DESCRIPTION, USER_INFO_TEXT } from '../constants/pages.ts'; @@ -14,6 +16,8 @@ export const cartPrice = (price: string): string => `$${price}`; export const minusCartPrice = (price: string): string => `-$${price}`; +export const discountText = (currentVariant: Variant): string => + `${Math.round((1 - currentVariant.discount / currentVariant.price) * 100)}%`; export const productAddedToCartMessage = (name: string): string => textTemplate(name, SERVER_MESSAGE[getCurrentLanguage()].SUCCESSFUL_ADD_PRODUCT_TO_CART); diff --git a/src/widgets/Header/model/HeaderModel.ts b/src/widgets/Header/model/HeaderModel.ts index d4588c06..fd7ac1d2 100644 --- a/src/widgets/Header/model/HeaderModel.ts +++ b/src/widgets/Header/model/HeaderModel.ts @@ -1,8 +1,5 @@ -import type { Cart } from '@/shared/types/cart.ts'; - import RouterModel from '@/app/Router/model/RouterModel.ts'; import NavigationModel from '@/entities/Navigation/model/NavigationModel.ts'; -import getCartModel from '@/shared/API/cart/model/CartModel.ts'; import getCustomerModel, { CustomerModel } from '@/shared/API/customer/model/CustomerModel.ts'; import getStore from '@/shared/Store/Store.ts'; import { setAuthToken, setCurrentLanguage, switchIsUserLoggedIn } from '@/shared/Store/actions.ts'; @@ -15,11 +12,6 @@ import { showErrorMessage, showSuccessMessage } from '@/shared/utils/userMessage import HeaderView from '../view/HeaderView.ts'; class HeaderModel { - private cartChangeHandler = (cart: Cart): boolean => { - this.view.updateCartCount(cart.products.reduce((acc, item) => acc + item.quantity, 0)); - return true; - }; - private navigation: NavigationModel; private parent: HTMLDivElement; @@ -52,7 +44,6 @@ class HeaderModel { this.observeCurrentUser(); this.setLogoutButtonHandler(); this.setLinksHandler(); - this.observeCartChange(); this.setChangeLanguageCheckboxHandler(); } @@ -67,10 +58,6 @@ class HeaderModel { return true; } - private observeCartChange(): boolean { - return getCartModel().observeCartChange(this.cartChangeHandler); - } - private observeCurrentUser(): void { observeStore(selectIsUserLoggedIn, () => { this.checkCurrentUser(); diff --git a/src/widgets/Header/view/HeaderView.ts b/src/widgets/Header/view/HeaderView.ts index b40dc6b2..16ad0d9b 100644 --- a/src/widgets/Header/view/HeaderView.ts +++ b/src/widgets/Header/view/HeaderView.ts @@ -1,6 +1,7 @@ import type { LanguageChoiceType } from '@/shared/constants/common.ts'; import RouterModel from '@/app/Router/model/RouterModel.ts'; +import CountBadgeModel from '@/entities/CountBadge/model/CountBadgeModel.ts'; import ButtonModel from '@/shared/Button/model/ButtonModel.ts'; import InputModel from '@/shared/Input/model/InputModel.ts'; import LinkModel from '@/shared/Link/model/LinkModel.ts'; @@ -23,9 +24,7 @@ import styles from './headerView.module.scss'; class HeaderView { private burgerButton: ButtonModel; - private cartBadge: HTMLSpanElement; - - private cartBadgeWrap: HTMLDivElement; + private countBadge = new CountBadgeModel(); private header: HTMLElement; @@ -51,11 +50,7 @@ class HeaderView { this.logoutButton = this.createLogoutButton(); this.linkLogo = this.createLinkLogo(); this.toCartLink = this.createToCartLink(); - this.cartBadgeWrap = this.createBadgeWrap(); - this.cartBadge = this.createBadge(); - this.toWishlistLink = this.createToWishlistLink(); - this.toProfileLink = this.createToProfileLink(); this.switchThemeCheckbox = this.createSwitchThemeCheckbox(); this.switchLanguageCheckbox = this.createSwitchLanguageCheckbox(); @@ -79,27 +74,6 @@ class HeaderView { }); } - private createBadge(): HTMLSpanElement { - this.cartBadge = createBaseElement({ - cssClasses: [styles.badge], - tag: 'span', - }); - this.cartBadgeWrap.append(this.cartBadge); - - return this.cartBadge; - } - - private createBadgeWrap(): HTMLDivElement { - this.cartBadgeWrap = createBaseElement({ - cssClasses: [styles.badgeWrap], - tag: 'div', - }); - - this.toCartLink.getHTML().append(this.cartBadgeWrap); - - return this.cartBadgeWrap; - } - private createBurgerButton(): ButtonModel { this.burgerButton = new ButtonModel({ classes: [styles.burgerButton], @@ -279,6 +253,8 @@ class HeaderView { .classList.toggle(styles.cartLinkActive, RouterModel.getCurrentPage() === PAGE_ID.CART_PAGE), ); + this.toCartLink.getHTML().append(this.countBadge.getHTML()); + return this.toCartLink; } @@ -403,16 +379,6 @@ class HeaderView { public showNavigationWrapper(): void { this.navigationWrapper.classList.remove(styles.hidden); } - - public updateCartCount(count?: number): void { - if (!count) { - this.cartBadgeWrap.classList.add(styles.hide); - } else { - this.cartBadgeWrap.classList.remove(styles.hide); - } - - this.cartBadge.textContent = count ? count.toString() : ''; - } } export default HeaderView; diff --git a/src/widgets/Header/view/headerView.module.scss b/src/widgets/Header/view/headerView.module.scss index cb8c4d90..db77a30d 100644 --- a/src/widgets/Header/view/headerView.module.scss +++ b/src/widgets/Header/view/headerView.module.scss @@ -476,27 +476,3 @@ opacity: 1; } } - -.badgeWrap { - position: absolute; - right: -30%; - top: -20%; - display: flex; - align-items: center; - justify-content: center; - border: var(--one) solid var(--noble-gray-1000); - border-radius: 100%; - width: calc(var(--tiny-offset) * 2); - height: calc(var(--tiny-offset) * 2); - font: var(--regular-font); - background-color: var(--steam-green-800); -} - -.badge { - display: block; - color: var(--noble-gray-200); -} - -.hide { - display: none; -}