Add password reset, order confirmation email with editable templates, and fix missing account entry points

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>
This commit is contained in:
Marco
2026-07-22 08:14:08 +00:00
parent adca6e0f64
commit f0df359db4
26 changed files with 865 additions and 81 deletions
+46
View File
@@ -90,6 +90,52 @@ export async function loginCustomer(input: { email: string; password: string }):
};
}
// Always resolves — never throws or returns a distinguishable "email not
// found" shape. Mirrors Payload's own forgot-password operation, which
// fails silently on a non-existent email specifically to avoid leaking
// which addresses are registered (see auth/operations/forgotPassword.js);
// the caller (app/api/account/forgot-password/route.ts) must preserve that
// by always responding the same way regardless of this call's outcome.
export async function requestPasswordReset(email: string): Promise<void> {
await fetch(`${PAYLOAD_URL}/api/customers/forgot-password`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email }),
}).catch(() => {
// Best-effort — same "never reveal anything" reasoning as above.
});
}
// Payload's reset-password operation logs the customer in on success (see
// auth/operations/resetPassword.js) and returns the same {token, user}
// shape as login — reused here so the frontend route can set the session
// cookie immediately, no separate login step needed after a reset.
export async function resetPassword(token: string, newPassword: string): Promise<AuthResult> {
const res = await fetch(`${PAYLOAD_URL}/api/customers/reset-password`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token, password: newPassword }),
});
if (!res.ok) return { ok: false, reason: "Der Link ist ungültig oder abgelaufen." };
const data: {
token: string;
user: { id: number; customerNumber: string; firstName: string; lastName: string; email: string; emailVerified: boolean };
} = await res.json();
return {
ok: true,
token: data.token,
customer: {
id: data.user.id,
customerNumber: data.user.customerNumber,
firstName: data.user.firstName,
lastName: data.user.lastName,
email: data.user.email,
emailVerified: data.user.emailVerified,
},
};
}
export async function getCustomerFromToken(token: string): Promise<CustomerSummary | null> {
const res = await fetch(`${PAYLOAD_URL}/api/customers/me`, {
headers: { Authorization: `JWT ${token}` },