Add active-product-count-driven automation
- Products gain `active`/`spotlight*`/`updatedAt` on the base Product type (folded in from the now-removed separate SpotlightProduct type) so shop grid, spotlight, and related-products can each filter `.active` from the same already-fetched list — cart/checkout/order-confirmation/product- detail pages keep resolving any product regardless of active status. - getSpotlightProduct() now derives from getProducts() instead of its own Payload query: with exactly 1 active product, that one IS the spotlight (overriding any `spotlight` flag elsewhere); otherwise same most-recently-updated tie-break as before, just computed client-side. - ProductGrid drops the already-dead SHOP_GRID_EXCLUDE_IDS list in favor of the same `active` filter, with an empty-state message if 0 active. - RelatedProducts gates on >=2 active products regardless of cart contents or how many display slots would otherwise resolve. - Navbar's "Shop" link becomes an anchor to the homepage spotlight section (id="spotlight") instead of a real /shop navigation whenever exactly 1 product is active — passed down from the now-async root layout, which fetches the catalog once for this decision.
This commit is contained in:
+25
-14
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { usePathname } from "next/navigation";
|
||||
@@ -31,16 +31,21 @@ function smoothScrollTo(targetY: number) {
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
const navLinks = [
|
||||
{ label: "Werkzeuge", href: "#werkzeuge" },
|
||||
{ label: "Blog", href: "/blog" },
|
||||
{ label: "Über Björn", href: "#ueber-bjoern" },
|
||||
{ label: "Shop", href: "/shop" },
|
||||
];
|
||||
|
||||
const anchorIds = navLinks
|
||||
.filter((l) => l.href.startsWith("#"))
|
||||
.map((l) => l.href.slice(1));
|
||||
// "Shop" becomes an in-page anchor to the homepage's ProductSpotlight
|
||||
// section (id="spotlight") instead of a real /shop navigation whenever
|
||||
// exactly 1 product is active — same reasoning as the other anchor links,
|
||||
// #werkzeuge/#ueber-bjoern already have (a full catalog grid is
|
||||
// degenerate UX with only 1 item to show). Passed down from
|
||||
// app/layout.tsx, which is the one place already fetching the product
|
||||
// catalog for this decision.
|
||||
function getNavLinks(singleActiveProduct: boolean) {
|
||||
return [
|
||||
{ label: "Werkzeuge", href: "#werkzeuge" },
|
||||
{ label: "Blog", href: "/blog" },
|
||||
{ label: "Über Björn", href: "#ueber-bjoern" },
|
||||
{ label: "Shop", href: singleActiveProduct ? "#spotlight" : "/shop" },
|
||||
];
|
||||
}
|
||||
|
||||
// "Werkzeuge" also covers standalone tool/product pages that live under
|
||||
// the Home "Werkzeuge" section conceptually — /todo-cards (ToDo-Karten),
|
||||
@@ -145,7 +150,7 @@ function CartLink() {
|
||||
);
|
||||
}
|
||||
|
||||
export function Navbar() {
|
||||
export function Navbar({ singleActiveProduct }: { singleActiveProduct: boolean }) {
|
||||
const pathname = usePathname();
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const [activeSection, setActiveSection] = useState("");
|
||||
@@ -154,6 +159,12 @@ export function Navbar() {
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
const hamburgerRef = useRef<HTMLButtonElement>(null);
|
||||
|
||||
const navLinks = useMemo(() => getNavLinks(singleActiveProduct), [singleActiveProduct]);
|
||||
const anchorIds = useMemo(
|
||||
() => navLinks.filter((l) => l.href.startsWith("#")).map((l) => l.href.slice(1)),
|
||||
[navLinks]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => setScrolled(window.scrollY > 8);
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
@@ -197,7 +208,7 @@ export function Navbar() {
|
||||
}
|
||||
}, 50);
|
||||
return () => clearTimeout(timer);
|
||||
}, [pathname]);
|
||||
}, [pathname, anchorIds]);
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => {
|
||||
@@ -216,7 +227,7 @@ export function Navbar() {
|
||||
onScroll();
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
return () => window.removeEventListener("scroll", onScroll);
|
||||
}, []);
|
||||
}, [anchorIds]);
|
||||
|
||||
// Close on viewport resize past the structural breakpoint, so the drawer
|
||||
// never lingers open behind the (now visible) desktop nav. The hamburger
|
||||
|
||||
@@ -29,7 +29,10 @@ export async function ProductSpotlight() {
|
||||
const discount = discountPercent(product.price, product.compareAtPrice);
|
||||
|
||||
return (
|
||||
<section className="w-full bg-bg-base py-12 md:py-16 px-[var(--layout-padding-x)]">
|
||||
// id="spotlight" — the Navbar's "Shop" link becomes an anchor to this
|
||||
// section instead of navigating to /shop whenever exactly 1 product is
|
||||
// active (see Navbar.tsx/layout.tsx).
|
||||
<section id="spotlight" className="w-full bg-bg-base py-12 md:py-16 px-[var(--layout-padding-x)]">
|
||||
<Reveal className="max-w-[75rem] mx-auto rounded-md flex flex-col md:flex-row gap-8 md:gap-12 items-center p-6 md:p-10">
|
||||
<div className="group relative w-full md:w-[23.75rem] md:shrink-0 aspect-[410/227] rounded-sm overflow-hidden">
|
||||
<Image
|
||||
|
||||
Reference in New Issue
Block a user