diff --git a/README.md b/README.md index e215bc4..2c65f03 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,194 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). +# 🦖 Dino Game - Solana Token Website -## Getting Started +A modern, dino-themed website for your Chrome offline dino game, integrated with Solana blockchain for token-based gameplay. -First, run the development server: +## ✨ Features +- **Wallet Integration**: Connect Phantom and other Solana wallets +- **Token Payment**: Pay 1 DINO token to play the game +- **Buy Token Button**: Purchase DINO tokens directly from the website +- **Game Integration**: Seamless iframe integration for your Unity game +- **Dino Theme**: Beautiful green and yellow color scheme matching the game +- **Responsive Design**: Works on all devices + +## 🚀 Getting Started + +### Prerequisites + +- Node.js 20.16.0 or higher +- npm or yarn package manager +- Solana wallet (Phantom recommended) + +### Installation + +1. Clone the repository: ```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev +git clone +cd dino_landing_page ``` -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +2. Install dependencies: +```bash +npm install +``` -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. +3. Start the development server: +```bash +npm run dev +``` -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. +4. Open [http://localhost:3000](http://localhost:3000) in your browser. -## Learn More +## 🔧 Configuration -To learn more about Next.js, take a look at the following resources: +### Update Your Wallet Address -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +In `app/components/GameSection.tsx`, replace the placeholder wallet address: -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! +```typescript +// Replace with your actual wallet address +const RECIPIENT_WALLET = new PublicKey('YOUR_ACTUAL_WALLET_ADDRESS_HERE'); +``` -## Deploy on Vercel +### Customize Token Amount -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. +Currently set to 0.001 SOL for demo purposes. To implement actual DINO token transfers: -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. +1. Create your DINO token on Solana +2. Update the transaction logic in `GameSection.tsx` +3. Use SPL Token Program for token transfers + +### Integrate Your Unity Game + +1. Export your Unity game to WebGL +2. Replace `public/game.html` with your game's `index.html` +3. Ensure your game works in an iframe +4. Test the integration + +## 🎮 How It Works + +1. **Connect Wallet**: Users connect their Solana wallet +2. **Buy Tokens**: Purchase DINO tokens (integrate with your preferred DEX) +3. **Pay to Play**: Send 1 DINO token to your wallet +4. **Game Launch**: Game opens in an iframe after successful payment +5. **Blockchain Integration**: All transactions are recorded on Solana + +## 🎨 Customization + +### Colors + +Update the CSS variables in `app/globals.css`: + +```css +:root { + --dino-green: #059669; + --dino-green-light: #10b981; + --dino-green-dark: #047857; + --dino-yellow: #eab308; + --dino-yellow-light: #fbbf24; + --dino-yellow-dark: #ca8a04; +} +``` + +### Styling + +- Modify Tailwind classes in components +- Update CSS animations and transitions +- Customize button styles and hover effects + +## 🔌 DEX Integration + +To enable actual token purchases, integrate with: + +- **Raydium**: Popular Solana DEX +- **Orca**: User-friendly DEX +- **Jupiter**: Best price aggregator +- **Metaplex**: NFT marketplace + +Example Raydium integration: + +```typescript +// In your buy token function +const raydiumUrl = `https://raydium.io/swap/?inputCurrency=sol&outputCurrency=${DINO_TOKEN_MINT}`; +window.open(raydiumUrl, '_blank'); +``` + +## 🚀 Deployment + +### Vercel (Recommended) + +1. Push your code to GitHub +2. Connect your repository to Vercel +3. Deploy automatically on push + +### Other Platforms + +- **Netlify**: Drag and drop deployment +- **AWS Amplify**: Full-stack deployment +- **Heroku**: Traditional hosting + +## 📱 Mobile Optimization + +The website is fully responsive and includes: + +- Touch-friendly buttons +- Mobile-optimized layouts +- Responsive iframe sizing +- Mobile wallet support + +## 🔒 Security Considerations + +- Always verify wallet connections +- Implement proper transaction validation +- Use HTTPS in production +- Validate iframe sources +- Implement rate limiting + +## 🐛 Troubleshooting + +### Common Issues + +1. **Wallet not connecting**: Ensure wallet extension is installed +2. **Transaction failing**: Check SOL balance for fees +3. **Game not loading**: Verify iframe source and CORS settings +4. **Build errors**: Check Node.js version compatibility + +### Debug Mode + +Enable debug logging in development: + +```typescript +// Add to components for debugging +console.log('Wallet connected:', publicKey?.toString()); +console.log('Transaction status:', transactionStatus); +``` + +## 📚 Resources + +- [Solana Documentation](https://docs.solana.com/) +- [Wallet Adapter](https://github.com/solana-labs/wallet-adapter) +- [Web3.js](https://solana-labs.github.io/solana-web3.js/) +- [Tailwind CSS](https://tailwindcss.com/) + +## 🤝 Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Submit a pull request + +## 📄 License + +This project is licensed under the MIT License - see the LICENSE file for details. + +## 🆘 Support + +For support and questions: + +- Create an issue in the repository +- Contact the development team +- Check the documentation + +--- + +**Happy Gaming! 🦖🎮** diff --git a/app/components/Header.tsx b/app/components/Header.tsx new file mode 100644 index 0000000..fd48042 --- /dev/null +++ b/app/components/Header.tsx @@ -0,0 +1,279 @@ +'use client'; + +import { useState, useEffect, useImperativeHandle, forwardRef } from 'react'; +import { useWallet } from '@solana/wallet-adapter-react'; +import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'; +import { Connection, PublicKey } from '@solana/web3.js'; +import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; +import Image from 'next/image'; +import '@solana/wallet-adapter-react-ui/styles.css'; +import { CLUSTER_URL } from '../shared'; +import { DINO_TOKEN_ADDRESS } from '../constants'; + +export interface HeaderRef { + refreshBalance: () => Promise; +} + +const Header = forwardRef((props, ref) => { + const { publicKey } = useWallet(); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + const [isScrolled, setIsScrolled] = useState(false); + const [dinoBalance, setDinoBalance] = useState(null); + const [isLoadingBalance, setIsLoadingBalance] = useState(false); + + const fetchDinoBalance = async () => { + if (!publicKey) { + setDinoBalance(null); + return; + } + + setIsLoadingBalance(true); + try { + const connection = new Connection(CLUSTER_URL); + const tokenMint = new PublicKey(DINO_TOKEN_ADDRESS); + + // Get token accounts for the user + const tokenAccounts = await connection.getParsedTokenAccountsByOwner( + publicKey, + { mint: tokenMint } + ); + + if (tokenAccounts.value.length > 0) { + const balance = tokenAccounts.value[0].account.data.parsed.info.tokenAmount.uiAmount; + setDinoBalance(balance); + } else { + setDinoBalance(0); + } + } catch (error) { + console.error('Error fetching DINO balance:', error); + setDinoBalance(null); + } finally { + setIsLoadingBalance(false); + } + }; + + // Expose refreshBalance function to parent component + useImperativeHandle(ref, () => ({ + refreshBalance: fetchDinoBalance + })); + + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 10); + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + useEffect(() => { + fetchDinoBalance(); + }, [publicKey]); + + const handleBuyDino = () => { + // Redirect to DexScreener - you can update this URL to the actual $DINO token page + window.open('https://dexscreener.com/solana', '_blank'); + }; + + const formatBalance = (balance: number | null) => { + if (balance === null) return '0'; + if (balance === 0) return '0'; + if (balance < 0.0001) return '< 0.0001'; + return balance.toFixed(4); + }; + + return ( +
+
+
+ {/* Logo and Title */} +
+
+
+ DINO Token Icon +
+

