fix(storefront): release-candidate walkthrough fixes
- Cart item description showed a stray literal "..." when the item had no
description text (cart.component.html) — now only renders the trailing
ellipsis when a description is present.
- Compare table showed raw internal stock enum values ("high"/"low"/etc.)
instead of localized labels (compare-table.component.ts) — now reuses the
same stock-label mapping used by product cards.
- Search with zero results incorrectly showed the empty-category messaging
("browse categories" / "go to parent category") stacked on top of the
search's own "nothing found" message (catalog-container.component.ts) —
isEmptyCategoryState now excludes active search queries so only the
search-appropriate empty state renders.
- Footer "About" link pointed to /about, which 404s; the actual CMS page
route is /about-us (bootstrap.json mock nav data) — corrected the route.
- Added missing public/assets/images/placeholder.svg, the fallback image
referenced by getMainImage() for items without photos (previously 404s
if that fallback path is ever hit).
Investigated and left as-is (not code bugs): /images/*.webp 404s on
product cards are references to a real backend/CDN not present in local
dev (confirmed via mock-data.interceptor.ts and api.service.ts image-URL
resolution) — expected dev-only gap. Footer "Contacts" link (/contacts)
has no corresponding static page content at all in mock data; flagging
for a content decision rather than fabricating copy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
8
public/assets/images/placeholder.svg
Normal file
8
public/assets/images/placeholder.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300" role="img" aria-label="No image available">
|
||||||
|
<rect width="400" height="300" fill="#e5e7eb"/>
|
||||||
|
<g fill="none" stroke="#9ca3af" stroke-width="2">
|
||||||
|
<rect x="40" y="40" width="320" height="220" rx="8"/>
|
||||||
|
<path d="M40 220 L140 130 L200 180 L260 110 L360 220" />
|
||||||
|
<circle cx="140" cy="100" r="20"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 380 B |
@@ -110,7 +110,10 @@ export class CatalogContainerComponent {
|
|||||||
readonly isDrawerLayout = computed(() => this.viewportWidth() <= 1024);
|
readonly isDrawerLayout = computed(() => this.viewportWidth() <= 1024);
|
||||||
readonly isMobile = computed(() => this.viewportWidth() <= 767);
|
readonly isMobile = computed(() => this.viewportWidth() <= 767);
|
||||||
readonly hasRawProducts = computed(() => this.rawProducts().length > 0);
|
readonly hasRawProducts = computed(() => this.rawProducts().length > 0);
|
||||||
readonly isEmptyCategoryState = computed(() => this.viewMode() === 'products' && !this.loadingProducts() && this.rawProducts().length === 0 && this.subcategoryChips().length === 0);
|
// A blank search query with zero raw results is a "nothing found" search result, not an
|
||||||
|
// empty category — showing category-navigation copy ("browse categories" / "go to parent
|
||||||
|
// category") here is misleading when the emptiness is caused by the user's search term.
|
||||||
|
readonly isEmptyCategoryState = computed(() => this.viewMode() === 'products' && !this.loadingProducts() && this.rawProducts().length === 0 && this.subcategoryChips().length === 0 && !this.state().search.trim());
|
||||||
readonly isFilteredEmptyState = computed(() => this.viewMode() === 'products' && !this.loadingProducts() && this.rawProducts().length > 0 && this.products().length === 0);
|
readonly isFilteredEmptyState = computed(() => this.viewMode() === 'products' && !this.loadingProducts() && this.rawProducts().length > 0 && this.products().length === 0);
|
||||||
readonly showDesktopFilters = computed(() => this.viewMode() === 'products' && !this.isEmptyCategoryState() && !this.isDrawerLayout() && !this.offlineState());
|
readonly showDesktopFilters = computed(() => this.viewMode() === 'products' && !this.isEmptyCategoryState() && !this.isDrawerLayout() && !this.offlineState());
|
||||||
readonly showMobileToolbar = computed(() => this.viewMode() === 'products' && !this.isEmptyCategoryState() && this.isMobile() && !this.offlineState());
|
readonly showMobileToolbar = computed(() => this.viewMode() === 'products' && !this.isEmptyCategoryState() && this.isMobile() && !this.offlineState());
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ interface CompareRow {
|
|||||||
identical: boolean;
|
identical: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const STOCK_LABEL_KEYS: Record<string, string> = {
|
||||||
|
high: 'catalog.stockHigh',
|
||||||
|
medium: 'catalog.stockMedium',
|
||||||
|
low: 'catalog.stockLow',
|
||||||
|
out: 'catalog.stockOut'
|
||||||
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-compare-table',
|
selector: 'app-compare-table',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -34,7 +41,7 @@ export class CompareTableComponent {
|
|||||||
const baseRows: CompareRow[] = [
|
const baseRows: CompareRow[] = [
|
||||||
this.toRow('price', this.i18n.t('ux.comparePrice'), products.map(product => `${product.price.toFixed(2)} ${product.currency}`)),
|
this.toRow('price', this.i18n.t('ux.comparePrice'), products.map(product => `${product.price.toFixed(2)} ${product.currency}`)),
|
||||||
this.toRow('rating', this.i18n.t('ux.compareRating'), products.map(product => `${(product.rating ?? 0).toFixed(1)}`)),
|
this.toRow('rating', this.i18n.t('ux.compareRating'), products.map(product => `${(product.rating ?? 0).toFixed(1)}`)),
|
||||||
this.toRow('stock', this.i18n.t('ux.compareStock'), products.map(product => product.remainings ?? this.i18n.t('ux.compareUnknown'))),
|
this.toRow('stock', this.i18n.t('ux.compareStock'), products.map(product => this.stockLabel(product.remainings))),
|
||||||
this.toRow('discount', this.i18n.t('ux.compareDiscount'), products.map(product => `${product.discount ?? 0}%`)),
|
this.toRow('discount', this.i18n.t('ux.compareDiscount'), products.map(product => `${product.discount ?? 0}%`)),
|
||||||
this.toRow('color', this.i18n.t('ux.compareColor'), products.map(product => product.colour ?? '—')),
|
this.toRow('color', this.i18n.t('ux.compareColor'), products.map(product => product.colour ?? '—')),
|
||||||
this.toRow('size', this.i18n.t('ux.compareSize'), products.map(product => product.size ?? '—'))
|
this.toRow('size', this.i18n.t('ux.compareSize'), products.map(product => product.size ?? '—'))
|
||||||
@@ -58,6 +65,14 @@ export class CompareTableComponent {
|
|||||||
return this.highlightDifferences && !row.identical;
|
return this.highlightDifferences && !row.identical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private stockLabel(remainings: string | undefined): string {
|
||||||
|
if (!remainings) {
|
||||||
|
return this.i18n.t('ux.compareUnknown');
|
||||||
|
}
|
||||||
|
const key = STOCK_LABEL_KEYS[remainings.toLowerCase()];
|
||||||
|
return key ? this.i18n.t(key) : remainings;
|
||||||
|
}
|
||||||
|
|
||||||
private toRow(key: string, label: string, values: string[]): CompareRow {
|
private toRow(key: string, label: string, values: string[]): CompareRow {
|
||||||
const normalized = values.map(value => value.trim().toLowerCase());
|
const normalized = values.map(value => value.trim().toLowerCase());
|
||||||
const identical = normalized.every(value => value === normalized[0]);
|
const identical = normalized.every(value => value === normalized[0]);
|
||||||
|
|||||||
@@ -40,7 +40,9 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="item-description">{{ itemDesc(item) || '' }}...</p>
|
@if (itemDesc(item)) {
|
||||||
|
<p class="item-description">{{ itemDesc(item) }}...</p>
|
||||||
|
}
|
||||||
|
|
||||||
@if (item.colour || (item.size && item.size.toLowerCase() !== 'default')) {
|
@if (item.colour || (item.size && item.size.toLowerCase() !== 'default')) {
|
||||||
<div class="cart-item-variants">
|
<div class="cart-item-variants">
|
||||||
|
|||||||
@@ -201,7 +201,7 @@
|
|||||||
{
|
{
|
||||||
"id": "footer-about",
|
"id": "footer-about",
|
||||||
"labelKey": "nav.about",
|
"labelKey": "nav.about",
|
||||||
"route": "/about",
|
"route": "/about-us",
|
||||||
"order": 1
|
"order": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user