diff --git a/README.md b/README.md index ebf6648..960f43a 100644 --- a/README.md +++ b/README.md @@ -393,24 +393,29 @@ own distinct badge, the two never combine), and at/below the product's own `lowStockThreshold`. Neither raw `stock` nor `lowStockThreshold` is exposed in the public `Product` type, only this derived boolean — the public API has no reason to leak exact counts. Shown as a "Nur noch wenige -verfügbar" pill (a new `--color-warning` token in `globals.css`, distinct -from the brand-colored discount badge so the two never read as the same -thing) — the same image-overlaid top-left pill Ausverkauft/discount -already use (`position: absolute`, outside layout flow) on -`ProductGrid.tsx`/`ProductSpotlight.tsx`/`RelatedProducts.tsx`/todo-cards' -`Pricing.tsx`, plus a `"(nur noch wenige)"` variant-select suffix in -`AddToCartButton`/`AddToCartInlineButton`. Stacks with the discount badge -in a `flex flex-col` column rather than being replaced by it — a -discounted product that's also low on stock (both are independent -booleans, not mutually exclusive states) needs to show both, discovered -live when `todo-karten` had exactly that combination and the low-stock -pill silently never rendered under the old either/or ternary. Only -"Ausverkauft" still wins outright, since it replaces both. Deliberately -**not** a separate -text line under the add-to-cart button (an earlier version did this) — -that made the button block's own height vary card-to-card in every grid -rendering it, breaking equal-height card alignment; the badge doesn't -participate in layout flow at all, so it can't cause that. +verfügbar" **text line** (`text-warning`, the same `--color-warning` token +in `globals.css`, distinct from the brand-colored discount badge) next to +the price on `ProductGrid.tsx`/`ProductSpotlight.tsx`/ +`RelatedProducts.tsx`/todo-cards' `Pricing.tsx` and — variant-specific, not +"any variant low" — under the product name in `CartContent.tsx`'s own +cart line items, plus the existing `"(nur noch wenige)"` variant-select +suffix in `AddToCartButton`/`AddToCartInlineButton`. The image-overlaid +top-left pill (`position: absolute`, outside layout flow) now shows only +Ausverkauft/discount — low-stock moved off the image into text on request, +since a shopper scanning product photos for "-20%" style badges reads a +long low-stock sentence pinned to the image as clutter, and a customer +already in the cart with that product had no low-stock signal there at +all before this. The earlier version of this text line (removed once, +see git history) broke equal-height card alignment in `ProductGrid.tsx`/ +`RelatedProducts.tsx` by only rendering when `lowStock` was true, so +cards with/without the line ended up different heights; this version +always renders the line's slot (`min-h-[1.05rem]`, empty when not +low-stock) so every card in a row reserves the same space regardless of +state — `ProductGrid.tsx` additionally still has its `flex-1` spacer +pinning the add-to-cart button to the same Y as before, so the reserved +height is belt-and-suspenders there, but load-bearing in +`RelatedProducts.tsx`, which has no such spacer. Only "Ausverkauft" still +wins outright over the discount pill, since it replaces it. **Pricing**: `app/lib/cartTotals.ts`'s `effectivePrice(entry, product)` — a selected variant's `priceOverride` wins over the base `product.price` diff --git a/app/cart/components/CartContent.tsx b/app/cart/components/CartContent.tsx index 58cbb6e..2460a62 100644 --- a/app/cart/components/CartContent.tsx +++ b/app/cart/components/CartContent.tsx @@ -184,6 +184,13 @@ export function CartContent({ // when the quantity or remove control is used, same "full // key" reasoning as cart.ts's own sameLine(). const lineKey = entry.variant ? `${product.id}::${entry.variant}` : product.id; + // The exact variant this line is for, not "any variant low" + // like the product-grid cards use — a cart line already has + // its variant chosen, so it should only warn when that + // specific variant (not some other one) is running low. + const lowStock = entry.variant + ? (product.variants.find((v) => v.name === entry.variant)?.lowStock ?? false) + : product.lowStock; return (
Nur noch wenige verfügbar
}{product.description}
Einzelpreis
diff --git a/app/cart/components/RelatedProducts.tsx b/app/cart/components/RelatedProducts.tsx index 620e5b0..845624f 100644 --- a/app/cart/components/RelatedProducts.tsx +++ b/app/cart/components/RelatedProducts.tsx @@ -159,28 +159,20 @@ export function RelatedProducts({ defaultTaxRate }: { defaultTaxRate: number }) /> {/* Same top-left pill pattern as ProductGrid.tsx/ ProductSpotlight.tsx — position: absolute, so it never - affects this card's height the way the old inline - low-stock text hint under the button used to (that - variability was exactly what broke equal card heights - in this grid). */} + affects this card's height. Only the discount/Ausverkauft + pill lives here now; the low-stock hint moved to a + reserved-height text line below (see the min-h paragraph + under the price) — plain conditional text here is what + broke equal card heights in this grid before. */} {fullyOutOfStock ? ( Ausverkauft ) : ( - (discount !== null || anyLowStock) && ( -+ {anyLowStock ? "Nur noch wenige verfügbar" : null} +
Nur noch wenige verfügbar
} {/* items-start at sm: — without it, the default cross-axis stretch makes "Mehr erfahren" grow to match AddToCartButton's own height whenever that one gets taller diff --git a/app/shop/components/ProductGrid.tsx b/app/shop/components/ProductGrid.tsx index 048d602..17c33b9 100644 --- a/app/shop/components/ProductGrid.tsx +++ b/app/shop/components/ProductGrid.tsx @@ -58,23 +58,10 @@ export async function ProductGrid() { Ausverkauft ) : ( - (discount !== null || anyLowStock) && ( - // Stacked, not either/or — a discounted product running - // low on stock at the same time (the actual todo-karten - // case) needs both badges, not just whichever came first - // in a ternary. -+ {anyLowStock ? "Nur noch wenige verfügbar" : null} +
{product.href && ( ) : ( - (discount !== null || anyLowStock) && ( -Nur noch wenige verfügbar
} {/* No "Sichere Zahlung" trust note here (unlike Cart/Checkout) — this is an add-to-cart step, not the actual payment step, so a payment-security reassurance is premature here and just