diff --git a/app/controllers/spree/admin/countries_controller.rb b/app/controllers/spree/admin/countries_controller.rb new file mode 100644 index 0000000000..4314e54b37 --- /dev/null +++ b/app/controllers/spree/admin/countries_controller.rb @@ -0,0 +1,9 @@ +module Spree + module Admin + class CountriesController < ResourceController + def collection + super.order(:name) + end + end + end +end diff --git a/app/controllers/spree/admin/states_controller.rb b/app/controllers/spree/admin/states_controller.rb new file mode 100644 index 0000000000..eebbee8d44 --- /dev/null +++ b/app/controllers/spree/admin/states_controller.rb @@ -0,0 +1,29 @@ +module Spree + module Admin + class StatesController < ResourceController + belongs_to 'spree/country' + before_filter :load_data + + def index + respond_with(@collection) do |format| + format.html + format.js { render partial: 'state_list' } + end + end + + protected + + def location_after_save + admin_country_states_url(@country) + end + + def collection + super.order(:name) + end + + def load_data + @countries = Country.order(:name) + end + end + end +end diff --git a/app/controllers/spree/admin/zones_controller.rb b/app/controllers/spree/admin/zones_controller.rb new file mode 100644 index 0000000000..5b5b9d876b --- /dev/null +++ b/app/controllers/spree/admin/zones_controller.rb @@ -0,0 +1,26 @@ +module Spree + module Admin + class ZonesController < ResourceController + before_filter :load_data, except: [:index] + + def new + @zone.zone_members.build + end + + protected + + def collection + params[:q] ||= {} + params[:q][:s] ||= "ascend_by_name" + @search = super.ransack(params[:q]) + @zones = @search.result.page(params[:page]).per(Spree::Config[:orders_per_page]) + end + + def load_data + @countries = Country.order(:name) + @states = State.order(:name) + @zones = Zone.order(:name) + end + end + end +end diff --git a/app/views/spree/admin/countries/_form.html.haml b/app/views/spree/admin/countries/_form.html.haml new file mode 100644 index 0000000000..8a2951ca9c --- /dev/null +++ b/app/views/spree/admin/countries/_form.html.haml @@ -0,0 +1,14 @@ +.row + .alpha.four.columns + .field + = f.label :name, t("spree.name") + = f.text_field :name, class: 'fullwidth' + .four.columns + .field + = f.label :iso_name, t("spree.iso_name") + = f.text_field :iso_name, class: 'fullwidth' + .omega.four.columns + .field.checkbox + %label + = f.check_box :states_required + = t("spree.states_required") diff --git a/app/views/spree/admin/countries/edit.html.haml b/app/views/spree/admin/countries/edit.html.haml new file mode 100644 index 0000000000..5c46ea91ea --- /dev/null +++ b/app/views/spree/admin/countries/edit.html.haml @@ -0,0 +1,16 @@ += render partial: 'spree/admin/shared/configuration_menu' + +- content_for :page_title do + = t("spree.editing_country") + +- content_for :page_actions do + %li + = button_link_to t("spree.back_to_countries_list"), spree.admin_countries_path, icon: 'icon-arrow-left' + += render partial: 'spree/shared/error_messages', locals: { target: @country } + += form_for [:admin, @country] do |f| + %fieldset.no-border-top + = render partial: 'form', locals: { f: f } + .clear + = render partial: 'spree/admin/shared/edit_resource_links' diff --git a/app/views/spree/admin/countries/index.html.haml b/app/views/spree/admin/countries/index.html.haml new file mode 100644 index 0000000000..285bf9d55f --- /dev/null +++ b/app/views/spree/admin/countries/index.html.haml @@ -0,0 +1,27 @@ += render partial: 'spree/admin/shared/configuration_menu' + +- content_for :page_title do + = t("spree.listing_countries") + +%table#listing_countries.index + %colgroup + %col{style: "width: 35%"}/ + %col{style: "width: 35%"}/ + %col{style: "width: 20%"}/ + %col{style: "width: 10%"}/ + %thead + %tr + %th= t("spree.country_name") + %th= t("spree.iso_name") + %th= t("spree.states_required") + %th.actions + %tbody + - @countries.each do |country| + - tr_class = cycle('odd', 'even') + - tr_id = spree_dom_id(country) + %tr{class: tr_class, id: tr_id} + %td= country.name + %td= country.iso_name + %td.align-center= country.states_required.to_s.titleize + %td.actions + = link_to_edit country, no_text: true diff --git a/app/views/spree/admin/states/_form.html.haml b/app/views/spree/admin/states/_form.html.haml new file mode 100644 index 0000000000..9f56da37d9 --- /dev/null +++ b/app/views/spree/admin/states/_form.html.haml @@ -0,0 +1,9 @@ +.row + .alpha.six.columns + = f.field_container :name do + = f.label :name, t("spree.name") + = f.text_field :name, class: 'fullwidth' + .omega.six.columns + = f.field_container :abbr do + = f.label :abbr, t("spree.abbreviation") + = f.text_field :abbr, class: 'fullwidth' diff --git a/app/views/spree/admin/states/_state_list.html.haml b/app/views/spree/admin/states/_state_list.html.haml new file mode 100644 index 0000000000..774c447a94 --- /dev/null +++ b/app/views/spree/admin/states/_state_list.html.haml @@ -0,0 +1,24 @@ +#new_state +%table#listing_states.index + %colgroup + %col{style: "width: 70%"}/ + %col{style: "width: 15%"}/ + %col{style: "width: 15%"}/ + %thead + %tr + %th= t("spree.name") + %th= t("spree.abbreviation") + %th.actions + %tbody + - @states.each do |state| + - tr_class = cycle('odd', 'even') + - tr_id = spree_dom_id(state) + %tr{class: tr_class, id: tr_id} + %td= state.name + %td.align-center= state.abbr + %td.actions + = link_to_with_icon 'icon-edit', t("spree.edit"), edit_admin_country_state_url(@country, state), no_text: true + = link_to_delete state, no_text: true + - if @states.empty? + %tr + %td{colspan: "3"}= t("spree.none") diff --git a/app/views/spree/admin/states/edit.html.haml b/app/views/spree/admin/states/edit.html.haml new file mode 100644 index 0000000000..636d1d022d --- /dev/null +++ b/app/views/spree/admin/states/edit.html.haml @@ -0,0 +1,16 @@ += render partial: 'spree/admin/shared/configuration_menu' + +- content_for :page_title do + = t("spree.editing_state") + %i.icon-arrow-right + = @state.name + +- content_for :page_actions do + %li + = button_link_to t("spree.back_to_states_list"), spree.admin_country_states_url(@country), icon: 'icon-arrow-left' + += render partial: 'spree/shared/error_messages', locals: { target: @state } += form_for [:admin, @country, @state] do |f| + %fieldset.no-border-top + = render partial: 'form', locals: { f: f } + = render partial: 'spree/admin/shared/edit_resource_links' diff --git a/app/views/spree/admin/states/index.html.haml b/app/views/spree/admin/states/index.html.haml new file mode 100644 index 0000000000..57035c79af --- /dev/null +++ b/app/views/spree/admin/states/index.html.haml @@ -0,0 +1,16 @@ += render partial: 'spree/admin/shared/configuration_menu' + +- content_for :page_title do + = t("spree.states") + +- content_for :page_actions do + %li#new_state_link + = button_link_to t("spree.new_state"), new_admin_country_state_url(@country), { remote: true, icon: 'icon-plus', id: 'new_state_link' } +.field.row + = label_tag :country, t("spree.country") + - databaseurl = "#{admin_states_path(format: :js)}?country_id=" + %select#country.observe_field.select2.fullwidth{"data-base-url" => databaseurl, "data-update" => "#state-list"} + = options_from_collection_for_select(@countries, :id, :name, @country.id) += image_tag 'select2-spinner.gif', plugin: 'spree', style: 'display:none;', id: 'busy_indicator' +#state-list + = render partial: 'state_list' diff --git a/app/views/spree/admin/states/new.html.haml b/app/views/spree/admin/states/new.html.haml new file mode 100644 index 0000000000..f7b988b24c --- /dev/null +++ b/app/views/spree/admin/states/new.html.haml @@ -0,0 +1,12 @@ += render partial: 'spree/admin/shared/configuration_menu' + += render partial: 'spree/shared/error_messages', locals: { target: @state } + +- content_for :page_title do + = t("spree.new_state") + += form_for [:admin, @country, @state] do |f| + %fieldset + %legend= t("spree.new_state") + = render partial: 'form', locals: { f: f } + = render partial: 'spree/admin/shared/new_resource_links' diff --git a/app/views/spree/admin/states/new.js.erb b/app/views/spree/admin/states/new.js.erb new file mode 100644 index 0000000000..4f8226e695 --- /dev/null +++ b/app/views/spree/admin/states/new.js.erb @@ -0,0 +1,2 @@ +$("#new_state").html("<%= escape_javascript(render :template => 'spree/admin/states/new', :formats => [:html], :handlers => [:erb]) %>"); +$("#new_state_link").parent().hide(); diff --git a/app/views/spree/admin/zones/_country_member.html.haml b/app/views/spree/admin/zones/_country_member.html.haml new file mode 100644 index 0000000000..f09ba19efc --- /dev/null +++ b/app/views/spree/admin/zones/_country_member.html.haml @@ -0,0 +1,4 @@ +%li + = f.hidden_field :zoneable_type, value: 'Spree::Country' + = f.collection_select(:zoneable_id, @countries, :id, :name, {include_blank: true}, {class: 'select2 fullwidth'}) + = remove_nested f diff --git a/app/views/spree/admin/zones/_form.html.haml b/app/views/spree/admin/zones/_form.html.haml new file mode 100644 index 0000000000..3fae4a2ee6 --- /dev/null +++ b/app/views/spree/admin/zones/_form.html.haml @@ -0,0 +1,23 @@ +.alpha.six.columns + %fieldset.no-border-bottom + %legend{align: "center"}= t("spree.general_settings") + = zone_form.field_container :name do + = zone_form.label :name, t("spree.name") + %br/ + = zone_form.text_field :name, class: 'fullwidth' + = zone_form.field_container :description do + = zone_form.label :description, t("spree.description") + %br/ + = zone_form.text_field :description, class: 'fullwidth' + .field + = zone_form.check_box :default_tax + = label_tag t("spree.default_tax_zone") + .field + = label_tag t("spree.type") + %ul + %li + = zone_form.radio_button('kind', 'country', { id: 'country_based' }) + = label_tag :country_based, t("spree.country_based") + %li + = zone_form.radio_button('kind', 'state', { id: 'state_based' }) + = label_tag :state_based, t("spree.state_based") diff --git a/app/views/spree/admin/zones/_member_type.html.haml b/app/views/spree/admin/zones/_member_type.html.haml new file mode 100644 index 0000000000..633067fabc --- /dev/null +++ b/app/views/spree/admin/zones/_member_type.html.haml @@ -0,0 +1,11 @@ += javascript_tag "var #{type}_member='#{generate_template(zone_form, :zone_members, {:partial => type + "_member"})}';" + +.omega.six.columns{id: "#{type}_members"} + %fieldset.no-border-bottom + %legend{align: "center"}= t("spree.type") + %ul.member-list.fields{id: "ul-nested-#{type.dasherize}"} + - members_of_type = zone_form.object.zone_members.select { |member| member.zoneable_type && member.zoneable_type == "Spree::#{type.camelize}" } + = zone_form.fields_for :zone_members, members_of_type do |member_form| + = render partial: "#{type}_member", locals: { f: member_form } + .field.align-center + = button_link_to t("spree.add_#{type}"), "##{type}_member", { icon: 'icon-plus', id:"nested-#{type.dasherize}" } diff --git a/app/views/spree/admin/zones/_state_member.html.haml b/app/views/spree/admin/zones/_state_member.html.haml new file mode 100644 index 0000000000..4b917288a7 --- /dev/null +++ b/app/views/spree/admin/zones/_state_member.html.haml @@ -0,0 +1,4 @@ +%li.field + = f.hidden_field :zoneable_type, value: 'Spree::State' + = f.collection_select(:zoneable_id, @states, :id, :name, {include_blank: true}, {class: 'select2 fullwidth'}) + = remove_nested f diff --git a/app/views/spree/admin/zones/edit.html.haml b/app/views/spree/admin/zones/edit.html.haml new file mode 100644 index 0000000000..4ef88128bc --- /dev/null +++ b/app/views/spree/admin/zones/edit.html.haml @@ -0,0 +1,18 @@ += render partial: 'spree/admin/shared/configuration_menu' + +- content_for :page_title do + = t("spree.editing_zone") + +- content_for :page_actions do + %li + = button_link_to t("spree.back_to_zones_list"), admin_zones_path, icon: 'icon-arrow-left' + += render partial: 'spree/shared/error_messages', locals: { target: @zone } + += form_for [:admin, @zone] do |zone_form| + %fieldset.no-border-top + = render partial: 'form', locals: { zone_form: zone_form } + = render partial: 'member_type', locals: { type: 'country', zone_form: zone_form } + = render partial: 'member_type', locals: { type: 'state', zone_form: zone_form } + .clear + = render partial: 'spree/admin/shared/edit_resource_links' diff --git a/app/views/spree/admin/zones/index.html.haml b/app/views/spree/admin/zones/index.html.haml new file mode 100644 index 0000000000..29c1cd4211 --- /dev/null +++ b/app/views/spree/admin/zones/index.html.haml @@ -0,0 +1,41 @@ += render partial: 'spree/admin/shared/configuration_menu' + +- content_for :page_title do + = t("spree.zones") + +- content_for :page_actions do + %li + = button_link_to t("spree.new_zone"), new_object_url, icon: 'icon-plus', id: 'admin_new_zone_link' + += paginate @zones + +- if @zones.empty? + .no-objects-found + = t("spree.none") + +- else + %table#listing_zones.index + %colgroup + %col{style: "width: 30%"}/ + %col{style: "width: 40%"}/ + %col{style: "width: 15%"}/ + %col{style: "width: 15%"}/ + %thead + %tr + %th= sort_link @search,:name, t("spree.name"), title: 'zones_order_by_name_title' + %th + = sort_link @search,:description, t("spree.description"), {}, {title: 'zones_order_by_description_title'} + %th= t("spree.default_tax") + %th.actions + %tbody + - @zones.each do |zone| + - tr_class = cycle('odd', 'even') + - tr_id = spree_dom_id(zone) + %tr{class: tr_class, id: tr_id} + %td= zone.name + %td= zone.description + %td.align-center= zone.default_tax + %td.actions + = link_to_edit zone, no_text: true + = link_to_delete zone, no_text: true += paginate @zones diff --git a/app/views/spree/admin/zones/new.html.haml b/app/views/spree/admin/zones/new.html.haml new file mode 100644 index 0000000000..b6bac9e73b --- /dev/null +++ b/app/views/spree/admin/zones/new.html.haml @@ -0,0 +1,15 @@ += render partial: 'spree/admin/shared/configuration_menu' + +- content_for :page_title do + = t("spree.new_zone") + +- content_for :page_actions do + %li + = button_link_to t("spree.back_to_zones_list"), spree.admin_zones_path, icon: 'icon-arrow-left' + += render partial: 'spree/shared/error_messages', locals: { target: @zone } + += form_for [:admin, @zone] do |zone_form| + = render partial: 'form', locals: { zone_form: zone_form } + .clear + = render partial: 'spree/admin/shared/new_resource_links' diff --git a/config/routes/spree.rb b/config/routes/spree.rb index 94da909f8b..a06644de8f 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -88,6 +88,12 @@ Spree::Core::Engine.routes.prepend do put :clear_api_key end end + + resources :zones + resources :countries do + resources :states + end + resources :states end resources :orders do diff --git a/spec/features/admin/configuration/states_spec.rb b/spec/features/admin/configuration/states_spec.rb new file mode 100755 index 0000000000..b762b33c84 --- /dev/null +++ b/spec/features/admin/configuration/states_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +describe "States" do + include AuthenticationWorkflow + + let!(:country) { create(:country) } + + before(:each) do + quick_login_as_admin + @hungary = Spree::Country.create!(name: "Hungary", iso_name: "Hungary") + Spree::Config[:default_country_id] = country.id + end + + # TODO: For whatever reason, rendering of the states page takes a non-trivial amount of time + # Therefore we navigate to it, and wait until what we see is visible + def go_to_states_page + visit spree.admin_country_states_path(country) + counter = 0 + until page.has_css?("#new_state_link") + raise "Could not see new state link!" if counter >= 10 + + sleep(2) + counter += 1 + end + end + + context "admin visiting states listing" do + let!(:state) { create(:state, country: country) } + + it "should correctly display the states" do + visit spree.admin_country_states_path(country) + expect(page).to have_content(state.name) + end + end + + context "creating and editing states" do + it "should allow an admin to edit existing states", js: true do + go_to_states_page + set_select2_field("country", country.id) + + click_link "new_state_link" + fill_in "state_name", with: "Calgary" + fill_in "Abbreviation", with: "CL" + click_button "Create" + expect(page).to have_content("successfully created!") + expect(page).to have_content("Calgary") + end + + it "should allow an admin to create states for non default countries", js: true do + go_to_states_page + set_select2_field "#country", @hungary.id + # Just so the change event actually gets triggered in this spec + # It is definitely triggered in the "real world" + page.execute_script("$('#country').trigger('change');") + + click_link "new_state_link" + fill_in "state_name", with: "Pest megye" + fill_in "Abbreviation", with: "PE" + click_button "Create" + expect(page).to have_content("successfully created!") + expect(page).to have_content("Pest megye") + expect(find("#s2id_country span.select2-chosen").text).to eq("Hungary") + end + + it "should show validation errors", js: true do + go_to_states_page + set_select2_field("country", country.id) + + click_link "new_state_link" + + fill_in "state_name", with: "" + fill_in "Abbreviation", with: "" + click_button "Create" + expect(page).to have_content("Name can't be blank") + end + end +end diff --git a/spec/features/admin/configuration/zones_spec.rb b/spec/features/admin/configuration/zones_spec.rb new file mode 100644 index 0000000000..394b06c0a3 --- /dev/null +++ b/spec/features/admin/configuration/zones_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe "Zones" do + include AuthenticationWorkflow + + before(:each) do + quick_login_as_admin + Spree::Zone.delete_all + visit spree.admin_path + click_link "Configuration" + end + + context "show" do + it "should display existing zones" do + create(:zone, name: "eastern", description: "zone is eastern") + create(:zone, name: "western", description: "cool san fran") + click_link "Zones" + + within_row(1) { expect(page).to have_content("eastern") } + within_row(2) { expect(page).to have_content("western") } + + click_link "zones_order_by_description_title" + + within_row(1) { expect(page).to have_content("western") } + within_row(2) { expect(page).to have_content("eastern") } + end + end + + context "create" do + it "should allow an admin to create a new zone" do + click_link "Zones" + click_link "admin_new_zone_link" + expect(page).to have_content("New Zone") + fill_in "zone_name", with: "japan" + fill_in "zone_description", with: "japanese time zone" + click_button "Create" + expect(page).to have_content("successfully created!") + end + end +end