- Make ChatService globally available for other modules, enabling market-related message handling. - Introduce a market message handler in ChatService to process market-related direct messages (DMs). - Update useMarket to register the market message handler with ChatService, streamlining order-related DM processing. - Refactor message handling logic to differentiate between market messages and regular chat messages, improving message management. - Enhance order update logging in nostrmarketService for better debugging and verification of order status.
901 lines
No EOL
26 KiB
TypeScript
901 lines
No EOL
26 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { ref, computed, readonly, watch } from 'vue'
|
|
import { invoiceService } from '@/lib/services/invoiceService'
|
|
import { paymentMonitor } from '@/lib/services/paymentMonitor'
|
|
import { nostrmarketService } from '../services/nostrmarketService'
|
|
import { useAuth } from '@/composables/useAuth'
|
|
import type { LightningInvoice } from '@/lib/services/invoiceService'
|
|
|
|
|
|
import type {
|
|
Market, Stall, Product, Order, ShippingZone,
|
|
OrderStatus, StallCart, FilterData, SortOptions,
|
|
PaymentRequest, PaymentStatus
|
|
} from '../types/market'
|
|
// Import types that are used in the store implementation
|
|
|
|
export const useMarketStore = defineStore('market', () => {
|
|
const auth = useAuth()
|
|
|
|
// Helper function to get user-specific storage key
|
|
const getUserStorageKey = (baseKey: string) => {
|
|
const userPubkey = auth.currentUser?.value?.pubkey
|
|
return userPubkey ? `${baseKey}_${userPubkey}` : baseKey
|
|
}
|
|
// Core market state
|
|
const markets = ref<Market[]>([])
|
|
const stalls = ref<Stall[]>([])
|
|
const products = ref<Product[]>([])
|
|
const orders = ref<Record<string, Order>>({})
|
|
const profiles = ref<Record<string, any>>({})
|
|
|
|
// Active selections
|
|
const activeMarket = ref<Market | null>(null)
|
|
const activeStall = ref<Stall | null>(null)
|
|
const activeProduct = ref<Product | null>(null)
|
|
|
|
// UI state
|
|
const isLoading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
const searchText = ref('')
|
|
const showFilterDetails = ref(false)
|
|
|
|
// Filtering and sorting
|
|
const filterData = ref<FilterData>({
|
|
categories: [],
|
|
merchants: [],
|
|
stalls: [],
|
|
currency: null,
|
|
priceFrom: null,
|
|
priceTo: null,
|
|
inStock: null,
|
|
paymentMethods: []
|
|
})
|
|
|
|
const sortOptions = ref<SortOptions>({
|
|
field: 'name',
|
|
order: 'asc'
|
|
})
|
|
|
|
// Enhanced shopping cart with stall-specific carts
|
|
const stallCarts = ref<Record<string, StallCart>>({})
|
|
|
|
// Legacy shopping cart (to be deprecated)
|
|
const shoppingCart = ref<Record<string, { product: Product; quantity: number }>>({})
|
|
|
|
// Checkout state
|
|
const checkoutCart = ref<StallCart | null>(null)
|
|
const checkoutStall = ref<Stall | null>(null)
|
|
const activeOrder = ref<Order | null>(null)
|
|
|
|
// Payment state
|
|
const paymentRequest = ref<PaymentRequest | null>(null)
|
|
const paymentStatus = ref<PaymentStatus | null>(null)
|
|
|
|
// Computed properties
|
|
const filteredProducts = computed(() => {
|
|
let filtered = products.value
|
|
|
|
// Search filter
|
|
if (searchText.value) {
|
|
const searchLower = searchText.value.toLowerCase()
|
|
filtered = filtered.filter(product =>
|
|
product.name.toLowerCase().includes(searchLower) ||
|
|
product.description?.toLowerCase().includes(searchLower) ||
|
|
product.stallName.toLowerCase().includes(searchLower)
|
|
)
|
|
}
|
|
|
|
// Category filter
|
|
if (filterData.value.categories.length > 0) {
|
|
filtered = filtered.filter(product =>
|
|
product.categories?.some(cat => filterData.value.categories.includes(cat))
|
|
)
|
|
}
|
|
|
|
// Merchant filter
|
|
if (filterData.value.merchants.length > 0) {
|
|
filtered = filtered.filter(product =>
|
|
filterData.value.merchants.includes(product.stall_id)
|
|
)
|
|
}
|
|
|
|
// Stall filter
|
|
if (filterData.value.stalls.length > 0) {
|
|
filtered = filtered.filter(product =>
|
|
filterData.value.stalls.includes(product.stall_id)
|
|
)
|
|
}
|
|
|
|
// Currency filter
|
|
if (filterData.value.currency) {
|
|
filtered = filtered.filter(product =>
|
|
product.currency === filterData.value.currency
|
|
)
|
|
}
|
|
|
|
// Price range filter
|
|
if (filterData.value.priceFrom !== null) {
|
|
filtered = filtered.filter(product =>
|
|
product.price >= filterData.value.priceFrom!
|
|
)
|
|
}
|
|
|
|
if (filterData.value.priceTo !== null) {
|
|
filtered = filtered.filter(product =>
|
|
product.price <= filterData.value.priceTo!
|
|
)
|
|
}
|
|
|
|
// In stock filter
|
|
if (filterData.value.inStock !== null) {
|
|
filtered = filtered.filter(product =>
|
|
filterData.value.inStock ? product.quantity > 0 : product.quantity === 0
|
|
)
|
|
}
|
|
|
|
// Payment methods filter
|
|
if (filterData.value.paymentMethods.length > 0) {
|
|
// For now, assume all products support Lightning payments
|
|
// This can be enhanced later with product-specific payment method support
|
|
filtered = filtered.filter(_product => true)
|
|
}
|
|
|
|
// Sort
|
|
filtered.sort((a, b) => {
|
|
const aVal = a[sortOptions.value.field as keyof Product]
|
|
const bVal = b[sortOptions.value.field as keyof Product]
|
|
|
|
if (typeof aVal === 'string' && typeof bVal === 'string') {
|
|
return sortOptions.value.order === 'asc'
|
|
? aVal.localeCompare(bVal)
|
|
: bVal.localeCompare(aVal)
|
|
}
|
|
|
|
if (typeof aVal === 'number' && typeof bVal === 'number') {
|
|
return sortOptions.value.order === 'asc'
|
|
? aVal - bVal
|
|
: bVal - aVal
|
|
}
|
|
|
|
return 0
|
|
})
|
|
|
|
return filtered
|
|
})
|
|
|
|
const allCategories = computed(() => {
|
|
const categories = new Set<string>()
|
|
products.value.forEach(product => {
|
|
product.categories?.forEach(cat => categories.add(cat))
|
|
})
|
|
return Array.from(categories).map(category => ({
|
|
category,
|
|
count: products.value.filter(p => p.categories?.includes(category)).length,
|
|
selected: filterData.value.categories.includes(category)
|
|
}))
|
|
})
|
|
|
|
// Enhanced cart computed properties
|
|
const allStallCarts = computed(() => Object.values(stallCarts.value))
|
|
|
|
const totalCartItems = computed(() => {
|
|
return allStallCarts.value.reduce((total, cart) => {
|
|
return total + cart.products.reduce((cartTotal, item) => cartTotal + item.quantity, 0)
|
|
}, 0)
|
|
})
|
|
|
|
const totalCartValue = computed(() => {
|
|
return allStallCarts.value.reduce((total, cart) => {
|
|
return total + cart.subtotal
|
|
}, 0)
|
|
})
|
|
|
|
const activeStallCart = computed(() => {
|
|
if (!checkoutStall.value) return null
|
|
return stallCarts.value[checkoutStall.value.id] || null
|
|
})
|
|
|
|
// Legacy cart computed properties (to be deprecated)
|
|
const cartTotal = computed(() => {
|
|
return Object.values(shoppingCart.value).reduce((total, item) => {
|
|
return total + (item.product.price * item.quantity)
|
|
}, 0)
|
|
})
|
|
|
|
const cartItemCount = computed(() => {
|
|
return Object.values(shoppingCart.value).reduce((count, item) => {
|
|
return count + item.quantity
|
|
}, 0)
|
|
})
|
|
|
|
// Actions
|
|
const setLoading = (loading: boolean) => {
|
|
isLoading.value = loading
|
|
}
|
|
|
|
const setError = (errorMessage: string | null) => {
|
|
error.value = errorMessage
|
|
}
|
|
|
|
const setSearchText = (text: string) => {
|
|
searchText.value = text
|
|
}
|
|
|
|
const setActiveMarket = (market: Market | null) => {
|
|
activeMarket.value = market
|
|
}
|
|
|
|
const setActiveStall = (stall: Stall | null) => {
|
|
activeStall.value = stall
|
|
}
|
|
|
|
const setActiveProduct = (product: Product | null) => {
|
|
activeProduct.value = product
|
|
}
|
|
|
|
const addProduct = (product: Product) => {
|
|
const existingIndex = products.value.findIndex(p => p.id === product.id)
|
|
if (existingIndex >= 0) {
|
|
products.value[existingIndex] = product
|
|
} else {
|
|
products.value.push(product)
|
|
}
|
|
}
|
|
|
|
const addStall = (stall: Stall) => {
|
|
const existingIndex = stalls.value.findIndex(s => s.id === stall.id)
|
|
if (existingIndex >= 0) {
|
|
stalls.value[existingIndex] = stall
|
|
} else {
|
|
stalls.value.push(stall)
|
|
}
|
|
}
|
|
|
|
const addMarket = (market: Market) => {
|
|
const existingIndex = markets.value.findIndex(m => m.d === market.d)
|
|
if (existingIndex >= 0) {
|
|
markets.value[existingIndex] = market
|
|
} else {
|
|
markets.value.push(market)
|
|
}
|
|
}
|
|
|
|
const addToCart = (product: Product, quantity: number = 1) => {
|
|
const existing = shoppingCart.value[product.id]
|
|
if (existing) {
|
|
existing.quantity += quantity
|
|
} else {
|
|
shoppingCart.value[product.id] = { product, quantity }
|
|
}
|
|
}
|
|
|
|
const removeFromCart = (productId: string) => {
|
|
delete shoppingCart.value[productId]
|
|
}
|
|
|
|
const updateCartQuantity = (productId: string, quantity: number) => {
|
|
if (quantity <= 0) {
|
|
removeFromCart(productId)
|
|
} else {
|
|
const item = shoppingCart.value[productId]
|
|
if (item) {
|
|
item.quantity = quantity
|
|
}
|
|
}
|
|
}
|
|
|
|
const clearCart = () => {
|
|
shoppingCart.value = {}
|
|
}
|
|
|
|
// Enhanced cart management methods
|
|
const addToStallCart = (product: Product, quantity: number = 1) => {
|
|
const stallId = product.stall_id
|
|
const stall = stalls.value.find(s => s.id === stallId)
|
|
|
|
if (!stall) {
|
|
console.error('Stall not found for product:', product.id)
|
|
return
|
|
}
|
|
|
|
// Initialize stall cart if it doesn't exist
|
|
if (!stallCarts.value[stallId]) {
|
|
stallCarts.value[stallId] = {
|
|
id: stallId,
|
|
merchant: stall.pubkey,
|
|
products: [],
|
|
subtotal: 0,
|
|
currency: stall.currency || 'sats'
|
|
}
|
|
}
|
|
|
|
const cart = stallCarts.value[stallId]
|
|
const existingItem = cart.products.find(item => item.product.id === product.id)
|
|
|
|
if (existingItem) {
|
|
existingItem.quantity = Math.min(existingItem.quantity + quantity, product.quantity)
|
|
} else {
|
|
cart.products.push({
|
|
product,
|
|
quantity: Math.min(quantity, product.quantity),
|
|
stallId
|
|
})
|
|
}
|
|
|
|
// Update cart subtotal
|
|
updateStallCartSubtotal(stallId)
|
|
}
|
|
|
|
const removeFromStallCart = (stallId: string, productId: string) => {
|
|
const cart = stallCarts.value[stallId]
|
|
if (cart) {
|
|
cart.products = cart.products.filter(item => item.product.id !== productId)
|
|
updateStallCartSubtotal(stallId)
|
|
|
|
// Remove empty carts
|
|
if (cart.products.length === 0) {
|
|
delete stallCarts.value[stallId]
|
|
}
|
|
}
|
|
}
|
|
|
|
const updateStallCartQuantity = (stallId: string, productId: string, quantity: number) => {
|
|
const cart = stallCarts.value[stallId]
|
|
if (cart) {
|
|
if (quantity <= 0) {
|
|
removeFromStallCart(stallId, productId)
|
|
} else {
|
|
const item = cart.products.find(item => item.product.id === productId)
|
|
if (item) {
|
|
item.quantity = Math.min(quantity, item.product.quantity)
|
|
updateStallCartSubtotal(stallId)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const updateStallCartSubtotal = (stallId: string) => {
|
|
const cart = stallCarts.value[stallId]
|
|
if (cart) {
|
|
cart.subtotal = cart.products.reduce((total, item) => {
|
|
return total + (item.product.price * item.quantity)
|
|
}, 0)
|
|
}
|
|
}
|
|
|
|
const clearStallCart = (stallId: string) => {
|
|
delete stallCarts.value[stallId]
|
|
}
|
|
|
|
const clearAllStallCarts = () => {
|
|
stallCarts.value = {}
|
|
}
|
|
|
|
const setCheckoutCart = (stallId: string) => {
|
|
const cart = stallCarts.value[stallId]
|
|
const stall = stalls.value.find(s => s.id === stallId)
|
|
|
|
if (cart && stall) {
|
|
checkoutCart.value = cart
|
|
checkoutStall.value = stall
|
|
}
|
|
}
|
|
|
|
const clearCheckout = () => {
|
|
checkoutCart.value = null
|
|
checkoutStall.value = null
|
|
activeOrder.value = null
|
|
paymentRequest.value = null
|
|
paymentStatus.value = null
|
|
}
|
|
|
|
const setShippingZone = (stallId: string, shippingZone: ShippingZone) => {
|
|
const cart = stallCarts.value[stallId]
|
|
if (cart) {
|
|
cart.shippingZone = shippingZone
|
|
}
|
|
}
|
|
|
|
// Order management methods
|
|
const createOrder = (orderData: Omit<Order, 'id' | 'createdAt' | 'updatedAt'> & { id?: string }) => {
|
|
const order: Order = {
|
|
...orderData,
|
|
id: orderData.id || generateOrderId(),
|
|
createdAt: Math.floor(Date.now() / 1000),
|
|
updatedAt: Math.floor(Date.now() / 1000)
|
|
}
|
|
|
|
orders.value[order.id] = order
|
|
activeOrder.value = order
|
|
|
|
// Save to localStorage for persistence
|
|
saveOrdersToStorage()
|
|
|
|
return order
|
|
}
|
|
|
|
const createAndPlaceOrder = async (orderData: Omit<Order, 'id' | 'createdAt' | 'updatedAt'>) => {
|
|
try {
|
|
// Create the order
|
|
const order = createOrder(orderData)
|
|
|
|
// Attempt to publish order via nostrmarket protocol
|
|
let nostrmarketSuccess = false
|
|
let nostrmarketError: string | undefined
|
|
|
|
try {
|
|
// Publish the order event to nostrmarket using proper protocol
|
|
const eventId = await nostrmarketService.publishOrder(order, order.sellerPubkey)
|
|
nostrmarketSuccess = true
|
|
order.sentViaNostr = true
|
|
order.nostrEventId = eventId
|
|
|
|
console.log('Order published via nostrmarket successfully:', eventId)
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : 'Unknown nostrmarket error'
|
|
order.nostrError = errorMessage
|
|
order.sentViaNostr = false
|
|
console.error('Failed to publish order via nostrmarket:', errorMessage)
|
|
}
|
|
|
|
// Update order status to 'pending'
|
|
updateOrderStatus(order.id, 'pending')
|
|
|
|
// Clear the checkout cart
|
|
if (checkoutCart.value) {
|
|
clearStallCart(checkoutCart.value.id)
|
|
}
|
|
|
|
// Clear checkout state
|
|
clearCheckout()
|
|
|
|
// Show appropriate success/error message
|
|
if (nostrmarketSuccess) {
|
|
console.log('Order created and published via nostrmarket successfully')
|
|
} else {
|
|
console.warn('Order created but nostrmarket publishing failed:', nostrmarketError)
|
|
}
|
|
|
|
return order
|
|
} catch (error) {
|
|
console.error('Failed to create and place order:', error)
|
|
throw new Error('Failed to place order. Please try again.')
|
|
}
|
|
}
|
|
|
|
// nostrmarket integration methods
|
|
const publishToNostrmarket = async () => {
|
|
try {
|
|
console.log('Publishing merchant catalog to nostrmarket...')
|
|
|
|
// Get all stalls and products
|
|
const allStalls = Object.values(stalls.value)
|
|
const allProducts = Object.values(products.value)
|
|
|
|
if (allStalls.length === 0) {
|
|
console.warn('No stalls to publish to nostrmarket')
|
|
return null
|
|
}
|
|
|
|
if (allProducts.length === 0) {
|
|
console.warn('No products to publish to nostrmarket')
|
|
return null
|
|
}
|
|
|
|
// Publish to nostrmarket
|
|
const result = await nostrmarketService.publishMerchantCatalog(allStalls, allProducts)
|
|
|
|
console.log('Successfully published to nostrmarket:', result)
|
|
|
|
// Update stalls and products with event IDs
|
|
for (const [stallId, eventId] of Object.entries(result.stalls)) {
|
|
const stall = stalls.value.find(s => s.id === stallId)
|
|
if (stall) {
|
|
stall.nostrEventId = eventId
|
|
}
|
|
}
|
|
|
|
for (const [productId, eventId] of Object.entries(result.products)) {
|
|
const product = products.value.find(p => p.id === productId)
|
|
if (product) {
|
|
product.nostrEventId = eventId
|
|
}
|
|
}
|
|
|
|
return result
|
|
} catch (error) {
|
|
console.error('Failed to publish to nostrmarket:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
// Invoice management methods
|
|
const createLightningInvoice = async (orderId: string, adminKey: string): Promise<LightningInvoice | null> => {
|
|
try {
|
|
const order = orders.value[orderId]
|
|
if (!order) {
|
|
throw new Error('Order not found')
|
|
}
|
|
|
|
// Create Lightning invoice with admin key and nostrmarket tag
|
|
// For nostrmarket compatibility, we need to use the original order ID if it exists
|
|
// If no originalOrderId exists, this order was created in the web-app, so use the current orderId
|
|
const orderIdForInvoice = order.originalOrderId || orderId
|
|
console.log('Creating invoice with order ID:', {
|
|
webAppOrderId: orderId,
|
|
originalOrderId: order.originalOrderId,
|
|
orderIdForInvoice: orderIdForInvoice,
|
|
hasOriginalOrderId: !!order.originalOrderId
|
|
})
|
|
|
|
const invoice = await invoiceService.createInvoice(order, adminKey, {
|
|
tag: "nostrmarket",
|
|
order_id: orderIdForInvoice, // Use original Nostr order ID for nostrmarket compatibility
|
|
merchant_pubkey: order.sellerPubkey,
|
|
buyer_pubkey: order.buyerPubkey
|
|
})
|
|
|
|
// Update order with invoice details
|
|
order.lightningInvoice = invoice
|
|
order.paymentHash = invoice.payment_hash
|
|
order.paymentStatus = 'pending'
|
|
order.paymentRequest = invoice.bolt11 // Use bolt11 field from LNBits response
|
|
|
|
// Save to localStorage after invoice creation
|
|
saveOrdersToStorage()
|
|
|
|
// Start monitoring payment
|
|
await paymentMonitor.startMonitoring(order, invoice)
|
|
|
|
// Set up payment update callback
|
|
paymentMonitor.onPaymentUpdate(orderId, (update) => {
|
|
handlePaymentUpdate(orderId, update)
|
|
})
|
|
|
|
console.log('Lightning invoice created for order:', {
|
|
orderId,
|
|
originalOrderId: order.originalOrderId,
|
|
nostrmarketOrderId: order.originalOrderId || orderId,
|
|
paymentHash: invoice.payment_hash,
|
|
amount: invoice.amount
|
|
})
|
|
|
|
return invoice
|
|
} catch (error) {
|
|
console.error('Failed to create Lightning invoice:', error)
|
|
throw new Error('Failed to create payment invoice')
|
|
}
|
|
}
|
|
|
|
const handlePaymentUpdate = (orderId: string, update: any) => {
|
|
const order = orders.value[orderId]
|
|
if (!order) return
|
|
|
|
// Update order payment status
|
|
order.paymentStatus = update.status
|
|
if (update.status === 'paid') {
|
|
order.paidAt = update.paidAt
|
|
updateOrderStatus(orderId, 'paid')
|
|
|
|
// Send payment confirmation via Nostr
|
|
sendPaymentConfirmation(order)
|
|
}
|
|
|
|
// Save to localStorage after payment update
|
|
saveOrdersToStorage()
|
|
|
|
console.log('Payment status updated for order:', {
|
|
orderId,
|
|
status: update.status,
|
|
amount: update.amount
|
|
})
|
|
}
|
|
|
|
const sendPaymentConfirmation = async (order: Order) => {
|
|
try {
|
|
if (!nostrmarketService.isReady) {
|
|
console.warn('Nostr not ready for payment confirmation')
|
|
return
|
|
}
|
|
|
|
// Create payment confirmation message
|
|
// const confirmation = {
|
|
// type: 'payment_confirmation',
|
|
// orderId: order.id,
|
|
// paymentHash: order.paymentHash,
|
|
// amount: order.total,
|
|
// currency: order.currency,
|
|
// paidAt: order.paidAt,
|
|
// message: 'Payment received! Your order is being processed.'
|
|
// }
|
|
|
|
// Send confirmation to customer
|
|
await nostrmarketService.publishOrder(order, order.buyerPubkey)
|
|
|
|
console.log('Payment confirmation sent via Nostr')
|
|
} catch (error) {
|
|
console.error('Failed to send payment confirmation:', error)
|
|
}
|
|
}
|
|
|
|
const getOrderInvoice = (orderId: string): LightningInvoice | null => {
|
|
const order = orders.value[orderId]
|
|
return order?.lightningInvoice || null
|
|
}
|
|
|
|
const getOrderPaymentStatus = (orderId: string): 'pending' | 'paid' | 'expired' | null => {
|
|
const order = orders.value[orderId]
|
|
return order?.paymentStatus || null
|
|
}
|
|
|
|
const updateOrderStatus = (orderId: string, status: OrderStatus) => {
|
|
const order = orders.value[orderId]
|
|
if (order) {
|
|
order.status = status
|
|
order.updatedAt = Date.now() / 1000
|
|
saveOrdersToStorage()
|
|
}
|
|
}
|
|
|
|
const updateOrder = (orderId: string, updatedOrder: Partial<Order>) => {
|
|
const order = orders.value[orderId]
|
|
if (order) {
|
|
// Create a completely new order object to ensure reactivity
|
|
const newOrder = {
|
|
...order,
|
|
...updatedOrder,
|
|
updatedAt: Date.now() / 1000
|
|
}
|
|
|
|
// Replace the entire order object to trigger reactivity
|
|
orders.value[orderId] = newOrder
|
|
|
|
console.log('🔄 Order updated in store:', {
|
|
orderId,
|
|
hasPaymentRequest: !!newOrder.paymentRequest,
|
|
status: newOrder.status,
|
|
paymentStatus: newOrder.paymentStatus
|
|
})
|
|
|
|
saveOrdersToStorage()
|
|
} else {
|
|
console.warn('updateOrder: Order not found:', orderId)
|
|
}
|
|
}
|
|
|
|
const setPaymentRequest = (request: PaymentRequest) => {
|
|
paymentRequest.value = request
|
|
}
|
|
|
|
const setPaymentStatus = (status: PaymentStatus) => {
|
|
paymentStatus.value = status
|
|
}
|
|
|
|
// Utility methods
|
|
const generateOrderId = () => {
|
|
return `order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
|
}
|
|
|
|
// Persistence methods
|
|
const saveOrdersToStorage = () => {
|
|
try {
|
|
const storageKey = getUserStorageKey('market_orders')
|
|
localStorage.setItem(storageKey, JSON.stringify(orders.value))
|
|
console.log('Saved orders to localStorage with key:', storageKey)
|
|
} catch (error) {
|
|
console.warn('Failed to save orders to localStorage:', error)
|
|
}
|
|
}
|
|
|
|
const loadOrdersFromStorage = () => {
|
|
try {
|
|
const storageKey = getUserStorageKey('market_orders')
|
|
const stored = localStorage.getItem(storageKey)
|
|
if (stored) {
|
|
const parsedOrders = JSON.parse(stored)
|
|
orders.value = parsedOrders
|
|
console.log('Loaded orders from localStorage with key:', storageKey, 'Orders count:', Object.keys(parsedOrders).length)
|
|
} else {
|
|
console.log('No orders found in localStorage for key:', storageKey)
|
|
// Clear any existing orders when switching users
|
|
orders.value = {}
|
|
}
|
|
} catch (error) {
|
|
console.warn('Failed to load orders from localStorage:', error)
|
|
// Clear orders on error
|
|
orders.value = {}
|
|
}
|
|
}
|
|
|
|
// Clear orders when user changes
|
|
const clearOrdersForUserChange = () => {
|
|
orders.value = {}
|
|
console.log('Cleared orders for user change')
|
|
}
|
|
|
|
// Payment utility methods
|
|
const calculateOrderTotal = (cart: StallCart, shippingZone?: ShippingZone) => {
|
|
const subtotal = cart.subtotal
|
|
const shippingCost = shippingZone?.cost || 0
|
|
return subtotal + shippingCost
|
|
}
|
|
|
|
const validateCartForCheckout = (stallId: string): { valid: boolean; errors: string[] } => {
|
|
const cart = stallCarts.value[stallId]
|
|
const errors: string[] = []
|
|
|
|
if (!cart || cart.products.length === 0) {
|
|
errors.push('Cart is empty')
|
|
return { valid: false, errors }
|
|
}
|
|
|
|
// Check if all products are still in stock
|
|
for (const item of cart.products) {
|
|
if (item.quantity > item.product.quantity) {
|
|
errors.push(`${item.product.name} is out of stock`)
|
|
}
|
|
}
|
|
|
|
// Check if cart has shipping zone selected
|
|
if (!cart.shippingZone) {
|
|
errors.push('Please select a shipping zone')
|
|
}
|
|
|
|
return { valid: errors.length === 0, errors }
|
|
}
|
|
|
|
const getCartSummary = (stallId: string) => {
|
|
const cart = stallCarts.value[stallId]
|
|
if (!cart) return null
|
|
|
|
const itemCount = cart.products.reduce((total, item) => total + item.quantity, 0)
|
|
const subtotal = cart.subtotal
|
|
const shippingCost = cart.shippingZone?.cost || 0
|
|
const total = subtotal + shippingCost
|
|
|
|
return {
|
|
itemCount,
|
|
subtotal,
|
|
shippingCost,
|
|
total,
|
|
currency: cart.currency
|
|
}
|
|
}
|
|
|
|
const updateFilterData = (newFilterData: Partial<FilterData>) => {
|
|
filterData.value = { ...filterData.value, ...newFilterData }
|
|
}
|
|
|
|
const clearFilters = () => {
|
|
filterData.value = {
|
|
categories: [],
|
|
merchants: [],
|
|
stalls: [],
|
|
currency: null,
|
|
priceFrom: null,
|
|
priceTo: null,
|
|
inStock: null,
|
|
paymentMethods: []
|
|
}
|
|
}
|
|
|
|
const toggleCategoryFilter = (category: string) => {
|
|
const index = filterData.value.categories.indexOf(category)
|
|
if (index >= 0) {
|
|
filterData.value.categories.splice(index, 1)
|
|
} else {
|
|
filterData.value.categories.push(category)
|
|
}
|
|
}
|
|
|
|
const updateSortOptions = (field: string, order: 'asc' | 'desc' = 'asc') => {
|
|
sortOptions.value = { field, order }
|
|
}
|
|
|
|
const formatPrice = (price: number, currency: string) => {
|
|
if (currency === 'sat') {
|
|
return `${price} sats`
|
|
}
|
|
return new Intl.NumberFormat('en-US', {
|
|
style: 'currency',
|
|
currency: currency.toUpperCase()
|
|
}).format(price)
|
|
}
|
|
|
|
// Initialize orders from localStorage
|
|
loadOrdersFromStorage()
|
|
|
|
// Watch for user changes and reload orders
|
|
watch(() => auth.currentUser?.value?.pubkey, (newPubkey, oldPubkey) => {
|
|
if (newPubkey !== oldPubkey) {
|
|
console.log('User changed, clearing and reloading orders. Old:', oldPubkey, 'New:', newPubkey)
|
|
clearOrdersForUserChange()
|
|
loadOrdersFromStorage()
|
|
}
|
|
})
|
|
|
|
return {
|
|
// State
|
|
markets: readonly(markets),
|
|
stalls: readonly(stalls),
|
|
products: readonly(products),
|
|
orders: readonly(orders),
|
|
profiles: readonly(profiles),
|
|
activeMarket: readonly(activeMarket),
|
|
activeStall: readonly(activeStall),
|
|
activeProduct: readonly(activeProduct),
|
|
isLoading: readonly(isLoading),
|
|
error: readonly(error),
|
|
searchText: readonly(searchText),
|
|
showFilterDetails: readonly(showFilterDetails),
|
|
filterData: readonly(filterData),
|
|
sortOptions: readonly(sortOptions),
|
|
shoppingCart: readonly(shoppingCart),
|
|
stallCarts: readonly(stallCarts),
|
|
checkoutCart: readonly(checkoutCart),
|
|
checkoutStall: readonly(checkoutStall),
|
|
activeOrder: readonly(activeOrder),
|
|
paymentRequest: readonly(paymentRequest),
|
|
paymentStatus: readonly(paymentStatus),
|
|
|
|
// Computed
|
|
filteredProducts,
|
|
allCategories,
|
|
allStallCarts,
|
|
totalCartItems,
|
|
totalCartValue,
|
|
activeStallCart,
|
|
cartTotal,
|
|
cartItemCount,
|
|
|
|
// Actions
|
|
setLoading,
|
|
setError,
|
|
setSearchText,
|
|
setActiveMarket,
|
|
setActiveStall,
|
|
setActiveProduct,
|
|
addProduct,
|
|
addStall,
|
|
addMarket,
|
|
addToCart,
|
|
removeFromCart,
|
|
updateCartQuantity,
|
|
clearCart,
|
|
updateFilterData,
|
|
clearFilters,
|
|
toggleCategoryFilter,
|
|
updateSortOptions,
|
|
formatPrice,
|
|
addToStallCart,
|
|
removeFromStallCart,
|
|
updateStallCartQuantity,
|
|
updateStallCartSubtotal,
|
|
clearStallCart,
|
|
clearAllStallCarts,
|
|
setCheckoutCart,
|
|
clearCheckout,
|
|
clearCheckoutCart: clearCheckout, // Alias for consistency
|
|
setShippingZone,
|
|
createOrder,
|
|
updateOrderStatus,
|
|
setPaymentRequest,
|
|
setPaymentStatus,
|
|
generateOrderId,
|
|
calculateOrderTotal,
|
|
validateCartForCheckout,
|
|
getCartSummary,
|
|
createAndPlaceOrder,
|
|
createLightningInvoice,
|
|
handlePaymentUpdate,
|
|
sendPaymentConfirmation,
|
|
getOrderInvoice,
|
|
getOrderPaymentStatus,
|
|
updateOrder,
|
|
saveOrdersToStorage,
|
|
loadOrdersFromStorage,
|
|
clearOrdersForUserChange,
|
|
publishToNostrmarket
|
|
}
|
|
})
|