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..704851297a2 --- /dev/null +++ b/admin/app/components/solidus_admin/layout/confirm/component.html.erb @@ -0,0 +1,18 @@ +
+ <%= 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 %> +
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/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?" 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/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/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/application.js b/admin/app/javascript/solidus_admin/application.js index 7e7a85ebe4a..2a8d70652d6 100644 --- a/admin/app/javascript/solidus_admin/application.js +++ b/admin/app/javascript/solidus_admin/application.js @@ -1,4 +1,11 @@ -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" + +if (Turbo.config) { + Turbo.config.forms.confirm = openConfirmModal +} else { + window.Turbo.setConfirmMethod(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..a4741280a81 --- /dev/null +++ b/admin/app/javascript/solidus_admin/confirm_modal.js @@ -0,0 +1,24 @@ +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 + * 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 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 + + return controller.open(message, { details, buttonText }) +} 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() - } - } -} 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 %> 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 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 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..8b5ba93a3ca --- /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 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")