import { ref, readonly } from 'vue' import { useNostrStore } from '@/stores/nostr' import { useMarketStore, type Market, type Stall, type Product } from '@/stores/market' import { config } from '@/lib/config' import { nip19 } from 'nostr-tools' // 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 isLoading = ref(false) const isConnected = ref(false) // Market loading state const loadMarket = async (naddr: string) => { try { isLoading.value = true marketStore.setLoading(true) marketStore.setError(null) console.log('Loading market with naddr:', naddr) // Decode naddr const { type, data } = nip19.decode(naddr) console.log('Decoded naddr:', { type, data }) if (type !== 'naddr' || data.kind !== MARKET_EVENT_KINDS.MARKET) { throw new Error('Invalid market naddr') } console.log('About to load market data...') // Load market data from Nostr await loadMarketData(data) console.log('Market data loaded successfully') } catch (err) { console.error('Error loading market:', err) marketStore.setError(err instanceof Error ? err.message : 'Failed to load market') // Don't throw error, let the UI handle it gracefully } finally { isLoading.value = false marketStore.setLoading(false) } } const loadMarketData = async (marketData: any) => { try { console.log('Starting loadMarketData...') console.log('Got Nostr client') // Load market configuration console.log('Loading market config...') await loadMarketConfig(marketData) console.log('Market config loaded') // Load stalls for this market console.log('Loading stalls...') await loadStalls(marketData.pubkey) console.log('Stalls loaded') // Load products for all stalls console.log('Loading products...') await loadProducts() console.log('Products loaded') // Subscribe to real-time updates console.log('Subscribing to updates...') try { subscribeToMarketUpdates() console.log('Subscribed to updates') } catch (err) { console.warn('Failed to subscribe to updates:', err) // Don't fail the entire load process if subscription fails } // Clear any error state since we successfully loaded the market data marketStore.setError(null) } catch (err) { console.error('Error loading market data:', err) throw err } } const loadMarketConfig = async (marketData: any) => { try { const client = nostrStore.getClient() console.log('Loading market config for:', marketData) // Fetch market configuration event const events = await client.fetchEvents({ 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: 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: 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) } } catch (err) { console.error('Error loading market config:', err) // Don't throw error, create default market instead const market: 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) } } const loadStalls = async (marketPubkey: string) => { try { const client = nostrStore.getClient() console.log('Loading stalls for market pubkey:', marketPubkey) // Fetch stall events for this market // Note: We need to fetch all stalls and then filter by the ones that belong to this market // since stalls don't have a direct market association in their tags const events = await client.fetchEvents({ kinds: [MARKET_EVENT_KINDS.STALL], authors: [marketPubkey] }) 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 => { try { const stallData = JSON.parse(event.content) const stallId = stallData.id if (!stallGroups.has(stallId)) { stallGroups.set(stallId, []) } stallGroups.get(stallId)!.push({ event, stallData }) } catch (err) { console.warn('Failed to parse stall event:', err) } }) // Process each stall group, keeping only the most recent version stallGroups.forEach((stallEvents, _stallId) => { // Sort by created_at timestamp (most recent first) stallEvents.sort((a, b) => b.event.created_at - a.event.created_at) // Take the most recent version const { event, stallData } = stallEvents[0] console.log('Processing most recent stall event:', event) console.log('Parsed stall data:', stallData) const stall: Stall = { id: stallData.id, // Use the stall's unique ID from content, not the Nostr event ID pubkey: event.pubkey, name: stallData.name, description: stallData.description, logo: stallData.logo, categories: stallData.categories, shipping: stallData.shipping } console.log('Created stall (most recent version):', stall) marketStore.addStall(stall) }) } catch (err) { console.error('Error loading stalls:', err) // Don't throw error, continue without stalls } } const loadProducts = async () => { try { const client = nostrStore.getClient() // Get all stall pubkeys const stallPubkeys = marketStore.stalls.map(stall => stall.pubkey) console.log('Loading products for stall pubkeys:', stallPubkeys) if (stallPubkeys.length === 0) { console.log('No stalls found, skipping product loading') return } // Fetch product events from all stalls const events = await client.fetchEvents({ kinds: [MARKET_EVENT_KINDS.PRODUCT], authors: stallPubkeys }) 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 => { try { const productData = JSON.parse(event.content) const productId = productData.id if (!productGroups.has(productId)) { productGroups.set(productId, []) } productGroups.get(productId)!.push({ event, productData }) } catch (err) { console.warn('Failed to parse product event:', err) } }) // Process each product group, keeping only the most recent version productGroups.forEach((productEvents, _productId) => { // Sort by created_at timestamp (most recent first) productEvents.sort((a, b) => b.event.created_at - a.event.created_at) // Take the most recent version const { event, productData } = productEvents[0] console.log('Processing most recent product event:', event) console.log('Parsed product data:', productData) const stall = marketStore.stalls.find(s => s.pubkey === event.pubkey) console.log('Found stall for product:', stall) if (stall) { const product: Product = { id: productData.id, // Use the product's unique ID from content, not the Nostr event ID stall_id: stall.id, stallName: stall.name, name: productData.name, description: productData.description, price: productData.price, currency: productData.currency, quantity: productData.quantity, images: productData.images, categories: productData.categories, createdAt: event.created_at, updatedAt: event.created_at } console.log('Created product (most recent version):', product) marketStore.addProduct(product) } else { console.warn('No stall found for product pubkey:', event.pubkey) } }) } catch (err) { console.error('Error loading products:', err) // Don't throw error, continue without products } // If no products found, add some sample products for testing if (marketStore.products.length === 0) { console.log('No products found, adding sample products for testing') addSampleProducts() } } const addSampleProducts = () => { // Create a sample stall if none exists if (marketStore.stalls.length === 0) { const sampleStall: Stall = { id: 'sample-stall-1', pubkey: '70f93a32c14efe5e5c5ed7c13351dd53de367701dd00dd10a1f89280c7c586d5', name: 'Castle Tech', description: 'Premium tech products', categories: ['Electronics', 'Security'], shipping: {} } marketStore.addStall(sampleStall) } const sampleProducts: Product[] = [ { id: 'sample-product-1', stall_id: 'sample-stall-1', stallName: 'Castle Tech', name: 'Seed Signer', description: 'Your Cyberpunk Cold Wallet', price: 100000, currency: 'sat', quantity: 15, images: [], categories: ['Hardware', 'Security'], createdAt: Date.now() / 1000, updatedAt: Date.now() / 1000 }, { id: 'sample-product-2', stall_id: 'sample-stall-1', stallName: 'Castle Tech', name: 'Bitcoin Node', description: 'Full Bitcoin node for maximum privacy', price: 50000, currency: 'sat', quantity: 10, images: [], categories: ['Hardware', 'Networking'], createdAt: Date.now() / 1000, updatedAt: Date.now() / 1000 } ] sampleProducts.forEach(product => { marketStore.addProduct(product) }) console.log('Added sample products:', sampleProducts.length) } const subscribeToMarketUpdates = () => { try { const client = nostrStore.getClient() // Subscribe to real-time market updates const filters = [ { kinds: [MARKET_EVENT_KINDS.STALL, MARKET_EVENT_KINDS.PRODUCT], since: Math.floor(Date.now() / 1000) } ] // Subscribe to each relay individually const unsubscribes = config.market.supportedRelays.map(relay => { const sub = client.poolInstance.subscribeMany( [relay], filters, { onevent: (event: any) => { handleMarketEvent(event) } } ) return () => sub.close() }) // Return a function that unsubscribes from all relays return () => { unsubscribes.forEach(unsub => unsub()) } } catch (err) { console.error('Error subscribing to market updates:', err) // Return a no-op function if subscription fails return () => {} } } const handleMarketEvent = (event: any) => { try { switch (event.kind) { case MARKET_EVENT_KINDS.STALL: handleStallEvent(event) break case MARKET_EVENT_KINDS.PRODUCT: handleProductEvent(event) break case MARKET_EVENT_KINDS.ORDER: handleOrderEvent(event) break } } catch (err) { console.error('Error handling market event:', err) } } const handleStallEvent = (event: any) => { try { const stallData = JSON.parse(event.content) const stall: Stall = { id: event.id, pubkey: event.pubkey, name: stallData.name, description: stallData.description, logo: stallData.logo, categories: stallData.categories, shipping: stallData.shipping } marketStore.addStall(stall) } catch (err) { console.warn('Failed to parse stall event:', err) } } const handleProductEvent = (event: any) => { try { const productData = JSON.parse(event.content) const stall = marketStore.stalls.find(s => s.pubkey === event.pubkey) if (stall) { const product: Product = { id: event.id, stall_id: stall.id, stallName: stall.name, name: productData.name, description: productData.description, price: productData.price, currency: productData.currency, quantity: productData.quantity, images: productData.images, categories: productData.categories, createdAt: event.created_at, updatedAt: event.created_at } marketStore.addProduct(product) } } catch (err) { console.warn('Failed to parse product event:', err) } } const handleOrderEvent = (event: any) => { try { const orderData = JSON.parse(event.content) // Handle order events (for future order management) console.log('Order event received:', orderData) } catch (err) { console.warn('Failed to parse order event:', err) } } const publishProduct = async (_productData: any) => { try { // Note: This would need to be signed with the user's private key // For now, we'll just log that this function needs to be implemented console.log('Product publishing not yet implemented - needs private key signing') } catch (err) { console.error('Error publishing product:', err) throw err } } const publishStall = async (_stallData: any) => { try { // Note: This would need to be signed with the user's private key // For now, we'll just log that this function needs to be implemented console.log('Stall publishing not yet implemented - needs private key signing') } catch (err) { console.error('Error publishing stall:', err) throw err } } const connectToMarket = async () => { try { console.log('Checking Nostr connection...') console.log('Current connection state:', nostrStore.isConnected) if (!nostrStore.isConnected) { console.log('Connecting to Nostr relays...') await nostrStore.connect() console.log('Connected to Nostr relays') } isConnected.value = nostrStore.isConnected console.log('Final connection state:', isConnected.value) if (!isConnected.value) { throw new Error('Failed to connect to Nostr relays') } } catch (err) { console.error('Error connecting to market:', err) throw err } } const disconnectFromMarket = () => { // Cleanup subscriptions and connections isConnected.value = false } return { // State isLoading: readonly(isLoading), isConnected: readonly(isConnected), // Actions loadMarket, connectToMarket, disconnectFromMarket, publishProduct, publishStall, subscribeToMarketUpdates } }