From 5cae3b5d413c4684c2614b255252d99dd6e5a99a Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 14:48:08 +0200 Subject: [PATCH 01/12] Create confirm modal component --- .../layout/confirm/component.html.erb | 12 +++++++++ .../solidus_admin/layout/confirm/component.rb | 26 +++++++++++++++++++ .../layout/confirm/component.yml | 4 +++ 3 files changed, 42 insertions(+) create mode 100644 admin/app/components/solidus_admin/layout/confirm/component.html.erb create mode 100644 admin/app/components/solidus_admin/layout/confirm/component.rb create mode 100644 admin/app/components/solidus_admin/layout/confirm/component.yml diff --git a/admin/app/components/solidus_admin/layout/confirm/component.html.erb b/admin/app/components/solidus_admin/layout/confirm/component.html.erb new file mode 100644 index 00000000000..68ebe250857 --- /dev/null +++ b/admin/app/components/solidus_admin/layout/confirm/component.html.erb @@ -0,0 +1,12 @@ +<%= render component("ui/modal").new( + title: t(".title"), + open: false, + id: "confirm" +) do |modal| %> + <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t(".cancel"), class: "confirm-cancel") %> +
+ <%= render component("ui/button").new(text: t(".confirm"), id: "confirm-accept", scheme: :danger) %> + <% end %> +<% end %> diff --git a/admin/app/components/solidus_admin/layout/confirm/component.rb b/admin/app/components/solidus_admin/layout/confirm/component.rb new file mode 100644 index 00000000000..a8582c42ef9 --- /dev/null +++ b/admin/app/components/solidus_admin/layout/confirm/component.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Component wrapper for confirmation dialog. This component uses a modal which +# is rendered in the layout initially hidden. To have it open to confirm a +# user's action, place the "data-turbo-confirm" on the submitter (or any other +# element that supports "data-turbo-confirm" attribute: form, link with +# "data-turbo-method") with the text you want to have in the modal title: +# +#
+# +#
+# +#
+#
+# +# You can add more details in the body of the modal using "data-confirm-details" +# attribute: +# +# +# +# To customize "Confirm" button text use "data-confirm-button" attribute: +# +# +# +class SolidusAdmin::Layout::Confirm::Component < SolidusAdmin::BaseComponent +end diff --git a/admin/app/components/solidus_admin/layout/confirm/component.yml b/admin/app/components/solidus_admin/layout/confirm/component.yml new file mode 100644 index 00000000000..88d249a4706 --- /dev/null +++ b/admin/app/components/solidus_admin/layout/confirm/component.yml @@ -0,0 +1,4 @@ +en: + cancel: "Cancel" + confirm: "Confirm" + title: "Are you sure?" From 42dbe32130b9833e07c00ca90effcaeb7dad7a3c Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 14:38:15 +0200 Subject: [PATCH 02/12] Update UI modal component * adds possibility to conditionally open modal on connect - use stimulus value instead of Dialog's "open" attribute (applying attribute directly on dialog element is discouraged by HTML specification https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/open#value); * adds identifier classes to title ".modal-title" and body ".modal-body" so that turbo-confirm can target them correctly; * adds "empty:hidden" so that when no content is passed the empty div does not take space in the modal; --- .../components/solidus_admin/ui/modal/component.html.erb | 6 ++---- admin/app/components/solidus_admin/ui/modal/component.js | 8 +++++++- admin/app/components/solidus_admin/ui/modal/component.rb | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/admin/app/components/solidus_admin/ui/modal/component.html.erb b/admin/app/components/solidus_admin/ui/modal/component.html.erb index bf301c6cf51..4e6480dc5c9 100644 --- a/admin/app/components/solidus_admin/ui/modal/component.html.erb +++ b/admin/app/components/solidus_admin/ui/modal/component.html.erb @@ -11,7 +11,7 @@
-

+

