diff --git a/README.md b/README.md index 2928800..3178173 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ explains what does have access instead. | Collection (slug) | Used for | Key fields | |---|---|---| -| `products` | `/shop` grid, homepage spotlight, cart, checkout | `name`, `slug` (cart item id — **not** Payload's numeric id, so existing localStorage carts survive catalog changes), `description`, `price`, `compareAtPrice` (optional strikethrough), `image`, `detailHref`, `sortOrder`, `active` (hides a product from the shop grid/spotlight/related-products only — cart/checkout/its own detail page still resolve it regardless, see Discount codes section below for the same opt-in-filtering principle), `spotlight` + `spotlightEyebrow`/`spotlightHeadline`/`spotlightText`/`spotlightImage` (homepage "Neu im Shop" section — falls back to `image` if no dedicated spotlight image is set; forced onto the sole active product when exactly 1 exists, see `getSpotlightProduct()`), `taxRatePercent` (optional per-product VAT override, see "Product bundles & per-product tax rates" below), `bundleItems` (optional — makes this product a bundle), `sku` (optional, even without variants — variants carry their own; snapshotted onto each order item at checkout and shown as "Art.-Nr." on the invoice PDF, the order-confirmation email, and `/konto/bestellungen/[orderNumber]`) | +| `products` | `/shop` grid, homepage spotlight, cart, checkout | `name`, `slug` (cart item id — **not** Payload's numeric id, so existing localStorage carts survive catalog changes), `description`, `price`, `compareAtPrice` (optional strikethrough), `image`, `detailHref`, `sortOrder`, `active` (hides a product from the shop grid/spotlight/related-products only — cart/checkout/its own detail page still resolve it regardless, see Discount codes section below for the same opt-in-filtering principle), `categories` (optional, `hasMany` relation to the backend's `product-categories` collection — own collection, not shared with blog's `categories`, so a "Karten" product category and a same-named blog category never collide; not required, so an untagged product still shows everywhere, just matches every `/shop` category filter instead of being excluded — see `Product.categories`' own comment in `lib/payload.ts`), `spotlight` + `spotlightEyebrow`/`spotlightHeadline`/`spotlightText`/`spotlightImage` (homepage "Neu im Shop" section — falls back to `image` if no dedicated spotlight image is set; forced onto the sole active product when exactly 1 exists, see `getSpotlightProduct()`), `taxRatePercent` (optional per-product VAT override, see "Product bundles & per-product tax rates" below), `bundleItems` (optional — makes this product a bundle), `sku` (optional, even without variants — variants carry their own; snapshotted onto each order item at checkout and shown as "Art.-Nr." on the invoice PDF, the order-confirmation email, and `/konto/bestellungen/[orderNumber]`) | | `discount-codes` | Cart discount input (`/cart`, display-only on `/checkout`) | `code`, `type` (`percent`/`fixed`), `value`, `validFrom`/`validUntil`, `minOrderValue`, `maxRedemptions`, `redemptionCount` (server-incremented only), `active`. **Not public-read** — see Discount codes section below | | `posts` | `/blog`, `/blog/[slug]` | `title`, `slug`, `categories` (`hasMany` relation to `categories` — a post can have several, joined with `", "` wherever rendered), `excerpt`, `thumbnail`, `content` (richText, supports custom Lexical blocks — Bild/Bildergalerie/Video/Zitat — rendered via `app/components/RichText.tsx`'s `@payloadcms/richtext-lexical/react` renderer with custom `JSXConverters`), `readTime` (auto-calculated on save from word count), `featured` (shown as the `/blog` hero post; most-recently-published wins if several are marked), `publishedAt`, `quoteLabel` (label + icon + underline shown next to every blockquote in `content`, default `"Merke dir:"` — leave empty to hide that framing, the blockquote text itself still renders), `relatedProduct` (optional relation to `products`, powers the "Passend dazu" card at the end of the post — leave empty to hide that card, or empty if the linked product has no `detailHref`), `seoTitle`/`seoDescription`/`seoImage` (each falls back to `title`/`excerpt`/`thumbnail` when empty, consumed by `/blog/[slug]`'s `generateMetadata()`) | | `categories` | Blog post categorization | `name`, `slug` (unique per tenant, not globally) | @@ -1401,8 +1401,24 @@ treatment as the wishlist icon). **Filters** are URL-search-param-driven everywhere (shareable/bookmarkable, no client-side-only state): `/konto/bestellungen`'s status/paymentStatus/year dropdowns (`OrderFilters.tsx`, `CustomSelect.tsx`); `/blog`'s category -toggle chips (`buildCategoryHref()`, plain server-rendered ``s); -`/shop`'s price min/max range (`PriceRangeFilter.tsx`). +toggle chips (`BlogCategoryFilter.tsx` — a Client Component using +`useRouter()` + `useTransition()`, chips disabled while a navigation is +pending so rapid clicks can't fire overlapping RSC navigations that +commit out of order); `/shop`'s left sidebar (`lg:+`) / filter bar +(``'s un-stylable