Skip to content

Commit

Permalink
feat: dual currency input supports fiat input
Browse files Browse the repository at this point in the history
  • Loading branch information
riccardobl committed Mar 16, 2024
1 parent f0d08ad commit 4ee0faf
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 109 deletions.
194 changes: 123 additions & 71 deletions src/app/components/form/DualCurrencyField/index.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLInputElement> & {
target: HTMLInputElement & {
valueInFiat: number;
formattedValueInFiat: string;
valueInSats: number;
formattedValueInSats: string;
};
};

export type Props = {
suffix?: string;
endAdornment?: React.ReactNode;
Expand All @@ -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({
Expand All @@ -30,7 +36,6 @@ export default function DualCurrencyField({
pattern,
title,
onChange,
onValueChange,
onFocus,
onBlur,
value,
Expand All @@ -47,93 +52,138 @@ export default function DualCurrencyField({
}: React.InputHTMLAttributes<HTMLInputElement> & Props) {
const { t: tCommon } = useTranslation("common");
const { getFormattedInCurrency, getCurrencyRate, settings } = useSettings();
const { account } = useAccount();

const inputEl = useRef<HTMLInputElement>(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<HTMLInputElement>) => {
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<HTMLInputElement>) => {
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 = (
<input
Expand All @@ -159,6 +209,7 @@ export default function DualCurrencyField({
disabled={disabled}
min={minValue}
max={maxValue}
step={useFiatAsMain ? "0.01" : "1"}
/>
);

Expand Down Expand Up @@ -190,7 +241,8 @@ export default function DualCurrencyField({
!!rangeExceeded && "text-red-500 dark:text-red-500"
)}
>
<RangeLabel min={minValue} max={maxValue} /> {tCommon("sats_other")}
<RangeLabel min={minValue} max={maxValue} />{" "}
{useFiatAsMain ? "" : tCommon("sats_other")}
</span>
)}
</div>
Expand Down
15 changes: 6 additions & 9 deletions src/app/screens/Keysend/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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}`}
Expand Down
16 changes: 6 additions & 10 deletions src/app/screens/LNURLPay/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -35,7 +37,6 @@ import type {
LNURLPaymentSuccessAction,
PaymentResponse,
} from "~/types";

const Dt = ({ children }: { children: React.ReactNode }) => (
<dt className="font-medium text-gray-800 dark:text-white">{children}</dt>
);
Expand Down Expand Up @@ -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
Expand Down
16 changes: 6 additions & 10 deletions src/app/screens/LNURLWithdraw/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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");
Expand Down Expand Up @@ -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}
/>
Expand Down
15 changes: 6 additions & 9 deletions src/app/screens/SendToBitcoinAddress/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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}
Expand Down

0 comments on commit 4ee0faf

Please sign in to comment.