This commit is contained in:
Your Name committed 2026-07-12 03:08:40 -04:00
1 parent 07f04e2065
commit 5d111b2866
2 files changed
+49 -66

No files matched your search

+48 -65
View File
@@ -12,84 +12,67 @@ export default function ProductImageGallery({ images, fallback, alt = 'Product I
const [currentIndex, setCurrentIndex] = useState(0)
// Sort images by order if they exist, else just use the array
const sortedImages = images?.length ? [...images].sort((a, b) => a.order - b.order) : []
const hasImages = sortedImages.length > 0
const sortedImages = images?.length ? [...images].sort((a, b) => (a.order || 0) - (b.order || 0)) : []
const currentImageUrl = hasImages ? sortedImages[currentIndex].url : fallback
if (sortedImages.length === 0) return null
const handleNext = () => {
setCurrentIndex((prev) => (prev === sortedImages.length - 1 ? 0 : prev + 1))
}
const handlePrev = () => {
setCurrentIndex((prev) => (prev === 0 ? sortedImages.length - 1 : prev - 1))
}
return (
<div className="space-y-4">
{/* Main Image */}
<div className="relative rounded-2xl overflow-hidden h-[480px] group bg-pop-bg shadow-sm border border-pop-black/10 flex items-center justify-center">
// If there's only one image, don't show thumbnails
if (sortedImages.length === 1) {
return (
<div className="relative aspect-square w-full flex-1 overflow-hidden rounded-3xl bg-pop-bg shadow-sm border border-pop-black/10 flex items-center justify-center">
<img
src={currentImageUrl}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
src={sortedImages[0].url}
className="w-full h-full object-cover"
alt={alt}
onError={(e) => { (e.target as HTMLImageElement).src = fallback }}
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/10 to-transparent pointer-events-none" />
{badgeText && (
<div className="absolute top-6 left-6 bg-pop-bg backdrop-blur-sm px-3 py-1.5 rounded-full text-[10px] font-bold uppercase tracking-widest text-pop-pink shadow-sm pointer-events-none">
<div className="absolute top-6 left-6 bg-pop-bg backdrop-blur-sm px-3 py-1.5 rounded-full text-[10px] font-bold uppercase tracking-widest text-pop-pink shadow-sm pointer-events-none z-10">
{badgeText}
</div>
)}
</div>
)
}
{/* Navigation Arrows */}
{sortedImages.length > 1 && (
<>
<button
onClick={handlePrev}
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 bg-pop-bg backdrop-blur-md rounded-full flex items-center justify-center text-pop-black opacity-0 group-hover:opacity-100 transition-opacity hover:bg-pop-bg hover:text-pop-pink hover:scale-110 shadow-sm"
aria-label="Previous image"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</button>
<button
onClick={handleNext}
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 bg-pop-bg backdrop-blur-md rounded-full flex items-center justify-center text-pop-black opacity-0 group-hover:opacity-100 transition-opacity hover:bg-pop-bg hover:text-pop-pink hover:scale-110 shadow-sm"
aria-label="Next image"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
</button>
</>
)}
return (
<div className="flex flex-col-reverse md:flex-row items-start gap-4 w-full">
{/* Thumbnails */}
<div className="flex flex-row md:flex-col gap-3 overflow-x-auto md:overflow-y-auto w-full md:w-20 lg:w-24 flex-none hide-scrollbar snap-x md:snap-y snap-mandatory pb-2 md:pb-0 md:pr-2">
{sortedImages.map((image, idx) => (
<button
key={image.id || idx}
type="button"
onClick={() => setCurrentIndex(idx)}
className={`relative aspect-square w-20 md:w-full flex-none overflow-hidden rounded-2xl border-2 transition-all snap-center ${
currentIndex === idx ? 'border-pop-pink shadow-md' : 'border-transparent opacity-60 hover:opacity-100'
}`}
>
<img
src={image.url}
alt={`${alt} thumbnail ${idx + 1}`}
className="w-full h-full object-cover"
onError={(e) => { (e.target as HTMLImageElement).src = fallback }}
/>
</button>
))}
</div>
{/* Thumbnails */}
{sortedImages.length > 1 && (
<div className="flex gap-4 overflow-x-auto pb-2 scrollbar-hide">
{sortedImages.map((image, idx) => (
<button
key={image.id || idx}
onClick={() => setCurrentIndex(idx)}
className={`relative flex-shrink-0 w-16 h-16 rounded-xl overflow-hidden border-2 transition-all ${
currentIndex === idx ? 'border-pop-black shadow-md' : 'border-transparent hover:border-pop-black/30'
}`}
>
<img
src={image.url}
alt={`${alt} thumbnail ${idx + 1}`}
className="w-full h-full object-cover"
onError={(e) => { (e.target as HTMLImageElement).src = fallback }}
/>
<div className={`absolute inset-0 bg-black/10 transition-opacity ${currentIndex === idx ? 'opacity-0' : 'opacity-100 group-hover:opacity-0'}`} />
</button>
))}
</div>
)}
{/* Main Image */}
<div className="relative aspect-square w-full flex-1 overflow-hidden rounded-3xl bg-pop-bg shadow-sm border border-pop-black/10 flex items-center justify-center">
<img
key={currentIndex}
src={sortedImages[currentIndex].url}
className="w-full h-full object-cover animate-fade-in"
alt={alt}
onError={(e) => { (e.target as HTMLImageElement).src = fallback }}
/>
{badgeText && (
<div className="absolute top-6 left-6 bg-pop-bg backdrop-blur-sm px-3 py-1.5 rounded-full text-[10px] font-bold uppercase tracking-widest text-pop-pink shadow-sm pointer-events-none z-10">
{badgeText}
</div>
)}
</div>
</div>
)
}
+1 -1
View File
@@ -185,7 +185,7 @@ export default function ProductDetailPage() {
<Navbar categories={categories} />
<div className="max-w-7xl mx-auto px-6 pt-24 md:pt-32 pb-24 space-y-12 md:space-y-24">
<div className="max-w-7xl mx-auto px-6 pt-24 md:pt-28 pb-24 space-y-8">
{/* 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">{t('common.home')}</Link>