Added zoom function to product detail
This commit is contained in:
@@ -10,14 +10,13 @@
|
||||
width: 99%;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
// transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
/* Fixed frame container to prevent layout shifts */
|
||||
.fixedFrameContainer {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 1; /* Square container */
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -28,7 +27,7 @@
|
||||
/* Style for product card (non-detail view) images */
|
||||
.cardImage {
|
||||
width: auto;
|
||||
height: 300px; /* Increased height for card view */
|
||||
height: 300px;
|
||||
max-width: 100%;
|
||||
margin: auto;
|
||||
object-fit: contain;
|
||||
@@ -64,11 +63,6 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
// .transitioning img {
|
||||
// opacity: 0.8;
|
||||
// transition: opacity 0.3s ease-in-out;
|
||||
// }
|
||||
|
||||
.arrowButton {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@@ -96,7 +90,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Fixed positions for arrows - they won't move with image size changes */
|
||||
.leftArrow {
|
||||
left: 8px;
|
||||
}
|
||||
@@ -105,7 +98,6 @@
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
/* Make sure the carousel wrapper has a defined position */
|
||||
.carouselWrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
@@ -134,6 +126,7 @@
|
||||
border-radius: 50%;
|
||||
background-color: rgba(200, 200, 200, 0.7);
|
||||
transition: background-color 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
@@ -180,6 +173,125 @@
|
||||
border: 2px solid #ff6b00;
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.modalOverlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
animation: fadeIn 0.2s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.modalContent {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10001;
|
||||
transition: all 0.2s ease;
|
||||
color: #333;
|
||||
|
||||
&:hover {
|
||||
background-color: grey;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.modalImageContainer {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.modalImage {
|
||||
max-width: 90%;
|
||||
max-height: 80vh;
|
||||
object-fit: contain;
|
||||
transition: transform 0.3s ease;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
|
||||
.modalControls {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
padding: 10px;
|
||||
border-radius: 50px;
|
||||
backdrop-filter: blur(10px);
|
||||
z-index: 10001;
|
||||
}
|
||||
|
||||
.controlButton {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive styling */
|
||||
@media (max-width: 768px) {
|
||||
.thumbnailContainer {
|
||||
@@ -202,6 +314,37 @@
|
||||
}
|
||||
|
||||
.cardImage {
|
||||
height: 180px; /* Adjusted height for mobile view */
|
||||
height: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.modalControls {
|
||||
bottom: 10px;
|
||||
padding: 8px;
|
||||
gap: 6px;
|
||||
// flex-wrap: wrap;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.controlButton {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.modalImage {
|
||||
max-width: 95%;
|
||||
max-height: 70vh;
|
||||
}
|
||||
}
|
||||
@@ -5,54 +5,65 @@ const ImageCarousel = ({
|
||||
images,
|
||||
altText,
|
||||
showThumbnails = false,
|
||||
isDetailView = false, // Prop to differentiate between card and detail view
|
||||
isDetailView = false,
|
||||
}) => {
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [scale, setScale] = useState(1);
|
||||
const [rotation, setRotation] = useState(0);
|
||||
const [position, setPosition] = useState({ x: 0, y: 0 });
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
|
||||
|
||||
const touchStartX = useRef(0);
|
||||
const touchEndX = useRef(0);
|
||||
const carouselRef = useRef(null);
|
||||
const modalImageRef = useRef(null);
|
||||
|
||||
// Check if there are multiple images
|
||||
const hasMultipleImages = Array.isArray(images) && images.length > 1;
|
||||
|
||||
// Get current image URL
|
||||
const currentImage =
|
||||
hasMultipleImages && images[currentIndex]
|
||||
? images[currentIndex].images_400x400
|
||||
: images[0]?.images_400x400 || "";
|
||||
|
||||
// Auto-slide functionality - every 9 seconds
|
||||
// Auto-slide functionality
|
||||
useEffect(() => {
|
||||
if (!hasMultipleImages) return;
|
||||
if (!hasMultipleImages || isModalOpen) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setCurrentIndex((prev) => (prev === images.length - 1 ? 0 : prev + 1));
|
||||
}, 9000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [hasMultipleImages, images]);
|
||||
}, [hasMultipleImages, images, isModalOpen]);
|
||||
|
||||
// Reset zoom/rotation when modal closes or image changes
|
||||
useEffect(() => {
|
||||
if (!isModalOpen) {
|
||||
setScale(1);
|
||||
setRotation(0);
|
||||
setPosition({ x: 0, y: 0 });
|
||||
}
|
||||
}, [isModalOpen, currentIndex]);
|
||||
|
||||
// Navigate to previous image
|
||||
const handlePrev = (e) => {
|
||||
if (e) e.stopPropagation();
|
||||
if (!hasMultipleImages) return;
|
||||
setCurrentIndex((prev) => (prev === 0 ? images.length - 1 : prev - 1));
|
||||
};
|
||||
|
||||
// Navigate to next image
|
||||
const handleNext = (e) => {
|
||||
if (e) e.stopPropagation();
|
||||
if (!hasMultipleImages) return;
|
||||
setCurrentIndex((prev) => (prev === images.length - 1 ? 0 : prev + 1));
|
||||
};
|
||||
|
||||
// Handle thumbnail click
|
||||
const handleThumbnailClick = (index, e) => {
|
||||
if (e) e.stopPropagation();
|
||||
setCurrentIndex(index);
|
||||
};
|
||||
|
||||
// Touch event handlers
|
||||
const handleTouchStart = (e) => {
|
||||
touchStartX.current = e.touches[0].clientX;
|
||||
};
|
||||
@@ -66,19 +77,15 @@ const ImageCarousel = ({
|
||||
|
||||
const touchDiff = touchStartX.current - touchEndX.current;
|
||||
|
||||
// Swipe threshold - only respond to intentional swipes
|
||||
if (Math.abs(touchDiff) > 50) {
|
||||
if (touchDiff > 0) {
|
||||
// Swipe left -> Next image
|
||||
handleNext();
|
||||
} else {
|
||||
// Swipe right -> Previous image
|
||||
handlePrev();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Apply transition effect using CSS
|
||||
useEffect(() => {
|
||||
if (carouselRef.current) {
|
||||
carouselRef.current.classList.add(styles.transitioning);
|
||||
@@ -87,13 +94,122 @@ const ImageCarousel = ({
|
||||
if (carouselRef.current) {
|
||||
carouselRef.current.classList.remove(styles.transitioning);
|
||||
}
|
||||
}, 900); // Match this timing with CSS transition duration
|
||||
}, 900);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [currentIndex]);
|
||||
|
||||
// If there's only one image, just show it - applying different classes based on view
|
||||
// Modal zoom functions
|
||||
const handleZoomIn = (e) => {
|
||||
e.stopPropagation();
|
||||
setScale((prev) => Math.min(prev + 0.5, 5));
|
||||
};
|
||||
|
||||
const handleZoomOut = (e) => {
|
||||
e.stopPropagation();
|
||||
setScale((prev) => Math.max(prev - 0.5, 0.5));
|
||||
};
|
||||
|
||||
const handleRotateLeft = (e) => {
|
||||
e.stopPropagation();
|
||||
setRotation((prev) => prev - 90);
|
||||
};
|
||||
|
||||
const handleRotateRight = (e) => {
|
||||
e.stopPropagation();
|
||||
setRotation((prev) => prev + 90);
|
||||
};
|
||||
|
||||
const handleReset = (e) => {
|
||||
e.stopPropagation();
|
||||
setScale(1);
|
||||
setRotation(0);
|
||||
setPosition({ x: 0, y: 0 });
|
||||
};
|
||||
|
||||
const handleFullscreen = (e) => {
|
||||
e.stopPropagation();
|
||||
if (modalImageRef.current) {
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen();
|
||||
} else {
|
||||
modalImageRef.current.requestFullscreen();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Dragging functions
|
||||
const handleMouseDown = (e) => {
|
||||
if (scale > 1) {
|
||||
setIsDragging(true);
|
||||
setDragStart({
|
||||
x: e.clientX - position.x,
|
||||
y: e.clientY - position.y,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseMove = (e) => {
|
||||
if (isDragging && scale > 1) {
|
||||
setPosition({
|
||||
x: e.clientX - dragStart.x,
|
||||
y: e.clientY - dragStart.y,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setIsDragging(false);
|
||||
};
|
||||
|
||||
const openModal = (e) => {
|
||||
if (e) e.stopPropagation();
|
||||
if (isDetailView) {
|
||||
setIsModalOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const closeModal = (e) => {
|
||||
if (e) e.stopPropagation();
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
|
||||
// Keyboard controls
|
||||
useEffect(() => {
|
||||
if (!isModalOpen) return;
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
switch (e.key) {
|
||||
case "Escape":
|
||||
closeModal();
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
handlePrev();
|
||||
break;
|
||||
case "ArrowRight":
|
||||
handleNext();
|
||||
break;
|
||||
case "+":
|
||||
case "=":
|
||||
handleZoomIn(e);
|
||||
break;
|
||||
case "-":
|
||||
handleZoomOut(e);
|
||||
break;
|
||||
case "r":
|
||||
case "R":
|
||||
handleRotateRight(e);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [isModalOpen]);
|
||||
|
||||
if (!hasMultipleImages) {
|
||||
return (
|
||||
<div className={isDetailView ? styles.fixedFrameContainer : undefined}>
|
||||
@@ -103,7 +219,208 @@ const ImageCarousel = ({
|
||||
className={`${styles.productImage} ${
|
||||
isDetailView ? styles.detailImage : styles.cardImage
|
||||
}`}
|
||||
onClick={isDetailView ? openModal : undefined}
|
||||
style={{ cursor: isDetailView ? "pointer" : "default" }}
|
||||
/>
|
||||
{isDetailView && renderModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderModal() {
|
||||
if (!isModalOpen) return null;
|
||||
|
||||
return (
|
||||
<div className={styles.modalOverlay} onClick={closeModal}>
|
||||
<div
|
||||
className={styles.modalContent}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
onMouseLeave={handleMouseUp}
|
||||
>
|
||||
<button className={styles.closeButton} onClick={closeModal}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e3e3e3"
|
||||
>
|
||||
<path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div className={styles.modalImageContainer} ref={modalImageRef}>
|
||||
<img
|
||||
src={currentImage || "/placeholder.svg"}
|
||||
alt={altText || "Ürün resmi"}
|
||||
className={styles.modalImage}
|
||||
style={{
|
||||
transform: `scale(${scale}) rotate(${rotation}deg) translate(${position.x}px, ${position.y}px)`,
|
||||
cursor:
|
||||
scale > 1 ? (isDragging ? "grabbing" : "grab") : "default",
|
||||
}}
|
||||
draggable={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Control buttons */}
|
||||
<div className={styles.modalControls}>
|
||||
<button
|
||||
className={styles.controlButton}
|
||||
onClick={handlePrev}
|
||||
title="Önceki (←)"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<polyline points="15 18 9 12 15 6"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={styles.controlButton}
|
||||
onClick={handleZoomOut}
|
||||
title="Uzaklaştır (-)"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="8" y1="11" x2="14" y2="11"></line>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={styles.controlButton}
|
||||
onClick={handleZoomIn}
|
||||
title="Yakınlaştır (+)"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="11" y1="8" x2="11" y2="14"></line>
|
||||
<line x1="8" y1="11" x2="14" y2="11"></line>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={styles.controlButton}
|
||||
onClick={handleFullscreen}
|
||||
title="Tam ekran"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={styles.controlButton}
|
||||
onClick={handleRotateLeft}
|
||||
title="Sola döndür"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M2.5 2v6h6M2.66 15.57a10 10 0 1 0 .57-8.38"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={styles.controlButton}
|
||||
onClick={handleRotateRight}
|
||||
title="Sağa döndür (R)"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={styles.controlButton}
|
||||
onClick={handleReset}
|
||||
title="Sıfırla"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"></path>
|
||||
<path d="M21 3v5h-5"></path>
|
||||
<path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"></path>
|
||||
<path d="M3 21v-5h5"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={styles.controlButton}
|
||||
onClick={handleNext}
|
||||
title="Sonraki (→)"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<polyline points="9 18 15 12 9 6"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -125,6 +442,8 @@ const ImageCarousel = ({
|
||||
className={`${styles.imageWrapper} ${
|
||||
isDetailView ? styles.fixedFrameContainer : ""
|
||||
}`}
|
||||
onClick={isDetailView ? openModal : undefined}
|
||||
style={{ cursor: isDetailView ? "pointer" : "default" }}
|
||||
>
|
||||
<img
|
||||
src={currentImage || "/placeholder.svg"}
|
||||
@@ -135,7 +454,6 @@ const ImageCarousel = ({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Navigation arrows */}
|
||||
<button
|
||||
onClick={handlePrev}
|
||||
className={`${styles.arrowButton} ${styles.leftArrow}`}
|
||||
@@ -176,7 +494,6 @@ const ImageCarousel = ({
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Indicators (dots) */}
|
||||
<div className={styles.indicators}>
|
||||
{images.map((_, idx) => (
|
||||
<span
|
||||
@@ -190,7 +507,6 @@ const ImageCarousel = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Thumbnails - only show if showThumbnails is true */}
|
||||
{showThumbnails && (
|
||||
<div className={styles.thumbnailContainer}>
|
||||
{images.map((image, idx) => (
|
||||
@@ -205,12 +521,14 @@ const ImageCarousel = ({
|
||||
src={
|
||||
image.thumbnail || image.images_400x400 || "/placeholder.svg"
|
||||
}
|
||||
alt={`${altText || "Ürün"} thumbnail ${idx + 1}`}
|
||||
alt={`${altText || "Product"} thumbnail ${idx + 1}`}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDetailView && renderModal()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user