// ============================================
// 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 (
);
}
return (
{/* Chat Modal */}
{activeChatFriend && (
setActiveChatFriend(null)}
/>
)}
{/* Header */}
{/* 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
{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 */}
{/* 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 ? (
) : (
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 (
);
}
return (
{ window.location.href = '/logout'; }}
/>
);
}
// Render
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render();