import { useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import { useSkateRelays } from '@/hooks/useSkateRelays'; /** NIP-56 report kind. */ export const REPORT_KIND = 1984; export interface SpotReportCounts { /** Every NIP-56 report referencing the spot. */ total: number; /** Reports specifically tagged `not-a-skate-spot` (see useNotASpotReport). */ notASpot: number; /** DISTINCT accounts that filed a `not-a-skate-spot` report (anti-brigade). */ reporters: number; } /** * Batch-read NIP-56 reports for many spots in ONE query. * * Reports carry the target's event id in an `e` tag, so asking for * `#e: [all spot ids]` is a single relay request — the same trick as the status * and photo batch hooks. * * WHY THIS EXISTS: people could already report a spot, but nothing ever brought * those reports to the admin — the only reader in the whole app was a count on * marketplace cards. A report button whose output nobody sees is decoration, so * the review queue now ranks reported spots to the top. */ export function useSpotReports(spots: Array<{ id: string }>) { const { querySkateContent, getQueryKeySuffix } = useSkateRelays(); const ids = useMemo(() => spots.map((s) => s.id).filter(Boolean), [spots]); const query = useQuery({ queryKey: ['spot-reports', ids.slice().sort().join(','), getQueryKeySuffix()], enabled: ids.length > 0, staleTime: 60_000, queryFn: async (c) => { const signal = AbortSignal.any([c.signal, AbortSignal.timeout(8000)]); return querySkateContent( [{ kinds: [REPORT_KIND], '#e': ids, limit: 1000 }], { signal }, ); }, }); const counts = useMemo(() => { const map = new Map(); const notASpotReporters = new Map>(); for (const ev of query.data ?? []) { // Our own taxonomy marker, or the NIP-56 'other' reason from the dialog. const isNotASpot = ev.tags.some(([t, v]) => t === 't' && v === 'not-a-skate-spot') || ev.tags.some(([t, v]) => t === 'l' && v === 'not-a-skate-spot'); // A report can reference several targets; count each. for (const [n, value] of ev.tags) { if (n !== 'e' || !value) continue; const entry = map.get(value) ?? { total: 0, notASpot: 0, reporters: 0 }; entry.total += 1; if (isNotASpot) { entry.notASpot += 1; const set = notASpotReporters.get(value) ?? new Set(); set.add(ev.pubkey); notASpotReporters.set(value, set); entry.reporters = set.size; } map.set(value, entry); } } return map; }, [query.data]); return { isLoading: query.isLoading, getCounts: (id: string): SpotReportCounts => counts.get(id) ?? { total: 0, notASpot: 0, reporters: 0 }, }; }