x
This commit is contained in:
1 parent
992335a52b
commit
fb46bd71ed
10 files changed
+24
-18
No files matched your search
@@ -566,7 +566,7 @@ export default function ProductEditPage() {
|
|||||||
<tbody className="divide-y divide-white/5">
|
<tbody className="divide-y divide-white/5">
|
||||||
{variants.length > 0 ? variants.map((v, idx) => (
|
{variants.length > 0 ? variants.map((v, idx) => (
|
||||||
<tr key={idx} className="hover:bg-white/[0.02] transition-colors">
|
<tr key={idx} className="hover:bg-white/[0.02] transition-colors">
|
||||||
{options.length > 0 ? options.map((opt, i) => {
|
{options.length > 0 ? options.map((_, i) => {
|
||||||
const optVal = i === 0 ? v.option1 : i === 1 ? v.option2 : v.option3
|
const optVal = i === 0 ? v.option1 : i === 1 ? v.option2 : v.option3
|
||||||
return (
|
return (
|
||||||
<td key={i} className="py-3 px-4 text-sm font-medium text-white">{optVal || '-'}</td>
|
<td key={i} className="py-3 px-4 text-sm font-medium text-white">{optVal || '-'}</td>
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export default function ProductsPage() {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
categoryId: selectedProduct.categoryId,
|
categoryId: selectedProduct.categoryId,
|
||||||
variants: selectedProduct.variants.map(v => ({ size: v.size, price: v.price })),
|
variants: selectedProduct.variants.map(v => ({ option1: v.option1, option2: v.option2, option3: v.option3, sku: v.sku, barcode: v.barcode, stock: v.stock, price: v.price })),
|
||||||
images: updatedImages
|
images: updatedImages
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -145,7 +145,7 @@ export default function ProductsPage() {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
categoryId: selectedProduct.categoryId,
|
categoryId: selectedProduct.categoryId,
|
||||||
variants: selectedProduct.variants.map(v => ({ size: v.size, price: v.price })),
|
variants: selectedProduct.variants.map(v => ({ option1: v.option1, option2: v.option2, option3: v.option3, sku: v.sku, barcode: v.barcode, stock: v.stock, price: v.price })),
|
||||||
images: updatedImages
|
images: updatedImages
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -324,9 +324,9 @@ export default function ProductsPage() {
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
<p className="text-xs font-medium text-silver-metallic">{product.variants?.length || 0} Sizes</p>
|
<p className="text-xs font-medium text-silver-metallic">{product.variants?.length || 0} Variants</p>
|
||||||
<p className="text-[10px] text-slate-600 truncate max-w-[120px]">
|
<p className="text-[10px] text-slate-600 truncate max-w-[120px]">
|
||||||
{product.variants?.map(v => v.size).join(', ') || '-'}
|
{product.variants?.map(v => [v.option1, v.option2, v.option3].filter(Boolean).join('/')).join(', ') || '-'}
|
||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4">
|
<td className="px-6 py-4">
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default function ProductCard({ product, enableQuickShop = false, category
|
|||||||
const { i18n, t } = useTranslation();
|
const { i18n, t } = useTranslation();
|
||||||
const isWished = isInWishlist(product.id);
|
const isWished = isInWishlist(product.id);
|
||||||
const [selectedVariant, setSelectedVariant] = useState(product.variants?.[0] || null);
|
const [selectedVariant, setSelectedVariant] = useState(product.variants?.[0] || null);
|
||||||
const displayName = resolveStr(product.name, i18n.language) || product.scent.name;
|
const displayName = resolveStr(product.name, i18n.language) || (product.brand ? product.brand.name : '');
|
||||||
const displayCategoryName = categoryNameOverride || resolveStr(product.category.name, i18n.language);
|
const displayCategoryName = categoryNameOverride || resolveStr(product.category.name, i18n.language);
|
||||||
|
|
||||||
const handleWishlistClick = (e: React.MouseEvent) => {
|
const handleWishlistClick = (e: React.MouseEvent) => {
|
||||||
@@ -52,7 +52,7 @@ export default function ProductCard({ product, enableQuickShop = false, category
|
|||||||
<img
|
<img
|
||||||
src={product.images?.[0]?.url || getCategoryImage(displayCategoryName)}
|
src={product.images?.[0]?.url || getCategoryImage(displayCategoryName)}
|
||||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
|
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
|
||||||
alt={product.scent.name}
|
alt={displayName}
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
(e.target as HTMLImageElement).style.display = 'none';
|
(e.target as HTMLImageElement).style.display = 'none';
|
||||||
}}
|
}}
|
||||||
@@ -100,7 +100,7 @@ export default function ProductCard({ product, enableQuickShop = false, category
|
|||||||
: 'border-pop-black/10 bg-transparent text-pop-black/70 hover:border-pop-black/30 hover:bg-pop-black/5'
|
: 'border-pop-black/10 bg-transparent text-pop-black/70 hover:border-pop-black/30 hover:bg-pop-black/5'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{variant.size}
|
{[variant.option1, variant.option2, variant.option3].filter(Boolean).join(' / ')}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -131,7 +131,7 @@ export default function ProductCard({ product, enableQuickShop = false, category
|
|||||||
quantity: 1,
|
quantity: 1,
|
||||||
price: selectedVariant.price || 0,
|
price: selectedVariant.price || 0,
|
||||||
productName: displayName,
|
productName: displayName,
|
||||||
variantSize: selectedVariant.size,
|
variantSize: [selectedVariant.option1, selectedVariant.option2, selectedVariant.option3].filter(Boolean).join(' / '),
|
||||||
productImage: product.images?.[0]?.url || getCategoryImage(displayCategoryName)
|
productImage: product.images?.[0]?.url || getCategoryImage(displayCategoryName)
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
"selectSize": "Select Size",
|
"selectSize": "Select Size",
|
||||||
"details": "Product Details",
|
"details": "Product Details",
|
||||||
"scent": "Scent",
|
"scent": "Scent",
|
||||||
|
"brand": "Brand",
|
||||||
"category": "Category",
|
"category": "Category",
|
||||||
"relatedProducts": "Related Products",
|
"relatedProducts": "Related Products",
|
||||||
"moreRelated": "More {{category}}",
|
"moreRelated": "More {{category}}",
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
"selectSize": "サイズを選択",
|
"selectSize": "サイズを選択",
|
||||||
"details": "商品詳細",
|
"details": "商品詳細",
|
||||||
"scent": "香り",
|
"scent": "香り",
|
||||||
|
"brand": "ブランド",
|
||||||
"category": "カテゴリー",
|
"category": "カテゴリー",
|
||||||
"relatedProducts": "関連商品",
|
"relatedProducts": "関連商品",
|
||||||
"moreRelated": "関連する {{category}}",
|
"moreRelated": "関連する {{category}}",
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
"selectSize": "사이즈 선택",
|
"selectSize": "사이즈 선택",
|
||||||
"details": "상품 상세 정보",
|
"details": "상품 상세 정보",
|
||||||
"scent": "향기",
|
"scent": "향기",
|
||||||
|
"brand": "브랜드",
|
||||||
"category": "카테고리",
|
"category": "카테고리",
|
||||||
"relatedProducts": "관련 상품",
|
"relatedProducts": "관련 상품",
|
||||||
"moreRelated": "더 많은 {{category}}",
|
"moreRelated": "더 많은 {{category}}",
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
"selectSize": "เลือกขนาด",
|
"selectSize": "เลือกขนาด",
|
||||||
"details": "รายละเอียดสินค้า",
|
"details": "รายละเอียดสินค้า",
|
||||||
"scent": "กลิ่น",
|
"scent": "กลิ่น",
|
||||||
|
"brand": "แบรนด์",
|
||||||
"category": "หมวดหมู่",
|
"category": "หมวดหมู่",
|
||||||
"relatedProducts": "สินค้าที่เกี่ยวข้อง",
|
"relatedProducts": "สินค้าที่เกี่ยวข้อง",
|
||||||
"moreRelated": "เพิ่มเติมในหมวด {{category}}",
|
"moreRelated": "เพิ่มเติมในหมวด {{category}}",
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
"selectSize": "Chọn kích thước",
|
"selectSize": "Chọn kích thước",
|
||||||
"details": "Chi tiết sản phẩm",
|
"details": "Chi tiết sản phẩm",
|
||||||
"scent": "Mùi hương",
|
"scent": "Mùi hương",
|
||||||
|
"brand": "Thương hiệu",
|
||||||
"category": "Danh mục",
|
"category": "Danh mục",
|
||||||
"relatedProducts": "Sản phẩm liên quan",
|
"relatedProducts": "Sản phẩm liên quan",
|
||||||
"moreRelated": "Thêm {{category}}",
|
"moreRelated": "Thêm {{category}}",
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
"selectSize": "选择尺寸",
|
"selectSize": "选择尺寸",
|
||||||
"details": "商品详情",
|
"details": "商品详情",
|
||||||
"scent": "香气",
|
"scent": "香气",
|
||||||
|
"brand": "品牌",
|
||||||
"category": "类别",
|
"category": "类别",
|
||||||
"relatedProducts": "相关商品",
|
"relatedProducts": "相关商品",
|
||||||
"moreRelated": "更多 {{category}}",
|
"moreRelated": "更多 {{category}}",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default function ProductDetailPage() {
|
|||||||
return v[lang] ?? v[lang.slice(0, 2)] ?? v['th'] ?? v['en'] ?? Object.values(v)[0] ?? ''
|
return v[lang] ?? v[lang.slice(0, 2)] ?? v['th'] ?? v['en'] ?? Object.values(v)[0] ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayName = resolveStr(product?.name, i18n.language) || product?.scent?.name || ''
|
const displayName = resolveStr(product?.name, i18n.language) || product?.brand?.name || ''
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`${API_BASE}/categories?lang=${i18n.language}`)
|
fetch(`${API_BASE}/categories?lang=${i18n.language}`)
|
||||||
@@ -134,7 +134,7 @@ export default function ProductDetailPage() {
|
|||||||
quantity: 1,
|
quantity: 1,
|
||||||
price: currentVariant.price || product.price || 0,
|
price: currentVariant.price || product.price || 0,
|
||||||
productName: displayName,
|
productName: displayName,
|
||||||
variantSize: currentVariant.size,
|
variantSize: [currentVariant.option1, currentVariant.option2, currentVariant.option3].filter(Boolean).join(' / '),
|
||||||
productImage: product.images?.[0]?.url || getCategoryImage(typeof product.category.name === 'string' ? product.category.name : (product.category.name as any)?.en || '')
|
productImage: product.images?.[0]?.url || getCategoryImage(typeof product.category.name === 'string' ? product.category.name : (product.category.name as any)?.en || '')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -175,7 +175,7 @@ export default function ProductDetailPage() {
|
|||||||
<ProductImageGallery
|
<ProductImageGallery
|
||||||
images={product.images || []}
|
images={product.images || []}
|
||||||
fallback={getCategoryImage(typeof product.category.name === 'string' ? product.category.name : (product.category.name as any)?.en || '')}
|
fallback={getCategoryImage(typeof product.category.name === 'string' ? product.category.name : (product.category.name as any)?.en || '')}
|
||||||
alt={product.scent.name}
|
alt={displayName}
|
||||||
badgeText={typeof product.category.name === 'string' ? product.category.name : (product.category.name as any)?.[i18n.language] || (product.category.name as any)?.en || ''}
|
badgeText={typeof product.category.name === 'string' ? product.category.name : (product.category.name as any)?.[i18n.language] || (product.category.name as any)?.en || ''}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,7 +204,7 @@ export default function ProductDetailPage() {
|
|||||||
{formatPrice(currentVariant?.price)}
|
{formatPrice(currentVariant?.price)}
|
||||||
</span>
|
</span>
|
||||||
{currentVariant && (
|
{currentVariant && (
|
||||||
<span className="text-spa-stone/60 text-sm font-semibold">/ {currentVariant.size}</span>
|
<span className="text-spa-stone/60 text-sm font-semibold">/ {[currentVariant.option1, currentVariant.option2, currentVariant.option3].filter(Boolean).join(' / ')}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -224,7 +224,7 @@ export default function ProductDetailPage() {
|
|||||||
: 'bg-spa-light text-spa-stone border-spa-stone/20 hover:border-spa-forest hover:text-spa-forest shadow-sm'
|
: 'bg-spa-light text-spa-stone border-spa-stone/20 hover:border-spa-forest hover:text-spa-forest shadow-sm'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{v.size}
|
{[v.option1, v.option2, v.option3].filter(Boolean).join(' / ')}
|
||||||
<span className={`block text-[10px] mt-0.5 ${selectedVariant === i ? 'text-spa-light' : 'text-spa-stone/60'}`}>
|
<span className={`block text-[10px] mt-0.5 ${selectedVariant === i ? 'text-spa-light' : 'text-spa-stone/60'}`}>
|
||||||
{formatPrice(v.price)}
|
{formatPrice(v.price)}
|
||||||
</span>
|
</span>
|
||||||
@@ -251,8 +251,8 @@ export default function ProductDetailPage() {
|
|||||||
{/* Meta */}
|
{/* Meta */}
|
||||||
<div className="border-t border-spa-stone/10 pt-6 grid grid-cols-2 gap-4 text-sm text-spa-stone/80">
|
<div className="border-t border-spa-stone/10 pt-6 grid grid-cols-2 gap-4 text-sm text-spa-stone/80">
|
||||||
<div>
|
<div>
|
||||||
<span className="text-xs uppercase tracking-widest block mb-1 font-semibold text-spa-stone/60">{t('product.scent')}</span>
|
<span className="text-xs uppercase tracking-widest block mb-1 font-semibold text-spa-stone/60">{t('product.brand', 'Brand')}</span>
|
||||||
<span className="text-pop-black font-bold">{product.scent.name}</span>
|
<span className="text-pop-black font-bold">{product.brand?.name || ''}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="text-xs uppercase tracking-widest block mb-1 font-semibold text-spa-stone/60">{t('product.category')}</span>
|
<span className="text-xs uppercase tracking-widest block mb-1 font-semibold text-spa-stone/60">{t('product.category')}</span>
|
||||||
@@ -337,7 +337,7 @@ export default function ProductDetailPage() {
|
|||||||
: 'bg-spa-light border-spa-stone/20 text-spa-stone'
|
: 'bg-spa-light border-spa-stone/20 text-spa-stone'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{v.size}
|
{[v.option1, v.option2, v.option3].filter(Boolean).join(' / ')}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -346,7 +346,7 @@ export default function ProductDetailPage() {
|
|||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="text-[10px] uppercase tracking-widest text-spa-stone/60 font-semibold">
|
<span className="text-[10px] uppercase tracking-widest text-spa-stone/60 font-semibold">
|
||||||
{product.variants && product.variants.length > 1 ? currentVariant?.size : t('cart.totalPrice')}
|
{product.variants && product.variants.length > 1 ? [currentVariant?.option1, currentVariant?.option2, currentVariant?.option3].filter(Boolean).join(' / ') : t('cart.totalPrice')}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xl font-bold text-rose-500">
|
<span className="text-xl font-bold text-rose-500">
|
||||||
{formatPrice(currentVariant?.price)}
|
{formatPrice(currentVariant?.price)}
|
||||||
|
|||||||
Reference in new issue
Block a user