diff --git a/app/controllers/currencies_controller.rb b/app/controllers/currencies_controller.rb new file mode 100644 index 00000000..d12f835a --- /dev/null +++ b/app/controllers/currencies_controller.rb @@ -0,0 +1,6 @@ +class CurrenciesController < ApplicationController + def show + @currency = Money::Currency.all_instances.find { |currency| currency.iso_code == params[:id] } + render json: { step: @currency.step, placeholder: Money.new(0, @currency).format } + end +end diff --git a/app/helpers/application_form_builder.rb b/app/helpers/application_form_builder.rb index b84f6d0a..397367f0 100644 --- a/app/helpers/application_form_builder.rb +++ b/app/helpers/application_form_builder.rb @@ -33,11 +33,13 @@ class ApplicationFormBuilder < ActionView::Helpers::FormBuilder readonly_currency = options[:readonly_currency] || false + currency = money&.currency || Money.default_currency default_options = { class: "form-field__input", value: money&.amount, - placeholder: Money.new(0, money&.currency || Money.default_currency).format, - step: "0.01" # Not all currencies have 2 decimal places + "data-money-field-target" => "amount", + placeholder: Money.new(0, currency).format, + step: currency.step } merged_options = default_options.merge(options) @@ -45,11 +47,11 @@ class ApplicationFormBuilder < ActionView::Helpers::FormBuilder grouped_options = currency_options_for_select selected_currency = money&.currency&.iso_code - @template.form_field_tag do + @template.form_field_tag data: { controller: "money-field" } do (label(method, *label_args(options)).to_s if options[:label]) + @template.tag.div(class: "flex items-center") do number_field(money_amount_method, merged_options.except(:label)) + - grouped_select(money_currency_method, grouped_options, { selected: selected_currency, disabled: readonly_currency }, class: "ml-auto form-field__input w-fit pr-8") + grouped_select(money_currency_method, grouped_options, { selected: selected_currency, disabled: readonly_currency }, class: "ml-auto form-field__input w-fit pr-8", data: { "money-field-target" => "currency", action: "change->money-field#handleCurrencyChange" }) end end end diff --git a/app/helpers/forms_helper.rb b/app/helpers/forms_helper.rb index ea7f24db..65959f0e 100644 --- a/app/helpers/forms_helper.rb +++ b/app/helpers/forms_helper.rb @@ -1,6 +1,7 @@ module FormsHelper - def form_field_tag(&) - tag.div class: "form-field", & + def form_field_tag(options = {}, &block) + options[:class] = [ "form-field", options[:class] ].compact.join(" ") + tag.div **options, &block end def radio_tab_tag(form:, name:, value:, label:, icon:, checked: false, disabled: false) diff --git a/app/javascript/controllers/money_field_controller.js b/app/javascript/controllers/money_field_controller.js new file mode 100644 index 00000000..09b6b67a --- /dev/null +++ b/app/javascript/controllers/money_field_controller.js @@ -0,0 +1,20 @@ +import { Controller } from "@hotwired/stimulus"; +import { CurrenciesService } from "services/currencies_service"; + +// 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"]; + + handleCurrencyChange() { + const selectedCurrency = event.target.value; + this.updateAmount(selectedCurrency); + } + + updateAmount(currency) { + (new CurrenciesService).get(currency).then((data) => { + this.amountTarget.placeholder = data.placeholder; + this.amountTarget.step = data.step; + }); + } +} \ No newline at end of file diff --git a/app/javascript/services/currencies_service.js b/app/javascript/services/currencies_service.js new file mode 100644 index 00000000..df70f628 --- /dev/null +++ b/app/javascript/services/currencies_service.js @@ -0,0 +1,5 @@ +export class CurrenciesService { + get(id) { + return fetch(`/currencies/${id}.json`).then((response) => response.json()); + } +} diff --git a/config/importmap.rb b/config/importmap.rb index e1512eb7..7ec2b7f7 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -5,6 +5,7 @@ pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true pin "@hotwired/stimulus", to: "stimulus.min.js" pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" pin_all_from "app/javascript/controllers", under: "controllers" +pin_all_from "app/javascript/services", under: "services", to: "services" pin "@github/hotkey", to: "@github--hotkey.js" # @3.1.0 # Custom namespace for local files diff --git a/config/routes.rb b/config/routes.rb index 5ad51151..dd3407c2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -46,6 +46,8 @@ Rails.application.routes.draw do end end + resources :currencies, only: %i[show] + # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. # Can be used by load balancers and uptime monitors to verify that the app is live. get "up" => "rails/health#show", as: :rails_health_check diff --git a/lib/money/currency.rb b/lib/money/currency.rb index 2069cb65..b35e2b3f 100644 --- a/lib/money/currency.rb +++ b/lib/money/currency.rb @@ -58,6 +58,10 @@ class Money::Currency @default_precision = currency_data["default_precision"] end + def step + (1.0/10**default_precision) + end + def <=>(other) return nil unless other.is_a?(Money::Currency) @iso_code <=> other.iso_code diff --git a/test/controllers/currencies_controller_test.rb b/test/controllers/currencies_controller_test.rb new file mode 100644 index 00000000..fbb36c0c --- /dev/null +++ b/test/controllers/currencies_controller_test.rb @@ -0,0 +1,12 @@ +require "test_helper" + +class CurrenciesControllerTest < ActionDispatch::IntegrationTest + setup do + sign_in @user = users(:family_admin) + end + + test "should show currency" do + get currency_url(id: "EUR", format: :json) + assert_response :success + end +end diff --git a/test/lib/money/currency_test.rb b/test/lib/money/currency_test.rb index e1b35f99..3b94ccc6 100644 --- a/test/lib/money/currency_test.rb +++ b/test/lib/money/currency_test.rb @@ -46,4 +46,8 @@ class Money::CurrencyTest < ActiveSupport::TestCase assert_equal "12", value4.cents_str(2) assert_equal "123", value4.cents_str(3) end + + test "step returns the smallest value of the currency" do + assert_equal 0.01, @currency.step + end end