diff --git a/admin/app/components/solidus_admin/return_reasons/edit/component.html.erb b/admin/app/components/solidus_admin/return_reasons/edit/component.html.erb
new file mode 100644
index 00000000000..8782aa23c43
--- /dev/null
+++ b/admin/app/components/solidus_admin/return_reasons/edit/component.html.erb
@@ -0,0 +1,26 @@
+<%= turbo_frame_tag :edit_return_reason_modal do %>
+ <%= render component("ui/modal").new(title: t(".title")) do |modal| %>
+ <%= form_for @return_reason, url: solidus_admin.return_reason_path(@return_reason), html: { id: form_id } do |f| %>
+
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %>
+
+
+ <% modal.with_actions do %>
+
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %>
+ <% end %>
+ <% end %>
+ <% end %>
+<% end %>
+<%= render component("return_reasons/index").new(page: @page) %>
diff --git a/admin/app/components/solidus_admin/return_reasons/edit/component.rb b/admin/app/components/solidus_admin/return_reasons/edit/component.rb
new file mode 100644
index 00000000000..2ce5ed7f0bf
--- /dev/null
+++ b/admin/app/components/solidus_admin/return_reasons/edit/component.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class SolidusAdmin::ReturnReasons::Edit::Component < SolidusAdmin::BaseComponent
+ def initialize(page:, return_reason:)
+ @page = page
+ @return_reason = return_reason
+ end
+
+ def form_id
+ dom_id(@return_reason, "#{stimulus_id}_edit_return_reason_form")
+ end
+end
diff --git a/admin/app/components/solidus_admin/return_reasons/edit/component.yml b/admin/app/components/solidus_admin/return_reasons/edit/component.yml
new file mode 100644
index 00000000000..03806d9bf30
--- /dev/null
+++ b/admin/app/components/solidus_admin/return_reasons/edit/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: "Edit Return Reason"
+ cancel: "Cancel"
+ submit: "Update Return Reason"
+ hints:
+ active: "When checked, this return reason will be available for selection when returning orders."
diff --git a/admin/app/components/solidus_admin/return_reasons/index/component.rb b/admin/app/components/solidus_admin/return_reasons/index/component.rb
index 75ba35f2b95..175d9575828 100644
--- a/admin/app/components/solidus_admin/return_reasons/index/component.rb
+++ b/admin/app/components/solidus_admin/return_reasons/index/component.rb
@@ -14,7 +14,25 @@ def search_key
end
def row_url(return_reason)
- spree.edit_admin_return_reason_path(return_reason)
+ spree.edit_admin_return_reason_path(return_reason, _turbo_frame: :edit_return_reason_modal)
+ end
+
+ def turbo_frames
+ %w[
+ new_return_reason_modal
+ edit_return_reason_modal
+ ]
+ end
+
+ def page_actions
+ render component("ui/button").new(
+ tag: :a,
+ text: t('.add'),
+ href: solidus_admin.new_return_reason_path,
+ data: { turbo_frame: :new_return_reason_modal },
+ icon: "add-line",
+ class: "align-self-end w-full",
+ )
end
def batch_actions
diff --git a/admin/app/components/solidus_admin/return_reasons/new/component.html.erb b/admin/app/components/solidus_admin/return_reasons/new/component.html.erb
new file mode 100644
index 00000000000..7a55ebbcacd
--- /dev/null
+++ b/admin/app/components/solidus_admin/return_reasons/new/component.html.erb
@@ -0,0 +1,27 @@
+<%= turbo_frame_tag :new_return_reason_modal do %>
+ <%= render component("ui/modal").new(title: t(".title")) do |modal| %>
+ <%= form_for @return_reason, url: solidus_admin.return_reasons_path, html: { id: form_id } do |f| %>
+
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %>
+
+
+ <% modal.with_actions do %>
+
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %>
+ <% end %>
+ <% end %>
+ <% end %>
+<% end %>
+
+<%= render component("return_reasons/index").new(page: @page) %>
diff --git a/admin/app/components/solidus_admin/return_reasons/new/component.rb b/admin/app/components/solidus_admin/return_reasons/new/component.rb
new file mode 100644
index 00000000000..7efe494f634
--- /dev/null
+++ b/admin/app/components/solidus_admin/return_reasons/new/component.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class SolidusAdmin::ReturnReasons::New::Component < SolidusAdmin::BaseComponent
+ def initialize(page:, return_reason:)
+ @page = page
+ @return_reason = return_reason
+ end
+
+ def form_id
+ dom_id(@return_reason, "#{stimulus_id}_new_return_reason_form")
+ end
+end
diff --git a/admin/app/components/solidus_admin/return_reasons/new/component.yml b/admin/app/components/solidus_admin/return_reasons/new/component.yml
new file mode 100644
index 00000000000..bbed4e31820
--- /dev/null
+++ b/admin/app/components/solidus_admin/return_reasons/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 Return Reason"
+ cancel: "Cancel"
+ submit: "Add Return Reason"
+ hints:
+ active: "When checked, this return reason will be available for selection when returning orders."
diff --git a/admin/app/controllers/solidus_admin/return_reasons_controller.rb b/admin/app/controllers/solidus_admin/return_reasons_controller.rb
index 4887f236794..ef47465ecd3 100644
--- a/admin/app/controllers/solidus_admin/return_reasons_controller.rb
+++ b/admin/app/controllers/solidus_admin/return_reasons_controller.rb
@@ -4,19 +4,86 @@ module SolidusAdmin
class ReturnReasonsController < SolidusAdmin::BaseController
include SolidusAdmin::ControllerHelpers::Search
- def index
- return_reasons = apply_search_to(
- Spree::ReturnReason.unscoped.order(id: :desc),
- param: :q,
- )
+ before_action :find_return_reason, only: %i[edit update]
- set_page_and_extract_portion_from(return_reasons)
+ def index
+ set_index_page
respond_to do |format|
format.html { render component('return_reasons/index').new(page: @page) }
end
end
+ def new
+ @return_reason = Spree::ReturnReason.new
+
+ set_index_page
+
+ respond_to do |format|
+ format.html { render component('return_reasons/new').new(page: @page, return_reason: @return_reason) }
+ end
+ end
+
+ def create
+ @return_reason = Spree::ReturnReason.new(return_reason_params)
+
+ if @return_reason.save
+ respond_to do |format|
+ flash[:notice] = t('.success')
+
+ format.html do
+ redirect_to solidus_admin.return_reasons_path, status: :see_other
+ end
+
+ format.turbo_stream do
+ render turbo_stream: ''
+ end
+ end
+ else
+ set_index_page
+
+ respond_to do |format|
+ format.html do
+ page_component = component('return_reasons/new').new(page: @page, return_reason: @return_reason)
+ render page_component, status: :unprocessable_entity
+ end
+ end
+ end
+ end
+
+ def edit
+ set_index_page
+
+ respond_to do |format|
+ format.html { render component('return_reasons/edit').new(page: @page, return_reason: @return_reason) }
+ end
+ end
+
+ def update
+ if @return_reason.update(return_reason_params)
+ respond_to do |format|
+ flash[:notice] = t('.success')
+
+ format.html do
+ redirect_to solidus_admin.return_reasons_path, status: :see_other
+ end
+
+ format.turbo_stream do
+ render turbo_stream: ''
+ end
+ end
+ else
+ set_index_page
+
+ respond_to do |format|
+ format.html do
+ page_component = component('return_reasons/edit').new(page: @page, return_reason: @return_reason)
+ render page_component, status: :unprocessable_entity
+ end
+ end
+ end
+ end
+
def destroy
@return_reason = Spree::ReturnReason.find_by!(id: params[:id])
@@ -28,8 +95,21 @@ def destroy
private
+ def find_return_reason
+ @return_reason = Spree::ReturnReason.find(params[:id])
+ end
+
+ def set_index_page
+ return_reasons = apply_search_to(
+ Spree::ReturnReason.unscoped.order(id: :desc),
+ param: :q,
+ )
+
+ set_page_and_extract_portion_from(return_reasons)
+ end
+
def return_reason_params
- params.require(:return_reason).permit(:return_reason_id, permitted_return_reason_attributes)
+ params.require(:return_reason).permit(:name, :active)
end
end
end
diff --git a/admin/config/locales/return_reasons.en.yml b/admin/config/locales/return_reasons.en.yml
index 248dc9769d7..7987e6f786a 100644
--- a/admin/config/locales/return_reasons.en.yml
+++ b/admin/config/locales/return_reasons.en.yml
@@ -3,4 +3,8 @@ en:
return_reasons:
title: "Return Reasons"
destroy:
- success: "Return Reasons were successfully removed."
+ success: "Return reasons were successfully removed."
+ create:
+ success: "Return reason was successfully created."
+ update:
+ success: "Return reason was successfully updated."
diff --git a/admin/config/routes.rb b/admin/config/routes.rb
index f82f324a81b..7ad5ef34c89 100644
--- a/admin/config/routes.rb
+++ b/admin/config/routes.rb
@@ -62,7 +62,7 @@
admin_resources :zones, only: [:index, :destroy]
admin_resources :refund_reasons, except: [:show]
admin_resources :reimbursement_types, only: [:index]
- admin_resources :return_reasons, only: [:index, :destroy]
+ admin_resources :return_reasons, except: [:show]
admin_resources :adjustment_reasons, except: [:show]
admin_resources :store_credit_reasons, except: [:show]
end
diff --git a/admin/spec/features/return_reasons_spec.rb b/admin/spec/features/return_reasons_spec.rb
index 3e929cd1dd4..5932101bf41 100644
--- a/admin/spec/features/return_reasons_spec.rb
+++ b/admin/spec/features/return_reasons_spec.rb
@@ -14,9 +14,82 @@
select_row("Default-return-reason")
click_on "Delete"
- expect(page).to have_content("Return Reasons were successfully removed.")
+ expect(page).to have_content("Return reasons were successfully removed.")
expect(page).not_to have_content("Default-return-reason")
expect(Spree::ReturnReason.count).to eq(0)
expect(page).to be_axe_clean
end
+
+ context "when creating a new return reason" do
+ let(:query) { "?page=1&q%5Bname_cont%5D=new" }
+
+ before do
+ visit "/admin/return_reasons#{query}"
+ click_on "Add new"
+ expect(page).to have_content("New Return Reason")
+ 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 return reason, keeping page and q params" do
+ fill_in "Name", with: "New Reason"
+ page.uncheck "return_reason[active]"
+
+ click_on "Add Return Reason"
+
+ expect(page).to have_content("Return reason was successfully created.")
+ expect(Spree::ReturnReason.find_by(name: "New Reason")).to be_present
+ expect(Spree::ReturnReason.find_by(name: "New Reason").active).to be_falsey
+ expect(page.current_url).to include(query)
+ end
+ end
+
+ context "with invalid data" do
+ it "fails to create a new return reason, keeping page and q params" do
+ click_on "Add Return Reason"
+
+ expect(page).to have_content("can't be blank")
+ expect(page.current_url).to include(query)
+ end
+ end
+ end
+
+ context "when editing an existing return reason" do
+ let(:query) { "?page=1&q%5Bname_cont%5D=reason" }
+
+ before do
+ Spree::ReturnReason.create(name: "Good Reason")
+ visit "/admin/return_reasons#{query}"
+ find_row("Good Reason").click
+ expect(page).to have_content("Edit Return Reason")
+ 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
+
+ it "successfully updates the existing return reason" do
+ fill_in "Name", with: "Better Reason"
+ page.uncheck "return_reason[active]"
+
+ click_on "Update Return Reason"
+ expect(page).to have_content("Return reason was successfully updated.")
+ expect(page).to have_content("Better Reason")
+ expect(page).not_to have_content("Good Reason")
+ expect(Spree::ReturnReason.find_by(name: "Better Reason")).to be_present
+ expect(Spree::ReturnReason.find_by(name: "Better Reason").active).to be_falsey
+ expect(page.current_url).to include(query)
+ end
+ end
end
diff --git a/admin/spec/requests/solidus_admin/return_reasons_spec.rb b/admin/spec/requests/solidus_admin/return_reasons_spec.rb
new file mode 100644
index 00000000000..179715484ba
--- /dev/null
+++ b/admin/spec/requests/solidus_admin/return_reasons_spec.rb
@@ -0,0 +1,132 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "SolidusAdmin::ReturnReasonsController", type: :request do
+ let(:admin_user) { create(:admin_user) }
+ let(:return_reason) { create(:return_reason) }
+
+ before do
+ allow_any_instance_of(SolidusAdmin::BaseController).to receive(:spree_current_user).and_return(admin_user)
+ end
+
+ describe "GET /index" do
+ it "renders the index template with a 200 OK status" do
+ get solidus_admin.return_reasons_path
+ expect(response).to have_http_status(:ok)
+ end
+ end
+
+ describe "GET /new" do
+ it "renders the new template with a 200 OK status" do
+ get solidus_admin.new_return_reason_path
+ expect(response).to have_http_status(:ok)
+ end
+ end
+
+ describe "GET /edit" do
+ it "renders the edit template with a 200 OK status" do
+ get solidus_admin.edit_return_reason_path(return_reason)
+ expect(response).to have_http_status(:ok)
+ end
+ end
+
+ describe "PATCH /update" do
+ context "with valid parameters" do
+ let(:valid_attributes) { { name: "Updated Return Reason", active: false } }
+
+ it "updates the return reason" do
+ patch solidus_admin.return_reason_path(return_reason), params: { return_reason: valid_attributes }
+ return_reason.reload
+ expect(return_reason.name).to eq("Updated Return Reason")
+ expect(return_reason.active).to be(false)
+ end
+
+ it "redirects to the index page with a 303 See Other status" do
+ patch solidus_admin.return_reason_path(return_reason), params: { return_reason: valid_attributes }
+ expect(response).to redirect_to(solidus_admin.return_reasons_path)
+ expect(response).to have_http_status(:see_other)
+ end
+
+ it "displays a success flash message" do
+ patch solidus_admin.return_reason_path(return_reason), params: { return_reason: valid_attributes }
+ follow_redirect!
+ expect(response.body).to include("Return reason was successfully updated.")
+ end
+ end
+
+ context "with invalid parameters" do
+ let(:invalid_attributes) { { name: "", active: false } }
+
+ it "does not update the return reason" do
+ original_name = return_reason.name
+ patch solidus_admin.return_reason_path(return_reason), params: { return_reason: invalid_attributes }
+ return_reason.reload
+ expect(return_reason.name).to eq(original_name)
+ end
+
+ it "renders the edit template with unprocessable_entity status" do
+ patch solidus_admin.return_reason_path(return_reason), params: { return_reason: invalid_attributes }
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+ end
+
+ describe "POST /create" do
+ context "with valid parameters" do
+ let(:valid_attributes) { { name: "Damaged item", active: true } }
+
+ it "creates a new ReturnReason" do
+ expect {
+ post solidus_admin.return_reasons_path, params: { return_reason: valid_attributes }
+ }.to change(Spree::ReturnReason, :count).by(1)
+ end
+
+ it "redirects to the index page with a 303 See Other status" do
+ post solidus_admin.return_reasons_path, params: { return_reason: valid_attributes }
+ expect(response).to redirect_to(solidus_admin.return_reasons_path)
+ expect(response).to have_http_status(:see_other)
+ end
+
+ it "displays a success flash message" do
+ post solidus_admin.return_reasons_path, params: { return_reason: valid_attributes }
+ follow_redirect!
+ expect(response.body).to include("Return reason was successfully created.")
+ end
+ end
+
+ context "with invalid parameters" do
+ let(:invalid_attributes) { { name: "" } }
+
+ it "does not create a new ReturnReason" do
+ expect {
+ post solidus_admin.return_reasons_path, params: { return_reason: invalid_attributes }
+ }.not_to change(Spree::ReturnReason, :count)
+ end
+
+ it "renders the new template with unprocessable_entity status" do
+ post solidus_admin.return_reasons_path, params: { return_reason: invalid_attributes }
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+ end
+
+ describe "DELETE /destroy" do
+ let!(:return_reason_to_delete) { create(:return_reason) }
+
+ it "deletes the return reason and redirects to the index page with a 303 See Other status" do
+ expect {
+ delete solidus_admin.return_reason_path(return_reason_to_delete)
+ }.to change(Spree::ReturnReason, :count).by(-1)
+
+ expect(response).to redirect_to(solidus_admin.return_reasons_path)
+ expect(response).to have_http_status(:see_other)
+ end
+
+ it "displays a success flash message after deletion" do
+ delete solidus_admin.return_reason_path(return_reason_to_delete)
+ follow_redirect!
+ expect(response.body).to include("Return reasons were successfully removed.")
+ end
+ end
+end