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

+77
View File
@@ -0,0 +1,77 @@
"use client";
import { create } from "zustand";
import { persist } from "zustand/middleware";
export interface CartItem {
productId: string;
slug: string;
name: string;
weightGrams: number;
priceCents: number;
image: string | null;
quantity: number;
}
interface CartState {
items: CartItem[];
isOpen: boolean;
openCart: () => void;
closeCart: () => void;
addItem: (item: Omit<CartItem, "quantity">, quantity?: number) => void;
removeItem: (productId: string) => void;
setQuantity: (productId: string, quantity: number) => void;
clear: () => void;
}
export const useCartStore = create<CartState>()(
persist(
(set) => ({
items: [],
isOpen: false,
openCart: () => set({ isOpen: true }),
closeCart: () => set({ isOpen: false }),
addItem: (item, quantity = 1) =>
set((state) => {
const existing = state.items.find((i) => i.productId === item.productId);
if (existing) {
return {
items: state.items.map((i) =>
i.productId === item.productId
? { ...i, quantity: i.quantity + quantity }
: i
),
isOpen: true,
};
}
return { items: [...state.items, { ...item, quantity }], isOpen: true };
}),
removeItem: (productId) =>
set((state) => ({
items: state.items.filter((i) => i.productId !== productId),
})),
setQuantity: (productId, quantity) =>
set((state) => ({
items: state.items
.map((i) => (i.productId === productId ? { ...i, quantity } : i))
.filter((i) => i.quantity > 0),
})),
clear: () => set({ items: [] }),
}),
{
name: "wabenhain-cart",
// Nur die Artikel persistieren — "isOpen" ist ein rein transienter
// UI-Zustand und darf nach einem Seiten-Reload nicht wiederhergestellt
// werden (sonst poppt das Cart-Drawer nach jedem Reload erneut auf).
partialize: (state) => ({ items: state.items }),
}
)
);
export function cartTotalCents(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.priceCents * item.quantity, 0);
}
export function cartItemCount(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.quantity, 0);
}