diff --git a/admin/app/components/solidus_admin/base_component.rb b/admin/app/components/solidus_admin/base_component.rb index e39c1707a4b..37daa9fea48 100644 --- a/admin/app/components/solidus_admin/base_component.rb +++ b/admin/app/components/solidus_admin/base_component.rb @@ -9,6 +9,7 @@ class BaseComponent < ViewComponent::Base include SolidusAdmin::ComponentsHelper include SolidusAdmin::StimulusHelper include SolidusAdmin::VoidElementsHelper + include SolidusAdmin::SolidusFormHelper include Turbo::FramesHelper def icon_tag(name, **attrs) diff --git a/admin/app/components/solidus_admin/payment_methods/edit/component.html.erb b/admin/app/components/solidus_admin/payment_methods/edit/component.html.erb new file mode 100644 index 00000000000..1319c32e244 --- /dev/null +++ b/admin/app/components/solidus_admin/payment_methods/edit/component.html.erb @@ -0,0 +1,17 @@ +<%= page id: :payment_method_form do %> + <%= page_header do %> + <%= page_header_back(solidus_admin.payment_methods_path) %> + <%= page_header_title(t(".title")) %> + <%= page_header_actions do %> + <%= render component("ui/button").new( + tag: :a, + text: t(".discard"), + href: solidus_admin.payment_methods_path, + scheme: :secondary + ) %> + <%= render component("ui/button").new(tag: :button, text: t(".save"), form: form_id) %> + <% end %> + <% end %> + + <%= render component("payment_methods/form").new(payment_method: @resource, url: solidus_admin.payment_method_path(@resource), form_id:) %> +<% end %> diff --git a/admin/app/components/solidus_admin/payment_methods/edit/component.rb b/admin/app/components/solidus_admin/payment_methods/edit/component.rb new file mode 100644 index 00000000000..08034e8db8e --- /dev/null +++ b/admin/app/components/solidus_admin/payment_methods/edit/component.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class SolidusAdmin::PaymentMethods::Edit::Component < SolidusAdmin::Resources::Edit::Component + include SolidusAdmin::Layout::PageHelpers +end diff --git a/admin/app/components/solidus_admin/payment_methods/edit/component.yml b/admin/app/components/solidus_admin/payment_methods/edit/component.yml new file mode 100644 index 00000000000..4a30ee3de6a --- /dev/null +++ b/admin/app/components/solidus_admin/payment_methods/edit/component.yml @@ -0,0 +1,5 @@ +en: + back: "Back" + discard: "Discard" + save: "Save" + title: "Edit Payment Method" diff --git a/admin/app/components/solidus_admin/payment_methods/form/component.html.erb b/admin/app/components/solidus_admin/payment_methods/form/component.html.erb new file mode 100644 index 00000000000..fdc7f1ea1db --- /dev/null +++ b/admin/app/components/solidus_admin/payment_methods/form/component.html.erb @@ -0,0 +1,27 @@ +<%= solidus_form_for @payment_method, as: "payment_method", url: @url, html: { id: @form_id } do |f| %> + <%= page_with_sidebar do %> + <%= page_with_sidebar_main do %> + <%= render component("ui/panel").new do %> + <%= f.text_field :name %> + <%= f.text_field :description %> + <%= f.switch_field :auto_capture, hint: t(".hints.autocapture").html_safe %> + <% end %> + + <%= render component("ui/panel").new(title: t(".deployment")) do %> + <%= f.select :type, Rails.application.config.spree.payment_methods.map { [_1.model_name.human, _1.to_s] } %> + <%= f.select :preference_source, Spree::PaymentMethod.available_preference_sources, include_blank: t(".preference_source_none") %> + <%= f.text_field :preferred_server %> + <%= f.switch_field :preferred_test_mode, hint: t(".hints.test_mode").html_safe %> + <% end %> + <% end %> + + <%= page_with_sidebar_aside do %> + <%= render component("ui/panel").new(title: t(".availability")) do %> + <%= f.checkbox :active %> + <%= f.select :store_ids, Spree::Store.pluck(:name, :id), multiple: true %> + <%= f.checkbox :available_to_admin %> + <%= f.checkbox :available_to_users %> + <% end %> + <% end %> + <% end %> +<% end %> diff --git a/admin/app/components/solidus_admin/payment_methods/form/component.rb b/admin/app/components/solidus_admin/payment_methods/form/component.rb new file mode 100644 index 00000000000..114647b8966 --- /dev/null +++ b/admin/app/components/solidus_admin/payment_methods/form/component.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class SolidusAdmin::PaymentMethods::Form::Component < SolidusAdmin::BaseComponent + include SolidusAdmin::Layout::PageHelpers + + def initialize(payment_method:, url:, form_id:) + @payment_method = payment_method + @url = url + @form_id = form_id + end +end diff --git a/admin/app/components/solidus_admin/payment_methods/form/component.yml b/admin/app/components/solidus_admin/payment_methods/form/component.yml new file mode 100644 index 00000000000..97e8ed8b578 --- /dev/null +++ b/admin/app/components/solidus_admin/payment_methods/form/component.yml @@ -0,0 +1,11 @@ +en: + availability: "Availability" + deployment: "Deployment" + hints: + autocapture: >- +

