diff --git a/src/app/components/form/DualCurrencyField/index.tsx b/src/app/components/form/DualCurrencyField/index.tsx index a9547fade7..d5a43b4f95 100644 --- a/src/app/components/form/DualCurrencyField/index.tsx +++ b/src/app/components/form/DualCurrencyField/index.tsx @@ -1,9 +1,20 @@ -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; +import { useAccount } from "~/app/context/AccountContext"; import { useSettings } from "~/app/context/SettingsContext"; import { classNames } from "~/app/utils"; import { RangeLabel } from "./rangeLabel"; +export type DualCurrencyFieldChangeEvent = + React.ChangeEvent & { + target: HTMLInputElement & { + valueInFiat: number; + formattedValueInFiat: string; + valueInSats: number; + formattedValueInSats: string; + }; + }; + export type Props = { suffix?: string; endAdornment?: React.ReactNode; @@ -13,12 +24,7 @@ export type Props = { rangeExceeded?: boolean; baseToAltRate?: number; showFiat?: boolean; - onValueChange?: ( - valueInSats: number, - formattedValueInSata: string, - valueInFiat: number, - formattedValueInFiat: string - ) => void; + onChange?: (e: DualCurrencyFieldChangeEvent) => void; }; export default function DualCurrencyField({ @@ -30,7 +36,6 @@ export default function DualCurrencyField({ pattern, title, onChange, - onValueChange, onFocus, onBlur, value, @@ -47,93 +52,138 @@ export default function DualCurrencyField({ }: React.InputHTMLAttributes & Props) { const { t: tCommon } = useTranslation("common"); const { getFormattedInCurrency, getCurrencyRate, settings } = useSettings(); + const { account } = useAccount(); const inputEl = useRef(null); const outerStyles = "rounded-md border border-gray-300 dark:border-gray-800 bg-white dark:bg-black transition duration-300"; + const initialized = useRef(false); const [useFiatAsMain, _setUseFiatAsMain] = useState(false); - const [altFormattedValue, setAltFormattedValue] = useState(""); const [minValue, setMinValue] = useState(min); const [maxValue, setMaxValue] = useState(max); - const [inputValue, setInputValue] = useState(value); + const [inputValue, setInputValue] = useState(value || 0); - const refreshValue = async (value: number, useFiatAsMain: boolean) => { - setInputValue(value); + const getValues = useCallback( + async (value: number, useFiatAsMain: boolean) => { + let valueInSats = Number(value); + let valueInFiat = 0; - let valueInSats = Number(value); - let valueInFiat; + if (showFiat) { + valueInFiat = Number(value); + const rate = await getCurrencyRate(); + if (useFiatAsMain) { + valueInSats = Math.round(valueInSats / rate); + } else { + valueInFiat = Math.round(valueInFiat * rate * 100) / 100.0; + } + } - if (showFiat) { - valueInFiat = Number(value); - const rate = await getCurrencyRate(); - if (useFiatAsMain) { - valueInSats = Math.round(valueInSats / rate); - } else { - valueInFiat = Math.round(valueInFiat * rate * 100) / 100.0; + const formattedSats = getFormattedInCurrency(valueInSats, "BTC"); + let formattedFiat = ""; + + if (showFiat && valueInFiat) { + formattedFiat = getFormattedInCurrency(valueInFiat, settings.currency); } - } - const formattedSats = getFormattedInCurrency(valueInSats, "BTC"); - let formattedFiat; + return { + valueInSats, + formattedSats, + valueInFiat, + formattedFiat, + }; + }, + [getCurrencyRate, getFormattedInCurrency, showFiat, settings.currency] + ); - if (showFiat && valueInFiat) { - formattedFiat = getFormattedInCurrency(valueInFiat, settings.currency); - setAltFormattedValue(useFiatAsMain ? formattedSats : formattedFiat); - } + useEffect(() => { + (async () => { + if (showFiat) { + const { formattedSats, formattedFiat } = await getValues( + Number(inputValue), + useFiatAsMain + ); + setAltFormattedValue(useFiatAsMain ? formattedSats : formattedFiat); + } + })(); + }, [useFiatAsMain, inputValue, getValues, showFiat]); - return { - valueInSats, - formattedSats, - valueInFiat, - formattedFiat, - }; - }; + const setUseFiatAsMain = useCallback( + async (v: boolean) => { + if (!showFiat) v = false; - const setUseFiatAsMain = async (v: boolean) => { - if (!showFiat) v = false; + const rate = showFiat ? await getCurrencyRate() : 1; + if (min) { + let minV; + if (v) { + minV = (Math.round(Number(min) * rate * 100) / 100.0).toString(); + } else { + minV = min; + } - if (v === useFiatAsMain) return; - const rate = showFiat ? await getCurrencyRate() : 1; - if (min) { - setMinValue(Number(min) * rate); - } - if (max) { - setMaxValue(Number(max) * rate); - } + setMinValue(minV); + } + if (max) { + let maxV; + if (v) { + maxV = (Math.round(Number(max) * rate * 100) / 100.0).toString(); + } else { + maxV = max; + } - let newValue; - if (v) { - newValue = Math.round(Number(inputValue) * rate * 100) / 100.0; - } else { - newValue = Math.round(Number(inputValue) / rate); - } - _setUseFiatAsMain(v); - refreshValue(newValue, v); - }; + setMaxValue(maxV); + } + + let newValue; + if (v) { + newValue = Math.round(Number(inputValue) * rate * 100) / 100.0; + } else { + newValue = Math.round(Number(inputValue) / rate); + } + + _setUseFiatAsMain(v); + setInputValue(newValue); + }, + [showFiat, getCurrencyRate, inputValue, min, max] + ); const swapCurrencies = () => { setUseFiatAsMain(!useFiatAsMain); }; - const onChangeWrapper = async (e: React.ChangeEvent) => { - const value = e.target.value; - const { valueInSats, formattedSats, valueInFiat, formattedFiat } = - await refreshValue(Number(value), useFiatAsMain); - if (onValueChange) { - onValueChange( - valueInSats, - formattedSats, - valueInFiat || 0, - formattedFiat || "" - ); - } - if (onChange) { - e.target.value = valueInSats.toString(); - onChange(e); + const onChangeWrapper = useCallback( + async (e: React.ChangeEvent) => { + setInputValue(e.target.value); + + if (onChange) { + const value = Number(e.target.value); + const { valueInSats, formattedSats, valueInFiat, formattedFiat } = + await getValues(value, useFiatAsMain); + const newEvent: DualCurrencyFieldChangeEvent = { + ...e, + target: { + ...e.target, + value: valueInSats.toString(), + valueInFiat, + formattedValueInFiat: formattedFiat, + valueInSats, + formattedValueInSats: formattedSats, + }, + }; + onChange(newEvent); + } + }, + [onChange, useFiatAsMain, getValues] + ); + + // default to fiat when account currency is set to anything other than BTC + useEffect(() => { + if (!initialized.current) { + setUseFiatAsMain(!!(account?.currency && account?.currency !== "BTC")); + initialized.current = true; } - }; + }, [account?.currency, setUseFiatAsMain]); const inputNode = ( ); @@ -190,7 +241,8 @@ export default function DualCurrencyField({ !!rangeExceeded && "text-red-500 dark:text-red-500" )} > - {tCommon("sats_other")} + {" "} + {useFiatAsMain ? "" : tCommon("sats_other")} )} diff --git a/src/app/screens/Keysend/index.tsx b/src/app/screens/Keysend/index.tsx index 75d32416e1..3e02645801 100644 --- a/src/app/screens/Keysend/index.tsx +++ b/src/app/screens/Keysend/index.tsx @@ -5,7 +5,9 @@ import Header from "@components/Header"; import IconButton from "@components/IconButton"; import ResultCard from "@components/ResultCard"; import SatButtons from "@components/SatButtons"; -import DualCurrencyField from "@components/form/DualCurrencyField"; +import DualCurrencyField, { + DualCurrencyFieldChangeEvent, +} from "@components/form/DualCurrencyField"; import { PopiconsChevronLeftLine } from "@popicons/react"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -118,14 +120,9 @@ function Keysend() { min={1} value={amountSat} showFiat={showFiat} - onValueChange={( - valueInSats, - formattedValueInSats, - valueInFiat, - formattedValueInFiat - ) => { - setAmountSat(valueInSats.toString()); - setFiatAmount(formattedValueInFiat); + onChange={(e: DualCurrencyFieldChangeEvent) => { + setAmountSat(e.target.value); + setFiatAmount(e.target.formattedValueInFiat); }} hint={`${tCommon("balance")}: ${auth?.balancesDecorated ?.accountBalance}`} diff --git a/src/app/screens/LNURLPay/index.tsx b/src/app/screens/LNURLPay/index.tsx index 8752ab44c7..e10ae1f551 100644 --- a/src/app/screens/LNURLPay/index.tsx +++ b/src/app/screens/LNURLPay/index.tsx @@ -5,7 +5,9 @@ import Hyperlink from "@components/Hyperlink"; import PublisherCard from "@components/PublisherCard"; import ResultCard from "@components/ResultCard"; import SatButtons from "@components/SatButtons"; -import DualCurrencyField from "@components/form/DualCurrencyField"; +import DualCurrencyField, { + DualCurrencyFieldChangeEvent, +} from "@components/form/DualCurrencyField"; import TextField from "@components/form/TextField"; import { PopiconsChevronBottomLine, @@ -35,7 +37,6 @@ import type { LNURLPaymentSuccessAction, PaymentResponse, } from "~/types"; - const Dt = ({ children }: { children: React.ReactNode }) => (
{children}
); @@ -441,14 +442,9 @@ function LNURLPay() { max={amountMax} rangeExceeded={rangeExceeded} value={valueSat} - onValueChange={( - valueInSats, - formattedValueInSats, - valueInFiat, - formattedValueInFiat - ) => { - setValueSat(valueInSats.toString()); - setFiatValue(formattedValueInFiat); + onChange={(e: DualCurrencyFieldChangeEvent) => { + setValueSat(e.target.value); + setFiatValue(e.target.formattedValueInFiat); }} showFiat={showFiat} hint={`${tCommon("balance")}: ${auth diff --git a/src/app/screens/LNURLWithdraw/index.tsx b/src/app/screens/LNURLWithdraw/index.tsx index bafad216fc..e26d9c7863 100644 --- a/src/app/screens/LNURLWithdraw/index.tsx +++ b/src/app/screens/LNURLWithdraw/index.tsx @@ -4,7 +4,9 @@ import Container from "@components/Container"; import ContentMessage from "@components/ContentMessage"; import PublisherCard from "@components/PublisherCard"; import ResultCard from "@components/ResultCard"; -import DualCurrencyField from "@components/form/DualCurrencyField"; +import DualCurrencyField, { + DualCurrencyFieldChangeEvent, +} from "@components/form/DualCurrencyField"; import axios from "axios"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -17,7 +19,6 @@ import { USER_REJECTED_ERROR } from "~/common/constants"; import api from "~/common/lib/api"; import msg from "~/common/lib/msg"; import type { LNURLWithdrawServiceResponse } from "~/types"; - function LNURLWithdraw() { const { t } = useTranslation("translation", { keyPrefix: "lnurlwithdraw" }); const { t: tCommon } = useTranslation("common"); @@ -107,14 +108,9 @@ function LNURLWithdraw() { min={Math.floor(minWithdrawable / 1000)} max={Math.floor(maxWithdrawable / 1000)} value={valueSat} - onValueChange={( - valueInSats, - formattedValueInSat, - valueInFiat, - formattedValueInFiat - ) => { - setValueSat(valueInSats.toString()); - setFiatValue(formattedValueInFiat); + onChange={(e: DualCurrencyFieldChangeEvent) => { + setValueSat(e.target.value); + setFiatValue(e.target.formattedValueInFiat); }} showFiat={showFiat} /> diff --git a/src/app/screens/SendToBitcoinAddress/index.tsx b/src/app/screens/SendToBitcoinAddress/index.tsx index aa753388c9..7dc46234cb 100644 --- a/src/app/screens/SendToBitcoinAddress/index.tsx +++ b/src/app/screens/SendToBitcoinAddress/index.tsx @@ -2,7 +2,9 @@ import Button from "@components/Button"; import ConfirmOrCancel from "@components/ConfirmOrCancel"; import Header from "@components/Header"; import IconButton from "@components/IconButton"; -import DualCurrencyField from "@components/form/DualCurrencyField"; +import DualCurrencyField, { + DualCurrencyFieldChangeEvent, +} from "@components/form/DualCurrencyField"; import { CreateSwapResponse } from "@getalby/sdk/dist/types"; import { PopiconsChevronLeftLine, @@ -248,14 +250,9 @@ function SendToBitcoinAddress() { label={tCommon("amount")} min={amountMin} max={amountMax} - onValueChange={( - valueInSats, - formattedValueInSats, - valueInFiat, - formattedValueInFiat - ) => { - setAmountSat(valueInSats.toString()); - setFiatAmount(formattedValueInFiat); + onChange={(e: DualCurrencyFieldChangeEvent) => { + setAmountSat(e.target.value); + setFiatAmount(e.target.formattedValueInFiat); }} showFiat={showFiat} value={amountSat}