@@ -23,9 +23,7 @@
-
- <%= content %> -
+ <% if actions? %>
diff --git a/admin/app/components/solidus_admin/ui/modal/component.js b/admin/app/components/solidus_admin/ui/modal/component.js index 193ad2b6fd6..dd8ab94d546 100644 --- a/admin/app/components/solidus_admin/ui/modal/component.js +++ b/admin/app/components/solidus_admin/ui/modal/component.js @@ -1,7 +1,13 @@ import { Controller } from "@hotwired/stimulus" export default class extends Controller { + static values = { + openOnConnect: { type: Boolean, default: true } + }; + connect() { - this.element.showModal() + if (this.openOnConnectValue) { + this.element.showModal(); + } } } diff --git a/admin/app/components/solidus_admin/ui/modal/component.rb b/admin/app/components/solidus_admin/ui/modal/component.rb index 7dae58a192d..d51e2e36dc9 100644 --- a/admin/app/components/solidus_admin/ui/modal/component.rb +++ b/admin/app/components/solidus_admin/ui/modal/component.rb @@ -3,10 +3,10 @@ class SolidusAdmin::UI::Modal::Component < SolidusAdmin::BaseComponent renders_one :actions - def initialize(title:, close_path: nil, open: false, **attributes) + def initialize(title:, close_path: nil, open: true, **attributes) @title = title @close_path = close_path @attributes = attributes - @attributes[:open] = open + @attributes.merge! stimulus_value(name: "open-on-connect", value: open) end end From 4b3a9cf3f04482cc445bfece7601bc512967f853 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 15:01:14 +0200 Subject: [PATCH 03/12] Render confirmation modal in application layout --- admin/app/views/layouts/solidus_admin/application.html.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/admin/app/views/layouts/solidus_admin/application.html.erb b/admin/app/views/layouts/solidus_admin/application.html.erb index 5508ea9da15..41bc8def781 100644 --- a/admin/app/views/layouts/solidus_admin/application.html.erb +++ b/admin/app/views/layouts/solidus_admin/application.html.erb @@ -33,5 +33,6 @@ <%= render component("layout/flashes/alerts").new(alerts:) %> <%= render component("layout/flashes/toasts").new(toasts:) %> + <%= render component("layout/confirm").new %> From 3ced6312ba60e1841a2de04d3d7262714a22797a Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 17:19:19 +0200 Subject: [PATCH 04/12] Update existing confirmation usages Updates several components to use "data-turbo-confirm" and new confirmation dialog instead of native browser confirm. Removes redundant confirm_controller.js. --- .../orders/cart/component.html.erb | 3 +-- .../products/show/component.html.erb | 3 +-- .../solidus_admin/products/show/component.js | 9 -------- .../users/edit/api_access/component.html.erb | 10 +++++---- .../users/edit/api_access/component.js | 9 -------- .../users/edit/api_access/component.yml | 10 +++++++-- .../controllers/confirm_controller.js | 21 ------------------- 7 files changed, 16 insertions(+), 49 deletions(-) delete mode 100644 admin/app/components/solidus_admin/products/show/component.js delete mode 100644 admin/app/components/solidus_admin/users/edit/api_access/component.js delete mode 100644 admin/app/javascript/solidus_admin/controllers/confirm_controller.js diff --git a/admin/app/components/solidus_admin/orders/cart/component.html.erb b/admin/app/components/solidus_admin/orders/cart/component.html.erb index e290d3b719f..c866af803d4 100644 --- a/admin/app/components/solidus_admin/orders/cart/component.html.erb +++ b/admin/app/components/solidus_admin/orders/cart/component.html.erb @@ -61,8 +61,7 @@ size: :s, title: t("spree.delete"), icon: 'close-line', - "data-controller": "confirm", - "data-confirm-text-value": t("spree.are_you_sure"), + "data-turbo-confirm": t("spree.are_you_sure") ) %> <% end %> diff --git a/admin/app/components/solidus_admin/products/show/component.html.erb b/admin/app/components/solidus_admin/products/show/component.html.erb index d30146e7e5c..a88917e1eca 100644 --- a/admin/app/components/solidus_admin/products/show/component.html.erb +++ b/admin/app/components/solidus_admin/products/show/component.html.erb @@ -155,8 +155,7 @@ tag: :button, text: t(".delete"), scheme: :danger, - "data-action": "click->#{stimulus_id}#confirmDelete", - "data-#{stimulus_id}-message-param": t(".delete_confirmation"), + "data-turbo-confirm": t(".delete_confirmation") ) %> <% end %> <% end %> diff --git a/admin/app/components/solidus_admin/products/show/component.js b/admin/app/components/solidus_admin/products/show/component.js deleted file mode 100644 index fd490e3c1b7..00000000000 --- a/admin/app/components/solidus_admin/products/show/component.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Controller } from "@hotwired/stimulus" - -export default class extends Controller { - confirmDelete(event) { - if (!confirm(event.params.message)) { - event.preventDefault() - } - } -} diff --git a/admin/app/components/solidus_admin/users/edit/api_access/component.html.erb b/admin/app/components/solidus_admin/users/edit/api_access/component.html.erb index 96d4e00ed9a..a0249dfc94d 100644 --- a/admin/app/components/solidus_admin/users/edit/api_access/component.html.erb +++ b/admin/app/components/solidus_admin/users/edit/api_access/component.html.erb @@ -16,8 +16,9 @@ text: t('.clear_key'), scheme: :secondary, type: :submit, - "data-action": "click->#{stimulus_id}#confirm", - "data-#{stimulus_id}-message-param": t(".confirm_clear_key"), + "data-turbo-confirm": t(".confirm.title"), + "data-confirm-details": t(".confirm.clear.details"), + "data-confirm-button": t(".confirm.clear.button"), ) %> <% end %> @@ -26,8 +27,9 @@ text: t('.regenerate_key'), scheme: :secondary, type: :submit, - "data-action": "click->#{stimulus_id}#confirm", - "data-#{stimulus_id}-message-param": t(".confirm_regenerate_key"), + "data-turbo-confirm": t(".confirm.title"), + "data-confirm-details": t(".confirm.regenerate.details"), + "data-confirm-button": t(".confirm.regenerate.button") ) %> <% end %>
diff --git a/admin/app/components/solidus_admin/users/edit/api_access/component.js b/admin/app/components/solidus_admin/users/edit/api_access/component.js deleted file mode 100644 index 910294c5462..00000000000 --- a/admin/app/components/solidus_admin/users/edit/api_access/component.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Controller } from "@hotwired/stimulus" - -export default class extends Controller { - confirm(event) { - if (!confirm(event.params.message)) { - event.preventDefault() - } - } -} diff --git a/admin/app/components/solidus_admin/users/edit/api_access/component.yml b/admin/app/components/solidus_admin/users/edit/api_access/component.yml index e865ba47c30..820fa4832c2 100644 --- a/admin/app/components/solidus_admin/users/edit/api_access/component.yml +++ b/admin/app/components/solidus_admin/users/edit/api_access/component.yml @@ -6,5 +6,11 @@ en: clear_key: Clear key regenerate_key: Regenerate key hidden: Hidden - confirm_clear_key: Are you sure you want to clear this user's API key? It will invalidate the existing key. - confirm_regenerate_key: Are you sure you want to regenerate this user's API key? It will invalidate the existing key. + confirm: + title: Are you sure? + clear: + details: Are you sure you want to clear this user's API key? It will invalidate the existing key. + button: Clear + regenerate: + details: Are you sure you want to regenerate this user's API key? It will invalidate the existing key. + button: Regenerate diff --git a/admin/app/javascript/solidus_admin/controllers/confirm_controller.js b/admin/app/javascript/solidus_admin/controllers/confirm_controller.js deleted file mode 100644 index 60e5f3c0a6f..00000000000 --- a/admin/app/javascript/solidus_admin/controllers/confirm_controller.js +++ /dev/null @@ -1,21 +0,0 @@ -import { Controller } from "@hotwired/stimulus" - -export default class extends Controller { - static values = {"text": String} - - connect() { - this.element.addEventListener("click", this) - this.element.addEventListener("submit", this) - } - - disconnect() { - this.element.removeEventListener("click", this) - this.element.removeEventListener("submit", this) - } - - handleEvent(event) { - if (!confirm(this.textValue)) { - event.preventDefault() - } - } -} From 6e35b0954016c67e55e09c4e17db9504ede667b6 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 19:07:14 +0200 Subject: [PATCH 05/12] Update UI table batch actions to use turbo-confirm --- .../solidus_admin/ui/table/component.js | 24 +++++++++---------- .../solidus_admin/ui/table/component.rb | 10 ++++---- .../solidus_admin/ui/table/component.yml | 1 + 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/admin/app/components/solidus_admin/ui/table/component.js b/admin/app/components/solidus_admin/ui/table/component.js index 12f377e2c63..29506f0e763 100644 --- a/admin/app/components/solidus_admin/ui/table/component.js +++ b/admin/app/components/solidus_admin/ui/table/component.js @@ -15,6 +15,7 @@ export default class extends Controller { "batchHeader", "tableBody", "selectedRowsCount", + "batchActionButton", ] static classes = ["selectedRow"] @@ -123,19 +124,6 @@ export default class extends Controller { return this.checkboxTargets.filter((checkbox) => checkbox.checked) } - confirmAction(event) { - const message = event.params.message - .replace("${count}", this.selectedRows().length) - .replace( - "${resource}", - this.selectedRows().length > 1 ? event.params.resourcePlural : event.params.resourceSingular - ) - - if (!confirm(message)) { - event.preventDefault() - } - } - render() { const selectedRows = this.selectedRows() @@ -172,5 +160,15 @@ export default class extends Controller { checkbox.checked = true else if (selectedRows.length > 0) checkbox.indeterminate = true }) + + // Update confirmation text + this.batchActionButtonTargets.forEach((button) => { + button.dataset.confirmDetails = button.dataset.confirmationTemplate + .replace("${count}", selectedRows.length) + .replace( + "${resource}", + this.selectedRows().length > 1 ? button.dataset.resourcePlural : button.dataset.resourceSingular, + ); + }); } } diff --git a/admin/app/components/solidus_admin/ui/table/component.rb b/admin/app/components/solidus_admin/ui/table/component.rb index d5c09018e50..5d0d29ad3b4 100644 --- a/admin/app/components/solidus_admin/ui/table/component.rb +++ b/admin/app/components/solidus_admin/ui/table/component.rb @@ -117,13 +117,15 @@ def render_batch_action_button(batch_action) } if batch_action.require_confirmation - params["data-action"] = "click->#{stimulus_id}#confirmAction" - params["data-#{stimulus_id}-message-param"] = t( + params["data-turbo-confirm"] = t(".are_you_sure") + params["data-confirmation-template"] = t( ".action_confirmation", action: batch_action.label.downcase ) - params["data-#{stimulus_id}-resource-singular-param"] = @data.singular_name.downcase - params["data-#{stimulus_id}-resource-plural-param"] = @data.plural_name.downcase + params["data-confirm-button"] = batch_action.label + params["data-resource-singular"] = @data.singular_name.downcase + params["data-resource-plural"] = @data.plural_name.downcase + params.merge! stimulus_target("batchActionButton") end render component("ui/button").new(**params) diff --git a/admin/app/components/solidus_admin/ui/table/component.yml b/admin/app/components/solidus_admin/ui/table/component.yml index 0b43b9cf759..61d4362418d 100644 --- a/admin/app/components/solidus_admin/ui/table/component.yml +++ b/admin/app/components/solidus_admin/ui/table/component.yml @@ -1,4 +1,5 @@ en: + are_you_sure: "Are you sure?" no_resources_found: "No %{resources} found" rows_selected: 'selected' select_all: 'Select all' From f4c6213c681e09e8c7344ff45f09bb1910bcb407 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 19:22:40 +0200 Subject: [PATCH 06/12] Add #accept_turbo_confirm feature helper --- admin/lib/solidus_admin/testing_support/feature_helpers.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/admin/lib/solidus_admin/testing_support/feature_helpers.rb b/admin/lib/solidus_admin/testing_support/feature_helpers.rb index b3d04bc97d7..4b356f77650 100644 --- a/admin/lib/solidus_admin/testing_support/feature_helpers.rb +++ b/admin/lib/solidus_admin/testing_support/feature_helpers.rb @@ -77,6 +77,12 @@ def clear_search def solidus_select_control(field) find_field(field, visible: :all).ancestor(".control") end + + def accept_turbo_confirm(title) + yield + dialog = find("dialog", text: title) + within(dialog) { find_button(id: "confirm-accept").click } + end end end end From d211fd78810aee48ac80839ecd9c2808f971d3a4 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 19:23:10 +0200 Subject: [PATCH 07/12] Update feature tests to pass with turbo-confirm --- admin/spec/features/orders/show_spec.rb | 2 +- admin/spec/features/products_spec.rb | 6 +++--- admin/spec/features/users_spec.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/admin/spec/features/orders/show_spec.rb b/admin/spec/features/orders/show_spec.rb index bb49cf173d6..25ec64d8e51 100644 --- a/admin/spec/features/orders/show_spec.rb +++ b/admin/spec/features/orders/show_spec.rb @@ -131,7 +131,7 @@ expect(Spree::Order.last.line_items.last.quantity).to eq(4) - accept_confirm("Are you sure?") { click_on "Delete" } + accept_turbo_confirm("Are you sure?") { click_on "Delete" } expect(page).to have_content("Line item removed successfully", wait: 5) expect(Spree::Order.last.line_items.count).to eq(0) diff --git a/admin/spec/features/products_spec.rb b/admin/spec/features/products_spec.rb index d46aeb9950c..1a64eba7040 100644 --- a/admin/spec/features/products_spec.rb +++ b/admin/spec/features/products_spec.rb @@ -30,7 +30,7 @@ visit "/admin/products" select_row("Just a product") - accept_confirm("Are you sure you want to delete 1 product?") do + accept_turbo_confirm("Are you sure you want to delete 1 product?") do click_button("Delete", wait: 5) end @@ -48,7 +48,7 @@ visit "/admin/products" find("main tbody tr:nth-child(2)").find("input").check - accept_confirm("Are you sure you want to discontinue 1 product?") do + accept_turbo_confirm("Are you sure you want to discontinue 1 product?") do click_button "Discontinue" end @@ -66,7 +66,7 @@ find("main tbody tr:nth-child(2)").find("input").check - accept_confirm("Are you sure you want to activate 1 product?") do + accept_turbo_confirm("Are you sure you want to activate 1 product?") do click_button "Activate" end diff --git a/admin/spec/features/users_spec.rb b/admin/spec/features/users_spec.rb index 86305e1d43d..021eda9e72d 100644 --- a/admin/spec/features/users_spec.rb +++ b/admin/spec/features/users_spec.rb @@ -83,11 +83,11 @@ expect(page).to have_content("Key generated") expect(page).to have_content("(hidden)") - click_on "Regenerate key" + accept_turbo_confirm("Are you sure?") { click_on "Regenerate key" } expect(page).to have_content("Key generated") expect(page).to have_content("(hidden)") - click_on "Clear key" + accept_turbo_confirm("Are you sure?") { click_on "Clear key" } expect(page).to have_content("Key cleared") expect(page).to have_content("No key") From 25f6ed7f875092f35bda332c1c9f6cefb3a56320 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 19:42:50 +0200 Subject: [PATCH 08/12] Add component preview --- .../layout/confirm/component_preview.rb | 13 +++++++++++++ .../component_preview/overview.html.erb | 18 ++++++++++++++++++ .../ui/modal/component_preview.rb | 4 ---- .../component_preview/with_actions.html.erb | 16 ---------------- .../layout/confirm/component_spec.rb | 9 +++++++++ .../solidus_admin/ui/modal/component_spec.rb | 1 - 6 files changed, 40 insertions(+), 21 deletions(-) create mode 100644 admin/spec/components/previews/solidus_admin/layout/confirm/component_preview.rb create mode 100644 admin/spec/components/previews/solidus_admin/layout/confirm/component_preview/overview.html.erb delete mode 100644 admin/spec/components/previews/solidus_admin/ui/modal/component_preview/with_actions.html.erb create mode 100644 admin/spec/components/solidus_admin/layout/confirm/component_spec.rb diff --git a/admin/spec/components/previews/solidus_admin/layout/confirm/component_preview.rb b/admin/spec/components/previews/solidus_admin/layout/confirm/component_preview.rb new file mode 100644 index 00000000000..d8119bb071e --- /dev/null +++ b/admin/spec/components/previews/solidus_admin/layout/confirm/component_preview.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# @component "layout/confirm" +class SolidusAdmin::Layout::Confirm::ComponentPreview < ViewComponent::Preview + include SolidusAdmin::Preview + + # @param title text + # @param body text + # @param button text + def overview(title: "Are you sure?", body: "You are about to delete something. This cannot be undone.", button: "Confirm") + render_with_template(locals: { title:, body:, button: }) + end +end diff --git a/admin/spec/components/previews/solidus_admin/layout/confirm/component_preview/overview.html.erb b/admin/spec/components/previews/solidus_admin/layout/confirm/component_preview/overview.html.erb new file mode 100644 index 00000000000..25a117dfd02 --- /dev/null +++ b/admin/spec/components/previews/solidus_admin/layout/confirm/component_preview/overview.html.erb @@ -0,0 +1,18 @@ +
+
+
+ <%= render component("ui/button").new( + type: :submit, + scheme: :secondary, + text: "Summon confirmation modal", + data: { + "turbo-confirm": title, + "confirm-details": body, + "confirm-button": button + } + ) %> +
+
+ + <%= render current_component.new %> +
diff --git a/admin/spec/components/previews/solidus_admin/ui/modal/component_preview.rb b/admin/spec/components/previews/solidus_admin/ui/modal/component_preview.rb index 5ee9f4709a2..abb4773c007 100644 --- a/admin/spec/components/previews/solidus_admin/ui/modal/component_preview.rb +++ b/admin/spec/components/previews/solidus_admin/ui/modal/component_preview.rb @@ -11,8 +11,4 @@ def with_text def with_form render_with_template end - - def with_actions - render_with_template - end end diff --git a/admin/spec/components/previews/solidus_admin/ui/modal/component_preview/with_actions.html.erb b/admin/spec/components/previews/solidus_admin/ui/modal/component_preview/with_actions.html.erb deleted file mode 100644 index 28ef8b9fc58..00000000000 --- a/admin/spec/components/previews/solidus_admin/ui/modal/component_preview/with_actions.html.erb +++ /dev/null @@ -1,16 +0,0 @@ -
-
- With Actions -
- - <%= render current_component.new(title: 'Delete view?', open: true) do |component| %> -

