import { useQuery } from '@tanstack/react-query'; import { useNostr } from '@nostrify/react'; import { useSkateRelays } from '@/hooks/useSkateRelays'; /** * Hook to track completed sales for a seller. * * Sales are tracked via status changes in marketplace listings (kind 30402). * A "sale" is counted when a listing has status = 'sold'. * * Future enhancement: Could also track via kind 1111 comments with specific tags * to confirm receipt/completion of sales. */ export function useSellerSales(pubkey: string | undefined) { const { querySkateContent, getQueryKeySuffix } = useSkateRelays(); return useQuery({ queryKey: ['seller-sales', pubkey, getQueryKeySuffix()], queryFn: async () => { if (!pubkey) return { totalSales: 0, recentSales: [] }; const signal = AbortSignal.timeout(8000); try { // Query all marketplace listings by this seller const events = await querySkateContent( [ { kinds: [30402], // NIP-99 Classified Listings authors: [pubkey], '#t': ['skateboarding'], limit: 200, }, ], { signal } ); // Deduplicate by d-tag (keep latest version of each listing) const listingMap = new Map(); for (const event of events) { const d = event.tags.find(([name]) => name === 'd')?.[1]; if (d) { const existing = listingMap.get(d); if (!existing || event.created_at > existing.created_at) { listingMap.set(d, event); } } } // Count listings with status = 'sold' const allListings = Array.from(listingMap.values()); const soldListings = allListings.filter(event => { const status = event.tags.find(([name]) => name === 'status')?.[1]; return status === 'sold'; }); // Get recent sales (last 5) const recentSales = soldListings .sort((a, b) => b.created_at - a.created_at) .slice(0, 5) .map(event => ({ id: event.id, title: event.tags.find(([name]) => name === 'title')?.[1] || 'Unknown item', soldAt: event.created_at, })); return { totalSales: soldListings.length, recentSales, }; } catch (error) { console.warn('[useSellerSales] Error:', error); return { totalSales: 0, recentSales: [] }; } }, enabled: !!pubkey, staleTime: 300000, // 5 minutes gcTime: 600000, // 10 minutes }); } /** * Batch hook to get sales for multiple sellers at once */ export function useBatchSellerSales(pubkeys: string[]) { const { querySkateContent, getQueryKeySuffix } = useSkateRelays(); return useQuery({ queryKey: ['batch-seller-sales', pubkeys.sort().join(','), getQueryKeySuffix()], queryFn: async () => { if (pubkeys.length === 0) return new Map(); const signal = AbortSignal.timeout(10000); try { // Query listings for all sellers at once const events = await querySkateContent( [ { kinds: [30402], authors: pubkeys, '#t': ['skateboarding'], limit: 500, }, ], { signal } ); // Group by pubkey and count sold items const salesByPubkey = new Map(); // Initialize all pubkeys with 0 for (const pk of pubkeys) { salesByPubkey.set(pk, 0); } // Deduplicate by pubkey+d-tag (keep latest version of each listing) const listingMap = new Map(); for (const event of events) { const d = event.tags.find(([name]) => name === 'd')?.[1]; if (d) { const key = `${event.pubkey}:${d}`; const existing = listingMap.get(key); if (!existing || event.created_at > existing.created_at) { listingMap.set(key, event); } } } // Count sold listings per pubkey for (const event of listingMap.values()) { const status = event.tags.find(([name]) => name === 'status')?.[1]; if (status === 'sold') { const current = salesByPubkey.get(event.pubkey) || 0; salesByPubkey.set(event.pubkey, current + 1); } } return salesByPubkey; } catch (error) { console.warn('[useBatchSellerSales] Error:', error); return new Map(); } }, enabled: pubkeys.length > 0, staleTime: 300000, gcTime: 600000, }); }