diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 79a3c248a..c06d90f97 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -45,9 +45,46 @@ module SettingsHelper } end - def settings_section(title:, subtitle: nil, collapsible: false, open: true, auto_open_param: nil, &block) + def settings_section(title:, subtitle: nil, collapsible: false, open: true, auto_open_param: nil, status: nil, meta: nil, &block) content = capture(&block) - render partial: "settings/section", locals: { title: title, subtitle: subtitle, content: content, collapsible: collapsible, open: open, auto_open_param: auto_open_param } + render partial: "settings/section", locals: { title: title, subtitle: subtitle, content: content, collapsible: collapsible, open: open, auto_open_param: auto_open_param, status: status, meta: meta } + end + + def provider_summary(provider_key) + case provider_key.to_s.downcase + when "plaid" + plaid_configured = @provider_configurations&.find { |c| c.provider_key.to_s.casecmp("plaid").zero? }&.configured? + plaid_configured ? { status: :ok } : { status: :off } + when "simplefin" + @simplefin_items&.any? ? { status: :ok } : { status: :off } + when "lunchflow" + @lunchflow_items&.any? ? { status: :ok } : { status: :off } + when "enable_banking" + @enable_banking_items&.any?(&:session_valid?) ? { status: :ok } : { status: :off } + when "coinstats" + @coinstats_items&.any? ? { status: :ok } : { status: :off } + when "mercury" + @mercury_items&.any? ? { status: :ok } : { status: :off } + when "coinbase" + @coinbase_items&.any? ? { status: :ok } : { status: :off } + when "binance" + Current.family.binance_items.active.any? ? { status: :ok } : { status: :off } + when "snaptrade" + configured_item = @snaptrade_items&.find(&:credentials_configured?) + if configured_item&.user_registered? + { status: :ok } + elsif configured_item + { status: :warn } + else + { status: :off } + end + when "indexa_capital" + @indexa_capital_items&.any? ? { status: :ok } : { status: :off } + when "sophtron" + @sophtron_items&.any? ? { status: :ok } : { status: :off } + else + { status: :off } + end end def settings_nav_footer diff --git a/app/views/settings/_section.html.erb b/app/views/settings/_section.html.erb index bb5873839..0f32b3561 100644 --- a/app/views/settings/_section.html.erb +++ b/app/views/settings/_section.html.erb @@ -1,4 +1,4 @@ -<%# locals: (title:, subtitle: nil, content:, collapsible: false, open: true, auto_open_param: nil) %> +<%# locals: (title:, subtitle: nil, content:, collapsible: false, open: true, auto_open_param: nil, status: nil, meta: nil) %> <% if collapsible %>
class="group bg-container shadow-border-xs rounded-xl p-4" @@ -13,6 +13,14 @@ <% end %> + <% if status.present? %> +
+ <% if meta.present? %> + <%= meta %> + <% end %> + <%= render "settings/providers/status_pill", status: status %> +
+ <% end %>
<%= content %> diff --git a/app/views/settings/providers/_status_pill.html.erb b/app/views/settings/providers/_status_pill.html.erb new file mode 100644 index 000000000..7b436bf1f --- /dev/null +++ b/app/views/settings/providers/_status_pill.html.erb @@ -0,0 +1,13 @@ +<%# locals: (status:) %> +<% + dot_class, pill_class = case status.to_sym + when :ok then [ "bg-success", "bg-success/10 text-success" ] + when :warn then [ "bg-warning", "bg-warning/10 text-warning" ] + when :err then [ "bg-destructive", "bg-destructive/10 text-destructive" ] + else [ "bg-gray-400", "bg-gray-100 text-secondary" ] + end +%> + + + <%= t("settings.providers.status.#{status}") %> + diff --git a/app/views/settings/providers/show.html.erb b/app/views/settings/providers/show.html.erb index 9f2b07c09..fd7aebba2 100644 --- a/app/views/settings/providers/show.html.erb +++ b/app/views/settings/providers/show.html.erb @@ -21,7 +21,8 @@ <% unless @encryption_error %> <% @provider_configurations.each do |config| %> - <%= settings_section title: config.provider_key.titleize, collapsible: true, open: false do %> + <% summary = provider_summary(config.provider_key) %> + <%= settings_section title: config.provider_key.titleize, collapsible: true, open: false, status: summary[:status], meta: summary[:meta] do %> <%= render "settings/providers/provider_form", configuration: config %> <% end %> <% end %> @@ -31,64 +32,36 @@ <%# They require custom UI for connection management, status display, and sync actions. %> <%# The controller excludes them from @provider_configurations (see prepare_show_context). %> - <%= settings_section title: "Lunch Flow", collapsible: true, open: false do %> - - <%= render "settings/providers/lunchflow_panel" %> - - <% end %> + <% + family_panels = [ + { key: "lunchflow", title: "Lunch Flow", turbo_id: "lunchflow", partial: "lunchflow_panel" }, + { key: "simplefin", title: "SimpleFIN", turbo_id: "simplefin", partial: "simplefin_panel" }, + { key: "enable_banking", title: "Enable Banking (beta)", turbo_id: "enable_banking", partial: "enable_banking_panel" }, + { key: "coinstats", title: "CoinStats (beta)", turbo_id: "coinstats", partial: "coinstats_panel" }, + { key: "mercury", title: "Mercury (beta)", turbo_id: "mercury", partial: "mercury_panel" }, + { key: "coinbase", title: "Coinbase (beta)", turbo_id: "coinbase", partial: "coinbase_panel" }, + { key: "binance", title: "Binance (beta)", turbo_id: "binance", partial: "binance_panel" }, + { key: "snaptrade", title: "SnapTrade (beta)", turbo_id: "snaptrade", partial: "snaptrade_panel", auto_open: "manage" }, + { key: "indexa_capital", title: "Indexa Capital (alpha)", turbo_id: "indexa_capital", partial: "indexa_capital_panel" }, + { key: "sophtron", title: "Sophtron (alpha)", turbo_id: "sophtron", partial: "sophtron_panel" }, + ] - <%= settings_section title: "SimpleFIN", collapsible: true, open: false do %> - - <%= render "settings/providers/simplefin_panel" %> - - <% end %> + status_order = { ok: 0, warn: 1, err: 2, off: 3 } + sorted_panels = family_panels.sort_by { |p| status_order[provider_summary(p[:key])[:status]] || 3 } + %> - <%= settings_section title: "Enable Banking (beta)", collapsible: true, open: false do %> - - <%= render "settings/providers/enable_banking_panel" %> - - <% end %> - - <%= settings_section title: "CoinStats (beta)", collapsible: true, open: false do %> - - <%= render "settings/providers/coinstats_panel" %> - - <% end %> - - <%= settings_section title: "Mercury (beta)", collapsible: true, open: false do %> - - <%= render "settings/providers/mercury_panel" %> - - <% end %> - - <%= settings_section title: "Coinbase (beta)", collapsible: true, open: false do %> - - <%= render "settings/providers/coinbase_panel" %> - - <% end %> - - <%= settings_section title: "Binance (beta)", collapsible: true, open: false do %> - - <%= render "settings/providers/binance_panel" %> - - <% end %> - - <%= settings_section title: "SnapTrade (beta)", collapsible: true, open: false, auto_open_param: "manage" do %> - - <%= render "settings/providers/snaptrade_panel" %> - - <% end %> - - <%= settings_section title: "Indexa Capital (alpha)", collapsible: true, open: false do %> - - <%= render "settings/providers/indexa_capital_panel" %> - - <% end %> - - <%= settings_section title: "Sophtron (alpha)", collapsible: true, open: false do %> - - <%= render "settings/providers/sophtron_panel" %> - + <% sorted_panels.each do |panel| %> + <% summary = provider_summary(panel[:key]) %> + <%= settings_section title: panel[:title], + collapsible: true, + open: false, + auto_open_param: panel[:auto_open], + status: summary[:status], + meta: summary[:meta] do %> + + <%= render "settings/providers/#{panel[:partial]}" %> + + <% end %> <% end %> <% end %>
diff --git a/config/locales/views/settings/en.yml b/config/locales/views/settings/en.yml index 79e7c69d3..200b07338 100644 --- a/config/locales/views/settings/en.yml +++ b/config/locales/views/settings/en.yml @@ -186,6 +186,11 @@ en: choose_label: (optional) change: Change photo providers: + status: + ok: Connected + warn: Action needed + err: Error + off: Not configured show: coinbase_title: Coinbase encryption_error: diff --git a/test/system/settings/providers_test.rb b/test/system/settings/providers_test.rb new file mode 100644 index 000000000..ffaf4a479 --- /dev/null +++ b/test/system/settings/providers_test.rb @@ -0,0 +1,51 @@ +require "application_system_test_case" + +class Settings::ProvidersTest < ApplicationSystemTestCase + setup do + @user = users(:family_admin) + @family = families(:dylan_family) + login_as @user + end + + test "shows status pill on section header for a configured provider" do + SimplefinItem.create!(family: @family, name: "Test SimpleFIN", access_url: "https://bridge.simplefin.org/simplefin/access") + + visit settings_providers_path + + within("details", text: "SimpleFIN") do + assert_text "Connected" + end + end + + test "shows not configured pill for an unconfigured provider" do + visit settings_providers_path + + within("details", text: "SimpleFIN") do + assert_text "Not configured" + end + end + + test "connected providers render before unconfigured ones" do + SimplefinItem.create!(family: @family, name: "Test SimpleFIN", access_url: "https://bridge.simplefin.org/simplefin/access") + + visit settings_providers_path + + sections = all("details summary").map(&:text) + simplefin_index = sections.index { |t| t.include?("SimpleFIN") } + binance_index = sections.index { |t| t.include?("Binance") } + + assert simplefin_index < binance_index, "Connected SimpleFIN should appear before unconfigured Binance" + end + + test "expanding a section still works as expected" do + visit settings_providers_path + + details = find("details", text: "SimpleFIN") + assert_nil details[:open], "Section should start collapsed" + + details.find("summary").click + + assert details[:open], "Section should open when clicked" + details.assert_text "Setup Token" + end +end