import React, { useState, useEffect, useRef, useCallback } from 'react'; import { Search, Globe, Users, Eye, ArrowLeftRight, Loader2, Clock, History, UserPlus, UserMinus, Check, X, Send, Settings, Save, ChevronLeft, RefreshCw, Plus, Minus } from 'lucide-react'; import { useAuth } from '../contexts/AuthContext'; import { useToast } from '../contexts/ToastContext'; import { supabase } from '../lib/supabase'; import { getFriends, getPendingRequests, getSentRequests, searchUsers, sendFriendRequest, acceptFriendRequest, declineFriendRequest, removeFriend, Friend, } from '../services/friendsService'; import { getTrades, getTradeHistory, acceptTrade, declineTrade, cancelTrade, Trade, TradeItem, } from '../services/tradesService'; import { getUserCollectionPaginated, getCardsByIds, getCollectionTotalValue } from '../services/api'; import { Card } from '../types'; import TradeCreator from './TradeCreator'; import TradeDetail from './TradeDetail'; import ConfirmModal from './ConfirmModal'; interface UserProfile { id: string; username: string | null; collection_visibility: 'public' | 'friends' | 'private' | null; } interface CollectionItem { card: Card; quantity: number; } type Tab = 'browse' | 'friends' | 'trades' | 'profile'; type FriendsSubTab = 'list' | 'requests' | 'search'; type TradesSubTab = 'pending' | 'history'; const VISIBILITY_OPTIONS = [ { value: 'public', label: 'Public', description: 'Anyone can view' }, { value: 'friends', label: 'Friends', description: 'Friends only' }, { value: 'private', label: 'Private', description: 'Only you' }, ] as const; const PAGE_SIZE = 50; export default function Community() { const { user } = useAuth(); const toast = useToast(); const [activeTab, setActiveTab] = useState('browse'); const [loading, setLoading] = useState(true); // Browse state const [browseSearch, setBrowseSearch] = useState(''); const [publicUsers, setPublicUsers] = useState([]); const [selectedUser, setSelectedUser] = useState(null); const [selectedUserCollection, setSelectedUserCollection] = useState([]); const [loadingCollection, setLoadingCollection] = useState(false); const [isLoadingMoreUserCards, setIsLoadingMoreUserCards] = useState(false); const [hasMoreUserCards, setHasMoreUserCards] = useState(false); const [userCollectionOffset, setUserCollectionOffset] = useState(0); const [userCollectionTotalCount, setUserCollectionTotalCount] = useState(0); const [userCollectionTotalValue, setUserCollectionTotalValue] = useState(0); const [isLoadingUserTotalValue, setIsLoadingUserTotalValue] = useState(true); const [showTradeCreator, setShowTradeCreator] = useState(false); const [userCollectionSearch, setUserCollectionSearch] = useState(''); const [hoveredUserCard, setHoveredUserCard] = useState(null); const [selectedUserCard, setSelectedUserCard] = useState(null); const [userCardFaceIndex, setUserCardFaceIndex] = useState>(new Map()); const userCollectionObserverTarget = useRef(null); // Friends state const [friendsSubTab, setFriendsSubTab] = useState('list'); const [friends, setFriends] = useState([]); const [pendingRequests, setPendingRequests] = useState([]); const [sentRequests, setSentRequests] = useState([]); const [friendSearch, setFriendSearch] = useState(''); const [friendSearchResults, setFriendSearchResults] = useState<{ id: string; username: string | null }[]>([]); const [searchingFriends, setSearchingFriends] = useState(false); const [friendListFilter, setFriendListFilter] = useState(''); const [requestsFilter, setRequestsFilter] = useState(''); // Trades state const [tradesSubTab, setTradesSubTab] = useState('pending'); const [pendingTrades, setPendingTrades] = useState([]); const [tradeHistory, setTradeHistory] = useState([]); const [tradeCardDetails, setTradeCardDetails] = useState>(new Map()); const [processingTradeId, setProcessingTradeId] = useState(null); const [selectedTrade, setSelectedTrade] = useState(null); // Profile state const [username, setUsername] = useState(''); const [collectionVisibility, setCollectionVisibility] = useState<'public' | 'friends' | 'private'>('private'); const [savingProfile, setSavingProfile] = useState(false); // Confirm modal state const [confirmModal, setConfirmModal] = useState<{ isOpen: boolean; title: string; message: string; onConfirm: () => void; variant: 'danger' | 'warning' | 'info' | 'success'; }>({ isOpen: false, title: '', message: '', onConfirm: () => {}, variant: 'danger' }); useEffect(() => { if (user) { loadAllData(); } }, [user]); // ============ REALTIME SUBSCRIPTIONS ============ // Subscribe to trade changes useEffect(() => { if (!user) return; const tradesChannel = supabase .channel('trades-changes') .on( 'postgres_changes', { event: '*', schema: 'public', table: 'trades', }, (payload: any) => { // Filter for trades involving this user const newData = payload.new || payload.old; if (newData && (newData.user1_id === user.id || newData.user2_id === user.id)) { console.log('Trade change:', payload); loadTradesData(); } } ) .subscribe(); return () => { supabase.removeChannel(tradesChannel); }; }, [user]); // Subscribe to friendship changes useEffect(() => { if (!user) return; const friendshipsChannel = supabase .channel('friendships-changes') .on( 'postgres_changes', { event: '*', schema: 'public', table: 'friendships', }, (payload: any) => { // Filter for friendships involving this user const newData = payload.new || payload.old; if (newData && (newData.requester_id === user.id || newData.addressee_id === user.id)) { console.log('Friendship change:', payload); loadFriendsData(); } } ) .subscribe(); return () => { supabase.removeChannel(friendshipsChannel); }; }, [user]); // Subscribe to profile changes (for visibility updates) useEffect(() => { if (!user) return; const profilesChannel = supabase .channel('profiles-changes') .on( 'postgres_changes', { event: 'UPDATE', schema: 'public', table: 'profiles', }, (payload: any) => { console.log('Profile change:', payload); // Reload public users if a profile's visibility changed if (payload.new && payload.old && payload.new.collection_visibility !== payload.old.collection_visibility) { loadPublicUsers(); } // Reload own profile if it's the current user if (payload.new && payload.new.id === user.id) { loadProfile(); } } ) .subscribe(); return () => { supabase.removeChannel(profilesChannel); }; }, [user]); // Subscribe to collection changes when viewing someone's collection useEffect(() => { if (!user || !selectedUser) return; const collectionsChannel = supabase .channel(`collections-${selectedUser.id}`) .on( 'postgres_changes', { event: '*', schema: 'public', table: 'collections', }, (payload: any) => { // Filter for the selected user's collections const data = payload.new || payload.old; if (data && data.user_id === selectedUser.id) { console.log('Collection change for viewed user:', payload); loadUserCollection(selectedUser.id); } } ) .subscribe(); return () => { supabase.removeChannel(collectionsChannel); }; }, [user, selectedUser]); // Helper function to check if a card has an actual back face const isDoubleFaced = (card: Card) => { const backFaceLayouts = ['transform', 'modal_dfc', 'double_faced_token', 'reversible_card']; return card.card_faces && card.card_faces.length > 1 && backFaceLayouts.includes(card.layout); }; // Helper function to get the current face index for a card const getCurrentFaceIndex = (cardId: string) => { return userCardFaceIndex.get(cardId) || 0; }; // Helper function to get the image URI for a card const getCardImageUri = (card: Card, faceIndex: number = 0) => { if (isDoubleFaced(card) && card.card_faces) { return card.card_faces[faceIndex]?.image_uris?.normal || card.card_faces[faceIndex]?.image_uris?.small; } return card.image_uris?.normal || card.image_uris?.small; }; // Helper function to get the large image URI for hover preview const getCardLargeImageUri = (card: Card, faceIndex: number = 0) => { if (isDoubleFaced(card) && card.card_faces) { return card.card_faces[faceIndex]?.image_uris?.large || card.card_faces[faceIndex]?.image_uris?.normal; } return card.image_uris?.large || card.image_uris?.normal; }; // Toggle card face const toggleCardFace = (cardId: string, totalFaces: number) => { setUserCardFaceIndex(prev => { const newMap = new Map(prev); const currentIndex = prev.get(cardId) || 0; const nextIndex = (currentIndex + 1) % totalFaces; newMap.set(cardId, nextIndex); return newMap; }); }; const loadAllData = async () => { if (!user) return; setLoading(true); try { await Promise.all([ loadPublicUsers(), loadFriendsData(), loadTradesData(), loadProfile(), ]); } catch (error) { console.error('Error loading data:', error); } finally { setLoading(false); } }; // ============ BROWSE FUNCTIONS ============ const loadPublicUsers = async () => { const { data, error } = await supabase .from('profiles') .select('id, username, collection_visibility') .eq('collection_visibility', 'public') .neq('id', user?.id) .order('username'); if (!error && data) { setPublicUsers(data); } }; const loadUserCollection = async (userId: string) => { setLoadingCollection(true); setIsLoadingUserTotalValue(true); setSelectedUserCollection([]); setUserCollectionOffset(0); try { // Load paginated collection for display const result = await getUserCollectionPaginated(userId, PAGE_SIZE, 0); setUserCollectionTotalCount(result.totalCount); setHasMoreUserCards(result.hasMore); if (result.items.size === 0) { setSelectedUserCollection([]); setUserCollectionTotalValue(0); setIsLoadingUserTotalValue(false); return; } const cardIds = Array.from(result.items.keys()); const cards = await getCardsByIds(cardIds); setSelectedUserCollection(cards.map((card) => ({ card, quantity: result.items.get(card.id) || 0, }))); setUserCollectionOffset(PAGE_SIZE); // Calculate total value (lightweight query from database) const totalValue = await getCollectionTotalValue(userId); setUserCollectionTotalValue(totalValue); } catch (error) { console.error('Error loading collection:', error); setSelectedUserCollection([]); setUserCollectionTotalValue(0); } finally { setLoadingCollection(false); setIsLoadingUserTotalValue(false); } }; // Load more cards for infinite scroll in user collection view const loadMoreUserCards = useCallback(async () => { if (!selectedUser || isLoadingMoreUserCards || !hasMoreUserCards) return; try { setIsLoadingMoreUserCards(true); const result = await getUserCollectionPaginated( selectedUser.id, PAGE_SIZE, userCollectionOffset ); setHasMoreUserCards(result.hasMore); if (result.items.size === 0) { return; } const cardIds = Array.from(result.items.keys()); const cards = await getCardsByIds(cardIds); const newCards = cards.map(card => ({ card, quantity: result.items.get(card.id) || 0, })); setSelectedUserCollection(prev => [...prev, ...newCards]); setUserCollectionOffset(prev => prev + PAGE_SIZE); } catch (error) { console.error('Error loading more cards:', error); } finally { setIsLoadingMoreUserCards(false); } }, [selectedUser, userCollectionOffset, hasMoreUserCards, isLoadingMoreUserCards]); // Intersection Observer for infinite scroll in user collection view useEffect(() => { if (!selectedUser) return; const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && hasMoreUserCards && !isLoadingMoreUserCards) { loadMoreUserCards(); } }, { threshold: 0.1 } ); const currentTarget = userCollectionObserverTarget.current; if (currentTarget) { observer.observe(currentTarget); } return () => { if (currentTarget) { observer.unobserve(currentTarget); } }; }, [selectedUser, hasMoreUserCards, isLoadingMoreUserCards, loadMoreUserCards]); // ============ FRIENDS FUNCTIONS ============ const loadFriendsData = async () => { if (!user) return; const [friendsData, pendingData, sentData] = await Promise.all([ getFriends(user.id), getPendingRequests(user.id), getSentRequests(user.id), ]); setFriends(friendsData); setPendingRequests(pendingData); setSentRequests(sentData); }; const handleSearchFriends = async () => { if (!user || friendSearch.trim().length < 2) return; setSearchingFriends(true); try { const results = await searchUsers(friendSearch, user.id); setFriendSearchResults(results || []); } catch (error) { console.error('Error searching users:', error); } finally { setSearchingFriends(false); } }; const handleSendRequest = async (addresseeId: string) => { if (!user) return; try { await sendFriendRequest(user.id, addresseeId); setFriendSearchResults((prev) => prev.filter((u) => u.id !== addresseeId)); await loadFriendsData(); toast.success('Friend request sent!'); } catch (error) { toast.error('Failed to send friend request'); } }; const handleAcceptRequest = async (friendshipId: string) => { try { await acceptFriendRequest(friendshipId); await loadFriendsData(); toast.success('Friend request accepted!'); } catch (error) { toast.error('Failed to accept request'); } }; const handleDeclineRequest = async (friendshipId: string) => { try { await declineFriendRequest(friendshipId); await loadFriendsData(); toast.info('Friend request declined'); } catch (error) { toast.error('Failed to decline request'); } }; const handleRemoveFriend = (friendshipId: string, friendName: string) => { setConfirmModal({ isOpen: true, title: 'Remove Friend', message: `Remove ${friendName} from your friends?`, variant: 'danger', onConfirm: async () => { try { await removeFriend(friendshipId); await loadFriendsData(); toast.success('Friend removed'); } catch (error) { toast.error('Failed to remove friend'); } }, }); }; const isAlreadyFriendOrPending = (userId: string) => { return friends.some((f) => f.id === userId) || pendingRequests.some((f) => f.id === userId) || sentRequests.some((f) => f.id === userId); }; // ============ TRADES FUNCTIONS ============ const loadTradesData = async () => { if (!user) return; const [pending, history] = await Promise.all([ getTrades(user.id).then((trades) => trades.filter((t) => t.status === 'pending')), getTradeHistory(user.id), ]); const allCardIds = new Set(); [...pending, ...history].forEach((trade) => { trade.items?.forEach((item) => allCardIds.add(item.card_id)); }); if (allCardIds.size > 0) { const cards = await getCardsByIds(Array.from(allCardIds)); const cardMap = new Map(); cards.forEach((card) => cardMap.set(card.id, card)); setTradeCardDetails(cardMap); } setPendingTrades(pending); setTradeHistory(history); }; const handleAcceptTrade = async (tradeId: string) => { setProcessingTradeId(tradeId); try { const success = await acceptTrade(tradeId); if (success) { await loadTradesData(); toast.success('Trade accepted! Cards exchanged.'); } else { toast.error('Failed. Check your collection.'); } } catch (error) { toast.error('Error accepting trade'); } finally { setProcessingTradeId(null); } }; const handleDeclineTrade = async (tradeId: string) => { setProcessingTradeId(tradeId); try { await declineTrade(tradeId); await loadTradesData(); toast.info('Trade declined'); } catch (error) { toast.error('Error declining trade'); } finally { setProcessingTradeId(null); } }; const handleCancelTrade = (tradeId: string) => { setConfirmModal({ isOpen: true, title: 'Cancel Trade', message: 'Cancel this trade offer?', variant: 'warning', onConfirm: async () => { setProcessingTradeId(tradeId); try { await cancelTrade(tradeId); await loadTradesData(); toast.info('Trade cancelled'); } catch (error) { toast.error('Error cancelling trade'); } finally { setProcessingTradeId(null); } }, }); }; // ============ PROFILE FUNCTIONS ============ const loadProfile = async () => { if (!user) return; const { data } = await supabase .from('profiles') .select('username, collection_visibility') .eq('id', user.id) .single(); if (data) { setUsername(data.username || ''); setCollectionVisibility(data.collection_visibility || 'private'); } }; const handleSaveProfile = async () => { if (!user) return; setSavingProfile(true); try { const { error } = await supabase .from('profiles') .upsert({ id: user.id, username, collection_visibility: collectionVisibility, updated_at: new Date(), }); if (error) throw error; toast.success('Profile updated!'); } catch (error) { toast.error('Failed to update profile'); } finally { setSavingProfile(false); } }; // ============ RENDER HELPERS ============ const calculateTradeItemsPrice = (items: TradeItem[] | undefined, ownerId: string): number => { const ownerItems = items?.filter((i) => i.owner_id === ownerId) || []; return ownerItems.reduce((total, item) => { const card = tradeCardDetails.get(item.card_id); const price = card?.prices?.usd ? parseFloat(card.prices.usd) : 0; return total + (price * item.quantity); }, 0); }; const renderTradeItems = (items: TradeItem[] | undefined, ownerId: string, label: string) => { const ownerItems = items?.filter((i) => i.owner_id === ownerId) || []; const totalPrice = calculateTradeItemsPrice(items, ownerId); if (ownerItems.length === 0) { return (

{label}:

Gift

); } return (

{label}:

${totalPrice.toFixed(2)}

{ownerItems.map((item) => { const card = tradeCardDetails.get(item.card_id); return (
{card?.name || 'Card'} {item.quantity > 1 && x{item.quantity}}
); })}
); }; // Loading state if (loading) { return (
); } // ============ USER COLLECTION VIEW ============ if (selectedUser) { const filteredUserCollection = selectedUserCollection.filter(({ card }) => card.name.toLowerCase().includes(userCollectionSearch.toLowerCase()) ); return (
{/* Header with Back and Trade buttons */}

