SkateSpots/src/hooks/useCrews.ts
shakespeare.diy f10aa7971c Step 3: Crews — map-anchored collective feature (kind 35006)
- New SkateCrew kind 35006 + type/parser + useCrews/useCrew hooks
- /crews directory, /create-crew, /edit-crew, /crew/:pubkey/:identifier pages
- Reuses the collective engine: NIP-32 membership attestation (confirmed/pending),
  CollectiveMembers + ConfirmMembershipCard, ClinkPayButton, SpotPicker
- Activity v1: home spots + crew member feed (no events, per plan)
- Map: teal/cyan Crews layer at home turf with crew markers + popups;
  wired through SpotMap props, layer toggles (mobile popover + filters sheet),
  and mobileLayerCount badge
- Nav: Crews added to navItemKeys (desktop header, mobile bottom bar, sidebar)
  with en/es i18n keys
- NIP.md: documents kind 35006, tag taxonomy, and shared membership mechanism

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 13:59:50 +07:00

72 lines
1.9 KiB
TypeScript

import { useQuery } from '@tanstack/react-query';
import { SKATE_CREW_KIND, parseSkateCrew, type SkateCrew } from '@/lib/types';
import { useSkateRelays } from '@/hooks/useSkateRelays';
export function useCrews(limit = 200) {
const { querySkateContent, getQueryKeySuffix } = useSkateRelays();
return useQuery({
queryKey: ['skate-crews', limit, getQueryKeySuffix()],
queryFn: async () => {
const signal = AbortSignal.timeout(10000);
const events = await querySkateContent(
[
{
kinds: [SKATE_CREW_KIND],
'#t': ['skatecrew'],
limit,
},
],
{ signal }
);
const crews: SkateCrew[] = [];
for (const event of events) {
const crew = parseSkateCrew(event);
if (crew) crews.push(crew);
}
return crews.sort((a, b) => b.createdAt - a.createdAt);
},
staleTime: 5 * 60 * 1000,
gcTime: 30 * 60 * 1000,
refetchOnMount: true,
});
}
export function useCrew(identifier: string, pubkey?: string) {
const { querySkateContent, getQueryKeySuffix } = useSkateRelays();
return useQuery({
queryKey: ['skate-crew', identifier, pubkey, getQueryKeySuffix()],
queryFn: async () => {
const signal = AbortSignal.timeout(10000);
const filter: {
kinds: number[];
'#d': string[];
authors?: string[];
limit: number;
} = {
kinds: [SKATE_CREW_KIND],
'#d': [identifier],
limit: 1,
};
if (pubkey) filter.authors = [pubkey];
const events = await querySkateContent([filter], { signal });
if (events.length === 0) return null;
const mostRecent = events.reduce((latest, current) =>
current.created_at > latest.created_at ? current : latest
);
return parseSkateCrew(mostRecent);
},
enabled: !!identifier,
staleTime: 5 * 60 * 1000,
gcTime: 30 * 60 * 1000,
refetchOnMount: true,
});
}