Make checkout's login prompt reactive instead of persistent, and give the confirmation email real style and voice

The always-visible "Schon Kundin?" toggle was gendered and shown to every
logged-out visitor regardless of relevance. Card 1's email field now
checks on blur (/api/account/check-email) whether that address already
has an account, and only then swaps in a gender-neutral login form,
pre-filled — the collision check in handleSubmit stays as a fallback.

The order-confirmation and password-reset emails also got a real visual
pass: same warm background/brand color/circular success-icon treatment as
the on-screen /bestellbestaetigung page, serif heading, thin brand
divider, table-based layout for email-client compatibility. Copy is
on-brand and a little playful now instead of generic transactional
boilerplate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-07-22 08:38:41 +00:00
parent ec75a480bd
commit fa02d95dff
6 changed files with 258 additions and 81 deletions
+29 -6
View File
@@ -244,9 +244,16 @@ check against Payload's public API, unlike most content on this site.
An account is required to buy — there is no guest checkout. Registration
happens inline in `/checkout`'s "1. Rechnungsadresse" card (a password
field appears there when nobody's logged in); returning customers can
instead expand a small "Schon Kundin? Einloggen" toggle in the same place
without leaving the page.
field appears there when nobody's logged in). There's no persistent
"already a customer? log in" prompt — that was gendered ("Schon Kundin?")
and shown to every logged-out visitor regardless of relevance. Instead,
the email field's `onBlur` calls `/api/account/check-email`
(`checkEmailExists()` in `customerAuth.ts`, service-secret authenticated —
Customers isn't public-read) and only *then* swaps Card 1's password field
out for an inline login form, gender-neutral copy, pre-filled with the
email just typed. `handleSubmit`'s own `emailExists` handling (see
"Checkout registration collisions" below) is the fallback for the case
this check was skipped or raced.
- **`app/lib/customerAuth.ts`** (server-only) is the single place that
talks to Payload's `customers` collection — a second, fully separate
@@ -413,9 +420,25 @@ dependency here for `LivePostContent.tsx`).
(`getEmailTemplate()` in `app/lib/payload.ts`, `draft` unset) — a Live
Preview edit never affects a live customer email until actually saved.
- `npx payload run src/seed-email-templates.ts` (Payload repo) seeds
sensible defaults for both rows; `sendOrderConfirmationEmail()` also has
a hardcoded fallback for the rare case a fresh install's order arrives
before that seed has run.
defaults for both rows — deliberately on-brand and a little playful
("Geschafft!" / "Kein Drama.", not generic transactional-email
boilerplate), matching this site's voice elsewhere (see e.g. the
testimonial copy). Editable in the admin afterward regardless.
`sendOrderConfirmationEmail()` also has a hardcoded fallback for the
rare case a fresh install's order arrives before that seed has run.
- **`emailShell()`'s visual design deliberately echoes `/bestellbestaetigung`**
(the on-screen order confirmation page) rather than reading as a generic
transactional email: same warm cream background/brand color as
`globals.css`'s `--color-*` tokens (hardcoded here as literal hex — email
clients don't resolve `var()` either), a circular brand-tinted icon
(✓ for order-confirmation, ✉ for password-reset) echoing that page's own
success-icon treatment, a thin brand-colored divider under the heading,
and a Georgia/serif heading font as the closest reliably-available
approximation of the site's Playfair Display (most email clients strip
`@font-face`/external font requests, so an actual web font isn't an
option here). Not shared code with the React page — this is plain
inline-styled HTML built for email-client compatibility (nested
`<table>`s, no flexbox) — just matched by eye.
### GDPR self-service