Add product categories + shop sidebar filters, fix CTA button alignment
Products now carry an optional categories relationship (backend: new product-categories collection mirroring the blog's categories pattern, migration applied and deployed). The shop page gains a left sidebar (styled like AccountNav) with a dual-handle price slider, category checkboxes, and an availability toggle — all instant-apply via searchParams, same union-filter semantics as the blog's category chips. Uncategorized products always match every category filter rather than disappearing. Also fixes CTA buttons sitting at different heights across sibling cards when one product's title wraps to two lines — MerklisteGrid.tsx and RelatedProducts.tsx get the same h-full/flex-1 spacer pattern ProductGrid.tsx already used.
This commit is contained in:
@@ -235,6 +235,11 @@ export type Product = {
|
||||
// exempts the individual product, not the whole cart.
|
||||
noShippingCost: boolean;
|
||||
variants: { name: string; priceOverride: number | null; outOfStock: boolean; lowStock: boolean; maxQty: number | null }[];
|
||||
// Empty for a product that predates this field or was never tagged —
|
||||
// treated as "matches every category filter" by the shop grid rather
|
||||
// than "matches none", since an uncategorized product shouldn't just
|
||||
// disappear the moment a category filter is applied.
|
||||
categories: string[];
|
||||
};
|
||||
|
||||
type PayloadProduct = {
|
||||
@@ -270,6 +275,7 @@ type PayloadProduct = {
|
||||
lowStockThreshold: number | null;
|
||||
}[]
|
||||
| null;
|
||||
categories: ({ name: string } | number)[] | null;
|
||||
};
|
||||
|
||||
// A product/variant is only actually unbuyable when it opted into
|
||||
@@ -330,6 +336,9 @@ export function mapPayloadProduct(product: PayloadProduct): Product {
|
||||
lowStock: isLowStock(v.trackInventory, v.stock, v.lowStockThreshold),
|
||||
maxQty: maxPurchasableQty(v.trackInventory, v.stock, v.allowBackorder),
|
||||
})),
|
||||
categories: (product.categories ?? [])
|
||||
.map((c) => (typeof c === "object" && c ? c.name : null))
|
||||
.filter((name): name is string => Boolean(name)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user