Skip to content

Commit 34599c8

Browse files
committed
Update admin view for store new and edit form
- Added component for rendering the store edit and new views. - Added address partial as a separate component within the store form. - Added the test cases for new, edit and address form component
1 parent 9856b8f commit 34599c8

File tree

15 files changed

+601
-1
lines changed

15 files changed

+601
-1
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<fieldset class="<%= stimulus_id %>"
2+
data-controller="<%= stimulus_id %>"
3+
>
4+
<div class="<%= stimulus_id %>--address-form flex flex-wrap gap-4 pb-4">
5+
<%= render component("ui/forms/field").text_field(@name, :legal_name, object: @store) %>
6+
<%= render component("ui/forms/field").text_field(@name, :address1, object: @store) %>
7+
<%= render component("ui/forms/field").text_field(@name, :address2, object: @store) %>
8+
<div class="flex gap-4 w-full">
9+
<%= render component("ui/forms/field").text_field(@name, :city, object: @store) %>
10+
<%= render component("ui/forms/field").text_field(@name, :zipcode, object: @store) %>
11+
</div>
12+
13+
<%= render component("ui/forms/field").select(
14+
@name,
15+
:country_id,
16+
Spree::Country.pluck(:name, :id),
17+
object: @store,
18+
value: @store.country_id,
19+
"data-#{stimulus_id}-target": "country",
20+
"data-action": "change->#{stimulus_id}#loadStates"
21+
) %>
22+
23+
<%= content_tag :div,
24+
class: "flex flex-col gap-2 w-full #{'hidden' unless @store.country&.states_required}",
25+
data: { "#{stimulus_id}-target": "stateNameWrapper" } do %>
26+
<%= render component("ui/forms/field").text_field(
27+
@name, :state_name,
28+
object: @store,
29+
value: @store.state_name,
30+
data: { "#{stimulus_id}-target": "stateName" }
31+
) %>
32+
<% end %>
33+
34+
<input autocomplete="off" type="hidden" name=<%= "#{@name}[state_id]" %>>
35+
36+
<%= content_tag :div,
37+
class: "flex flex-col gap-2 w-full #{'hidden' if @store.country&.states_required}",
38+
data: { "#{stimulus_id}-target": "stateWrapper" } do %>
39+
<%= render component("ui/forms/field").select(
40+
@name, :state_id,
41+
state_options,
42+
object: @store,
43+
value: @store.state_id,
44+
data: { "#{stimulus_id}-target": "state" }
45+
) %>
46+
<% end %>
47+
48+
<%= render component("ui/forms/field").text_field(@name, :contact_phone, object: @store) %>
49+
</div>
50+
</fieldset>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Controller } from "@hotwired/stimulus"
2+
3+
export default class extends Controller {
4+
static targets = ["country", "state", "stateName", "stateWrapper", "stateNameWrapper"]
5+
6+
loadStates() {
7+
const countryId = this.countryTarget.value
8+
9+
fetch(`/admin/countries/${countryId}/states`)
10+
.then((response) => response.json())
11+
.then((data) => {
12+
this.updateStateOptions(data)
13+
})
14+
}
15+
16+
updateStateOptions(states) {
17+
if (states.length === 0) {
18+
this.toggleStateFields(false)
19+
} else {
20+
this.toggleStateFields(true)
21+
this.populateStateSelect(states)
22+
}
23+
}
24+
25+
toggleStateFields(showSelect) {
26+
const stateWrapper = this.stateWrapperTarget
27+
const stateNameWrapper = this.stateNameWrapperTarget
28+
const stateSelect = this.stateTarget
29+
const stateName = this.stateNameTarget
30+
31+
if (showSelect) {
32+
// Show state select dropdown.
33+
stateSelect.disabled = false
34+
stateName.value = ""
35+
stateWrapper.classList.remove("hidden")
36+
stateNameWrapper.classList.add("hidden")
37+
} else {
38+
// Show state name text input if no states to choose from.
39+
stateSelect.disabled = true
40+
stateWrapper.classList.add("hidden")
41+
stateNameWrapper.classList.remove("hidden")
42+
}
43+
}
44+
45+
populateStateSelect(states) {
46+
const stateSelect = this.stateTarget
47+
stateSelect.innerHTML = ""
48+
49+
states.forEach((state) => {
50+
const option = document.createElement("option")
51+
option.value = state.id
52+
option.innerText = state.name
53+
stateSelect.appendChild(option)
54+
})
55+
}
56+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
class SolidusAdmin::Stores::AddressForm::Component < SolidusAdmin::BaseComponent
4+
def initialize(store:)
5+
@name = "store"
6+
@store = store
7+
end
8+
9+
def state_options
10+
country = @store.country
11+
return [] unless country && country.states_required
12+
13+
country.states.pluck(:name, :id)
14+
end
15+
end
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<%= page do %>
2+
<%= page_header do %>
3+
<%= page_header_back(solidus_admin.stores_path) %>
4+
<%= page_header_title(t(".title", store: @store&.name)) %>
5+
<% end %>
6+
7+
<%= form_for @store, url: solidus_admin.store_path(@store), html: { id: form_id } do |f| %>
8+
<%= page_with_sidebar do %>
9+
<%= page_with_sidebar_main do %>
10+
<%= render component("ui/panel").new(title: t(".store_details")) do %>
11+
<div class="flex flex-wrap gap-4 pb-4">
12+
<%= render component("ui/forms/field").text_field(f, :name, required: true) %>
13+
<%= render component("ui/forms/field").text_field(f, :code, required: true) %>
14+
<%= render component("ui/forms/field").text_field(f, :seo_title) %>
15+
<%= render component("ui/forms/field").text_field(f, :meta_keywords) %>
16+
<%= render component("ui/forms/field").text_area(f, :meta_description) %>
17+
<%= render component("ui/forms/field").text_field(f, :tax_id) %>
18+
<%= render component("ui/forms/field").text_field(f, :vat_id) %>
19+
<%= render component("ui/forms/field").text_field(f, :url, required: true) %>
20+
<%= render component("ui/forms/field").text_field(f, :mail_from_address, required: true) %>
21+
<%= render component("ui/forms/field").text_field(f, :bcc_email) %>
22+
<%= render component("ui/forms/field").select(
23+
f,
24+
:default_currency,
25+
currency_options,
26+
include_blank: true
27+
) %>
28+
<%= render component("ui/forms/field").select(
29+
f,
30+
:cart_tax_country_iso,
31+
cart_tax_country_options,
32+
include_blank: t(".no_cart_tax_country")
33+
) %>
34+
<%= render component("ui/forms/field").select(
35+
f,
36+
:available_locales,
37+
localization_options,
38+
multiple: true,
39+
class: "select2",
40+
name: "store[available_locales][]"
41+
) %>
42+
</div>
43+
<%= render component("ui/forms/field").text_area(f, :description) %>
44+
<% end %>
45+
46+
<%= render component("ui/panel").new(title: t(".address")) do %>
47+
<div class="js-addresses-form">
48+
<%= render component("stores/address_form").new(
49+
store: @store,
50+
) %>
51+
</div>
52+
<% end %>
53+
<% end %>
54+
<% end %>
55+
<% end %>
56+
57+
<%= page_footer do %>
58+
<%= page_footer_actions do %>
59+
<div class="py-1.5 text-center">
60+
<%= render component("ui/button").new(tag: :button, text: t(".update"), form: form_id) %>
61+
<%= render component("ui/button").new(tag: :a, text: t(".cancel"), href: solidus_admin.edit_store_path(@store), scheme: :secondary) %>
62+
</div>
63+
<% end %>
64+
<% end %>
65+
<% end %>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# frozen_string_literal: true
2+
3+
class SolidusAdmin::Stores::Edit::Component < SolidusAdmin::BaseComponent
4+
include SolidusAdmin::Layout::PageHelpers
5+
6+
# Define the necessary attributes for the component
7+
attr_reader :store, :available_countries
8+
9+
# Initialize the component with required data
10+
def initialize(store:)
11+
@store = store
12+
@available_countries = fetch_available_countries
13+
end
14+
15+
def form_id
16+
@form_id ||= "#{stimulus_id}--form-#{@store.id}"
17+
end
18+
19+
def currency_options
20+
Spree::Config.available_currencies.map(&:iso_code)
21+
end
22+
23+
# Generates options for cart tax countries
24+
def cart_tax_country_options
25+
fetch_available_countries(restrict_to_zone: Spree::Config[:checkout_zone]).map do |country|
26+
[country.name, country.iso]
27+
end
28+
end
29+
30+
# Generates available locales
31+
def localization_options
32+
Spree.i18n_available_locales.map do |locale|
33+
[
34+
I18n.t('spree.i18n.this_file_language', locale: locale, default: locale.to_s),
35+
locale
36+
]
37+
end
38+
end
39+
40+
# Fetch countries for the address form
41+
def available_country_options
42+
Spree::Country.order(:name).map { |country| [country.name, country.id] }
43+
end
44+
45+
private
46+
47+
# Fetch the available countries for the localization section
48+
def fetch_available_countries(restrict_to_zone: Spree::Config[:checkout_zone])
49+
countries = Spree::Country.available(restrict_to_zone:)
50+
51+
country_names = Carmen::Country.all.map do |country|
52+
[country.code, country.name]
53+
end.to_h
54+
55+
country_names.update I18n.t('spree.country_names', default: {}).stringify_keys
56+
57+
countries.collect do |country|
58+
country.name = country_names.fetch(country.iso, country.name)
59+
country
60+
end.sort_by { |country| country.name.parameterize }
61+
end
62+
end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
en:
2+
title: "%{store}"
3+
store_details: Store Details
4+
address: Address
5+
update: Update
6+
cancel: Cancel
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<%= page do %>
2+
<%= page_header do %>
3+
<%= page_header_back(solidus_admin.stores_path) %>
4+
<%= page_header_title(t(".title")) %>
5+
<% end %>
6+
7+
<%= form_for @store, url: solidus_admin.stores_path, html: { id: form_id } do |f| %>
8+
<%= page_with_sidebar do %>
9+
<%= page_with_sidebar_main do %>
10+
<%= render component("ui/panel").new(title: t(".store_details")) do %>
11+
<div class="flex flex-wrap gap-4 pb-4">
12+
<%= render component("ui/forms/field").text_field(f, :name, required: true) %>
13+
<%= render component("ui/forms/field").text_field(f, :code, required: true) %>
14+
<%= render component("ui/forms/field").text_field(f, :seo_title) %>
15+
<%= render component("ui/forms/field").text_field(f, :meta_keywords) %>
16+
<%= render component("ui/forms/field").text_area(f, :meta_description) %>
17+
<%= render component("ui/forms/field").text_field(f, :tax_id) %>
18+
<%= render component("ui/forms/field").text_field(f, :vat_id) %>
19+
<%= render component("ui/forms/field").text_field(f, :url, required: true) %>
20+
<%= render component("ui/forms/field").text_field(f, :mail_from_address, required: true) %>
21+
<%= render component("ui/forms/field").text_field(f, :bcc_email) %>
22+
<%= render component("ui/forms/field").select(
23+
f,
24+
:default_currency,
25+
currency_options,
26+
include_blank: true
27+
) %>
28+
<%= render component("ui/forms/field").select(
29+
f,
30+
:cart_tax_country_iso,
31+
cart_tax_country_options,
32+
include_blank: t(".no_cart_tax_country")
33+
) %>
34+
<%= render component("ui/forms/field").select(
35+
f,
36+
:available_locales,
37+
localization_options,
38+
multiple: true,
39+
class: "select2",
40+
name: "store[available_locales][]"
41+
) %>
42+
</div>
43+
<%= render component("ui/forms/field").text_area(f, :description) %>
44+
<% end %>
45+
46+
<%= render component("ui/panel").new(title: t(".address")) do %>
47+
<div class="js-addresses-form">
48+
<%= render component("stores/address_form").new(
49+
store: @store,
50+
) %>
51+
</div>
52+
<% end %>
53+
<% end %>
54+
<% end %>
55+
<% end %>
56+
57+
<%= page_footer do %>
58+
<%= page_footer_actions do %>
59+
<div class="py-1.5 text-center">
60+
<%= render component("ui/button").new(tag: :button, text: t(".save"), form: form_id) %>
61+
<%= render component("ui/button").new(tag: :a, text: t(".cancel"), href: solidus_admin.new_store_path, scheme: :secondary) %>
62+
</div>
63+
<% end %>
64+
<% end %>
65+
<% end %>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# frozen_string_literal: true
2+
3+
class SolidusAdmin::Stores::New::Component < SolidusAdmin::BaseComponent
4+
include SolidusAdmin::Layout::PageHelpers
5+
6+
# Define the necessary attributes for the component
7+
attr_reader :store, :available_countries
8+
9+
# Initialize the component with required data
10+
def initialize(store:)
11+
@store = store
12+
@available_countries = fetch_available_countries
13+
end
14+
15+
def form_id
16+
@form_id ||= "#{stimulus_id}--form-#{@store.id}"
17+
end
18+
19+
def currency_options
20+
Spree::Config.available_currencies.map(&:iso_code)
21+
end
22+
23+
# Generates options for cart tax countries
24+
def cart_tax_country_options
25+
fetch_available_countries(restrict_to_zone: Spree::Config[:checkout_zone]).map do |country|
26+
[country.name, country.iso]
27+
end
28+
end
29+
30+
# Generates available locales
31+
def localization_options
32+
Spree.i18n_available_locales.map do |locale|
33+
[
34+
I18n.t('spree.i18n.this_file_language', locale: locale, default: locale.to_s),
35+
locale
36+
]
37+
end
38+
end
39+
40+
# Fetch countries for the address form
41+
def available_country_options
42+
Spree::Country.order(:name).map { |country| [country.name, country.id] }
43+
end
44+
45+
private
46+
47+
# Fetch the available countries for the localization section
48+
def fetch_available_countries(restrict_to_zone: Spree::Config[:checkout_zone])
49+
countries = Spree::Country.available(restrict_to_zone:)
50+
51+
country_names = Carmen::Country.all.map do |country|
52+
[country.code, country.name]
53+
end.to_h
54+
55+
country_names.update I18n.t('spree.country_names', default: {}).stringify_keys
56+
57+
countries.collect do |country|
58+
country.name = country_names.fetch(country.iso, country.name)
59+
country
60+
end.sort_by { |country| country.name.parameterize }
61+
end
62+
end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
en:
2+
title: "New Store"
3+
save: Save
4+
store_details: Store Details
5+
address: Address
6+
cancel: Cancel

0 commit comments

Comments
 (0)