Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<fieldset class="<%= stimulus_id %>"
data-controller="<%= stimulus_id %>"
>
<div class="<%= stimulus_id %>--address-form flex flex-wrap gap-4 pb-4">
<%= render component("ui/forms/field").text_field(@name, :legal_name, object: @store) %>
<%= render component("ui/forms/field").text_field(@name, :address1, object: @store) %>
<%= render component("ui/forms/field").text_field(@name, :address2, object: @store) %>
<div class="flex gap-4 w-full">
<%= render component("ui/forms/field").text_field(@name, :city, object: @store) %>
<%= render component("ui/forms/field").text_field(@name, :zipcode, object: @store) %>
</div>

<%= render component("ui/forms/field").select(
@name,
:country_id,
Spree::Country.pluck(:name, :id),
object: @store,
value: @store.country_id,
"data-#{stimulus_id}-target": "country",
"data-action": "change->#{stimulus_id}#loadStates"
) %>
<%= content_tag :div,
class: "flex flex-col gap-2 w-full #{'hidden' if @store.country&.states_required}",
data: { "#{stimulus_id}-target": "stateNameWrapper" } do %>
<%= render component("ui/forms/field").text_field(
@name, :state_name,
object: @store,
value: @store.state_name,
data: { "#{stimulus_id}-target": "stateName" }
) %>
<% end %>

<input autocomplete="off" type="hidden" name=<%= "#{@name}[state_id]" %>>

<%= content_tag :div,
class: "flex flex-col gap-2 w-full #{'hidden' unless @store.country&.states_required}",
data: { "#{stimulus_id}-target": "stateWrapper" } do %>
<%= render component("ui/forms/field").select(
@name, :state_id,
state_options,
object: @store,
value: @store.state_id,
data: { "#{stimulus_id}-target": "state" }
) %>
<% end %>
<%= render component("ui/forms/field").text_field(@name, :tax_id, object: @store) %>
<%= render component("ui/forms/field").text_field(@name, :vat_id, object: @store, hint: t(".hint.vat_id").html_safe) %>
</div>
</fieldset>
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Controller } from "@hotwired/stimulus"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a address form component that handles states. Can we use that one instead of implementing a second one?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can look into reusing the logic to pick a state. Does it make sense if everything else should be detached from the address table?


