From 209fc7bf958c2f2aeee6079ca19f2cd11d330b20 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Sun, 11 Dec 2022 15:33:02 -0500 Subject: [PATCH 1/5] allow continue button to disable qr scanner --- frontend/src/components/QrCodeScanner.tsx | 104 +++++++++++++++++++++- frontend/src/routes/Send.tsx | 6 -- 2 files changed, 102 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/QrCodeScanner.tsx b/frontend/src/components/QrCodeScanner.tsx index e11fb1a00..0b3308d4c 100644 --- a/frontend/src/components/QrCodeScanner.tsx +++ b/frontend/src/components/QrCodeScanner.tsx @@ -1,5 +1,15 @@ +import { SendConfirmParams } from "../routes/SendConfirm" +import { detectPaymentType, objectToSearchParams, PaymentType, toastAnything } from "@util/dumb" import { Html5Qrcode, Html5QrcodeSupportedFormats } from "html5-qrcode" -import { memo, useEffect, useRef, useState } from "react" +import { memo, useContext, useEffect, useRef, useState } from "react" +import toast from "react-hot-toast" +import ActionButton from "./ActionButton" +import { useNavigate } from "react-router-dom" +import { NodeManager } from "node-manager"; +import { NodeManagerContext } from "./GlobalStateProvider" +import { useSearchParams } from "react-router-dom"; +import { inputStyle } from "../styles" +import bip21 from "bip21" type Props = { autoStart?: boolean @@ -7,6 +17,16 @@ type Props = { onValidCode: (data: any) => Promise } +type UnifiedQrOptions = + { + amount?: number; + lightning?: string; + label?: string; + message?: string; + }; + +export type MutinyBip21 = { address: string, options: UnifiedQrOptions }; + const QrCodeDetectorComponent = ({ autoStart = true, onCodeDetected, @@ -16,6 +36,82 @@ const QrCodeDetectorComponent = ({ const [errorMessage, setErrorMessage] = useState("") const [cameraReady, setCameraReady] = useState(false) const qrCodeRef = useRef(null) + const [textFieldDestination, setDestination] = useState("") + const { nodeManager } = useContext(NodeManagerContext); + let navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const sendAll = searchParams.get("all") + + async function navigateForInvoice(invoiceStr: string) { + try { + let invoice = await nodeManager?.decode_invoice(invoiceStr); + console.table(invoice); + if (invoice?.amount_sats && Number(invoice?.amount_sats) > 0) { + const params = objectToSearchParams({ destination: invoiceStr, amount: invoice?.amount_sats.toString(), description: invoice?.description || undefined }) + navigate(`/send/confirm?${params}`) + } else { + const params = objectToSearchParams({ destination: invoiceStr, description: invoice?.description || undefined }) + navigate(`/send/amount?${params}`) + } + } catch (e) { + console.error(e); + toastAnything(e); + } + } + + async function navigateForBip21(bip21String: string) { + const { address, options } = bip21.decode(bip21String) as MutinyBip21; + if (options?.lightning) { + await navigateForInvoice(options.lightning) + } else if (options?.amount) { + try { + const amount = NodeManager.convert_btc_to_sats(options.amount ?? 0.0); + if (!amount) { + throw new Error("Failed to convert BTC to sats") + } + if (options.label) { + const params = objectToSearchParams({ destination: address, amount: amount.toString(), description: options.label }) + navigate(`/send/confirm?${params}`) + } else { + const params = objectToSearchParams({ destination: address, amount: amount.toString() }) + navigate(`/send/confirm?${params}`) + } + } catch (e) { + console.error(e) + toastAnything(e); + } + } + } + + async function handleContinue(qrRead?: string) { + let destination: string = qrRead || textFieldDestination; + if (!destination) { + toast("You didn't paste anything!"); + return + } + + let paymentType = detectPaymentType(destination) + + if (paymentType === PaymentType.unknown) { + toast("Couldn't parse that one, buddy") + return + } + + if (paymentType === PaymentType.invoice) { + await navigateForInvoice(destination) + } else if (paymentType === PaymentType.bip21) { + await navigateForBip21(destination) + } else if (paymentType === PaymentType.onchain) { + if (sendAll === "true") { + const params = objectToSearchParams({ destination, all: "true" }) + navigate(`/send/confirm?${params}`) + } else { + const params = objectToSearchParams({ destination }) + navigate(`/send/amount?${params}`) + } + } + qrCodeRef.current?.stop() + } useEffect(() => { if (detecting) { @@ -57,7 +153,7 @@ const QrCodeDetectorComponent = ({ }, [detecting, onCodeDetected, onValidCode]) return ( -
+
{errorMessage &&

{errorMessage}

}
@@ -66,6 +162,10 @@ const QrCodeDetectorComponent = ({ {!cameraReady &&

Loading scanner...

} + setDestination(e.target.value)} value={textFieldDestination} className={`w-full ${inputStyle({ accent: "green" })}`} type="text" placeholder='Paste invoice, pubkey, or address' /> + handleContinue(undefined)}> + Continue +
) diff --git a/frontend/src/routes/Send.tsx b/frontend/src/routes/Send.tsx index 890845a62..bbae566b0 100644 --- a/frontend/src/routes/Send.tsx +++ b/frontend/src/routes/Send.tsx @@ -3,7 +3,6 @@ import { useNavigate } from "react-router"; import Close from "../components/Close"; import PageTitle from "../components/PageTitle"; import ScreenMain from "../components/ScreenMain"; -import { inputStyle } from "../styles"; import toast from "react-hot-toast" import MutinyToaster from "../components/MutinyToaster"; import { detectPaymentType, objectToSearchParams, PaymentType, toastAnything } from "@util/dumb"; @@ -12,7 +11,6 @@ import bip21 from "bip21" import { NodeManager } from "node-manager"; import { QrCodeScanner } from "@components/QrCodeScanner"; import { SendConfirmParams } from "./SendConfirm"; -import ActionButton from "@components/ActionButton"; import { useSearchParams } from "react-router-dom"; type UnifiedQrOptions = @@ -129,10 +127,6 @@ function Send() { - setDestination(e.target.value)} value={textFieldDestination} className={`w-full ${inputStyle({ accent: "green" })}`} type="text" placeholder='Paste invoice, pubkey, or address' /> - handleContinue(undefined)}> - Continue - From 07116de4b898790294a3c87cda1c3003ea3c4ece Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Sun, 11 Dec 2022 15:36:34 -0500 Subject: [PATCH 2/5] qr scanner is too damn big --- frontend/src/index.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/src/index.css b/frontend/src/index.css index c88665326..079addf42 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -118,3 +118,8 @@ dd { *::-webkit-scrollbar { display: none; } + +.qrContainer { + width: 800px; + margin: auto; +} \ No newline at end of file From 4a2f49e8d6e2634d1209469a9d5c805f8d1c786b Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Sun, 11 Dec 2022 16:56:35 -0500 Subject: [PATCH 3/5] add spacing and video size fix --- frontend/src/index.css | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/src/index.css b/frontend/src/index.css index 079addf42..3e9f75936 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -120,6 +120,14 @@ dd { } .qrContainer { - width: 800px; - margin: auto; + width: 70%; + margin: 1rem auto 0 auto; +} + +.inputContainer { + width: 70%; + margin: 0 auto auto auto; +} +.inputContainer > input { + margin: 0 auto 1rem auto } \ No newline at end of file From 6beaa020becdfefaf75b081eb6d33af6505e199e Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Sun, 11 Dec 2022 16:59:07 -0500 Subject: [PATCH 4/5] add spacing and video size fix --- frontend/src/components/QrCodeScanner.tsx | 24 +++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/frontend/src/components/QrCodeScanner.tsx b/frontend/src/components/QrCodeScanner.tsx index 0b3308d4c..f51030546 100644 --- a/frontend/src/components/QrCodeScanner.tsx +++ b/frontend/src/components/QrCodeScanner.tsx @@ -38,9 +38,9 @@ const QrCodeDetectorComponent = ({ const qrCodeRef = useRef(null) const [textFieldDestination, setDestination] = useState("") const { nodeManager } = useContext(NodeManagerContext); - let navigate = useNavigate(); const [searchParams] = useSearchParams(); const sendAll = searchParams.get("all") + let navigate = useNavigate(); async function navigateForInvoice(invoiceStr: string) { try { @@ -153,21 +153,25 @@ const QrCodeDetectorComponent = ({ }, [detecting, onCodeDetected, onValidCode]) return ( -
- {errorMessage &&

{errorMessage}

} -
-
-
+ <> +
+ {errorMessage &&

{errorMessage}

} +
+
+
+
+ {!cameraReady && +

Loading scanner...

+ }
- {!cameraReady && -

Loading scanner...

- } +
+
setDestination(e.target.value)} value={textFieldDestination} className={`w-full ${inputStyle({ accent: "green" })}`} type="text" placeholder='Paste invoice, pubkey, or address' /> handleContinue(undefined)}> Continue
-
+ ) } From 1a6275f2ccd03db7210fbcd51bd3e8182045a4bb Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Sun, 11 Dec 2022 18:01:32 -0500 Subject: [PATCH 5/5] fix setDestination not called --- frontend/src/routes/Send.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/routes/Send.tsx b/frontend/src/routes/Send.tsx index bbae566b0..f0c15a47a 100644 --- a/frontend/src/routes/Send.tsx +++ b/frontend/src/routes/Send.tsx @@ -27,7 +27,7 @@ function Send() { const { nodeManager } = useContext(NodeManagerContext); let navigate = useNavigate(); - const [textFieldDestination, setDestination] = useState("") + const [textFieldDestination] = useState("") const [searchParams] = useSearchParams(); const sendAll = searchParams.get("all")