import { useState, useMemo } from 'react'; import { useSeoMeta } from '@unhead/react'; import { Link } from 'react-router-dom'; import { Layout } from '@/components/Layout'; import { EventCard, EventCardSkeleton } from '@/components/EventCard'; import { TourCard, TourCardSkeleton } from '@/components/TourCard'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { TrustFilter, type TrustFilterLevel, filterByTrustLevel } from '@/components/TrustFilter'; import { SortDropdown, type SortOption } from '@/components/SortDropdown'; import { useSkateEvents } from '@/hooks/useSkateEvents'; import { useTours } from '@/hooks/useTours'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useFollowing } from '@/hooks/useFollowing'; import { useBatchTrustScores } from '@/hooks/useBatchTrustScores'; import { useComments } from '@/hooks/useComments'; import { useTranslation } from '@/contexts/LanguageContext'; import { EVENT_TYPE_LABELS, TOUR_CATEGORIES, TOUR_SKILL_LEVELS, type TourCategory, type SkateTour } from '@/lib/types'; import { Search, Plus, Calendar, X, Route, } from 'lucide-react'; import { cn } from '@/lib/utils'; export default function EventsPage() { const { t } = useTranslation(); useSeoMeta({ title: 'Skate Events & Tours - SkateSpots', description: 'Discover skateboarding events, competitions, tours, and sessions near you.', }); const { user } = useCurrentUser(); const { data: followingList } = useFollowing(user?.pubkey); const followCount = followingList?.length ?? 0; const smartDefault: TrustFilterLevel = (user && followCount >= 5) ? 'trusted' : 'all'; const { data: events, isLoading: eventsLoading } = useSkateEvents(100); const { data: tours, isLoading: toursLoading } = useTours(100); // Main view: events or tours const [mainView, setMainView] = useState<'events' | 'tours'>('events'); // Event filters const [searchQuery, setSearchQuery] = useState(''); const [selectedTypes, setSelectedTypes] = useState([]); const [activeTab, setActiveTab] = useState<'upcoming' | 'past'>('upcoming'); const [trustFilter, setTrustFilter] = useState(smartDefault); const [sortBy, setSortBy] = useState('recent'); // Tour filters const [tourSearchQuery, setTourSearchQuery] = useState(''); const [tourCategory, setTourCategory] = useState('all'); const [tourSkillLevel, setTourSkillLevel] = useState('all'); const [tourVerifiedOnly, setTourVerifiedOnly] = useState(false); const [tourTrustFilter, setTourTrustFilter] = useState(smartDefault); const [tourSortBy, setTourSortBy] = useState('recent'); const now = Math.floor(Date.now() / 1000); // Fetch trust scores for all event organizers and tour guides const allPubkeys = useMemo(() => { const eventPubkeys = events?.map((e) => e.pubkey) || []; const tourPubkeys = tours?.map((t) => t.pubkey) || []; return [...new Set([...eventPubkeys, ...tourPubkeys])]; }, [events, tours]); const { data: trustScores } = useBatchTrustScores(allPubkeys); const getTrustScore = (pubkey: string) => trustScores?.get(pubkey); // Fetch comment counts for popularity sorting const allEventIds = useMemo(() => { const eventIds = events?.map((e) => e.event.id) || []; const tourIds = tours?.map((t) => t.event.id) || []; return [...eventIds, ...tourIds]; }, [events, tours]); const { data: allComments } = useComments(allEventIds); const getCommentCount = (eventId: string) => { return allComments?.filter((c) => { const eTag = c.tags.find(([tag]) => tag === 'e')?.[1]; return eTag === eventId; }).length || 0; }; // Filter and sort tours const filteredTours = useMemo(() => { if (!tours) return []; let filtered = tours.filter((tour) => { // Search filter if (tourSearchQuery) { const query = tourSearchQuery.toLowerCase(); const matchesTitle = tour.title.toLowerCase().includes(query); const matchesLocation = tour.location?.toLowerCase().includes(query); const matchesDescription = tour.description?.toLowerCase().includes(query); const matchesCities = tour.cities?.some(c => c.toLowerCase().includes(query)); const matchesCountries = tour.countries?.some(c => c.toLowerCase().includes(query)); if (!matchesTitle && !matchesLocation && !matchesDescription && !matchesCities && !matchesCountries) { return false; } } // Category filter if (tourCategory !== 'all' && tour.category !== tourCategory) { return false; } // Skill level filter if (tourSkillLevel !== 'all' && tour.skillLevel !== tourSkillLevel) { return false; } return true; }); // Apply trust filter (soft for 'trusted', strict for 'verified'/'following') if (trustScores && tourTrustFilter !== 'all') { if (tourTrustFilter === 'trusted') { filtered.sort((a, b) => { const tA = (getTrustScore(a.pubkey) ?? -1) >= 60 ? 1 : 0; const tB = (getTrustScore(b.pubkey) ?? -1) >= 60 ? 1 : 0; return tB - tA; }); } else { filtered = filterByTrustLevel(filtered, tourTrustFilter, getTrustScore); } } // Apply sorting filtered.sort((a, b) => { switch (tourSortBy) { case 'trust': { const scoreA = getTrustScore(a.pubkey) || 0; const scoreB = getTrustScore(b.pubkey) || 0; if (scoreB !== scoreA) return scoreB - scoreA; return b.createdAt - a.createdAt; } case 'popular': { const popA = getCommentCount(a.event.id); const popB = getCommentCount(b.event.id); if (popB !== popA) return popB - popA; return b.createdAt - a.createdAt; } case 'recent': default: return b.createdAt - a.createdAt; } }); return filtered; }, [tours, tourSearchQuery, tourCategory, tourSkillLevel, tourTrustFilter, trustScores, tourSortBy]); // Filter and sort events const filteredEvents = useMemo(() => { if (!events) return { upcoming: [], past: [] }; let filtered = events.filter((event) => { // Search filter if (searchQuery) { const query = searchQuery.toLowerCase(); const matchesTitle = event.title.toLowerCase().includes(query); const matchesLocation = event.location?.toLowerCase().includes(query); const matchesDescription = event.description?.toLowerCase().includes(query); if (!matchesTitle && !matchesLocation && !matchesDescription) { return false; } } // Type filter if (selectedTypes.length > 0) { const hasMatchingType = event.types.some((type) => selectedTypes.includes(type) ); if (!hasMatchingType) return false; } return true; }); // Apply trust filter (soft for 'trusted', strict for 'verified'/'following') if (trustScores && trustFilter !== 'all') { if (trustFilter === 'trusted') { filtered.sort((a, b) => { const tA = (getTrustScore(a.pubkey) ?? -1) >= 60 ? 1 : 0; const tB = (getTrustScore(b.pubkey) ?? -1) >= 60 ? 1 : 0; return tB - tA; }); } else { filtered = filterByTrustLevel(filtered, trustFilter, getTrustScore); } } // Split into upcoming and past let upcoming = filtered.filter((e) => e.start >= now); let past = filtered.filter((e) => e.start < now); // Apply sorting const sortFn = (a: typeof events[0], b: typeof events[0]) => { switch (sortBy) { case 'trust': { const scoreA = getTrustScore(a.pubkey) || 0; const scoreB = getTrustScore(b.pubkey) || 0; if (scoreB !== scoreA) return scoreB - scoreA; // Highest first return a.start - b.start; // Then by time } case 'popular': { const popA = getCommentCount(a.event.id); const popB = getCommentCount(b.event.id); if (popB !== popA) return popB - popA; // Most popular first return a.start - b.start; } case 'recent': default: return 0; // Keep time-based sorting } }; // Sort upcoming by start time (ascending) + secondary sort upcoming.sort((a, b) => { if (sortBy === 'recent') return a.start - b.start; return sortFn(a, b); }); // Sort past by start time (descending) + secondary sort past.sort((a, b) => { if (sortBy === 'recent') return b.start - a.start; return sortFn(a, b); }); return { upcoming, past }; }, [events, searchQuery, selectedTypes, trustFilter, trustScores, sortBy, now, allComments]); const toggleType = (type: string) => { setSelectedTypes((prev) => prev.includes(type) ? prev.filter((t) => t !== type) : [...prev, type] ); }; const clearFilters = () => { setSearchQuery(''); setSelectedTypes([]); setTrustFilter('all'); setSortBy('recent'); }; const clearTourFilters = () => { setTourSearchQuery(''); setTourCategory('all'); setTourSkillLevel('all'); setTourVerifiedOnly(false); setTourTrustFilter('all'); setTourSortBy('recent'); }; const hasActiveFilters = searchQuery || selectedTypes.length > 0 || trustFilter !== 'all' || sortBy !== 'recent'; const hasActiveTourFilters = tourSearchQuery || tourCategory !== 'all' || tourSkillLevel !== 'all' || tourVerifiedOnly || tourTrustFilter !== 'all' || tourSortBy !== 'recent'; const isLoading = mainView === 'events' ? eventsLoading : toursLoading; return (
{/* Header */}

{mainView === 'events' ? t('events.title') : 'Skate Tours'}

{mainView === 'events' ? t('events.findEvents') : 'Discover local guides, national road trips, and world destruction competitions'}

{user ? ( ) : ( 🔒 {t('events.loginToCreate')} )}
{/* Main View Toggle: Events / Tours */}
{/* Events View */} {mainView === 'events' && ( <> {/* Filters */}
{/* Search, Trust Filter, and Sort */}
setSearchQuery(e.target.value)} className="pl-9" />
{/* Type filters */}
{Object.entries(EVENT_TYPE_LABELS).map(([type, label]) => ( toggleType(type)} > {label} ))} {hasActiveFilters && ( {t('common.clearAll')} )}
{/* Tabs */} setActiveTab(v as 'upcoming' | 'past')}> {t('events.upcomingEvents')} {filteredEvents.upcoming.length > 0 && ( {filteredEvents.upcoming.length} )} {t('events.pastEvents')} {filteredEvents.past.length > 0 && ( {filteredEvents.past.length} )} {eventsLoading ? (
{Array.from({ length: 6 }).map((_, i) => ( ))}
) : filteredEvents.upcoming.length > 0 ? (
{filteredEvents.upcoming.map((event) => ( ))}
) : (

{hasActiveFilters ? t('events.noEventsMatch') : t('events.noUpcoming')}

{hasActiveFilters ? ( ) : ( user && ( ) )}
)}
{eventsLoading ? (
{Array.from({ length: 6 }).map((_, i) => ( ))}
) : filteredEvents.past.length > 0 ? (
{filteredEvents.past.map((event) => ( ))}
) : (

{t('events.noPast')}

)}
)} {/* Tours View */} {mainView === 'tours' && ( <> {/* Tour Filters */}
{/* Search, Category, Skill Level, Sort, Trust */}
setTourSearchQuery(e.target.value)} className="pl-9" />
{/* Tour Category Quick Filters */}
{Object.entries(TOUR_CATEGORIES).map(([key, info]) => ( setTourCategory(tourCategory === key ? 'all' : key as TourCategory)} > {info.emoji} {info.label} ))} {hasActiveTourFilters && ( Clear All )}
{/* Tours Grid */} {toursLoading ? (
{Array.from({ length: 6 }).map((_, i) => ( ))}
) : filteredTours.length > 0 ? (
{filteredTours.map((tour) => ( ))}
) : (

{hasActiveTourFilters ? 'No tours match your filters' : 'No tours available yet'}

{hasActiveTourFilters ? ( ) : ( user && ( ) )}
)} )}
); }