// ============================================ // SOCIAL HUB - Unified Premium React Application // Friends, Messages, Guild - All in One // ============================================ const { useState, useEffect, useRef, useCallback } = React; const CoinRushNavbar = window.CoinRushNavbar; // ============================================ // SVG ICONS // ============================================ const Icons = { users: , message: , guild: , search: , plus: , check: , x: , send: , crown: , trophy: , coins: , settings: , star: , shield: , logout: , userPlus: , clock: , bell: , chevronRight: , externalLink: , }; // Format coins const formatCoins = (cents) => { const coins = (cents || 0) / 100; return coins.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }; // ============================================ // MAIN SOCIAL HUB COMPONENT // ============================================ function SocialHub({ user }) { const [activeSection, setActiveSection] = useState('overview'); const [friends, setFriends] = useState([]); const [pendingRequests, setPendingRequests] = useState([]); const [guild, setGuild] = useState(null); const [guildList, setGuildList] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState([]); const [activeChatFriend, setActiveChatFriend] = useState(null); const [loading, setLoading] = useState(true); const [stats, setStats] = useState({ friends: 0, unreadMessages: 0, guildMembers: 0, guildRank: null }); // Load initial data useEffect(() => { loadAllData(); }, []); const loadAllData = async () => { setLoading(true); await Promise.all([ loadFriends(), loadGuild(), loadGuildList() ]); setLoading(false); }; const loadFriends = async () => { try { const res = await fetch('/api/friends/list', { credentials: 'include' }); if (res.ok) { const data = await res.json(); setFriends(data.friends || []); setPendingRequests(data.pending_requests || []); setStats(prev => ({ ...prev, friends: data.friends?.length || 0 })); } } catch (err) { console.error('Failed to load friends:', err); } }; const loadGuild = async () => { try { const res = await fetch('/api/guilds/my-guild', { credentials: 'include' }); if (res.ok) { const data = await res.json(); if (data.in_guild) { setGuild(data.guild); setStats(prev => ({ ...prev, guildMembers: data.guild.total_members || 0 })); } } } catch (err) { console.error('Failed to load guild:', err); } }; const loadGuildList = async () => { try { const res = await fetch('/api/guilds/list', { credentials: 'include' }); if (res.ok) { const data = await res.json(); setGuildList(data.guilds || []); } } catch (err) { console.error('Failed to load guilds:', err); } }; // Search users useEffect(() => { if (searchQuery.length < 3) { setSearchResults([]); return; } const timer = setTimeout(async () => { try { const res = await fetch(`/api/users/search?query=${encodeURIComponent(searchQuery)}`, { credentials: 'include' }); if (res.ok) { const data = await res.json(); setSearchResults(data); } } catch (err) { console.error('Search failed:', err); } }, 400); return () => clearTimeout(timer); }, [searchQuery]); const sendFriendRequest = async (username) => { try { const res = await fetch('/api/friends/add', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username }), credentials: 'include' }); const data = await res.json(); if (res.ok) { setSearchResults(prev => prev.map(u => u.username === username ? { ...u, has_pending_request: true } : u )); } else { alert(data.detail || 'Failed to send request'); } } catch (err) { alert('Error sending request'); } }; const acceptRequest = async (requestId) => { try { const res = await fetch(`/api/friends/accept/${requestId}`, { method: 'POST', credentials: 'include' }); if (res.ok) { loadFriends(); } } catch (err) { console.error('Failed to accept request:', err); } }; const rejectRequest = async (requestId) => { try { const res = await fetch(`/api/friends/reject/${requestId}`, { method: 'POST', credentials: 'include' }); if (res.ok) { loadFriends(); } } catch (err) { console.error('Failed to reject request:', err); } }; if (loading) { return (

Loading Social Hub...

); } return (
{/* Chat Modal */} {activeChatFriend && ( setActiveChatFriend(null)} /> )} {/* Header */}

{Icons.users} Social Hub

Connect with friends and join guilds

{/* Quick Stats */}
{stats.friends} Friends
{pendingRequests.length} Requests
{guild && (
{stats.guildMembers} Guild
)}
{/* Navigation */} {/* Main Content */}
{activeSection === 'overview' && ( )} {activeSection === 'friends' && ( )} {activeSection === 'search' && ( )} {activeSection === 'guild' && ( )}
); } // ============================================ // OVERVIEW SECTION // ============================================ function OverviewSection({ friends, pendingRequests, guild, onChat, onAccept, onReject, onNavigate }) { const onlineFriends = friends.filter(f => f.is_online); return (
{/* Pending Requests Alert */} {pendingRequests.length > 0 && (
{Icons.bell}
{pendingRequests.length} pending friend request{pendingRequests.length > 1 ? 's' : ''}

Someone wants to connect with you!

)}
{/* Online Friends */}

{Icons.users} Online Friends

{onlineFriends.length}
{onlineFriends.length === 0 ? (

No friends online right now

) : (
{onlineFriends.slice(0, 5).map(friend => (
onChat(friend)}>
{friend.username[0].toUpperCase()}
{friend.username}
))} {onlineFriends.length > 5 && ( )}
)}
{/* Guild Card */}

{Icons.guild} My Guild

{guild ? (
{guild.name[0].toUpperCase()}

{guild.name}

{guild.total_members || 0}
{formatCoins(guild.treasury || 0)}
Lv.{guild.level || 1}
) : (

You're not in a guild yet

)}
{/* Quick Actions */}

{Icons.star} Quick Actions

); } // ============================================ // FRIENDS SECTION // ============================================ function FriendsSection({ friends, pendingRequests, onChat, onAccept, onReject, onNavigate }) { return (
{/* Pending Requests */} {pendingRequests.length > 0 && (

{Icons.bell} Pending Requests {pendingRequests.length}

{pendingRequests.map(req => (
{req.from_username[0].toUpperCase()}
{req.from_username} wants to be your friend
))}
)} {/* Friends List */}

{Icons.users} My Friends {friends.length}

{friends.length === 0 ? (
{Icons.users}

No friends yet

Start adding friends to chat and play together!

) : (
{friends.map(friend => (
{friend.username[0].toUpperCase()}
{friend.username} Level {friend.level || 1} • {friend.is_online ? 'Online' : 'Offline'}
))}
)}
); } // ============================================ // SEARCH SECTION // ============================================ function SearchSection({ query, setQuery, results, onSendRequest }) { return (

{Icons.search} Find Friends

{Icons.search}
setQuery(e.target.value)} />
{query.length > 0 && query.length < 3 && (

Type at least 3 characters to search

)} {results.length > 0 && (
{results.map(user => (
{user.username[0].toUpperCase()}
{user.username} Level {user.level || 1}
{user.is_friend ? ( {Icons.check} Friends ) : user.has_pending_request ? ( {Icons.clock} Pending ) : ( )}
))}
)} {query.length >= 3 && results.length === 0 && (
{Icons.search}

No users found

Try a different search term

)}
); } // ============================================ // GUILD SECTION // ============================================ function GuildSection({ guild, guildList, user, onRefresh }) { const [showCreateWizard, setShowCreateWizard] = useState(false); const [donateAmount, setDonateAmount] = useState(''); const GuildCreateWizard = window.GuildCreateWizard; const joinGuild = async (guildId) => { try { const res = await fetch(`/api/guilds/join/${guildId}`, { method: 'POST', credentials: 'include' }); const data = await res.json(); if (res.ok) { onRefresh(); } else { alert(data.detail || 'Failed to join guild'); } } catch (err) { alert('Error joining guild'); } }; const leaveGuild = async () => { if (!confirm('Are you sure you want to leave this guild?')) return; try { const res = await fetch('/api/guilds/leave', { method: 'POST', credentials: 'include' }); if (res.ok) { onRefresh(); } } catch (err) { alert('Error leaving guild'); } }; const donateToTreasury = async () => { const amount = parseFloat(donateAmount) * 100; // Convert to cents if (!amount || amount < 100) { alert('Minimum donation is 1.00'); return; } try { const res = await fetch('/api/guilds/donate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ amount }) }); const data = await res.json(); if (res.ok) { setDonateAmount(''); onRefresh(); alert('Donation successful!'); } else { alert(data.detail || 'Failed to donate'); } } catch (err) { alert('Error donating'); } }; const handleGuildCreated = (newGuild) => { setShowCreateWizard(false); onRefresh(); }; // If user is in a guild, show guild details if (guild) { return (
{guild.name[0].toUpperCase()}

