Konto-Aktionen links (profile, wishlist, logout) used to live at the bottom of the orders page's own content — unreachable once the order list got long enough. New KontoShell + AccountNav give every /konto/* page a persistent sidebar (sm+) / tab bar (mobile) instead, always reachable regardless of list length. The order filters also collapse behind a "Filter" toggle on mobile now, since the tab bar above them left little room for 3 full-width dropdowns.
Also: replaced the Unicode "→" arrow in CTA links (Tools.tsx, Blog.tsx, ProductGrid.tsx) with an SVG icon — the glyph isn't covered by the site's custom fonts, so browsers fall back to a system font per-platform; confirmed sitting visibly low relative to the label text on Android/Chrome (Galaxy S22), not reproducible in desktop Chromium. An SVG has no font-fallback path, so it renders identically everywhere.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Root cause 1 (count doubling): LogoutButton never cleared the local
cart, and mergeServerCartIntoLocal() (called on login) adds server
quantities into the existing local ones rather than replacing — since
CartSync mirrors local to the server continuously, local and server
already held the same quantities at logout time, so every login added
them together, doubling the count each cycle. Fix: clear the local
cart on logout, so the next login's merge starts from empty (or only
genuine guest-session additions) instead of re-adding already-synced
quantities.
Root cause 2 (page couldn't load): readCart()/getCart() only guarded
against JSON.parse syntax errors, not against the parsed value being a
non-array — once localStorage held a corrupted (inflated/malformed)
value, CartContent.tsx's cart.map() threw uncaught during render.
Fix: validate Array.isArray() after parsing, falling back to [].
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The forced 4-column layout overflowed on narrow phones — badge labels
like "In Bearbeitung" and the order number wrapped/overlapped into
neighboring columns. Drop to 2 columns below 400px (the field order
already tiles cleanly into 3 rows of 2). Also stop OrderStatusBadge/
PaymentStatusBadge from stretching to their flex-col parent's full
width — self-start keeps the badge background only as wide as its label.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- switch-to-stripe eligibility gained paymentStatus !== "paid" — an
order an admin already marked paid by hand must never also be
switchable to Stripe.
- PaymentStatusBadge: "Offen" now renders in the same red/subtle
style as a failed status (was neutral grey) — worth visually flagging,
now that it's a real tracked state rather than a permanent placeholder.
New "Zahlungsart ändern" button on the account order detail page,
shown for a still-'received', still-manual (Überweisung) order when an
active Stripe payment method exists. Reuses PaymentStep (the same Stripe
Payment Element checkout uses) and /checkout/verarbeitung's polling logic
(both now take a returnContext prop/param to land back on the order page
instead of clearing the cart and redirecting to /bestellbestaetigung).
Also:
- Payment status badge (Offen/Bezahlt/...) next to the existing Zahlungsart
display on the order detail page.
- A one-line mention of the switch option in the Vorkasse unpaid notice
in the order-confirmation email, shown only when a Stripe option is
actually active (hasOnlinePaymentOption).
- CustomerOrderDetail gained paymentProvider (was missing from the type
entirely, even though the field already existed on the order).
Backend counterpart: docker/payload's switchPaymentToStripeEndpoint.
Bug fixes:
- Navbar login/logout state now updates immediately (custom ep-auth-changed
event) instead of requiring a hard reload
- Status-change email links were broken by an un-encoded "#" in the order
number; fixed for all 4 status emails
- Cart discount code: manual input field restored (was removed entirely)
- Quote-label underline now scales with the label's actual text width
- Number Ranges admin list now shows the invoice prefix/counter columns
Pricing & VAT:
- Prices show the real per-product VAT rate ("inkl. X% MwSt.") instead of
a generic disclosure
- Cart/checkout/confirmation totals show the actual € amount of VAT
included, broken down per rate when a cart spans more than one
(new lib/taxBreakdown.ts, shared with the invoice PDF's own math)
- Account order pages gained product thumbnails and the same VAT breakdown
Low-stock warning: a "Nur noch wenige verfügbar" badge/hint across the
shop grid, spotlight, and add-to-cart variant pickers, driven by the
existing lowStockThreshold field (still never exposes raw stock counts).
Invoice PDFs: product thumbnails on every line item, a plain "Netto"
label (rate was redundant, already stated on the MwSt. line below), no
more duplicate USt-IdNr. in the header, and — for a Stornorechnung
specifically — an explicit "Versand" line that was previously only
folded silently into the tax totals.
Checkout:
- Optional deviating shipping address (separate from the billing address
used for the invoice), with its own toggle + address form
- Full checkout draft persistence (name/address/shipping/payment
selections) survives navigating away and back, via localStorage
- Invoice PDF shows a third "Lieferadresse" block when the shipping
address differs from billing
Mobile navigation: fullscreen panel with a circular reveal animation from
the hamburger's corner, replacing the old in-flow accordion drawer; no
login CTA inside it (redundant with the always-visible header icon).
Admin-facing (Payload backend, mirrored where the frontend has a ported
copy of the same renderer): dashboard rebuilt as individual cards, split
into 3 task queues (received/processing/returns) instead of 2, revenue
and order counts now exclude cancelled/returned orders immediately, and
the low-stock alert links to the specific affected product(s) instead of
the unfiltered list. A new immediate email notifies the shop owner the
moment an order comes in, instead of only via the daily digest.
Testimonials admin list now groups by page instead of interleaving all
three grids' entries. ~45 English admin field descriptions translated to
German for consistency.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Password reset uses Payload's built-in forgot/reset-password flow,
customized to link to this app instead of the Payload admin. Order
confirmation email and the password-reset email's wording both come from
a new Payload email-templates collection, editable without a deploy and
previewable via Live Preview at /email-preview/[type] (same mechanism as
Posts/LegalPages/Testimonials, sample data instead of a real document).
Also: order numbers get a random suffix (prevents guessing, motivated by
a considered-and-deferred guest order-lookup feature); the discount code
field only shows in the cart when a code is actually active (codes now
apply via a ?code= link instead of manual entry); and three navigation
gaps found while testing — no reachable login link with an empty cart, no
logout link anywhere, no way back from profile to order history.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>