feat: Wabenhain Honig-Shop Anwendung

Vollstaendige Next.js/Prisma-E-Commerce-Anwendung fuer eine Imkerei:
Shop, Warenkorb, Stripe-Checkout, Kundenkonto mit 2FA, Admin-Bereich
(Produkte, Ernten, Chargen, Bestellungen, Gutscheine, admin-editierbare
Website-Inhalte), i18n (DE/EN), Rate-Limiting/CSP/Audit-Log-Haertung,
PostgreSQL- und MySQL-Unterstuetzung ueber austauschbare Prisma-
Treiber-Adapter, Unit-/Komponenten-/E2E-Tests.
This commit is contained in:
Nick Szaunig committed 2026-08-17 14:30:28 +02:00
1 parent 5c76fcc266
commit c7927f2883
240 files changed
+26096 -137

No files matched your search

+49
View File
@@ -0,0 +1,49 @@
import type { Metadata } from "next";
import { getCurrentCustomer } from "@/lib/auth";
import { getAddressesForCustomer } from "@/lib/account-data";
import { getShippingTiers } from "@/lib/shipping";
import { getBulkDiscountTiers } from "@/lib/bulk-discount";
import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
import { CheckoutForm, type CheckoutInitialValues } from "./checkout-form";
// Vorausfuellen aus Kundendaten aendert sich pro Nutzer/Anmeldestatus, und
// Versandstaffeln koennen sich im Admin-Bereich jederzeit aendern —
// Rendering pro Request statt statischem Build entkoppelt zudem den
// Docker-Build-Schritt von einer zur Build-Zeit erreichbaren Datenbank.
export const dynamic = "force-dynamic";
export async function generateMetadata(): Promise<Metadata> {
const t = getDictionary(await getLocale());
return { title: t.checkoutPage.eyebrow };
}
export default async function CheckoutPage() {
const [tiers, bulkDiscountTiers] = await Promise.all([getShippingTiers(), getBulkDiscountTiers()]);
const tierValues = tiers.map((t) => ({
minQuantity: t.minQuantity,
maxQuantity: t.maxQuantity,
priceCents: t.priceCents,
}));
const customer = await getCurrentCustomer();
if (!customer) return <CheckoutForm tiers={tierValues} bulkDiscountTiers={bulkDiscountTiers} />;
// getAddressesForCustomer sortiert Standardadresse (isDefault) zuerst, sonst
// die zuletzt angelegte — die erste Adresse in der Liste ist also immer die
// sinnvollste Vorbelegung fuer den Checkout.
const addresses = await getAddressesForCustomer(customer.id);
const defaultAddress = addresses[0];
const initialValues: CheckoutInitialValues = {
firstName: customer.firstName,
lastName: customer.lastName,
email: customer.email,
phone: customer.phone ?? "",
street: defaultAddress?.street ?? "",
zip: defaultAddress?.zip ?? "",
city: defaultAddress?.city ?? "",
};
return <CheckoutForm initialValues={initialValues} tiers={tierValues} bulkDiscountTiers={bulkDiscountTiers} />;
}