From fc8a1d4201984e47587025f90b96552fb647534f Mon Sep 17 00:00:00 2001
From: Marco
Date: Thu, 30 Jul 2026 22:59:23 +0000
Subject: [PATCH] Add standalone registration page; fix 2 merkliste bugs; honor
login redirect
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
New /konto/registrieren (+ RegisterForm.tsx) — until now an account
could only be created inline during checkout, which stopped making
sense the moment the Wishlist gave accounts a non-purchase use case.
LoginForm now also honors ?redirect= (previously ignored, always
landing on /konto/bestellungen regardless of where the login was
triggered from, e.g. WishlistButton's login-gate) and links to the
new registration page instead of implying "only via checkout".
Fixed: removing an item from the wishlist didn't disappear from
/konto/merkliste — that page was a Server Component with a fixed
render, not reactive to the client-side toggle. New MerklisteGrid.tsx
(Client Component) uses useWishlist()'s live state as the actual
source of truth for which products are still shown.
Fixed: AddToCartInlineButton looked broken on that same page —
`className="w-full"` was passed to it, but its `className` prop
*replaces* the whole default styling (`?? default`, not a merge),
throwing away all the button's actual styling. Its default is
already `w-full`; no override needed.
Co-Authored-By: Claude Sonnet 5
---
app/konto/login/components/LoginForm.tsx | 23 +++-
app/konto/login/page.tsx | 8 +-
.../merkliste/components/MerklisteGrid.tsx | 88 ++++++++++++
app/konto/merkliste/page.tsx | 71 +---------
.../registrieren/components/RegisterForm.tsx | 127 ++++++++++++++++++
app/konto/registrieren/page.tsx | 29 ++++
6 files changed, 271 insertions(+), 75 deletions(-)
create mode 100644 app/konto/merkliste/components/MerklisteGrid.tsx
create mode 100644 app/konto/registrieren/components/RegisterForm.tsx
create mode 100644 app/konto/registrieren/page.tsx
diff --git a/app/konto/login/components/LoginForm.tsx b/app/konto/login/components/LoginForm.tsx
index 96d30fc..53b692b 100644
--- a/app/konto/login/components/LoginForm.tsx
+++ b/app/konto/login/components/LoginForm.tsx
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
-import { useRouter } from "next/navigation";
+import { useRouter, useSearchParams } from "next/navigation";
import Link from "next/link";
import { Reveal } from "../../../components/Reveal";
import { mergeServerCartIntoLocal } from "../../../lib/cart";
@@ -9,6 +9,12 @@ import { dispatchAuthChanged } from "../../../lib/auth";
export function LoginForm() {
const router = useRouter();
+ const searchParams = useSearchParams();
+ // WishlistButton (and anything else login-gated) sends the shopper back
+ // here with ?redirect=, e.g. a product page they
+ // wanted to heart — previously ignored, always landing on
+ // /konto/bestellungen regardless of where the login was triggered from.
+ const redirectTo = searchParams.get("redirect") || "/konto/bestellungen";
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState(null);
@@ -32,7 +38,7 @@ export function LoginForm() {
}
await mergeServerCartIntoLocal();
dispatchAuthChanged();
- router.push("/konto/bestellungen");
+ router.push(redirectTo);
router.refresh();
} catch {
setError("Login ist gerade nicht möglich.");
@@ -81,11 +87,14 @@ export function LoginForm() {
Passwort vergessen?
- Noch kein Konto? Einfach beim{" "}
-
- nächsten Einkauf
- {" "}
- anlegen.
+ Noch kein Konto?{" "}
+
+ Jetzt anlegen
+
+ .
);
diff --git a/app/konto/login/page.tsx b/app/konto/login/page.tsx
index 0162be4..45f8a32 100644
--- a/app/konto/login/page.tsx
+++ b/app/konto/login/page.tsx
@@ -1,4 +1,5 @@
import type { Metadata } from "next";
+import { Suspense } from "react";
import { LoginForm } from "./components/LoginForm";
import { Footer } from "../../components/Footer";
@@ -16,7 +17,12 @@ export default function KontoLoginPage() {
return (
<>
-
+ {/* Suspense required — LoginForm uses useSearchParams() (?redirect=,
+ e.g. from WishlistButton's login-gate) which opts any consumer
+ into client-side rendering unless wrapped. */}
+
+
+
>
diff --git a/app/konto/merkliste/components/MerklisteGrid.tsx b/app/konto/merkliste/components/MerklisteGrid.tsx
new file mode 100644
index 0000000..bcb55c2
--- /dev/null
+++ b/app/konto/merkliste/components/MerklisteGrid.tsx
@@ -0,0 +1,88 @@
+"use client";
+
+import Image from "next/image";
+import { RevealGroup, RevealItem } from "../../../components/Reveal";
+import { formatPrice, discountPercent } from "../../../lib/format";
+import { AddToCartInlineButton } from "../../../components/AddToCartInlineButton";
+import { WishlistButton } from "../../../components/WishlistButton";
+import { useWishlist } from "../../../lib/useWishlist";
+import { effectiveTaxRate } from "../../../lib/cartTotals";
+import type { Product } from "../../../lib/payload";
+
+// Client Component so removing an item (WishlistButton toggling it off)
+// disappears from this grid immediately — the server-rendered initial
+// list alone doesn't react to that client-side toggle at all (the parent
+// page.tsx is a Server Component, its render is fixed at request time).
+// useWishlist()'s live `items` is the actual source of truth for which
+// products are still wishlisted; `initialProducts` only supplies the
+// display data (name/image/price) for whatever numericIds are currently
+// wishlisted, since the wishlist itself only stores product ids.
+export function MerklisteGrid({
+ initialProducts,
+ defaultTaxRate,
+ kleinunternehmer,
+}: {
+ initialProducts: Product[];
+ defaultTaxRate: number;
+ kleinunternehmer: boolean;
+}) {
+ const { items } = useWishlist();
+ const wishlistedIds = new Set(items.map((i) => i.productId));
+ // Preserve the wishlist's own order (most-recently-added-first, via
+ // `items`) rather than initialProducts' own order.
+ const productsByNumericId = new Map(initialProducts.map((p) => [p.numericId, p]));
+ const visibleProducts = items.map((i) => productsByNumericId.get(i.productId)).filter((p): p is Product => Boolean(p));
+
+ if (visibleProducts.length === 0) {
+ return
+ {/* No className override — AddToCartInlineButton's `className`
+ prop REPLACES its whole default styling (`?? defaultClass`,
+ not a merge), so passing just "w-full" here previously threw
+ away all the button's actual styling. Its default is
+ already `w-full`. */}
+
+
+
+ );
+ })}
+
+ );
+}
diff --git a/app/konto/merkliste/page.tsx b/app/konto/merkliste/page.tsx
index 8b7de53..d4c50c3 100644
--- a/app/konto/merkliste/page.tsx
+++ b/app/konto/merkliste/page.tsx
@@ -1,15 +1,11 @@
import type { Metadata } from "next";
import { redirect, notFound } from "next/navigation";
import Link from "next/link";
-import Image from "next/image";
-import { Reveal, RevealGroup, RevealItem } from "../../components/Reveal";
+import { Reveal } from "../../components/Reveal";
import { Footer } from "../../components/Footer";
-import { formatPrice, discountPercent } from "../../lib/format";
import { getSessionCustomer, getWishlist } from "../../lib/customerAuth";
import { getProductsByIds, getWishlistEnabled, getDefaultTaxRatePercent, getKleinunternehmer } from "../../lib/payload";
-import { effectiveTaxRate } from "../../lib/cartTotals";
-import { AddToCartInlineButton } from "../../components/AddToCartInlineButton";
-import { WishlistButton } from "../../components/WishlistButton";
+import { MerklisteGrid } from "./components/MerklisteGrid";
// robots: noindex — account area, same reasoning as /konto/bestellungen.
export const metadata: Metadata = {
@@ -37,13 +33,7 @@ export default async function KontoMerklistePage() {
getDefaultTaxRatePercent(),
getKleinunternehmer(),
]);
- const products = await getProductsByIds(wishlistItems.map((i) => i.productId));
- // Preserve the wishlist's own most-recently-added-first order rather
- // than whatever order the products query happens to return in.
- const productsByNumericId = new Map(products.map((p) => [p.numericId, p]));
- const orderedProducts = wishlistItems
- .map((item) => productsByNumericId.get(item.productId))
- .filter((p): p is NonNullable => Boolean(p));
+ const initialProducts = await getProductsByIds(wishlistItems.map((i) => i.productId));
return (
<>
@@ -53,60 +43,7 @@ export default async function KontoMerklistePage() {
Meine Merkliste