From 01cf1404b5fae0250fd377ddac10b7e165be4f58 Mon Sep 17 00:00:00 2001 From: andrea longhi Date: Wed, 28 Feb 2024 15:43:03 +0100 Subject: [PATCH 1/9] Fix tax category filtering We need to whitelist the ransackable attributes used on the new admin tax categories page, or filtering won't work. --- core/app/models/spree/tax_category.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/app/models/spree/tax_category.rb b/core/app/models/spree/tax_category.rb index f1d75da1910..d4dc8f857ff 100644 --- a/core/app/models/spree/tax_category.rb +++ b/core/app/models/spree/tax_category.rb @@ -4,6 +4,8 @@ module Spree class TaxCategory < Spree::Base include Spree::SoftDeletable + self.allowed_ransackable_attributes = %w[name description] + after_discard do self.tax_rate_tax_categories = [] end From 8f8a4be1c4ce972a0d40d311111576828911b2f3 Mon Sep 17 00:00:00 2001 From: andrea longhi Date: Wed, 28 Feb 2024 10:45:43 +0100 Subject: [PATCH 2/9] Fix tax category params The former ones were not correct, probably just a leftover from a cut and paste. --- .../app/controllers/solidus_admin/tax_categories_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/app/controllers/solidus_admin/tax_categories_controller.rb b/admin/app/controllers/solidus_admin/tax_categories_controller.rb index f5f0bc1378e..2567ee50228 100644 --- a/admin/app/controllers/solidus_admin/tax_categories_controller.rb +++ b/admin/app/controllers/solidus_admin/tax_categories_controller.rb @@ -34,7 +34,7 @@ def load_tax_category end def tax_category_params - params.require(:tax_category).permit(:tax_category_id, permitted_tax_category_attributes) + params.require(:tax_category).permit(:name, :description, :is_default, :tax_code) end end end From 187849644269ca7909f9fd29319cb00c14500798 Mon Sep 17 00:00:00 2001 From: andrea longhi Date: Mon, 4 Mar 2024 17:03:08 +0100 Subject: [PATCH 3/9] Rework modal component The component now behaves like a standard dialog: * closes on the current page, rather than redirecting elsewhere; * the close button is a standard dialog closing form; * the `open` attribute is removed, thus allowing ESC keypress to close the modal, and creating a focus trap when open. The previous behavior (i.e. have the modal open by default) is achieved via the new Stimulus controller, which on connect opens the dialog via JS. --- .../solidus_admin/ui/modal/component.html.erb | 14 +++++++------- .../components/solidus_admin/ui/modal/component.js | 7 +++++++ .../components/solidus_admin/ui/modal/component.rb | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 admin/app/components/solidus_admin/ui/modal/component.js 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 c9ce14bbf80..8b408161258 100644 --- a/admin/app/components/solidus_admin/ui/modal/component.html.erb +++ b/admin/app/components/solidus_admin/ui/modal/component.html.erb @@ -14,13 +14,13 @@

<%= @title %>

