Blog categories (hasMany), shipping-address contact fields, product SKU on invoices
- Posts.categories is now hasMany — blog list/detail/live-preview render a comma-joined list instead of a single category. - Checkout's "Abweichende Lieferadresse" gains optional Firma + Kontakt- E-Mail/Telefon fields (handed to the shipping carrier, not used for customer communication). - Customer profile can now store its own shipping address (mirroring Customers.ts's new "Lieferadresse" tab), prefilling the checkout override instead of always starting blank. - Product-level optional SKU (previously only on variants) snapshots onto each order item and shows up on invoices (visual + EN16931 XML), the confirmation email, and the account order detail page.
This commit is contained in:
@@ -120,6 +120,7 @@ export default async function KontoBestellungDetailPage({ params }: { params: Pr
|
||||
{order.hasDifferentShippingAddress && (
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<p className="text-label text-text-muted">Lieferadresse</p>
|
||||
{order.shippingCompanyName && <p className="text-body-sm text-text-primary">{order.shippingCompanyName}</p>}
|
||||
<p className="text-body-sm text-text-primary">
|
||||
{order.shippingFirstName} {order.shippingLastName}
|
||||
</p>
|
||||
@@ -127,6 +128,11 @@ export default async function KontoBestellungDetailPage({ params }: { params: Pr
|
||||
<p className="text-body-sm text-text-primary">
|
||||
{order.shippingZip} {order.shippingCity}, {order.shippingCountry}
|
||||
</p>
|
||||
{(order.shippingContactEmail || order.shippingContactPhone) && (
|
||||
<p className="text-body-sm text-text-muted">
|
||||
{[order.shippingContactEmail, order.shippingContactPhone].filter(Boolean).join(" · ")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -145,6 +151,7 @@ export default async function KontoBestellungDetailPage({ params }: { params: Pr
|
||||
</p>
|
||||
{!order.kleinunternehmer && <p className="text-label text-text-muted">inkl. {item.taxRatePercent}% MwSt.</p>}
|
||||
{item.bundleContents && <p className="text-label text-text-muted">{item.bundleContents}</p>}
|
||||
{item.sku && <p className="text-label text-text-muted">Art.-Nr. {item.sku}</p>}
|
||||
{item.returnQuantity > 0 && (
|
||||
<p className="text-label text-text-muted">davon {item.returnQuantity} zurückgesendet</p>
|
||||
)}
|
||||
|
||||
@@ -35,6 +35,10 @@ export function ProfileForm({
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [deliveryMethod, setDeliveryMethod] = useState<"address" | "packstation">(profile.deliveryMethod ?? "address");
|
||||
const [hasDifferentShippingAddress, setHasDifferentShippingAddress] = useState(profile.hasDifferentShippingAddress);
|
||||
const [shippingDeliveryMethod, setShippingDeliveryMethod] = useState<"address" | "packstation">(
|
||||
profile.shippingDeliveryMethod ?? "address",
|
||||
);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -58,6 +62,21 @@ export function ProfileForm({
|
||||
country: String(form.get("country") ?? ""),
|
||||
companyName: String(form.get("companyName") ?? "") || undefined,
|
||||
vatId: String(form.get("vatId") ?? "") || undefined,
|
||||
hasDifferentShippingAddress,
|
||||
shippingFirstName: hasDifferentShippingAddress ? String(form.get("shippingFirstName") ?? "") : undefined,
|
||||
shippingLastName: hasDifferentShippingAddress ? String(form.get("shippingLastName") ?? "") : undefined,
|
||||
shippingCompanyName: hasDifferentShippingAddress ? String(form.get("shippingCompanyName") ?? "") || undefined : undefined,
|
||||
shippingDeliveryMethod: hasDifferentShippingAddress ? shippingDeliveryMethod : undefined,
|
||||
shippingStreet: hasDifferentShippingAddress ? String(form.get("shippingStreet") ?? "") || undefined : undefined,
|
||||
shippingPackstationNumber: hasDifferentShippingAddress
|
||||
? String(form.get("shippingPackstationNumber") ?? "") || undefined
|
||||
: undefined,
|
||||
shippingPostNumber: hasDifferentShippingAddress ? String(form.get("shippingPostNumber") ?? "") || undefined : undefined,
|
||||
shippingZip: hasDifferentShippingAddress ? String(form.get("shippingZip") ?? "") : undefined,
|
||||
shippingCity: hasDifferentShippingAddress ? String(form.get("shippingCity") ?? "") : undefined,
|
||||
shippingCountry: hasDifferentShippingAddress ? String(form.get("shippingCountry") ?? "") : undefined,
|
||||
shippingContactEmail: hasDifferentShippingAddress ? String(form.get("shippingContactEmail") ?? "") || undefined : undefined,
|
||||
shippingContactPhone: hasDifferentShippingAddress ? String(form.get("shippingContactPhone") ?? "") || undefined : undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -91,6 +110,11 @@ export function ProfileForm({
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-4 items-start w-full">
|
||||
{/* Explicit heading, matching /checkout's own "1. Rechnungsadresse"
|
||||
card title — without it, this section and the "Lieferadresse"
|
||||
one further down read as one undifferentiated form instead of
|
||||
two distinct addresses. */}
|
||||
<p className="font-semibold text-body-sm text-text-primary">Rechnungsadresse</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 w-full">
|
||||
<Field label="Vorname" name="firstName" type="text" defaultValue={profile.firstName} required />
|
||||
<Field label="Nachname" name="lastName" type="text" defaultValue={profile.lastName} required />
|
||||
@@ -164,6 +188,111 @@ export function ProfileForm({
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div className="h-px bg-border w-full" />
|
||||
|
||||
<label className="flex gap-3 items-start w-full cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={hasDifferentShippingAddress}
|
||||
onChange={(e) => setHasDifferentShippingAddress(e.target.checked)}
|
||||
className="size-5 shrink-0 mt-0.5 rounded-xs border border-border accent-brand"
|
||||
/>
|
||||
<span className="text-body-sm text-text-primary">
|
||||
Abweichende Lieferadresse hinterlegen — wird beim Checkout vorgeschlagen, sobald dort "Abweichende Lieferadresse" aktiviert wird
|
||||
</span>
|
||||
</label>
|
||||
|
||||
{hasDifferentShippingAddress && (
|
||||
<div className="flex flex-col gap-4 items-start w-full">
|
||||
<p className="font-semibold text-body-sm text-text-primary">Lieferadresse</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 w-full">
|
||||
<Field label="Vorname" name="shippingFirstName" type="text" defaultValue={profile.shippingFirstName ?? ""} required />
|
||||
<Field label="Nachname" name="shippingLastName" type="text" defaultValue={profile.shippingLastName ?? ""} required />
|
||||
</div>
|
||||
<Field
|
||||
label="Firma (optional)"
|
||||
name="shippingCompanyName"
|
||||
type="text"
|
||||
defaultValue={profile.shippingCompanyName ?? ""}
|
||||
wrapperClassName="w-full"
|
||||
/>
|
||||
|
||||
<div className="w-full flex flex-col gap-2 items-start">
|
||||
<span className="text-label text-text-muted">Lieferart</span>
|
||||
<div className="flex w-full max-w-sm rounded-sm border border-border overflow-hidden">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShippingDeliveryMethod("address")}
|
||||
aria-pressed={shippingDeliveryMethod === "address"}
|
||||
className={`flex-1 py-3 text-body-sm font-bold transition-colors ${shippingDeliveryMethod === "address" ? "bg-brand text-text-primary" : "text-text-muted hover:text-text-primary"}`}
|
||||
>
|
||||
Lieferadresse
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShippingDeliveryMethod("packstation")}
|
||||
aria-pressed={shippingDeliveryMethod === "packstation"}
|
||||
className={`flex-1 py-3 text-body-sm font-bold border-l border-border transition-colors ${shippingDeliveryMethod === "packstation" ? "bg-brand text-text-primary" : "text-text-muted hover:text-text-primary"}`}
|
||||
>
|
||||
Packstation
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{shippingDeliveryMethod === "address" ? (
|
||||
<Field
|
||||
label="Straße und Hausnummer"
|
||||
name="shippingStreet"
|
||||
type="text"
|
||||
defaultValue={profile.shippingStreet ?? ""}
|
||||
required
|
||||
wrapperClassName="w-full"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-col sm:flex-row gap-4 w-full">
|
||||
<Field
|
||||
label="Packstationnummer"
|
||||
name="shippingPackstationNumber"
|
||||
type="text"
|
||||
defaultValue={profile.shippingPackstationNumber ?? ""}
|
||||
required
|
||||
/>
|
||||
<Field label="Postnummer" name="shippingPostNumber" type="text" defaultValue={profile.shippingPostNumber ?? ""} required />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 w-full">
|
||||
<Field label="PLZ" name="shippingZip" type="text" defaultValue={profile.shippingZip ?? ""} required />
|
||||
<Field label="Ort" name="shippingCity" type="text" defaultValue={profile.shippingCity ?? ""} required />
|
||||
</div>
|
||||
|
||||
<label className="flex flex-col gap-2 items-start w-full sm:w-1/2">
|
||||
<span className="text-label text-text-muted">Land</span>
|
||||
<select name="shippingCountry" defaultValue={profile.shippingCountry ?? "Deutschland"} required className={`${inputClass} bg-bg-base`}>
|
||||
{shippingCountries.map((c) => (
|
||||
<option key={c.name}>{c.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 w-full">
|
||||
<Field
|
||||
label="Kontakt-E-Mail (optional)"
|
||||
name="shippingContactEmail"
|
||||
type="email"
|
||||
defaultValue={profile.shippingContactEmail ?? ""}
|
||||
/>
|
||||
<Field
|
||||
label="Telefonnummer (optional)"
|
||||
name="shippingContactPhone"
|
||||
type="tel"
|
||||
defaultValue={profile.shippingContactPhone ?? ""}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-label text-text-muted">Werden nur dem Versanddienstleister übergeben, z. B. für Lieferbenachrichtigungen.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <p className="text-label text-red-600">{error}</p>}
|
||||
{success && <p className="text-label text-success">Gespeichert.</p>}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user