From f5b898a69a6889ea5a9d572f0b0788c9b1fa70e9 Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 30 Jul 2026 23:07:49 +0000 Subject: [PATCH] Gate shop/blog filters behind CompanySettings toggles; fix blog filter z-index bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed: blog category filter bar was rendering behind the featured-post card — that card pulls itself up (-mt-8, z-10) to overlap the hero's bottom edge (its original design), but inserting the filter bar between the hero and that card meant the same pull now overlapped the filter bar instead, with the card's stacking rendering on top. Given relative z-20 to stay above that overlap regardless. Also bumped the inactive chip's background to bg-bg-muted — border-border on bg-base is only a ~2% lightness difference, nearly invisible as a pill outline. Both filters (shop price, blog category) now gated behind new CompanySettings toggles (shopFilterEnabled/blogFilterEnabled), same off-by-default pattern as wishlistEnabled/searchEnabled. Co-Authored-By: Claude Sonnet 5 --- app/blog/page.tsx | 25 +++++++++++++++++------ app/lib/payload.ts | 31 +++++++++++++++++++++++++++++ app/shop/components/ProductGrid.tsx | 11 +++++----- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/app/blog/page.tsx b/app/blog/page.tsx index 45f01e3..20e6279 100644 --- a/app/blog/page.tsx +++ b/app/blog/page.tsx @@ -4,7 +4,7 @@ import Image from "next/image"; import { Reveal, RevealGroup, RevealItem } from "../components/Reveal"; import { Newsletter } from "../components/Newsletter"; import { Footer } from "../components/Footer"; -import { getBlogPosts } from "../lib/payload"; +import { getBlogPosts, getBlogFilterEnabled } from "../lib/payload"; import { formatDate } from "../lib/format"; export const metadata: Metadata = { @@ -41,10 +41,10 @@ export default async function BlogOverviewPage({ }: { searchParams: Promise<{ categories?: string }>; }) { - const allPosts = await getBlogPosts(100); + const [allPosts, blogFilterEnabled] = await Promise.all([getBlogPosts(100), getBlogFilterEnabled()]); const { categories: categoriesParam } = await searchParams; - const activeCategories = categoriesParam ? categoriesParam.split(",").filter(Boolean) : []; - const allCategories = distinctCategories(allPosts); + const activeCategories = blogFilterEnabled && categoriesParam ? categoriesParam.split(",").filter(Boolean) : []; + const allCategories = blogFilterEnabled ? distinctCategories(allPosts) : []; const posts = activeCategories.length === 0 ? allPosts : allPosts.filter((post) => post.categories.some((c) => activeCategories.includes(c))); const [featured, ...rest] = posts; @@ -84,18 +84,31 @@ export default async function BlogOverviewPage({ hit the identical bug and switched to a plain animate — this filter bar doesn't need a scroll-reveal animation at all, so it's simplest to just not wrap it in Reveal in the first place. */} + {/* relative z-20 — the featured-post card right below pulls itself + up by -mt-8 with its own z-10 to overlap the *hero's* bottom + edge (its original, intended design). Inserting this filter + bar between the hero and that card meant the same -mt-8 pull + now overlapped THIS bar instead, and the card's higher/equal + stacking rendered on top of it, visually hiding the chips + behind the card. z-20 keeps this bar above that overlap + regardless. */} {allCategories.length > 1 && ( -
+
{allCategories.map((category) => { const active = activeCategories.includes(category); return ( {category} diff --git a/app/lib/payload.ts b/app/lib/payload.ts index 9ea22e9..6b08013 100644 --- a/app/lib/payload.ts +++ b/app/lib/payload.ts @@ -1039,6 +1039,37 @@ export async function getSearchEnabled(): Promise { return data.docs?.[0]?.searchEnabled ?? false; } +// Same pattern as getWishlistEnabled()/getSearchEnabled() — gates the +// shop overview's price-range filter (ProductGrid.tsx/PriceRangeFilter.tsx). +export async function getShopFilterEnabled(): Promise { + const params = new URLSearchParams({ "where[tenant.slug][equals]": TENANT_SLUG, limit: "1" }); + const res = await fetch(`${PAYLOAD_URL}/api/company-settings?${params}`, { + headers: { "x-order-service-secret": process.env.ORDER_SERVICE_SECRET || "" }, + next: { revalidate: 60 }, + }); + if (!res.ok) { + console.error(`getShopFilterEnabled: Payload returned ${res.status} ${res.statusText}`); + return false; + } + const data: { docs?: { shopFilterEnabled?: boolean }[] } = await res.json(); + return data.docs?.[0]?.shopFilterEnabled ?? false; +} + +// Same pattern — gates the blog overview's category filter (blog/page.tsx). +export async function getBlogFilterEnabled(): Promise { + const params = new URLSearchParams({ "where[tenant.slug][equals]": TENANT_SLUG, limit: "1" }); + const res = await fetch(`${PAYLOAD_URL}/api/company-settings?${params}`, { + headers: { "x-order-service-secret": process.env.ORDER_SERVICE_SECRET || "" }, + next: { revalidate: 60 }, + }); + if (!res.ok) { + console.error(`getBlogFilterEnabled: Payload returned ${res.status} ${res.statusText}`); + return false; + } + const data: { docs?: { blogFilterEnabled?: boolean }[] } = await res.json(); + return data.docs?.[0]?.blogFilterEnabled ?? false; +} + export type SeoSettings = { defaultTitle: string | null; titleTemplate: string | null; diff --git a/app/shop/components/ProductGrid.tsx b/app/shop/components/ProductGrid.tsx index 8878aa7..e39084c 100644 --- a/app/shop/components/ProductGrid.tsx +++ b/app/shop/components/ProductGrid.tsx @@ -1,6 +1,6 @@ import Link from "next/link"; import Image from "next/image"; -import { getProducts, getShippingSettings, getDefaultTaxRatePercent, getKleinunternehmer, getWishlistEnabled } from "../../lib/payload"; +import { getProducts, getShippingSettings, getDefaultTaxRatePercent, getKleinunternehmer, getWishlistEnabled, getShopFilterEnabled } from "../../lib/payload"; import { effectiveTaxRate } from "../../lib/cartTotals"; import { formatPrice, discountPercent } from "../../lib/format"; import { RevealGroup, RevealItem } from "../../components/Reveal"; @@ -21,12 +21,13 @@ import { PriceRangeFilter } from "./PriceRangeFilter"; // 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([ + const [allProducts, shipping, defaultTaxRate, kleinunternehmer, wishlistEnabled, shopFilterEnabled] = await Promise.all([ getProducts(), getShippingSettings(), getDefaultTaxRatePercent(), getKleinunternehmer(), getWishlistEnabled(), + getShopFilterEnabled(), ]); const allActiveProducts = allProducts.filter((p) => p.active); @@ -41,13 +42,13 @@ export async function ProductGrid({ searchParams }: { searchParams?: { minPrice? 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 minPrice = shopFilterEnabled && searchParams?.minPrice ? Number(searchParams.minPrice) : null; + const maxPrice = shopFilterEnabled && searchParams?.maxPrice ? Number(searchParams.maxPrice) : null; const products = allActiveProducts.filter((p) => (minPrice === null || p.price >= minPrice) && (maxPrice === null || p.price <= maxPrice)); return (
- {catalogMin !== catalogMax && } + {shopFilterEnabled && catalogMin !== catalogMax && } {products.length === 0 &&

Keine Produkte in dieser Preisspanne gefunden.

}