diff --git a/app/components/DS/disclosure.html.erb b/app/components/DS/disclosure.html.erb index 8253491bb..6923183b5 100644 --- a/app/components/DS/disclosure.html.erb +++ b/app/components/DS/disclosure.html.erb @@ -1,10 +1,13 @@ -
> - <%= tag.summary class: class_names( - "px-3 py-2 rounded-xl cursor-pointer flex items-center justify-between bg-surface", - "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-900 theme-dark:focus-visible:outline-white" - ) do %> +
> + <%= tag.summary class: summary_classes do %> <% if summary_content? %> - <%= summary_content %> + <%# `` is `display: list-item`, so a flex inner div would + shrink-wrap to content width and any `justify-between` inside + the slot has nothing to distribute. Wrap in a `w-full` block + so caller-supplied flex rows stretch across the card. %> +
+ <%= summary_content %> +
<% else %>
<% if align == :left %> diff --git a/app/components/DS/disclosure.rb b/app/components/DS/disclosure.rb index d301d671e..a634b2143 100644 --- a/app/components/DS/disclosure.rb +++ b/app/components/DS/disclosure.rb @@ -1,12 +1,48 @@ class DS::Disclosure < DesignSystemComponent renders_one :summary_content - attr_reader :title, :align, :open, :opts + VARIANTS = %i[default card].freeze - def initialize(title: nil, align: "right", open: false, **opts) + attr_reader :title, :align, :open, :variant, :opts + + # `:default` — bg-surface summary, no chrome on the `
`. Use + # for inline expanders inside a parent card. + # + # `:card` — `
` itself becomes a `bg-container shadow-border-xs + # rounded-xl` card; the summary inherits the container (no own bg). + # Use for provider-item rows (binance, lunchflow, plaid, etc.) where + # each card is the surface and the summary is custom rich content. + # Callers in `:card` mode should pass their own `summary_content` + # slot; the built-in title rendering assumes the `:default` shape. + def initialize(title: nil, align: "right", open: false, variant: :default, **opts) @title = title @align = align.to_sym @open = open + @variant = variant.to_sym @opts = opts + + raise ArgumentError, "Invalid variant: #{@variant}. Must be one of #{VARIANTS.inspect}" unless VARIANTS.include?(@variant) + end + + def details_classes + case variant + when :card + "group bg-container p-4 shadow-border-xs rounded-xl" + else + "group" + end + end + + def summary_classes + case variant + when :card + # Card variant: no bg on summary — the parent details *is* the + # surface. Keep cursor + focus-visible ring + flex baseline. + # Ring token matches `settings/provider_card.html.erb` (the + # established focus pattern on container cards). + "list-none cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-alpha-black-300 rounded-xl" + else + "px-3 py-2 rounded-xl cursor-pointer flex items-center justify-between bg-surface focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-alpha-black-300" + end end end diff --git a/app/components/DS/search_input.html.erb b/app/components/DS/search_input.html.erb new file mode 100644 index 000000000..e79fd6715 --- /dev/null +++ b/app/components/DS/search_input.html.erb @@ -0,0 +1,17 @@ +<%= tag.div class: container_classes do %> + <%= tag.input type: "search", + name: name, + value: value, + placeholder: placeholder, + "aria-label": aria_label, + autocomplete: "off", + class: input_classes, + **opts %> + <% if variant == :embedded %> + <%= helpers.icon("search", class: "absolute inset-0 ml-2 transform top-1/2 -translate-y-1/2") %> + <% else %> +
+ <%= helpers.icon("search", class: "text-secondary") %> +
+ <% end %> +<% end %> diff --git a/app/components/DS/search_input.rb b/app/components/DS/search_input.rb new file mode 100644 index 000000000..51c3dc9ca --- /dev/null +++ b/app/components/DS/search_input.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +# `DS::SearchInput` is the search-field primitive. +# +# Two variants: +# +# - `:standalone` (default) — top-of-list filter inputs (Preferences +# currency search, Settings/Bank Sync provider filter). Bordered +# bg-container surface, icon-on-left, full token-backed focus ring. +# +# - `:embedded` — search-inside-a-panel (DS::Select internal search, +# splits category filter, any future DS::Popover that hosts a filter). +# No border / no own focus ring — the parent panel provides the +# chrome, so adding ring + outline here would compete with the +# parent's focus-within state. +# +# For `form.search_field :foo` inside a `styled_form_with` block, +# keep using the form helper — it routes through `StyledFormBuilder`'s +# form-field CSS, which is a different visual contract. +class DS::SearchInput < DesignSystemComponent + VARIANTS = %i[standalone embedded].freeze + + attr_reader :variant, :name, :placeholder, :value, :aria_label, :extra_classes, :opts + + def initialize(variant: :standalone, name: nil, placeholder: nil, value: nil, aria_label: nil, class: nil, **opts) + @variant = variant.to_sym + @name = name + @placeholder = placeholder + @value = value + @aria_label = aria_label || placeholder + @extra_classes = binding.local_variable_get(:class) + @opts = opts + + raise ArgumentError, "Invalid variant: #{@variant}. Must be one of #{VARIANTS.inspect}" unless VARIANTS.include?(@variant) + end + + def container_classes + class_names("relative", extra_classes) + end + + def input_classes + # `text-base sm:text-sm` — keep the base font at 16px so iOS Safari + # does not zoom the viewport when the input is focused. Shrink to + # 14px from `sm:` upward. The previous unconditional `text-sm` + # triggered the mobile zoom regression. + case variant + when :embedded + # No own focus ring — the parent panel handles focus chrome via + # `focus-within`. `focus:outline-hidden focus:ring-0` neutralizes + # the browser default so it doesn't compete with the panel's + # state. + "bg-container text-primary text-base sm:text-sm placeholder:text-secondary font-normal " \ + "h-10 pl-10 w-full border-none rounded-lg " \ + "focus:outline-hidden focus:ring-0" + else + # `focus-visible:outline-*` matches the focus-ring pattern from + # DS::Button (base.css) so every interactive surface in the design + # system uses the same ring token. Replaces the broken + # `focus:ring-gray-500` from the inline callsites — that utility + # had no backing token and rendered invisibly on the bordered + # bg-container surface. + "block w-full border border-secondary rounded-md py-2.5 pl-10 pr-3 bg-container text-base sm:text-sm " \ + "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-900 " \ + "theme-dark:focus-visible:outline-white" + end + end + + def icon_classes + variant == :embedded ? "absolute inset-0 ml-2 transform top-1/2 -translate-y-1/2" : "text-secondary" + end + + def icon_wrapper_classes + # Standalone variant wraps the icon in a positioned div; embedded + # places the icon as an absolutely-positioned sibling so the parent + # panel can stay in control of vertical alignment. + variant == :embedded ? nil : "absolute inset-0 ml-2 top-1/2 -translate-y-1/2 pointer-events-none" + end +end diff --git a/app/components/DS/select.html.erb b/app/components/DS/select.html.erb index 6c06d83f1..1ac9b027f 100644 --- a/app/components/DS/select.html.erb +++ b/app/components/DS/select.html.erb @@ -33,15 +33,15 @@
+ <% end %> <% end %> diff --git a/app/views/coinstats_items/_coinstats_item.html.erb b/app/views/coinstats_items/_coinstats_item.html.erb index ba8b90b2d..aae9b65ef 100644 --- a/app/views/coinstats_items/_coinstats_item.html.erb +++ b/app/views/coinstats_items/_coinstats_item.html.erb @@ -1,82 +1,84 @@ <%# locals: (coinstats_item:) %> <%= tag.div id: dom_id(coinstats_item) do %> -
- -
- <%= icon "chevron-right", class: "group-open:transform group-open:rotate-90" %> + <%= render DS::Disclosure.new(variant: :card, open: true) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+
+ <%= icon "chevron-right", class: "group-open:rotate-90 motion-safe:transition-transform motion-safe:duration-150" %> -
-
- <%= tag.p coinstats_item.institution_display_name.first.upcase, class: "text-success text-xs font-medium" %> +
+
+ <%= tag.p coinstats_item.institution_display_name.first.upcase, class: "text-success text-xs font-medium" %> +
-
-
-
- <%= tag.p coinstats_item.institution_display_name, class: "font-medium text-primary" %> - <% if coinstats_item.scheduled_for_deletion? %> -

<%= t(".deletion_in_progress") %>

+
+
+ <%= tag.p coinstats_item.institution_display_name, class: "font-medium text-primary" %> + <% if coinstats_item.scheduled_for_deletion? %> +

<%= t(".deletion_in_progress") %>

+ <% end %> +
+

<%= t(".provider_name") %>

+ <% if coinstats_item.syncing? %> +
+ <%= icon "loader", size: "sm", class: "animate-spin" %> + <%= tag.span t(".syncing") %> +
+ <% elsif coinstats_item.requires_update? %> +
+ <%= icon "alert-triangle", size: "sm", color: "warning" %> + <%= tag.span t(".reconnect") %> +
+ <% else %> +

+ <% if coinstats_item.last_synced_at %> + <% if coinstats_item.sync_status_summary %> + <%= t(".status_with_summary", timestamp: time_ago_in_words(coinstats_item.last_synced_at), summary: coinstats_item.sync_status_summary) %> + <% else %> + <%= t(".status", timestamp: time_ago_in_words(coinstats_item.last_synced_at)) %> + <% end %> + <% else %> + <%= t(".status_never") %> + <% end %> +

<% end %>
-

<%= t(".provider_name") %>

- <% if coinstats_item.syncing? %> -
- <%= icon "loader", size: "sm", class: "animate-spin" %> - <%= tag.span t(".syncing") %> -
- <% elsif coinstats_item.requires_update? %> -
- <%= icon "alert-triangle", size: "sm", color: "warning" %> - <%= tag.span t(".reconnect") %> -
- <% else %> -

