diff --git a/README.md b/README.md index 4866a13..befad97 100644 --- a/README.md +++ b/README.md @@ -1999,6 +1999,43 @@ field-lock security logic is still tested in the **Payload backend's** own `test:unit` — see that repo's README's own "Tests" section, since that's where that logic actually lives. +## 2026-07-30 changes + +**Blog posts can have multiple categories.** `BlogPost.categories` is now +`string[]` (was a single `category: string`), joined with `", "` wherever +a single category used to render (`/blog`, `/blog/[slug]`, +`LivePostContent.tsx`, `components/Blog.tsx`). Backend field is +`Posts.categories`, a `hasMany` relationship — see the Payload backend's +own README. + +**Checkout's "Abweichende Lieferadresse" gained Firma + Kontakt-E-Mail/ +Telefon** (all optional) — handed to the shipping carrier, not used for +any customer communication (that stays the account email above). Backed +by new `Orders.shipping*` fields with no shipping-side `vatId` (billing- +only concept, deliberately not mirrored). + +**The customer profile (`/konto/profil`) can now store its own shipping +address**, mirroring the checkout's own "Abweichende Lieferadresse" +section field-for-field (same checkbox pattern, same fields incl. the new +Firma/Kontakt fields above) — prefills the checkout section for a +returning customer instead of always starting blank. Backed by +`Customers`' new "Lieferadresse" tab in the Payload backend. + +**Products can have an optional SKU** even without variants +(`Products.sku` — variants already had their own). Snapshotted onto each +order item at checkout (`app/api/checkout/route.ts` — variant SKU takes +precedence over the product's own) and shown as "Art.-Nr." on the invoice +PDF, the order-confirmation email, and `/konto/bestellungen/[orderNumber]`. + +**`@einfach-produktiv/invoicing` bumped to 0.2.7** for the SKU + +shipping-contact rendering above — re-run `npm install +@einfach-produktiv/invoicing` after pulling if your local `node_modules` +predates this. + +Full plan for an n8n-driven blog-post content-automation pipeline (not yet +built) lives in the Payload backend repo's `docs/blog-automation-plan.md`, +not duplicated here since it changes independently of this frontend. + ## Deployment - **Dockerfile**: 3-stage build (`deps` → `builder` → `runner`) with diff --git a/app/api/account/profile/route.ts b/app/api/account/profile/route.ts index 1b3e6d5..5fba295 100644 --- a/app/api/account/profile/route.ts +++ b/app/api/account/profile/route.ts @@ -16,10 +16,7 @@ export async function PATCH(request: Request) { const { firstName, lastName, - deliveryMethod, street, - packstationNumber, - postNumber, zip, city, country, @@ -44,7 +41,8 @@ export async function PATCH(request: Request) { !firstName || typeof lastName !== "string" || !lastName || - (deliveryMethod !== "address" && deliveryMethod !== "packstation") || + typeof street !== "string" || + !street || typeof zip !== "string" || !zip || typeof city !== "string" || @@ -54,12 +52,6 @@ export async function PATCH(request: Request) { ) { return NextResponse.json({ ok: false, reason: "Bitte alle Pflichtfelder ausfüllen." }, { status: 400 }); } - if (deliveryMethod === "address" && !street) { - return NextResponse.json({ ok: false, reason: "Bitte Straße und Hausnummer angeben." }, { status: 400 }); - } - if (deliveryMethod === "packstation" && (!packstationNumber || !postNumber)) { - return NextResponse.json({ ok: false, reason: "Bitte Packstation- und Postnummer angeben." }, { status: 400 }); - } // Both independently optional (see Customers.ts's own comment) — only // format-checked when actually provided, same as the backend field itself. const normalizedVatId = typeof vatId === "string" && vatId ? normalizeVatId(vatId) : undefined; @@ -97,10 +89,8 @@ export async function PATCH(request: Request) { const result = await updateCustomerProfile(session.token, session.customer.id, { firstName, lastName, - deliveryMethod, + deliveryMethod: "address", street, - packstationNumber, - postNumber, zip, city, country, diff --git a/app/konto/profil/components/ProfileForm.tsx b/app/konto/profil/components/ProfileForm.tsx index 5e3165a..057720b 100644 --- a/app/konto/profil/components/ProfileForm.tsx +++ b/app/konto/profil/components/ProfileForm.tsx @@ -34,7 +34,6 @@ export function ProfileForm({ shippingCountries: ShippingCountry[]; }) { 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", @@ -53,10 +52,12 @@ export function ProfileForm({ const body = { firstName: String(form.get("firstName") ?? ""), lastName: String(form.get("lastName") ?? ""), - deliveryMethod, + // Always a plain street address — same "Rechnungsadresse is never a + // Packstation" rule as /checkout's own billing card (an invoice + // needs a real postal address). Packstation is only ever offered + // below, in the optional "Lieferadresse" section. + deliveryMethod: "address" as const, street: String(form.get("street") ?? "") || undefined, - packstationNumber: String(form.get("packstationNumber") ?? "") || undefined, - postNumber: String(form.get("postNumber") ?? "") || undefined, zip: String(form.get("zip") ?? ""), city: String(form.get("city") ?? ""), country: String(form.get("country") ?? ""), @@ -136,43 +137,18 @@ export function ProfileForm({ /> -
- Lieferart -
- - -
-
- - {deliveryMethod === "address" ? ( - - ) : ( -
- - -
- )} + {/* Always a plain street address, no Lieferart/Packstation toggle — + matches /checkout's own Rechnungsadresse card exactly (an + invoice needs a real postal address). Packstation is only ever + offered below, in the optional "Lieferadresse" section. */} +