'use client'; import { useState, useRef, useEffect } from 'react'; import { useWallet } from '@solana/wallet-adapter-react'; import { Connection, PublicKey, Transaction } from '@solana/web3.js'; import {createTransferInstruction, getAssociatedTokenAddress, createAssociatedTokenAccountInstruction } from '@solana/spl-token'; import Header, { HeaderRef } from './components/Header'; import Leaderboard from './components/Leaderboard'; import { CLUSTER_URL, ENTRY_FEE_DINO } from './shared'; import { DINO_TOKEN_ADDRESS, FEE_COLLECTOR } from './constants'; interface DashboardData { leaderboard: Array<{ owner: string; score: string }>; available_tx: string | null; attempts_count:number; } export default function Home() { const { publicKey, sendTransaction } = useWallet(); const [isProcessing, setIsProcessing] = useState(false); const [txHash, setTxHash] = useState(null); const [error, setError] = useState(null); const [hasTicket, setHasTicket] = useState(false); const [ticketTxHash, setTicketTxHash] = useState(null); const [showPaymentSuccess, setShowPaymentSuccess] = useState(false); const [highscore, setHighscore] = useState(0); const [dashboardData, setDashboardData] = useState({ leaderboard: [], available_tx: null, attempts_count: 0 }); const [dashboardLoading, setDashboardLoading] = useState(true); const [dashboardError, setDashboardError] = useState(null); const [isRefreshing, setIsRefreshing] = useState(false); const [refreshCounter, setRefreshCounter] = useState(0); const [isPracticeMode, setIsPracticeMode] = useState(false); const headerRef = useRef(null); // Poll for available tickets every 5 seconds (only when no ticket is available) useEffect(() => { if (!publicKey) { setHasTicket(false); return; } // Don't poll if we already have a ticket if (hasTicket) { return; } const checkTicket = async () => { try { const response = await fetch(`https://vps.playpoolstudios.com/dino/api/get_available_tx.php?owner=${publicKey.toString()}`); if (response.ok) { const ticketData = await response.text(); // If we get a valid transaction hash (not "0"), we have a ticket const ticketAvailable = ticketData !== "0" && ticketData.trim().length > 0; setHasTicket(ticketAvailable); // If ticket becomes available, stop polling immediately if (ticketAvailable) { setTicketTxHash(ticketData); return; } setTicketTxHash(null); } else { setHasTicket(false); setTicketTxHash(null); } } catch (error) { console.error('Error checking ticket:', error); setHasTicket(false); } finally { } }; // Check immediately checkTicket(); // Set up polling every 5 seconds const interval = setInterval(checkTicket, 5000); // Cleanup interval on unmount, when publicKey changes, or when ticket becomes available return () => clearInterval(interval); }, [publicKey, hasTicket]); useEffect(() => { const handleMessage = (event: MessageEvent) => { if (event.origin === window.location.origin && typeof event.data === "string") { try { const message = JSON.parse(event.data); if (message?.type === "gameClose" && typeof message.status === "number") { game_close_signal(message.status); } } catch (error) { console.error("JSON parse error from Unity message:", error); } } }; window.addEventListener("message", handleMessage); return () => window.removeEventListener("message", handleMessage); }, []); // Fetch dashboard data every 5 seconds useEffect(() => { const fetchDashboardData = async (isInitialFetch = false) => { try { // Only show loading state on initial fetch, not on subsequent polls if (isInitialFetch) { setDashboardLoading(true); } else { // Show refreshing state for background updates setIsRefreshing(true); } const response = await fetch('https://vps.playpoolstudios.com/dino/api/get_dashboard_data.php'); if (!response.ok) { throw new Error('Failed to fetch dashboard data'); } const data: DashboardData = await response.json(); setDashboardData(data); setDashboardError(null); // Set highscore from first player in leaderboard if available if (data.leaderboard && data.leaderboard.length > 0) { const firstPlayerScore = parseInt(data.leaderboard[0].score); if (!isNaN(firstPlayerScore)) { setHighscore(firstPlayerScore); } } // Increment refresh counter for smooth updates if (!isInitialFetch) { setRefreshCounter(prev => prev + 1); } } catch (err) { console.error('Error fetching dashboard data:', err); setDashboardError(err instanceof Error ? err.message : 'Failed to fetch dashboard data'); } finally { if (isInitialFetch) { setDashboardLoading(false); } else { // Hide refreshing state after a short delay for smooth UX setTimeout(() => setIsRefreshing(false), 500); } } }; // Initial fetch with loading state fetchDashboardData(true); // Refresh dashboard data every 5 seconds without loading state const interval = setInterval(() => fetchDashboardData(false), 5000); return () => clearInterval(interval); }, []); const game_close_signal = (status: number) => { console.log("game_close_signal", status); setHasTicket(false); setTicketTxHash(null); setIsPracticeMode(false); }; // Auto-hide payment success panel after 5 seconds useEffect(() => { console.log('useEffect triggered:', { txHash, showPaymentSuccess }); if (txHash && !showPaymentSuccess) { console.log('Starting progress animation'); setShowPaymentSuccess(true); console.log('showPaymentSuccess set to true'); } else { console.log('Condition not met:', { txHash: !!txHash, showPaymentSuccess }); } }, [txHash, showPaymentSuccess]); // Test useEffect useEffect(() => { console.log('Test useEffect - component mounted'); }, []); const handleEnterGame = async () => { if (!publicKey) { setError('Please connect your wallet first'); return; } setIsProcessing(true); setError(null); setTxHash(null); try { const connection = new Connection(CLUSTER_URL); const dinoMint = new PublicKey(DINO_TOKEN_ADDRESS); // Get the user's DINO token account const userTokenAccount = await getAssociatedTokenAddress( dinoMint, publicKey ); // Get or create the fee collector's DINO token account const feeCollectorTokenAccount = await getAssociatedTokenAddress( dinoMint, FEE_COLLECTOR ); const transaction = new Transaction(); // Check if fee collector token account exists, if not create it const feeCollectorAccountInfo = await connection.getAccountInfo(feeCollectorTokenAccount); if (!feeCollectorAccountInfo) { transaction.add( createAssociatedTokenAccountInstruction( publicKey, feeCollectorTokenAccount, FEE_COLLECTOR, dinoMint ) ); } // Add the transfer instruction (1 DINO token = 1,000,000,000 lamports for 9 decimals) const transferAmount = ENTRY_FEE_DINO * 1_000_000_000; // 1 DINO token transaction.add( createTransferInstruction( userTokenAccount, feeCollectorTokenAccount, publicKey, transferAmount ) ); // Get recent blockhash const { blockhash } = await connection.getLatestBlockhash(); transaction.recentBlockhash = blockhash; transaction.feePayer = publicKey; // Send the transaction const signature = await sendTransaction(transaction, connection); // Wait for confirmation const confirmation = await connection.confirmTransaction(signature, 'confirmed'); if (confirmation.value.err) { throw new Error('Transaction failed'); } setTxHash(signature); console.log('Transaction successful! Hash:', signature); console.log('txHash state set to:', signature); // Refresh the dino balance in header after successful payment if (headerRef.current) { await headerRef.current.refreshBalance(); } // Immediately check for ticket availability after successful payment try { const ticketResponse = await fetch(`https://vps.playpoolstudios.com/dino/api/get_available_tx.php?owner=${publicKey.toString()}`); if (ticketResponse.ok) { const ticketData = await ticketResponse.text(); const ticketAvailable = ticketData !== "0" && ticketData.trim().length > 0; setHasTicket(ticketAvailable); } } catch (ticketError) { console.warn('Error checking ticket after payment:', ticketError); } // Send transaction to validator try { const validatorUrl = `https://solpay.playpoolstudios.com/tx/new?tx=${signature}&target_address=${FEE_COLLECTOR.toString()}&amount=1000000000&sender_address=${publicKey.toString()}&token_mint=${dinoMint.toString()}`; // Add leaderboard entry const leaderboardUrl = 'https://vps.playpoolstudios.com/dino/api/add_leaderboard.php'; const leaderboardData = new FormData(); leaderboardData.append('tx', signature); leaderboardData.append('owner', publicKey.toString()); leaderboardData.append('score', '0'); // Initial score of 0 try { const leaderboardResponse = await fetch(leaderboardUrl, { method: 'POST', body: leaderboardData }); if (!leaderboardResponse.ok) { console.warn('Failed to add leaderboard entry'); } } catch (leaderboardError) { console.warn('Error adding leaderboard entry:', leaderboardError); } const response = await fetch(validatorUrl); if (response.ok) { const result = await response.json(); console.log('Transaction validated:', result); } else { console.warn('Failed to validate transaction with server'); } } catch (validationError) { console.warn('Error validating transaction with server:', validationError); } } catch (err) { console.error('Error processing payment:', err); setError(err instanceof Error ? err.message : 'Failed to process payment'); } finally { setIsProcessing(false); } }; return (
{/* Game iframe - shown when user has a ticket or in practice mode */} {(hasTicket || isPracticeMode) && (