import { useQuery } from '@tanstack/react-query'; import { MARKETPLACE_ITEM_KIND, parseMarketplaceItem, type MarketplaceItem } from '@/lib/types'; import { useSkateRelays } from '@/hooks/useSkateRelays'; import { useAdminModeration, filterAdminHidden } from '@/hooks/useAdminModeration'; const STALE_TIME = 5 * 60 * 1000; // 5 min const GC_TIME = 30 * 60 * 1000; // 30 min const TIMEOUT = 8000; export function useMarketplaceItems(limit = 100, options: { includeHidden?: boolean } = {}) { const { includeHidden = false } = options; const { querySkateContent, getQueryKeySuffix } = useSkateRelays(); const hidden = useAdminModeration(); return useQuery({ queryKey: ['marketplace-items', limit, getQueryKeySuffix()], // `includeHidden`: admin tooling only (review queue + undo). select: (items: MarketplaceItem[]) => includeHidden ? items : filterAdminHidden(items, MARKETPLACE_ITEM_KIND, hidden), queryFn: async () => { const signal = AbortSignal.timeout(TIMEOUT); const events = await querySkateContent( [ { kinds: [MARKETPLACE_ITEM_KIND], '#t': ['skateboarding'], limit, }, ], { signal, requireAny: true } ); const items: MarketplaceItem[] = []; for (const event of events) { // Skip showcase-only Stash items (public gear that is NOT for sale): // they belong on the profile's public Stash, not the sellable marketplace. const isShowcaseOnly = event.tags.some((t) => t[0] === 'showcase' && t[1] === 'true'); if (isShowcaseOnly) continue; const item = parseMarketplaceItem(event); if (item) items.push(item); } return items.sort((a, b) => b.createdAt - a.createdAt); }, staleTime: STALE_TIME, gcTime: GC_TIME, refetchOnMount: true, }); } export function useMarketplaceAtSpot(spotPubkey: string, spotIdentifier: string, limit = 20) { const { querySkateContent, getQueryKeySuffix } = useSkateRelays(); const hidden = useAdminModeration(); return useQuery({ queryKey: ['marketplace-at-spot', spotPubkey, spotIdentifier, getQueryKeySuffix()], select: (items: MarketplaceItem[]) => filterAdminHidden(items, MARKETPLACE_ITEM_KIND, hidden), queryFn: async () => { const signal = AbortSignal.timeout(TIMEOUT); const spotRef = `30818:${spotPubkey}:${spotIdentifier}`; const events = await querySkateContent( [ { kinds: [MARKETPLACE_ITEM_KIND], '#a': [spotRef], '#t': ['skateboarding'], limit, }, ], { signal } ); const items: MarketplaceItem[] = []; for (const event of events) { const isShowcaseOnly = event.tags.some((t) => t[0] === 'showcase' && t[1] === 'true'); if (isShowcaseOnly) continue; const item = parseMarketplaceItem(event); if (item && (!item.status || item.status === 'active')) { items.push(item); } } return items.sort((a, b) => b.createdAt - a.createdAt); }, enabled: !!spotPubkey && !!spotIdentifier, staleTime: STALE_TIME, gcTime: GC_TIME, refetchOnMount: true, }); } /** Fetch a single marketplace listing by author + `d` (including sold/inactive). */ export function useMarketplaceItem(pubkey?: string, identifier?: string) { const { querySkateContent, getQueryKeySuffix } = useSkateRelays(); return useQuery({ queryKey: ['marketplace-item', pubkey, identifier, getQueryKeySuffix()], enabled: !!pubkey && !!identifier, staleTime: STALE_TIME, gcTime: GC_TIME, queryFn: async (): Promise => { if (!pubkey || !identifier) return null; const signal = AbortSignal.timeout(TIMEOUT); const events = await querySkateContent( [{ kinds: [MARKETPLACE_ITEM_KIND], authors: [pubkey], '#d': [identifier], limit: 5 }], { signal }, ); const newest = [...events].sort((a, b) => b.created_at - a.created_at)[0]; return newest ? parseMarketplaceItem(newest) : null; }, }); }