From f2c05deaed8e9f12133951768f25ec9f0dee7c02 Mon Sep 17 00:00:00 2001 From: Marco Date: Sun, 19 Jul 2026 21:49:30 +0000 Subject: [PATCH] Add /checkout page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the dead "Zur Kasse gehen" link on /cart. Built against the actual Figma frame (page-checkout, node 4761:465, jCCZyh1DGwdjpv1wGge9To): breadcrumb, 4-step progress stepper (Warenkorb done / Adresse active / Zahlung+Abschluss upcoming), Rechnungsadresse form, Versandart (real radio toggle — switching to Express actually updates the shown total), Zahlungsart (Kreditkarte/PayPal/Überweisung with the real exported payment-network logos), sidebar order summary reusing the same cart/products data as /cart, trust bullets, and a newsletter hint box (divider under the text, not the icon — deliberately different from /cart's own version per this page's own Figma spec). No real payment processing — "Jetzt kaufen" links to /bestellbestaetigung (not yet built) same as the rest of this prototype site's forms (cart, newsletter signup) don't hit a real backend either. Co-Authored-By: Claude Sonnet 5 --- app/checkout/components/CheckoutContent.tsx | 380 ++++++++++++++++++++ app/checkout/page.tsx | 26 ++ public/icon-payment-amex.png | Bin 0 -> 1286 bytes public/icon-payment-bank.png | Bin 0 -> 981 bytes public/icon-payment-mastercard.png | Bin 0 -> 1368 bytes public/icon-payment-paypal.png | Bin 0 -> 2369 bytes public/icon-payment-visa.png | Bin 0 -> 1570 bytes 7 files changed, 406 insertions(+) create mode 100644 app/checkout/components/CheckoutContent.tsx create mode 100644 app/checkout/page.tsx create mode 100644 public/icon-payment-amex.png create mode 100644 public/icon-payment-bank.png create mode 100644 public/icon-payment-mastercard.png create mode 100644 public/icon-payment-paypal.png create mode 100644 public/icon-payment-visa.png diff --git a/app/checkout/components/CheckoutContent.tsx b/app/checkout/components/CheckoutContent.tsx new file mode 100644 index 0000000..5603f34 --- /dev/null +++ b/app/checkout/components/CheckoutContent.tsx @@ -0,0 +1,380 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import Image from "next/image"; +import { useCart } from "../../lib/cart"; +import { useProducts } from "../../lib/products"; +import { formatPrice } from "../../lib/format"; +import { SHIPPING_COST, FREE_SHIPPING_THRESHOLD } from "../../lib/shipping"; +import { Reveal } from "../../components/Reveal"; + +const EXPRESS_SHIPPING_COST = 4.9; + +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 ( +
+ +
+ ); + } + if (state === "active") { + return ( +
+ {number} +
+ ); + } + return ( +
+ {number} +
+ ); +} + +function FormField({ + label, + ...props +}: { label: string } & React.InputHTMLAttributes) { + return ( + + ); +} + +export function CheckoutContent() { + const cart = useCart(); + const products = useProducts(); + const [shippingMethod, setShippingMethod] = useState<"standard" | "express">("standard"); + const [paymentMethod, setPaymentMethod] = useState<"card" | "paypal" | "bank">("card"); + + const productsLoading = products.length === 0 && cart.length > 0; + const items = cart + .map((entry) => ({ entry, product: products.find((p) => p.id === entry.id) })) + .filter((row): row is { entry: typeof cart[number]; product: NonNullable<(typeof row)["product"]> } => Boolean(row.product)); + + const subtotal = items.reduce((sum, { entry, product }) => sum + entry.qty * product.price, 0); + const freeShipping = subtotal >= FREE_SHIPPING_THRESHOLD; + const shipping = items.length === 0 || freeShipping + ? 0 + : shippingMethod === "express" + ? EXPRESS_SHIPPING_COST + : SHIPPING_COST; + const total = subtotal + shipping; + + if (!productsLoading && items.length === 0) { + return ( + +

+ Checkout +

+

+ Dein Warenkorb ist leer — es gibt noch nichts zum Abschließen. +

+ + Zum Shop + +
+ ); + } + + return ( + <> + {/* Header — breadcrumb, stepper, title */} + +

+ Startseite + + Warenkorb + + Checkout +

+ +
+ {steps.map((step, i) => ( +
+
+
+ + + {step.label} + +
+
+ {i < steps.length - 1 &&
} +
+ ))} +
+ +
+

+ Checkout +

+

Fast geschafft! Nur noch ein paar Angaben.

+
+ + +
+ {/* Form column */} +
+ {/* 1. Rechnungsadresse */} + +

+ 1. Rechnungsadresse +

+
+ + +
+ + +
+ + +
+ + +
+ + {/* 2. Versandart */} + +

+ 2. Versandart +

+ {( + [ + { id: "standard", label: "Standardversand (2–4 Werktage)", price: SHIPPING_COST }, + { id: "express", label: "Expressversand (1–2 Werktage)", price: EXPRESS_SHIPPING_COST }, + ] as const + ).map((option) => ( + + ))} +
+ + {/* 3. Zahlungsart */} + +

+ 3. Zahlungsart +

+ + + + + + + + + Jetzt kaufen (zahlungspflichtig) + + +
+ + Sichere SSL-Verschlüsselung +
+
+
+ + {/* Sidebar */} + +
+

+ Deine Bestellung +

+ + {items.map(({ entry, product }) => ( +
+
+ {product.name} +
+
+

{product.name}

+

{entry.qty} × {formatPrice(product.price)}

+
+

{formatPrice(entry.qty * product.price)}

+
+ ))} + +
+ +
+ Zwischensumme + + {formatPrice(subtotal)} +
+ +
+
+ Versand + + + {shipping === 0 ? "Kostenlos" : formatPrice(shipping)} + +
+

innerhalb Deutschlands

+
+ +
+ +
+
+ + Gesamtsumme + + + {formatPrice(total)} +
+

inkl. MwSt.

+
+
+ +
+ {[ + { icon: "/icon-lock.svg", title: "Sichere Zahlung", desc: "Deine Daten sind bei uns sicher und geschützt." }, + { icon: "/icon-trust-return.png", title: "14 Tage Rückgaberecht", desc: "Nicht zufrieden? Sende deine Bestellung innerhalb von 14 Tagen zurück." }, + { icon: "/icon-trust-leaf.png", title: "Nachhaltig verpackt", desc: "Wir achten auf umweltfreundliche Materialien und plastikfreien Versand." }, + ].map((b) => ( +
+ +
+

{b.title}

+

{b.desc}

+
+
+ ))} +
+ + {/* Newsletter hint — divider under the TEXT, not the icon, per + this page's own Figma spec (different from /cart's version, + which puts the divider under the icon — the two aren't meant + to be identical, see project notes on this pattern). */} +
+ +
+

+ Nach deiner Bestellung bekommst du regelmäßig Impulse & Tipps per E-Mail. +

+

+ Für mehr Klarheit, Fokus und Struktur – jede Woche. +

+
+
+
+
+
+
+ +
+ + ); +} diff --git a/app/checkout/page.tsx b/app/checkout/page.tsx new file mode 100644 index 0000000..acff019 --- /dev/null +++ b/app/checkout/page.tsx @@ -0,0 +1,26 @@ +import type { Metadata } from "next"; +import { CheckoutContent } from "./components/CheckoutContent"; +import { TrustRow } from "../components/TrustRow"; +import { Footer } from "../components/Footer"; + +// robots: noindex — transactional page, same reasoning as /cart. +export const metadata: Metadata = { + title: "Checkout", + description: "Schließe deine Bestellung bei einfach produktiv ab.", + robots: { + index: false, + follow: true, + }, +}; + +export default function CheckoutPage() { + return ( + <> +
+ + +
+