Skip to content

Commit

Permalink
feat: support sat-fiat switch in dual currency input
Browse files Browse the repository at this point in the history
  • Loading branch information
riccardobl committed Mar 15, 2024
1 parent 5ea210a commit f0d08ad
Show file tree
Hide file tree
Showing 13 changed files with 169 additions and 142 deletions.
6 changes: 3 additions & 3 deletions src/app/components/BudgetControl/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ type Props = {
onRememberChange: ChangeEventHandler<HTMLInputElement>;
budget: string;
onBudgetChange: ChangeEventHandler<HTMLInputElement>;
fiatAmount: string;
disabled?: boolean;
showFiat?: boolean;
};

function BudgetControl({
remember,
onRememberChange,
budget,
onBudgetChange,
fiatAmount,
disabled = false,
showFiat = false,
}: Props) {
const { t } = useTranslation("components", {
keyPrefix: "budget_control",
Expand Down Expand Up @@ -60,8 +60,8 @@ function BudgetControl({

<div>
<DualCurrencyField
showFiat={showFiat}
autoFocus
fiatValue={fiatAmount}
id="budget"
min={0}
label={t("budget.label")}
Expand Down
20 changes: 2 additions & 18 deletions src/app/components/SitePreferences/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,12 @@ export type Props = {
};

function SitePreferences({ launcherType, allowance, onEdit, onDelete }: Props) {
const {
isLoading: isLoadingSettings,
settings,
getFormattedFiat,
} = useSettings();
const { isLoading: isLoadingSettings, settings } = useSettings();
const showFiat = !isLoadingSettings && settings.showFiat;
const { account } = useAccount();
const [modalIsOpen, setIsOpen] = useState(false);
const [budget, setBudget] = useState("");
const [lnurlAuth, setLnurlAuth] = useState(false);
const [fiatAmount, setFiatAmount] = useState("");

const [originalPermissions, setOriginalPermissions] = useState<
Permission[] | null
Expand Down Expand Up @@ -79,17 +74,6 @@ function SitePreferences({ launcherType, allowance, onEdit, onDelete }: Props) {
fetchPermissions();
}, [account?.id, allowance.id]);

useEffect(() => {
if (budget !== "" && showFiat) {
const getFiat = async () => {
const res = await getFormattedFiat(budget);
setFiatAmount(res);
};

getFiat();
}
}, [budget, showFiat, getFormattedFiat]);

function openModal() {
setBudget(allowance.totalBudget.toString());
setLnurlAuth(allowance.lnurlAuth);
Expand Down Expand Up @@ -196,7 +180,7 @@ function SitePreferences({ launcherType, allowance, onEdit, onDelete }: Props) {
placeholder={tCommon("sats", { count: 0 })}
value={budget}
hint={t("hint")}
fiatValue={fiatAmount}
showFiat={showFiat}
onChange={(e) => setBudget(e.target.value)}
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/form/DualCurrencyField/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Props } from "./index";
import DualCurrencyField from "./index";

const props: Props = {
fiatValue: "$10.00",
showFiat: true,
label: "Amount",
};

Expand Down
119 changes: 106 additions & 13 deletions src/app/components/form/DualCurrencyField/index.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useSettings } from "~/app/context/SettingsContext";
import { classNames } from "~/app/utils";

import { RangeLabel } from "./rangeLabel";

export type Props = {
suffix?: string;
endAdornment?: React.ReactNode;
fiatValue: string;
label: string;
hint?: string;
amountExceeded?: boolean;
rangeExceeded?: boolean;
baseToAltRate?: number;
showFiat?: boolean;
onValueChange?: (
valueInSats: number,
formattedValueInSata: string,
valueInFiat: number,
formattedValueInFiat: string
) => void;
};

export default function DualCurrencyField({
label,
fiatValue,
showFiat = true,
id,
placeholder,
required = false,
pattern,
title,
onChange,
onValueChange,
onFocus,
onBlur,
value,
Expand All @@ -38,10 +46,95 @@ export default function DualCurrencyField({
rangeExceeded,
}: React.InputHTMLAttributes<HTMLInputElement> & Props) {
const { t: tCommon } = useTranslation("common");
const { getFormattedInCurrency, getCurrencyRate, settings } = useSettings();

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 [useFiatAsMain, _setUseFiatAsMain] = useState(false);

const [altFormattedValue, setAltFormattedValue] = useState("");
const [minValue, setMinValue] = useState(min);
const [maxValue, setMaxValue] = useState(max);
const [inputValue, setInputValue] = useState(value);

const refreshValue = async (value: number, useFiatAsMain: boolean) => {
setInputValue(value);

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;
}
}

const formattedSats = getFormattedInCurrency(valueInSats, "BTC");
let formattedFiat;

if (showFiat && valueInFiat) {
formattedFiat = getFormattedInCurrency(valueInFiat, settings.currency);
setAltFormattedValue(useFiatAsMain ? formattedSats : formattedFiat);
}

return {
valueInSats,
formattedSats,
valueInFiat,
formattedFiat,
};
};

const setUseFiatAsMain = async (v: boolean) => {
if (!showFiat) v = false;

if (v === useFiatAsMain) return;
const rate = showFiat ? await getCurrencyRate() : 1;
if (min) {
setMinValue(Number(min) * rate);
}
if (max) {
setMaxValue(Number(max) * rate);
}

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);
};

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 inputNode = (
<input
ref={inputEl}
Expand All @@ -57,15 +150,15 @@ export default function DualCurrencyField({
required={required}
pattern={pattern}
title={title}
onChange={onChange}
onChange={onChangeWrapper}
onFocus={onFocus}
onBlur={onBlur}
value={value}
value={inputValue}
autoFocus={autoFocus}
autoComplete={autoComplete}
disabled={disabled}
min={min}
max={max}
min={minValue}
max={maxValue}
/>
);

Expand All @@ -90,14 +183,14 @@ export default function DualCurrencyField({
>
{label}
</label>
{(min || max) && (
{(minValue || maxValue) && (
<span
className={classNames(
"text-xs text-gray-700 dark:text-neutral-400",
!!rangeExceeded && "text-red-500 dark:text-red-500"
)}
>
<RangeLabel min={min} max={max} /> {tCommon("sats_other")}
<RangeLabel min={minValue} max={maxValue} /> {tCommon("sats_other")}
</span>
)}
</div>
Expand All @@ -114,9 +207,9 @@ export default function DualCurrencyField({
>
{inputNode}

{!!fiatValue && (
<p className="helper text-gray-500 z-1 pointer-events-none">
~{fiatValue}
{!!altFormattedValue && (
<p className="helper text-gray-500 z-1" onClick={swapCurrencies}>
~{altFormattedValue}
</p>
)}

Expand Down
6 changes: 4 additions & 2 deletions src/app/context/SettingsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ interface SettingsContextType {
getFormattedNumber: (amount: number | string) => string;
getFormattedInCurrency: (
amount: number | string,
currency?: ACCOUNT_CURRENCIES
currency?: ACCOUNT_CURRENCIES | CURRENCIES
) => string;
getCurrencyRate: () => Promise<number>;
}

type Setting = Partial<SettingsStorage>;
Expand Down Expand Up @@ -115,7 +116,7 @@ export const SettingsProvider = ({

const getFormattedInCurrency = (
amount: number | string,
currency = "BTC" as ACCOUNT_CURRENCIES
currency = "BTC" as ACCOUNT_CURRENCIES | CURRENCIES
) => {
if (currency === "BTC") {
return getFormattedSats(amount);
Expand Down Expand Up @@ -149,6 +150,7 @@ export const SettingsProvider = ({
getFormattedSats,
getFormattedNumber,
getFormattedInCurrency,
getCurrencyRate,
settings,
updateSetting,
isLoading,
Expand Down
10 changes: 1 addition & 9 deletions src/app/screens/ConfirmKeysend/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ function ConfirmKeysend() {
((parseInt(amount) || 0) * 10).toString()
);
const [fiatAmount, setFiatAmount] = useState("");
const [fiatBudgetAmount, setFiatBudgetAmount] = useState("");
const [loading, setLoading] = useState(false);
const [successMessage, setSuccessMessage] = useState("");

Expand All @@ -54,13 +53,6 @@ function ConfirmKeysend() {
})();
}, [amount, showFiat, getFormattedFiat]);

useEffect(() => {
(async () => {
const res = await getFormattedFiat(budget);
setFiatBudgetAmount(res);
})();
}, [budget, showFiat, getFormattedFiat]);

async function confirm() {
if (rememberMe && budget) {
await saveBudget();
Expand Down Expand Up @@ -153,7 +145,7 @@ function ConfirmKeysend() {
</div>
<div>
<BudgetControl
fiatAmount={fiatBudgetAmount}
showFiat={showFiat}
remember={rememberMe}
onRememberChange={(event) => {
setRememberMe(event.target.checked);
Expand Down
12 changes: 1 addition & 11 deletions src/app/screens/ConfirmPayment/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ function ConfirmPayment() {
((invoice.satoshis || 0) * 10).toString()
);
const [fiatAmount, setFiatAmount] = useState("");
const [fiatBudgetAmount, setFiatBudgetAmount] = useState("");

const formattedInvoiceSats = getFormattedSats(invoice.satoshis || 0);

Expand All @@ -57,15 +56,6 @@ function ConfirmPayment() {
})();
}, [invoice.satoshis, showFiat, getFormattedFiat]);

useEffect(() => {
(async () => {
if (showFiat && budget) {
const res = await getFormattedFiat(budget);
setFiatBudgetAmount(res);
}
})();
}, [budget, showFiat, getFormattedFiat]);

const [rememberMe, setRememberMe] = useState(false);
const [loading, setLoading] = useState(false);
const [successMessage, setSuccessMessage] = useState("");
Expand Down Expand Up @@ -160,7 +150,7 @@ function ConfirmPayment() {
<div>
{navState.origin && (
<BudgetControl
fiatAmount={fiatBudgetAmount}
showFiat={showFiat}
remember={rememberMe}
onRememberChange={(event) => {
setRememberMe(event.target.checked);
Expand Down
Loading

0 comments on commit f0d08ad

Please sign in to comment.