diff --git a/components/DualCurrencyInput.tsx b/components/DualCurrencyInput.tsx index 5a1d898..aa61f15 100644 --- a/components/DualCurrencyInput.tsx +++ b/components/DualCurrencyInput.tsx @@ -8,6 +8,7 @@ import { SATS_REGEX, } from "~/lib/constants"; import { useAppStore } from "~/lib/state/appStore"; +import { cn } from "~/lib/utils"; import { RefreshCw } from "./Icons"; import { Input } from "./ui/input"; import { Text } from "./ui/text"; @@ -17,6 +18,8 @@ type DualCurrencyInputProps = { setAmount(amount: string): void; autoFocus?: boolean; readOnly?: boolean; + max?: number; + min?: number; }; export function DualCurrencyInput({ @@ -24,6 +27,8 @@ export function DualCurrencyInput({ setAmount, autoFocus = false, readOnly = false, + max, + min, }: DualCurrencyInputProps) { const getFiatAmount = useGetFiatAmount(); const getSatsAmount = useGetSatsAmount(); @@ -61,7 +66,11 @@ export function DualCurrencyInput({ return ( max) || (min && Number(amount) < min)) && + "text-destructive", + )} placeholder="0" keyboardType={inputMode === "sats" ? "number-pad" : "decimal-pad"} value={inputMode === "sats" ? amount : fiatAmount} diff --git a/pages/send/LNURLPay.tsx b/pages/send/LNURLPay.tsx index a44e2d7..50bb0b9 100644 --- a/pages/send/LNURLPay.tsx +++ b/pages/send/LNURLPay.tsx @@ -11,6 +11,7 @@ import { Input } from "~/components/ui/input"; import { Text } from "~/components/ui/text"; import { errorToast } from "~/lib/errorToast"; import { LNURLPayServiceResponse, lnurl } from "~/lib/lnurl"; +import { cn } from "~/lib/utils"; export function LNURLPay() { const { @@ -24,10 +25,17 @@ export function LNURLPay() { }; const lnurlDetails: LNURLPayServiceResponse = JSON.parse(lnurlDetailsJSON); const [isLoading, setLoading] = React.useState(false); - const [amount, setAmount] = React.useState(amountParam ?? 0); + const [amount, setAmount] = React.useState(amountParam ?? ""); const [comment, setComment] = React.useState(""); const [isAmountReadOnly, setAmountReadOnly] = React.useState(false); + const isAmountInvalid = React.useMemo(() => { + const min = Math.floor(lnurlDetails.minSendable / 1000); + const max = Math.floor(lnurlDetails.maxSendable / 1000); + + return Number(amount) < min || Number(amount) > max; + }, [amount, lnurlDetails.minSendable, lnurlDetails.maxSendable]); + useEffect(() => { // Handle fixed amount LNURLs if (lnurlDetails.minSendable === lnurlDetails.maxSendable) { @@ -76,7 +84,27 @@ export function LNURLPay() { setAmount={setAmount} readOnly={isAmountReadOnly} autoFocus={!isAmountReadOnly && !amount} + min={Math.floor(lnurlDetails.minSendable / 1000)} + max={Math.floor(lnurlDetails.maxSendable / 1000)} /> + + + Between{" "} + {new Intl.NumberFormat().format( + Math.floor(lnurlDetails.minSendable / 1000), + )} + {" and "} + {new Intl.NumberFormat().format( + Math.floor(lnurlDetails.maxSendable / 1000), + )}{" "} + sats + + Comment @@ -96,7 +124,7 @@ export function LNURLPay() { size="lg" className="flex flex-row gap-2" onPress={requestInvoice} - disabled={isLoading} + disabled={isLoading || isAmountInvalid} > {isLoading && } Next diff --git a/pages/withdraw/Withdraw.tsx b/pages/withdraw/Withdraw.tsx index 2de74f6..db76185 100644 --- a/pages/withdraw/Withdraw.tsx +++ b/pages/withdraw/Withdraw.tsx @@ -5,15 +5,22 @@ import React, { useEffect } from "react"; import { View } from "react-native"; import DismissableKeyboardView from "~/components/DismissableKeyboardView"; import { DualCurrencyInput } from "~/components/DualCurrencyInput"; -import { ClipboardPaste } from "~/components/Icons"; +import { AlertCircle, ClipboardPaste } from "~/components/Icons"; import Loading from "~/components/Loading"; import QRCodeScanner from "~/components/QRCodeScanner"; import Screen from "~/components/Screen"; import { Button } from "~/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardTitle, +} from "~/components/ui/card"; import { Text } from "~/components/ui/text"; import { useGetFiatAmount } from "~/hooks/useGetFiatAmount"; import { errorToast } from "~/lib/errorToast"; import { useAppStore } from "~/lib/state/appStore"; +import { cn } from "~/lib/utils"; export function Withdraw() { const { url } = useLocalSearchParams<{ url: string }>(); @@ -26,6 +33,16 @@ export function Withdraw() { const [lnurlDetails, setLnurlDetails] = React.useState(); + const isAmountInvalid = React.useMemo(() => { + if (!lnurlDetails) { + return true; + } + const min = Math.floor(lnurlDetails.minWithdrawable / 1000); + const max = Math.floor(lnurlDetails.maxWithdrawable / 1000); + + return Number(valueSat) < min || Number(valueSat) > max; + }, [valueSat, lnurlDetails]); + // Delay starting the QR scanner if url has valid lnurl withdraw info useEffect(() => { if (url) { @@ -109,17 +126,6 @@ export function Withdraw() { return; } - if (Number(valueSat) < lnurlDetails.minWithdrawable / 1000) { - throw new Error( - `Amount below minimum limit of ${lnurlDetails.minWithdrawable} sats`, - ); - } - if (Number(valueSat) > lnurlDetails.maxWithdrawable / 1000) { - throw new Error( - `Amount exceeds maximum limit of ${lnurlDetails.maxWithdrawable} sats.`, - ); - } - setLoadingConfirm(true); const nwcClient = useAppStore.getState().nwcClient; @@ -222,8 +228,28 @@ export function Withdraw() { + + + Between{" "} + {new Intl.NumberFormat().format( + Math.floor(lnurlDetails.minWithdrawable / 1000), + )} + {" and "} + {new Intl.NumberFormat().format( + Math.floor(lnurlDetails.maxWithdrawable / 1000), + )}{" "} + sats + + Description @@ -235,11 +261,34 @@ export function Withdraw() { )} + {lnurlDetails.minWithdrawable !== + lnurlDetails.maxWithdrawable && ( + + + + + Withdraw Limit + + Enter an amount between{" "} + + {Math.floor(lnurlDetails.minWithdrawable / 1000)}{" "} + sats + {" "} + and{" "} + + {Math.floor(lnurlDetails.maxWithdrawable / 1000)}{" "} + sats + + + + + + )}