feat: add ProductDetailPage introduce ImageViewer and ImageLightbox components for enhanced image display

- ProductDetailPage is being used in lieu of a modal becaues Lightbox
image gallery (modal) being embedded in another modal was causing too
much buggy behavior
- Added ImageViewer component to manage and display product images with
features like lightbox, thumbnails, and image cycling controls.
- Replaced ProgressiveImageGallery with ImageViewer in
ProductDetailDialog and ProductDetailPage for improved user experience
and maintainability.
- Implemented useImageLightbox composable to handle lightbox
functionality, including keyboard navigation and swipe gestures.
- Updated routing to include a dedicated product detail page for better
navigation and user flow.

These changes significantly enhance the image viewing experience in the
product detail context, providing a more dynamic and user-friendly
interface.
This commit is contained in:
padreug 2025-09-28 12:22:39 +02:00
parent bff158cb74
commit 3aec5bbdb3
8 changed files with 1100 additions and 384 deletions

View file

@ -10,7 +10,7 @@
<div class="grid gap-6 md:grid-cols-2">
<!-- Product Images with Lightbox -->
<div class="space-y-4">
<ProgressiveImageGallery
<ImageViewer
:images="productImages"
:alt="product.name"
container-class="aspect-square rounded-lg overflow-hidden bg-gray-100"
@ -20,9 +20,12 @@
:show-thumbnails="productImages.length > 1"
:show-lightbox="true"
:show-badge="productImages.length > 1"
:show-cycle-controls="productImages.length > 1"
:is-embedded="true"
@error="handleImageError"
@image-change="handleImageChange"
@lightbox-open="handleLightboxOpen"
@lightbox-close="handleLightboxClose"
/>
</div>
@ -145,7 +148,7 @@ import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Badge } from '@/components/ui/badge'
import ProgressiveImageGallery from '@/components/ui/ProgressiveImageGallery.vue'
import ImageViewer from '@/components/ui/ImageViewer.vue'
import { ShoppingCart, Store, Plus, Minus, X } from 'lucide-vue-next'
import { useToast } from '@/core/composables/useToast'
import type { Product } from '../types/market'
@ -220,6 +223,16 @@ const handleImageChange = (index: number, src: string) => {
console.log('Image changed to index:', index, 'src:', src)
}
const handleLightboxOpen = (index: number) => {
// Optional: Handle lightbox open events if needed
console.log('Lightbox opened at index:', index)
}
const handleLightboxClose = () => {
// Optional: Handle lightbox close events if needed
console.log('Lightbox closed')
}
// Reset state when dialog opens/closes
watch(() => props.isOpen, (newVal) => {
if (newVal) {

View file

@ -27,23 +27,15 @@
/>
</div>
<!-- Product Detail Dialog - Now managed internally -->
<ProductDetailDialog
v-if="selectedProduct"
:product="selectedProduct"
:isOpen="showProductDetail"
@close="closeProductDetail"
@add-to-cart="handleDialogAddToCart"
/>
</LoadingErrorState>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { Package as EmptyIcon } from 'lucide-vue-next'
import ProductCard from './ProductCard.vue'
import ProductDetailDialog from './ProductDetailDialog.vue'
import LoadingErrorState from './LoadingErrorState.vue'
import type { Product } from '../types/market'
@ -99,29 +91,15 @@ const gridClasses = computed(() => {
return classes.join(' ')
})
// Internal state for product detail dialog
const showProductDetail = ref(false)
const selectedProduct = ref<Product | null>(null)
const router = useRouter()
// Handle view details internally
// Handle view details by navigating to product page
const handleViewDetails = (product: Product) => {
selectedProduct.value = product
showProductDetail.value = true
}
const closeProductDetail = () => {
showProductDetail.value = false
selectedProduct.value = null
router.push(`/market/product/${product.id}`)
}
// Handle add to cart from product card (quick add, quantity 1)
const handleAddToCart = (product: Product) => {
emit('add-to-cart', product, 1)
}
// Handle add to cart from dialog (with custom quantity)
const handleDialogAddToCart = (product: Product, quantity: number) => {
emit('add-to-cart', product, quantity)
closeProductDetail()
}
</script>