const { useState, useEffect, useCallback, useRef } = React; const ProductCard = ({ product, onAddToCart, onClick, index = 0 }) => { const { user, cart } = useApp(); const [isAdding, setIsAdding] = useState(false); const currentInCart = cart.find(item => item.id === product.id)?.quantity || 0; const atStockLimit = product.stock_quantity > 0 && currentInCart >= product.stock_quantity; const handleAdd = (e) => { e.stopPropagation(); setIsAdding(true); onAddToCart(product); setTimeout(() => setIsAdding(false), 300); }; const buttonDisabled = product.stock_quantity <= 0 || !user || atStockLimit; const buttonText = !user ? '登入購買' : product.stock_quantity <= 0 ? '補貨中' : atStockLimit ? '已達庫存上限' : '加入購物車'; return (
onClick && onClick(product)} > {product.image_url ? ( {product.name} ) : (
)}
{product.name}
{product.weight_grams && (
{product.weight_grams}g · {(product.purity * 100).toFixed(2)}%
)} {user ? (
NT$ {product.price?.toLocaleString()} {product.show_tax_price != null && ( {product.show_tax_price ? '(含稅)' : '(未稅)'} )}
) : (
登入查看
)}
{product.stock_quantity > 0 ? `庫存 ${product.stock_quantity}` : '已售完'} {user?._ref && {user._ref}}
); }; const ProductDetailModal = ({ product, onClose, onAddToCart }) => { const { user, cart } = useApp(); const currentInCart = cart.find(item => item.id === product.id)?.quantity || 0; const atStockLimit = product.stock_quantity > 0 && currentInCart >= product.stock_quantity; const overlayRef = useRef(null); const contentRef = useRef(null); const closingRef = useRef(false); const animateClose = useCallback(() => { if (closingRef.current) return; closingRef.current = true; const content = contentRef.current; const overlay = overlayRef.current; if (!content || !overlay) { onClose(); return; } Motion.animate(content, { opacity: 0, transform: 'translateY(60px)' }, { duration: 0.2, easing: 'ease-in' }) .then(() => Motion.animate(overlay, { opacity: 0 }, { duration: 0.1 })) .then(() => onClose()); }, [onClose]); useEffect(() => { const overlay = overlayRef.current; const content = contentRef.current; if (!overlay || !content) return; Motion.animate(overlay, { opacity: [0, 1] }, { duration: 0.15 }); Motion.animate(content, { opacity: [0, 1], transform: ['translateY(60px) scale(0.97)', 'translateY(0px) scale(1)'] }, { duration: 0.35, easing: [0.22, 1, 0.36, 1] } ); const handleKey = (e) => { if (e.key === 'Escape') animateClose(); }; document.addEventListener('keydown', handleKey); document.body.style.overflow = 'hidden'; return () => { document.removeEventListener('keydown', handleKey); document.body.style.overflow = ''; }; }, [animateClose]); const handleBackdropClick = (e) => { if (e.target === overlayRef.current) animateClose(); }; const handleAddToCart = () => { if (onAddToCart(product) !== false) { animateClose(); } }; return (
{product.image_url ? ( {product.name} ) : (
)}
{product.name}
{product.description && (
{product.description}
)}
材質
{metalTypeLabel(product.metal_type)}
重量
{product.weight_grams ? `${product.weight_grams}g` : '—'}
純度
{product.purity ? `${(product.purity * 100).toFixed(2)}%` : '—'}
分類
{product.category || '—'}
{user ? (
NT$ {product.price?.toLocaleString()} {product.show_tax_price != null && ( {product.show_tax_price ? '(含稅)' : '(未稅)'} )}
) : (
登入查看價格
)}
{product.stock_quantity > 0 ? `庫存 ${product.stock_quantity} 件` : '已售完'} {user?._ref && {user._ref}}
); }; const ProductList = () => { const { products, cart, addToCart, showToast, metalPrices } = useApp(); const [selectedCategory, setSelectedCategory] = useState('all'); const [selectedProductId, setSelectedProductId] = useState(null); const selectedProduct = selectedProductId ? products.find(p => p.id === selectedProductId) : null; const categories = [...new Set(products.map(p => p.category).filter(Boolean))]; const filteredProducts = selectedCategory === 'all' ? products : products.filter(p => p.category === selectedCategory); const groupedProducts = filteredProducts.reduce((acc, product) => { const category = product.category || '其他'; if (!acc[category]) acc[category] = []; acc[category].push(product); return acc; }, {}); const handleAddToCart = (product) => { if (addToCart(product)) { const newQty = (cart.find(item => item.id === product.id)?.quantity || 0) + 1; const maxStock = product.stock_quantity || 0; showToast(`已加入: ${product.name}(${newQty}/${maxStock})`); return true; } return false; }; return (
{categories.length > 0 && (
{categories.map(cat => ( ))}
)} {Object.entries(groupedProducts).map(([category, items]) => (

{category}

{items.map((product, idx) => ( setSelectedProductId(product.id)} index={idx} /> ))}
))} {products.length === 0 && (

暫無商品

)} {selectedProduct && ( setSelectedProductId(null)} onAddToCart={handleAddToCart} /> )}
); };