wallet
This commit is contained in:
parent
ae64342e76
commit
0a8d247391
4637
package-lock.json
generated
4637
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
|
|
@ -10,15 +10,22 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@fontsource/manrope": "^5.2.5",
|
||||
"@privy-io/react-auth": "^2.7.2",
|
||||
"@solana/web3.js": "^1.98.0",
|
||||
"bs58": "^6.0.0",
|
||||
"ethers": "^6.13.5",
|
||||
"next": "15.2.4",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-icons": "^5.5.0"
|
||||
"react": "^19.1.0",
|
||||
"react-auth": "^1.0.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-toastify": "^11.0.5",
|
||||
"viem": "^2.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/node": "^22",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
|
|
|
|||
|
|
@ -31,3 +31,24 @@ body {
|
|||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
/* Grid animation */
|
||||
@keyframes scrollGrid {
|
||||
from {
|
||||
transform: perspective(1000px) rotateX(45deg) translateY(0);
|
||||
}
|
||||
to {
|
||||
transform: perspective(1000px) rotateX(45deg) translateY(-100px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Grid container */
|
||||
.grid-container {
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-image:
|
||||
linear-gradient(rgba(100, 100, 100, 0.3) 1px, transparent 1px),
|
||||
linear-gradient(to right, rgba(100, 100, 100, 0.3) 1px, transparent 1px);
|
||||
background-size: 50px 50px;
|
||||
animation: scrollGrid 2s linear infinite;
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
|
||||
import { PrivyProvider } from "@privy-io/react-auth";
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
|
|
@ -23,6 +23,7 @@ export default function RootLayout({
|
|||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
import Footer from "@/components/Footer";
|
||||
import Header from "@/components/Header";
|
||||
import HeroSection from "@/components/Home";
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
|
||||
<div className="bg-[rgb(22,22,22)]">
|
||||
<Header/>
|
||||
<HeroSection/>
|
||||
<Footer/>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
|
|
|||
11
src/components/3DGrid.tsx
Normal file
11
src/components/3DGrid.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
"use client";
|
||||
|
||||
export default function ThreeDGrid() {
|
||||
return (
|
||||
<div className="relative w-full h-[100px] overflow-hidden bg-black">
|
||||
<div className="absolute inset-0 perspective-1000">
|
||||
<div className="grid-container animate-scrollGrid" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,16 +4,39 @@ import Link from "next/link";
|
|||
import Image from "next/image";
|
||||
import { FaTelegram, FaXTwitter } from "react-icons/fa6";
|
||||
import { useState } from "react";
|
||||
import { PrivyProvider, usePrivy } from "@privy-io/react-auth";
|
||||
import PrivyButton from "./PrivyButton";
|
||||
import { toSolanaWalletConnectors } from "@privy-io/react-auth/solana";
|
||||
|
||||
export default function Header() {
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||
|
||||
const { login, logout, user } = usePrivy();
|
||||
// Toggle the nav drawer
|
||||
const toggleDrawer = () => {
|
||||
setIsDrawerOpen(!isDrawerOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<><PrivyProvider
|
||||
appId="cm8spd7l600lfe4am1phq9qq8"
|
||||
clientId="client-WY5i4HS6T7JP44iKMQUyZXwftzwKLFvEsGvMtFY1znXSj"
|
||||
config={{
|
||||
// Customize Privy's appearance in your app
|
||||
appearance: {
|
||||
theme: 'dark',
|
||||
accentColor: '#f89016',
|
||||
logo: 'https://your-logo-url'
|
||||
},
|
||||
// Create embedded wallets for users who don't have a wallet
|
||||
embeddedWallets: {
|
||||
createOnLogin: 'users-without-wallets'
|
||||
},
|
||||
externalWallets:{
|
||||
solana:{connectors:toSolanaWalletConnectors()}
|
||||
}
|
||||
|
||||
}}
|
||||
>
|
||||
<header className="w-full bg-[rgb(22,22,22)] shadow-md">
|
||||
<div className="container mx-auto flex items-center justify-between p-12 max-w-screen-xl w-full">
|
||||
{/* Logo */}
|
||||
|
|
@ -69,10 +92,11 @@ export default function Header() {
|
|||
/>
|
||||
</a>
|
||||
|
||||
{/* Login Button */}
|
||||
{/* Login Button
|
||||
<button className="px-15 py-3 bg-[rgb(248,144,22)] text-black font-bold rounded-lg transition-transform duration-300 hover:scale-115 hover:bg-white hover:text-[rgb(248,144,22)]">
|
||||
Connected
|
||||
</button>
|
||||
</button>*/}
|
||||
<PrivyButton></PrivyButton>
|
||||
</div>
|
||||
|
||||
{/* Mobile-only view (Logo & Login) */}
|
||||
|
|
@ -171,5 +195,6 @@ export default function Header() {
|
|||
</div>
|
||||
)}
|
||||
</header>
|
||||
</PrivyProvider></>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
122
src/components/Home.tsx
Normal file
122
src/components/Home.tsx
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import OpenGames from "./OpenGames";
|
||||
|
||||
export default function HeroSection() {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className="flex flex-col items-center text-center py-16">
|
||||
{/* Centered Image */}
|
||||
<Image
|
||||
src="/duelfiassets/Playing on Arcade Machine no BG.png"
|
||||
alt="DuelFi Hero"
|
||||
width={150}
|
||||
height={50}
|
||||
className="mb-6"
|
||||
/>
|
||||
|
||||
{/* Big Text */}
|
||||
<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>
|
||||
|
||||
{/* Create a Game Button */}
|
||||
<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">
|
||||
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>
|
||||
|
||||
{/* How it Works */}
|
||||
<p
|
||||
className="mt-4 text-gray-400 cursor-pointer hover:underline font-mono"
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
>
|
||||
How it works?..
|
||||
</p>
|
||||
|
||||
<OpenGames />
|
||||
</section>
|
||||
|
||||
{/* Modal */}
|
||||
{isModalOpen && (
|
||||
<div className="fixed inset-0 bg-black/70 flex justify-center items-start pt-10 z-50">
|
||||
<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"
|
||||
>
|
||||
<h2 className="text-xl font-bold mb-4">How It Works</h2>
|
||||
|
||||
{/* Steps */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-[rgb(248,144,22)] font-bold text-lg">1. Connect Your Wallet</h3>
|
||||
<p className="text-xs text-gray-400 font-mono">
|
||||
Start by linking your wallet securely.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-[rgb(248,144,22)] font-bold text-lg">2. Create or Join Game</h3>
|
||||
<p className="text-xs text-gray-400 font-mono">
|
||||
Pick a game and set a wager, or join an existing match.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-[rgb(248,144,22)] font-bold text-lg">3. Place Your Bet</h3>
|
||||
<p className="text-xs text-gray-400 font-mono">
|
||||
Confirm your wager and get ready to play.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-[rgb(248,144,22)] font-bold text-lg">4. Claim Your Winnings</h3>
|
||||
<p className="text-xs text-gray-400 font-mono">
|
||||
Win the game and collect your rewards instantly!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Okay Button */}
|
||||
<button
|
||||
className="mt-6 w-full bg-[rgb(248,144,22)] text-black font-semibold py-2 rounded-xl transition hover:bg-white hover:scale-105"
|
||||
onClick={() => setIsModalOpen(false)}
|
||||
>
|
||||
Okay
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
63
src/components/OpenGames.tsx
Normal file
63
src/components/OpenGames.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
|
||||
const games = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Block Drop",
|
||||
entryFee: "0.1 SOL",
|
||||
thumbnail: "/duelfiassets/Block Drop Illustration.jpeg",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Venom Trails",
|
||||
entryFee: "0.02 ETH",
|
||||
thumbnail: "/duelfiassets/Venom Trail Game Cover Illustration.png",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Wall Smash",
|
||||
entryFee: "0.05 ETH",
|
||||
thumbnail: "/duelfiassets/Wall Smash Game Cover Illustration.png",
|
||||
},
|
||||
];
|
||||
|
||||
export default function OpenGames() {
|
||||
return (
|
||||
<section className="py-16 px-6">
|
||||
<h2 className="text-3xl font-bold text-white mb-6">Open Games</h2>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{games.map((game) => (
|
||||
<div
|
||||
key={game.id}
|
||||
className="relative group bg-[rgb(30,30,30)] rounded-2xl p-2 w-50 overflow-hidden cursor-pointer transition-all duration-300 hover:-translate-y-2"
|
||||
>
|
||||
{/* Game Thumbnail */}
|
||||
<div className="relative w-full h-40 overflow-hidden rounded-xl">
|
||||
<Image
|
||||
src={game.thumbnail}
|
||||
alt={game.name}
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
className="transition-transform duration-300 group-hover:scale-110"
|
||||
/>
|
||||
|
||||
{/* Join Button - Centered inside Image */}
|
||||
<button className="absolute inset-0 flex items-center justify-center bg-black/60 text-white font-semibold rounded-xl opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
Join Game
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Game Info - Left Aligned */}
|
||||
<div className="mt-4 px-3 text-left">
|
||||
<h3 className="text-lg font-semibold text-white py-2">{game.name}</h3>
|
||||
<p className="text-xs text-gray-400 font-mono py-1">Entry Fee</p>
|
||||
<p className="text-xs text-white font-mono py-1">{game.entryFee}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
208
src/components/PrivyButton.tsx
Normal file
208
src/components/PrivyButton.tsx
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { usePrivy, useSolanaWallets, useWallets } from "@privy-io/react-auth";
|
||||
import { Connection, PublicKey } from "@solana/web3.js"; // Solana Web3.js imports
|
||||
import { toast, ToastContainer } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import bs58 from "bs58"; // Import bs58 library to validate the address format
|
||||
|
||||
export default function PrivyButton() {
|
||||
const { login, logout, user } = usePrivy();
|
||||
const { wallets } = useSolanaWallets();
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [solWallet, setSolWallet] = useState("");
|
||||
const [solBalance, setSolBalance] = useState("--"); // Placeholder
|
||||
const [tokenBalance, setTokenBalance] = useState("--"); // Placeholder
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSolBalance = async () => {
|
||||
|
||||
wallets.forEach((wallet)=>{
|
||||
console.log(wallet.address + " : " + wallet.type);
|
||||
if(wallet.type == "solana"){
|
||||
setSolWallet(wallet.address);
|
||||
}
|
||||
})
|
||||
if (solWallet!="") {
|
||||
const walletAddress = solWallet; // Access the Solana wallet address
|
||||
try {
|
||||
const connection = new Connection("https://api.devnet.solana.com", "confirmed");
|
||||
const publicKey = new PublicKey(walletAddress);
|
||||
|
||||
const balance = await connection.getBalance(publicKey);
|
||||
setSolBalance((balance / 1e9).toFixed(2)); // Convert lamports to SOL (1 SOL = 1e9 lamports)
|
||||
} catch (error) {
|
||||
console.error("Failed to get balance:", error);
|
||||
setSolBalance("Error");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (wallets) {
|
||||
fetchSolBalance();
|
||||
}
|
||||
}, [user, wallets]); // Added wallets to dependency array
|
||||
|
||||
// Function to validate if the address is a valid Solana address (base58)
|
||||
const isValidSolanaAddress = (address: string) => {
|
||||
try {
|
||||
bs58.decode(address); // Attempt to decode the address
|
||||
return true; // If no error, it's a valid base58 Solana address
|
||||
} catch (e) {
|
||||
return false; // Invalid address format
|
||||
}
|
||||
};
|
||||
|
||||
// Close modal when clicking outside
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
|
||||
setIsModalOpen(false);
|
||||
}
|
||||
}
|
||||
if (isModalOpen) {
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
} else {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
}
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, [isModalOpen]);
|
||||
|
||||
const customLogin = () => {
|
||||
login({ walletChainType: "solana-only" });
|
||||
};
|
||||
|
||||
const copyToClipboard = async () => {
|
||||
const walletAddress = solWallet ?? ""; // Use an empty string if wallet address is undefined or null
|
||||
if (walletAddress) {
|
||||
await navigator.clipboard.writeText(walletAddress);
|
||||
toast.success("Wallet address copied!"); // Toast notification for successful copy
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{user ? (
|
||||
<button
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
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>
|
||||
<p className="text-xs font-mono text-gray-700">
|
||||
{solWallet?.slice(0, 6)}...
|
||||
{solWallet?.slice(-4)}
|
||||
</p>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={customLogin}
|
||||
className="bg-[rgb(248,144,22)] hover:bg-[rgb(248,200,100)] text-white px-6 py-3 rounded-md transition duration-300 hover:scale-105"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Modal */}
|
||||
{isModalOpen && user ? (
|
||||
<div className="fixed inset-0 bg-black/70 flex justify-center items-start pt-10 z-50">
|
||||
<div
|
||||
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"
|
||||
>
|
||||
<h2 className="text-xl font-bold mb-4">Account Info</h2>
|
||||
|
||||
{/* Wallet Address with Copy Button */}
|
||||
<div className="mb-4">
|
||||
<p className="text-gray-400 text-xs font-mono">Connected Wallet</p>
|
||||
<div className="flex items-center justify-between bg-gray-800 p-2 rounded-md">
|
||||
<p className="font-mono text-sm truncate">{solWallet}</p>
|
||||
<button
|
||||
onClick={copyToClipboard}
|
||||
className="text-gray-300 hover:text-white transition p-1"
|
||||
>
|
||||
📋
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SOL Balance */}
|
||||
<div className="mb-4">
|
||||
<p className="text-gray-400 text-xs font-mono">SOL Balance</p>
|
||||
<p className="text-sm">{solBalance} SOL</p>
|
||||
</div>
|
||||
|
||||
{/* Token Balance */}
|
||||
<div className="mb-4">
|
||||
<p className="text-gray-400 text-xs font-mono">Token Balance</p>
|
||||
<p className="text-sm">{tokenBalance}</p>
|
||||
</div>
|
||||
|
||||
{/* Socials */}
|
||||
{(user?.discord?.username || user?.email?.address) && (
|
||||
<div className="mb-4">
|
||||
<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
|
||||
</a>
|
||||
)}
|
||||
{user?.email?.address && (
|
||||
<span className="text-gray-400 text-sm">{user?.email?.address}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sign Out Button */}
|
||||
<button
|
||||
className="mt-6 w-full bg-[rgb(248,144,22)] text-black font-semibold py-2 rounded-xl transition hover:bg-white"
|
||||
onClick={() => {
|
||||
logout();
|
||||
setIsModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* Toast container */}
|
||||
<ToastContainer
|
||||
position="top-right"
|
||||
autoClose={3000}
|
||||
hideProgressBar
|
||||
newestOnTop
|
||||
closeButton={false}
|
||||
pauseOnHover
|
||||
draggable
|
||||
pauseOnFocusLoss
|
||||
/>
|
||||
|
||||
{/* 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user