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": {
|
"dependencies": {
|
||||||
"@fontsource/manrope": "^5.2.5",
|
"@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",
|
"next": "15.2.4",
|
||||||
"react": "^19.0.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-auth": "^1.0.0",
|
||||||
"react-icons": "^5.5.0"
|
"react-dom": "^19.1.0",
|
||||||
|
"react-icons": "^5.5.0",
|
||||||
|
"react-toastify": "^11.0.5",
|
||||||
|
"viem": "^2.24.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
"@types/node": "^20",
|
"@types/node": "^22",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
|
|
|
||||||
|
|
@ -31,3 +31,24 @@ body {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
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 type { Metadata } from "next";
|
||||||
import { Geist, Geist_Mono } from "next/font/google";
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
import { PrivyProvider } from "@privy-io/react-auth";
|
||||||
const geistSans = Geist({
|
const geistSans = Geist({
|
||||||
variable: "--font-geist-sans",
|
variable: "--font-geist-sans",
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
|
|
@ -23,6 +23,7 @@ export default function RootLayout({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
import Header from "@/components/Header";
|
import Header from "@/components/Header";
|
||||||
|
import HeroSection from "@/components/Home";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<div className="bg-[rgb(22,22,22)]">
|
<div className="bg-[rgb(22,22,22)]">
|
||||||
<Header/>
|
<Header/>
|
||||||
|
<HeroSection/>
|
||||||
<Footer/>
|
<Footer/>
|
||||||
</div>
|
</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 Image from "next/image";
|
||||||
import { FaTelegram, FaXTwitter } from "react-icons/fa6";
|
import { FaTelegram, FaXTwitter } from "react-icons/fa6";
|
||||||
import { useState } from "react";
|
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() {
|
export default function Header() {
|
||||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||||
|
const { login, logout, user } = usePrivy();
|
||||||
// Toggle the nav drawer
|
// Toggle the nav drawer
|
||||||
const toggleDrawer = () => {
|
const toggleDrawer = () => {
|
||||||
setIsDrawerOpen(!isDrawerOpen);
|
setIsDrawerOpen(!isDrawerOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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">
|
<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">
|
<div className="container mx-auto flex items-center justify-between p-12 max-w-screen-xl w-full">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
|
|
@ -69,10 +92,11 @@ export default function Header() {
|
||||||
/>
|
/>
|
||||||
</a>
|
</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)]">
|
<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
|
Connected
|
||||||
</button>
|
</button>*/}
|
||||||
|
<PrivyButton></PrivyButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile-only view (Logo & Login) */}
|
{/* Mobile-only view (Logo & Login) */}
|
||||||
|
|
@ -171,5 +195,6 @@ export default function Header() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</header>
|
</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