From 1fa662feddf3ad53c5a9cbd106a0fcde41b05947 Mon Sep 17 00:00:00 2001 From: Replit user <> Date: Wed, 17 Apr 2024 07:29:47 +0000 Subject: [PATCH] Added modal messaging to pay button --- components/invoice-card.tsx | 15 ++- .../utility-components/redeem-button.tsx | 5 +- components/wallet/pay-button.tsx | 121 ++++++++++++++---- components/wallet/receive-button.tsx | 7 +- components/wallet/send-button.tsx | 16 ++- components/wallet/transactions.tsx | 19 ++- pages/wallet/index.tsx | 61 +++++---- 7 files changed, 181 insertions(+), 63 deletions(-) diff --git a/components/invoice-card.tsx b/components/invoice-card.tsx index 18a6516..7cf9333 100644 --- a/components/invoice-card.tsx +++ b/components/invoice-card.tsx @@ -20,7 +20,12 @@ import { ClipboardIcon, EnvelopeIcon, } from "@heroicons/react/24/outline"; -import { CashuMint, CashuWallet, getEncodedToken, Proof } from "@cashu/cashu-ts"; +import { + CashuMint, + CashuWallet, + getEncodedToken, + Proof, +} from "@cashu/cashu-ts"; import { getLocalStorageData, isUserLoggedIn, @@ -231,7 +236,9 @@ export default function InvoiceCard({ } const mintKeySetResponse = await mint.getKeySets(); const mintKeySetIds = mintKeySetResponse?.keysets; - const filteredProofs = tokens.filter((p: Proof) => mintKeySetIds?.includes(p.id)); + const filteredProofs = tokens.filter( + (p: Proof) => mintKeySetIds?.includes(p.id), + ); const tokenToSend = await wallet.send(price, filteredProofs); const encodedSendToken = getEncodedToken({ token: [ @@ -245,7 +252,9 @@ export default function InvoiceCard({ // captureInvoicePaidmetric(metricsInvoiceId, productData.id); // another metric to capture native Cashu payments is needed const changeProofs = tokenToSend?.returnChange; - const remainingProofs = tokens.filter((p: Proof) => !mintKeySetIds?.includes(p.id)); + const remainingProofs = tokens.filter( + (p: Proof) => !mintKeySetIds?.includes(p.id), + ); let proofArray; if (changeProofs.length >= 1 && changeProofs) { proofArray = [...remainingProofs, ...changeProofs]; diff --git a/components/utility-components/redeem-button.tsx b/components/utility-components/redeem-button.tsx index e72edba..e0a452f 100644 --- a/components/utility-components/redeem-button.tsx +++ b/components/utility-components/redeem-button.tsx @@ -114,7 +114,10 @@ export default function RedeemButton({ token }: { token: string }) { const changeProofs = response?.change; const changeAmount = Array.isArray(changeProofs) && changeProofs.length > 0 - ? changeProofs.reduce((acc, current: Proof) => acc + current.amount, 0) + ? changeProofs.reduce( + (acc, current: Proof) => acc + current.amount, + 0, + ) : 0; if (changeAmount >= 1 && changeProofs) { setRedemptionChangeAmount(changeAmount); diff --git a/components/wallet/pay-button.tsx b/components/wallet/pay-button.tsx index ca9903a..43d210e 100644 --- a/components/wallet/pay-button.tsx +++ b/components/wallet/pay-button.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useContext, useMemo } from "react"; import { useForm, Controller } from "react-hook-form"; import Link from "next/link"; -import { BoltIcon } from "@heroicons/react/24/outline"; +import { BoltIcon, CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/outline"; import { Button, Textarea, @@ -15,6 +15,7 @@ import { getLocalStorageData } from "../utility/nostr-helper-functions"; import { SHOPSTRBUTTONCLASSNAMES } from "../utility/STATIC-VARIABLES"; import { LightningAddress } from "@getalby/lightning-tools"; import { CashuMint, CashuWallet, Proof } from "@cashu/cashu-ts"; +// import { Invoice } from "@getalby/lightning-tools"; import { formatWithCommas } from "../utility-components/display-monetary-info"; const PayButton = () => { @@ -25,6 +26,7 @@ const PayButton = () => { const [wallet, setWallet] = useState(); const [proofs, setProofs] = useState([]); + // const [totalAmount, setTotalAmount] = useState(0); const [feeAmount, setFeeAmount] = useState(""); const { mints, tokens, history } = getLocalStorageData(); @@ -53,6 +55,23 @@ const PayButton = () => { await handlePay(invoiceString); }; + const calculateFee = async (invoice) => { + if (invoice && /^lnbc/.test(invoice)) { + const fee = await wallet?.getFee(invoice); + if (fee) { + setFeeAmount(formatWithCommas(fee, "sats")); + // const invoiceValue = new Invoice({ invoice }); + // const { satoshi } = invoiceValue; + // const total = satoshi + fee; + // setTotalAmount(total); + } else { + setFeeAmount(""); + } + } else { + setFeeAmount(""); + } + }; + const handlePay = async (invoiceString: string) => { setIsPaid(false); setPaymentFailed(false); @@ -98,7 +117,6 @@ const PayButton = () => { setIsPaid(true); } catch (error) { console.log(error); - setIsPaid(true); setPaymentFailed(true); } }; @@ -140,11 +158,7 @@ const PayButton = () => { control={payControl} rules={{ required: "A Lightning invoice is required.", - validate: async (value) => { - const fee = await wallet?.getFee(value); - if (fee && fee >= 1) { - setFeeAmount(formatWithCommas(fee, "sats")); - } + validate: (value) => { return ( /^lnbc/.test(value) || "The lightning invoice must start with 'lnbc'." @@ -170,34 +184,87 @@ const PayButton = () => { labelPlacement="inside" isInvalid={isErrored} errorMessage={errorMessage} - // controller props - onChange={onChange} // send value to hook form + onChange={async (e) => { + const newValue = e.target.value; + onChange(newValue); + await calculateFee(newValue); + }} onBlur={onBlur} // notify when input is touched/blur value={value} /> - {feeAmount && ( -
+ {feeAmount && feeAmount >= 1 && ( +
Estimated Fee: {feeAmount}
)} - {isPaid && ( + {/* {totalAmount && totalAmount >= 1 && ( +
+ Total Amount: {totalAmount} sats +
+ )} */} + {paymentFailed ? ( <> - {paymentFailed ? ( -
- Invoice payment failed! No routes could be found, - or you don't have enough funds. Please try - again with a new invoice, or change your mint in - settings. -
- ) : ( - <> -
- Invoice paid successfully! -
- - )} + setPaymentFailed(false)} + // className="bg-light-fg dark:bg-dark-fg text-black dark:text-white" + classNames={{ + body: "py-6 ", + backdrop: "bg-[#292f46]/50 backdrop-opacity-60", + header: "border-b-[1px] border-[#292f46]", + footer: "border-t-[1px] border-[#292f46]", + closeButton: "hover:bg-black/5 active:bg-white/10", + }} + isDismissable={true} + scrollBehavior={"normal"} + placement={"center"} + size="2xl" + > + + +
+ +
Invoice payment failed! No routes could be found, + or you don't have enough funds. Please try + again with a new invoice, or change your mint in + settings.
+
+
+
+
- )} + ) : null} + {isPaid ? ( + <> + setIsPaid(false)} + // className="bg-light-fg dark:bg-dark-fg text-black dark:text-white" + classNames={{ + body: "py-6 ", + backdrop: "bg-[#292f46]/50 backdrop-opacity-60", + header: "border-b-[1px] border-[#292f46]", + footer: "border-t-[1px] border-[#292f46]", + closeButton: "hover:bg-black/5 active:bg-white/10", + }} + isDismissable={true} + scrollBehavior={"normal"} + placement={"center"} + size="2xl" + > + + +
+ +
Token successfully claimed!
+
+
+
+
+ + ) : null} ); }} diff --git a/components/wallet/receive-button.tsx b/components/wallet/receive-button.tsx index b459af9..e6a1a02 100644 --- a/components/wallet/receive-button.tsx +++ b/components/wallet/receive-button.tsx @@ -16,7 +16,12 @@ import { } from "@nextui-org/react"; import { SHOPSTRBUTTONCLASSNAMES } from "../utility/STATIC-VARIABLES"; import { getLocalStorageData } from "../utility/nostr-helper-functions"; -import { CashuMint, CashuWallet, getDecodedToken, Proof } from "@cashu/cashu-ts"; +import { + CashuMint, + CashuWallet, + getDecodedToken, + Proof, +} from "@cashu/cashu-ts"; const ReceiveButton = () => { const [showReceiveModal, setShowReceiveModal] = useState(false); diff --git a/components/wallet/send-button.tsx b/components/wallet/send-button.tsx index 3b01fa1..afebbe3 100644 --- a/components/wallet/send-button.tsx +++ b/components/wallet/send-button.tsx @@ -5,7 +5,6 @@ import { ClipboardIcon, CheckIcon, CheckCircleIcon, - XCircleIcon, } from "@heroicons/react/24/outline"; import { Card, @@ -23,7 +22,12 @@ import { } from "@nextui-org/react"; import { SHOPSTRBUTTONCLASSNAMES } from "../utility/STATIC-VARIABLES"; import { getLocalStorageData } from "../utility/nostr-helper-functions"; -import { CashuMint, CashuWallet, getEncodedToken, Proof } from "@cashu/cashu-ts"; +import { + CashuMint, + CashuWallet, + getEncodedToken, + Proof, +} from "@cashu/cashu-ts"; const SendButton = () => { const [showSendModal, setShowSendModal] = useState(false); @@ -61,7 +65,9 @@ const SendButton = () => { const wallet = new CashuWallet(mint); const mintKeySetResponse = await mint.getKeySets(); const mintKeySetIds = mintKeySetResponse?.keysets; - const filteredProofs = tokens.filter((p: Proof) => mintKeySetIds?.includes(p.id)); + const filteredProofs = tokens.filter( + (p: Proof) => mintKeySetIds?.includes(p.id), + ); const tokenToSend = await wallet.send(numSats, filteredProofs); const encodedSendToken = getEncodedToken({ token: [ @@ -73,7 +79,9 @@ const SendButton = () => { }); setNewToken(encodedSendToken); const changeProofs = tokenToSend?.returnChange; - const remainingProofs = tokens.filter((p: Proof) => !mintKeySetIds?.includes(p.id)); + const remainingProofs = tokens.filter( + (p: Proof) => !mintKeySetIds?.includes(p.id), + ); let proofArray; if (changeProofs.length >= 1 && changeProofs) { proofArray = [...remainingProofs, ...changeProofs]; diff --git a/components/wallet/transactions.tsx b/components/wallet/transactions.tsx index 87f4135..33934c1 100644 --- a/components/wallet/transactions.tsx +++ b/components/wallet/transactions.tsx @@ -14,10 +14,21 @@ const Transactions = () => { const [history, setHistory] = useState([]); useEffect(() => { - const localData = getLocalStorageData(); - if (localData && localData.history) { - setHistory(localData.history); - } + // Function to fetch and update transactions + const fetchAndUpdateTransactions = () => { + const localData = getLocalStorageData(); + if (localData && localData.history) { + setHistory(localData.history); + } + }; + // Initial fetch + fetchAndUpdateTransactions(); + // Set up polling with setInterval + const interval = setInterval(() => { + fetchAndUpdateTransactions(); + }, 5000); // Polling every 5000 milliseconds (5 seconds) + // Clean up on component unmount + return () => clearInterval(interval); }, []); const formatDate = (timestamp: number) => { diff --git a/pages/wallet/index.tsx b/pages/wallet/index.tsx index e80cbb8..e0e02cf 100644 --- a/pages/wallet/index.tsx +++ b/pages/wallet/index.tsx @@ -14,29 +14,44 @@ const Wallet = () => { const [walletBalance, setWalletBalance] = useState(0); const [mint, setMint] = useState(""); - const { mints, tokens } = getLocalStorageData(); - - useEffect(() => { - let tokensTotal = - tokens && tokens.length >= 1 - ? tokens.reduce((acc, token: Proof) => acc + token.amount, 0) - : 0; - setTotalBalance(tokensTotal); - }, [tokens]); - useEffect(() => { - const getWalleteBalance = async () => { - const currentMint = new CashuMint(mints[0]); - setMint(mints[0]); - const mintKeySetResponse = await currentMint.getKeySets(); - const mintKeySetIds = mintKeySetResponse?.keysets; - const filteredProofs = tokens.filter((p: Proof) => mintKeySetIds?.includes(p.id)); - let walletTotal = - filteredProofs && filteredProofs.length >= 1 ? filteredProofs.reduce((acc, p: Proof) => acc + p.amount, 0) : 0 - setWalletBalance(walletTotal); - } - getWalleteBalance(); - }, [mints, tokens]); + // Function to fetch and update balances + const fetchAndUpdateBalances = async () => { + const localData = getLocalStorageData(); + if (localData && localData.tokens) { + let tokensTotal = + localData.tokens && localData.tokens.length >= 1 + ? localData.tokens.reduce( + (acc, token: Proof) => acc + token.amount, + 0, + ) + : 0; + setTotalBalance(tokensTotal); + } + if (localData && localData.mints && localData.tokens) { + const currentMint = new CashuMint(localData.mints[0]); + setMint(localData.mints[0]); + const mintKeySetResponse = await currentMint.getKeySets(); + const mintKeySetIds = mintKeySetResponse?.keysets; + const filteredProofs = localData.tokens.filter( + (p: Proof) => mintKeySetIds?.includes(p.id), + ); + let walletTotal = + filteredProofs && filteredProofs.length >= 1 + ? filteredProofs.reduce((acc, p: Proof) => acc + p.amount, 0) + : 0; + setWalletBalance(walletTotal); + } + }; + // Initial fetch + fetchAndUpdateBalances(); + // Set up polling with setInterval + const interval = setInterval(() => { + fetchAndUpdateBalances(); + }, 5000); // Polling every 5000 milliseconds (5 seconds) + // Clean up on component unmount + return () => clearInterval(interval); + }, []); return (
@@ -46,7 +61,7 @@ const Wallet = () => {

-

+

{mint}: {totalBalance} sats