209 lines
7.0 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|