duelfi_web/src/components/HeroSection.tsx
2025-07-30 14:53:01 +05:30

358 lines
12 KiB
TypeScript

/* eslint-disable react-hooks/exhaustive-deps */
"use client";
import { useEffect, useState, useRef } from "react";
// import Image from "next/image";
import OpenGames from "./OpenGames";
import GameModal from "./GameModal";
import { HowItWorksModal } from "./HowItWorksModal";
import { FirstVisitModal } from "./FirstVisitModal";
import YourGames from "./YourGames";
import { Bet } from "@/types/Bet";
import { fetchOpenBets, createBet, getVaultByAddress, fetchOpenBetsDev } from "@/shared/solana_helpers";
import { ConnectedSolanaWallet, usePrivy, useSolanaWallets } from "@privy-io/react-auth";
import { RematchModal } from "./RematchModal";
import { API_URL, CLUSTER_URL } from '../data/shared';
import { clusterApiUrl } from "@solana/web3.js";
import { getCurrencyByMint } from "@/data/currencies";
import { usePracticeGame } from "@/contexts/PracticeGameContext";
export default function HeroSection() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isGameModalOpen, setIsGameModalOpen] = useState(false);
const [isFirstVisitModalOpen, setIsFirstVisitModalOpen] = useState(false);
const [bets, setBets] = useState<Bet[]>([]);
const [solWallet, setSolWallet] = useState<ConnectedSolanaWallet>();
const [myActiveBet, setMyActiveBet] = useState<Bet>();
const [rematch, setRematch] = useState(false);
const [lastActiveBet, setLastActiveBet] = useState<Bet>();
const [rematchInProgress, setRematchInProgress] = useState(false);
const [rematchTxError, setRematchTxError] = useState(false);
const { wallets, ready } = useSolanaWallets();
const { user } = usePrivy();
const iframeRef = useRef<HTMLIFrameElement>(null);
const { isPracticeGameOpen, isActiveGameOpen } = usePracticeGame();
// Check if this is the user's first visit
useEffect(() => {
if (typeof window !== 'undefined') {
const hasVisited = localStorage.getItem('duelfi_has_visited');
if (!hasVisited) {
setIsFirstVisitModalOpen(true);
localStorage.setItem('duelfi_has_visited', 'true');
}
}
}, []);
const game_close_signal = (status: number) => {
setRematch(status == 1);
setMyActiveBet(undefined);
};
const updateBets = async () => {
if(ready){
const wallet = wallets.find((_wallet) => _wallet.type === "solana") || wallets[0];
setSolWallet(wallet);
}
let fetchedBets:Bet[] = [];
if(CLUSTER_URL == clusterApiUrl("devnet")){
fetchedBets = await fetchOpenBetsDev();
}else{
fetchedBets = await fetchOpenBets();
}
const filteredBets = fetchedBets.filter((bet) => !(bet.owner_id && bet.joiner_id));
const filledBets = fetchedBets.filter((bet) => bet.owner_id && bet.joiner_id);
setBets(filteredBets);
if (!ready || wallets.length === 0) return;
let activeBet = filledBets.find((bet) => bet.owner_id === user?.id || bet.joiner_id === user?.id);
if(activeBet){
await new Promise(resolve => setTimeout(resolve, 2000));
const betHistoryResponse = await fetch(`${API_URL}get_game_completed.php?address=${activeBet.address}`);
const betHistory = await betHistoryResponse.text();
console.log(`bet history for ${activeBet.address}: ${betHistory}`);
if(betHistory == "1"){
activeBet = undefined;
}
}
if(rematch){
setMyActiveBet(undefined);
return;
}
if (!myActiveBet && lastActiveBet?.address !== activeBet?.address) {
setMyActiveBet(activeBet);
setLastActiveBet(activeBet);
}
};
const handleCreateRematch = async () => {
console.log('Creating rematch...');
if (!lastActiveBet || !solWallet) return;
setRematchInProgress(true);
setRematchTxError(false);
try {
const currency = getCurrencyByMint(lastActiveBet.currency ?? "SOL");
// Step 1: Create new bet
const tx = await createBet(
solWallet,
user?.id ?? "",
lastActiveBet.wager,
lastActiveBet.id,
true,
currency?.tokenAddress ?? "11111111111111111111111111111111"
);
console.log("Rematch created. Transaction ID:", tx);
// Step 2: Inform backend of rematch link
const set_response = await fetch(
`${API_URL}set_rematch_address.php?address=${lastActiveBet.address}&rematch_address=${tx}`
);
console.log(await set_response.text());
// Step 3: Poll until vault account is found
const pollUntilFound = async (
maxRetries = 10,
delayMs = 10000
): Promise<Bet | undefined> => {
for (let i:number = 0; i < maxRetries; i++) {
const vault = await getVaultByAddress(solWallet, tx);
if (vault) return vault;
console.log(`Waiting for vault account... (${i + 1}/${maxRetries})`);
await new Promise(res => setTimeout(res, delayMs));
}
return undefined;
};
const newBetAcc = await pollUntilFound();
if (!newBetAcc) {
console.error("Failed to retrieve new bet vault after retries.");
setRematchTxError(true);
} else {
// Step 4: Set new active bet
setMyActiveBet(newBetAcc);
setLastActiveBet(newBetAcc);
setRematch(false);
}
} catch (err) {
console.error("Create rematch failed:", err);
setRematchTxError(true);
} finally {
setRematchInProgress(false);
}
};
const handleJoinRematch = async () => {
if (!lastActiveBet || !solWallet) return;
try {
const pollForRematchAddress = async (
maxRetries = 10,
delayMs = 10000
): Promise<string | null> => {
for (let i = 0; i < maxRetries; i++) {
console.log(`Polling rematch address... (${i + 1}/${maxRetries})`);
const response = await fetch(
`${API_URL}get_rematch_address.php?address=${lastActiveBet.address}`
);
const rematchAddress = (await response.text()).trim();
if (rematchAddress.length > 5) {
console.log("Found rematch address:", rematchAddress);
return rematchAddress;
}
await new Promise(res => setTimeout(res, delayMs));
}
console.warn("Rematch address not found after max retries.");
return null;
};
const rematchAddress = await pollForRematchAddress();
if (!rematchAddress) return;
const pollForVault = async (
address: string,
maxRetries = 10,
delayMs = 10000
): Promise<Bet | undefined> => {
for (let i:number = 0; i < maxRetries; i++) {
console.log(`Polling vault for address ${address}... (${i + 1}/${maxRetries})`);
const vault = await getVaultByAddress(solWallet, address);
if (vault) return vault;
await new Promise(res => setTimeout(res, delayMs));
}
console.warn("Vault not found after max retries.");
return;
};
const rematchVault:Bet | undefined = await pollForVault(rematchAddress);
if (rematchVault) {
// const tx = await joinBet(solWallet, user?.id!, rematchVault.id, rematchVault.address);
setMyActiveBet(rematchVault);
setLastActiveBet(rematchVault);
setRematch(false);
console.log("Rematch vault set as active.");
}
} catch (err) {
console.error("Error during handleJoinRematch:", err);
}
};
useEffect(() => {
if(rematch){
if(true){
handleCreateRematch();
}else{
handleJoinRematch();
}
}
},[rematch, handleCreateRematch, handleJoinRematch]);
useEffect(() => {
if (ready) {
updateBets();
}
}, [ready]);
useEffect(() => {
if (!ready) return;
updateBets();
const interval = setInterval(updateBets, 3000);
return () => clearInterval(interval);
}, [ready]);
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);
}, []);
return (
<>
{!isPracticeGameOpen && !isActiveGameOpen && (
<>
{myActiveBet ? (
(
myActiveBet.owner_id && myActiveBet.joiner_id ? (
<div className="w-full h-screen flex justify-center items-center bg-black">
<iframe
ref={iframeRef}
src={`/UnityBuild/${myActiveBet.id}/index.html?betId=${myActiveBet.id}&owner=${myActiveBet.owner_id}&joiner=${myActiveBet.joiner_id}&address=${myActiveBet.address}&uid=${user?.id}&pubkey=${solWallet?.address}&wager=${myActiveBet.wager * 1e8}&isDev=${(CLUSTER_URL == clusterApiUrl("devnet")) ? "true" : "false"}`}
className="w-full h-full"
allowFullScreen
/>
</div>
) : (
<div className="w-full h-screen flex justify-center items-center bg-black">
<label>Loading...</label>
</div>
)
)
) : (
<section className="flex flex-col items-center text-center py-16">
<video
autoPlay
loop
muted
playsInline
className="w-full max-w-4xl mb-8 rounded-lg"
>
<source src="/duelfiassets/headervideo.MP4" type="video/mp4" />
Your browser does not support the video tag.
</video>
{/* <Image
src="/duelfiassets/Playing on Arcade Machine no BG.png"
alt="DuelFi Hero"
width={150}
height={50}
className="mb-6"
/>
<h1 className="text-4xl font-bold text-white">
Instant <span className="text-[rgb(248,144,22)]">Duels</span>, Instant{" "}
<span className="text-[rgb(248,144,22)]">Wins</span>.
</h1> */}
{bets.filter(bet => bet.owner_id === user?.id).length === 0 && (
<button
className="mt-12 px-12 py-4 bg-[rgb(248,144,22)] hover:bg-white text-black text-sm font-semibold rounded-xl flex items-center gap-2 transition duration-300 hover:scale-110"
onClick={() => setIsGameModalOpen(true)}
>
Create a Game
<svg
xmlns="http://www.w3.org/2000/svg"
className="w-5 h-5"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M7 7h10v10" />
<path d="M7 17 17 7" />
</svg>
</button>
)}
<p
className="mt-4 text-gray-400 cursor-pointer hover:underline font-mono"
onClick={() => setIsModalOpen(true)}
>
How it works?..
</p>
<YourGames bets={bets} />
<OpenGames bets={bets} />
</section>
)}
</>
)}
{rematch && (
<RematchModal
isOwner= {lastActiveBet?.owner_id === user?.id}
inProgress={rematchInProgress}
hasError={rematchTxError}
wallets={solWallet!}
/>
)}
<GameModal isOpen={isGameModalOpen} onClose={() => setIsGameModalOpen(false)} />
<HowItWorksModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
<FirstVisitModal isOpen={isFirstVisitModalOpen} onClose={() => setIsFirstVisitModalOpen(false)} />
</>
);
}