Crews page
- "Near me" geolocation toggle with haversine distance sort + distance chips
- Crew count + cities social-proof stat header
- Deterministic per-crew vibe-colour accents (no more wall of teal)
- Activity pulse ("🔥 active this week") + OG/Verified/Established rep badges
- Rich empty state ("Claim your local scene") instead of dead "No crews yet"
Crew synergies
- Crew treasury: transparent, non-custodial NIP-75 zap goal (kind 9041) with
live progress bar tallied from zap receipts; funds settle to founder's own
Lightning wallet — no shared wallet or new wallet settings needed
- Crew-hosted events: host a jam/comp as the crew (event a-tag → crew coord),
"Hosted by [crew]" on event pages, Crew Events list on the crew page
- Crew ↔ Lab hiring pipeline: "Open Call / looking-for" flags (filmer, editor,
coach, builder, members) surfaced on the crew page
- Request to join: encrypted NIP-17 DM to founder + admins (no founder-push
bottleneck, no public join event)
- Reverse links: "Crews that skate here" on spot pages
Contacts (0xchat-style + Armada-style privacy)
- New /contacts page with Crew-mates / Follows / Recent DMs / Saved tabs
- Per-contact quick actions: message, zap, view profile, star
- Private encrypted contacts list (NIP-51 kind 30000, NIP-44 encrypted to self)
- Added to burger menu + desktop More menu (gated to logged-in users)
Docs: NIP.md updated with crew treasury, crew-hosted events, looking-for tags,
and the private encrypted contacts list.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import { useSkateRelays } from '@/hooks/useSkateRelays';
|
|
import {
|
|
SKATE_CREW_KIND,
|
|
CALENDAR_EVENT_KIND,
|
|
parseSkateEvent,
|
|
type SkateEvent,
|
|
} from '@/lib/types';
|
|
|
|
/**
|
|
* Events hosted BY a crew — calendar events (kind 31923) carrying an `a` tag
|
|
* that points back at the crew coordinate (`35006:pubkey:d`).
|
|
*
|
|
* This powers the "Hosted by [crew]" link on events and the crew's own
|
|
* "Upcoming jams" list. Crew-hosted events close the Crews ↔ Events ↔ Zaps loop.
|
|
*/
|
|
export function useCrewEvents(crewPubkey?: string, crewIdentifier?: string, limit = 30) {
|
|
const { querySkateContent, getQueryKeySuffix } = useSkateRelays();
|
|
const crewCoord =
|
|
crewPubkey && crewIdentifier
|
|
? `${SKATE_CREW_KIND}:${crewPubkey}:${crewIdentifier}`
|
|
: '';
|
|
|
|
return useQuery({
|
|
queryKey: ['crew-events', crewCoord, getQueryKeySuffix()],
|
|
enabled: !!crewCoord,
|
|
staleTime: 60 * 1000,
|
|
gcTime: 30 * 60 * 1000,
|
|
queryFn: async () => {
|
|
const signal = AbortSignal.timeout(10000);
|
|
const events = await querySkateContent(
|
|
[{ kinds: [CALENDAR_EVENT_KIND], '#a': [crewCoord], limit }],
|
|
{ signal },
|
|
);
|
|
|
|
const parsed: SkateEvent[] = [];
|
|
for (const ev of events) {
|
|
const skateEvent = parseSkateEvent(ev);
|
|
// Guard against loose relay matches — the `a` tag must be present.
|
|
const hasCrewRef = ev.tags.some(([t, v]) => t === 'a' && v === crewCoord);
|
|
if (skateEvent && hasCrewRef) parsed.push(skateEvent);
|
|
}
|
|
|
|
// Soonest upcoming first, then past.
|
|
const now = Math.floor(Date.now() / 1000);
|
|
return parsed.sort((a, b) => {
|
|
const aFuture = a.start >= now ? 0 : 1;
|
|
const bFuture = b.start >= now ? 0 : 1;
|
|
if (aFuture !== bFuture) return aFuture - bFuture;
|
|
return a.start - b.start;
|
|
});
|
|
},
|
|
});
|
|
}
|