profile data
This commit is contained in:
parent
145b7946f8
commit
dd303efd01
|
|
@ -2,6 +2,9 @@ import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
/* config options here */
|
||||||
|
images: {
|
||||||
|
domains: ["pbs.twimg.com","vps.playpoolstudios.com"], // ✅ add Twitter's image domain
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|
|
||||||
|
|
@ -52,3 +52,45 @@ body {
|
||||||
background-size: 50px 50px;
|
background-size: 50px 50px;
|
||||||
animation: scrollGrid 2s linear infinite;
|
animation: scrollGrid 2s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Slide in from the top */
|
||||||
|
@keyframes slide-down {
|
||||||
|
0% {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slide out to the top */
|
||||||
|
@keyframes slide-up {
|
||||||
|
0% {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes slide-down {
|
||||||
|
0% {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-enter {
|
||||||
|
animation: slide-down 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-exit {
|
||||||
|
animation: slide-up 0.2s ease-in;
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import { toSolanaWalletConnectors } from "@privy-io/react-auth/solana";
|
||||||
import { Toaster } from "sonner";
|
import { Toaster } from "sonner";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<div className="bg-[rgb(22,22,22)]">
|
<div className="bg-[rgb(22,22,22)]">
|
||||||
|
|
@ -19,22 +20,24 @@ export default function Home() {
|
||||||
appearance: {
|
appearance: {
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
accentColor: '#f89016',
|
accentColor: '#f89016',
|
||||||
logo: 'https://your-logo-url'
|
logo: 'https://i.postimg.cc/3xymQbkZ/Logo-no-BG-4.png'
|
||||||
},
|
},
|
||||||
// Create embedded wallets for users who don't have a wallet
|
// Create embedded wallets for users who don't have a wallet
|
||||||
embeddedWallets: {
|
embeddedWallets: {
|
||||||
createOnLogin: 'users-without-wallets'
|
createOnLogin: 'users-without-wallets'
|
||||||
},
|
},
|
||||||
externalWallets:{
|
externalWallets: {
|
||||||
solana:{connectors:toSolanaWalletConnectors()}
|
solana: { connectors: toSolanaWalletConnectors() }
|
||||||
}
|
}
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<>
|
||||||
<Toaster position="top-right" richColors />
|
<Toaster position="top-right" richColors />
|
||||||
<Header/>
|
<Header />
|
||||||
<HeroSection/>
|
<HeroSection />
|
||||||
<Footer/>
|
<Footer />
|
||||||
|
</>
|
||||||
</PrivyProvider>
|
</PrivyProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { usePrivy, useSolanaWallets } from "@privy-io/react-auth";
|
import { usePrivy, useSolanaWallets } from "@privy-io/react-auth";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { games } from "../data/games";
|
import { games } from "../data/games";
|
||||||
|
|
@ -17,19 +17,39 @@ interface GameModalProps {
|
||||||
|
|
||||||
export default function GameModal({ isOpen, onClose }: GameModalProps) {
|
export default function GameModal({ isOpen, onClose }: GameModalProps) {
|
||||||
const { wallets } = useSolanaWallets();
|
const { wallets } = useSolanaWallets();
|
||||||
const { authenticated } = usePrivy();
|
const { authenticated, user } = usePrivy();
|
||||||
|
|
||||||
const [selectedGame, setSelectedGame] = useState<Game | null>(null);
|
const [selectedGame, setSelectedGame] = useState<Game | null>(null);
|
||||||
const [selectedPrice, setSelectedPrice] = useState<number | null>(null);
|
const [selectedPrice, setSelectedPrice] = useState<number | null>(null);
|
||||||
const [isProcessing, setIsProcessing] = useState(false);
|
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 () => {
|
const handleCreateGame = async () => {
|
||||||
if (!authenticated) {
|
if (!authenticated) {
|
||||||
toast.error("Please log in with Privy.");
|
toast.error("Please log in with Privy.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const wallet = wallets[0];
|
let wallet = wallets[0];
|
||||||
|
wallets.forEach((_wallet) => {
|
||||||
|
if (_wallet.type === "solana") {
|
||||||
|
wallet = _wallet;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (!wallet) {
|
if (!wallet) {
|
||||||
toast.error("Please connect your wallet.");
|
toast.error("Please connect your wallet.");
|
||||||
return;
|
return;
|
||||||
|
|
@ -42,15 +62,16 @@ export default function GameModal({ isOpen, onClose }: GameModalProps) {
|
||||||
|
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
try {
|
try {
|
||||||
const tx = await createBet(wallet, selectedPrice, selectedGame);
|
const tx = await createBet(wallet, user?.id ?? "", selectedPrice, selectedGame);
|
||||||
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
|
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
|
||||||
|
if (tx.length > 5) {
|
||||||
toast.success(`Bet created successfully!`, {
|
toast.success(`Bet created successfully!`, {
|
||||||
action: {
|
action: {
|
||||||
label: "View TX",
|
label: "View TX",
|
||||||
onClick: () => window.open(url, "_blank"),
|
onClick: () => window.open(url, "_blank"),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
onClose();
|
onClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating bet:", error);
|
console.error("Error creating bet:", error);
|
||||||
|
|
@ -58,20 +79,39 @@ export default function GameModal({ isOpen, onClose }: GameModalProps) {
|
||||||
} finally {
|
} finally {
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!shouldRender) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black/70 flex justify-center items-start pt-10 z-50" onClick={onClose}>
|
<div
|
||||||
<div className="bg-[rgb(30,30,30)] text-white w-full max-w-lg p-6 rounded-2xl shadow-lg" onClick={(e) => e.stopPropagation()}>
|
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 ? (
|
{isProcessing ? (
|
||||||
<div className="flex flex-col items-center">
|
<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">
|
<svg
|
||||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
className="animate-spin h-8 w-8 text-blue-400 mb-4"
|
||||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path>
|
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>
|
</svg>
|
||||||
<h3 className="text-lg font-semibold">Processing...</h3>
|
<h3 className="text-lg font-semibold">Processing...</h3>
|
||||||
<p className="text-gray-400 text-sm mt-2">Creating your bet, please wait...</p>
|
<p className="text-gray-400 text-sm mt-2">Creating your bet, please wait...</p>
|
||||||
|
|
@ -79,9 +119,15 @@ export default function GameModal({ isOpen, onClose }: GameModalProps) {
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<h2 className="text-xl font-bold mb-4">Create Game</h2>
|
<h2 className="text-xl font-bold mb-4">Create Game</h2>
|
||||||
<GameSelection games={games} selectedGame={selectedGame} onSelect={setSelectedGame} />
|
<GameSelection
|
||||||
<PriceSelection selectedPrice={selectedPrice} onSelect={setSelectedPrice} />
|
games={games}
|
||||||
|
selectedGame={selectedGame}
|
||||||
|
onSelect={setSelectedGame}
|
||||||
|
/>
|
||||||
|
<PriceSelection
|
||||||
|
selectedPrice={selectedPrice}
|
||||||
|
onSelect={setSelectedPrice}
|
||||||
|
/>
|
||||||
<button
|
<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"
|
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}
|
onClick={handleCreateGame}
|
||||||
|
|
|
||||||
|
|
@ -78,8 +78,7 @@ export default function Header() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile-only view (Logo & Login) */}
|
{/* Mobile-only view (Logo & Login) */}
|
||||||
<div className="flex md:hidden items-center gap-4">
|
<div className="flex md:hidden items-center ">
|
||||||
{/* Hamburger Button */}
|
|
||||||
<PrivyButton></PrivyButton>
|
<PrivyButton></PrivyButton>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,35 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
interface HowItWorksModalProps {
|
interface HowItWorksModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HowItWorksModal({ isOpen, onClose }: HowItWorksModalProps) {
|
export function HowItWorksModal({ isOpen, onClose }: HowItWorksModalProps) {
|
||||||
if (!isOpen) return null;
|
const [shouldRender, setShouldRender] = useState(isOpen);
|
||||||
|
const [animationClass, setAnimationClass] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
setShouldRender(true);
|
||||||
|
setAnimationClass("modal-enter");
|
||||||
|
} else {
|
||||||
|
setAnimationClass("modal-exit");
|
||||||
|
// Wait for animation to finish before unmounting
|
||||||
|
const timer = setTimeout(() => setShouldRender(false), 200); // match animation duration
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
if (!shouldRender) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-black/70 flex justify-center items-start pt-10 z-50" onClick={onClose}>
|
|
||||||
<div
|
<div
|
||||||
className="bg-[rgb(30,30,30)] text-white w-full max-w-lg p-6 rounded-2xl shadow-lg transform transition-transform duration-300 animate-slide-down"
|
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 transform transition-transform duration-300 ${animationClass}`}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<h2 className="text-xl font-bold mb-4">How It Works</h2>
|
<h2 className="text-xl font-bold mb-4">How It Works</h2>
|
||||||
|
|
@ -22,7 +42,9 @@ interface HowItWorksModalProps {
|
||||||
{ step: "Claim Your Winnings", desc: "Win the game and collect your rewards instantly!" },
|
{ step: "Claim Your Winnings", desc: "Win the game and collect your rewards instantly!" },
|
||||||
].map(({ step, desc }, index) => (
|
].map(({ step, desc }, index) => (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
<h3 className="text-[rgb(248,144,22)] font-bold text-lg">{index + 1}. {step}</h3>
|
<h3 className="text-[rgb(248,144,22)] font-bold text-lg">
|
||||||
|
{index + 1}. {step}
|
||||||
|
</h3>
|
||||||
<p className="text-xs text-gray-400 font-mono">{desc}</p>
|
<p className="text-xs text-gray-400 font-mono">{desc}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -37,5 +59,4 @@ interface HowItWorksModalProps {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,33 @@ import Image from "next/image";
|
||||||
import { games } from "../data/games";
|
import { games } from "../data/games";
|
||||||
import { useSolanaWallets } from "@privy-io/react-auth";
|
import { useSolanaWallets } from "@privy-io/react-auth";
|
||||||
import { fetchOpenBets } from "@/shared/solana_helpers";
|
import { fetchOpenBets } from "@/shared/solana_helpers";
|
||||||
import {Bet} from "../types/Bet";
|
import { Bet } from "../types/Bet";
|
||||||
|
import { fetchUserById } from "@/shared/data_fetcher";
|
||||||
export default function YourGames() {
|
export default function YourGames() {
|
||||||
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);
|
||||||
|
|
||||||
// Function to fetch open bets
|
// Function to fetch open bets
|
||||||
const updateBets= async ()=>{
|
const updateBets = async () => {
|
||||||
const bets:Bet[] = await fetchOpenBets(wallets[0]);
|
let wallet = wallets[0];
|
||||||
setMyBets(bets.filter((bet) => bet.owner !== wallets[0].address));
|
wallets.forEach((_wallet) => {
|
||||||
|
if (wallet.type === "solana") {
|
||||||
|
wallet = _wallet;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const bets: Bet[] = await fetchOpenBets(wallet);
|
||||||
|
const filteredBets = (bets.filter((bet) => bet.owner !== wallet.address));
|
||||||
|
const enrichedBets = await Promise.all(
|
||||||
|
filteredBets.map(async (bet) => {
|
||||||
|
const ownerProfile = await fetchUserById(bet.owner_id);
|
||||||
|
return {
|
||||||
|
...bet,
|
||||||
|
ownerProfile, // contains {username, x_profile_url, etc.}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setMyBets(enrichedBets);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
console.log(`Got ${bets.length} bets`);
|
console.log(`Got ${bets.length} bets`);
|
||||||
}
|
}
|
||||||
|
|
@ -33,14 +50,13 @@ 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 ? <></> : (
|
||||||
<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}`)
|
console.log(`Finding game for the id ${bet.id}`)
|
||||||
const game = games.find((g) => g.id === bet.id); // Match game
|
const game = games.find((g) => g.id === bet.id); // Match game
|
||||||
|
|
||||||
if (!game) return null; // Skip unmatched bets
|
if (!game) return null; // Skip unmatched bets
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={bet.id + bet.owner}
|
key={bet.id + bet.owner}
|
||||||
|
|
@ -53,15 +69,43 @@ export default function YourGames() {
|
||||||
alt={game.name}
|
alt={game.name}
|
||||||
layout="fill"
|
layout="fill"
|
||||||
objectFit="cover"
|
objectFit="cover"
|
||||||
className="transition-transform duration-300 group-hover:scale-110"
|
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">
|
||||||
|
<span className="text-white text-xl font-bold">Join</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Game Info */}
|
{/* 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>
|
||||||
<p className="text-xs text-gray-400 font-mono py-1">Wager</p>
|
|
||||||
<p className="text-xs text-white font-mono py-1">{bet.wager} SOL</p>
|
<div className="flex justify-between text-xs font-mono py-1">
|
||||||
|
<p className="text-gray-400">Wager</p>
|
||||||
|
<p className="text-gray-400">Prize</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between text-xs font-mono py-1">
|
||||||
|
<p className="text-white">{bet.wager} SOL</p>
|
||||||
|
<p className="text-white">{(bet.wager * 2).toFixed(2)} SOL</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* User Info */}
|
||||||
|
{bet.ownerProfile && (
|
||||||
|
<div className="flex items-center gap-2 mt-4">
|
||||||
|
<Image
|
||||||
|
src={bet.ownerProfile.x_profile_url || `https://vps.playpoolstudios.com/duelfi/profile_pics/${bet.ownerProfile.id}.jpg`}
|
||||||
|
alt={bet.ownerProfile.username}
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
className="rounded-full"
|
||||||
|
/>
|
||||||
|
<p className="text-white text-sm font-mono">{bet.ownerProfile.username}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,53 +2,106 @@
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { usePrivy, useSolanaWallets } from "@privy-io/react-auth";
|
import { usePrivy, useSolanaWallets } from "@privy-io/react-auth";
|
||||||
import { Connection, PublicKey } from "@solana/web3.js"; // Solana Web3.js imports
|
import { Connection, PublicKey } from "@solana/web3.js";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import "react-toastify/dist/ReactToastify.css";
|
import "react-toastify/dist/ReactToastify.css";
|
||||||
import { CLUSTER_URL } from "@/data/shared";
|
import { CLUSTER_URL } from "@/data/shared";
|
||||||
|
import { useFundWallet } from "@privy-io/react-auth/solana";
|
||||||
|
|
||||||
export default function PrivyButton() {
|
export default function PrivyButton() {
|
||||||
const { login, logout, user } = usePrivy();
|
const { login, logout, user, linkTwitter, unlinkTwitter } = usePrivy();
|
||||||
|
const { fundWallet} = useFundWallet();
|
||||||
const { wallets } = useSolanaWallets();
|
const { wallets } = useSolanaWallets();
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [solWallet, setSolWallet] = useState("");
|
const [solWallet, setSolWallet] = useState("");
|
||||||
const [solBalance, setSolBalance] = useState("--");
|
const [solBalance, setSolBalance] = useState("--");
|
||||||
|
|
||||||
|
const [username, setUsername] = useState("Tester");
|
||||||
|
const [bio, setBio] = useState("");
|
||||||
|
const [avatar, setAvatar] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const [isUsernameClaimModalOpen, setIsUsernameClaimModalOpen] = useState(false);
|
||||||
|
const [newUsername, setNewUsername] = useState("");
|
||||||
|
|
||||||
const modalRef = useRef<HTMLDivElement>(null);
|
const modalRef = useRef<HTMLDivElement>(null);
|
||||||
|
const updateSolWallet = ()=>{
|
||||||
const fetchSolBalance = async () => {
|
wallets.forEach((wallet) => {
|
||||||
|
if (wallet.type === "solana") {
|
||||||
wallets.forEach((wallet)=>{
|
|
||||||
console.log(wallet.address + " : " + wallet.type);
|
|
||||||
if(wallet.type == "solana"){
|
|
||||||
setSolWallet(wallet.address);
|
setSolWallet(wallet.address);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
if (solWallet!="") {
|
}
|
||||||
const walletAddress = solWallet; // Access the Solana wallet address
|
const fetchSolBalance = async () => {
|
||||||
|
updateSolWallet();
|
||||||
|
|
||||||
|
if (solWallet !== "") {
|
||||||
try {
|
try {
|
||||||
const connection = new Connection(CLUSTER_URL, "confirmed");
|
const connection = new Connection(CLUSTER_URL, "confirmed");
|
||||||
const publicKey = new PublicKey(walletAddress);
|
const publicKey = new PublicKey(solWallet);
|
||||||
|
|
||||||
const balance = await connection.getBalance(publicKey);
|
const balance = await connection.getBalance(publicKey);
|
||||||
setSolBalance((balance / 1e9).toFixed(2)); // Convert lamports to SOL (1 SOL = 1e9 lamports)
|
setSolBalance((balance / 1e9).toFixed(2));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to get balance:", error);
|
console.error("Failed to get balance:", error);
|
||||||
setSolBalance("Error");
|
setSolBalance("Error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveProfileChanges = async () => {
|
||||||
|
if (!user) {
|
||||||
|
toast.error("User not found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateUrl = `https://vps.playpoolstudios.com/duelfi/api/update_profile.php?id=${user.id}&username=${username}&bio=${bio}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(updateUrl);
|
||||||
|
const data = await response.text();
|
||||||
|
|
||||||
|
if (data == "0") {
|
||||||
|
toast.success("Profile updated successfully!");
|
||||||
|
} else {
|
||||||
|
toast.error("Failed to update profile.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error updating profile:", error);
|
||||||
|
toast.error("An error occurred while updating the profile.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchUserData = async () => {
|
||||||
|
if (user) {
|
||||||
|
const apiUrl = `https://vps.playpoolstudios.com/duelfi/api/get_user_by_id.php?id=${user?.id}`;
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiUrl);
|
||||||
|
const data = await response.json();
|
||||||
|
if (data === 0) {
|
||||||
|
setIsUsernameClaimModalOpen(true); // Show modal if user isn't registered
|
||||||
|
} else {
|
||||||
|
setUsername(data.username || "Tester");
|
||||||
|
setBio(data.bio || "");
|
||||||
|
|
||||||
|
// Check if the user has a profile picture URL and update in database
|
||||||
|
const customProfileUrl = `https://vps.playpoolstudios.com/duelfi/profile_pics/${user.id}.jpg`;
|
||||||
|
const profilePictureUrl = user?.twitter?.profilePictureUrl ?? customProfileUrl;
|
||||||
|
if (profilePictureUrl) {
|
||||||
|
const updatePicUrlApi = `https://vps.playpoolstudios.com/duelfi/api/update_x_pic_url.php?id=${user?.id}&url=${profilePictureUrl}`;
|
||||||
|
await fetch(updatePicUrlApi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch user data:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
|
|
||||||
if (wallets) {
|
if (wallets) {
|
||||||
fetchSolBalance();
|
fetchSolBalance();
|
||||||
}
|
}
|
||||||
}, [user, wallets]); // Added wallets to dependency array
|
}, [user, wallets]);
|
||||||
|
|
||||||
// Function to validate if the address is a valid Solana address (base58)
|
|
||||||
|
|
||||||
// Close modal when clicking outside
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleClickOutside(event: MouseEvent) {
|
function handleClickOutside(event: MouseEvent) {
|
||||||
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
|
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
|
||||||
|
|
@ -63,52 +116,207 @@ export default function PrivyButton() {
|
||||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
}, [isModalOpen]);
|
}, [isModalOpen]);
|
||||||
|
|
||||||
|
// Fetch user data on login
|
||||||
|
useEffect(() => {
|
||||||
|
if (user) {
|
||||||
|
fetchUserData();
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
const customLogin = () => {
|
const customLogin = () => {
|
||||||
login({ walletChainType: "solana-only" });
|
login({ walletChainType: "solana-only" });
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyToClipboard = async () => {
|
const copyToClipboard = async () => {
|
||||||
const walletAddress = solWallet ?? ""; // Use an empty string if wallet address is undefined or null
|
if (solWallet) {
|
||||||
if (walletAddress) {
|
await navigator.clipboard.writeText(solWallet);
|
||||||
await navigator.clipboard.writeText(walletAddress);
|
toast.success("Wallet address copied!");
|
||||||
toast.success("Wallet address copied!"); // Toast notification for successful copy
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAvatarChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = async () => {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ensure the user is authenticated and the privy_id is available
|
||||||
|
if (!user?.id) {
|
||||||
|
toast.error('No Privy ID found!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the form data
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
formData.append('privy_id', user.id); // Append the privy_id
|
||||||
|
|
||||||
|
// Upload the avatar image to your server
|
||||||
|
const uploadResponse = await fetch('https://vps.playpoolstudios.com/duelfi/api/upload_profile_picture.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploadData = await uploadResponse.json();
|
||||||
|
console.log(uploadData);
|
||||||
|
if (uploadData.success) {
|
||||||
|
// Get the image URL from the response (assuming it returns the image path)
|
||||||
|
const imageUrl = uploadData.imageUrl;
|
||||||
|
|
||||||
|
// Update the avatar state and database
|
||||||
|
setAvatar(imageUrl);
|
||||||
|
const updatePicUrlApi = `https://vps.playpoolstudios.com/duelfi/api/update_x_pic_url.php?id=${user?.id}&url=${imageUrl}`;
|
||||||
|
await fetch(updatePicUrlApi);
|
||||||
|
|
||||||
|
toast.success('Profile picture uploaded successfully!');
|
||||||
|
} else {
|
||||||
|
|
||||||
|
toast.error('Failed to upload profile picture!');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error uploading avatar:", error);
|
||||||
|
toast.error("An error occurred while uploading the profile picture.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleUsernameClaim = async () => {
|
||||||
|
if (newUsername.trim()) {
|
||||||
|
const apiUrl = `https://vps.playpoolstudios.com/duelfi/api/register.php?id=${user?.id}&username=${newUsername}`;
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiUrl);
|
||||||
|
const data = await response.text();
|
||||||
|
if (data == "0") {
|
||||||
|
toast.success("Username claimed successfully!");
|
||||||
|
setIsUsernameClaimModalOpen(false);
|
||||||
|
} else {
|
||||||
|
toast.error("Failed to claim username.");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error claiming username:", error);
|
||||||
|
toast.error("An error occurred while claiming your username.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.error("Username cannot be empty!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const twitterProfilePic: string = user?.twitter?.profilePictureUrl ?? "";
|
||||||
|
const customProfileUrl = `https://vps.playpoolstudios.com/duelfi/profile_pics/${user?.id}.jpg`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{user ? (
|
{user ? (
|
||||||
<button
|
<button
|
||||||
onClick={() =>{setIsModalOpen(true); fetchSolBalance();}}
|
onClick={() => {
|
||||||
|
setIsModalOpen(true);
|
||||||
|
fetchSolBalance();
|
||||||
|
fetchUserData();
|
||||||
|
}}
|
||||||
className="bg-[rgb(248,144,22)] hover:bg-[rgb(248,200,100)] text-black px-6 py-3 rounded-md transition duration-300 hover:scale-105"
|
className="bg-[rgb(248,144,22)] hover:bg-[rgb(248,200,100)] text-black px-6 py-3 rounded-md transition duration-300 hover:scale-105"
|
||||||
>
|
>
|
||||||
<span className="font-bold">Connected</span>
|
<span className="font-bold">Connected</span>
|
||||||
<p className="text-xs font-mono text-gray-700">
|
<p className="text-xs font-mono text-gray-700">
|
||||||
{solWallet?.slice(0, 6)}...
|
{solWallet?.slice(0, 6)}...{solWallet?.slice(-4)}
|
||||||
{solWallet?.slice(-4)}
|
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
onClick={customLogin}
|
onClick={customLogin}
|
||||||
className="bg-[rgb(248,144,22)] hover:bg-[rgb(248,200,100)] text-black font-bold px-6 py-3 rounded-md transition duration-300 hover:scale-105"
|
className="bg-[rgb(248,144,22)] hover:bg-[rgb(248,200,100)] text-black font-bold px-6 py-3 rounded-md transition duration-300 hover:scale-115 w-30 mx-3"
|
||||||
>
|
>
|
||||||
Sign In
|
Sign In
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Modal */}
|
{isModalOpen && user && (
|
||||||
{isModalOpen && user ? (
|
|
||||||
<div className="fixed inset-0 bg-black/70 flex justify-center items-start pt-10 z-50">
|
<div className="fixed inset-0 bg-black/70 flex justify-center items-start pt-10 z-50">
|
||||||
<div
|
<div
|
||||||
ref={modalRef}
|
ref={modalRef}
|
||||||
className="bg-[rgb(30,30,30)] text-white w-full max-w-md p-6 rounded-2xl shadow-lg transform transition-transform duration-300 animate-slide-down"
|
className="bg-[rgb(30,30,30)] text-white w-full max-w-2xl p-6 rounded-2xl shadow-lg transform transition-transform duration-300 animate-slide-down space-y-6"
|
||||||
>
|
>
|
||||||
<h2 className="text-xl font-bold mb-4">Account Info</h2>
|
<h2 className="text-2xl font-bold mb-2">Your Profile</h2>
|
||||||
|
|
||||||
{/* Wallet Address with Copy Button */}
|
{/* Avatar + Link Twitter Row */}
|
||||||
<div className="mb-4">
|
<div className="flex items-center justify-between gap-4 flex-wrap">
|
||||||
<p className="text-gray-400 text-xs font-mono">Connected Wallet</p>
|
<div className="flex items-center gap-4">
|
||||||
|
<img
|
||||||
|
src={user?.twitter ? (twitterProfilePic) : (avatar ?? customProfileUrl)}
|
||||||
|
alt=""
|
||||||
|
className="w-16 h-16 rounded-full border border-gray-500 object-cover"
|
||||||
|
/>
|
||||||
|
{(!user.twitter) ? (<label className="bg-gray-800 hover:bg-gray-700 text-white text-sm px-4 py-2 rounded-md cursor-pointer">
|
||||||
|
Change
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
onChange={handleAvatarChange}
|
||||||
|
className="hidden"
|
||||||
|
/>
|
||||||
|
</label>) : (<></>)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{user.twitter ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm text-gray-300 font-mono">
|
||||||
|
Connected as {user.twitter.username}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => unlinkTwitter(user.twitter?.subject ?? "")}
|
||||||
|
className="bg-black text-white px-4 py-2 rounded-md text-sm hover:bg-gray-900"
|
||||||
|
>
|
||||||
|
Unlink
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={linkTwitter}
|
||||||
|
className="bg-black text-white px-4 py-2 rounded-md text-sm hover:bg-gray-900"
|
||||||
|
>
|
||||||
|
Link X (Twitter)
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Username */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs text-gray-400 mb-1">Username</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
className="w-full bg-gray-800 text-white p-2 rounded-md"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bio */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs text-gray-400 mb-1">Bio</label>
|
||||||
|
<textarea
|
||||||
|
value={bio}
|
||||||
|
onChange={(e) => setBio(e.target.value)}
|
||||||
|
className="w-full bg-gray-800 text-white p-2 rounded-md"
|
||||||
|
rows={2}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/* Save Button */}
|
||||||
|
<button
|
||||||
|
className="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-md font-semibold"
|
||||||
|
onClick={saveProfileChanges}
|
||||||
|
>
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Divider */}
|
||||||
|
<hr className="border-gray-600" />
|
||||||
|
|
||||||
|
{/* Wallet Info */}
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-400 text-xs font-mono mb-1">Connected Wallet</p>
|
||||||
<div className="flex items-center justify-between bg-gray-800 p-2 rounded-md">
|
<div className="flex items-center justify-between bg-gray-800 p-2 rounded-md">
|
||||||
<p className="font-mono text-sm truncate">{solWallet}</p>
|
<p className="font-mono text-sm truncate">{solWallet}</p>
|
||||||
<button
|
<button
|
||||||
|
|
@ -118,37 +326,18 @@ export default function PrivyButton() {
|
||||||
📋
|
📋
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* SOL Balance */}
|
<p className="text-gray-400 text-xs font-mono mt-4">SOL Balance</p>
|
||||||
<div className="mb-4">
|
<p className="text-sm mb-3">{solBalance} SOL</p>
|
||||||
<p className="text-gray-400 text-xs font-mono">SOL Balance</p>
|
|
||||||
<p className="text-sm">{solBalance} SOL</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Socials */}
|
<button
|
||||||
{(user?.discord?.username || user?.email?.address) && (
|
onClick={()=>{fundWallet(solWallet, {})}}
|
||||||
<div className="mb-4">
|
className="w-full bg-purple-600 hover:bg-purple-700 text-white font-semibold py-2 rounded-xl transition"
|
||||||
<p className="text-gray-400 text-xs font-mono">Socials</p>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
{user?.discord?.username && (
|
|
||||||
<a
|
|
||||||
href={`https://discord.com/users/${user?.discord?.username}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-blue-400 text-sm hover:underline"
|
|
||||||
>
|
>
|
||||||
Discord
|
Fund Wallet
|
||||||
</a>
|
</button>
|
||||||
)}
|
|
||||||
{user?.email?.address && (
|
|
||||||
<span className="text-gray-400 text-sm">{user?.email?.address}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Sign Out Button */}
|
|
||||||
<button
|
<button
|
||||||
className="mt-6 w-full bg-[rgb(248,144,22)] text-black font-semibold py-2 rounded-xl transition hover:bg-white"
|
className="mt-6 w-full bg-[rgb(248,144,22)] text-black font-semibold py-2 rounded-xl transition hover:bg-white"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -160,24 +349,30 @@ export default function PrivyButton() {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
)}
|
||||||
|
|
||||||
|
{/* Username Claim Modal */}
|
||||||
|
{isUsernameClaimModalOpen && (
|
||||||
|
<div className="fixed inset-0 bg-black/70 flex justify-center items-center z-50">
|
||||||
|
<div className="bg-[rgb(30,30,30)] text-white w-full max-w-lg p-6 rounded-2xl shadow-lg space-y-6">
|
||||||
|
<h2 className="text-2xl font-bold">Claim Your Username</h2>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={newUsername}
|
||||||
|
onChange={(e) => setNewUsername(e.target.value)}
|
||||||
|
className="w-full bg-[rgb(10,10,10)] text-white p-2 rounded-md"
|
||||||
|
placeholder="Enter your new username"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleUsernameClaim}
|
||||||
|
className="w-full bg-[rgb(248,144,22)] hover:bg-orange-400 text-white px-4 py-2 rounded-md transition duration-500 hover:scale-105"
|
||||||
|
>
|
||||||
|
Claim
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Animation */}
|
|
||||||
<style jsx>{`
|
|
||||||
@keyframes slide-down {
|
|
||||||
from {
|
|
||||||
transform: translateY(-100%);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: translateY(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.animate-slide-down {
|
|
||||||
animation: slide-down 0.3s ease-out;
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,14 @@ export default function YourGames() {
|
||||||
|
|
||||||
// Fetch bets
|
// Fetch bets
|
||||||
const updateBets = async () => {
|
const updateBets = async () => {
|
||||||
const bets: Bet[] = await fetchOpenBets(wallets[0]);
|
let wallet = wallets[0];
|
||||||
setMyBets(bets.filter((bet) => bet.owner === wallets[0].address));
|
wallets.forEach((_wallet) => {
|
||||||
|
if (wallet.type === "solana") {
|
||||||
|
wallet = _wallet;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const bets: Bet[] = await fetchOpenBets(wallet);
|
||||||
|
setMyBets(bets.filter((bet) => bet.owner === wallet.address));
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -34,8 +40,14 @@ export default function YourGames() {
|
||||||
const handleCloseBet = async () => {
|
const handleCloseBet = async () => {
|
||||||
if (!selectedBet) return;
|
if (!selectedBet) return;
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
|
let wallet = wallets[0];
|
||||||
|
wallets.forEach((_wallet) => {
|
||||||
|
if (wallet.type === "solana") {
|
||||||
|
wallet = _wallet;
|
||||||
|
}
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
const tx = await closeBet(wallets[0], selectedBet.id);
|
const tx = await closeBet(wallet, selectedBet.id);
|
||||||
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
|
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
|
||||||
|
|
||||||
toast.success(`Closed the bet successfully!`, {
|
toast.success(`Closed the bet successfully!`, {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* IDL can be found at `target/idl/bets.json`.
|
* IDL can be found at `target/idl/bets.json`.
|
||||||
*/
|
*/
|
||||||
export type Bets = {
|
export type Bets = {
|
||||||
"address": "HxsDuhD7wcPxcMsrYdteMYxkffuwff8HoxhZ7NuFtM37",
|
"address": "JAf3ZkQ469okXAzA6BKJeKBb9ZkCtZanULaUsapskoyn",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "bets",
|
"name": "bets",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
|
@ -121,6 +121,10 @@ export type Bets = {
|
||||||
"name": "wager",
|
"name": "wager",
|
||||||
"type": "u64"
|
"type": "u64"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "userId",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "gameId",
|
"name": "gameId",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -206,6 +210,10 @@ export type Bets = {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"args": [
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "userId",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "gameId",
|
"name": "gameId",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -244,8 +252,8 @@ export type Bets = {
|
||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"code": 6000,
|
"code": 6000,
|
||||||
"name": "customError",
|
"name": "betNotFilled",
|
||||||
"msg": "Custom error message"
|
"msg": "Bet is not filled yet!"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"types": [
|
"types": [
|
||||||
|
|
@ -262,10 +270,18 @@ export type Bets = {
|
||||||
"name": "owner",
|
"name": "owner",
|
||||||
"type": "pubkey"
|
"type": "pubkey"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ownerId",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "joiner",
|
"name": "joiner",
|
||||||
"type": "pubkey"
|
"type": "pubkey"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "joinerId",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "wager",
|
"name": "wager",
|
||||||
"type": "u64"
|
"type": "u64"
|
||||||
|
|
@ -295,5 +311,4 @@ export type Bets = {
|
||||||
"value": "\"anchor\""
|
"value": "\"anchor\""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"address": "HxsDuhD7wcPxcMsrYdteMYxkffuwff8HoxhZ7NuFtM37",
|
"address": "JAf3ZkQ469okXAzA6BKJeKBb9ZkCtZanULaUsapskoyn",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "bets",
|
"name": "bets",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
|
@ -115,6 +115,10 @@
|
||||||
"name": "wager",
|
"name": "wager",
|
||||||
"type": "u64"
|
"type": "u64"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "game_id",
|
"name": "game_id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -200,6 +204,10 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"args": [
|
"args": [
|
||||||
|
{
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "game_id",
|
"name": "game_id",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -238,8 +246,8 @@
|
||||||
"errors": [
|
"errors": [
|
||||||
{
|
{
|
||||||
"code": 6000,
|
"code": 6000,
|
||||||
"name": "CustomError",
|
"name": "BetNotFilled",
|
||||||
"msg": "Custom error message"
|
"msg": "Bet is not filled yet!"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"types": [
|
"types": [
|
||||||
|
|
@ -256,10 +264,18 @@
|
||||||
"name": "owner",
|
"name": "owner",
|
||||||
"type": "pubkey"
|
"type": "pubkey"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "owner_id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "joiner",
|
"name": "joiner",
|
||||||
"type": "pubkey"
|
"type": "pubkey"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "joiner_id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "wager",
|
"name": "wager",
|
||||||
"type": "u64"
|
"type": "u64"
|
||||||
|
|
@ -289,4 +305,4 @@
|
||||||
"value": "\"anchor\""
|
"value": "\"anchor\""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
6
src/shared/data_fetcher.ts
Normal file
6
src/shared/data_fetcher.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export async function fetchUserById(id: string) {
|
||||||
|
const res = await fetch(`https://vps.playpoolstudios.com/duelfi/api/get_user_by_id.php?id=${id}`);
|
||||||
|
if (!res.ok) return null;
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { Bet } from "@/types/Bet";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Game } from "@/types/Game";
|
import { Game } from "@/types/Game";
|
||||||
|
|
||||||
|
|
||||||
export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet[]> => {
|
export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet[]> => {
|
||||||
try {
|
try {
|
||||||
if (!wallets) return [];
|
if (!wallets) return [];
|
||||||
|
|
@ -39,7 +38,9 @@ export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet
|
||||||
return {
|
return {
|
||||||
id: betAcc.gameId,
|
id: betAcc.gameId,
|
||||||
owner: betAcc.owner.toBase58(),
|
owner: betAcc.owner.toBase58(),
|
||||||
|
owner_id:betAcc.ownerId,
|
||||||
joiner: betAcc.joiner ? betAcc.joiner.toBase58() : "Open",
|
joiner: betAcc.joiner ? betAcc.joiner.toBase58() : "Open",
|
||||||
|
joiner_id:betAcc.joinerId,
|
||||||
wager: betAcc.wager.toNumber() / LAMPORTS_PER_SOL
|
wager: betAcc.wager.toNumber() / LAMPORTS_PER_SOL
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
|
@ -120,7 +121,7 @@ export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function createBet(wallets:ConnectedSolanaWallet,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 = {
|
||||||
publicKey: new PublicKey(wallets.address),
|
publicKey: new PublicKey(wallets.address),
|
||||||
|
|
@ -134,7 +135,7 @@ export async function createBet(wallets:ConnectedSolanaWallet,selectedPrice:numb
|
||||||
const program = new Program<Bets>(idl, provider);
|
const program = new Program<Bets>(idl, provider);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const nonce = 1;
|
const nonce = getRandomInt(100000000);
|
||||||
const [bet_list_pda] = await PublicKey.findProgramAddress(
|
const [bet_list_pda] = await PublicKey.findProgramAddress(
|
||||||
[Buffer.from("bets_list")],
|
[Buffer.from("bets_list")],
|
||||||
program.programId
|
program.programId
|
||||||
|
|
@ -145,7 +146,7 @@ export async function createBet(wallets:ConnectedSolanaWallet,selectedPrice:numb
|
||||||
|
|
||||||
// Create transaction
|
// Create transaction
|
||||||
const tx = await program.methods
|
const tx = await program.methods
|
||||||
.createBet(new BN(selectedPrice * 1000000000), selectedGame.id, new BN(nonce))
|
.createBet(new BN(selectedPrice * 1000000000),uid, selectedGame.id, new BN(nonce))
|
||||||
.accounts({
|
.accounts({
|
||||||
betsList: bet_list_pda,
|
betsList: bet_list_pda,
|
||||||
})
|
})
|
||||||
|
|
@ -177,3 +178,7 @@ export async function createBet(wallets:ConnectedSolanaWallet,selectedPrice:numb
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRandomInt(max:number):number {
|
||||||
|
return Math.floor(Math.random() * max);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
export interface Bet {
|
export interface Bet {
|
||||||
id: string;
|
id: string;
|
||||||
owner: string;
|
owner: string;
|
||||||
|
owner_id:string;
|
||||||
joiner: string;
|
joiner: string;
|
||||||
|
joiner_id:string;
|
||||||
wager: number;
|
wager: number;
|
||||||
|
ownerProfile?: {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
bio: string;
|
||||||
|
x_profile_url: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user