Working state: Serbian at root (/), English at /en, proper i18n structure
This commit is contained in:
182
src/components/product/ProductDetail.tsx
Normal file
182
src/components/product/ProductDetail.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { motion } from "framer-motion";
|
||||
import { WooProduct, formatPrice, getProductImage } from "@/lib/woocommerce";
|
||||
import { useCartStore } from "@/stores/cartStore";
|
||||
import ProductCard from "@/components/product/ProductCard";
|
||||
|
||||
interface ProductDetailProps {
|
||||
product: WooProduct;
|
||||
relatedProducts: WooProduct[];
|
||||
}
|
||||
|
||||
export default function ProductDetail({ product, relatedProducts }: ProductDetailProps) {
|
||||
const [selectedImage, setSelectedImage] = useState(0);
|
||||
const [quantity, setQuantity] = useState(1);
|
||||
const [activeTab, setActiveTab] = useState<"details" | "ingredients" | "usage">("details");
|
||||
const addItem = useCartStore((state) => state.addItem);
|
||||
|
||||
const images = product.images?.length > 0
|
||||
? product.images
|
||||
: [{ id: 0, src: "/placeholder-product.jpg", alt: product.name }];
|
||||
|
||||
const handleAddToCart = () => {
|
||||
addItem({
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
price: product.price || product.regular_price,
|
||||
quantity,
|
||||
image: images[0]?.src || "",
|
||||
sku: product.sku || "",
|
||||
});
|
||||
};
|
||||
|
||||
const stripHtml = (html: string) => {
|
||||
return html.replace(/<[^>]*>/g, "");
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="py-12 md:py-20 px-4">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
>
|
||||
<div className="relative aspect-square bg-background-ice mb-4">
|
||||
{images[selectedImage] && (
|
||||
<Image
|
||||
src={images[selectedImage].src}
|
||||
alt={images[selectedImage].alt || product.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{images.length > 1 && (
|
||||
<div className="flex gap-2 overflow-x-auto">
|
||||
{images.map((image, index) => (
|
||||
<button
|
||||
key={image.id}
|
||||
onClick={() => setSelectedImage(index)}
|
||||
className={`relative w-20 h-20 flex-shrink-0 ${
|
||||
selectedImage === index ? "ring-2 ring-foreground" : ""
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
src={image.src}
|
||||
alt={image.alt || product.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
>
|
||||
<h1 className="text-3xl md:text-4xl font-serif mb-4">
|
||||
{product.name}
|
||||
</h1>
|
||||
|
||||
<p className="text-2xl text-foreground-muted mb-6">
|
||||
{product.price ? formatPrice(product.price) : "Contact for price"}
|
||||
</p>
|
||||
|
||||
<div className="prose prose-sm max-w-none mb-8 text-foreground-muted">
|
||||
<p>{stripHtml(product.short_description || product.description.slice(0, 200))}</p>
|
||||
</div>
|
||||
|
||||
{product.stock_status === "instock" ? (
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="flex items-center border border-border">
|
||||
<button
|
||||
onClick={() => setQuantity(Math.max(1, quantity - 1))}
|
||||
className="px-4 py-3"
|
||||
>
|
||||
-
|
||||
</button>
|
||||
<span className="px-4 py-3">{quantity}</span>
|
||||
<button
|
||||
onClick={() => setQuantity(quantity + 1)}
|
||||
className="px-4 py-3"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleAddToCart}
|
||||
className="flex-1 py-3 bg-foreground text-white hover:bg-accent-dark transition-colors"
|
||||
>
|
||||
Add to Cart
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-3 bg-red-50 text-red-600 text-center mb-8">
|
||||
Out of Stock
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="border-t border-border/30">
|
||||
<div className="flex border-b border-border/30">
|
||||
{(["details", "ingredients", "usage"] as const).map((tab) => (
|
||||
<button
|
||||
key={tab}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
className={`flex-1 py-4 font-medium capitalize ${
|
||||
activeTab === tab
|
||||
? "border-b-2 border-foreground"
|
||||
: "text-foreground-muted"
|
||||
}`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="py-6 text-foreground-muted">
|
||||
{activeTab === "details" && (
|
||||
<p>{stripHtml(product.description)}</p>
|
||||
)}
|
||||
{activeTab === "ingredients" && (
|
||||
<p>Natural ingredients - Contact for detailed information.</p>
|
||||
)}
|
||||
{activeTab === "usage" && (
|
||||
<p>Apply to clean skin or hair. Use daily for best results.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{relatedProducts.length > 0 && (
|
||||
<section className="py-12 px-4 bg-background-ice">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<h2 className="text-2xl font-serif text-center mb-8">
|
||||
You May Also Like
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
{relatedProducts.map((product, index) => (
|
||||
<ProductCard key={product.id} product={product} index={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user