- <%= render component('ui/button').new( - tag: :a, - href: @close_path, - icon: 'close-line', - scheme: :ghost, - title: t('.close'), - ) %> +
+ <%= render component('ui/button').new( + icon: 'close-line', + scheme: :ghost, + title: t('.close'), + ) %> +
diff --git a/admin/app/components/solidus_admin/ui/modal/component.js b/admin/app/components/solidus_admin/ui/modal/component.js new file mode 100644 index 00000000000..3d9ec5c408e --- /dev/null +++ b/admin/app/components/solidus_admin/ui/modal/component.js @@ -0,0 +1,7 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + connect() { + 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 86f2401c04c..7dae58a192d 100644 --- a/admin/app/components/solidus_admin/ui/modal/component.rb +++ b/admin/app/components/solidus_admin/ui/modal/component.rb @@ -3,7 +3,7 @@ class SolidusAdmin::UI::Modal::Component < SolidusAdmin::BaseComponent renders_one :actions - def initialize(title:, close_path: nil, open: true, **attributes) + def initialize(title:, close_path: nil, open: false, **attributes) @title = title @close_path = close_path @attributes = attributes From dca0c68f54a246e679f31779d310567c86a0308e Mon Sep 17 00:00:00 2001 From: andrea longhi Date: Thu, 29 Feb 2024 11:47:28 +0100 Subject: [PATCH 4/9] Add turbo frames to page component This way we can inject arbitrary turbo frames into pages rendered via this component. --- .../solidus_admin/ui/pages/index/component.html.erb | 4 ++++ .../app/components/solidus_admin/ui/pages/index/component.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/admin/app/components/solidus_admin/ui/pages/index/component.html.erb b/admin/app/components/solidus_admin/ui/pages/index/component.html.erb index e9774650a8c..69916f08dd4 100644 --- a/admin/app/components/solidus_admin/ui/pages/index/component.html.erb +++ b/admin/app/components/solidus_admin/ui/pages/index/component.html.erb @@ -37,4 +37,8 @@ <% end %> <% end %> <% end %> + + <% turbo_frames.each do |frame| %> + <%= turbo_frame_tag frame %> + <% end %> <% end %> diff --git a/admin/app/components/solidus_admin/ui/pages/index/component.rb b/admin/app/components/solidus_admin/ui/pages/index/component.rb index 15d31a4089e..6ee519537bb 100644 --- a/admin/app/components/solidus_admin/ui/pages/index/component.rb +++ b/admin/app/components/solidus_admin/ui/pages/index/component.rb @@ -98,4 +98,8 @@ def render_sidebar page_with_sidebar_aside { sidebar } if sidebar end + + def turbo_frames + [] + end end From ef061448635ff10005065107c4e46c06bafb8332 Mon Sep 17 00:00:00 2001 From: andrea longhi Date: Tue, 27 Feb 2024 18:30:49 +0100 Subject: [PATCH 5/9] Show link to new tax category page The correct method name is `page_actions`, for this reason it was previously not showing. Also, href is changed to a new route from the new admin. --- .../solidus_admin/tax_categories/index/component.rb | 4 ++-- admin/config/routes.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/admin/app/components/solidus_admin/tax_categories/index/component.rb b/admin/app/components/solidus_admin/tax_categories/index/component.rb index 47ff3fb60bf..fc14ed906c1 100644 --- a/admin/app/components/solidus_admin/tax_categories/index/component.rb +++ b/admin/app/components/solidus_admin/tax_categories/index/component.rb @@ -13,11 +13,11 @@ def search_url solidus_admin.tax_categories_path end - def actions + def page_actions render component("ui/button").new( tag: :a, text: t('.add'), - href: spree.new_admin_tax_category_path, + href: solidus_admin.new_tax_category_path, icon: "add-line", class: "align-self-end w-full", ) diff --git a/admin/config/routes.rb b/admin/config/routes.rb index c938ea27de6..f8f729d68f6 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -51,7 +51,7 @@ admin_resources :option_types, only: [:index, :destroy], sortable: true admin_resources :taxonomies, only: [:index, :destroy], sortable: true admin_resources :promotion_categories, only: [:index, :destroy] - admin_resources :tax_categories, only: [:index, :destroy] + admin_resources :tax_categories, only: [:new, :index, :destroy] admin_resources :tax_rates, only: [:index, :destroy] admin_resources :payment_methods, only: [:index, :destroy], sortable: true admin_resources :stock_items, only: [:index, :edit, :update] From 565e58ad724daaf3db0ff1c8d42d8f8d76a2eb9c Mon Sep 17 00:00:00 2001 From: andrea longhi Date: Mon, 4 Mar 2024 16:24:10 +0100 Subject: [PATCH 6/9] Update turbo-rails gem to v2.0 The new version adds cool new features such as page morphing and refreshes. Specifically, we're going to use turbo stream refreshes in a later commit. --- admin/solidus_admin.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/solidus_admin.gemspec b/admin/solidus_admin.gemspec index 5878002997a..41867f53963 100644 --- a/admin/solidus_admin.gemspec +++ b/admin/solidus_admin.gemspec @@ -33,6 +33,6 @@ Gem::Specification.new do |s| s.add_dependency 'solidus_backend' s.add_dependency 'solidus_core', '> 4.2' s.add_dependency 'stimulus-rails', '~> 1.2' - s.add_dependency 'turbo-rails', '~> 1.4' + s.add_dependency 'turbo-rails', '~> 2.0' s.add_dependency 'view_component', '~> 3.9' end From 15899d4fd12d80c0c814701dc75c5625b71b06dd Mon Sep 17 00:00:00 2001 From: andrea longhi Date: Mon, 4 Mar 2024 17:36:56 +0100 Subject: [PATCH 7/9] Add TaxCategoriesController#new action The `new` action will render a modal dialog above a list of tax categories, like the `index` action. For this reason, the common controller code is extracted to a private method. --- .../tax_categories_controller.rb | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/admin/app/controllers/solidus_admin/tax_categories_controller.rb b/admin/app/controllers/solidus_admin/tax_categories_controller.rb index 2567ee50228..09ac37e3cbb 100644 --- a/admin/app/controllers/solidus_admin/tax_categories_controller.rb +++ b/admin/app/controllers/solidus_admin/tax_categories_controller.rb @@ -4,13 +4,18 @@ module SolidusAdmin class TaxCategoriesController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search - def index - tax_categories = apply_search_to( - Spree::TaxCategory.order(created_at: :desc, id: :desc), - param: :q, - ) + def new + @tax_category = Spree::TaxCategory.new - set_page_and_extract_portion_from(tax_categories) + set_index_page + + respond_to do |format| + format.html { render component('tax_categories/new').new(page: @page, tax_category: @tax_category) } + end + end + + def index + set_index_page respond_to do |format| format.html { render component('tax_categories/index').new(page: @page) } @@ -36,5 +41,14 @@ def load_tax_category def tax_category_params params.require(:tax_category).permit(:name, :description, :is_default, :tax_code) end + + def set_index_page + tax_categories = apply_search_to( + Spree::TaxCategory.order(created_at: :desc, id: :desc), + param: :q, + ) + + set_page_and_extract_portion_from(tax_categories) + end end end From bcbd4ebd4180303a40736a4ae9ced067a864fd0c Mon Sep 17 00:00:00 2001 From: andrea longhi Date: Tue, 27 Feb 2024 19:03:14 +0100 Subject: [PATCH 8/9] Add New Tax Category component The component renders: 1) a list of tax categories in the background; 2) a dialog modal with the form for creating a new resource. The modal is wrapped into a turbo frame tag, so it can be enclosed in other pages (i.e. the tax categories index) asynchronously. --- .../tax_categories/index/component.rb | 6 +++- .../tax_categories/new/component.html.erb | 28 +++++++++++++++++++ .../tax_categories/new/component.rb | 12 ++++++++ .../tax_categories/new/component.yml | 8 ++++++ 4 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 admin/app/components/solidus_admin/tax_categories/new/component.html.erb create mode 100644 admin/app/components/solidus_admin/tax_categories/new/component.rb create mode 100644 admin/app/components/solidus_admin/tax_categories/new/component.yml diff --git a/admin/app/components/solidus_admin/tax_categories/index/component.rb b/admin/app/components/solidus_admin/tax_categories/index/component.rb index fc14ed906c1..c435fb1da7c 100644 --- a/admin/app/components/solidus_admin/tax_categories/index/component.rb +++ b/admin/app/components/solidus_admin/tax_categories/index/component.rb @@ -17,12 +17,16 @@ def page_actions render component("ui/button").new( tag: :a, text: t('.add'), - href: solidus_admin.new_tax_category_path, + href: solidus_admin.new_tax_category_path, data: { turbo_frame: :new_tax_category_modal }, icon: "add-line", class: "align-self-end w-full", ) end + def turbo_frames + %w[new_tax_category_modal] + end + def search_key :name_or_description_cont end diff --git a/admin/app/components/solidus_admin/tax_categories/new/component.html.erb b/admin/app/components/solidus_admin/tax_categories/new/component.html.erb new file mode 100644 index 00000000000..b1a97dcc9be --- /dev/null +++ b/admin/app/components/solidus_admin/tax_categories/new/component.html.erb @@ -0,0 +1,28 @@ +<%= turbo_frame_tag :new_tax_category_modal do %> + <%= render component("ui/modal").new(title: t(".title")) do |modal| %> + <%= form_for @tax_category, url: solidus_admin.tax_categories_path(page: params[:page], q: params[:q]), html: { id: form_id } do |f| %> +
+ <%= render component("ui/forms/field").text_field(f, :name) %> + <%= render component("ui/forms/field").text_field(f, :tax_code) %> + <%= render component("ui/forms/field").text_field(f, :description) %> + +
+ <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> +
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %> + <% end %> + <% end %> + <% end %> +<% end %> + +<%= render component("tax_categories/index").new(page: @page) %> diff --git a/admin/app/components/solidus_admin/tax_categories/new/component.rb b/admin/app/components/solidus_admin/tax_categories/new/component.rb new file mode 100644 index 00000000000..d61ef1e3dd5 --- /dev/null +++ b/admin/app/components/solidus_admin/tax_categories/new/component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SolidusAdmin::TaxCategories::New::Component < SolidusAdmin::TaxCategories::Index::Component + def initialize(page:, tax_category:) + @page = page + @tax_category = tax_category + end + + def form_id + dom_id(@tax_category, "#{stimulus_id}_new_tax_category_form") + end +end diff --git a/admin/app/components/solidus_admin/tax_categories/new/component.yml b/admin/app/components/solidus_admin/tax_categories/new/component.yml new file mode 100644 index 00000000000..24706db9edf --- /dev/null +++ b/admin/app/components/solidus_admin/tax_categories/new/component.yml @@ -0,0 +1,8 @@ +# Add your component translations here. +# Use the translation in the example in your template with `t(".hello")`. +en: + title: "New Tax Category" + cancel: "Cancel" + submit: "Add Tax Category" + hints: + is_default: "When checked, this tax category will be selected by default when creating new products or variants." From 5584f931e8045a77055a82d90c76b9374dfaa575 Mon Sep 17 00:00:00 2001 From: andrea longhi Date: Mon, 4 Mar 2024 18:02:00 +0100 Subject: [PATCH 9/9] Complete tax category creation process The `#create` controller action allows the creation of a new tax category. Failed creation re-renders the form with errors within the turbo frame tag, while successful creation is handled via a turbo stream refresh tag that reloads the page. Scroll position is preserved thanks to the custom Turbo meta tag that enables the feature globally on the new admin. Integration specs are added to test the complete feature. --- .../tax_categories_controller.rb | 29 ++++++++++++++ .../solidus_admin/application.html.erb | 1 + admin/config/locales/tax_categories.en.yml | 2 + admin/config/routes.rb | 2 +- admin/spec/features/tax_categories_spec.rb | 39 +++++++++++++++++++ 5 files changed, 72 insertions(+), 1 deletion(-) diff --git a/admin/app/controllers/solidus_admin/tax_categories_controller.rb b/admin/app/controllers/solidus_admin/tax_categories_controller.rb index 09ac37e3cbb..cc9e833a2ba 100644 --- a/admin/app/controllers/solidus_admin/tax_categories_controller.rb +++ b/admin/app/controllers/solidus_admin/tax_categories_controller.rb @@ -14,6 +14,35 @@ def new end end + def create + @tax_category = Spree::TaxCategory.new(tax_category_params) + + if @tax_category.save + respond_to do |format| + flash[:notice] = t('.success') + + format.html do + redirect_to solidus_admin.tax_categories_path, status: :see_other + end + + format.turbo_stream do + # we need to explicitly write the refresh tag for now. + # See https://github.com/hotwired/turbo-rails/issues/579 + render turbo_stream: '' + end + end + else + set_index_page + + respond_to do |format| + format.html do + page_component = component('tax_categories/new').new(page: @page, tax_category: @tax_category) + render page_component, status: :unprocessable_entity + end + end + end + end + def index set_index_page diff --git a/admin/app/views/layouts/solidus_admin/application.html.erb b/admin/app/views/layouts/solidus_admin/application.html.erb index 4fa30ebdfd8..246051776a2 100644 --- a/admin/app/views/layouts/solidus_admin/application.html.erb +++ b/admin/app/views/layouts/solidus_admin/application.html.erb @@ -11,6 +11,7 @@ <%= stylesheet_link_tag SolidusAdmin::Config.theme_path(session[:admin_light_theme]), media: '(prefers-color-scheme: light)', "data-turbo-track": "reload" %> <%= stylesheet_link_tag SolidusAdmin::Config.theme_path(session[:admin_dark_theme]), media: '(prefers-color-scheme: dark)', "data-turbo-track": "reload" %> <%= javascript_importmap_tags "solidus_admin/application", shim: false, importmap: SolidusAdmin.importmap %> + diff --git a/admin/config/locales/tax_categories.en.yml b/admin/config/locales/tax_categories.en.yml index 9162bb44069..c86f9400f42 100644 --- a/admin/config/locales/tax_categories.en.yml +++ b/admin/config/locales/tax_categories.en.yml @@ -4,3 +4,5 @@ en: title: "Tax Categories" destroy: success: "Tax categories were successfully removed." + create: + success: "Tax category was successfully created." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index f8f729d68f6..37003949356 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -51,7 +51,7 @@ admin_resources :option_types, only: [:index, :destroy], sortable: true admin_resources :taxonomies, only: [:index, :destroy], sortable: true admin_resources :promotion_categories, only: [:index, :destroy] - admin_resources :tax_categories, only: [:new, :index, :destroy] + admin_resources :tax_categories, only: [:new, :index, :create, :destroy] admin_resources :tax_rates, only: [:index, :destroy] admin_resources :payment_methods, only: [:index, :destroy], sortable: true admin_resources :stock_items, only: [:index, :edit, :update] diff --git a/admin/spec/features/tax_categories_spec.rb b/admin/spec/features/tax_categories_spec.rb index cbc15b436c9..b91a24256dc 100644 --- a/admin/spec/features/tax_categories_spec.rb +++ b/admin/spec/features/tax_categories_spec.rb @@ -21,4 +21,43 @@ expect(Spree::TaxCategory.count).to eq(1) expect(page).to be_axe_clean end + + context "when creating a new tax category" do + let(:query) { "?page=1&q%5Bname_or_description_cont%5D=Cloth" } + + before do + visit "/admin/tax_categories#{query}" + click_on "Add new" + expect(page).to have_content("New Tax Category") + expect(page).to be_axe_clean + end + + it "opens a modal" do + expect(page).to have_selector("dialog") + within("dialog") { click_on "Cancel" } + expect(page).not_to have_selector("dialog") + expect(page.current_url).to include(query) + end + + context "with valid data" do + it "successfully creates a new tax category, keeping page and q params" do + fill_in "Name", with: "Clothing" + + click_on "Add Tax Category" + + expect(page).to have_content("Tax category was successfully created.") + expect(Spree::TaxCategory.find_by(name: "Clothing")).to be_present + expect(page.current_url).to include(query) + end + end + + context "with invalid data" do + it "fails to create a new tax category, keeping page and q params" do + click_on "Add Tax Category" + + expect(page).to have_content "can't be blank" + expect(page.current_url).to include(query) + end + end + end end