Validate PLZ/Packstationnummer/Postnummer format at checkout, pick up invoice layout fix
Native HTML5 pattern validation, mirroring the same rules the backend now enforces (Orders.ts/Customers.ts, same commit on that repo): PLZ digit count by country (5/DE, 4/AT+CH), Packstationnummer 1-3 digits, Postnummer 6-10 digits — catches the exact mix-up (long number in Packstationnummer, short number in Postnummer) found in a real order's data while investigating this. Client-side only for instant feedback; the backend re-validates regardless. Also bumps @einfach-produktiv/invoicing to pick up the invoice PDF layout fixes (centered footer, clustered summary rows, relocated paid badge, stacked Packstation/Postnummer address lines).
This commit is contained in:
@@ -20,6 +20,19 @@ import { readCheckoutDraft, writeCheckoutDraft, clearCheckoutDraft } from "../..
|
||||
import type { ShippingMethod, 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 <select>, so a fixed 3-way
|
||||
// lookup is enough — unlike the backend's own zip check, which stays
|
||||
// free-text-country-tolerant since Payload's admin has no such select.
|
||||
const PLZ_DIGITS: Record<string, number> = { Deutschland: 5, Österreich: 4, Schweiz: 4 };
|
||||
function plzPattern(country: string): string {
|
||||
const digits = PLZ_DIGITS[country] ?? 4;
|
||||
return `\\d{${digits}}`;
|
||||
}
|
||||
|
||||
function FormField({
|
||||
label,
|
||||
wrapperClassName = "flex-1 min-w-0",
|
||||
@@ -554,7 +567,19 @@ export function CheckoutContent({
|
||||
wrapperClassName="w-full sm:w-[calc(50%-0.5rem)] sm:flex-none min-w-0"
|
||||
/>
|
||||
<div className="flex flex-col sm:flex-row gap-4 w-full">
|
||||
<FormField label="PLZ" name="zip" type="text" value={zip} onChange={(e) => setZip(e.target.value)} placeholder="10115" autoComplete="postal-code" required />
|
||||
<FormField
|
||||
label="PLZ"
|
||||
name="zip"
|
||||
type="text"
|
||||
value={zip}
|
||||
onChange={(e) => setZip(e.target.value)}
|
||||
placeholder="10115"
|
||||
autoComplete="postal-code"
|
||||
inputMode="numeric"
|
||||
pattern={plzPattern(country)}
|
||||
title={`PLZ muss aus ${PLZ_DIGITS[country] ?? 4} Ziffern bestehen.`}
|
||||
required
|
||||
/>
|
||||
<FormField label="Ort" name="city" type="text" value={city} onChange={(e) => setCity(e.target.value)} placeholder="Berlin" autoComplete="address-level2" required />
|
||||
</div>
|
||||
<label className="flex flex-col gap-2 items-start w-full">
|
||||
@@ -657,6 +682,9 @@ export function CheckoutContent({
|
||||
inputMode="numeric"
|
||||
placeholder="123"
|
||||
autoComplete="off"
|
||||
pattern="\d{1,3}"
|
||||
maxLength={3}
|
||||
title="Packstationsnummer muss aus 1 bis 3 Ziffern bestehen (1–999)."
|
||||
required
|
||||
/>
|
||||
<FormField
|
||||
@@ -665,8 +693,11 @@ export function CheckoutContent({
|
||||
value={shippingPostNumber}
|
||||
onChange={(e) => setShippingPostNumber(e.target.value)}
|
||||
inputMode="numeric"
|
||||
placeholder="1234567"
|
||||
placeholder="1234567890"
|
||||
autoComplete="off"
|
||||
pattern="\d{6,10}"
|
||||
maxLength={10}
|
||||
title="Postnummer muss aus 6 bis 10 Ziffern bestehen."
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -679,6 +710,9 @@ export function CheckoutContent({
|
||||
onChange={(e) => setShippingZip(e.target.value)}
|
||||
placeholder="10115"
|
||||
autoComplete="off"
|
||||
inputMode="numeric"
|
||||
pattern={plzPattern(shippingCountry)}
|
||||
title={`PLZ muss aus ${PLZ_DIGITS[shippingCountry] ?? 4} Ziffern bestehen.`}
|
||||
required
|
||||
/>
|
||||
<FormField
|
||||
|
||||
Reference in New Issue
Block a user