698 lines
28 KiB
TypeScript
698 lines
28 KiB
TypeScript
'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, TOKEN_PROGRAM_ID } 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';
|
|
import {
|
|
sendTransactionWithMobileOptimization,
|
|
isMobileDevice,
|
|
getMobileCommitmentLevel,
|
|
validateTransactionForMobile
|
|
} from './utils/mobileWalletUtils';
|
|
|
|
interface DashboardData {
|
|
leaderboard: Array<{ owner: string; score: string }>;
|
|
available_tx: string | null;
|
|
attempts_count:number;
|
|
}
|
|
|
|
export default function Home() {
|
|
const wallet = useWallet();
|
|
const { publicKey, sendTransaction } = wallet;
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
const [txHash, setTxHash] = useState<string | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [hasTicket, setHasTicket] = useState(false);
|
|
const [ticketTxHash, setTicketTxHash] = useState<string | null>(null);
|
|
const [showPaymentSuccess, setShowPaymentSuccess] = useState(false);
|
|
const [highscore, setHighscore] = useState(0);
|
|
const [dashboardData, setDashboardData] = useState<DashboardData>({ leaderboard: [], available_tx: null, attempts_count: 0 });
|
|
const [dashboardLoading, setDashboardLoading] = useState(true);
|
|
const [dashboardError, setDashboardError] = useState<string | null>(null);
|
|
const headerRef = useRef<HeaderRef>(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 () => {
|
|
try {
|
|
setDashboardLoading(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();
|
|
|
|
setHighscore(parseInt(data.leaderboard[0].score));
|
|
setDashboardData(data);
|
|
setDashboardError(null);
|
|
} catch (err) {
|
|
console.error('Error fetching dashboard data:', err);
|
|
setDashboardError(err instanceof Error ? err.message : 'Failed to fetch dashboard data');
|
|
} finally {
|
|
setDashboardLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchDashboardData();
|
|
|
|
// Refresh dashboard data every 5 seconds
|
|
const interval = setInterval(fetchDashboardData, 5000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
const game_close_signal = (status: number) => {
|
|
console.log("game_close_signal", status);
|
|
setHasTicket(false);
|
|
setTicketTxHash(null);
|
|
};
|
|
|
|
// 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;
|
|
}
|
|
|
|
// CRITICAL: Verify wallet connection is stable for mobile wallets
|
|
if (isMobileDevice()) {
|
|
console.log('Mobile device detected - verifying wallet connection...');
|
|
|
|
// Check if wallet is still connected
|
|
if (!wallet.connected || !wallet.publicKey) {
|
|
setError('Wallet connection lost. Please reconnect your wallet and try again.');
|
|
return;
|
|
}
|
|
|
|
// Verify public key matches
|
|
if (!wallet.publicKey.equals(publicKey)) {
|
|
setError('Wallet public key mismatch. Please refresh the page and reconnect your wallet.');
|
|
return;
|
|
}
|
|
|
|
console.log('Mobile wallet connection verified successfully');
|
|
}
|
|
|
|
setIsProcessing(true);
|
|
setError(null);
|
|
setTxHash(null);
|
|
|
|
try {
|
|
const connection = new Connection(CLUSTER_URL, 'confirmed');
|
|
const dinoMint = new PublicKey(DINO_TOKEN_ADDRESS);
|
|
|
|
// Get the user's DINO token account
|
|
const userTokenAccount = await getAssociatedTokenAddress(
|
|
dinoMint,
|
|
publicKey
|
|
);
|
|
|
|
// Check user's token account status and balance
|
|
console.log('Checking user token account status...');
|
|
const userTokenAccountInfo = await connection.getAccountInfo(userTokenAccount);
|
|
if (!userTokenAccountInfo) {
|
|
throw new Error('DINO token account not found. Please ensure you have DINO tokens in your wallet.');
|
|
}
|
|
|
|
// Get token balance
|
|
const userTokenAccounts = await connection.getParsedTokenAccountsByOwner(
|
|
publicKey,
|
|
{ mint: dinoMint }
|
|
);
|
|
|
|
if (userTokenAccounts.value.length === 0) {
|
|
throw new Error('No DINO tokens found in your wallet.');
|
|
}
|
|
|
|
const userBalance = userTokenAccounts.value[0].account.data.parsed.info.tokenAmount.uiAmount;
|
|
console.log('User DINO balance:', userBalance);
|
|
|
|
if (userBalance < ENTRY_FEE_DINO) {
|
|
throw new Error(`Insufficient DINO tokens. You have ${userBalance} DINO, but need ${ENTRY_FEE_DINO} DINO to play.`);
|
|
}
|
|
|
|
// 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) {
|
|
console.log('Creating fee collector token account...');
|
|
const createAccountIx = createAssociatedTokenAccountInstruction(
|
|
publicKey,
|
|
feeCollectorTokenAccount,
|
|
FEE_COLLECTOR,
|
|
dinoMint
|
|
);
|
|
transaction.add(createAccountIx);
|
|
console.log('Added create account instruction');
|
|
}
|
|
|
|
// Add the transfer instruction (10 DINO tokens = 10,000,000,000 lamports for 9 decimals)
|
|
const transferAmount = ENTRY_FEE_DINO * 1_000_000_000; // 10 DINO tokens
|
|
console.log('Adding transfer instruction for', ENTRY_FEE_DINO, 'DINO tokens (', transferAmount, 'lamports)');
|
|
|
|
const transferIx = createTransferInstruction(
|
|
userTokenAccount,
|
|
feeCollectorTokenAccount,
|
|
publicKey,
|
|
transferAmount
|
|
);
|
|
|
|
// CRITICAL: Validate the transfer instruction for mobile wallets
|
|
console.log('Transfer instruction validation:');
|
|
console.log('- From account:', userTokenAccount.toString());
|
|
console.log('- To account:', feeCollectorTokenAccount.toString());
|
|
console.log('- Authority:', publicKey.toString());
|
|
console.log('- Amount (lamports):', transferAmount);
|
|
console.log('- Program ID:', transferIx.programId.toString());
|
|
console.log('- Expected SPL Token Program ID:', TOKEN_PROGRAM_ID.toString());
|
|
|
|
// Ensure the instruction is using the correct SPL Token program
|
|
if (!transferIx.programId.equals(TOKEN_PROGRAM_ID)) {
|
|
throw new Error(`Transfer instruction program ID mismatch. Expected ${TOKEN_PROGRAM_ID.toString()}, got ${transferIx.programId.toString()}`);
|
|
}
|
|
|
|
// Ensure the instruction is properly formatted
|
|
if (!transferIx.keys || transferIx.keys.length === 0) {
|
|
throw new Error('Transfer instruction missing required account keys');
|
|
}
|
|
|
|
// Log all account keys in the instruction
|
|
transferIx.keys.forEach((key, index) => {
|
|
console.log(`- Key ${index}: ${key.pubkey.toString()} (${key.isSigner ? 'signer' : 'non-signer'}, ${key.isWritable ? 'writable' : 'readonly'})`);
|
|
});
|
|
|
|
transaction.add(transferIx);
|
|
console.log('Added transfer instruction');
|
|
|
|
// Get recent blockhash with higher commitment for mobile wallets
|
|
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
|
|
transaction.recentBlockhash = blockhash;
|
|
transaction.lastValidBlockHeight = lastValidBlockHeight;
|
|
transaction.feePayer = publicKey;
|
|
|
|
// CRITICAL: Double-check that all required fields are set for mobile wallets
|
|
if (!transaction.feePayer) {
|
|
throw new Error('Transaction fee payer not set - this will cause signature verification to fail');
|
|
}
|
|
if (!transaction.recentBlockhash) {
|
|
throw new Error('Transaction blockhash not set - this will cause signature verification to fail');
|
|
}
|
|
if (!transaction.lastValidBlockHeight) {
|
|
throw new Error('Transaction lastValidBlockHeight not set - this will cause signature verification to fail');
|
|
}
|
|
|
|
// Ensure the transaction is properly formatted for mobile wallets
|
|
console.log('Transaction details before preflight:');
|
|
console.log('- Recent blockhash:', transaction.recentBlockhash);
|
|
console.log('- Last valid block height:', transaction.lastValidBlockHeight);
|
|
console.log('- Fee payer:', transaction.feePayer?.toString());
|
|
console.log('- Number of instructions:', transaction.instructions.length);
|
|
console.log('- Instructions:', transaction.instructions.map((ix, i) => `${i}: ${ix.programId.toString()}`));
|
|
|
|
// Validate transaction for mobile wallets
|
|
const validationErrors = validateTransactionForMobile(transaction);
|
|
if (validationErrors.length > 0) {
|
|
console.warn('Transaction validation errors:', validationErrors);
|
|
// For mobile wallets, we might want to be more strict
|
|
if (isMobileDevice()) {
|
|
throw new Error(`Transaction validation failed: ${validationErrors.join(', ')}`);
|
|
}
|
|
}
|
|
|
|
// Preflight check for mobile wallets
|
|
try {
|
|
console.log('Running preflight check for transaction...');
|
|
const { value: { err, logs } } = await connection.simulateTransaction(transaction);
|
|
|
|
if (err) {
|
|
console.warn('Preflight check failed:', err);
|
|
console.log('Preflight logs:', logs);
|
|
|
|
// For mobile wallets, we'll try to fix common issues and retry
|
|
if (isMobileDevice()) {
|
|
console.log('Attempting to fix transaction for mobile wallet...');
|
|
|
|
// Refresh blockhash and retry preflight
|
|
const { blockhash: newBlockhash, lastValidBlockHeight: newLastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
|
|
transaction.recentBlockhash = newBlockhash;
|
|
transaction.lastValidBlockHeight = newLastValidBlockHeight;
|
|
|
|
// Retry preflight with fresh blockhash
|
|
const retryResult = await connection.simulateTransaction(transaction);
|
|
if (retryResult.value.err) {
|
|
console.warn('Preflight retry also failed:', retryResult.value.err);
|
|
// On mobile, we might want to proceed anyway as some wallets handle this differently
|
|
console.log('Proceeding with transaction despite preflight failure (mobile wallet behavior)');
|
|
} else {
|
|
console.log('Preflight retry successful!');
|
|
}
|
|
} else {
|
|
// On desktop, be more strict about preflight failures
|
|
throw new Error(`Transaction simulation failed: ${err}`);
|
|
}
|
|
} else {
|
|
console.log('Preflight check passed successfully');
|
|
}
|
|
} catch (simError) {
|
|
console.warn('Preflight simulation failed:', simError);
|
|
|
|
// On mobile, we might want to proceed anyway as some wallets handle this differently
|
|
if (isMobileDevice()) {
|
|
console.log('Proceeding with transaction despite preflight failure (mobile wallet behavior)');
|
|
} else {
|
|
throw new Error('Transaction preflight check failed. Please try again.');
|
|
}
|
|
}
|
|
|
|
// Send the transaction with mobile-optimized options
|
|
let signature: string;
|
|
|
|
try {
|
|
signature = await sendTransactionWithMobileOptimization(
|
|
transaction,
|
|
connection,
|
|
wallet,
|
|
{
|
|
skipPreflight: false, // Try with preflight first
|
|
preflightCommitment: 'confirmed',
|
|
maxRetries: 3,
|
|
commitment: getMobileCommitmentLevel()
|
|
}
|
|
);
|
|
} catch (error) {
|
|
console.warn('Mobile-optimized transaction failed, trying with preflight disabled...');
|
|
|
|
// If mobile-optimized fails, try with preflight disabled for mobile wallets
|
|
if (isMobileDevice()) {
|
|
try {
|
|
// Create a completely fresh transaction with new blockhash
|
|
console.log('Creating fresh transaction for mobile wallet fallback...');
|
|
const freshTransaction = new Transaction();
|
|
|
|
// Re-add all instructions
|
|
transaction.instructions.forEach(instruction => {
|
|
freshTransaction.add(instruction);
|
|
});
|
|
|
|
// Get fresh blockhash
|
|
const { blockhash: freshBlockhash, lastValidBlockHeight: freshLastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
|
|
freshTransaction.recentBlockhash = freshBlockhash;
|
|
freshTransaction.lastValidBlockHeight = freshLastValidBlockHeight;
|
|
freshTransaction.feePayer = publicKey;
|
|
|
|
console.log('Fresh transaction created with new blockhash');
|
|
|
|
signature = await sendTransactionWithMobileOptimization(
|
|
freshTransaction,
|
|
connection,
|
|
wallet,
|
|
{
|
|
skipPreflight: true, // Disable preflight as fallback
|
|
preflightCommitment: 'confirmed',
|
|
maxRetries: 2,
|
|
commitment: getMobileCommitmentLevel()
|
|
}
|
|
);
|
|
} catch (fallbackError) {
|
|
console.error('Fallback transaction also failed:', fallbackError);
|
|
throw fallbackError;
|
|
}
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Remove the old confirmation logic since it's handled in the utility
|
|
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=10000000000&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);
|
|
|
|
// Enhanced error handling for mobile wallets
|
|
let errorMessage = 'Failed to process payment';
|
|
|
|
if (err instanceof Error) {
|
|
// Log additional debugging info for mobile wallets
|
|
if (isMobileDevice()) {
|
|
console.log('Mobile device detected, additional debugging info:');
|
|
console.log('User agent:', navigator.userAgent);
|
|
console.log('Screen size:', window.innerWidth, 'x', window.innerHeight);
|
|
console.log('Wallet connected:', !!publicKey);
|
|
}
|
|
|
|
if (err.message.includes('signature verification failed')) {
|
|
errorMessage = 'Mobile wallet signature verification failed. Please try again or check your wallet settings.';
|
|
} else if (err.message.includes('missing signature')) {
|
|
errorMessage = 'Transaction signature missing. Please approve the transaction in your wallet.';
|
|
} else if (err.message.includes('User rejected')) {
|
|
errorMessage = 'Transaction was rejected. Please try again.';
|
|
} else if (err.message.includes('insufficient funds')) {
|
|
errorMessage = 'Insufficient DINO tokens. Please check your balance.';
|
|
} else if (err.message.includes('Transaction simulation failed')) {
|
|
errorMessage = 'Transaction validation failed. Please check your wallet connection and try again.';
|
|
} else if (err.message.includes('blockhash')) {
|
|
errorMessage = 'Transaction expired. Please try again.';
|
|
} else if (err.message.includes('preflight')) {
|
|
errorMessage = 'Transaction preflight check failed. This usually means there\'s an issue with your wallet connection or token balance. Try the Debug Wallet button to diagnose the issue.';
|
|
} else if (err.message.includes('insufficient lamports')) {
|
|
errorMessage = 'Insufficient SOL for transaction fees. Please ensure you have some SOL in your wallet for gas fees.';
|
|
} else if (err.message.includes('invalid account')) {
|
|
errorMessage = 'Invalid account detected. Please refresh the page and reconnect your wallet.';
|
|
} else {
|
|
errorMessage = err.message;
|
|
}
|
|
}
|
|
|
|
setError(errorMessage);
|
|
} finally {
|
|
setIsProcessing(false);
|
|
}
|
|
};
|
|
|
|
const handleDebugWallet = async () => {
|
|
if (!publicKey) {
|
|
setError('Please connect your wallet first');
|
|
return;
|
|
}
|
|
|
|
console.log('=== WALLET DEBUG INFO ===');
|
|
console.log('Public key:', publicKey.toString());
|
|
console.log('Is mobile device:', isMobileDevice());
|
|
|
|
try {
|
|
const connection = new Connection(CLUSTER_URL, 'confirmed');
|
|
const dinoMint = new PublicKey(DINO_TOKEN_ADDRESS);
|
|
|
|
// Test basic connection
|
|
console.log('Testing Solana connection...');
|
|
const version = await connection.getVersion();
|
|
console.log('Solana version:', version);
|
|
|
|
// Test token account
|
|
console.log('Testing token account...');
|
|
const userTokenAccount = await getAssociatedTokenAddress(dinoMint, publicKey);
|
|
console.log('User token account:', userTokenAccount.toString());
|
|
|
|
const userTokenAccountInfo = await connection.getAccountInfo(userTokenAccount);
|
|
console.log('Token account exists:', !!userTokenAccountInfo);
|
|
|
|
if (userTokenAccountInfo) {
|
|
const userTokenAccounts = await connection.getParsedTokenAccountsByOwner(
|
|
publicKey,
|
|
{ mint: dinoMint }
|
|
);
|
|
|
|
if (userTokenAccounts.value.length > 0) {
|
|
const balance = userTokenAccounts.value[0].account.data.parsed.info.tokenAmount.uiAmount;
|
|
console.log('DINO balance:', balance);
|
|
} else {
|
|
console.log('No DINO tokens found');
|
|
}
|
|
}
|
|
|
|
// Test simple transaction simulation
|
|
console.log('Testing simple transaction simulation...');
|
|
const testTransaction = new Transaction();
|
|
testTransaction.recentBlockhash = (await connection.getLatestBlockhash('confirmed')).blockhash;
|
|
testTransaction.feePayer = publicKey;
|
|
|
|
try {
|
|
const simResult = await connection.simulateTransaction(testTransaction);
|
|
console.log('Simple transaction simulation result:', simResult);
|
|
} catch (simError) {
|
|
console.error('Simple transaction simulation failed:', simError);
|
|
}
|
|
|
|
console.log('=== END DEBUG INFO ===');
|
|
setError('Debug info logged to console. Check browser console for details.');
|
|
|
|
} catch (error) {
|
|
console.error('Debug failed:', error);
|
|
setError(`Debug failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen">
|
|
<Header ref={headerRef} />
|
|
|
|
{/* Game iframe - shown when user has a ticket */}
|
|
{hasTicket && (
|
|
<div className="w-full h-screen flex justify-center items-center bg-white" style={{ marginTop: '80px' }}>
|
|
<iframe
|
|
src={`/Build/index.html?tx=${ticketTxHash}&highscore=${highscore}`}
|
|
className="w-full h-full"
|
|
title="Dino Game"
|
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
allowFullScreen
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Main content - only shown when no ticket */}
|
|
{!hasTicket && (
|
|
<main className="pt-20">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
|
<div className="text-center">
|
|
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
|
Welcome to <span className="text-green-600">$DINO</span>
|
|
</h2>
|
|
<p className="text-xl text-gray-600 max-w-2xl mx-auto mb-8">
|
|
Beat the highscore to win the jackpot!
|
|
</p>
|
|
|
|
|
|
|
|
{/* Enter Button - only show when no ticket */}
|
|
{!hasTicket && (
|
|
<div className="flex flex-col items-center space-y-4">
|
|
<button
|
|
onClick={handleEnterGame}
|
|
disabled={!publicKey || isProcessing}
|
|
className={`px-8 py-4 text-xl font-bold rounded-xl shadow-lg transition-all duration-300 transform hover:scale-105 hover:-translate-y-1 ${
|
|
!publicKey
|
|
? 'bg-gray-400 cursor-not-allowed'
|
|
: isProcessing
|
|
? 'bg-yellow-500 cursor-wait'
|
|
: 'bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700 text-white shadow-green-500/25 hover:shadow-xl hover:shadow-green-500/30'
|
|
}`}
|
|
>
|
|
{!publicKey
|
|
? 'Connect Wallet to Enter'
|
|
: isProcessing
|
|
? 'Processing Payment...'
|
|
: `Enter Game - Pay ${ENTRY_FEE_DINO} $DINO`
|
|
}
|
|
</button>
|
|
|
|
{/* Debug Button */}
|
|
{publicKey && (
|
|
<button
|
|
onClick={handleDebugWallet}
|
|
className="px-6 py-3 text-sm font-medium bg-blue-500 hover:bg-blue-600 text-white rounded-lg transition-all duration-300 hover:scale-105"
|
|
>
|
|
Debug Wallet
|
|
</button>
|
|
)}
|
|
|
|
{/* Status Messages */}
|
|
{error && (
|
|
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-lg max-w-md">
|
|
<p className="text-sm">{error}</p>
|
|
</div>
|
|
)}
|
|
|
|
{txHash && (
|
|
<div
|
|
className={`bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded-lg max-w-md transition-all duration-300 ease-in-out transform ${
|
|
showPaymentSuccess
|
|
? 'opacity-100 scale-100 translate-y-0'
|
|
: 'opacity-0 scale-95 translate-y-2'
|
|
}`}
|
|
>
|
|
<p className="text-sm font-semibold mb-2">Payment Successful!</p>
|
|
<p className="text-xs break-all">Transaction Hash: {txHash}</p>
|
|
<a
|
|
href={`https://explorer.solana.com/tx/${txHash}?cluster=devnet`}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-xs underline hover:text-green-800"
|
|
>
|
|
View on Explorer
|
|
</a>
|
|
</div>
|
|
)}
|
|
|
|
</div>
|
|
)}
|
|
|
|
{/* Leaderboard */}
|
|
<div className="mt-12 max-w-2xl mx-auto">
|
|
<Leaderboard
|
|
leaderboard={dashboardData.leaderboard}
|
|
loading={dashboardLoading}
|
|
error={dashboardError}
|
|
attemptsCount={dashboardData.attempts_count}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|