diff --git a/README.md b/README.md index 0f44406..4a47b68 100644 --- a/README.md +++ b/README.md @@ -2218,6 +2218,101 @@ here to pick it up. bridge → Coolify API → rebuild + restart. Full mechanics in `~/dev/README.md`. +## Wishlist, search, filters, feature toggles (2026-07-30 evening) + +A cluster of new features, all gated behind new `CompanySettings` +booleans (Payload admin → Company Settings → **Features** tab, all off +by default — each feature stays entirely invisible in the frontend +until explicitly switched on): `wishlistEnabled`, `searchEnabled`, +`shopFilterEnabled`, `blogFilterEnabled`, `orderFilterEnabled`. + +**Wishlist.** Backend `wishlist-items` collection (customer + product + +optional variant, unique per combination, customer-scoped access — see +the Payload backend's own README). Frontend: `useWishlist.ts` (a +fetch-based hook with an optimistic toggle + a `window` event so every +`WishlistButton`/the Navbar badge on a page stay in sync — not +`localStorage`-backed like the cart, since a wishlist needs a logged-in +customer to mean anything), `WishlistButton.tsx` (heart icon, redirects +to login with `?redirect=` if logged out), the Navbar's heart icon + +count badge (hidden below `sm:` — Account+Cart are the only +always-visible icons on true mobile, a 3rd icon there risks the nav- +overflow bug class documented in the `figma-to-nextjs` skill), and +`/konto/merkliste` (a Client Component grid — `MerklisteGrid.tsx` — so +removing an item disappears immediately, unlike a plain Server +Component render which wouldn't react to the client-side toggle at all). +`Product` gained a `numericId` field (the raw Payload id) alongside its +existing slug `id`, since `wishlist-items.product` is a real numeric +relationship, unlike the cart/checkout's slug-keyed "commerce id". + +Found and fixed a real race condition in `useWishlist.ts`: the "refetch +me" event used to fire immediately after the optimistic local update, +before the POST request was even sent — another instance's resulting +refetch could beat the actual server write, cache the pre-toggle list, +and never get told to refetch again (the Navbar count stayed one +behind). The event now only fires after the request settles. + +**Standalone registration.** Until now an account only ever got created +inline during checkout. That stopped making sense the moment a +wishlist gave accounts a reason to exist independent of a purchase — +new `/konto/registrieren` (+ `RegisterForm.tsx`, reusing the existing +`/api/account/register` route). `LoginForm.tsx` now also honors a +`?redirect=` param (previously ignored, always landing on +`/konto/bestellungen`) and links to the new page instead of implying +"only via checkout". + +**Instant search.** New `/api/search` route — plain Payload +`where[...][contains]` queries (Postgres `ILIKE`) against Products + +Posts, not a real search index (Meilisearch/Algolia); fine at this +catalog's current size, worth upgrading once it grows past ~20 products +(see the e-commerce SOTA gaps memory note). `SearchOverlay.tsx` +(debounced 250ms, grouped results) + a Navbar search icon (same +`hidden sm:` reasoning as the wishlist icon). + +**Filters, everywhere URL-search-param-driven** (shareable/bookmarkable, +no client-side-only state): +- `/konto/bestellungen` — status/paymentStatus/year, via 3 + `CustomSelect` dropdowns (`OrderFilters.tsx`). Went through two + earlier UI iterations first: a wall of filter chips (too cluttered, + too much vertical space) and then plain native ``s +wherever their un-stylable options popup matters. Also used for +checkout's two country pickers now (`includeAllOption={false}` — a +country is always genuinely selected, no "clear" state like the +filter-dropdown use case; `fullWidth` — matches the surrounding w-full +form fields instead of the filter bar's shrink-to-fit sizing). + +**Assorted same-evening bugfixes:** +- Blog category filter bar was invisible (wrapped in ``, which + sits right at the hero's bottom edge — exactly the + `whileInView(margin:"-80px")` trap `Reveal.tsx`'s own comment + documents: an element already visible without scrolling can + permanently never register as "entered view"). Now a plain `div`. +- Then found rendering *behind* the featured-post card instead (that + card pulls itself up `-mt-8`/`z-10` to overlap the *hero's* bottom + edge — its original design — but the filter bar now sat where that + pull landed). Given `relative z-20` to stay above it. +- `AddToCartInlineButton`'s `className` prop *replaces* its whole + default styling (`?? default`, not a merge) — `/konto/merkliste` + passed `className="w-full"` and lost all the button's actual styling + as a result. Removed the override (the default is already `w-full`). + +**How to apply if you're reading this cold:** every one of these +features requires the matching `CompanySettings` toggle to actually be +switched on before it shows up anywhere in the frontend — if a feature +"isn't showing," check that first. + ## Related design source - `~/dev/einfach-produktiv/mockups/` — Figma-stage mockup PNGs