From 8264943f238b1ef1a466d0772252f66596337c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Mata?= Date: Sat, 14 Feb 2026 01:10:25 +0100 Subject: [PATCH] Show disabled import options when no accounts exist (#977) * Show disabled import options before accounts exist Keep account-dependent import choices visible on /imports/new and render them as disabled with guidance when no accounts are available. * Refactor disabled import options: extract partial, fix accessibility (#986) - Extract _import_option partial to eliminate duplicated enabled/disabled markup across TransactionImport, TradeImport, and MintImport (also used by AccountImport, CategoryImport, RuleImport for consistency) - Replace misleading chevron-right with lock icon in disabled state - Add aria-disabled="true" for screen reader accessibility - Remove redundant default: parameter from t() call - Fix locale key ordering (requires_account after import_* keys) - Fix extra blank line in test file - Add assertion for aria-disabled attribute in test https://claude.ai/code/session_016j9tDYEBfWX9Dzd99rAYjX Co-authored-by: Claude * Tailwind fixes --------- Co-authored-by: Claude --- app/views/imports/_import_option.html.erb | 37 +++++ app/views/imports/new.html.erb | 150 +++++++------------- config/locales/views/imports/en.yml | 1 + test/controllers/imports_controller_test.rb | 14 ++ 4 files changed, 101 insertions(+), 101 deletions(-) create mode 100644 app/views/imports/_import_option.html.erb diff --git a/app/views/imports/_import_option.html.erb b/app/views/imports/_import_option.html.erb new file mode 100644 index 000000000..b7c73ffa7 --- /dev/null +++ b/app/views/imports/_import_option.html.erb @@ -0,0 +1,37 @@ +<%# locals: (type:, label:, icon_name: nil, icon_bg_class: nil, icon_text_class: nil, enabled:, disabled_message: nil, button_class: "flex items-center justify-between p-4 group cursor-pointer w-full", image: nil) %> +
  • + <% if enabled %> + <%= button_to imports_path(import: { type: type }), class: button_class, data: { turbo: false } do %> +
    + <% if image %> + <%= image_tag(image, alt: "#{label} logo", class: "w-8 h-8 rounded-md") %> + <% else %> +
    + <%= icon(icon_name, color: "current") %> +
    + <% end %> + <%= label %> +
    + <%= icon("chevron-right") %> + <% end %> + <% else %> +
    +
    + <% if image %> + <%= image_tag(image, alt: "#{label} logo", class: "w-8 h-8 rounded-md") %> + <% else %> +
    + <%= icon(icon_name, color: "current") %> +
    + <% end %> +
    + <%= label %> + <%= disabled_message %> +
    +
    + <%= icon("lock", size: "sm", class: "text-secondary") %> +
    + <% end %> + + <%= render "shared/ruler" %> +
  • diff --git a/app/views/imports/new.html.erb b/app/views/imports/new.html.erb index 45ff29415..0b8c1a490 100644 --- a/app/views/imports/new.html.erb +++ b/app/views/imports/new.html.erb @@ -2,6 +2,9 @@ <% dialog.with_header(title: t(".title"), subtitle: t(".description")) %> <% dialog.with_body do %> + <% has_accounts = Current.family.accounts.any? %> + <% requires_account_message = t(".requires_account") %> +

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

      @@ -25,120 +28,65 @@ <% end %> - <% if Current.family.accounts.any? && (params[:type].nil? || params[:type] == "TransactionImport") %> -
    • - <%= button_to imports_path(import: { type: "TransactionImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %> -
      -
      - - <%= icon("file-spreadsheet", color: "current") %> - -
      - - <%= t(".import_transactions") %> - -
      - <%= icon("chevron-right") %> - <% end %> - - <%= render "shared/ruler" %> -
    • + <% if params[:type].nil? || params[:type] == "TransactionImport" %> + <%= render "imports/import_option", + type: "TransactionImport", + icon_name: "file-spreadsheet", + icon_bg_class: "bg-indigo-500/5", + icon_text_class: "text-indigo-500", + label: t(".import_transactions"), + enabled: has_accounts, + disabled_message: requires_account_message %> <% end %> - <% if Current.family.accounts.any? && (params[:type].nil? || params[:type] == "TradeImport") %> -
    • - <%= button_to imports_path(import: { type: "TradeImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %> -
      -
      - - <%= icon("square-percent", color: "current") %> - -
      - - <%= t(".import_portfolio") %> - -
      - <%= icon("chevron-right") %> - <% end %> - - <%= render "shared/ruler" %> -
    • + <% if params[:type].nil? || params[:type] == "TradeImport" %> + <%= render "imports/import_option", + type: "TradeImport", + icon_name: "square-percent", + icon_bg_class: "bg-yellow-500/5", + icon_text_class: "text-yellow-500", + label: t(".import_portfolio"), + enabled: has_accounts, + disabled_message: requires_account_message %> <% end %> <% if params[:type].nil? || params[:type] == "AccountImport" %> -
    • - <%= button_to imports_path(import: { type: "AccountImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %> -
      -
      - - <%= icon("building", color: "current") %> - -
      - - <%= t(".import_accounts") %> - -
      - <%= icon("chevron-right") %> - <% end %> - - <%= render "shared/ruler" %> -
    • + <%= render "imports/import_option", + type: "AccountImport", + icon_name: "building", + icon_bg_class: "bg-violet-500/5", + icon_text_class: "text-violet-500", + label: t(".import_accounts"), + enabled: true %> <% end %> <% if params[:type].nil? || params[:type] == "CategoryImport" %> -
    • - <%= button_to imports_path(import: { type: "CategoryImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %> -
      -
      - - <%= icon("shapes", color: "current") %> - -
      - - <%= t(".import_categories") %> - -
      - <%= icon("chevron-right") %> - <% end %> - - <%= render "shared/ruler" %> -
    • + <%= render "imports/import_option", + type: "CategoryImport", + icon_name: "shapes", + icon_bg_class: "bg-blue-500/5", + icon_text_class: "text-blue-500", + label: t(".import_categories"), + enabled: true %> <% end %> <% if params[:type].nil? || params[:type] == "RuleImport" %> -
    • - <%= button_to imports_path(import: { type: "RuleImport" }), class: "flex items-center justify-between p-4 group cursor-pointer w-full", data: { turbo: false } do %> -
      -
      - - <%= icon("workflow", color: "current") %> - -
      - - <%= t(".import_rules") %> - -
      - <%= icon("chevron-right") %> - <% end %> - - <%= render "shared/ruler" %> -
    • + <%= render "imports/import_option", + type: "RuleImport", + icon_name: "workflow", + icon_bg_class: "bg-green-500/5", + icon_text_class: "text-green-500", + label: t(".import_rules"), + enabled: true %> <% end %> - <% if Current.family.accounts.any? && (params[:type].nil? || params[:type] == "MintImport" || params[:type] == "TransactionImport") %> -
    • - <%= button_to imports_path(import: { type: "MintImport" }), class: "flex items-center justify-between p-4 group w-full", data: { turbo: false } do %> -
      - <%= image_tag("mint-logo.jpeg", alt: "Mint logo", class: "w-8 h-8 rounded-md") %> - - <%= t(".import_mint") %> - -
      - <%= icon("chevron-right") %> - <% end %> - - <%= render "shared/ruler" %> -
    • + <% if params[:type].nil? || params[:type] == "MintImport" || params[:type] == "TransactionImport" %> + <%= render "imports/import_option", + type: "MintImport", + image: "mint-logo.jpeg", + label: t(".import_mint"), + enabled: has_accounts, + disabled_message: requires_account_message %> <% end %> <% if (params[:type].nil? || params[:type].in?(%w[DocumentImport PdfImport])) && @document_upload_extensions.any? %> diff --git a/config/locales/views/imports/en.yml b/config/locales/views/imports/en.yml index 740f9b2bd..6be06ff26 100644 --- a/config/locales/views/imports/en.yml +++ b/config/locales/views/imports/en.yml @@ -104,6 +104,7 @@ en: import_transactions: Import transactions import_file: Import document import_file_description: AI-powered analysis for PDFs and searchable upload for other supported files + requires_account: Import accounts first to unlock this option. resume: Resume %{type} sources: Sources title: New Import diff --git a/test/controllers/imports_controller_test.rb b/test/controllers/imports_controller_test.rb index 8c3738519..72f04c1cf 100644 --- a/test/controllers/imports_controller_test.rb +++ b/test/controllers/imports_controller_test.rb @@ -23,6 +23,20 @@ class ImportsControllerTest < ActionDispatch::IntegrationTest assert_select "turbo-frame#modal" end + test "shows disabled account-dependent imports when family has no accounts" do + sign_in users(:empty) + + get new_import_url + + assert_response :success + assert_select "button", text: "Import accounts" + assert_select "button", text: "Import transactions", count: 0 + assert_select "button", text: "Import investments", count: 0 + assert_select "button", text: "Import from Mint", count: 0 + assert_select "span", text: "Import accounts first to unlock this option.", count: 3 + assert_select "div[aria-disabled=true]", count: 3 + end + test "creates import" do assert_difference "Import.count", 1 do post imports_url, params: {