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.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.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}
/>
)}
);
};