import { useState } from 'react'; import { Link } from 'react-router-dom'; import { Card, CardContent, CardHeader } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Skeleton } from '@/components/ui/skeleton'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Route, Clock, MapPin, Users, Zap, Globe, Calendar, Trophy, Skull, MoreVertical, Flag } from 'lucide-react'; import { useAuthor } from '@/hooks/useAuthor'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { genUserName } from '@/lib/genUserName'; import { ZapButton } from '@/components/ZapButton'; import { TrustBadge } from '@/components/TrustBadge'; import { ReportDialog } from '@/components/ReportDialog'; import type { SkateTour, TourCategory } from '@/lib/types'; import { TOUR_CATEGORIES, TOUR_SKILL_LEVELS } from '@/lib/types'; import { cn } from '@/lib/utils'; interface TourCardProps { tour: SkateTour; compact?: boolean; } function getCategoryIcon(category: TourCategory) { switch (category) { case 'local': return ; case 'national': return ; case 'international': return ; case 'destruction': return ; default: return ; } } function formatDuration(duration?: number, unit?: string): string { if (!duration) return ''; switch (unit) { case 'hours': return `${duration} hr${duration > 1 ? 's' : ''}`; case 'days': return `${duration} day${duration > 1 ? 's' : ''}`; case 'weeks': return duration >= 4 ? `${Math.floor(duration / 4)} month${Math.floor(duration / 4) > 1 ? 's' : ''}` : `${duration} week${duration > 1 ? 's' : ''}`; default: return `${duration}`; } } function formatDateRange(startDate?: number, endDate?: number): string { if (!startDate) return ''; const start = new Date(startDate * 1000); const startStr = start.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); if (!endDate) return startStr; const end = new Date(endDate * 1000); const endStr = end.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); return `${startStr} - ${endStr}`; } function getTourStatus(tour: SkateTour): { label: string; color: string } | null { if (tour.category !== 'destruction') return null; const now = Date.now() / 1000; if (tour.startDate && now < tour.startDate) { return { label: 'Registration Open', color: '#ccff00' }; } else if (tour.startDate && tour.endDate && now >= tour.startDate && now <= tour.endDate) { return { label: 'Active', color: '#ff0040' }; } else if (tour.endDate && now > tour.endDate) { return { label: 'Completed', color: '#888' }; } return null; } export function TourCard({ tour, compact = false }: TourCardProps) { const author = useAuthor(tour.pubkey); const { user } = useCurrentUser(); const metadata = author.data?.metadata; const displayName = metadata?.name ?? genUserName(tour.pubkey); const [showReportDialog, setShowReportDialog] = useState(false); const categoryInfo = TOUR_CATEGORIES[tour.category]; const skillInfo = tour.skillLevel ? TOUR_SKILL_LEVELS[tour.skillLevel as keyof typeof TOUR_SKILL_LEVELS] : null; const tourStatus = getTourStatus(tour); const isOwner = user?.pubkey === tour.pubkey; // Get location text based on category const locationText = tour.category === 'destruction' || tour.category === 'international' ? tour.countries?.join(', ') || tour.cities?.join(', ') || 'Worldwide' : tour.location || tour.cities?.join(', '); if (compact) { return ( { e.currentTarget.style.borderColor = `${categoryInfo.color}99`; e.currentTarget.style.boxShadow = `0 10px 40px ${categoryInfo.color}33`; }} onMouseLeave={(e) => { e.currentTarget.style.borderColor = `${categoryInfo.color}33`; e.currentTarget.style.boxShadow = `0 0 0 0 ${categoryInfo.color}00`; }} >
{/* Category Badge */}
{categoryInfo.emoji}
{/* Content */}

{tour.title}

{tourStatus && ( {tourStatus.label} )}
{categoryInfo.label} {tour.duration && ( {formatDuration(tour.duration, tour.durationUnit)} )} {locationText && ( {locationText} )}
); } return ( { e.currentTarget.style.borderColor = `${categoryInfo.color}80`; e.currentTarget.style.boxShadow = `0 20px 50px ${categoryInfo.color}20`; }} onMouseLeave={(e) => { e.currentTarget.style.borderColor = `${categoryInfo.color}33`; e.currentTarget.style.boxShadow = 'none'; }} > {/* Image */}
{tour.image ? ( {tour.title} ) : (
{tour.category === 'destruction' ? ( ) : ( )}
)} {/* Category overlay */}
{categoryInfo.emoji} {categoryInfo.label}
{/* Status/Price overlay */}
{tourStatus && ( {tourStatus.label} )} {tour.prizePool && ( {tour.prizePool.toLocaleString()} sats )}
{/* Guide/Organizer info */}
{displayName.slice(0, 2).toUpperCase()}

{displayName}

{metadata?.nip05 && (
{metadata.nip05}
)}

{tour.title}

{/* Tour dates for destruction tours */} {tour.category === 'destruction' && tour.startDate && (
{formatDateRange(tour.startDate, tour.endDate)}
)} {/* Tour meta info */}
{tour.duration && ( {formatDuration(tour.duration, tour.durationUnit)} )} {tour.maxGroupSize && ( Max {tour.maxGroupSize} )} {tour.maxTeams && ( {tour.maxTeams} teams max )} {skillInfo && ( {skillInfo.emoji} {skillInfo.label} )}
{locationText && (

{locationText}

)} {tour.description && (

{tour.description}

)} {/* Price or Entry Fee */}
{tour.category === 'destruction' && tour.entryFee ? ( Entry: {tour.entryFee.toLocaleString()} sats ) : tour.price ? ( {tour.price.toLocaleString()} sats ) : ( Contact for price )}
e.preventDefault()}> {/* More options menu */} {!isOwner && ( { e.preventDefault(); e.stopPropagation(); setShowReportDialog(true); }} className="text-red-500 focus:text-red-500" > Report )}
{/* Report Dialog */} ); } export function TourCardSkeleton({ compact = false }: { compact?: boolean }) { if (compact) { return (
); } return (
); }