154 lines
4.5 KiB
TypeScript
154 lines
4.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { usePrivy, useSolanaWallets } from "@privy-io/react-auth";
|
|
import { toast } from "sonner";
|
|
import { games } from "../data/games";
|
|
import { PriceSelection } from "./PriceSelection";
|
|
import { GameSelection } from "./GameSelection";
|
|
import { createBet } from "@/shared/solana_helpers";
|
|
import { Game } from "@/types/Game";
|
|
import { connection, EXPLORER_TX_TEMPLATE } from "@/data/shared";
|
|
import { CONFIRMATION_THRESHOLD } from "@/shared/constants";
|
|
|
|
interface GameModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export default function GameModal({ isOpen, onClose }: GameModalProps) {
|
|
const { wallets } = useSolanaWallets();
|
|
const { authenticated, user } = usePrivy();
|
|
|
|
const [selectedGame, setSelectedGame] = useState<Game | null>(null);
|
|
const [selectedPrice, setSelectedPrice] = useState<number | null>(null);
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
|
|
const [shouldRender, setShouldRender] = useState(isOpen);
|
|
const [animationClass, setAnimationClass] = useState("");
|
|
|
|
useEffect(() => {
|
|
if (isOpen) {
|
|
setShouldRender(true);
|
|
setAnimationClass("modal-enter");
|
|
} else {
|
|
setAnimationClass("modal-exit");
|
|
const timer = setTimeout(() => setShouldRender(false), 200);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [isOpen]);
|
|
|
|
const handleCreateGame = async () => {
|
|
if (!authenticated) {
|
|
toast.error("Please log in with Privy.");
|
|
return;
|
|
}
|
|
|
|
let wallet = wallets[0];
|
|
wallets.forEach((_wallet) => {
|
|
if (_wallet.type === "solana") {
|
|
wallet = _wallet;
|
|
}
|
|
});
|
|
|
|
if (!wallet) {
|
|
toast.error("Please connect your wallet.");
|
|
return;
|
|
}
|
|
|
|
if (!selectedGame || selectedPrice === null) {
|
|
toast.error("Please select a game and a price.");
|
|
return;
|
|
}
|
|
|
|
setIsProcessing(true);
|
|
toast.loading("Creating bet");
|
|
try {
|
|
const tx = await createBet(wallet, user?.id ?? "", selectedPrice, selectedGame.id, false);
|
|
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
|
|
if (tx.length > 5) {
|
|
connection.confirmTransaction(tx, CONFIRMATION_THRESHOLD).finally(()=>{
|
|
toast.dismiss();
|
|
|
|
toast.success(`Bet created successfully!`, {
|
|
action: {
|
|
label: "View TX",
|
|
onClick: () => window.open(url, "_blank"),
|
|
},
|
|
});
|
|
|
|
})
|
|
|
|
}
|
|
onClose();
|
|
} catch (error) {
|
|
console.error("Error creating bet:", error);
|
|
toast.error("Failed to create bet. Please try again.");
|
|
toast.dismiss();
|
|
|
|
} finally {
|
|
setIsProcessing(false);
|
|
|
|
}
|
|
};
|
|
|
|
if (!shouldRender) return null;
|
|
|
|
return (
|
|
<div
|
|
className="fixed inset-0 bg-black/70 flex justify-center items-start pt-10 z-50"
|
|
onClick={onClose}
|
|
>
|
|
<div
|
|
className={`bg-[rgb(30,30,30)] text-white w-full max-w-lg p-6 rounded-2xl shadow-lg ${animationClass}`}
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
{isProcessing ? (
|
|
<div className="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">Creating your bet, please wait...</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<h2 className="text-xl font-bold mb-4">Create Game</h2>
|
|
<GameSelection
|
|
games={games}
|
|
selectedGame={selectedGame}
|
|
onSelect={setSelectedGame}
|
|
/>
|
|
<PriceSelection
|
|
selectedPrice={selectedPrice}
|
|
onSelect={setSelectedPrice}
|
|
/>
|
|
<button
|
|
className="mt-6 w-full py-2 rounded-xl font-semibold bg-[rgb(248,144,22)] text-black hover:bg-white hover:scale-105"
|
|
onClick={handleCreateGame}
|
|
>
|
|
Confirm & Create Bet
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|