From bf6c5d079a7f5d2466c42c386ef8c01d5bcb023c Mon Sep 17 00:00:00 2001
From: Marco
Date: Fri, 31 Jul 2026 22:29:36 +0000
Subject: [PATCH] Fix order-filter dropdowns, restructure Konto identity line,
drop blog reset link
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- CustomSelect.tsx: mousedown on an option blurred the trigger button
before the click landed, unmounting the list before anything could be
selected — add preventDefault on the list's mousedown to stop that.
- KontoShell no longer renders "Eingeloggt als ..." above each page's
content; each page renders its own title then AccountIdentity right
below it, and ProfileForm's now-duplicate email/Kundennummer line is
removed.
- Blog category filter: drop the separate "Zurücksetzen" link — clicking
an active category badge again already deactivates it.
- Werkzeuge cards: more vertical gap between stacked cards below sm:.
- Legal pages' "Stand: ..." line is now derived from the LegalPages doc's
own updatedAt instead of a hand-typed string.
Co-Authored-By: Claude Sonnet 5
Claude-Session: https://claude.ai/code/session_018unaXmuzVA8ct1b6WoyP1U
---
README.md | 23 +++++++++++++++-----
app/agb/page.tsx | 3 ++-
app/blog/page.tsx | 8 -------
app/components/CustomSelect.tsx | 5 +++++
app/components/Tools.tsx | 2 +-
app/datenschutz/page.tsx | 3 ++-
app/konto/bestellungen/page.tsx | 12 +++++++----
app/konto/components/AccountIdentity.tsx | 7 ++++++
app/konto/components/KontoShell.tsx | 24 ++++++---------------
app/konto/merkliste/page.tsx | 12 +++++++----
app/konto/profil/components/ProfileForm.tsx | 7 ------
app/konto/profil/page.tsx | 9 +++++++-
app/lib/format.ts | 9 ++++++++
app/lib/payload.ts | 3 +++
app/widerruf/page.tsx | 3 ++-
15 files changed, 80 insertions(+), 50 deletions(-)
create mode 100644 app/konto/components/AccountIdentity.tsx
diff --git a/README.md b/README.md
index 2ad8da8..6bb7233 100644
--- a/README.md
+++ b/README.md
@@ -165,7 +165,7 @@ explains what does have access instead.
| `discount-codes` | Cart discount input (`/cart`, display-only on `/checkout`) | `code`, `type` (`percent`/`fixed`), `value`, `validFrom`/`validUntil`, `minOrderValue`, `maxRedemptions`, `redemptionCount` (server-incremented only), `active`. **Not public-read** — see Discount codes section below |
| `posts` | `/blog`, `/blog/[slug]` | `title`, `slug`, `categories` (`hasMany` relation to `categories` — a post can have several, joined with `", "` wherever rendered), `excerpt`, `thumbnail`, `content` (richText, supports custom Lexical blocks — Bild/Bildergalerie/Video/Zitat — rendered via `app/components/RichText.tsx`'s `@payloadcms/richtext-lexical/react` renderer with custom `JSXConverters`), `readTime` (auto-calculated on save from word count), `featured` (shown as the `/blog` hero post; most-recently-published wins if several are marked), `publishedAt`, `quoteLabel` (label + icon + underline shown next to every blockquote in `content`, default `"Merke dir:"` — leave empty to hide that framing, the blockquote text itself still renders), `relatedProduct` (optional relation to `products`, powers the "Passend dazu" card at the end of the post — leave empty to hide that card, or empty if the linked product has no `detailHref`), `seoTitle`/`seoDescription`/`seoImage` (each falls back to `title`/`excerpt`/`thumbnail` when empty, consumed by `/blog/[slug]`'s `generateMetadata()`) |
| `categories` | Blog post categorization | `name`, `slug` (unique per tenant, not globally) |
-| `legal-pages` | `/impressum`, `/datenschutz`, `/agb`, `/widerruf` | `type` (`impressum`/`datenschutz`/`agb`/`widerruf`, one doc per type per tenant), `title`, `content` (richText), `attachment` (optional file, e.g. the Muster-Widerrufsformular PDF) |
+| `legal-pages` | `/impressum`, `/datenschutz`, `/agb`, `/widerruf` | `type` (`impressum`/`datenschutz`/`agb`/`widerruf`, one doc per type per tenant), `title`, `content` (richText), `attachment` (optional file, e.g. the Muster-Widerrufsformular PDF). Each page's "Stand: …" line is derived from this doc's own `updatedAt` (`formatMonthYear()` in `app/lib/format.ts`), not a hand-typed string — it updates automatically the moment an admin edits the content, no separate field to remember |
| `trust-badges` | Horizontal "Schneller Versand / Versandkostenfrei / Mit Liebe verpackt" row — shown on `/shop`, `/cart`, `/checkout`, `/widerruf`, `/agb`, 404 | `title`, `description` (supports `{{lieferzeit}}`/`{{kostenfreiab}}` placeholders, resolved by the frontend from `shipping-settings`/`shipping-methods` at render time — not by Payload itself), `icon`, `sortOrder` |
| `cart-trust-badges` | Sidebar bullets on `/cart` (title only) and `/checkout` (title + description) — deliberately a separate collection from `trust-badges` so the two pages can't drift into showing different claims | `title`, `description`, `icon`, `sortOrder` |
| `shipping-methods` | `/checkout` shipping selection | `title` (no day-range in the title text — that lives in `shipping-settings` now, keeping both in one title used to drift), `description`, `price`, `freeShippingThreshold` (per-method, optional — leave empty for a method that should never be free, e.g. Express), `active` (inactive methods are hidden, not shown disabled), `sortOrder` |
@@ -1269,16 +1269,29 @@ whatever page sent the visitor to log in instead of always
`orders/[orderNumber]` (PATCH — cancel/return-request, see "Order
cancellation & returns" below), and `orders/[orderNumber]/invoice`
(GET — invoice PDF download, see "Invoice PDFs" below).
+- **`app/konto/components/KontoShell.tsx`** is the shared shell for every
+ logged-in `/konto/*` page (bestellungen, merkliste, profil) —
+ `AccountNav.tsx` (persistent sidebar on `sm:`+, a horizontal tab bar
+ below it) plus the ``/`
-
Stand: Juli 2026
+ {page &&
Stand: {formatMonthYear(page.updatedAt)}
}
{/* MobileSectionTOC — below lg: only, see SectionTOC.tsx's own
diff --git a/app/blog/page.tsx b/app/blog/page.tsx
index 1475650..d090b06 100644
--- a/app/blog/page.tsx
+++ b/app/blog/page.tsx
@@ -96,14 +96,6 @@ export default async function BlogOverviewPage({
);
})}
- {activeCategories.length > 0 && (
-
- Zurücksetzen
-
- )}
)}
diff --git a/app/components/CustomSelect.tsx b/app/components/CustomSelect.tsx
index 482d67c..0c64da0 100644
--- a/app/components/CustomSelect.tsx
+++ b/app/components/CustomSelect.tsx
@@ -131,6 +131,11 @@ export function CustomSelect({
ref={listRef}
role="listbox"
aria-label={label}
+ // Without this, a mousedown on an
blurs the trigger button
+ // first (li isn't focusable) — the resulting onBlur closes and
+ // unmounts this list before the click event ever fires, so
+ // nothing is ever selectable by mouse/touch.
+ onMouseDown={(e) => e.preventDefault()}
className="absolute z-20 mt-1 w-full sm:min-w-[12rem] max-h-64 overflow-y-auto bg-bg-base border border-border rounded-sm shadow-lg py-1"
>
{allOptions.map((o, i) => (
diff --git a/app/components/Tools.tsx b/app/components/Tools.tsx
index de9583d..c6f8024 100644
--- a/app/components/Tools.tsx
+++ b/app/components/Tools.tsx
@@ -35,7 +35,7 @@ export async function Tools() {
arrives where the fluid floor also sits), stacked below sm;
internal icon+text layout stays horizontal at every size, only
the outer span changes */}
-
+
{tools.map((tool) => (
Datenschutzerklärung
-
Stand: Juli 2026
+ {page &&
Stand: {formatMonthYear(page.updatedAt)}
}
{/* MobileSectionTOC — below lg: only, see SectionTOC.tsx's own
diff --git a/app/konto/bestellungen/page.tsx b/app/konto/bestellungen/page.tsx
index 45f1ce1..e0a3a7b 100644
--- a/app/konto/bestellungen/page.tsx
+++ b/app/konto/bestellungen/page.tsx
@@ -14,6 +14,7 @@ import { getOrderFilterEnabled } from "../../lib/payload";
import { OrderStatusBadge } from "../components/OrderStatusBadge";
import { PaymentStatusBadge } from "../components/PaymentStatusBadge";
import { KontoShell } from "../components/KontoShell";
+import { AccountIdentity } from "../components/AccountIdentity";
import { OrderFilters } from "./components/OrderFilters";
// robots: noindex — account area, same reasoning as /checkout.
@@ -59,11 +60,14 @@ export default async function KontoBestellungenPage({
]);
return (
-
+
-
+ Eingeloggt als {email} (Kundennummer {customerNumber})
+
+ );
+}
diff --git a/app/konto/components/KontoShell.tsx b/app/konto/components/KontoShell.tsx
index 093163f..63785ea 100644
--- a/app/konto/components/KontoShell.tsx
+++ b/app/konto/components/KontoShell.tsx
@@ -12,28 +12,18 @@ import { AccountNav } from "./AccountNav";
// page-specific logic each page still needs (e.g. merkliste's return-URL
// login redirect, profil's second profile-fetch redirect).
//
-// `customer` is rendered here, once, so "Eingeloggt als …" looks and
-// appears identically on every /konto/* page — it used to be hand-copied
-// onto individual pages (bestellungen had it, merkliste/profil didn't),
-// which read as inconsistent rather than a deliberate per-page choice.
-export async function KontoShell({
- children,
- customer,
-}: {
- children: ReactNode;
- customer: { email: string; customerNumber: string };
-}) {
+// "Eingeloggt als …" is NOT rendered here (even though `customer` used to
+// be the only prop this shell needed) — it belongs directly under each
+// page's own title so it reads as "page title, then who's logged in", not
+// above the title before the page has even introduced itself. See
+// AccountIdentity.tsx, rendered by each page right after its own heading.
+export async function KontoShell({ children }: { children: ReactNode }) {
return (
<>
-
-
- Eingeloggt als {customer.email} (Kundennummer {customer.customerNumber})
-
- {children}
-
+
{children}
diff --git a/app/konto/merkliste/page.tsx b/app/konto/merkliste/page.tsx
index c30f4cf..4a3c4c3 100644
--- a/app/konto/merkliste/page.tsx
+++ b/app/konto/merkliste/page.tsx
@@ -4,6 +4,7 @@ import { Reveal } from "../../components/Reveal";
import { getSessionCustomer, getWishlist } from "../../lib/customerAuth";
import { getProductsByIds, getWishlistEnabled, getDefaultTaxRatePercent, getKleinunternehmer } from "../../lib/payload";
import { KontoShell } from "../components/KontoShell";
+import { AccountIdentity } from "../components/AccountIdentity";
import { MerklisteGrid } from "./components/MerklisteGrid";
// robots: noindex — account area, same reasoning as /konto/bestellungen.
@@ -40,11 +41,14 @@ export default async function KontoMerklistePage() {
const initialProducts = await getProductsByIds(wishlistItems.map((i) => i.productId));
return (
-
+
-