+ DINO +

+
+
+ + {/* Desktop Navigation Links */} + + + {/* Buy $DINO Button and Wallet Connect - Desktop */} +
+ + + {publicKey ? ( +
+ {/* DINO Balance Display */} +
+
+
+ + {isLoadingBalance ? ( +
+
+ Loading... +
+ ) : ( + `${formatBalance(dinoBalance)} $DINO` + )} +
+
+
+ + +
+ ) : ( + + )} +
+ + {/* Mobile Menu Button */} +
+ + + +
+
+ + {/* Mobile Menu */} + {isMobileMenuOpen && ( +
+
+ + Support + + + Whitepaper + +
+ Socials: + + + + + + + + + + +
+ {publicKey && ( +
+
+
+
+ + {isLoadingBalance ? ( +
+
+ Loading... +
+ ) : ( + `${formatBalance(dinoBalance)} $DINO` + )} +
+
+
+
+ )} +
+
+ )} +
+
+ ); +}); + +export default Header; diff --git a/app/components/Leaderboard.tsx b/app/components/Leaderboard.tsx new file mode 100644 index 0000000..3209528 --- /dev/null +++ b/app/components/Leaderboard.tsx @@ -0,0 +1,166 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { ENTRY_FEE_DINO, MAX_ATTEMPTS } from '../shared'; + +interface LeaderboardEntry { + owner: string; + score: string; +} + +interface LeaderboardProps { + leaderboard: LeaderboardEntry[]; + loading: boolean; + error: string | null; + attemptsCount: number; +} + +export default function Leaderboard({ leaderboard, loading, error, attemptsCount }: LeaderboardProps) { + + // Only show loading state if we have no data yet + if (loading && leaderboard.length === 0) { + return ( +
+

🏆 Leaderboard

+
+
+
+
+ ); + } + + if (error) { + return ( +
+

🏆 Leaderboard

+
+

Failed to load leaderboard

+ +
+
+ ); + } + + // Sort leaderboard by score (highest first) + const sortedLeaderboard = [...leaderboard].sort((a, b) => + parseInt(b.score) - parseInt(a.score) + ); + + return ( +
+

