v1 almost
BIN
public/UnityBuild/tetris/Build/prod.data
Normal file
22
public/UnityBuild/tetris/Build/prod.framework.js
Normal file
1
public/UnityBuild/tetris/Build/prod.loader.js
Normal file
BIN
public/UnityBuild/tetris/Build/prod.wasm
Normal file
BIN
public/UnityBuild/tetris/TemplateData/MemoryProfiler.png
Normal file
|
After Width: | Height: | Size: 665 B |
BIN
public/UnityBuild/tetris/TemplateData/favicon.ico
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/UnityBuild/tetris/TemplateData/fullscreen-button.png
Normal file
|
After Width: | Height: | Size: 175 B |
|
After Width: | Height: | Size: 96 B |
|
After Width: | Height: | Size: 109 B |
BIN
public/UnityBuild/tetris/TemplateData/progress-bar-full-dark.png
Normal file
|
After Width: | Height: | Size: 74 B |
|
After Width: | Height: | Size: 84 B |
16
public/UnityBuild/tetris/TemplateData/style.css
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
body { padding: 0; margin: 0 }
|
||||||
|
#unity-container { position: absolute }
|
||||||
|
#unity-container.unity-desktop { left: 50%; top: 50%; transform: translate(-50%, -50%) }
|
||||||
|
#unity-container.unity-mobile { position: fixed; width: 100%; height: 100% }
|
||||||
|
#unity-canvas { background: #231F20 }
|
||||||
|
.unity-mobile #unity-canvas { width: 100%; height: 100% }
|
||||||
|
#unity-loading-bar { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); display: none }
|
||||||
|
#unity-logo { width: 154px; height: 130px; background: url('unity-logo-dark.png') no-repeat center }
|
||||||
|
#unity-progress-bar-empty { width: 141px; height: 18px; margin-top: 10px; margin-left: 6.5px; background: url('progress-bar-empty-dark.png') no-repeat center }
|
||||||
|
#unity-progress-bar-full { width: 0%; height: 18px; margin-top: 10px; background: url('progress-bar-full-dark.png') no-repeat center }
|
||||||
|
#unity-footer { position: relative }
|
||||||
|
.unity-mobile #unity-footer { display: none }
|
||||||
|
#unity-webgl-logo { float:left; width: 204px; height: 38px; background: url('webgl-logo.png') no-repeat center }
|
||||||
|
#unity-build-title { float: right; margin-right: 10px; line-height: 38px; font-family: arial; font-size: 18px }
|
||||||
|
#unity-fullscreen-button { cursor:pointer; float: right; width: 38px; height: 38px; background: url('fullscreen-button.png') no-repeat center }
|
||||||
|
#unity-warning { position: absolute; left: 50%; top: 5%; transform: translate(-50%); background: white; padding: 10px; display: none }
|
||||||
BIN
public/UnityBuild/tetris/TemplateData/unity-logo-dark.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
public/UnityBuild/tetris/TemplateData/unity-logo-light.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
public/UnityBuild/tetris/TemplateData/webgl-logo.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
public/UnityBuild/tetris/TemplateData/webmemd-icon.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
122
public/UnityBuild/tetris/index.html
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-us">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
|
<title>Unity WebGL Player | TetrisMP</title>
|
||||||
|
<link rel="shortcut icon" href="TemplateData/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="TemplateData/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="unity-container" class="unity-desktop">
|
||||||
|
<canvas id="unity-canvas" width=1280 height=720 tabindex="-1"></canvas>
|
||||||
|
<div id="unity-loading-bar">
|
||||||
|
<div id="unity-logo"></div>
|
||||||
|
<div id="unity-progress-bar-empty">
|
||||||
|
<div id="unity-progress-bar-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="unity-warning"> </div>
|
||||||
|
<div id="unity-footer">
|
||||||
|
<div id="unity-webgl-logo"></div>
|
||||||
|
<div id="unity-fullscreen-button"></div>
|
||||||
|
<div id="unity-build-title">TetrisMP</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var container = document.querySelector("#unity-container");
|
||||||
|
var canvas = document.querySelector("#unity-canvas");
|
||||||
|
var loadingBar = document.querySelector("#unity-loading-bar");
|
||||||
|
var progressBarFull = document.querySelector("#unity-progress-bar-full");
|
||||||
|
var fullscreenButton = document.querySelector("#unity-fullscreen-button");
|
||||||
|
var warningBanner = document.querySelector("#unity-warning");
|
||||||
|
|
||||||
|
// Shows a temporary message banner/ribbon for a few seconds, or
|
||||||
|
// a permanent error message on top of the canvas if type=='error'.
|
||||||
|
// If type=='warning', a yellow highlight color is used.
|
||||||
|
// Modify or remove this function to customize the visually presented
|
||||||
|
// way that non-critical warnings and error messages are presented to the
|
||||||
|
// user.
|
||||||
|
function unityShowBanner(msg, type) {
|
||||||
|
function updateBannerVisibility() {
|
||||||
|
warningBanner.style.display = warningBanner.children.length ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
var div = document.createElement('div');
|
||||||
|
div.innerHTML = msg;
|
||||||
|
warningBanner.appendChild(div);
|
||||||
|
if (type == 'error') div.style = 'background: red; padding: 10px;';
|
||||||
|
else {
|
||||||
|
if (type == 'warning') div.style = 'background: yellow; padding: 10px;';
|
||||||
|
setTimeout(function() {
|
||||||
|
warningBanner.removeChild(div);
|
||||||
|
updateBannerVisibility();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
updateBannerVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildUrl = "Build";
|
||||||
|
var loaderUrl = buildUrl + "/prod.loader.js";
|
||||||
|
var config = {
|
||||||
|
dataUrl: buildUrl + "/prod.data",
|
||||||
|
frameworkUrl: buildUrl + "/prod.framework.js",
|
||||||
|
codeUrl: buildUrl + "/prod.wasm",
|
||||||
|
streamingAssetsUrl: "StreamingAssets",
|
||||||
|
companyName: "DefaultCompany",
|
||||||
|
productName: "TetrisMP",
|
||||||
|
productVersion: "1.0",
|
||||||
|
showBanner: unityShowBanner,
|
||||||
|
};
|
||||||
|
|
||||||
|
// By default, Unity keeps WebGL canvas render target size matched with
|
||||||
|
// the DOM size of the canvas element (scaled by window.devicePixelRatio)
|
||||||
|
// Set this to false if you want to decouple this synchronization from
|
||||||
|
// happening inside the engine, and you would instead like to size up
|
||||||
|
// the canvas DOM size and WebGL render target sizes yourself.
|
||||||
|
// config.matchWebGLToCanvasSize = false;
|
||||||
|
|
||||||
|
if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
|
||||||
|
// Mobile device style: fill the whole browser client area with the game canvas:
|
||||||
|
|
||||||
|
var meta = document.createElement('meta');
|
||||||
|
meta.name = 'viewport';
|
||||||
|
meta.content = 'width=device-width, height=device-height, initial-scale=1.0, user-scalable=no, shrink-to-fit=yes';
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(meta);
|
||||||
|
container.className = "unity-mobile";
|
||||||
|
canvas.className = "unity-mobile";
|
||||||
|
|
||||||
|
// To lower canvas resolution on mobile devices to gain some
|
||||||
|
// performance, uncomment the following line:
|
||||||
|
// config.devicePixelRatio = 1;
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Desktop style: Render the game canvas in a window that can be maximized to fullscreen:
|
||||||
|
|
||||||
|
canvas.style.width = "1280px";
|
||||||
|
canvas.style.height = "720px";
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingBar.style.display = "block";
|
||||||
|
|
||||||
|
var script = document.createElement("script");
|
||||||
|
script.src = loaderUrl;
|
||||||
|
script.onload = () => {
|
||||||
|
createUnityInstance(canvas, config, (progress) => {
|
||||||
|
progressBarFull.style.width = 100 * progress + "%";
|
||||||
|
}).then((unityInstance) => {
|
||||||
|
loadingBar.style.display = "none";
|
||||||
|
fullscreenButton.onclick = () => {
|
||||||
|
unityInstance.SetFullscreen(1);
|
||||||
|
};
|
||||||
|
}).catch((message) => {
|
||||||
|
alert(message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
document.body.appendChild(script);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,18 +1,74 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import OpenGames from "./OpenGames";
|
import OpenGames from "./OpenGames";
|
||||||
import GameModal from "./GameModal";
|
import GameModal from "./GameModal";
|
||||||
import { HowItWorksModal } from "./HowItWorksModal";
|
import { HowItWorksModal } from "./HowItWorksModal";
|
||||||
import YourGames from "./YourGames";
|
import YourGames from "./YourGames";
|
||||||
|
import { Bet } from "@/types/Bet";
|
||||||
|
import { fetchOpenBets } from "@/shared/solana_helpers";
|
||||||
|
import { usePrivy, useSolanaWallets } from "@privy-io/react-auth";
|
||||||
|
|
||||||
export default function HeroSection() {
|
export default function HeroSection() {
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [isGameModalOpen, setIsGameModalOpen] = useState(false);
|
const [isGameModalOpen, setIsGameModalOpen] = useState(false);
|
||||||
|
const [bets, setBets] = useState<Bet[]>([]);
|
||||||
|
const [myActiveBet, setMyActiveBet] = useState<Bet>();
|
||||||
|
const { wallets, ready } = useSolanaWallets();
|
||||||
|
const {user} = usePrivy();
|
||||||
|
|
||||||
|
async function updateBets() {
|
||||||
|
if (!ready || wallets.length === 0) return;
|
||||||
|
|
||||||
|
// Find the wallet to use (either Solana wallet or the first available wallet)
|
||||||
|
let wallet = wallets.find((_wallet) => _wallet.type === "solana") || wallets[0];
|
||||||
|
|
||||||
|
// Fetch the open bets
|
||||||
|
const fetchedBets = await fetchOpenBets(wallet);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Filter out the bets that are not filled (do not have both owner_id and joiner_id)
|
||||||
|
const filteredBets = fetchedBets.filter((bet) => !(bet.owner_id && bet.joiner_id));
|
||||||
|
|
||||||
|
// Create a new list for filled bets (bets that have both owner_id and joiner_id set)
|
||||||
|
const filledBets = fetchedBets.filter((bet) => bet.owner_id && bet.joiner_id);
|
||||||
|
const activeBet = filledBets.find((bet)=> bet.owner_id == user?.id || bet.joiner_id==user?.id);
|
||||||
|
console.log("My active bet:", activeBet);
|
||||||
|
console.log("Filtered bets:", filteredBets);
|
||||||
|
console.log("Filled bets:", filledBets); // This is the new list for filled bets
|
||||||
|
|
||||||
|
// Set the state for filtered bets and active bet
|
||||||
|
setBets(filteredBets);
|
||||||
|
setMyActiveBet(activeBet);
|
||||||
|
|
||||||
|
// If needed, you can also use the filledBets list for something else
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!ready) return;
|
||||||
|
|
||||||
|
updateBets();
|
||||||
|
const interval = setInterval(updateBets, 10000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [ready]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{myActiveBet ? (
|
||||||
|
// Render Unity WebGL game when myActiveBet is found
|
||||||
|
<div className="w-full h-screen flex justify-center items-center bg-black">
|
||||||
|
<iframe
|
||||||
|
src={`/UnityBuild/${myActiveBet.id}/index.html?betId=${myActiveBet.id}&owner=${myActiveBet.owner_id}&joiner=${myActiveBet.joiner_id}&address=${myActiveBet.address}`} // Change this to the actual path of your Unity WebGL build
|
||||||
|
className="w-full h-full"
|
||||||
|
allowFullScreen
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// Render the original UI when no active bet
|
||||||
<section className="flex flex-col items-center text-center py-16">
|
<section className="flex flex-col items-center text-center py-16">
|
||||||
<Image
|
<Image
|
||||||
src="/duelfiassets/Playing on Arcade Machine no BG.png"
|
src="/duelfiassets/Playing on Arcade Machine no BG.png"
|
||||||
|
|
@ -54,10 +110,10 @@ export default function HeroSection() {
|
||||||
How it works?..
|
How it works?..
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<YourGames />
|
<YourGames bets={bets} />
|
||||||
<OpenGames />
|
<OpenGames bets={bets} />
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
<GameModal isOpen={isGameModalOpen} onClose={() => setIsGameModalOpen(false)} />
|
<GameModal isOpen={isGameModalOpen} onClose={() => setIsGameModalOpen(false)} />
|
||||||
<HowItWorksModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
|
<HowItWorksModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,51 @@
|
||||||
"use client";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { games } from "../data/games";
|
import { games } from "../data/games";
|
||||||
import { useSolanaWallets } from "@privy-io/react-auth";
|
import { usePrivy, useSolanaWallets } from "@privy-io/react-auth";
|
||||||
import { fetchOpenBets } from "@/shared/solana_helpers";
|
import { joinBet } from "@/shared/solana_helpers";
|
||||||
import { Bet } from "../types/Bet";
|
import { Bet } from "../types/Bet";
|
||||||
import { fetchUserById } from "@/shared/data_fetcher";
|
import { fetchUserById } from "@/shared/data_fetcher";
|
||||||
export default function YourGames() {
|
import { toast } from "sonner";
|
||||||
|
import { EXPLORER_TX_TEMPLATE } from "@/data/shared";
|
||||||
|
|
||||||
|
interface GameModalProps {
|
||||||
|
bets: Bet[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function YourGames({ bets }: GameModalProps) {
|
||||||
const { wallets, ready } = useSolanaWallets();
|
const { wallets, ready } = useSolanaWallets();
|
||||||
const [myBets, setMyBets] = useState<Bet[]>([]);
|
const [myBets, setMyBets] = useState<Bet[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const { user } = usePrivy();
|
||||||
|
const [selectedBet, setSelectedBet] = useState<Bet | null>(null); // Track selected bet
|
||||||
|
|
||||||
|
const handleJoinGame = async () => {
|
||||||
|
if (!selectedBet) return;
|
||||||
|
|
||||||
|
let wallet = wallets[0];
|
||||||
|
wallets.forEach((_wallet) => {
|
||||||
|
if (wallet.type === "solana") {
|
||||||
|
wallet = _wallet;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const tx = await joinBet(wallet, user?.id ?? "", selectedBet.id, selectedBet.address);
|
||||||
|
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
|
||||||
|
|
||||||
|
if (tx.length > 5) {
|
||||||
|
toast.success("Joined game successfully!", {
|
||||||
|
action: {
|
||||||
|
label: "View TX",
|
||||||
|
onClick: () => window.open(url, "_blank"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast.error("Failed to join this game");
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedBet(null); // Close modal
|
||||||
|
};
|
||||||
|
|
||||||
// Function to fetch open bets
|
|
||||||
const updateBets = async () => {
|
const updateBets = async () => {
|
||||||
let wallet = wallets[0];
|
let wallet = wallets[0];
|
||||||
wallets.forEach((_wallet) => {
|
wallets.forEach((_wallet) => {
|
||||||
|
|
@ -19,29 +53,29 @@ export default function YourGames() {
|
||||||
wallet = _wallet;
|
wallet = _wallet;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const bets: Bet[] = await fetchOpenBets(wallet);
|
|
||||||
const filteredBets = (bets.filter((bet) => bet.owner !== wallet.address));
|
const filteredBets = bets.filter((bet) => bet.owner !== wallet.address);
|
||||||
const enrichedBets = await Promise.all(
|
const enrichedBets = await Promise.all(
|
||||||
filteredBets.map(async (bet) => {
|
filteredBets.map(async (bet) => {
|
||||||
const ownerProfile = await fetchUserById(bet.owner_id);
|
const ownerProfile = await fetchUserById(bet.owner_id);
|
||||||
return {
|
return {
|
||||||
...bet,
|
...bet,
|
||||||
ownerProfile, // contains {username, x_profile_url, etc.}
|
ownerProfile,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
setMyBets(enrichedBets);
|
setMyBets(enrichedBets);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
console.log(`Got ${bets.length} bets`);
|
console.log(`Got ${bets.length} bets`);
|
||||||
}
|
};
|
||||||
// Auto-refresh every 10 seconds
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ready) return;
|
if (!ready) return;
|
||||||
updateBets();
|
updateBets();
|
||||||
const interval = setInterval(updateBets, 10000); // Refresh every 10s
|
const interval = setInterval(updateBets, 10000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
return () => clearInterval(interval); // Cleanup on unmount
|
}, [bets]);
|
||||||
}, [ready]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-16 px-6">
|
<section className="py-16 px-6">
|
||||||
|
|
@ -49,20 +83,20 @@ export default function YourGames() {
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<p className="text-gray-400">Loading Open games...</p>
|
<p className="text-gray-400">Loading Open games...</p>
|
||||||
) :
|
) : myBets.length === 0 ? (
|
||||||
myBets.length === 0 ? <></> : (
|
<p className="text-gray-400">No open games available</p>
|
||||||
|
) : (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{myBets.map((bet) => {
|
{myBets.map((bet) => {
|
||||||
console.log(`Finding game for the id ${bet.id}`)
|
const game = games.find((g) => g.id === bet.id);
|
||||||
const game = games.find((g) => g.id === bet.id); // Match game
|
if (!game) return null;
|
||||||
|
|
||||||
if (!game) return null; // Skip unmatched bets
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={bet.id + bet.owner}
|
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"
|
className="relative group bg-[rgb(30,30,30)] rounded-2xl p-2 w-50 overflow-hidden cursor-pointer transition-all duration-300"
|
||||||
|
onClick={() => setSelectedBet(bet)} // Open modal
|
||||||
>
|
>
|
||||||
{/* Game Thumbnail */}
|
|
||||||
<div className="relative w-full h-40 overflow-hidden rounded-xl">
|
<div className="relative w-full h-40 overflow-hidden rounded-xl">
|
||||||
<Image
|
<Image
|
||||||
src={game.thumbnail}
|
src={game.thumbnail}
|
||||||
|
|
@ -71,14 +105,11 @@ export default function YourGames() {
|
||||||
objectFit="cover"
|
objectFit="cover"
|
||||||
className="transition-all duration-300 group-hover:brightness-50"
|
className="transition-all duration-300 group-hover:brightness-50"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Join Overlay */}
|
|
||||||
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
|
||||||
<span className="text-white text-xl font-bold">Join</span>
|
<span className="text-white text-xl font-bold">Join</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Game Info */}
|
|
||||||
<div className="mt-4 px-3 text-left">
|
<div className="mt-4 px-3 text-left">
|
||||||
<h3 className="text-lg font-semibold text-white py-2">{game.name}</h3>
|
<h3 className="text-lg font-semibold text-white py-2">{game.name}</h3>
|
||||||
|
|
||||||
|
|
@ -92,7 +123,6 @@ export default function YourGames() {
|
||||||
<p className="text-white">{(bet.wager * 2).toFixed(2)} SOL</p>
|
<p className="text-white">{(bet.wager * 2).toFixed(2)} SOL</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* User Info */}
|
|
||||||
{bet.ownerProfile && (
|
{bet.ownerProfile && (
|
||||||
<div className="flex items-center gap-2 mt-4">
|
<div className="flex items-center gap-2 mt-4">
|
||||||
<Image
|
<Image
|
||||||
|
|
@ -105,13 +135,41 @@ export default function YourGames() {
|
||||||
<p className="text-white text-sm font-mono">{bet.ownerProfile.username}</p>
|
<p className="text-white text-sm font-mono">{bet.ownerProfile.username}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Modal */}
|
||||||
|
{selectedBet && (
|
||||||
|
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
|
||||||
|
<div className="bg-gray-900 text-white p-6 rounded-lg shadow-lg max-w-sm w-full">
|
||||||
|
<h2 className="text-xl font-bold mb-4">Are you sure to join this bet?</h2>
|
||||||
|
<p><strong>Wager:</strong> {selectedBet.wager} SOL | <strong>Prize:</strong> {(selectedBet.wager * 2).toFixed(2)} SOL</p>
|
||||||
|
<p><strong>Game:</strong> {games.find(g => g.id === selectedBet.id)?.name}</p>
|
||||||
|
<div className="flex items-center mt-3">
|
||||||
|
{selectedBet.ownerProfile && (
|
||||||
|
<>
|
||||||
|
<Image
|
||||||
|
src={selectedBet.ownerProfile.x_profile_url || `https://vps.playpoolstudios.com/duelfi/profile_pics/${selectedBet.ownerProfile.id}.jpg`}
|
||||||
|
alt={selectedBet.ownerProfile.username}
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
className="w-8 h-8 rounded-full mr-2"
|
||||||
|
/>
|
||||||
|
<p><strong>Offered by:</strong> {selectedBet.ownerProfile.username}</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end gap-3 mt-4">
|
||||||
|
<button onClick={() => setSelectedBet(null)} className="bg-gray-700 px-4 py-2 rounded">Cancel</button>
|
||||||
|
<button onClick={handleJoinGame} className="bg-blue-500 px-4 py-2 rounded">Confirm</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,14 @@ import Image from "next/image";
|
||||||
import { games } from "../data/games";
|
import { games } from "../data/games";
|
||||||
import { FiTrash } from "react-icons/fi";
|
import { FiTrash } from "react-icons/fi";
|
||||||
import { useSolanaWallets } from "@privy-io/react-auth";
|
import { useSolanaWallets } from "@privy-io/react-auth";
|
||||||
import { fetchOpenBets, closeBet } from "@/shared/solana_helpers";
|
import { closeBet } from "@/shared/solana_helpers";
|
||||||
import { Bet } from "../types/Bet";
|
import { Bet } from "../types/Bet";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { EXPLORER_TX_TEMPLATE } from "@/data/shared";
|
import { EXPLORER_TX_TEMPLATE } from "@/data/shared";
|
||||||
|
interface GameModalProps {
|
||||||
export default function YourGames() {
|
bets: Bet[];
|
||||||
|
}
|
||||||
|
export default function YourGames({bets}:GameModalProps) {
|
||||||
const { wallets, ready } = useSolanaWallets();
|
const { wallets, ready } = useSolanaWallets();
|
||||||
const [myBets, setMyBets] = useState<Bet[]>([]);
|
const [myBets, setMyBets] = useState<Bet[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
@ -24,7 +26,6 @@ export default function YourGames() {
|
||||||
wallet = _wallet;
|
wallet = _wallet;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const bets: Bet[] = await fetchOpenBets(wallet);
|
|
||||||
setMyBets(bets.filter((bet) => bet.owner === wallet.address));
|
setMyBets(bets.filter((bet) => bet.owner === wallet.address));
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
@ -34,7 +35,7 @@ export default function YourGames() {
|
||||||
updateBets();
|
updateBets();
|
||||||
const interval = setInterval(updateBets, 10000);
|
const interval = setInterval(updateBets, 10000);
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [ready]);
|
}, [bets]);
|
||||||
|
|
||||||
// Handle bet closing
|
// Handle bet closing
|
||||||
const handleCloseBet = async () => {
|
const handleCloseBet = async () => {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet
|
||||||
const betAcc = await program.account.betVault.fetch(bet);
|
const betAcc = await program.account.betVault.fetch(bet);
|
||||||
console.log(betAcc.gameId);
|
console.log(betAcc.gameId);
|
||||||
return {
|
return {
|
||||||
|
address: bet.toString(),
|
||||||
id: betAcc.gameId,
|
id: betAcc.gameId,
|
||||||
owner: betAcc.owner.toBase58(),
|
owner: betAcc.owner.toBase58(),
|
||||||
owner_id:betAcc.ownerId,
|
owner_id:betAcc.ownerId,
|
||||||
|
|
@ -55,8 +56,6 @@ export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet
|
||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function closeBet(wallets: ConnectedSolanaWallet, betId: string): Promise<string> {
|
export async function closeBet(wallets: ConnectedSolanaWallet, betId: string): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const connection = new Connection(CLUSTER_URL);
|
const connection = new Connection(CLUSTER_URL);
|
||||||
|
|
@ -120,7 +119,6 @@ export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function createBet(wallets:ConnectedSolanaWallet, uid:string,selectedPrice:number,selectedGame:Game):Promise<string>{
|
export async function createBet(wallets:ConnectedSolanaWallet, uid:string,selectedPrice:number,selectedGame:Game):Promise<string>{
|
||||||
const connection = new Connection(CLUSTER_URL);
|
const connection = new Connection(CLUSTER_URL);
|
||||||
const wallet = {
|
const wallet = {
|
||||||
|
|
@ -179,6 +177,60 @@ export async function createBet(wallets:ConnectedSolanaWallet, uid:string,select
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function joinBet(wallets:ConnectedSolanaWallet, uid:string, gameId:string, address:string):Promise<string>{
|
||||||
|
const connection = new Connection(CLUSTER_URL);
|
||||||
|
const wallet = {
|
||||||
|
publicKey: new PublicKey(wallets.address),
|
||||||
|
signTransaction: wallets.signTransaction,
|
||||||
|
signAllTransactions: wallets.signAllTransactions,
|
||||||
|
};
|
||||||
|
const provider = new AnchorProvider(connection, wallet, {
|
||||||
|
preflightCommitment: "confirmed",
|
||||||
|
});
|
||||||
|
|
||||||
|
const program = new Program<Bets>(idl, provider);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [bet_list_pda] = await PublicKey.findProgramAddress(
|
||||||
|
[Buffer.from("bets_list")],
|
||||||
|
program.programId
|
||||||
|
);
|
||||||
|
const connection = new Connection(CLUSTER_URL, "confirmed");
|
||||||
|
const betVaultPubkey = new PublicKey(address);
|
||||||
|
|
||||||
|
console.log(`bets list : ${bet_list_pda}`);
|
||||||
|
|
||||||
|
// Create transaction
|
||||||
|
const tx = await program.methods
|
||||||
|
.joinBet(uid,gameId)
|
||||||
|
.accounts({
|
||||||
|
betVault: betVaultPubkey,
|
||||||
|
})
|
||||||
|
.transaction(); // Get transaction object instead of RPC call
|
||||||
|
|
||||||
|
tx.feePayer = new PublicKey(wallets.address);
|
||||||
|
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
||||||
|
|
||||||
|
// Sign transaction with Privy
|
||||||
|
const signedTx = await wallet.signTransaction(tx);
|
||||||
|
|
||||||
|
// Send transaction// Replace with correct RPC endpoint
|
||||||
|
const txId = await connection.sendRawTransaction(signedTx.serialize());
|
||||||
|
|
||||||
|
console.log(`Transaction ID: ${txId}`);
|
||||||
|
return txId;
|
||||||
|
} catch (error:unknown) {
|
||||||
|
|
||||||
|
|
||||||
|
// const errorMessage = String(error); // Converts error to string safely
|
||||||
|
toast.error("Failed to create bet.");
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
function getRandomInt(max:number):number {
|
function getRandomInt(max:number):number {
|
||||||
return Math.floor(Math.random() * max);
|
return Math.floor(Math.random() * max);
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
export interface Bet {
|
export interface Bet {
|
||||||
|
address:string;
|
||||||
id: string;
|
id: string;
|
||||||
owner: string;
|
owner: string;
|
||||||
owner_id:string;
|
owner_id:string;
|
||||||
|
|
|
||||||