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 { ProBadge } from '@/components/ProBadge'; 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.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 */} e.stopPropagation()} > {!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 ( ); }
{locationText}
{tour.description}