From 5a06b24231e90e7d5149f129e29f19d92c2d5ff4 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 14:36:30 +0200 Subject: [PATCH 01/10] Install rolemodel/turbo-confirm https://github.com/RoleModel/turbo-confirm --- .../javascript/solidus_admin/application.js | 1 + .../solidus_admin/rolemodel/turbo-confirm.js | 15 +++++++++++++++ .../vendor/@rolemodel--turbo-confirm.js | 18 ++++++++++++++++++ admin/config/importmap.rb | 3 +++ 4 files changed, 37 insertions(+) create mode 100644 admin/app/javascript/solidus_admin/rolemodel/turbo-confirm.js create mode 100644 admin/app/javascript/vendor/@rolemodel--turbo-confirm.js diff --git a/admin/app/javascript/solidus_admin/application.js b/admin/app/javascript/solidus_admin/application.js index 7e7a85ebe4a..6e5c0a253f5 100644 --- a/admin/app/javascript/solidus_admin/application.js +++ b/admin/app/javascript/solidus_admin/application.js @@ -2,3 +2,4 @@ import "@hotwired/turbo-rails" import "vendor/custom_elements" import "solidus_admin/controllers" import "solidus_admin/web_components/solidus_select" +import "solidus_admin/turbo-confirm" diff --git a/admin/app/javascript/solidus_admin/rolemodel/turbo-confirm.js b/admin/app/javascript/solidus_admin/rolemodel/turbo-confirm.js new file mode 100644 index 00000000000..e7b45a362d8 --- /dev/null +++ b/admin/app/javascript/solidus_admin/rolemodel/turbo-confirm.js @@ -0,0 +1,15 @@ +import TC from "@rolemodel/turbo-confirm" + +TC.start({ + messageSlotSelector: ".modal-title", + contentSlots: { + body: { + contentAttribute: "confirm-details", + slotSelector: ".modal-body" + }, + acceptText: { + contentAttribute: "confirm-button", + slotSelector: "#confirm-accept" + } + } +}); diff --git a/admin/app/javascript/vendor/@rolemodel--turbo-confirm.js b/admin/app/javascript/vendor/@rolemodel--turbo-confirm.js new file mode 100644 index 00000000000..b040dde8c5d --- /dev/null +++ b/admin/app/javascript/vendor/@rolemodel--turbo-confirm.js @@ -0,0 +1,18 @@ +// @rolemodel/turbo-confirm@2.1.1 downloaded from https://ga.jspm.io/npm:@rolemodel/turbo-confirm@2.1.1/src/index.js + +const dispatch=(t,e=document,{bubbles:o=true,cancelable:n=true,prefix:i="rms",detail:r}={})=>{const s=new CustomEvent(`${i}:${t}`,{bubbles:o,cancelable:n,detail:r});e.dispatchEvent(s);return!s.defaultPrevented};class TurboConfirmError extends Error{name="TurboConfirmError";static missingDialog(t,e){return new this(`No element matching dialogSelector: '${t}'`,{cause:e})}static noTurbo(){return new this('Turbo is not defined. Be sure to import "@hotwired/turbo-rails" before calling the `start()` function')}}class ConfirmationController{initialContent;#t;constructor(t){this.delegate=t;this.accept=this.accept.bind(this);this.deny=this.deny.bind(this)}showConfirm(t){this.#e();for(const[e,o]of Object.entries(t)){const t=this.element.querySelector(e);t&&o&&(t.innerHTML=o)}this.#o();this.delegate.showConfirm(this.element);return new Promise((t=>this.#t=t))}accept(){this.#t(true);this.#n()}deny(){this.#t(false);this.#n()}get acceptButtons(){return this.element.querySelectorAll(this.delegate.acceptSelector)}get denyButtons(){return this.element.querySelectorAll(this.delegate.denySelector)}get element(){return document.querySelector(this.delegate.dialogSelector)}#n(){this.#t=null;this.delegate.hideConfirm(this.element);this.#i();setTimeout(this.#r.bind(this),this.delegate.animationDuration)}#o(){this.acceptButtons.forEach((t=>t.addEventListener("click",this.accept)));this.denyButtons.forEach((t=>t.addEventListener("click",this.deny)));this.element.addEventListener("cancel",this.deny)}#i(){this.acceptButtons.forEach((t=>t.removeEventListener("click",this.accept)));this.denyButtons.forEach((t=>t.removeEventListener("click",this.deny)));this.element.removeEventListener("cancel",this.deny)}#e(){try{this.initialContent=this.element.innerHTML}catch(t){throw TurboConfirmError.missingDialog(this.delegate.dialogSelector,t)}}#r(){try{this.element.innerHTML=this.initialContent}catch{}}}class TurboConfirm{#s;#c={dialogSelector:"#confirm",activeClass:"modal--active",acceptSelector:"#confirm-accept",denySelector:".confirm-cancel",animationDuration:300,showConfirmCallback:t=>t.showModal&&t.showModal(),hideConfirmCallback:t=>t.close&&t.close(),messageSlotSelector:"#confirm-title",contentSlots:{body:{contentAttribute:"confirm-details",slotSelector:"#confirm-body"},acceptText:{contentAttribute:"confirm-button",slotSelector:"#confirm-accept"}}};constructor(t={}){for(const[e,o]of Object.entries(t))this.#c[e]=o;this.#s=new ConfirmationController(this)} +/** + * Present a confirmation challenge to the user. + * @public + * @param {string} [message] - The main challenge message; Value of `data-turbo-confirm` attribute. + * @param {HTMLFormElement} [_formElement] - (ignored) `form` element that contains the submitter. + * @param {HTMLElement} [submitter] - button of input of type submit that triggered the form submission. + * @returns {Promise} - A promise that resolves to true if the user accepts the challenge or false if they deny it. + */confirm(t,e,o){const n=this.#l(o);const i=this.#a(t,n);return this.confirmWithContent(i)} +/** + * Present a confirmation challenge to the user. + * @public + * @param {Object} contentMap - A map of CSS selectors to HTML content to be inserted into the dialog. + * @returns {Promise} - A promise that resolves to true if the user accepts the challenge or false if they deny it. + */confirmWithContent(t){return this.#s.showConfirm(t)}showConfirm(t){t.classList.add(this.#c.activeClass);typeof this.#c.showConfirmCallback==="function"&&this.#c.showConfirmCallback(t)}hideConfirm(t){t.classList.remove(this.#c.activeClass);typeof this.#c.hideConfirmCallback==="function"&&this.#c.hideConfirmCallback(t)}get dialogSelector(){return this.#c.dialogSelector}get acceptSelector(){return this.#c.acceptSelector}get denySelector(){return this.#c.denySelector}get animationDuration(){return this.#c.animationDuration}#a(t,e){const o={};t&&(o[this.#c.messageSlotSelector]=t);if(e)for(const t of Object.keys(this.#c.contentSlots))o[this.#h(t)]=this.#f(t,e);return o}#h(t){return this.#c.contentSlots[t].slotSelector}#f(t,e){return e.getAttribute(`data-${this.#c.contentSlots[t].contentAttribute}`)}#l(t){const e=t??document.activeElement;return e.closest("[data-turbo-confirm]")}}const start=t=>{if(!window.Turbo)throw TurboConfirmError.noTurbo();const e=new TurboConfirm(t);const confirmationHandler=async(t,o,n)=>{const i=await e.confirm(t,o,n);dispatch(i?"confirm-accept":"confirm-reject",n);return i};window.Turbo.config?window.Turbo.config.forms.confirm=confirmationHandler:window.Turbo.setConfirmMethod(confirmationHandler)};var t={start:start};export{TurboConfirm,t as default}; + diff --git a/admin/config/importmap.rb b/admin/config/importmap.rb index 09ec1d8f7a5..32d6cbb194e 100644 --- a/admin/config/importmap.rb +++ b/admin/config/importmap.rb @@ -21,3 +21,6 @@ pin "tom-select", to: "https://ga.jspm.io/npm:tom-select@2.4.3/dist/esm/tom-select.complete.js" pin "@orchidjs/sifter", to: "https://ga.jspm.io/npm:@orchidjs/sifter@1.1.0/dist/esm/sifter.js" pin "@orchidjs/unicode-variants", to: "https://ga.jspm.io/npm:@orchidjs/unicode-variants@1.1.2/dist/esm/index.js" + +pin "@rolemodel/turbo-confirm", to: "vendor/@rolemodel--turbo-confirm.js" # @2.1.1 +pin "solidus_admin/turbo-confirm", to: "solidus_admin/rolemodel/turbo-confirm.js" From 1d1b3baab8e4f0e3d0e1a9899271ea39e030cd22 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 14:48:08 +0200 Subject: [PATCH 02/10] Create confirm modal component --- .../layout/confirm/component.html.erb | 12 +++++++++ .../solidus_admin/layout/confirm/component.rb | 25 +++++++++++++++++++ .../layout/confirm/component.yml | 4 +++ 3 files changed, 41 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..ea5a692da75 --- /dev/null +++ b/admin/app/components/solidus_admin/layout/confirm/component.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# Component wrapper for confirmation dialog to use with rolemodel/turbo-confirm. +# +# The modal is rendered in the layout initially hidden. +# To have it open to confirm 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: +# +# +# For more details see https://github.com/RoleModel/turbo-confirm. + +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 03d82b058101e65a6719a01afa833fed6a7f9225 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 14:38:15 +0200 Subject: [PATCH 03/10] 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 15b5fbdd8aa20703abb31310a985adb6324be7fc Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 15:01:14 +0200 Subject: [PATCH 04/10] Render confirmation modal in application layout --- admin/app/views/layouts/solidus_admin/application.html.erb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/admin/app/views/layouts/solidus_admin/application.html.erb b/admin/app/views/layouts/solidus_admin/application.html.erb index 6755eea3c1d..924d44cc321 100644 --- a/admin/app/views/layouts/solidus_admin/application.html.erb +++ b/admin/app/views/layouts/solidus_admin/application.html.erb @@ -36,5 +36,7 @@ <%= render component("ui/toast").new(text: message, scheme: key.to_sym == :error ? :error : :default) %> <% end %>
+ + <%= render component("layout/confirm").new %> From 1ec20a3f93f87623da5e0441416818f422288a8f Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 17:19:19 +0200 Subject: [PATCH 05/10] 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 cc4eef3281a..d34a6f3e9bb 100644 --- a/admin/app/components/solidus_admin/orders/cart/component.html.erb +++ b/admin/app/components/solidus_admin/orders/cart/component.html.erb @@ -62,8 +62,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 f8095763f04..2614fba5b20 100644 --- a/admin/app/components/solidus_admin/products/show/component.html.erb +++ b/admin/app/components/solidus_admin/products/show/component.html.erb @@ -143,8 +143,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 8187417f60878e23ce21ebe694b329711807511e Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 19:07:14 +0200 Subject: [PATCH 06/10] 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 d8396aa2629..1a02fca25ae 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 fe3607912db24539139d7dcaadf54ee4b3a9cfbf Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 19:22:40 +0200 Subject: [PATCH 07/10] 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 6f28ad553cb..a9774492432 100644 --- a/admin/lib/solidus_admin/testing_support/feature_helpers.rb +++ b/admin/lib/solidus_admin/testing_support/feature_helpers.rb @@ -61,6 +61,12 @@ def clear_search find('button[aria-label="Clear"]').click end 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 35948bf1d7eb53d9820aa9bd80c459a19462985e Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 19:23:10 +0200 Subject: [PATCH 08/10] 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 610bee167c2..55f70774585 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 6efb41af821..f3c99fe3112 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 cc9c6a5d7fc..e62d3f70cdf 100644 --- a/admin/spec/features/users_spec.rb +++ b/admin/spec/features/users_spec.rb @@ -82,11 +82,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 151273c83bef41887b3c9d58e23255db20c7b760 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Tue, 24 Jun 2025 19:42:50 +0200 Subject: [PATCH 09/10] 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..2743c734aad --- /dev/null +++ b/admin/spec/components/previews/solidus_admin/layout/confirm/component_preview.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# @component "ui/modal" +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 28d04f0983d82f1d9755fc0451fd556f52677045 Mon Sep 17 00:00:00 2001 From: Eugene Chaikin Date: Wed, 25 Jun 2025 18:29:41 +0200 Subject: [PATCH 10/10] Set animation duration to immediate Default animation duration was 300ms. It controls how long turbo-control will wait before restoring default confirm modal content (empty template). Due to that there could be a condition if you click fast enough when requesting a different confirm dialog, e.g. in products table clicking "Delete", closing modal and then clicking "Deactivate", then modal contents would be overwritten with previous modal contents. Since we don't use any kind of animation for modals we can put 0 duration to mitigate that bug. --- admin/app/javascript/solidus_admin/rolemodel/turbo-confirm.js | 1 + 1 file changed, 1 insertion(+) diff --git a/admin/app/javascript/solidus_admin/rolemodel/turbo-confirm.js b/admin/app/javascript/solidus_admin/rolemodel/turbo-confirm.js index e7b45a362d8..fbbabad5c49 100644 --- a/admin/app/javascript/solidus_admin/rolemodel/turbo-confirm.js +++ b/admin/app/javascript/solidus_admin/rolemodel/turbo-confirm.js @@ -1,6 +1,7 @@ import TC from "@rolemodel/turbo-confirm" TC.start({ + animationDuration: 0, messageSlotSelector: ".modal-title", contentSlots: { body: {