import React, { useState, useEffect } from 'react'; import { ChevronDown, ChevronUp, Plus, Settings, Crown, TrendingUp, Users, DollarSign, Target, BarChart3, X } from 'lucide-react'; import { pusherClient, triggerPusherEvent } from '../lib/pusher'; const ChessApp = () => { const [currentUser, setCurrentUser] = useState(null); const [users, setUsers] = useState({}); const [platformAccount, setPlatformAccount] = useState({ balance: 0, totalFees: 0, transactionCount: 0 }); const [games, setGames] = useState([]); const [bets, setBets] = useState({}); const [isAdmin, setIsAdmin] = useState(false); const [showAdminPanel, setShowAdminPanel] = useState(false); const [expandedGames, setExpandedGames] = useState({}); const [showGameForm, setShowGameForm] = useState(false); const [newGameData, setNewGameData] = useState({ player1: '', player2: '' }); const [showBetForm, setShowBetForm] = useState({}); const [showBetConfirmation, setShowBetConfirmation] = useState(false); const [pendingBet, setPendingBet] = useState(null); const [betType, setBetType] = useState('hedged'); const [newBetData, setNewBetData] = useState({ gameId: '', betAmount: 10, condition: '', certainty: 50 }); const [showDashboard, setShowDashboard] = useState(false); const [showAdminLogin, setShowAdminLogin] = useState(false); const [adminPassword, setAdminPassword] = useState(''); const [showOnlineUsers, setShowOnlineUsers] = useState(false); const [marketHistory, setMarketHistory] = useState({}); const [selectedWager, setSelectedWager] = useState(null); const [showExistingBetForm, setShowExistingBetForm] = useState({}); const [existingBetAmount, setExistingBetAmount] = useState(10); const [existingBetPosition, setExistingBetPosition] = useState('yes'); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // API helper functions const api = { async getUsers() { const response = await fetch('/api/users'); if (!response.ok) throw new Error('Failed to fetch users'); return response.json(); }, async saveUser(user) { const response = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user }) }); if (!response.ok) throw new Error('Failed to save user'); return response.json(); }, async updateUserBalance(userId, balance) { const response = await fetch('/api/users', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId, updates: { balance } }) }); if (!response.ok) throw new Error('Failed to update user balance'); return response.json(); }, async getGames() { const response = await fetch('/api/games'); if (!response.ok) throw new Error('Failed to fetch games'); return response.json(); }, async saveGame(game) { const response = await fetch('/api/games', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ game }) }); if (!response.ok) throw new Error('Failed to save game'); return response.json(); }, async getBets() { const response = await fetch('/api/bets'); if (!response.ok) throw new Error('Failed to fetch bets'); return response.json(); }, async saveBet(gameId, bet, marketProbability) { const response = await fetch('/api/bets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ gameId, bet, marketProbability }) }); if (!response.ok) throw new Error('Failed to save bet'); return response.json(); }, async getPlatformAccount() { const response = await fetch('/api/platform'); if (!response.ok) throw new Error('Failed to fetch platform account'); return response.json(); }, async savePlatformAccount(platformAccount) { const response = await fetch('/api/platform', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ platformAccount }) }); if (!response.ok) throw new Error('Failed to save platform account'); return response.json(); } }; // Initialize app and load data useEffect(() => { const initializeApp = async () => { try { setError(null); // Load all data from backend const [usersData, gamesData, betsData, platformData] = await Promise.all([ api.getUsers(), api.getGames(), api.getBets(), api.getPlatformAccount() ]); setUsers(usersData); setGames(gamesData); setBets(betsData); setPlatformAccount(platformData); // Check for existing user in localStorage const savedUserId = typeof window !== 'undefined' ? localStorage.getItem('chessUserId') : null; if (savedUserId && usersData[savedUserId]) { setCurrentUser(savedUserId); setIsAdmin(usersData[savedUserId].is_admin || false); } else { // Create new user const newUserId = 'user_' + Math.random().toString(36).substr(2, 9); const myceliumName = generateMyceliumName(); const newUser = { id: newUserId, name: myceliumName, balance: 1000, is_admin: false }; await api.saveUser(newUser); setUsers(prev => ({ ...prev, [newUserId]: newUser })); setCurrentUser(newUserId); if (typeof window !== 'undefined') { localStorage.setItem('chessUserId', newUserId); } } setLoading(false); } catch (error) { console.error('Failed to initialize app:', error); setError('Failed to load application. Please refresh the page.'); setLoading(false); } }; initializeApp(); }, []); // Set up Pusher real-time listeners useEffect(() => { if (!currentUser || !pusherClient) return; const channel = pusherClient.subscribe('chess-tournament'); channel.bind('new-game', (data) => { setGames(prev => { const exists = prev.find(g => g.id === data.game.id); return exists ? prev : [...prev, data.game]; }); setExpandedGames(prev => ({ ...prev, [data.game.id]: true })); }); channel.bind('new-bet', (data) => { setBets(prev => ({ ...prev, [data.gameId]: [...(prev[data.gameId] || []), data.bet] })); updateMarketHistory(data.gameId, data.bet.condition, data.marketProbability); }); channel.bind('user-update', (data) => { setUsers(prev => ({ ...prev, [data.userId]: data.user })); if (data.userId === currentUser) { // Update local user data } }); channel.bind('platform-update', (data) => { setPlatformAccount(data.platformAccount); }); return () => { channel.unbind_all(); pusherClient.unsubscribe('chess-tournament'); }; }, [currentUser]); // Auto-expand new games useEffect(() => { if (games.length > 0) { const initialExpanded = {}; games.forEach(game => { if (!(game.id in expandedGames)) { initialExpanded[game.id] = true; } }); if (Object.keys(initialExpanded).length > 0) { setExpandedGames(prev => ({ ...prev, ...initialExpanded })); } } }, [games]); // Update admin status when user changes useEffect(() => { if (currentUser && users[currentUser]) { setIsAdmin(users[currentUser].is_admin || false); } }, [currentUser, users]); const generateMyceliumName = () => { const titles = ['Spore', 'Network', 'Colony', 'Cluster', 'Node', 'Branch', 'Root', 'Cap']; const names = ['Shiitake', 'Oyster', 'Chanterelle', 'Morel', 'Porcini', 'Enoki', 'Maitake', 'Reishi', 'Cordyceps', 'Agaricus']; const suffixes = ['Weaver', 'Connector', 'Spreader', 'Fruiter', 'Decomposer', 'Networker', 'Symbiont', 'Grower']; const title = titles[Math.floor(Math.random() * titles.length)]; const name = names[Math.floor(Math.random() * names.length)]; const suffix = suffixes[Math.floor(Math.random() * suffixes.length)]; return `${title} ${name} ${suffix}`; }; const updateMarketHistory = (gameId, condition, probability) => { const key = `${gameId}-${condition}`; setMarketHistory(prev => { const currentHistory = prev[key] || []; const newEntry = { timestamp: new Date().toISOString(), probability: parseFloat(probability) }; return { ...prev, [key]: [...currentHistory, newEntry].slice(-10) }; }); }; const calculateTokenPrices = (gameId, betCondition) => { const gameBets = bets[gameId] || []; const relevantBets = gameBets.filter(bet => bet.condition.toLowerCase().trim() === betCondition.toLowerCase().trim()); if (relevantBets.length === 0) { return { yesPrice: 0.50, noPrice: 0.50, marketProbability: 50 }; } let totalWeightedCertainty = 0; let totalAmount = 0; relevantBets.forEach(bet => { totalWeightedCertainty += bet.certainty * bet.amount; totalAmount += bet.amount; }); const marketProbability = totalWeightedCertainty / totalAmount; const yesPrice = (marketProbability / 100).toFixed(2); const noPrice = (1 - (marketProbability / 100)).toFixed(2); return { yesPrice: parseFloat(yesPrice), noPrice: parseFloat(noPrice), marketProbability: marketProbability.toFixed(1) }; }; const calculateOdds = (gameId, betCondition) => { const gameBets = bets[gameId] || []; const relevantBets = gameBets.filter(bet => bet.condition.toLowerCase().trim() === betCondition.toLowerCase().trim()); if (relevantBets.length === 0) { return { odds: 'No bets', totalAmount: 0, avgCertainty: 0, betCount: 0 }; } let totalWeightedCertainty = 0; let totalAmount = 0; relevantBets.forEach(bet => { totalWeightedCertainty += bet.certainty * bet.amount; totalAmount += bet.amount; }); const avgCertainty = totalWeightedCertainty / totalAmount; const impliedProbability = avgCertainty / 100; const odds = impliedProbability > 0 ? (1 / impliedProbability).toFixed(2) : '∞'; return { odds: `${odds}:1`, totalAmount, avgCertainty: avgCertainty.toFixed(1), betCount: relevantBets.length }; }; const getUniqueBetConditions = (gameId) => { const gameBets = bets[gameId] || []; const conditions = [...new Set(gameBets.map(bet => bet.condition))]; return conditions; }; const toggleGameExpansion = (gameId) => { setExpandedGames(prev => ({ ...prev, [gameId]: !prev[gameId] })); }; const addGame = async () => { if (newGameData.player1 && newGameData.player2) { try { const gameId = 'game_' + Math.random().toString(36).substr(2, 9); const newGame = { id: gameId, player1: newGameData.player1, player2: newGameData.player2, status: 'upcoming', created_at: new Date().toISOString() }; setGames(prev => [...prev, newGame]); setBets(prev => ({ ...prev, [gameId]: [] })); setExpandedGames(prev => ({ ...prev, [gameId]: true })); setNewGameData({ player1: '', player2: '' }); setShowGameForm(false); await api.saveGame(newGame); } catch (error) { console.error('Failed to add game:', error); } } }; const openBetForm = (gameId, condition = '') => { setNewBetData({ gameId, betAmount: 10, condition, certainty: 50 }); setShowBetForm(prev => ({ ...prev, [gameId]: true })); }; const closeBetForm = (gameId) => { setShowBetForm(prev => ({ ...prev, [gameId]: false })); }; const calculateBetDetails = (amount, certainty, type) => { const platformFee = amount * 0.01; // 1% fee const netCost = amount - platformFee; if (type === 'hedged') { const yesTokens = Math.floor((netCost * (certainty / 100)) / 0.50 * 100); const noTokens = Math.floor((netCost * ((100 - certainty) / 100)) / 0.50 * 100); return { yesTokens, noTokens, platformFee, netCost, actualCost: amount }; } else { const tokens = Math.floor(netCost / 0.50 * 100); return { yesTokens: tokens, noTokens: 0, platformFee, netCost, actualCost: amount }; } }; const prepareBet = () => { const { gameId, betAmount, condition, certainty } = newBetData; if (!condition || betAmount <= 0) return; const userBalance = users[currentUser]?.balance || 0; if (betAmount > userBalance) { alert('Insufficient balance!'); return; } const betDetails = calculateBetDetails(betAmount, certainty, betType); const { marketProbability } = calculateTokenPrices(gameId, condition); setPendingBet({ gameId, condition, certainty, amount: betAmount, betType, ...betDetails, marketProbability }); setShowBetConfirmation(true); }; const confirmBet = async () => { if (!pendingBet) return; try { const betId = 'bet_' + Math.random().toString(36).substr(2, 9); const newBet = { id: betId, game_id: pendingBet.gameId, user_id: currentUser, user_name: users[currentUser].name, amount: pendingBet.amount, condition: pendingBet.condition, certainty: pendingBet.certainty, yes_tokens: pendingBet.yesTokens, no_tokens: pendingBet.noTokens, bet_type: pendingBet.betType, actual_cost: pendingBet.actualCost, platform_fee: pendingBet.platformFee, net_cost: pendingBet.netCost, created_at: new Date().toISOString() }; // Optimistically update UI setBets(prev => ({ ...prev, [pendingBet.gameId]: [...(prev[pendingBet.gameId] || []), newBet] })); // Update user balance const newBalance = users[currentUser].balance - pendingBet.amount; setUsers(prev => ({ ...prev, [currentUser]: { ...prev[currentUser], balance: newBalance } })); // Update platform account setPlatformAccount(prev => ({ balance: prev.balance + pendingBet.platformFee, totalFees: prev.totalFees + pendingBet.platformFee, transactionCount: prev.transactionCount + 1 })); // Save to backend await api.saveBet(pendingBet.gameId, newBet, pendingBet.marketProbability); await api.updateUserBalance(currentUser, newBalance); // Close forms setShowBetConfirmation(false); setPendingBet(null); setShowBetForm({}); setShowExistingBetForm({}); } catch (error) { console.error('Failed to place bet:', error); alert('Failed to place bet. Please try again.'); } }; const placeBetOnExisting = (gameId, condition, position) => { const { yesPrice, noPrice, marketProbability } = calculateTokenPrices(gameId, condition); const certainty = position === 'yes' ? parseFloat(marketProbability) : (100 - parseFloat(marketProbability)); setNewBetData({ gameId, betAmount: existingBetAmount, condition, certainty }); setBetType('non-hedged'); const betDetails = calculateBetDetails(existingBetAmount, certainty, 'non-hedged'); setPendingBet({ gameId, condition, certainty, amount: existingBetAmount, betType: 'non-hedged', position, ...betDetails, marketProbability }); setShowBetConfirmation(true); }; const handleAdminLogin = () => { if (adminPassword === 'mycelium2024') { setIsAdmin(true); setShowAdminLogin(false); setAdminPassword(''); // Update user in database const updatedUser = { ...users[currentUser], is_admin: true }; setUsers(prev => ({ ...prev, [currentUser]: updatedUser })); api.saveUser(updatedUser); } else { alert('Invalid password'); } }; const getUserBets = () => { const userBets = []; Object.entries(bets).forEach(([gameId, gameBets]) => { gameBets.forEach(bet => { if (bet.user_id === currentUser) { const game = games.find(g => g.id === gameId); userBets.push({ ...bet, game }); } }); }); return userBets; }; // Loading state if (loading) { return (
🍄
Connecting to the mycelial network...
Loading from Supabase...
); } // Error state if (error) { return (
🚨
Connection Error
{error}
); } // Not logged in state if (!currentUser || !users[currentUser]) { return (
🍄
Initializing user session...
); } const currentUserData = users[currentUser]; const userBets = getUserBets(); return (
{/* Header */}
🍄

Commons Hub Chess

Mycelial Prediction Network

{!isAdmin && ( )} {isAdmin && ( )}
🟫 {currentUserData.balance?.toFixed(0) || 0}
{currentUserData.name}
{/* Admin Login Modal */} {showAdminLogin && (

Admin Login

setAdminPassword(e.target.value)} className="w-full p-3 bg-gray-800 border border-green-600/30 rounded text-white mb-4" onKeyPress={(e) => e.key === 'Enter' && handleAdminLogin()} />
)} {/* Bet Confirmation Modal */} {showBetConfirmation && pendingBet && (

Confirm Your Bet

Condition: {pendingBet.condition}
Your Certainty: {pendingBet.certainty}%
Market Says: {pendingBet.marketProbability}%
Bet Type: {pendingBet.betType}

Amount: {pendingBet.amount} tokens
Platform Fee (1%): {pendingBet.platformFee.toFixed(2)} tokens
Net Investment: {pendingBet.netCost.toFixed(2)} tokens
{pendingBet.betType === 'hedged' && ( <>
YES Tokens: {pendingBet.yesTokens}
NO Tokens: {pendingBet.noTokens}
)}
)} {/* Dashboard Panel */} {showDashboard && (

Your Dashboard

{currentUserData.balance?.toFixed(0) || 0}
Balance
{userBets.length}
Active Bets
{userBets.reduce((sum, bet) => sum + (bet.amount || 0), 0).toFixed(0)}
Total Wagered
{games.length}
Games
{userBets.length > 0 && (

Your Bets

{userBets.map((bet, idx) => (
{bet.condition} {bet.amount} tokens
{bet.game?.player1} vs {bet.game?.player2} • {bet.certainty}% certainty
))}
)}
)} {/* Online Users Panel */} {showOnlineUsers && (

Network Participants

{Object.values(users).map((user) => (
{user.name}
{user.is_admin && 👑 Admin • } Balance: {user.balance?.toFixed(0) || 0}
))}
)} {/* Admin Panel */} {showAdminPanel && isAdmin && (

Admin Panel

{platformAccount.balance?.toFixed(2) || 0}
Platform Balance
{platformAccount.totalFees?.toFixed(2) || 0}
Total Fees Collected
{platformAccount.transactionCount || 0}
Transactions
{showGameForm && (

New Game

setNewGameData(prev => ({ ...prev, player1: e.target.value }))} className="p-3 bg-gray-700 border border-gray-600 rounded text-white" /> setNewGameData(prev => ({ ...prev, player2: e.target.value }))} className="p-3 bg-gray-700 border border-gray-600 rounded text-white" />
)}
)} {/* Games List */}

