Enhance market module with new chat and events features
- Introduce chat module with components, services, and composables for real-time messaging. - Implement events module with API service, components, and ticket purchasing functionality. - Update app configuration to include new modules and their respective settings. - Refactor existing components to integrate with the new chat and events features. - Enhance market store and services to support new functionalities and improve order management. - Update routing to accommodate new views for chat and events, ensuring seamless navigation.
This commit is contained in:
parent
519a9003d4
commit
e40ac91417
46 changed files with 6305 additions and 3264 deletions
884
src/modules/market/stores/market.ts
Normal file
884
src/modules/market/stores/market.ts
Normal file
|
|
@ -0,0 +1,884 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref, computed, readonly, watch } from 'vue'
|
||||
import { nostrOrders } from '@/composables/useNostrOrders'
|
||||
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 (!nostrOrders.isReady.value) {
|
||||
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 nostrOrders.publishOrderEvent(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) {
|
||||
Object.assign(order, updatedOrder)
|
||||
order.updatedAt = Date.now() / 1000
|
||||
saveOrdersToStorage()
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
setShippingZone,
|
||||
createOrder,
|
||||
updateOrderStatus,
|
||||
setPaymentRequest,
|
||||
setPaymentStatus,
|
||||
generateOrderId,
|
||||
calculateOrderTotal,
|
||||
validateCartForCheckout,
|
||||
getCartSummary,
|
||||
createAndPlaceOrder,
|
||||
createLightningInvoice,
|
||||
handlePaymentUpdate,
|
||||
sendPaymentConfirmation,
|
||||
getOrderInvoice,
|
||||
getOrderPaymentStatus,
|
||||
updateOrder,
|
||||
saveOrdersToStorage,
|
||||
loadOrdersFromStorage,
|
||||
clearOrdersForUserChange,
|
||||
publishToNostrmarket
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue