diff --git a/app/javascript/controllers/money_field_controller.js b/app/javascript/controllers/money_field_controller.js index 686ca713a..db8b1211b 100644 --- a/app/javascript/controllers/money_field_controller.js +++ b/app/javascript/controllers/money_field_controller.js @@ -1,11 +1,17 @@ import { Controller } from "@hotwired/stimulus"; -import parseLocaleFloat from "utils/parse_locale_float"; import { CurrenciesService } from "services/currencies_service"; +import parseLocaleFloat from "utils/parse_locale_float"; // Connects to data-controller="money-field" // when currency select change, update the input value with the correct placeholder and step export default class extends Controller { static targets = ["amount", "currency", "symbol"]; + static values = { + precision: Number, + step: String, + }; + + requestSequence = 0; handleCurrencyChange(e) { const selectedCurrency = e.target.value; @@ -13,18 +19,33 @@ export default class extends Controller { } updateAmount(currency) { - new CurrenciesService().get(currency).then((currency) => { - this.amountTarget.step = currency.step; + const requestId = ++this.requestSequence; + new CurrenciesService().get(currency).then((currencyData) => { + if (requestId !== this.requestSequence) return; + + this.amountTarget.step = + this.hasStepValue && + this.stepValue !== "" && + (this.stepValue === "any" || Number.isFinite(Number(this.stepValue))) + ? this.stepValue + : currencyData.step; const rawValue = this.amountTarget.value.trim(); if (rawValue !== "") { const parsedAmount = parseLocaleFloat(rawValue); if (Number.isFinite(parsedAmount)) { - this.amountTarget.value = parsedAmount.toFixed(currency.default_precision); + const precision = + this.hasPrecisionValue && Number.isInteger(this.precisionValue) + ? this.precisionValue + : currencyData.default_precision; + this.amountTarget.value = parsedAmount.toFixed(precision); } } - this.symbolTarget.innerText = currency.symbol; + this.symbolTarget.innerText = currencyData.symbol; + }).catch(() => { + // Catch prevents Unhandled Promise Rejection for network failures. + // Silently ignored as they are unactionable by the user. }); } } diff --git a/app/views/shared/_money_field.html.erb b/app/views/shared/_money_field.html.erb index 787798c44..41776c28f 100644 --- a/app/views/shared/_money_field.html.erb +++ b/app/views/shared/_money_field.html.erb @@ -7,7 +7,10 @@ end currency = Money::Currency.new(currency_value || options[:default_currency] || "USD") %> -
+
data-money-field-precision-value="<%= options[:precision] %>"<% end %> + <% if options[:step].present? %>data-money-field-step-value="<%= options[:step] %>"<% end %>> <% if options[:label_tooltip] %>
<%= form.label options[:label] || t(".label"), class: "form-field__label" do %> diff --git a/app/views/transactions/convert_to_trade.html.erb b/app/views/transactions/convert_to_trade.html.erb index dc1e5a4ac..631a5c9ab 100644 --- a/app/views/transactions/convert_to_trade.html.erb +++ b/app/views/transactions/convert_to_trade.html.erb @@ -119,7 +119,7 @@
<%= f.label :price, t(".price_label"), class: "font-medium text-sm text-primary block" %> <%= f.number_field :price, - step: "0.0001", + step: "any", min: "0", placeholder: t(".price_placeholder"), class: "form-field__input border border-secondary rounded-lg px-3 py-2 w-full text-primary bg-container",