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 @@ + + + + + + + {{ currentImageIndex + 1 }} of {{ images.length }} + + + + + + + + + No image available + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ lightboxImageIndex + 1 }} / {{ images.length }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ lightboxImageIndex + 1 }} / {{ images.length }} + + + + + + + + + + + \ 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 @@ - - - + + + No image available - + + + + + + + + + + + + + + + + + + + - + diff --git a/src/modules/market/views/MarketPage.vue b/src/modules/market/views/MarketPage.vue index 8d4e15d..b4ca8e2 100644 --- a/src/modules/market/views/MarketPage.vue +++ b/src/modules/market/views/MarketPage.vue @@ -238,7 +238,6 @@ const addToCart = (product: Product, quantity?: number) => { marketStore.addToStallCart(product, quantity || 1) } - const viewStall = (stallId: string) => { // Navigate to the stall view page router.push(`/market/stall/${stallId}`)
Image upload coming soon