Active Games

{games.length} game{games.length !== 1 ? 's' : ''} • {Object.values(bets).flat().length} bet{Object.values(bets).flat().length !== 1 ? 's' : ''}
{games.length === 0 ? (
🎯
No games yet
{isAdmin ? 'Create a game to get started!' : 'Waiting for admin to create games...'}
) : ( games.map((game) => { const gameBets = bets[game.id] || []; const conditions = getUniqueBetConditions(game.id); const isExpanded = expandedGames[game.id]; return (
{/* Game Header */}
toggleGameExpansion(game.id)} className="p-4 cursor-pointer hover:bg-gray-800/30 flex justify-between items-center" >
♟️
{game.player1} vs {game.player2}
{gameBets.length} bet{gameBets.length !== 1 ? 's' : ''} • {conditions.length} market{conditions.length !== 1 ? 's' : ''}
{game.status?.replace('_', ' ')} {isExpanded ? : }
{/* Expanded Game Content */} {isExpanded && (
{/* Default Markets */}

Quick Markets

{[`${game.player1} wins`, `${game.player2} wins`].map((condition) => { const { yesPrice, noPrice, marketProbability } = calculateTokenPrices(game.id, condition); const odds = calculateOdds(game.id, condition); const hasMarket = odds.betCount > 0; return (
{condition} {hasMarket && ( {odds.betCount} bets )}
{/* Quick bet form */} {showExistingBetForm[`${game.id}-${condition}`] && (
setExistingBetAmount(Number(e.target.value))} min="1" max={currentUserData.balance} className="flex-1 p-2 bg-gray-600 rounded text-white text-sm" /> tokens
)} {hasMarket && (
Market: {marketProbability}% • Pool: {odds.totalAmount}
)}
); })}
{/* Custom Conditions */} {conditions.filter(c => !c.includes('wins')).length > 0 && (

Custom Markets

{conditions.filter(c => !c.includes('wins')).map((condition) => { const { yesPrice, noPrice, marketProbability } = calculateTokenPrices(game.id, condition); const odds = calculateOdds(game.id, condition); return (
{condition}
{odds.betCount} bets • {marketProbability}% likely
); })}
)} {/* Create Custom Bet */}
{!showBetForm[game.id] ? ( ) : (

New Prediction

setNewBetData(prev => ({ ...prev, condition: e.target.value }))} className="w-full p-3 bg-gray-700 border border-gray-600 rounded text-white" />
setNewBetData(prev => ({ ...prev, certainty: Number(e.target.value) }))} className="w-full" />
Unlikely 50/50 Very Likely
setNewBetData(prev => ({ ...prev, betAmount: Number(e.target.value) }))} className="w-full p-3 bg-gray-700 border border-gray-600 rounded text-white" />
)}
{/* Recent Bets on this game */} {gameBets.length > 0 && (

Recent Activity

{gameBets.slice(-5).reverse().map((bet, idx) => (
{bet.user_name} bet on "{bet.condition}" {bet.amount} tokens
))}
)}
)}
); }) )}
{/* Footer */}
🍄 Mycelial Prediction Network • 1% Platform Fee
Real-time via Pusher • Data stored in Supabase
); }; export default ChessApp;