Add wishlist feature (frontend), gated by CompanySettings.wishlistEnabled

New: useWishlist.ts (fetch+optimistic-toggle hook, server-backed since
a wishlist needs a logged-in customer, unlike the guest-friendly cart),
WishlistButton.tsx (heart toggle, login-redirects on 401), Navbar's
wishlist icon+badge (hidden below sm: — Account+Cart are the only
always-visible icons on true mobile, a 3rd icon there risks the same
computed nav-overflow class of bug documented in the figma-to-nextjs
skill), and /konto/merkliste (list page, 404s if the feature gets
disabled after a customer already has rows).

Product gained numericId (the raw Payload id) alongside its existing
slug id — WishlistItems.product is a real numeric relationship field,
unlike cart/checkout's slug-keyed "commerce id".

Backend counterpart (WishlistItems collection, CompanySettings toggle,
migration) already deployed separately.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-07-30 22:28:30 +00:00
parent 1d91a3f21c
commit 11aa9689c1
10 changed files with 462 additions and 6 deletions
+3 -3
View File
@@ -4,7 +4,7 @@ import "./globals.css";
import { Navbar } from "./components/Navbar";
import { CartFlyProvider } from "./components/CartFly";
import { CartSync } from "./components/CartSync";
import { getProducts, getSeoSettings, getCompanySettings } from "./lib/payload";
import { getProducts, getSeoSettings, getCompanySettings, getWishlistEnabled } from "./lib/payload";
import { buildOrganizationSchema } from "./lib/structuredData";
const inter = Inter({
@@ -67,7 +67,7 @@ export default async function RootLayout({
// just to know whether Navbar's "Shop" link should behave as an anchor
// to the homepage spotlight instead of a real /shop navigation (see
// Navbar.tsx/ProductSpotlight.tsx).
const [products, seller] = await Promise.all([getProducts(), getCompanySettings()]);
const [products, seller, wishlistEnabled] = await Promise.all([getProducts(), getCompanySettings(), getWishlistEnabled()]);
const singleActiveProduct = products.filter((p) => p.active).length === 1;
// Organization JSON-LD on every page — one canonical node (@id) that
// Product/Article schemas elsewhere link back to via `{ "@id": ... }`
@@ -87,7 +87,7 @@ export default async function RootLayout({
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema) }} />
<CartFlyProvider>
<CartSync />
<Navbar singleActiveProduct={singleActiveProduct} />
<Navbar singleActiveProduct={singleActiveProduct} wishlistEnabled={wishlistEnabled} />
{children}
</CartFlyProvider>
</body>