move to gitea

This commit is contained in:
Mamadou Sall
2025-08-24 22:41:31 +02:00
commit 032b1d9452
122 changed files with 28723 additions and 0 deletions

View File

@@ -0,0 +1,215 @@
// src/components/product/ProductImageGallery.tsx
'use client';
import { useState } from 'react';
import Image from 'next/image';
import { WooCommerceImage } from '@/types/woocommerce';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { ChevronLeft, ChevronRight, Expand, Heart } from 'lucide-react';
import { cn } from '@/lib/utils';
import {
Dialog,
DialogContent,
DialogTrigger,
} from '@/components/ui/dialog';
interface ProductImageGalleryProps {
images: WooCommerceImage[];
productName: string;
isOnSale?: boolean;
discountPercentage?: number;
isFeatured?: boolean;
}
export default function ProductImageGallery({
images,
productName,
isOnSale = false,
discountPercentage = 0,
isFeatured = false,
}: ProductImageGalleryProps): JSX.Element {
const [currentImageIndex, setCurrentImageIndex] = useState(0);
const [isWishlisted, setIsWishlisted] = useState(false);
const hasMultipleImages = images.length > 1;
const currentImage = images[currentImageIndex] || images[0];
const goToPrevious = (): void => {
setCurrentImageIndex((prev) =>
prev === 0 ? images.length - 1 : prev - 1
);
};
const goToNext = (): void => {
setCurrentImageIndex((prev) =>
prev === images.length - 1 ? 0 : prev + 1
);
};
const goToImage = (index: number): void => {
setCurrentImageIndex(index);
};
const toggleWishlist = (): void => {
setIsWishlisted(!isWishlisted);
};
if (!currentImage) {
return (
<div className="aspect-[3/4] bg-gray-200 rounded-lg flex items-center justify-center">
<span className="text-gray-400">Aucune image disponible</span>
</div>
);
}
return (
<div className="space-y-4">
{/* Main Image */}
<div className="relative group">
<div className="aspect-[3/4] relative overflow-hidden rounded-lg bg-gray-100">
<Image
src={currentImage.src}
alt={currentImage.alt || productName}
fill
className="object-cover transition-transform duration-700 group-hover:scale-105"
sizes="(max-width: 768px) 100vw, 50vw"
priority
/>
{/* Badges */}
<div className="absolute top-4 left-4 flex flex-col gap-2 z-10">
{isOnSale && discountPercentage > 0 && (
<Badge className="bg-red-500 text-white">
-{discountPercentage}%
</Badge>
)}
{isFeatured && (
<Badge className="bg-black text-white">
Nouveauté
</Badge>
)}
</div>
{/* Wishlist Button */}
<Button
variant="ghost"
size="sm"
onClick={toggleWishlist}
className={cn(
"absolute top-4 right-4 w-10 h-10 p-0 rounded-full z-10 transition-all duration-300",
"bg-white/80 hover:bg-white backdrop-blur-sm",
isWishlisted && "bg-red-50 text-red-500 hover:bg-red-100"
)}
>
<Heart
className={cn(
"w-5 h-5 transition-all duration-300",
isWishlisted && "fill-current"
)}
/>
</Button>
{/* Navigation Arrows */}
{hasMultipleImages && (
<>
<Button
variant="ghost"
size="sm"
onClick={goToPrevious}
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 p-0 rounded-full bg-white/80 hover:bg-white backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity duration-300 z-10"
>
<ChevronLeft className="w-5 h-5" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={goToNext}
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 p-0 rounded-full bg-white/80 hover:bg-white backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity duration-300 z-10"
>
<ChevronRight className="w-5 h-5" />
</Button>
</>
)}
{/* Expand Button */}
<Dialog>
<DialogTrigger asChild>
<Button
variant="ghost"
size="sm"
className="absolute bottom-4 right-4 w-10 h-10 p-0 rounded-full bg-white/80 hover:bg-white backdrop-blur-sm opacity-0 group-hover:opacity-100 transition-opacity duration-300 z-10"
>
<Expand className="w-5 h-5" />
</Button>
</DialogTrigger>
<DialogContent className="max-w-4xl w-full h-full max-h-[90vh] p-0">
<div className="relative w-full h-full">
<Image
src={currentImage.src}
alt={currentImage.alt || productName}
fill
className="object-contain"
sizes="90vw"
/>
</div>
</DialogContent>
</Dialog>
{/* Image Counter */}
{hasMultipleImages && (
<div className="absolute bottom-4 left-4 bg-black/60 text-white px-3 py-1 rounded-full text-sm backdrop-blur-sm">
{currentImageIndex + 1} / {images.length}
</div>
)}
</div>
</div>
{/* Thumbnail Navigation */}
{hasMultipleImages && (
<div className="flex gap-3 overflow-x-auto pb-2">
{images.map((image, index) => (
<button
key={image.id}
onClick={() => goToImage(index)}
className={cn(
"relative flex-shrink-0 w-20 h-20 rounded-lg overflow-hidden border-2 transition-all duration-300",
currentImageIndex === index
? "border-black shadow-md"
: "border-gray-200 hover:border-gray-400"
)}
>
<Image
src={image.src}
alt={image.alt || `${productName} - Image ${index + 1}`}
fill
className="object-cover"
sizes="80px"
/>
</button>
))}
</div>
)}
{/* Mobile Dots Indicator */}
{hasMultipleImages && (
<div className="flex justify-center gap-2 md:hidden">
{images.map((_, index) => (
<button
key={index}
onClick={() => goToImage(index)}
className={cn(
"w-2 h-2 rounded-full transition-all duration-300",
currentImageIndex === index
? "bg-black"
: "bg-gray-300"
)}
/>
))}
</div>
)}
</div>
);
}