diff --git a/Dockerfile b/Dockerfile index 1d6f427..3faee91 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,6 +3,17 @@ FROM node:18-alpine AS builder WORKDIR /app +# Accept build args for Next.js public env vars (baked in at build time) +ARG NEXT_PUBLIC_PUSHER_APP_KEY +ARG NEXT_PUBLIC_PUSHER_CLUSTER +ARG NEXT_PUBLIC_SUPABASE_URL +ARG NEXT_PUBLIC_SUPABASE_ANON_KEY + +ENV NEXT_PUBLIC_PUSHER_APP_KEY=$NEXT_PUBLIC_PUSHER_APP_KEY +ENV NEXT_PUBLIC_PUSHER_CLUSTER=$NEXT_PUBLIC_PUSHER_CLUSTER +ENV NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL +ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=$NEXT_PUBLIC_SUPABASE_ANON_KEY + COPY package*.json ./ RUN npm ci diff --git a/backlog/completed/task-1 - Migrate-bored.jeffemmett.com-and-betting.jeffemmett.com-to-Netcup-hosting.md b/backlog/completed/task-1 - Migrate-bored.jeffemmett.com-and-betting.jeffemmett.com-to-Netcup-hosting.md new file mode 100644 index 0000000..2f1d06a --- /dev/null +++ b/backlog/completed/task-1 - Migrate-bored.jeffemmett.com-and-betting.jeffemmett.com-to-Netcup-hosting.md @@ -0,0 +1,24 @@ +--- +id: task-1 +title: Migrate bored.jeffemmett.com and betting.jeffemmett.com to Netcup hosting +status: Done +assignee: [] +created_date: '2025-12-06 17:22' +labels: [] +dependencies: [] +priority: high +--- + +## Description + + +Migrate both domains from Firebase/Vercel to self-hosted Docker containers on Netcup RS 8000 + +Completed: +- Cloned repos from Gitea +- Created Dockerfiles and docker-compose.yml +- Deployed containers with Traefik labels +- Updated Cloudflare tunnel config +- Updated DNS records to point to tunnel +- Pushed changes to Gitea + diff --git a/components/ChessApp.js b/components/ChessApp.js index be39e55..1f172aa 100644 --- a/components/ChessApp.js +++ b/components/ChessApp.js @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { ChevronDown, ChevronUp, Plus, Settings, Crown } from 'lucide-react'; +import { ChevronDown, ChevronUp, Plus, Settings, Crown, TrendingUp, Users, DollarSign, Target, BarChart3, X } from 'lucide-react'; import { pusherClient, triggerPusherEvent } from '../lib/pusher'; const ChessApp = () => { @@ -14,10 +14,10 @@ const ChessApp = () => { const [showGameForm, setShowGameForm] = useState(false); const [newGameData, setNewGameData] = useState({ player1: '', player2: '' }); const [showBetForm, setShowBetForm] = useState({}); - const [showBetConfirmation, setShowBetConfirmation] = useState({}); + const [showBetConfirmation, setShowBetConfirmation] = useState(false); const [pendingBet, setPendingBet] = useState(null); const [betType, setBetType] = useState('hedged'); - const [newBetData, setNewBetData] = useState({ gameId: '', betAmount: 0, condition: '', certainty: 50 }); + const [newBetData, setNewBetData] = useState({ gameId: '', betAmount: 10, condition: '', certainty: 50 }); const [showDashboard, setShowDashboard] = useState(false); const [showAdminLogin, setShowAdminLogin] = useState(false); const [adminPassword, setAdminPassword] = useState(''); @@ -25,7 +25,8 @@ const ChessApp = () => { const [marketHistory, setMarketHistory] = useState({}); const [selectedWager, setSelectedWager] = useState(null); const [showExistingBetForm, setShowExistingBetForm] = useState({}); - const [showAdvancedAnalysis, setShowAdvancedAnalysis] = useState(false); + const [existingBetAmount, setExistingBetAmount] = useState(10); + const [existingBetPosition, setExistingBetPosition] = useState('yes'); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); @@ -36,7 +37,7 @@ const ChessApp = () => { if (!response.ok) throw new Error('Failed to fetch users'); return response.json(); }, - + async saveUser(user) { const response = await fetch('/api/users', { method: 'POST', @@ -46,7 +47,7 @@ const ChessApp = () => { 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', @@ -56,13 +57,13 @@ const ChessApp = () => { 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', @@ -72,13 +73,13 @@ const ChessApp = () => { 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', @@ -88,13 +89,13 @@ const ChessApp = () => { 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', @@ -111,7 +112,7 @@ const ChessApp = () => { const initializeApp = async () => { try { setError(null); - + // Load all data from backend const [usersData, gamesData, betsData, platformData] = await Promise.all([ api.getUsers(), @@ -125,23 +126,29 @@ const ChessApp = () => { setBets(betsData); setPlatformAccount(platformData); - // Create user if none exists - if (Object.keys(usersData).length === 0) { + // 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, - isAdmin: false + is_admin: false }; - + await api.saveUser(newUser); - setUsers({ [newUserId]: newUser }); + setUsers(prev => ({ ...prev, [newUserId]: newUser })); setCurrentUser(newUserId); - } else { - // Set current user to first user (in production, this would be from auth) - setCurrentUser(Object.keys(usersData)[0]); + if (typeof window !== 'undefined') { + localStorage.setItem('chessUserId', newUserId); + } } setLoading(false); @@ -161,7 +168,6 @@ const ChessApp = () => { const channel = pusherClient.subscribe('chess-tournament'); - // Listen for new games channel.bind('new-game', (data) => { setGames(prev => { const exists = prev.find(g => g.id === data.game.id); @@ -170,22 +176,21 @@ const ChessApp = () => { setExpandedGames(prev => ({ ...prev, [data.game.id]: true })); }); - // Listen for new bets channel.bind('new-bet', (data) => { setBets(prev => ({ ...prev, [data.gameId]: [...(prev[data.gameId] || []), data.bet] })); - // Update market history updateMarketHistory(data.gameId, data.bet.condition, data.marketProbability); }); - // Listen for user updates channel.bind('user-update', (data) => { setUsers(prev => ({ ...prev, [data.userId]: data.user })); + if (data.userId === currentUser) { + // Update local user data + } }); - // Listen for platform account updates channel.bind('platform-update', (data) => { setPlatformAccount(data.platformAccount); }); @@ -214,7 +219,7 @@ const ChessApp = () => { // Update admin status when user changes useEffect(() => { if (currentUser && users[currentUser]) { - setIsAdmin(users[currentUser].isAdmin || false); + setIsAdmin(users[currentUser].is_admin || false); } }, [currentUser, users]); @@ -222,11 +227,11 @@ const ChessApp = () => { 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}`; }; @@ -238,37 +243,33 @@ const ChessApp = () => { timestamp: new Date().toISOString(), probability: parseFloat(probability) }; - - const updatedHistory = [...currentHistory, newEntry].slice(-10); - return { ...prev, - [key]: updatedHistory + [key]: [...currentHistory, newEntry].slice(-10) }; }); }; - // Rest of your calculation functions (unchanged) 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), @@ -279,23 +280,23 @@ const ChessApp = () => { 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, @@ -310,23 +311,11 @@ const ChessApp = () => { return conditions; }; - const updateUserName = async (newName) => { - try { - const updatedUser = { ...users[currentUser], name: newName }; - setUsers(prev => ({ ...prev, [currentUser]: updatedUser })); - await api.saveUser(updatedUser); - } catch (error) { - console.error('Failed to update user name:', error); - } - }; - - const updateUserBalance = async (userId, newBalance) => { - try { - await api.updateUserBalance(userId, newBalance); - // The real-time update will come through Pusher - } catch (error) { - console.error('Failed to update user balance:', error); - } + const toggleGameExpansion = (gameId) => { + setExpandedGames(prev => ({ + ...prev, + [gameId]: !prev[gameId] + })); }; const addGame = async () => { @@ -338,29 +327,181 @@ const ChessApp = () => { player1: newGameData.player1, player2: newGameData.player2, status: 'upcoming', - createdAt: new Date().toISOString() + created_at: new Date().toISOString() }; - - // Optimistically update UI + setGames(prev => [...prev, newGame]); setBets(prev => ({ ...prev, [gameId]: [] })); setExpandedGames(prev => ({ ...prev, [gameId]: true })); setNewGameData({ player1: '', player2: '' }); setShowGameForm(false); - - // Save to backend + await api.saveGame(newGame); } catch (error) { console.error('Failed to add game:', error); - // Revert optimistic update on error - setGames(prev => prev.filter(g => g.id !== newGame.id)); } } }; - // Continue with rest of component logic... - // (I'll create the rest in the next update due to length) + 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 (
@@ -373,6 +514,7 @@ const ChessApp = () => { ); } + // Error state if (error) { return (
@@ -380,8 +522,8 @@ const ChessApp = () => {
🚨
Connection Error
{error}
- + + {!isAdmin && ( + + )} + {isAdmin && ( + + )} +
+
+ 🟫 {currentUserData.balance?.toFixed(0) || 0} +
+
{currentUserData.name}
-
{currentUserData.name}
-
-
-
🚀
-

Backend Successfully Connected!

-

Your app is now running with real backend services:

-
-
-
🔌 Pusher Real-time
-
Live updates between users
-
-
-
🗄️ Supabase Database
-
Persistent data storage
-
-
-
⚡ Vercel Hosting
-
Production deployment
+
+ {/* 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()} + /> +
+ + +
- -
-

👥 {Object.keys(users).length} users connected

-

🎮 {games.length} games created

-

💰 {Object.values(bets).flat().length} bets placed

+ )} + + {/* 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; \ No newline at end of file +export default ChessApp; diff --git a/docker-compose.yml b/docker-compose.yml index 1c8db0f..5ebbce8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,16 @@ services: betting-app: - build: . + build: + context: . + args: + - NEXT_PUBLIC_PUSHER_APP_KEY=f3c4d32f8247f83439e8 + - NEXT_PUBLIC_PUSHER_CLUSTER=us2 + - NEXT_PUBLIC_SUPABASE_URL=https://vzpxtpqpmjmrobsmdxoe.supabase.co + - NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZ6cHh0cHFwbWptcm9ic21keG9lIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTAzNDc2MjMsImV4cCI6MjA2NTkyMzYyM30.B2Z_0hHbJad9gi0qY2RBGRAGc52NjIBehHxG_7_KnDU container_name: betting-app-prod restart: unless-stopped + env_file: + - .env.local labels: - "traefik.enable=true" - "traefik.http.routers.betting-app.rule=Host(`betting.jeffemmett.com`)" diff --git a/env.local b/env.local deleted file mode 100644 index 83e9cd5..0000000 --- a/env.local +++ /dev/null @@ -1,9 +0,0 @@ - # Pusher Configuration - NEXT_PUBLIC_PUSHER_APP_KEY=f3c4d32f8247f83439e8 - NEXT_PUBLIC_PUSHER_CLUSTER=us2 - PUSHER_APP_ID=2010598 - PUSHER_SECRET=3395fb093b110cec7bf2 - - # Supabase Configuration - NEXT_PUBLIC_SUPABASE_URL=https://vzpxtpqpmjmrobsmdxoe.supabase.co - NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZ6cHh0cHFwbWptcm9ic21keG9lIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTAzNDc2MjMsImV4cCI6MjA2NTkyMzYyM30.B2Z_0hHbJad9gi0qY2RBGRAGc52NjIBehHxG_7_KnDU \ No newline at end of file diff --git a/pages/index.js b/pages/index.js index e385cb6..541a697 100644 --- a/pages/index.js +++ b/pages/index.js @@ -22,7 +22,7 @@ export default function Home() { Commons Hub Chess Tournament - +