import { useQuery } from '@tanstack/react-query'; import { CALENDAR_EVENT_KIND, parseSkateEvent, isFeaturedActive, type SkateEvent } from '@/lib/types'; import { useSkateRelays } from '@/hooks/useSkateRelays'; import { useAdminModeration, filterAdminHidden } from '@/hooks/useAdminModeration'; import { verifiedFeaturedCoords } from '@/lib/featuredVerification'; export function useSkateEvents(limit = 50, options: { includeHidden?: boolean } = {}) { const { includeHidden = false } = options; const { querySkateContent, getQueryKeySuffix } = useSkateRelays(); const hidden = useAdminModeration(); return useQuery({ queryKey: ['skate-events', limit, getQueryKeySuffix()], // `includeHidden`: admin tooling only (review queue + undo). select: (events: SkateEvent[]) => includeHidden ? events : filterAdminHidden(events, CALENDAR_EVENT_KIND, hidden), queryFn: async () => { const signal = AbortSignal.timeout(15000); const events = await querySkateContent( [ { kinds: [CALENDAR_EVENT_KIND], '#t': ['skateboarding'], limit, }, ], { signal, requireAny: true } ); const skateEvents: SkateEvent[] = []; for (const event of events) { const skateEvent = parseSkateEvent(event); if (skateEvent) skateEvents.push(skateEvent); } // Verified/paid featured first, then soonest start time. const coordOf = (ev: SkateEvent) => `${CALENDAR_EVENT_KIND}:${ev.pubkey}:${ev.identifier}`; const featuredCoords = skateEvents.filter((ev) => isFeaturedActive(ev.featuredUntil)).map(coordOf); const verified = await verifiedFeaturedCoords(featuredCoords, (f) => querySkateContent([f], { signal })); const isPro = (ev: SkateEvent) => isFeaturedActive(ev.featuredUntil) && (verified === null || verified.has(coordOf(ev))); return skateEvents.sort((a, b) => { const aF = isPro(a) ? 1 : 0; const bF = isPro(b) ? 1 : 0; if (aF !== bF) return bF - aF; return a.start - b.start; }); }, staleTime: 60000, }); } export function useUpcomingSkateEvents(limit = 20, options: { enabled?: boolean } = {}) { const { enabled = true } = options; const { querySkateContent, getQueryKeySuffix } = useSkateRelays(); const hidden = useAdminModeration(); const now = Math.floor(Date.now() / 1000); return useQuery({ queryKey: ['upcoming-skate-events', limit, getQueryKeySuffix()], enabled, select: (events: SkateEvent[]) => filterAdminHidden(events, CALENDAR_EVENT_KIND, hidden), queryFn: async () => { const signal = AbortSignal.timeout(15000); const events = await querySkateContent( [ { kinds: [CALENDAR_EVENT_KIND], '#t': ['skateboarding'], limit: limit * 2, // Fetch more to filter }, ], { signal } ); const skateEvents: SkateEvent[] = []; for (const event of events) { const skateEvent = parseSkateEvent(event); // Only include future events if (skateEvent && skateEvent.start >= now) { skateEvents.push(skateEvent); } } // Verified/paid featured first, then soonest start time. const coordOf = (ev: SkateEvent) => `${CALENDAR_EVENT_KIND}:${ev.pubkey}:${ev.identifier}`; const featuredCoords = skateEvents.filter((ev) => isFeaturedActive(ev.featuredUntil)).map(coordOf); const verified = await verifiedFeaturedCoords(featuredCoords, (f) => querySkateContent([f], { signal })); const isPro = (ev: SkateEvent) => isFeaturedActive(ev.featuredUntil) && (verified === null || verified.has(coordOf(ev))); return skateEvents .sort((a, b) => { const aF = isPro(a) ? 1 : 0; const bF = isPro(b) ? 1 : 0; if (aF !== bF) return bF - aF; return a.start - b.start; }) .slice(0, limit); }, staleTime: 60000, }); } /** Events authored by one pubkey (profile page). Upcoming first, then recent. */ export function useUserEvents(pubkey: string | undefined) { const { querySkateContent, getQueryKeySuffix } = useSkateRelays(); const hidden = useAdminModeration(); return useQuery({ queryKey: ['user-skate-events', pubkey, getQueryKeySuffix()], enabled: !!pubkey, select: (events: SkateEvent[]) => filterAdminHidden(events, CALENDAR_EVENT_KIND, hidden), queryFn: async () => { if (!pubkey) return []; const signal = AbortSignal.timeout(12000); const events = await querySkateContent( [{ kinds: [CALENDAR_EVENT_KIND], authors: [pubkey], '#t': ['skateboarding'], limit: 50, }], { signal }, ); const now = Math.floor(Date.now() / 1000); return events .map(parseSkateEvent) .filter((event): event is SkateEvent => !!event) .sort((a, b) => { const aUpcoming = a.start >= now; const bUpcoming = b.start >= now; if (aUpcoming !== bUpcoming) return aUpcoming ? -1 : 1; return aUpcoming ? a.start - b.start : b.start - a.start; }); }, staleTime: 60_000, }); } export function useSkateEvent(identifier: string, pubkey?: string) { const { querySkateContent, getQueryKeySuffix } = useSkateRelays(); const hidden = useAdminModeration(); return useQuery({ queryKey: ['skate-event', identifier, pubkey, getQueryKeySuffix()], select: (evt: SkateEvent | null) => evt && hidden.has({ coord: `${CALENDAR_EVENT_KIND}:${evt.pubkey}:${evt.identifier}`, id: evt.id }) ? null : evt, queryFn: async () => { const signal = AbortSignal.timeout(10000); const filter: { kinds: number[]; '#d': string[]; authors?: string[]; limit: number; } = { kinds: [CALENDAR_EVENT_KIND], '#d': [identifier], limit: 1, }; if (pubkey) { filter.authors = [pubkey]; } const events = await querySkateContent([filter], { signal }); if (events.length === 0) return null; return parseSkateEvent(events[0]); }, enabled: !!identifier, staleTime: 60000, }); }