Commit Graph

425 Commits

Author SHA1 Message Date
Marco 233a41f7c9 Home Blog section: cap width at max-w-[75rem], same as the rest of the site
Was full-bleed w-full with no cap, so on very wide screens the featured
card (and its now-h-full image column) kept stretching wider while the
image's height stayed pinned to the text column's content height —
increasingly squashed the wider the viewport got.
2026-08-25 20:20:18 +00:00
Marco 24ae330037 Home Blog featured post: image stretches to full row height, not a fixed 220px
The 58%-wide image column at a hardcoded 220px tall produced a badly
elongated crop on wide viewports. sm:h-full lets the grid's natural
row-stretch (driven by the text column) size it instead, matching how
/blog's own featured card already does it.
2026-08-25 20:14:32 +00:00
Marco 821fbb0207 Blog: drop hero photo, plain text header like /shop's ShopHeader
Same breadcrumb/title/description block as every other page, no more
bespoke photo-bleed layout. Featured post card goes back to sitting
flush below the header (no more negative-margin overlap trick, which
only made sense with the photo's bottom edge to overlap).
2026-08-25 19:36:49 +00:00
Marco 194117c07d Der Eine.: add Testimonials + closing Pricing CTA, reorder PasstDazu last
Testimonials now pulled from Payload (page: "der-eine", same collection
as todo-cards/newsletter/challenge) instead of a hardcoded array. Pricing
adds a second, final buy CTA (same image+price+button card as the other
product pages' Pricing.tsx) for visitors who scroll past Hero's button.
PasstDazu moves to right before the Footer.
2026-08-25 19:28:47 +00:00
Marco d2332c37e9 Der Eine. Focus.tsx: left-align body text below md instead of justify 2026-08-25 18:58:09 +00:00
Marco d2255c47f2 Tasse "Die Pause": pass productId into AddToCartButton
Both buy buttons were missing productId, only passing numericId.
2026-08-25 18:42:27 +00:00
Marco 2b39ec757e Der Eine.: unboxed bold-outline icons, mobile gallery padding + justified text
Focus.tsx icons now match the site's established icon-trust-*/icon-step-*
look (bold single-stroke outline, no circle badge) instead of the earlier
thin-stroke circled version. Hero.tsx's gallery now gets the same
horizontal page padding as the text column below lg:, and Focus.tsx's
body copy switches from centered to justified below md: to avoid ragged
centered multi-line text on small screens.
2026-08-25 18:42:24 +00:00
Marco 1f537ccb06 PasstDazu driven by Products.relatedProduct; circle back on Focus icons
- PasstDazu.tsx now takes a productSlug prop and reads its cross-sell
  target from the product's own relatedProduct field (Payload) instead
  of hardcoding "todo-karten" — reusable on any product page now.
- payload.ts: Product/PayloadProduct gained relatedProduct, resolved via
  a bounded-recursion mapPayloadProduct call (safe — the fetch's depth:2
  means the nested relation's own relatedProduct is never populated).
- Focus.tsx icons: added the black stroked circle back around each glyph
  (matches icon-step-3.png exactly) — dropped in the previous pass by
  mistake while fixing the color/fill.
2026-08-25 13:45:52 +00:00
Marco b59c9a0e4b Focus.tsx icons: match /todo-cards' actual icon style
Bold uniform-weight black outline, no fill, no circle badge — same
language as tool-icon-todo-karten.png / icon-step-2.png / icon-step-3.png,
not the brand-orange circle-badge treatment tried previously.
2026-08-25 13:33:21 +00:00
Marco 6debcd5a46 Der Eine.: drop "Warum" section, widen + restyle Focus icons
- Removed the brand-story "Warum Der Eine.?" section entirely — for a
  6,90€ accessory item its one unique fact (the two-sided engraving) was
  already covered by Hero/Focus, the rest was pure narrative that didn't
  aid the buying decision.
- Focus.tsx ("Was kann er?") widened to max-w-[75rem] (the site's
  standard content width, same as Tools.tsx/HowItWorks.tsx) instead of
  the narrower inset borrowed from HowItWorks' 3-item version.
- Icons switched from plain black line icons to a circle-badge + brand
  orange (#f6a701) stroke treatment — matching the icon language already
  used elsewhere on this page (IconCheck) and site (HowItWorks' own
  stroked-circle step icon), the flat black icons read as a mismatched
  style.
2026-08-25 13:29:07 +00:00
Marco b3d7855b0e Focus.tsx ("Was kann er?"): icons in HowItWorks row style
Same card shape as todo-cards/components/HowItWorks.tsx (icon on top,
centered, RevealGroup/RevealItem, identical typography) instead of the
inline-checkmark bullets tried first. Icons drawn from the der-eine.png
mockup's own icon row (pencil/sparkle/shield/pen line icons — no matching
/public asset existed for these concepts).
2026-08-25 13:21:55 +00:00
Marco ba6e486e14 Revert Der Eine. mockup redesign, keep only 2 targeted changes
The full mockup-driven redesign (Banner/Lifestyle sections, restyled
Focus/Why/Pricing) was more than asked for — reverted back to the
pre-redesign version (Hero/Focus/Why exactly match commit 13fb37e), with
only the two changes actually requested applied on top:

- Focus.tsx ("Was kann er?"): dropped the product photo, plain text
  section now — a second product shot here was redundant with Hero's own.
- Pricing.tsx (bottom price/buy bar) replaced by PasstDazu.tsx, a
  "Passt dazu" cross-sell card linking to ToDo-Karten — same markup as
  blog/[slug]/page.tsx's own related-product card, hardcoded to
  "todo-karten" the same way Hero.tsx hardcodes "stift-kugelschreiber".

Kept the Payload product.image/gallery repointed at the mockup-derived
photos (ids 88/89) rather than reverting those too — the previous gallery
was mismatched stock photos of a different-colored pen, an independent
correction from the layout redesign.
2026-08-25 13:16:40 +00:00
Marco 71cd75425d Redesign Der Eine. PDP from mockup, reusing existing site patterns
Sections rebuilt to match der-eine.png (hero, tagline banner, brand-story
+ feature icons, lifestyle photo, final CTA bar with trust badges), but
each piece is built from patterns that already exist elsewhere on the
site rather than one-off layouts:
- Focus.tsx (feature icons) mirrors todo-cards/HowItWorks.tsx's exact
  icon-top/title/desc card shape and typography
- Why.tsx (brand story) mirrors todo-cards/Focus.tsx's photo+text layout
- Pricing.tsx's trust-badge row reuses getCartTrustBadges() (real,
  already-vetted copy) with /cart's own markup, not invented text
- Banner.tsx reuses --font-caveat and /icon-separator-right.svg (both
  already used in Divider.tsx) instead of introducing new assets

Photos: product.image/gallery repointed (via direct DB write, see
reference_payload_direct_db_access) to two new Payload media docs
cropped directly from the mockup — the previous gallery was mismatched
supplier stock photos of a different-colored pen. Lifestyle.tsx's
desk photo is a static /public asset (page decoration showing the pen
next to another product, not a photo of the pen alone, so it doesn't
belong in Products.gallery).
2026-08-25 13:10:00 +00:00
Marco 4fa80d6bfb Allow bold formatting in the spotlight product's marketing text
Products.spotlightText switches from a plain textarea to a richText field
(Lexical, same default editor as every other richText field here) so
admins get a formatting toolbar. Rendered via a small dedicated inline
converter (bold + paragraphs only) rather than the full block-oriented
RichText.tsx, which is styled for article-length content (blog posts,
legal pages) and would add heading/paragraph spacing this teaser doesn't
want.
2026-08-25 12:50:00 +00:00
Marco eb160cac61 Preserve paragraph breaks in the spotlight product's text
spotlightText is a plain textarea, and default HTML whitespace handling
collapsed admin-entered paragraph breaks into one run-on line.
2026-08-25 12:36:07 +00:00
Marco 13fb37eb5e Rename Der Alltagsstift back to Der Eine., move route to /der-eine
New copy throughout (hero, "Was kann er?", the "Warum Der Eine.?" brand-story
section replacing the old phone-vs-pen scenario grid, final CTA) plus the
Payload product's name/detailHref updated to match (done directly via psql,
no Payload admin API key available — see reference_payload_direct_db_access).
2026-08-25 12:36:03 +00:00
Marco 988f259371 Add internal redirect short links (/r/<code>)
A static short link — e.g. printed on a QR code — that resolves to a
Payload-editable internal target path, so the link itself never needs
reprinting when the underlying content moves. Tracks a click count.
2026-08-25 12:17:08 +00:00
Marco a085a75dea Allow blog meta row (category/readtime/date) to wrap on narrow cards
Long category names no longer force horizontal overflow — the row
wraps to a second line instead, with each segment kept from breaking
mid-word via whitespace-nowrap.
2026-08-24 22:53:12 +00:00
Marco b6e76ccd22 Add bespoke product detail pages for Der Alltagsstift and the mug
/der-alltagsstift mirrors the todo-cards pattern (Hero/HowItWorks/
Focus/Pricing) — renamed from "Der Eine" for brand-tonality reasons,
copy grounded in visually-verified product facts only.

/tasse-die-pause is a deliberately minimal Hero+Pricing scaffold —
naming/design concept for that product is still undecided, so the
headline reads product.name live from Payload instead of a hardcoded
tagline.
2026-08-24 22:52:41 +00:00
Marco c065a3223f Link product images to detail pages, not just titles
Image sits as a sibling to the badge/WishlistButton overlays, not
wrapping them — nested interactive elements would be invalid HTML and
would fire navigation on a wishlist click.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 08:19:42 +00:00
Marco 7f95711048 Link cart line-item titles/images to product detail pages
Cart line items had their own separate markup from ProductCard.tsx and
never got the title-link treatment — same product.href check, only
rendered as a link when the product actually has a detail page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 08:13:01 +00:00
Marco e7ad9cd88d Document ProductCard consolidation, swipe gallery, dynamic legal-page sourcing, shipping-methods, NotifyMeForm redesign
README had fallen behind several recent changes: the shared ProductCard
component (was still describing three independently-duplicated grids),
ProductGallery's touch-swipe support, legal-pages' contentPart2 field
and company-settings-sourced name/address/email, shipping-methods as
the real cost/threshold source (lib/shipping.ts's old hardcoded
constants were deleted), and NotifyMeForm's single-row redesign.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 08:07:51 +00:00
Marco 340dcd2c94 Fix notify-form input height to actually match the cart button
py-3 alone didn't match AddToCartInlineButton's real height — that
button's tallest child is its 1.875rem cart-icon image, not its text,
so an input with the same padding but only text content still rendered
shorter. Explicit h-14 matches the button's actual 56px.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 07:23:34 +00:00
Marco 70bc839f48 Restore reserved low-stock line, clearer notify-me placeholder text
NotifyMeForm's single-row redesign matched AddToCartInlineButton's
button height exactly, which made the sold-out card's earlier
low-stock-line omission overcorrect — it ended up shorter than its
siblings instead of taller. Keeping that line unconditional (like every
other card) is what actually lines them up. Also swapped the generic
"E-Mail-Adresse" placeholder for one that states the purpose.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 07:17:14 +00:00
Marco 82c1b1fd43 Match sold-out card height to in-stock siblings
NotifyMeForm was two stacked rows (email input + full-width button),
making an out-of-stock ProductCard taller than its in-stock siblings.
Redesigned as a single input with the submit control embedded inside it
(same py-3 as AddToCartInlineButton's own button, so the row height
matches exactly), and ProductCard now skips its reserved-height
low-stock line entirely for a fully-out-of-stock product, since that
line can never apply there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 07:14:49 +00:00
Marco c8f231baa6 Dynamic AGB address, real shipping costs on /versand instead of hardcoded constants
- AGB: name/address/email now come from company-settings via new
  VertragspartnerBlock (mid-document, see LegalPages.ts's contentPart2).
- VersandSections/VersandModal/CartContent/CheckoutContent/versand page:
  shipping cost and free-shipping threshold now come from the real
  ShippingMethods data (already fetched elsewhere for checkout), not the
  hardcoded lib/shipping.ts constants — those had already drifted from
  the real Payload values once. lib/shipping.ts deleted, nothing left to
  export.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 06:53:12 +00:00
Marco 9617478b0d Add mobile swipe gallery, shared ProductCard, dynamic Datenschutz address, GSC verification
- 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>
2026-08-02 05:38:55 +00:00
Marco 23f8efcc2b Fix Klaro backdrop only covering a column, not full viewport
Root cause: .cookie-modal is the full-viewport wrapper, NOT the
dialog box — the previous centering fix wrongly applied
position/transform to it. Adding a transform to that wrapper created
a new containing block for its position:fixed children, so .cm-bg
(the backdrop) started positioning itself relative to the now-
shrunken/centered wrapper instead of the real viewport — the dark
overlay only covered a 640px-wide column (screenshot-confirmed
2026-08-02) instead of the whole screen.

Moved all sizing/position/shadow overrides to .cm-modal.cm-klaro (the
actual dialog box, a sibling of .cm-bg — both children of the
untouched full-screen .cookie-modal). Also fixed several selectors
that were quietly matching nothing: .cn-body doesn't exist in the
settings modal (that's .cm-header/.cm-body), and the footer button
row is .cm-footer-buttons, not .cm-buttons (that class belongs to the
small notice/context-notice components only). Restyled the modal's
close button and structural padding to match, now that the real
selectors are confirmed against klaro's own consent-modal.jsx source.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 22:20:16 +00:00
Marco 6e6f273524 Match Klaro modal backdrop color to NewsletterModal's own overlay
Klaro's default .cm-bg is plain black at 50% opacity — functionally
correct (standard modal dimming, confirmed intentional, not a bug)
but a different shade from this site's own established modal
backdrop. Now matches NewsletterModal.tsx's
rgba(134,134,134,0.9) exactly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 22:15:15 +00:00
Marco 3336ce77c1 Fix Klaro settings modal losing horizontal centering
The unconditional max-width:640px override collided with Klaro's own
breakpoint-dependent centering (margin:auto only applies above an
internal JS-set breakpoint; below it the modal is fixed/width:100%
with no left/margin, so the wider modal rendered flush-left instead
of centered). Explicit left:50%+translate(-50%,-50%) centers on both
axes regardless of which of Klaro's own branches is active.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 22:12:12 +00:00
Marco 90c52687bf Fix invisible service descriptions, broken empty-translation glitch; redesign modal list
Real bugs, both confirmed via screenshot 2026-08-02:
- dark3 was set to a near-white cream tone assuming background-only
  use, but Klaro's .cm-list-description rule uses it as a TEXT color
  — every service description was almost invisible. Changed to
  --color-text-muted.
- The service.disableAll.description="" override to hide Klaro's
  boilerplate toggle-all text broke its own t() lookup, rendering a
  literal "[missing translation: de/service/disableAll/description]"
  string instead — worse than the original. Reverted, hidden via a
  plain CSS display:none on .cm-toggle-all .cm-list-description
  instead.

Also a real redesign pass on the settings modal's service/purpose
list (wider modal, row separators, bigger/clearer toggle switches,
distinct footer) — was previously only reachable via the notice's own
generic styling, still visibly "default library layout" per Marco's
own read.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 22:09:28 +00:00
Marco 7ef6d2652b Klaro polish: remove redundant toggle-all text, responsive button rows, real per-service descriptions
- Empties Klaro's own "Mit diesem Schalter..." boilerplate under the
  modal's per-service toggle-all switch — redundant next to a plainly
  labeled switch.
- .cn-ok/.cn-buttons/.cm-buttons now explicit flex-wrap rows (side by
  side until they genuinely don't fit, then wrap) instead of relying
  on Klaro's own fragile inline-block flow.
- Every tracking-codes provider gets a real description in the modal's
  service list (was title-only) — what it does and, where publicly
  documented and stable, the actual cookie names/lifetimes (_ga/
  _ga_<container-id> for GA4, _fbp/fr for Facebook Pixel). GTM/Maps
  descriptions are deliberately non-specific about exact cookies —
  neither has a fixed, publicly enumerable list.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 22:04:13 +00:00
Marco ac6c3d6b71 Add GoogleMapsEmbed.tsx — consent-gated Maps embed, prepared but unused
Uses Klaro's own built-in contextual-consent mechanism (not
loadTrackingCode.ts's script-injection path) — the iframe renders
with data-name="google-maps" and its real src; Klaro's DOM scan finds
it after mount and blanks/restores src based on that service's
consent state, showing its own placeholder (styled via
KlaroConsentManager.tsx's KlaroTheme) in the meantime.

No map embedded anywhere in the app yet — this is prep work only, per
explicit request ahead of an actual page needing one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:58:19 +00:00
Marco 417bbd430e Add product image gallery, harden Klaro border reset, remove attribution link
ProductGallery.tsx (main image + thumbnail strip) wired into
TodoKartenHero.tsx, only replacing the static hero photo once a
product actually has extra gallery photos — no visual change
otherwise. Backend field: Products.gallery (see payload repo).

Klaro's hard black border was still showing after the previous
color-only pass — the targeted border:none rule wasn't enough, so
this blanket-resets border/outline on every .klaro descendant and
re-adds only the shadow this theme actually wants. Also disables the
"Realisiert mit Klaro!" attribution link (BSD-3-Clause has no
on-page-attribution requirement, Klaro's own disablePoweredBy flag
covers this cleanly).

Updates the frontend README with the gallery + Klaro fixes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:41:23 +00:00
Marco f6b001dd11 Restyle Klaro to actually match the brand, not just its accent colors
CSS-var overrides alone (colors, corner position) still left Klaro's
own border/shadow/spacing/typography, which read as an obviously
bolted-on library widget next to this site's hand-designed
components. New KlaroTheme component overrides Klaro's real DOM
classnames directly (confirmed against kiprotect/klaro's own scss
source) — kills the default border, adds this site's own soft-shadow
card look, restyles every button, uses the site's actual serif/sans
font pairing. Also warms up the notice/modal copy.

Updates both READMEs with the tracking-codes/Klaro/back-in-stock/
settings-search/DHL-shipment-label work from this session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:20:28 +00:00
Marco a8ab3e4f44 Fix Klaro's remaining default-blue accent (Einstellungen button, modal links)
green1/red1 covered the notice's accept/decline buttons, but blue1/
blue2 (the modal's "Einstellungen" button + in-modal links) were
still Klaro's stock blue — only visible once the modal itself opens,
easy to miss.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:12:14 +00:00
Marco 72b7bc629c Restyle Klaro banner to brand + add persistent reopen button
Was full-width ('wide' theme) with Klaro's default green/dark
palette, neither matching the brand. Now: compact bottom-left corner
notice (~380px), brand colors via Klaro's CSS-var overrides
(dark1/light1 etc. — confusingly named, they're background/text, not
a dark-mode switch), neutral gray decline button instead of red.

Also adds a persistent bottom-left cookie icon (KlaroConsentManager.tsx)
so a visitor can reopen the consent manager any time via Klaro.show(),
not just on first visit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 21:07:26 +00:00
Marco 43ab5f2407 Swap Zahlungsstatus/Zahlungsart order in order detail view
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 20:41:49 +00:00
Marco 537543bf91 Replace custom cookie banner with Klaro (open-source, self-hosted CMP)
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>
2026-08-01 20:33:10 +00:00
Marco 259002f5c0 Add product thumbnail to back-in-stock email, left-align notify button text
renderBackInStockHtml() (and sendBackInStockEmails.ts on the backend)
now show a small product image above the product name, when the
product has one. Also left-aligns NotifyMeForm's button text to match
the email input's own left-aligned placeholder — was centered, read
as inconsistent next to it.

Also adds the klaro dependency, ahead of swapping the custom cookie
banner for it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 20:19:59 +00:00
Marco 87388479de Fix out-of-stock CTA regressions: grid alignment, preview accuracy, label length
- Replace the invisible reserved-space trick with items-start on every
  product grid (ProductGrid/MerklisteGrid/RelatedProducts) — the
  reservation looked worse in practice (visible dead space under
  in-stock cards' buttons) than letting an out-of-stock card simply be
  taller than its siblings.
- New renderBackInStockHtml() in lib/emailTemplates.ts, used by the
  Live Preview instead of the generic order-status renderer — that one
  showed a fake order number and "Bestellung ansehen", neither of
  which apply to a back-in-stock mail (no order exists). CTA is now
  "Zum Produkt", matching the real backend send.
- Shortened NotifyMeForm's button label ("Benachrichtigen") — the
  longer version wrapped to two lines on narrow single-column cards.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 20:12:23 +00:00
Marco 70ee518e1d Ship tracking-codes Phase 2: cookie consent banner + gated script injection
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>
2026-08-01 20:03:11 +00:00
Marco c6b12e9d60 Fix missing back-in-stock email Live Preview (404)
'back-in-stock' was added to the Payload-side email-templates type
but never wired into this repo's own EmailTemplateType union or the
Live Preview page's VALID_TYPES/fallback-heading maps — every other
type is registered in three places, this one was only in one, so
opening its Live Preview 404'd.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 19:57:47 +00:00
Marco d046e5c3bb Fix out-of-stock CTA: replace button instead of stacking, collapse notify form
Two problems in the previous back-in-stock commit: the disabled
"Ausverkauft" button and NotifyMeForm stacked, making out-of-stock
cards visibly taller than in-stock siblings — and since ProductGrid's
cards rely on plain CSS Grid row-stretch for equal card height, that
extra height stretched sibling cards and pushed their own buttons
down (screenshot: "In den Warenkorb" CTAs misaligned across a row).

NotifyMeForm now replaces the button slot entirely when out of stock
(matching the height of a normal button when collapsed), and only
expands to the email input after a click.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 19:55:38 +00:00
Marco 2638515683 Fix NotifyMeForm layout: stack input/button instead of side-by-side
Narrow product cards (shop grid, related products) truncated the
email placeholder when input and button shared a row.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 19:52:37 +00:00
Marco 0dcaad8b8c Add back-in-stock notification signup form
NotifyMeForm.tsx replaces the disabled Add-to-cart button's spot once
a product/variant is out of stock — POSTs to the new
/api/stock-notifications route, which forwards to the backend's new
stock-notifications collection. Threaded product.numericId (not the
commerce slug id) into AddToCartButton/AddToCartInlineButton for this,
same split WishlistButton already uses.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 19:48:00 +00:00
Marco b0df2f13fa Fix shop filter spacing: consistent checkbox gaps, more room below mobile filter bar
CategoryFilter used gap-4 between the category group and the
availability checkbox but gap-2 within the category list itself,
reading as uneven spacing. ProductGrid's <lg filter bar only had
pb-6 before the product grid.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 19:01:16 +00:00
Marco 51632b1668 Fix wishlist items not saving: pass customerId into toggleWishlistItem
The create POST omitted the customer relationship field entirely, so
added items never matched getWishlist's customer-scoped query. Also
scope the toggle-off fallback lookup to the current customer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 15:04:35 +00:00
Marco d010a3aff1 Add wishlist button to cart page's related-products cards
RelatedProducts.tsx was the only product-card grid missing the
WishlistButton (ProductGrid.tsx/ProductSpotlight.tsx already had it).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-01 11:50:31 +00:00
Marco 1ec442cc05 Fix guest cart never reaching the server on login
mergeServerCartIntoLocal() relied on CartSync's own change-triggered
push to upload the merged cart — but if the server cart was empty
(first login on this account) and the local guest cart wasn't, the
merge loop never actually ran (nothing in the server response to
add), so no write/change-event ever fired and CartSync's effect never
pushed anything. The guest cart looked fine on the device that just
logged in, but never reached any other device. Now pushes explicitly
at the end regardless of whether anything changed.
2026-08-01 10:46:57 +00:00