Move low-stock hint to badge, right-align VAT breakdown, gate cart discount field, split billing/shipping delivery method
- Removed the inline "Nur noch wenige verfügbar" text hint from AddToCartButton/AddToCartInlineButton (was making card heights vary in every grid that renders them — RelatedProducts, ProductSpotlight's CTA row) — now only shown via the same image-overlaid pill badge Ausverkauft/discount already use (position: absolute, doesn't affect layout). Added that badge to RelatedProducts.tsx and todo-cards' Pricing.tsx, which didn't have it before. - RelatedProducts cards now also show "inkl. X% MwSt." (was missing entirely) - VatBreakdown rows are now flex rows with a spacer instead of plain text, so every € amount right-aligns to the same edge regardless of how many digits the rate itself has (was visibly staggered with mixed 7%/19% rates) - Cart's manual discount-code field only renders when Payload actually has at least one active code right now (lib/discountServer.ts's new hasActiveDiscountCode()) — no point showing an open field that could never validate. An already-applied code (e.g. from an older session) still always shows its own result row regardless. - Checkout's "1. Rechnungsadresse" no longer offers a Packstation option — a Packstation isn't a valid billing address for an invoice. Only a plain street address now; Packstation is only offered on the separate, optional "Abweichende Lieferadresse" section, which already had its own address/Packstation toggle. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -70,7 +70,6 @@ export function CheckoutContent({
|
||||
const [shippingMethodId, setShippingMethodId] = useState<number | null>(shippingMethods[0]?.id ?? null);
|
||||
const [paymentMethodId, setPaymentMethodId] = useState<number | null>(paymentMethods[0]?.id ?? null);
|
||||
const [versandOpen, setVersandOpen] = useState(false);
|
||||
const [deliveryMethod, setDeliveryMethod] = useState<"address" | "packstation">(savedProfile?.deliveryMethod ?? "address");
|
||||
const [purchaseError, setPurchaseError] = useState<string | null>(null);
|
||||
const [purchasing, setPurchasing] = useState(false);
|
||||
const [showLogin, setShowLogin] = useState(false);
|
||||
@@ -90,9 +89,10 @@ export function CheckoutContent({
|
||||
const [firstName, setFirstName] = useState(savedProfile?.firstName ?? "");
|
||||
const [lastName, setLastName] = useState(savedProfile?.lastName ?? "");
|
||||
const [email, setEmail] = useState(savedProfile?.email ?? customerEmail ?? "");
|
||||
// Always a plain street address — a Packstation isn't a valid Rechnungs-
|
||||
// adresse (an invoice needs a real postal address). Packstation is only
|
||||
// ever offered on the separate, optional shipping-address override below.
|
||||
const [street, setStreet] = useState(savedProfile?.street ?? "");
|
||||
const [packstationNumber, setPackstationNumber] = useState(savedProfile?.packstationNumber ?? "");
|
||||
const [postNumber, setPostNumber] = useState(savedProfile?.postNumber ?? "");
|
||||
const [zip, setZip] = useState(savedProfile?.zip ?? "");
|
||||
const [city, setCity] = useState(savedProfile?.city ?? "");
|
||||
const [country, setCountry] = useState(savedProfile?.country ?? "Deutschland");
|
||||
@@ -130,10 +130,7 @@ export function CheckoutContent({
|
||||
if (draft.firstName) setFirstName(draft.firstName);
|
||||
if (draft.lastName) setLastName(draft.lastName);
|
||||
if (draft.email) setEmail(draft.email);
|
||||
if (draft.deliveryMethod) setDeliveryMethod(draft.deliveryMethod);
|
||||
if (draft.street) setStreet(draft.street);
|
||||
if (draft.packstationNumber) setPackstationNumber(draft.packstationNumber);
|
||||
if (draft.postNumber) setPostNumber(draft.postNumber);
|
||||
if (draft.zip) setZip(draft.zip);
|
||||
if (draft.city) setCity(draft.city);
|
||||
if (draft.country) setCountry(draft.country);
|
||||
@@ -161,10 +158,7 @@ export function CheckoutContent({
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
deliveryMethod,
|
||||
street,
|
||||
packstationNumber,
|
||||
postNumber,
|
||||
zip,
|
||||
city,
|
||||
country,
|
||||
@@ -187,10 +181,7 @@ export function CheckoutContent({
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
deliveryMethod,
|
||||
street,
|
||||
packstationNumber,
|
||||
postNumber,
|
||||
zip,
|
||||
city,
|
||||
country,
|
||||
@@ -317,10 +308,10 @@ export function CheckoutContent({
|
||||
// one address-card field that stays uncontrolled/unpersisted (see
|
||||
// lib/checkoutDraft.ts's own comment on why).
|
||||
password: customerEmail ? undefined : String(form.get("password") ?? ""),
|
||||
deliveryMethod,
|
||||
// Rechnungsadresse is always a plain street address — see the
|
||||
// street state's own comment on why Packstation isn't offered here.
|
||||
deliveryMethod: "address" as const,
|
||||
street: street || undefined,
|
||||
packstationNumber: packstationNumber || undefined,
|
||||
postNumber: postNumber || undefined,
|
||||
zip,
|
||||
city,
|
||||
country,
|
||||
@@ -547,79 +538,21 @@ export function CheckoutContent({
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{/* Segmented control, same sm:w-[calc(50%-0.5rem)] half-row
|
||||
width as the field(s) below it — Lieferadresse keeps
|
||||
Straße und Hausnummer, Packstation swaps it out for
|
||||
Packstationnummer + Postnummer (DHL's two Packstation
|
||||
identifiers; there's no house number to give). */}
|
||||
<div className="w-full sm:w-[calc(50%-0.5rem)] flex flex-col gap-2 items-start">
|
||||
<span className="text-label text-text-muted">Lieferart</span>
|
||||
<div className="flex w-full rounded-sm border border-border overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDeliveryMethod("address")}
|
||||
aria-pressed={deliveryMethod === "address"}
|
||||
className={`flex-1 py-3 text-body-sm font-bold transition-colors ${
|
||||
deliveryMethod === "address"
|
||||
? "bg-brand text-text-primary"
|
||||
: "text-text-muted hover:text-text-primary"
|
||||
}`}
|
||||
>
|
||||
Lieferadresse
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDeliveryMethod("packstation")}
|
||||
aria-pressed={deliveryMethod === "packstation"}
|
||||
className={`flex-1 py-3 text-body-sm font-bold border-l border-border transition-colors ${
|
||||
deliveryMethod === "packstation"
|
||||
? "bg-brand text-text-primary"
|
||||
: "text-text-muted hover:text-text-primary"
|
||||
}`}
|
||||
>
|
||||
Packstation
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{deliveryMethod === "address" ? (
|
||||
<FormField
|
||||
label="Straße und Hausnummer"
|
||||
name="street"
|
||||
type="text"
|
||||
value={street}
|
||||
onChange={(e) => setStreet(e.target.value)}
|
||||
placeholder="Musterstraße 1"
|
||||
autoComplete="street-address"
|
||||
required
|
||||
wrapperClassName="w-full sm:w-[calc(50%-0.5rem)] sm:flex-none min-w-0"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-col sm:flex-row gap-4 w-full">
|
||||
<FormField
|
||||
label="Packstationnummer"
|
||||
name="packstationNumber"
|
||||
type="text"
|
||||
value={packstationNumber}
|
||||
onChange={(e) => setPackstationNumber(e.target.value)}
|
||||
inputMode="numeric"
|
||||
placeholder="123"
|
||||
autoComplete="off"
|
||||
required
|
||||
/>
|
||||
<FormField
|
||||
label="Postnummer"
|
||||
name="postNumber"
|
||||
type="text"
|
||||
value={postNumber}
|
||||
onChange={(e) => setPostNumber(e.target.value)}
|
||||
inputMode="numeric"
|
||||
placeholder="1234567"
|
||||
autoComplete="off"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* Always a plain street address — Packstation isn't a valid
|
||||
Rechnungsadresse (an invoice needs a real postal address).
|
||||
Packstation is only ever offered below, in the optional
|
||||
"Abweichende Lieferadresse" section's own Lieferart toggle. */}
|
||||
<FormField
|
||||
label="Straße und Hausnummer"
|
||||
name="street"
|
||||
type="text"
|
||||
value={street}
|
||||
onChange={(e) => setStreet(e.target.value)}
|
||||
placeholder="Musterstraße 1"
|
||||
autoComplete="street-address"
|
||||
required
|
||||
wrapperClassName="w-full sm:w-[calc(50%-0.5rem)] sm:flex-none min-w-0"
|
||||
/>
|
||||
<div className="flex flex-col sm:flex-row gap-4 w-full">
|
||||
<FormField label="PLZ" name="zip" type="text" value={zip} onChange={(e) => setZip(e.target.value)} placeholder="10115" autoComplete="postal-code" required />
|
||||
<FormField label="Ort" name="city" type="text" value={city} onChange={(e) => setCity(e.target.value)} placeholder="Berlin" autoComplete="address-level2" required />
|
||||
|
||||
Reference in New Issue
Block a user