Premium Jackpot is an hourly high-stakes lottery where players compete for a shared prize pool. Every hour on the hour, a winner is randomly selected based on their contribution to the pool.
Draws happen every hour at :00
Minimum bet: 100 coins
Higher bets = higher chance to win
House fee: 3.2%
)}
{activeTab === 'how' && (
How to Play
Place Your Bet - Enter the amount you want to bet (minimum 100 coins)
Wait for the Draw - Watch the countdown timer
Winner Selection - A random winner is picked at :00
Collect Winnings - If you win, the prize is automatically credited
Your winning chance = Your Bet รท Total Pool ร 100%
)}
{activeTab === 'prizes' && (
Prize Distribution
Winner receives the entire prize pool minus 3.2% house fee
XP rewards based on participation and wins
Special bonuses during Rush Hour events
)}
{activeTab === 'bonuses' && (
Hourly Bonuses
Each hour features a random bonus that enhances the winner's reward:
);
}
// ============================================
// MAIN APP COMPONENT
// ============================================
function PremiumJackpotApp() {
const [user, setUser] = useState(null);
const [round, setRound] = useState(null);
const [history, setHistory] = useState([]);
const [isConnected, setIsConnected] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [showRules, setShowRules] = useState(false);
const [showDailyModal, setShowDailyModal] = useState(false);
const [winner, setWinner] = useState(null);
const [showAuthModal, setShowAuthModal] = useState(false);
const [authMode, setAuthMode] = useState('login');
const wsRef = useRef(null);
const winnerRef = useRef(null);
const userRef = useRef(null);
// Keep refs in sync
useEffect(() => {
winnerRef.current = winner;
}, [winner]);
useEffect(() => {
userRef.current = user;
}, [user]);
// Expose functions to window for navbar
useEffect(() => {
window.showPremiumRules = () => setShowRules(true);
window.dailyRewardsModal = {
open: () => setShowDailyModal(true),
close: () => setShowDailyModal(false)
};
window.openAuthModal = (mode = 'login') => {
setAuthMode(mode);
setShowAuthModal(true);
};
return () => {
delete window.showPremiumRules;
delete window.dailyRewardsModal;
delete window.openAuthModal;
};
}, []);
// Fetch user on mount
useEffect(() => {
const fetchUser = async () => {
try {
const res = await fetch('/me', { credentials: 'include' });
if (res.ok) {
const data = await res.json();
setUser(data);
}
} catch (e) {
console.error('Failed to fetch user:', e);
}
};
fetchUser();
}, []);
// Fetch round data
const fetchRound = useCallback(async () => {
try {
const res = await fetch('/premium-jackpot/current', { credentials: 'include' });
if (res.ok) {
const data = await res.json();
setRound(data);
}
} catch (e) {
console.error('Failed to fetch round:', e);
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
fetchRound();
const interval = setInterval(fetchRound, CONFIG.refreshInterval);
return () => clearInterval(interval);
}, [fetchRound]);
// Fetch history
useEffect(() => {
const fetchHistory = async () => {
try {
const res = await fetch('/premium-jackpot/history', { credentials: 'include' });
if (res.ok) {
const data = await res.json();
setHistory(data);
}
} catch (e) {
console.error('Failed to fetch history:', e);
}
};
fetchHistory();
}, []);
// WebSocket connection
useEffect(() => {
const connect = () => {
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
const ws = new WebSocket(`${protocol}://${window.location.host}/ws/lobby`);
ws.onopen = () => {
console.log('๐ Premium Jackpot WS connected');
setIsConnected(true);
};
ws.onerror = (error) => {
console.error('โ Premium Jackpot WS error:', error);
};
ws.onclose = () => {
console.log('๐ Premium Jackpot WS disconnected');
setIsConnected(false);
setTimeout(connect, CONFIG.wsReconnectDelay);
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
handleWSMessage(data);
} catch (e) {
console.error('WS message error:', e);
}
};
wsRef.current = ws;
};
connect();
return () => {
if (wsRef.current) {
wsRef.current.close();
}
};
}, []);
const handleWSMessage = (data) => {
console.log('๐ฅ Premium Jackpot WS:', data.type, data);
switch (data.type) {
case 'premium_jp_deposit':
// Someone deposited - refresh round data
fetchRound();
break;
case 'premium_jp_winner':
// Winner announced! Set winner state
console.log('[PJ] Winner announced:', data.winner, 'Prize:', data.prize);
setWinner({ name: data.winner, prize: data.prize });
// Refresh round and history after a delay (but winner display stays)
setTimeout(() => {
console.log('[PJ] Refreshing round data (winner still displayed)');
fetchRound();
}, 3000);
setTimeout(() => {
fetch('/premium-jackpot/history', { credentials: 'include' })
.then(r => r.ok ? r.json() : [])
.then(setHistory);
}, 4000);
// Clear winner after 55 seconds so it shows until ~59:05
setTimeout(() => {
console.log('[PJ] Clearing winner display after 55s');
setWinner(null);
}, 55000);
break;
case 'premium_jp_new_round':
// New round started - DON'T clear winner here, let the timeout handle it
console.log('[PJ] New round started (keeping winner if exists)');
fetchRound();
break;
case 'premium_jp_status':
fetchRound();
break;
case 'balance_update':
// LIVE BALANCE UPDATE - use ref to get current user
const currentUser = userRef.current;
if (currentUser && data.username === currentUser.username) {
const oldBalance = currentUser.balance || 0;
const newBalance = data.balance_raw || data.balance; // Support both formats
const delta = newBalance - oldBalance;
console.log('[PJ] Balance update:', oldBalance, '->', newBalance, 'Delta:', delta);
// Update local user state immediately
setUser(prev => prev ? { ...prev, balance: newBalance } : prev);
// Trigger navbar balance animation
if (window.updateCoinRushBalance) {
window.updateCoinRushBalance(newBalance, delta);
}
}
break;
}
};
const handleDeposit = async (amount) => {
const res = await fetch('/premium-jackpot/deposit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ amount: parseFloat(amount) })
});
if (!res.ok) {
const error = await res.json().catch(() => ({ detail: 'Failed to place bet' }));
throw new Error(error.detail || 'Failed to place bet');
}
const data = await res.json();
setRound(data);
// Update user balance with floating animation
const newBalance = (user?.balance || 0) - amount * 100;
setUser(prev => prev ? { ...prev, balance: newBalance } : prev);
if (window.updateCoinRushBalance) {
window.updateCoinRushBalance(newBalance, -amount * 100);
}
// Refresh user data
fetch('/me', { credentials: 'include' })
.then(r => r.ok ? r.json() : null)
.then(data => { if (data) setUser(data); });
};
const handleLogout = async () => {
try {
await fetch('/auth/logout', { method: 'POST', credentials: 'include' });
} finally {
setUser(null);
window.location.reload();
}
};
// Only lock when resolved - scheduled, open, and countdown all allow betting
const isLocked = round?.status === 'resolved';
return (
setShowRules(false)} />
setShowDailyModal(false)}
user={user}
onClaim={() => {
fetch('/me', { credentials: 'include' })
.then(res => res.ok ? res.json() : null)
.then(data => { if (data) setUser(data); });
}}
/>
{/* Auth Modal */}
{typeof AuthModal !== 'undefined' && (
setShowAuthModal(false)}
initialMode={authMode}
onSuccess={(userData) => {
setUser(userData);
setShowAuthModal(false);
}}
/>
)}
{/* Winner is now shown inline in JackpotHeroDisplay, not as overlay */}
{/* Sliding History Bar at Top */}
{isLoading ? (
{Icons.spinner}
Loading Premium Jackpot...
) : (
{/* Left Column - Bet Panel + Participants */}
{/* Center - Main Display */}
{
console.log('[PJ] Timer ended, waiting for winner...');
}}
onWinnerDismiss={() => setWinner(null)}
/>
{/* Right Column - Chat Only (stretched) */}