- <% if coinstats_item.last_synced_at %> - <% if coinstats_item.sync_status_summary %> - <%= t(".status_with_summary", timestamp: time_ago_in_words(coinstats_item.last_synced_at), summary: coinstats_item.sync_status_summary) %> - <% else %> - <%= t(".status", timestamp: time_ago_in_words(coinstats_item.last_synced_at)) %> - <% end %> - <% else %> - <%= t(".status_never") %> - <% end %> -

- <% end %>
+ + <% if Current.user&.admin? %> +
+ <% if coinstats_item.requires_update? %> + <%= render DS::Link.new( + text: t(".update_api_key"), + icon: "refresh-cw", + variant: "secondary", + href: settings_providers_path, + frame: "_top" + ) %> + <% elsif Rails.env.development? %> + <%= icon( + "refresh-cw", + as_button: true, + href: sync_coinstats_item_path(coinstats_item) + ) %> + <% end %> + + <%= render DS::Menu.new do |menu| %> + <% menu.with_item( + variant: "button", + text: t(".delete"), + icon: "trash-2", + href: coinstats_item_path(coinstats_item), + method: :delete, + confirm: CustomConfirm.for_resource_deletion(coinstats_item.institution_display_name, high_severity: true) + ) %> + <% end %> +
+ <% end %>
- - <% if Current.user&.admin? %> -
- <% if coinstats_item.requires_update? %> - <%= render DS::Link.new( - text: t(".update_api_key"), - icon: "refresh-cw", - variant: "secondary", - href: settings_providers_path, - frame: "_top" - ) %> - <% elsif Rails.env.development? %> - <%= icon( - "refresh-cw", - as_button: true, - href: sync_coinstats_item_path(coinstats_item) - ) %> - <% end %> - - <%= render DS::Menu.new do |menu| %> - <% menu.with_item( - variant: "button", - text: t(".delete"), - icon: "trash-2", - href: coinstats_item_path(coinstats_item), - method: :delete, - confirm: CustomConfirm.for_resource_deletion(coinstats_item.institution_display_name, high_severity: true) - ) %> - <% end %> -
- <% end %> -
+ <% end %> <% unless coinstats_item.scheduled_for_deletion? %>
@@ -103,5 +105,5 @@ <% end %>
<% end %> -
+ <% end %> <% end %> diff --git a/app/views/enable_banking_items/_enable_banking_item.html.erb b/app/views/enable_banking_items/_enable_banking_item.html.erb index 3c31e8f6e..a63eaa478 100644 --- a/app/views/enable_banking_items/_enable_banking_item.html.erb +++ b/app/views/enable_banking_items/_enable_banking_item.html.erb @@ -1,85 +1,87 @@ <%# locals: (enable_banking_item:) %> <%= tag.div id: dom_id(enable_banking_item) do %> -
- -
- <%= icon "chevron-right", class: "group-open:transform group-open:rotate-90" %> + <%= render DS::Disclosure.new(variant: :card, open: true) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+
+ <%= icon "chevron-right", class: "group-open:rotate-90 motion-safe:transition-transform motion-safe:duration-150" %> -
- <% if enable_banking_item.logo.attached? %> - <%= image_tag enable_banking_item.logo, class: "rounded-full h-full w-full", loading: "lazy" %> - <% else %> -
- <%= tag.p enable_banking_item.institution_display_name.first.upcase, class: "text-success text-xs font-medium" %> -
- <% end %> -
- -
-
- <%= tag.p enable_banking_item.institution_display_name, class: "font-medium text-primary" %> - <% if enable_banking_item.scheduled_for_deletion? %> -

<%= t(".deletion_in_progress") %>

+
+ <% if enable_banking_item.logo.attached? %> + <%= image_tag enable_banking_item.logo, class: "rounded-full h-full w-full", loading: "lazy" %> + <% else %> +
+ <%= tag.p enable_banking_item.institution_display_name.first.upcase, class: "text-success text-xs font-medium" %> +
<% end %>
-

<%= t(".provider_name") %>

- <% if enable_banking_item.syncing? %> -
- <%= icon "loader", size: "sm", class: "animate-spin" %> - <%= tag.span t(".syncing") %> -
- <% elsif enable_banking_item.requires_update? %> -
- <%= icon "alert-triangle", size: "sm", color: "warning" %> - <%= tag.span t(".reconnect") %> -
- <% else %> -

- <% if enable_banking_item.last_synced_at %> - <%= t(".last_synced", time: time_ago_in_words(enable_banking_item.last_synced_at)) %> - <% if enable_banking_item.sync_status_summary %> - · <%= enable_banking_item.sync_status_summary %> - <% end %> - <% else %> - <%= t(".never_synced") %> + +

+
+ <%= tag.p enable_banking_item.institution_display_name, class: "font-medium text-primary" %> + <% if enable_banking_item.scheduled_for_deletion? %> +

<%= t(".deletion_in_progress") %>

<% end %> -

- <% end %> -
-
- - <% if Current.user&.admin? %> -
- <% if enable_banking_item.requires_update? %> - <%= button_to reauthorize_enable_banking_item_path(enable_banking_item), - method: :post, - class: "inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-lg text-white bg-warning hover:opacity-90 transition-colors", - data: { turbo: false } do %> - <%= icon "refresh-cw", size: "sm" %> - <%= t(".update") %> +
+

<%= t(".provider_name") %>

+ <% if enable_banking_item.syncing? %> +
+ <%= icon "loader", size: "sm", class: "animate-spin" %> + <%= tag.span t(".syncing") %> +
+ <% elsif enable_banking_item.requires_update? %> +
+ <%= icon "alert-triangle", size: "sm", color: "warning" %> + <%= tag.span t(".reconnect") %> +
+ <% else %> +

+ <% if enable_banking_item.last_synced_at %> + <%= t(".last_synced", time: time_ago_in_words(enable_banking_item.last_synced_at)) %> + <% if enable_banking_item.sync_status_summary %> + · <%= enable_banking_item.sync_status_summary %> + <% end %> + <% else %> + <%= t(".never_synced") %> + <% end %> +

<% end %> - <% elsif Rails.env.development? %> - <%= icon( - "refresh-cw", - as_button: true, - href: sync_enable_banking_item_path(enable_banking_item) - ) %> - <% end %> - - <%= render DS::Menu.new do |menu| %> - <% menu.with_item( - variant: "button", - text: t(".delete"), - icon: "trash-2", - href: enable_banking_item_path(enable_banking_item), - method: :delete, - confirm: CustomConfirm.for_resource_deletion(enable_banking_item.institution_display_name, high_severity: true) - ) %> - <% end %> +
- <% end %> -
+ + <% if Current.user&.admin? %> +
+ <% if enable_banking_item.requires_update? %> + <%= button_to reauthorize_enable_banking_item_path(enable_banking_item), + method: :post, + class: "inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-lg text-white bg-warning hover:opacity-90 transition-colors", + data: { turbo: false } do %> + <%= icon "refresh-cw", size: "sm" %> + <%= t(".update") %> + <% end %> + <% elsif Rails.env.development? %> + <%= icon( + "refresh-cw", + as_button: true, + href: sync_enable_banking_item_path(enable_banking_item) + ) %> + <% end %> + + <%= render DS::Menu.new do |menu| %> + <% menu.with_item( + variant: "button", + text: t(".delete"), + icon: "trash-2", + href: enable_banking_item_path(enable_banking_item), + method: :delete, + confirm: CustomConfirm.for_resource_deletion(enable_banking_item.institution_display_name, high_severity: true) + ) %> + <% end %> +
+ <% end %> + + <% end %> <% unless enable_banking_item.scheduled_for_deletion? %>
@@ -127,5 +129,5 @@ <% end %>
<% end %> -
+ <% end %> <% end %> diff --git a/app/views/ibkr_items/_ibkr_item.html.erb b/app/views/ibkr_items/_ibkr_item.html.erb index 905eda0dd..f65e9b411 100644 --- a/app/views/ibkr_items/_ibkr_item.html.erb +++ b/app/views/ibkr_items/_ibkr_item.html.erb @@ -3,81 +3,83 @@ <%= tag.div id: dom_id(ibkr_item) do %> <% unlinked_count = ibkr_item.unlinked_accounts_count %> -
- -
- <%= icon "chevron-right", class: "group-open:transform group-open:rotate-90" %> + <%= render DS::Disclosure.new(variant: :card, open: true) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+
+ <%= icon "chevron-right", class: "group-open:rotate-90 motion-safe:transition-transform motion-safe:duration-150" %> -
- IB -
+
+ IB +
-
-
- <%= tag.p ibkr_item.institution_display_name, class: "font-medium text-primary" %> - <% if ibkr_item.scheduled_for_deletion? %> -

<%= t(".deletion_in_progress") %>

+
+
+ <%= tag.p ibkr_item.institution_display_name, class: "font-medium text-primary" %> + <% if ibkr_item.scheduled_for_deletion? %> +

<%= t(".deletion_in_progress") %>

+ <% end %> +
+

<%= t(".flex_web_service") %>

