From bce5a9f82cb025f0085786ec8e571114da005487 Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 30 Jul 2026 22:53:51 +0000 Subject: [PATCH] Replace shop price toggle-badges with a real min/max range filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preset price buckets as toggle chips were reverted per feedback ("keine toggle badges") — PriceRangeFilter.tsx is a plain min/max number-input pair instead, still a URL search param underneath (?minPrice=&maxPrice=), same shareable/bookmarkable approach. Co-Authored-By: Claude Sonnet 5 --- app/shop/components/PriceRangeFilter.tsx | 80 ++++++++++++++++++++++++ app/shop/components/ProductGrid.tsx | 65 ++++--------------- app/shop/page.tsx | 2 +- 3 files changed, 93 insertions(+), 54 deletions(-) create mode 100644 app/shop/components/PriceRangeFilter.tsx diff --git a/app/shop/components/PriceRangeFilter.tsx b/app/shop/components/PriceRangeFilter.tsx new file mode 100644 index 0000000..42167fe --- /dev/null +++ b/app/shop/components/PriceRangeFilter.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; + +// Real min/max range (not preset toggle buckets, tried first and +// reverted 2026-07-30 — "keine toggle badges") — two plain number inputs, +// submitted via a small Client Component's router.push. Still a plain +// URL search param underneath (?minPrice=&maxPrice=), so the result stays +// shareable/bookmarkable like every other filter on the site. +export function PriceRangeFilter({ catalogMin, catalogMax }: { catalogMin: number; catalogMax: number }) { + const router = useRouter(); + const searchParams = useSearchParams(); + const [minPrice, setMinPrice] = useState(searchParams.get("minPrice") ?? ""); + const [maxPrice, setMaxPrice] = useState(searchParams.get("maxPrice") ?? ""); + + const hasFilter = Boolean(searchParams.get("minPrice") || searchParams.get("maxPrice")); + + function apply(e: React.FormEvent) { + e.preventDefault(); + const params = new URLSearchParams(); + if (minPrice) params.set("minPrice", minPrice); + if (maxPrice) params.set("maxPrice", maxPrice); + const qs = params.toString(); + router.push(qs ? `/shop?${qs}` : "/shop"); + } + + function reset() { + setMinPrice(""); + setMaxPrice(""); + router.push("/shop"); + } + + return ( +
+ + + + + {hasFilter && ( + + )} +
+ ); +} diff --git a/app/shop/components/ProductGrid.tsx b/app/shop/components/ProductGrid.tsx index 5391d61..8878aa7 100644 --- a/app/shop/components/ProductGrid.tsx +++ b/app/shop/components/ProductGrid.tsx @@ -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 { PriceRangeFilter } from "./PriceRangeFilter"; // Server Component — fetches straight from Payload (getProducts(), ISR // cached 60s) rather than going through the client-side useProducts() @@ -16,26 +17,10 @@ import { WishlistButton } from "../../components/WishlistButton"; // Products have no category taxonomy today (only Posts do) — a "N // checkboxes" category sidebar isn't buildable against real data yet, so // this only covers price, the one dimension that already exists on every -// product. 3 buckets split evenly across the active catalog's actual -// min/max price (not fixed round-number thresholds like "unter 20 €") so -// the ranges stay sensible regardless of what's actually being sold — -// re-computed on every request from whatever's active, never stale. -function buildPriceBuckets(products: { price: number }[]): { label: string; min: number; max: number }[] { - if (products.length === 0) return []; - const prices = products.map((p) => p.price); - const min = Math.min(...prices); - const max = Math.max(...prices); - if (min === max) return []; - const step = (max - min) / 3; - const bounds = [min, min + step, min + step * 2, max]; - return [0, 1, 2].map((i) => ({ - label: i === 0 ? `bis ${formatPrice(bounds[i + 1])}` : i === 2 ? `ab ${formatPrice(bounds[i])}` : `${formatPrice(bounds[i])}–${formatPrice(bounds[i + 1])}`, - min: bounds[i], - max: bounds[i + 1], - })); -} - -export async function ProductGrid({ searchParams }: { searchParams?: { priceBucket?: string } }) { +// product. A real min/max range (PriceRangeFilter.tsx), not preset toggle +// buckets — tried buckets-as-toggle-chips first, reverted 2026-07-30 +// ("keine toggle badges"). +export async function ProductGrid({ searchParams }: { searchParams?: { minPrice?: string; maxPrice?: string } }) { const [allProducts, shipping, defaultTaxRate, kleinunternehmer, wishlistEnabled] = await Promise.all([ getProducts(), getShippingSettings(), @@ -53,42 +38,16 @@ export async function ProductGrid({ searchParams }: { searchParams?: { priceBuck ); } - const priceBuckets = buildPriceBuckets(allActiveProducts); - const activeBucketIndex = searchParams?.priceBucket ? Number(searchParams.priceBucket) : null; - const activeBucket = activeBucketIndex !== null ? priceBuckets[activeBucketIndex] : undefined; - const products = activeBucket - ? allActiveProducts.filter((p) => p.price >= activeBucket.min && p.price <= activeBucket.max) - : allActiveProducts; + const catalogPrices = allActiveProducts.map((p) => p.price); + const catalogMin = Math.min(...catalogPrices); + const catalogMax = Math.max(...catalogPrices); + const minPrice = searchParams?.minPrice ? Number(searchParams.minPrice) : null; + const maxPrice = searchParams?.maxPrice ? Number(searchParams.maxPrice) : null; + const products = allActiveProducts.filter((p) => (minPrice === null || p.price >= minPrice) && (maxPrice === null || p.price <= maxPrice)); return (
- {priceBuckets.length > 0 && ( -
- - Alle Preise - - {priceBuckets.map((bucket, i) => ( - - {bucket.label} - - ))} -
- )} + {catalogMin !== catalogMax && } {products.length === 0 &&

Keine Produkte in dieser Preisspanne gefunden.

} diff --git a/app/shop/page.tsx b/app/shop/page.tsx index 8017512..f24f37a 100644 --- a/app/shop/page.tsx +++ b/app/shop/page.tsx @@ -22,7 +22,7 @@ export const metadata: Metadata = { export default async function ShopPage({ searchParams, }: { - searchParams: Promise<{ priceBucket?: string }>; + searchParams: Promise<{ minPrice?: string; maxPrice?: string }>; }) { const resolvedSearchParams = await searchParams; return (