Gate search behind CompanySettings.searchEnabled; fix two reported bugs
Search icon now gated behind the new backend toggle, same pattern as wishlistEnabled — off by default. Fixed: blog category filter bar was invisible — it was wrapped in <Reveal>, which sits right at/just past the hero's bottom edge, the exact "already near the initial viewport" position Reveal.tsx's own comment documents as a whileInView(margin:"-80px") trap (an element already visible without scrolling can permanently never register as "entered view", since `once: true` never gets a second chance). Now a plain div, no scroll-reveal animation needed for a filter bar anyway. Fixed: wishlist count showing one behind on the Navbar badge — useWishlist.ts's toggle() broadcast the "refetch me" event immediately after the optimistic local update, before the POST request was even sent. Another instance's resulting refetch could race the actual server-side write, get the pre-toggle list, and then never get told to refetch again. Broadcast now only fires after the request settles. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1022,6 +1022,23 @@ export async function getWishlistEnabled(): Promise<boolean> {
|
||||
return data.docs?.[0]?.wishlistEnabled ?? false;
|
||||
}
|
||||
|
||||
// Same pattern as getWishlistEnabled() — gates the Navbar's search icon
|
||||
// (SearchOverlay.tsx). Off by default so the feature stays invisible
|
||||
// until a tenant explicitly wants it.
|
||||
export async function getSearchEnabled(): Promise<boolean> {
|
||||
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(`getSearchEnabled: Payload returned ${res.status} ${res.statusText}`);
|
||||
return false;
|
||||
}
|
||||
const data: { docs?: { searchEnabled?: boolean }[] } = await res.json();
|
||||
return data.docs?.[0]?.searchEnabled ?? false;
|
||||
}
|
||||
|
||||
export type SeoSettings = {
|
||||
defaultTitle: string | null;
|
||||
titleTemplate: string | null;
|
||||
|
||||
+12
-6
@@ -48,9 +48,17 @@ export function useWishlist() {
|
||||
[items],
|
||||
);
|
||||
|
||||
// Optimistic: flips the local list immediately, reconciles with the
|
||||
// server response (or reverts on failure) rather than waiting for the
|
||||
// round trip — same "feels instant" reasoning as AddToCartButton.
|
||||
// Optimistic: flips the LOCAL list immediately (this component's own
|
||||
// `items`/`cachedItems`), reconciles with the server response (or
|
||||
// reverts on failure) rather than waiting for the round trip — same
|
||||
// "feels instant" reasoning as AddToCartButton. Crucially, `broadcast()`
|
||||
// (which tells every OTHER useWishlist() instance — e.g. the Navbar
|
||||
// badge — to refetch) only fires AFTER the request settles, never
|
||||
// before: broadcasting immediately after the optimistic update used to
|
||||
// race the POST itself — another instance's resulting `load()` could
|
||||
// hit the server before the toggle had actually been persisted there,
|
||||
// fetch the pre-toggle list, and then never get told to refetch again,
|
||||
// leaving e.g. the Navbar count permanently one behind the real value.
|
||||
const toggle = useCallback(async (productId: number, variant = "") => {
|
||||
const wasWishlisted = cachedItems?.some((i) => i.productId === productId && i.variant === variant) ?? false;
|
||||
const optimistic = wasWishlisted
|
||||
@@ -58,7 +66,6 @@ export function useWishlist() {
|
||||
: [...(cachedItems ?? []), { id: -1, productId, variant }];
|
||||
cachedItems = optimistic;
|
||||
setItems(optimistic);
|
||||
broadcast();
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/account/wishlist", {
|
||||
@@ -69,7 +76,6 @@ export function useWishlist() {
|
||||
if (res.status === 401) {
|
||||
cachedItems = wasWishlisted ? [...optimistic, { id: -1, productId, variant }] : optimistic.filter((i) => i.productId !== productId);
|
||||
setItems(cachedItems);
|
||||
broadcast();
|
||||
return { ok: false as const, unauthorized: true as const };
|
||||
}
|
||||
if (!res.ok) throw new Error("request failed");
|
||||
@@ -79,11 +85,11 @@ export function useWishlist() {
|
||||
// that hasn't refetched the list yet) rather than trusting the
|
||||
// optimistic placeholder id (-1) forever.
|
||||
await load();
|
||||
broadcast();
|
||||
return { ok: true as const, wishlisted: data.wishlisted ?? !wasWishlisted };
|
||||
} catch {
|
||||
cachedItems = wasWishlisted ? [...optimistic, { id: -1, productId, variant }] : optimistic.filter((i) => i.productId !== productId);
|
||||
setItems(cachedItems);
|
||||
broadcast();
|
||||
return { ok: false as const };
|
||||
}
|
||||
}, []);
|
||||
|
||||
Reference in New Issue
Block a user