{guild.name}

{guild.description || 'No description set'}

{Icons.users}
{guild.total_members || 0}
Members
{Icons.coins}
{formatCoins(guild.treasury || 0)}
Treasury
{Icons.star}
Lv.{guild.level || 1}
Level
{Icons.trophy}
{guild.total_xp || 0}
Total XP
{/* Donate Section */}

{Icons.coins} Donate to Treasury

setDonateAmount(e.target.value)} min="1" step="0.01" />
{/* Actions */}
); } // If user is not in a guild, show guild creation wizard or list return (
{/* Multi-Step Guild Creation Wizard */} {showCreateWizard && GuildCreateWizard && ( setShowCreateWizard(false)} onSuccess={handleGuildCreated} /> )} {/* Create Your Own Guild CTA */}
{Icons.crown}

Start Your Own Guild

Create a community, lead members, compete in wars, and build a treasury together.

{Icons.guild} Available Guilds

{guildList.length === 0 ? (
{Icons.guild}

No guilds available yet

Be the first to create a guild and start building your community!

) : (
{guildList.map(g => (
{g.name[0].toUpperCase()}

{g.name}

{g.total_members || 0} members - Lv.{g.level || 1}

))}
)}
); } // ============================================ // CHAT MODAL // ============================================ function ChatModal({ friend, onClose }) { const [messages, setMessages] = useState([]); const [input, setInput] = useState(''); const [sending, setSending] = useState(false); const messagesEndRef = useRef(null); useEffect(() => { loadMessages(); const interval = setInterval(loadMessages, 3000); return () => clearInterval(interval); }, [friend]); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); const loadMessages = async () => { try { const res = await fetch(`/api/messages/conversation/${friend.id}`, { credentials: 'include' }); if (res.ok) { const data = await res.json(); setMessages(data.messages || []); } } catch (err) { console.error('Failed to load messages:', err); } }; const sendMessage = async () => { if (!input.trim() || sending) return; setSending(true); try { const res = await fetch('/api/messages/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ receiver_username: friend.username, message: input.trim() }) }); if (res.ok) { setInput(''); loadMessages(); } } catch (err) { console.error('Failed to send:', err); } setSending(false); }; return (
e.stopPropagation()}>
{friend.username[0].toUpperCase()}

{friend.username}

{friend.is_online ? 'Online' : 'Offline'}
{messages.length === 0 ? (

No messages yet. Say hi!

) : ( messages.map((msg, i) => (
{msg.message}
{new Date(msg.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
)) )}
setInput(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && sendMessage()} disabled={sending} />
); } // ============================================ // MAIN APP WRAPPER // ============================================ function MainApp() { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { async function fetchUser() { try { const res = await fetch('/api/user', { credentials: 'include' }); if (!res.ok) { window.location.href = '/'; return; } const data = await res.json(); setUser(data); } catch (err) { console.error('Failed to load user:', err); window.location.href = '/'; } setLoading(false); } fetchUser(); }, []); if (loading) { return (

Loading...

); } return (
{ window.location.href = '/logout'; }} />
); } // Render const root = ReactDOM.createRoot(document.getElementById('root')); root.render();