feat: add /bestellbestaetigung order confirmation page
Matches the Figma mockup (checkmark hero, order summary card,
delivery-status panel, testimonial band) with the checkout's 4-step
bar inserted (all steps done) and the "Bis dahin: Lass dich
inspirieren" block omitted, per request.
Extracted the step bar into a shared CheckoutSteps component so
/checkout and /bestellbestaetigung don't duplicate it. The actually-
selected shipping cost and payment method are captured into a
sessionStorage snapshot by /checkout's "Jetzt kaufen" click (there's
no real order backend, so this click is what "placing the order"
means here) and read back on the confirmation page — not just
defaulted to the first active method of each, so the receipt matches
what the shopper actually picked. Also tightened the checkout
newsletter-consent copy ("Wenn du zustimmst" instead of "Wenn du
oben zustimmst").
This commit is contained in:
@@ -3,44 +3,15 @@
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useCart } from "../../lib/cart";
|
||||
import { useCart, clearCart } from "../../lib/cart";
|
||||
import { useProducts } from "../../lib/products";
|
||||
import { formatPrice } from "../../lib/format";
|
||||
import { Reveal } from "../../components/Reveal";
|
||||
import { VersandModal } from "../../components/VersandModal";
|
||||
import { CheckoutSteps } from "../../components/CheckoutSteps";
|
||||
import { ORDER_KEY, generateOrderNumber, type OrderSnapshot } from "../../lib/order";
|
||||
import type { ShippingMethod, PaymentMethod, TrustBadge } from "../../lib/payload";
|
||||
|
||||
const steps = [
|
||||
{ label: "Warenkorb", state: "done" as const },
|
||||
{ label: "Adresse", state: "active" as const },
|
||||
{ label: "Zahlung", state: "upcoming" as const },
|
||||
{ label: "Abschluss", state: "upcoming" as const },
|
||||
];
|
||||
|
||||
function StepCircle({ state, number }: { state: "done" | "active" | "upcoming"; number: number }) {
|
||||
if (state === "done") {
|
||||
return (
|
||||
<div className="flex size-9 items-center justify-center rounded-full bg-brand text-text-primary">
|
||||
<svg viewBox="0 0 20 20" className="size-4" fill="none" aria-hidden="true">
|
||||
<path d="m4 10 4 4 8-8" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (state === "active") {
|
||||
return (
|
||||
<div className="flex size-9 items-center justify-center rounded-full bg-brand font-bold text-body-sm text-bg-white">
|
||||
{number}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex size-9 items-center justify-center rounded-full border border-border font-bold text-body-sm text-text-muted">
|
||||
{number}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FormField({
|
||||
label,
|
||||
wrapperClassName = "flex-1 min-w-0",
|
||||
@@ -86,6 +57,28 @@ export function CheckoutContent({
|
||||
subtotal >= selectedShipping.freeShippingThreshold;
|
||||
const shipping = items.length === 0 || freeShipping ? 0 : selectedShipping?.price ?? 0;
|
||||
const total = subtotal + shipping;
|
||||
const selectedPayment = paymentMethods.find((m) => m.id === paymentMethodId) ?? null;
|
||||
|
||||
// Captures the actually-selected shipping/payment method as the order
|
||||
// snapshot /bestellbestaetigung reads — see lib/order.ts's own comment,
|
||||
// there's no real order backend so this click IS what "placing the
|
||||
// order" means here.
|
||||
function handlePurchase() {
|
||||
const snapshot: OrderSnapshot = {
|
||||
items: cart,
|
||||
orderNumber: generateOrderNumber(),
|
||||
orderDateIso: new Date().toISOString(),
|
||||
shippingCost: shipping,
|
||||
paymentMethodTitle: selectedPayment?.title ?? "—",
|
||||
};
|
||||
try {
|
||||
window.sessionStorage.setItem(ORDER_KEY, JSON.stringify(snapshot));
|
||||
} catch {
|
||||
// sessionStorage unavailable (private browsing etc.) — the
|
||||
// confirmation page falls back to its own empty state.
|
||||
}
|
||||
clearCart();
|
||||
}
|
||||
|
||||
if (!productsLoading && items.length === 0) {
|
||||
return (
|
||||
@@ -121,25 +114,7 @@ export function CheckoutContent({
|
||||
<span className="text-text-primary">Checkout</span>
|
||||
</p>
|
||||
|
||||
<div className="flex items-center w-full max-w-[50rem]">
|
||||
{steps.map((step, i) => (
|
||||
<div key={step.label} className="flex items-center flex-1 last:flex-initial">
|
||||
<div className="flex flex-col items-center gap-0 shrink-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<StepCircle state={step.state} number={i + 1} />
|
||||
<span
|
||||
className={`hidden sm:block text-body-sm whitespace-nowrap ${
|
||||
step.state === "upcoming" ? "text-text-muted" : "font-semibold text-text-primary"
|
||||
}`}
|
||||
>
|
||||
{step.label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{i < steps.length - 1 && <div className="h-px bg-border flex-1 mx-4 min-w-4" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<CheckoutSteps current={2} />
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<p
|
||||
@@ -322,6 +297,7 @@ export function CheckoutContent({
|
||||
|
||||
<Link
|
||||
href="/bestellbestaetigung"
|
||||
onClick={handlePurchase}
|
||||
className="w-full flex items-center justify-center py-4 rounded-sm bg-brand hover:bg-brand-hover active:scale-[0.97] transition-all font-bold text-body text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2 focus-visible:ring-offset-bg-base"
|
||||
>
|
||||
Jetzt kaufen (zahlungspflichtig)
|
||||
@@ -433,7 +409,7 @@ export function CheckoutContent({
|
||||
shouldn't promise emails as if opting in were already
|
||||
decided either. */}
|
||||
<p className="text-body-sm leading-[1.45]">
|
||||
Wenn du oben zustimmst, bekommst du nach deiner Bestellung regelmäßig Impulse & Tipps per E-Mail.
|
||||
Wenn du zustimmst, bekommst du nach deiner Bestellung regelmäßig Impulse & Tipps per E-Mail.
|
||||
</p>
|
||||
<p className="text-body-sm leading-[1.45]">
|
||||
Für mehr Klarheit, Fokus und Struktur – jede Woche.
|
||||
|
||||
Reference in New Issue
Block a user