From 3e54e88db7802170539e1698df6a6ae5874ee066 Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Sun, 26 Jul 2026 00:12:43 +0400 Subject: [PATCH] fix(storefront): add missing placeholder image asset and onerror fallback getMainImage() referenced /assets/images/placeholder.svg as the no-image fallback, but src/assets/images/ never existed - any item with zero photos rendered a browser broken-image icon instead of a placeholder. Added the asset. Also added an (error) handler (onImageError) on every dynamic that renders a user/admin-supplied URL (product card, cart line item, cart payment QR code, product gallery main + thumbnails) so a 404'd/broken image URL swaps to the shared placeholder instead of shipping broken. --- .../components/product-card/product-card.component.html | 2 +- .../components/product-card/product-card.component.ts | 3 ++- .../product-gallery/product-gallery.component.html | 4 ++-- .../product-gallery/product-gallery.component.ts | 4 +++- src/app/pages/cart/cart.component.html | 4 ++-- src/app/pages/cart/cart.component.ts | 3 ++- src/app/utils/item.utils.ts | 9 +++++++++ src/assets/images/placeholder.svg | 8 ++++++++ 8 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 src/assets/images/placeholder.svg diff --git a/src/app/components/product-card/product-card.component.html b/src/app/components/product-card/product-card.component.html index 4fe7075..3a17925 100644 --- a/src/app/components/product-card/product-card.component.html +++ b/src/app/components/product-card/product-card.component.html @@ -1,7 +1,7 @@
- + @if (showDiscountBadge && item.discount > 0) {
-{{ item.discount }}%
} diff --git a/src/app/components/product-card/product-card.component.ts b/src/app/components/product-card/product-card.component.ts index b8311d5..4e0f19f 100644 --- a/src/app/components/product-card/product-card.component.ts +++ b/src/app/components/product-card/product-card.component.ts @@ -4,7 +4,7 @@ import { RouterLink } from '@angular/router'; import { Product } from '../../core/products/models/product-domain.model'; import { LangRoutePipe } from '../../pipes/lang-route.pipe'; import { TranslatePipe } from '../../i18n/translate.pipe'; -import { cleanDescription, getBadgeClass, getDiscountedPrice, getMainImage } from '../../utils/item.utils'; +import { cleanDescription, getBadgeClass, getDiscountedPrice, getMainImage, onImageError } from '../../utils/item.utils'; const STOCK_LABEL_KEYS: Record = { high: 'catalog.stockHigh', @@ -56,6 +56,7 @@ export class ProductCardComponent { readonly getDiscountedPrice = getDiscountedPrice; readonly getBadgeClass = getBadgeClass; readonly cleanDescription = cleanDescription; + readonly onImageError = onImageError; onAddToCart(event: Event): void { this.addToCart.emit({ itemID: this.item.itemID, event }); diff --git a/src/app/features/website/product/components/product-gallery/product-gallery.component.html b/src/app/features/website/product/components/product-gallery/product-gallery.component.html index 39982bd..af05787 100644 --- a/src/app/features/website/product/components/product-gallery/product-gallery.component.html +++ b/src/app/features/website/product/components/product-gallery/product-gallery.component.html @@ -31,7 +31,7 @@ } @default { - + } }
@@ -45,7 +45,7 @@ } @else if (item.type !== 'image') { {{ item.type }} } - + } diff --git a/src/app/features/website/product/components/product-gallery/product-gallery.component.ts b/src/app/features/website/product/components/product-gallery/product-gallery.component.ts index f2b6c69..ea99c0c 100644 --- a/src/app/features/website/product/components/product-gallery/product-gallery.component.ts +++ b/src/app/features/website/product/components/product-gallery/product-gallery.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { Product } from '../../../../../core/products/models/product-domain.model'; -import { getMainImage } from '../../../../../utils/item.utils'; +import { getMainImage, onImageError } from '../../../../../utils/item.utils'; import { TranslatePipe } from '../../../../../i18n/translate.pipe'; type ProductMediaType = 'image' | 'video' | 'pdf' | 'manual' | 'warranty'; @@ -22,6 +22,8 @@ interface GalleryMediaItem { changeDetection: ChangeDetectionStrategy.OnPush }) export class ProductGalleryComponent { + readonly onImageError = onImageError; + @Input({ required: true }) product!: Product; @Input() selectedIndex = 0; diff --git a/src/app/pages/cart/cart.component.html b/src/app/pages/cart/cart.component.html index b8c52d4..6efc9b3 100644 --- a/src/app/pages/cart/cart.component.html +++ b/src/app/pages/cart/cart.component.html @@ -29,7 +29,7 @@ (touchstart)="onSwipeStart(item.itemID, $event)">
- +
@@ -216,7 +216,7 @@ @if (qrCodeUrl()) {
- +
diff --git a/src/app/pages/cart/cart.component.ts b/src/app/pages/cart/cart.component.ts index 6818d0c..9e71d84 100644 --- a/src/app/pages/cart/cart.component.ts +++ b/src/app/pages/cart/cart.component.ts @@ -9,7 +9,7 @@ import { EMPTY, interval, of, Subscription } from 'rxjs'; import { catchError, exhaustMap, take, timeout } from 'rxjs/operators'; import { DeliverySelectorComponent } from '../../components/delivery-selector/delivery-selector.component'; import { TelegramLoginComponent } from '../../components/telegram-login/telegram-login.component'; -import { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass, getTranslatedField } from '../../utils/item.utils'; +import { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass, getTranslatedField, onImageError } from '../../utils/item.utils'; import { LangRoutePipe } from '../../pipes/lang-route.pipe'; import { TranslatePipe } from '../../i18n/translate.pipe'; import { TranslateService } from '../../i18n/translate.service'; @@ -255,6 +255,7 @@ export class CartComponent implements OnDestroy { } readonly getMainImage = getMainImage; + readonly onImageError = onImageError; readonly trackByItemId = trackByItemId; readonly getDiscountedPrice = getDiscountedPrice; readonly getBadgeClass = getBadgeClass; diff --git a/src/app/utils/item.utils.ts b/src/app/utils/item.utils.ts index aaee42c..13c73c4 100644 --- a/src/app/utils/item.utils.ts +++ b/src/app/utils/item.utils.ts @@ -13,6 +13,15 @@ export function getMainImage(item: Item): string { return item.photos?.[0]?.url || '/assets/images/placeholder.svg'; } +const PLACEHOLDER_IMAGE = '/assets/images/placeholder.svg'; + +/** Swaps a broken/404'd src to the shared placeholder, once, to avoid an infinite error loop. */ +export function onImageError(event: Event): void { + const img = event.target as HTMLImageElement; + if (img.src.endsWith(PLACEHOLDER_IMAGE)) return; + img.src = PLACEHOLDER_IMAGE; +} + export function trackByItemId(_index: number, item: Item): number | string { return item.id || item.itemID; } diff --git a/src/assets/images/placeholder.svg b/src/assets/images/placeholder.svg new file mode 100644 index 0000000..da37c7c --- /dev/null +++ b/src/assets/images/placeholder.svg @@ -0,0 +1,8 @@ + + + + + + + +