Auto-capture setting charges customer's account upon transaction authorization.

+

Enable to reduce manual intervention and streamline the payment process.

+ test_mode: >- +

Payment methods test mode allows users to simulate transactions using dummy data, ensuring the payment gateway's functionality without real transactions.

+

In test mode, users can check if payment methods, such as credit cards or digital wallets, are functioning correctly before going live with real transactions.

+ preference_source_none: "Custom" diff --git a/admin/app/components/solidus_admin/payment_methods/index/component.rb b/admin/app/components/solidus_admin/payment_methods/index/component.rb index 7bf64ffa63f..9a7f34f6fd4 100644 --- a/admin/app/components/solidus_admin/payment_methods/index/component.rb +++ b/admin/app/components/solidus_admin/payment_methods/index/component.rb @@ -13,8 +13,8 @@ def search_url solidus_admin.payment_methods_path end - def row_url(payment_method) - spree.edit_admin_payment_method_path(payment_method) + def edit_path(payment_method) + solidus_admin.edit_payment_method_path(payment_method) end def sortable_options @@ -28,7 +28,7 @@ def page_actions render component("ui/button").new( tag: :a, text: t('.add'), - href: spree.new_admin_payment_method_path, + href: solidus_admin.new_payment_method_path, icon: "add-line", ) end @@ -40,6 +40,7 @@ def batch_actions action: solidus_admin.payment_methods_path, method: :delete, icon: 'delete-bin-7-line', + require_confirmation: true }, ] end @@ -59,13 +60,13 @@ def columns { header: :name, data: ->(payment_method) do - content_tag :div, payment_method.name + link_to payment_method.name, edit_path(payment_method), class: "body-link" end }, { header: :type, data: ->(payment_method) do - content_tag :div, payment_method.model_name.human + link_to payment_method.model_name.human, edit_path(payment_method), class: "body-link" end }, { diff --git a/admin/app/components/solidus_admin/payment_methods/new/component.html.erb b/admin/app/components/solidus_admin/payment_methods/new/component.html.erb new file mode 100644 index 00000000000..fcf0b5417a3 --- /dev/null +++ b/admin/app/components/solidus_admin/payment_methods/new/component.html.erb @@ -0,0 +1,17 @@ +<%= page id: :payment_method_form do %> + <%= page_header do %> + <%= page_header_back(solidus_admin.payment_methods_path) %> + <%= page_header_title(t(".title")) %> + <%= page_header_actions do %> + <%= render component("ui/button").new( + tag: :a, + text: t(".discard"), + href: solidus_admin.payment_methods_path, + scheme: :secondary + ) %> + <%= render component("ui/button").new(tag: :button, text: t(".save"), form: form_id) %> + <% end %> + <% end %> + + <%= render component("payment_methods/form").new(payment_method: @resource, url: solidus_admin.payment_methods_path, form_id:) %> +<% end %> diff --git a/admin/app/components/solidus_admin/payment_methods/new/component.rb b/admin/app/components/solidus_admin/payment_methods/new/component.rb new file mode 100644 index 00000000000..f384cd56b11 --- /dev/null +++ b/admin/app/components/solidus_admin/payment_methods/new/component.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class SolidusAdmin::PaymentMethods::New::Component < SolidusAdmin::Resources::New::Component + include SolidusAdmin::Layout::PageHelpers +end diff --git a/admin/app/components/solidus_admin/payment_methods/new/component.yml b/admin/app/components/solidus_admin/payment_methods/new/component.yml new file mode 100644 index 00000000000..57197ddaac4 --- /dev/null +++ b/admin/app/components/solidus_admin/payment_methods/new/component.yml @@ -0,0 +1,5 @@ +en: + back: "Back" + discard: "Discard" + save: "Save" + title: "New Payment Method" diff --git a/admin/app/controllers/solidus_admin/payment_methods_controller.rb b/admin/app/controllers/solidus_admin/payment_methods_controller.rb index fc3ce0316dd..746fb068ad5 100644 --- a/admin/app/controllers/solidus_admin/payment_methods_controller.rb +++ b/admin/app/controllers/solidus_admin/payment_methods_controller.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module SolidusAdmin - class PaymentMethodsController < SolidusAdmin::BaseController - include SolidusAdmin::ControllerHelpers::Search + class PaymentMethodsController < SolidusAdmin::ResourcesController include SolidusAdmin::Moveable search_scope(:all) @@ -11,26 +10,21 @@ class PaymentMethodsController < SolidusAdmin::BaseController search_scope(:storefront, &:available_to_users) search_scope(:admin, &:available_to_admin) - def index - payment_methods = apply_search_to( - Spree::PaymentMethod.ordered_by_position, - param: :q, - ) + private - set_page_and_extract_portion_from(payment_methods) + def resource_class = Spree::PaymentMethod - respond_to do |format| - format.html { render component('payment_methods/index').new(page: @page) } - end - end + def resources_collection = resource_class.all - def destroy - @payment_methods = Spree::PaymentMethod.where(id: params[:id]) + def resources_sorting_options = { position: :asc } - Spree::PaymentMethod.transaction { @payment_methods.destroy_all } + def permitted_resource_params + params.require(:payment_method).permit(:name, :description, :auto_capture, :type, :preference_source, + :preferred_server, :preferred_test_mode, :active, :available_to_admin, :available_to_users, store_ids: []) + end - flash[:notice] = t('.success') - redirect_back_or_to payment_methods_path, status: :see_other + def resource_form_container + :payment_method_form end end end diff --git a/admin/app/controllers/solidus_admin/resources_controller.rb b/admin/app/controllers/solidus_admin/resources_controller.rb index b4656a2f5d2..a0ff326d0a4 100644 --- a/admin/app/controllers/solidus_admin/resources_controller.rb +++ b/admin/app/controllers/solidus_admin/resources_controller.rb @@ -133,7 +133,7 @@ def render_resource_form_with_errors(page_component) render page_component, status: :unprocessable_entity end format.turbo_stream do - render turbo_stream: turbo_stream.replace(:resource_modal, page_component), + render turbo_stream: turbo_stream.replace(resource_form_container, page_component), status: :unprocessable_entity end end @@ -165,5 +165,9 @@ def blueprint_view raise NotImplementedError, "You must implement the blueprint_view method in #{self.class}" end + + def resource_form_container + :resource_modal + end end end diff --git a/admin/app/helpers/solidus_admin/solidus_form_helper.rb b/admin/app/helpers/solidus_admin/solidus_form_helper.rb new file mode 100644 index 00000000000..c4a335ba930 --- /dev/null +++ b/admin/app/helpers/solidus_admin/solidus_form_helper.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module SolidusAdmin + module SolidusFormHelper + def solidus_form_for(*args, **kwargs, &block) + form_for(*args, **kwargs, builder: SolidusAdmin::FormBuilder, &block) + end + end +end diff --git a/admin/app/lib/solidus_admin/form_builder.rb b/admin/app/lib/solidus_admin/form_builder.rb new file mode 100644 index 00000000000..81c0763163e --- /dev/null +++ b/admin/app/lib/solidus_admin/form_builder.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +class SolidusAdmin::FormBuilder < ActionView::Helpers::FormBuilder + include SolidusAdmin::ComponentsHelper + + delegate :render, to: :@template + + def text_field(method, **options) + render component("ui/forms/field").text_field(self, method, **options) + end + + def text_area(method, **options) + render component("ui/forms/field").text_area(self, method, **options) + end + + def select(method, choices, **options) + render component("ui/forms/field").select(self, method, choices, **options) + end + + def checkbox(method, label: nil, checked: nil, hint: nil, **options) + label = @object.class.human_attribute_name(method) if label.nil? + label_options = options.delete(:label_options) || {} + checked = @object.public_send(method) if checked.nil? + hint_options = options.delete(:hint_options) || {} + + component_instance = component("ui/forms/checkbox").new(object_name: @object_name, checked:, method:, **options) + render component_instance do |checkbox| + checkbox.with_label(text: label, **label_options) + checkbox.with_hint(text: hint, **hint_options) if hint + end + end + + def checkbox_row(method, options:, row_title:, **attrs) + render component("ui/checkbox_row").new(form: self, method:, options:, row_title:, **attrs) + end + + def input(method, **options) + name = "#{@object_name}[#{method}]" + value = @object.public_send(method) if options[:value].nil? + render component("ui/forms/input").new(name:, value:, **options) + end + + def hidden_field(method, **options) + input(method, type: :hidden, autocomplete: "off", **options) + end + + def switch_field(method, label: nil, include_hidden: true, **options) + label = @object.class.human_attribute_name(method) if label.nil? + name = "#{@object_name}[#{method}]" + error = @object.errors[method] + checked = @object.public_send(method) + render component("ui/forms/switch_field").new(label:, name:, error:, checked:, include_hidden:, **options) + end + + def submit(text, **options) + render component("ui/button").new(type: :submit, text:, form: id, **options) + end +end diff --git a/admin/config/locales/payment_methods.en.yml b/admin/config/locales/payment_methods.en.yml index c9b1308e35e..53ef2fc2c00 100644 --- a/admin/config/locales/payment_methods.en.yml +++ b/admin/config/locales/payment_methods.en.yml @@ -2,5 +2,9 @@ en: solidus_admin: payment_methods: title: "Payment Methods" + create: + success: "Payment method was successfully created." destroy: - success: "Payment Methods were successfully removed." + success: "Payment methods were successfully removed." + update: + success: "Payment method was successfully updated." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index ec215522965..3b1c9ce2b5d 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -74,7 +74,7 @@ admin_resources :promotion_categories, only: [:index, :destroy] admin_resources :tax_categories, except: [:show] admin_resources :tax_rates, only: [:index, :destroy] - admin_resources :payment_methods, only: [:index, :destroy], sortable: true + admin_resources :payment_methods, except: [:show], sortable: true admin_resources :stock_items, only: [:index, :edit, :update] admin_resources :shipping_methods, only: [:index, :destroy] admin_resources :shipping_categories, except: [:show] diff --git a/admin/lib/solidus_admin/testing_support/feature_helpers.rb b/admin/lib/solidus_admin/testing_support/feature_helpers.rb index 6f28ad553cb..cd81a9f46c0 100644 --- a/admin/lib/solidus_admin/testing_support/feature_helpers.rb +++ b/admin/lib/solidus_admin/testing_support/feature_helpers.rb @@ -61,6 +61,11 @@ def clear_search find('button[aria-label="Clear"]').click end end + + def switch(locator, on: true) + checkbox = find(:label, text: locator).find(:checkbox) + on ? checkbox.check : checkbox.uncheck + end end end end diff --git a/admin/spec/features/payment_methods_spec.rb b/admin/spec/features/payment_methods_spec.rb index 06b4dc0f3ab..a3aeb7d8295 100644 --- a/admin/spec/features/payment_methods_spec.rb +++ b/admin/spec/features/payment_methods_spec.rb @@ -41,8 +41,8 @@ expect(page).to be_axe_clean select_row("Check") - click_on "Delete" - expect(page).to have_content("Payment Methods were successfully removed.") + accept_confirm("Are you sure you want to delete 1 payment method?") { click_on "Delete" } + expect(page).to have_content("Payment methods were successfully removed.") expect(page).not_to have_content("Check") expect(Spree::PaymentMethod.count).to eq(3) end @@ -52,4 +52,74 @@ let(:displayed_attribute) { :name } let(:path) { solidus_admin.payment_methods_path } end + + context "creating payment method" do + before { create(:store, name: "Store") } + context "with valid attributes" do + it "creates payment method" do + visit "/admin/payment_methods" + click_on "Add new" + + expect(page).to have_current_path("/admin/payment_methods/new") + expect(page).to be_axe_clean + + fill_in "Name", with: "Checking" + fill_in "Description", with: "Payment Method Description" + switch "Auto Capture" + solidus_select "Check Payments", from: "Type" + fill_in "Server", with: "test" + switch "Test Mode" + check "Active" + solidus_select("Store", from: "Stores") + check "Available to Admin" + check "Available to Users" + + click_on "Save" + + expect(page).to have_content("Payment method was successfully created.") + expect(page).to have_content("Checking") + expect(page).to have_content("Check Payments") + end + end + + context "with invalid attributes" do + it "shows validation errors" do + visit "/admin/payment_methods" + click_on "Add new" + click_on "Save" + expect(page).to have_content("can't be blank") + end + end + end + + context "updating payment method" do + before { create(:payment_method, name: "Check payments") } + + context "with valid attributes" do + it "updates payment method" do + visit "/admin/payment_methods" + click_on "Check payments" + + fill_in "Name", with: "Checking payments" + solidus_select "Check Payments", from: "Type" + click_on "Save" + + expect(page).to have_content("Payment method was successfully updated.") + expect(page).to have_content("Checking payments") + expect(page).to have_content("Check Payments") + end + end + + context "with invalid attributes" do + it "shows validation errors" do + visit "/admin/payment_methods" + click_on "Check payments" + + fill_in "Name", with: "" + click_on "Save" + + expect(page).to have_content("can't be blank") + end + end + end end diff --git a/admin/spec/requests/solidus_admin/payment_methods_spec.rb b/admin/spec/requests/solidus_admin/payment_methods_spec.rb index 5cc99dae2ab..e452d8f0fab 100644 --- a/admin/spec/requests/solidus_admin/payment_methods_spec.rb +++ b/admin/spec/requests/solidus_admin/payment_methods_spec.rb @@ -2,10 +2,17 @@ require "spec_helper" require "solidus_admin/testing_support/shared_examples/moveable" +require 'solidus_admin/testing_support/shared_examples/crud_resource_requests' RSpec.describe "SolidusAdmin::PaymentMethodsController", type: :request do it_behaves_like "requests: moveable" do let(:factory) { :payment_method } let(:request_path) { solidus_admin.move_payment_method_path(record, format: :js) } end + + include_examples "CRUD resource requests", "payment_method" do + let(:resource_class) { Spree::PaymentMethod } + let(:valid_attributes) { { name: "Credit Card", type: "Spree::PaymentMethod::BogusCreditCard" } } + let(:invalid_attributes) { { name: "", type: "" } } + end end diff --git a/core/config/locales/en.yml b/core/config/locales/en.yml index ad8164abb08..99957eb3a4b 100644 --- a/core/config/locales/en.yml +++ b/core/config/locales/en.yml @@ -158,6 +158,9 @@ en: display_on: Display name: Name preference_source: Preference Source + preferred_server: Server + preferred_test_mode: Test Mode + store_ids: Stores type: Type spree/price: amount: Price