import { useState, useMemo } from 'react'; import { useSeoMeta } from '@unhead/react'; import { Link } from 'react-router-dom'; import { Layout } from '@/components/Layout'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { Skeleton } from '@/components/ui/skeleton'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { DropdownMenu, DropdownMenuContent, DropdownMenuCheckboxItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { useMarketplaceItems } from '@/hooks/useMarketplace'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useBatchTrustScores } from '@/hooks/useBatchTrustScores'; import { useBatchSellerSales } from '@/hooks/useSellerSales'; import { useFollowList } from '@/hooks/useFollowList'; import { GearSwapItemCard } from '@/components/GearSwapItemCard'; import { GEAR_CATEGORIES, type MarketplaceItem } from '@/lib/types'; import { ShoppingCart, Plus, Search, Filter, ArrowUpDown, Users, ShieldCheck, UserCheck, AlertCircle, Loader2, } from 'lucide-react'; type SortOption = 'newest' | 'price_low' | 'price_high' | 'trust'; type TrustFilter = 'all' | 'verified' | 'trusted' | 'following'; export default function GearSwapPage() { useSeoMeta({ title: 'Gear Swap - SkateSpots', description: 'Buy & sell used skate gear with other skaters. Decks, trucks, wheels, shoes, and more.', }); const { user } = useCurrentUser(); const { data: items, isLoading, error } = useMarketplaceItems(200); const { data: followList } = useFollowList(user?.pubkey); // Filter state const [searchQuery, setSearchQuery] = useState(''); const [selectedCategory, setSelectedCategory] = useState('all'); const [sortBy, setSortBy] = useState('newest'); const [trustFilter, setTrustFilter] = useState('all'); const [showOnlyWTB, setShowOnlyWTB] = useState(false); const [showOnlySelling, setShowOnlySelling] = useState(false); // Get unique seller pubkeys for batch queries const sellerPubkeys = useMemo(() => { if (!items) return []; const pubkeys = new Set(items.map(item => item.pubkey)); return Array.from(pubkeys); }, [items]); // Batch fetch trust scores and sales for all sellers const { data: trustScores } = useBatchTrustScores(sellerPubkeys); const { data: sellerSales } = useBatchSellerSales(sellerPubkeys); // Build follow set for quick lookup const followingSet = useMemo(() => { if (!followList) return new Set(); return new Set( followList.tags .filter(([tag]) => tag === 'p') .map(([, pubkey]) => pubkey) ); }, [followList]); // Filter and sort items const filteredItems = useMemo(() => { if (!items) return []; let filtered = [...items]; // Filter out expired items const now = Math.floor(Date.now() / 1000); filtered = filtered.filter(item => { if (item.expiration && item.expiration < now) return false; if (item.status && item.status !== 'active') return false; return true; }); // Search filter if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); filtered = filtered.filter(item => item.title.toLowerCase().includes(query) || item.description?.toLowerCase().includes(query) || item.summary?.toLowerCase().includes(query) || item.location?.toLowerCase().includes(query) ); } // Category filter if (selectedCategory !== 'all') { filtered = filtered.filter(item => item.category?.includes(selectedCategory) ); } // Listing type filters if (showOnlyWTB) { filtered = filtered.filter(item => item.listingType === 'wtb'); } if (showOnlySelling) { filtered = filtered.filter(item => item.listingType !== 'wtb'); } // Trust filter if (trustFilter !== 'all' && trustScores) { filtered = filtered.filter(item => { const score = trustScores.get(item.pubkey); switch (trustFilter) { case 'verified': return score !== undefined && score >= 90; case 'trusted': return score !== undefined && score >= 60; case 'following': return followingSet.has(item.pubkey); default: return true; } }); } // Sort switch (sortBy) { case 'price_low': filtered.sort((a, b) => { const aPrice = a.price?.amount ? parseFloat(a.price.amount) : Infinity; const bPrice = b.price?.amount ? parseFloat(b.price.amount) : Infinity; return aPrice - bPrice; }); break; case 'price_high': filtered.sort((a, b) => { const aPrice = a.price?.amount ? parseFloat(a.price.amount) : 0; const bPrice = b.price?.amount ? parseFloat(b.price.amount) : 0; return bPrice - aPrice; }); break; case 'trust': filtered.sort((a, b) => { const aScore = trustScores?.get(a.pubkey) ?? 0; const bScore = trustScores?.get(b.pubkey) ?? 0; return bScore - aScore; }); break; case 'newest': default: filtered.sort((a, b) => b.createdAt - a.createdAt); break; } return filtered; }, [items, searchQuery, selectedCategory, sortBy, trustFilter, showOnlyWTB, showOnlySelling, trustScores, followingSet]); const categoryOptions = [ { value: 'all', label: 'All Categories' }, ...Object.entries(GEAR_CATEGORIES).map(([key, label]) => ({ value: key, label, })), ]; return (
{/* Header */}

GEAR SWAP

Buy & sell used skate gear with other skaters

{/* Search & Filters */} {/* Search */}
setSearchQuery(e.target.value)} className="pl-10 bg-gray-900 border-gray-700 h-12 text-lg" />
{/* Category Pills */}
{categoryOptions.map(({ value, label }) => ( ))}
{/* Filter Row */}
{/* Condition / Listing Type */}
{/* Trust Filter */} Filter by Trust setTrustFilter('all')} > All Sellers setTrustFilter('verified')} > Verified Only setTrustFilter('trusted')} > Trusted+ setTrustFilter('following')} disabled={!user} > Following Only
{/* Sort */}
{/* Results Count */} {!isLoading && (

{filteredItems.length} {filteredItems.length === 1 ? 'listing' : 'listings'} found

)} {/* Loading State */} {isLoading && (
{Array.from({ length: 8 }).map((_, i) => (
))}
)} {/* Error State */} {error && (

Failed to load listings

Please try again later.

)} {/* Empty State */} {!isLoading && !error && filteredItems.length === 0 && (

No listings found

{searchQuery || selectedCategory !== 'all' || trustFilter !== 'all' ? 'No gear matches your current filters. Try clearing them or searching something else.' : 'Be the first to list your gear — decks, trucks, wheels, shoes, anything skate!'}

{(searchQuery || selectedCategory !== 'all' || trustFilter !== 'all') && ( )}
Have questions? Check the FAQ →
)} {/* Listings Grid */} {!isLoading && !error && filteredItems.length > 0 && (
{filteredItems.map((item) => ( ))}
)}
); }