import { useState, useMemo } from 'react'; import { useSeoMeta } from '@unhead/react'; import { Link } from 'react-router-dom'; import { Layout } from '@/components/Layout'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { CrewRepBadge, CrewActivityPulse } from '@/components/CrewBadge'; import { useCrews } from '@/hooks/useCrews'; import { useCrewsActivity, crewCoordinate } from '@/hooks/useCrewActivity'; import { useGeolocation } from '@/hooks/useGeolocation'; import { crewAccent, crewDistanceKm, formatDistance, type LatLng } from '@/lib/crewHelpers'; import { CREW_VIBE_LABELS, CREW_FOCUS_LABELS, type SkateCrew, type CrewFocus } from '@/lib/types'; import type { CrewActivity } from '@/lib/crewHelpers'; import { cn } from '@/lib/utils'; import { Users, Plus, Search, MapPin, X, HardHat, Camera, GraduationCap, Sparkles, Navigation, Loader2 } from 'lucide-react'; const FOCUS_FILTERS: { key: CrewFocus | 'all'; label: string; icon: typeof Users }[] = [ { key: 'all', label: 'All', icon: Users }, { key: 'builder', label: CREW_FOCUS_LABELS.builder, icon: HardHat }, { key: 'photo', label: CREW_FOCUS_LABELS.photo, icon: Camera }, { key: 'coaching', label: CREW_FOCUS_LABELS.coaching, icon: GraduationCap }, { key: 'mixed', label: CREW_FOCUS_LABELS.mixed, icon: Sparkles }, ]; const FOCUS_BADGE_ICONS: Record = { mixed: Sparkles, builder: HardHat, photo: Camera, coaching: GraduationCap, }; function CrewCard({ crew, activity, distanceKm, }: { crew: SkateCrew; activity?: CrewActivity; distanceKm: number | null; }) { const accent = crewAccent(crew); return ( {crew.name.slice(0, 2).toUpperCase()}

{crew.name} {crew.members.length}

{crew.focus !== 'mixed' && (() => { const FocusIcon = FOCUS_BADGE_ICONS[crew.focus]; return ( {CREW_FOCUS_LABELS[crew.focus]} ); })()}
{(crew.location || distanceKm !== null) && (

{crew.location} {distanceKm !== null && ( · {formatDistance(distanceKm)} )}

)} {crew.vibes.length > 0 && (
{crew.vibes.slice(0, 3).map((v) => ( {CREW_VIBE_LABELS[v] || v} ))}
)}
); } export default function CrewsPage() { useSeoMeta({ title: 'Crews - SkateSpots', description: 'Find your people. Discover local skate crews, collectives and communities near you.', }); const { data: crews, isLoading } = useCrews(200); const { data: activityMap } = useCrewsActivity(crews); const geo = useGeolocation(); const [query, setQuery] = useState(''); const [focusFilter, setFocusFilter] = useState('all'); const [nearMe, setNearMe] = useState(false); const userLoc: LatLng | null = geo.coords; // Stats: crew count + distinct cities (social proof header). const { cityCount } = useMemo(() => { const cities = new Set( (crews ?? []) .map((c) => c.location?.trim().toLowerCase()) .filter((c): c is string => !!c), ); return { cityCount: cities.size }; }, [crews]); const filtered = useMemo(() => { if (!crews) return []; const q = query.trim().toLowerCase(); let list = crews.filter((c) => { if (focusFilter !== 'all' && c.focus !== focusFilter) return false; if (!q) return true; return ( c.name.toLowerCase().includes(q) || c.location?.toLowerCase().includes(q) || c.bio?.toLowerCase().includes(q) ); }); // Near-me: sort by distance ascending, crews without geo sink to the bottom. if (nearMe && userLoc) { list = [...list].sort((a, b) => { const da = crewDistanceKm(a, userLoc); const db = crewDistanceKm(b, userLoc); if (da === null && db === null) return 0; if (da === null) return 1; if (db === null) return -1; return da - db; }); } return list; }, [crews, query, focusFilter, nearMe, userLoc]); const handleNearMe = () => { if (nearMe) { setNearMe(false); return; } if (!userLoc) geo.request(); setNearMe(true); }; return (
{/* Header */}

Crews

Find your people — local crews, collectives & communities

{/* Social-proof stat line */} {!isLoading && crews && crews.length > 0 && (

{crews.length} crew{crews.length === 1 ? '' : 's'} {cityCount > 0 && ` · ${cityCount} ${cityCount === 1 ? 'city' : 'cities'}`}

)}
{/* Search */}
setQuery(e.target.value)} placeholder="Search crews by name or city…" className="h-10 w-full rounded-xl border border-white/10 bg-white/5 pl-9 pr-9 text-sm text-white placeholder:text-gray-600 transition-all focus:border-teal-500/50 focus:outline-none" /> {query && ( )}
{/* Filters row: focus chips + Near me */}
{FOCUS_FILTERS.map(({ key, label, icon: Icon }) => { const active = focusFilter === key; return ( ); })}
{geo.error && nearMe && (

{geo.error}

)} {/* List */} {isLoading ? (
{Array.from({ length: 5 }).map((_, i) => (
))}
) : filtered.length === 0 ? ( ) : (
{filtered.map((crew) => ( ))}
)}
); } function EmptyState({ query, onNearMe, hasLocation, }: { query: string; onNearMe: () => void; hasLocation: boolean; }) { if (query) { return (

No crews match

Try a different search.

); } // Rich empty state — never feels dead. Prompts to claim the local scene. return (
🛹

Claim your local scene

No crews planted here yet. Be the first — start your crew, link your home spots, and put your scene on the map for everyone to find.

{!hasLocation && ( )}
); }