Skip to content

Commit

Permalink
feat: add BroadcastChannel for synchronization
Browse files Browse the repository at this point in the history
  • Loading branch information
YulikK committed Jun 24, 2024
1 parent 6dbd515 commit d018814
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 57 deletions.
2 changes: 1 addition & 1 deletion src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ interface ImportMeta {
MODE: 'development' | 'production';
PROD: boolean;
VITE_APP_CTP_API_URL: string;
VITE_APP_CTP_AUTH_UR: string;
VITE_APP_CTP_AUTH_URL: string;
VITE_APP_CTP_CLIENT_ID: string;
VITE_APP_CTP_CLIENT_SECRET: string;
VITE_APP_CTP_PROJECT_KEY: string;
Expand Down
152 changes: 113 additions & 39 deletions src/pages/CartPage/model/CartPageModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Page } from '@/shared/types/page.ts';
import SummaryModel from '@/entities/Summary/model/SummaryModel.ts';
import getCartModel from '@/shared/API/cart/model/CartModel.ts';
import getCustomerModel from '@/shared/API/customer/model/CustomerModel.ts';
import { isChannelMessage } from '@/shared/API/types/validation.ts';
import LoaderModel from '@/shared/Loader/model/LoaderModel.ts';
import getStore from '@/shared/Store/Store.ts';
import { setCurrentPage } from '@/shared/Store/actions.ts';
Expand All @@ -12,6 +13,7 @@ import { SERVER_MESSAGE_KEY } from '@/shared/constants/messages.ts';
import { PAGE_ID } from '@/shared/constants/pages.ts';
import { LOADER_SIZE } from '@/shared/constants/sizes.ts';
import { CartActive } from '@/shared/types/cart.ts';
import { ChannelMessage } from '@/shared/types/channel.ts';
import { promoCodeAppliedMessage, promoCodeDeleteMessage } from '@/shared/utils/messageTemplates.ts';
import { showErrorMessage, showSuccessMessage } from '@/shared/utils/userMessage.ts';
import ProductOrderModel from '@/widgets/ProductOrder/model/ProductOrderModel.ts';
Expand All @@ -29,26 +31,45 @@ class CartPageModel implements Page {

private cartCouponSummary: SummaryModel;

private channel: BroadcastChannel;

private productCouponSummary: SummaryModel;

private productsItem: ProductOrderModel[] = [];

private view: CartPageView;

constructor(parent: HTMLDivElement) {
this.channel = new BroadcastChannel(`${import.meta.env.VITE_APP_CTP_PROJECT_KEY}`);
this.channel.onmessage = this.onChannelMessage.bind(this);
this.cartCouponSummary = new SummaryModel(TITLE_SUMMARY.cart, this.deleteDiscountHandler.bind(this));
this.productCouponSummary = new SummaryModel(TITLE_SUMMARY.product, this.deleteDiscountHandler.bind(this));
this.view = new CartPageView(
parent,
this.cartCouponSummary,
this.productCouponSummary,
this.clearCart.bind(this),
this.clearCartHandler.bind(this),
this.addDiscountHandler.bind(this),
);

this.init().catch(showErrorMessage);
}

private addDiscount(cart: Cart): void {
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.cartCouponSummary.update(this.cart.discountsCart);
this.productCouponSummary.update(this.cart.discountsProduct);
this.view.updateTotal(this.cart);
}

private async addDiscountHandler(discountCode: string): Promise<void> {
if (discountCode.trim()) {
if (discountCode.trim() === HAPPY_BIRTHDAY) {
Expand All @@ -61,36 +82,46 @@ class CartPageModel implements Page {
.then((cart) => {
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.cartCouponSummary.update(this.cart.discountsCart);
this.productCouponSummary.update(this.cart.discountsProduct);
this.view.updateTotal(this.cart);
this.channel.postMessage({ cart, type: ChannelMessage.ADD_DISCOUNT });
this.addDiscount(cart);
}
})
.catch(showErrorMessage)
.finally(() => loader.remove());
}
}

private changeProductHandler(cart: Cart): void {
private addProduct(cart: Cart): void {
this.cart = cart;
const newItem = new ProductOrderModel(
this.cart.products[this.cart.products.length - 1],
this.changeProductHandler.bind(this),
);
this.productsItem.push(newItem);
if (this.productsItem.length > 1) {
this.view.addItem(newItem);
this.cartCouponSummary.update(this.cart.discountsCart);
this.productCouponSummary.update(this.cart.discountsProduct);
this.view.updateTotal(this.cart);
} else {
this.view.renderCart(this.productsItem);
}
}

private changeProduct(cart: Cart): void {
this.cart = cart;
this.productsItem = this.productsItem.filter((productItem) => {
const searchEl = this.cart?.products.find((item) => item.lineItemId === productItem.getProduct().lineItemId);
if (searchEl) {
productItem.setProduct(searchEl);
productItem.updateProductHandler(CartActive.UPDATE).catch(showErrorMessage);
}
if (!searchEl) {
productItem.getHTML().remove();
return false;
}
return true;
});

if (!this.productsItem.length) {
this.view.renderEmpty();
}
Expand All @@ -99,6 +130,11 @@ class CartPageModel implements Page {
this.view.updateTotal(this.cart);
}

private changeProductHandler(cart: Cart): void {
this.changeProduct(cart);
this.channel.postMessage({ cart, type: ChannelMessage.ITEM_CHANGE });
}

private async checkBirthday(): Promise<void> {
if (!getStore().getState().isUserLoggedIn) {
throw showErrorMessage(SERVER_MESSAGE_KEY.COUPON_NEED_LOGIN);
Expand All @@ -124,28 +160,48 @@ class CartPageModel implements Page {
}
}

private async clearCart(): Promise<void> {
private clearCart(cart: Cart | null): void {
this.cart = cart;
showSuccessMessage(SERVER_MESSAGE_KEY.SUCCESSFUL_CLEAR_CART);
this.productsItem = this.productsItem.filter((productItem) => {
const searchEl = this.cart?.products.find((item) => item.lineItemId === productItem.getProduct().lineItemId);
if (!searchEl) {
productItem.getHTML().remove();
return false;
}
return true;
});
this.renderCart();
}

private async clearCartHandler(): Promise<void> {
await getCartModel()
.clearCart()
.then((cart) => {
this.cart = cart;
showSuccessMessage(SERVER_MESSAGE_KEY.SUCCESSFUL_CLEAR_CART);
this.productsItem = this.productsItem.filter((productItem) => {
const searchEl = this.cart?.products.find((item) => item.lineItemId === productItem.getProduct().lineItemId);
if (!searchEl) {
productItem.getHTML().remove();
return false;
}
return true;
});
this.renderCart();
this.channel.postMessage({ cart, type: ChannelMessage.CLEAR_CART });
this.clearCart(cart);
})
.catch((error: Error) => {
showErrorMessage(error);
return this.cart;
});
}

private deleteDiscount(cart: Cart): void {
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.cartCouponSummary.update(this.cart.discountsCart);
this.productCouponSummary.update(this.cart.discountsProduct);
this.view.updateTotal(this.cart);
}

private async deleteDiscountHandler(coupon: Coupon): Promise<void> {
const loader = new LoaderModel(LOADER_SIZE.SMALL).getHTML();
this.view.getCouponButton().append(loader);
Expand All @@ -154,18 +210,8 @@ class CartPageModel implements Page {
.then((cart) => {
if (cart) {
showSuccessMessage(promoCodeDeleteMessage(coupon.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.cartCouponSummary.update(this.cart.discountsCart);
this.productCouponSummary.update(this.cart.discountsProduct);
this.view.updateTotal(this.cart);
this.channel.postMessage({ cart, type: ChannelMessage.DELETE_COUPON });
this.deleteDiscount(cart);
}
})
.catch(showErrorMessage)
Expand All @@ -179,6 +225,34 @@ class CartPageModel implements Page {
observeStore(selectCurrentLanguage, () => this.view.updateLanguage());
}

private onChannelMessage(event: MessageEvent): void {
if (isChannelMessage(event.data)) {
switch (event.data.type) {
case ChannelMessage.ADD_DISCOUNT:
this.addDiscount(event.data.cart);
break;
case ChannelMessage.ADD_PRODUCT:
this.addProduct(event.data.cart);
break;
case ChannelMessage.DELETE_COUPON:
this.deleteDiscount(event.data.cart);
break;
case ChannelMessage.CLEAR_CART:
this.clearCart(event.data.cart);
break;
case ChannelMessage.ITEM_CHANGE:
this.changeProduct(event.data.cart);
break;
default:
break;
}

if (event.data.cart) {
getCartModel().dispatchUpdate(event.data.cart);
}
}
}

private renderCart(): void {
if (this.cart) {
this.cart.products.forEach((product) => {
Expand Down
10 changes: 9 additions & 1 deletion src/pages/CartPage/view/CartPageView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,11 @@ class CartPageView {
});
}

public addItem(productsItem: ProductOrderModel): void {
this.productRow.push(productsItem.getHTML());
this.tableBody?.append(productsItem.getHTML());
}

public getCouponButton(): HTMLButtonElement {
return this.couponButton;
}
Expand All @@ -332,7 +337,10 @@ class CartPageView {
this.productRow.map((productEl) => productEl.remove());
this.productRow = [];
this.addTableHeader();
productsItem.forEach((productEl) => this.tableBody?.append(productEl.getHTML()));
productsItem.forEach((productEl) => {
this.productRow.push(productEl.getHTML());
this.tableBody?.append(productEl.getHTML());
});
this.addTotalInfo();
}

Expand Down
17 changes: 11 additions & 6 deletions src/shared/API/cart/model/CartModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
import getStore from '@/shared/Store/Store.ts';
import { setAnonymousCartId, setAnonymousId } from '@/shared/Store/actions.ts';
import { PRICE_FRACTIONS } from '@/shared/constants/product.ts';
import { ChannelMessage } from '@/shared/types/channel.ts';
import { showErrorMessage } from '@/shared/utils/userMessage.ts';

import type { OptionsRequest } from '../../types/type.ts';
Expand All @@ -36,9 +37,12 @@ type Callback = (cart: Cart) => boolean;
export class CartModel {
private callback: Callback[] = [];

private channel: BroadcastChannel;

private root: CartApi;

constructor() {
this.channel = new BroadcastChannel(`${import.meta.env.VITE_APP_CTP_PROJECT_KEY}`);
this.root = new CartApi();
this.getCart()
.then((cart) => {
Expand Down Expand Up @@ -163,10 +167,6 @@ export class CartModel {
await Promise.all(otherCarts.map((id) => this.root.deleteCart(id)));
}

private dispatchUpdate(cart: Cart): void {
this.callback.forEach((callback) => callback(cart));
}

private async getAnonymousCart(anonymousCartId: string): Promise<Cart | null> {
const data = await this.root.getAnonymCart(anonymousCartId);
if (!data.body.customerId) {
Expand Down Expand Up @@ -283,6 +283,7 @@ export class CartModel {
const data = await this.root.updateCart(cart, actions);
const result = this.getCartFromData(data);
this.dispatchUpdate(result);
this.channel.postMessage({ cart: result, type: ChannelMessage.ADD_PRODUCT });
return result;
}

Expand Down Expand Up @@ -341,10 +342,14 @@ export class CartModel {
];
const data = await this.root.updateCart(cart, actions);
const result = this.getCartFromData(data);
this.dispatchUpdate(cart);
this.dispatchUpdate(result);
return result;
}

public dispatchUpdate(cart: Cart): void {
this.callback.forEach((callback) => callback(cart));
}

public async editProductCount(editCartItem: EditCartItem): Promise<Cart> {
const cart = await this.getCart();

Expand All @@ -358,7 +363,7 @@ export class CartModel {

const data = await this.root.updateCart(cart, actions);
const result = this.getCartFromData(data);
this.dispatchUpdate(cart);
this.dispatchUpdate(result);
return result;
}

Expand Down
Loading

0 comments on commit d018814

Please sign in to comment.