SkateSpots/src/hooks/useBatchTrustScores.ts
shakespeare.diy e29d35f1e4 fix: restore full skate-spots source tree from misplaced src/src/ copy
The entire skate-spots codebase was accidentally copied into src/src/ and
public/public/ instead of src/ and public/ directly. This left the real
src/ directory as blank MKStack template stubs, explaining why the landing
page showed the default template instead of SkateSpots.

Changes made:
- Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx,
  all pages, components, hooks, contexts, lib, i18n, test)
- Copied all files from public/public/ up to public/ (images, manifest, etc.)
- Deleted src/src/ and public/public/ nested directories
- Removed HelloWorld.tsx template stub from pages/
- Retained the previously fixed index.css (Tailwind v3 directives)

Full skate-spots feature set is now active:
- All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.)
- NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler
- Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.)
- Outfit Variable font, Amber pre-check in main.tsx
- All hooks (useSkateSpots, useCoaches, useZaps, etc.)
- i18n support (en/es)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:18:28 +07:00

123 lines
4 KiB
TypeScript

import { useQuery } from '@tanstack/react-query';
import { useNostr } from '@nostrify/react';
import { useCurrentUser } from './useCurrentUser';
/**
* Hook to fetch trust scores for multiple pubkeys in batch.
* More efficient than calling useTrustScore multiple times.
*
* @param pubkeys - Array of pubkeys to calculate trust scores for
* @returns Map of pubkey to trust score (0-100)
*/
export function useBatchTrustScores(pubkeys: string[]) {
const { nostr } = useNostr();
const { user } = useCurrentUser();
return useQuery({
queryKey: ['batch-trust-scores', pubkeys.sort().join(','), user?.pubkey],
queryFn: async (c) => {
const signal = AbortSignal.any([c.signal, AbortSignal.timeout(8000)]);
const scores = new Map<string, number>();
try {
// Get user's follow list (kind 3)
const sourcePubkey = user?.pubkey || '20e7c953439e66f3882ab96fe0faa8ba246d6fb12c03b0cb4e2dc4623b8bcb21';
const followListEvents = await nostr.query(
[{ kinds: [3], authors: [sourcePubkey], limit: 1 }],
{ signal }
);
const followList = followListEvents[0];
if (!followList) {
console.warn('[useBatchTrustScores] No follow list found');
// Default all to 0
pubkeys.forEach((pk) => scores.set(pk, 0));
return scores;
}
// Extract followed pubkeys
const following = new Set(
followList.tags
.filter(([tag]) => tag === 'p')
.map(([, pubkey]) => pubkey)
);
// Process each pubkey
for (const targetPubkey of pubkeys) {
// Self score = 100
if (user?.pubkey && targetPubkey === user.pubkey) {
scores.set(targetPubkey, 100);
continue;
}
// Direct follow = 90
if (following.has(targetPubkey)) {
scores.set(targetPubkey, 90);
continue;
}
// Default to 0 (will check friend-of-friend and NIP-05 later)
scores.set(targetPubkey, 0);
}
// Get friend-of-friend relationships (batch query)
const uncheckedPubkeys = pubkeys.filter((pk) => scores.get(pk) === 0);
if (uncheckedPubkeys.length > 0) {
const friendsFollowLists = await nostr.query(
[{ kinds: [3], authors: Array.from(following), limit: 100 }],
{ signal }
);
// Build a set of all friends-of-friends
const friendsOfFriends = new Set<string>();
for (const friendList of friendsFollowLists) {
friendList.tags
.filter(([tag]) => tag === 'p')
.forEach(([, pubkey]) => friendsOfFriends.add(pubkey));
}
// Check friend-of-friend = 60
for (const pubkey of uncheckedPubkeys) {
if (friendsOfFriends.has(pubkey)) {
scores.set(pubkey, 60);
}
}
}
// Check NIP-05 verification for remaining 0-score pubkeys
const unverifiedPubkeys = pubkeys.filter((pk) => scores.get(pk) === 0);
if (unverifiedPubkeys.length > 0) {
const profileEvents = await nostr.query(
[{ kinds: [0], authors: unverifiedPubkeys }],
{ signal }
);
for (const profileEvent of profileEvents) {
try {
const metadata = JSON.parse(profileEvent.content);
if (metadata.nip05) {
scores.set(profileEvent.pubkey, 30);
}
} catch (e) {
// Ignore JSON parse errors
}
}
}
return scores;
} catch (error) {
console.warn('[useBatchTrustScores] Error calculating batch trust scores:', error);
// Return default scores of 0
pubkeys.forEach((pk) => scores.set(pk, 0));
return scores;
}
},
enabled: pubkeys.length > 0,
staleTime: 3600000, // Cache for 1 hour
gcTime: 86400000, // Keep in cache for 24 hours
retry: 1,
});
}