{selectedUser.username}'s Collection

{/* Search input */}
setUserCollectionSearch(e.target.value)} placeholder="Search cards by name, type, or text..." className="w-full pl-10 pr-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
{/* Collection */}

{userCollectionSearch ? `Found ${filteredUserCollection.length} card(s)` : `Cards (${selectedUserCollection.length} unique, ${selectedUserCollection.reduce((acc, c) => acc + c.quantity, 0)} total)` }

{/* Collection Value Summary */}
{userCollectionSearch ? 'Filtered Value' : 'Total Collection Value'}
{isLoadingUserTotalValue ? ( ) : userCollectionSearch ? ( // For search results, calculate from filtered collection `$${filteredUserCollection.reduce((total, { card, quantity }) => { const price = card.prices?.usd ? parseFloat(card.prices.usd) : 0; return total + (price * quantity); }, 0).toFixed(2)}` ) : ( // For full collection, use pre-calculated total `$${userCollectionTotalValue.toFixed(2)}` )}
{loadingCollection ? (
) : selectedUserCollection.length === 0 ? (

Empty collection

) : filteredUserCollection.length === 0 ? (

No cards found

Try a different search term

) : (
{filteredUserCollection.map(({ card, quantity }) => { const currentFaceIndex = getCurrentFaceIndex(card.id); const isMultiFaced = isDoubleFaced(card); const displayName = isMultiFaced && card.card_faces ? card.card_faces[currentFaceIndex]?.name || card.name : card.name; return (
setHoveredUserCard(card)} onMouseLeave={() => setHoveredUserCard(null)} onClick={() => setSelectedUserCard({ card, quantity })} > {/* Card thumbnail */}
{displayName} {/* Quantity badge */}
x{quantity}
{/* Price badge */} {card.prices?.usd && (
${card.prices.usd}
)} {/* Flip button for double-faced cards */} {isMultiFaced && ( )}
{/* Card name below thumbnail */}
{displayName}
); })}
)} {/* Infinite scroll loading indicator */} {!userCollectionSearch && isLoadingMoreUserCards && (
)} {/* Observer target for infinite scroll */} {!userCollectionSearch && hasMoreUserCards && !isLoadingMoreUserCards && (
)} {/* End of collection indicator */} {!userCollectionSearch && !hasMoreUserCards && selectedUserCollection.length > 0 && (
End of collection • {userCollectionTotalCount} total cards
)}
{/* Hover Card Preview - desktop only, only show if no card is selected */} {hoveredUserCard && !selectedUserCard && (() => { const currentFaceIndex = getCurrentFaceIndex(hoveredUserCard.id); const isMultiFaced = isDoubleFaced(hoveredUserCard); const currentFace = isMultiFaced && hoveredUserCard.card_faces ? hoveredUserCard.card_faces[currentFaceIndex] : null; const displayName = currentFace?.name || hoveredUserCard.name; const displayTypeLine = currentFace?.type_line || hoveredUserCard.type_line; const displayOracleText = currentFace?.oracle_text || hoveredUserCard.oracle_text; return (
{displayName} {isMultiFaced && (
Face {currentFaceIndex + 1}/{hoveredUserCard.card_faces!.length}
)}

{displayName}

{displayTypeLine}

{displayOracleText && (

{displayOracleText}

)} {hoveredUserCard.prices?.usd && (
${hoveredUserCard.prices.usd}
)}
); })()} {/* Card Detail Panel - slides in from right */} {selectedUserCard && (() => { const currentFaceIndex = getCurrentFaceIndex(selectedUserCard.card.id); const isMultiFaced = isDoubleFaced(selectedUserCard.card); const currentFace = isMultiFaced && selectedUserCard.card.card_faces ? selectedUserCard.card.card_faces[currentFaceIndex] : null; const displayName = currentFace?.name || selectedUserCard.card.name; const displayTypeLine = currentFace?.type_line || selectedUserCard.card.type_line; const displayOracleText = currentFace?.oracle_text || selectedUserCard.card.oracle_text; return ( <> {/* Backdrop */}
setSelectedUserCard(null)} /> {/* Sliding Panel */}
{/* Close button */}
{/* Card Image */}
{displayName} {isMultiFaced && ( <>
Face {currentFaceIndex + 1}/{selectedUserCard.card.card_faces!.length}
)}
{/* Card Info */}

{displayName}

{displayTypeLine}

{displayOracleText && (

{displayOracleText}

)} {selectedUserCard.card.prices?.usd && (
${selectedUserCard.card.prices.usd} each
Total value: ${(parseFloat(selectedUserCard.card.prices.usd) * selectedUserCard.quantity).toFixed(2)}
)} {/* Quantity Display */}

Quantity in Collection

{selectedUserCard.quantity}
copies
); })()} {showTradeCreator && ( setShowTradeCreator(false)} onTradeCreated={() => { setShowTradeCreator(false); loadTradesData(); toast.success('Trade proposal sent!'); }} /> )}
); } const filteredPublicUsers = publicUsers.filter( (u) => !browseSearch || u.username?.toLowerCase().includes(browseSearch.toLowerCase()) ); // ============ MAIN VIEW ============ return (
{/* Header */}

