diff --git a/public/img/png/expand-arrow.png b/public/img/png/expand-arrow.png new file mode 100644 index 00000000..ba917d13 Binary files /dev/null and b/public/img/png/expand-arrow.png differ diff --git a/src/entities/ProductPrice/model/ProductPriceModel.ts b/src/entities/ProductPrice/model/ProductPriceModel.ts index 74f46909..84a8e4ec 100644 --- a/src/entities/ProductPrice/model/ProductPriceModel.ts +++ b/src/entities/ProductPrice/model/ProductPriceModel.ts @@ -13,6 +13,10 @@ class ProductPriceModel { public getHTML(): HTMLDivElement { return this.view.getHTML(); } + + public updatePrice(params: { new: number; old: number }): void { + this.view.updatesPrice(params); + } } export default ProductPriceModel; diff --git a/src/entities/ProductPrice/view/ProductPriceView.ts b/src/entities/ProductPrice/view/ProductPriceView.ts index 233d5098..6226b399 100644 --- a/src/entities/ProductPrice/view/ProductPriceView.ts +++ b/src/entities/ProductPrice/view/ProductPriceView.ts @@ -19,7 +19,7 @@ class ProductPriceView { } private createBasicPrice(): HTMLSpanElement { - const innerContent = this.params.new ? `$${this.params.new.toFixed(2)}` : `$${this.params.old?.toFixed(2)}`; + const innerContent = this.getBasePrice(); // this.params.new ? `$${this.params.new.toFixed(2)}` : `$${this.params.old?.toFixed(2)}`; this.basicPrice = createBaseElement({ cssClasses: ['basicPrice'], innerContent, @@ -44,7 +44,7 @@ class ProductPriceView { } private createOldPrice(): HTMLSpanElement { - const innerContent = this.params.new ? `$${this.params.old.toFixed(2)}` : ''; + const innerContent = this.getOldPrice(); // this.params.new ? `$${this.params.old.toFixed(2)}` : ''; this.oldPrice = createBaseElement({ cssClasses: ['oldPrice'], innerContent, @@ -54,9 +54,28 @@ class ProductPriceView { return this.oldPrice; } + private getBasePrice(): string { + return this.params.new ? `$${this.params.new.toFixed(2)}` : `$${this.params.old?.toFixed(2)}`; + } + + private getOldPrice(): string { + return this.params.new ? `$${this.params.old.toFixed(2)}` : ''; + } + public getHTML(): HTMLDivElement { return this.view; } + + public updatesPrice(params: { new: number; old: number }): void { + this.params = params; + this.basicPrice.textContent = this.getBasePrice(); + this.oldPrice.textContent = this.getOldPrice(); + if (!this.params.new) { + this.basicPrice.classList.add('gray'); + } else { + this.basicPrice.classList.remove('gray'); + } + } } export default ProductPriceView; diff --git a/src/pages/CartPage/model/CartPageModel.ts b/src/pages/CartPage/model/CartPageModel.ts index 2052d82c..111a7906 100644 --- a/src/pages/CartPage/model/CartPageModel.ts +++ b/src/pages/CartPage/model/CartPageModel.ts @@ -1,4 +1,3 @@ -import type { Cart } from '@/shared/types/cart.ts'; import type { Page } from '@/shared/types/page.ts'; import getCartModel from '@/shared/API/cart/model/CartModel.ts'; @@ -9,6 +8,7 @@ import observeStore, { selectCurrentLanguage } from '@/shared/Store/observer.ts' import { SERVER_MESSAGE_KEYS } from '@/shared/constants/messages.ts'; import { PAGE_ID } from '@/shared/constants/pages.ts'; import { LOADER_SIZE } from '@/shared/constants/sizes.ts'; +import { type Cart, CartActive } from '@/shared/types/cart.ts'; import { promoCodeAppliedMessage } from '@/shared/utils/messageTemplates.ts'; import { showErrorMessage, showSuccessMessage } from '@/shared/utils/userMessage.ts'; import ProductOrderModel from '@/widgets/ProductOrder/model/ProductOrderModel.ts'; @@ -38,6 +38,14 @@ class CartPageModel implements Page { if (cart) { showSuccessMessage(promoCodeAppliedMessage(discountCode)); this.cart = cart; + this.productsItem.forEach((productItem) => { + const idLine = productItem.getProduct().lineItemId; + const updateLine = this.cart?.products.find((item) => item.lineItemId === idLine); + if (updateLine) { + productItem.setProduct(updateLine); + productItem.updateProductHandler(CartActive.UPDATE).catch(showErrorMessage); + } + }); this.view.updateTotal(this.cart); } }) diff --git a/src/pages/CartPage/view/CartPageView.ts b/src/pages/CartPage/view/CartPageView.ts index 0b263bd6..cf327335 100644 --- a/src/pages/CartPage/view/CartPageView.ts +++ b/src/pages/CartPage/view/CartPageView.ts @@ -29,7 +29,7 @@ const TITLE = { CLEAR: { en: 'Clear all', ru: 'Очистить' }, CONTINUE: { en: 'Continue Shopping', ru: 'Продолжить покупки' }, COUPON_APPLY: { en: 'Coupon Apply', ru: 'Применить купон' }, - COUPON_DISCOUNT: { en: 'Coupon Discount', ru: 'Скидка по купону' }, + COUPON_DISCOUNT: { en: 'Cart Coupons', ru: 'Скидки на корзину' }, EMPTY: { en: `Oops! Looks like you haven't added the item to your cart yet.`, ru: `Ой! Похоже, вы еще не добавили товар в корзину.`, @@ -50,7 +50,9 @@ class CartPageView { private couponButton: HTMLButtonElement; - private discount: HTMLParagraphElement; + private discountList: HTMLUListElement; + + private discountTotal: HTMLElement; private empty: HTMLDivElement; @@ -74,6 +76,8 @@ class CartPageView { private total: HTMLParagraphElement; + private totalDiscountTitle: HTMLParagraphElement; + private totalWrap: HTMLDivElement; constructor(parent: HTMLDivElement, clearCallback: ClearCallback, addDiscountCallback: DiscountCallback) { @@ -87,12 +91,18 @@ class CartPageView { this.productWrap.classList.add(styles.products); this.subTotal = createBaseElement({ cssClasses: [styles.totalTitle], tag: 'p' }); this.total = createBaseElement({ cssClasses: [styles.totalPrice], tag: 'p' }); - this.discount = createBaseElement({ cssClasses: [styles.title], tag: 'p' }); + this.discountTotal = createBaseElement({ cssClasses: [styles.couponsWrap], tag: 'summary' }); + this.discountList = createBaseElement({ cssClasses: [styles.couponsList], tag: 'ul' }); this.couponButton = createBaseElement({ cssClasses: [styles.button, styles.applyBtn], innerContent: TITLE.BUTTON_COUPON[this.language], tag: 'button', }); + this.totalDiscountTitle = createBaseElement({ + cssClasses: [styles.title], + innerContent: TITLE.COUPON_DISCOUNT[this.language], + tag: 'p', + }); this.clear = new LinkModel({ classes: [styles.continue, styles.clear], text: TITLE.CLEAR[this.language] }); this.totalWrap = this.createWrapHTML(); this.totalWrap.classList.add(styles.total); @@ -229,15 +239,10 @@ class CartPageView { return tdDelete; } - private createDiscountHTML(): HTMLDivElement { - const discountWrap = createBaseElement({ cssClasses: [styles.totalWrap], tag: 'div' }); - const discountTitle = createBaseElement({ - cssClasses: [styles.title], - innerContent: TITLE.COUPON_DISCOUNT[this.language], - tag: 'p', - }); - discountWrap.append(discountTitle, this.discount); - this.textElement.push({ element: discountTitle, textItem: TITLE.COUPON_DISCOUNT }); + private createDiscountHTML(): HTMLDetailsElement { + const discountWrap = createBaseElement({ cssClasses: [styles.totalWrap], tag: 'details' }); + discountWrap.append(this.discountTotal, this.discountList); + this.textElement.push({ element: this.totalDiscountTitle, textItem: TITLE.COUPON_DISCOUNT }); return discountWrap; } @@ -310,6 +315,8 @@ class CartPageView { public renderCart(productsItem: ProductOrderModel[]): void { this.productWrap.innerHTML = ''; this.totalWrap.innerHTML = ''; + this.discountTotal.innerHTML = ''; + this.discountList.innerHTML = ''; this.productWrap.classList.remove(styles.hide); this.totalWrap.classList.remove(styles.hide); this.empty.classList.add(styles.hide); @@ -323,6 +330,8 @@ class CartPageView { public renderEmpty(): void { this.productWrap.innerHTML = ''; this.totalWrap.innerHTML = ''; + this.discountTotal.innerHTML = ''; + this.discountList.innerHTML = ''; this.productWrap.classList.add(styles.hide); this.totalWrap.classList.add(styles.hide); this.empty.classList.remove(styles.hide); @@ -341,8 +350,37 @@ class CartPageView { } public updateTotal(cart: Cart): void { - this.subTotal.innerHTML = `$ ${(cart.total + cart.discounts).toFixed(2)}`; - this.discount.innerHTML = `-$ ${cart.discounts.toFixed(2)}`; + this.discountTotal.innerHTML = ''; + this.discountList.innerHTML = ''; + let totalDiscount = 0; + cart.discounts.forEach((discount) => { + const couponItem = createBaseElement({ cssClasses: [styles.couponWrap], tag: 'li' }); + const couponTitle = createBaseElement({ + cssClasses: [styles.title], + innerContent: discount.coupon.code, + tag: 'p', + }); + const couponValue = createBaseElement({ + cssClasses: [styles.title], + innerContent: `-$ ${discount.value.toFixed(2)}`, + tag: 'p', + }); + couponItem.append(couponTitle, couponValue); + this.discountList.append(couponItem); + totalDiscount += discount.value; + }); + if (totalDiscount) { + const totalDiscountWrap = createBaseElement({ cssClasses: [styles.couponWrap], tag: 'div' }); + const totalDiscountValue = createBaseElement({ + cssClasses: [styles.title, styles.totalDiscount], + innerContent: `-$ ${totalDiscount.toFixed(2)}`, + tag: 'p', + }); + totalDiscountWrap.append(this.totalDiscountTitle, totalDiscountValue); + this.discountTotal.append(totalDiscountWrap); + } + const subTotal = cart.total + totalDiscount; + this.subTotal.innerHTML = `$ ${subTotal.toFixed(2)}`; this.total.innerHTML = `$ ${cart.total.toFixed(2)}`; } } diff --git a/src/pages/CartPage/view/cartPageView.module.scss b/src/pages/CartPage/view/cartPageView.module.scss index 9cf48e49..f3162ed6 100644 --- a/src/pages/CartPage/view/cartPageView.module.scss +++ b/src/pages/CartPage/view/cartPageView.module.scss @@ -111,6 +111,7 @@ padding: var(--tiny-offset) 0; font: var(--regular-font); color: var(--noble-gray-800); + transition: all 0.2s; } .totalWrap { @@ -125,6 +126,24 @@ } } +.couponsWrap { + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + + &:hover p { + color: var(--steam-green-800); + } +} + +.couponWrap { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +} + .totalPrice { padding: var(--tiny-offset) 0; font: var(--bold-font); @@ -207,3 +226,12 @@ $color: var(--steam-green-800); .hide { display: none; } + +.couponsWrap:not(:empty)::after { + content: ''; + display: inline-block; + width: var(--tiny-offset); + height: var(--tiny-offset); + background: url('/img/png/expand-arrow.png') no-repeat center center; + background-size: contain; +} diff --git a/src/shared/API/cart/model/CartModel.ts b/src/shared/API/cart/model/CartModel.ts index e26cf5ef..bcb2cff5 100644 --- a/src/shared/API/cart/model/CartModel.ts +++ b/src/shared/API/cart/model/CartModel.ts @@ -1,4 +1,4 @@ -import type { AddCartItem, Cart, CartProduct, EditCartItem } from '@/shared/types/cart.ts'; +import type { AddCartItem, Cart, CartCoupon, CartProduct, EditCartItem } from '@/shared/types/cart.ts'; import type { CartPagedQueryResponse, Cart as CartResponse, @@ -15,6 +15,7 @@ import { showErrorMessage } from '@/shared/utils/userMessage.ts'; import type { OptionsRequest } from '../../types/type.ts'; +import getDiscountModel from '../../discount/model/DiscountModel.ts'; import getProductModel from '../../product/model/ProductModel.ts'; import FilterProduct from '../../product/utils/filter.ts'; import { Attribute, FilterFields } from '../../types/type.ts'; @@ -23,6 +24,7 @@ import getCartApi, { type CartApi } from '../CartApi.ts'; enum ACTIONS { addDiscountCode = 'addDiscountCode', + removeDiscountCode = 'removeDiscountCode', removeLineItem = 'removeLineItem', setAnonymousId = 'setAnonymousId', } @@ -55,10 +57,22 @@ export class CartModel { if (data.anonymousId && !authToken && !anonymousId) { getStore().dispatch(setAnonymousId(data.anonymousId)); } - const discount = data.discountOnTotalPrice?.discountedAmount?.centAmount; + const discounts: CartCoupon[] = []; + if (data.discountOnTotalPrice && data.discountOnTotalPrice.includedDiscounts.length) { + const allDiscounts = getDiscountModel().getAllCoupons(); + data.discountOnTotalPrice.includedDiscounts.forEach((discount) => { + const findDiscount = allDiscounts.find((el) => el.id === discount.discount.id); + if (findDiscount && discount.discountedAmount.centAmount > 0) { + discounts.push({ + coupon: findDiscount, + value: discount.discountedAmount.centAmount / PRICE_FRACTIONS || 0, + }); + } + }); + } return { anonymousId: data.anonymousId || null, - discounts: discount ? discount / PRICE_FRACTIONS : 0, + discounts, id: data.id, products: data.lineItems.map((lineItem) => this.adaptLineItem(lineItem)), total: data.totalPrice.centAmount / PRICE_FRACTIONS || 0, @@ -70,16 +84,25 @@ export class CartModel { const price = product.price.discounted?.value.centAmount ? product.price.discounted?.value.centAmount : product.price.value.centAmount; + const priceCoupon = + product.discountedPricePerQuantity.length && + product.discountedPricePerQuantity[0].discountedPrice.value.centAmount + ? product.discountedPricePerQuantity[0].discountedPrice.value.centAmount + : 0; const result: CartProduct = { images: product.variant.images?.length ? product.variant.images[0].url : '', key: product.productKey || '', lineItemId: product.id, name: [], price: price / PRICE_FRACTIONS || 0, + priceCouponDiscount: priceCoupon / PRICE_FRACTIONS || 0, productId: product.productId || '', quantity: product.quantity || 0, size: null, - totalPrice: product.totalPrice.centAmount / PRICE_FRACTIONS || 0, + totalPrice: priceCoupon + ? (price * product.quantity) / PRICE_FRACTIONS || 0 + : product.totalPrice.centAmount / PRICE_FRACTIONS || 0, + totalPriceCouponDiscount: priceCoupon ? product.totalPrice.centAmount / PRICE_FRACTIONS || 0 : 0, }; result.name.push(...getProductModel().adaptLocalizationValue(product.name)); if (product.variant.attributes) { @@ -118,7 +141,7 @@ export class CartModel { private getCartFromData(data: CartResponse | ClientResponse): Cart { let cart: Cart = { anonymousId: null, - discounts: 0, + discounts: [], id: '', products: [], total: 0, @@ -249,6 +272,24 @@ export class CartModel { }); } + public async deleteCoupon(id: string): Promise { + if (!this.cart) { + this.cart = await this.getCart(); + } + const action: MyCartUpdateAction[] = [ + { + action: ACTIONS.removeDiscountCode, + discountCode: { + id, + typeId: 'discount-code', + }, + }, + ]; + const data = await this.root.updateCart(this.cart, action); + this.cart = this.getCartFromData(data); + return this.cart; + } + public async deleteProductFromCart(products: CartProduct): Promise { if (!this.cart) { this.cart = await this.getCart(); diff --git a/src/shared/API/discount/DiscountApi.ts b/src/shared/API/discount/DiscountApi.ts new file mode 100644 index 00000000..30511ba4 --- /dev/null +++ b/src/shared/API/discount/DiscountApi.ts @@ -0,0 +1,24 @@ +import type { ClientResponse, DiscountCodePagedQueryResponse } from '@commercetools/platform-sdk'; + +import getApiClient, { type ApiClient } from '../sdk/client.ts'; + +export class DiscountApi { + private client: ApiClient; + + constructor() { + this.client = getApiClient(); + } + + public async getCoupons(): Promise> { + const data = await this.client.apiRoot().discountCodes().get().execute(); + return data; + } +} + +const createDiscountApi = (): DiscountApi => new DiscountApi(); + +const discountApi = createDiscountApi(); + +export default function getDiscountApi(): DiscountApi { + return discountApi; +} diff --git a/src/shared/API/discount/model/DiscountModel.ts b/src/shared/API/discount/model/DiscountModel.ts new file mode 100644 index 00000000..9104d855 --- /dev/null +++ b/src/shared/API/discount/model/DiscountModel.ts @@ -0,0 +1,53 @@ +import type { Coupon } from '@/shared/types/cart.ts'; +import type { ClientResponse, DiscountCode, DiscountCodePagedQueryResponse } from '@commercetools/platform-sdk'; + +import { showErrorMessage } from '@/shared/utils/userMessage.ts'; + +import { isClientResponse, isDiscountCodePagedQueryResponse } from '../../types/validation.ts'; +import getDiscountApi, { type DiscountApi } from '../DiscountApi.ts'; + +export class DiscountModel { + private coupons: Coupon[] = []; + + private root: DiscountApi; + + constructor() { + this.root = getDiscountApi(); + this.init().catch(showErrorMessage); + } + + private adaptCoupon(data: DiscountCode): Coupon { + return { + code: data.code, + id: data.cartDiscounts[0].id, + }; + } + + private getCouponsFromData(data: ClientResponse): Coupon[] { + const coupons: Coupon[] = []; + if (isClientResponse(data) && isDiscountCodePagedQueryResponse(data.body)) { + coupons.push(...data.body.results.map((el) => this.adaptCoupon(el))); + } + return coupons; + } + + private async init(): Promise { + if (!this.coupons.length) { + const data = await this.root.getCoupons(); + this.coupons = this.getCouponsFromData(data); + } + return this.coupons; + } + + public getAllCoupons(): Coupon[] { + return this.coupons; + } +} + +const createDiscountModel = (): DiscountModel => new DiscountModel(); + +const discountModel = createDiscountModel(); + +export default function getDiscountModel(): DiscountModel { + return discountModel; +} diff --git a/src/shared/API/sdk/client.ts b/src/shared/API/sdk/client.ts index 91e9b139..6b6edbc1 100644 --- a/src/shared/API/sdk/client.ts +++ b/src/shared/API/sdk/client.ts @@ -119,7 +119,6 @@ export class ApiClient { anonymousId, }, }; - client.withAnonymousSessionFlow(anonymOptions); getStore().dispatch(setAnonymousId(anonymousId)); this.anonymConnection = this.getConnection(client.build()); diff --git a/src/shared/API/types/validation.ts b/src/shared/API/types/validation.ts index 2dd9e480..51c14a93 100644 --- a/src/shared/API/types/validation.ts +++ b/src/shared/API/types/validation.ts @@ -7,6 +7,7 @@ import type { Customer, CustomerPagedQueryResponse, CustomerSignInResult, + DiscountCodePagedQueryResponse, ErrorResponse, FacetRange, FacetTerm, @@ -298,3 +299,18 @@ export function isShoppingListPagedQueryResponse(data: unknown): data is Shoppin Array.isArray(data.results), ); } + +export function isDiscountCodePagedQueryResponse(data: unknown): data is DiscountCodePagedQueryResponse { + return Boolean( + typeof data === 'object' && + data && + 'count' in data && + typeof data.count === 'number' && + 'limit' in data && + typeof data.limit === 'number' && + 'total' in data && + typeof data.total === 'number' && + 'results' in data && + Array.isArray(data.results), + ); +} diff --git a/src/shared/types/cart.ts b/src/shared/types/cart.ts index 3377c37e..937548cc 100644 --- a/src/shared/types/cart.ts +++ b/src/shared/types/cart.ts @@ -2,7 +2,7 @@ import type { SizeType, localization } from './product.ts'; export interface Cart { anonymousId: null | string; - discounts: number; + discounts: CartCoupon[]; id: string; products: CartProduct[]; total: number; @@ -15,10 +15,12 @@ export interface CartProduct { lineItemId: string; name: localization[]; price: number; + priceCouponDiscount: number; productId: string; quantity: number; size: SizeType | null; totalPrice: number; + totalPriceCouponDiscount: number; } export interface AddCartItem { @@ -37,4 +39,15 @@ export enum CartActive { DELETE = 'delete', MINUS = 'minus', PLUS = 'plus', + UPDATE = 'update', +} + +export interface Coupon { + code: string; + id: string; +} + +export interface CartCoupon { + coupon: Coupon; + value: number; } diff --git a/src/widgets/Header/model/HeaderModel.ts b/src/widgets/Header/model/HeaderModel.ts index 3533490e..d4588c06 100644 --- a/src/widgets/Header/model/HeaderModel.ts +++ b/src/widgets/Header/model/HeaderModel.ts @@ -16,7 +16,7 @@ import HeaderView from '../view/HeaderView.ts'; class HeaderModel { private cartChangeHandler = (cart: Cart): boolean => { - this.view.updateCartCount(cart.products.length); + this.view.updateCartCount(cart.products.reduce((acc, item) => acc + item.quantity, 0)); return true; }; diff --git a/src/widgets/ProductOrder/model/ProductOrderModel.ts b/src/widgets/ProductOrder/model/ProductOrderModel.ts index 636144ee..b6c5367f 100644 --- a/src/widgets/ProductOrder/model/ProductOrderModel.ts +++ b/src/widgets/ProductOrder/model/ProductOrderModel.ts @@ -1,5 +1,6 @@ import type { Cart, CartProduct, EditCartItem } from '@/shared/types/cart.ts'; +import ProductPriceModel from '@/entities/ProductPrice/model/ProductPriceModel.ts'; import getCartModel from '@/shared/API/cart/model/CartModel.ts'; import LoaderModel from '@/shared/Loader/model/LoaderModel.ts'; import getStore from '@/shared/Store/Store.ts'; @@ -17,14 +18,26 @@ type Callback = (cart: Cart) => void; class ProductOrderModel { private callback: Callback; + private price: ProductPriceModel; + private productItem: CartProduct; + private total: ProductPriceModel; + private view: ProductOrderView; constructor(productItem: CartProduct, callback: Callback) { this.callback = callback; this.productItem = productItem; - this.view = new ProductOrderView(this.productItem, this.updateProductHandler.bind(this)); + this.price = new ProductPriceModel({ new: this.productItem.priceCouponDiscount, old: this.productItem.price }); + this.total = new ProductPriceModel({ + new: + this.productItem.totalPrice === this.productItem.totalPriceCouponDiscount + ? 0 + : this.productItem.totalPriceCouponDiscount, + old: this.productItem.totalPrice, + }); + this.view = new ProductOrderView(this.productItem, this.price, this.total, this.updateProductHandler.bind(this)); this.init(); } @@ -94,6 +107,11 @@ class ProductOrderModel { return this.productItem; } + public setProduct(product: CartProduct): CartProduct { + this.productItem = product; + return this.productItem; + } + public async updateProductHandler(active: CartActive): Promise { switch (active) { case CartActive.DELETE: { @@ -108,6 +126,10 @@ class ProductOrderModel { await this.activePlus(); break; } + case CartActive.UPDATE: { + this.updateView(this.productItem); + break; + } default: break; } diff --git a/src/widgets/ProductOrder/view/ProductOrderView.ts b/src/widgets/ProductOrder/view/ProductOrderView.ts index feaf6c13..10081c36 100644 --- a/src/widgets/ProductOrder/view/ProductOrderView.ts +++ b/src/widgets/ProductOrder/view/ProductOrderView.ts @@ -1,3 +1,4 @@ +import type ProductPriceModel from '@/entities/ProductPrice/model/ProductPriceModel'; import type { LanguageChoiceType } from '@/shared/constants/common.ts'; import type { CartProduct } from '@/shared/types/cart'; import type { languageVariants } from '@/shared/types/common'; @@ -40,7 +41,7 @@ class ProductOrderView { private language: LanguageChoiceType; - private price: HTMLTableCellElement; + private priceElement: ProductPriceModel; private productItem: CartProduct; @@ -48,12 +49,21 @@ class ProductOrderView { private textElement: textElementsType[] = []; - private total: HTMLTableCellElement; + private totalElement: ProductPriceModel; private view: HTMLTableRowElement; - constructor(productItem: CartProduct, callback: CallbackActive) { + constructor( + productItem: CartProduct, + priceElement: ProductPriceModel, + totalElement: ProductPriceModel, + callback: CallbackActive, + ) { this.productItem = productItem; + this.priceElement = priceElement; + this.totalElement = totalElement; + this.totalElement.getHTML().classList.add(styles.priceElement); + this.priceElement.getHTML().classList.add(styles.priceElement); this.language = getStore().getState().currentLanguage; this.callback = callback; this.quantity = createBaseElement({ @@ -61,16 +71,6 @@ class ProductOrderView { innerContent: this.productItem.quantity.toString(), tag: 'p', }); - this.price = createBaseElement({ - cssClasses: [styles.td, styles.priceCell, styles.priceText], - innerContent: `$${this.productItem.price.toFixed(2)}`, - tag: 'td', - }); - this.total = createBaseElement({ - cssClasses: [styles.td, styles.totalCell, styles.totalText], - innerContent: `$${this.productItem.totalPrice.toFixed(2)}`, - tag: 'td', - }); this.deleteButton = createBaseElement({ cssClasses: [styles.deleteButton], tag: 'button' }); this.view = this.createHTML(); } @@ -88,21 +88,15 @@ class ProductOrderView { private createHTML(): HTMLTableRowElement { this.view = createBaseElement({ cssClasses: [styles.tr, styles.trProduct], tag: 'tr' }); const imgCell = this.createImgCell(); - const tdProduct = createBaseElement({ - cssClasses: [styles.td, styles.nameCell, styles.mainText], - innerContent: this.productItem.name[Number(getStore().getState().currentLanguage === LANGUAGE_CHOICE.RU)].value, - tag: 'td', - }); - const tdSize = createBaseElement({ - cssClasses: [styles.td, styles.sizeCell, styles.sizeText], - innerContent: this.productItem.size ? `${TITLE.SIZE[this.language]}: ${this.productItem.size}` : '', - tag: 'td', - }); + const tdProduct = this.createTdProduct(); + const tdSize = this.createTdSize(); + const tdPrice = this.createTdPrice(); this.textElement.push({ element: tdSize, textItem: TITLE.SIZE }); this.textElement.push({ element: tdProduct, textItem: TITLE.NAME }); + const tdTotal = this.createTdTotal(); const quantityCell = this.createQuantityCell(); const deleteCell = this.createDeleCell(); - this.view.append(imgCell, tdProduct, tdSize, this.price, quantityCell, this.total, deleteCell); + this.view.append(imgCell, tdProduct, tdSize, tdPrice, quantityCell, tdTotal, deleteCell); const animation = new Hammer(this.view); animation.on('swipeleft', () => { if (window.innerWidth <= TABLET_WIDTH) { @@ -159,6 +153,40 @@ class ProductOrderView { return tdQuantity; } + private createTdPrice(): HTMLTableCellElement { + const td = createBaseElement({ + cssClasses: [styles.td, styles.priceCell, styles.priceText], + tag: 'td', + }); + td.append(this.priceElement.getHTML()); + return td; + } + + private createTdProduct(): HTMLTableCellElement { + return createBaseElement({ + cssClasses: [styles.td, styles.nameCell, styles.mainText], + innerContent: this.productItem.name[Number(getStore().getState().currentLanguage === LANGUAGE_CHOICE.RU)].value, + tag: 'td', + }); + } + + private createTdSize(): HTMLTableCellElement { + return createBaseElement({ + cssClasses: [styles.td, styles.sizeCell, styles.sizeText], + innerContent: this.productItem.size ? `${TITLE.SIZE[this.language]}: ${this.productItem.size}` : '', + tag: 'td', + }); + } + + private createTdTotal(): HTMLTableCellElement { + const td = createBaseElement({ + cssClasses: [styles.td, styles.totalCell, styles.totalText], + tag: 'td', + }); + td.append(this.totalElement.getHTML()); + return td; + } + public getDeleteButton(): HTMLButtonElement { return this.deleteButton; } @@ -170,8 +198,15 @@ class ProductOrderView { public updateInfo(productItem: CartProduct): void { this.productItem = productItem; this.quantity.textContent = this.productItem.quantity.toString(); - this.price.textContent = `$${this.productItem.price.toFixed(2)}`; - this.total.textContent = `$${this.productItem.totalPrice.toFixed(2)}`; + + this.priceElement.updatePrice({ new: this.productItem.priceCouponDiscount, old: this.productItem.price }); + this.totalElement.updatePrice({ + new: + this.productItem.totalPrice === this.productItem.totalPriceCouponDiscount + ? 0 + : this.productItem.totalPriceCouponDiscount, + old: this.productItem.totalPrice, + }); } public updateLanguage(): void { diff --git a/src/widgets/ProductOrder/view/productOrderView.module.scss b/src/widgets/ProductOrder/view/productOrderView.module.scss index 4ff26fe2..08e66e71 100644 --- a/src/widgets/ProductOrder/view/productOrderView.module.scss +++ b/src/widgets/ProductOrder/view/productOrderView.module.scss @@ -141,6 +141,10 @@ } .priceCell { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; grid-area: 2 / 3 / 4 / 4; @media (max-width: 768px) { @@ -168,8 +172,9 @@ .quantityText { padding: var(--tiny-offset); - font: var(--regular-font); - color: var(--noble-gray-800); + font: var(--medium-font); + letter-spacing: var(--one); + color: var(--noble-gray-700); } .totalText { @@ -178,12 +183,21 @@ color: var(--steam-green-800); } +.priceDiscountText, .priceText { padding: var(--tiny-offset) 0; font: var(--regular-font); color: var(--noble-gray-700); } +.priceDiscountText { + color: var(--steam-green-700); +} + +.priceDiscountText:empty { + display: none; +} + .sizeText { padding: var(--tiny-offset); font: var(--regular-font); @@ -199,10 +213,11 @@ @include green-btn; border-radius: 50%; - padding: 0; + padding: var(--tiny-offset); width: calc(var(--tiny-offset) * 2.5); height: calc(var(--tiny-offset) * 2.5); - font: var(--regular-font); + font: var(--medium-font); + letter-spacing: var(--one); } .mobileHide { @@ -210,3 +225,21 @@ display: none; } } + +.discount { + text-decoration: line-through; +} + +.priceElement { + flex-direction: column; + margin: 0; + + span:empty { + display: none; + } + + @media (max-width: 768px) { + flex-direction: row; + justify-content: start; + } +}