duelfi_web/src/components/PrivyButton.tsx
2025-03-30 00:59:33 +05:30

209 lines
7.0 KiB
TypeScript

"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>
</>
);
}