From 47d03dd61befddf4d14f7066e76143d2af789a88 Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 23 Jul 2026 20:21:13 +0000 Subject: [PATCH] Feed checkout's country selects from Payload instead of a hardcoded list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both the billing and shipping-override country selects, plus PLZ maxLength/pattern validation, now read from the new shipping-countries collection (getShippingCountries()) rather than a hardcoded Deutschland/Österreich(/Schweiz) array. Lets an admin add or reorder destination countries without a frontend deploy. Co-Authored-By: Claude Sonnet 5 --- app/checkout/components/CheckoutContent.tsx | 58 ++++++++++++--------- app/checkout/page.tsx | 6 ++- app/lib/payload.ts | 40 ++++++++++++++ 3 files changed, 78 insertions(+), 26 deletions(-) diff --git a/app/checkout/components/CheckoutContent.tsx b/app/checkout/components/CheckoutContent.tsx index 112b008..efafb2e 100644 --- a/app/checkout/components/CheckoutContent.tsx +++ b/app/checkout/components/CheckoutContent.tsx @@ -19,19 +19,20 @@ import { dispatchAuthChanged } from "../../lib/auth"; import { readCheckoutDraft, writeCheckoutDraft, clearCheckoutDraft } from "../../lib/checkoutDraft"; import { normalizeVatId, isValidVatId } from "../../lib/vatId"; import { computeExemptTotals, destinationCountry, isExemptionEligibleCountry } from "../../lib/vatExemption"; -import type { ShippingMethod, PaymentMethod, TrustBadge, ShippingSettings } from "../../lib/payload"; +import type { ShippingMethod, ShippingCountry, PaymentMethod, TrustBadge, ShippingSettings } from "../../lib/payload"; import type { CustomerProfile } from "../../lib/customerAuth"; // Native HTML5 pattern validation (instant, no round-trip) mirroring the // same rules Orders.ts/Customers.ts enforce server-side — a plausibility // check, not the source of truth (the backend re-validates regardless of -// what a customer's browser did or didn't catch). Only Germany/Austria/ -// Switzerland are offered in the country options are built from the same list). +function plzPattern(country: string, plzDigitsMap: Record): string { + const digits = plzDigitsMap[country] ?? 4; return `\\d{${digits}}`; } @@ -48,9 +49,9 @@ function validateEmailFormat(value: string): string { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? "" : "Bitte eine gültige E-Mail-Adresse angeben."; } -function validateZip(value: string, country: string): string { +function validateZip(value: string, country: string, plzDigitsMap: Record): string { if (!value.trim()) return "PLZ ist erforderlich."; - const digits = PLZ_DIGITS[country] ?? 4; + const digits = plzDigitsMap[country] ?? 4; return new RegExp(`^\\d{${digits}}$`).test(value) ? "" : `PLZ muss aus ${digits} Ziffern bestehen.`; } @@ -95,6 +96,7 @@ function FormField({ export function CheckoutContent({ shippingMethods, + shippingCountries, paymentMethods, trustBadges, shippingSettings, @@ -103,6 +105,10 @@ export function CheckoutContent({ savedProfile, }: { shippingMethods: ShippingMethod[]; + /** Which countries the "Land" @@ -987,14 +997,14 @@ export function CheckoutContent({ type="text" value={shippingZip} onChange={(e) => setShippingZip(e.target.value)} - onBlur={(e) => setFieldError("shippingZip", validateZip(e.target.value, shippingCountry), e.target)} + onBlur={(e) => setFieldError("shippingZip", validateZip(e.target.value, shippingCountry, plzDigitsMap), e.target)} error={fieldErrors.shippingZip} placeholder="10115" autoComplete="off" inputMode="numeric" - pattern={plzPattern(shippingCountry)} - maxLength={PLZ_DIGITS[shippingCountry] ?? 4} - title={`PLZ muss aus ${PLZ_DIGITS[shippingCountry] ?? 4} Ziffern bestehen.`} + pattern={plzPattern(shippingCountry, plzDigitsMap)} + maxLength={plzDigitsMap[shippingCountry] ?? 4} + title={`PLZ muss aus ${plzDigitsMap[shippingCountry] ?? 4} Ziffern bestehen.`} required /> - - - + {shippingCountries.map((c) => ( + + ))} diff --git a/app/checkout/page.tsx b/app/checkout/page.tsx index f8fb5f7..69b8230 100644 --- a/app/checkout/page.tsx +++ b/app/checkout/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import { CheckoutContent } from "./components/CheckoutContent"; import { TrustRow } from "../components/TrustRow"; import { Footer } from "../components/Footer"; -import { getShippingMethods, getPaymentMethods, getCartTrustBadges, getShippingSettings, getDefaultTaxRatePercent } from "../lib/payload"; +import { getShippingMethods, getShippingCountries, getPaymentMethods, getCartTrustBadges, getShippingSettings, getDefaultTaxRatePercent } from "../lib/payload"; import { getSessionCustomer, getCustomerProfile } from "../lib/customerAuth"; // robots: noindex — transactional page, same reasoning as /cart. @@ -16,8 +16,9 @@ export const metadata: Metadata = { }; export default async function CheckoutPage() { - const [shippingMethods, paymentMethods, trustBadges, shippingSettings, defaultTaxRate, session] = await Promise.all([ + const [shippingMethods, shippingCountries, paymentMethods, trustBadges, shippingSettings, defaultTaxRate, session] = await Promise.all([ getShippingMethods(), + getShippingCountries(), getPaymentMethods(), getCartTrustBadges(), getShippingSettings(), @@ -33,6 +34,7 @@ export default async function CheckoutPage() {
{ })); } +// Feeds /checkout's "Land"