"use client"; import { useEffect, useRef, useState } from "react"; import Image from "next/image"; import { addToCart, useCart } from "../lib/cart"; import { useCartFly } from "./CartFly"; import { NotifyMeForm } from "./NotifyMeForm"; // Exported so consumers like RelatedProducts.tsx can delay their own // follow-up UI changes (e.g. swapping out this exact card) until after // the success state has actually had time to be seen. export const FEEDBACK_MS = 2000; /** * Add-to-cart button that stays on the page (unlike AddToCartButton, which * navigates to /cart) — used wherever a product grid lets you keep browsing, * so a click needs its own success confirmation instead of relying on the * navigation itself as feedback. */ export function AddToCartInlineButton({ id, numericId, label = "In den Warenkorb", className, outOfStock = false, maxQty = null, variants = [], }: { id: string; /** Payload's real numeric product id (`product.numericId`) — only used to * scope a NotifyMeForm signup once out of stock, never for the cart/ * checkout path itself (that stays on the slug `id` above). */ numericId: number; label?: string; className?: string; /** Product-level — only meaningful when `variants` is empty. A varianted * product's buyability is entirely per-variant instead (see below). */ outOfStock?: boolean; /** Product-level cap on total cart quantity — only meaningful when * `variants` is empty, same split as `outOfStock`. null means no cap * (backorder allowed / inventory untracked). See lib/payload.ts's * maxPurchasableQty(). */ maxQty?: number | null; /** Optional — products.variants (name + optional priceOverride + its own * outOfStock). When non-empty, a variant must be picked (defaults to the * first *in-stock* one, or just the first if all are out) before "add to * cart" is enabled — the selected variant's name is snapshotted onto the * cart line and, later, the order itself. */ variants?: { name: string; priceOverride: number | null; outOfStock: boolean; lowStock: boolean; maxQty: number | null }[]; }) { const [added, setAdded] = useState(false); const [selectedVariant, setSelectedVariant] = useState(variants.find((v) => !v.outOfStock)?.name ?? variants[0]?.name); const timeoutRef = useRef | undefined>(undefined); const buttonRef = useRef(null); const { fly } = useCartFly(); const cart = useCart(); useEffect(() => () => clearTimeout(timeoutRef.current), []); // Whichever is actually being offered right now — the selected variant's // own flag if there are variants, otherwise the plain product-level one. const currentlyOutOfStock = variants.length > 0 ? (variants.find((v) => v.name === selectedVariant)?.outOfStock ?? false) : outOfStock; const currentMaxQty = variants.length > 0 ? (variants.find((v) => v.name === selectedVariant)?.maxQty ?? null) : maxQty; // How much of this exact (id, variant) line is already sitting in the // cart — capped adds mean "In den Warenkorb" must go disabled once this // reaches currentMaxQty, not just when the product is fully sold out. const qtyInCart = cart.find((i) => i.id === id && i.variant === selectedVariant)?.qty ?? 0; const limitReached = currentMaxQty != null && qtyInCart >= currentMaxQty; const disabled = currentlyOutOfStock || limitReached; function handleClick() { if (disabled) return; addToCart(id, 1, selectedVariant); if (buttonRef.current) fly(buttonRef.current); setAdded(true); clearTimeout(timeoutRef.current); timeoutRef.current = setTimeout(() => setAdded(false), FEEDBACK_MS); } const base = className ?? "flex items-center justify-between px-5 py-3 rounded-sm border w-full transition-all duration-200 active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2 focus-visible:ring-offset-bg-base"; // Branch the border/bg classes instead of appending an "override" on top // of the default ones — Tailwind v4 has no leading-`!` important prefix // anymore (it's a trailing `!` now), so two conflicting utilities like // border-border/border-success both being present would silently race on // CSS source order instead of one cleanly winning. const stateClasses = disabled ? "border-border opacity-60 cursor-not-allowed" : added ? "border-success bg-success-subtle" : "border-border hover:border-brand"; return ( // Low stock isn't shown as its own text line here (see // AddToCartButton.tsx's identical comment on why) — the image-overlaid // pill badge (ProductGrid.tsx/RelatedProducts.tsx, position: absolute, // outside layout flow) is where this shows now, same as // Ausverkauft/discount already do.
{variants.length > 0 && ( )} {currentlyOutOfStock && 0 ? (selectedVariant ?? "") : ""} />}
); }