Squash merge remove-dangling-bits into market-implementation-squashed
This commit is contained in:
parent
4bc15cfa2f
commit
2f0024478d
17 changed files with 569 additions and 859 deletions
|
|
@ -48,7 +48,7 @@
|
|||
<h5>Stalls Published:</h5>
|
||||
<ul>
|
||||
<li v-for="(eventId, stallId) in lastResult.stalls" :key="stallId">
|
||||
{{ getStallName(stallId) }}: {{ eventId }}
|
||||
{{ getStallName(String(stallId)) }}: {{ eventId }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
<h5>Products Published:</h5>
|
||||
<ul>
|
||||
<li v-for="(eventId, productId) in lastResult.products" :key="productId">
|
||||
{{ getProductName(productId) }}: {{ eventId }}
|
||||
{{ getProductName(String(productId)) }}: {{ eventId }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -72,7 +72,6 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useMarketStore } from '@/stores/market'
|
||||
import { nostrmarketService } from '@/lib/services/nostrmarketService'
|
||||
|
||||
const marketStore = useMarketStore()
|
||||
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ import { useRouter } from 'vue-router'
|
|||
import { useMarketStore } from '@/stores/market'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
import { useMarket } from '@/composables/useMarket'
|
||||
import { orderEvents } from '@/composables/useOrderEvents'
|
||||
import { useOrderEvents } from '@/composables/useOrderEvents'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import {
|
||||
|
|
@ -236,6 +236,7 @@ const router = useRouter()
|
|||
const marketStore = useMarketStore()
|
||||
const auth = useAuth()
|
||||
const { isConnected } = useMarket()
|
||||
const orderEvents = useOrderEvents()
|
||||
|
||||
// Computed properties
|
||||
const orderStats = computed(() => {
|
||||
|
|
|
|||
|
|
@ -202,12 +202,13 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
// import { useMarketStore } from '@/stores/market'
|
||||
import { orderEvents } from '@/composables/useOrderEvents'
|
||||
import { useOrderEvents } from '@/composables/useOrderEvents'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Plus, X } from 'lucide-vue-next'
|
||||
|
||||
// const marketStore = useMarketStore()
|
||||
const orderEvents = useOrderEvents()
|
||||
|
||||
// Local state
|
||||
const activeSettingsTab = ref('store')
|
||||
|
|
|
|||
|
|
@ -9,10 +9,7 @@
|
|||
<div class="flex items-center gap-3">
|
||||
<!-- Order Events Status -->
|
||||
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<div
|
||||
class="w-2 h-2 rounded-full"
|
||||
:class="orderEvents.isSubscribed ? 'bg-green-500' : 'bg-yellow-500'"
|
||||
></div>
|
||||
<div class="w-2 h-2 rounded-full" :class="orderEvents.isSubscribed ? 'bg-green-500' : 'bg-yellow-500'"></div>
|
||||
<span>{{ orderEvents.isSubscribed ? 'Live updates' : 'Connecting...' }}</span>
|
||||
</div>
|
||||
<Button @click="navigateToMarket" variant="outline">
|
||||
|
|
@ -32,7 +29,7 @@
|
|||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-muted-foreground">Pending:</span>
|
||||
<Badge variant="outline" class="text-yellow-600">{{ pendingOrders }}</Badge>
|
||||
<Badge variant="outline" class="text-amber-600">{{ pendingOrders }}</Badge>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-muted-foreground">Paid:</span>
|
||||
|
|
@ -46,7 +43,8 @@
|
|||
|
||||
<!-- Filter Controls -->
|
||||
<div class="flex gap-2">
|
||||
<select v-model="statusFilter" class="px-3 py-2 border border-input rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring bg-background text-foreground">
|
||||
<select v-model="statusFilter"
|
||||
class="px-3 py-2 border border-input rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring bg-background text-foreground">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="pending">Pending</option>
|
||||
<option value="paid">Paid</option>
|
||||
|
|
@ -55,7 +53,8 @@
|
|||
<option value="delivered">Delivered</option>
|
||||
<option value="cancelled">Cancelled</option>
|
||||
</select>
|
||||
<select v-model="sortBy" class="px-3 py-2 border border-input rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring bg-background text-foreground">
|
||||
<select v-model="sortBy"
|
||||
class="px-3 py-2 border border-input rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring bg-background text-foreground">
|
||||
<option value="createdAt">Date Created</option>
|
||||
<option value="total">Order Total</option>
|
||||
<option value="status">Status</option>
|
||||
|
|
@ -65,12 +64,9 @@
|
|||
|
||||
<!-- Orders List -->
|
||||
<div v-if="filteredOrders.length > 0" class="space-y-4">
|
||||
<div
|
||||
v-for="order in sortedOrders"
|
||||
:key="order.id"
|
||||
class="bg-card border rounded-lg p-6 hover:shadow-md transition-shadow"
|
||||
>
|
||||
<!-- Order Header -->
|
||||
<div v-for="order in sortedOrders" :key="order.id"
|
||||
class="bg-card border rounded-lg p-6 hover:shadow-md transition-shadow">
|
||||
<!-- Order Header -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 bg-primary/10 rounded-full flex items-center justify-center">
|
||||
|
|
@ -81,247 +77,124 @@
|
|||
<p class="text-sm text-muted-foreground">
|
||||
{{ formatDate(order.createdAt) }}
|
||||
</p>
|
||||
<!-- Nostr Status -->
|
||||
<div v-if="order.sentViaNostr !== undefined" class="flex items-center gap-2 mt-1">
|
||||
<div v-if="order.sentViaNostr" class="flex items-center gap-1 text-xs text-green-600">
|
||||
<div class="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||
Sent via Nostr
|
||||
</div>
|
||||
<div v-else class="flex items-center gap-1 text-xs text-red-600">
|
||||
<div class="w-2 h-2 bg-red-500 rounded-full"></div>
|
||||
Nostr failed
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<Badge :variant="getStatusVariant(order.status)">
|
||||
<Badge :variant="getStatusVariant(order.status)" :class="getStatusColor(order.status)">
|
||||
{{ formatStatus(order.status) }}
|
||||
</Badge>
|
||||
<!-- Payment Status Indicator -->
|
||||
<div v-if="order.lightningInvoice" class="flex items-center gap-2">
|
||||
<Badge
|
||||
:variant="order.paymentStatus === 'paid' ? 'default' : 'secondary'"
|
||||
class="text-xs"
|
||||
>
|
||||
<div class="flex items-center gap-1">
|
||||
<div v-if="order.paymentStatus === 'paid'" class="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||
<div v-else class="w-2 h-2 bg-yellow-500 rounded-full animate-pulse"></div>
|
||||
{{ order.paymentStatus === 'paid' ? 'Paid' : 'Payment Pending' }}
|
||||
</div>
|
||||
</Badge>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="text-lg font-semibold text-foreground">
|
||||
{{ formatPrice(order.total, order.currency) }}
|
||||
</p>
|
||||
<p class="text-sm text-muted-foreground">{{ order.currency.toUpperCase() }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Order Summary -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
|
||||
<!-- Items -->
|
||||
<div>
|
||||
<h4 class="font-medium text-foreground mb-2">Items</h4>
|
||||
<div class="space-y-1">
|
||||
<div
|
||||
v-for="item in order.items.slice(0, 3)"
|
||||
:key="item.productId"
|
||||
class="text-sm text-muted-foreground"
|
||||
>
|
||||
{{ item.productName }} × {{ item.quantity }}
|
||||
</div>
|
||||
<div v-if="order.items.length > 3" class="text-sm text-muted-foreground">
|
||||
+{{ order.items.length - 3 }} more items
|
||||
</div>
|
||||
<!-- Order Items -->
|
||||
<div class="mb-4">
|
||||
<h4 class="font-medium text-foreground mb-2">Items</h4>
|
||||
<div class="space-y-1">
|
||||
<div v-for="item in order.items.slice(0, 3)" :key="item.productId" class="text-sm text-muted-foreground">
|
||||
{{ item.productName }} × {{ item.quantity }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payment Status -->
|
||||
<div>
|
||||
<h4 class="font-medium text-foreground mb-2">Payment</h4>
|
||||
<div v-if="order.lightningInvoice" class="space-y-1 text-sm">
|
||||
<p class="text-muted-foreground">
|
||||
<span class="font-medium">Status:</span> {{ order.paymentStatus === 'paid' ? 'Paid' : 'Pending' }}
|
||||
</p>
|
||||
<p class="text-muted-foreground">
|
||||
<span class="font-medium">Invoice:</span> {{ order.lightningInvoice.payment_hash.slice(0, 8) }}...
|
||||
</p>
|
||||
</div>
|
||||
<div v-else class="text-sm text-muted-foreground">
|
||||
Waiting for merchant invoice
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Shipping -->
|
||||
<div>
|
||||
<h4 class="font-medium text-foreground mb-2">Shipping</h4>
|
||||
<div class="space-y-1 text-sm text-muted-foreground">
|
||||
<p>
|
||||
<span class="font-medium">Zone:</span> {{ order.shippingZone?.name || 'N/A' }}
|
||||
</p>
|
||||
<p v-if="order.shippingZone?.estimatedDays">
|
||||
<span class="font-medium">Est. Delivery:</span> {{ order.shippingZone.estimatedDays }}
|
||||
</p>
|
||||
<div v-if="order.items.length > 3" class="text-sm text-muted-foreground">
|
||||
+{{ order.items.length - 3 }} more items
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payment Status & Actions -->
|
||||
<div v-if="order.status === 'pending'" class="mb-4 p-4 bg-muted/50 border border-border rounded-lg">
|
||||
<div class="flex-1">
|
||||
<h4 class="font-medium text-foreground mb-2">Payment Required</h4>
|
||||
<div v-if="order.lightningInvoice" class="space-y-2">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
<span class="font-medium text-green-600">✓</span> Lightning invoice received from merchant
|
||||
</p>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Amount: <span class="font-medium text-foreground">{{ formatPrice(order.total, order.currency) }}</span>
|
||||
</p>
|
||||
<!-- Payment Section -->
|
||||
<div v-if="order.lightningInvoice" class="mb-4 p-4 bg-muted/50 border border-border rounded-lg">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<Zap class="w-4 h-4 text-yellow-500" />
|
||||
<span class="font-medium text-foreground">Payment Required</span>
|
||||
</div>
|
||||
<div v-else-if="order.paymentRequest" class="space-y-2">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
<span class="font-medium text-green-600">✓</span> Payment request received from merchant
|
||||
</p>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Amount: <span class="text-sm text-muted-foreground">{{ formatPrice(order.total, order.currency) }}</span>
|
||||
</p>
|
||||
<Badge :variant="order.paymentStatus === 'paid' ? 'default' : 'secondary'" class="text-xs"
|
||||
:class="order.paymentStatus === 'paid' ? 'text-green-600' : 'text-amber-600'">
|
||||
{{ order.paymentStatus === 'paid' ? 'Paid' : 'Pending' }}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Payment Details -->
|
||||
<div class="space-y-3">
|
||||
<!-- Payment Request -->
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-muted-foreground mb-1">
|
||||
Payment Request
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<input :value="order.lightningInvoice?.bolt11 || ''" readonly disabled
|
||||
class="flex-1 font-mono text-xs bg-muted border border-input rounded-md px-3 py-1 text-foreground" />
|
||||
<Button @click="copyPaymentRequest(order.lightningInvoice?.bolt11 || '')" variant="outline" size="sm">
|
||||
<Copy class="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payment Options -->
|
||||
<div class="mt-4 space-y-3">
|
||||
<h5 class="text-sm font-medium text-foreground">Payment Options:</h5>
|
||||
|
||||
<!-- Lightning Payment -->
|
||||
<div v-if="order.paymentRequest" class="bg-card border rounded-lg p-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<Zap class="w-4 h-4 text-yellow-500" />
|
||||
<span class="font-medium text-sm">Lightning Payment</span>
|
||||
</div>
|
||||
<Badge variant="outline" class="text-xs">Recommended</Badge>
|
||||
<!-- Payment Actions -->
|
||||
<div class="flex gap-2">
|
||||
<Button @click="openLightningWallet(order.lightningInvoice?.bolt11 || '')" variant="default" size="sm"
|
||||
class="flex-1" :disabled="!order.lightningInvoice?.bolt11">
|
||||
<Zap class="w-3 h-3 mr-1" />
|
||||
Pay with Lightning
|
||||
</Button>
|
||||
<Button @click="toggleQRCode(order.id)" variant="outline" size="sm">
|
||||
<QrCode class="w-3 h-3" />
|
||||
{{ order.showQRCode ? 'Hide QR' : 'Show QR' }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- QR Code -->
|
||||
<div v-if="order.showQRCode" class="flex justify-center">
|
||||
<div class="w-48 h-48">
|
||||
<div v-if="order.qrCodeDataUrl && !order.qrCodeError" class="w-full h-full">
|
||||
<img :src="order.qrCodeDataUrl"
|
||||
:alt="`QR Code for ${formatPrice(order.total, order.currency)} payment`"
|
||||
class="w-full h-full border border-border rounded-lg" />
|
||||
</div>
|
||||
<div v-else-if="order.qrCodeLoading"
|
||||
class="w-full h-full bg-muted rounded-lg flex items-center justify-center">
|
||||
<div class="text-center text-muted-foreground">
|
||||
<div class="text-4xl mb-2 animate-pulse">⚡</div>
|
||||
<div class="text-sm">Generating QR...</div>
|
||||
</div>
|
||||
|
||||
<!-- QR Code -->
|
||||
<div class="text-center mb-3">
|
||||
<div class="w-32 h-32 mx-auto mb-2">
|
||||
<div v-if="order.qrCodeDataUrl && !order.qrCodeError" class="w-full h-full">
|
||||
<img
|
||||
:src="order.qrCodeDataUrl"
|
||||
:alt="`QR Code for ${formatPrice(order.total, order.currency)} payment`"
|
||||
class="w-full h-full border border-border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="order.qrCodeLoading" class="w-full h-full bg-muted rounded-lg flex items-center justify-center">
|
||||
<div class="text-center text-muted-foreground">
|
||||
<div class="text-2xl mb-1 animate-pulse">⚡</div>
|
||||
<div class="text-xs">Generating...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="w-full h-full bg-muted rounded-lg flex items-center justify-center">
|
||||
<div class="text-center text-muted-foreground">
|
||||
<div class="text-2xl mb-1">⚡</div>
|
||||
<div class="text-xs">No QR</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payment Request Link -->
|
||||
<div class="space-y-2">
|
||||
<label class="block text-xs font-medium text-muted-foreground">
|
||||
Payment Request
|
||||
</label>
|
||||
<!-- Debug info -->
|
||||
<div class="text-xs text-red-500 mb-2">
|
||||
Debug: paymentRequest = "{{ order.paymentRequest }}" (length: {{ order.paymentRequest?.length || 0 }})<br>
|
||||
Debug: paymentHash = "{{ order.paymentHash }}" (length: {{ order.paymentHash?.length || 0 }})<br>
|
||||
Debug: Order keys: {{ Object.keys(order).join(', ') }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Input
|
||||
:value="order.paymentRequest || ''"
|
||||
readonly
|
||||
class="flex-1 font-mono text-xs"
|
||||
/>
|
||||
<Button
|
||||
@click="copyPaymentRequest(order.paymentRequest)"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="!order.paymentRequest"
|
||||
>
|
||||
<Copy class="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payment Actions -->
|
||||
<div class="flex gap-2 mt-3">
|
||||
<Button
|
||||
@click="openLightningWallet(order.paymentRequest)"
|
||||
variant="default"
|
||||
size="sm"
|
||||
class="flex-1"
|
||||
>
|
||||
<Zap class="w-3 h-3 mr-1" />
|
||||
Pay with Lightning
|
||||
</Button>
|
||||
<Button
|
||||
@click="downloadQRCode(order)"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
<Download class="w-3 h-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-else class="w-full h-full bg-muted rounded-lg flex items-center justify-center">
|
||||
<div class="text-center text-muted-foreground">
|
||||
<div class="text-4xl mb-2">⚡</div>
|
||||
<div class="text-sm">No QR</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="space-y-2">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
<span class="font-medium text-amber-600">⏳</span> Waiting for merchant to generate payment invoice
|
||||
</p>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
The merchant will send you a Lightning invoice via Nostr once they process your order
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Waiting for Invoice -->
|
||||
<div v-else-if="order.status === 'pending'" class="mb-4 p-4 bg-muted/50 border border-border rounded-lg">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div class="w-2 h-2 bg-amber-500 rounded-full animate-pulse"></div>
|
||||
<span class="font-medium text-foreground">Waiting for Payment Invoice</span>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
The merchant will send you a Lightning invoice via Nostr once they process your order
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex justify-end gap-2 pt-4 border-t border-border">
|
||||
<Button
|
||||
v-if="order.status === 'pending'"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@click="cancelOrder(order.id)"
|
||||
>
|
||||
<Button v-if="order.status === 'pending'" variant="outline" size="sm" @click="cancelOrder(order.id)">
|
||||
Cancel Order
|
||||
</Button>
|
||||
<Button
|
||||
v-if="order.lightningInvoice"
|
||||
variant="default"
|
||||
size="sm"
|
||||
@click="togglePaymentDisplay(order.id)"
|
||||
>
|
||||
<Wallet class="w-4 h-4 mr-2" />
|
||||
{{ expandedPayments.has(order.id) ? 'Hide' : 'Show' }} Payment
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@click="copyOrderId(order.id)"
|
||||
>
|
||||
<Button variant="outline" size="sm" @click="copyOrderId(order.id)">
|
||||
Copy Order ID
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- Payment Display (Expandable) -->
|
||||
<div v-if="expandedPayments.has(order.id) && order.lightningInvoice" class="mt-4 pt-4 border-t border-border">
|
||||
<PaymentDisplay :order-id="order.id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -358,25 +231,23 @@
|
|||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useMarketStore } from '@/stores/market'
|
||||
import { orderEvents } from '@/composables/useOrderEvents'
|
||||
import { useOrderEvents } from '@/composables/useOrderEvents'
|
||||
import { relayHubComposable } from '@/composables/useRelayHub'
|
||||
import { auth } from '@/composables/useAuth'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Package, Store, Wallet, Zap, Copy, Download } from 'lucide-vue-next'
|
||||
import { Package, Store, Zap, Copy, QrCode } from 'lucide-vue-next'
|
||||
import { toast } from 'vue-sonner'
|
||||
import PaymentDisplay from './PaymentDisplay.vue'
|
||||
import type { OrderStatus } from '@/stores/market'
|
||||
|
||||
const router = useRouter()
|
||||
const marketStore = useMarketStore()
|
||||
const relayHub = relayHubComposable
|
||||
const orderEvents = useOrderEvents()
|
||||
|
||||
// Local state
|
||||
const statusFilter = ref('')
|
||||
const sortBy = ref('createdAt')
|
||||
const expandedPayments = ref(new Set<string>())
|
||||
|
||||
// Computed properties
|
||||
const allOrders = computed(() => Object.values(marketStore.orders))
|
||||
|
|
@ -388,7 +259,7 @@ const filteredOrders = computed(() => {
|
|||
|
||||
const sortedOrders = computed(() => {
|
||||
const orders = [...filteredOrders.value]
|
||||
|
||||
|
||||
switch (sortBy.value) {
|
||||
case 'total':
|
||||
return orders.sort((a, b) => b.total - a.total)
|
||||
|
|
@ -442,6 +313,18 @@ const getStatusVariant = (status: OrderStatus) => {
|
|||
return variantMap[status] || 'outline'
|
||||
}
|
||||
|
||||
const getStatusColor = (status: OrderStatus) => {
|
||||
const colorMap: Record<OrderStatus, string> = {
|
||||
pending: 'text-amber-600',
|
||||
paid: 'text-green-600',
|
||||
processing: 'text-blue-600',
|
||||
shipped: 'text-blue-600',
|
||||
delivered: 'text-green-600',
|
||||
cancelled: 'text-red-600'
|
||||
}
|
||||
return colorMap[status] || 'text-muted-foreground'
|
||||
}
|
||||
|
||||
const formatPrice = (price: number, currency: string) => {
|
||||
return marketStore.formatPrice(price, currency)
|
||||
}
|
||||
|
|
@ -451,14 +334,6 @@ const cancelOrder = (orderId: string) => {
|
|||
console.log('Cancelling order:', orderId)
|
||||
}
|
||||
|
||||
const togglePaymentDisplay = (orderId: string) => {
|
||||
if (expandedPayments.value.has(orderId)) {
|
||||
expandedPayments.value.delete(orderId)
|
||||
} else {
|
||||
expandedPayments.value.add(orderId)
|
||||
}
|
||||
}
|
||||
|
||||
const copyOrderId = async (orderId: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(orderId)
|
||||
|
|
@ -476,14 +351,14 @@ const copyPaymentRequest = async (paymentRequest: string) => {
|
|||
hasValue: !!paymentRequest,
|
||||
length: paymentRequest?.length
|
||||
})
|
||||
|
||||
|
||||
if (!paymentRequest) {
|
||||
toast.error('No payment request available', {
|
||||
description: 'Please wait for the merchant to send the payment request'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(paymentRequest)
|
||||
toast.success('Payment request copied to clipboard', {
|
||||
|
|
@ -505,7 +380,7 @@ const openLightningWallet = (paymentRequest: string) => {
|
|||
`bitcoin:${paymentRequest}`,
|
||||
paymentRequest
|
||||
]
|
||||
|
||||
|
||||
// Try each protocol
|
||||
for (const protocol of protocols) {
|
||||
try {
|
||||
|
|
@ -518,33 +393,60 @@ const openLightningWallet = (paymentRequest: string) => {
|
|||
console.warn('Failed to open protocol:', protocol, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Fallback: copy to clipboard
|
||||
copyPaymentRequest(paymentRequest)
|
||||
}
|
||||
|
||||
const downloadQRCode = async (order: any) => {
|
||||
if (!order.qrCodeDataUrl) {
|
||||
toast.error('QR code not available', {
|
||||
description: 'Please wait for the QR code to generate'
|
||||
const toggleQRCode = async (orderId: string) => {
|
||||
// Toggle QR code visibility for the order
|
||||
const order = marketStore.orders[orderId]
|
||||
if (order) {
|
||||
// If showing QR code and it doesn't exist yet, generate it
|
||||
if (!order.showQRCode && order.lightningInvoice?.bolt11 && !order.qrCodeDataUrl) {
|
||||
await generateQRCode(orderId, order.lightningInvoice.bolt11)
|
||||
}
|
||||
|
||||
marketStore.updateOrder(orderId, {
|
||||
showQRCode: !order.showQRCode
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const generateQRCode = async (orderId: string, bolt11: string) => {
|
||||
try {
|
||||
const link = document.createElement('a')
|
||||
link.href = order.qrCodeDataUrl
|
||||
link.download = `payment-qr-${order.id}.png`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
toast.success('QR code downloaded', {
|
||||
description: 'You can now scan it with your mobile wallet'
|
||||
// Set loading state
|
||||
marketStore.updateOrder(orderId, {
|
||||
qrCodeLoading: true,
|
||||
qrCodeError: null
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Failed to download QR code:', err)
|
||||
toast.error('Failed to download QR code', {
|
||||
description: 'Please try again'
|
||||
|
||||
// Import QRCode library dynamically
|
||||
const QRCode = await import('qrcode')
|
||||
|
||||
// Generate QR code
|
||||
const qrCodeDataUrl = await QRCode.toDataURL(bolt11, {
|
||||
width: 192,
|
||||
margin: 2,
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#FFFFFF'
|
||||
}
|
||||
})
|
||||
|
||||
// Update order with QR code
|
||||
marketStore.updateOrder(orderId, {
|
||||
qrCodeDataUrl,
|
||||
qrCodeLoading: false,
|
||||
qrCodeError: null
|
||||
})
|
||||
|
||||
console.log('QR code generated for order:', orderId)
|
||||
} catch (error) {
|
||||
console.error('Failed to generate QR code:', error)
|
||||
marketStore.updateOrder(orderId, {
|
||||
qrCodeLoading: false,
|
||||
qrCodeError: error instanceof Error ? error.message : 'Failed to generate QR code'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -556,7 +458,7 @@ onMounted(() => {
|
|||
// Orders are already loaded in the market store
|
||||
console.log('Order History component loaded with', allOrders.value.length, 'orders')
|
||||
console.log('Market store orders:', marketStore.orders)
|
||||
|
||||
|
||||
// Debug: Log order details for orders with payment requests
|
||||
allOrders.value.forEach(order => {
|
||||
if (order.paymentRequest) {
|
||||
|
|
@ -569,16 +471,16 @@ onMounted(() => {
|
|||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
console.log('Order events status:', orderEvents.isSubscribed.value)
|
||||
console.log('Relay hub connected:', relayHub.isConnected.value)
|
||||
console.log('Auth status:', auth.isAuthenticated)
|
||||
console.log('Current user:', auth.currentUser?.value?.pubkey)
|
||||
|
||||
|
||||
// Start listening for order events if not already listening
|
||||
if (!orderEvents.isSubscribed.value) {
|
||||
console.log('Starting order events listener...')
|
||||
orderEvents.startListening()
|
||||
orderEvents.initialize()
|
||||
} else {
|
||||
console.log('Order events already listening')
|
||||
}
|
||||
|
|
@ -590,10 +492,9 @@ watch(
|
|||
([isAuth, isConnected]) => {
|
||||
if (isAuth && isConnected && !orderEvents.isSubscribed.value) {
|
||||
console.log('Auth and relay hub ready, starting order events listener...')
|
||||
orderEvents.startListening()
|
||||
orderEvents.initialize()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="bg-white border rounded-lg p-6">
|
||||
<div class="bg-background border rounded-lg p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900">Payment</h3>
|
||||
<h3 class="text-lg font-semibold text-foreground">Payment</h3>
|
||||
<Badge :variant="getPaymentStatusVariant(paymentStatus)">
|
||||
{{ formatPaymentStatus(paymentStatus) }}
|
||||
</Badge>
|
||||
|
|
@ -10,15 +10,15 @@
|
|||
<!-- Invoice Information -->
|
||||
<div v-if="invoice" class="space-y-4">
|
||||
<!-- Amount and Status -->
|
||||
<div class="flex items-center justify-between p-4 bg-gray-50 rounded-lg">
|
||||
<div class="flex items-center justify-between p-4 bg-muted/50 rounded-lg">
|
||||
<div>
|
||||
<p class="text-sm text-gray-600">Amount</p>
|
||||
<p class="text-2xl font-bold text-gray-900">
|
||||
<p class="text-sm text-muted-foreground">Amount</p>
|
||||
<p class="text-2xl font-bold text-foreground">
|
||||
{{ invoice.amount }} {{ currency }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="text-sm text-gray-600">Status</p>
|
||||
<p class="text-sm text-muted-foreground">Status</p>
|
||||
<p class="text-lg font-semibold" :class="getStatusColor(paymentStatus)">
|
||||
{{ formatPaymentStatus(paymentStatus) }}
|
||||
</p>
|
||||
|
|
@ -28,8 +28,8 @@
|
|||
<!-- Lightning Invoice QR Code -->
|
||||
<div class="text-center">
|
||||
<div class="mb-4">
|
||||
<h4 class="font-medium text-gray-900 mb-2">Lightning Invoice</h4>
|
||||
<p class="text-sm text-gray-600">Scan with your Lightning wallet to pay</p>
|
||||
<h4 class="font-medium text-foreground mb-2">Lightning Invoice</h4>
|
||||
<p class="text-sm text-muted-foreground">Scan with your Lightning wallet to pay</p>
|
||||
</div>
|
||||
|
||||
<!-- QR Code -->
|
||||
|
|
@ -38,17 +38,17 @@
|
|||
<img
|
||||
:src="qrCodeDataUrl"
|
||||
:alt="`QR Code for ${invoice.amount} ${currency} payment`"
|
||||
class="w-full h-full border border-gray-200 rounded-lg"
|
||||
class="w-full h-full border border-border rounded-lg"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="qrCodeLoading" class="w-full h-full bg-gray-100 rounded-lg flex items-center justify-center">
|
||||
<div class="text-center text-gray-500">
|
||||
<div v-else-if="qrCodeLoading" class="w-full h-full bg-muted rounded-lg flex items-center justify-center">
|
||||
<div class="text-center text-muted-foreground">
|
||||
<div class="text-4xl mb-2 animate-pulse">⚡</div>
|
||||
<div class="text-sm">Generating QR...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="qrCodeError" class="w-full h-full bg-red-50 border border-red-200 rounded-lg flex items-center justify-center">
|
||||
<div class="text-center text-red-500">
|
||||
<div v-else-if="qrCodeError" class="w-full h-full bg-destructive/10 border border-destructive/20 rounded-lg flex items-center justify-center">
|
||||
<div class="text-center text-destructive">
|
||||
<div class="text-4xl mb-2">⚠️</div>
|
||||
<div class="text-sm">{{ qrCodeError }}</div>
|
||||
<Button
|
||||
|
|
@ -61,8 +61,8 @@
|
|||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="w-full h-full bg-gray-100 rounded-lg flex items-center justify-center">
|
||||
<div class="text-center text-gray-500">
|
||||
<div v-else class="w-full h-full bg-muted rounded-lg flex items-center justify-center">
|
||||
<div class="text-center text-muted-foreground">
|
||||
<div class="text-4xl mb-2">⚡</div>
|
||||
<div class="text-sm">No invoice</div>
|
||||
</div>
|
||||
|
|
@ -71,20 +71,12 @@
|
|||
|
||||
<!-- QR Code Actions -->
|
||||
<div v-if="qrCodeDataUrl && !qrCodeError" class="mb-4">
|
||||
<Button
|
||||
@click="downloadQRCode"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="w-full"
|
||||
>
|
||||
<Download class="w-4 h-4 mr-2" />
|
||||
Download QR Code
|
||||
</Button>
|
||||
<!-- Download button removed -->
|
||||
</div>
|
||||
|
||||
<!-- Payment Request -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label class="block text-sm font-medium text-foreground mb-2">
|
||||
Payment Request
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
|
|
@ -116,23 +108,23 @@
|
|||
|
||||
<!-- Payment Details -->
|
||||
<div class="border-t pt-4">
|
||||
<h4 class="font-medium text-gray-900 mb-3">Payment Details</h4>
|
||||
<h4 class="font-medium text-foreground mb-3">Payment Details</h4>
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">Payment Hash:</span>
|
||||
<span class="font-mono text-gray-900">{{ formatHash(invoice.payment_hash) }}</span>
|
||||
<span class="text-muted-foreground">Payment Hash:</span>
|
||||
<span class="font-mono text-foreground">{{ formatHash(invoice.payment_hash) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">Created:</span>
|
||||
<span class="text-gray-900">{{ formatDate(invoice.created_at ? new Date(invoice.created_at).getTime() : Date.now()) }}</span>
|
||||
<span class="text-muted-foreground">Created:</span>
|
||||
<span class="text-foreground">{{ formatDate(invoice.created_at) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">Expires:</span>
|
||||
<span class="text-gray-900">{{ formatDate(invoice.expiry ? new Date(invoice.expiry).getTime() : Date.now()) }}</span>
|
||||
<span class="text-muted-foreground">Expires:</span>
|
||||
<span class="text-foreground">{{ formatDate(invoice.expiry) }}</span>
|
||||
</div>
|
||||
<div v-if="paidAt" class="flex justify-between">
|
||||
<span class="text-gray-600">Paid At:</span>
|
||||
<span class="text-gray-900">{{ formatDate(paidAt) }}</span>
|
||||
<span class="text-muted-foreground">Paid At:</span>
|
||||
<span class="text-foreground">{{ formatDate(paidAt) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -140,14 +132,14 @@
|
|||
|
||||
<!-- No Invoice State -->
|
||||
<div v-else class="text-center py-8">
|
||||
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Wallet class="w-8 h-8 text-gray-400" />
|
||||
<div class="w-16 h-16 bg-muted rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Wallet class="w-8 h-8 text-muted-foreground" />
|
||||
</div>
|
||||
<h4 class="text-lg font-medium text-gray-900 mb-2">No Payment Invoice</h4>
|
||||
<p class="text-gray-600 mb-4">
|
||||
<h4 class="text-lg font-medium text-foreground mb-2">No Payment Invoice</h4>
|
||||
<p class="text-muted-foreground mb-4">
|
||||
A Lightning invoice will be sent by the merchant once they process your order.
|
||||
</p>
|
||||
<p class="text-sm text-gray-500">
|
||||
<p class="text-sm text-muted-foreground">
|
||||
You'll receive the invoice via Nostr when it's ready.
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -193,10 +185,8 @@ import { Badge } from '@/components/ui/badge'
|
|||
import {
|
||||
Copy,
|
||||
Wallet,
|
||||
|
||||
Info,
|
||||
CheckCircle,
|
||||
Download
|
||||
CheckCircle
|
||||
} from 'lucide-vue-next'
|
||||
import { useMarketStore } from '@/stores/market'
|
||||
import QRCode from 'qrcode'
|
||||
|
|
@ -266,9 +256,9 @@ const formatPaymentStatus = (status: string) => {
|
|||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'paid': return 'text-green-600'
|
||||
case 'pending': return 'text-yellow-600'
|
||||
case 'expired': return 'text-red-600'
|
||||
default: return 'text-gray-600'
|
||||
case 'pending': return 'text-amber-600'
|
||||
case 'expired': return 'text-destructive'
|
||||
default: return 'text-muted-foreground'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -277,9 +267,20 @@ const formatHash = (hash: string) => {
|
|||
return `${hash.substring(0, 8)}...${hash.substring(hash.length - 8)}`
|
||||
}
|
||||
|
||||
const formatDate = (timestamp: number) => {
|
||||
if (!timestamp) return 'N/A'
|
||||
return new Date(timestamp * 1000).toLocaleString()
|
||||
const formatDate = (dateValue: string | number | undefined) => {
|
||||
if (!dateValue) return 'N/A'
|
||||
|
||||
let timestamp: number
|
||||
|
||||
if (typeof dateValue === 'string') {
|
||||
// Handle ISO date strings from LNBits API
|
||||
timestamp = new Date(dateValue).getTime()
|
||||
} else {
|
||||
// Handle Unix timestamps (seconds) from our store
|
||||
timestamp = dateValue * 1000
|
||||
}
|
||||
|
||||
return new Date(timestamp).toLocaleString()
|
||||
}
|
||||
|
||||
const copyPaymentRequest = async () => {
|
||||
|
|
@ -302,17 +303,6 @@ const openInWallet = () => {
|
|||
window.open(walletUrl, '_blank')
|
||||
}
|
||||
|
||||
const downloadQRCode = () => {
|
||||
if (!qrCodeDataUrl.value) return
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = qrCodeDataUrl.value
|
||||
link.download = `qr-code-${invoice.value?.amount}-${currency.value}.png`
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
|
||||
const retryQRCode = () => {
|
||||
if (invoice.value?.bolt11) {
|
||||
generateQRCode(invoice.value.bolt11)
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ const payWithWallet = async () => {
|
|||
const { useAuth } = await import('@/composables/useAuth')
|
||||
const auth = useAuth()
|
||||
|
||||
if (!auth.currentUser.value?.walletId || !auth.currentUser.value?.adminKey) {
|
||||
if (!auth.currentUser.value?.wallets?.[0]?.id || !auth.currentUser.value?.wallets?.[0]?.adminkey) {
|
||||
toast.error('Please connect your wallet to pay')
|
||||
return
|
||||
}
|
||||
|
|
@ -263,8 +263,8 @@ const payWithWallet = async () => {
|
|||
// Pay the invoice
|
||||
const result = await payInvoiceWithWallet(
|
||||
lightningInvoice.value,
|
||||
auth.currentUser.value.walletId,
|
||||
auth.currentUser.value.adminKey
|
||||
auth.currentUser.value.wallets[0].id,
|
||||
auth.currentUser.value.wallets[0].adminkey
|
||||
)
|
||||
|
||||
console.log('Payment result:', result)
|
||||
|
|
|
|||
|
|
@ -20,5 +20,11 @@ const modelValue = useVModel(props, 'modelValue', emits, {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<input v-model="modelValue" :class="cn('flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50', props.class)">
|
||||
<input
|
||||
v-model="modelValue"
|
||||
:class="cn(
|
||||
'flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm text-foreground shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
|
||||
props.class
|
||||
)"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue