352 lines
12 KiB
TypeScript
352 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";
|
|
|
|
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);
|
|
|
|
// 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 (
|
|
<>
|
|
{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)} />
|
|
</>
|
|
);
|
|
}
|