Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
192a059
Create component for zones creation
chaimann Mar 19, 2025
f7ddd2d
Create component for zones edition
chaimann Mar 19, 2025
8d5b5de
Create `.submit` helper method for buttons
chaimann Mar 19, 2025
498f6a0
Create `.cancel` helper method for buttons
chaimann Mar 19, 2025
3d7358b
Merge "new" & "edit" into "form" component
chaimann Mar 19, 2025
abdce76
Define `title` of the modal in form component
chaimann Mar 19, 2025
fe76121
Order zone countries by name
chaimann Mar 19, 2025
525a64c
Make "Kind" select toggle country/state select fields
chaimann Mar 19, 2025
7d00ce0
Create shortcuts for stimulus data attributes
chaimann Mar 19, 2025
390eacd
Update rubocop Naming/MethodParameterName cop config
chaimann Mar 20, 2025
532dddf
Avoid N+1 when loading zones index page
chaimann Mar 20, 2025
57a5344
Use remote option loading for states select
chaimann Apr 8, 2025
f53a2b2
Fix select input placeholder visibility in Firefox
chaimann Apr 9, 2025
6164eb3
Update zones form
chaimann Apr 9, 2025
f6d976f
Update field component #select
chaimann Apr 9, 2025
a7c2ead
Update solidus_select feature helper to account for options loading
chaimann Apr 10, 2025
12761d2
Update solidus_select feature helper to accept array of values
chaimann Apr 10, 2025
7610505
Add tests
chaimann Apr 9, 2025
9adf583
Remove duplicate test
chaimann Apr 9, 2025
fd07b45
Explicitly disable offending cop
chaimann Apr 10, 2025
620b45d
Autocorrect rubocop offences
chaimann May 28, 2025
9b7e333
Escape "on" in yaml for correct parsing
chaimann May 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ Naming/MethodParameterName:
- id
- to
- _
- "on"

# Rubocop doesn't understand side-effects
Style/IdenticalConditionalBranches:
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ gem 'puma', '< 7', require: false
gem 'i18n-tasks', '~> 0.9', require: false
gem 'rspec_junit_formatter', require: false
gem 'yard', require: false
gem 'db-query-matchers', require: false

# Ensure the requirement is also updated in core/lib/spree/testing_support/factory_bot.rb
gem 'factory_bot_rails', '>= 4.8', require: false
Expand Down
13 changes: 13 additions & 0 deletions admin/app/blueprints/solidus_admin/state_blueprint.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module SolidusAdmin
class StateBlueprint < Blueprinter::Base
identifier :id

field :name

view :state_with_country do
field :state_with_country, name: :name
end
end
end
1 change: 1 addition & 0 deletions admin/app/components/solidus_admin/base_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module SolidusAdmin
# BaseComponent is the base class for all components in Solidus Admin.
class BaseComponent < ViewComponent::Base
include SolidusAdmin::ComponentsHelper
include SolidusAdmin::StimulusHelper
include SolidusAdmin::VoidElementsHelper
include Turbo::FramesHelper

Expand Down
10 changes: 10 additions & 0 deletions admin/app/components/solidus_admin/ui/button/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ def initialize(
]
end

def self.submit(resource:, **attrs)
resource_name = resource.model_name.human
text = resource.new_record? ? t('.submit.create', resource_name:) : t('.submit.update', resource_name:)
new(text:, type: :submit, **attrs)
end

def self.cancel
new(scheme: :secondary, text: t('.cancel'))
end

