Detect checkout email collisions and show login state in the navbar
Registering with an email that already has an account previously just failed with a generic error and no clear next step. registerCustomer() now flags emailExists specifically, and checkout switches straight to the login toggle (email pre-filled, scrolled into view) instead. The account icon also gets a small underline while logged in, matching the nav links' active-state styling — it was otherwise the only nav element that gave no visual signal of session state. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/navigation";
|
||||
@@ -67,6 +67,7 @@ export function CheckoutContent({
|
||||
const [purchaseError, setPurchaseError] = useState<string | null>(null);
|
||||
const [purchasing, setPurchasing] = useState(false);
|
||||
const [showLogin, setShowLogin] = useState(false);
|
||||
const accountGateRef = useRef<HTMLDivElement>(null);
|
||||
const [loginEmail, setLoginEmail] = useState("");
|
||||
const [loginPassword, setLoginPassword] = useState("");
|
||||
const [loginError, setLoginError] = useState<string | null>(null);
|
||||
@@ -156,6 +157,16 @@ export function CheckoutContent({
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!data.ok) {
|
||||
// The email typed into Card 1 already belongs to an existing
|
||||
// account — registering was never going to work here. Switch
|
||||
// straight to the login toggle with that email pre-filled instead
|
||||
// of just showing an error with no clear next step; the customer
|
||||
// only needs to add their password and resubmit.
|
||||
if (data.emailExists) {
|
||||
setLoginEmail(body.email);
|
||||
setShowLogin(true);
|
||||
accountGateRef.current?.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}
|
||||
setPurchaseError(data.reason || "Die Bestellung konnte nicht abgeschlossen werden.");
|
||||
setPurchasing(false);
|
||||
return;
|
||||
@@ -234,7 +245,10 @@ export function CheckoutContent({
|
||||
{/* Account gate — an account is required to buy, so this either
|
||||
confirms the active session or offers "already a customer?"
|
||||
login inline (registration itself happens as part of the main
|
||||
form submit below, via the password field in Card 1). */}
|
||||
form submit below, via the password field in Card 1). Ref used
|
||||
to scroll this into view if a checkout submit turns out to
|
||||
collide with an existing account (see handleSubmit). */}
|
||||
<div ref={accountGateRef} className="w-full">
|
||||
{customerEmail ? (
|
||||
<p className="text-body-sm text-text-primary">
|
||||
Eingeloggt als <span className="font-bold">{customerEmail}</span>{" "}
|
||||
@@ -278,6 +292,7 @@ export function CheckoutContent({
|
||||
{loginError && <p className="text-label text-red-600 w-full">{loginError}</p>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Reveal>
|
||||
|
||||
<form onSubmit={handleSubmit} className="flex flex-col lg:flex-row gap-8 lg:gap-10 items-start pb-10 pt-2 px-[var(--layout-padding-x)] w-full">
|
||||
|
||||
@@ -107,13 +107,18 @@ function AccountLink({ variant }: { variant: "icon" | "mobile" }) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
aria-label={loggedIn ? "Mein Konto" : "Anmelden"}
|
||||
aria-label={loggedIn ? "Mein Konto (eingeloggt)" : "Anmelden"}
|
||||
className="relative flex h-11 w-11 items-center justify-center shrink-0 active:scale-[0.9] transition-transform"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" className="h-6 w-6 text-text-primary" fill="none" aria-hidden="true">
|
||||
<circle cx="12" cy="8" r="3.6" stroke="currentColor" strokeWidth="1.8" />
|
||||
<path d="M4.5 20c1.2-4 4-6 7.5-6s6.3 2 7.5 6" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
|
||||
</svg>
|
||||
{/* Only real "am I logged in?" signal on the site outside /konto
|
||||
itself — same brand-colored underline language as the desktop
|
||||
nav links' active-state indicator, so it reads as consistent
|
||||
rather than a new visual idiom. */}
|
||||
{loggedIn && <span aria-hidden className="absolute bottom-1 h-[2px] w-4 bg-brand rounded-full" />}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
+20
-2
@@ -39,7 +39,9 @@ export type CustomerSummary = {
|
||||
emailVerified: boolean;
|
||||
};
|
||||
|
||||
export type AuthResult = { ok: true; token: string; customer: CustomerSummary } | { ok: false; reason: string };
|
||||
export type AuthResult =
|
||||
| { ok: true; token: string; customer: CustomerSummary }
|
||||
| { ok: false; reason: string; emailExists?: boolean };
|
||||
|
||||
export async function registerCustomer(input: {
|
||||
firstName: string;
|
||||
@@ -58,7 +60,23 @@ export async function registerCustomer(input: {
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => null);
|
||||
const message: string | undefined = data?.errors?.[0]?.message;
|
||||
return { ok: false, reason: message ?? "Diese E-Mail-Adresse ist bereits registriert." };
|
||||
// Payload's own message for a duplicate unique field is a generic
|
||||
// "The following field is invalid: email" — not distinguishable from
|
||||
// any other email-field validation failure by content alone, but at
|
||||
// this point the client's own type="email" + required already ruled
|
||||
// out a malformed/missing address, so "email" being the flagged field
|
||||
// here in practice only ever means one thing: this address is already
|
||||
// registered. emailExists lets the checkout UI react to that
|
||||
// specifically (switch to the login toggle) instead of just showing
|
||||
// an error the customer has no clear next step for.
|
||||
const emailExists = Boolean(message?.toLowerCase().includes("email"));
|
||||
return {
|
||||
ok: false,
|
||||
reason: emailExists
|
||||
? "Diese E-Mail-Adresse ist bereits registriert. Bitte logge dich stattdessen ein."
|
||||
: (message ?? "Registrierung ist gerade nicht möglich."),
|
||||
emailExists,
|
||||
};
|
||||
}
|
||||
|
||||
return loginCustomer(input);
|
||||
|
||||
Reference in New Issue
Block a user