feat: Kaufjourney-Optimierungen fuer Kund:innen (Shop, Produktseite, Warenkorb, Checkout)

Konkrete Reibungspunkte entlang der Kauf-Journey behoben:
- Shop-Suche als sichtbares, editierbares Feld im Filter-Formular statt nur
  in der kleinen Kopfzeilen-Suche — Suche/Kategorie/Sortierung/Verfuegbarkeit
  lassen sich jetzt in beide Richtungen kombinieren, ohne sich gegenseitig
  zurueckzusetzen; plus "Suche zuruecksetzen"-Link.
- Versandkosten- und Lieferzeit-Hinweis direkt auf der Produktdetailseite
  (bisher erst im Warenkorb/Checkout sichtbar).
- Produktgalerie per Touch-Wisch bedienbar, nicht nur ueber die
  Vorschau-Kacheln (mobile Kaufentscheidung).
- Immer sichtbare mobile Kaufleiste auf der Produktseite, damit der
  Warenkorb-Button auf langen Seiten (Bewertungen, verwandte Produkte) nicht
  wieder erst erscrollt werden muss.
- Live-Lagerbestand (Ausverkauft/Nur noch N) jetzt auch im Cart-Drawer und
  auf der Warenkorb-Seite sichtbar, nicht nur auf der Produktkarte — neuer
  Endpunkt /api/shop/cart-stock + useCartStock-Hook.
- Checkout- und Adressformulare: autoComplete/inputMode/type auf allen
  Feldern (given-name, tel, postal-code, ...) fuer Mobil-Autofill und
  passende Bildschirmtastaturen.
- Bestellpositionen in "Meine Bestellungen" und der Bestellverfolgung
  verlinken jetzt zur Produktseite (samt Bewertungs-Anker) — bisher war die
  Bewertungsfunktion nach dem Kauf nur durch manuelles Zurueeknavigieren
  auffindbar.

Neue Tests fuer die Lagerbestand-Hinweise im Warenkorb; tsc/vitest/build
bleiben gruen (142 Tests).
This commit is contained in:
Nick Szaunig committed 2026-09-05 18:02:38 +02:00
1 parent 54919d82ed
commit 0b2196460a
16 files changed
+445 -84

No files matched your search

+26 -6
View File
@@ -1,4 +1,5 @@
import type { Metadata } from "next";
import type { HTMLAttributes } from "react";
import { redirect } from "next/navigation";
import { Container, SectionHeading, Button } from "@/components/ui";
import { BackLink } from "@/components/back-link";
@@ -84,15 +85,27 @@ export default async function KontoAdressenPage(props: PageProps<"/konto/adresse
<div className="mt-14 border-t border-ink/10 pt-10">
<h2 className="text-lg text-forest mb-6">{t.addressesPage.addNewHeading}</h2>
<form action={addAddressAction} className="space-y-4 max-w-md">
<Field name="label" label={t.addressesPage.labelField} defaultValue={t.addressesPage.labelDefaultValue} required />
<Field
name="label"
label={t.addressesPage.labelField}
defaultValue={t.addressesPage.labelDefaultValue}
autoComplete="off"
required
/>
<div className="grid grid-cols-2 gap-4">
<Field name="firstName" label={t.account.firstNameLabel} required />
<Field name="lastName" label={t.account.lastNameLabel} required />
<Field name="firstName" label={t.account.firstNameLabel} autoComplete="given-name" required />
<Field name="lastName" label={t.account.lastNameLabel} autoComplete="family-name" required />
</div>
<Field name="street" label={t.checkoutPage.streetLabel} required />
<Field name="street" label={t.checkoutPage.streetLabel} autoComplete="street-address" required />
<div className="grid grid-cols-2 gap-4">
<Field name="zip" label={t.checkoutPage.zipLabel} required />
<Field name="city" label={t.checkoutPage.cityLabel} required />
<Field
name="zip"
label={t.checkoutPage.zipLabel}
autoComplete="postal-code"
inputMode="numeric"
required
/>
<Field name="city" label={t.checkoutPage.cityLabel} autoComplete="address-level2" required />
</div>
<label className="flex items-center gap-2 text-sm text-ink/70">
<input type="checkbox" name="isDefault" />
@@ -110,11 +123,16 @@ function Field({
name,
label,
defaultValue,
autoComplete,
inputMode,
required = false,
}: {
name: string;
label: string;
defaultValue?: string;
/** Browser-/Mobil-Autofill fuer Adressfelder — dieselben Tokens wie im Checkout-Formular. */
autoComplete?: string;
inputMode?: HTMLAttributes<HTMLInputElement>["inputMode"];
required?: boolean;
}) {
return (
@@ -123,6 +141,8 @@ function Field({
<input
name={name}
defaultValue={defaultValue}
autoComplete={autoComplete}
inputMode={inputMode}
required={required}
className="mt-1 w-full border border-ink/20 bg-background px-4 py-3 focus:outline-none focus:border-forest"
/>