Enhance CreateProductDialog and MerchantStore components for product editing functionality

- Update CreateProductDialog to support both creating and editing products, with dynamic button text and pre-populated fields for existing products.
- Refactor product submission logic to handle updates, including error handling and user feedback.
- Modify MerchantStore to manage product editing state and integrate updated product handling in the dialog.

These changes improve the user experience by allowing merchants to easily edit existing products, enhancing the overall product management workflow.
This commit is contained in:
padreug 2025-09-09 03:35:43 +02:00
parent c00a172fb6
commit 98544e2e79
2 changed files with 134 additions and 21 deletions

View file

@ -2,7 +2,7 @@
<Dialog :open="isOpen" @update:open="(open) => !open && $emit('close')"> <Dialog :open="isOpen" @update:open="(open) => !open && $emit('close')">
<DialogContent class="sm:max-w-3xl max-h-[90vh] overflow-y-auto"> <DialogContent class="sm:max-w-3xl max-h-[90vh] overflow-y-auto">
<DialogHeader> <DialogHeader>
<DialogTitle>Add New Product to {{ stall?.name }}</DialogTitle> <DialogTitle>{{ product ? 'Edit' : 'Add New' }} Product{{ stall?.name ? ` ${product ? 'in' : 'to'} ${stall.name}` : '' }}</DialogTitle>
</DialogHeader> </DialogHeader>
<form @submit="onSubmit" class="space-y-6 py-4" autocomplete="off"> <form @submit="onSubmit" class="space-y-6 py-4" autocomplete="off">
@ -183,8 +183,7 @@
type="submit" type="submit"
:disabled="isCreating || !isFormValid" :disabled="isCreating || !isFormValid"
> >
<span v-if="isCreating">Creating...</span> {{ submitButtonText }}
<span v-else>Create Product</span>
</Button> </Button>
</div> </div>
</form> </form>
@ -212,7 +211,7 @@ import {
FormMessage, FormMessage,
} from '@/components/ui/form' } from '@/components/ui/form'
import { Package } from 'lucide-vue-next' import { Package } from 'lucide-vue-next'
import type { NostrmarketAPI, Stall, CreateProductRequest } from '../services/nostrmarketAPI' import type { NostrmarketAPI, Stall, Product, CreateProductRequest } from '../services/nostrmarketAPI'
import { auth } from '@/composables/useAuthService' import { auth } from '@/composables/useAuthService'
import { useToast } from '@/core/composables/useToast' import { useToast } from '@/core/composables/useToast'
import { injectService, SERVICE_TOKENS } from '@/core/di-container' import { injectService, SERVICE_TOKENS } from '@/core/di-container'
@ -221,12 +220,14 @@ import { injectService, SERVICE_TOKENS } from '@/core/di-container'
interface Props { interface Props {
isOpen: boolean isOpen: boolean
stall?: Stall | null stall?: Stall | null
product?: Product | null // For editing existing products
} }
const props = defineProps<Props>() const props = defineProps<Props>()
const emit = defineEmits<{ const emit = defineEmits<{
close: [] close: []
created: [product: any] created: [product: any]
updated: [product: any]
}>() }>()
// Services // Services
@ -237,6 +238,13 @@ const toast = useToast()
const isCreating = ref(false) const isCreating = ref(false)
const createError = ref<string | null>(null) const createError = ref<string | null>(null)
// Computed properties
const isEditMode = computed(() => !!props.product?.id)
const submitButtonText = computed(() => isCreating.value ?
(isEditMode.value ? 'Updating...' : 'Creating...') :
(isEditMode.value ? 'Update Product' : 'Create Product')
)
// Product form schema // Product form schema
const productFormSchema = toTypedSchema(z.object({ const productFormSchema = toTypedSchema(z.object({
name: z.string().min(1, "Product name is required").max(100, "Product name must be less than 100 characters"), name: z.string().min(1, "Product name is required").max(100, "Product name must be less than 100 characters"),
@ -274,10 +282,82 @@ const isFormValid = computed(() => meta.value.valid)
// Product form submit handler // Product form submit handler
const onSubmit = form.handleSubmit(async (values) => { const onSubmit = form.handleSubmit(async (values) => {
await createProduct(values) await createOrUpdateProduct(values)
}) })
// Methods // Methods
const createOrUpdateProduct = async (formData: any) => {
if (isEditMode.value) {
await updateProduct(formData)
} else {
await createProduct(formData)
}
}
const updateProduct = async (formData: any) => {
const currentUser = auth.currentUser?.value
if (!currentUser?.wallets?.length || !props.product?.id) {
toast.error('No active store or product ID available')
return
}
const {
name,
description,
price,
quantity,
categories,
images,
active,
use_autoreply,
autoreply_message
} = formData
isCreating.value = true
createError.value = null
try {
const productData: Product = {
id: props.product.id,
stall_id: props.product.stall_id,
name,
categories: categories || [],
images: images || [],
price: Number(price),
quantity: Number(quantity),
active,
pending: false,
config: {
description: description || '',
currency: props.stall?.currency || props.product.config.currency,
use_autoreply,
autoreply_message: use_autoreply ? autoreply_message || '' : '',
shipping: props.product.config.shipping || []
}
}
const updatedProduct = await nostrmarketAPI.updateProduct(
currentUser.wallets[0].adminkey,
props.product.id,
productData
)
// Reset form and close dialog
resetForm()
emit('updated', updatedProduct)
emit('close')
toast.success(`Product "${name}" updated successfully!`)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to update product'
console.error('Error updating product:', error)
createError.value = errorMessage
toast.error(`Failed to update product: ${errorMessage}`)
} finally {
isCreating.value = false
}
}
const createProduct = async (formData: any) => { const createProduct = async (formData: any) => {
const currentUser = auth.currentUser?.value const currentUser = auth.currentUser?.value
if (!currentUser?.wallets?.length || !props.stall) { if (!currentUser?.wallets?.length || !props.stall) {
@ -342,9 +422,18 @@ const createProduct = async (formData: any) => {
// Initialize data when dialog opens // Initialize data when dialog opens
watch(() => props.isOpen, async (isOpen) => { watch(() => props.isOpen, async (isOpen) => {
if (isOpen) { if (isOpen) {
// Reset form to initial state // If editing, pre-populate with existing product data
resetForm({ const initialValues = props.product ? {
values: { name: props.product.name || '',
description: props.product.config?.description || '',
price: props.product.price || 0,
quantity: props.product.quantity || 1,
categories: props.product.categories || [],
images: props.product.images || [],
active: props.product.active ?? true,
use_autoreply: props.product.config?.use_autoreply || false,
autoreply_message: props.product.config?.autoreply_message || ''
} : {
name: '', name: '',
description: '', description: '',
price: 0, price: 0,
@ -355,7 +444,9 @@ watch(() => props.isOpen, async (isOpen) => {
use_autoreply: false, use_autoreply: false,
autoreply_message: '' autoreply_message: ''
} }
})
// Reset form with appropriate initial values
resetForm({ values: initialValues })
// Wait for reactivity // Wait for reactivity
await nextTick() await nextTick()

View file

@ -293,7 +293,11 @@
<!-- Product Actions --> <!-- Product Actions -->
<div class="flex justify-end pt-2 border-t"> <div class="flex justify-end pt-2 border-t">
<Button variant="ghost" size="sm"> <Button
@click="editProduct(product)"
variant="ghost"
size="sm"
>
Edit Edit
</Button> </Button>
</div> </div>
@ -316,8 +320,10 @@
<CreateProductDialog <CreateProductDialog
:is-open="showCreateProductDialog" :is-open="showCreateProductDialog"
:stall="activeStall" :stall="activeStall"
@close="showCreateProductDialog = false" :product="editingProduct"
@close="closeProductDialog"
@created="onProductCreated" @created="onProductCreated"
@updated="onProductUpdated"
/> />
</template> </template>
@ -370,6 +376,7 @@ const isLoadingProducts = ref(false)
// Dialog state // Dialog state
const showCreateStoreDialog = ref(false) const showCreateStoreDialog = ref(false)
const showCreateProductDialog = ref(false) const showCreateProductDialog = ref(false)
const editingProduct = ref<Product | null>(null)
// Computed properties // Computed properties
const userHasMerchantProfile = computed(() => { const userHasMerchantProfile = computed(() => {
@ -556,6 +563,21 @@ const onProductCreated = async (_product: Product) => {
toast.success('Product created successfully!') toast.success('Product created successfully!')
} }
const onProductUpdated = async (_product: Product) => {
await loadStallProducts()
toast.success('Product updated successfully!')
}
const editProduct = (product: Product) => {
editingProduct.value = product
showCreateProductDialog.value = true
}
const closeProductDialog = () => {
showCreateProductDialog.value = false
editingProduct.value = null
}
// Lifecycle // Lifecycle
onMounted(async () => { onMounted(async () => {
console.log('Merchant Store component loaded') console.log('Merchant Store component loaded')