🏆 Leaderboard

+ + {/* Combined Prize and Attempts Information */} +
+
+

💰 Prize ({MAX_ATTEMPTS * ENTRY_FEE_DINO} DINO Total)

+
+
🎯 Attempts Remaining:
+
{MAX_ATTEMPTS - attemptsCount}
+
+
+
+
+ 🥇 1st Place: + {(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.8).toFixed(1)} DINO (80%) +
+
+ 🥈 2nd Place: + {(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.1).toFixed(1)} DINO (10%) +
+
+ 🥉 3rd Place: + {(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.05).toFixed(1)} DINO (5%) +
+
+
+

Leaderboard resets after {MAX_ATTEMPTS} attempts

+
+
+ + {sortedLeaderboard.length === 0 ? ( +
+

No scores yet. Be the first to play!

+
+ ) : ( +
+ {sortedLeaderboard.map((entry, index) => ( +
+
+
+ {index + 1} +
+
+

+ {entry.owner.length > 20 + ? `${entry.owner.slice(0, 8)}...${entry.owner.slice(-8)}` + : entry.owner + } +

+ {entry.owner.length > 20 && ( +

+ {entry.owner} +

+ )} + {/* Prize indicators for top 3 */} + {index < 3 && ( +
+ + {index === 0 + ? `🥇 ${(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.8).toFixed(1)} DINO Prize` + : index === 1 + ? `🥈 ${(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.1).toFixed(1)} DINO Prize` + : `🥉 ${(MAX_ATTEMPTS * ENTRY_FEE_DINO * 0.05).toFixed(1)} DINO Prize` + } + +
+ )} +
+
+
+

{entry.score}

+

points

+
+
+ ))} +
+ )} + +
+
+

+ Updates every 5 seconds • {sortedLeaderboard.length} player{sortedLeaderboard.length !== 1 ? 's' : ''} +

