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>
76 lines
3.1 KiB
TypeScript
76 lines
3.1 KiB
TypeScript
import { Link } from 'react-router-dom';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
import { useCrewsForSpot } from '@/hooks/useCrewsForSpot';
|
|
import { crewAccent } from '@/lib/crewHelpers';
|
|
import { Users } from 'lucide-react';
|
|
|
|
/**
|
|
* Reverse link on a spot page: "Crews that skate here". Makes the local scene
|
|
* visible from the spot side, not just from the crew side.
|
|
*/
|
|
export function CrewsForSpotCard({
|
|
spotPubkey,
|
|
spotIdentifier,
|
|
}: {
|
|
spotPubkey?: string;
|
|
spotIdentifier?: string;
|
|
}) {
|
|
const { data: crews, isLoading } = useCrewsForSpot(spotPubkey, spotIdentifier);
|
|
|
|
// Nothing to show and not loading → render nothing (keeps the page clean).
|
|
if (!isLoading && (!crews || crews.length === 0)) return null;
|
|
|
|
return (
|
|
<Card className="border-2 border-teal-500/20 bg-black/90">
|
|
<CardHeader className="pb-3">
|
|
<CardTitle className="flex items-center gap-2 text-lg font-black uppercase text-white">
|
|
<Users className="h-5 w-5 text-teal-400" /> Crews that skate here
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-2">
|
|
{isLoading ? (
|
|
Array.from({ length: 2 }).map((_, i) => (
|
|
<div key={i} className="flex items-center gap-3">
|
|
<Skeleton className="h-10 w-10 rounded-xl" />
|
|
<div className="flex-1 space-y-1.5">
|
|
<Skeleton className="h-3.5 w-28" />
|
|
<Skeleton className="h-3 w-20" />
|
|
</div>
|
|
</div>
|
|
))
|
|
) : (
|
|
crews!.map((crew) => {
|
|
const accent = crewAccent(crew);
|
|
return (
|
|
<Link
|
|
key={`${crew.pubkey}:${crew.identifier}`}
|
|
to={`/crew/${crew.pubkey}/${crew.identifier}`}
|
|
className="flex items-center gap-3 rounded-xl border border-white/5 bg-black/40 p-2 transition-colors hover:bg-black/60"
|
|
>
|
|
<Avatar className="h-10 w-10 rounded-xl border" style={{ borderColor: `${accent.hex}55` }}>
|
|
<AvatarImage src={crew.image} alt={crew.name} className="object-cover" />
|
|
<AvatarFallback
|
|
className="rounded-xl text-xs font-black"
|
|
style={{ color: accent.hex, background: `${accent.hex}22` }}
|
|
>
|
|
{crew.name.slice(0, 2).toUpperCase()}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="truncate text-sm font-bold text-white">{crew.name}</p>
|
|
<p className="flex items-center gap-1 text-xs text-gray-500">
|
|
<Users className="h-3 w-3" /> {crew.members.length} member
|
|
{crew.members.length === 1 ? '' : 's'}
|
|
{crew.location && ` · ${crew.location}`}
|
|
</p>
|
|
</div>
|
|
</Link>
|
|
);
|
|
})
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|