UNPKG

leumas-private-shared

Version:

Private React JSX Package For Leumas Shared Components, Headers, Footers, Asides, Login Pages, API Key Manager and much more. Styles and everything reusable to avoid DRY code across all of our subdomains

195 lines (158 loc) 7.1 kB
// PayWithMetaMaskButton.jsx import React, { useState, useEffect } from 'react'; import MetamaskSVG from "./MetamaskSVG.svg" import Spinner2 from '../../Loaders/Spinner2'; import MessageResponse from './MessageResponse'; const PayWithMetaMaskButton = ({ amount, recipientAddress, onPaymentSuccess, digitalProduct = null, productName = 'Item or Service', productType = 'Digital Download' }) => { const [account, setAccount] = useState(null); const [token, setToken] = useState("ETH"); // default token is ETH const [message, setMessage] = useState(null); const [messageType, setMessageType] = useState(null); const [transactionHash, setTransactionHash] = useState(null); const tokensList = ["ETH"]; // supported tokens const [transactionSuccess, setTransactionSuccess] = useState(false); const allowedProductTypes = ["Digital Download", "Monthly Subscription", "Service", "Physical Product" , "Donation", "Web Tool"]; const [loading , setLoading] = useState(false); if (!allowedProductTypes.includes(productType)) { throw new Error(`Invalid productType: ${productType}. Must be one of: ${allowedProductTypes.join(', ')}`); } const handleTokenChange = (e) => { setToken(e.target.value); } const connectMetaMask = async () => { if (typeof window.ethereum !== 'undefined') { try { const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); setAccount(accounts[0]); } catch (error) { console.error('User denied account access...'); } } else { alert('Please install MetaMask to use this feature!'); } }; const getConvertedAmount = () => { if (token === 'ETH' && conversionRate) { return amount / conversionRate; } return amount; // For other tokens, we keep the amount as is (no conversion) }; const [errorMessage, setErrorMessage] = useState(null); // State to hold error messages const sendTransaction = async () => { console.log('Sending transaction to:', recipientAddress); if (!account) { await connectMetaMask(); const accounts = await window.ethereum.request({ method: 'eth_accounts' }); if (!accounts.length) { setErrorMessage('Transaction declined by the user.'); return; } } if (typeof window.ethereum !== 'undefined' && account) { const convertedAmount = getConvertedAmount(); const valueInWei = BigInt(Math.round(convertedAmount * 10**18)).toString(16); const transactionParameters = { from: account, to: recipientAddress, value: '0x' + valueInWei, }; setLoading(true); try { const txHash = await window.ethereum.request({ method: 'eth_sendTransaction', params: [transactionParameters], }); console.log('Transaction Hash:', txHash); setTransactionHash(txHash); const getReceipt = async () => { const txReceipt = await window.ethereum.request({ method: 'eth_getTransactionReceipt', params: [txHash], }); const etherscanURL = `https://goerli.etherscan.io/tx/${txReceipt}`; if (txReceipt && txReceipt.status === '0x1') { console.log('Transaction confirmed on Ethereum:', txReceipt); setTransactionSuccess(true); setMessage( <span> Transaction Successful <a href={etherscanURL} target="_blank" rel="noopener noreferrer"> {txHash} </a> </span> ); setMessageType('success'); setLoading(false); // Notify parent components that payment was successful if (typeof onPaymentSuccess === "function") { onPaymentSuccess(); } } else if (txReceipt) { console.error('Transaction failed:', txReceipt); setErrorMessage('Payment could not be processed.'); setLoading(false); } else { setTimeout(getReceipt, 5000); } }; getReceipt(); } catch (error) { console.error('Payment failed:', error.message); setLoading(false); setErrorMessage(error.message.includes('User denied') ? 'Transaction declined by the user.' : 'Payment could not be processed.'); } } }; const [conversionRate, setConversionRate] = useState(null); // ETH to USD conversion rate // Function to get the current ETH to USD conversion rate from CoinGecko const fetchConversionRate = async () => { const backendEndpoint = `${import.meta.env.VITE_REACT_APP_LEUMAS_API_ENDPOINT}/getConversionRate`; try { const response = await fetch(backendEndpoint); const data = await response.json(); setConversionRate(data.ethereum.usd); // Update this line based on the actual structure of the response } catch (error) { console.error("Failed to fetch conversion rate:", error); } }; useEffect(() => { fetchConversionRate(); }, []); const convertedAmount = amount / conversionRate; const displayAmount = getConvertedAmount(); return ( <div className="flex flex-col items-center space-y-4 border-glow p-4 rounded-md shadow-md"> <h2 className="text-lg font-bold text-black flex items-center gap-2">{productName} - <p className='text-blue-400'>{productType}</p></h2> <div className="flex items-center space-x-4"> <img src={MetamaskSVG} alt="MetaMask" className="w-6 h-6 mr-2" /> <select value={token} onChange={handleTokenChange} className="bg-white text-gray-700 py-2 px-3 rounded border border-gray-300 focus:border-green-500 focus:ring focus:ring-green-200 focus:ring-opacity-50" > {tokensList.map(t => <option value={t} key={t}>{t}</option>)} </select> <button className="bg-green-500 hover:bg-green-700 active:bg-green-900 text-white font-bold py-2 px-4 rounded shadow-md transition-transform transform active:scale-95" onClick={sendTransaction} > Price: {displayAmount.toFixed(5)} {token} ({amount} USD) </button> </div> {loading && <Spinner2 loading={true}/>} {transactionSuccess && ( <MessageResponse message={message} messageType={messageType} /> )} </div> ); }; export default PayWithMetaMaskButton;