From c0bce7feacb21145829e83ed74089de5aa2b0b66 Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Thu, 13 Aug 2026 09:06:46 +0400 Subject: [PATCH] fix: Buy Now raced ahead of addToCart, per-product SEO tags never applied CartService.addItem() fired an async dynamic import + API call for new items but returned void immediately - buyNow() navigated to /cart before the item was actually added, landing the user on an empty or stale cart. addItem() now returns a Promise that resolves once the cart signal actually contains the item; buyNow() awaits it before navigating. Also wired SeoService.setItemMeta()/resetToDefaults() into the product detail page - built and working, but never called anywhere, so every product page rendered the site-wide default OG/Twitter tags instead of per-product ones. Co-Authored-By: Claude Sonnet 5 --- .../product-details-container.component.ts | 17 ++++++--- src/app/services/cart.service.ts | 38 ++++++++++++------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/src/app/features/website/product/containers/product-details-container.component.ts b/src/app/features/website/product/containers/product-details-container.component.ts index 7460c77..d850e1d 100644 --- a/src/app/features/website/product/containers/product-details-container.component.ts +++ b/src/app/features/website/product/containers/product-details-container.component.ts @@ -16,6 +16,7 @@ import { LanguageService } from '../../../../services/language.service'; import { DEFAULT_PRODUCT_PAGE_CONFIG, DEFAULT_USER_EXPERIENCE_CONFIG, ProductPageConfig } from '../../../../shared/models/config'; import { getStockStatus, getTranslatedField } from '../../../../utils/item.utils'; import { ProductShareService } from '../../user-experience/services/product-share.service'; +import { SeoService } from '../../../../services/seo.service'; import { ProductDeliveryInformationComponent } from '../components/delivery-information/delivery-information.component'; import { ProductActionsComponent } from '../components/product-actions/product-actions.component'; import { ProductGalleryComponent } from '../components/product-gallery/product-gallery.component'; @@ -70,6 +71,7 @@ export class ProductDetailsContainerComponent { private readonly languageService = inject(LanguageService); private readonly translate = inject(TranslateService); private readonly shareService = inject(ProductShareService); + private readonly seoService = inject(SeoService); readonly productPageConfigState = signal>(this.resolveProductPageConfig()); readonly userExperienceConfig = signal(this.resolveUserExperienceConfig()); @@ -229,6 +231,8 @@ export class ProductDetailsContainerComponent { this.route.paramMap .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(params => this.loadProduct(Number(params.get('id')))); + + this.destroyRef.onDestroy(() => this.seoService.resetToDefaults()); } loadProduct(productId: number): void { @@ -261,6 +265,7 @@ export class ProductDetailsContainerComponent { } this.product.set(product); + this.seoService.setItemMeta(product); if (this.userExperienceConfig().recentlyViewed.enabled) { this.uxFacade.trackRecentlyViewed(product, this.userExperienceConfig().recentlyViewed.maxItems); } @@ -303,10 +308,10 @@ export class ProductDetailsContainerComponent { } } - addToCart(): void { + addToCart(): Promise { const current = this.product(); - if (!current) return; - this.cartService.addItem(current.itemID, 1, { + if (!current) return Promise.resolve(); + return this.cartService.addItem(current.itemID, 1, { colour: this.selectedColour() ?? undefined, size: this.selectedSize() ?? undefined, price: this.effectivePrice(), @@ -314,9 +319,9 @@ export class ProductDetailsContainerComponent { }); } - buyNow(): void { - this.addToCart(); - this.router.navigate([`/${this.languageService.currentLanguage()}/cart`]); + async buyNow(): Promise { + await this.addToCart(); + void this.router.navigate([`/${this.languageService.currentLanguage()}/cart`]); } toggleWishlist(): void { diff --git a/src/app/services/cart.service.ts b/src/app/services/cart.service.ts index 99ff51c..7a07de6 100644 --- a/src/app/services/cart.service.ts +++ b/src/app/services/cart.service.ts @@ -203,20 +203,30 @@ export class CartService { return this.cartItems().findIndex(item => this.isSameCartLine(item, itemID, variant)); } - addItem(itemID: number, quantity: number = 1, variant?: CartVariant): void { + /** + * Resolves once the item is actually present in the cart signal - not + * merely once the call was fired. New (not-yet-in-cart) items fetch their + * details from the API first, so callers that navigate straight after + * (e.g. Buy Now -> /cart) must await this rather than treat it as fired + * synchronously. + */ + addItem(itemID: number, quantity: number = 1, variant?: CartVariant): Promise { // Prevent duplicate API calls for same item - if (this.addingItems.has(itemID)) return; - + if (this.addingItems.has(itemID)) return Promise.resolve(); + const currentItems = this.cartItems(); const existingItem = currentItems.find(i => this.isSameCartLine(i, itemID, variant)); - + if (existingItem) { // Item exists, increase quantity this.updateQuantity(itemID, existingItem.quantity + quantity, variant); - } else { - // Get item details from API and add to cart - this.addingItems.add(itemID); - import('./api.service').then(({ ApiService }) => { + return Promise.resolve(); + } + + // Get item details from API and add to cart + this.addingItems.add(itemID); + return import('./api.service').then(({ ApiService }) => + new Promise((resolve) => { this.injector.get(ApiService).getItem(itemID).subscribe({ next: (item) => { const cartItem = this.normalizeCartItem({ @@ -229,17 +239,19 @@ export class CartService { }); this.cartItems.set([...this.cartItems(), cartItem]); this.addingItems.delete(itemID); + resolve(); }, error: (err) => { console.error('Error adding to cart:', err); this.addingItems.delete(itemID); + resolve(); } }); - }).catch((err) => { - console.error('Error loading API service:', err); - this.addingItems.delete(itemID); - }); - } + }) + ).catch((err) => { + console.error('Error loading API service:', err); + this.addingItems.delete(itemID); + }); } updateQuantity(itemID: number, quantity: number, variant?: CartVariant): void {