diff --git a/src/app/api/shop/cart-stock/route.ts b/src/app/api/shop/cart-stock/route.ts new file mode 100644 index 0000000..d7ec011 --- /dev/null +++ b/src/app/api/shop/cart-stock/route.ts @@ -0,0 +1,43 @@ +import { NextResponse } from "next/server"; +import { getProductsByIds } from "@/lib/data"; +import { rateLimit, getClientIp } from "@/lib/rate-limit"; + +const MAX_IDS_PER_REQUEST = 50; +const STOCK_ATTEMPTS_PER_WINDOW = 60; +const STOCK_WINDOW_MS = 60 * 1000; + +/** + * Aktueller Lagerbestand fuer eine Liste von Produkt-IDs — der Warenkorb + * (Zustand/localStorage) kennt nur den Stand beim letzten "Hinzufuegen", + * nicht den aktuellen. Cart-Drawer und Warenkorb-Seite fragen das hierueber + * live ab, damit "Ausverkauft"/"Nur noch N"-Hinweise auch dort erscheinen, + * nicht nur auf der Produktkarte/-detailseite (siehe product-card.tsx). + * Rein oeffentliche, bereits im Shop sichtbare Bestandsdaten — keine + * Authentifizierung noetig, aber wie jeder andere oeffentliche Endpunkt + * rate-limitiert. + */ +export async function GET(request: Request) { + const ip = await getClientIp(); + if (!rateLimit(`cart-stock:${ip}`, STOCK_ATTEMPTS_PER_WINDOW, STOCK_WINDOW_MS)) { + return NextResponse.json({ stock: {} }, { status: 429 }); + } + + const idsParam = new URL(request.url).searchParams.get("ids") ?? ""; + const ids = idsParam + .split(",") + .map((id) => id.trim()) + .filter(Boolean) + .slice(0, MAX_IDS_PER_REQUEST); + + if (ids.length === 0) { + return NextResponse.json({ stock: {} }); + } + + const products = await getProductsByIds(ids); + const stock: Record = {}; + for (const product of products) { + stock[product.id] = product.isActive ? (product.inventory?.quantity ?? 0) : 0; + } + + return NextResponse.json({ stock }); +} diff --git a/src/app/bestellung-verfolgen/page.tsx b/src/app/bestellung-verfolgen/page.tsx index b3639f5..d1fc6db 100644 --- a/src/app/bestellung-verfolgen/page.tsx +++ b/src/app/bestellung-verfolgen/page.tsx @@ -104,7 +104,10 @@ export default async function TrackOrderPage(props: PageProps<"/bestellung-verfo diff --git a/src/app/checkout/checkout-form.tsx b/src/app/checkout/checkout-form.tsx index 92b48de..94665d3 100644 --- a/src/app/checkout/checkout-form.tsx +++ b/src/app/checkout/checkout-form.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, type FormEvent } from "react"; +import { useState, type FormEvent, type HTMLAttributes } from "react"; import { useRouter } from "next/navigation"; import { Container, SectionHeading, Button } from "@/components/ui"; import { TrustBadges } from "@/components/trust-badges"; @@ -115,15 +115,37 @@ export function CheckoutForm({
- - + +
- +
@@ -158,16 +180,35 @@ export function CheckoutForm({ name="street" label={t.checkoutPage.streetLabel} defaultValue={initialValues?.street} + autoComplete="street-address" required />
- - + +
) : null} - +