Compare commits
No commits in common. "81db5d2d9fb46f8b2c348fe193cd47110b53f028" and "f62cb87445d4b5b6b0b47461460e08c78f6e14fa" have entirely different histories.
81db5d2d9f
...
f62cb87445
8 changed files with 193 additions and 313 deletions
|
|
@ -158,7 +158,7 @@
|
||||||
: 'bg-muted'
|
: 'bg-muted'
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<ChatMessageContent :content="message.content" />
|
<p class="text-sm">{{ message.content }}</p>
|
||||||
<p class="text-xs opacity-70 mt-1">
|
<p class="text-xs opacity-70 mt-1">
|
||||||
{{ formatTime(message.created_at) }}
|
{{ formatTime(message.created_at) }}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -325,7 +325,7 @@
|
||||||
: 'bg-muted'
|
: 'bg-muted'
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<ChatMessageContent :content="message.content" />
|
<p class="text-sm">{{ message.content }}</p>
|
||||||
<p class="text-xs opacity-70 mt-1">
|
<p class="text-xs opacity-70 mt-1">
|
||||||
{{ formatTime(message.created_at) }}
|
{{ formatTime(message.created_at) }}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -376,7 +376,6 @@ import { Badge } from '@/components/ui/badge'
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||||
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
|
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
|
||||||
import { useChat } from '../composables/useChat'
|
import { useChat } from '../composables/useChat'
|
||||||
import ChatMessageContent from './ChatMessageContent.vue'
|
|
||||||
|
|
||||||
import { useFuzzySearch } from '@/composables/useFuzzySearch'
|
import { useFuzzySearch } from '@/composables/useFuzzySearch'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
<template>
|
|
||||||
<!-- Order Message -->
|
|
||||||
<div v-if="parsedOrder" class="min-w-[200px]">
|
|
||||||
<div class="flex items-center gap-2 font-semibold text-sm mb-3">
|
|
||||||
<ShoppingBag class="w-4 h-4" />
|
|
||||||
<span>Order Placed</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-xs space-y-2">
|
|
||||||
<!-- Items -->
|
|
||||||
<div v-if="parsedOrder.items?.length" class="space-y-1">
|
|
||||||
<div
|
|
||||||
v-for="(item, index) in parsedOrder.items"
|
|
||||||
:key="index"
|
|
||||||
class="flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<span class="opacity-70">{{ item.quantity }}x</span>
|
|
||||||
<span>Item</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Divider -->
|
|
||||||
<div class="border-t border-current opacity-20 my-2" />
|
|
||||||
|
|
||||||
<!-- Shipping -->
|
|
||||||
<div v-if="shippingLabel" class="flex items-center gap-2">
|
|
||||||
<Truck class="w-3 h-3 opacity-70" />
|
|
||||||
<span>{{ shippingLabel }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Order Reference -->
|
|
||||||
<div class="opacity-60 text-[10px] font-mono mt-2">
|
|
||||||
#{{ shortOrderId }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Regular Text Message -->
|
|
||||||
<p v-else class="text-sm whitespace-pre-wrap break-words">{{ content }}</p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { ShoppingBag, Truck } from 'lucide-vue-next'
|
|
||||||
|
|
||||||
interface OrderItem {
|
|
||||||
product_id: string
|
|
||||||
quantity: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OrderContact {
|
|
||||||
name?: string
|
|
||||||
email?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ParsedOrder {
|
|
||||||
type: number
|
|
||||||
id: string
|
|
||||||
items?: OrderItem[]
|
|
||||||
contact?: OrderContact
|
|
||||||
shipping_id?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
content: string
|
|
||||||
}>()
|
|
||||||
|
|
||||||
// Try to parse the content as an order message
|
|
||||||
const parsedOrder = computed<ParsedOrder | null>(() => {
|
|
||||||
try {
|
|
||||||
// Check if content looks like JSON
|
|
||||||
const trimmed = props.content.trim()
|
|
||||||
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsed = JSON.parse(trimmed)
|
|
||||||
|
|
||||||
// Validate it's an order message (has type and id fields)
|
|
||||||
if (typeof parsed.type === 'number' && typeof parsed.id === 'string' && parsed.id.startsWith('order_')) {
|
|
||||||
return parsed as ParsedOrder
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
} catch {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Format shipping label
|
|
||||||
const shippingLabel = computed(() => {
|
|
||||||
if (!parsedOrder.value?.shipping_id) return null
|
|
||||||
|
|
||||||
const id = parsedOrder.value.shipping_id
|
|
||||||
// Extract zone name if it follows the pattern "zonename-hash"
|
|
||||||
if (id.includes('-')) {
|
|
||||||
const zoneName = id.split('-')[0]
|
|
||||||
// Capitalize first letter
|
|
||||||
return zoneName.charAt(0).toUpperCase() + zoneName.slice(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'Standard'
|
|
||||||
})
|
|
||||||
|
|
||||||
// Short order ID for display
|
|
||||||
const shortOrderId = computed(() => {
|
|
||||||
if (!parsedOrder.value?.id) return ''
|
|
||||||
// Extract the unique part from "order_timestamp_randomstring"
|
|
||||||
const parts = parsedOrder.value.id.split('_')
|
|
||||||
if (parts.length >= 3) {
|
|
||||||
return parts[2].slice(0, 8) // Just the random part
|
|
||||||
}
|
|
||||||
return parsedOrder.value.id.slice(-8)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
@ -389,18 +389,14 @@ const onSubmit = form.handleSubmit(async (values) => {
|
||||||
throw new Error('No wallet admin key available')
|
throw new Error('No wallet admin key available')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build full stall object with updated values (API requires PUT with full object)
|
// Update the stall with new values
|
||||||
const stallToUpdate = {
|
const updatedStall = await nostrmarketAPI.updateStall(adminKey, currentStall.value.id, {
|
||||||
...currentStall.value,
|
|
||||||
name: values.name,
|
name: values.name,
|
||||||
config: {
|
config: {
|
||||||
...currentStall.value.config,
|
|
||||||
description: values.description || '',
|
description: values.description || '',
|
||||||
image_url: values.imageUrl || undefined
|
image_url: values.imageUrl || undefined
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
const updatedStall = await nostrmarketAPI.updateStall(adminKey, stallToUpdate)
|
|
||||||
|
|
||||||
currentStall.value = updatedStall
|
currentStall.value = updatedStall
|
||||||
toast.success('Store settings saved successfully!')
|
toast.success('Store settings saved successfully!')
|
||||||
|
|
|
||||||
|
|
@ -331,17 +331,23 @@ export class NostrmarketAPI extends BaseService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update an existing stall
|
* Update an existing stall
|
||||||
* Note: The LNbits API uses PUT and expects the full stall object
|
|
||||||
*/
|
*/
|
||||||
async updateStall(
|
async updateStall(
|
||||||
walletAdminkey: string,
|
walletAdminkey: string,
|
||||||
stallData: Stall
|
stallId: string,
|
||||||
|
stallData: Partial<{
|
||||||
|
name: string
|
||||||
|
config: {
|
||||||
|
description?: string
|
||||||
|
image_url?: string
|
||||||
|
}
|
||||||
|
}>
|
||||||
): Promise<Stall> {
|
): Promise<Stall> {
|
||||||
const stall = await this.request<Stall>(
|
const stall = await this.request<Stall>(
|
||||||
`/api/v1/stall/${stallData.id}`,
|
`/api/v1/stall/${stallId}`,
|
||||||
walletAdminkey,
|
walletAdminkey,
|
||||||
{
|
{
|
||||||
method: 'PUT',
|
method: 'PATCH',
|
||||||
body: JSON.stringify(stallData),
|
body: JSON.stringify(stallData),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<Button @click="$router.push('/market')" variant="outline">
|
<Button @click="$router.push('/market')" variant="outline">
|
||||||
Continue Shopping
|
Continue Shopping
|
||||||
</Button>
|
</Button>
|
||||||
<Button @click="$router.push('/market/dashboard?tab=orders')" variant="default">
|
<Button @click="$router.push('/market/dashboard')" variant="default">
|
||||||
View My Orders
|
View My Orders
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -108,17 +108,15 @@
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Shipping & Contact Information -->
|
<!-- Shipping Information -->
|
||||||
<Card>
|
<Card v-if="!orderConfirmed">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Shipping & Contact</CardTitle>
|
<CardTitle>Shipping Information</CardTitle>
|
||||||
<CardDescription>Select shipping and provide your contact details</CardDescription>
|
<CardDescription>Select your shipping zone</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent class="space-y-6">
|
<CardContent>
|
||||||
<!-- Shipping Zones -->
|
<!-- Shipping Zones -->
|
||||||
<div v-if="availableShippingZones.length > 0">
|
<div v-if="availableShippingZones.length > 0" class="space-y-3">
|
||||||
<Label class="mb-3 block">Shipping Zone</Label>
|
|
||||||
<div class="space-y-3">
|
|
||||||
<div
|
<div
|
||||||
v-for="zone in availableShippingZones"
|
v-for="zone in availableShippingZones"
|
||||||
:key="zone.id"
|
:key="zone.id"
|
||||||
|
|
@ -152,64 +150,37 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="text-center py-4 bg-muted/50 rounded-lg">
|
<div v-else class="text-center py-6">
|
||||||
<p class="text-muted-foreground">This merchant hasn't configured shipping zones yet.</p>
|
<p class="text-muted-foreground">This merchant hasn't configured shipping zones yet.</p>
|
||||||
<p class="text-sm text-muted-foreground">Please contact the merchant for shipping information.</p>
|
<p class="text-sm text-muted-foreground">Please contact the merchant for shipping information.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Separator -->
|
<!-- Confirm Order Button -->
|
||||||
<div class="border-t border-border" />
|
<div class="mt-6">
|
||||||
|
<Button
|
||||||
<!-- Shipping Address (shown when required for physical delivery) -->
|
@click="confirmOrder"
|
||||||
<div v-if="requiresShippingAddress">
|
:disabled="availableShippingZones.length > 0 && !selectedShippingZone"
|
||||||
<Label for="address">Shipping Address *</Label>
|
class="w-full"
|
||||||
<Textarea
|
size="lg"
|
||||||
id="address"
|
>
|
||||||
v-model="contactData.address"
|
Confirm Order
|
||||||
placeholder="Full shipping address..."
|
</Button>
|
||||||
rows="3"
|
|
||||||
/>
|
|
||||||
<p class="text-xs text-muted-foreground mt-1">Required for physical delivery</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<!-- Message to Merchant (visible) -->
|
<!-- Contact & Payment Information -->
|
||||||
|
<Card v-if="orderConfirmed">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Contact & Payment Information</CardTitle>
|
||||||
|
<CardDescription>Provide your details for order processing</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent class="space-y-6">
|
||||||
|
<!-- Contact Form -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div>
|
<div>
|
||||||
<Label for="message">Message to Merchant (optional)</Label>
|
<Label for="email">Email (optional)</Label>
|
||||||
<Textarea
|
|
||||||
id="message"
|
|
||||||
v-model="contactData.message"
|
|
||||||
placeholder="Any special instructions or notes..."
|
|
||||||
rows="3"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Optional Contact Info (collapsible) -->
|
|
||||||
<Collapsible v-model:open="showOptionalContact">
|
|
||||||
<CollapsibleTrigger class="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors cursor-pointer w-full">
|
|
||||||
<ChevronDown
|
|
||||||
class="w-4 h-4 transition-transform"
|
|
||||||
:class="{ 'rotate-180': showOptionalContact }"
|
|
||||||
/>
|
|
||||||
<span>Additional contact info (optional)</span>
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
<CollapsibleContent class="pt-4 space-y-4">
|
|
||||||
<!-- Contact Address (optional for digital/pickup) -->
|
|
||||||
<div v-if="!requiresShippingAddress">
|
|
||||||
<Label for="address">Contact Address</Label>
|
|
||||||
<Textarea
|
|
||||||
id="address"
|
|
||||||
v-model="contactData.address"
|
|
||||||
placeholder="Contact address (optional)..."
|
|
||||||
rows="3"
|
|
||||||
/>
|
|
||||||
<p class="text-xs text-muted-foreground mt-1">Optional for digital items or pickup</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div>
|
|
||||||
<Label for="email">Email</Label>
|
|
||||||
<Input
|
<Input
|
||||||
id="email"
|
id="email"
|
||||||
v-model="contactData.email"
|
v-model="contactData.email"
|
||||||
|
|
@ -220,7 +191,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label for="npub">Alternative Npub</Label>
|
<Label for="npub">Alternative Npub (optional)</Label>
|
||||||
<Input
|
<Input
|
||||||
id="npub"
|
id="npub"
|
||||||
v-model="contactData.npub"
|
v-model="contactData.npub"
|
||||||
|
|
@ -229,28 +200,70 @@
|
||||||
<p class="text-xs text-muted-foreground mt-1">Different Npub for communication</p>
|
<p class="text-xs text-muted-foreground mt-1">Different Npub for communication</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleContent>
|
|
||||||
</Collapsible>
|
|
||||||
|
|
||||||
<!-- Payment Method (Lightning only for now) -->
|
<div>
|
||||||
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
<Label for="address">
|
||||||
<span>⚡</span>
|
{{ selectedShippingZone?.requiresPhysicalShipping !== false ? 'Shipping Address' : 'Contact Address (optional)' }}
|
||||||
<span>Payment via Lightning Network</span>
|
</Label>
|
||||||
|
<Textarea
|
||||||
|
id="address"
|
||||||
|
v-model="contactData.address"
|
||||||
|
:placeholder="selectedShippingZone?.requiresPhysicalShipping !== false
|
||||||
|
? 'Full shipping address...'
|
||||||
|
: 'Contact address (optional)...'"
|
||||||
|
rows="3"
|
||||||
|
/>
|
||||||
|
<p class="text-xs text-muted-foreground mt-1">
|
||||||
|
{{ selectedShippingZone?.requiresPhysicalShipping !== false
|
||||||
|
? 'Required for physical delivery'
|
||||||
|
: 'Optional for digital items or pickup' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label for="message">Message to Merchant (optional)</Label>
|
||||||
|
<Textarea
|
||||||
|
id="message"
|
||||||
|
v-model="contactData.message"
|
||||||
|
placeholder="Any special instructions or notes..."
|
||||||
|
rows="3"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Payment Method Selection -->
|
||||||
|
<div>
|
||||||
|
<Label>Payment Method</Label>
|
||||||
|
<div class="flex gap-3 mt-2">
|
||||||
|
<div
|
||||||
|
v-for="method in paymentMethods"
|
||||||
|
:key="method.value"
|
||||||
|
@click="paymentMethod = method.value"
|
||||||
|
:class="[
|
||||||
|
'p-3 border rounded-lg cursor-pointer text-center flex-1 transition-colors',
|
||||||
|
paymentMethod === method.value
|
||||||
|
? 'border-primary bg-primary/10'
|
||||||
|
: 'border-border hover:border-primary/50'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<p class="font-medium">{{ method.label }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Place Order Button -->
|
<!-- Place Order Button -->
|
||||||
<div class="pt-4 border-t border-border">
|
<div class="pt-4 border-t border-border">
|
||||||
<Button
|
<Button
|
||||||
@click="placeOrder"
|
@click="placeOrder"
|
||||||
:disabled="isPlacingOrder || !canPlaceOrder"
|
:disabled="isPlacingOrder || (selectedShippingZone?.requiresPhysicalShipping !== false && !contactData.address)"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
size="lg"
|
size="lg"
|
||||||
>
|
>
|
||||||
<span v-if="isPlacingOrder" class="animate-spin mr-2">⚡</span>
|
<span v-if="isPlacingOrder" class="animate-spin mr-2">⚡</span>
|
||||||
Place Order - {{ formatPrice(orderTotal, checkoutCart.currency) }}
|
Place Order - {{ formatPrice(orderTotal, checkoutCart.currency) }}
|
||||||
</Button>
|
</Button>
|
||||||
<p v-if="!canPlaceOrder" class="text-xs text-destructive mt-2 text-center">
|
<p v-if="selectedShippingZone?.requiresPhysicalShipping !== false && !contactData.address"
|
||||||
{{ orderValidationMessage }}
|
class="text-xs text-destructive mt-2 text-center">
|
||||||
|
Shipping address is required for physical delivery
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
@ -280,14 +293,8 @@ import { Label } from '@/components/ui/label'
|
||||||
import ProgressiveImage from '@/components/ui/image/ProgressiveImage.vue'
|
import ProgressiveImage from '@/components/ui/image/ProgressiveImage.vue'
|
||||||
import {
|
import {
|
||||||
Package,
|
Package,
|
||||||
CheckCircle,
|
CheckCircle
|
||||||
ChevronDown
|
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import {
|
|
||||||
Collapsible,
|
|
||||||
CollapsibleContent,
|
|
||||||
CollapsibleTrigger
|
|
||||||
} from '@/components/ui/collapsible'
|
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const marketStore = useMarketStore()
|
const marketStore = useMarketStore()
|
||||||
|
|
@ -296,10 +303,11 @@ const authService = injectService(SERVICE_TOKENS.AUTH_SERVICE) as any
|
||||||
// State
|
// State
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const error = ref<string | null>(null)
|
const error = ref<string | null>(null)
|
||||||
|
const orderConfirmed = ref(false)
|
||||||
const orderPlaced = ref(false)
|
const orderPlaced = ref(false)
|
||||||
const isPlacingOrder = ref(false)
|
const isPlacingOrder = ref(false)
|
||||||
const selectedShippingZone = ref<any>(null)
|
const selectedShippingZone = ref<any>(null)
|
||||||
const showOptionalContact = ref(false)
|
const paymentMethod = ref('ln')
|
||||||
|
|
||||||
// Form data
|
// Form data
|
||||||
const contactData = ref({
|
const contactData = ref({
|
||||||
|
|
@ -309,7 +317,12 @@ const contactData = ref({
|
||||||
message: ''
|
message: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: Add BTC Onchain and Cashu payment options in the future
|
// Payment methods
|
||||||
|
const paymentMethods = [
|
||||||
|
{ label: 'Lightning Network', value: 'ln' },
|
||||||
|
{ label: 'BTC Onchain', value: 'btc' },
|
||||||
|
{ label: 'Cashu', value: 'cashu' }
|
||||||
|
]
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
const stallId = computed(() => route.params.stallId as string)
|
const stallId = computed(() => route.params.stallId as string)
|
||||||
|
|
@ -370,41 +383,26 @@ const availableShippingZones = computed(() => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Determine if shipping address is required
|
|
||||||
const requiresShippingAddress = computed(() => {
|
|
||||||
return selectedShippingZone.value?.requiresPhysicalShipping !== false
|
|
||||||
})
|
|
||||||
|
|
||||||
// Validation for placing order
|
|
||||||
const canPlaceOrder = computed(() => {
|
|
||||||
// Must select shipping zone if zones are available
|
|
||||||
if (availableShippingZones.value.length > 0 && !selectedShippingZone.value) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Must provide address if physical shipping is required
|
|
||||||
if (requiresShippingAddress.value && !contactData.value.address) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
const orderValidationMessage = computed(() => {
|
|
||||||
if (availableShippingZones.value.length > 0 && !selectedShippingZone.value) {
|
|
||||||
return 'Please select a shipping zone'
|
|
||||||
}
|
|
||||||
if (requiresShippingAddress.value && !contactData.value.address) {
|
|
||||||
return 'Shipping address is required for physical delivery'
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
})
|
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const selectShippingZone = (zone: any) => {
|
const selectShippingZone = (zone: any) => {
|
||||||
selectedShippingZone.value = zone
|
selectedShippingZone.value = zone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const confirmOrder = () => {
|
||||||
|
// Allow proceeding if no shipping zones are available (e.g., for digital goods)
|
||||||
|
if (availableShippingZones.value.length > 0 && !selectedShippingZone.value) {
|
||||||
|
error.value = 'Please select a shipping zone'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
orderConfirmed.value = true
|
||||||
|
}
|
||||||
|
|
||||||
const placeOrder = async () => {
|
const placeOrder = async () => {
|
||||||
if (!canPlaceOrder.value) {
|
// Only require shipping address if selected zone requires physical shipping
|
||||||
|
const requiresShippingAddress = selectedShippingZone.value?.requiresPhysicalShipping !== false
|
||||||
|
|
||||||
|
if (requiresShippingAddress && !contactData.value.address) {
|
||||||
|
error.value = 'Shipping address is required for this delivery method'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -466,7 +464,7 @@ const placeOrder = async () => {
|
||||||
currency: checkoutCart.value.currency,
|
currency: checkoutCart.value.currency,
|
||||||
requiresPhysicalShipping: false
|
requiresPhysicalShipping: false
|
||||||
},
|
},
|
||||||
paymentMethod: 'lightning' as const,
|
paymentMethod: paymentMethod.value === 'ln' ? 'lightning' as const : 'btc_onchain' as const,
|
||||||
subtotal: orderSubtotal.value,
|
subtotal: orderSubtotal.value,
|
||||||
shippingCost: selectedShippingZone.value?.cost || 0,
|
shippingCost: selectedShippingZone.value?.cost || 0,
|
||||||
total: orderTotal.value,
|
total: orderTotal.value,
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,6 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
import { useMarketStore } from '@/modules/market/stores/market'
|
import { useMarketStore } from '@/modules/market/stores/market'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import {
|
import {
|
||||||
|
|
@ -78,13 +77,10 @@ import MerchantStore from '../components/MerchantStore.vue'
|
||||||
import MarketSettings from '../components/MarketSettings.vue'
|
import MarketSettings from '../components/MarketSettings.vue'
|
||||||
import { auth } from '@/composables/useAuthService'
|
import { auth } from '@/composables/useAuthService'
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const marketStore = useMarketStore()
|
const marketStore = useMarketStore()
|
||||||
|
|
||||||
// Local state - check for tab query param
|
// Local state
|
||||||
const validTabs = ['overview', 'orders', 'store', 'settings']
|
const activeTab = ref('overview')
|
||||||
const initialTab = validTabs.includes(route.query.tab as string) ? route.query.tab as string : 'overview'
|
|
||||||
const activeTab = ref(initialTab)
|
|
||||||
|
|
||||||
// Computed properties for tab badges
|
// Computed properties for tab badges
|
||||||
const orderCount = computed(() => {
|
const orderCount = computed(() => {
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,7 @@ watch(comments, (newComments) => {
|
||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen bg-background">
|
<div class="min-h-screen bg-background">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<header class="sticky top-0 z-30 bg-background/95 backdrop-blur border-b">
|
<header class="sticky top-0 z-40 bg-background/95 backdrop-blur border-b">
|
||||||
<div class="max-w-4xl mx-auto px-4 py-3">
|
<div class="max-w-4xl mx-auto px-4 py-3">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<Button variant="ghost" size="sm" @click="goBack" class="h-8 w-8 p-0">
|
<Button variant="ghost" size="sm" @click="goBack" class="h-8 w-8 p-0">
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<PWAInstallPrompt auto-show />
|
<PWAInstallPrompt auto-show />
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="sticky top-0 z-30 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b">
|
<div class="sticky top-0 z-40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b">
|
||||||
<div class="max-w-4xl mx-auto flex items-center justify-between px-4 py-2 sm:px-6">
|
<div class="max-w-4xl mx-auto flex items-center justify-between px-4 py-2 sm:px-6">
|
||||||
<h1 class="text-lg font-semibold">Feed</h1>
|
<h1 class="text-lg font-semibold">Feed</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue