151 lines
6.1 KiB
TypeScript
151 lines
6.1 KiB
TypeScript
/**
|
|
* @component QuickViewModal
|
|
* @purpose Product Quick View modal dialog previewing images, dynamic variant selection, pricing, specs, and Add to Cart action.
|
|
* @a11y ARIA modal role, escape listener
|
|
* @dependencies cartStore, catalogService, lucide-react
|
|
*/
|
|
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import Image from 'next/image';
|
|
import { X, Star, ShoppingBag, Check, ShieldCheck, Truck } from 'lucide-react';
|
|
import { useCartStore } from '@/store/cartStore';
|
|
import { formatCurrency, getImageUrl } from '@/lib/utils';
|
|
|
|
export function QuickViewModal() {
|
|
const product = useCartStore((state) => state.quickViewProduct);
|
|
const setQuickViewProduct = useCartStore((state) => state.setQuickViewProduct);
|
|
const addToCart = useCartStore((state) => state.addToCart);
|
|
|
|
const [selectedVariantIndex, setSelectedVariantIndex] = useState(0);
|
|
const [added, setAdded] = useState(false);
|
|
|
|
if (!product) return null;
|
|
|
|
const variant = product.variants[selectedVariantIndex] || product.variants[0];
|
|
const mainImage = getImageUrl(product.images[0]?.image_url);
|
|
const price = variant?.price || 0;
|
|
const comparePrice = variant?.compare_price;
|
|
|
|
const handleAddToCart = () => {
|
|
addToCart(product, variant, 1);
|
|
setAdded(true);
|
|
setTimeout(() => setAdded(false), 2000);
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 overflow-y-auto flex items-center justify-center p-4">
|
|
{/* Backdrop */}
|
|
<div
|
|
onClick={() => setQuickViewProduct(null)}
|
|
className="fixed inset-0 bg-black/60 backdrop-blur-xs animate-in fade-in duration-200"
|
|
/>
|
|
|
|
{/* Modal Dialog Box */}
|
|
<div className="relative bg-white rounded-2xl shadow-2xl max-w-3xl w-full p-6 overflow-hidden z-10 animate-in zoom-in-95 duration-200 border border-gray-200">
|
|
<button
|
|
onClick={() => setQuickViewProduct(null)}
|
|
className="absolute top-4 right-4 p-1 text-gray-400 hover:text-gray-600 rounded-full hover:bg-gray-100 transition-colors z-20"
|
|
>
|
|
<X className="w-5 h-5" />
|
|
</button>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 items-center">
|
|
{/* Left Gallery Image */}
|
|
<div className="relative h-64 md:h-80 rounded-xl bg-gray-50 overflow-hidden border border-gray-200">
|
|
{mainImage ? (
|
|
<Image
|
|
src={mainImage}
|
|
alt={product.name}
|
|
fill
|
|
className="object-cover"
|
|
/>
|
|
) : (
|
|
<div className="w-full h-full flex items-center justify-center text-xs text-gray-400">No Image</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Right Product Specifications */}
|
|
<div className="flex flex-col justify-between h-full">
|
|
<div>
|
|
<span className="text-[10px] font-bold text-[#e4382f] uppercase tracking-wider">
|
|
Quick Preview
|
|
</span>
|
|
<h3 className="text-lg font-extrabold text-[#1b2559] mt-1 leading-snug">
|
|
{product.name}
|
|
</h3>
|
|
|
|
<div className="flex items-center gap-1 text-amber-400 text-xs mt-2 mb-3">
|
|
{Array.from({ length: 5 }).map((_, i) => (
|
|
<Star key={i} className="w-3.5 h-3.5 fill-current" />
|
|
))}
|
|
<span className="text-gray-400 text-[11px] ml-1 font-medium">(42 Reviews)</span>
|
|
</div>
|
|
|
|
<div className="flex items-baseline gap-2 mb-4">
|
|
<span className="text-2xl font-black text-[#e4382f]">{formatCurrency(price)}</span>
|
|
{comparePrice && (
|
|
<span className="text-sm text-gray-400 line-through">
|
|
{formatCurrency(comparePrice)}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<p className="text-xs text-gray-600 leading-relaxed mb-4">
|
|
{product.description}
|
|
</p>
|
|
|
|
{/* Variant Selector */}
|
|
{product.variants.length > 1 && (
|
|
<div className="mb-4">
|
|
<label className="block text-xs font-bold text-[#1b2559] mb-1.5">
|
|
Select Variant:
|
|
</label>
|
|
<div className="flex flex-wrap gap-2">
|
|
{product.variants.map((v, idx) => (
|
|
<button
|
|
key={v.variant_id}
|
|
onClick={() => setSelectedVariantIndex(idx)}
|
|
className={`px-3 py-1.5 text-xs font-bold rounded-lg border transition-all ${
|
|
selectedVariantIndex === idx
|
|
? 'border-[#e4382f] bg-red-50 text-[#e4382f]'
|
|
: 'border-gray-200 hover:border-gray-300 text-gray-700'
|
|
}`}
|
|
>
|
|
{v.sku} - {formatCurrency(v.price)}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<div className="flex items-center gap-4 text-[11px] text-gray-500 mb-4 border-t border-b border-gray-100 py-2">
|
|
<span className="flex items-center gap-1"><Truck className="w-3.5 h-3.5 text-[#e4382f]" /> Free Express Shipping</span>
|
|
<span className="flex items-center gap-1"><ShieldCheck className="w-3.5 h-3.5 text-[#e4382f]" /> 1 Year Warranty</span>
|
|
</div>
|
|
|
|
<button
|
|
onClick={handleAddToCart}
|
|
className={`w-full py-3 rounded-lg font-bold text-xs flex items-center justify-center gap-2 transition-colors shadow-md cursor-pointer ${
|
|
added ? 'bg-[#01b574] text-white' : 'bg-[#e4382f] hover:bg-[#c92e26] text-white'
|
|
}`}
|
|
>
|
|
{added ? (
|
|
<>
|
|
<Check className="w-4 h-4" /> Added to Cart!
|
|
</>
|
|
) : (
|
|
<>
|
|
<ShoppingBag className="w-4 h-4" /> Add to Cart Now
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|