"use client"; import Link from "next/link"; import { useState } from "react"; import { motion } from "motion/react"; import { ContentImage } from "@/components/content-image"; import { QuantityStepper } from "@/components/quantity-stepper"; import { StarRating } from "@/components/star-rating"; import { Button } from "@/components/ui"; import { formatPrice, formatPricePerKg } from "@/lib/format"; import { useCartStore } from "@/store/cart-store"; import { useI18n } from "@/i18n/i18n-provider"; import type { ProductWithRelations } from "@/lib/data"; import type { ProductCategory } from "@/generated/prisma/client"; // Unterscheidet die Platzhalterfarbe im Shop-Grid nach Kategorie, statt // jedes Produkt im selben Honigton zu zeigen — verbessert die Scanbarkeit // des Rasters, bis echte Produktfotografie vorliegt. const categoryTone: Record = { HONIG: "honey", KERZEN: "forest", PROPOLIS: "cream", }; /** * Leichtgewichtiger Produkt-Verweis fuer Herkunfts-/Chargenseiten (Charge, * Ernte) — nur Bild, Name und Link, ohne Preis/Bestand/Warenkorb-Funktion. * Diese Seiten geht es um Nachweis/Herkunft, nicht ums Verkaufen. */ export function ProductTeaserLink({ slug, name, image, }: { slug: string; name: string; image?: string | null; }) { const { t } = useI18n(); return ( {name} {t.product.viewProduct} ); } export function TasteScale({ label, value, tone = "default", }: { label: string; value: number; /** "inverted" fuer Abschnitte auf dunklem Grund (z.B. bg-forest) — sonst waeren Label und leere Skalenpunkte auf dunklem Hintergrund kaum lesbar. */ tone?: "default" | "inverted"; }) { return (
{label}
{Array.from({ length: 5 }, (_, i) => ( ))}
); } function toCartItem(product: ProductWithRelations) { return { productId: product.id, slug: product.slug, name: product.name, weightGrams: product.weightGrams, priceCents: product.priceCents, image: product.images[0] ?? null, }; } export function AddToCartButton({ product, quantity = 1, variant, className = "", }: { product: ProductWithRelations; quantity?: number; variant?: "primary" | "secondary" | "ghost"; className?: string; }) { const { t } = useI18n(); const addItem = useCartStore((s) => s.addItem); const [justAdded, setJustAdded] = useState(false); return ( ); } /** * Wie AddToCartButton, aber mit einem Mengen-Steller davor — fuer die * Produktdetailseite, wo Kund:innen die Menge vor dem Hinzufuegen waehlen * koennen sollen, statt sie erst im Warenkorb nachtraeglich anzupassen. */ export function AddToCartWithQuantity({ product, maxQuantity, className = "", }: { product: ProductWithRelations; maxQuantity: number; className?: string; }) { const { t } = useI18n(); const addItem = useCartStore((s) => s.addItem); const [quantity, setQuantity] = useState(1); const [justAdded, setJustAdded] = useState(false); return (
); } export function ProductCard({ product, reviewSummary, }: { product: ProductWithRelations; /** Optional — nur die Shop-Uebersicht laedt Bewertungen gebuendelt fuer das ganze Raster. */ reviewSummary?: { average: number | null; count: number }; }) { const { t, locale } = useI18n(); const stock = product.inventory?.quantity ?? 0; return ( {stock === 0 ? ( {t.product.outOfStock} ) : stock <= 5 ? ( {t.product.onlyLeft(stock)} ) : null}

{product.name}

{product.tasteTags.join(" · ")}

0 ? "" : "invisible" }`} > {(reviewSummary?.average ?? 0).toFixed(1)} ({reviewSummary?.count ?? 0})

{product.weightGrams} g {formatPrice(product.priceCents, product.currency, locale)}

{formatPricePerKg(product.pricePerKgCents, product.currency, locale)}

{stock > 0 ? ( ) : null} {product.category === "HONIG" ? t.product.toHoney : t.product.toProduct}
); }