167 lines
6.0 KiB
TypeScript
167 lines
6.0 KiB
TypeScript
"use client";
|
|
import { useEffect, useState } from "react";
|
|
import Image from "next/image";
|
|
import { games } from "../data/games";
|
|
import { FiTrash } from "react-icons/fi";
|
|
import { usePrivy, useSolanaWallets } from "@privy-io/react-auth";
|
|
import { closeBet } from "@/shared/solana_helpers";
|
|
import { Bet } from "../types/Bet";
|
|
import { toast } from "sonner";
|
|
import { connection, EXPLORER_TX_TEMPLATE } from "@/data/shared";
|
|
import { CONFIRMATION_THRESHOLD } from "@/shared/constants";
|
|
interface GameModalProps {
|
|
bets: Bet[];
|
|
}
|
|
export default function YourGames({bets}:GameModalProps) {
|
|
const {user} = usePrivy();
|
|
const { wallets, ready } = useSolanaWallets();
|
|
const [myBets, setMyBets] = useState<Bet[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [selectedBet, setSelectedBet] = useState<Bet | null>(null);
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
|
|
// Fetch bets
|
|
const updateBets = async () => {
|
|
if(ready && user){
|
|
let wallet = wallets[0];
|
|
wallets.forEach((_wallet) => {
|
|
if (wallet.type === "solana") {
|
|
wallet = _wallet;
|
|
}
|
|
});
|
|
setMyBets(bets.filter((bet) => bet.owner === wallet.address));
|
|
|
|
}
|
|
setLoading(false);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!ready) return;
|
|
updateBets();
|
|
const interval = setInterval(updateBets, 10000);
|
|
return () => clearInterval(interval);
|
|
}, [bets, ready]);
|
|
|
|
// Handle bet closing
|
|
const handleCloseBet = async () => {
|
|
if (!selectedBet) return;
|
|
setIsProcessing(true);
|
|
toast.loading("Closing bet");
|
|
let wallet = wallets[0];
|
|
wallets.forEach((_wallet) => {
|
|
if (wallet.type === "solana") {
|
|
wallet = _wallet;
|
|
}
|
|
});
|
|
try {
|
|
const tx = await closeBet(wallet, selectedBet.id, user?.id ?? "na");
|
|
|
|
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
|
|
connection.confirmTransaction(tx, CONFIRMATION_THRESHOLD).finally(()=>{
|
|
toast.dismiss();
|
|
|
|
toast.success(`Closed the bet successfully!`, {
|
|
action: {
|
|
label: "View TX",
|
|
onClick: () => window.open(url, "_blank"),
|
|
},
|
|
});
|
|
updateBets();
|
|
})
|
|
|
|
} catch (error) {
|
|
toast.dismiss();
|
|
|
|
console.error("Error closing bet:", error);
|
|
toast.message("Failed to close the bet. Please try again.");
|
|
} finally {
|
|
setIsProcessing(false);
|
|
setSelectedBet(null);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<section className="py-16 px-6">
|
|
|
|
|
|
{loading ? (
|
|
<p className="text-gray-400">Loading...</p>
|
|
) : !user || !ready || myBets.length === 0 ? (
|
|
<></>
|
|
) : (
|
|
<><h2 className="text-3xl font-bold text-white mb-6">Your Game</h2>
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
{myBets.map((bet) => {
|
|
console.log(`Bet id:${bet.id}`);
|
|
const game = games.find((g) => g.id === bet.id);
|
|
if (!game) return null;
|
|
|
|
return (
|
|
<div key={bet.address} className="relative group bg-[rgb(30,30,30)] rounded-2xl p-2 w-50 overflow-hidden cursor-pointer transition-all duration-300">
|
|
{/* Game Thumbnail */}
|
|
<div className="relative w-full h-60 overflow-hidden rounded-xl">
|
|
<Image
|
|
src={game.thumbnail}
|
|
alt={game.name}
|
|
layout="fill"
|
|
objectFit="cover"
|
|
className="transition-transform duration-300 group-hover:scale-110"
|
|
/>
|
|
</div>
|
|
|
|
{/* Game Info */}
|
|
<div className="mt-4 px-3 text-left">
|
|
<h3 className="text-lg font-semibold text-white py-2">{game.name}</h3>
|
|
<p className="text-xs text-gray-400 font-mono py-1">Entry</p>
|
|
<p className="text-xs text-white font-mono py-1">{bet.wager} SOL</p>
|
|
</div>
|
|
|
|
{/* Close Button */}
|
|
<button
|
|
className="absolute bottom-3 right-3 bg-red-600 text-white p-2 rounded-full transition duration-300 hover:bg-red-800"
|
|
onClick={() => setSelectedBet(bet)}
|
|
>
|
|
<FiTrash size={16} />
|
|
</button>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* Confirmation Modal */}
|
|
{selectedBet && !isProcessing && (
|
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
<div className="bg-[rgb(30,30,30)] text-white p-6 rounded-lg shadow-lg w-96">
|
|
<h3 className="text-lg font-semibold mb-4">Close Game</h3>
|
|
<p className="mb-6">Are you sure you want to close this Game? You will receive your refund back to your wallet.</p>
|
|
<div className="flex justify-end space-x-4">
|
|
<button className="px-4 py-2 bg-gray-600 rounded hover:bg-gray-700 hover:scale-105 transition-all duration-300" onClick={() => setSelectedBet(null)}>
|
|
Cancel
|
|
</button>
|
|
<button className="px-4 py-2 bg-red-600 rounded hover:bg-red-700 hover:scale-105 transition-all duration-300" onClick={handleCloseBet}>
|
|
Close Game
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Processing Modal */}
|
|
{isProcessing && (
|
|
<div className="fixed inset-0 bg-black/30 flex items-center justify-center z-50">
|
|
<div className="bg-gray-900 text-white p-6 rounded-lg shadow-lg w-96 flex flex-col items-center">
|
|
<svg className="animate-spin h-8 w-8 text-blue-400 mb-4" viewBox="0 0 24 24" fill="none">
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path>
|
|
</svg>
|
|
<h3 className="text-lg font-semibold">Processing...</h3>
|
|
<p className="text-gray-400 text-sm mt-2">Please wait while we close your Game.</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|