Community

{/* Tabs */}
{[ { id: 'browse' as Tab, label: 'Browse', icon: Globe }, { id: 'friends' as Tab, label: `Friends`, count: friends.length, icon: Users }, { id: 'trades' as Tab, label: `Trades`, count: pendingTrades.length, icon: ArrowLeftRight }, { id: 'profile' as Tab, label: 'Profile', icon: Settings }, ].map((tab) => ( ))}
{/* ============ BROWSE TAB ============ */} {activeTab === 'browse' && (
{/* Search */}
setBrowseSearch(e.target.value)} placeholder="Search users..." className="w-full pl-10 pr-4 py-2.5 bg-gray-800 border border-gray-700 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
{/* Users List */} {filteredPublicUsers.length === 0 ? (

No public collections

) : (
{filteredPublicUsers.map((userProfile) => ( ))}
)} {/* Friends shortcut */} {friends.length > 0 && (

Your friends

{friends.slice(0, 3).map((friend) => ( ))} {friends.length > 3 && ( )}
)}
)} {/* ============ FRIENDS TAB ============ */} {activeTab === 'friends' && (
{/* Sub tabs */}
{[ { id: 'list' as FriendsSubTab, label: 'List' }, { id: 'requests' as FriendsSubTab, label: 'Requests', count: pendingRequests.length }, { id: 'search' as FriendsSubTab, label: 'Add' }, ].map((tab) => ( ))}
{/* Friends List */} {friendsSubTab === 'list' && (
{/* Search input */}
setFriendListFilter(e.target.value)} placeholder="Search friends..." className="w-full pl-9 pr-8 py-2 bg-gray-700 border border-gray-600 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent" /> {friendListFilter && ( )}
{friends.length === 0 ? (

No friends yet

) : friends.filter((f) => !friendListFilter || f.username?.toLowerCase().includes(friendListFilter.toLowerCase()) ).length === 0 ? (

No friends match "{friendListFilter}"

) : (
{friends .filter((f) => !friendListFilter || f.username?.toLowerCase().includes(friendListFilter.toLowerCase())) .map((friend) => (
{friend.username || 'Unknown'}
))}
)}
)} {/* Requests */} {friendsSubTab === 'requests' && (
{/* Search input */}
setRequestsFilter(e.target.value)} placeholder="Search requests..." className="w-full pl-9 pr-8 py-2 bg-gray-700 border border-gray-600 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent" /> {requestsFilter && ( )}
{(() => { const filteredPending = pendingRequests.filter((r) => !requestsFilter || r.username?.toLowerCase().includes(requestsFilter.toLowerCase()) ); const filteredSent = sentRequests.filter((r) => !requestsFilter || r.username?.toLowerCase().includes(requestsFilter.toLowerCase()) ); return ( <> {filteredPending.length > 0 && (

Received

{filteredPending.map((req) => (
{req.username || 'Unknown'}
))}
)} {filteredSent.length > 0 && (

Sent

{filteredSent.map((req) => (
{req.username || 'Unknown'}
Pending
))}
)} {pendingRequests.length === 0 && sentRequests.length === 0 && (

No requests

)} {(pendingRequests.length > 0 || sentRequests.length > 0) && filteredPending.length === 0 && filteredSent.length === 0 && (

No requests match "{requestsFilter}"

)} ); })()}
)} {/* Search/Add */} {friendsSubTab === 'search' && (
setFriendSearch(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSearchFriends()} placeholder="Username..." className="flex-1 px-3 py-2.5 bg-gray-800 border border-gray-700 rounded-lg text-sm" />
{friendSearchResults.length > 0 && (
{friendSearchResults.map((result) => (
{result.username || 'Unknown'} {isAlreadyFriendOrPending(result.id) ? ( Connected ) : ( )}
))}
)}
)}
)} {/* ============ TRADES TAB ============ */} {activeTab === 'trades' && (
{/* Sub tabs */}
{/* Trades List */} {(tradesSubTab === 'pending' ? pendingTrades : tradeHistory).length === 0 ? (

{tradesSubTab === 'pending' ? 'No pending trades' : 'No history'}

) : (
{(tradesSubTab === 'pending' ? pendingTrades : tradeHistory).map((trade) => { const isUser1 = trade.user1_id === user?.id; const myUserId = user?.id || ''; const otherUserId = isUser1 ? trade.user2_id : trade.user1_id; const otherUser = isUser1 ? trade.user2 : trade.user1; const statusColors: Record = { accepted: 'text-green-400', declined: 'text-red-400', cancelled: 'text-gray-400', pending: 'text-yellow-400', }; // Both users can view details for pending trades const canViewDetails = trade.status === 'pending'; return (
canViewDetails && setSelectedTrade(trade)} > {/* Header */}
With: {otherUser?.username}
{trade.status}
{/* Items */}
{renderTradeItems(trade.items, myUserId, 'You Give')} {renderTradeItems(trade.items, otherUserId, 'You Get')}
{canViewDetails && (

Tap to view details

)} {/* Actions - Allow any user to cancel pending trade */} {tradesSubTab === 'pending' && (
e.stopPropagation()}>
)}
); })}
)}
)} {/* ============ PROFILE TAB ============ */} {activeTab === 'profile' && (
{/* Username */}
setUsername(e.target.value)} className="w-full px-3 py-2.5 bg-gray-800 border border-gray-700 rounded-lg text-sm" placeholder="Your username" />
{/* Visibility */}
{VISIBILITY_OPTIONS.map((option) => ( ))}
{/* Save */}
)} {/* Trade Detail Modal */} {selectedTrade && ( setSelectedTrade(null)} onAccept={handleAcceptTrade} onDecline={handleDeclineTrade} onTradeUpdated={() => { setSelectedTrade(null); loadTradesData(); }} /> )} {/* Confirm Modal */} setConfirmModal({ ...confirmModal, isOpen: false })} onConfirm={confirmModal.onConfirm} title={confirmModal.title} message={confirmModal.message} variant={confirmModal.variant} />
); }