Fix order-filter dropdowns, restructure Konto identity line, drop blog reset link
- 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018unaXmuzVA8ct1b6WoyP1U
This commit is contained in:
+2
-1
@@ -9,6 +9,7 @@ import { RichText, extractHeadings } from "../components/RichText";
|
||||
import { LiveRichText } from "../components/LiveRichText";
|
||||
import { SectionTOC, MobileSectionTOC } from "../components/SectionTOC";
|
||||
import { getLegalPage } from "../lib/payload";
|
||||
import { formatMonthYear } from "../lib/format";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "AGB",
|
||||
@@ -36,7 +37,7 @@ export default async function AgbPage() {
|
||||
>
|
||||
Allgemeine Geschäftsbedingungen
|
||||
</p>
|
||||
<p className="text-body text-text-muted">Stand: Juli 2026</p>
|
||||
{page && <p className="text-body text-text-muted">Stand: {formatMonthYear(page.updatedAt)}</p>}
|
||||
</Reveal>
|
||||
|
||||
{/* MobileSectionTOC — below lg: only, see SectionTOC.tsx's own
|
||||
|
||||
@@ -96,14 +96,6 @@ export default async function BlogOverviewPage({
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
{activeCategories.length > 0 && (
|
||||
<Link
|
||||
href="/blog"
|
||||
className="inline-flex items-center px-3 py-1.5 text-body-sm font-semibold text-text-muted underline hover:text-brand transition-colors"
|
||||
>
|
||||
Zurücksetzen
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -131,6 +131,11 @@ export function CustomSelect({
|
||||
ref={listRef}
|
||||
role="listbox"
|
||||
aria-label={label}
|
||||
// Without this, a mousedown on an <li> 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) => (
|
||||
|
||||
@@ -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 */}
|
||||
<RevealGroup className="grid grid-cols-1 sm:grid-cols-12 gap-10 sm:gap-[var(--layout-grid-gap)] px-[var(--layout-padding-x)] w-full">
|
||||
<RevealGroup className="grid grid-cols-1 gap-y-14 sm:grid-cols-12 sm:gap-y-10 gap-x-10 sm:gap-x-[var(--layout-grid-gap)] px-[var(--layout-padding-x)] w-full">
|
||||
{tools.map((tool) => (
|
||||
<RevealItem
|
||||
key={tool.id}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { RichText, extractHeadings } from "../components/RichText";
|
||||
import { LiveRichText } from "../components/LiveRichText";
|
||||
import { SectionTOC, MobileSectionTOC } from "../components/SectionTOC";
|
||||
import { getLegalPage } from "../lib/payload";
|
||||
import { formatMonthYear } from "../lib/format";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Datenschutzerklärung",
|
||||
@@ -35,7 +36,7 @@ export default async function DatenschutzPage() {
|
||||
>
|
||||
Datenschutzerklärung
|
||||
</p>
|
||||
<p className="text-body text-text-muted">Stand: Juli 2026</p>
|
||||
{page && <p className="text-body text-text-muted">Stand: {formatMonthYear(page.updatedAt)}</p>}
|
||||
</Reveal>
|
||||
|
||||
{/* MobileSectionTOC — below lg: only, see SectionTOC.tsx's own
|
||||
|
||||
@@ -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 (
|
||||
<KontoShell customer={session.customer}>
|
||||
<KontoShell>
|
||||
<Reveal className="flex flex-col gap-6 items-start w-full">
|
||||
<p className="font-semibold text-h-feature text-text-primary" style={{ fontFamily: "var(--font-lora)" }}>
|
||||
Meine Bestellungen
|
||||
</p>
|
||||
<div className="flex flex-col gap-1 items-start">
|
||||
<p className="font-semibold text-h-feature text-text-primary" style={{ fontFamily: "var(--font-lora)" }}>
|
||||
Meine Bestellungen
|
||||
</p>
|
||||
<AccountIdentity email={session.customer.email} customerNumber={session.customer.customerNumber} />
|
||||
</div>
|
||||
|
||||
{orderFilterEnabled && availableYears.length > 0 && (
|
||||
<Suspense fallback={null}>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export function AccountIdentity({ email, customerNumber }: { email: string; customerNumber: string }) {
|
||||
return (
|
||||
<p className="text-body-sm text-text-muted">
|
||||
Eingeloggt als {email} (Kundennummer {customerNumber})
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<>
|
||||
<main className="flex flex-col flex-1 bg-bg-base">
|
||||
<div className="flex flex-col sm:flex-row gap-6 sm:gap-10 w-full max-w-[75rem] mx-auto px-[var(--layout-padding-x)] pt-8 sm:pt-10 pb-16">
|
||||
<AccountNav />
|
||||
<div className="flex-1 min-w-0 flex flex-col gap-6">
|
||||
<p className="text-body-sm text-text-muted">
|
||||
Eingeloggt als {customer.email} (Kundennummer {customer.customerNumber})
|
||||
</p>
|
||||
{children}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0 flex flex-col gap-6">{children}</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
@@ -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 (
|
||||
<KontoShell customer={session.customer}>
|
||||
<KontoShell>
|
||||
<Reveal className="flex flex-col gap-6 items-start w-full">
|
||||
<p className="font-semibold text-h-feature text-text-primary" style={{ fontFamily: "var(--font-lora)" }}>
|
||||
Meine Merkliste
|
||||
</p>
|
||||
<div className="flex flex-col gap-1 items-start">
|
||||
<p className="font-semibold text-h-feature text-text-primary" style={{ fontFamily: "var(--font-lora)" }}>
|
||||
Meine Merkliste
|
||||
</p>
|
||||
<AccountIdentity email={session.customer.email} customerNumber={session.customer.customerNumber} />
|
||||
</div>
|
||||
|
||||
<MerklisteGrid initialProducts={initialProducts} defaultTaxRate={defaultTaxRate} kleinunternehmer={kleinunternehmer} />
|
||||
</Reveal>
|
||||
|
||||
@@ -103,13 +103,6 @@ export function ProfileForm({
|
||||
|
||||
return (
|
||||
<Reveal className="flex flex-col gap-6 items-start w-full">
|
||||
<p className="font-semibold text-h-feature text-text-primary" style={{ fontFamily: "var(--font-lora)" }}>
|
||||
Mein Profil
|
||||
</p>
|
||||
<p className="text-body-sm text-text-muted">
|
||||
{profile.email} · Kundennummer {profile.customerNumber}
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4 items-start w-full">
|
||||
{/* Explicit heading, matching /checkout's own "1. Rechnungsadresse"
|
||||
card title — without it, this section and the "Lieferadresse"
|
||||
|
||||
@@ -3,6 +3,7 @@ import { redirect } from "next/navigation";
|
||||
import { getSessionCustomer, getCustomerProfile } from "../../lib/customerAuth";
|
||||
import { getShippingCountries } from "../../lib/payload";
|
||||
import { KontoShell } from "../components/KontoShell";
|
||||
import { AccountIdentity } from "../components/AccountIdentity";
|
||||
import { ProfileForm } from "./components/ProfileForm";
|
||||
import { PasswordForm } from "./components/PasswordForm";
|
||||
import { VerificationBanner } from "./components/VerificationBanner";
|
||||
@@ -28,8 +29,14 @@ export default async function KontoProfilPage({
|
||||
const { verified } = await searchParams;
|
||||
|
||||
return (
|
||||
<KontoShell customer={{ email: profile.email, customerNumber: session.customer.customerNumber }}>
|
||||
<KontoShell>
|
||||
<div className="flex flex-col gap-10 items-start w-full max-w-[40rem]">
|
||||
<div className="flex flex-col gap-1 items-start w-full">
|
||||
<p className="font-semibold text-h-feature text-text-primary" style={{ fontFamily: "var(--font-lora)" }}>
|
||||
Mein Profil
|
||||
</p>
|
||||
<AccountIdentity email={profile.email} customerNumber={session.customer.customerNumber} />
|
||||
</div>
|
||||
<VerificationBanner emailVerified={profile.emailVerified} justVerified={verified === "1" || verified === "0" ? verified : undefined} />
|
||||
<ProfileForm profile={profile} shippingCountries={shippingCountries} />
|
||||
<PasswordForm email={profile.email} />
|
||||
|
||||
@@ -31,3 +31,12 @@ export function formatDate(iso: string): string {
|
||||
const day = String(d.getDate()).padStart(2, "0");
|
||||
return `${day}. ${MONTHS_DE[d.getMonth()]} ${d.getFullYear()}`;
|
||||
}
|
||||
|
||||
// "Juli 2026" — for legal pages' "Stand: …" line, derived from the
|
||||
// LegalPages doc's own updatedAt so it always reflects when the template
|
||||
// was actually last edited, instead of a hand-typed string an admin has
|
||||
// to remember to bump on every content change.
|
||||
export function formatMonthYear(iso: string): string {
|
||||
const d = new Date(iso);
|
||||
return `${MONTHS_DE[d.getMonth()]} ${d.getFullYear()}`;
|
||||
}
|
||||
|
||||
@@ -838,6 +838,7 @@ export type LegalPage = {
|
||||
title: string;
|
||||
content: unknown;
|
||||
attachment: { url: string; title: string } | null;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
type PayloadLegalPage = {
|
||||
@@ -845,6 +846,7 @@ type PayloadLegalPage = {
|
||||
title: string;
|
||||
content: unknown;
|
||||
attachment: { url: string; title: string } | number | null;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export async function getLegalPage(type: LegalPageType, options?: { draft?: boolean }): Promise<LegalPage | null> {
|
||||
@@ -872,6 +874,7 @@ export async function getLegalPage(type: LegalPageType, options?: { draft?: bool
|
||||
typeof doc.attachment === "object" && doc.attachment
|
||||
? { url: doc.attachment.url, title: doc.attachment.title }
|
||||
: null,
|
||||
updatedAt: doc.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { RichText, extractHeadings } from "../components/RichText";
|
||||
import { LiveRichText } from "../components/LiveRichText";
|
||||
import { SectionTOC, MobileSectionTOC } from "../components/SectionTOC";
|
||||
import { getLegalPage } from "../lib/payload";
|
||||
import { formatMonthYear } from "../lib/format";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Widerrufsrecht",
|
||||
@@ -36,7 +37,7 @@ export default async function WiderrufPage() {
|
||||
>
|
||||
Widerrufsrecht
|
||||
</p>
|
||||
<p className="text-body text-text-muted">Stand: Juli 2026</p>
|
||||
{page && <p className="text-body text-text-muted">Stand: {formatMonthYear(page.updatedAt)}</p>}
|
||||
</Reveal>
|
||||
|
||||
{/* MobileSectionTOC — below lg: only, see SectionTOC.tsx's own
|
||||
|
||||
Reference in New Issue
Block a user