+ <% if ibkr_item.syncing? %> +
+ <%= icon "loader", size: "sm", class: "animate-spin" %> + <%= tag.span t(".syncing") %> +
+ <% elsif ibkr_item.requires_update? %> +
+ <%= icon "alert-triangle", size: "sm", color: "warning" %> + <%= tag.span t(".requires_update") %> +
+ <% elsif ibkr_item.sync_error.present? %> +
+ <%= render DS::Tooltip.new(text: ibkr_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %> + <%= tag.span t(".error"), class: "text-destructive" %> +
+ <% else %> +

+ <% if ibkr_item.last_synced_at %> + <%= t(".synced", time: time_ago_in_words(ibkr_item.last_synced_at), summary: ibkr_item.sync_status_summary) %> + <% else %> + <%= t(".never_synced") %> + <% end %> +

<% end %>
-

<%= t(".flex_web_service") %>

- <% if ibkr_item.syncing? %> -
- <%= icon "loader", size: "sm", class: "animate-spin" %> - <%= tag.span t(".syncing") %> -
- <% elsif ibkr_item.requires_update? %> -
- <%= icon "alert-triangle", size: "sm", color: "warning" %> - <%= tag.span t(".requires_update") %> -
- <% elsif ibkr_item.sync_error.present? %> -
- <%= render DS::Tooltip.new(text: ibkr_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %> - <%= tag.span t(".error"), class: "text-destructive" %> -
- <% else %> -

- <% if ibkr_item.last_synced_at %> - <%= t(".synced", time: time_ago_in_words(ibkr_item.last_synced_at), summary: ibkr_item.sync_status_summary) %> - <% else %> - <%= t(".never_synced") %> - <% end %> -

- <% end %>
-
- <% if Current.user&.admin? %> -
- <%= icon( - "refresh-cw", - as_button: true, - href: sync_ibkr_item_path(ibkr_item), - disabled: ibkr_item.syncing? - ) %> + <% if Current.user&.admin? %> +
+ <%= icon( + "refresh-cw", + as_button: true, + href: sync_ibkr_item_path(ibkr_item), + disabled: ibkr_item.syncing? + ) %> - <%= render DS::Menu.new do |menu| %> - <% if unlinked_count > 0 %> + <%= render DS::Menu.new do |menu| %> + <% if unlinked_count > 0 %> + <% menu.with_item( + variant: "link", + text: t(".setup_accounts"), + icon: "settings", + href: setup_accounts_ibkr_item_path(ibkr_item), + frame: :modal + ) %> + <% end %> <% menu.with_item( - variant: "link", - text: t(".setup_accounts"), - icon: "settings", - href: setup_accounts_ibkr_item_path(ibkr_item), - frame: :modal + variant: "button", + text: t(".delete"), + icon: "trash-2", + href: ibkr_item_path(ibkr_item), + method: :delete, + confirm: CustomConfirm.for_resource_deletion(ibkr_item.institution_display_name, high_severity: true) ) %> <% end %> - <% menu.with_item( - variant: "button", - text: t(".delete"), - icon: "trash-2", - href: ibkr_item_path(ibkr_item), - method: :delete, - confirm: CustomConfirm.for_resource_deletion(ibkr_item.institution_display_name, high_severity: true) - ) %> - <% end %> -
- <% end %> -
+ + <% end %> + + <% end %> <% unless ibkr_item.scheduled_for_deletion? %>
@@ -110,5 +112,5 @@ <% end %>
<% end %> -
+ <% end %> <% end %> diff --git a/app/views/indexa_capital_items/_indexa_capital_item.html.erb b/app/views/indexa_capital_items/_indexa_capital_item.html.erb index 7f90af7bd..e6535d76a 100644 --- a/app/views/indexa_capital_items/_indexa_capital_item.html.erb +++ b/app/views/indexa_capital_items/_indexa_capital_item.html.erb @@ -1,101 +1,103 @@ <%# locals: (indexa_capital_item:) %> <%= tag.div id: dom_id(indexa_capital_item) do %> -
- -
- <%= icon "chevron-right", class: "group-open:transform group-open:rotate-90" %> + <% unlinked_count = indexa_capital_item.unlinked_accounts_count %> -
-
- <%= tag.p indexa_capital_item.name.first.upcase, class: "text-primary text-xs font-medium" %> + <%= render DS::Disclosure.new(variant: :card, open: true) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+
+ <%= icon "chevron-right", class: "group-open:rotate-90 motion-safe:transition-transform motion-safe:duration-150" %> + +
+
+ <%= tag.p indexa_capital_item.name.first.upcase, class: "text-primary text-xs font-medium" %> +
-
- <% unlinked_count = indexa_capital_item.unlinked_accounts_count %> - -
-
- <%= tag.p indexa_capital_item.name, class: "font-medium text-primary" %> - <% if indexa_capital_item.scheduled_for_deletion? %> -

<%= t(".deletion_in_progress") %>

+
+
+ <%= tag.p indexa_capital_item.name, class: "font-medium text-primary" %> + <% if indexa_capital_item.scheduled_for_deletion? %> +

<%= t(".deletion_in_progress") %>

+ <% end %> +
+

<%= t(".provider_name") %>

+ <% if indexa_capital_item.syncing? %> +
+ <%= icon "loader", size: "sm", class: "animate-spin" %> + <%= tag.span t(".syncing") %> +
+ <% elsif indexa_capital_item.requires_update? %> +
+ <%= icon "alert-triangle", size: "sm", color: "warning" %> + <%= tag.span t(".requires_update") %> +
+ <% elsif indexa_capital_item.sync_error.present? %> +
+ <%= render DS::Tooltip.new(text: indexa_capital_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %> + <%= tag.span t(".error"), class: "text-destructive" %> +
+ <% else %> +

+ <% if indexa_capital_item.last_synced_at %> + <%= t(".status", timestamp: time_ago_in_words(indexa_capital_item.last_synced_at), summary: indexa_capital_item.sync_status_summary) %> + <% else %> + <%= t(".status_never") %> + <% end %> +

<% end %>
-

<%= t(".provider_name") %>

- <% if indexa_capital_item.syncing? %> -
- <%= icon "loader", size: "sm", class: "animate-spin" %> - <%= tag.span t(".syncing") %> -
- <% elsif indexa_capital_item.requires_update? %> -
- <%= icon "alert-triangle", size: "sm", color: "warning" %> - <%= tag.span t(".requires_update") %> -
- <% elsif indexa_capital_item.sync_error.present? %> -
- <%= render DS::Tooltip.new(text: indexa_capital_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %> - <%= tag.span t(".error"), class: "text-destructive" %> -
- <% else %> -

- <% if indexa_capital_item.last_synced_at %> - <%= t(".status", timestamp: time_ago_in_words(indexa_capital_item.last_synced_at), summary: indexa_capital_item.sync_status_summary) %> - <% else %> - <%= t(".status_never") %> - <% end %> -

- <% end %>
-
- <% if Current.user&.admin? %> -
- <% if indexa_capital_item.requires_update? %> - <%= render DS::Link.new( - text: t(".update_credentials"), - icon: "refresh-cw", - variant: "secondary", - href: settings_providers_path, - frame: "_top" - ) %> - <% else %> - <%= icon( - "refresh-cw", - as_button: true, - href: sync_indexa_capital_item_path(indexa_capital_item), - disabled: indexa_capital_item.syncing? - ) %> - <% end %> - - <%= render DS::Menu.new do |menu| %> - <% if unlinked_count > 0 %> - <% menu.with_item( - variant: "link", - text: t(".setup_action"), - icon: "settings", - href: setup_accounts_indexa_capital_item_path(indexa_capital_item), - frame: :modal + <% if Current.user&.admin? %> +
+ <% if indexa_capital_item.requires_update? %> + <%= render DS::Link.new( + text: t(".update_credentials"), + icon: "refresh-cw", + variant: "secondary", + href: settings_providers_path, + frame: "_top" + ) %> + <% else %> + <%= icon( + "refresh-cw", + as_button: true, + href: sync_indexa_capital_item_path(indexa_capital_item), + disabled: indexa_capital_item.syncing? ) %> <% end %> - <% menu.with_item( - variant: "link", - text: t(".update_credentials"), - icon: "cable", - href: settings_providers_path(manage: "1") - ) %> - <% menu.with_item( - variant: "button", - text: t(".delete"), - icon: "trash-2", - href: indexa_capital_item_path(indexa_capital_item), - method: :delete, - confirm: CustomConfirm.for_resource_deletion(indexa_capital_item.name, high_severity: true) - ) %> - <% end %> -
- <% end %> -
+ + <%= render DS::Menu.new do |menu| %> + <% if unlinked_count > 0 %> + <% menu.with_item( + variant: "link", + text: t(".setup_action"), + icon: "settings", + href: setup_accounts_indexa_capital_item_path(indexa_capital_item), + frame: :modal + ) %> + <% end %> + <% menu.with_item( + variant: "link", + text: t(".update_credentials"), + icon: "cable", + href: settings_providers_path(manage: "1") + ) %> + <% menu.with_item( + variant: "button", + text: t(".delete"), + icon: "trash-2", + href: indexa_capital_item_path(indexa_capital_item), + method: :delete, + confirm: CustomConfirm.for_resource_deletion(indexa_capital_item.name, high_severity: true) + ) %> + <% end %> + + <% end %> + + <% end %> <% unless indexa_capital_item.scheduled_for_deletion? %>
@@ -144,5 +146,5 @@ <% end %>
<% end %> -
+ <% end %> <% end %> diff --git a/app/views/kraken_items/_kraken_item.html.erb b/app/views/kraken_items/_kraken_item.html.erb index 590d900fa..784ee1715 100644 --- a/app/views/kraken_items/_kraken_item.html.erb +++ b/app/views/kraken_items/_kraken_item.html.erb @@ -1,48 +1,50 @@ <%# locals: (kraken_item:, unlinked_count: kraken_item.unlinked_accounts_count) %> <%= tag.div id: dom_id(kraken_item) do %> -
- - <%= icon "chevron-right", class: "group-open:transform group-open:rotate-90" %> + <%= render DS::Disclosure.new(variant: :card, open: true) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+ <%= icon "chevron-right", class: "group-open:rotate-90 motion-safe:transition-transform motion-safe:duration-150" %> -
- <%= icon "waves", size: "sm", class: "text-primary" %> -
+
+ <%= icon "waves", size: "sm", class: "text-primary" %> +
-
-
- <%= tag.p kraken_item.institution_display_name, class: "font-medium text-primary" %> - <% if kraken_item.scheduled_for_deletion? %> -

<%= t(".deletion_in_progress") %>

+
+
+ <%= tag.p kraken_item.institution_display_name, class: "font-medium text-primary" %> + <% if kraken_item.scheduled_for_deletion? %> +

<%= t(".deletion_in_progress") %>

+ <% end %> +
+

<%= t(".provider_name") %>

+ + <% if kraken_item.syncing? %> +
+ <%= icon "loader", size: "sm", class: "animate-spin" %> + <%= tag.span t(".syncing") %> +
+ <% elsif kraken_item.requires_update? %> +
+ <%= icon "alert-triangle", size: "sm", color: "warning" %> + <%= tag.span t(".reconnect") %> +
+ <% else %> +

+ <% if kraken_item.last_synced_at %> + <% if kraken_item.sync_status_summary %> + <%= t(".status_with_summary", timestamp: time_ago_in_words(kraken_item.last_synced_at), summary: kraken_item.sync_status_summary) %> + <% else %> + <%= t(".status", timestamp: time_ago_in_words(kraken_item.last_synced_at)) %> + <% end %> + <% else %> + <%= t(".status_never") %> + <% end %> +

<% end %>
-

<%= t(".provider_name") %>

- - <% if kraken_item.syncing? %> -
- <%= icon "loader", size: "sm", class: "animate-spin" %> - <%= tag.span t(".syncing") %> -
- <% elsif kraken_item.requires_update? %> -
- <%= icon "alert-triangle", size: "sm", color: "warning" %> - <%= tag.span t(".reconnect") %> -
- <% else %> -

- <% if kraken_item.last_synced_at %> - <% if kraken_item.sync_status_summary %> - <%= t(".status_with_summary", timestamp: time_ago_in_words(kraken_item.last_synced_at), summary: kraken_item.sync_status_summary) %> - <% else %> - <%= t(".status", timestamp: time_ago_in_words(kraken_item.last_synced_at)) %> - <% end %> - <% else %> - <%= t(".status_never") %> - <% end %> -

- <% end %>
-
+ <% end %> <% if Current.user&.admin? %>
@@ -111,5 +113,5 @@ <% end %>
<% end %> -
+ <% end %> <% end %> diff --git a/app/views/lunchflow_items/_lunchflow_item.html.erb b/app/views/lunchflow_items/_lunchflow_item.html.erb index 0b0fecc76..a4db679b2 100644 --- a/app/views/lunchflow_items/_lunchflow_item.html.erb +++ b/app/views/lunchflow_items/_lunchflow_item.html.erb @@ -1,82 +1,84 @@ <%# locals: (lunchflow_item:) %> <%= tag.div id: dom_id(lunchflow_item) do %> -
- -
- <%= icon "chevron-right", class: "group-open:transform group-open:rotate-90" %> + <%= render DS::Disclosure.new(variant: :card, open: true) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+
+ <%= icon "chevron-right", class: "group-open:rotate-90 motion-safe:transition-transform motion-safe:duration-150" %> -
- <% if lunchflow_item.logo.attached? %> - <%= image_tag lunchflow_item.logo, class: "rounded-full h-full w-full", loading: "lazy" %> - <% else %> -
- <%= tag.p lunchflow_item.name.first.upcase, class: "text-orange-600 text-xs font-medium" %> -
- <% end %> -
- -
-
- <%= tag.p lunchflow_item.name, class: "font-medium text-primary" %> - <% if lunchflow_item.scheduled_for_deletion? %> -

<%= t(".deletion_in_progress") %>

+
+ <% if lunchflow_item.logo.attached? %> + <%= image_tag lunchflow_item.logo, class: "rounded-full h-full w-full", loading: "lazy" %> + <% else %> +
+ <%= tag.p lunchflow_item.name.first.upcase, class: "text-orange-600 text-xs font-medium" %> +
<% end %>
- <% if lunchflow_item.accounts.any? %> -

- <%= lunchflow_item.institution_summary %> -

- <% end %> - <% if lunchflow_item.syncing? %> -
- <%= icon "loader", size: "sm", class: "animate-spin" %> - <%= tag.span t(".syncing") %> -
- <% elsif lunchflow_item.sync_error.present? %> -
- <%= render DS::Tooltip.new(text: lunchflow_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %> - <%= tag.span t(".error"), class: "text-destructive" %> -
- <% else %> -

- <% if lunchflow_item.last_synced_at %> - <% if lunchflow_item.sync_status_summary %> - <%= t(".status_with_summary", timestamp: time_ago_in_words(lunchflow_item.last_synced_at), summary: lunchflow_item.sync_status_summary) %> - <% else %> - <%= t(".status", timestamp: time_ago_in_words(lunchflow_item.last_synced_at)) %> - <% end %> - <% else %> - <%= t(".status_never") %> + +

+
+ <%= tag.p lunchflow_item.name, class: "font-medium text-primary" %> + <% if lunchflow_item.scheduled_for_deletion? %> +

<%= t(".deletion_in_progress") %>

<% end %> -

- <% end %> +
+ <% if lunchflow_item.accounts.any? %> +

+ <%= lunchflow_item.institution_summary %> +

+ <% end %> + <% if lunchflow_item.syncing? %> +
+ <%= icon "loader", size: "sm", class: "animate-spin" %> + <%= tag.span t(".syncing") %> +
+ <% elsif lunchflow_item.sync_error.present? %> +
+ <%= render DS::Tooltip.new(text: lunchflow_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %> + <%= tag.span t(".error"), class: "text-destructive" %> +
+ <% else %> +

+ <% if lunchflow_item.last_synced_at %> + <% if lunchflow_item.sync_status_summary %> + <%= t(".status_with_summary", timestamp: time_ago_in_words(lunchflow_item.last_synced_at), summary: lunchflow_item.sync_status_summary) %> + <% else %> + <%= t(".status", timestamp: time_ago_in_words(lunchflow_item.last_synced_at)) %> + <% end %> + <% else %> + <%= t(".status_never") %> + <% end %> +

+ <% end %> +
+ + <% if Current.user&.admin? %> +
+ <% if Rails.env.development? %> + <%= icon( + "refresh-cw", + as_button: true, + href: sync_lunchflow_item_path(lunchflow_item) + ) %> + <% end %> + + <%= render DS::Menu.new do |menu| %> + <% menu.with_item( + variant: "button", + text: t(".delete"), + icon: "trash-2", + href: lunchflow_item_path(lunchflow_item), + method: :delete, + confirm: CustomConfirm.for_resource_deletion(lunchflow_item.name, high_severity: true) + ) %> + <% end %> +
+ <% end %>
- - <% if Current.user&.admin? %> -
- <% if Rails.env.development? %> - <%= icon( - "refresh-cw", - as_button: true, - href: sync_lunchflow_item_path(lunchflow_item) - ) %> - <% end %> - - <%= render DS::Menu.new do |menu| %> - <% menu.with_item( - variant: "button", - text: t(".delete"), - icon: "trash-2", - href: lunchflow_item_path(lunchflow_item), - method: :delete, - confirm: CustomConfirm.for_resource_deletion(lunchflow_item.name, high_severity: true) - ) %> - <% end %> -
- <% end %> -
+ <% end %> <% unless lunchflow_item.scheduled_for_deletion? %>
@@ -128,5 +130,5 @@ <% end %>
<% end %> -
+ <% end %> <% end %> diff --git a/app/views/mercury_items/_mercury_item.html.erb b/app/views/mercury_items/_mercury_item.html.erb index 6485da548..9fb8f7923 100644 --- a/app/views/mercury_items/_mercury_item.html.erb +++ b/app/views/mercury_items/_mercury_item.html.erb @@ -1,82 +1,84 @@ <%# locals: (mercury_item:) %> <%= tag.div id: dom_id(mercury_item) do %> -
- -
- <%= icon "chevron-right", class: "group-open:transform group-open:rotate-90" %> + <%= render DS::Disclosure.new(variant: :card, open: true) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+
+ <%= icon "chevron-right", class: "group-open:rotate-90 motion-safe:transition-transform motion-safe:duration-150" %> -
- <% if mercury_item.logo.attached? %> - <%= image_tag mercury_item.logo, class: "rounded-full h-full w-full", loading: "lazy" %> - <% else %> -
- <%= tag.p mercury_item.name.first.upcase, class: "text-blue-600 text-xs font-medium" %> -
- <% end %> -
- -
-
- <%= tag.p mercury_item.name, class: "font-medium text-primary" %> - <% if mercury_item.scheduled_for_deletion? %> -

<%= t(".deletion_in_progress") %>

+
+ <% if mercury_item.logo.attached? %> + <%= image_tag mercury_item.logo, class: "rounded-full h-full w-full", loading: "lazy" %> + <% else %> +
+ <%= tag.p mercury_item.name.first.upcase, class: "text-blue-600 text-xs font-medium" %> +
<% end %>
- <% if mercury_item.accounts.any? %> -

- <%= mercury_item.institution_summary %> -

- <% end %> - <% if mercury_item.syncing? %> -
- <%= icon "loader", size: "sm", class: "animate-spin" %> - <%= tag.span t(".syncing") %> -
- <% elsif mercury_item.sync_error.present? %> -
- <%= render DS::Tooltip.new(text: mercury_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %> - <%= tag.span t(".error"), class: "text-destructive" %> -
- <% else %> -

- <% if mercury_item.last_synced_at %> - <% if mercury_item.sync_status_summary %> - <%= t(".status_with_summary", timestamp: time_ago_in_words(mercury_item.last_synced_at), summary: mercury_item.sync_status_summary) %> - <% else %> - <%= t(".status", timestamp: time_ago_in_words(mercury_item.last_synced_at)) %> - <% end %> - <% else %> - <%= t(".status_never") %> + +

+
+ <%= tag.p mercury_item.name, class: "font-medium text-primary" %> + <% if mercury_item.scheduled_for_deletion? %> +

<%= t(".deletion_in_progress") %>

<% end %> -

- <% end %> +
+ <% if mercury_item.accounts.any? %> +

+ <%= mercury_item.institution_summary %> +

+ <% end %> + <% if mercury_item.syncing? %> +
+ <%= icon "loader", size: "sm", class: "animate-spin" %> + <%= tag.span t(".syncing") %> +
+ <% elsif mercury_item.sync_error.present? %> +
+ <%= render DS::Tooltip.new(text: mercury_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %> + <%= tag.span t(".error"), class: "text-destructive" %> +
+ <% else %> +

+ <% if mercury_item.last_synced_at %> + <% if mercury_item.sync_status_summary %> + <%= t(".status_with_summary", timestamp: time_ago_in_words(mercury_item.last_synced_at), summary: mercury_item.sync_status_summary) %> + <% else %> + <%= t(".status", timestamp: time_ago_in_words(mercury_item.last_synced_at)) %> + <% end %> + <% else %> + <%= t(".status_never") %> + <% end %> +

+ <% end %> +
+ + <% if Current.user&.admin? %> +
+ <% if Rails.env.development? %> + <%= icon( + "refresh-cw", + as_button: true, + href: sync_mercury_item_path(mercury_item) + ) %> + <% end %> + + <%= render DS::Menu.new do |menu| %> + <% menu.with_item( + variant: "button", + text: t(".delete"), + icon: "trash-2", + href: mercury_item_path(mercury_item), + method: :delete, + confirm: CustomConfirm.for_resource_deletion(mercury_item.name, high_severity: true) + ) %> + <% end %> +
+ <% end %>
- - <% if Current.user&.admin? %> -
- <% if Rails.env.development? %> - <%= icon( - "refresh-cw", - as_button: true, - href: sync_mercury_item_path(mercury_item) - ) %> - <% end %> - - <%= render DS::Menu.new do |menu| %> - <% menu.with_item( - variant: "button", - text: t(".delete"), - icon: "trash-2", - href: mercury_item_path(mercury_item), - method: :delete, - confirm: CustomConfirm.for_resource_deletion(mercury_item.name, high_severity: true) - ) %> - <% end %> -
- <% end %> -
+ <% end %> <% unless mercury_item.scheduled_for_deletion? %>
@@ -128,5 +130,5 @@ <% end %>
<% end %> -
+ <% end %> <% end %> diff --git a/app/views/plaid_items/_plaid_item.html.erb b/app/views/plaid_items/_plaid_item.html.erb index e7fd2254f..96e621d64 100644 --- a/app/views/plaid_items/_plaid_item.html.erb +++ b/app/views/plaid_items/_plaid_item.html.erb @@ -1,82 +1,84 @@ <%# locals: (plaid_item:) %> <%= tag.div id: dom_id(plaid_item) do %> -
- -
- <%= icon "chevron-right", class: "group-open:transform group-open:rotate-90" %> + <%= render DS::Disclosure.new(variant: :card, open: true) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+
+ <%= icon "chevron-right", class: "group-open:rotate-90 motion-safe:transition-transform motion-safe:duration-150" %> -
- <% if plaid_item.logo.attached? %> - <%= image_tag plaid_item.logo, class: "rounded-full h-full w-full", loading: "lazy" %> - <% else %> -
- <%= tag.p plaid_item.name.first.upcase, class: "text-blue-600 text-xs font-medium" %> -
- <% end %> -
- -
-
- <%= tag.p plaid_item.name, class: "font-medium text-primary" %> - <% if plaid_item.scheduled_for_deletion? %> -

(deletion in progress...)

+
+ <% if plaid_item.logo.attached? %> + <%= image_tag plaid_item.logo, class: "rounded-full h-full w-full", loading: "lazy" %> + <% else %> +
+ <%= tag.p plaid_item.name.first.upcase, class: "text-blue-600 text-xs font-medium" %> +
<% end %>
- <% if plaid_item.syncing? %> -
- <%= icon "loader", size: "sm", class: "animate-pulse" %> - <%= tag.span t(".syncing") %> + +
+
+ <%= tag.p plaid_item.name, class: "font-medium text-primary" %> + <% if plaid_item.scheduled_for_deletion? %> +

(deletion in progress...)

+ <% end %>
- <% elsif plaid_item.requires_update? %> -
- <%= icon "alert-triangle", size: "sm", color: "warning" %> - <%= tag.span t(".requires_update") %> -
- <% elsif plaid_item.sync_error.present? %> -
- <%= icon "alert-circle", size: "sm", color: "destructive" %> - <%= tag.span t(".error"), class: "text-destructive" %> -
- <% else %> -

- <%= plaid_item.last_synced_at ? t(".status", timestamp: time_ago_in_words(plaid_item.last_synced_at)) : t(".status_never") %> -

- <% end %> + <% if plaid_item.syncing? %> +
+ <%= icon "loader", size: "sm", class: "animate-pulse" %> + <%= tag.span t(".syncing") %> +
+ <% elsif plaid_item.requires_update? %> +
+ <%= icon "alert-triangle", size: "sm", color: "warning" %> + <%= tag.span t(".requires_update") %> +
+ <% elsif plaid_item.sync_error.present? %> +
+ <%= icon "alert-circle", size: "sm", color: "destructive" %> + <%= tag.span t(".error"), class: "text-destructive" %> +
+ <% else %> +

+ <%= plaid_item.last_synced_at ? t(".status", timestamp: time_ago_in_words(plaid_item.last_synced_at)) : t(".status_never") %> +

+ <% end %> +
+ + <% if Current.user&.admin? %> +
+ <% if plaid_item.requires_update? %> + <%= render DS::Link.new( + text: t(".update"), + icon: "refresh-cw", + variant: "secondary", + href: edit_plaid_item_path(plaid_item), + frame: "modal" + ) %> + <% elsif Rails.env.development? %> + <%= icon( + "refresh-cw", + as_button: true, + href: sync_plaid_item_path(plaid_item) + ) %> + <% end %> + + <%= render DS::Menu.new do |menu| %> + <% menu.with_item( + variant: "button", + text: t(".delete"), + icon: "trash-2", + href: plaid_item_path(plaid_item), + method: :delete, + confirm: CustomConfirm.for_resource_deletion(plaid_item.name, high_severity: true) + ) %> + <% end %> +
+ <% end %>
- - <% if Current.user&.admin? %> -
- <% if plaid_item.requires_update? %> - <%= render DS::Link.new( - text: t(".update"), - icon: "refresh-cw", - variant: "secondary", - href: edit_plaid_item_path(plaid_item), - frame: "modal" - ) %> - <% elsif Rails.env.development? %> - <%= icon( - "refresh-cw", - as_button: true, - href: sync_plaid_item_path(plaid_item) - ) %> - <% end %> - - <%= render DS::Menu.new do |menu| %> - <% menu.with_item( - variant: "button", - text: t(".delete"), - icon: "trash-2", - href: plaid_item_path(plaid_item), - method: :delete, - confirm: CustomConfirm.for_resource_deletion(plaid_item.name, high_severity: true) - ) %> - <% end %> -
- <% end %> -
+ <% end %> <% unless plaid_item.scheduled_for_deletion? %>
@@ -103,5 +105,5 @@ <% end %>
<% end %> -
+ <% end %> <% end %> diff --git a/app/views/settings/preferences/show.html.erb b/app/views/settings/preferences/show.html.erb index d2e0fd9a1..80b767bd1 100644 --- a/app/views/settings/preferences/show.html.erb +++ b/app/views/settings/preferences/show.html.erb @@ -127,18 +127,13 @@ aria-atomic="true">

-
- " - aria-label="<%= t(".currency_search_placeholder") %>" - data-list-filter-target="input" - data-action="input->list-filter#filter" - class="block w-full border border-secondary rounded-md py-2.5 pl-10 pr-3 bg-container focus:ring-gray-500 sm:text-sm"> -
- <%= icon("search", class: "text-secondary") %> -
-
+ <%= render DS::SearchInput.new( + placeholder: t(".currency_search_placeholder"), + data: { + list_filter_target: "input", + action: "input->list-filter#filter" + } + ) %>
<% active_items.each do |item| %> -
- -
-
-

<%= item.name.to_s.first.to_s.upcase %>

-
-
-

<%= item.name %>

-

<%= item.sync_status_summary %>

+ <%= render DS::Disclosure.new(variant: :card) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+
+
+

<%= item.name.to_s.first.to_s.upcase %>

+
+
+

<%= item.name %>

+

<%= item.sync_status_summary %>

+
-
+ <% end %>
@@ -102,16 +104,18 @@
<% end %>
-
+ <% end %> <% end %>
<% end %> -
class="group bg-container p-4 shadow-border-xs rounded-xl"> - - <%= icon "plus" %> - <%= t("brex_items.provider_panel.add_connection") %> - + <%= render DS::Disclosure.new(variant: :card, open: active_items.none?) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+ <%= icon "plus" %> + <%= t("brex_items.provider_panel.add_connection") %> +
+ <% end %> <% brex_item = Current.family.brex_items.build(name: t("brex_items.provider_panel.default_connection_name")) %> <%= styled_form_with model: brex_item, @@ -138,7 +142,7 @@ <%= form.submit t("brex_items.provider_panel.add_connection") %>
<% end %> -
+ <% end %>
<% if credentialed_items.any? %> diff --git a/app/views/settings/providers/_kraken_panel.html.erb b/app/views/settings/providers/_kraken_panel.html.erb index 8490b1c49..39719602c 100644 --- a/app/views/settings/providers/_kraken_panel.html.erb +++ b/app/views/settings/providers/_kraken_panel.html.erb @@ -24,24 +24,26 @@ <% if items.any? %>
<% items.each do |item| %> -
- -
-
- <%= icon "waves", size: "sm", class: "text-primary" %> -
-
-

<%= item.name %>

-

- <% if item.syncing? %> - <%= t("settings.providers.kraken_panel.syncing") %> - <% else %> - <%= item.sync_status_summary %> - <% end %> -

+ <%= render DS::Disclosure.new(variant: :card) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+
+
+ <%= icon "waves", size: "sm", class: "text-primary" %> +
+
+

<%= item.name %>

+

+ <% if item.syncing? %> + <%= t("settings.providers.kraken_panel.syncing") %> + <% else %> + <%= item.sync_status_summary %> + <% end %> +

+
-
+ <% end %>
@@ -97,7 +99,7 @@
<% end %>
-
+ <% end %> <% end %>
<% end %> diff --git a/app/views/settings/providers/_mercury_panel.html.erb b/app/views/settings/providers/_mercury_panel.html.erb index 4e711b4c7..c653ed66a 100644 --- a/app/views/settings/providers/_mercury_panel.html.erb +++ b/app/views/settings/providers/_mercury_panel.html.erb @@ -19,18 +19,20 @@ <% if active_items.any? %>
<% active_items.each do |item| %> -
- -
-
-

<%= item.name.to_s.first.to_s.upcase %>

-
-
-

<%= item.name %>

-

<%= item.sync_status_summary %>

+ <%= render DS::Disclosure.new(variant: :card) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+
+
+

<%= item.name.to_s.first.to_s.upcase %>

+
+
+

<%= item.name %>

+

<%= item.sync_status_summary %>

+
-
+ <% end %>
@@ -82,7 +84,7 @@
<% end %>
-
+ <% end %> <% end %>
<% end %> diff --git a/app/views/settings/providers/_search_filters.html.erb b/app/views/settings/providers/_search_filters.html.erb index f4c295faf..30c36b172 100644 --- a/app/views/settings/providers/_search_filters.html.erb +++ b/app/views/settings/providers/_search_filters.html.erb @@ -1,16 +1,13 @@
-
- " - placeholder="<%= t("settings.providers.search_filters.placeholder") %>" - class="block w-full border border-secondary rounded-md py-2.5 pl-10 pr-3 bg-container focus:ring-gray-500 sm:text-sm"> -
- <%= icon "search", class: "text-secondary" %> -
-
+ <%= render DS::SearchInput.new( + class: "flex-1 min-w-[200px]", + placeholder: t("settings.providers.search_filters.placeholder"), + aria_label: t("settings.providers.search_filters.aria_label"), + data: { + providers_filter_target: "input", + action: "input->providers-filter#filter" + } + ) %>
<% %w[all bank crypto investment].each do |kind| %> <% active = kind == "all" %> diff --git a/app/views/simplefin_items/_simplefin_item.html.erb b/app/views/simplefin_items/_simplefin_item.html.erb index d09d9b202..8c632113f 100644 --- a/app/views/simplefin_items/_simplefin_item.html.erb +++ b/app/views/simplefin_items/_simplefin_item.html.erb @@ -16,175 +16,177 @@ end end %> -
- -
- <%= icon "chevron-right", class: "group-open:transform group-open:rotate-90" %> + <%= render DS::Disclosure.new(variant: :card, open: true) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+
+ <%= icon "chevron-right", class: "group-open:rotate-90 motion-safe:transition-transform motion-safe:duration-150" %> -
- <% if simplefin_item.logo.attached? %> - <%= image_tag simplefin_item.logo, class: "rounded-full h-full w-full", loading: "lazy" %> - <% else %> -
- <%= tag.p simplefin_item.name.first.upcase, class: "text-success text-xs font-medium" %> -
- <% end %> -
- -
-
- <%= tag.p simplefin_item.name, class: "font-medium text-primary" %> - <% if simplefin_item.scheduled_for_deletion? %> -

<%= t(".deletion_in_progress") %>

+
+ <% if simplefin_item.logo.attached? %> + <%= image_tag simplefin_item.logo, class: "rounded-full h-full w-full", loading: "lazy" %> + <% else %> +
+ <%= tag.p simplefin_item.name.first.upcase, class: "text-success text-xs font-medium" %> +
<% end %>
- <% if simplefin_item.accounts.any? %> -

- <%= simplefin_item.institution_summary %> -

- <%# Extra inline badges from latest sync stats - only show warnings %> - <% stats = (@simplefin_sync_stats_map || {})[simplefin_item.id] || {} %> - <% has_warnings = stats["accounts_skipped"].to_i > 0 || - stats["rate_limited"].present? || - stats["rate_limited_at"].present? || - stats["total_errors"].to_i > 0 || - (stats["errors"].is_a?(Array) && stats["errors"].any?) %> - <% if has_warnings %> -
- <% if stats["accounts_skipped"].to_i > 0 %> - <%= render DS::Tooltip.new(text: t(".accounts_skipped_tooltip"), icon: "alert-triangle", size: "sm", color: "warning", as: :span) %> - <%= t(".accounts_skipped_label", count: stats["accounts_skipped"].to_i) %> - <% end %> - <% if stats["rate_limited"].present? || stats["rate_limited_at"].present? %> - <% ts = stats["rate_limited_at"] %> - <% ago = (ts.present? ? (begin; time_ago_in_words(Time.parse(ts)); rescue StandardError; nil; end) : nil) %> - <%= render DS::Tooltip.new( - text: (ago ? t(".rate_limited_ago", time: ago) : t(".rate_limited_recently")), - icon: "clock", - size: "sm", - color: "warning", - as: :span - ) %> - <% end %> - - <% if stats["total_errors"].to_i > 0 || (stats["errors"].is_a?(Array) && stats["errors"].any?) %> - <% tooltip_text = simplefin_error_tooltip(stats) %> - <% if tooltip_text.present? %> - <%= render DS::Tooltip.new(text: tooltip_text, icon: "alert-octagon", size: "sm", color: "warning", as: :span) %> +
+
+ <%= tag.p simplefin_item.name, class: "font-medium text-primary" %> + <% if simplefin_item.scheduled_for_deletion? %> +

<%= t(".deletion_in_progress") %>

+ <% end %> +
+ <% if simplefin_item.accounts.any? %> +

+ <%= simplefin_item.institution_summary %> +

+ <%# Extra inline badges from latest sync stats - only show warnings %> + <% stats = (@simplefin_sync_stats_map || {})[simplefin_item.id] || {} %> + <% has_warnings = stats["accounts_skipped"].to_i > 0 || + stats["rate_limited"].present? || + stats["rate_limited_at"].present? || + stats["total_errors"].to_i > 0 || + (stats["errors"].is_a?(Array) && stats["errors"].any?) %> + <% if has_warnings %> +
+ <% if stats["accounts_skipped"].to_i > 0 %> + <%= render DS::Tooltip.new(text: t(".accounts_skipped_tooltip"), icon: "alert-triangle", size: "sm", color: "warning", as: :span) %> + <%= t(".accounts_skipped_label", count: stats["accounts_skipped"].to_i) %> + <% end %> + + <% if stats["rate_limited"].present? || stats["rate_limited_at"].present? %> + <% ts = stats["rate_limited_at"] %> + <% ago = (ts.present? ? (begin; time_ago_in_words(Time.parse(ts)); rescue StandardError; nil; end) : nil) %> + <%= render DS::Tooltip.new( + text: (ago ? t(".rate_limited_ago", time: ago) : t(".rate_limited_recently")), + icon: "clock", + size: "sm", + color: "warning", + as: :span + ) %> + <% end %> + + <% if stats["total_errors"].to_i > 0 || (stats["errors"].is_a?(Array) && stats["errors"].any?) %> + <% tooltip_text = simplefin_error_tooltip(stats) %> + <% if tooltip_text.present? %> + <%= render DS::Tooltip.new(text: tooltip_text, icon: "alert-octagon", size: "sm", color: "warning", as: :span) %> + <% end %> <% end %> - <% end %> -
- <% end %> - <% end %> - <%# Determine if all reported errors are benign duplicate-skips (suppress scary banner). Computed in controller for testability. %> - <% duplicate_only_errors = (@simplefin_duplicate_only_map || {})[simplefin_item.id] || false %> - <% if simplefin_item.syncing? %> -
- <%= icon "loader", size: "sm", class: "animate-spin" %> - <%= tag.span t(".syncing") %> -
- <% elsif simplefin_item.requires_update? %> -
- <%= icon "alert-triangle", size: "sm", color: "warning" %> - <%= tag.span t(".requires_update") %> -
- <% elsif (stale_status = simplefin_item.stale_sync_status)[:stale] %> -
- <%= icon "alert-circle", size: "sm", color: "warning" %> - <%= tag.span stale_status[:message], class: "text-sm" %> -
- <% elsif (pending_status = simplefin_item.stale_pending_status)[:count] > 0 %> -
-
- <%= icon "clock", size: "sm", color: "secondary" %> - <%= tag.span pending_status[:message], class: "text-sm" %> - <%= t(".stale_pending_note") %> -
- <% if pending_status[:accounts]&.any? %> -
- <%= t(".stale_pending_accounts", accounts: pending_status[:accounts].join(", ")) %>
<% end %> -
- <% elsif (reconciled_status = simplefin_item.last_sync_reconciled_status)[:count] > 0 %> -
- <%= icon "check-circle", size: "sm", color: "success" %> - <%= tag.span reconciled_status[:message], class: "text-sm" %> - <%= t(".reconciled_details_note") %> -
- <% elsif simplefin_item.rate_limited_message.present? %> -
- <%= icon "clock", size: "sm", color: "warning" %> - <%= tag.span simplefin_item.rate_limited_message %> -
- <% elsif simplefin_item.sync_error.present? && !duplicate_only_errors %> -
- <%= render DS::Tooltip.new(text: simplefin_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %> - <%= tag.span t(".error"), class: "text-destructive" %> -
- <% elsif duplicate_only_errors %> -
- <%= icon "info", size: "sm" %> - <%= tag.span t(".duplicate_accounts_skipped"), class: "text-secondary" %> -
- <% else %> -

- <% if simplefin_item.last_synced_at %> - <% if simplefin_item.sync_status_summary %> - <%= t(".status_with_summary", timestamp: time_ago_in_words(simplefin_item.last_synced_at), summary: simplefin_item.sync_status_summary) %> - <% else %> - <%= t(".status", timestamp: time_ago_in_words(simplefin_item.last_synced_at)) %> + <% end %> + <%# Determine if all reported errors are benign duplicate-skips (suppress scary banner). Computed in controller for testability. %> + <% duplicate_only_errors = (@simplefin_duplicate_only_map || {})[simplefin_item.id] || false %> + <% if simplefin_item.syncing? %> +

+ <%= icon "loader", size: "sm", class: "animate-spin" %> + <%= tag.span t(".syncing") %> +
+ <% elsif simplefin_item.requires_update? %> +
+ <%= icon "alert-triangle", size: "sm", color: "warning" %> + <%= tag.span t(".requires_update") %> +
+ <% elsif (stale_status = simplefin_item.stale_sync_status)[:stale] %> +
+ <%= icon "alert-circle", size: "sm", color: "warning" %> + <%= tag.span stale_status[:message], class: "text-sm" %> +
+ <% elsif (pending_status = simplefin_item.stale_pending_status)[:count] > 0 %> +
+
+ <%= icon "clock", size: "sm", color: "secondary" %> + <%= tag.span pending_status[:message], class: "text-sm" %> + <%= t(".stale_pending_note") %> +
+ <% if pending_status[:accounts]&.any? %> +
+ <%= t(".stale_pending_accounts", accounts: pending_status[:accounts].join(", ")) %> +
<% end %> - <% else %> - <%= t(".status_never") %> - <% end %> -

- <% end %> +
+ <% elsif (reconciled_status = simplefin_item.last_sync_reconciled_status)[:count] > 0 %> +
+ <%= icon "check-circle", size: "sm", color: "success" %> + <%= tag.span reconciled_status[:message], class: "text-sm" %> + <%= t(".reconciled_details_note") %> +
+ <% elsif simplefin_item.rate_limited_message.present? %> +
+ <%= icon "clock", size: "sm", color: "warning" %> + <%= tag.span simplefin_item.rate_limited_message %> +
+ <% elsif simplefin_item.sync_error.present? && !duplicate_only_errors %> +
+ <%= render DS::Tooltip.new(text: simplefin_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %> + <%= tag.span t(".error"), class: "text-destructive" %> +
+ <% elsif duplicate_only_errors %> +
+ <%= icon "info", size: "sm" %> + <%= tag.span t(".duplicate_accounts_skipped"), class: "text-secondary" %> +
+ <% else %> +

+ <% if simplefin_item.last_synced_at %> + <% if simplefin_item.sync_status_summary %> + <%= t(".status_with_summary", timestamp: time_ago_in_words(simplefin_item.last_synced_at), summary: simplefin_item.sync_status_summary) %> + <% else %> + <%= t(".status", timestamp: time_ago_in_words(simplefin_item.last_synced_at)) %> + <% end %> + <% else %> + <%= t(".status_never") %> + <% end %> +

+ <% end %> +
-
- <% if Current.user&.admin? %> -
- <% if simplefin_item.requires_update? %> - <%= render DS::Link.new( - text: t(".update"), - icon: "refresh-cw", - variant: "secondary", - href: edit_simplefin_item_path(simplefin_item), - frame: "modal" - ) %> - <% else %> - <%= icon( - "refresh-cw", - as_button: true, - href: sync_simplefin_item_path(simplefin_item), - disabled: simplefin_item.syncing? - ) %> - <% end %> - - <%= render DS::Menu.new do |menu| %> - <% if unlinked_count.to_i > 0 %> - <% menu.with_item( - variant: "link", - text: t(".setup_accounts_menu"), - icon: "settings", - href: setup_accounts_simplefin_item_path(simplefin_item), - frame: :modal + <% if Current.user&.admin? %> +
+ <% if simplefin_item.requires_update? %> + <%= render DS::Link.new( + text: t(".update"), + icon: "refresh-cw", + variant: "secondary", + href: edit_simplefin_item_path(simplefin_item), + frame: "modal" + ) %> + <% else %> + <%= icon( + "refresh-cw", + as_button: true, + href: sync_simplefin_item_path(simplefin_item), + disabled: simplefin_item.syncing? ) %> <% end %> - <% menu.with_item( - variant: "button", - text: t(".delete"), - icon: "trash-2", - href: simplefin_item_path(simplefin_item), - method: :delete, - confirm: CustomConfirm.for_resource_deletion(simplefin_item.name, high_severity: true) - ) %> - <% end %> -
- <% end %> -
+ + <%= render DS::Menu.new do |menu| %> + <% if unlinked_count.to_i > 0 %> + <% menu.with_item( + variant: "link", + text: t(".setup_accounts_menu"), + icon: "settings", + href: setup_accounts_simplefin_item_path(simplefin_item), + frame: :modal + ) %> + <% end %> + <% menu.with_item( + variant: "button", + text: t(".delete"), + icon: "trash-2", + href: simplefin_item_path(simplefin_item), + method: :delete, + confirm: CustomConfirm.for_resource_deletion(simplefin_item.name, high_severity: true) + ) %> + <% end %> +
+ <% end %> +
+ <% end %> <% unless simplefin_item.scheduled_for_deletion? %>
@@ -237,5 +239,5 @@ <% end %>
<% end %> - + <% end %> <% end %> diff --git a/app/views/snaptrade_items/_snaptrade_item.html.erb b/app/views/snaptrade_items/_snaptrade_item.html.erb index 636d09dbe..4314200c8 100644 --- a/app/views/snaptrade_items/_snaptrade_item.html.erb +++ b/app/views/snaptrade_items/_snaptrade_item.html.erb @@ -1,114 +1,116 @@ <%# locals: (snaptrade_item:) %> <%= tag.div id: dom_id(snaptrade_item) do %> -
- -
- <%= icon "chevron-right", class: "group-open:transform group-open:rotate-90" %> + <% unlinked_count = snaptrade_item.unlinked_accounts_count %> -
- <% if snaptrade_item.logo.attached? %> - <%= image_tag snaptrade_item.logo, class: "rounded-full h-full w-full", loading: "lazy" %> - <% else %> -
- <%= tag.p snaptrade_item.name.first.upcase, class: "text-success text-xs font-medium" %> -
- <% end %> -
+ <%= render DS::Disclosure.new(variant: :card, open: true) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+
+ <%= icon "chevron-right", class: "group-open:rotate-90 motion-safe:transition-transform motion-safe:duration-150" %> - <% unlinked_count = snaptrade_item.unlinked_accounts_count %> - -
-
- <%= tag.p snaptrade_item.name, class: "font-medium text-primary" %> - <% if snaptrade_item.scheduled_for_deletion? %> -

<%= t(".deletion_in_progress") %>

+
+ <% if snaptrade_item.logo.attached? %> + <%= image_tag snaptrade_item.logo, class: "rounded-full h-full w-full", loading: "lazy" %> + <% else %> +
+ <%= tag.p snaptrade_item.name.first.upcase, class: "text-success text-xs font-medium" %> +
<% end %>
- <% if snaptrade_item.snaptrade_accounts.any? %> -

- <%= snaptrade_item.brokerage_summary %> -

- <% end %> - <% if snaptrade_item.syncing? %> -
- <%= icon "loader", size: "sm", class: "animate-spin" %> - <%= tag.span t(".syncing") %> -
- <% elsif snaptrade_item.requires_update? %> -
- <%= icon "alert-triangle", size: "sm", color: "warning" %> - <%= tag.span t(".requires_update") %> -
- <% elsif snaptrade_item.sync_error.present? %> -
- <%= render DS::Tooltip.new(text: snaptrade_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %> - <%= tag.span t(".error"), class: "text-destructive" %> -
- <% else %> -

- <% if snaptrade_item.last_synced_at %> - <%= t(".status", timestamp: time_ago_in_words(snaptrade_item.last_synced_at), summary: snaptrade_item.sync_status_summary) %> - <% else %> - <%= t(".status_never") %> + +

+
+ <%= tag.p snaptrade_item.name, class: "font-medium text-primary" %> + <% if snaptrade_item.scheduled_for_deletion? %> +

<%= t(".deletion_in_progress") %>

<% end %> -

- <% end %> +
+ <% if snaptrade_item.snaptrade_accounts.any? %> +

+ <%= snaptrade_item.brokerage_summary %> +

+ <% end %> + <% if snaptrade_item.syncing? %> +
+ <%= icon "loader", size: "sm", class: "animate-spin" %> + <%= tag.span t(".syncing") %> +
+ <% elsif snaptrade_item.requires_update? %> +
+ <%= icon "alert-triangle", size: "sm", color: "warning" %> + <%= tag.span t(".requires_update") %> +
+ <% elsif snaptrade_item.sync_error.present? %> +
+ <%= render DS::Tooltip.new(text: snaptrade_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %> + <%= tag.span t(".error"), class: "text-destructive" %> +
+ <% else %> +

+ <% if snaptrade_item.last_synced_at %> + <%= t(".status", timestamp: time_ago_in_words(snaptrade_item.last_synced_at), summary: snaptrade_item.sync_status_summary) %> + <% else %> + <%= t(".status_never") %> + <% end %> +

+ <% end %> +
-
- <% if Current.user&.admin? %> -
- <% if snaptrade_item.requires_update? || !snaptrade_item.user_registered? %> - <%= render DS::Link.new( - text: t(".reconnect"), - icon: "link", - variant: "secondary", - href: connect_snaptrade_item_path(snaptrade_item) - ) %> - <% else %> - <%= icon( - "refresh-cw", - as_button: true, - href: sync_snaptrade_item_path(snaptrade_item), - disabled: snaptrade_item.syncing? - ) %> - <% end %> - - <%= render DS::Menu.new do |menu| %> - <% menu.with_item( - variant: "link", - text: t(".connect_brokerage"), - icon: "plus", - href: connect_snaptrade_item_path(snaptrade_item) - ) %> - <% if unlinked_count > 0 %> - <% menu.with_item( - variant: "link", - text: t(".setup_accounts_menu"), - icon: "settings", - href: setup_accounts_snaptrade_item_path(snaptrade_item), - frame: :modal + <% if Current.user&.admin? %> +
+ <% if snaptrade_item.requires_update? || !snaptrade_item.user_registered? %> + <%= render DS::Link.new( + text: t(".reconnect"), + icon: "link", + variant: "secondary", + href: connect_snaptrade_item_path(snaptrade_item) + ) %> + <% else %> + <%= icon( + "refresh-cw", + as_button: true, + href: sync_snaptrade_item_path(snaptrade_item), + disabled: snaptrade_item.syncing? ) %> <% end %> - <% menu.with_item( - variant: "link", - text: t(".manage_connections"), - icon: "cable", - href: settings_providers_path(manage: "1") - ) %> - <% menu.with_item( - variant: "button", - text: t(".delete"), - icon: "trash-2", - href: snaptrade_item_path(snaptrade_item), - method: :delete, - confirm: CustomConfirm.for_resource_deletion(snaptrade_item.name, high_severity: true) - ) %> - <% end %> -
- <% end %> -
+ + <%= render DS::Menu.new do |menu| %> + <% menu.with_item( + variant: "link", + text: t(".connect_brokerage"), + icon: "plus", + href: connect_snaptrade_item_path(snaptrade_item) + ) %> + <% if unlinked_count > 0 %> + <% menu.with_item( + variant: "link", + text: t(".setup_accounts_menu"), + icon: "settings", + href: setup_accounts_snaptrade_item_path(snaptrade_item), + frame: :modal + ) %> + <% end %> + <% menu.with_item( + variant: "link", + text: t(".manage_connections"), + icon: "cable", + href: settings_providers_path(manage: "1") + ) %> + <% menu.with_item( + variant: "button", + text: t(".delete"), + icon: "trash-2", + href: snaptrade_item_path(snaptrade_item), + method: :delete, + confirm: CustomConfirm.for_resource_deletion(snaptrade_item.name, high_severity: true) + ) %> + <% end %> +
+ <% end %> + + <% end %> <% unless snaptrade_item.scheduled_for_deletion? %>
@@ -155,5 +157,5 @@ <% end %>
<% end %> - + <% end %> <% end %> diff --git a/app/views/sophtron_items/_sophtron_item.html.erb b/app/views/sophtron_items/_sophtron_item.html.erb index d91450a86..e8f64bb6e 100644 --- a/app/views/sophtron_items/_sophtron_item.html.erb +++ b/app/views/sophtron_items/_sophtron_item.html.erb @@ -4,105 +4,107 @@ <% provider_display_name = sophtron_item.provider_display_name %> <% manual_sync_required = sophtron_item.manual_sync_required? %> <% connected_institution_options = sophtron_item.connected_institution_options %> -
- -
- <%= icon "chevron-right", class: "group-open:transform group-open:rotate-90" %> + <%= render DS::Disclosure.new(variant: :card, open: true) do |disclosure| %> + <% disclosure.with_summary_content do %> +
+
+ <%= icon "chevron-right", class: "group-open:rotate-90 motion-safe:transition-transform motion-safe:duration-150" %> -
- <% if sophtron_item.logo.attached? %> - <%= image_tag sophtron_item.logo, class: "rounded-full h-full w-full", loading: "lazy" %> - <% else %> -
- <%= tag.p provider_display_name.first.upcase, class: "text-orange-600 text-xs font-medium" %> -
- <% end %> -
- -
-
- <%= tag.p provider_display_name, class: "font-medium text-primary" %> - <% if manual_sync_required %> - <%= t(".manual_sync") %> - <% end %> - <% if sophtron_item.scheduled_for_deletion? %> -

<%= t(".deletion_in_progress") %>

+
+ <% if sophtron_item.logo.attached? %> + <%= image_tag sophtron_item.logo, class: "rounded-full h-full w-full", loading: "lazy" %> + <% else %> +
+ <%= tag.p provider_display_name.first.upcase, class: "text-orange-600 text-xs font-medium" %> +
<% end %>
- <% if sophtron_item.accounts.any? %> -

- <%= sophtron_item.institution_summary %> -

- <% end %> - <% if sophtron_item.syncing? %> -
- <%= icon "loader", size: "sm", class: "animate-spin" %> - <%= tag.span t(".syncing") %> -
- <% elsif sophtron_item.sync_error.present? %> -
- <%= render DS::Tooltip.new(text: sophtron_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %> - <%= tag.span t(".error"), class: "text-destructive" %> -
- <% else %> -

- <% if sophtron_item.last_synced_at %> - <% if sophtron_item.sync_status_summary %> - <%= t(".status_with_summary", timestamp: time_ago_in_words(sophtron_item.last_synced_at), summary: sophtron_item.sync_status_summary) %> - <% else %> - <%= t(".status", timestamp: time_ago_in_words(sophtron_item.last_synced_at)) %> - <% end %> - <% else %> - <%= t(".status_never") %> + +

+
+ <%= tag.p provider_display_name, class: "font-medium text-primary" %> + <% if manual_sync_required %> + <%= t(".manual_sync") %> <% end %> -

- <% end %> + <% if sophtron_item.scheduled_for_deletion? %> +

<%= t(".deletion_in_progress") %>

+ <% end %> +
+ <% if sophtron_item.accounts.any? %> +

+ <%= sophtron_item.institution_summary %> +

+ <% end %> + <% if sophtron_item.syncing? %> +
+ <%= icon "loader", size: "sm", class: "animate-spin" %> + <%= tag.span t(".syncing") %> +
+ <% elsif sophtron_item.sync_error.present? %> +
+ <%= render DS::Tooltip.new(text: sophtron_item.sync_error, icon: "alert-circle", size: "sm", color: "destructive", as: :span) %> + <%= tag.span t(".error"), class: "text-destructive" %> +
+ <% else %> +

+ <% if sophtron_item.last_synced_at %> + <% if sophtron_item.sync_status_summary %> + <%= t(".status_with_summary", timestamp: time_ago_in_words(sophtron_item.last_synced_at), summary: sophtron_item.sync_status_summary) %> + <% else %> + <%= t(".status", timestamp: time_ago_in_words(sophtron_item.last_synced_at)) %> + <% end %> + <% else %> + <%= t(".status_never") %> + <% end %> +

+ <% end %> +
-
-
- <% if manual_sync_required || Rails.env.development? %> - <%= render DS::Button.new( - variant: :icon, - icon: "refresh-cw", - href: sync_sophtron_item_path(sophtron_item), - frame: (manual_sync_required ? "modal" : nil), - title: t(".sync_now") - ) %> - <% end %> +
+ <% if manual_sync_required || Rails.env.development? %> + <%= render DS::Button.new( + variant: :icon, + icon: "refresh-cw", + href: sync_sophtron_item_path(sophtron_item), + frame: (manual_sync_required ? "modal" : nil), + title: t(".sync_now") + ) %> + <% end %> - <%= render DS::Menu.new do |menu| %> - <% if connected_institution_options.many? %> - <% connected_institution_options.each do |institution| %> - <% institution_manual_sync_required = sophtron_item.manual_sync_required_for_institution?(institution[:institution_key]) %> + <%= render DS::Menu.new do |menu| %> + <% if connected_institution_options.many? %> + <% connected_institution_options.each do |institution| %> + <% institution_manual_sync_required = sophtron_item.manual_sync_required_for_institution?(institution[:institution_key]) %> + <% menu.with_item( + variant: "button", + text: t(institution_manual_sync_required ? ".automatic_sync_for" : ".manual_sync_action_for", institution: institution[:name]), + icon: institution_manual_sync_required ? "refresh-cw" : "pause-circle", + href: toggle_manual_sync_sophtron_item_path(sophtron_item, institution_key: institution[:institution_key]), + method: :post + ) %> + <% end %> + <% else %> <% menu.with_item( variant: "button", - text: t(institution_manual_sync_required ? ".automatic_sync_for" : ".manual_sync_action_for", institution: institution[:name]), - icon: institution_manual_sync_required ? "refresh-cw" : "pause-circle", - href: toggle_manual_sync_sophtron_item_path(sophtron_item, institution_key: institution[:institution_key]), + text: t(manual_sync_required ? ".automatic_sync" : ".manual_sync_action"), + icon: manual_sync_required ? "refresh-cw" : "pause-circle", + href: toggle_manual_sync_sophtron_item_path(sophtron_item), method: :post ) %> <% end %> - <% else %> <% menu.with_item( variant: "button", - text: t(manual_sync_required ? ".automatic_sync" : ".manual_sync_action"), - icon: manual_sync_required ? "refresh-cw" : "pause-circle", - href: toggle_manual_sync_sophtron_item_path(sophtron_item), - method: :post + text: t(".delete"), + icon: "trash-2", + href: sophtron_item_path(sophtron_item), + method: :delete, + confirm: CustomConfirm.for_resource_deletion(provider_display_name, high_severity: true) ) %> <% end %> - <% menu.with_item( - variant: "button", - text: t(".delete"), - icon: "trash-2", - href: sophtron_item_path(sophtron_item), - method: :delete, - confirm: CustomConfirm.for_resource_deletion(provider_display_name, high_severity: true) - ) %> - <% end %> +
-
+ <% end %> <% unless sophtron_item.scheduled_for_deletion? %>
@@ -154,5 +156,5 @@ <% end %>
<% end %> -
+ <% end %> <% end %> diff --git a/app/views/splits/_category_select.html.erb b/app/views/splits/_category_select.html.erb index 001919322..033a0915a 100644 --- a/app/views/splits/_category_select.html.erb +++ b/app/views/splits/_category_select.html.erb @@ -33,14 +33,15 @@ <% end %>