mirror of
https://github.com/we-promise/sure.git
synced 2026-05-21 19:44:55 +00:00
feat(settings/providers): surface connection status in section headers
Lifts the per-panel status indicator up to each collapsed accordion header so admins can see at a glance which providers are connected without expanding every section. Connected providers sort first. - Add optional status: and meta: locals to settings/_section partial; pill hides via group-open:hidden when the section is expanded - New settings/providers/_status_pill partial (ok/warn/err/off states) - Add SettingsHelper#provider_summary to centralise the connected-vs-not logic already scattered across panel partials - Refactor show.html.erb to pass status to every section and sort family_panels by connection state - Add settings.providers.status.* i18n keys - Add system tests asserting pill renders and sort order https://claude.ai/code/session_01KW2HCN9rP1fiyQuw7Cju9D
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 %>
|
||||
<details <%= "open" if open %>
|
||||
class="group bg-container shadow-border-xs rounded-xl p-4"
|
||||
@@ -13,6 +13,14 @@
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% if status.present? %>
|
||||
<div class="flex items-center gap-2 shrink-0 group-open:hidden">
|
||||
<% if meta.present? %>
|
||||
<span class="text-xs text-subdued"><%= meta %></span>
|
||||
<% end %>
|
||||
<%= render "settings/providers/status_pill", status: status %>
|
||||
</div>
|
||||
<% end %>
|
||||
</summary>
|
||||
<div class="space-y-4 mt-4">
|
||||
<%= content %>
|
||||
|
||||
13
app/views/settings/providers/_status_pill.html.erb
Normal file
13
app/views/settings/providers/_status_pill.html.erb
Normal file
@@ -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
|
||||
%>
|
||||
<span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium <%= pill_class %>">
|
||||
<span class="w-1.5 h-1.5 rounded-full flex-shrink-0 <%= dot_class %>"></span>
|
||||
<%= t("settings.providers.status.#{status}") %>
|
||||
</span>
|
||||
@@ -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 %>
|
||||
<turbo-frame id="lunchflow-providers-panel">
|
||||
<%= render "settings/providers/lunchflow_panel" %>
|
||||
</turbo-frame>
|
||||
<% 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 %>
|
||||
<turbo-frame id="simplefin-providers-panel">
|
||||
<%= render "settings/providers/simplefin_panel" %>
|
||||
</turbo-frame>
|
||||
<% 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 %>
|
||||
<turbo-frame id="enable_banking-providers-panel">
|
||||
<%= render "settings/providers/enable_banking_panel" %>
|
||||
</turbo-frame>
|
||||
<% end %>
|
||||
|
||||
<%= settings_section title: "CoinStats (beta)", collapsible: true, open: false do %>
|
||||
<turbo-frame id="coinstats-providers-panel">
|
||||
<%= render "settings/providers/coinstats_panel" %>
|
||||
</turbo-frame>
|
||||
<% end %>
|
||||
|
||||
<%= settings_section title: "Mercury (beta)", collapsible: true, open: false do %>
|
||||
<turbo-frame id="mercury-providers-panel">
|
||||
<%= render "settings/providers/mercury_panel" %>
|
||||
</turbo-frame>
|
||||
<% end %>
|
||||
|
||||
<%= settings_section title: "Coinbase (beta)", collapsible: true, open: false do %>
|
||||
<turbo-frame id="coinbase-providers-panel">
|
||||
<%= render "settings/providers/coinbase_panel" %>
|
||||
</turbo-frame>
|
||||
<% end %>
|
||||
|
||||
<%= settings_section title: "Binance (beta)", collapsible: true, open: false do %>
|
||||
<turbo-frame id="binance-providers-panel">
|
||||
<%= render "settings/providers/binance_panel" %>
|
||||
</turbo-frame>
|
||||
<% end %>
|
||||
|
||||
<%= settings_section title: "SnapTrade (beta)", collapsible: true, open: false, auto_open_param: "manage" do %>
|
||||
<turbo-frame id="snaptrade-providers-panel">
|
||||
<%= render "settings/providers/snaptrade_panel" %>
|
||||
</turbo-frame>
|
||||
<% end %>
|
||||
|
||||
<%= settings_section title: "Indexa Capital (alpha)", collapsible: true, open: false do %>
|
||||
<turbo-frame id="indexa_capital-providers-panel">
|
||||
<%= render "settings/providers/indexa_capital_panel" %>
|
||||
</turbo-frame>
|
||||
<% end %>
|
||||
|
||||
<%= settings_section title: "Sophtron (alpha)", collapsible: true, open: false do %>
|
||||
<turbo-frame id="sophtron-providers-panel">
|
||||
<%= render "settings/providers/sophtron_panel" %>
|
||||
</turbo-frame>
|
||||
<% 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 %>
|
||||
<turbo-frame id="<%= panel[:turbo_id] %>-providers-panel">
|
||||
<%= render "settings/providers/#{panel[:partial]}" %>
|
||||
</turbo-frame>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -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:
|
||||
|
||||
51
test/system/settings/providers_test.rb
Normal file
51
test/system/settings/providers_test.rb
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user