feat: implement i18n for product and category, add 5 new locales
This commit is contained in:
1 parent
b867063efb
commit
96d874233d
17 files changed
+557
-62
No files matched your search
@@ -1,10 +1,17 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useState } from 'react';
|
||||
import type { Product } from '../types';
|
||||
import type { Product, TranslatableString } from '../types';
|
||||
import { getCategoryImage, formatPrice } from '../types';
|
||||
import { Heart, ShoppingBag } from 'lucide-react';
|
||||
import { useWishlistStore } from '../store/wishlistStore';
|
||||
import { useCartStore } from '../store/cartStore';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function resolveStr(v: TranslatableString | null | undefined, lang: string): string {
|
||||
if (!v) return ''
|
||||
if (typeof v === 'string') return v
|
||||
return v[lang] ?? v[lang.slice(0, 2)] ?? v['th'] ?? v['en'] ?? Object.values(v)[0] ?? ''
|
||||
}
|
||||
|
||||
interface ProductCardProps {
|
||||
product: Product;
|
||||
@@ -15,8 +22,10 @@ export default function ProductCard({ product, enableQuickShop = false }: Produc
|
||||
const navigate = useNavigate();
|
||||
const { addItem, removeItem, isInWishlist } = useWishlistStore();
|
||||
const { addToCart } = useCartStore();
|
||||
const { i18n, t } = useTranslation();
|
||||
const isWished = isInWishlist(product.id);
|
||||
const [selectedVariant, setSelectedVariant] = useState(product.variants?.[0] || null);
|
||||
const displayName = resolveStr(product.name, i18n.language) || product.scent.name;
|
||||
|
||||
const handleWishlistClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -67,7 +76,7 @@ export default function ProductCard({ product, enableQuickShop = false }: Produc
|
||||
<div className="p-4 flex flex-col flex-1">
|
||||
<div className="mb-2">
|
||||
<h3 className="text-base md:text-lg font-bold text-pop-black leading-tight line-clamp-1 group-hover:text-spa-forest transition-colors">
|
||||
{product.scent.name}
|
||||
{displayName}
|
||||
</h3>
|
||||
<p className="text-[10px] md:text-xs text-spa-stone/60 mt-1 uppercase tracking-widest line-clamp-1 font-semibold">
|
||||
{product.category.name}
|
||||
@@ -109,7 +118,7 @@ export default function ProductCard({ product, enableQuickShop = false }: Produc
|
||||
</span>
|
||||
{!enableQuickShop && (
|
||||
<span className="text-[10px] md:text-xs font-bold text-spa-forest uppercase tracking-widest flex items-center gap-1 group-hover:translate-x-1 transition-transform">
|
||||
View Details →
|
||||
{t('product.viewDetails')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -125,7 +134,7 @@ export default function ProductCard({ product, enableQuickShop = false }: Produc
|
||||
variantId: selectedVariant.id,
|
||||
quantity: 1,
|
||||
price: selectedVariant.price || 0,
|
||||
productName: product.scent.name,
|
||||
productName: displayName,
|
||||
variantSize: selectedVariant.size,
|
||||
productImage: product.images?.[0]?.url || getCategoryImage(product.category.name)
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'
|
||||
import { useAuthStore } from '../store/authStore'
|
||||
import { API_BASE } from '../types'
|
||||
import { Star } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface Review {
|
||||
id: number
|
||||
@@ -15,6 +16,7 @@ interface Review {
|
||||
}
|
||||
|
||||
export default function ProductReviews({ productId }: { productId: number }) {
|
||||
const { t } = useTranslation()
|
||||
const [reviews, setReviews] = useState<Review[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const { customer } = useAuthStore()
|
||||
@@ -81,7 +83,7 @@ export default function ProductReviews({ productId }: { productId: number }) {
|
||||
<div className="flex flex-col md:flex-row gap-12 items-start">
|
||||
{/* Summary */}
|
||||
<div className="w-full md:w-1/3 bg-spa-light p-8 rounded-3xl text-center space-y-4">
|
||||
<h2 className="text-3xl font-bold text-pop-black">Reviews</h2>
|
||||
<h2 className="text-3xl font-bold text-pop-black">{t('product.reviews')}</h2>
|
||||
<div className="text-6xl font-black text-spa-forest">{avgRating}</div>
|
||||
<div className="flex justify-center gap-1">
|
||||
{[1, 2, 3, 4, 5].map(star => (
|
||||
|
||||
@@ -3,6 +3,10 @@ import { initReactI18next } from 'react-i18next'
|
||||
|
||||
import enTranslation from './locales/en/translation.json'
|
||||
import thTranslation from './locales/th/translation.json'
|
||||
import jaTranslation from "./locales/ja/translation.json"
|
||||
import zhTranslation from "./locales/zh/translation.json"
|
||||
import viTranslation from "./locales/vi/translation.json"
|
||||
import koTranslation from "./locales/ko/translation.json"
|
||||
|
||||
// Retrieve default lang from localStorage if set
|
||||
const savedLang = localStorage.getItem('homaroma_lang') || 'th'
|
||||
@@ -13,6 +17,10 @@ i18n
|
||||
resources: {
|
||||
en: { translation: enTranslation },
|
||||
th: { translation: thTranslation },
|
||||
ja: { translation: jaTranslation },
|
||||
zh: { translation: zhTranslation },
|
||||
vi: { translation: viTranslation },
|
||||
ko: { translation: koTranslation },
|
||||
},
|
||||
lng: savedLang, // default language (Thai)
|
||||
fallbackLng: 'en',
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
"continueShopping": "Continue Shopping",
|
||||
"subtotal": "Subtotal",
|
||||
"shippingNote": "Shipping and taxes calculated at checkout.",
|
||||
"proceed": "Proceed to Checkout"
|
||||
"proceed": "Proceed to Checkout",
|
||||
"addToCart": "Add to Cart",
|
||||
"totalPrice": "Total Price"
|
||||
},
|
||||
"footer": {
|
||||
"catchphrase": "Elevate your space with premium fragrances.",
|
||||
@@ -35,5 +37,23 @@
|
||||
"followUs": "Follow Us",
|
||||
"contactUs": "Contact Us",
|
||||
"rights": "All rights reserved."
|
||||
},
|
||||
"common": {
|
||||
"home": "Home",
|
||||
"shop": "Shop",
|
||||
"viewAll": "View All →",
|
||||
"backToShop": "Back to Shop"
|
||||
},
|
||||
"product": {
|
||||
"notFound": "Product not found",
|
||||
"premiumCollection": "Premium Collection",
|
||||
"selectSize": "Select Size",
|
||||
"details": "Product Details",
|
||||
"scent": "Scent",
|
||||
"category": "Category",
|
||||
"relatedProducts": "Related Products",
|
||||
"moreRelated": "More {{category}}",
|
||||
"reviews": "Reviews",
|
||||
"viewDetails": "View Details →"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"nav": {
|
||||
"home": "ホーム",
|
||||
"shop": "ショップ",
|
||||
"collections": "コレクション",
|
||||
"scentGuide": "香りガイド",
|
||||
"about": "私たちについて",
|
||||
"menu": "メニュー",
|
||||
"faq": "よくある質問",
|
||||
"contact": "お問い合わせ",
|
||||
"shopNow": "今すぐ購入",
|
||||
"findMyScent": "香りを探す ✨",
|
||||
"promotions": "プロモーション",
|
||||
"profile": "マイプロフィール",
|
||||
"search": {
|
||||
"title": "検索",
|
||||
"placeholder": "香りを検索...",
|
||||
"quickLinks": "クイックリンク",
|
||||
"allProducts": "すべての商品"
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "カート",
|
||||
"items": "{{count}} 点",
|
||||
"empty": "カートは空です。",
|
||||
"continueShopping": "ショッピングを続ける",
|
||||
"subtotal": "小計",
|
||||
"shippingNote": "送料と税金はチェックアウト時に計算されます。",
|
||||
"proceed": "レジへ進む",
|
||||
"addToCart": "カートに追加",
|
||||
"totalPrice": "合計金額"
|
||||
},
|
||||
"footer": {
|
||||
"catchphrase": "プレミアムな香りで空間をアップグレード",
|
||||
"explore": "探索",
|
||||
"support": "サポート",
|
||||
"followUs": "フォローする",
|
||||
"contactUs": "お問い合わせ",
|
||||
"rights": "All rights reserved."
|
||||
},
|
||||
"common": {
|
||||
"home": "ホーム",
|
||||
"shop": "ショップ",
|
||||
"viewAll": "すべて見る →",
|
||||
"backToShop": "ショップに戻る"
|
||||
},
|
||||
"product": {
|
||||
"notFound": "商品が見つかりません",
|
||||
"premiumCollection": "プレミアムコレクション",
|
||||
"selectSize": "サイズを選択",
|
||||
"details": "商品詳細",
|
||||
"scent": "香り",
|
||||
"category": "カテゴリー",
|
||||
"relatedProducts": "関連商品",
|
||||
"moreRelated": "関連する {{category}}",
|
||||
"reviews": "レビュー",
|
||||
"viewDetails": "詳細を見る →"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"nav": {
|
||||
"home": "홈",
|
||||
"shop": "쇼핑",
|
||||
"collections": "컬렉션",
|
||||
"scentGuide": "향기 가이드",
|
||||
"about": "소개",
|
||||
"menu": "메뉴",
|
||||
"faq": "자주 묻는 질문",
|
||||
"contact": "연락처",
|
||||
"shopNow": "지금 쇼핑하기",
|
||||
"findMyScent": "나의 향기 찾기 ✨",
|
||||
"promotions": "프로모션",
|
||||
"profile": "내 프로필",
|
||||
"search": {
|
||||
"title": "검색",
|
||||
"placeholder": "향기 검색...",
|
||||
"quickLinks": "빠른 링크",
|
||||
"allProducts": "모든 제품"
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "장바구니",
|
||||
"items": "{{count}} 개",
|
||||
"empty": "장바구니가 비어 있습니다.",
|
||||
"continueShopping": "쇼핑 계속하기",
|
||||
"subtotal": "소계",
|
||||
"shippingNote": "배송비 및 세금은 결제 시 계산됩니다.",
|
||||
"proceed": "결제 진행",
|
||||
"addToCart": "장바구니 담기",
|
||||
"totalPrice": "총 가격"
|
||||
},
|
||||
"footer": {
|
||||
"catchphrase": "프리미엄 향기로 공간을 업그레이드하세요.",
|
||||
"explore": "탐색",
|
||||
"support": "지원",
|
||||
"followUs": "팔로우",
|
||||
"contactUs": "연락처",
|
||||
"rights": "모든 권리 보유."
|
||||
},
|
||||
"common": {
|
||||
"home": "홈",
|
||||
"shop": "쇼핑",
|
||||
"viewAll": "모두 보기 →",
|
||||
"backToShop": "쇼핑으로 돌아가기"
|
||||
},
|
||||
"product": {
|
||||
"notFound": "상품을 찾을 수 없습니다",
|
||||
"premiumCollection": "프리미엄 컬렉션",
|
||||
"selectSize": "사이즈 선택",
|
||||
"details": "상품 상세 정보",
|
||||
"scent": "향기",
|
||||
"category": "카테고리",
|
||||
"relatedProducts": "관련 상품",
|
||||
"moreRelated": "더 많은 {{category}}",
|
||||
"reviews": "리뷰",
|
||||
"viewDetails": "상세 보기 →"
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,9 @@
|
||||
"continueShopping": "เลือกซื้อสินค้าต่อ",
|
||||
"subtotal": "ยอดรวมยอด",
|
||||
"shippingNote": "ค่าจัดส่งและภาษีจะถูกคำนวณท้ายสุด",
|
||||
"proceed": "ดำเนินการชำระเงิน"
|
||||
"proceed": "ดำเนินการชำระเงิน",
|
||||
"addToCart": "เพิ่มลงตะกร้า",
|
||||
"totalPrice": "ราคารวม"
|
||||
},
|
||||
"footer": {
|
||||
"catchphrase": "ยกระดับพื้นที่ของคุณด้วยน้ำหอมระดับพรีเมียม",
|
||||
@@ -35,5 +37,23 @@
|
||||
"followUs": "ติดตามเรา",
|
||||
"contactUs": "ติดต่อสอบถาม",
|
||||
"rights": "สงวนลิขสิทธิ์."
|
||||
},
|
||||
"common": {
|
||||
"home": "หน้าหลัก",
|
||||
"shop": "ร้านค้า",
|
||||
"viewAll": "ดูทั้งหมด →",
|
||||
"backToShop": "กลับไปร้านค้า"
|
||||
},
|
||||
"product": {
|
||||
"notFound": "ไม่พบสินค้า",
|
||||
"premiumCollection": "พรีเมียมคอลเลกชัน",
|
||||
"selectSize": "เลือกขนาด",
|
||||
"details": "รายละเอียดสินค้า",
|
||||
"scent": "กลิ่น",
|
||||
"category": "หมวดหมู่",
|
||||
"relatedProducts": "สินค้าที่เกี่ยวข้อง",
|
||||
"moreRelated": "เพิ่มเติมในหมวด {{category}}",
|
||||
"reviews": "รีวิว",
|
||||
"viewDetails": "ดูรายละเอียด →"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"nav": {
|
||||
"home": "Trang chủ",
|
||||
"shop": "Cửa hàng",
|
||||
"collections": "Bộ sưu tập",
|
||||
"scentGuide": "Hướng dẫn mùi hương",
|
||||
"about": "Giới thiệu",
|
||||
"menu": "Menu",
|
||||
"faq": "Câu hỏi thường gặp",
|
||||
"contact": "Liên hệ",
|
||||
"shopNow": "Mua ngay",
|
||||
"findMyScent": "Tìm mùi hương của bạn ✨",
|
||||
"promotions": "Khuyến mãi",
|
||||
"profile": "Hồ sơ của tôi",
|
||||
"search": {
|
||||
"title": "Tìm kiếm",
|
||||
"placeholder": "Tìm mùi hương...",
|
||||
"quickLinks": "Liên kết nhanh",
|
||||
"allProducts": "Tất cả sản phẩm"
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Giỏ hàng",
|
||||
"items": "{{count}} sản phẩm",
|
||||
"empty": "Giỏ hàng của bạn đang trống.",
|
||||
"continueShopping": "Tiếp tục mua sắm",
|
||||
"subtotal": "Tổng phụ",
|
||||
"shippingNote": "Phí vận chuyển và thuế được tính lúc thanh toán.",
|
||||
"proceed": "Tiến hành thanh toán",
|
||||
"addToCart": "Thêm vào giỏ",
|
||||
"totalPrice": "Tổng giá"
|
||||
},
|
||||
"footer": {
|
||||
"catchphrase": "Nâng tầm không gian với hương thơm cao cấp.",
|
||||
"explore": "Khám phá",
|
||||
"support": "Hỗ trợ",
|
||||
"followUs": "Theo dõi chúng tôi",
|
||||
"contactUs": "Liên hệ",
|
||||
"rights": "Bảo lưu mọi quyền."
|
||||
},
|
||||
"common": {
|
||||
"home": "Trang chủ",
|
||||
"shop": "Cửa hàng",
|
||||
"viewAll": "Xem tất cả →",
|
||||
"backToShop": "Quay lại cửa hàng"
|
||||
},
|
||||
"product": {
|
||||
"notFound": "Không tìm thấy sản phẩm",
|
||||
"premiumCollection": "Bộ sưu tập Cao cấp",
|
||||
"selectSize": "Chọn kích thước",
|
||||
"details": "Chi tiết sản phẩm",
|
||||
"scent": "Mùi hương",
|
||||
"category": "Danh mục",
|
||||
"relatedProducts": "Sản phẩm liên quan",
|
||||
"moreRelated": "Thêm {{category}}",
|
||||
"reviews": "Đánh giá",
|
||||
"viewDetails": "Xem chi tiết →"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"nav": {
|
||||
"home": "首页",
|
||||
"shop": "商店",
|
||||
"collections": "系列",
|
||||
"scentGuide": "香气指南",
|
||||
"about": "关于我们",
|
||||
"menu": "菜单",
|
||||
"faq": "常见问题",
|
||||
"contact": "联系我们",
|
||||
"shopNow": "立即购买",
|
||||
"findMyScent": "寻找我的香气 ✨",
|
||||
"promotions": "促销",
|
||||
"profile": "我的主页",
|
||||
"search": {
|
||||
"title": "搜索",
|
||||
"placeholder": "搜索香气...",
|
||||
"quickLinks": "快速链接",
|
||||
"allProducts": "所有商品"
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "购物车",
|
||||
"items": "{{count}} 件",
|
||||
"empty": "您的购物车是空的。",
|
||||
"continueShopping": "继续购物",
|
||||
"subtotal": "小计",
|
||||
"shippingNote": "运费和税费将在结账时计算。",
|
||||
"proceed": "去结账",
|
||||
"addToCart": "加入购物车",
|
||||
"totalPrice": "总价"
|
||||
},
|
||||
"footer": {
|
||||
"catchphrase": "用高级香氛提升您的空间。",
|
||||
"explore": "探索",
|
||||
"support": "支持",
|
||||
"followUs": "关注我们",
|
||||
"contactUs": "联系我们",
|
||||
"rights": "版权所有。"
|
||||
},
|
||||
"common": {
|
||||
"home": "首页",
|
||||
"shop": "商店",
|
||||
"viewAll": "查看全部 →",
|
||||
"backToShop": "返回商店"
|
||||
},
|
||||
"product": {
|
||||
"notFound": "未找到商品",
|
||||
"premiumCollection": "高级系列",
|
||||
"selectSize": "选择尺寸",
|
||||
"details": "商品详情",
|
||||
"scent": "香气",
|
||||
"category": "类别",
|
||||
"relatedProducts": "相关商品",
|
||||
"moreRelated": "更多 {{category}}",
|
||||
"reviews": "评价",
|
||||
"viewDetails": "查看详情 →"
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,15 @@ export default function ProductDetailPage() {
|
||||
const [cmsSections, setCmsSections] = useState<PageSection[]>([])
|
||||
|
||||
const { addToCart } = useCartStore()
|
||||
const { i18n } = useTranslation()
|
||||
const { i18n, t } = useTranslation()
|
||||
|
||||
function resolveStr(v: TranslatableString | null | undefined, lang: string): string {
|
||||
if (!v) return ''
|
||||
if (typeof v === 'string') return v
|
||||
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 || ''
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${API_BASE}/categories?lang=${i18n.language}`)
|
||||
@@ -104,9 +112,9 @@ export default function ProductDetailPage() {
|
||||
<Navbar categories={categories} />
|
||||
<div className="pt-40 text-center space-y-4">
|
||||
<p className="text-4xl">⚠️</p>
|
||||
<p className="text-spa-stone font-medium">{error ?? 'Product not found'}</p>
|
||||
<p className="text-spa-stone font-medium">{error ?? t('product.notFound')}</p>
|
||||
<Link to="/shop" className="text-spa-forest hover:underline text-sm font-bold">
|
||||
Back to Shop
|
||||
{t('common.backToShop')}
|
||||
</Link>
|
||||
</div>
|
||||
<Footer categories={categories} />
|
||||
@@ -123,7 +131,7 @@ export default function ProductDetailPage() {
|
||||
variantId: currentVariant.id,
|
||||
quantity: 1,
|
||||
price: currentVariant.price || product.price || 0,
|
||||
productName: product.scent.name,
|
||||
productName: displayName,
|
||||
variantSize: currentVariant.size,
|
||||
productImage: product.images?.[0]?.url || getCategoryImage(product.category.name)
|
||||
})
|
||||
@@ -147,15 +155,15 @@ export default function ProductDetailPage() {
|
||||
<div className="max-w-7xl mx-auto px-6 pt-24 md:pt-32 pb-24 space-y-12 md:space-y-24">
|
||||
{/* Breadcrumb */}
|
||||
<div className="flex items-center flex-wrap gap-2 text-[10px] md:text-xs text-spa-stone/60 uppercase tracking-widest font-semibold mb-6 md:mb-0">
|
||||
<Link to="/" className="hover:text-spa-forest transition-colors">Home</Link>
|
||||
<Link to="/" className="hover:text-spa-forest transition-colors">{t('common.home')}</Link>
|
||||
<span>/</span>
|
||||
<Link to="/shop" className="hover:text-spa-forest transition-colors">Shop</Link>
|
||||
<Link to="/shop" className="hover:text-spa-forest transition-colors">{t('common.shop')}</Link>
|
||||
<span>/</span>
|
||||
<Link to={`/category/${product.category.id}`} className="hover:text-spa-forest transition-colors">
|
||||
{product.category.name}
|
||||
</Link>
|
||||
<span>/</span>
|
||||
<span className="text-spa-forest">{product.scent.name}</span>
|
||||
<span className="text-spa-forest">{displayName}</span>
|
||||
</div>
|
||||
|
||||
{/* Product Detail */}
|
||||
@@ -177,10 +185,10 @@ export default function ProductDetailPage() {
|
||||
{product.category.name}
|
||||
</p>
|
||||
<h1 className="text-5xl font-bold leading-tight text-pop-black">
|
||||
{product.scent.name}
|
||||
{displayName}
|
||||
</h1>
|
||||
<p className="text-spa-stone/80 text-sm uppercase tracking-widest font-semibold">
|
||||
Premium Collection
|
||||
{t('product.premiumCollection')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -201,7 +209,7 @@ export default function ProductDetailPage() {
|
||||
{/* Variant Selector */}
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs uppercase tracking-widest text-spa-stone/80 font-bold">
|
||||
Select Size
|
||||
{t('product.selectSize')}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{product.variants.map((v, i) => (
|
||||
@@ -229,7 +237,7 @@ export default function ProductDetailPage() {
|
||||
onClick={handleAddToCart}
|
||||
className="flex-1 py-4 bg-spa-forest text-white font-bold rounded-lg tracking-widest uppercase hover:bg-spa-stone hover:shadow-lg transition-all duration-300"
|
||||
>
|
||||
Add to Cart
|
||||
{t('cart.addToCart')}
|
||||
</button>
|
||||
<button className="bg-white border border-spa-stone/20 text-spa-stone p-4 rounded-lg hover:border-spa-forest hover:text-spa-forest shadow-sm transition-all">
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
@@ -241,11 +249,11 @@ export default function ProductDetailPage() {
|
||||
{/* Meta */}
|
||||
<div className="border-t border-spa-stone/10 pt-6 grid grid-cols-2 gap-4 text-sm text-spa-stone/80">
|
||||
<div>
|
||||
<span className="text-xs uppercase tracking-widest block mb-1 font-semibold text-spa-stone/60">Scent</span>
|
||||
<span className="text-xs uppercase tracking-widest block mb-1 font-semibold text-spa-stone/60">{t('product.scent')}</span>
|
||||
<span className="text-pop-black font-bold">{product.scent.name}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs uppercase tracking-widest block mb-1 font-semibold text-spa-stone/60">Category</span>
|
||||
<span className="text-xs uppercase tracking-widest block mb-1 font-semibold text-spa-stone/60">{t('product.category')}</span>
|
||||
<Link
|
||||
to={`/category/${product.category.id}`}
|
||||
className="text-pop-black font-bold hover:text-spa-forest transition-colors"
|
||||
@@ -260,7 +268,7 @@ export default function ProductDetailPage() {
|
||||
{/* Full Details Section */}
|
||||
{resolveTranslatable(product.details, i18n.language) && (
|
||||
<div className="pt-16 border-t border-spa-stone/10 space-y-6">
|
||||
<h2 className="text-3xl font-bold text-pop-black">Product Details</h2>
|
||||
<h2 className="text-3xl font-bold text-pop-black">{t('product.details')}</h2>
|
||||
<div
|
||||
className="prose prose-spa prose-lg max-w-4xl product-editorial text-spa-stone"
|
||||
dangerouslySetInnerHTML={{ __html: resolveTranslatable(product.details, i18n.language) }}
|
||||
@@ -277,15 +285,15 @@ export default function ProductDetailPage() {
|
||||
<div className="flex justify-between items-end">
|
||||
<div>
|
||||
<p className="text-spa-forest uppercase tracking-[0.3em] text-xs font-bold opacity-80 mb-2">
|
||||
More {product.category.name}
|
||||
{t('product.moreRelated', { category: typeof product.category.name === 'string' ? product.category.name : (product.category.name as any)?.[i18n.language] || (product.category.name as any)?.['en'] })}
|
||||
</p>
|
||||
<h2 className="text-3xl font-bold text-pop-black">Related Products</h2>
|
||||
<h2 className="text-3xl font-bold text-pop-black">{t('product.relatedProducts')}</h2>
|
||||
</div>
|
||||
<Link
|
||||
to={`/category/${product.category.id}`}
|
||||
className="text-spa-forest text-sm font-bold uppercase tracking-widest hover:underline"
|
||||
>
|
||||
View All →
|
||||
{t('common.viewAll')}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 md:gap-8">
|
||||
@@ -332,7 +340,7 @@ export default function ProductDetailPage() {
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[10px] uppercase tracking-widest text-spa-stone/60 font-semibold">
|
||||
{product.variants && product.variants.length > 1 ? currentVariant?.size : 'Total Price'}
|
||||
{product.variants && product.variants.length > 1 ? currentVariant?.size : t('cart.totalPrice')}
|
||||
</span>
|
||||
<span className="text-xl font-bold text-rose-500">
|
||||
{formatPrice(currentVariant?.price)}
|
||||
@@ -342,7 +350,7 @@ export default function ProductDetailPage() {
|
||||
onClick={handleAddToCart}
|
||||
className="flex-1 py-3.5 bg-spa-forest text-white font-bold rounded-lg tracking-widest uppercase hover:bg-spa-stone shadow-sm transition-all flex justify-center items-center gap-2"
|
||||
>
|
||||
Add to Cart
|
||||
{t('cart.addToCart')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user