Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/app/context/SettingsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface SettingsContextType {
amount: number | string,
currency?: ACCOUNT_CURRENCIES
) => string;
getCurrencyRate: () => Promise<number>;
}

type Setting = Partial<SettingsStorage>;
Expand Down Expand Up @@ -152,6 +153,7 @@ export const SettingsProvider = ({
settings,
updateSetting,
isLoading,
getCurrencyRate,
};

return (
Expand Down
108 changes: 104 additions & 4 deletions src/app/screens/MakeInvoice/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useNavigationState } from "~/app/hooks/useNavigationState";
import { USER_REJECTED_ERROR } from "~/common/constants";
import api from "~/common/lib/api";
import msg from "~/common/lib/msg";
import { numSatsInBtc } from "~/common/utils/currencyConvert";
import type { OriginData, RequestInvoiceArgs } from "~/types";

const Dt = ({ children }: { children: React.ReactNode }) => (
Expand All @@ -29,6 +30,7 @@ function MakeInvoice() {
isLoading: isLoadingSettings,
settings,
getFormattedFiat,
getCurrencyRate,
} = useSettings();
const showFiat = !isLoadingSettings && settings.showFiat;

Expand All @@ -40,6 +42,8 @@ function MakeInvoice() {
const [loading, setLoading] = useState(false);
const [valueSat, setValueSat] = useState(invoiceAttributes.amount || "");
const [fiatValue, setFiatValue] = useState("");
const [valueFiatString, setValueFiatString] = useState("");
const [isSatsPrimary, setIsSatsPrimary] = useState(true);
const [memo, setMemo] = useState(invoiceAttributes.memo || "");
const [error, setError] = useState("");
const { t: tCommon } = useTranslation("common");
Expand All @@ -48,13 +52,91 @@ function MakeInvoice() {
});

useEffect(() => {
if (valueSat !== "" && showFiat) {
if (valueSat !== "" && showFiat && isSatsPrimary) {
(async () => {
const res = await getFormattedFiat(valueSat);
setFiatValue(res);
const formattedFiat = await getFormattedFiat(valueSat);
setFiatValue(formattedFiat);
const numericFiat = formattedFiat.replace(/[^0-9.]/g, "");
setValueFiatString(numericFiat);
})();
} else if (valueSat === "" && isSatsPrimary) {
setFiatValue("");
setValueFiatString("");
}
}, [valueSat, showFiat, getFormattedFiat]);
}, [valueSat, showFiat, getFormattedFiat, isSatsPrimary]);

useEffect(() => {
if (!isSatsPrimary && valueFiatString && valueFiatString !== ".") {
(async () => {
try {
setLoading(true);
const rate = await getCurrencyRate();
if (!rate) {
setError(tCommon("error") + ": Failed to fetch currency rate");
console.error("Failed to fetch currency rate.");
setValueSat("");
return;
}

const fiatNumericValue = parseFloat(valueFiatString);
if (isNaN(fiatNumericValue)) {
setError(tCommon("error") + ": Invalid amount");
setValueSat("");
return;
}

const calculatedSats = Math.floor(
(fiatNumericValue / rate) * numSatsInBtc
);

if (
invoiceAttributes.minimumAmount &&
calculatedSats <
(typeof invoiceAttributes.minimumAmount === "string"
? parseInt(invoiceAttributes.minimumAmount)
: invoiceAttributes.minimumAmount)
) {
setError(t("errors.amount_too_small"));
setValueSat("");
return;
} else if (
invoiceAttributes.maximumAmount &&
calculatedSats >
(typeof invoiceAttributes.maximumAmount === "string"
? parseInt(invoiceAttributes.maximumAmount)
: invoiceAttributes.maximumAmount)
) {
setError(t("errors.amount_too_big"));
setValueSat("");
return;
} else {
setError("");
}

setValueSat(calculatedSats.toString());
} catch (e) {
console.error("Error during fiat to sats conversion:", e);
if (e instanceof Error) setError(tCommon("error") + ": " + e.message);
else setError(tCommon("error") + ": Conversion failed");
setValueSat("");
} finally {
setLoading(false);
}
})();
} else if (!isSatsPrimary && !valueFiatString) {
setValueSat("");
setError("");
}
}, [
valueFiatString,
isSatsPrimary,
getCurrencyRate,
invoiceAttributes.minimumAmount,
invoiceAttributes.maximumAmount,
t,
tCommon,
setLoading,
]);

function handleValueChange(amount: string) {
setError("");
Expand All @@ -76,6 +158,15 @@ function MakeInvoice() {
setError(t("errors.amount_too_big"));
}
setValueSat(amount);
setIsSatsPrimary(true);
}

function handleFiatInputChange(e: React.ChangeEvent<HTMLInputElement>) {
const newValue = e.target.value;
if (/^\d*\.?\d*$/.test(newValue)) {
setValueFiatString(newValue);
setIsSatsPrimary(false);
}
}

function handleMemoChange(e: React.ChangeEvent<HTMLInputElement>) {
Expand Down Expand Up @@ -135,6 +226,15 @@ function MakeInvoice() {
fiatValue={fiatValue}
/>
<SatButtons onClick={handleValueChange} />
{showFiat && (
<TextField
id="fiatAmount"
label="Fiat Amount (e.g., USD)"
value={valueFiatString}
onChange={handleFiatInputChange}
placeholder="Enter amount in your local currency"
/>
)}
</div>
) : (
<dl className="overflow-hidden">
Expand Down
6 changes: 6 additions & 0 deletions src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,12 @@
"memo": {
"label": "Memo"
},
"fiat_amount": {
"label": "Local Currency Amount"
},
"enter_fiat_amount": {
"placeholder": "Enter amount in local currency"
},
"errors": {
"amount_too_small": "Amount is less than minimum",
"amount_too_big": "Amount exceeds maximum"
Expand Down