Add persistent account navigation (sidebar/tabs); fix CTA arrow on Android

Konto-Aktionen links (profile, wishlist, logout) used to live at the bottom of the orders page's own content — unreachable once the order list got long enough. New KontoShell + AccountNav give every /konto/* page a persistent sidebar (sm+) / tab bar (mobile) instead, always reachable regardless of list length. The order filters also collapse behind a "Filter" toggle on mobile now, since the tab bar above them left little room for 3 full-width dropdowns.

Also: replaced the Unicode "→" arrow in CTA links (Tools.tsx, Blog.tsx, ProductGrid.tsx) with an SVG icon — the glyph isn't covered by the site's custom fonts, so browsers fall back to a system font per-platform; confirmed sitting visibly low relative to the label text on Android/Chrome (Galaxy S22), not reproducible in desktop Chromium. An SVG has no font-fallback path, so it renders identically everywhere.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-07-31 21:58:04 +00:00
parent 92727ff22a
commit 6f033d52b5
11 changed files with 218 additions and 115 deletions
@@ -1,5 +1,6 @@
"use client";
import { useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { CustomSelect } from "../../../components/CustomSelect";
@@ -31,6 +32,16 @@ export function OrderFilters({
const paymentStatus = searchParams.get("paymentStatus") ?? "";
const year = searchParams.get("year") ?? "";
const hasAnyFilter = Boolean(status || paymentStatus || year);
const activeCount = [status, paymentStatus, year].filter(Boolean).length;
// Collapsed by default on mobile — with the account tab bar now also
// stacked above this (see KontoShell/AccountNav), 3 full-width dropdowns
// always visible left little room for the actual order list. Desktop
// (sm+) ignores this entirely and always shows the row inline, same as
// before. Starts expanded whenever a filter is already active (arriving
// via a shared/bookmarked filtered URL shouldn't hide what's applied) —
// a lazy initializer since it only needs to run once, on mount.
const [expanded, setExpanded] = useState(() => hasAnyFilter);
function setParam(key: string, value: string) {
const params = new URLSearchParams(searchParams.toString());
@@ -43,19 +54,38 @@ export function OrderFilters({
const yearOptions = years.map((y) => ({ value: y, label: y }));
return (
<div className="flex flex-col sm:flex-row sm:items-center gap-3 w-full">
<CustomSelect label="Alle Status" options={statusOptions} value={status} onChange={(v) => setParam("status", v)} />
<CustomSelect label="Alle Zahlungsstatus" options={paymentStatusOptions} value={paymentStatus} onChange={(v) => setParam("paymentStatus", v)} />
<CustomSelect label="Alle Jahre" options={yearOptions} value={year} onChange={(v) => setParam("year", v)} />
{hasAnyFilter && (
<button
type="button"
onClick={() => router.push("/konto/bestellungen")}
className="text-body-sm font-semibold text-text-muted underline hover:text-brand transition-colors self-start sm:self-auto"
>
Zurücksetzen
</button>
)}
<div className="w-full">
<button
type="button"
onClick={() => setExpanded((v) => !v)}
className="sm:hidden flex items-center justify-between w-full px-4 py-3 border border-border rounded-sm text-body-sm font-bold text-text-primary"
>
<span className="flex items-center gap-2">
Filter
{activeCount > 0 && (
<span className="flex items-center justify-center min-w-[1.1rem] h-[1.1rem] px-1 rounded-full bg-brand text-[0.6875rem] font-bold leading-none text-text-primary">
{activeCount}
</span>
)}
</span>
<span aria-hidden>{expanded ? "▴" : "▾"}</span>
</button>
<div
className={`${expanded ? "flex" : "hidden"} sm:flex flex-col sm:flex-row sm:items-center gap-3 w-full mt-3 sm:mt-0`}
>
<CustomSelect label="Alle Status" options={statusOptions} value={status} onChange={(v) => setParam("status", v)} />
<CustomSelect label="Alle Zahlungsstatus" options={paymentStatusOptions} value={paymentStatus} onChange={(v) => setParam("paymentStatus", v)} />
<CustomSelect label="Alle Jahre" options={yearOptions} value={year} onChange={(v) => setParam("year", v)} />
{hasAnyFilter && (
<button
type="button"
onClick={() => router.push("/konto/bestellungen")}
className="text-body-sm font-semibold text-text-muted underline hover:text-brand transition-colors self-start sm:self-auto"
>
Zurücksetzen
</button>
)}
</div>
</div>
);
}
+5 -25
View File
@@ -3,7 +3,6 @@ import { Suspense } from "react";
import { redirect } from "next/navigation";
import Link from "next/link";
import { Reveal } from "../../components/Reveal";
import { Footer } from "../../components/Footer";
import { formatPrice, formatDate } from "../../lib/format";
import {
getSessionCustomer,
@@ -14,7 +13,7 @@ import {
import { getOrderFilterEnabled } from "../../lib/payload";
import { OrderStatusBadge } from "../components/OrderStatusBadge";
import { PaymentStatusBadge } from "../components/PaymentStatusBadge";
import { LogoutButton } from "../components/LogoutButton";
import { KontoShell } from "../components/KontoShell";
import { OrderFilters } from "./components/OrderFilters";
// robots: noindex — account area, same reasoning as /checkout.
@@ -60,9 +59,8 @@ export default async function KontoBestellungenPage({
]);
return (
<>
<main className="flex flex-col flex-1 bg-bg-base">
<Reveal className="flex flex-col gap-6 items-start pt-10 pb-16 px-[var(--layout-padding-x)] w-full max-w-[56rem] mx-auto">
<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>
@@ -135,25 +133,7 @@ export default async function KontoBestellungenPage({
</div>
)}
{/* self-center below sm — this row otherwise inherits the parent
Reveal's items-start (left-aligned); centered here per its
own request, but only the row itself (self-center keeps its
shrink-to-fit content width, doesn't stretch it to the full
parent width the way w-full/justify-center on the parent
would). Back to left-aligned (matching the rest of the page)
from sm up. */}
<div className="flex gap-6 self-center sm:self-start">
<Link href="/konto/profil" className="underline text-body-sm text-text-primary hover:text-brand transition-colors">
Profil &amp; Adresse
</Link>
<Link href="/shop" className="underline text-body-sm text-text-primary hover:text-brand transition-colors">
Weiter einkaufen
</Link>
<LogoutButton />
</div>
</Reveal>
</main>
<Footer />
</>
</Reveal>
</KontoShell>
);
}