import { useEffect, useState } from "react"; import { Link, useNavigate, useParams } from "react-router-dom"; import { apiFetch } from "../../api/client"; import { formatCents, type Category, type PriceTier, type Product, type Unit } from "../../api/types"; import { useCart } from "./CartContext"; import { useMediaUrl } from "./useMediaUrl"; import { useSiteSettings } from "./SiteSettingsContext"; import GalleryThumb from "./GalleryThumb"; import { useI18n } from "../../i18n/LanguageContext"; export default function ProductDetailPage() { const { t } = useI18n(); const site = useSiteSettings(); const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const { addItem } = useCart(); const [product, setProduct] = useState(null); const [categories, setCategories] = useState([]); const [siblings, setSiblings] = useState([]); const [tiers, setTiers] = useState([]); const [units, setUnits] = useState([]); const [gallery, setGallery] = useState([]); const [loading, setLoading] = useState(true); const [notFound, setNotFound] = useState(false); const [selectedTierId, setSelectedTierId] = useState(""); const [multiplier, setMultiplier] = useState(1); const [added, setAdded] = useState(false); const primaryImage = useMediaUrl(product?.primary_media_id); useEffect(() => { if (!id) return; setLoading(true); setNotFound(false); setAdded(false); apiFetch(`/api/products/${id}`) .then((p) => { setProduct(p); return Promise.all([ apiFetch<{ price_tiers: PriceTier[] }>(`/api/price-tiers/by-product/${p.id}`), apiFetch<{ units: Unit[] }>("/api/units"), apiFetch<{ gallery: { media_id: string }[] }>(`/api/product-gallery/${p.id}`), apiFetch<{ categories: Category[] }>("/api/categories"), apiFetch<{ products: Product[] }>(`/api/products?category_id=${p.category_id}`), ]); }) .then(([t, u, g, c, siblingList]) => { setTiers(t.price_tiers); setUnits(u.units); setGallery(g.gallery.map((item) => item.media_id)); setSelectedTierId(t.price_tiers[0]?.id ?? ""); setCategories(c.categories); setSiblings(siblingList.products); }) .catch(() => setNotFound(true)) .finally(() => setLoading(false)); }, [id]); function unitSymbol(unitId: string) { return units.find((u) => u.id === unitId)?.symbol ?? ""; } function tierLabel(t: PriceTier) { return `${t.quantity} ${unitSymbol(t.unit_id)} — ${formatCents(t.price_cents)}`; } function handleAddToCart() { if (!product) return; const tier = tiers.find((t) => t.id === selectedTierId); if (!tier) return; addItem({ productId: product.id, productName: product.name, priceTierId: tier.id, unitLabel: `${tier.quantity} ${unitSymbol(tier.unit_id)}`, unitPriceCents: tier.price_cents, multiplier, }); setAdded(true); } if (loading) return
{t("common.loading")}
; if (notFound || !product) return

{t("storefront.productDetail.notFound")}

; const category = categories.find((c) => c.id === product.category_id); const siblingIndex = siblings.findIndex((p) => p.id === product.id); const previousProduct = siblingIndex > 0 ? siblings[siblingIndex - 1] : undefined; const nextProduct = siblingIndex >= 0 && siblingIndex < siblings.length - 1 ? siblings[siblingIndex + 1] : undefined; return (

← {t("storefront.productDetail.backLink")}

{primaryImage && {product.name}} {gallery.length > 0 && (
{gallery.map((mediaId) => ( ))}
)}

{product.name}

{category && ( {category.name} )} {product.description &&

{product.description}

} {tiers.length === 0 ? (

{t("storefront.productDetail.notAvailable")}

) : !(site.orders_enabled && site.customer_login_enabled) ? (

{t("storefront.productDetail.chooseQuantity")}

    {tiers.map((tier) => (
  • {tierLabel(tier)}
  • ))}
) : (

{t("storefront.productDetail.chooseQuantity")}

setMultiplier(Math.max(1, Number(e.target.value)))} />
{added && ( )}
{added &&

{t("storefront.productDetail.added")}

}
)}
{(previousProduct || nextProduct) && (
{previousProduct ? ( ← {t("storefront.productDetail.previousProduct")} {previousProduct.name} ) : ( )} {nextProduct && ( {t("storefront.productDetail.nextProduct")} → {nextProduct.name} )}
)}
); }