diff --git a/src/App.tsx b/src/App.tsx index 2f95bcb..18ed739 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -80,7 +80,9 @@ function AppContent() { return (
- {renderPage()} +
+ {renderPage()} +
); diff --git a/src/components/Community.tsx b/src/components/Community.tsx index 19436b8..67ea0b9 100644 --- a/src/components/Community.tsx +++ b/src/components/Community.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { Search, Globe, Users, Eye, ArrowLeftRight, Loader2, Clock, History, UserPlus, UserMinus, Check, X, Send, Settings, Save } from 'lucide-react'; +import { Search, Globe, Users, Eye, ArrowLeftRight, Loader2, Clock, History, UserPlus, UserMinus, Check, X, Send, Settings, Save, ChevronLeft } from 'lucide-react'; import { useAuth } from '../contexts/AuthContext'; import { useToast } from '../contexts/ToastContext'; import { supabase } from '../lib/supabase'; @@ -44,9 +44,9 @@ type FriendsSubTab = 'list' | 'requests' | 'search'; type TradesSubTab = 'pending' | 'history'; const VISIBILITY_OPTIONS = [ - { value: 'public', label: 'Public', description: 'Anyone can view your collection' }, - { value: 'friends', label: 'Friends Only', description: 'Only friends can view' }, - { value: 'private', label: 'Private', description: 'Only you can view' }, + { value: 'public', label: 'Public', description: 'Anyone can view' }, + { value: 'friends', label: 'Friends', description: 'Friends only' }, + { value: 'private', label: 'Private', description: 'Only you' }, ] as const; export default function Community() { @@ -214,7 +214,7 @@ export default function Community() { setConfirmModal({ isOpen: true, title: 'Remove Friend', - message: `Are you sure you want to remove ${friendName} from your friends?`, + message: `Remove ${friendName} from your friends?`, variant: 'danger', onConfirm: async () => { try { @@ -264,9 +264,9 @@ export default function Community() { const success = await acceptTrade(tradeId); if (success) { await loadTradesData(); - toast.success('Trade accepted! Cards have been exchanged.'); + toast.success('Trade accepted! Cards exchanged.'); } else { - toast.error('Failed to execute trade. Check your collection.'); + toast.error('Failed. Check your collection.'); } } catch (error) { toast.error('Error accepting trade'); @@ -292,7 +292,7 @@ export default function Community() { setConfirmModal({ isOpen: true, title: 'Cancel Trade', - message: 'Are you sure you want to cancel this trade offer?', + message: 'Cancel this trade offer?', variant: 'warning', onConfirm: async () => { setProcessingTradeId(tradeId); @@ -350,21 +350,18 @@ export default function Community() { const renderTradeItems = (items: TradeItem[] | undefined, ownerId: string, label: string) => { const ownerItems = items?.filter((i) => i.owner_id === ownerId) || []; if (ownerItems.length === 0) { - return
{label}: Nothing (gift)
; + return

{label}: Gift

; } return (
-
{label}:
-
+

{label}:

+
{ownerItems.map((item) => { const card = tradeCardDetails.get(item.card_id); return ( -
- {card?.image_uris?.small && ( - {card.name} - )} - {card?.name || item.card_id} +
+ {card?.name || 'Card'} {item.quantity > 1 && x{item.quantity}}
); @@ -374,71 +371,78 @@ export default function Community() { ); }; + // Loading state if (loading) { return (
-
+
); } - // Viewing a user's collection + // ============ USER COLLECTION VIEW ============ if (selectedUser) { return ( -
-
-
-
- -

{selectedUser.username}'s Collection

-
+
+ {/* Header */} +
+
+ +

{selectedUser.username}

+
+ {/* Collection Grid */} +
{loadingCollection ? ( -
- +
+
) : selectedUserCollection.length === 0 ? ( -

This collection is empty

+

Empty collection

) : ( -
+
{selectedUserCollection.map(({ card, quantity }) => (
-
- {card.name} -
x{quantity}
-
-
{card.name}
+ {card.name} + + x{quantity} +
))}
)} - - {showTradeCreator && ( - setShowTradeCreator(false)} - onTradeCreated={() => { - setShowTradeCreator(false); - toast.success('Trade proposal sent!'); - }} - /> - )}
+ + {showTradeCreator && ( + setShowTradeCreator(false)} + onTradeCreated={() => { + setShowTradeCreator(false); + toast.success('Trade proposal sent!'); + }} + /> + )}
); } @@ -447,203 +451,255 @@ export default function Community() { (u) => !browseSearch || u.username?.toLowerCase().includes(browseSearch.toLowerCase()) ); - const friendProfiles: UserProfile[] = friends.map((f) => ({ - id: f.id, - username: f.username, - collection_visibility: 'friends', - })); - + // ============ MAIN VIEW ============ return ( -
-
-

Community

+
+ {/* Header */} +
+

Community

- {/* Main Tabs */} -
+ {/* Tabs - Scrollable on mobile */} +
{[ { id: 'browse' as Tab, label: 'Browse', icon: Globe }, - { id: 'friends' as Tab, label: `Friends (${friends.length})`, icon: Users }, - { id: 'trades' as Tab, label: `Trades (${pendingTrades.length})`, icon: ArrowLeftRight }, + { 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) => ( ))}
+
+ {/* Content */} +
{/* ============ BROWSE TAB ============ */} {activeTab === 'browse' && ( -
+
+ {/* Search */}
- + setBrowseSearch(e.target.value)} - placeholder="Search public collections..." - 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" + 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 found

+

No public collections

) : ( -
+
{filteredPublicUsers.map((userProfile) => ( -
-
- - {userProfile.username || 'Unknown'} + -
+ + ))}
)} + + {/* Friends shortcut */} + {friends.length > 0 && ( +
+

Your friends

+
+ {friends.slice(0, 3).map((friend) => ( + + ))} + {friends.length > 3 && ( + + )} +
+
+ )}
)} {/* ============ FRIENDS TAB ============ */} {activeTab === 'friends' && ( -
-
- {(['list', 'requests', 'search'] as FriendsSubTab[]).map((tab) => ( +
+ {/* 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' && ( -
- {friends.length === 0 ? ( -

No friends yet. Search for users to add them!

- ) : ( - friends.map((friend) => ( -
- {friend.username || 'Unknown'} -
+ friends.length === 0 ? ( +

No friends yet

+ ) : ( +
+ {friends.map((friend) => ( +
+ {friend.username || 'Unknown'} +
- )) + ))} +
+ ) + )} + + {/* Requests */} + {friendsSubTab === 'requests' && ( +
+ {pendingRequests.length > 0 && ( +
+

Received

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

Sent

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

No requests

)}
)} - {friendsSubTab === 'requests' && ( -
-
-

Received

- {pendingRequests.length === 0 ? ( -

No pending requests

- ) : ( -
- {pendingRequests.map((req) => ( -
- {req.username || 'Unknown'} -
- - -
-
- ))} -
- )} -
-
-

Sent

- {sentRequests.length === 0 ? ( -

No sent requests

- ) : ( -
- {sentRequests.map((req) => ( -
-
- - {req.username || 'Unknown'} -
- Pending -
- ))} -
- )} -
-
- )} - + {/* Search/Add */} {friendsSubTab === 'search' && ( -
+
setFriendSearch(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleSearchFriends()} - placeholder="Search by username..." - className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500" + 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'} +
+ {result.username || 'Unknown'} {isAlreadyFriendOrPending(result.id) ? ( - Already connected + Connected ) : ( - )} @@ -658,75 +714,99 @@ export default function Community() { {/* ============ TRADES TAB ============ */} {activeTab === 'trades' && ( -
-
+
+ {/* Sub tabs */} +
+ {/* Trades List */} {(tradesSubTab === 'pending' ? pendingTrades : tradeHistory).length === 0 ? ( -

- {tradesSubTab === 'pending' ? 'No pending trades' : 'No trade history'} +

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

) : ( -
+
{(tradesSubTab === 'pending' ? pendingTrades : tradeHistory).map((trade) => { const isSender = trade.sender_id === user?.id; const otherUser = isSender ? trade.receiver : trade.sender; - const statusColor = trade.status === 'accepted' ? 'text-green-400' : trade.status === 'declined' ? 'text-red-400' : trade.status === 'cancelled' ? 'text-gray-400' : 'text-yellow-400'; + const statusColors: Record = { + accepted: 'text-green-400', + declined: 'text-red-400', + cancelled: 'text-gray-400', + pending: 'text-yellow-400', + }; return ( -
+
+ {/* Header */}
-
- - {isSender ? `To: ${otherUser?.username}` : `From: ${otherUser?.username}`} +
+ + + {isSender ? 'To' : 'From'}: {otherUser?.username} +
- {trade.status} + + {trade.status} +
-
- {renderTradeItems(trade.items, trade.sender_id, isSender ? 'You give' : 'They give')} - {renderTradeItems(trade.items, trade.receiver_id, isSender ? 'You receive' : 'They receive')} + {/* Items */} +
+ {renderTradeItems(trade.items, trade.sender_id, isSender ? 'Give' : 'Receive')} + {renderTradeItems(trade.items, trade.receiver_id, isSender ? 'Get' : 'Send')}
- {trade.message &&
Message: {trade.message}
} - + {/* Actions */} {tradesSubTab === 'pending' && (
{isSender ? ( - ) : ( <> - - )}
)} - -
{new Date(trade.created_at || '').toLocaleDateString()}
); })} @@ -737,52 +817,53 @@ export default function Community() { {/* ============ PROFILE TAB ============ */} {activeTab === 'profile' && ( -
+
+ {/* Username */}
- + setUsername(e.target.value)} - className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500" - placeholder="Enter your username" + 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 */}
)}
+ {/* Confirm Modal */} setConfirmModal({ ...confirmModal, isOpen: false })}