diff --git a/admin/app/components/solidus_admin/properties/edit/component.html.erb b/admin/app/components/solidus_admin/properties/edit/component.html.erb
new file mode 100644
index 00000000000..e116b899486
--- /dev/null
+++ b/admin/app/components/solidus_admin/properties/edit/component.html.erb
@@ -0,0 +1,17 @@
+<%= turbo_frame_tag :edit_property_modal do %>
+ <%= render component("ui/modal").new(title: t(".title")) do |modal| %>
+ <%= form_for @property, url: solidus_admin.property_path(@property), html: { id: form_id } do |f| %>
+
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %>
+ <%= render component("ui/forms/field").text_field(f, :presentation, 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("properties/index").new(page: @page) %>
diff --git a/admin/app/components/solidus_admin/properties/edit/component.rb b/admin/app/components/solidus_admin/properties/edit/component.rb
new file mode 100644
index 00000000000..b2ed34d9114
--- /dev/null
+++ b/admin/app/components/solidus_admin/properties/edit/component.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class SolidusAdmin::Properties::Edit::Component < SolidusAdmin::BaseComponent
+ def initialize(page:, property:)
+ @page = page
+ @property = property
+ end
+
+ def form_id
+ dom_id(@property, "#{stimulus_id}_edit_property_form")
+ end
+end
diff --git a/admin/app/components/solidus_admin/properties/edit/component.yml b/admin/app/components/solidus_admin/properties/edit/component.yml
new file mode 100644
index 00000000000..bed9d213c8c
--- /dev/null
+++ b/admin/app/components/solidus_admin/properties/edit/component.yml
@@ -0,0 +1,4 @@
+en:
+ title: "Edit Property"
+ cancel: "Cancel"
+ submit: "Update Property"
diff --git a/admin/app/components/solidus_admin/properties/index/component.rb b/admin/app/components/solidus_admin/properties/index/component.rb
index e8289365922..61aca2318c4 100644
--- a/admin/app/components/solidus_admin/properties/index/component.rb
+++ b/admin/app/components/solidus_admin/properties/index/component.rb
@@ -5,6 +5,10 @@ def model_class
Spree::Property
end
+ def title
+ t('solidus_admin.properties.title')
+ end
+
def search_key
:name_cont
end
@@ -14,14 +18,21 @@ def search_url
end
def row_url(property)
- spree.admin_property_path(property)
+ solidus_admin.edit_property_path(property, _turbo_frame: :edit_property_modal)
+ end
+
+ def turbo_frames
+ %w[
+ new_property_modal
+ edit_property_modal
+ ]
end
def page_actions
render component("ui/button").new(
tag: :a,
text: t('.add'),
- href: spree.new_admin_property_path,
+ href: solidus_admin.new_property_path, data: { turbo_frame: :new_property_modal },
icon: "add-line",
)
end
diff --git a/admin/app/components/solidus_admin/properties/new/component.html.erb b/admin/app/components/solidus_admin/properties/new/component.html.erb
new file mode 100644
index 00000000000..d306cebcf4c
--- /dev/null
+++ b/admin/app/components/solidus_admin/properties/new/component.html.erb
@@ -0,0 +1,18 @@
+<%= turbo_frame_tag :new_property_modal do %>
+ <%= render component("ui/modal").new(title: t(".title")) do |modal| %>
+ <%= form_for @property, url: solidus_admin.properties_path, html: { id: form_id } do |f| %>
+
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %>
+ <%= render component("ui/forms/field").text_field(f, :presentation, 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("properties/index").new(page: @page) %>
diff --git a/admin/app/components/solidus_admin/properties/new/component.rb b/admin/app/components/solidus_admin/properties/new/component.rb
new file mode 100644
index 00000000000..55e8806c68f
--- /dev/null
+++ b/admin/app/components/solidus_admin/properties/new/component.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class SolidusAdmin::Properties::New::Component < SolidusAdmin::BaseComponent
+ def initialize(page:, property:)
+ @page = page
+ @property = property
+ end
+
+ def form_id
+ dom_id(@property, "#{stimulus_id}_new_property_form")
+ end
+end
diff --git a/admin/app/components/solidus_admin/properties/new/component.yml b/admin/app/components/solidus_admin/properties/new/component.yml
new file mode 100644
index 00000000000..e91d4784c52
--- /dev/null
+++ b/admin/app/components/solidus_admin/properties/new/component.yml
@@ -0,0 +1,4 @@
+en:
+ title: "New Property"
+ cancel: "Cancel"
+ submit: "Add Property"
diff --git a/admin/app/controllers/solidus_admin/properties_controller.rb b/admin/app/controllers/solidus_admin/properties_controller.rb
index 666451459d4..53db315d8d6 100644
--- a/admin/app/controllers/solidus_admin/properties_controller.rb
+++ b/admin/app/controllers/solidus_admin/properties_controller.rb
@@ -4,21 +4,86 @@ module SolidusAdmin
class PropertiesController < SolidusAdmin::BaseController
include SolidusAdmin::ControllerHelpers::Search
- def index
- properties = apply_search_to(
- Spree::Property.order(created_at: :desc, id: :desc),
- param: :q,
- )
+ before_action :set_property, only: %i[edit update]
- set_page_and_extract_portion_from(
- properties,
- )
+ def index
+ set_index_page
respond_to do |format|
format.html { render component('properties/index').new(page: @page) }
end
end
+ def new
+ @property = Spree::Property.new
+
+ set_index_page
+
+ respond_to do |format|
+ format.html { render component('properties/new').new(page: @page, property: @property) }
+ end
+ end
+
+ def create
+ @property = Spree::Property.new(property_params)
+
+ if @property.save
+ respond_to do |format|
+ flash[:notice] = t('.success')
+
+ format.html do
+ redirect_to solidus_admin.properties_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('properties/new').new(page: @page, property: @property)
+ render page_component, status: :unprocessable_entity
+ end
+ end
+ end
+ end
+
+ def edit
+ set_index_page
+
+ respond_to do |format|
+ format.html { render component('properties/edit').new(page: @page, property: @property) }
+ end
+ end
+
+ def update
+ if @property.update(property_params)
+ respond_to do |format|
+ flash[:notice] = t('.success')
+
+ format.html do
+ redirect_to solidus_admin.properties_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('properties/edit').new(page: @page, property: @property)
+ render page_component, status: :unprocessable_entity
+ end
+ end
+ end
+ end
+
def destroy
@properties = Spree::Property.where(id: params[:id])
@@ -29,5 +94,24 @@ def destroy
flash[:notice] = t('.success')
redirect_to properties_path, status: :see_other
end
+
+ private
+
+ def set_property
+ @property = Spree::Property.find(params[:id])
+ end
+
+ def property_params
+ params.require(:property).permit(:name, :presentation)
+ end
+
+ def set_index_page
+ properties = apply_search_to(
+ Spree::Property.unscoped.order(id: :desc),
+ param: :q,
+ )
+
+ set_page_and_extract_portion_from(properties)
+ end
end
end
diff --git a/admin/config/locales/properties.en.yml b/admin/config/locales/properties.en.yml
index 5bc4aec5cbb..95ef30fcae4 100644
--- a/admin/config/locales/properties.en.yml
+++ b/admin/config/locales/properties.en.yml
@@ -2,5 +2,9 @@ en:
solidus_admin:
properties:
title: "Properties"
+ create:
+ success: "Property was successfully created."
+ update:
+ success: "Property was successfully updated."
destroy:
success: "Properties were successfully removed."
diff --git a/admin/config/routes.rb b/admin/config/routes.rb
index c42101b7720..70ef4c7e50b 100644
--- a/admin/config/routes.rb
+++ b/admin/config/routes.rb
@@ -56,7 +56,7 @@
end
admin_resources :promotions, only: [:index, :destroy]
- admin_resources :properties, only: [:index, :destroy]
+ admin_resources :properties, except: [:show]
admin_resources :option_types, only: [:index, :destroy], sortable: true
admin_resources :taxonomies, only: [:index, :destroy], sortable: true
admin_resources :promotion_categories, only: [:index, :destroy]
diff --git a/admin/spec/features/properties_spec.rb b/admin/spec/features/properties_spec.rb
index 7c2025e5bfe..d3eb375613a 100644
--- a/admin/spec/features/properties_spec.rb
+++ b/admin/spec/features/properties_spec.rb
@@ -21,4 +21,89 @@
expect(page).not_to have_content("Type prop")
expect(Spree::Property.count).to eq(1)
end
+
+ context "creating a new property" do
+ it "creates a new product property" do
+ visit "/admin/properties"
+ click_on "Add new"
+
+ fill_in "Name", with: "Color"
+ fill_in "Presentation", with: "Cool Color"
+ click_on "Add Property"
+
+ expect(page).to have_content("Property was successfully created.")
+ expect(page).to have_content("Color")
+ expect(page).to have_content("Cool Color")
+ expect(Spree::Property.count).to eq(1)
+ end
+
+ it "shows validation errors" do
+ visit "/admin/properties"
+ click_on "Add new"
+
+ fill_in "Name", with: ""
+ click_on "Add Property"
+
+ expect(page).to have_content("can't be blank")
+ expect(Spree::Property.count).to eq(0)
+ end
+ end
+
+ context "editing an existing property" do
+ let!(:property) { create(:property, name: "Color", presentation: "Cool Color") }
+
+ it "updates the property" do
+ visit "/admin/properties"
+ find_row("Color").click
+
+ fill_in "Name", with: "Size"
+ fill_in "Presentation", with: "Cool Size"
+ click_on "Update Property"
+
+ expect(page).to have_content("Property was successfully updated.")
+ expect(page).to have_content("Size")
+ expect(page).to have_content("Cool Size")
+ expect(Spree::Property.count).to eq(1)
+ end
+
+ it "shows validation errors" do
+ visit "/admin/properties"
+ find_row("Color").click
+
+ fill_in "Name", with: ""
+ click_on "Update Property"
+
+ expect(page).to have_content("can't be blank")
+ expect(Spree::Property.count).to eq(1)
+ end
+ end
+
+ context "editing an existing property" do
+ let!(:property) { create(:property, name: "Color", presentation: "Cool Color") }
+
+ it "updates the property" do
+ visit "/admin/properties"
+ find_row("Color").click
+
+ fill_in "Name", with: "Size"
+ fill_in "Presentation", with: "Cool Size"
+ click_on "Update Property"
+
+ expect(page).to have_content("Property was successfully updated.")
+ expect(page).to have_content("Size")
+ expect(page).to have_content("Cool Size")
+ expect(Spree::Property.count).to eq(1)
+ end
+
+ it "shows validation errors" do
+ visit "/admin/properties"
+ find_row("Color").click
+
+ fill_in "Name", with: ""
+ click_on "Update Property"
+
+ expect(page).to have_content("can't be blank")
+ expect(Spree::Property.count).to eq(1)
+ end
+ end
end
diff --git a/admin/spec/requests/solidus_admin/properties_spec.rb b/admin/spec/requests/solidus_admin/properties_spec.rb
new file mode 100644
index 00000000000..fa781e8153a
--- /dev/null
+++ b/admin/spec/requests/solidus_admin/properties_spec.rb
@@ -0,0 +1,133 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+RSpec.describe "SolidusAdmin::PropertiesController", type: :request do
+ let(:admin_user) { create(:admin_user) }
+ let(:property) { create(:property) }
+
+ 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.properties_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_property_path
+ expect(response).to have_http_status(:ok)
+ end
+ end
+
+ describe "POST /create" do
+ context "with valid parameters" do
+ let(:valid_attributes) { { name: "Material", presentation: "Material Type" } }
+
+ it "creates a new Property" do
+ expect {
+ post solidus_admin.properties_path, params: { property: valid_attributes }
+ }.to change(Spree::Property, :count).by(1)
+ end
+
+ it "redirects to the index page with a 303 See Other status" do
+ post solidus_admin.properties_path, params: { property: valid_attributes }
+ expect(response).to redirect_to(solidus_admin.properties_path)
+ expect(response).to have_http_status(:see_other)
+ end
+
+ it "displays a success flash message" do
+ post solidus_admin.properties_path, params: { property: valid_attributes }
+ follow_redirect!
+ expect(response.body).to include("Property was successfully created.")
+ end
+ end
+
+ context "with invalid parameters" do
+ let(:invalid_attributes) { { name: "", presentation: "" } }
+
+ it "does not create a new Property" do
+ expect {
+ post solidus_admin.properties_path, params: { property: invalid_attributes }
+ }.not_to change(Spree::Property, :count)
+ end
+
+ it "renders the new template with unprocessable_entity status" do
+ post solidus_admin.properties_path, params: { property: invalid_attributes }
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+ end
+
+ describe "GET /edit" do
+ it "renders the edit template with a 200 OK status" do
+ get solidus_admin.edit_property_path(property)
+ expect(response).to have_http_status(:ok)
+ end
+ end
+
+ describe "PATCH /update" do
+ context "with valid parameters" do
+ let(:valid_attributes) { { name: "Updated Name", presentation: "Updated Presentation" } }
+
+ it "updates the property" do
+ patch solidus_admin.property_path(property), params: { property: valid_attributes }
+ property.reload
+ expect(property.name).to eq("Updated Name")
+ expect(property.presentation).to eq("Updated Presentation")
+ end
+
+ it "redirects to the index page with a 303 See Other status" do
+ patch solidus_admin.property_path(property), params: { property: valid_attributes }
+ expect(response).to redirect_to(solidus_admin.properties_path)
+ expect(response).to have_http_status(:see_other)
+ end
+
+ it "displays a success flash message" do
+ patch solidus_admin.property_path(property), params: { property: valid_attributes }
+ follow_redirect!
+ expect(response.body).to include("Property was successfully updated.")
+ end
+ end
+
+ context "with invalid parameters" do
+ let(:invalid_attributes) { { name: "", presentation: "Updated Presentation" } }
+
+ it "does not update the property" do
+ original_name = property.name
+ patch solidus_admin.property_path(property), params: { property: invalid_attributes }
+ property.reload
+ expect(property.name).to eq(original_name)
+ end
+
+ it "renders the edit template with unprocessable_entity status" do
+ patch solidus_admin.property_path(property), params: { property: invalid_attributes }
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+ end
+
+ describe "DELETE /destroy" do
+ it "deletes the property and redirects to the index page with a 303 See Other status" do
+ # Ensure the property exists before attempting to delete it.
+ property
+
+ expect {
+ delete solidus_admin.property_path(property)
+ }.to change(Spree::Property, :count).by(-1)
+
+ expect(response).to redirect_to(solidus_admin.properties_path)
+ expect(response).to have_http_status(:see_other)
+ end
+
+ it "displays a success flash message after deletion" do
+ delete solidus_admin.property_path(property)
+ follow_redirect!
+ expect(response.body).to include("Properties were successfully removed.")
+ end
+ end
+end