- This can't be undone. T-shirt SM view will no longer be available in your - admin! -

- <% component.with_actions do %> - <%= render component("ui/button").new(text: t('.close'), scheme: :secondary) %> - <%= render component("ui/button").new(scheme: :primary, text: "Delete") %> - <% end %> - <% end %> -
diff --git a/admin/spec/components/solidus_admin/layout/confirm/component_spec.rb b/admin/spec/components/solidus_admin/layout/confirm/component_spec.rb new file mode 100644 index 00000000000..664d7d227f2 --- /dev/null +++ b/admin/spec/components/solidus_admin/layout/confirm/component_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusAdmin::Layout::Confirm::Component, type: :component do + it "renders the overview preview" do + render_preview(:overview) + end +end diff --git a/admin/spec/components/solidus_admin/ui/modal/component_spec.rb b/admin/spec/components/solidus_admin/ui/modal/component_spec.rb index 6d27081c71f..399cbb13afa 100644 --- a/admin/spec/components/solidus_admin/ui/modal/component_spec.rb +++ b/admin/spec/components/solidus_admin/ui/modal/component_spec.rb @@ -6,6 +6,5 @@ it "renders the overview preview" do render_preview(:with_text) render_preview(:with_form) - render_preview(:with_actions) end end From 988b6fd898390502d440027efdeb392982bc7dd4 Mon Sep 17 00:00:00 2001 From: Chris Todorov Date: Fri, 24 Jul 2026 11:46:57 -0700 Subject: [PATCH 09/12] Wire up a custom Turbo confirm handler Use plain DOM calls to update the confirm dialog markup and show the confirmation modal. This change also pins the new module in the importmap so the bare specifier resolves. Co-authored-by: Senem Soy Co-Authored-By: Claude Sonnet 5 --- .../javascript/solidus_admin/application.js | 5 ++- .../javascript/solidus_admin/confirm_modal.js | 32 +++++++++++++++++++ admin/config/importmap.rb | 1 + 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 admin/app/javascript/solidus_admin/confirm_modal.js diff --git a/admin/app/javascript/solidus_admin/application.js b/admin/app/javascript/solidus_admin/application.js index 7e7a85ebe4a..c0f3c27011f 100644 --- a/admin/app/javascript/solidus_admin/application.js +++ b/admin/app/javascript/solidus_admin/application.js @@ -1,4 +1,7 @@ -import "@hotwired/turbo-rails" +import { Turbo } from "@hotwired/turbo-rails" import "vendor/custom_elements" import "solidus_admin/controllers" +import { openConfirmModal } from "solidus_admin/confirm_modal" import "solidus_admin/web_components/solidus_select" + +Turbo.config.forms.confirm = openConfirmModal diff --git a/admin/app/javascript/solidus_admin/confirm_modal.js b/admin/app/javascript/solidus_admin/confirm_modal.js new file mode 100644 index 00000000000..936e9bcfba0 --- /dev/null +++ b/admin/app/javascript/solidus_admin/confirm_modal.js @@ -0,0 +1,32 @@ +/* + * Opens a confirmation modal with the given message and options. + * This is used as the Turbo confirm modal replacement. It returns a + * Promise that resolves to true if the user confirms, or false if + * the user cancels. + * + * @param {string} message - The message to display in the confirmation modal. + * @param {HTMLFormElement} formElement - Form element that triggered the + * confirmation. + * @param {HTMLElement} submitter - The element that triggered the form + * submission. + * @returns {Promise} - A promise that resolves to true if the user + * confirms, or false if the user cancels. + */ +export function openConfirmModal(message, formElement, submitter) { + const dialog = document.getElementById("confirm") + const details = submitter?.dataset.confirmDetails ?? formElement?.dataset.confirmDetails ?? "" + const buttonText = submitter?.dataset.confirmButton ?? formElement?.dataset.confirmButton + const accept = dialog.querySelector("#confirm-accept") + + dialog.querySelector(".modal-title").textContent = message + dialog.querySelector(".modal-body").textContent = details + if (buttonText) accept.textContent = buttonText + + dialog.showModal() + + return new Promise((resolve) => { + const controller = new AbortController() + accept.addEventListener("click", () => { resolve(true); controller.abort(); dialog.close() }, { signal: controller.signal }) + dialog.addEventListener("close", () => { resolve(false); controller.abort() }, { signal: controller.signal }) + }) +} diff --git a/admin/config/importmap.rb b/admin/config/importmap.rb index 09ec1d8f7a5..6a96078059a 100644 --- a/admin/config/importmap.rb +++ b/admin/config/importmap.rb @@ -10,6 +10,7 @@ pin "@rails/request.js", to: "https://cdn.jsdelivr.net/npm/@rails/request.js@0.0.9/+esm" pin "solidus_admin/application", preload: true +pin "solidus_admin/confirm_modal" pin "solidus_admin/utils" pin "solidus_admin/tom-select", to: "solidus_admin/tom-select/tom-select.js" pin "vendor/custom_elements", preload: true From 14d27a2430a0c0ec1f247b0397a624e32c40fd69 Mon Sep 17 00:00:00 2001 From: Chris Todorov Date: Fri, 24 Jul 2026 19:48:17 -0700 Subject: [PATCH 10/12] Consolidate confirm modal behavior into a Stimulus controller Move DOM manipulation out of confirm_modal.js and into a proper component.js Stimulus controller for layout/confirm, attached via data-controller on a wrapper around the modal. confirm_modal.js is now a thin adapter that looks up the controller instance and calls its `open` method, matching how the rest of the admin's per-component JS is structured (e.g. ui/modal, ui/table). Co-Authored-By: Claude Sonnet 5 --- .../layout/confirm/component.html.erb | 28 +++++++++++-------- .../solidus_admin/layout/confirm/component.js | 21 ++++++++++++++ .../javascript/solidus_admin/confirm_modal.js | 18 ++++-------- 3 files changed, 43 insertions(+), 24 deletions(-) create mode 100644 admin/app/components/solidus_admin/layout/confirm/component.js diff --git a/admin/app/components/solidus_admin/layout/confirm/component.html.erb b/admin/app/components/solidus_admin/layout/confirm/component.html.erb index 68ebe250857..704851297a2 100644 --- a/admin/app/components/solidus_admin/layout/confirm/component.html.erb +++ b/admin/app/components/solidus_admin/layout/confirm/component.html.erb @@ -1,12 +1,18 @@ -<%= render component("ui/modal").new( - title: t(".title"), - open: false, - id: "confirm" -) do |modal| %> - <% modal.with_actions do %> -
- <%= render component("ui/button").new(scheme: :secondary, text: t(".cancel"), class: "confirm-cancel") %> -
- <%= render component("ui/button").new(text: t(".confirm"), id: "confirm-accept", scheme: :danger) %> +
+ <%= render component("ui/modal").new( + title: t(".title"), + open: false + ) do |modal| %> + <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t(".cancel")) %> +
+ <%= render component("ui/button").new( + text: t(".confirm"), + id: "confirm-accept", + scheme: :danger, + data: { "#{stimulus_id}-target": "accept" } + ) %> + <% end %> <% end %> -<% end %> +
diff --git a/admin/app/components/solidus_admin/layout/confirm/component.js b/admin/app/components/solidus_admin/layout/confirm/component.js new file mode 100644 index 00000000000..49e447b0b86 --- /dev/null +++ b/admin/app/components/solidus_admin/layout/confirm/component.js @@ -0,0 +1,21 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["accept"] + + open(message, { details = "", buttonText } = {}) { + const dialog = this.element.querySelector("dialog") + + dialog.querySelector(".modal-title").textContent = message + dialog.querySelector(".modal-body").textContent = details + if (buttonText) this.acceptTarget.textContent = buttonText + + dialog.showModal() + + return new Promise((resolve) => { + const controller = new AbortController() + this.acceptTarget.addEventListener("click", () => { resolve(true); controller.abort(); dialog.close() }, { signal: controller.signal }) + dialog.addEventListener("close", () => { resolve(false); controller.abort() }, { signal: controller.signal }) + }) + } +} diff --git a/admin/app/javascript/solidus_admin/confirm_modal.js b/admin/app/javascript/solidus_admin/confirm_modal.js index 936e9bcfba0..a4741280a81 100644 --- a/admin/app/javascript/solidus_admin/confirm_modal.js +++ b/admin/app/javascript/solidus_admin/confirm_modal.js @@ -1,3 +1,5 @@ +import { application } from "solidus_admin/controllers/application" + /* * Opens a confirmation modal with the given message and options. * This is used as the Turbo confirm modal replacement. It returns a @@ -13,20 +15,10 @@ * confirms, or false if the user cancels. */ export function openConfirmModal(message, formElement, submitter) { - const dialog = document.getElementById("confirm") + const element = document.getElementById("confirm") + const controller = application.getControllerForElementAndIdentifier(element, "layout--confirm") const details = submitter?.dataset.confirmDetails ?? formElement?.dataset.confirmDetails ?? "" const buttonText = submitter?.dataset.confirmButton ?? formElement?.dataset.confirmButton - const accept = dialog.querySelector("#confirm-accept") - - dialog.querySelector(".modal-title").textContent = message - dialog.querySelector(".modal-body").textContent = details - if (buttonText) accept.textContent = buttonText - - dialog.showModal() - return new Promise((resolve) => { - const controller = new AbortController() - accept.addEventListener("click", () => { resolve(true); controller.abort(); dialog.close() }, { signal: controller.signal }) - dialog.addEventListener("close", () => { resolve(false); controller.abort() }, { signal: controller.signal }) - }) + return controller.open(message, { details, buttonText }) } From 9130127f87bd50661348ec005f8765fb8ea6e39e Mon Sep 17 00:00:00 2001 From: Chris Todorov Date: Thu, 30 Jul 2026 14:03:09 -0700 Subject: [PATCH 11/12] Fix standard violations in new component preview Co-authored-by: Senem Soy --- .../previews/solidus_admin/layout/confirm/component_preview.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/spec/components/previews/solidus_admin/layout/confirm/component_preview.rb b/admin/spec/components/previews/solidus_admin/layout/confirm/component_preview.rb index d8119bb071e..8b5ba93a3ca 100644 --- a/admin/spec/components/previews/solidus_admin/layout/confirm/component_preview.rb +++ b/admin/spec/components/previews/solidus_admin/layout/confirm/component_preview.rb @@ -8,6 +8,6 @@ class SolidusAdmin::Layout::Confirm::ComponentPreview < ViewComponent::Preview # @param body text # @param button text def overview(title: "Are you sure?", body: "You are about to delete something. This cannot be undone.", button: "Confirm") - render_with_template(locals: { title:, body:, button: }) + render_with_template(locals: {title:, body:, button:}) end end From 827680efecd186188aa06e0bc71f06ef399321a9 Mon Sep 17 00:00:00 2001 From: Chris Todorov Date: Tue, 18 Aug 2026 11:37:05 -0700 Subject: [PATCH 12/12] Support older version of Turbo for confirm modal `Turbo.config` was introduced in version 8.0.6[^1]. This change ensures that apps using older versions of Turbo can still get the custom confirmation modal behaviour and not error out. [^1]: https://github.com/hotwired/turbo/releases/tag/8.0.6 --- admin/app/javascript/solidus_admin/application.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/admin/app/javascript/solidus_admin/application.js b/admin/app/javascript/solidus_admin/application.js index c0f3c27011f..2a8d70652d6 100644 --- a/admin/app/javascript/solidus_admin/application.js +++ b/admin/app/javascript/solidus_admin/application.js @@ -4,4 +4,8 @@ import "solidus_admin/controllers" import { openConfirmModal } from "solidus_admin/confirm_modal" import "solidus_admin/web_components/solidus_select" -Turbo.config.forms.confirm = openConfirmModal +if (Turbo.config) { + Turbo.config.forms.confirm = openConfirmModal +} else { + window.Turbo.setConfirmMethod(openConfirmModal) +}