/**
* CoinRush Social Panel - Friends, Guilds, and Private Messages
* Comprehensive social system with React
*/
const { useState, useEffect, useRef, useCallback } = React;
// ============================================
// ICONS
// ============================================
const SocialIcons = {
users: (
),
guild: (
),
message: (
),
add: (
),
check: (
),
x: (
),
send: (
),
crown: (
),
logout: (
),
};
// ============================================
// FRIEND LIST COMPONENT
// ============================================
function FriendsList({ currentUser, onOpenDM }) {
const [friends, setFriends] = useState([]);
const [requests, setRequests] = useState({ received: [], sent: [] });
const [showAddFriend, setShowAddFriend] = useState(false);
const [addUsername, setAddUsername] = useState('');
const [loading, setLoading] = useState(false);
const loadFriends = useCallback(async () => {
try {
const res = await fetch('/api/friends/list');
if (res.ok) {
const data = await res.json();
setFriends(data.friends || []);
}
} catch (e) {
console.error('Failed to load friends:', e);
}
}, []);
const loadRequests = useCallback(async () => {
try {
const res = await fetch('/api/friends/requests');
if (res.ok) {
const data = await res.json();
setRequests(data);
}
} catch (e) {
console.error('Failed to load requests:', e);
}
}, []);
useEffect(() => {
loadFriends();
loadRequests();
const interval = setInterval(() => {
loadFriends();
loadRequests();
}, 30000); // Refresh every 30 seconds
return () => clearInterval(interval);
}, [loadFriends, loadRequests]);
const sendRequest = async () => {
if (!addUsername.trim()) return;
setLoading(true);
try {
const res = await fetch('/api/friends/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: addUsername.trim() })
});
const data = await res.json();
if (res.ok) {
alert(data.message);
setAddUsername('');
setShowAddFriend(false);
loadFriends();
loadRequests();
} else {
alert(data.detail || 'Failed to send request');
}
} catch (e) {
alert('Network error');
} finally {
setLoading(false);
}
};
const acceptRequest = async (requestId) => {
try {
const res = await fetch(`/api/friends/accept/${requestId}`, { method: 'POST' });
if (res.ok) {
loadFriends();
loadRequests();
}
} catch (e) {
console.error('Failed to accept:', e);
}
};
const rejectRequest = async (requestId) => {
try {
const res = await fetch(`/api/friends/reject/${requestId}`, { method: 'POST' });
if (res.ok) {
loadRequests();
}
} catch (e) {
console.error('Failed to reject:', e);
}
};
const removeFriend = async (friendId) => {
if (!confirm('Remove this friend?')) return;
try {
const res = await fetch(`/api/friends/remove/${friendId}`, { method: 'DELETE' });
if (res.ok) {
loadFriends();
}
} catch (e) {
console.error('Failed to remove:', e);
}
};
return (
{SocialIcons.users} Friends ({friends.length})
{showAddFriend && (
setAddUsername(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendRequest()}
disabled={loading}
/>
)}
{requests.received.length > 0 && (
Friend Requests ({requests.received.length})
{requests.received.map(req => (
{req.username}
LVL {req.level}
))}
)}
{friends.length === 0 ? (
No friends yet. Add some to start chatting!
) : (
friends.map(friend => (
{friend.username.charAt(0).toUpperCase()}
{friend.username}
LVL {friend.level}
))
)}
);
}
// ============================================
// PRIVATE MESSAGE MODAL
// ============================================
function PrivateMessageModal({ friend, onClose, currentUser }) {
const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState('');
const [loading, setLoading] = useState(false);
const messagesEndRef = useRef(null);
const loadMessages = useCallback(async () => {
if (!friend) return;
try {
const res = await fetch(`/api/messages/conversation/${friend.id}`);
if (res.ok) {
const data = await res.json();
setMessages(data.messages || []);
}
} catch (e) {
console.error('Failed to load messages:', e);
}
}, [friend]);
useEffect(() => {
loadMessages();
const interval = setInterval(loadMessages, 3000); // Poll every 3 seconds
return () => clearInterval(interval);
}, [loadMessages]);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const sendMessage = async () => {
if (!inputValue.trim() || loading) return;
setLoading(true);
try {
const res = await fetch('/api/messages/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
receiver_username: friend.username,
message: inputValue.trim()
})
});
if (res.ok) {
setInputValue('');
loadMessages();
} else {
const data = await res.json();
alert(data.detail || 'Failed to send message');
}
} catch (e) {
alert('Network error');
} finally {
setLoading(false);
}
};
if (!friend) return null;
return (
e.stopPropagation()}>
{SocialIcons.message} {friend.username}
{messages.length === 0 ? (
No messages yet. Start the conversation!
) : (
messages.map((msg, idx) => (
{msg.message}
{new Date(msg.created_at).toLocaleTimeString()}
))
)}
setInputValue(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
disabled={loading}
maxLength={500}
/>
);
}
// ============================================
// GUILD WARS COMPONENT
// ============================================
function GuildWars({ guild, myRole }) {
const [activeWar, setActiveWar] = useState(null);
const [warHistory, setWarHistory] = useState([]);
const [timeRemaining, setTimeRemaining] = useState('--:--:--');
useEffect(() => {
// Mock data - in production, fetch from API
const mockWar = {
id: 1,
enemy_guild: { name: 'Shadow Legion', tag: 'SHDW', score: 4250 },
our_score: 5120,
status: 'active',
ends_at: new Date(Date.now() + 3600000 * 2).toISOString() // 2 hours
};
setActiveWar(mockWar);
// Timer countdown
const timer = setInterval(() => {
if (mockWar) {
const now = new Date();
const end = new Date(mockWar.ends_at);
const diff = end - now;
if (diff > 0) {
const hours = Math.floor(diff / 3600000);
const mins = Math.floor((diff % 3600000) / 60000);
const secs = Math.floor((diff % 60000) / 1000);
setTimeRemaining(`${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`);
} else {
setTimeRemaining('ENDED');
}
}
}, 1000);
return () => clearInterval(timer);
}, []);
if (!activeWar) {
return (
⚔️
Guild Wars
No Active War
No active guild war. Start one to compete against other guilds!
{myRole === 'leader' && (
)}
);
}
return (
⚔️
Guild Wars
🔴 LIVE
[{guild?.tag || 'YOU'}]
{activeWar.our_score.toLocaleString()}
VS
[{activeWar.enemy_guild.tag}]
{activeWar.enemy_guild.score.toLocaleString()}
Time Remaining
{timeRemaining}
);
}
// ============================================
// WEEKLY CHALLENGES COMPONENT
// ============================================
function WeeklyChallenges({ guild }) {
const [challenges, setChallenges] = useState([]);
const [timeUntilReset, setTimeUntilReset] = useState('');
useEffect(() => {
// Mock challenges data
setChallenges([
{
id: 1,
title: 'Wager $50,000 collectively',
icon: '💰',
iconType: 'wager',
current: 32500,
target: 50000,
reward: 5000,
completed: false
},
{
id: 2,
title: 'Win 100 games as a guild',
icon: '🏆',
iconType: 'wins',
current: 87,
target: 100,
reward: 3000,
completed: false
},
{
id: 3,
title: 'Have 5 members hit a 10x multiplier',
icon: '🔥',
iconType: 'streak',
current: 5,
target: 5,
reward: 2500,
completed: true
},
{
id: 4,
title: 'Recruit 3 new members',
icon: '👥',
iconType: 'social',
current: 1,
target: 3,
reward: 1500,
completed: false
}
]);
// Calculate time until Sunday midnight
const now = new Date();
const sunday = new Date(now);
sunday.setDate(sunday.getDate() + (7 - sunday.getDay()));
sunday.setHours(23, 59, 59, 999);
const updateTimer = () => {
const diff = sunday - new Date();
if (diff > 0) {
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
setTimeUntilReset(`${days}d ${hours}h`);
}
};
updateTimer();
const timer = setInterval(updateTimer, 60000);
return () => clearInterval(timer);
}, []);
return (
🎯
Weekly Challenges
⏱️
Resets in {timeUntilReset}
{challenges.map(challenge => (
{challenge.icon}
{challenge.title}
{challenge.current.toLocaleString()} / {challenge.target.toLocaleString()}
${(challenge.reward / 100).toFixed(0)}
Reward
))}
);
}
// ============================================
// GUILD PERKS COMPONENT
// ============================================
function GuildPerks({ guild, level }) {
const allPerks = [
{ id: 1, name: 'Lucky Bonus', description: '+1% on all wins', icon: '🍀', requiredLevel: 1, active: level >= 1 },
{ id: 2, name: 'Group Chat', description: 'Private guild chat', icon: '💬', requiredLevel: 2, active: level >= 2 },
{ id: 3, name: 'XP Boost', description: '+10% XP for all members', icon: '⚡', requiredLevel: 3, active: level >= 3 },
{ id: 4, name: 'Daily Bonus', description: 'Extra daily reward', icon: '🎁', requiredLevel: 5, active: level >= 5 },
{ id: 5, name: 'Fee Rebate', description: '-5% platform fees', icon: '💎', requiredLevel: 7, active: level >= 7 },
{ id: 6, name: 'VIP Access', description: 'Exclusive games', icon: '👑', requiredLevel: 10, active: level >= 10 },
];
return (
✨ Guild Perks
{allPerks.map(perk => (
{perk.icon}
{perk.name}
{perk.description}
{!perk.active && (
Level {perk.requiredLevel}
)}
))}
);
}
// ============================================
// GUILD ACHIEVEMENTS COMPONENT
// ============================================
function GuildAchievements({ guild }) {
const achievements = [
{ id: 1, name: 'First Steps', icon: '👶', unlocked: true },
{ id: 2, name: 'Growing Strong', icon: '💪', unlocked: true },
{ id: 3, name: 'War Victor', icon: '⚔️', unlocked: false },
{ id: 4, name: 'Millionaire', icon: '💰', unlocked: true },
{ id: 5, name: 'Social Butterfly', icon: '🦋', unlocked: false },
{ id: 6, name: 'Challenge Master', icon: '🎯', unlocked: false },
{ id: 7, name: 'Legendary', icon: '🌟', unlocked: false },
{ id: 8, name: 'Undefeated', icon: '🏆', unlocked: false },
];
return (
🏅 Achievements
{achievements.map(ach => (
))}
);
}
// ============================================
// GUILD ACTIVITY FEED COMPONENT
// ============================================
function GuildActivityFeed({ guildId }) {
const [activities, setActivities] = useState([]);
useEffect(() => {
// Mock activity data
setActivities([
{ id: 1, user: 'CryptoKing', action: 'won big', amount: '+$2,450', time: '2m ago', positive: true },
{ id: 2, user: 'LuckyAce', action: 'completed challenge', amount: '+$50', time: '5m ago', positive: true },
{ id: 3, user: 'NightRider', action: 'donated to treasury', amount: '+$100', time: '12m ago', positive: true },
{ id: 4, user: 'StarGazer', action: 'joined the guild', amount: null, time: '25m ago', positive: null },
{ id: 5, user: 'WaveRunner', action: 'won war point', amount: '+10 pts', time: '32m ago', positive: true },
]);
}, [guildId]);
return (
Live Activity
{activities.map(activity => (
{activity.user.charAt(0)}
{activity.user} {activity.action}
{activity.time}
{activity.amount && (
{activity.amount}
)}
))}
);
}
// ============================================
// GUILD PANEL
// ============================================
// ============================================
// Donate Modal
// ============================================
function DonateModal({ onClose, onDonate }) {
const [amount, setAmount] = useState('');
return (
e.stopPropagation()}>
Donate to Treasury
Contribute to your guild's treasury to help unlock perks and events.
$
setAmount(e.target.value)}
autoFocus
/>
);
}
function GuildPanel({ currentUser }) {
const [myGuild, setMyGuild] = useState(null);
const [activeTab, setActiveTab] = useState('overview'); // overview, members, challenges, leaderboard, browse, create
const [publicGuilds, setPublicGuilds] = useState([]);
const [leaderboard, setLeaderboard] = useState([]);
const [createForm, setCreateForm] = useState({ name: '', tag: '', description: '', is_public: true });
const [loading, setLoading] = useState(false);
const [showDonate, setShowDonate] = useState(false);
const loadMyGuild = useCallback(async () => {
try {
const res = await fetch('/api/guilds/my-guild');
if (res.ok) {
const data = await res.json();
if (data.in_guild) {
setMyGuild(data);
if (activeTab === 'browse' || activeTab === 'create') setActiveTab('overview');
} else {
setMyGuild(null);
if (activeTab === 'overview' || activeTab === 'members') setActiveTab('browse');
}
}
} catch (e) {
console.error('Failed to load guild:', e);
}
}, [activeTab]);
const loadPublicGuilds = useCallback(async () => {
try {
const res = await fetch('/api/guilds/list');
if (res.ok) {
const data = await res.json();
setPublicGuilds(data.guilds || []);
}
} catch (e) {
console.error('Failed to load guilds:', e);
}
}, []);
const loadLeaderboard = useCallback(async () => {
try {
const res = await fetch('/api/guilds/leaderboard');
if (res.ok) {
const data = await res.json();
setLeaderboard(data.leaderboard || []);
}
} catch (e) {
console.error('Failed to load leaderboard:', e);
}
}, []);
useEffect(() => {
loadMyGuild();
if (activeTab === 'browse') loadPublicGuilds();
if (activeTab === 'leaderboard') loadLeaderboard();
}, [loadMyGuild, activeTab, loadPublicGuilds, loadLeaderboard]);
const createGuild = async () => {
if (!createForm.name.trim() || !createForm.tag.trim()) {
alert('Name and tag are required');
return;
}
setLoading(true);
try {
const res = await fetch('/api/guilds/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(createForm)
});
const data = await res.json();
if (res.ok) {
alert(data.message);
setCreateForm({ name: '', tag: '', description: '', is_public: true });
loadMyGuild();
} else {
alert(data.detail || 'Failed to create guild');
}
} catch (e) {
alert('Network error');
} finally {
setLoading(false);
}
};
const joinGuild = async (guildId) => {
try {
const res = await fetch(`/api/guilds/join/${guildId}`, { method: 'POST' });
const data = await res.json();
if (res.ok) {
alert(data.message);
loadMyGuild();
} else {
alert(data.detail || 'Failed to join guild');
}
} catch (e) {
alert('Network error');
}
};
const leaveGuild = async () => {
if (!confirm('Are you sure you want to leave your guild?')) return;
try {
const res = await fetch('/api/guilds/leave', { method: 'POST' });
const data = await res.json();
if (res.ok) {
alert(data.message);
loadMyGuild();
}
} catch (e) {
alert('Network error');
}
};
const handleDonate = async (amount) => {
if (!amount || amount <= 0) return;
try {
const res = await fetch('/api/guilds/donate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount })
});
const data = await res.json();
if (res.ok) {
alert(data.message);
setShowDonate(false);
loadMyGuild();
} else {
alert(data.detail || 'Failed to donate');
}
} catch (e) {
alert('Network error');
}
};
const handleKick = async (userId) => {
if (!confirm('Kick this member?')) return;
try {
const res = await fetch(`/api/guilds/kick/${userId}`, { method: 'POST' });
if (res.ok) {
loadMyGuild();
} else {
const data = await res.json();
alert(data.detail);
}
} catch (e) {
alert('Network error');
}
};
const handlePromote = async (userId) => {
try {
const res = await fetch(`/api/guilds/promote/${userId}`, { method: 'POST' });
if (res.ok) {
loadMyGuild();
} else {
const data = await res.json();
alert(data.detail);
}
} catch (e) {
alert('Network error');
}
};
// Render Content based on state
const renderContent = () => {
if (activeTab === 'leaderboard') {
const top3 = leaderboard.slice(0, 3);
const rest = leaderboard.slice(3);
return (
{/* Podium for top 3 */}
{top3.length >= 3 && (
🥈
[{top3[1].tag}]
{top3[1].name}
${(top3[1].total_wagered / 100).toLocaleString()}
👑
[{top3[0].tag}]
{top3[0].name}
${(top3[0].total_wagered / 100).toLocaleString()}
🥉
[{top3[2].tag}]
{top3[2].name}
${(top3[2].total_wagered / 100).toLocaleString()}
)}
{/* Rest of leaderboard */}
Rank
Guild
Members
Total Wagered
{(top3.length < 3 ? leaderboard : rest).map((g, idx) => (
{top3.length < 3 && idx === 0 ? '🥇' : top3.length < 3 && idx === 1 ? '🥈' : top3.length < 3 && idx === 2 ? '🥉' : `#${g.rank || idx + 4}`}
[{g.tag}]
{g.name}
{g.total_members}
${(g.total_wagered / 100).toLocaleString()}
))}
);
}
if (myGuild && myGuild.in_guild) {
const { guild, members, my_role } = myGuild;
if (activeTab === 'wars') {
return (
📜 War History
[{guild.tag}] vs [SHDW]
5,120 - 4,250
Victory
[{guild.tag}] vs [APEX]
3,800 - 2,100
Victory
[{guild.tag}] vs [ELITE]
2,500 - 4,800
Defeat
);
}
if (activeTab === 'members') {
return (
Members ({members.length})
{members.map(member => (
{member.username}
{member.role === 'leader' && {SocialIcons.crown} Leader}
{member.role === 'officer' && Officer}
LVL {member.level}
${(member.contribution_wagered / 100).toLocaleString()} contributed
{/* Management Actions */}
{my_role === 'leader' && member.role !== 'leader' && (
)}
))}
);
}
// Overview
const nextLevelWager = guild.level * 1000000; // Mock requirement
const progress = Math.min(100, (guild.total_wagered / nextLevelWager) * 100);
return (
[{guild.tag}] {guild.name}
{guild.description || 'No description set.'}
{/* Level Progress */}
Level {guild.level}
Level {guild.level + 1}
${(guild.total_wagered / 100).toLocaleString()} / ${(nextLevelWager / 100).toLocaleString()} wagered
Level
{guild.level}
Members
{guild.total_members}/{guild.max_members}
Total Wagered
${(guild.total_wagered / 100).toLocaleString()}
Treasury
${(guild.treasury / 100).toLocaleString()}
{/* Guild Wars */}
{/* Weekly Challenges */}
{/* Guild Perks */}
{/* Guild Achievements */}
{/* Activity Feed */}
);
}
// Not in guild
if (activeTab === 'create') {
return (
Create a New Guild
setCreateForm({...createForm, name: e.target.value})}
maxLength={32}
/>
setCreateForm({...createForm, tag: e.target.value.toUpperCase()})}
maxLength={6}
/>
);
}
// Browse
return (
Public Guilds
{publicGuilds.length === 0 ? (
No public guilds available. Create one!
) : (
publicGuilds.map(guild => (
[{guild.tag}] {guild.name}
{guild.description || 'No description'}
{guild.total_members}/{guild.max_members} members
LVL {guild.level}
${(guild.total_wagered / 100).toLocaleString()} wagered
))
)}
);
};
return (
{myGuild && myGuild.in_guild ? (
<>
>
) : (
<>
>
)}
{renderContent()}
{showDonate && (
setShowDonate(false)}
onDonate={handleDonate}
/>
)}
);
}
// Export for global use
window.GuildPanel = GuildPanel;
// ============================================
// MAIN SOCIAL PANEL
// ============================================
function SocialPanel({ currentUser }) {
const [activeTab, setActiveTab] = useState('friends');
const [activeDM, setActiveDM] = useState(null);
const goToGuildHub = () => {
window.location.href = '/guild';
};
return (
{activeTab === 'friends' && (
setActiveDM(friend)}
/>
)}
{activeDM && (
setActiveDM(null)}
/>
)}
);
}
// Export for global use
window.SocialPanel = SocialPanel;
window.renderSocialPanel = function(containerId, currentUser) {
const container = document.getElementById(containerId);
if (!container) {
console.error(`Social panel container #${containerId} not found`);
return;
}
const root = ReactDOM.createRoot(container);
root.render();
};