+ {loading && leaderboard.length > 0 && ( +
+
+ Refreshing... +
+ )} +
+
+
+ ); +} diff --git a/app/components/WalletProvider.tsx b/app/components/WalletProvider.tsx new file mode 100644 index 0000000..8f397eb --- /dev/null +++ b/app/components/WalletProvider.tsx @@ -0,0 +1,41 @@ +'use client'; + +import { WalletAdapterNetwork } from '@solana/wallet-adapter-base'; +import { ConnectionProvider, WalletProvider as SolanaWalletProvider } from '@solana/wallet-adapter-react'; +import { WalletModalProvider } from '@solana/wallet-adapter-react-ui'; +import { PhantomWalletAdapter, SolflareWalletAdapter } from '@solana/wallet-adapter-wallets'; +import { clusterApiUrl } from '@solana/web3.js'; +import { useMemo } from 'react'; + +// Import wallet adapter CSS +import '@solana/wallet-adapter-react-ui/styles.css'; + +interface WalletProviderProps { + children: React.ReactNode; +} + +export default function WalletProvider({ children }: WalletProviderProps) { + // The network can be set to 'devnet', 'testnet', or 'mainnet-beta'. + const network = WalletAdapterNetwork.Devnet; + + // You can also provide a custom RPC endpoint. + const endpoint = useMemo(() => clusterApiUrl(network), [network]); + + const wallets = useMemo( + () => [ + new PhantomWalletAdapter(), + new SolflareWalletAdapter(), + ], + [] + ); + + return ( + + + + {children} + + + + ); +} diff --git a/app/constants.ts b/app/constants.ts new file mode 100644 index 0000000..96a16fe --- /dev/null +++ b/app/constants.ts @@ -0,0 +1,5 @@ +import { PublicKey } from "@solana/web3.js"; + +export const DINO_TOKEN_ADDRESS = "diNoZ1L9UEiJMv8i43BjZoPEe2tywwaBTBnKQYf28FT"; +export const FEE_COLLECTOR = new PublicKey("cocD4r4yNpHxPq7CzUebxEMyLki3X4d2Y3HcTX5ptUc"); + \ No newline at end of file diff --git a/app/favicon.ico b/app/favicon.ico index 718d6fe..3a93cc4 100644 Binary files a/app/favicon.ico and b/app/favicon.ico differ diff --git a/app/globals.css b/app/globals.css index a2dc41e..ee47795 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,26 +1,50 @@ -@import "tailwindcss"; +@tailwind base; +@tailwind components; +@tailwind utilities; -:root { - --background: #ffffff; - --foreground: #171717; -} - -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; +/* Custom base styles */ +@layer base { + html { + scroll-behavior: smooth; + } + + body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } } -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; +/* Custom component styles */ +@layer components { + .animate-in { + animation: fadeIn 0.3s ease-in-out; + } + + .slide-in-from-top-2 { + animation: slideInFromTop 0.3s ease-out; + } +} + +/* Custom animations */ +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes slideInFromTop { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } } diff --git a/app/layout.tsx b/app/layout.tsx index f7fa87e..bc4bbf2 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,20 +1,10 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); +import WalletProvider from "./components/WalletProvider"; export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "DINO - Solana Token", + description: "DINO token on Solana blockchain", }; export default function RootLayout({ @@ -24,10 +14,10 @@ export default function RootLayout({ }>) { return ( - - {children} + + + {children} + ); diff --git a/app/page.tsx b/app/page.tsx index 21b686d..a13fdf9 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,103 +1,385 @@ -import Image from "next/image"; +'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() { - return ( -
-
- Next.js logo -
    -
  1. - Get started by editing{" "} - - app/page.tsx - - . -
  2. -
  3. - Save and see your changes instantly. -
  4. -
+ 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 headerRef = useRef(null); -
- - Vercel logomark { + 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(); + 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; + } + + 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 */} + {hasTicket && ( +
+