From dc829ffc88fc8a94e0e1bbb1d99acef77d697e9b Mon Sep 17 00:00:00 2001 From: X-Hades-X Date: Mon, 22 Apr 2024 11:44:59 +0200 Subject: [PATCH 1/9] Pin support --- package-lock.json | 10 ++++ package.json | 1 + src/hooks/index.ts | 1 + src/hooks/useNfc.ts | 24 ++++++++- src/hooks/usePin.tsx | 24 +++++++++ src/screens/Invoice/Invoice.tsx | 93 ++++++++++++++++++++++++++++++--- 6 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 src/hooks/usePin.tsx diff --git a/package-lock.json b/package-lock.json index d8d20c3..df7966f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "react-native-dotenv": "3.4.9", "react-native-linear-gradient": "2.8.3", "react-native-nfc-manager": "3.14.11", + "react-native-pin-view": "^3.0.3", "react-native-progress": "5.0.1", "react-native-qrcode-svg": "^6.3.0", "react-native-safe-area-context": "4.7.2", @@ -24848,6 +24849,15 @@ "@expo/config-plugins": "~7.2.5" } }, + "node_modules/react-native-pin-view": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/react-native-pin-view/-/react-native-pin-view-3.0.3.tgz", + "integrity": "sha512-0xCQBpwrP06wDjUlfwViFMzi1ph+9Y1ytYWkGtdIcy9XcR5RK94DOGDisNUb8LrvHjVIoeuoDIkYPVcvv43GGA==", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-progress": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-native-progress/-/react-native-progress-5.0.1.tgz", diff --git a/package.json b/package.json index b14076e..9322bb5 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "react-native-dotenv": "3.4.9", "react-native-linear-gradient": "2.8.3", "react-native-nfc-manager": "3.14.11", + "react-native-pin-view": "^3.0.3", "react-native-progress": "5.0.1", "react-native-qrcode-svg": "^6.3.0", "react-native-safe-area-context": "4.7.2", diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 6efc678..1bd63a9 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -7,5 +7,6 @@ export { useBackHandler } from "./useBackHandler"; export { useIsScreenSizeMin } from "./useIsScreenSizeMin"; export { useIsCameraAvailable } from "./useIsCameraAvailable"; export { useNfc } from "./useNfc"; +export { usePin } from "./usePin"; export { useRates } from "./useRates"; export { useVersionTag } from "./useVersionTag"; diff --git a/src/hooks/useNfc.ts b/src/hooks/useNfc.ts index eb78537..a695c6e 100644 --- a/src/hooks/useNfc.ts +++ b/src/hooks/useNfc.ts @@ -17,6 +17,8 @@ export const useNfc = () => { const [isNfcNeedsTap, setIsNfcNeedsTap] = useState(false); const [isNfcNeedsPermission, setIsNfcNeedsPermission] = useState(false); const [isNfcActionSuccess, setIsNfcActionSuccess] = useState(false); + const [isPinRequired, setIsPinRequired] = useState(false); + const [isPinConfirmed, setIsPinConfirmed] = useState(false); const setupNfc = useCallback(async () => { if (await getIsNfcSupported()) { @@ -49,7 +51,7 @@ export const useNfc = () => { }, []); const readingNfcLoop = useCallback( - async (pr: string) => { + async (pr: string, amount: number, getPin) => { setIsNfcActionSuccess(false); await NFC.stopRead(); @@ -114,15 +116,31 @@ export const useNfc = () => { tag: "withdrawRequest"; callback: string; k1: string; + pinLimit: number; }>(cardData); + let pin = ""; + if (cardDataResponse.pinLimit) { + //if the card has pin enabled + //check the amount didn't exceed the limit + const limitSat = cardDataResponse.pinLimit; + if (limitSat <= amount) { + setIsPinRequired(true); + pin = await getPin(); + setIsPinConfirmed(true); + } + } else { + setIsPinRequired(false); + } + const { data: callbackResponseData } = await axios.get<{ reason: { detail: string }; status: "OK" | "ERROR"; }>(cardDataResponse.callback, { params: { k1: cardDataResponse.k1, - pr + pr, + pin } }); @@ -233,6 +251,8 @@ export const useNfc = () => { isNfcNeedsTap, isNfcNeedsPermission, isNfcActionSuccess, + isPinRequired, + isPinConfirmed, setupNfc, stopNfc, readingNfcLoop diff --git a/src/hooks/usePin.tsx b/src/hooks/usePin.tsx new file mode 100644 index 0000000..d2c34f2 --- /dev/null +++ b/src/hooks/usePin.tsx @@ -0,0 +1,24 @@ +import React, { useState } from 'react' + +const createPromise = () => { + let resolver; + return [ new Promise(( resolve, reject ) => { + resolver = resolve + }), resolver] +} + +export const usePin = () => { + const [ resolver, setResolver ] = useState({ resolve: null }) + + const getPin = async () => { + const [ promise, resolve ] = await createPromise() + setResolver({ resolve }) + return promise; + } + + const onConfirm = async(pin) => { + resolver.resolve(pin); + } + + return [ getPin, onConfirm ] +} \ No newline at end of file diff --git a/src/screens/Invoice/Invoice.tsx b/src/screens/Invoice/Invoice.tsx index e8dc10e..7c1617c 100644 --- a/src/screens/Invoice/Invoice.tsx +++ b/src/screens/Invoice/Invoice.tsx @@ -6,7 +6,8 @@ import { CheckboxField, ComponentStack, Loader, - Text + Text, + Icon } from "@components"; import { faBolt, @@ -21,10 +22,10 @@ import { import addDays from "date-fns/addDays"; import intlFormat from "date-fns/intlFormat"; import bolt11, { PaymentRequestObject } from "bolt11"; -import { useNfc } from "@hooks"; +import { useNfc, usePin } from "@hooks"; import { XOR } from "ts-essentials"; import { ThemeContext } from "@config"; -import { useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { useCallback, useContext, useEffect, useMemo, useState, useRef } from "react"; import { Vibration } from "react-native"; import { useTheme } from "styled-components"; import { ListItem } from "@components/ItemsList/components/ListItem"; @@ -32,6 +33,10 @@ import { faBitcoin } from "@fortawesome/free-brands-svg-icons"; import axios from "axios"; import * as S from "./styled"; +import { ImageBackground, SafeAreaView, StatusBar } from "react-native"; +import ReactNativePinView from "react-native-pin-view"; +import { faDeleteLeft, faUnlock } from "@fortawesome/free-solid-svg-icons"; + const numberWithSpaces = (nb: number) => nb.toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, " "); @@ -61,7 +66,9 @@ export const Invoice = () => { isNfcScanning, isNfcLoading, isNfcNeedsTap, - isNfcActionSuccess + isNfcActionSuccess, + isPinRequired, + isPinConfirmed } = useNfc(); const { @@ -86,6 +93,26 @@ export const Invoice = () => { const { satoshis } = decodedInvoice || {}; + const [getPin, onConfirm] = usePin() + + const pinView = useRef(null) + const [showRemoveButton, setShowRemoveButton] = useState(false) + const [enteredPin, setEnteredPin] = useState("") + const [showCompletedButton, setShowCompletedButton] = useState(false) + + useEffect(() => { + if (enteredPin.length > 0) { + setShowRemoveButton(true) + } else { + setShowRemoveButton(false) + } + if (enteredPin.length === 4) { + setShowCompletedButton(true) + } else { + setShowCompletedButton(false) + } + }, [enteredPin]) + useEffect(() => { void setupNfc(); }, []); @@ -98,9 +125,9 @@ export const Invoice = () => { useEffect(() => { if (isNfcAvailable && !isNfcNeedsTap && lightningInvoice) { - void readingNfcLoop(lightningInvoice); + void readingNfcLoop(lightningInvoice, satoshis, getPin); } - }, [readingNfcLoop, isNfcAvailable, isNfcNeedsTap, lightningInvoice]); + }, [readingNfcLoop, isNfcAvailable, isNfcNeedsTap, lightningInvoice, satoshis]); useEffect(() => { if (isNfcActionSuccess) { @@ -280,6 +307,60 @@ export const Invoice = () => { onPress={onReturnToHome} /> + ) : isPinRequired && !isPinConfirmed ? ( + <> + + + + Boltcard PIN + + setEnteredPin(value)} + buttonAreaStyle={{ + marginTop: 24, + }} + inputAreaStyle={{ + marginBottom: 24, + }} + inputViewEmptyStyle={{ + backgroundColor: "transparent", + borderWidth: 1, + borderColor: "#FFF", + }} + inputViewFilledStyle={{ + backgroundColor: "#FFF", + }} + buttonViewStyle={{ + borderWidth: 1, + borderColor: "#FFF", + }} + buttonTextStyle={{ + color: "#FFF", + }} + onButtonPress={key => { + if (key === "custom_left") { + pinView.current.clear() + } + if (key === "custom_right") { + onConfirm(enteredPin) + } + }} + customLeftButton={showRemoveButton ? : null} + customRightButton={showCompletedButton ? : null} + /> + + ) : isNfcLoading || isNfcScanning ? ( Date: Wed, 24 Apr 2024 14:23:02 +0200 Subject: [PATCH 2/9] handle everything in useNfc hook for now --- src/hooks/index.ts | 1 - src/hooks/useNfc.ts | 22 +++++++++++++++++++++- src/hooks/usePin.tsx | 24 ------------------------ src/screens/Invoice/Invoice.tsx | 11 +++++------ 4 files changed, 26 insertions(+), 32 deletions(-) delete mode 100644 src/hooks/usePin.tsx diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 1bd63a9..6efc678 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -7,6 +7,5 @@ export { useBackHandler } from "./useBackHandler"; export { useIsScreenSizeMin } from "./useIsScreenSizeMin"; export { useIsCameraAvailable } from "./useIsCameraAvailable"; export { useNfc } from "./useNfc"; -export { usePin } from "./usePin"; export { useRates } from "./useRates"; export { useVersionTag } from "./useVersionTag"; diff --git a/src/hooks/useNfc.ts b/src/hooks/useNfc.ts index a695c6e..1dfb48c 100644 --- a/src/hooks/useNfc.ts +++ b/src/hooks/useNfc.ts @@ -20,6 +20,25 @@ export const useNfc = () => { const [isPinRequired, setIsPinRequired] = useState(false); const [isPinConfirmed, setIsPinConfirmed] = useState(false); + const [ pinResolver, setPinResolver ] = useState({ resolve: null }) + + const pinInputPromise = () => { + let pinResolver; + return [ new Promise(( resolve, reject ) => { + pinResolver = resolve + }), pinResolver] + } + + const getPin = async () => { + const [ promise, resolve ] = await pinInputPromise() + setPinResolver({ resolve }) + return promise; + } + + const setPin = async(pin) => { + pinResolver.resolve(pin); + } + const setupNfc = useCallback(async () => { if (await getIsNfcSupported()) { try { @@ -51,7 +70,7 @@ export const useNfc = () => { }, []); const readingNfcLoop = useCallback( - async (pr: string, amount: number, getPin) => { + async (pr: string, amount: number) => { setIsNfcActionSuccess(false); await NFC.stopRead(); @@ -253,6 +272,7 @@ export const useNfc = () => { isNfcActionSuccess, isPinRequired, isPinConfirmed, + setPin, setupNfc, stopNfc, readingNfcLoop diff --git a/src/hooks/usePin.tsx b/src/hooks/usePin.tsx deleted file mode 100644 index d2c34f2..0000000 --- a/src/hooks/usePin.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React, { useState } from 'react' - -const createPromise = () => { - let resolver; - return [ new Promise(( resolve, reject ) => { - resolver = resolve - }), resolver] -} - -export const usePin = () => { - const [ resolver, setResolver ] = useState({ resolve: null }) - - const getPin = async () => { - const [ promise, resolve ] = await createPromise() - setResolver({ resolve }) - return promise; - } - - const onConfirm = async(pin) => { - resolver.resolve(pin); - } - - return [ getPin, onConfirm ] -} \ No newline at end of file diff --git a/src/screens/Invoice/Invoice.tsx b/src/screens/Invoice/Invoice.tsx index 7c1617c..f6b548d 100644 --- a/src/screens/Invoice/Invoice.tsx +++ b/src/screens/Invoice/Invoice.tsx @@ -22,7 +22,7 @@ import { import addDays from "date-fns/addDays"; import intlFormat from "date-fns/intlFormat"; import bolt11, { PaymentRequestObject } from "bolt11"; -import { useNfc, usePin } from "@hooks"; +import { useNfc } from "@hooks"; import { XOR } from "ts-essentials"; import { ThemeContext } from "@config"; import { useCallback, useContext, useEffect, useMemo, useState, useRef } from "react"; @@ -68,7 +68,8 @@ export const Invoice = () => { isNfcNeedsTap, isNfcActionSuccess, isPinRequired, - isPinConfirmed + isPinConfirmed, + setPin } = useNfc(); const { @@ -93,8 +94,6 @@ export const Invoice = () => { const { satoshis } = decodedInvoice || {}; - const [getPin, onConfirm] = usePin() - const pinView = useRef(null) const [showRemoveButton, setShowRemoveButton] = useState(false) const [enteredPin, setEnteredPin] = useState("") @@ -125,7 +124,7 @@ export const Invoice = () => { useEffect(() => { if (isNfcAvailable && !isNfcNeedsTap && lightningInvoice) { - void readingNfcLoop(lightningInvoice, satoshis, getPin); + void readingNfcLoop(lightningInvoice, satoshis); } }, [readingNfcLoop, isNfcAvailable, isNfcNeedsTap, lightningInvoice, satoshis]); @@ -353,7 +352,7 @@ export const Invoice = () => { pinView.current.clear() } if (key === "custom_right") { - onConfirm(enteredPin) + setPin(enteredPin) } }} customLeftButton={showRemoveButton ? : null} From 5c2baff151c57adbed7c37bb73486e144b12f3bc Mon Sep 17 00:00:00 2001 From: X-Hades-X Date: Wed, 24 Apr 2024 16:00:01 +0200 Subject: [PATCH 3/9] move pinpad into own component --- package-lock.json | 106 ++++++++++++++++++++++++++++++++ package.json | 1 + src/screens/Invoice/Invoice.tsx | 76 +---------------------- src/screens/PinPad/PinPad.tsx | 82 ++++++++++++++++++++++++ src/screens/PinPad/index.ts | 1 + src/screens/PinPad/styled.ts | 0 6 files changed, 192 insertions(+), 74 deletions(-) create mode 100644 src/screens/PinPad/PinPad.tsx create mode 100644 src/screens/PinPad/index.ts create mode 100644 src/screens/PinPad/styled.ts diff --git a/package-lock.json b/package-lock.json index df7966f..1b31648 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "react-device-detect": "2.2.3", "react-dom": "18.2.0", "react-i18next": "13.2.2", + "react-modal-sheet": "^2.2.0", "react-native": "0.73.2", "react-native-animated-linear-gradient": "1.3.0", "react-native-bars": "2.3.0", @@ -4077,6 +4078,43 @@ } } }, + "node_modules/@react-aria/ssr": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.2.tgz", + "integrity": "sha512-0gKkgDYdnq1w+ey8KzG9l+H5Z821qh9vVjztk55rUg71vTk/Eaebeir+WtzcLLwTjw3m/asIjx8Y59y1lJZhBw==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/ssr/node_modules/@swc/helpers": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.10.tgz", + "integrity": "sha512-CU+RF9FySljn7HVSkkjiB84hWkvTaI3rtLvF433+jRSBL2hMu3zX5bGhHS8C80SM++h4xy8hBSnUHFQHmRXSBw==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.17.0.tgz", + "integrity": "sha512-NEul0cQ6tQPdNSHYzNYD+EfFabeYNvDwEiHB82kK/Tsfhfm84SM+baben/at2N51K7iRrJPr5hC5fi4+P88lNg==", + "dependencies": { + "@react-aria/ssr": "^3.6.0", + "@react-stately/utils": "^3.6.0", + "@react-types/shared": "^3.18.1", + "@swc/helpers": "^0.4.14", + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, "node_modules/@react-native-clipboard/clipboard": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.13.2.tgz", @@ -6203,6 +6241,33 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@react-stately/utils": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.9.1.tgz", + "integrity": "sha512-yzw75GE0iUWiyps02BOAPTrybcsMIxEJlzXqtvllAb01O9uX5n0i3X+u2eCpj2UoDF4zS08Ps0jPgWxg8xEYtA==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/utils/node_modules/@swc/helpers": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.10.tgz", + "integrity": "sha512-CU+RF9FySljn7HVSkkjiB84hWkvTaI3rtLvF433+jRSBL2hMu3zX5bGhHS8C80SM++h4xy8hBSnUHFQHmRXSBw==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@react-types/shared": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.22.1.tgz", + "integrity": "sha512-PCpa+Vo6BKnRMuOEzy5zAZ3/H5tnQg1e80khMhK2xys0j6ZqzkgQC+fHMNZ7VDFNLqqNMj/o0eVeSBDh2POjkw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, "node_modules/@remix-run/router": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz", @@ -6616,6 +6681,15 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/@swc/helpers": { + "version": "0.4.36", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.36.tgz", + "integrity": "sha512-5lxnyLEYFskErRPenYItLRSge5DjrJngYKdVjRSrWfza9G6KkgHEXi0vUZiyUeMU5JfXH1YnvXZzSp8ul88o2Q==", + "dependencies": { + "legacy-swc-helpers": "npm:@swc/helpers@=0.4.14", + "tslib": "^2.4.0" + } + }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -10354,6 +10428,14 @@ "node": ">=4" } }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -20013,6 +20095,15 @@ "shell-quote": "^1.7.3" } }, + "node_modules/legacy-swc-helpers": { + "name": "@swc/helpers", + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", + "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -24619,6 +24710,21 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-modal-sheet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-modal-sheet/-/react-modal-sheet-2.2.0.tgz", + "integrity": "sha512-OAIWuVWxMx3zQqrMLbYWnczadplg0WLd+AaBWmN5+ysNF5/pneqjkOV3AWaIZOCIF4TcrejiCsTduutbzCRP2Q==", + "dependencies": { + "@react-aria/utils": "3.17.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "framer-motion": ">=6", + "react": ">=16" + } + }, "node_modules/react-native": { "version": "0.73.2", "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.2.tgz", diff --git a/package.json b/package.json index 9322bb5..d647a0b 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "react-device-detect": "2.2.3", "react-dom": "18.2.0", "react-i18next": "13.2.2", + "react-modal-sheet": "^2.2.0", "react-native": "0.73.2", "react-native-animated-linear-gradient": "1.3.0", "react-native-bars": "2.3.0", diff --git a/src/screens/Invoice/Invoice.tsx b/src/screens/Invoice/Invoice.tsx index f6b548d..f5697be 100644 --- a/src/screens/Invoice/Invoice.tsx +++ b/src/screens/Invoice/Invoice.tsx @@ -33,9 +33,7 @@ import { faBitcoin } from "@fortawesome/free-brands-svg-icons"; import axios from "axios"; import * as S from "./styled"; -import { ImageBackground, SafeAreaView, StatusBar } from "react-native"; -import ReactNativePinView from "react-native-pin-view"; -import { faDeleteLeft, faUnlock } from "@fortawesome/free-solid-svg-icons"; +import { PinPad } from "../PinPad"; const numberWithSpaces = (nb: number) => nb.toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, " "); @@ -94,24 +92,6 @@ export const Invoice = () => { const { satoshis } = decodedInvoice || {}; - const pinView = useRef(null) - const [showRemoveButton, setShowRemoveButton] = useState(false) - const [enteredPin, setEnteredPin] = useState("") - const [showCompletedButton, setShowCompletedButton] = useState(false) - - useEffect(() => { - if (enteredPin.length > 0) { - setShowRemoveButton(true) - } else { - setShowRemoveButton(false) - } - if (enteredPin.length === 4) { - setShowCompletedButton(true) - } else { - setShowCompletedButton(false) - } - }, [enteredPin]) - useEffect(() => { void setupNfc(); }, []); @@ -307,59 +287,7 @@ export const Invoice = () => { /> ) : isPinRequired && !isPinConfirmed ? ( - <> - - - - Boltcard PIN - - setEnteredPin(value)} - buttonAreaStyle={{ - marginTop: 24, - }} - inputAreaStyle={{ - marginBottom: 24, - }} - inputViewEmptyStyle={{ - backgroundColor: "transparent", - borderWidth: 1, - borderColor: "#FFF", - }} - inputViewFilledStyle={{ - backgroundColor: "#FFF", - }} - buttonViewStyle={{ - borderWidth: 1, - borderColor: "#FFF", - }} - buttonTextStyle={{ - color: "#FFF", - }} - onButtonPress={key => { - if (key === "custom_left") { - pinView.current.clear() - } - if (key === "custom_right") { - setPin(enteredPin) - } - }} - customLeftButton={showRemoveButton ? : null} - customRightButton={showCompletedButton ? : null} - /> - - + ) : isNfcLoading || isNfcScanning ? ( { + const pinView = useRef(null); + const [showRemoveButton, setShowRemoveButton] = useState(false); + const [enteredPin, setEnteredPin] = useState(""); + + const [showCompletedButton, setShowCompletedButton] = useState(false) + + useEffect(() => { + if (enteredPin.length > 0) { + setShowRemoveButton(true) + } else { + setShowRemoveButton(false) + } + if (enteredPin.length === 4) { + setShowCompletedButton(true) + } else { + setShowCompletedButton(false) + } + }, [enteredPin]) + + return ( + <> + + + + Boltcard PIN + + setEnteredPin(value)} + buttonAreaStyle={{ + marginTop: 24, + }} + inputAreaStyle={{ + marginBottom: 24, + }} + inputViewEmptyStyle={{ + backgroundColor: "transparent", + borderWidth: 1, + borderColor: "#FFF", + }} + inputViewFilledStyle={{ + backgroundColor: "#FFF", + }} + buttonViewStyle={{ + borderWidth: 1, + borderColor: "#FFF", + }} + buttonTextStyle={{ + color: "#FFF", + }} + onButtonPress={key => { + if (key === "custom_left") { + pinView.current.clear() + } + if (key === "custom_right") { + props.newPin(enteredPin) + } + }} + customLeftButton={showRemoveButton ? : null} + customRightButton={showCompletedButton ? : null} + /> + + + ); +} \ No newline at end of file diff --git a/src/screens/PinPad/index.ts b/src/screens/PinPad/index.ts new file mode 100644 index 0000000..3be94e5 --- /dev/null +++ b/src/screens/PinPad/index.ts @@ -0,0 +1 @@ +export { PinPad } from "./PinPad"; diff --git a/src/screens/PinPad/styled.ts b/src/screens/PinPad/styled.ts new file mode 100644 index 0000000..e69de29 From d28605b5f201559a563df2d96fa9f540542864dd Mon Sep 17 00:00:00 2001 From: X-Hades-X Date: Thu, 16 May 2024 18:48:12 +0200 Subject: [PATCH 4/9] add bottom drawer --- package-lock.json | 117 +++------------------------- package.json | 2 +- src/screens/Invoice/Invoice.tsx | 5 ++ src/screens/PinPad/PinPad.tsx | 132 +++++++++++++++++++------------- 4 files changed, 94 insertions(+), 162 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1b31648..3dff56b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,8 +40,8 @@ "react-device-detect": "2.2.3", "react-dom": "18.2.0", "react-i18next": "13.2.2", - "react-modal-sheet": "^2.2.0", "react-native": "0.73.2", + "react-native-animated-bottom-drawer": "^0.0.23", "react-native-animated-linear-gradient": "1.3.0", "react-native-bars": "2.3.0", "react-native-blur-effect": "1.1.3", @@ -4078,43 +4078,6 @@ } } }, - "node_modules/@react-aria/ssr": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.2.tgz", - "integrity": "sha512-0gKkgDYdnq1w+ey8KzG9l+H5Z821qh9vVjztk55rUg71vTk/Eaebeir+WtzcLLwTjw3m/asIjx8Y59y1lJZhBw==", - "dependencies": { - "@swc/helpers": "^0.5.0" - }, - "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" - } - }, - "node_modules/@react-aria/ssr/node_modules/@swc/helpers": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.10.tgz", - "integrity": "sha512-CU+RF9FySljn7HVSkkjiB84hWkvTaI3rtLvF433+jRSBL2hMu3zX5bGhHS8C80SM++h4xy8hBSnUHFQHmRXSBw==", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@react-aria/utils": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.17.0.tgz", - "integrity": "sha512-NEul0cQ6tQPdNSHYzNYD+EfFabeYNvDwEiHB82kK/Tsfhfm84SM+baben/at2N51K7iRrJPr5hC5fi4+P88lNg==", - "dependencies": { - "@react-aria/ssr": "^3.6.0", - "@react-stately/utils": "^3.6.0", - "@react-types/shared": "^3.18.1", - "@swc/helpers": "^0.4.14", - "clsx": "^1.1.1" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" - } - }, "node_modules/@react-native-clipboard/clipboard": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@react-native-clipboard/clipboard/-/clipboard-1.13.2.tgz", @@ -6241,33 +6204,6 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/@react-stately/utils": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.9.1.tgz", - "integrity": "sha512-yzw75GE0iUWiyps02BOAPTrybcsMIxEJlzXqtvllAb01O9uX5n0i3X+u2eCpj2UoDF4zS08Ps0jPgWxg8xEYtA==", - "dependencies": { - "@swc/helpers": "^0.5.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" - } - }, - "node_modules/@react-stately/utils/node_modules/@swc/helpers": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.10.tgz", - "integrity": "sha512-CU+RF9FySljn7HVSkkjiB84hWkvTaI3rtLvF433+jRSBL2hMu3zX5bGhHS8C80SM++h4xy8hBSnUHFQHmRXSBw==", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@react-types/shared": { - "version": "3.22.1", - "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.22.1.tgz", - "integrity": "sha512-PCpa+Vo6BKnRMuOEzy5zAZ3/H5tnQg1e80khMhK2xys0j6ZqzkgQC+fHMNZ7VDFNLqqNMj/o0eVeSBDh2POjkw==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" - } - }, "node_modules/@remix-run/router": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz", @@ -6681,15 +6617,6 @@ "url": "https://github.com/sponsors/gregberge" } }, - "node_modules/@swc/helpers": { - "version": "0.4.36", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.36.tgz", - "integrity": "sha512-5lxnyLEYFskErRPenYItLRSge5DjrJngYKdVjRSrWfza9G6KkgHEXi0vUZiyUeMU5JfXH1YnvXZzSp8ul88o2Q==", - "dependencies": { - "legacy-swc-helpers": "npm:@swc/helpers@=0.4.14", - "tslib": "^2.4.0" - } - }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -10428,14 +10355,6 @@ "node": ">=4" } }, - "node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "engines": { - "node": ">=6" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -20095,15 +20014,6 @@ "shell-quote": "^1.7.3" } }, - "node_modules/legacy-swc-helpers": { - "name": "@swc/helpers", - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz", - "integrity": "sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==", - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -24710,21 +24620,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, - "node_modules/react-modal-sheet": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/react-modal-sheet/-/react-modal-sheet-2.2.0.tgz", - "integrity": "sha512-OAIWuVWxMx3zQqrMLbYWnczadplg0WLd+AaBWmN5+ysNF5/pneqjkOV3AWaIZOCIF4TcrejiCsTduutbzCRP2Q==", - "dependencies": { - "@react-aria/utils": "3.17.0" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "framer-motion": ">=6", - "react": ">=16" - } - }, "node_modules/react-native": { "version": "0.73.2", "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.73.2.tgz", @@ -24778,6 +24673,16 @@ "react": "18.2.0" } }, + "node_modules/react-native-animated-bottom-drawer": { + "version": "0.0.23", + "resolved": "https://registry.npmjs.org/react-native-animated-bottom-drawer/-/react-native-animated-bottom-drawer-0.0.23.tgz", + "integrity": "sha512-xOYOWxNViNno9wtYtvDpW90M5N4RDgdLq2jRjR9lxTAcpKsl93C7oex/NANQzd5aC9Vh7SEnyh0DlbSqprB6kg==", + "license": "ISC", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-animated-linear-gradient": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/react-native-animated-linear-gradient/-/react-native-animated-linear-gradient-1.3.0.tgz", diff --git a/package.json b/package.json index d647a0b..ed3ede2 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,8 @@ "react-device-detect": "2.2.3", "react-dom": "18.2.0", "react-i18next": "13.2.2", - "react-modal-sheet": "^2.2.0", "react-native": "0.73.2", + "react-native-animated-bottom-drawer": "^0.0.23", "react-native-animated-linear-gradient": "1.3.0", "react-native-bars": "2.3.0", "react-native-blur-effect": "1.1.3", diff --git a/src/screens/Invoice/Invoice.tsx b/src/screens/Invoice/Invoice.tsx index f5697be..421666f 100644 --- a/src/screens/Invoice/Invoice.tsx +++ b/src/screens/Invoice/Invoice.tsx @@ -87,6 +87,7 @@ export const Invoice = () => { const [isScheduledSwap, setIsScheduledSwap] = useState(false); const [isSwapLoading, setIsSwapLoading] = useState(); + const [isPinVisible, setIsPinVisible] = useState(true); const [swapFees, setSwapFees] = useState(); const [decodedInvoice, setDecodedInvoice] = useState(); @@ -115,6 +116,10 @@ export const Invoice = () => { } }, [isNfcActionSuccess]); + useEffect(() => { + setIsPinVisible(true) + }, [isPinRequired, isPinConfirmed]); + const onGetSwapQuote = useCallback(async () => { if (amount) { setIsSwapLoading(true); diff --git a/src/screens/PinPad/PinPad.tsx b/src/screens/PinPad/PinPad.tsx index e799f3d..6a3f20a 100644 --- a/src/screens/PinPad/PinPad.tsx +++ b/src/screens/PinPad/PinPad.tsx @@ -1,15 +1,27 @@ -import { useRef, useState, useEffect } from 'react'; -import { SafeAreaView, StatusBar } from "react-native"; +import { useRef, useState, useEffect, useCallback } from 'react'; +import { SafeAreaView, StatusBar, View } from "react-native"; import ReactNativePinView from "react-native-pin-view"; +import BottomDrawer, { + BottomDrawerMethods, +} from 'react-native-animated-bottom-drawer'; import { Text, Icon } from "@components"; import { faDeleteLeft, faUnlock } from "@fortawesome/free-solid-svg-icons"; export const PinPad = (props) => { const pinView = useRef(null); + const bottomDrawerRef = useRef(null); const [showRemoveButton, setShowRemoveButton] = useState(false); const [enteredPin, setEnteredPin] = useState(""); + const [showCompletedButton, setShowCompletedButton] = useState(false); + const [isVisible, setIsVisible] = useState(true); - const [showCompletedButton, setShowCompletedButton] = useState(false) + useEffect(() => { + if(isVisible) { + bottomDrawerRef.current.open(); + } else { + bottomDrawerRef.current.close() + } + }, [isVisible]); useEffect(() => { if (enteredPin.length > 0) { @@ -22,61 +34,71 @@ export const PinPad = (props) => { } else { setShowCompletedButton(false) } - }, [enteredPin]) + }, [enteredPin]); + + const onClose = useCallback(() => { + setIsVisible(false); + }, []); return ( <> - - - - Boltcard PIN - - setEnteredPin(value)} - buttonAreaStyle={{ - marginTop: 24, - }} - inputAreaStyle={{ - marginBottom: 24, - }} - inputViewEmptyStyle={{ - backgroundColor: "transparent", - borderWidth: 1, - borderColor: "#FFF", - }} - inputViewFilledStyle={{ - backgroundColor: "#FFF", - }} - buttonViewStyle={{ - borderWidth: 1, - borderColor: "#FFF", - }} - buttonTextStyle={{ - color: "#FFF", - }} - onButtonPress={key => { - if (key === "custom_left") { - pinView.current.clear() - } - if (key === "custom_right") { - props.newPin(enteredPin) - } - }} - customLeftButton={showRemoveButton ? : null} - customRightButton={showCompletedButton ? : null} - /> - + + + + Boltcard PIN + + setEnteredPin(value)} + buttonAreaStyle={{ + marginTop: 24, + }} + inputAreaStyle={{ + marginBottom: 24, + }} + inputViewEmptyStyle={{ + backgroundColor: "transparent", + borderWidth: 1, + borderColor: "#FFF", + }} + inputViewFilledStyle={{ + backgroundColor: "#FFF", + }} + buttonViewStyle={{ + borderWidth: 1, + borderColor: "#FFF", + }} + buttonTextStyle={{ + color: "#FFF", + }} + onButtonPress={key => { + if (key === "custom_left") { + pinView.current.clear() + } + if (key === "custom_right") { + props.newPin(enteredPin) + } + }} + customLeftButton={showRemoveButton ? : null} + customRightButton={showCompletedButton ? : null} + /> + + ); } \ No newline at end of file From d634623ad66808ba57acf39df7f9734f273dd333 Mon Sep 17 00:00:00 2001 From: X-Hades-X Date: Thu, 16 May 2024 21:31:38 +0200 Subject: [PATCH 5/9] cleanup --- src/screens/Invoice/Invoice.tsx | 5 +- src/screens/PinPad/PinPad.tsx | 83 ++++++++++++++++++--------------- 2 files changed, 48 insertions(+), 40 deletions(-) diff --git a/src/screens/Invoice/Invoice.tsx b/src/screens/Invoice/Invoice.tsx index 421666f..adea616 100644 --- a/src/screens/Invoice/Invoice.tsx +++ b/src/screens/Invoice/Invoice.tsx @@ -6,8 +6,7 @@ import { CheckboxField, ComponentStack, Loader, - Text, - Icon + Text } from "@components"; import { faBolt, @@ -25,7 +24,7 @@ import bolt11, { PaymentRequestObject } from "bolt11"; import { useNfc } from "@hooks"; import { XOR } from "ts-essentials"; import { ThemeContext } from "@config"; -import { useCallback, useContext, useEffect, useMemo, useState, useRef } from "react"; +import { useCallback, useContext, useEffect, useMemo, useState } from "react"; import { Vibration } from "react-native"; import { useTheme } from "styled-components"; import { ListItem } from "@components/ItemsList/components/ListItem"; diff --git a/src/screens/PinPad/PinPad.tsx b/src/screens/PinPad/PinPad.tsx index 6a3f20a..7be7d88 100644 --- a/src/screens/PinPad/PinPad.tsx +++ b/src/screens/PinPad/PinPad.tsx @@ -1,5 +1,5 @@ import { useRef, useState, useEffect, useCallback } from 'react'; -import { SafeAreaView, StatusBar, View } from "react-native"; +import { SafeAreaView, StatusBar, StyleSheet, View } from "react-native"; import ReactNativePinView from "react-native-pin-view"; import BottomDrawer, { BottomDrawerMethods, @@ -12,9 +12,34 @@ export const PinPad = (props) => { const bottomDrawerRef = useRef(null); const [showRemoveButton, setShowRemoveButton] = useState(false); const [enteredPin, setEnteredPin] = useState(""); + const [buttonPressed, setButtonPressed] = useState(""); const [showCompletedButton, setShowCompletedButton] = useState(false); const [isVisible, setIsVisible] = useState(true); + const styles = StyleSheet.create({ + container: { + backgroundColor: "rgba(0,0,0,0.5)", + }, + handleContainer: { + backgroundColor: "transparent", + }, + pinPad: { + backgroundColor: "transparent", + alignItems: "center", + flex: 1, + }, + pinPadTitle: { + paddingTop: 48, + paddingBottom: 24, + color: "rgba(255,255,255,1)", + fontSize: 48, + }, + whiteBorder: { + borderWidth: 1, + borderColor: "#FFF", + }, + }); + useEffect(() => { if(isVisible) { bottomDrawerRef.current.open(); @@ -36,6 +61,15 @@ export const PinPad = (props) => { } }, [enteredPin]); + useEffect(() => { + if (buttonPressed === "custom_left") { + pinView.current.clearAll() + } + if (buttonPressed === "custom_right") { + props.newPin(enteredPin) + } + }, [buttonPressed]) + const onClose = useCallback(() => { setIsVisible(false); }, []); @@ -46,17 +80,14 @@ export const PinPad = (props) => { ref={bottomDrawerRef} openOnMount gestureMode={"none"} closeOnBackdropPress={false} - initialHeight={500} + initialHeight={535} backdropOpacity={0} + customStyles={styles} > + style={styles.pinPad}> + style={styles.pinPadTitle}> Boltcard PIN { pinLength={4} buttonSize={60} onValueChange={value => setEnteredPin(value)} - buttonAreaStyle={{ - marginTop: 24, - }} - inputAreaStyle={{ - marginBottom: 24, - }} - inputViewEmptyStyle={{ - backgroundColor: "transparent", - borderWidth: 1, - borderColor: "#FFF", - }} - inputViewFilledStyle={{ - backgroundColor: "#FFF", - }} - buttonViewStyle={{ - borderWidth: 1, - borderColor: "#FFF", - }} - buttonTextStyle={{ - color: "#FFF", - }} - onButtonPress={key => { - if (key === "custom_left") { - pinView.current.clear() - } - if (key === "custom_right") { - props.newPin(enteredPin) - } - }} + buttonAreaStyle={{ marginTop: 24 }} + inputAreaStyle={{ marginBottom: 24 }} + inputViewEmptyStyle={[{ backgroundColor: "transparent" }, styles.whiteBorder]} + inputViewFilledStyle={{ backgroundColor: "#FFF" }} + buttonViewStyle={styles.whiteBorder} + buttonTextStyle={{ color: "#FFF" }} + onButtonPress={key => setButtonPressed(key)} customLeftButton={showRemoveButton ? : null} customRightButton={showCompletedButton ? : null} /> From a988fc192425469b8e2ede224526a64bcd8a9bf8 Mon Sep 17 00:00:00 2001 From: X-Hades-X Date: Mon, 20 May 2024 10:09:38 +0200 Subject: [PATCH 6/9] fix failed pin state --- src/hooks/useNfc.ts | 9 +++++++-- src/screens/Invoice/Invoice.tsx | 2 +- src/screens/PinPad/styled.ts | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/hooks/useNfc.ts b/src/hooks/useNfc.ts index 1dfb48c..e4cf6b6 100644 --- a/src/hooks/useNfc.ts +++ b/src/hooks/useNfc.ts @@ -94,7 +94,7 @@ export const useNfc = () => { }); if (!isIos) { - readingNfcLoop(pr); + readingNfcLoop(pr, amount); } else { setIsNfcAvailable(false); } @@ -227,9 +227,14 @@ export const useNfc = () => { await NFC.stopRead(); setIsNfcLoading(false); + setIsPinConfirmed(false); + setIsPinRequired(false); if (debitCardData?.status === "OK" && !error) { setIsNfcActionSuccess(true); } else { + if (debitCardData?.status === "ERROR" && !error) { + error = debitCardData; + } toast.show( typeof error?.reason === "string" ? error.reason @@ -240,7 +245,7 @@ export const useNfc = () => { ); if (!isIos) { - readingNfcLoop(pr); + readingNfcLoop(pr, amount); } } diff --git a/src/screens/Invoice/Invoice.tsx b/src/screens/Invoice/Invoice.tsx index adea616..b6ad2f2 100644 --- a/src/screens/Invoice/Invoice.tsx +++ b/src/screens/Invoice/Invoice.tsx @@ -174,7 +174,7 @@ export const Invoice = () => { type: "bitcoin", title: t("startScanning"), onPress: () => { - void readingNfcLoop(lightningInvoice); + void readingNfcLoop(lightningInvoice, satoshis); } } } diff --git a/src/screens/PinPad/styled.ts b/src/screens/PinPad/styled.ts index e69de29..3a7c10d 100644 --- a/src/screens/PinPad/styled.ts +++ b/src/screens/PinPad/styled.ts @@ -0,0 +1 @@ +// used StyleSheet \ No newline at end of file From fb42dc20ef9051cdf07dae6322fd606c6e1d8c5b Mon Sep 17 00:00:00 2001 From: X-Hades-X Date: Mon, 20 May 2024 10:50:46 +0200 Subject: [PATCH 7/9] check undefined since it could be 0 --- src/hooks/useNfc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useNfc.ts b/src/hooks/useNfc.ts index e4cf6b6..0079b83 100644 --- a/src/hooks/useNfc.ts +++ b/src/hooks/useNfc.ts @@ -139,7 +139,7 @@ export const useNfc = () => { }>(cardData); let pin = ""; - if (cardDataResponse.pinLimit) { + if (cardDataResponse.pinLimit !== undefined) { //if the card has pin enabled //check the amount didn't exceed the limit const limitSat = cardDataResponse.pinLimit; From a83a0df314b4d839faa87fe8b4ada0079fb21de2 Mon Sep 17 00:00:00 2001 From: X-Hades-X Date: Fri, 24 May 2024 13:59:05 +0200 Subject: [PATCH 8/9] changes from review --- package-lock.json | 4 +- package.json | 4 +- src/components/PinPad/PinPad.tsx | 75 +++++++++++++ src/{screens => components}/PinPad/index.ts | 0 src/components/PinPad/styled.ts | 37 +++++++ src/components/index.ts | 1 + src/hooks/useNfc.ts | 83 +++++++------- src/screens/Invoice/Invoice.tsx | 12 +-- src/screens/PinPad/PinPad.tsx | 113 -------------------- src/screens/PinPad/styled.ts | 1 - 10 files changed, 166 insertions(+), 164 deletions(-) create mode 100644 src/components/PinPad/PinPad.tsx rename src/{screens => components}/PinPad/index.ts (100%) create mode 100644 src/components/PinPad/styled.ts delete mode 100644 src/screens/PinPad/PinPad.tsx delete mode 100644 src/screens/PinPad/styled.ts diff --git a/package-lock.json b/package-lock.json index 3dff56b..d80df58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,7 @@ "react-dom": "18.2.0", "react-i18next": "13.2.2", "react-native": "0.73.2", - "react-native-animated-bottom-drawer": "^0.0.23", + "react-native-animated-bottom-drawer": "0.0.23", "react-native-animated-linear-gradient": "1.3.0", "react-native-bars": "2.3.0", "react-native-blur-effect": "1.1.3", @@ -53,7 +53,7 @@ "react-native-dotenv": "3.4.9", "react-native-linear-gradient": "2.8.3", "react-native-nfc-manager": "3.14.11", - "react-native-pin-view": "^3.0.3", + "react-native-pin-view": "3.0.3", "react-native-progress": "5.0.1", "react-native-qrcode-svg": "^6.3.0", "react-native-safe-area-context": "4.7.2", diff --git a/package.json b/package.json index ed3ede2..7445a2c 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "react-dom": "18.2.0", "react-i18next": "13.2.2", "react-native": "0.73.2", - "react-native-animated-bottom-drawer": "^0.0.23", + "react-native-animated-bottom-drawer": "0.0.23", "react-native-animated-linear-gradient": "1.3.0", "react-native-bars": "2.3.0", "react-native-blur-effect": "1.1.3", @@ -59,7 +59,7 @@ "react-native-dotenv": "3.4.9", "react-native-linear-gradient": "2.8.3", "react-native-nfc-manager": "3.14.11", - "react-native-pin-view": "^3.0.3", + "react-native-pin-view": "3.0.3", "react-native-progress": "5.0.1", "react-native-qrcode-svg": "^6.3.0", "react-native-safe-area-context": "4.7.2", diff --git a/src/components/PinPad/PinPad.tsx b/src/components/PinPad/PinPad.tsx new file mode 100644 index 0000000..bf5662a --- /dev/null +++ b/src/components/PinPad/PinPad.tsx @@ -0,0 +1,75 @@ +import { useRef, useState, useEffect } from 'react'; +import ReactNativePinView from "react-native-pin-view"; +import BottomDrawer, { + BottomDrawerMethods, +} from 'react-native-animated-bottom-drawer'; +import { Icon } from "@components"; +import { faDeleteLeft } from "@fortawesome/free-solid-svg-icons"; +import * as S from "./styled"; + +type PinViewFunctions = { + clearAll: () => void; +}; + +type PinPadProps = { + onPinEntered: (pin: string) => void; +} + +export const PinPad = (props: PinPadProps) => { + const pinView = useRef(null); + const bottomDrawerRef = useRef(null); + const [showRemoveButton, setShowRemoveButton] = useState(false); + const [enteredPin, setEnteredPin] = useState(""); + const [buttonPressed, setButtonPressed] = useState(""); + + useEffect(() => { + if (enteredPin.length > 0) { + setShowRemoveButton(true) + } else { + setShowRemoveButton(false) + } + if (enteredPin.length === 4) { + props.onPinEntered(enteredPin) + } + }, [enteredPin]); + + useEffect(() => { + if (buttonPressed === "custom_left" && pinView.current) { + pinView.current.clearAll() + } + }, [buttonPressed]) + + return ( + <> + + + + Boltcard PIN + + setEnteredPin(value)} + buttonAreaStyle={{ marginTop: 24 }} + inputAreaStyle={{ marginBottom: 24 }} + inputViewEmptyStyle={S.PinViewStyles.whiteBorderTransparent} + inputViewFilledStyle={{ backgroundColor: "#FFF" }} + buttonViewStyle={S.PinViewStyles.whiteBorder} + buttonTextStyle={{ color: "#FFF" }} + onButtonPress={key => setButtonPressed(key)} + customLeftButton={showRemoveButton ? : undefined} + /> + + + + ); +} \ No newline at end of file diff --git a/src/screens/PinPad/index.ts b/src/components/PinPad/index.ts similarity index 100% rename from src/screens/PinPad/index.ts rename to src/components/PinPad/index.ts diff --git a/src/components/PinPad/styled.ts b/src/components/PinPad/styled.ts new file mode 100644 index 0000000..a05454a --- /dev/null +++ b/src/components/PinPad/styled.ts @@ -0,0 +1,37 @@ +import styled from "styled-components"; +import { View, Text } from "@components"; +import { StyleSheet } from "react-native"; + +export const PinPadContainer = styled(View)` + backgroundColor: transparent; + alignItems: center; + flex: 1; +`; + +export const PinPadTitle = styled(Text)` + paddingTop: 48px; + paddingBottom: 24px; + color: rgba(255,255,255,1); + fontSize: 48px; +`; + +export const BottomDrawerStyles = StyleSheet.create({ + container: { + backgroundColor: "rgba(0,0,0,0.5)", + }, + handleContainer: { + backgroundColor: "transparent", + } +}); + +export const PinViewStyles = StyleSheet.create({ + whiteBorder: { + borderWidth: 1, + borderColor: "#FFF", + }, + whiteBorderTransparent: { + borderWidth: 1, + borderColor: "#FFF", + backgroundColor: "transparent" + }, +}); \ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts index 59dbaf9..086f6f8 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -39,3 +39,4 @@ export { SplashScreen } from "./SplashScreen"; export { StatusBar } from "./StatusBar"; export { CircleProgress } from "./CircleProgress"; export { BitcoinIcon } from "./BitcoinIcon"; +export { PinPad } from "./PinPad"; diff --git a/src/hooks/useNfc.ts b/src/hooks/useNfc.ts index 0079b83..d26ffeb 100644 --- a/src/hooks/useNfc.ts +++ b/src/hooks/useNfc.ts @@ -20,24 +20,26 @@ export const useNfc = () => { const [isPinRequired, setIsPinRequired] = useState(false); const [isPinConfirmed, setIsPinConfirmed] = useState(false); - const [ pinResolver, setPinResolver ] = useState({ resolve: null }) + const [ pinResolver, setPinResolver ] = useState({ resolve: undefined }) - const pinInputPromise = () => { - let pinResolver; - return [ new Promise(( resolve, reject ) => { - pinResolver = resolve - }), pinResolver] + const setPin = (pin: string) => { + if(pinResolver.resolve) { + pinResolver.resolve(pin); + } } - const getPin = async () => { - const [ promise, resolve ] = await pinInputPromise() - setPinResolver({ resolve }) - return promise; - } + const getPin = useCallback(async () => { + const pinInputPromise = () => { + let _pinResolver; + return [ new Promise(( resolve ) => { + _pinResolver = resolve + }), _pinResolver] + } - const setPin = async(pin) => { - pinResolver.resolve(pin); - } + const [ promise, resolve ] = pinInputPromise() + setPinResolver({ resolve }) + return promise as Promise; + }, []); const setupNfc = useCallback(async () => { if (await getIsNfcSupported()) { @@ -70,7 +72,8 @@ export const useNfc = () => { }, []); const readingNfcLoop = useCallback( - async (pr: string, amount: number) => { + async (pr: string, amount?: number | null) => { + setIsNfcActionSuccess(false); await NFC.stopRead(); @@ -135,35 +138,41 @@ export const useNfc = () => { tag: "withdrawRequest"; callback: string; k1: string; - pinLimit: number; + pinLimit?: number; }>(cardData); let pin = ""; if (cardDataResponse.pinLimit !== undefined) { - //if the card has pin enabled - //check the amount didn't exceed the limit - const limitSat = cardDataResponse.pinLimit; - if (limitSat <= amount) { - setIsPinRequired(true); - pin = await getPin(); - setIsPinConfirmed(true); + + if (!amount) { + error = { reason: "No amount set. Can't make withdrawRequest"} + } else { + //if the card has pin enabled + //check the amount didn't exceed the limit + const limitSat = cardDataResponse.pinLimit; + if (limitSat <= amount) { + setIsPinRequired(true); + pin = await getPin(); + setIsPinConfirmed(true); + } } } else { - setIsPinRequired(false); + setIsPinRequired(false); } - const { data: callbackResponseData } = await axios.get<{ - reason: { detail: string }; - status: "OK" | "ERROR"; - }>(cardDataResponse.callback, { - params: { - k1: cardDataResponse.k1, - pr, - pin - } - }); - - debitCardData = callbackResponseData; + if (!error) { + const { data: callbackResponseData } = await axios.get<{ + reason: { detail: string }; + status: "OK" | "ERROR"; + }>(cardDataResponse.callback, { + params: { + k1: cardDataResponse.k1, + pr, + pin + } + }) + debitCardData = callbackResponseData; + } } else { // const { data: cardRequest } = await axios.get<{ payLink?: string }>( // lnHttpsRequest @@ -255,7 +264,7 @@ export const useNfc = () => { } }); }, - [toast, t] + [toast, t, getPin] ); const stopNfc = useCallback(() => { diff --git a/src/screens/Invoice/Invoice.tsx b/src/screens/Invoice/Invoice.tsx index b6ad2f2..0cad627 100644 --- a/src/screens/Invoice/Invoice.tsx +++ b/src/screens/Invoice/Invoice.tsx @@ -6,7 +6,8 @@ import { CheckboxField, ComponentStack, Loader, - Text + Text, + PinPad } from "@components"; import { faBolt, @@ -32,8 +33,6 @@ import { faBitcoin } from "@fortawesome/free-brands-svg-icons"; import axios from "axios"; import * as S from "./styled"; -import { PinPad } from "../PinPad"; - const numberWithSpaces = (nb: number) => nb.toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g, " "); @@ -86,7 +85,6 @@ export const Invoice = () => { const [isScheduledSwap, setIsScheduledSwap] = useState(false); const [isSwapLoading, setIsSwapLoading] = useState(); - const [isPinVisible, setIsPinVisible] = useState(true); const [swapFees, setSwapFees] = useState(); const [decodedInvoice, setDecodedInvoice] = useState(); @@ -115,10 +113,6 @@ export const Invoice = () => { } }, [isNfcActionSuccess]); - useEffect(() => { - setIsPinVisible(true) - }, [isPinRequired, isPinConfirmed]); - const onGetSwapQuote = useCallback(async () => { if (amount) { setIsSwapLoading(true); @@ -291,7 +285,7 @@ export const Invoice = () => { /> ) : isPinRequired && !isPinConfirmed ? ( - + ) : isNfcLoading || isNfcScanning ? ( { - const pinView = useRef(null); - const bottomDrawerRef = useRef(null); - const [showRemoveButton, setShowRemoveButton] = useState(false); - const [enteredPin, setEnteredPin] = useState(""); - const [buttonPressed, setButtonPressed] = useState(""); - const [showCompletedButton, setShowCompletedButton] = useState(false); - const [isVisible, setIsVisible] = useState(true); - - const styles = StyleSheet.create({ - container: { - backgroundColor: "rgba(0,0,0,0.5)", - }, - handleContainer: { - backgroundColor: "transparent", - }, - pinPad: { - backgroundColor: "transparent", - alignItems: "center", - flex: 1, - }, - pinPadTitle: { - paddingTop: 48, - paddingBottom: 24, - color: "rgba(255,255,255,1)", - fontSize: 48, - }, - whiteBorder: { - borderWidth: 1, - borderColor: "#FFF", - }, - }); - - useEffect(() => { - if(isVisible) { - bottomDrawerRef.current.open(); - } else { - bottomDrawerRef.current.close() - } - }, [isVisible]); - - useEffect(() => { - if (enteredPin.length > 0) { - setShowRemoveButton(true) - } else { - setShowRemoveButton(false) - } - if (enteredPin.length === 4) { - setShowCompletedButton(true) - } else { - setShowCompletedButton(false) - } - }, [enteredPin]); - - useEffect(() => { - if (buttonPressed === "custom_left") { - pinView.current.clearAll() - } - if (buttonPressed === "custom_right") { - props.newPin(enteredPin) - } - }, [buttonPressed]) - - const onClose = useCallback(() => { - setIsVisible(false); - }, []); - - return ( - <> - - - - Boltcard PIN - - setEnteredPin(value)} - buttonAreaStyle={{ marginTop: 24 }} - inputAreaStyle={{ marginBottom: 24 }} - inputViewEmptyStyle={[{ backgroundColor: "transparent" }, styles.whiteBorder]} - inputViewFilledStyle={{ backgroundColor: "#FFF" }} - buttonViewStyle={styles.whiteBorder} - buttonTextStyle={{ color: "#FFF" }} - onButtonPress={key => setButtonPressed(key)} - customLeftButton={showRemoveButton ? : null} - customRightButton={showCompletedButton ? : null} - /> - - - - ); -} \ No newline at end of file diff --git a/src/screens/PinPad/styled.ts b/src/screens/PinPad/styled.ts deleted file mode 100644 index 3a7c10d..0000000 --- a/src/screens/PinPad/styled.ts +++ /dev/null @@ -1 +0,0 @@ -// used StyleSheet \ No newline at end of file From 6302cf59d4132d4fa60c7cb73d3a048d74cd803e Mon Sep 17 00:00:00 2001 From: X-Hades-X Date: Sun, 26 May 2024 17:53:59 +0200 Subject: [PATCH 9/9] solve more lint issues --- src/components/PinPad/PinPad.tsx | 8 ++++++-- src/hooks/useNfc.ts | 10 ++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/components/PinPad/PinPad.tsx b/src/components/PinPad/PinPad.tsx index bf5662a..fece970 100644 --- a/src/components/PinPad/PinPad.tsx +++ b/src/components/PinPad/PinPad.tsx @@ -15,6 +15,8 @@ type PinPadProps = { onPinEntered: (pin: string) => void; } +const LeftButton = () => ; + export const PinPad = (props: PinPadProps) => { const pinView = useRef(null); const bottomDrawerRef = useRef(null); @@ -37,7 +39,7 @@ export const PinPad = (props: PinPadProps) => { if (buttonPressed === "custom_left" && pinView.current) { pinView.current.clearAll() } - }, [buttonPressed]) + }, [buttonPressed]); return ( <> @@ -55,6 +57,7 @@ export const PinPad = (props: PinPadProps) => { { buttonViewStyle={S.PinViewStyles.whiteBorder} buttonTextStyle={{ color: "#FFF" }} onButtonPress={key => setButtonPressed(key)} - customLeftButton={showRemoveButton ? : undefined} + // @ts-ignore + customLeftButton={showRemoveButton ? : undefined} /> diff --git a/src/hooks/useNfc.ts b/src/hooks/useNfc.ts index d26ffeb..54ccf4b 100644 --- a/src/hooks/useNfc.ts +++ b/src/hooks/useNfc.ts @@ -20,10 +20,12 @@ export const useNfc = () => { const [isPinRequired, setIsPinRequired] = useState(false); const [isPinConfirmed, setIsPinConfirmed] = useState(false); - const [ pinResolver, setPinResolver ] = useState({ resolve: undefined }) + const [ pinResolver, setPinResolver ] = useState<{ + resolve: (v: string) => void; + }>(); const setPin = (pin: string) => { - if(pinResolver.resolve) { + if(pinResolver?.resolve) { pinResolver.resolve(pin); } } @@ -31,13 +33,13 @@ export const useNfc = () => { const getPin = useCallback(async () => { const pinInputPromise = () => { let _pinResolver; - return [ new Promise(( resolve ) => { + return [ new Promise(( resolve ) => { _pinResolver = resolve }), _pinResolver] } const [ promise, resolve ] = pinInputPromise() - setPinResolver({ resolve }) + setPinResolver({ resolve } as unknown as { resolve: (v: string) => void }) return promise as Promise; }, []);