From 82c1b1fd43d458c491149bb209e62c6f3329d035 Mon Sep 17 00:00:00 2001 From: Marco Date: Sun, 2 Aug 2026 07:14:49 +0000 Subject: [PATCH] Match sold-out card height to in-stock siblings NotifyMeForm was two stacked rows (email input + full-width button), making an out-of-stock ProductCard taller than its in-stock siblings. Redesigned as a single input with the submit control embedded inside it (same py-3 as AddToCartInlineButton's own button, so the row height matches exactly), and ProductCard now skips its reserved-height low-stock line entirely for a fully-out-of-stock product, since that line can never apply there. Co-Authored-By: Claude Sonnet 5 --- app/components/NotifyMeForm.tsx | 70 +++++++++++++++++---------------- app/components/ProductCard.tsx | 15 +++++-- 2 files changed, 48 insertions(+), 37 deletions(-) diff --git a/app/components/NotifyMeForm.tsx b/app/components/NotifyMeForm.tsx index 336976b..8bcbad7 100644 --- a/app/components/NotifyMeForm.tsx +++ b/app/components/NotifyMeForm.tsx @@ -2,21 +2,27 @@ import { useState } from "react"; import { isValidEmail } from "../lib/email"; +import { ArrowRightIcon } from "./ArrowRightIcon"; /** * Replaces the (disabled) Add-to-cart button's spot once a product/variant * is out of stock — lets a visitor leave their email to be notified once * lib/jobs/sendBackInStockEmails.ts (Payload backend) sends the "it's - * back" mail. Always shows the email input + submit button directly (no - * extra click to reveal them). The resulting out-of-stock card is taller - * than its in-stock siblings — ProductGrid.tsx/MerklisteGrid.tsx/ - * RelatedProducts.tsx all use `items-start` on their grid so that doesn't - * cascade into pushing every other card's button down to match (see - * AddToCartInlineButton.tsx's own comment). `productId` is the numeric - * Payload id (`product.numericId`), NOT AddToCartInlineButton/ - * AddToCartButton's own `id`/`productId` props — those are the commerce - * slug (see lib/payload.ts's Product.id comment) — same "numericId, not - * id" split WishlistButton already uses. + * back" mail. Always shows the email input + submit control directly (no + * extra click to reveal them). + * + * One input with the submit control embedded inside it (absolutely + * positioned, not a layout sibling) — not a separate stacked button below + * the input. A first version stacked input + full-width "Benachrichtigen" + * button, which made an out-of-stock ProductCard.tsx visibly taller than + * its in-stock siblings (two form rows vs. one button); shrinking that + * stacked version's own padding was tried and reverted — the actual ask + * was to keep every control's height untouched and make the CARD match + * instead. Embedding the submit icon inside the input keeps this whole + * control to exactly one row, sized with the same `py-3` as + * AddToCartInlineButton's own "In den Warenkorb" button, so a sold-out + * card ends up exactly as tall as an in-stock one — see that component's + * own button classes. */ export function NotifyMeForm({ productId, variantName = "" }: { productId: number; variantName?: string }) { const [email, setEmail] = useState(""); @@ -55,31 +61,27 @@ export function NotifyMeForm({ productId, variantName = "" }: { productId: numbe } return ( - // Stacked, not side-by-side — narrow product cards (shop grid, related - // products) don't leave enough width for input + button in one row - // without either truncating the placeholder or squeezing the button. -
- setEmail(e.target.value)} - placeholder="E-Mail-Adresse" - aria-label="E-Mail-Adresse für Verfügbarkeits-Info" - className="w-full rounded-sm border border-border px-3 py-2 text-body-sm text-text-primary bg-bg-base focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand" - /> - {/* Short on purpose — "Bei Verfügbarkeit benachrichtigen" wrapped to - two lines on narrow single-column cards (e.g. /konto/merkliste). - The "Ausverkauft" badge + email field right above already say - what this is for, so "Benachrichtigen" alone reads fine here. */} - + +
+ setEmail(e.target.value)} + placeholder="E-Mail-Adresse" + aria-label="E-Mail-Adresse für Verfügbarkeits-Info" + className="w-full rounded-sm border border-border pl-3 pr-12 py-3 text-body-sm text-text-primary bg-bg-base focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand" + /> + +
{error &&

{error}

}
); } - diff --git a/app/components/ProductCard.tsx b/app/components/ProductCard.tsx index f63abe6..48280b7 100644 --- a/app/components/ProductCard.tsx +++ b/app/components/ProductCard.tsx @@ -108,9 +108,18 @@ export function ProductCard({ {!kleinunternehmer && inkl. {taxRate}% MwSt.}

{belowPrice} - {/* Always rendered, text conditional — reserved height keeps every - card in a row equal height regardless of low-stock status. */} -

{anyLowStock ? "Nur noch wenige verfügbar" : null}

+ {/* Reserved height keeps every IN-STOCK card in a row equal height + regardless of low-stock status — but a fully-out-of-stock + product can never show this line (anyLowStock is meaningless + once nothing's left to sell), so it's omitted entirely rather + than reserving dead space here. That dead space was exactly + what made a sold-out card's NotifyMeForm sit lower than + necessary, growing the whole card taller than its in-stock + siblings — see NotifyMeForm.tsx's own comment on that height + mismatch. */} + {!fullyOutOfStock && ( +

{anyLowStock ? "Nur noch wenige verfügbar" : null}

+ )} {/* flex-1 spacer — pins every card's button to the same Y regardless of whether the title/category line wraps. */}