- ProductGallery: touch swipe (left/right) now navigates slides on mobile,
previously click-only.
- New shared ProductCard component used by ProductGrid/RelatedProducts/
MerklisteGrid — product title is now the card's link (replaces the
separate "Mehr erfahren" line), consistent aspect ratio across all
three grids, more compact cards.
- Datenschutz: name/address/email now come from company-settings via a
new VerantwortlicherBlock, same single-source pattern as Impressum's
AnbieterAngaben — no more hand-typed address to keep in sync.
- layout.tsx: render googleSearchConsoleVerification via Next's native
verification.google metadata field.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Swaps the hand-rolled CookieBanner.tsx/useConsent.ts/TrackingScripts.tsx
for kiprotect/klaro — brings a real per-service consent list and
bundled German UI translations that a custom implementation would
have had to build from scratch (per user feedback that a proper CMP
is worth it over a purely custom binary accept/reject banner).
klaroConfig.ts builds Klaro's config dynamically from the existing
tracking-codes backend collection (one Klaro "service" per row,
grouped by consentCategory as its purpose). loadTrackingCode.ts is
the actual script-injection side effect, wired in via each service's
`callback(consent)` — same GA4/Facebook-Pixel/GTM/custom loader logic
TrackingScripts.tsx had, just triggered imperatively instead of
declaratively rendered. Brand color applied via Klaro's CSS custom
property overrides (styling.green1 etc.), not custom SCSS.
No @types/klaro package exists — types/klaro.d.ts declares only the
small surface actually used.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CookieBanner.tsx (equally-weighted Akzeptieren/Ablehnen, TTDSG) +
useConsent() cookie hook + TrackingScripts.tsx render active
tracking-codes rows only once their consentCategory is actually
accepted ('necessary' always renders). Wired into layout.tsx via the
new getTrackingCodes() fetcher. This is what makes the Phase 1
backend collection (tracking-codes) actually usable end to end.
Also reworks NotifyMeForm back to always-visible input+button (better
UX than a collapse-to-reveal step) — the resulting taller CTA is now
reserved on every card via NotifyMeFormReservedSpace, an invisible
twin rendered behind the real button, so an in-stock card's row
height matches an out-of-stock sibling's without the grid's
row-stretch pushing buttons out of alignment.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
New: useWishlist.ts (fetch+optimistic-toggle hook, server-backed since
a wishlist needs a logged-in customer, unlike the guest-friendly cart),
WishlistButton.tsx (heart toggle, login-redirects on 401), Navbar's
wishlist icon+badge (hidden below sm: — Account+Cart are the only
always-visible icons on true mobile, a 3rd icon there risks the same
computed nav-overflow class of bug documented in the figma-to-nextjs
skill), and /konto/merkliste (list page, 404s if the feature gets
disabled after a customer already has rows).
Product gained numericId (the raw Payload id) alongside its existing
slug id — WishlistItems.product is a real numeric relationship field,
unlike cart/checkout's slug-keyed "commerce id".
Backend counterpart (WishlistItems collection, CompanySettings toggle,
migration) already deployed separately.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Organization (site-wide), Product (/todo-cards), BlogPosting (every
/blog/[slug]) JSON-LD via new app/lib/structuredData.ts — no new
Payload fields needed, derived from existing data. Verified locally
by curling each page and checking the rendered script tag.
- Order confirmation email gains the same "please transfer to this
account, processed after payment received" notice the invoice PDF
already had for Vorkasse orders — OrderConfirmationData's new
isManualPayment flag is set explicitly by each caller (never derived
from paymentMethodTitle, which already broke once this session after
a payment-methods rename). CompanySettings gains bankName (existed on
the backend, was missing from the frontend's type/usage).
- Newsletter signup now detects an already-subscribed email
(verified empirically: Brevo's doubleOptinConfirmation endpoint gives
identical 201 responses for new vs. already-confirmed contacts) via a
GET /v3/contacts/{email} pre-check, and shows a distinct message
instead of silently resending the confirmation mail. Success message
text centralized in useNewsletterSignup.ts instead of duplicated
across 4 forms.
- Bumped @einfach-produktiv/invoicing to the version with the
unpaid-notice layout fix (full width, more top spacing — was
squeezed into the narrow paid-badge column).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RichText.tsx switched to Payload's official React renderer + custom
JSXConverters (same call signature, LiveRichText/LivePostContent
untouched) — needed to render the new Lexical Blocks the Payload repo's
Posts.content just gained. Converters follow the existing CMS-image
convention (relative + aspect-[...] + fill + object-cover); the video
block resolves YouTube/Vimeo links to an iframe embed.
New getSeoSettings() fetcher (same pattern as getKleinunternehmer()),
app/layout.tsx now generateMetadata() reading it with the same fallback
values it used to hardcode. Per-post SEO overrides (seoTitle/
seoDescription/seoImage) wired into the blog detail page's metadata,
falling back to title/excerpt/thumbnail when empty.
Also fixed while auditing every page's metadata: missing descriptions on
3 konto pages, a static title on the dynamic order-detail route, and
missing OG images on /shop and /blog.
Checkout now persists orders server-side (Payload orders collection,
re-priced from live product data, discount codes redeemed exactly once)
instead of writing a client-only sessionStorage snapshot. Buying requires
an account (registration inline in checkout, no separate step) — accounts
get order history with delivery status, profile/address editing, password
change, and a cart that syncs across devices while logged in.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Products gain `active`/`spotlight*`/`updatedAt` on the base Product type
(folded in from the now-removed separate SpotlightProduct type) so shop
grid, spotlight, and related-products can each filter `.active` from the
same already-fetched list — cart/checkout/order-confirmation/product-
detail pages keep resolving any product regardless of active status.
- getSpotlightProduct() now derives from getProducts() instead of its own
Payload query: with exactly 1 active product, that one IS the spotlight
(overriding any `spotlight` flag elsewhere); otherwise same
most-recently-updated tie-break as before, just computed client-side.
- ProductGrid drops the already-dead SHOP_GRID_EXCLUDE_IDS list in favor of
the same `active` filter, with an empty-state message if 0 active.
- RelatedProducts gates on >=2 active products regardless of cart contents
or how many display slots would otherwise resolve.
- Navbar's "Shop" link becomes an anchor to the homepage spotlight section
(id="spotlight") instead of a real /shop navigation whenever exactly 1
product is active — passed down from the now-async root layout, which
fetches the catalog once for this decision.
- New /shop overview, /cart (real cart state via useSyncExternalStore),
/versand (shipping policy page with TOC) and /newsletter detail page
- Cart: quantity/removal, order summary, related-products cross-sell
with randomized picks, VersandModal quick-reference instead of
navigating away, MwSt. disclosure next to unit prices
- Add-to-cart UX: inline success feedback (green state) plus a
fly-to-navbar-cart-icon animation (CartFlyProvider) with a delayed
badge count-up; AddToCartButton no longer navigates straight to /cart
- Shared lib/products.ts catalog and lib/shipping.ts constants (cost,
free-shipping threshold, handling/transit days) so cart, trust badges
and the versand page can never drift apart
- Fix Navbar smooth-scroll easing (ease-out instead of ease-in-out, no
more perceived start delay); compress newsletter modal photo
2.4MB -> 85KB to fix first-open jank
New /todo-cards page (Hero, How-it-works, Focus, Testimonials, Pricing)
built with the fluid clamp() token system (app/lib/fluid.ts) and
scroll-reveal animations (app/components/Reveal.tsx), matching styling
consistency with /challenge's testimonial section.
Navbar: page-aware active state and anchor-link navigation from any
route (not just "/"), fixed logo click to actually navigate instead of
silently rewriting the URL, fluid nav text size, custom hash-scroll
handling for cross-page anchor links.
Hero: responsive breakpoint fix for the text/image split at Tablet
widths, entrance animation for the brand's orange dot, removed a red
dot artifact from hero.png.
Also includes prior uncommitted work on About/Blog/Newsletter/Footer
and the cart lib (app/lib/cart.ts).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Dark bg (#111). Top divider is a 3px div in #30302c (not a CSS border).
Three justify-between columns: Lora SemiBold logo with gold period (#fdb705),
Lora Regular @handle centered (added weight 400 to Lora config), Inter 14px
legal links with gap-6. Footer sits outside <main> so mt-auto pushes it down.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>