export default class extends Controller {
static targets = ["country", "state", "stateName", "stateWrapper", "stateNameWrapper"]

loadStates() {
const countryId = this.countryTarget.value

fetch(`/admin/countries/${countryId}/states`)
.then((response) => response.json())
.then((data) => {
this.updateStateOptions(data)
})
}

updateStateOptions(states) {
if (states.length === 0) {
this.toggleStateFields(false)
} else {
this.toggleStateFields(true)
this.populateStateSelect(states)
}
}

toggleStateFields(showSelect) {
const stateWrapper = this.stateWrapperTarget
const stateNameWrapper = this.stateNameWrapperTarget
const stateSelect = this.stateTarget
const stateName = this.stateNameTarget

if (showSelect) {
// Show state select dropdown.
stateSelect.disabled = false
stateName.value = ""
stateWrapper.classList.remove("hidden")
stateNameWrapper.classList.add("hidden")
} else {
// Show state name text input if no states to choose from.
stateSelect.disabled = true
stateWrapper.classList.add("hidden")
stateNameWrapper.classList.remove("hidden")
}
}

populateStateSelect(states) {
const stateSelect = this.stateTarget
stateSelect.innerHTML = ""

states.forEach((state) => {
const option = document.createElement("option")
option.value = state.id
option.innerText = state.name
stateSelect.appendChild(option)
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

class SolidusAdmin::Stores::AddressForm::Component < SolidusAdmin::BaseComponent
def initialize(store:)
@name = "store"
@store = store
end

def state_options
country = @store.country
return [] unless country && country.states_required

country.states.pluck(:name, :id)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
en:
hint:
vat_id: Enter your VAT-ID without the Country Prefix (eg IT for Italy or DE for Germany) but solely the identification string.
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<%= page do %>
<%= page_header do %>
<%= page_header_back(solidus_admin.stores_path) %>
<%= page_header_title(t(".title", store: @store&.name)) %>
<%= page_header_actions do %>
<div class="py-1.5 text-center">
<%= render component("ui/button").new(tag: :button, text: t(".update"), form: form_id) %>
<%= render component("ui/button").new(tag: :a, text: t(".cancel"), href: solidus_admin.edit_store_path(@store), scheme: :secondary) %>
</div>
<% end %>
<% end %>

<%= form_for @store, url: solidus_admin.store_path(@store), html: { id: form_id } do |f| %>
<%= page_with_sidebar do %>
<%= page_with_sidebar_main do %>
<%= render component("ui/panel").new(title: t(".store_settings")) do %>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why haven't you used the existing address form component?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It didn't seem indicated to invoke the address list for performance reasons.
These values should be cached and provided separately as otherwise the address tabled needs to be used for values that in some scenarios could be embedded on every page depending on the implementation.

<div class="flex flex-wrap gap-4 pb-4">
<%= render component("ui/forms/field").text_field(f, :name, required: true) %>
<%= render component("ui/forms/field").text_field(f, :url, required: true) %>
<%= render component("ui/forms/field").text_field(f, :code, required: true, hint: t(".hint.code").html_safe) %>
</div>
<% end %>

<%= render component("ui/panel").new(title: t(".regional_settings")) do %>
<div class="flex flex-wrap gap-4 pb-4">
<%= render component("ui/forms/field").select(
f,
:default_currency,
currency_options,
include_blank: true,
hint: t(".hint.default_currency").html_safe
) %>
<%= render component("ui/forms/field").select(
f,
:cart_tax_country_iso,
cart_tax_country_options,
include_blank: t(".no_cart_tax_country"),
hint: t(".hint.cart_tax_country_iso").html_safe
) %>
<%= render component("ui/forms/field").select(
f,
:available_locales,
localization_options,
multiple: true,
class: "select2",
name: "store[available_locales][]",
hint: t(".hint.available_locales").html_safe
) %>
</div>
<% end %>

<%= render component("ui/panel").new(title: t(".email_settings")) do %>
<div class="flex flex-wrap gap-4 pb-4">
<%= render component("ui/forms/field").text_field(f, :mail_from_address, required: true) %>
<%= render component("ui/forms/field").text_field(f, :bcc_email) %>
</div>
<% end %>

<%= render component("ui/panel").new(title: t(".store_legal_addres")) do %>
<div class="flex flex-wrap gap-4 pb-4">
<div class="js-addresses-form">
<%= render component("stores/address_form").new(
store: @store,
) %>
</div>
</div>
<% end %>

<%= render component("ui/panel").new(title: t(".contact_options")) do %>
<div class="flex flex-wrap gap-4 pb-4">
<%= render component("ui/forms/field").text_field(f, :contact_phone) %>
<%= render component("ui/forms/field").text_field(f, :contact_email) %>
</div>
<% end %>

<%= render component("ui/panel").new(title: t(".content_on_storefront")) do %>
<div class="flex flex-wrap gap-4 pb-4">
<%= render component("ui/forms/field").text_field(f, :seo_title) %>
<%= render component("ui/forms/field").text_area(f, :description) %>
<%= render component("ui/forms/field").text_field(f, :meta_keywords) %>
<%= render component("ui/forms/field").text_area(f, :meta_description) %>
</div>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
62 changes: 62 additions & 0 deletions admin/app/components/solidus_admin/stores/edit/component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

class SolidusAdmin::Stores::Edit::Component < SolidusAdmin::BaseComponent
include SolidusAdmin::Layout::PageHelpers

# Define the necessary attributes for the component
attr_reader :store, :available_countries

# Initialize the component with required data
def initialize(store:)
@store = store
@available_countries = fetch_available_countries
end

def form_id
@form_id ||= "#{stimulus_id}--form-#{@store.id}"
end

def currency_options
Spree::Config.available_currencies.map(&:iso_code)
end

# Generates options for cart tax countries
def cart_tax_country_options
fetch_available_countries(restrict_to_zone: Spree::Config[:checkout_zone]).map do |country|
[country.name, country.iso]
end
end

# Generates available locales
def localization_options
Spree.i18n_available_locales.map do |locale|
[
I18n.t('spree.i18n.this_file_language', locale: locale, default: locale.to_s),
locale
]
end
end

# Fetch countries for the address form
def available_country_options
Spree::Country.order(:name).map { |country| [country.name, country.id] }
end

private

# Fetch the available countries for the localization section
def fetch_available_countries(restrict_to_zone: Spree::Config[:checkout_zone])
countries = Spree::Country.available(restrict_to_zone:)

country_names = Carmen::Country.all.map do |country|
[country.code, country.name]
end.to_h

country_names.update I18n.t('spree.country_names', default: {}).stringify_keys

countries.collect do |country|
country.name = country_names.fetch(country.iso, country.name)
country
end.sort_by { |country| country.name.parameterize }
end
end
17 changes: 17 additions & 0 deletions admin/app/components/solidus_admin/stores/edit/component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
en:
address: Address
cancel: Cancel
contact_options: Contact Options
content_on_storefront: Content on Storefront
email_settings: Email Settings
hint:
available_locales: This determines which locales are available for your customers to choose from in the storefront.
cart_tax_country_iso: 'This determines which country is used for taxes on carts (orders which don''t yet have an address).<br> Default: None.'
code: An identifier for your store. Developers may need this value if you operate multiple storefronts.
default_currency: This determines which currency will be used for the storefront's product prices. Please, be aware that changing this configuration, only products that have prices in the selected currency will be listed on your storefront. <br>This setting won't change the default currency used when you create a product. For that, only the global `Spree::Config.currency` is taken into account.
regional_settings: Regional Settings
store_details: Store Details
store_legal_addres: Store Legal Address
store_settings: Store Name & URL / URI Settings
title: "%{store}"
update: Update
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<%= page do %>
<%= page_header do %>
<%= page_header_back(solidus_admin.stores_path) %>
<%= page_header_title(t(".title")) %>
<%= page_header_actions do %>
<div class="py-1.5 text-center">
<%= render component("ui/button").new(tag: :button, text: t(".save"), form: form_id) %>
<%= render component("ui/button").new(tag: :a, text: t(".cancel"), href: solidus_admin.new_store_path, scheme: :secondary) %>
</div>
<% end %>
<% end %>

<%= form_for @store, url: solidus_admin.stores_path, html: { id: form_id } do |f| %>
<%= page_with_sidebar do %>
<%= page_with_sidebar_main do %>
<%= render component("ui/panel").new(title: t(".store_settings")) do %>
<div class="flex flex-wrap gap-4 pb-4">
<%= render component("ui/forms/field").text_field(f, :name, required: true) %>
<%= render component("ui/forms/field").text_field(f, :url, required: true) %>
<%= render component("ui/forms/field").text_field(f, :code, required: true, hint: t(".hint.code").html_safe) %>
</div> <!-- Closing div was missing -->
<% end %>

<%= render component("ui/panel").new(title: t(".regional_settings")) do %>
<div class="flex flex-wrap gap-4 pb-4">
<%= render component("ui/forms/field").select(
f,
:default_currency,
currency_options,
include_blank: true,
hint: t(".hint.default_currency").html_safe
) %>
<%= render component("ui/forms/field").select(
f,
:cart_tax_country_iso,
cart_tax_country_options,
include_blank: t(".no_cart_tax_country"),
hint: t(".hint.cart_tax_country_iso").html_safe
) %>
<%= render component("ui/forms/field").select(
f,
:available_locales,
localization_options,
multiple: true,
class: "select2",
name: "store[available_locales][]",
hint: t(".hint.available_locales").html_safe
) %>
</div>
<% end %>

<%= render component("ui/panel").new(title: t(".email_settings")) do %>
<div class="flex flex-wrap gap-4 pb-4">
<%= render component("ui/forms/field").text_field(f, :mail_from_address, required: true) %>
<%= render component("ui/forms/field").text_field(f, :bcc_email) %>
</div>
<% end %>

<%= render component("ui/panel").new(title: t(".store_legal_addres")) do %>
<div class="flex flex-wrap gap-4 pb-4">
<div class="js-addresses-form">
<%= render component("stores/address_form").new(
store: @store,
) %>
</div>
</div>
<% end %>

<%= render component("ui/panel").new(title: t(".contact_options")) do %>
<div class="flex flex-wrap gap-4 pb-4">
<%= render component("ui/forms/field").text_field(f, :contact_phone) %>
<%= render component("ui/forms/field").text_field(f, :contact_email) %>
</div>
<% end %>

<%= render component("ui/panel").new(title: t(".content_on_storefront")) do %>
<div class="flex flex-wrap gap-4 pb-4">
<%= render component("ui/forms/field").text_field(f, :seo_title) %>
<%= render component("ui/forms/field").text_area(f, :description) %>
<%= render component("ui/forms/field").text_field(f, :meta_keywords) %>
<%= render component("ui/forms/field").text_area(f, :meta_description) %>
</div>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
Loading
Loading