Connect Store Settings to actual store data via API
- Rewrote MarketSettings.vue to use real Stall model fields - Added updateStall API method to nostrmarketAPI.ts - Editable fields: name, description, imageUrl - Read-only fields: currency, shipping zones (set at creation) - Form validation with Zod schema - Loading states and proper error handling 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
39a7dc2096
commit
4c62daf46c
2 changed files with 228 additions and 294 deletions
|
|
@ -2,331 +2,237 @@
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-2xl font-bold text-foreground">Market Settings</h2>
|
<h2 class="text-2xl font-bold text-foreground">Store Settings</h2>
|
||||||
<p class="text-muted-foreground mt-1">Configure your store and market preferences</p>
|
<p class="text-muted-foreground mt-1">Configure your store information</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Settings Tabs -->
|
<!-- Loading State -->
|
||||||
<div class="border-b border-border">
|
<div v-if="isLoading" class="flex justify-center py-12">
|
||||||
<nav class="flex space-x-8">
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||||||
<button
|
|
||||||
v-for="tab in settingsTabs"
|
|
||||||
:key="tab.id"
|
|
||||||
@click="activeSettingsTab = tab.id"
|
|
||||||
:class="[
|
|
||||||
'py-2 px-1 border-b-2 font-medium text-sm transition-colors',
|
|
||||||
activeSettingsTab === tab.id
|
|
||||||
? 'border-primary text-primary'
|
|
||||||
: 'border-transparent text-muted-foreground hover:text-foreground hover:border-muted-foreground'
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
{{ tab.name }}
|
|
||||||
</button>
|
|
||||||
</nav>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Settings Content -->
|
<!-- No Store State -->
|
||||||
<div class="min-h-[500px]">
|
<div v-else-if="!currentStall" class="text-center py-12">
|
||||||
<!-- Store Settings Tab -->
|
<div class="w-16 h-16 bg-muted rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
<div v-if="activeSettingsTab === 'store'" class="space-y-6">
|
<Store class="w-8 h-8 text-muted-foreground" />
|
||||||
<div class="bg-card p-6 rounded-lg border shadow-sm">
|
|
||||||
<h3 class="text-lg font-semibold text-foreground mb-4">Store Information</h3>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-foreground mb-2">Store Name</label>
|
|
||||||
<Input v-model="storeSettings.name" placeholder="Enter store name" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-foreground mb-2">Store Description</label>
|
|
||||||
<Input v-model="storeSettings.description" placeholder="Enter store description" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-foreground mb-2">Contact Email</label>
|
|
||||||
<Input v-model="storeSettings.contactEmail" type="email" placeholder="Enter contact email" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-foreground mb-2">Store Category</label>
|
|
||||||
<select v-model="storeSettings.category" class="w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring bg-background text-foreground">
|
|
||||||
<option value="">Select category</option>
|
|
||||||
<option value="electronics">Electronics</option>
|
|
||||||
<option value="clothing">Clothing</option>
|
|
||||||
<option value="books">Books</option>
|
|
||||||
<option value="food">Food & Beverages</option>
|
|
||||||
<option value="services">Services</option>
|
|
||||||
<option value="other">Other</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-6">
|
|
||||||
<Button @click="saveStoreSettings" variant="default">
|
|
||||||
Save Store Settings
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<h3 class="text-lg font-medium text-foreground mb-2">No Store Found</h3>
|
||||||
|
<p class="text-muted-foreground">
|
||||||
|
Create a store first to manage its settings
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Payment Settings Tab -->
|
<!-- Store Settings Form -->
|
||||||
<div v-else-if="activeSettingsTab === 'payment'" class="space-y-6">
|
<div v-else class="space-y-6">
|
||||||
<div class="bg-card p-6 rounded-lg border shadow-sm">
|
<!-- Store Information -->
|
||||||
<h3 class="text-lg font-semibold text-foreground mb-4">Payment Configuration</h3>
|
<div class="bg-card p-6 rounded-lg border shadow-sm">
|
||||||
<div class="space-y-4">
|
<h3 class="text-lg font-semibold text-foreground mb-4">Store Information</h3>
|
||||||
<div>
|
<form @submit="onSubmit" class="space-y-4">
|
||||||
<label class="block text-sm font-medium text-foreground mb-2">Default Currency</label>
|
<FormField v-slot="{ componentField }" name="name">
|
||||||
<select v-model="paymentSettings.defaultCurrency" class="w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring bg-background text-foreground">
|
<FormItem>
|
||||||
<option value="sat">Satoshi (sats)</option>
|
<FormLabel>Store Name *</FormLabel>
|
||||||
<option value="btc">Bitcoin (BTC)</option>
|
<FormControl>
|
||||||
<option value="usd">US Dollar (USD)</option>
|
<Input
|
||||||
<option value="eur">Euro (EUR)</option>
|
placeholder="Enter store name"
|
||||||
</select>
|
:disabled="isSaving"
|
||||||
</div>
|
v-bind="componentField"
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-foreground mb-2">Invoice Expiry (minutes)</label>
|
|
||||||
<Input v-model="paymentSettings.invoiceExpiry" type="number" min="5" max="1440" placeholder="60" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-foreground mb-2">Auto-generate Invoices</label>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<input
|
|
||||||
v-model="paymentSettings.autoGenerateInvoices"
|
|
||||||
type="checkbox"
|
|
||||||
class="h-4 w-4 text-primary focus:ring-primary border-input rounded"
|
|
||||||
/>
|
/>
|
||||||
<label class="ml-2 text-sm text-foreground">
|
</FormControl>
|
||||||
Automatically generate Lightning invoices for new orders
|
<FormMessage />
|
||||||
</label>
|
</FormItem>
|
||||||
</div>
|
</FormField>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-6">
|
|
||||||
<Button @click="savePaymentSettings" variant="default">
|
|
||||||
Save Payment Settings
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Nostr Settings Tab -->
|
<FormField v-slot="{ componentField }" name="description">
|
||||||
<div v-else-if="activeSettingsTab === 'nostr'" class="space-y-6">
|
<FormItem>
|
||||||
<div class="bg-card p-6 rounded-lg border shadow-sm">
|
<FormLabel>Description</FormLabel>
|
||||||
<h3 class="text-lg font-semibold text-foreground mb-4">Nostr Network Configuration</h3>
|
<FormControl>
|
||||||
<div class="space-y-4">
|
<Textarea
|
||||||
<div>
|
placeholder="Describe your store and what you sell"
|
||||||
<label class="block text-sm font-medium text-foreground mb-2">Relay Connections</label>
|
:disabled="isSaving"
|
||||||
<div class="space-y-2">
|
v-bind="componentField"
|
||||||
<div v-for="relay in nostrSettings.relays" :key="relay" class="flex items-center gap-2">
|
rows="3"
|
||||||
<Input :value="relay" readonly class="flex-1" />
|
/>
|
||||||
<Button @click="removeRelay(relay)" variant="outline" size="sm">
|
</FormControl>
|
||||||
<X class="w-4 h-4" />
|
<FormDescription>
|
||||||
</Button>
|
This description will be shown to customers browsing your store
|
||||||
</div>
|
</FormDescription>
|
||||||
<div class="flex gap-2">
|
<FormMessage />
|
||||||
<Input v-model="newRelay" placeholder="wss://relay.example.com" class="flex-1" />
|
</FormItem>
|
||||||
<Button @click="addRelay" variant="outline">
|
</FormField>
|
||||||
Add Relay
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-foreground mb-2">Nostr Public Key</label>
|
|
||||||
<Input :value="nostrSettings.pubkey" readonly class="font-mono text-sm" />
|
|
||||||
<p class="text-xs text-muted-foreground mt-1">Your Nostr public key for receiving orders</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-foreground mb-2">Connection Status</label>
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<div
|
|
||||||
class="w-3 h-3 rounded-full"
|
|
||||||
:class="orderEvents.isSubscribed ? 'bg-green-500' : 'bg-yellow-500'"
|
|
||||||
></div>
|
|
||||||
<span class="text-sm text-muted-foreground">
|
|
||||||
{{ orderEvents.isSubscribed ? 'Connected to Nostr network' : 'Connecting to Nostr network...' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-6">
|
|
||||||
<Button @click="saveNostrSettings" variant="default">
|
|
||||||
Save Nostr Settings
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Shipping Settings Tab -->
|
<FormField v-slot="{ componentField }" name="imageUrl">
|
||||||
<div v-else-if="activeSettingsTab === 'shipping'" class="space-y-6">
|
<FormItem>
|
||||||
<div class="bg-card p-6 rounded-lg border shadow-sm">
|
<FormLabel>Store Image URL</FormLabel>
|
||||||
<h3 class="text-lg font-semibold text-foreground mb-4">Shipping Zones</h3>
|
<FormControl>
|
||||||
<div class="space-y-4">
|
<Input
|
||||||
<div v-for="zone in shippingSettings.zones" :key="zone.id" class="border border-border rounded-lg p-4">
|
placeholder="https://example.com/store-image.jpg"
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
:disabled="isSaving"
|
||||||
<div>
|
v-bind="componentField"
|
||||||
<label class="block text-sm font-medium text-foreground mb-1">Zone Name</label>
|
/>
|
||||||
<Input v-model="zone.name" placeholder="Zone name" />
|
</FormControl>
|
||||||
</div>
|
<FormDescription>
|
||||||
<div>
|
Optional image to represent your store
|
||||||
<label class="block text-sm font-medium text-foreground mb-1">Cost</label>
|
</FormDescription>
|
||||||
<Input v-model="zone.cost" type="number" min="0" step="0.01" placeholder="0.00" />
|
<FormMessage />
|
||||||
</div>
|
</FormItem>
|
||||||
<div>
|
</FormField>
|
||||||
<label class="block text-sm font-medium text-foreground mb-1">Currency</label>
|
|
||||||
<select v-model="zone.currency" class="w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring bg-background text-foreground">
|
<!-- Read-only Fields -->
|
||||||
<option value="sat">Satoshi (sats)</option>
|
<div class="pt-4 border-t space-y-4">
|
||||||
<option value="btc">Bitcoin (BTC)</option>
|
<div>
|
||||||
<option value="usd">US Dollar (USD)</option>
|
<label class="block text-sm font-medium text-muted-foreground mb-1">Currency</label>
|
||||||
</select>
|
<div class="text-foreground">{{ currentStall.currency }}</div>
|
||||||
</div>
|
<p class="text-xs text-muted-foreground mt-1">Currency is set when the store is created and cannot be changed</p>
|
||||||
</div>
|
|
||||||
<div class="mt-3 flex justify-end">
|
|
||||||
<Button @click="removeShippingZone(zone.id)" variant="outline" size="sm">
|
|
||||||
Remove Zone
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Button @click="addShippingZone" variant="outline">
|
|
||||||
<Plus class="w-4 h-4 mr-2" />
|
<div>
|
||||||
Add Shipping Zone
|
<label class="block text-sm font-medium text-muted-foreground mb-1">Shipping Zones</label>
|
||||||
|
<div class="text-foreground">{{ currentStall.shipping_zones?.length || 0 }} zone(s) configured</div>
|
||||||
|
<p class="text-xs text-muted-foreground mt-1">Manage shipping zones when creating a new store</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-4">
|
||||||
|
<Button type="submit" :disabled="isSaving || !isFormValid">
|
||||||
|
<span v-if="isSaving">Saving...</span>
|
||||||
|
<span v-else>Save Changes</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-6">
|
</form>
|
||||||
<Button @click="saveShippingSettings" variant="default">
|
|
||||||
Save Shipping Settings
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
// import { useOrderEvents } from '@/composables/useOrderEvents' // TODO: Move to market module
|
import { useForm } from 'vee-validate'
|
||||||
|
import { toTypedSchema } from '@vee-validate/zod'
|
||||||
|
import * as z from 'zod'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
import { Plus, X } from 'lucide-vue-next'
|
import { Textarea } from '@/components/ui/textarea'
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from '@/components/ui/form'
|
||||||
|
import { Store } from 'lucide-vue-next'
|
||||||
|
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||||
|
import type { NostrmarketAPI, Stall } from '../services/nostrmarketAPI'
|
||||||
|
import { auth } from '@/composables/useAuthService'
|
||||||
|
import { useToast } from '@/core/composables/useToast'
|
||||||
|
|
||||||
// const marketStore = useMarketStore()
|
// Services
|
||||||
// const orderEvents = useOrderEvents() // TODO: Move to market module
|
const nostrmarketAPI = injectService(SERVICE_TOKENS.NOSTRMARKET_API) as NostrmarketAPI
|
||||||
const orderEvents = { isSubscribed: ref(false) } // Temporary mock
|
const paymentService = injectService(SERVICE_TOKENS.PAYMENT_SERVICE) as any
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
// Local state
|
// State
|
||||||
const activeSettingsTab = ref('store')
|
const isLoading = ref(true)
|
||||||
const newRelay = ref('')
|
const isSaving = ref(false)
|
||||||
|
const currentStall = ref<Stall | null>(null)
|
||||||
|
|
||||||
// Settings data
|
// Form schema - only fields that exist in the Stall model
|
||||||
const storeSettings = ref({
|
const formSchema = toTypedSchema(z.object({
|
||||||
name: 'My Store',
|
name: z.string().min(1, "Store name is required").max(100, "Store name must be less than 100 characters"),
|
||||||
description: 'A great place to shop',
|
description: z.string().max(500, "Description must be less than 500 characters").optional(),
|
||||||
contactEmail: 'store@example.com',
|
imageUrl: z.string().url("Must be a valid URL").optional().or(z.literal(''))
|
||||||
category: 'other'
|
}))
|
||||||
|
|
||||||
|
// Form setup
|
||||||
|
const form = useForm({
|
||||||
|
validationSchema: formSchema,
|
||||||
|
initialValues: {
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
imageUrl: ''
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const paymentSettings = ref({
|
const { resetForm, meta } = form
|
||||||
defaultCurrency: 'sat',
|
const isFormValid = computed(() => meta.value.valid)
|
||||||
invoiceExpiry: 60,
|
|
||||||
autoGenerateInvoices: true
|
|
||||||
})
|
|
||||||
|
|
||||||
const nostrSettings = ref({
|
// Load store data
|
||||||
relays: [
|
const loadStoreData = async () => {
|
||||||
'wss://relay.damus.io',
|
const currentUser = auth.currentUser?.value
|
||||||
'wss://relay.snort.social',
|
if (!currentUser?.wallets?.length) {
|
||||||
'wss://nostr-pub.wellorder.net'
|
isLoading.value = false
|
||||||
],
|
return
|
||||||
pubkey: 'npub1...' // TODO: Get from auth
|
}
|
||||||
})
|
|
||||||
|
|
||||||
const shippingSettings = ref({
|
const inkey = paymentService.getPreferredWalletInvoiceKey()
|
||||||
zones: [
|
if (!inkey) {
|
||||||
{
|
isLoading.value = false
|
||||||
id: '1',
|
return
|
||||||
name: 'Local',
|
}
|
||||||
cost: 0,
|
|
||||||
currency: 'sat',
|
try {
|
||||||
estimatedDays: '1-2 days'
|
const stalls = await nostrmarketAPI.getStalls(inkey)
|
||||||
},
|
if (stalls && stalls.length > 0) {
|
||||||
{
|
currentStall.value = stalls[0]
|
||||||
id: '2',
|
|
||||||
name: 'Domestic',
|
// Update form with current values
|
||||||
cost: 1000,
|
resetForm({
|
||||||
currency: 'sat',
|
values: {
|
||||||
estimatedDays: '3-5 days'
|
name: stalls[0].name || '',
|
||||||
},
|
description: stalls[0].config?.description || '',
|
||||||
{
|
imageUrl: stalls[0].config?.image_url || ''
|
||||||
id: '3',
|
}
|
||||||
name: 'International',
|
})
|
||||||
cost: 5000,
|
|
||||||
currency: 'sat',
|
|
||||||
estimatedDays: '7-14 days'
|
|
||||||
}
|
}
|
||||||
]
|
} catch (error) {
|
||||||
|
console.error('Failed to load store data:', error)
|
||||||
|
toast.error('Failed to load store settings')
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save store settings
|
||||||
|
const onSubmit = form.handleSubmit(async (values) => {
|
||||||
|
if (!currentStall.value?.id) return
|
||||||
|
|
||||||
|
isSaving.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const adminKey = paymentService.getPreferredWalletAdminKey()
|
||||||
|
if (!adminKey) {
|
||||||
|
throw new Error('No wallet admin key available')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the stall with new values
|
||||||
|
const updatedStall = await nostrmarketAPI.updateStall(adminKey, currentStall.value.id, {
|
||||||
|
name: values.name,
|
||||||
|
config: {
|
||||||
|
description: values.description || '',
|
||||||
|
image_url: values.imageUrl || undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
currentStall.value = updatedStall
|
||||||
|
toast.success('Store settings saved successfully!')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save store settings:', error)
|
||||||
|
toast.error('Failed to save store settings')
|
||||||
|
} finally {
|
||||||
|
isSaving.value = false
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Settings tabs
|
// Watch for auth changes
|
||||||
const settingsTabs = [
|
watch(() => auth.currentUser?.value?.pubkey, async (newPubkey, oldPubkey) => {
|
||||||
{ id: 'store', name: 'Store Settings' },
|
if (newPubkey !== oldPubkey) {
|
||||||
{ id: 'payment', name: 'Payment Settings' },
|
isLoading.value = true
|
||||||
{ id: 'nostr', name: 'Nostr Network' },
|
await loadStoreData()
|
||||||
{ id: 'shipping', name: 'Shipping Zones' }
|
|
||||||
]
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
const saveStoreSettings = () => {
|
|
||||||
// TODO: Save store settings
|
|
||||||
console.log('Saving store settings:', storeSettings.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const savePaymentSettings = () => {
|
|
||||||
// TODO: Save payment settings
|
|
||||||
console.log('Saving payment settings:', paymentSettings.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveNostrSettings = () => {
|
|
||||||
// TODO: Save Nostr settings
|
|
||||||
console.log('Saving Nostr settings:', nostrSettings.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const saveShippingSettings = () => {
|
|
||||||
// TODO: Save shipping settings
|
|
||||||
console.log('Saving shipping settings:', shippingSettings.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addRelay = () => {
|
|
||||||
if (newRelay.value && !nostrSettings.value.relays.includes(newRelay.value)) {
|
|
||||||
nostrSettings.value.relays.push(newRelay.value)
|
|
||||||
newRelay.value = ''
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
const removeRelay = (relay: string) => {
|
// Initialize
|
||||||
const index = nostrSettings.value.relays.indexOf(relay)
|
onMounted(async () => {
|
||||||
if (index > -1) {
|
await loadStoreData()
|
||||||
nostrSettings.value.relays.splice(index, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addShippingZone = () => {
|
|
||||||
const newZone = {
|
|
||||||
id: Date.now().toString(),
|
|
||||||
name: 'New Zone',
|
|
||||||
cost: 0,
|
|
||||||
currency: 'sat',
|
|
||||||
estimatedDays: '3-5 days'
|
|
||||||
}
|
|
||||||
shippingSettings.value.zones.push(newZone)
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeShippingZone = (zoneId: string) => {
|
|
||||||
const index = shippingSettings.value.zones.findIndex(z => z.id === zoneId)
|
|
||||||
if (index > -1) {
|
|
||||||
shippingSettings.value.zones.splice(index, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lifecycle
|
|
||||||
onMounted(() => {
|
|
||||||
console.log('Market Settings component loaded')
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -329,6 +329,34 @@ export class NostrmarketAPI extends BaseService {
|
||||||
return stall
|
return stall
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing stall
|
||||||
|
*/
|
||||||
|
async updateStall(
|
||||||
|
walletAdminkey: string,
|
||||||
|
stallId: string,
|
||||||
|
stallData: Partial<{
|
||||||
|
name: string
|
||||||
|
config: {
|
||||||
|
description?: string
|
||||||
|
image_url?: string
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
): Promise<Stall> {
|
||||||
|
const stall = await this.request<Stall>(
|
||||||
|
`/api/v1/stall/${stallId}`,
|
||||||
|
walletAdminkey,
|
||||||
|
{
|
||||||
|
method: 'PATCH',
|
||||||
|
body: JSON.stringify(stallData),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
this.debug('Updated stall:', { stallId: stall.id, stallName: stall.name })
|
||||||
|
|
||||||
|
return stall
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get available shipping zones
|
* Get available shipping zones
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue