SkateSpots/src/hooks/useSkateRelays.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

140 lines
4.3 KiB
TypeScript

import { useNostr } from '@nostrify/react';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useAppContext } from '@/hooks/useAppContext';
import { SKATE_RELAY } from '@/lib/types';
import type { NostrEvent, NostrFilter } from '@nostrify/nostrify';
/**
* Hook that provides relay-aware query functions for skate-specific content.
*
* Logic:
* - Logged out: Always use community relay only (bootstrap)
* - Logged in + useSkateRelay=true: Community relay + user relays (default)
* - Logged in + useSkateRelay=false: User relays only (opted out)
*/
export function useSkateRelays() {
const { nostr } = useNostr();
const { user } = useCurrentUser();
const { config } = useAppContext();
const isLoggedIn = !!user;
const useSkateRelay = config.useSkateRelay ?? true;
/**
* Get the list of relay URLs to query for skate content
*/
const getReadRelays = (): string[] => {
// Logged out: always use community relay only
if (!isLoggedIn) {
return [SKATE_RELAY];
}
// Logged in: check setting
const userRelays = config.relayMetadata.relays
.filter(r => r.read)
.map(r => r.url);
if (useSkateRelay) {
// Include community relay + user relays (deduplicated)
const relaySet = new Set([SKATE_RELAY, ...userRelays]);
return Array.from(relaySet);
} else {
// User opted out - only their relays
// If they have no relays configured, fall back to community relay
return userRelays.length > 0 ? userRelays : [SKATE_RELAY];
}
};
/**
* Get the list of relay URLs to publish skate content to
*/
const getWriteRelays = (): string[] => {
// Logged out shouldn't be publishing, but just in case
if (!isLoggedIn) {
return [SKATE_RELAY];
}
const userRelays = config.relayMetadata.relays
.filter(r => r.write)
.map(r => r.url);
if (useSkateRelay) {
// Include community relay + user relays (deduplicated)
const relaySet = new Set([SKATE_RELAY, ...userRelays]);
return Array.from(relaySet);
} else {
// User opted out - only their relays
// If they have no relays configured, fall back to community relay
return userRelays.length > 0 ? userRelays : [SKATE_RELAY];
}
};
/**
* Query skate content from appropriate relays based on user state/settings.
* Automatically handles parallel queries and deduplication for addressable events.
*/
const querySkateContent = async (
filters: NostrFilter[],
options: { signal: AbortSignal }
): Promise<NostrEvent[]> => {
const relays = getReadRelays();
console.log(`[useSkateRelays] Querying ${relays.length} relay(s):`, relays);
// Query all relays in parallel
const queryPromises = relays.map(async (relayUrl) => {
try {
const relay = nostr.relay(relayUrl);
const events = await relay.query(filters, options);
console.log(`[useSkateRelays] ${relayUrl} returned ${events.length} events`);
return events;
} catch (error) {
console.warn(`[useSkateRelays] Failed to query ${relayUrl}:`, error);
return [];
}
});
const results = await Promise.all(queryPromises);
const allEvents = results.flat();
// Deduplicate addressable events by pubkey+kind+d-tag
const eventMap = new Map<string, NostrEvent>();
for (const event of allEvents) {
const dTag = event.tags.find(([name]) => name === 'd')?.[1];
if (dTag) {
// Addressable event - dedupe by coordinate
const key = `${event.pubkey}:${event.kind}:${dTag}`;
const existing = eventMap.get(key);
if (!existing || event.created_at > existing.created_at) {
eventMap.set(key, event);
}
} else {
// Regular event - dedupe by id
eventMap.set(event.id, event);
}
}
const uniqueEvents = Array.from(eventMap.values());
console.log(`[useSkateRelays] Total unique events: ${uniqueEvents.length}`);
return uniqueEvents;
};
/**
* Generate a stable query key suffix based on relay configuration.
* Used to invalidate cache when relay settings change.
*/
const getQueryKeySuffix = (): string => {
const relays = getReadRelays();
return relays.sort().join(',');
};
return {
getReadRelays,
getWriteRelays,
querySkateContent,
getQueryKeySuffix,
isLoggedIn,
useSkateRelay,
};
}