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:
1 parent
54919d82ed
commit
0b2196460a
16 files changed
+445
-84
No files matched your search
+24
-4
@@ -116,11 +116,25 @@ export default async function ShopPage(props: PageProps<"/shop">) {
|
||||
|
||||
<div className="mt-6 flex flex-wrap items-center gap-4">
|
||||
{/* GET-Formular statt Client-Component: funktioniert ohne JavaScript,
|
||||
die Auswahl wird per Seiten-Neuladen als Query-Parameter uebernommen. */}
|
||||
<form action="/shop" method="GET" className="flex items-center gap-2 text-sm text-ink/70">
|
||||
{query ? <input type="hidden" name="q" value={query} /> : null}
|
||||
die Auswahl wird per Seiten-Neuladen als Query-Parameter uebernommen.
|
||||
Die Suche ist bewusst Teil DIESES Formulars (statt nur der kleinen
|
||||
Kopfzeilen-Suche): so werden Kategorie/Verfuegbarkeit-Filter beim
|
||||
Aendern der Suche innerhalb der Shop-Seite nicht verworfen — anders
|
||||
als bei der Kopfzeilen-Suche, die immer neu auf /shop startet. */}
|
||||
<form action="/shop" method="GET" className="flex flex-wrap items-center gap-2 text-sm text-ink/70">
|
||||
{category ? <input type="hidden" name="kategorie" value={category} /> : null}
|
||||
{onlyAvailable ? <input type="hidden" name="verfuegbar" value="1" /> : null}
|
||||
<label htmlFor="shop-search" className="sr-only">
|
||||
{t.shop.searchInputLabel}
|
||||
</label>
|
||||
<input
|
||||
id="shop-search"
|
||||
type="search"
|
||||
name="q"
|
||||
defaultValue={query ?? ""}
|
||||
placeholder={t.nav.searchPlaceholder}
|
||||
className="border border-ink/20 bg-background px-3 py-2 focus:outline-none focus:border-forest"
|
||||
/>
|
||||
<label htmlFor="sortierung">{t.shop.sortLabel}</label>
|
||||
<select
|
||||
id="sortierung"
|
||||
@@ -157,7 +171,13 @@ export default async function ShopPage(props: PageProps<"/shop">) {
|
||||
|
||||
{query ? (
|
||||
<p className="mt-6 text-sm text-ink/70">
|
||||
{t.shop.searchResults(query, products.length)}
|
||||
{t.shop.searchResults(query, products.length)}{" "}
|
||||
<Link
|
||||
href={buildHref({ kategorie: category, sortierung: sort, verfuegbar: onlyAvailable ? "1" : undefined })}
|
||||
className="underline hover:text-forest"
|
||||
>
|
||||
{t.shop.clearSearch}
|
||||
</Link>
|
||||
</p>
|
||||
) : null}
|
||||
<h2 className="sr-only">{t.shop.productsHeading}</h2>
|
||||
|
||||
Reference in new issue
Block a user