Document product categories and the new /shop filter sidebar
Products table gains the categories field; Search & filters section covers the price slider, category checkboxes, availability toggle, the blog's client-side category filter, and the RevealGroup key fix both grids needed to stop going blank after a filter change.
This commit is contained in:
@@ -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 `<Link>`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
|
||||
(`<lg`, both rendered by `ProductGrid.tsx`) — a dual-handle price range
|
||||
slider (`PriceRangeFilter.tsx`, drag-to-filter, commits on release not
|
||||
per drag-frame), category checkboxes (`CategoryFilter.tsx`, union match
|
||||
— a product with no category tagged still matches every filter rather
|
||||
than disappearing, see `Product.categories`' own comment in
|
||||
`lib/payload.ts`), and a "Nur verfügbare Produkte" availability toggle
|
||||
(same component). Sidebar styled to match `AccountNav.tsx`'s label/
|
||||
spacing treatment. Both `/blog` and `/shop`'s grids key their
|
||||
`RevealGroup` on the actually-rendered item set — `whileInView` only
|
||||
fires once per component instance, and a filter change re-renders the
|
||||
same page in place rather than remounting it, so without the key a
|
||||
freshly filtered list could mount into an already-settled `RevealGroup`
|
||||
and stay stuck at `opacity: 0` (reported as the grid "going blank"
|
||||
after filtering).
|
||||
**`CustomSelect.tsx`** (`app/components/`) is a fully custom-styled
|
||||
dropdown (own trigger + `role="listbox"` panel, keyboard nav,
|
||||
click-outside-to-close) used wherever a native `<select>`'s un-stylable
|
||||
|
||||
Reference in New Issue
Block a user