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:
@@ -0,0 +1,17 @@
|
||||
// Replaces the Unicode "→" (U+2192) previously used inline in CTA links
|
||||
// (Tools.tsx, Blog.tsx, ProductGrid.tsx) — that glyph isn't covered by the
|
||||
// site's custom web fonts, so browsers fall back to a system font just for
|
||||
// that one character. The fallback's vertical metrics differ enough by
|
||||
// platform (confirmed: sat visibly low relative to the label text on a
|
||||
// Samsung Galaxy S22/Android Chrome, not reproducible in desktop Chromium)
|
||||
// that centering it via flex `items-center` alone isn't reliable across
|
||||
// devices. An SVG has no font-fallback path — it renders identically
|
||||
// everywhere. `currentColor` stroke follows the parent Link's own
|
||||
// text/hover color, same as every other icon in this codebase.
|
||||
export function ArrowRightIcon() {
|
||||
return (
|
||||
<svg aria-hidden width="16" height="12" viewBox="0 0 16 12" fill="none" className="shrink-0">
|
||||
<path d="M1 6H15M15 6L10 1M15 6L10 11" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { getBlogPosts } from "../lib/payload";
|
||||
import { Reveal, RevealGroup, RevealItem } from "./Reveal";
|
||||
import { ArrowRightIcon } from "./ArrowRightIcon";
|
||||
|
||||
export async function Blog() {
|
||||
const posts = await getBlogPosts(3);
|
||||
@@ -69,7 +70,7 @@ export async function Blog() {
|
||||
href={featured.href}
|
||||
className="flex items-center gap-1 font-bold text-body text-text-primary whitespace-nowrap hover:text-brand transition-colors"
|
||||
>
|
||||
<span aria-hidden>→</span>
|
||||
<ArrowRightIcon />
|
||||
<span>Zum Beitrag</span>
|
||||
</Link>
|
||||
</div>
|
||||
@@ -122,7 +123,7 @@ export async function Blog() {
|
||||
href={post.href}
|
||||
className="flex items-center gap-1 font-bold text-body whitespace-nowrap hover:text-brand transition-colors"
|
||||
>
|
||||
<span aria-hidden>→</span>
|
||||
<ArrowRightIcon />
|
||||
<span>Zum Beitrag</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { Reveal, RevealGroup, RevealItem } from "./Reveal";
|
||||
import { ArrowRightIcon } from "./ArrowRightIcon";
|
||||
import { getWerkzeugeCards } from "../lib/payload";
|
||||
|
||||
// Content now lives in Payload (WerkzeugeCards collection). Icons use a
|
||||
@@ -87,17 +88,14 @@ export async function Tools() {
|
||||
{tool.description}
|
||||
</p>
|
||||
</div>
|
||||
{/* flex items-center + arrow as its own span, not inline text
|
||||
— the → glyph sits low relative to the surrounding text's
|
||||
cap-height in the font used here, off-center against the
|
||||
label if it's just part of the same text node (fixed
|
||||
2026-07-24, same pattern ProductGrid.tsx's "Mehr
|
||||
erfahren" link already uses). */}
|
||||
{/* SVG arrow, not a Unicode "→" character — see
|
||||
ArrowRightIcon.tsx's own comment on why (font-fallback
|
||||
vertical-metrics mismatch, platform-dependent). */}
|
||||
<Link
|
||||
href={tool.ctaHref}
|
||||
className="flex items-center gap-1 font-bold leading-normal text-body whitespace-nowrap hover:text-brand transition-colors"
|
||||
>
|
||||
<span aria-hidden>→</span>
|
||||
<ArrowRightIcon />
|
||||
<span>{tool.ctaLabel}</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 & 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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { clearCart } from "../../lib/cart";
|
||||
import { dispatchAuthChanged } from "../../lib/auth";
|
||||
|
||||
// Persistent account navigation — a fixed sidebar (sm+) / horizontal tab
|
||||
// bar (below sm) shown on every logged-in /konto/* page via KontoShell.
|
||||
// Replaces the "Profil & Adresse / Weiter einkaufen / Abmelden" links that
|
||||
// used to live at the bottom of the orders page's content — those became
|
||||
// unreachable without scrolling past an arbitrarily long order list. Being
|
||||
// part of the shell now (not page content), reachability no longer depends
|
||||
// on how much is above it.
|
||||
const NAV_ITEMS = [
|
||||
{ href: "/konto/bestellungen", label: "Bestellungen", wishlistOnly: false },
|
||||
{ href: "/konto/merkliste", label: "Merkliste", wishlistOnly: true },
|
||||
{ href: "/konto/profil", label: "Profil", wishlistOnly: false },
|
||||
] as const;
|
||||
|
||||
export function AccountNav({ wishlistEnabled }: { wishlistEnabled: boolean }) {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const items = NAV_ITEMS.filter((item) => !item.wishlistOnly || wishlistEnabled);
|
||||
|
||||
// Same sequence as the old LogoutButton (now folded in here, its one
|
||||
// call site): clear the local cart (already mirrored server-side by
|
||||
// CartSync, so safe to drop — the next login's mergeServerCartIntoLocal()
|
||||
// restores it), tell already-mounted Client Components (Navbar's
|
||||
// AccountLink) the auth state changed, leave the account area, then
|
||||
// force a fresh Server Component render.
|
||||
async function handleLogout() {
|
||||
await fetch("/api/account/logout", { method: "POST" });
|
||||
clearCart();
|
||||
dispatchAuthChanged();
|
||||
router.push("/");
|
||||
router.refresh();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Desktop/tablet — sidebar, sm+ (640px). Sits to the left of the
|
||||
page content inside KontoShell's flex row. */}
|
||||
<nav className="hidden sm:flex sm:flex-col sm:w-48 shrink-0 gap-1">
|
||||
<p className="text-label font-bold text-text-muted uppercase tracking-wide px-3 pb-2">Mein Konto</p>
|
||||
{items.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`px-3 py-2 rounded-sm text-body-sm font-semibold transition-colors ${
|
||||
pathname.startsWith(item.href) ? "bg-bg-muted text-text-primary" : "text-text-muted hover:text-text-primary"
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleLogout}
|
||||
className="mt-4 px-3 py-2 text-left rounded-sm text-body-sm font-semibold text-red-600 hover:bg-red-50 transition-colors"
|
||||
>
|
||||
Abmelden
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
{/* Mobile — horizontal tab bar, below sm. overflow-x-auto rather
|
||||
than wrapping: 4 items at once already fits most phones, and a
|
||||
scrollable single row reads clearly as "more tabs this way"
|
||||
rather than a wrapped second line competing for attention with
|
||||
the page content right below it. */}
|
||||
<nav className="sm:hidden flex items-center gap-2 overflow-x-auto pb-1 -mx-[var(--layout-padding-x)] px-[var(--layout-padding-x)]">
|
||||
{items.map((item) => (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`shrink-0 px-4 py-2 rounded-full text-body-sm font-bold whitespace-nowrap transition-colors ${
|
||||
pathname.startsWith(item.href) ? "bg-brand text-text-primary" : "text-text-muted"
|
||||
}`}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleLogout}
|
||||
className="shrink-0 px-4 py-2 rounded-full text-body-sm font-bold whitespace-nowrap text-red-600"
|
||||
>
|
||||
Abmelden
|
||||
</button>
|
||||
</nav>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { Footer } from "../../components/Footer";
|
||||
import { getWishlistEnabled } from "../../lib/payload";
|
||||
import { AccountNav } from "./AccountNav";
|
||||
|
||||
// Shared shell for every logged-in /konto/* page (bestellungen, merkliste,
|
||||
// profil) — the <main>/<Footer> wrapping plus the persistent AccountNav
|
||||
// were previously duplicated per page, with the equivalent of the nav
|
||||
// buried as plain links at the bottom of the orders page's own content
|
||||
// (unreachable once the order list got long enough to push it below the
|
||||
// fold). Each page keeps its own auth-check/redirect and data-fetching
|
||||
// exactly as before — this only replaces the outer chrome, not the
|
||||
// page-specific logic each page still needs (e.g. merkliste's return-URL
|
||||
// login redirect, profil's second profile-fetch redirect).
|
||||
export async function KontoShell({ children }: { children: ReactNode }) {
|
||||
const wishlistEnabled = await getWishlistEnabled();
|
||||
|
||||
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 wishlistEnabled={wishlistEnabled} />
|
||||
<div className="flex-1 min-w-0 flex flex-col">{children}</div>
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { dispatchAuthChanged } from "../../lib/auth";
|
||||
import { clearCart } from "../../lib/cart";
|
||||
|
||||
export function LogoutButton() {
|
||||
const router = useRouter();
|
||||
|
||||
async function handleLogout() {
|
||||
await fetch("/api/account/logout", { method: "POST" });
|
||||
// The local cart is already mirrored server-side by CartSync, so it's
|
||||
// safe to clear it here — the next login's mergeServerCartIntoLocal()
|
||||
// restores it from the server. Without this, the local cart survived
|
||||
// logout untouched, and mergeServerCartIntoLocal()'s additive merge
|
||||
// (existing.qty += qty) would add the already-synced server quantities
|
||||
// on top of it on every login, doubling every logout/login cycle.
|
||||
clearCart();
|
||||
dispatchAuthChanged();
|
||||
router.push("/");
|
||||
router.refresh();
|
||||
}
|
||||
|
||||
return (
|
||||
<button type="button" onClick={handleLogout} className="underline text-body-sm text-text-primary hover:text-brand transition-colors">
|
||||
Abmelden
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { Metadata } from "next";
|
||||
import { redirect, notFound } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { Reveal } from "../../components/Reveal";
|
||||
import { Footer } from "../../components/Footer";
|
||||
import { getSessionCustomer, getWishlist } from "../../lib/customerAuth";
|
||||
import { getProductsByIds, getWishlistEnabled, getDefaultTaxRatePercent, getKleinunternehmer } from "../../lib/payload";
|
||||
import { KontoShell } from "../components/KontoShell";
|
||||
import { MerklisteGrid } from "./components/MerklisteGrid";
|
||||
|
||||
// robots: noindex — account area, same reasoning as /konto/bestellungen.
|
||||
@@ -41,26 +40,14 @@ export default async function KontoMerklistePage() {
|
||||
const initialProducts = await getProductsByIds(wishlistItems.map((i) => i.productId));
|
||||
|
||||
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-[75rem] mx-auto">
|
||||
<p className="font-semibold text-h-feature text-text-primary" style={{ fontFamily: "var(--font-lora)" }}>
|
||||
Meine Merkliste
|
||||
</p>
|
||||
<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>
|
||||
|
||||
<MerklisteGrid initialProducts={initialProducts} defaultTaxRate={defaultTaxRate} kleinunternehmer={kleinunternehmer} />
|
||||
|
||||
<div className="flex gap-6 self-center sm:self-start">
|
||||
<Link href="/konto/bestellungen" className="underline text-body-sm text-text-primary hover:text-brand transition-colors">
|
||||
Meine Bestellungen
|
||||
</Link>
|
||||
<Link href="/shop" className="underline text-body-sm text-text-primary hover:text-brand transition-colors">
|
||||
Weiter einkaufen
|
||||
</Link>
|
||||
</div>
|
||||
</Reveal>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
<MerklisteGrid initialProducts={initialProducts} defaultTaxRate={defaultTaxRate} kleinunternehmer={kleinunternehmer} />
|
||||
</Reveal>
|
||||
</KontoShell>
|
||||
);
|
||||
}
|
||||
|
||||
+12
-16
@@ -1,9 +1,8 @@
|
||||
import type { Metadata } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { Footer } from "../../components/Footer";
|
||||
import { getSessionCustomer, getCustomerProfile } from "../../lib/customerAuth";
|
||||
import { getShippingCountries } from "../../lib/payload";
|
||||
import { KontoShell } from "../components/KontoShell";
|
||||
import { ProfileForm } from "./components/ProfileForm";
|
||||
import { PasswordForm } from "./components/PasswordForm";
|
||||
import { VerificationBanner } from "./components/VerificationBanner";
|
||||
@@ -29,19 +28,16 @@ export default async function KontoProfilPage({
|
||||
const { verified } = await searchParams;
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="flex flex-col flex-1 bg-bg-base">
|
||||
<div className="flex flex-col gap-10 items-start pt-10 pb-16 px-[var(--layout-padding-x)] w-full max-w-[40rem] mx-auto">
|
||||
<Link href="/konto/bestellungen" className="text-body-sm text-text-muted hover:text-brand transition-colors">
|
||||
← Meine Bestellungen
|
||||
</Link>
|
||||
<VerificationBanner emailVerified={profile.emailVerified} justVerified={verified === "1" || verified === "0" ? verified : undefined} />
|
||||
<ProfileForm profile={profile} shippingCountries={shippingCountries} />
|
||||
<PasswordForm email={profile.email} />
|
||||
<AccountDataSection />
|
||||
</div>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
<KontoShell>
|
||||
<div className="flex flex-col gap-10 items-start w-full max-w-[40rem]">
|
||||
<p className="font-semibold text-h-feature text-text-primary" style={{ fontFamily: "var(--font-lora)" }}>
|
||||
Mein Profil
|
||||
</p>
|
||||
<VerificationBanner emailVerified={profile.emailVerified} justVerified={verified === "1" || verified === "0" ? verified : undefined} />
|
||||
<ProfileForm profile={profile} shippingCountries={shippingCountries} />
|
||||
<PasswordForm email={profile.email} />
|
||||
<AccountDataSection />
|
||||
</div>
|
||||
</KontoShell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { formatPrice, discountPercent } from "../../lib/format";
|
||||
import { RevealGroup, RevealItem } from "../../components/Reveal";
|
||||
import { AddToCartInlineButton } from "../../components/AddToCartInlineButton";
|
||||
import { WishlistButton } from "../../components/WishlistButton";
|
||||
import { ArrowRightIcon } from "../../components/ArrowRightIcon";
|
||||
import { PriceRangeFilter } from "./PriceRangeFilter";
|
||||
|
||||
// Server Component — fetches straight from Payload (getProducts(), ISR
|
||||
@@ -157,7 +158,7 @@ export async function ProductGrid({ searchParams }: { searchParams?: { minPrice?
|
||||
className="flex items-center gap-1 font-bold text-label text-text-primary hover:text-brand transition-colors"
|
||||
>
|
||||
<span>Mehr erfahren</span>
|
||||
<span aria-hidden>→</span>
|
||||
<ArrowRightIcon />
|
||||
</Link>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user