def call
content = []
content << render(component('ui/icon').new(name: @icon, class: @icon_classes)) if @icon
Expand Down
5 changes: 5 additions & 0 deletions admin/app/components/solidus_admin/ui/button/component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
en:
cancel: "Cancel"
submit:
create: "Add %{resource_name}"
update: "Update %{resource_name}"
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ def self.select(form, method, choices, object: nil, hint: nil, tip: nil, size: :
hint:,
tip:,
size: size,
name: "#{object_name}[#{method}]",
name: "#{object_name}[#{method}]#{'[]' if attributes[:multiple].present?}",
choices:,
value: (object.public_send(method) if object.respond_to?(method)),
value: object.try(method),
error: (errors.to_sentence.capitalize if errors),
**attributes
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def prepare_classes(size:)
[&_.item_.remove-button]:has-[:disabled]:cursor-not-allowed"
end

input_classes = ["[&_input]:has-[.item]:placeholder:invisible [&_input:disabled]:cursor-not-allowed [&_input:disabled]:bg-gray-50
input_classes = ["[&_input]:has-[.item]:placeholder:opacity-0 [&_input:disabled]:cursor-not-allowed [&_input:disabled]:bg-gray-50
[&_input]:peer-invalid:placeholder:text-red-400"]

unless @attributes[:multiple]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= render component("zones/form").new(zone: @zone, form_url:, form_id:) %>
4 changes: 4 additions & 0 deletions admin/app/components/solidus_admin/zones/edit/component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class SolidusAdmin::Zones::Edit::Component < SolidusAdmin::Resources::Edit::Component
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<%= turbo_frame_tag :resource_modal, target: "_top" do %>
<%= render component("ui/modal").new(title:) do |modal| %>
<%= form_for @zone, url: @form_url, html: { id: @form_id, **stimulus_controller, **stimulus_value(name: :kind, value: @zone.kind) } do |f| %>
<div class="flex flex-col gap-6 pb-4">
<%= render component("ui/forms/field").text_field(f, :name, class: "required") %>
<div class="hidden" <%= tag.attributes(stimulus_target("countriesWrapper")) %>>
<%= render component("ui/forms/field").select(
f,
:country_ids,
Spree::Country.order(:name).map { |c| [c.name, c.id] },
object: @zone,
multiple: true,
class: "required",
**stimulus_target("countriesSelect")
) %>
</div>
<div class="hidden" <%= tag.attributes(stimulus_target("statesWrapper")) %>>
<%= render component("ui/forms/field").select(
f,
:state_ids,
Spree::State.where(id: @zone.state_ids).map { |s| [s.state_with_country, s.id] },
object: @zone,
multiple: true,
class: "required",
src: solidus_admin.states_url(view: :state_with_country),
"data-query-param": "q[name_or_country_name_cont]",
**stimulus_target("statesSelect")
) %>
</div>
<%= render component("ui/forms/field").text_field(f, :description, class: "required") %>
<%= render component("ui/forms/field").select(
f,
:kind,
[[t(".kinds.country"), :country], [t(".kinds.state"), :state]],
object: @zone,
value: @zone.kind || :state,
**stimulus_action("toggleKind", on: "change")
) %>
</div>
<% modal.with_actions do %>
<form method="dialog">
<%= render component("ui/button").cancel %>
</form>
<%= render component("ui/button").submit(resource: @zone, form: @form_id) %>
<% end %>
<% end %>
<% end %>
<% end %>
32 changes: 32 additions & 0 deletions admin/app/components/solidus_admin/zones/form/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ["countriesWrapper", "statesWrapper", "countriesSelect", "statesSelect"]
static values = {
kind: { type: String, default: "state" }
}

connect() {
this.toggleSelectsVisibility();
this.toggleSelectDisabled();
}

toggleKind(event) {
if (!event.target.value) return;

this.kindValue = event.target.value;

this.toggleSelectsVisibility();
this.toggleSelectDisabled();
}

toggleSelectsVisibility() {
this.countriesWrapperTarget.classList.toggle("hidden", this.kindValue === "state");
this.statesWrapperTarget.classList.toggle("hidden", this.kindValue === "country");
}

toggleSelectDisabled() {
this.countriesSelectTarget.disabled = this.kindValue === "state";
this.statesSelectTarget.disabled = this.kindValue === "country";
}
}
13 changes: 13 additions & 0 deletions admin/app/components/solidus_admin/zones/form/component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

class SolidusAdmin::Zones::Form::Component < SolidusAdmin::BaseComponent
def initialize(zone:, form_url:, form_id:)
@zone = zone
@form_url = form_url
@form_id = form_id
end

def title
@zone.new_record? ? t(".title.new") : t(".title.edit")
end
end
7 changes: 7 additions & 0 deletions admin/app/components/solidus_admin/zones/form/component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
en:
kinds:
country: "Country based"
state: "State based"
title:
edit: "Edit Zone"
new: "New Zone"
65 changes: 52 additions & 13 deletions admin/app/components/solidus_admin/zones/index/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ def model_class
Spree::Zone
end

def title
t('solidus_admin.zones.title')
end

def search_key
:name_or_description_cont
end
Expand All @@ -13,15 +17,20 @@ def search_url
solidus_admin.zones_path
end

def row_url(zone)
spree.edit_admin_zone_path(zone)
def edit_path(zone)
solidus_admin.edit_zone_path(zone, **search_filter_params)
end

def turbo_frames
%w[resource_modal]
end

def page_actions
render component("ui/button").new(
tag: :a,
text: t('.add'),
href: spree.new_admin_zone_path,
href: solidus_admin.new_zone_path(**search_filter_params),
data: { turbo_frame: :resource_modal },
icon: "add-line",
class: "align-self-end w-full",
)
Expand All @@ -48,16 +57,46 @@ def filters

def columns
[
:name,
:description,
{
header: :kind,
data: -> { component('ui/badge').new(name: _1.kind, color: _1.kind == 'country' ? :green : :blue) },
},
{
header: :zone_members,
data: -> { _1.zone_members.map(&:zoneable).map(&:name).to_sentence },
},
name_column,
description_column,
kind_column,
zone_members_column,
]
end

def name_column
{
header: :name,
data: ->(zone) do
link_to zone.name, edit_path(zone),
data: { turbo_frame: :resource_modal },
class: 'body-link'
end
}
end

def description_column
{
header: :description,
data: ->(zone) do
link_to zone.description, edit_path(zone),
data: { turbo_frame: :resource_modal },
class: 'body-link'
end
}
end

def kind_column
{
header: :kind,
data: -> { component('ui/badge').new(name: _1.kind, color: _1.kind == 'country' ? :green : :blue) },
}
end

def zone_members_column
{
header: :zone_members,
data: -> { _1.zone_members.map(&:zoneable).map(&:name).to_sentence },
}
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= render component("zones/form").new(zone: @zone, form_url:, form_id:) %>
4 changes: 4 additions & 0 deletions admin/app/components/solidus_admin/zones/new/component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# frozen_string_literal: true

class SolidusAdmin::Zones::New::Component < SolidusAdmin::Resources::New::Component
end
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class ResourcesController < SolidusAdmin::BaseController
def index
respond_to do |format|
format.html { render index_component.new(page: @page) }
format.json { render json: blueprint.render(@page.records, view: blueprint_view) }
end
end

Expand Down Expand Up @@ -77,7 +78,7 @@ def set_paginated_resources
).tap do |resources|
instance_variable_set("@#{plural_resource_name}", resources)
# sets @page instance variable in geared_pagination gem
set_page_and_extract_portion_from(resources, ordered_by: resources_sorting_options)
set_page_and_extract_portion_from(resources, ordered_by: resources_sorting_options, per_page:)
end
end

Expand All @@ -89,6 +90,8 @@ def resources_collection
resource_class.all
end

def per_page; end

def set_resource
@resource ||= resource_class.find(params[:id]).tap do |resource|
instance_variable_set("@#{resource_name}", resource)
Expand Down
30 changes: 30 additions & 0 deletions admin/app/controllers/solidus_admin/states_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

module SolidusAdmin
class StatesController < SolidusAdmin::ResourcesController
skip_before_action :authorize_solidus_admin_user!

private

def resource_class
Spree::State
end

def resources_sorting_options
{ name: :asc }
end

def blueprint
SolidusAdmin::StateBlueprint
end

def blueprint_view
view = params[:view]&.to_sym
blueprint.view?(view) ? view : :default
end

def per_page
100
end
end
end
32 changes: 6 additions & 26 deletions admin/app/controllers/solidus_admin/zones_controller.rb
Original file line number Diff line number Diff line change
@@ -1,35 +1,15 @@
# frozen_string_literal: true

module SolidusAdmin
class ZonesController < SolidusAdmin::BaseController
include SolidusAdmin::ControllerHelpers::Search

def index
zones = apply_search_to(
Spree::Zone.order(id: :desc),
param: :q
)

set_page_and_extract_portion_from(zones)

respond_to do |format|
format.html { render component('zones/index').new(page: @page) }
end
end

def destroy
@zones = Spree::Zone.where(id: params[:id])

Spree::Zone.transaction { @zones.destroy_all }
class ZonesController < SolidusAdmin::ResourcesController
private

flash[:notice] = t('.success')
redirect_back_or_to zones_path, status: :see_other
end
def resource_class = Spree::Zone

private
def resources_collection = Spree::Zone.includes(zone_members: :zoneable)

def zone_params
params.require(:zone).permit(:zone_id, permitted_zone_attributes)
def permitted_resource_params
params.require(:zone).permit(:name, :description, state_ids: [], country_ids: [])
end
end
end
Loading
Loading