From bff158cb74ba6f3fa26416965d9dc7b050b38159 Mon Sep 17 00:00:00 2001 From: padreug Date: Sun, 28 Sep 2025 04:05:20 +0200 Subject: [PATCH] feat: enhance product management with new dialog and image handling features - Introduced ProductDetailDialog component for displaying detailed product information, including images, price, and availability. - Implemented image cycling functionality in ProductCard for better user experience when viewing multiple product images. - Enhanced CreateProductDialog to support image uploads with improved validation and navigation protection during form editing. - Added logic to manage uploaded images and ensure proper handling of existing product images. - Updated MarketPage to integrate the new ProductDetailDialog, allowing users to view product details seamlessly. These changes significantly improve the product management experience, enhancing both the display and interaction with product images. --- src/components/ui/ProgressiveImageGallery.vue | 355 ++++++++++++++++++ .../market/components/CreateProductDialog.vue | 115 ++++-- src/modules/market/components/ProductCard.vue | 101 ++++- .../market/components/ProductDetailDialog.vue | 82 ++-- src/modules/market/views/MarketPage.vue | 1 - 5 files changed, 561 insertions(+), 93 deletions(-) create mode 100644 src/components/ui/ProgressiveImageGallery.vue diff --git a/src/components/ui/ProgressiveImageGallery.vue b/src/components/ui/ProgressiveImageGallery.vue new file mode 100644 index 0000000..935bbdb --- /dev/null +++ b/src/components/ui/ProgressiveImageGallery.vue @@ -0,0 +1,355 @@ + + + + + \ No newline at end of file diff --git a/src/modules/market/components/CreateProductDialog.vue b/src/modules/market/components/CreateProductDialog.vue index 93d6042..3232804 100644 --- a/src/modules/market/components/CreateProductDialog.vue +++ b/src/modules/market/components/CreateProductDialog.vue @@ -127,11 +127,17 @@ Product Images - Add images to showcase your product -
- -

Image upload coming soon

-
+ Add up to 5 images to showcase your product. The first image will be the primary display image. +
@@ -218,12 +224,13 @@ import { FormLabel, FormMessage, } from '@/components/ui/form' -import { Package } from 'lucide-vue-next' import type { NostrmarketAPI, Stall, CreateProductRequest } from '../services/nostrmarketAPI' import type { Product } from '../types/market' import { auth } from '@/composables/useAuthService' import { useToast } from '@/core/composables/useToast' import { injectService, SERVICE_TOKENS } from '@/core/di-container' +import ImageUpload from '@/modules/base/components/ImageUpload.vue' +import type { ImageUploadService } from '@/modules/base/services/ImageUploadService' // Props and emits interface Props { @@ -242,11 +249,13 @@ const emit = defineEmits<{ // Services const nostrmarketAPI = injectService(SERVICE_TOKENS.NOSTRMARKET_API) as NostrmarketAPI const paymentService = injectService(SERVICE_TOKENS.PAYMENT_SERVICE) as any +const imageService = injectService(SERVICE_TOKENS.IMAGE_UPLOAD_SERVICE) const toast = useToast() // Local state const isCreating = ref(false) const createError = ref(null) +const uploadedImages = ref([]) // Track uploaded images with their metadata // Computed properties const isEditMode = computed(() => !!props.product?.id) @@ -311,18 +320,31 @@ const updateProduct = async (formData: any) => { return } - const { - name, - description, - price, - quantity, - categories, - images, - active, - use_autoreply, - autoreply_message + const { + name, + description, + price, + quantity, + categories, + active, + use_autoreply, + autoreply_message } = formData + // Get uploaded image URLs from the image service + const images: string[] = [] + if (uploadedImages.value && uploadedImages.value.length > 0) { + for (const img of uploadedImages.value) { + if (img.alias) { + // Get the full URL for the image + const imageUrl = imageService.getImageUrl(img.alias) + if (imageUrl) { + images.push(imageUrl) + } + } + } + } + isCreating.value = true createError.value = null @@ -382,18 +404,31 @@ const createProduct = async (formData: any) => { return } - const { - name, - description, - price, - quantity, - categories, - images, - active, - use_autoreply, - autoreply_message + const { + name, + description, + price, + quantity, + categories, + active, + use_autoreply, + autoreply_message } = formData + // Get uploaded image URLs from the image service + const images: string[] = [] + if (uploadedImages.value && uploadedImages.value.length > 0) { + for (const img of uploadedImages.value) { + if (img.alias) { + // Get the full URL for the image + const imageUrl = imageService.getImageUrl(img.alias) + if (imageUrl) { + images.push(imageUrl) + } + } + } + } + isCreating.value = true createError.value = null @@ -470,6 +505,34 @@ watch(() => props.isOpen, async (isOpen) => { // Reset form with appropriate initial values resetForm({ values: initialValues }) + // Convert existing image URLs to the format expected by ImageUpload component + if (props.product?.images && props.product.images.length > 0) { + // For existing products, we need to convert URLs back to a format ImageUpload can display + uploadedImages.value = props.product.images.map((url, index) => { + let alias = url + + // If it's a full pict-rs URL, extract just the file ID + if (url.includes('/image/original/')) { + const parts = url.split('/image/original/') + if (parts.length > 1 && parts[1]) { + alias = parts[1] + } + } else if (url.startsWith('http://') || url.startsWith('https://')) { + // Keep full URLs as-is + alias = url + } + + return { + alias: alias, + delete_token: '', + isPrimary: index === 0, + details: {} + } + }) + } else { + uploadedImages.value = [] + } + // Wait for reactivity await nextTick() diff --git a/src/modules/market/components/ProductCard.vue b/src/modules/market/components/ProductCard.vue index c570930..b8fd899 100644 --- a/src/modules/market/components/ProductCard.vue +++ b/src/modules/market/components/ProductCard.vue @@ -1,11 +1,11 @@