import { ref, computed, onMounted, onUnmounted, readonly } from 'vue' import { useNostrStore } from '@/stores/nostr' import { useMarketStore } from '@/stores/market' import { useRelayHub } from '@/composables/useRelayHub' import { config } from '@/lib/config' // Nostr event kinds for market functionality const MARKET_EVENT_KINDS = { MARKET: 30019, STALL: 30017, PRODUCT: 30018, ORDER: 30020 } as const export function useMarket() { const nostrStore = useNostrStore() const marketStore = useMarketStore() const relayHub = useRelayHub() // State const isLoading = ref(false) const error = ref(null) const isConnected = ref(false) const activeMarket = computed(() => marketStore.activeMarket) const markets = computed(() => marketStore.markets) const stalls = computed(() => marketStore.stalls) const products = computed(() => marketStore.products) const orders = computed(() => marketStore.orders) // Connection state const connectionStatus = computed(() => { if (isConnected.value) return 'connected' if (nostrStore.isConnecting) return 'connecting' if (nostrStore.error) return 'error' return 'disconnected' }) // Load market from naddr const loadMarket = async (naddr: string) => { try { isLoading.value = true error.value = null console.log('Loading market from naddr:', naddr) // Parse naddr to get market data const marketData = { identifier: naddr.split(':')[2] || 'default', pubkey: naddr.split(':')[1] || nostrStore.account?.pubkey || '' } if (!marketData.pubkey) { throw new Error('No pubkey available for market') } await loadMarketData(marketData) } catch (err) { error.value = err instanceof Error ? err : new Error('Failed to load market') console.error('Error loading market:', err) throw err } finally { isLoading.value = false } } // Load market data from Nostr events const loadMarketData = async (marketData: any) => { try { console.log('Loading market data for:', marketData) // Fetch market configuration event const events = await relayHub.queryEvents([ { kinds: [MARKET_EVENT_KINDS.MARKET], authors: [marketData.pubkey], '#d': [marketData.identifier] } ]) console.log('Found market events:', events.length) if (events.length > 0) { const marketEvent = events[0] console.log('Market event:', marketEvent) const market = { d: marketData.identifier, pubkey: marketData.pubkey, relays: config.market.supportedRelays, selected: true, opts: JSON.parse(marketEvent.content) } marketStore.addMarket(market) marketStore.setActiveMarket(market) } else { console.warn('No market events found') // Create a default market if none exists const market = { d: marketData.identifier, pubkey: marketData.pubkey, relays: config.market.supportedRelays, selected: true, opts: { name: 'Ariège Market', description: 'A communal market to sell your goods', merchants: [], ui: {} } } marketStore.addMarket(market) marketStore.setActiveMarket(market) } } catch (err) { console.error('Error loading market data:', err) // Don't throw error, create default market instead const market = { d: marketData.identifier, pubkey: marketData.pubkey, relays: config.market.supportedRelays, selected: true, opts: { name: 'Default Market', description: 'A default market', merchants: [], ui: {} } } marketStore.addMarket(market) marketStore.setActiveMarket(market) } } // Load stalls from market merchants const loadStalls = async () => { try { // Get the active market to filter by its merchants const activeMarket = marketStore.activeMarket if (!activeMarket) { console.warn('No active market found') return } const merchants = [...(activeMarket.opts.merchants || [])] console.log('Loading stalls from market merchants:', merchants) if (merchants.length === 0) { console.log('No merchants in market, skipping stall loading') return } // Fetch stall events from market merchants only const events = await relayHub.queryEvents([ { kinds: [MARKET_EVENT_KINDS.STALL], authors: merchants } ]) console.log('Found stall events:', events.length) // Group events by stall ID and keep only the most recent version const stallGroups = new Map() events.forEach((event: any) => { const stallId = event.tags.find((tag: any) => tag[0] === 'd')?.[1] if (stallId) { if (!stallGroups.has(stallId)) { stallGroups.set(stallId, []) } stallGroups.get(stallId)!.push(event) } }) // Process each stall group stallGroups.forEach((stallEvents, stallId) => { // Sort by created_at and take the most recent const latestEvent = stallEvents.sort((a: any, b: any) => b.created_at - a.created_at)[0] try { const stallData = JSON.parse(latestEvent.content) const stall = { id: stallId, pubkey: latestEvent.pubkey, name: stallData.name || 'Unnamed Stall', description: stallData.description || '', created_at: latestEvent.created_at, ...stallData } marketStore.addStall(stall) } catch (err) { console.warn('Failed to parse stall data:', err) } }) } catch (err) { console.error('Error loading stalls:', err) } } // Load products from market stalls const loadProducts = async () => { try { const activeMarket = marketStore.activeMarket if (!activeMarket) { console.warn('No active market found') return } const merchants = [...(activeMarket.opts.merchants || [])] if (merchants.length === 0) { console.log('No merchants in market, skipping product loading') return } // Fetch product events from market merchants const events = await relayHub.queryEvents([ { kinds: [MARKET_EVENT_KINDS.PRODUCT], authors: merchants } ]) console.log('Found product events:', events.length) // Group events by product ID and keep only the most recent version const productGroups = new Map() events.forEach((event: any) => { const productId = event.tags.find((tag: any) => tag[0] === 'd')?.[1] if (productId) { if (!productGroups.has(productId)) { productGroups.set(productId, []) } productGroups.get(productId)!.push(event) } }) // Process each product group productGroups.forEach((productEvents, productId) => { // Sort by created_at and take the most recent const latestEvent = productEvents.sort((a: any, b: any) => b.created_at - a.created_at)[0] try { const productData = JSON.parse(latestEvent.content) const product = { id: productId, stall_id: productData.stall_id || 'unknown', stallName: productData.stallName || 'Unknown Stall', name: productData.name || 'Unnamed Product', description: productData.description || '', price: productData.price || 0, currency: productData.currency || 'sats', quantity: productData.quantity || 1, images: productData.images || [], categories: productData.categories || [], createdAt: latestEvent.created_at, updatedAt: latestEvent.created_at } marketStore.addProduct(product) } catch (err) { console.warn('Failed to parse product data:', err) } }) } catch (err) { console.error('Error loading products:', err) } } // Add sample products for testing const addSampleProducts = () => { const sampleProducts = [ { id: 'sample-1', stall_id: 'sample-stall', stallName: 'Sample Stall', pubkey: nostrStore.account?.pubkey || '', name: 'Sample Product 1', description: 'This is a sample product for testing', price: 1000, currency: 'sats', quantity: 1, images: [], categories: [], createdAt: Math.floor(Date.now() / 1000), updatedAt: Math.floor(Date.now() / 1000) }, { id: 'sample-2', stall_id: 'sample-stall', stallName: 'Sample Stall', pubkey: nostrStore.account?.pubkey || '', name: 'Sample Product 2', description: 'Another sample product for testing', price: 2000, currency: 'sats', quantity: 1, images: [], categories: [], createdAt: Math.floor(Date.now() / 1000), updatedAt: Math.floor(Date.now() / 1000) } ] sampleProducts.forEach(product => { marketStore.addProduct(product) }) } // Subscribe to market updates const subscribeToMarketUpdates = (): (() => void) | null => { try { const activeMarket = marketStore.activeMarket if (!activeMarket) { console.warn('No active market found for subscription') return null } // Subscribe to market events const unsubscribe = relayHub.subscribe({ id: `market-${activeMarket.d}`, filters: [ { kinds: [MARKET_EVENT_KINDS.MARKET] }, { kinds: [MARKET_EVENT_KINDS.STALL] }, { kinds: [MARKET_EVENT_KINDS.PRODUCT] }, { kinds: [MARKET_EVENT_KINDS.ORDER] } ], onEvent: (event: any) => { handleMarketEvent(event) } }) return unsubscribe } catch (error) { console.error('Failed to subscribe to market updates:', error) return null } } // Handle incoming market events const handleMarketEvent = (event: any) => { console.log('Received market event:', event) switch (event.kind) { case MARKET_EVENT_KINDS.MARKET: // Handle market updates break case MARKET_EVENT_KINDS.STALL: // Handle stall updates handleStallEvent(event) break case MARKET_EVENT_KINDS.PRODUCT: // Handle product updates handleProductEvent(event) break case MARKET_EVENT_KINDS.ORDER: // Handle order updates handleOrderEvent(event) break } } // Process pending products (products without stalls) const processPendingProducts = () => { const productsWithoutStalls = products.value.filter(product => { // Check if product has a stall tag return !product.stall_id }) if (productsWithoutStalls.length > 0) { console.log('Found products without stalls:', productsWithoutStalls.length) // You could create default stalls or handle this as needed } } // Handle stall events const handleStallEvent = (event: any) => { try { const stallId = event.tags.find((tag: any) => tag[0] === 'd')?.[1] if (stallId) { const stallData = JSON.parse(event.content) const stall = { id: stallId, pubkey: event.pubkey, name: stallData.name || 'Unnamed Stall', description: stallData.description || '', created_at: event.created_at, ...stallData } marketStore.addStall(stall) } } catch (err) { console.warn('Failed to handle stall event:', err) } } // Handle product events const handleProductEvent = (event: any) => { try { const productId = event.tags.find((tag: any) => tag[0] === 'd')?.[1] if (productId) { const productData = JSON.parse(event.content) const product = { id: productId, stall_id: productData.stall_id || 'unknown', stallName: productData.stallName || 'Unknown Stall', pubkey: event.pubkey, name: productData.name || 'Unnamed Product', description: productData.description || '', price: productData.price || 0, currency: productData.currency || 'sats', quantity: productData.quantity || 1, images: productData.images || [], categories: productData.categories || [], createdAt: event.created_at, updatedAt: event.created_at } marketStore.addProduct(product) } } catch (err) { console.warn('Failed to handle product data:', err) } } // Handle order events const handleOrderEvent = (event: any) => { try { const orderData = JSON.parse(event.content) const order = { id: event.id, stall_id: orderData.stall_id || 'unknown', product_id: orderData.product_id || 'unknown', buyer_pubkey: event.pubkey, seller_pubkey: orderData.seller_pubkey || '', quantity: orderData.quantity || 1, total_price: orderData.total_price || 0, currency: orderData.currency || 'sats', status: orderData.status || 'pending', payment_request: orderData.payment_request, created_at: event.created_at, updated_at: event.created_at } // Note: addOrder method doesn't exist in the store, so we'll just log it console.log('Received order event:', order) } catch (err) { console.warn('Failed to handle order event:', err) } } // Publish a product const publishProduct = async (_productData: any) => { // Implementation would depend on your event creation logic console.log('Publishing product:', _productData) } // Publish a stall const publishStall = async (_stallData: any) => { // Implementation would depend on your event creation logic console.log('Publishing stall:', _stallData) } // Connect to market const connectToMarket = async () => { try { console.log('Connecting to market...') // Connect to relay hub await relayHub.connect() isConnected.value = relayHub.isConnected.value if (!isConnected.value) { throw new Error('Failed to connect to Nostr relays') } console.log('Connected to market') // Load market data await loadMarketData({ identifier: 'default', pubkey: nostrStore.account?.pubkey || '' }) // Load stalls and products await loadStalls() await loadProducts() // Subscribe to updates subscribeToMarketUpdates() } catch (err) { error.value = err instanceof Error ? err : new Error('Failed to connect to market') console.error('Error connecting to market:', err) throw err } } // Disconnect from market const disconnectFromMarket = () => { isConnected.value = false error.value = null console.log('Disconnected from market') } // Initialize market on mount onMounted(async () => { if (nostrStore.isConnected) { await connectToMarket() } }) // Cleanup on unmount onUnmounted(() => { disconnectFromMarket() }) return { // State isLoading: readonly(isLoading), error: readonly(error), isConnected: readonly(isConnected), connectionStatus: readonly(connectionStatus), activeMarket: readonly(activeMarket), markets: readonly(markets), stalls: readonly(stalls), products: readonly(products), orders: readonly(orders), // Actions loadMarket, connectToMarket, disconnectFromMarket, addSampleProducts, processPendingProducts, publishProduct, publishStall, subscribeToMarketUpdates } }