diff --git a/README.md b/README.md index ae62152..4f122a9 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,13 @@ since Coolify builds with `--no-cache` and a failed build only surfaces there otherwise. **Environment variables:** `PAYLOAD_URL` (defaults to `https://payload.mk360.de` -if unset, see `app/lib/payload.ts`). Set in Coolify's app settings for -production, not in a committed `.env` — this app has no other secrets. +if unset, see `app/lib/payload.ts`). `PAYLOAD_PREVIEW_SECRET` (no safe +default — required for Live Preview, see below; must match the value set on +the Payload backend). `NEXT_PUBLIC_PAYLOAD_URL` (optional, defaults to the +same `https://payload.mk360.de` — only needed if the client-side Live +Preview components should ever point somewhere else). Set in Coolify's app +settings for production, not in a committed `.env` — this app has no other +secrets. ## Pages @@ -79,13 +84,16 @@ admin-gated in the Payload admin UI at `payload.mk360.de/admin`. | `shipping-settings` | Delivery-time disclosure shown on `/shop`, homepage spotlight, ToDo-Karten, `/cart`, `/checkout`, `/versand` (Art. 246a §1 Abs.1 Nr.8 EGBGB requires this visible before checkout) | `handlingDaysMin`/`handlingDaysMax` (processing time before it ships), `transitDaysMin`/`transitDaysMax` (carrier time) — the app derives the combined total itself. One row per tenant. | | `payment-methods` | `/checkout` payment selection | `title`, `icons` (array — e.g. 3 logos for "Kreditkarte"), `active`, `sortOrder` | | `werkzeuge-cards` | Homepage "Meine Werkzeuge" 3-card grid | `title`, `description`, `icon`, `ctaLabel`, `ctaHref`, `sortOrder` | +| `testimonials` | Customer testimonial grids on `/todo-cards`, `/newsletter`, `/challenge` | `quote`, `name`, `role`, `avatar`, `page` (`todo-cards`/`newsletter`/`challenge` — which page's grid this appears in), `sortOrder`. The single-quote "photo band" testimonials on `/not-found` and `/bestellbestaetigung` are a different shape (no avatar/role) and stay hardcoded, not part of this collection. | | `media` | Shared upload collection backing every `image`/`icon`/`thumbnail`/`attachment` field above | `alt` (required for images), `title` (optional display name for download links) | All of the above (except `media`, `users`, `tenants`) are grouped in the Payload admin sidebar under **Commerce** (`products`, `shipping-methods`, `shipping-settings`, `payment-methods`, `trust-badges`, `cart-trust-badges`) -or **Content** (`posts`, `categories`, `legal-pages`, `werkzeuge-cards`); -`media`/`users`/`tenants` sit under **Platform**. +or **Content** (`posts`, `categories`, `legal-pages`, `werkzeuge-cards`, +`testimonials`); `media`/`users`/`tenants` sit under **Platform** — `users` +and `tenants` are hidden from non-super-admins' nav entirely (see the infra +README's Payload CMS section for the access-control details). Editorial changes (prices, copy, images, toggling a shipping/payment method on or off) all happen in the Payload admin UI — no code deploy needed. Adding @@ -93,6 +101,45 @@ a *new field* to any collection above requires editing the collection file in `docker/payload/src/collections/` and a migration, which does need a deploy of the Payload service. +### Live Preview + +`posts`, `legal-pages`, and `testimonials` support Payload's Live Preview — +opening a document in the Payload admin shows this app's real rendered page +in an iframe, updating as you type, no save required. + +- **`app/api/preview/route.ts`** — validates `PAYLOAD_PREVIEW_SECRET` + (matching value required on the Payload side too, or this route 401s) and + a `path`, enables Next.js Draft Mode, then redirects into the real page. + This is the link Payload's `livePreview.url` resolvers point at (see + `docker/payload/src/lib/previewUrl.ts` in the infra repo) — never the page + directly. +- Each supported page checks `draftMode().isEnabled` and renders a + `"use client"` Live-Preview-aware component instead of the plain static + one only when it's `true` — ordinary visitors are never in Draft Mode, so + they always get the plain version with zero extra client JS: + - `app/components/LiveRichText.tsx` — the 4 legal pages' body content + (their headings/sidebars are hardcoded per page, not CMS-sourced, so + `content` is the only field worth live-previewing there) + - `app/blog/[slug]/components/LivePostContent.tsx` — title/excerpt/ + thumbnail/body of a blog post (the author bio card, "Weiterlesen" card, + and Footer stay static — they either aren't post-specific or are about + a *different* post, not the one open in the admin) + - `app/components/LiveTestimonialsGrid.tsx` — the one testimonial + currently open in the admin, merged by `id` into the rest of that + page's already-fetched grid (Live Preview is inherently single-document, + but this page renders several at once) +- All three use `@payloadcms/live-preview-react`'s `useLivePreview` hook and + reuse the same mapping functions (`mapPayloadPost`, `mapPayloadTestimonial`, + exported from `app/lib/payload.ts`) the plain server-side fetchers use, so + the two code paths can't silently drift apart. +- `app/lib/payload.ts` deliberately never imports `next/headers` itself — + callers (the Server Component pages) call `draftMode()` themselves and + pass the result in as a plain `{ draft: boolean }` option. Importing + `next/headers` anywhere in that module breaks the production build the + moment a `"use client"` component (which also needs this file's mapping + functions/types) tries to bundle it — a real RSC-boundary regression hit + once already when adding this feature. + ## Cart & checkout — demo status - **Cart** (`app/lib/cart.ts`) is entirely client-side, stored in