From bc5e039be1718665d3594dcb51eeae2267b2e5df Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 18 Jan 2026 22:35:45 +0000 Subject: [PATCH] feat: improve zap wallet payment flow UX Improvements to the zap window to better communicate wallet payment status: - Add clear "Paying with wallet..." message during NWC payment attempts - Show QR code immediately on payment timeout or failure - Improve error messages with actionable guidance - Always display "Open in External Wallet" option in QR view - Rename "Retry with Wallet" to "Retry with NWC Wallet" for clarity - Generate QR code upfront to enable instant display on errors This provides better feedback when wallet payments fail or timeout, giving users clear fallback options without confusion. --- src/components/ZapWindow.tsx | 112 ++++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 47 deletions(-) diff --git a/src/components/ZapWindow.tsx b/src/components/ZapWindow.tsx index 861327e..0e308fc 100644 --- a/src/components/ZapWindow.tsx +++ b/src/components/ZapWindow.tsx @@ -120,6 +120,7 @@ export function ZapWindow({ const [selectedAmount, setSelectedAmount] = useState(null); const [customAmount, setCustomAmount] = useState(""); const [isProcessing, setIsProcessing] = useState(false); + const [isPayingWithWallet, setIsPayingWithWallet] = useState(false); const [isPaid, setIsPaid] = useState(false); const [qrCodeUrl, setQrCodeUrl] = useState(""); const [invoice, setInvoice] = useState(""); @@ -365,9 +366,15 @@ export function ZapWindow({ const invoiceText = invoiceResponse.pr; + // Generate QR code upfront so we can show it immediately on error + const qrUrl = await generateQrCode(invoiceText); + setQrCodeUrl(qrUrl); + setInvoice(invoiceText); + // Step 5: Pay or show QR code if (useWallet && wallet && walletInfo?.methods.includes("pay_invoice")) { // Pay with NWC wallet with timeout + setIsPayingWithWallet(true); try { // Race between payment and 30 second timeout const paymentPromise = payInvoice(invoiceText); @@ -379,25 +386,27 @@ export function ZapWindow({ await refreshBalance(); setIsPaid(true); + setIsPayingWithWallet(false); toast.success(`⚡ Zapped ${amount} sats to ${recipientName}!`); } catch (error) { + // Payment failed or timed out - show QR code immediately + setIsPayingWithWallet(false); + setPaymentTimedOut(true); + setShowQrDialog(true); + + // Show specific error message if (error instanceof Error && error.message === "TIMEOUT") { - // Payment timed out - show QR code with retry option - setPaymentTimedOut(true); - const qrUrl = await generateQrCode(invoiceText); - setQrCodeUrl(qrUrl); - setInvoice(invoiceText); - setShowQrDialog(true); + toast.error("Payment timed out. Use QR code or retry with wallet."); } else { - // Other payment error - re-throw - throw error; + toast.error( + error instanceof Error + ? `Payment failed: ${error.message}` + : "Payment failed. Use QR code or retry.", + ); } } } else { - // Show QR code and invoice - const qrUrl = await generateQrCode(invoiceText); - setQrCodeUrl(qrUrl); - setInvoice(invoiceText); + // Show QR code and invoice directly setShowQrDialog(true); } } catch (error) { @@ -435,6 +444,7 @@ export function ZapWindow({ if (!invoice || !wallet) return; setIsProcessing(true); + setIsPayingWithWallet(true); setShowQrDialog(false); setPaymentTimedOut(false); @@ -452,18 +462,19 @@ export function ZapWindow({ setShowQrDialog(false); toast.success("⚡ Payment successful!"); } catch (error) { + setShowQrDialog(true); + setPaymentTimedOut(true); + if (error instanceof Error && error.message === "TIMEOUT") { toast.error("Payment timed out. Please try manually."); - setPaymentTimedOut(true); - setShowQrDialog(true); } else { toast.error( error instanceof Error ? error.message : "Failed to retry payment", ); - setShowQrDialog(true); } } finally { setIsProcessing(false); + setIsPayingWithWallet(false); } }; @@ -515,38 +526,43 @@ export function ZapWindow({ {/* Actions */} - +
+ {/* Retry with wallet button if payment failed/timed out */} + {paymentTimedOut && + wallet && + walletInfo?.methods.includes("pay_invoice") && ( + + )} - {/* Retry with wallet button if payment timed out */} - {paymentTimedOut && - wallet && - walletInfo?.methods.includes("pay_invoice") && ( - - )} + {/* Always show option to open in external wallet */} + +
) : ( <> @@ -658,7 +674,9 @@ export function ZapWindow({ {isProcessing ? ( <> - Processing... + {isPayingWithWallet + ? "Paying with wallet..." + : "Processing..."} ) : isPaid ? ( <>