From d00ade8137f5927c70ee72656015bf7492930293 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 7 Mar 2014 13:18:30 +1100 Subject: [PATCH 001/107] Splitting out shopping spec, starting on Paypal plumbing --- .../shop/checkout_controller_spec.rb | 16 ++++ .../consumer/shopping/checkout_auth_spec.rb | 84 ++++++++++++++++ .../shopping/checkout_plumbing_spec.rb | 45 +++++++++ .../consumer/shopping/checkout_spec.rb | 96 +------------------ 4 files changed, 146 insertions(+), 95 deletions(-) create mode 100644 spec/features/consumer/shopping/checkout_auth_spec.rb create mode 100644 spec/features/consumer/shopping/checkout_plumbing_spec.rb diff --git a/spec/controllers/shop/checkout_controller_spec.rb b/spec/controllers/shop/checkout_controller_spec.rb index fd33e84b88..7ef21530c5 100644 --- a/spec/controllers/shop/checkout_controller_spec.rb +++ b/spec/controllers/shop/checkout_controller_spec.rb @@ -48,4 +48,20 @@ describe Shop::CheckoutController do assigns[:order].ship_address.address1.should be_nil end end + + describe "Paypal routing" do + + let(:payment_method) { create(:payment_method) } + before do + controller.stub(:current_distributor).and_return(distributor) + controller.stub(:current_order_cycle).and_return(order_cycle) + controller.stub(:current_order).and_return(order) + + end + + it "should check the payment method for Paypalness if we've selected one" do + Spree::PaymentMethod.should_receive(:find).with(payment_method.id).and_return payment_method + post :update, order: {payments_attributes: [{payment_method_id: payment_method.id}]} + end + end end diff --git a/spec/features/consumer/shopping/checkout_auth_spec.rb b/spec/features/consumer/shopping/checkout_auth_spec.rb new file mode 100644 index 0000000000..de14f121e7 --- /dev/null +++ b/spec/features/consumer/shopping/checkout_auth_spec.rb @@ -0,0 +1,84 @@ +require 'spec_helper' + +feature "As a consumer I want to check out my cart", js: true do + include AuthenticationWorkflow + include WebHelper + + let(:distributor) { create(:distributor_enterprise) } + let(:supplier) { create(:supplier_enterprise) } + let(:order_cycle) { create(:order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise)) } + let(:product) { create(:simple_product, supplier: supplier) } + + before do + create_enterprise_group_for distributor + exchange = Exchange.find(order_cycle.exchanges.to_enterprises(distributor).outgoing.first.id) + exchange.variants << product.master + end + + describe "Login behaviour" do + let(:user) { create_enterprise_user } + before do + select_distributor + select_order_cycle + add_product_to_cart + end + + it "renders the login form if user is logged out" do + visit "/shop/checkout" + within "section[role='main']" do + page.should have_content "I HAVE AN OFN ACCOUNT" + end + end + + it "does not not render the login form if user is logged in" do + login_to_consumer_section + visit "/shop/checkout" + within "section[role='main']" do + page.should_not have_content "I HAVE AN OFN ACCOUNT" + end + end + + it "renders the signup link if user is logged out" do + visit "/shop/checkout" + within "section[role='main']" do + page.should have_content "NEW TO OFN" + end + end + + it "does not not render the signup form if user is logged in" do + login_to_consumer_section + visit "/shop/checkout" + within "section[role='main']" do + page.should_not have_content "NEW TO OFN" + end + end + + it "redirects to the checkout page when logging in from the checkout page" do + visit "/shop/checkout" + within "#checkout_login" do + fill_in "spree_user[email]", with: user.email + fill_in "spree_user[password]", with: user.password + click_button "Login" + end + + current_path.should == "/shop/checkout" + within "section[role='main']" do + page.should_not have_content "I have an OFN Account" + end + end + + it "redirects to the checkout page when signing up from the checkout page" do + visit "/shop/checkout" + within "#checkout_signup" do + fill_in "spree_user[email]", with: "test@gmail.com" + fill_in "spree_user[password]", with: "password" + fill_in "spree_user[password_confirmation]", with: "password" + click_button "Sign Up" + end + current_path.should == "/shop/checkout" + within "section[role='main']" do + page.should_not have_content "Sign Up" + end + end + end +end diff --git a/spec/features/consumer/shopping/checkout_plumbing_spec.rb b/spec/features/consumer/shopping/checkout_plumbing_spec.rb new file mode 100644 index 0000000000..b972667ca9 --- /dev/null +++ b/spec/features/consumer/shopping/checkout_plumbing_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + + +feature "As a consumer I want to check out my cart", js: true do + include AuthenticationWorkflow + include WebHelper + + let(:distributor) { create(:distributor_enterprise) } + let(:supplier) { create(:supplier_enterprise) } + let(:order_cycle) { create(:order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise)) } + let(:product) { create(:simple_product, supplier: supplier) } + + before do + create_enterprise_group_for distributor + exchange = Exchange.find(order_cycle.exchanges.to_enterprises(distributor).outgoing.first.id) + exchange.variants << product.master + end + describe "Attempting to access checkout without meeting the preconditions" do + it "redirects to the homepage if no distributor is selected" do + visit "/shop/checkout" + current_path.should == root_path + end + + it "redirects to the shop page if we have a distributor but no order cycle selected" do + select_distributor + visit "/shop/checkout" + current_path.should == shop_path + end + + it "redirects to the shop page if the current order is empty" do + select_distributor + select_order_cycle + visit "/shop/checkout" + current_path.should == shop_path + end + + it "renders checkout if we have distributor and order cycle selected" do + select_distributor + select_order_cycle + add_product_to_cart + visit "/shop/checkout" + current_path.should == "/shop/checkout" + end + end +end diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb index 0ed9cda6ac..16315be7a7 100644 --- a/spec/features/consumer/shopping/checkout_spec.rb +++ b/spec/features/consumer/shopping/checkout_spec.rb @@ -16,101 +16,6 @@ feature "As a consumer I want to check out my cart", js: true do exchange.variants << product.master end - describe "Attempting to access checkout without meeting the preconditions" do - it "redirects to the homepage if no distributor is selected" do - visit "/shop/checkout" - current_path.should == root_path - end - - it "redirects to the shop page if we have a distributor but no order cycle selected" do - select_distributor - visit "/shop/checkout" - current_path.should == shop_path - end - - it "redirects to the shop page if the current order is empty" do - select_distributor - select_order_cycle - visit "/shop/checkout" - current_path.should == shop_path - end - - it "renders checkout if we have distributor and order cycle selected" do - select_distributor - select_order_cycle - add_product_to_cart - visit "/shop/checkout" - current_path.should == "/shop/checkout" - end - end - - describe "Login behaviour" do - let(:user) { create_enterprise_user } - before do - select_distributor - select_order_cycle - add_product_to_cart - end - - it "renders the login form if user is logged out" do - visit "/shop/checkout" - within "section[role='main']" do - page.should have_content "I HAVE AN OFN ACCOUNT" - end - end - - it "does not not render the login form if user is logged in" do - login_to_consumer_section - visit "/shop/checkout" - within "section[role='main']" do - page.should_not have_content "I HAVE AN OFN ACCOUNT" - end - end - - it "renders the signup link if user is logged out" do - visit "/shop/checkout" - within "section[role='main']" do - page.should have_content "NEW TO OFN" - end - end - - it "does not not render the signup form if user is logged in" do - login_to_consumer_section - visit "/shop/checkout" - within "section[role='main']" do - page.should_not have_content "NEW TO OFN" - end - end - - it "redirects to the checkout page when logging in from the checkout page" do - visit "/shop/checkout" - within "#checkout_login" do - fill_in "spree_user[email]", with: user.email - fill_in "spree_user[password]", with: user.password - click_button "Login" - end - - current_path.should == "/shop/checkout" - within "section[role='main']" do - page.should_not have_content "I have an OFN Account" - end - end - - it "redirects to the checkout page when signing up from the checkout page" do - visit "/shop/checkout" - within "#checkout_signup" do - fill_in "spree_user[email]", with: "test@gmail.com" - fill_in "spree_user[password]", with: "password" - fill_in "spree_user[password_confirmation]", with: "password" - click_button "Sign Up" - end - current_path.should == "/shop/checkout" - within "section[role='main']" do - page.should_not have_content "Sign Up" - end - end - end - # Run these tests both logged in and logged out! [:in, :out].each do |auth_state| describe "logged #{auth_state.to_s}, distributor selected, order cycle selected, product in cart" do @@ -179,6 +84,7 @@ feature "As a consumer I want to check out my cart", js: true do describe "with payment methods" do let(:pm1) { create(:payment_method, distributors: [distributor], name: "Roger rabbit", type: "Spree::PaymentMethod::Check") } let(:pm2) { create(:payment_method, distributors: [distributor]) } + let(:pm3) { create(:payment_method, distributors: [distributor], name: "Paypal", type: "Spree::BillingIntegration::PaypalExpress") } before do pm1 # Lazy evaluation of ze create()s From e5cea07d9ebf819f06dce2971ded7a3998088264 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 7 Mar 2014 13:18:53 +1100 Subject: [PATCH 002/107] Splitting out shopping spec, starting on Paypal plumbing --- spec/controllers/shop/checkout_controller_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/controllers/shop/checkout_controller_spec.rb b/spec/controllers/shop/checkout_controller_spec.rb index 7ef21530c5..787be18955 100644 --- a/spec/controllers/shop/checkout_controller_spec.rb +++ b/spec/controllers/shop/checkout_controller_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe Shop::CheckoutController do + render_views let(:distributor) { double(:distributor) } let(:order_cycle) { create(:order_cycle) } let(:order) { create(:order) } From 57903924302c1a96e1e1a63a0a1741703d6c6416 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 7 Mar 2014 14:10:49 +1100 Subject: [PATCH 003/107] Bypassing the stage check on the Paypal callbacks --- app/controllers/shop/checkout_controller.rb | 28 +++++++++++++++++-- .../shop/checkout_controller_spec.rb | 7 ++--- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/controllers/shop/checkout_controller.rb b/app/controllers/shop/checkout_controller.rb index 011560e1f5..8f18250521 100644 --- a/app/controllers/shop/checkout_controller.rb +++ b/app/controllers/shop/checkout_controller.rb @@ -19,7 +19,7 @@ class Shop::CheckoutController < Spree::CheckoutController state_callback(:after) else flash[:error] = t(:payment_processing_failed) - respond_with @order, location: main_app.shop_checkout_path + render :edit return end end @@ -29,10 +29,10 @@ class Shop::CheckoutController < Spree::CheckoutController flash[:commerce_tracking] = "nothing special" respond_with(@order, :location => order_path(@order)) else - respond_with @order, location: main_app.shop_checkout_path + render :edit end else - respond_with @order, location: main_app.shop_checkout_path + render :edit end end @@ -76,4 +76,26 @@ class Shop::CheckoutController < Spree::CheckoutController flash[:error] = t(:spree_inventory_error_flash_for_insufficient_quantity) redirect_to main_app.shop_path end + + # Overriding from github.com/spree/spree_paypal_express + def redirect_to_paypal_express_form_if_needed + return unless params[:order][:payments_attributes] + + payment_method = Spree::PaymentMethod.find(params[:order][:payments_attributes].first[:payment_method_id]) + return unless payment_method.kind_of?(Spree::BillingIntegration::PaypalExpress) || payment_method.kind_of?(Spree::BillingIntegration::PaypalExpressUk) + + update_params = object_params.dup + update_params.delete(:payments_attributes) + if @order.update_attributes(update_params) + fire_event('spree.checkout.update') + render :edit and return unless apply_coupon_code + end + + load_order + if not @order.errors.empty? + render :edit and return + end + + redirect_to(paypal_payment_order_checkout_url(@order, :payment_method_id => payment_method.id)) and return + end end diff --git a/spec/controllers/shop/checkout_controller_spec.rb b/spec/controllers/shop/checkout_controller_spec.rb index 787be18955..8886afe510 100644 --- a/spec/controllers/shop/checkout_controller_spec.rb +++ b/spec/controllers/shop/checkout_controller_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Shop::CheckoutController do - render_views let(:distributor) { double(:distributor) } let(:order_cycle) { create(:order_cycle) } let(:order) { create(:order) } @@ -51,18 +50,16 @@ describe Shop::CheckoutController do end describe "Paypal routing" do - let(:payment_method) { create(:payment_method) } before do controller.stub(:current_distributor).and_return(distributor) controller.stub(:current_order_cycle).and_return(order_cycle) controller.stub(:current_order).and_return(order) - end it "should check the payment method for Paypalness if we've selected one" do - Spree::PaymentMethod.should_receive(:find).with(payment_method.id).and_return payment_method - post :update, order: {payments_attributes: [{payment_method_id: payment_method.id}]} + Spree::PaymentMethod.should_receive(:find).with(payment_method.id.to_s).and_return payment_method + spree_post :update, order: {payments_attributes: [{payment_method_id: payment_method.id}]} end end end From df435c0a3874ce825dce7380dae90deb6b73d24e Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 7 Mar 2014 15:10:20 +1100 Subject: [PATCH 004/107] Adding Paypal plumbing --- app/controllers/shop/checkout_controller.rb | 17 ++++++++++++++++- config/environments/test.rb | 3 +++ .../shop/checkout_controller_spec.rb | 8 +++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/controllers/shop/checkout_controller.rb b/app/controllers/shop/checkout_controller.rb index 8f18250521..f9e416c677 100644 --- a/app/controllers/shop/checkout_controller.rb +++ b/app/controllers/shop/checkout_controller.rb @@ -4,6 +4,7 @@ class Shop::CheckoutController < Spree::CheckoutController prepend_before_filter :require_order_cycle prepend_before_filter :require_distributor_chosen skip_before_filter :check_registration + skip_before_filter :redirect_to_paypal_express_form_if_needed include EnterprisesHelper @@ -14,7 +15,12 @@ class Shop::CheckoutController < Spree::CheckoutController if @order.update_attributes(params[:order]) fire_event('spree.checkout.update') + while @order.state != "complete" + if @order.state == "payment" + return if redirect_to_paypal_express_form_if_needed + end + if @order.next state_callback(:after) else @@ -96,6 +102,15 @@ class Shop::CheckoutController < Spree::CheckoutController render :edit and return end - redirect_to(paypal_payment_order_checkout_url(@order, :payment_method_id => payment_method.id)) and return + redirect_to(paypal_payment_order_checkout_url(@order, :payment_method_id => payment_method.id)) + true + end + + def order_opts_with_new_cancel_return_url(order, payment_method_id, stage) + opts = order_opts_without_new_cancel_return_url(order, payment_method_id, stage) + opts[:cancel_return_url] = main_app.shop_checkout_url + opts + end + alias_method_chain :order_opts, :new_cancel_return_url end diff --git a/config/environments/test.rb b/config/environments/test.rb index 483ed4960c..e1291ab4b4 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -37,3 +37,6 @@ Openfoodnetwork::Application.configure do # Print deprecation notices to the stderr config.active_support.deprecation = :stderr end + +# Allows us to use _url helpers in Rspec +Rails.application.routes.default_url_options[:host] = 'test.host' diff --git a/spec/controllers/shop/checkout_controller_spec.rb b/spec/controllers/shop/checkout_controller_spec.rb index 8886afe510..dac804ea6c 100644 --- a/spec/controllers/shop/checkout_controller_spec.rb +++ b/spec/controllers/shop/checkout_controller_spec.rb @@ -50,7 +50,7 @@ describe Shop::CheckoutController do end describe "Paypal routing" do - let(:payment_method) { create(:payment_method) } + let(:payment_method) { create(:payment_method, type: "Spree::BillingIntegration::PaypalExpress") } before do controller.stub(:current_distributor).and_return(distributor) controller.stub(:current_order_cycle).and_return(order_cycle) @@ -59,7 +59,13 @@ describe Shop::CheckoutController do it "should check the payment method for Paypalness if we've selected one" do Spree::PaymentMethod.should_receive(:find).with(payment_method.id.to_s).and_return payment_method + order.stub(:update_attributes).and_return true spree_post :update, order: {payments_attributes: [{payment_method_id: payment_method.id}]} end + + it "should override the cancel return url" do + controller.stub(:params).and_return({payment_method_id: payment_method.id}) + controller.send(:order_opts, order, payment_method.id, 'payment')[:cancel_return_url].should == shop_checkout_url + end end end From 40c986892e8235a6906b367f5e35928b429428a6 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 7 Mar 2014 15:18:26 +1100 Subject: [PATCH 005/107] Patching the paypal test --- app/controllers/shop/checkout_controller.rb | 5 ++--- spec/controllers/shop/checkout_controller_spec.rb | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/shop/checkout_controller.rb b/app/controllers/shop/checkout_controller.rb index f9e416c677..6fd69a4d17 100644 --- a/app/controllers/shop/checkout_controller.rb +++ b/app/controllers/shop/checkout_controller.rb @@ -14,8 +14,6 @@ class Shop::CheckoutController < Spree::CheckoutController def update if @order.update_attributes(params[:order]) fire_event('spree.checkout.update') - - while @order.state != "complete" if @order.state == "payment" return if redirect_to_paypal_express_form_if_needed @@ -106,7 +104,8 @@ class Shop::CheckoutController < Spree::CheckoutController true end - + + # Overriding to customize the cancel url def order_opts_with_new_cancel_return_url(order, payment_method_id, stage) opts = order_opts_without_new_cancel_return_url(order, payment_method_id, stage) opts[:cancel_return_url] = main_app.shop_checkout_url diff --git a/spec/controllers/shop/checkout_controller_spec.rb b/spec/controllers/shop/checkout_controller_spec.rb index dac804ea6c..c27bc2bfd9 100644 --- a/spec/controllers/shop/checkout_controller_spec.rb +++ b/spec/controllers/shop/checkout_controller_spec.rb @@ -60,6 +60,7 @@ describe Shop::CheckoutController do it "should check the payment method for Paypalness if we've selected one" do Spree::PaymentMethod.should_receive(:find).with(payment_method.id.to_s).and_return payment_method order.stub(:update_attributes).and_return true + order.stub(:state).and_return "payment" spree_post :update, order: {payments_attributes: [{payment_method_id: payment_method.id}]} end From 451e76b33f6cd1ae63ac4dd3846fd1962aebd680 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 7 Mar 2014 15:27:52 +1100 Subject: [PATCH 006/107] Tweaking our paypal express plumbing further --- app/controllers/shop/checkout_controller.rb | 2 +- config/routes.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/shop/checkout_controller.rb b/app/controllers/shop/checkout_controller.rb index 6fd69a4d17..1b9b3f9196 100644 --- a/app/controllers/shop/checkout_controller.rb +++ b/app/controllers/shop/checkout_controller.rb @@ -100,7 +100,7 @@ class Shop::CheckoutController < Spree::CheckoutController render :edit and return end - redirect_to(paypal_payment_order_checkout_url(@order, :payment_method_id => payment_method.id)) + redirect_to(main_app.shop_paypal_payment_url(@order, :payment_method_id => payment_method.id)) true end diff --git a/config/routes.rb b/config/routes.rb index 4fa61e8754..14519b4b13 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -13,6 +13,8 @@ Openfoodnetwork::Application.routes.draw do #end get '/checkout', :to => 'checkout#edit' , :as => :checkout put '/checkout', :to => 'checkout#update' , :as => :update_checkout + + get "/checkout/paypal_payment", to: 'checkout#paypal_payment', as: :paypal_payment end resources :enterprises do From 57eb65f721410529064f01bc2936d11212a1df8a Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 12 Mar 2014 12:07:41 +1100 Subject: [PATCH 007/107] Adding an accessor for requires shipping address --- app/models/spree/shipping_method_decorator.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/spree/shipping_method_decorator.rb b/app/models/spree/shipping_method_decorator.rb index bfb4ce2bb6..88e9e626e7 100644 --- a/app/models/spree/shipping_method_decorator.rb +++ b/app/models/spree/shipping_method_decorator.rb @@ -1,6 +1,7 @@ Spree::ShippingMethod.class_eval do has_and_belongs_to_many :distributors, join_table: 'distributors_shipping_methods', :class_name => 'Enterprise', association_foreign_key: 'distributor_id' attr_accessible :distributor_ids + attr_accessible :require_ship_address scope :managed_by, lambda { |user| if user.has_spree_role?('admin') From 68eaa6173262a9602a7f3f597d404421111f89d9 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 6 Mar 2014 11:47:07 +1100 Subject: [PATCH 008/107] Enterprise user can delete product images --- app/models/spree/ability_decorator.rb | 2 +- spec/features/admin/products_spec.rb | 15 +++++++++++++++ spec/models/spree/ability_spec.rb | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 668791f555..0fe8d22e59 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -16,7 +16,7 @@ class AbilityDecorator can [:admin, :index, :read, :create, :edit, :update, :search, :destroy], Spree::Variant can [:admin, :index, :read, :create, :edit, :destroy], Spree::ProductProperty - can [:admin, :index, :read, :create, :edit], Spree::Image + can [:admin, :index, :read, :create, :edit, :destroy], Spree::Image can [:admin, :index, :read, :search], Spree::Taxon can [:admin, :index, :read, :create, :edit], Spree::Classification diff --git a/spec/features/admin/products_spec.rb b/spec/features/admin/products_spec.rb index 092d5239d4..ebdb95aa9e 100644 --- a/spec/features/admin/products_spec.rb +++ b/spec/features/admin/products_spec.rb @@ -144,6 +144,21 @@ feature %q{ product.distributors.should == [@distributors[0]] end + + scenario "deleting product images", js: true do + product = create(:simple_product, supplier: @supplier2) + image = File.open(File.expand_path('../../../../app/assets/images/logo.jpg', __FILE__)) + Spree::Image.create({:viewable_id => product.master.id, :viewable_type => 'Spree::Variant', :alt => "position 1", :attachment => image, :position => 1}) + + visit spree.admin_product_images_path(product) + page.should have_selector "table[data-hook='images_table'] td img", visible: true + product.reload.images.count.should == 1 + + page.find('a.delete-resource').click + wait_until { product.reload.images.count == 0 } + + page.should_not have_selector "table[data-hook='images_table'] td img", visible: true + end end end end diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 193a6c0fd5..86c24aa9ec 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -52,7 +52,7 @@ module Spree end it "should be able to read/write their enterprises' product images" do - should have_ability([:admin, :index, :read, :create, :edit], for: Spree::Image) + should have_ability([:admin, :index, :read, :create, :edit, :destroy], for: Spree::Image) end it "should be able to read Taxons (in order to create classifications)" do From 98e9f7d7e186d2dab7c67b8130a79c1d79d12490 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 6 Mar 2014 11:59:34 +1100 Subject: [PATCH 009/107] Add custom noimage image for spree --- app/assets/images/noimage/large.png | Bin 0 -> 63558 bytes app/assets/images/noimage/mini.png | Bin 0 -> 4888 bytes app/assets/images/noimage/product.png | Bin 0 -> 63558 bytes app/assets/images/noimage/small.png | Bin 0 -> 15840 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/assets/images/noimage/large.png create mode 100644 app/assets/images/noimage/mini.png create mode 100644 app/assets/images/noimage/product.png create mode 100644 app/assets/images/noimage/small.png diff --git a/app/assets/images/noimage/large.png b/app/assets/images/noimage/large.png new file mode 100644 index 0000000000000000000000000000000000000000..c9b2af198c8437463f419ee5d40bcc3c3a3c4133 GIT binary patch literal 63558 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/noimage/mini.png b/app/assets/images/noimage/mini.png new file mode 100644 index 0000000000000000000000000000000000000000..dac04c5863c7e2a7b589a30948d43d4d0fb5bb0f GIT binary patch literal 4888 zcmV+z6X)!SP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L02dMf02dMgXP?qi00007bV*G`2i^k) z05lz52W{N|01}uMdG(JE~XaK{B4qB!ZAY#eq=-35LwW$vNM+SAQg6xn64p z?({r&|2zBaz0P{iT6?W`tp)rSu08L*i~sX$Q)SgBepgzz$>=4PDk@5l8E z7oM|UvuoGr_U@in+%8vQRM($fk)5?>V?{VbQa)fyc^Lx?MG+Jq2rkQ-lXm1n0k8`d zP*PVPio~MpIOFy4!N4s8vDhr>$U-UQrH*uF#QFR;clGyIDne}Ydp(C^y74VVQ66?2 zrzvI_x41NIPs*^QC+A&#wS7ireSJMLGBW<5-{w``J9V=kytd<@AZC#K;Hg=fqN{+f#W!2TGl4N*L%QmoSWLZx_$!y zM8p9gBO?RrOG+;ufZYcU003%t?@8WTRdwTaS(%9FQN}pm0fsIjiV&k-&e^fi!;`DW zrlce$#>M3p=H^_N7>xTj-7p>}Npp!P>UFzrh?(YWpV#~IrHe9uAOye3vg|Jaz}Bkj zJ2#YkerLHoU^rl zk7r!Y{Q18quWwit(W6fStd{{LD{?~jeOzAEO{QtSGz1eaO}pE&ory%UND)en?bxd& z0WM9u`}*vxr_alU${QL!I>dRulu{58h)CxglK~`YE?0bEZcfpPP-tDy<9%XlO&tu& z^a8kNWlrcy&RN2i%BuL1y80Q#wRNsD@~+R$dRYkG3ILq(E`Ytr85;p0*>>y|3=E7h z*p?;E+X5TPOH>BsaY-as<>lT30K4}6<;wP+?w7C2%$&Zhy5EBssZ*yG5z(c8dG}pyAQrn-H;kWpHEoVz8lMC311X)mOw)YEFwJ`aU~6?X ziw^|$2YufA3UhNFEvcxvhJStN|)x6nyS3fAB`@J^9N?ywl!H2Ub`-H;ke@3`Ug2@Z2%?= zON{^hiIxw)EQAn}WGvwIz0ecsFII(UHZ5zCA{3YHI9Dl(cq}0hSkvCqvxS2(MO8Lh zmi0vd?QwozUe5gV-ZOfvFE778;PqzqM+R~kV~4pAhiu!v)#G-zUzfS?Kes?*V5ObvCoiTCzDn(I#_P5G^48Xj3^RS`3qKI?;u4!5KnU+;@x}&2JjJ+wi z5KA&M2bN`L?K%@BLDc5=`o_<`a>fS$T#0dUzfct34x*n6&Wp~-RTcH9X`15{A?gBN z&w2oT00>|J5o3(C2K@eGCr-CM2LP6B->aMEqSXcYW8(b&#Y7l=;Mj?0-J06|sa(ji ztns5qjF{Ti)fHibv3x4$Y#&LIo5Gb35s|SXl>Lj>YHGfCkI5mafXro4u~IEE4ql z?vEKpza%PJ844{qCxTUku_7n*ca?AN?nZJ#!Z~|uOG_KQF88Z3!*~irY*fndv2C5* zH#)Zcu*dB_31D|j*ZuKvfmBw1vbME*1 zXXu9h9bMPIN(AZgcrKBS^Haw0{PL{qT^GV;8=9JxQ|;}0y&m`PqOsT$L}0_>6P9Gn zO)G9_-s`t)YkDvcJbBHO$tVBzUdxUKjLyp)1W>c52?<`mIC%Wn?TQc!rIZ7Vu|E~& zU_6adIforSGc)sF^z-j4BO#@x}~)#d5v z>HX98y1GfWX-_gN^Ufi2IhsrRW>PTj?XKS5G$F*H3st_jeg_t3r2njWmqk0N^+>K}yP&BvTk*5@32hxG~Sdme_khNY`~WCxWah?5%o%<7RTt0oqYqZR8&CN_9ZS&ds@@fn{3-29o3Ed zZYU@?$2~TdS3GCg*1TZ}NsF`RrhRxZYUYm4P7F&*LU&(3uYCRWGAU(kI=801;1ER()3`4=F)@^tmR89DU}ADIRu>eUFk<=*(xFET(=MJi@rpYD zplSbp06=S78EVW?%<4NDTuGsZ3_#)>qduF+KW zQAf)0hG|SV3m`u|9h)mFR~fp|7x4MoMx~^D!IAWZWxa_QeF`kt!e>lr58+TqXfa7iLuFc`fr$6jydygdTd#$GS7EQP==ZqasiceT<+jg*|wr;xP z$avGT`h6bnY5+KRX?%;_Nfx}1Ndq6kMBF30d1b;;ckt+tta5iC0^;^-jR!B}Wp{Q?L05!12~rT$+-C4doR}R3WAl5J!ZUu9`flWzSo0 zbrT36Hdu#%Ah`G)xqMOzuio&-`i3bYu7EbUp7k#WAxHKhYu)^Vdj;d+DPKXPxwH^EM-rgQu5@5s`vl9Y=KTAn>DT-1*cjnCA9ZgL$P1}Am zIWc~HVQ%h=T(Ccvl-2AdqE9Yp80XBf?dvjT&DtjfkB=GF7{(YhRXf7Dh%g2)2AH;$ z-4~5MWI4_vDP>^D9X>OFxPY%0fWd`0)!yCpJI2`FF(XDSAMAqkb$Z+~#(>UIysfjziBU1mkYVnlszn9Ih5ZU@QP8>744xnK^O7$z5;1Gl7V_ z4iPR%8To$8>C=Y+BrwiOR^{fdsBdm|b#`|rXqronL?ZDnm+O?^?Ea;T7Jb{XOhu@g z{jr)jGcyxq4GqYfKmX}1l~r+mug9{-QhrUQ>IMP_>u4dwP%%AOGpS z(d{+0UykbfZv__xD|17oL&;^DX<4D=Lp{s#hK3g-v6wCx*KNn?=Zs}@&Ra~|`r)eF z+(X-IYJb9oz~TiN4{fck{?4<7a!`re$rgOsm-K z^L;}%jDK~zw8sIAcckN9UsgUPDL&rS-Pd<}xbgKzqq^<_KqRS)1{0z|M0|TqZEZ^O zurGD@^-YcG`k45*;GV3sv_AsCj-5M^kT@L2Pn}!?;2h;Y6@cJE<;+$L)4X#-d3g>I z`AE_w#OTi-`88YQcDXt=RcTgL<*+20$H7_|_&`y9-qCH+n@Q3a*&yYmq5jkaRZ$Lv=FW+oS9`sop#e*?7M)NOZ(OUNAo#jy4UTl6`c3jj-9h?QRZ_uE-tt%F)pr1QG~7trDOcqF{!Hy z@>7;%E^H=AodgCU#5e$m3kC)g4FE}!0mK*sY{!|nIb4}=-UE*9Rn=IMwFngrjaPK{ z_3cPWPM+V<-F?4h*{TqHJm>6JZcTe564T4wn)XCY*YDLd?RzUi*=s*ZW;8aS9BVl> z`q&38-Hh`!OBOD;spZs3_s+N8ts0d)JfBEjeyY8FO-fQyVS4JU4=#8@Q&L|KAJ@=6 z&=*{p6N;=WEi{^GK%MTMnx^{$w%vk}9S-6u|+(jYkl2;e}R-{%x8 zSkTeX)HJ=dvunG@<$6Qf5EvR zO;MgQOfwt6qU6MceE{Mu%e; zuG=L^0|1s~E&At^r%wl4PM=OXb^3Htds}CcCy+2~d3M&r`E%2V^z@TMBcoNeW8W(YE(TacL|%`(U5|86T31>= ztbFHgB9IBjBm)isKr|X#8wmKy|3@eF08m_4hs6sPz?4#{sI8ytkjx^YvgO%XJISk;6VLBGco6@;0Q?m@^HPVA1U`5G0000< KMNUMnLSTXfcWr|J literal 0 HcmV?d00001 diff --git a/app/assets/images/noimage/product.png b/app/assets/images/noimage/product.png new file mode 100644 index 0000000000000000000000000000000000000000..44ca117e5f83c31e5f9d6ea9982d49ecf5d3c7b5 GIT binary patch literal 63558 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 literal 0 HcmV?d00001 diff --git a/app/assets/images/noimage/small.png b/app/assets/images/noimage/small.png new file mode 100644 index 0000000000000000000000000000000000000000..e8ed5f07d95155069101f2aa6747d254a79c3d78 GIT binary patch literal 15840 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 q&w-OCKc38w$H(L2@$q Date: Fri, 7 Mar 2014 11:44:51 +1100 Subject: [PATCH 010/107] Reformat --- spec/features/admin/bulk_order_management_spec.rb | 4 ++-- spec/features/admin/bulk_product_update_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index 168421f77c..8c4483451b 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -34,7 +34,7 @@ feature %q{ page.should have_text "No matching line items found." end - context "displaying the list of line items " do + context "displaying the list of line items" do let!(:o1) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } let!(:o2) { FactoryGirl.create(:order, state: 'complete', completed_at: Time.now ) } let!(:o3) { FactoryGirl.create(:order, state: 'address', completed_at: nil ) } @@ -374,4 +374,4 @@ feature %q{ end end end -end \ No newline at end of file +end diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 336792a3ae..73cad12f3c 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -97,8 +97,8 @@ feature %q{ visit '/admin/products/bulk_edit' - page.should have_field "price", with: "22.0" - page.should have_field "price", with: "44.0" + page.should have_field "price", with: "22.0" + page.should have_field "price", with: "44.0" page.should_not have_field "price", with: "66.0", visible: true end From 0b39db01653b5c8622e848e4be320287511074ed Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 7 Mar 2014 13:10:11 +1100 Subject: [PATCH 011/107] Display Rails env on landing page in dev and staging --- .../stylesheets/search/temp_landing_page.css.scss | 10 +++++++++- app/views/home/temp_landing_page.html.haml | 3 +++ spec/features/consumer/temp_landing_page_spec.rb | 3 +-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/search/temp_landing_page.css.scss b/app/assets/stylesheets/search/temp_landing_page.css.scss index 87f5a969ac..89c5ffd6bb 100644 --- a/app/assets/stylesheets/search/temp_landing_page.css.scss +++ b/app/assets/stylesheets/search/temp_landing_page.css.scss @@ -4,6 +4,14 @@ .landing-page-row { padding-top: emCalc(40); padding-bottom: emCalc(30); + + #environment { + font-size: 110%; + font-weight: bold; + padding: emCalc(5); + border-radius: emCalc(5); + background-color: #98ca45; + } } .distributor-link-row { @@ -49,7 +57,7 @@ a.inactive { } } .top-bar-section ul li { - float: left; + float: left; } .top-bar-section .divider, .top-bar-section [role="separator"] { clear: none; diff --git a/app/views/home/temp_landing_page.html.haml b/app/views/home/temp_landing_page.html.haml index d8cc87dc11..3ce57de4bc 100644 --- a/app/views/home/temp_landing_page.html.haml +++ b/app/views/home/temp_landing_page.html.haml @@ -39,6 +39,9 @@ .large-12.columns.centered %h3 WHERE WOULD YOU LIKE TO SHOP? %p.secondary Select your hub from the list below + - if Rails.env.development? || Rails.env.staging? + .large-12.columns.centered + #environment= Rails.env.capitalize - @groups.in_groups_of(4, false) do |row| .row.landing-page-row.hub_group{:class => (row.last == @groups.last ? "with-bottom-border" : "")} diff --git a/spec/features/consumer/temp_landing_page_spec.rb b/spec/features/consumer/temp_landing_page_spec.rb index b0cc4adab2..5a111aa132 100644 --- a/spec/features/consumer/temp_landing_page_spec.rb +++ b/spec/features/consumer/temp_landing_page_spec.rb @@ -61,14 +61,13 @@ feature %q{ end it "should grey out hubs that are not in an order cycle" do - create(:simple_order_cycle, distributors: [d1, d3]) create(:simple_product, distributors: [d1, d2]) visit root_path page.should have_selector 'a.shop-distributor.active', text: 'Murandaka' - page.should have_selector 'a.shop-distributor.inactive', text: 'Ballantyne' + page.should have_selector 'a.shop-distributor.inactive', text: 'Ballantyne' page.should have_selector 'a.shop-distributor.active', text: "O'Hea Street" page.should have_selector 'a.shop-distributor.inactive', text: 'PepperTree Place' end From 93a661ac08dd7ebbe56706f303e1e454f44d82df Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 7 Mar 2014 13:34:57 +1100 Subject: [PATCH 012/107] Do not show product unit fields when creating a new product - make this interface less confusing --- .../products/new/add_unit_form.html.haml.deface | 17 ----------------- spec/features/admin/products_spec.rb | 8 -------- 2 files changed, 25 deletions(-) delete mode 100644 app/overrides/spree/admin/products/new/add_unit_form.html.haml.deface diff --git a/app/overrides/spree/admin/products/new/add_unit_form.html.haml.deface b/app/overrides/spree/admin/products/new/add_unit_form.html.haml.deface deleted file mode 100644 index 03021b41d9..0000000000 --- a/app/overrides/spree/admin/products/new/add_unit_form.html.haml.deface +++ /dev/null @@ -1,17 +0,0 @@ -/ insert_before "[data-hook='new_product_attrs']" - -.row - .alpha.six.columns - = f.label :variant_unit, 'Variant unit' - = f.select :variant_unit, product_variant_unit_options, {:include_blank => true}, {:class => "select2 fullwidth"} - = f.error_message_on :variant_unit - - .four.columns - = f.label :variant_unit_scale, 'Variant unit scale' - = f.text_field :variant_unit_scale, {class: "fullwidth"} - = f.error_message_on :variant_unit_scale - - .omega.six.columns - = f.label :variant_unit_name, 'Variant unit name' - = f.text_field :variant_unit_name, {class: "fullwidth"} - = f.error_message_on :variant_unit_name diff --git a/spec/features/admin/products_spec.rb b/spec/features/admin/products_spec.rb index ebdb95aa9e..20d599404b 100644 --- a/spec/features/admin/products_spec.rb +++ b/spec/features/admin/products_spec.rb @@ -23,9 +23,6 @@ feature %q{ fill_in 'product_name', with: 'A new product !!!' fill_in 'product_price', with: '19.99' select 'New supplier', from: 'product_supplier_id' - select 'Weight', from: 'product_variant_unit' - fill_in 'product_variant_unit_scale', with: 1000 - fill_in 'product_variant_unit_name', with: '' click_button 'Create' @@ -34,11 +31,6 @@ feature %q{ product.supplier.should == @supplier product.group_buy.should be_false - product.variant_unit.should == 'weight' - product.variant_unit_scale.should == 1000 - product.variant_unit_name.should == '' - product.option_types.first.name.should == 'unit_weight' - # Distributors within('#sidebar') { click_link 'Product Distributions' } From 356099a49aee03419162e0148076d8bf85d0c60f Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 12 Mar 2014 12:31:35 +1100 Subject: [PATCH 013/107] When product unit changed, remove option types from master as well as other variants --- app/models/spree/product_decorator.rb | 2 +- spec/models/spree/product_spec.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 7682720708..7f84913d68 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -151,7 +151,7 @@ Spree::Product.class_eval do if variant_unit_changed? option_types.delete self.class.all_variant_unit_option_types option_types << variant_unit_option_type if variant_unit.present? - variants.each { |v| v.delete_unit_option_values } + variants_including_master.each { |v| v.delete_unit_option_values } end end diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index d42f811422..813a57558f 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -429,6 +429,16 @@ module Spree p.update_attributes!(variant_unit: 'volume', variant_unit_scale: 0.001) }.to change(v.option_values(true), :count).by(-1) end + + it "removes the related option values from its master variant" do + ot = Spree::OptionType.find_by_name 'unit_weight' + p.master.update_attributes!(unit_value: 1) + p.reload + + expect { + p.update_attributes!(variant_unit: 'volume', variant_unit_scale: 0.001) + }.to change(p.master.option_values(true), :count).by(-1) + end end describe "returning the variant unit option type" do From 4d8a23346c65d63a034188e2b999fb6cd4aa22ee Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 12 Mar 2014 12:35:28 +1100 Subject: [PATCH 014/107] Fix bug: Unselecting product variant unit in BPE doesn't save --- .../admin/bulk_product_update.js.coffee | 4 ++++ .../unit/bulk_product_update_spec.js.coffee | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index 97116f673b..260249371f 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -394,7 +394,11 @@ productEditModule.controller "AdminProductEditCtrl", [ else product.variant_unit = product.variant_unit_with_scale product.variant_unit_scale = null + else + product.variant_unit = product.variant_unit_scale = null + $scope.packVariant product, product.master if product.master + if product.variants for id, variant of product.variants $scope.packVariant product, variant diff --git a/spec/javascripts/unit/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/bulk_product_update_spec.js.coffee index bd85419ac9..58a5ce1941 100644 --- a/spec/javascripts/unit/bulk_product_update_spec.js.coffee +++ b/spec/javascripts/unit/bulk_product_update_spec.js.coffee @@ -690,6 +690,21 @@ describe "AdminProductEditCtrl", -> variant_unit_scale: 1000 variant_unit_with_scale: 'volume_1000' + it "extracts a null value into null variant_unit and variant_unit_scale", -> + testProduct = + id: 1 + variant_unit: 'weight' + variant_unit_scale: 1 + variant_unit_with_scale: null + + scope.packProduct(testProduct) + + expect(testProduct).toEqual + id: 1 + variant_unit: null + variant_unit_scale: null + variant_unit_with_scale: null + it "extracts when variant_unit_with_scale is 'items'", -> testProduct = id: 1 From a8373b7bef5e009a8a77875b962047d46b52a14a Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 12 Mar 2014 13:34:05 +1100 Subject: [PATCH 015/107] Fix specs: stale data --- app/models/order_cycle.rb | 2 +- spec/models/exchange_spec.rb | 2 ++ spec/models/order_cycle_spec.rb | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 1f98e8f7c4..f934a4d632 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -23,7 +23,7 @@ class OrderCycle < ActiveRecord::Base scope :distributing_product, lambda { |product| joins(:exchanges => :variants). merge(Exchange.outgoing). - where('spree_variants.id IN (?)', product.variants_including_master.map(&:id)). + where('spree_variants.id IN (?)', product.variants_including_master.pluck(:id)). select('DISTINCT order_cycles.*') } scope :with_distributor, lambda { |distributor| diff --git a/spec/models/exchange_spec.rb b/spec/models/exchange_spec.rb index 7095dd03f9..870025b29f 100644 --- a/spec/models/exchange_spec.rb +++ b/spec/models/exchange_spec.rb @@ -124,6 +124,7 @@ describe Exchange do p = create(:simple_product) ex = create(:exchange) ex.variants << p.master + p.reload Exchange.with_product(p).should == [ex] end @@ -133,6 +134,7 @@ describe Exchange do v = create(:variant, product: p) ex = create(:exchange) ex.variants << v + p.reload Exchange.with_product(p).should == [ex] end diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index 47e49a73e3..cdfe6c5828 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -63,6 +63,8 @@ describe OrderCycle do p = create(:product) d = create(:distributor_enterprise) oc = create(:simple_order_cycle, distributors: [d], variants: [p.master]) + p.reload + OrderCycle.distributing_product(p).should == [oc] end @@ -71,6 +73,8 @@ describe OrderCycle do v = create(:variant, product: p) d = create(:distributor_enterprise) oc = create(:simple_order_cycle, distributors: [d], variants: [v]) + p.reload + OrderCycle.distributing_product(p).should == [oc] end @@ -80,6 +84,7 @@ describe OrderCycle do oc = create(:simple_order_cycle) ex = create(:exchange, order_cycle: oc, sender: s, receiver: oc.coordinator) ex.variants << p.master + p.reload OrderCycle.distributing_product(p).should == [] end From 011668c0b667702ff362ffb593c922b911eb5f03 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 12 Mar 2014 13:34:34 +1100 Subject: [PATCH 016/107] Display line breaks in order confirmation email distributor info --- app/helpers/html_helper.rb | 17 ++++++++++++++++- spec/helpers/html_helper_spec.rb | 27 ++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/app/helpers/html_helper.rb b/app/helpers/html_helper.rb index c648cf3ace..522760d7d9 100644 --- a/app/helpers/html_helper.rb +++ b/app/helpers/html_helper.rb @@ -1,5 +1,20 @@ module HtmlHelper def strip_html(html) - strip_tags(html).andand.gsub(/ /i, ' ').andand.gsub(/&/i, '&') + squeeze_linebreaks substitute_entities strip_tags add_linebreaks html end + + def substitute_entities(html) + html.andand.gsub(/ /i, ' ').andand.gsub(/&/i, '&') + end + + def add_linebreaks(html) + # I know Cthulu is coming for me. Forgive me. + # http://stackoverflow.com/a/1732454/2720566 + html.andand.gsub(/<\/h[^>]>|]*>|<\/p>|<\/div>/, "\\1\n") + end + + def squeeze_linebreaks(html) + html.andand.squeeze "\n" + end + end diff --git a/spec/helpers/html_helper_spec.rb b/spec/helpers/html_helper_spec.rb index 729da60d48..4b3118ea10 100644 --- a/spec/helpers/html_helper_spec.rb +++ b/spec/helpers/html_helper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe HtmlHelper do describe "stripping html from a string" do it "strips tags" do - helper.strip_html('

Hello world!

').should == 'Hello world!' + helper.strip_html('

Hello world!

').should == "Hello world!\n" end it "removes nbsp and amp entities" do @@ -13,5 +13,30 @@ describe HtmlHelper do it "returns nil for nil input" do helper.strip_html(nil).should be_nil end + + describe "line breaks" do + it "adds a line break after heading tags" do + helper.strip_html("

foo

").should == "foo\n"; + helper.strip_html("

foo

").should == "foo\n"; + end + + it "adds a line break after br tags" do + helper.strip_html("foo
").should == "foo\n"; + helper.strip_html("foo
").should == "foo\n"; + helper.strip_html("foo
").should == "foo\n"; + end + + it "adds a line break after p tags" do + helper.strip_html("

foo

").should == "foo\n"; + end + + it "adds a line break after div tags" do + helper.strip_html("
foo
").should == "foo\n"; + end + + it "squeezes multiple line breaks" do + helper.strip_html("

foo



bar").should == "foo\nbar"; + end + end end end From f7d8d866eb8ac69b6f2bc6cd66ecbf8a6920a532 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 12 Mar 2014 13:52:13 +1100 Subject: [PATCH 017/107] Show two linebreaks for end of block level tags, strip whitespace from end of string --- app/helpers/html_helper.rb | 11 ++++++----- spec/helpers/html_helper_spec.rb | 34 ++++++++++++++++---------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/app/helpers/html_helper.rb b/app/helpers/html_helper.rb index 522760d7d9..95b7e5d0db 100644 --- a/app/helpers/html_helper.rb +++ b/app/helpers/html_helper.rb @@ -1,6 +1,6 @@ module HtmlHelper def strip_html(html) - squeeze_linebreaks substitute_entities strip_tags add_linebreaks html + strip_surrounding_whitespace substitute_entities strip_tags add_linebreaks html end def substitute_entities(html) @@ -10,11 +10,12 @@ module HtmlHelper def add_linebreaks(html) # I know Cthulu is coming for me. Forgive me. # http://stackoverflow.com/a/1732454/2720566 - html.andand.gsub(/<\/h[^>]>|]*>|<\/p>|<\/div>/, "\\1\n") + html. + andand.gsub(/<\/h[^>]>|<\/p>|<\/div>/, "\\1\n\n"). + andand.gsub(/]*>/, "\\1\n") end - def squeeze_linebreaks(html) - html.andand.squeeze "\n" + def strip_surrounding_whitespace(html) + html.andand.strip end - end diff --git a/spec/helpers/html_helper_spec.rb b/spec/helpers/html_helper_spec.rb index 4b3118ea10..3a19ddfce0 100644 --- a/spec/helpers/html_helper_spec.rb +++ b/spec/helpers/html_helper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe HtmlHelper do describe "stripping html from a string" do it "strips tags" do - helper.strip_html('

Hello world!

').should == "Hello world!\n" + helper.strip_html('

Hello world!

').should == "Hello world!" end it "removes nbsp and amp entities" do @@ -15,27 +15,27 @@ describe HtmlHelper do end describe "line breaks" do - it "adds a line break after heading tags" do - helper.strip_html("

foo

").should == "foo\n"; - helper.strip_html("

foo

").should == "foo\n"; + it "adds two line breaks after heading tags" do + helper.strip_html("

foo

bar").should == "foo\n\nbar"; + helper.strip_html("

foo

bar").should == "foo\n\nbar"; + end + + it "adds two line breaks after p tags" do + helper.strip_html("

foo

bar").should == "foo\n\nbar"; + end + + it "adds two line breaks after div tags" do + helper.strip_html("
foo
bar").should == "foo\n\nbar"; end it "adds a line break after br tags" do - helper.strip_html("foo
").should == "foo\n"; - helper.strip_html("foo
").should == "foo\n"; - helper.strip_html("foo
").should == "foo\n"; + helper.strip_html("foo
bar").should == "foo\nbar"; + helper.strip_html("foo
bar").should == "foo\nbar"; + helper.strip_html("foo
bar").should == "foo\nbar"; end - it "adds a line break after p tags" do - helper.strip_html("

foo

").should == "foo\n"; - end - - it "adds a line break after div tags" do - helper.strip_html("
foo
").should == "foo\n"; - end - - it "squeezes multiple line breaks" do - helper.strip_html("

foo



bar").should == "foo\nbar"; + it "strips line breaks at the end of the string" do + helper.strip_html("
foo

").should == "foo"; end end end From 0d97397084c552ca1b1d7a14a9e43695ce4cd26c Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 12 Mar 2014 14:48:45 +1100 Subject: [PATCH 018/107] Fix fragile specs (ordering) --- spec/controllers/spree/admin/reports_controller_spec.rb | 8 ++++---- spec/features/admin/products_spec.rb | 4 ++-- .../products_and_inventory_report_spec.rb | 2 +- spec/models/spree/variant_spec.rb | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/controllers/spree/admin/reports_controller_spec.rb b/spec/controllers/spree/admin/reports_controller_spec.rb index 87d9676308..8b4d80fcab 100644 --- a/spec/controllers/spree/admin/reports_controller_spec.rb +++ b/spec/controllers/spree/admin/reports_controller_spec.rb @@ -173,12 +173,12 @@ describe Spree::Admin::ReportsController do it "builds suppliers for the current user" do spree_get :products_and_inventory - assigns(:suppliers).should == [s1, s2, s3] + assigns(:suppliers).sort.should == [s1, s2, s3].sort end it "builds order cycles for the current user" do spree_get :products_and_inventory - assigns(:order_cycles).should == [ocB, ocA] + assigns(:order_cycles).sort.should == [ocB, ocA].sort end it "assigns report types" do @@ -216,7 +216,7 @@ describe Spree::Admin::ReportsController do it "should build distributors for the current user" do spree_get :customers - assigns(:distributors).should == [d1, d2, d3] + assigns(:distributors).sort.should == [d1, d2, d3].sort end it "builds suppliers for the current user" do @@ -226,7 +226,7 @@ describe Spree::Admin::ReportsController do it "builds order cycles for the current user" do spree_get :customers - assigns(:order_cycles).should == [ocB, ocA] + assigns(:order_cycles).sort.should == [ocB, ocA].sort end it "assigns report types" do diff --git a/spec/features/admin/products_spec.rb b/spec/features/admin/products_spec.rb index 20d599404b..77f1d8f637 100644 --- a/spec/features/admin/products_spec.rb +++ b/spec/features/admin/products_spec.rb @@ -42,8 +42,8 @@ feature %q{ click_button 'Update' product.reload - product.distributors.should == [@distributors[0], @distributors[2]] - product.product_distributions.map { |pd| pd.enterprise_fee }.should == [@enterprise_fees[0], @enterprise_fees[2]] + product.distributors.sort.should == [@distributors[0], @distributors[2]].sort + product.product_distributions.map { |pd| pd.enterprise_fee }.sort.should == [@enterprise_fees[0], @enterprise_fees[2]].sort end diff --git a/spec/lib/open_food_network/products_and_inventory_report_spec.rb b/spec/lib/open_food_network/products_and_inventory_report_spec.rb index c65d7bcae0..33b507faab 100644 --- a/spec/lib/open_food_network/products_and_inventory_report_spec.rb +++ b/spec/lib/open_food_network/products_and_inventory_report_spec.rb @@ -110,7 +110,7 @@ module OpenFoodNetwork it "should return unfiltered variants sans-params" do product1 = create(:simple_product, supplier: supplier) product2 = create(:simple_product, supplier: supplier) - subject.filter(Spree::Variant.scoped).should == [product1.master, product2.master] + subject.filter(Spree::Variant.scoped).sort.should == [product1.master, product2.master].sort end describe "based on report type" do it "returns only variants on hand" do diff --git a/spec/models/spree/variant_spec.rb b/spec/models/spree/variant_spec.rb index fee4dc1edd..d51a34e254 100644 --- a/spec/models/spree/variant_spec.rb +++ b/spec/models/spree/variant_spec.rb @@ -16,7 +16,7 @@ module Spree end it "returns variants in stock or on demand, but not those that are neither" do - Variant.where(is_master: false).in_stock.should == [@v_in_stock, @v_on_demand] + Variant.where(is_master: false).in_stock.sort.should == [@v_in_stock, @v_on_demand].sort end end end From f73cd3db7d6ba92b28cc2b0da3313322609f3c44 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 12 Mar 2014 12:39:15 +1100 Subject: [PATCH 019/107] Adding producer details popups --- app/views/shop/shop/_producers.html.haml | 9 +++++++++ app/views/shop/shop/_products.html.haml | 3 ++- app/views/shop/shop/show.html.haml | 12 ++++-------- 3 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 app/views/shop/shop/_producers.html.haml diff --git a/app/views/shop/shop/_producers.html.haml b/app/views/shop/shop/_producers.html.haml new file mode 100644 index 0000000000..f59cb268c6 --- /dev/null +++ b/app/views/shop/shop/_producers.html.haml @@ -0,0 +1,9 @@ +%section + %p.title.avenir{"data-section-title" => ""} + %a{href: "#producers"} Our Producers + .content{"data-section-content" => ""} + %ul + - for producer in @producers + %li + %a{"data-reveal-id" => "producer_details_#{producer.id}"} + = producer.name diff --git a/app/views/shop/shop/_products.html.haml b/app/views/shop/shop/_products.html.haml index 26ab4d17b6..8387b9adc7 100644 --- a/app/views/shop/shop/_products.html.haml +++ b/app/views/shop/shop/_products.html.haml @@ -18,7 +18,8 @@ %div %h5 {{ product.name }} - {{ product.supplier.name }} + %a{"data-reveal-id" => "producer_details_{{product.supplier.id}}"} + {{ product.supplier.name }} %td.notes {{ product.notes | truncate:80 }} %td %span{"ng-hide" => "product.variants.length > 0"} {{ product.master.options_text }} diff --git a/app/views/shop/shop/show.html.haml b/app/views/shop/shop/show.html.haml index 5072576f6f..290c026bae 100644 --- a/app/views/shop/shop/show.html.haml +++ b/app/views/shop/shop/show.html.haml @@ -1,5 +1,4 @@ %shop.darkswarm{"ng-app" => "Shop"} - - content_for :order_cycle_form do %strong.avenir Ready for %select.avenir#order_cycle_id{"ng-model" => "order_cycle.order_cycle_id", @@ -12,6 +11,8 @@ %strong {{ order_cycle.orders_close_at | date_in_words }} = render partial: "shop/details" + -# This partial generates the producer detail popovers + = render partial: "shop/shop/producer_details" %tabs .row @@ -23,13 +24,7 @@ %img.about.right{src: @distributor.promo_image.url(:large)} %p= @distributor.long_description.andand.html_safe - %section - %p.title.avenir{"data-section-title" => ""} - %a{href: "#producers"} Our Producers - .content{"data-section-content" => ""} - %ul - - for producer in @producers - %li= producer.name + = render 'shop/shop/producers' %section %p.title.avenir{"data-section-title" => ""} @@ -45,6 +40,7 @@ %products.row = render partial: "shop/shop/products" + #footer %section.row = render partial: "shop/shop/contact_us" From 3ec80aaaa34dde26a243a17eeb7f88820a668e2b Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 12 Mar 2014 12:52:21 +1100 Subject: [PATCH 020/107] Styling improvements to the popups for producers --- app/assets/javascripts/darkswarm/all.js.coffee | 1 + app/assets/stylesheets/darkswarm/shop.css.sass | 4 ++++ app/views/shop/shop/_producer_details.html.haml | 9 +++++++++ 3 files changed, 14 insertions(+) create mode 100644 app/views/shop/shop/_producer_details.html.haml diff --git a/app/assets/javascripts/darkswarm/all.js.coffee b/app/assets/javascripts/darkswarm/all.js.coffee index 1f2ecf155d..ba5c6b8086 100644 --- a/app/assets/javascripts/darkswarm/all.js.coffee +++ b/app/assets/javascripts/darkswarm/all.js.coffee @@ -13,3 +13,4 @@ $ -> $(document).foundation() + $(document).foundation('reveal', {animation: 'fade'}) diff --git a/app/assets/stylesheets/darkswarm/shop.css.sass b/app/assets/stylesheets/darkswarm/shop.css.sass index 1b9109cfaa..b858770200 100644 --- a/app/assets/stylesheets/darkswarm/shop.css.sass +++ b/app/assets/stylesheets/darkswarm/shop.css.sass @@ -90,6 +90,10 @@ product max-width: 555px @media all and (max-width: 768px) height: auto !important + ul + list-style-type: none + padding-left: none + & > .title, &.active > .title text-transform: uppercase line-height: 50px diff --git a/app/views/shop/shop/_producer_details.html.haml b/app/views/shop/shop/_producer_details.html.haml new file mode 100644 index 0000000000..c763a65728 --- /dev/null +++ b/app/views/shop/shop/_producer_details.html.haml @@ -0,0 +1,9 @@ +- for producer in @producers + .reveal-modal{id: "producer_details_#{producer.id}"} + %h2 + %img.left{src: producer.logo.url(:thumb)} + = producer.name + %img.about.right{src: producer.promo_image.url(:large)} + = producer.description.andand.html_safe + %a.close-reveal-modal × + From a1ed9519318259d59428d7b5296b7e02ee75d72f Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 12 Mar 2014 13:20:50 +1100 Subject: [PATCH 021/107] Default shipping address same as billing address, rendering data panel for pickup info --- .../darkswarm/controllers/checkout_controller.js.coffee | 4 +--- app/views/shop/checkout/_form.html.haml | 7 ++++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee index 8349ef1a81..54c949d247 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee @@ -1,14 +1,12 @@ angular.module("Checkout").controller "CheckoutCtrl", ($scope, $rootScope, order) -> $scope.require_ship_address = false - $scope.shipping_method = -1 - $scope.payment_method = -1 - $scope.same_as_billing = true $scope.order = order # Our shipping_methods comes through as a hash like so: {id: requires_shipping_address} # Here we default to the first shipping method if none is selected $scope.order.shipping_method_id ||= Object.keys(order.shipping_methods)[0] + $scope.order.ship_address_same_as_billing ||= true $scope.require_ship_address = $scope.order.shipping_methods[$scope.order.shipping_method_id] $scope.shippingMethodChanged = -> diff --git a/app/views/shop/checkout/_form.html.haml b/app/views/shop/checkout/_form.html.haml index 6213cbf3ac..fa08d0c451 100644 --- a/app/views/shop/checkout/_form.html.haml +++ b/app/views/shop/checkout/_form.html.haml @@ -57,11 +57,16 @@ "ng-change" => "shippingMethodChanged()", "ng-model" => "order.shipping_method_id" + #distributor_address.panel{"ng-show" => "!require_ship_address"} + = @order.distributor.distributor_info.andand.html_safe + = @order.order_cycle.pickup_time_for(@order.distributor) + = @order.order_cycle.pickup_instructions_for(@order.distributor) + = f.fields_for :ship_address, @order.ship_address do |sa| + #ship_address{"ng-show" => "require_ship_address"} %label = check_box_tag "order[ship_address_same_as_billing]", true, @order.ship_address_same_as_billing, - "ng-checked" => "order.ship_address_same_as_billing == 'true'", "ng-model" => "order.ship_address_same_as_billing" Shipping address same as billing address? From 2f7688f864411b1b3c45f01111c6b3886bd1eaa5 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 12 Mar 2014 13:23:39 +1100 Subject: [PATCH 022/107] Switching some form order --- app/views/shop/checkout/_form.html.haml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/views/shop/checkout/_form.html.haml b/app/views/shop/checkout/_form.html.haml index fa08d0c451..5ba9c2967a 100644 --- a/app/views/shop/checkout/_form.html.haml +++ b/app/views/shop/checkout/_form.html.haml @@ -33,19 +33,20 @@ "ng-model" => "order.bill_address.address2" .row .large-6.columns + = ba.text_field :city, "ng-model" => "order.bill_address.city" - .large-6.columns - = ba.select :country_id, available_countries.map{|c|[c.name, c.id]}, - {include_blank: false}, "ng-model" => "order.bill_address.country_id" - .row .large-6.columns = ba.select :state_id, @order.billing_address.country.states.map{|c|[c.name, c.id]}, "ng-model" => "order.bill_address.state_id" - .large-6.columns.right + .row + .large-6.columns = ba.text_field :zipcode, "ng-model" => "order.bill_address.zipcode" + .large-6.columns.right + = ba.select :country_id, available_countries.map{|c|[c.name, c.id]}, + {include_blank: false}, "ng-model" => "order.bill_address.country_id" %fieldset#shipping %legend Shipping From f96e420f010e28e4ef43be31f6b39afefb79e757 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 12 Mar 2014 13:41:05 +1100 Subject: [PATCH 023/107] Re-arranging our radio boxes --- app/views/shop/checkout/_form.html.haml | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/app/views/shop/checkout/_form.html.haml b/app/views/shop/checkout/_form.html.haml index 5ba9c2967a..15b4665e8d 100644 --- a/app/views/shop/checkout/_form.html.haml +++ b/app/views/shop/checkout/_form.html.haml @@ -42,7 +42,7 @@ "ng-model" => "order.bill_address.state_id" .row .large-6.columns - = ba.text_field :zipcode, + = ba.text_field :zipcode, label: "Postcode", "ng-model" => "order.bill_address.zipcode" .large-6.columns.right = ba.select :country_id, available_countries.map{|c|[c.name, c.id]}, @@ -53,10 +53,15 @@ - for ship_method, i in current_distributor.shipping_methods.uniq .row .large-12.columns - = f.radio_button :shipping_method_id, ship_method.id, - text: ship_method.name, - "ng-change" => "shippingMethodChanged()", - "ng-model" => "order.shipping_method_id" + -#= f.radio_button :shipping_method_id, ship_method.id, + -#text: ship_method.name, + -#"ng-change" => "shippingMethodChanged()", + -#"ng-model" => "order.shipping_method_id" + %label + = radio_button_tag "order[shipping_method_id]", ship_method.id, false, + "ng-change" => "shippingMethodChanged()", + "ng-model" => "order.shipping_method_id" + = ship_method.name #distributor_address.panel{"ng-show" => "!require_ship_address"} = @order.distributor.distributor_info.andand.html_safe @@ -84,13 +89,13 @@ .large-6.columns = sa.text_field :city .large-6.columns - = sa.select :country_id, available_countries.map{|c|[c.name, c.id]}, - {include_blank: false} + = sa.select :state_id, @order.shipping_address.country.states.map{|c|[c.name, c.id]} .row .large-6.columns - = sa.select :state_id, @order.shipping_address.country.states.map{|c|[c.name, c.id]} + = sa.text_field :zipcode, label: "Postcode" .large-6.columns.right - = sa.text_field :zipcode + = sa.select :country_id, available_countries.map{|c|[c.name, c.id]}, + {include_blank: false} .row .large-6.columns = sa.text_field :firstname From 9848a724b72440be9cff4f72ad8b5308f59cfc04 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 12 Mar 2014 14:23:48 +1100 Subject: [PATCH 024/107] Adding Back to Cart link --- app/views/shop/checkout/_form.html.haml | 1 + app/views/shop/checkout/_summary.html.haml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/views/shop/checkout/_form.html.haml b/app/views/shop/checkout/_form.html.haml index 15b4665e8d..21c700c3f3 100644 --- a/app/views/shop/checkout/_form.html.haml +++ b/app/views/shop/checkout/_form.html.haml @@ -1,4 +1,5 @@ %checkout{"ng-controller" => "CheckoutCtrl"} + = f_form_for current_order, url: main_app.shop_update_checkout_path, html: {name: "checkout", id: "checkout_form"} do |f| :javascript diff --git a/app/views/shop/checkout/_summary.html.haml b/app/views/shop/checkout/_summary.html.haml index edda8bc43a..6225f7c069 100644 --- a/app/views/shop/checkout/_summary.html.haml +++ b/app/views/shop/checkout/_summary.html.haml @@ -20,4 +20,6 @@ %th= label %td= total + %div + %a{href: cart_url} Back to Cart = f.submit "Purchase", class: "button" From c08f739d4a340807cfaa3a6297aa6d8828135cc3 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 12 Mar 2014 15:13:25 +1100 Subject: [PATCH 025/107] Minor HTML changes --- .../stylesheets/darkswarm/shop.css.sass | 2 ++ app/views/shop/shop/_about_us.html.haml | 25 ++++++++++++++++--- app/views/shop/shop/_contact_us.html.haml | 18 ------------- app/views/shop/shop/_products.html.haml | 4 +-- app/views/shop/shop/show.html.haml | 13 +--------- 5 files changed, 27 insertions(+), 35 deletions(-) delete mode 100644 app/views/shop/shop/_contact_us.html.haml diff --git a/app/assets/stylesheets/darkswarm/shop.css.sass b/app/assets/stylesheets/darkswarm/shop.css.sass index b858770200..58776a6fd5 100644 --- a/app/assets/stylesheets/darkswarm/shop.css.sass +++ b/app/assets/stylesheets/darkswarm/shop.css.sass @@ -93,6 +93,8 @@ product ul list-style-type: none padding-left: none + .panel + padding-bottom: 1.25em & > .title, &.active > .title text-transform: uppercase diff --git a/app/views/shop/shop/_about_us.html.haml b/app/views/shop/shop/_about_us.html.haml index 74ef2eaa47..78d9390cd5 100644 --- a/app/views/shop/shop/_about_us.html.haml +++ b/app/views/shop/shop/_about_us.html.haml @@ -1,3 +1,22 @@ -.about.right.text-right.small-2.large-3.columns - %h3 About Us - %p= @distributor.long_description.andand.html_safe +%section#about{class: current_order_cycle ? nil : "active" } + %p.title.avenir{"data-section-title" => ""} + %a{href: "#about"} About Us + + .content{"data-section-content" => ""} + %img.about.right{src: @distributor.promo_image.url(:large)} + %p= @distributor.long_description.andand.html_safe + + .panel + %p + %strong E + %a{href: "mailto:#{@distributor.email}"}= @distributor.email + + - unless @distributor.website.blank? + %strong W + %a{href: @distributor.website}= @distributor.website + + = [@distributor.address.address1, @distributor.address.address2].join ", " + %br + = @distributor.address.city + = @distributor.address.state + = @distributor.address.zipcode diff --git a/app/views/shop/shop/_contact_us.html.haml b/app/views/shop/shop/_contact_us.html.haml deleted file mode 100644 index 58a69ed38f..0000000000 --- a/app/views/shop/shop/_contact_us.html.haml +++ /dev/null @@ -1,18 +0,0 @@ -.contact.small-2.large-3.columns - %h3 Contact - - %p - %strong E - %a{href: "mailto:#{@distributor.email}"}= @distributor.email - - - unless @distributor.website.blank? - %p - %strong W - %a{href: @distributor.website}= @distributor.website - - %p - = @distributor.address.address1 - %br - = @distributor.address.address2 - %br - = @distributor.address.city diff --git a/app/views/shop/shop/_products.html.haml b/app/views/shop/shop/_products.html.haml index 8387b9adc7..90a27d2c22 100644 --- a/app/views/shop/shop/_products.html.haml +++ b/app/views/shop/shop/_products.html.haml @@ -2,7 +2,7 @@ %h5 Check out when you have selected everything you want = form_for :order, :url => populate_orders_path, html: {:class => "custom"} do %input#search.text{"ng-model" => "query", placeholder: "Search"} - %input.button.right{type: :submit, value: "Check Out"} + %input.button.right{type: :submit, value: "Add to Cart"} %table %thead %th.name Item @@ -52,4 +52,4 @@ %td{colspan: 2}{{ product.notes | truncate:80 }} %tr.variant{"ng-repeat" => "variant in product.variants", "ng-show" => "product.show_variants"} = render partial: "shop/shop/variant" - %input.button.right{type: :submit, value: "Check Out"} + %input.button.right{type: :submit, value: "Add to Cart"} diff --git a/app/views/shop/shop/show.html.haml b/app/views/shop/shop/show.html.haml index 290c026bae..a7450e3145 100644 --- a/app/views/shop/shop/show.html.haml +++ b/app/views/shop/shop/show.html.haml @@ -17,13 +17,8 @@ %tabs .row .section-container.auto{"data-section" => "", "data-options" => "one_up: false"} - %section#about{class: current_order_cycle ? nil : "active" } - %p.title.avenir{"data-section-title" => ""} - %a{href: "#about"} About Us - .content{"data-section-content" => ""} - %img.about.right{src: @distributor.promo_image.url(:large)} - %p= @distributor.long_description.andand.html_safe + = render 'shop/shop/about_us' = render 'shop/shop/producers' %section @@ -40,9 +35,3 @@ %products.row = render partial: "shop/shop/products" - - #footer - %section.row - = render partial: "shop/shop/contact_us" - = render partial: "shop/shop/about_us" - = render partial: "shared/copyright" From 35bd1257d919876f92dfa756fc1fe00eb6eec95b Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 12 Mar 2014 15:23:35 +1100 Subject: [PATCH 026/107] Opening variants by default --- app/views/shop/shop/_products.html.haml | 2 ++ app/views/shop/shop/products.rabl | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/app/views/shop/shop/_products.html.haml b/app/views/shop/shop/_products.html.haml index 90a27d2c22..0a1f67d0e7 100644 --- a/app/views/shop/shop/_products.html.haml +++ b/app/views/shop/shop/_products.html.haml @@ -53,3 +53,5 @@ %tr.variant{"ng-repeat" => "variant in product.variants", "ng-show" => "product.show_variants"} = render partial: "shop/shop/variant" %input.button.right{type: :submit, value: "Add to Cart"} + + diff --git a/app/views/shop/shop/products.rabl b/app/views/shop/shop/products.rabl index 662196953e..c92a1afaa0 100644 --- a/app/views/shop/shop/products.rabl +++ b/app/views/shop/shop/products.rabl @@ -1,5 +1,10 @@ collection @products attributes :id, :name, :permalink, :count_on_hand, :on_demand, :group_buy + +node :show_variants do + true +end + node do |product| { notes: strip_tags(product.notes), From 7c50e1a41dd1989accb43abb6fd3cf352acb1d7a Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 12 Mar 2014 17:17:07 +1100 Subject: [PATCH 027/107] Fixing up the specs --- .../controllers/checkout_controller.js.coffee | 3 ++- app/views/shop/checkout/_order.rabl | 5 ++++- spec/features/consumer/shopping/checkout_spec.rb | 4 ++-- spec/features/consumer/shopping/shopping_spec.rb | 10 ++-------- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee index 54c949d247..a98c0bcd60 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee @@ -6,7 +6,7 @@ angular.module("Checkout").controller "CheckoutCtrl", ($scope, $rootScope, order # Our shipping_methods comes through as a hash like so: {id: requires_shipping_address} # Here we default to the first shipping method if none is selected $scope.order.shipping_method_id ||= Object.keys(order.shipping_methods)[0] - $scope.order.ship_address_same_as_billing ||= true + $scope.order.ship_address_same_as_billing = true if $scope.order.ship_address_same_as_billing == null $scope.require_ship_address = $scope.order.shipping_methods[$scope.order.shipping_method_id] $scope.shippingMethodChanged = -> @@ -16,3 +16,4 @@ angular.module("Checkout").controller "CheckoutCtrl", ($scope, $rootScope, order $scope.purchase = (event)-> event.preventDefault() checkout.submit() + diff --git a/app/views/shop/checkout/_order.rabl b/app/views/shop/checkout/_order.rabl index 237a782d85..fc18538afd 100644 --- a/app/views/shop/checkout/_order.rabl +++ b/app/views/shop/checkout/_order.rabl @@ -1,5 +1,8 @@ object current_order -attributes :id, :email, :shipping_method_id, :ship_address_same_as_billing +attributes :id, :email, :shipping_method_id +node :ship_address_same_as_billing do + current_order.ship_address_same_as_billing == "true" +end child current_order.bill_address => :bill_address do attributes :phone, :firstname, :lastname, :address1, :address2, :city, :country_id, :state_id, :zipcode diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb index 16315be7a7..0fd76077d6 100644 --- a/spec/features/consumer/shopping/checkout_spec.rb +++ b/spec/features/consumer/shopping/checkout_spec.rb @@ -119,7 +119,7 @@ feature "As a consumer I want to check out my cart", js: true do fill_in "Customer E-Mail", with: "test@test.com" fill_in "Phone", with: "0468363090" fill_in "City", with: "Melbourne" - fill_in "Zip Code", with: "3066" + fill_in "Postcode", with: "3066" end click_button "Purchase" page.should have_content "Your order has been processed successfully" @@ -137,7 +137,7 @@ feature "As a consumer I want to check out my cart", js: true do fill_in "Customer E-Mail", with: "test@test.com" fill_in "Phone", with: "0468363090" fill_in "City", with: "Melbourne" - fill_in "Zip Code", with: "3066" + fill_in "Postcode", with: "3066" end check "Shipping address same as billing address?" click_button "Purchase" diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index 467ef2365f..d42470be26 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -134,13 +134,11 @@ feature "As a consumer I want to shop with a distributor", js: true do page.should_not have_selector("#variants_#{product.master.id}", visible: true) end - it "collapses variants by default" do - page.should_not have_text variant1.options_text + it "expands variants by default" do + page.should have_text variant1.options_text end it "expands variants" do - find(".expand").trigger "click" - page.should have_text variant1.options_text find(".collapse").trigger "click" page.should_not have_text variant1.options_text end @@ -161,7 +159,6 @@ feature "As a consumer I want to shop with a distributor", js: true do page.should_not have_selector 'tr.product > td', text: "from $33.00" # Page should have variant prices (with fee) - find(".expand").trigger 'click' page.should have_selector 'tr.variant > td.price', text: "$43.00" page.should have_selector 'tr.variant > td.price', text: "$53.00" @@ -223,7 +220,6 @@ feature "As a consumer I want to shop with a distributor", js: true do page.should_not have_content p3.name # It shows on demand variants - within(".product-#{p4.id}") { find(".expand", visible: true).trigger "click" } page.should have_content v3.options_text # It does not show variants that are neither on hand or on demand @@ -262,7 +258,6 @@ feature "As a consumer I want to shop with a distributor", js: true do let(:variant) { create(:variant, product: product, on_hand: 10 ) } before do build_and_select_order_cycle_with_variants - find(".expand").trigger "click" end it "should show group buy input" do @@ -289,7 +284,6 @@ feature "As a consumer I want to shop with a distributor", js: true do build_and_select_order_cycle_with_variants end it "should let us add products to our cart" do - find(".expand").trigger "click" fill_in "variants[#{variant.id}]", with: "1" first("form.custom > input.button.right").click current_path.should == "/cart" From 8a7dad020be9aca17b9eae4cae0edfdf9939d0c1 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 13 Mar 2014 11:24:33 +1100 Subject: [PATCH 028/107] Modifying the back to cart button --- app/assets/stylesheets/darkswarm/checkout.css.sass | 4 ++++ app/views/shop/checkout/_summary.html.haml | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/darkswarm/checkout.css.sass b/app/assets/stylesheets/darkswarm/checkout.css.sass index c864d4928a..676ab08e43 100644 --- a/app/assets/stylesheets/darkswarm/checkout.css.sass +++ b/app/assets/stylesheets/darkswarm/checkout.css.sass @@ -1,2 +1,6 @@ checkout display: block + + orderdetails + .button, table + width: 100% diff --git a/app/views/shop/checkout/_summary.html.haml b/app/views/shop/checkout/_summary.html.haml index 6225f7c069..23289b696a 100644 --- a/app/views/shop/checkout/_summary.html.haml +++ b/app/views/shop/checkout/_summary.html.haml @@ -20,6 +20,5 @@ %th= label %td= total - %div - %a{href: cart_url} Back to Cart = f.submit "Purchase", class: "button" + %a.button.secondary{href: cart_url} Back to Cart From e7bed58616706cd624eddefae3dac9bb78b783f3 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 13 Mar 2014 11:25:06 +1100 Subject: [PATCH 029/107] Removing text as per #327 --- app/views/shop/shop/_products.html.haml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/shop/shop/_products.html.haml b/app/views/shop/shop/_products.html.haml index 0a1f67d0e7..22bc7c7933 100644 --- a/app/views/shop/shop/_products.html.haml +++ b/app/views/shop/shop/_products.html.haml @@ -1,5 +1,4 @@ %products{"ng-controller" => "ProductsCtrl", "ng-show" => "order_cycle.order_cycle_id != null"} - %h5 Check out when you have selected everything you want = form_for :order, :url => populate_orders_path, html: {:class => "custom"} do %input#search.text{"ng-model" => "query", placeholder: "Search"} %input.button.right{type: :submit, value: "Add to Cart"} From 1ba720481ff8ecc5613995ccf63204d362fc1a6b Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 13 Mar 2014 11:41:46 +1100 Subject: [PATCH 030/107] Finally fixing the same as billing address checkbox? --- .../darkswarm/controllers/checkout_controller.js.coffee | 1 + app/models/spree/order_decorator.rb | 6 ++++++ app/views/shop/checkout/_form.html.haml | 1 + app/views/shop/checkout/_order.rabl | 5 +---- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee index a98c0bcd60..60b0715d25 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee @@ -7,6 +7,7 @@ angular.module("Checkout").controller "CheckoutCtrl", ($scope, $rootScope, order # Here we default to the first shipping method if none is selected $scope.order.shipping_method_id ||= Object.keys(order.shipping_methods)[0] $scope.order.ship_address_same_as_billing = true if $scope.order.ship_address_same_as_billing == null + $scope.require_ship_address = $scope.order.shipping_methods[$scope.order.shipping_method_id] $scope.shippingMethodChanged = -> diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index e78c5686d5..c744a3270a 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -65,6 +65,12 @@ Spree::Order.class_eval do where("state != ?", state) } + # Accessors + # + def ship_address_same_as_billing=(string_value) + @ship_address_same_as_billing = (string_value == "true") + end + # -- Methods def products_available_from_new_distribution diff --git a/app/views/shop/checkout/_form.html.haml b/app/views/shop/checkout/_form.html.haml index 21c700c3f3..ab66cd9d19 100644 --- a/app/views/shop/checkout/_form.html.haml +++ b/app/views/shop/checkout/_form.html.haml @@ -73,6 +73,7 @@ #ship_address{"ng-show" => "require_ship_address"} %label + = hidden_field_tag "order[ship_address_same_as_billing]", "false" = check_box_tag "order[ship_address_same_as_billing]", true, @order.ship_address_same_as_billing, "ng-model" => "order.ship_address_same_as_billing" Shipping address same as billing address? diff --git a/app/views/shop/checkout/_order.rabl b/app/views/shop/checkout/_order.rabl index fc18538afd..237a782d85 100644 --- a/app/views/shop/checkout/_order.rabl +++ b/app/views/shop/checkout/_order.rabl @@ -1,8 +1,5 @@ object current_order -attributes :id, :email, :shipping_method_id -node :ship_address_same_as_billing do - current_order.ship_address_same_as_billing == "true" -end +attributes :id, :email, :shipping_method_id, :ship_address_same_as_billing child current_order.bill_address => :bill_address do attributes :phone, :firstname, :lastname, :address1, :address2, :city, :country_id, :state_id, :zipcode From 9c655a855e8bb1ad5c0d763a9180d1c1ae5b85da Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 13 Mar 2014 11:47:55 +1100 Subject: [PATCH 031/107] Redirecting to the shop page when a distributor is selected --- app/controllers/enterprises_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/enterprises_controller.rb b/app/controllers/enterprises_controller.rb index d4b6ade04b..d9c285206a 100644 --- a/app/controllers/enterprises_controller.rb +++ b/app/controllers/enterprises_controller.rb @@ -67,7 +67,7 @@ class EnterprisesController < BaseController order.order_cycle = order_cycle_options.first if order_cycle_options.count == 1 order.save! - redirect_to main_app.enterprise_path(distributor) + redirect_to main_app.shop_path end # essentially the new 'show' action that renders non-spree view From 0c18a9c79001281a3b2a1d709a82af0fb9e54e2c Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 13 Mar 2014 11:56:55 +1100 Subject: [PATCH 032/107] Changing links on cart --- .../spree/orders/edit/list_view.html.haml.deface | 6 ++++-- .../spree/orders/edit/quick_checkout.html.haml.deface | 11 ++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/overrides/spree/orders/edit/list_view.html.haml.deface b/app/overrides/spree/orders/edit/list_view.html.haml.deface index 68416cf87d..d8a83443ed 100644 --- a/app/overrides/spree/orders/edit/list_view.html.haml.deface +++ b/app/overrides/spree/orders/edit/list_view.html.haml.deface @@ -1,4 +1,6 @@ -/ insert_bottom "[id='clear_cart_link']" +/ replace_contents "[id='clear_cart_link']" %div - = link_to "Shop in List View", main_app.shop_path + = link_to "Continue Shopping in List View", main_app.shop_path, class: "button primary checkout" + = t(:or) + = submit_tag t(:empty_cart), :class => 'button gray' diff --git a/app/overrides/spree/orders/edit/quick_checkout.html.haml.deface b/app/overrides/spree/orders/edit/quick_checkout.html.haml.deface index c0dd485877..a94cab591a 100644 --- a/app/overrides/spree/orders/edit/quick_checkout.html.haml.deface +++ b/app/overrides/spree/orders/edit/quick_checkout.html.haml.deface @@ -1,4 +1,9 @@ -/ insert_bottom "[data-hook='cart_buttons']" +/ replace_contents "[data-hook='cart_buttons']" -%div - = link_to "Quick Checkout", main_app.shop_checkout_path += button_tag :class => 'primary', :id => 'update-button' do + = t(:update) + += link_to "Quick Checkout", main_app.shop_checkout_path, class: "button checkout primary" + += button_tag :class => 'button checkout gray', :id => 'checkout-link', :name => 'checkout' do + Old Checkout From f6321afbb598de168a5a392c08bc1dac511477d6 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 13 Mar 2014 13:51:32 +1100 Subject: [PATCH 033/107] Fixing up a bunch of specs to accommodate the new routing --- ...ises_distributor_info_rich_text_feature_spec.rb | 1 + spec/features/consumer/browse_products_spec.rb | 1 + spec/features/consumer/checkout_spec.rb | 14 +++++++++----- spec/features/consumer/cms_spec.rb | 7 +++++-- spec/features/consumer/distributors_spec.rb | 3 +-- spec/features/consumer/order_cycles_spec.rb | 9 ++++++++- spec/features/consumer/suppliers_spec.rb | 5 +---- spec/features/consumer/temp_landing_page_spec.rb | 2 +- 8 files changed, 27 insertions(+), 15 deletions(-) diff --git a/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb b/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb index 4482246878..a000ae2b8b 100644 --- a/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb +++ b/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb @@ -111,6 +111,7 @@ feature "enterprises distributor info as rich text" do login_to_consumer_section click_link 'Green Grass' + visit enterprise_path d # -- Product details page click_link p.name diff --git a/spec/features/consumer/browse_products_spec.rb b/spec/features/consumer/browse_products_spec.rb index a12899d4bc..8213e00035 100644 --- a/spec/features/consumer/browse_products_spec.rb +++ b/spec/features/consumer/browse_products_spec.rb @@ -82,6 +82,7 @@ feature %q{ # When I am in that order cycle visit root_path click_link d.name + visit enterprise_path d # And I view the product click_link p.name diff --git a/spec/features/consumer/checkout_spec.rb b/spec/features/consumer/checkout_spec.rb index b55cc7821a..4f8d4bc3ab 100644 --- a/spec/features/consumer/checkout_spec.rb +++ b/spec/features/consumer/checkout_spec.rb @@ -92,7 +92,7 @@ feature %q{ # When I add some apples and some garlic to my cart click_link 'Fuji apples' click_button 'Add To Cart' - click_link 'Continue shopping' + visit enterprise_path @distributor1 click_link 'Garlic' click_button 'Add To Cart' @@ -112,11 +112,12 @@ feature %q{ # And I am logged in login_to_consumer_section click_link "FruitAndVeg" + visit enterprise_path @distributor1 # When I add some bananas and zucchini to my cart click_link 'Bananas' click_button 'Add To Cart' - click_link 'Continue shopping' + visit enterprise_path @distributor1 click_link 'Zucchini' click_button 'Add To Cart' @@ -355,10 +356,11 @@ feature %q{ login_to_consumer_section click_link 'FruitAndVeg' + visit enterprise_path @distributor1 click_link 'Bananas' click_button 'Add To Cart' - click_link 'Continue shopping' + visit enterprise_path @distributor1 click_link 'Zucchini' click_button 'Add To Cart' @@ -433,12 +435,13 @@ feature %q{ country: Spree::Country.find_by_name('Australia'))) click_link 'FruitAndVeg' - click_link 'Logout' + click_link 'Sign Out' click_link 'FruitAndVeg' + visit enterprise_path @distributor1 click_link 'Bananas' click_button 'Add To Cart' - click_link 'Continue shopping' + visit enterprise_path @distributor1 click_link 'Zucchini' click_button 'Add To Cart' @@ -529,6 +532,7 @@ feature %q{ # Distributors distributor1 = FactoryGirl.create(:distributor_enterprise, name: "FruitAndVeg") + @distributor1 = distributor1 distributor2 = FactoryGirl.create(:distributor_enterprise, name: "MoreFreshStuff") create_enterprise_group_for distributor1 distributor_fee1 = create(:enterprise_fee, enterprise: distributor1, fee_type: 'packing', amount: 7) diff --git a/spec/features/consumer/cms_spec.rb b/spec/features/consumer/cms_spec.rb index 36653bea01..de52f2973e 100644 --- a/spec/features/consumer/cms_spec.rb +++ b/spec/features/consumer/cms_spec.rb @@ -7,9 +7,9 @@ feature %q{ } do include AuthenticationWorkflow include WebHelper + let(:d) { create(:distributor_enterprise, :name => 'Edible garden') } background do - d = create(:distributor_enterprise, :name => 'Edible garden') create_enterprise_group_for d end @@ -22,6 +22,7 @@ feature %q{ # and proceed to the shop front click_on 'Edible garden' + visit enterprise_path d # Then I should not see the home page content page.should_not have_content 'Home page content' @@ -36,9 +37,10 @@ feature %q{ # When I visit the home page visit spree.root_path - # and proceed to the shop front click_on "Edible garden" + visit enterprise_path d + # Then I should see a menu with these pages page.should have_selector 'ul#main-nav-bar li', :text => 'One' @@ -56,6 +58,7 @@ feature %q{ # When I go to one of the pages visit spree.root_path click_on "Edible garden" + visit enterprise_path d click_link 'Two' # Then I should see the page diff --git a/spec/features/consumer/distributors_spec.rb b/spec/features/consumer/distributors_spec.rb index f4652ac5b9..70b38dddbd 100644 --- a/spec/features/consumer/distributors_spec.rb +++ b/spec/features/consumer/distributors_spec.rb @@ -101,8 +101,7 @@ feature %q{ click_link d1.name # Then I should see the distributor details - page.should have_selector 'h1', :text => d1.name - page.should have_selector 'div.enterprise-description', :text => 'Hello, world!' + page.should have_content d1.name # And I should see the first, but not the second product page.should have_content p1.name diff --git a/spec/features/consumer/order_cycles_spec.rb b/spec/features/consumer/order_cycles_spec.rb index 591b7af002..ea4dde4147 100644 --- a/spec/features/consumer/order_cycles_spec.rb +++ b/spec/features/consumer/order_cycles_spec.rb @@ -44,6 +44,7 @@ feature %q{ visit spree.root_path click_link d.name + visit enterprise_path d page.should have_select 'order_order_cycle_id' select_by_value oc1.id, from: 'order_order_cycle_id' @@ -63,9 +64,10 @@ feature %q{ oc1 = create(:simple_order_cycle, name: 'oc 1', distributors: [d1], orders_close_at: 5.minutes.ago) oc2 = create(:simple_order_cycle, name: 'oc 1', distributors: [d2], orders_close_at: 3.minutes.ago) click_link d1.name + visit enterprise_path d1 page.should have_content "Orders are currently closed for this hub" - page.should have_content "The last cycle closed 5 minutes ago." + page.should have_content "The last cycle closed 5 minutes ago" page.should have_content "Please contact your hub directly to see if they accept late orders, or wait until the next cycle opens." page.should have_content d1.email page.should have_content d1.phone @@ -77,11 +79,14 @@ feature %q{ create(:simple_order_cycle, name: 'oc 1', distributors: [d1], orders_open_at: 10.days.from_now, orders_close_at: 11.days.from_now) click_link d1.name + visit enterprise_path d1 + page.should have_content "The next order cycle opens in 10 days" end it "should show nothing when there is no next order cycle" do click_link d1.name + visit enterprise_path d1 page.should_not have_content "The next order cycle opens" page.should_not have_content "No products found" end @@ -97,6 +102,7 @@ feature %q{ visit spree.root_path click_link d.name + visit enterprise_path d click_link p.name click_button 'Add To Cart' @@ -148,6 +154,7 @@ feature %q{ # When I select an order cycle and add a product to my cart visit spree.root_path click_link 'Green Grass' + visit enterprise_path d click_link p.name click_button 'Add To Cart' diff --git a/spec/features/consumer/suppliers_spec.rb b/spec/features/consumer/suppliers_spec.rb index ca5430e93f..7c69c2e64b 100644 --- a/spec/features/consumer/suppliers_spec.rb +++ b/spec/features/consumer/suppliers_spec.rb @@ -30,10 +30,7 @@ feature %q{ click_link d.name # Then that hub should be selected - page.should have_selector 'h1', text: d.name - - # And I should be on the hub page - within('#products') { page.should have_content p.name } + page.should have_content d.name end scenario "viewing a list of suppliers (with active products) in the sidebar when there's 5 or fewer", :future => true do diff --git a/spec/features/consumer/temp_landing_page_spec.rb b/spec/features/consumer/temp_landing_page_spec.rb index 5a111aa132..183284514f 100644 --- a/spec/features/consumer/temp_landing_page_spec.rb +++ b/spec/features/consumer/temp_landing_page_spec.rb @@ -74,7 +74,7 @@ feature %q{ it "should link to the hub page" do click_on 'Murandaka' - page.should have_content 'CART' + current_path.should == "/shop" end end From 179844b5cf3f65e2a7b341d9f73f161bc1915552 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 13 Mar 2014 14:32:14 +1100 Subject: [PATCH 034/107] Fixing another spec --- spec/features/consumer/order_cycles_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/features/consumer/order_cycles_spec.rb b/spec/features/consumer/order_cycles_spec.rb index ea4dde4147..e73d2938ea 100644 --- a/spec/features/consumer/order_cycles_spec.rb +++ b/spec/features/consumer/order_cycles_spec.rb @@ -106,8 +106,8 @@ feature %q{ click_link p.name click_button 'Add To Cart' + visit enterprise_path d - click_link 'Continue shopping' click_link 'Change Collection Date' @@ -160,7 +160,8 @@ feature %q{ # And the order cycle expires and I load a page Timecop.travel(oc.orders_close_at + 1.day) do - click_link 'Continue shopping' + + visit enterprise_path d # Then I should see an expiry message page.should have_content "Sorry, orders for this order cycle closed 1 day ago! Please contact your hub directly to see if they can accept late orders." From 3370865a013709281b39a175fac1d6fd744cd037 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 13 Mar 2014 14:50:06 +1100 Subject: [PATCH 035/107] Adding group siblings to the shop page --- app/controllers/shop/shop_controller.rb | 2 +- app/views/shop/shop/_groups.html.haml | 13 +++++++++++++ app/views/shop/shop/_modals.html.haml | 19 +++++++++++++++++++ .../shop/shop/_producer_details.html.haml | 9 --------- app/views/shop/shop/show.html.haml | 11 +++-------- spec/controllers/shop/shop_controller_spec.rb | 14 +++++++++++++- 6 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 app/views/shop/shop/_groups.html.haml create mode 100644 app/views/shop/shop/_modals.html.haml delete mode 100644 app/views/shop/shop/_producer_details.html.haml diff --git a/app/controllers/shop/shop_controller.rb b/app/controllers/shop/shop_controller.rb index b7f124acfa..81c43f0b6a 100644 --- a/app/controllers/shop/shop_controller.rb +++ b/app/controllers/shop/shop_controller.rb @@ -7,6 +7,7 @@ class Shop::ShopController < BaseController def show # All suppliers of all our products @producers = Exchange.where(receiver_id: @distributor.id).map{ |ex| ex.variants.map {|v| v.product.supplier }}.flatten.uniq + @groups = current_distributor.groups end def products @@ -14,7 +15,6 @@ class Shop::ShopController < BaseController .products_distributed_by(current_distributor).andand .select { |p| p.has_stock_for_distribution?(current_order_cycle, current_distributor) }.andand .sort_by {|p| p.name } - render json: "", status: 404 end end diff --git a/app/views/shop/shop/_groups.html.haml b/app/views/shop/shop/_groups.html.haml new file mode 100644 index 0000000000..814067e043 --- /dev/null +++ b/app/views/shop/shop/_groups.html.haml @@ -0,0 +1,13 @@ +%section + %p.title.avenir{"data-section-title" => ""} + %a{href: "#groups"} Our Groups + .content{"data-section-content" => ""} + %ul + - for group in @groups + %li + %h4= group.name + %ul + - for sibling in group.enterprises.except(current_distributor) + %li + %a{"data-reveal-id" => "sibling_details_#{sibling.id}"} + = sibling.name diff --git a/app/views/shop/shop/_modals.html.haml b/app/views/shop/shop/_modals.html.haml new file mode 100644 index 0000000000..992f38d504 --- /dev/null +++ b/app/views/shop/shop/_modals.html.haml @@ -0,0 +1,19 @@ +- for producer in @producers + .reveal-modal{id: "producer_details_#{producer.id}"} + %h2 + %img.left{src: producer.logo.url(:thumb)} + = producer.name + %img.about.right{src: producer.promo_image.url(:large)} + = producer.description.andand.html_safe + %a.close-reveal-modal × + +- for group in @groups + - for sibling in group.enterprises.except(current_distributor) + .reveal-modal{id: "sibling_details_#{sibling.id}"} + %h2 + %img.left{src: sibling.logo.url(:thumb)} + = sibling.name + %img.about.right{src: sibling.promo_image.url(:large)} + = sibling.description.andand.html_safe + %a.close-reveal-modal × + diff --git a/app/views/shop/shop/_producer_details.html.haml b/app/views/shop/shop/_producer_details.html.haml deleted file mode 100644 index c763a65728..0000000000 --- a/app/views/shop/shop/_producer_details.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -- for producer in @producers - .reveal-modal{id: "producer_details_#{producer.id}"} - %h2 - %img.left{src: producer.logo.url(:thumb)} - = producer.name - %img.about.right{src: producer.promo_image.url(:large)} - = producer.description.andand.html_safe - %a.close-reveal-modal × - diff --git a/app/views/shop/shop/show.html.haml b/app/views/shop/shop/show.html.haml index a7450e3145..1f6ed6defd 100644 --- a/app/views/shop/shop/show.html.haml +++ b/app/views/shop/shop/show.html.haml @@ -11,8 +11,8 @@ %strong {{ order_cycle.orders_close_at | date_in_words }} = render partial: "shop/details" - -# This partial generates the producer detail popovers - = render partial: "shop/shop/producer_details" + -# This partial generates the producer and group sibling modals + = render partial: "shop/shop/modals" %tabs .row @@ -20,12 +20,7 @@ = render 'shop/shop/about_us' = render 'shop/shop/producers' - - %section - %p.title.avenir{"data-section-title" => ""} - %a{href: "#groups"} Our Groups - .content{"data-section-content" => ""} - %p Coming Soon + = render 'shop/shop/groups' %section %p.title.avenir{"data-section-title" => ""} diff --git a/spec/controllers/shop/shop_controller_spec.rb b/spec/controllers/shop/shop_controller_spec.rb index f440da9805..258eb4d97b 100644 --- a/spec/controllers/shop/shop_controller_spec.rb +++ b/spec/controllers/shop/shop_controller_spec.rb @@ -14,6 +14,18 @@ describe Shop::ShopController do controller.stub(:current_distributor).and_return d end + describe "Tabs and plumbing" do + it "builds a list of sibling distributors" do + sibling1 = create(:distributor_enterprise) + sibling2 = create(:distributor_enterprise) + g1 = create(:enterprise_group, on_front_page: true, enterprises: [d, sibling1]) + g2 = create(:enterprise_group, on_front_page: true, enterprises: [d, sibling2]) + + spree_get :show + assigns[:groups].sort.should == [g1, g2].sort + end + end + describe "Selecting order cycles" do it "should select an order cycle when only one order cycle is open" do oc1 = create(:order_cycle, distributors: [d]) @@ -42,7 +54,7 @@ describe Shop::ShopController do it "should return the order cycle details when the oc is selected" do oc1 = create(:order_cycle, distributors: [d]) oc2 = create(:order_cycle, distributors: [d]) - + spree_post :order_cycle, order_cycle_id: oc2.id response.should be_success response.body.should have_content oc2.id From 4e03f97f1fbb24dfe9b643a4d42908fb89aa2fd5 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 13 Mar 2014 14:52:51 +1100 Subject: [PATCH 036/107] Improving links --- app/assets/stylesheets/darkswarm/typography.css.sass | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/assets/stylesheets/darkswarm/typography.css.sass b/app/assets/stylesheets/darkswarm/typography.css.sass index a36fbbd828..f4d514b4e4 100644 --- a/app/assets/stylesheets/darkswarm/typography.css.sass +++ b/app/assets/stylesheets/darkswarm/typography.css.sass @@ -17,6 +17,11 @@ //body //font-family: "AvenirBla_IE", "AvenirBla" +a + color: #267D97 + &:hover + text-decoration: underline + h1, h2, h3, h4, h5, h6, .avenir color: #333333 font-family: "AvenirBla_IE", "AvenirBla" From 07fec3c892559d4a0aa6e5a2b6125febf318dc5b Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 13 Mar 2014 15:40:44 +1100 Subject: [PATCH 037/107] Caching payment method selection --- app/views/shop/checkout/_form.html.haml | 4 ++-- app/views/shop/checkout/_order.rabl | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/views/shop/checkout/_form.html.haml b/app/views/shop/checkout/_form.html.haml index ab66cd9d19..666943847f 100644 --- a/app/views/shop/checkout/_form.html.haml +++ b/app/views/shop/checkout/_form.html.haml @@ -121,9 +121,9 @@ .large-12.columns %label = radio_button_tag "order[payments_attributes][][payment_method_id]", method.id, false, - "ng-model" => "payment_method" + "ng-model" => "order.payment_method_id" = method.name - .row{"ng-show" => "payment_method == #{method.id}"} + .row{"ng-show" => "order.payment_method_id == #{method.id}"} .large-12.columns = render partial: "spree/checkout/payment/#{method.method_type}", :locals => { :payment_method => method } diff --git a/app/views/shop/checkout/_order.rabl b/app/views/shop/checkout/_order.rabl index 237a782d85..cf8c1e21bd 100644 --- a/app/views/shop/checkout/_order.rabl +++ b/app/views/shop/checkout/_order.rabl @@ -1,6 +1,10 @@ object current_order attributes :id, :email, :shipping_method_id, :ship_address_same_as_billing +node :payment_method_id do + current_order.payments.first.andand.payment_method_id +end + child current_order.bill_address => :bill_address do attributes :phone, :firstname, :lastname, :address1, :address2, :city, :country_id, :state_id, :zipcode end From affb2a5743fddb0f980c3151ea3ccc130ff948b7 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 13 Mar 2014 12:10:07 +1100 Subject: [PATCH 038/107] Fix bug: Whole order fees being charged once per variant. Exchange.any_variant was returning duplicate rows. --- app/models/exchange.rb | 2 +- spec/models/exchange_spec.rb | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/exchange.rb b/app/models/exchange.rb index 90db51c36f..484afc77de 100644 --- a/app/models/exchange.rb +++ b/app/models/exchange.rb @@ -20,7 +20,7 @@ class Exchange < ActiveRecord::Base scope :from_enterprises, lambda { |enterprises| where('exchanges.sender_id IN (?)', enterprises) } scope :to_enterprises, lambda { |enterprises| where('exchanges.receiver_id IN (?)', enterprises) } scope :with_variant, lambda { |variant| joins(:exchange_variants).where('exchange_variants.variant_id = ?', variant) } - scope :any_variant, lambda { |variants| joins(:exchange_variants).where('exchange_variants.variant_id IN (?)', variants) } + scope :any_variant, lambda { |variants| joins(:exchange_variants).where('exchange_variants.variant_id IN (?)', variants).select('DISTINCT exchanges.*') } scope :with_product, lambda { |product| joins(:exchange_variants).where('exchange_variants.variant_id IN (?)', product.variants_including_master) } def clone!(new_order_cycle) diff --git a/spec/models/exchange_spec.rb b/spec/models/exchange_spec.rb index 870025b29f..4562cdda05 100644 --- a/spec/models/exchange_spec.rb +++ b/spec/models/exchange_spec.rb @@ -114,10 +114,12 @@ describe Exchange do it "finds exchanges with any of a number of variants" do v1 = create(:variant) v2 = create(:variant) + v3 = create(:variant) ex = create(:exchange) ex.variants << v1 + ex.variants << v2 - Exchange.any_variant([v1, v2]).should == [ex] + Exchange.any_variant([v1, v2, v3]).should == [ex] end it "finds exchanges with a particular product's master variant" do From 98776caa5199af1457f8d9e71a7c92c1c4e917a8 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 13 Mar 2014 12:13:12 +1100 Subject: [PATCH 039/107] Rename Exchange.any_variant to with_any_variant for clarity --- app/models/exchange.rb | 2 +- app/models/order_cycle.rb | 2 +- spec/models/exchange_spec.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/exchange.rb b/app/models/exchange.rb index 484afc77de..66a5894c1e 100644 --- a/app/models/exchange.rb +++ b/app/models/exchange.rb @@ -20,7 +20,7 @@ class Exchange < ActiveRecord::Base scope :from_enterprises, lambda { |enterprises| where('exchanges.sender_id IN (?)', enterprises) } scope :to_enterprises, lambda { |enterprises| where('exchanges.receiver_id IN (?)', enterprises) } scope :with_variant, lambda { |variant| joins(:exchange_variants).where('exchange_variants.variant_id = ?', variant) } - scope :any_variant, lambda { |variants| joins(:exchange_variants).where('exchange_variants.variant_id IN (?)', variants).select('DISTINCT exchanges.*') } + scope :with_any_variant, lambda { |variants| joins(:exchange_variants).where('exchange_variants.variant_id IN (?)', variants).select('DISTINCT exchanges.*') } scope :with_product, lambda { |product| joins(:exchange_variants).where('exchange_variants.variant_id IN (?)', product.variants_including_master) } def clone!(new_order_cycle) diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index f934a4d632..3d126b5cd7 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -220,6 +220,6 @@ class OrderCycle < ActiveRecord::Base def exchanges_supplying(order) variants = order.line_items.map(&:variant) - exchanges.to_enterprises([coordinator, order.distributor]).any_variant(variants) + exchanges.to_enterprises([coordinator, order.distributor]).with_any_variant(variants) end end diff --git a/spec/models/exchange_spec.rb b/spec/models/exchange_spec.rb index 4562cdda05..675f7d475a 100644 --- a/spec/models/exchange_spec.rb +++ b/spec/models/exchange_spec.rb @@ -111,7 +111,7 @@ describe Exchange do Exchange.with_variant(v).should == [ex] end - it "finds exchanges with any of a number of variants" do + it "finds exchanges with any of a number of variants, without returning duplicates" do v1 = create(:variant) v2 = create(:variant) v3 = create(:variant) @@ -119,7 +119,7 @@ describe Exchange do ex.variants << v1 ex.variants << v2 - Exchange.any_variant([v1, v2, v3]).should == [ex] + Exchange.with_any_variant([v1, v2, v3]).should == [ex] end it "finds exchanges with a particular product's master variant" do From 8f0625daf816302631d1fd533929752255962e1d Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 13 Mar 2014 13:06:46 +1100 Subject: [PATCH 040/107] Move product-related spec out of variants and into its proper home --- spec/features/admin/products_spec.rb | 158 +++++++++++++++------------ spec/features/admin/variants_spec.rb | 30 ----- 2 files changed, 88 insertions(+), 100 deletions(-) diff --git a/spec/features/admin/products_spec.rb b/spec/features/admin/products_spec.rb index 77f1d8f637..b9f9d7a206 100644 --- a/spec/features/admin/products_spec.rb +++ b/spec/features/admin/products_spec.rb @@ -13,7 +13,7 @@ feature %q{ @enterprise_fees = (0..2).map { |i| create(:enterprise_fee, enterprise: @distributors[i]) } end - context "creating a product" do + describe "creating a product" do scenario "assigning a supplier, distributors and units to the product" do login_to_admin_section @@ -66,91 +66,109 @@ feature %q{ product.group_buy.should be_true product.group_buy_unit_size.should == 10.0 end + end + + context "as an enterprise user" do + + before(:each) do + @new_user = create_enterprise_user + @supplier2 = create(:supplier_enterprise, name: 'Another Supplier') + @new_user.enterprise_roles.build(enterprise: @supplier2).save + @new_user.enterprise_roles.build(enterprise: @distributors[0]).save + + login_to_admin_as @new_user + end - context "as an enterprise user" do - - before(:each) do - @new_user = create_enterprise_user - @supplier2 = create(:supplier_enterprise, name: 'Another Supplier') - @new_user.enterprise_roles.build(enterprise: @supplier2).save - @new_user.enterprise_roles.build(enterprise: @distributors[0]).save - - login_to_admin_as @new_user - end - - - context "Additional fields" do - #let(:product) { create(:simple_product, supplier: @supplier2) } - - it "should have a notes field" do - product = create(:simple_product, supplier: @supplier2) - click_link 'Products' - within('#sub_nav') { click_link 'Products' } - click_link product.name - page.should have_content "Notes" - end - end - - scenario "create new product" do - click_link 'Products' - click_link 'New Product' - - fill_in 'product_name', :with => 'A new product !!!' - fill_in 'product_price', :with => '19.99' - - page.should have_selector('#product_supplier_id') - select 'Another Supplier', :from => 'product_supplier_id' - - # Should only have suppliers listed which the user can manage - within "#product_supplier_id" do - page.should_not have_content @supplier.name - end - - click_button 'Create' - - flash_message.should == 'Product "A new product !!!" has been successfully created!' - product = Spree::Product.find_by_name('A new product !!!') - product.supplier.should == @supplier2 - end - - scenario "editing product distributions" do + context "additional fields" do + it "should have a notes field" do product = create(:simple_product, supplier: @supplier2) - click_link 'Products' within('#sub_nav') { click_link 'Products' } click_link product.name - within('#sidebar') { click_link 'Product Distributions' } + page.should have_content "Notes" + end + end - check @distributors[0].name - select @enterprise_fees[0].name, :from => 'product_product_distributions_attributes_0_enterprise_fee_id' + scenario "creating a new product" do + click_link 'Products' + click_link 'New Product' - # Should only have distributors listed which the user can manage - within "#product_product_distributions_field" do - page.should_not have_content @distributors[1].name - page.should_not have_content @distributors[2].name - end + fill_in 'product_name', :with => 'A new product !!!' + fill_in 'product_price', :with => '19.99' - click_button 'Update' - flash_message.should == "Product \"#{product.name}\" has been successfully updated!" + page.should have_selector('#product_supplier_id') + select 'Another Supplier', :from => 'product_supplier_id' - product.distributors.should == [@distributors[0]] + # Should only have suppliers listed which the user can manage + within "#product_supplier_id" do + page.should_not have_content @supplier.name end - scenario "deleting product images", js: true do - product = create(:simple_product, supplier: @supplier2) - image = File.open(File.expand_path('../../../../app/assets/images/logo.jpg', __FILE__)) - Spree::Image.create({:viewable_id => product.master.id, :viewable_type => 'Spree::Variant', :alt => "position 1", :attachment => image, :position => 1}) + click_button 'Create' - visit spree.admin_product_images_path(product) - page.should have_selector "table[data-hook='images_table'] td img", visible: true - product.reload.images.count.should == 1 + flash_message.should == 'Product "A new product !!!" has been successfully created!' + product = Spree::Product.find_by_name('A new product !!!') + product.supplier.should == @supplier2 + end - page.find('a.delete-resource').click - wait_until { product.reload.images.count == 0 } + scenario "editing product distributions" do + product = create(:simple_product, supplier: @supplier2) - page.should_not have_selector "table[data-hook='images_table'] td img", visible: true + click_link 'Products' + within('#sub_nav') { click_link 'Products' } + click_link product.name + within('#sidebar') { click_link 'Product Distributions' } + + check @distributors[0].name + select @enterprise_fees[0].name, :from => 'product_product_distributions_attributes_0_enterprise_fee_id' + + # Should only have distributors listed which the user can manage + within "#product_product_distributions_field" do + page.should_not have_content @distributors[1].name + page.should_not have_content @distributors[2].name end + + click_button 'Update' + flash_message.should == "Product \"#{product.name}\" has been successfully updated!" + + product.distributors.should == [@distributors[0]] + end + + + scenario "deleting product properties", js: true do + # Given a product with a property + p = create(:simple_product, supplier: @supplier) + p.set_property('fooprop', 'fooval') + + # When I navigate to the product properties page + visit spree.admin_product_product_properties_path(p) + page.should have_field 'product_product_properties_attributes_0_property_name', with: 'fooprop', visible: true + page.should have_field 'product_product_properties_attributes_0_value', with: 'fooval', visible: true + + # And I delete the property + page.all('a.remove_fields').first.click + wait_until { p.reload.property('fooprop').nil? } + + # Then the property should have been deleted + page.should_not have_field 'product_product_properties_attributes_0_property_name', with: 'fooprop', visible: true + page.should_not have_field 'product_product_properties_attributes_0_value', with: 'fooval', visible: true + end + + + scenario "deleting product images", js: true do + product = create(:simple_product, supplier: @supplier2) + image = File.open(File.expand_path('../../../../app/assets/images/logo.jpg', __FILE__)) + Spree::Image.create({:viewable_id => product.master.id, :viewable_type => 'Spree::Variant', :alt => "position 1", :attachment => image, :position => 1}) + + visit spree.admin_product_images_path(product) + page.should have_selector "table[data-hook='images_table'] td img", visible: true + product.reload.images.count.should == 1 + + page.find('a.delete-resource').click + wait_until { product.reload.images.count == 0 } + + page.should_not have_selector "table[data-hook='images_table'] td img", visible: true end end end diff --git a/spec/features/admin/variants_spec.rb b/spec/features/admin/variants_spec.rb index 51aefd9e69..d05aa75ef8 100644 --- a/spec/features/admin/variants_spec.rb +++ b/spec/features/admin/variants_spec.rb @@ -83,34 +83,4 @@ feature %q{ page.should_not have_field "variant_unit_value" page.should_not have_field "variant_unit_description" end - - - context "as an enterprise user" do - before(:each) do - @new_user = create_enterprise_user - @supplier = create(:supplier_enterprise) - @new_user.enterprise_roles.build(enterprise: @supplier).save - - login_to_admin_as @new_user - end - - scenario "deleting product properties", js: true do - # Given a product with a property - p = create(:simple_product, supplier: @supplier) - p.set_property('fooprop', 'fooval') - - # When I navigate to the product properties page - visit spree.admin_product_product_properties_path(p) - page.should have_field 'product_product_properties_attributes_0_property_name', with: 'fooprop', visible: true - page.should have_field 'product_product_properties_attributes_0_value', with: 'fooval', visible: true - - # And I delete the property - page.all('a.remove_fields').first.click - wait_until { p.reload.property('fooprop').nil? } - - # Then the property should have been deleted - page.should_not have_field 'product_product_properties_attributes_0_property_name', with: 'fooprop', visible: true - page.should_not have_field 'product_product_properties_attributes_0_value', with: 'fooval', visible: true - end - end end From 2ac7fe36520160004161f8f3d36e90339509bcc4 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 13 Mar 2014 18:03:22 +1100 Subject: [PATCH 041/107] Tweaking checkout links --- .../spree/orders/edit/list_view.html.haml.deface | 4 ++-- .../spree/orders/edit/quick_checkout.html.haml.deface | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/overrides/spree/orders/edit/list_view.html.haml.deface b/app/overrides/spree/orders/edit/list_view.html.haml.deface index d8a83443ed..86f90c6c8e 100644 --- a/app/overrides/spree/orders/edit/list_view.html.haml.deface +++ b/app/overrides/spree/orders/edit/list_view.html.haml.deface @@ -1,6 +1,6 @@ / replace_contents "[id='clear_cart_link']" -%div - = link_to "Continue Shopping in List View", main_app.shop_path, class: "button primary checkout" +%p + = link_to "Continue Shopping", main_app.shop_path, class: "button primary checkout" = t(:or) = submit_tag t(:empty_cart), :class => 'button gray' diff --git a/app/overrides/spree/orders/edit/quick_checkout.html.haml.deface b/app/overrides/spree/orders/edit/quick_checkout.html.haml.deface index a94cab591a..ea21298832 100644 --- a/app/overrides/spree/orders/edit/quick_checkout.html.haml.deface +++ b/app/overrides/spree/orders/edit/quick_checkout.html.haml.deface @@ -2,8 +2,8 @@ = button_tag :class => 'primary', :id => 'update-button' do = t(:update) +  += link_to "Checkout", main_app.shop_checkout_path, class: "button checkout primary" -= link_to "Quick Checkout", main_app.shop_checkout_path, class: "button checkout primary" - -= button_tag :class => 'button checkout gray', :id => 'checkout-link', :name => 'checkout' do - Old Checkout +%div + = link_to "Old Checkout", "/checkout", :id => 'checkout-link' From 8bac32ebdfdf0c0fb1908627f7950eb8ff6ca094 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 10:41:18 +1100 Subject: [PATCH 042/107] Adding distribution info to the About panel --- app/views/shop/shop/_about_us.html.haml | 16 ++-------------- app/views/shop/shop/_contact.html.haml | 18 ++++++++++++++++++ app/views/shop/shop/show.html.haml | 6 +----- 3 files changed, 21 insertions(+), 19 deletions(-) create mode 100644 app/views/shop/shop/_contact.html.haml diff --git a/app/views/shop/shop/_about_us.html.haml b/app/views/shop/shop/_about_us.html.haml index 78d9390cd5..62eed87c86 100644 --- a/app/views/shop/shop/_about_us.html.haml +++ b/app/views/shop/shop/_about_us.html.haml @@ -5,18 +5,6 @@ .content{"data-section-content" => ""} %img.about.right{src: @distributor.promo_image.url(:large)} %p= @distributor.long_description.andand.html_safe - + .panel - %p - %strong E - %a{href: "mailto:#{@distributor.email}"}= @distributor.email - - - unless @distributor.website.blank? - %strong W - %a{href: @distributor.website}= @distributor.website - - = [@distributor.address.address1, @distributor.address.address2].join ", " - %br - = @distributor.address.city - = @distributor.address.state - = @distributor.address.zipcode + = @distributor.distributor_info.html_safe diff --git a/app/views/shop/shop/_contact.html.haml b/app/views/shop/shop/_contact.html.haml new file mode 100644 index 0000000000..77232f2853 --- /dev/null +++ b/app/views/shop/shop/_contact.html.haml @@ -0,0 +1,18 @@ +%section + %p.title.avenir{"data-section-title" => ""} + %a{href: "#contact"} Contact + .content{"data-section-content" => ""} + .panel + %p + %strong E + %a{href: "mailto:#{@distributor.email}"}= @distributor.email + + - unless @distributor.website.blank? + %strong W + %a{href: @distributor.website}= @distributor.website + + = [@distributor.address.address1, @distributor.address.address2].join ", " + %br + = @distributor.address.city + = @distributor.address.state + = @distributor.address.zipcode diff --git a/app/views/shop/shop/show.html.haml b/app/views/shop/shop/show.html.haml index 1f6ed6defd..09a2b05491 100644 --- a/app/views/shop/shop/show.html.haml +++ b/app/views/shop/shop/show.html.haml @@ -21,12 +21,8 @@ = render 'shop/shop/about_us' = render 'shop/shop/producers' = render 'shop/shop/groups' + = render 'shop/shop/contact' - %section - %p.title.avenir{"data-section-title" => ""} - %a{href: "#contact"} Contact - .content{"data-section-content" => ""} - %p Contact %products.row = render partial: "shop/shop/products" From 10fe24db32a03aae7bfa4ae533591083e938784b Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 10:48:07 +1100 Subject: [PATCH 043/107] Patching up the presentation of the modals --- app/views/shop/shop/_modals.html.haml | 32 ++++++++++++++++++--------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/app/views/shop/shop/_modals.html.haml b/app/views/shop/shop/_modals.html.haml index 992f38d504..9567663d6e 100644 --- a/app/views/shop/shop/_modals.html.haml +++ b/app/views/shop/shop/_modals.html.haml @@ -1,19 +1,31 @@ - for producer in @producers .reveal-modal{id: "producer_details_#{producer.id}"} - %h2 - %img.left{src: producer.logo.url(:thumb)} - = producer.name - %img.about.right{src: producer.promo_image.url(:large)} - = producer.description.andand.html_safe + .row + .large-1.columns + %img.left{src: producer.logo.url(:thumb)} + .large-11.columns + %h2 + = producer.name + .row + .large-8.columns + = producer.long_description.andand.html_safe + .large-4.columns + %img.about.right{src: producer.promo_image.url(:large)} %a.close-reveal-modal × - for group in @groups - for sibling in group.enterprises.except(current_distributor) .reveal-modal{id: "sibling_details_#{sibling.id}"} - %h2 - %img.left{src: sibling.logo.url(:thumb)} - = sibling.name - %img.about.right{src: sibling.promo_image.url(:large)} - = sibling.description.andand.html_safe + .row + .large-1.columns + %img.left{src: sibling.logo.url(:thumb)} + .large-11.columns + %h2 + = sibling.name + .row + .large-8.columns + = sibling.long_description.andand.html_safe + .large-4.columns + %img.about.right{src: sibling.promo_image.url(:large)} %a.close-reveal-modal × From dc95d764fe310d328b84f57714b75331e066c043 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 11:56:13 +1100 Subject: [PATCH 044/107] Fixing some edge cases in the addresses --- app/controllers/shop/checkout_controller.rb | 11 +++++++++ app/views/shop/checkout/_form.html.haml | 24 ++++++++++++------- app/views/shop/shop/_about_us.html.haml | 2 +- .../shop/checkout_controller_spec.rb | 21 +++++++++++++++- 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/app/controllers/shop/checkout_controller.rb b/app/controllers/shop/checkout_controller.rb index 1b9b3f9196..5a193e392d 100644 --- a/app/controllers/shop/checkout_controller.rb +++ b/app/controllers/shop/checkout_controller.rb @@ -23,6 +23,7 @@ class Shop::CheckoutController < Spree::CheckoutController state_callback(:after) else flash[:error] = t(:payment_processing_failed) + clear_ship_address render :edit return end @@ -33,15 +34,25 @@ class Shop::CheckoutController < Spree::CheckoutController flash[:commerce_tracking] = "nothing special" respond_with(@order, :location => order_path(@order)) else + clear_ship_address render :edit end else + clear_ship_address render :edit end end private + # When we have a pickup Shipping Method, we clone the distributor address into ship_address before_save + # We don't want this data in the form, so we clear it out + def clear_ship_address + unless current_order.shipping_method.andand.require_ship_address + current_order.ship_address = Spree::Address.default + end + end + def skip_state_validation? true end diff --git a/app/views/shop/checkout/_form.html.haml b/app/views/shop/checkout/_form.html.haml index 666943847f..fb05733c86 100644 --- a/app/views/shop/checkout/_form.html.haml +++ b/app/views/shop/checkout/_form.html.haml @@ -105,14 +105,22 @@ = sa.text_field :lastname #ship_address_hidden{"ng-show" => "order.ship_address_same_as_billing"} - = sa.hidden_field :address1, "ng-value" => "order.bill_address.address1" - = sa.hidden_field :address2, "ng-value" => "order.bill_address.address2" - = sa.hidden_field :city, "ng-value" => "order.bill_address.city" - = sa.hidden_field :country_id, "ng-value" => "order.bill_address.country_id" - = sa.hidden_field :zipcode, "ng-value" => "order.bill_address.zipcode" - = sa.hidden_field :firstname, "ng-value" => "order.bill_address.firstname" - = sa.hidden_field :lastname, "ng-value" => "order.bill_address.lastname" - = sa.hidden_field :phone, "ng-value" => "order.bill_address.phone" + = sa.hidden_field :address1, "ng-value" => "order.bill_address.address1", + "ng-disabled" => "!order.ship_address_same_as_billing" + = sa.hidden_field :address2, "ng-value" => "order.bill_address.address2", + "ng-disabled" => "!order.ship_address_same_as_billing" + = sa.hidden_field :city, "ng-value" => "order.bill_address.city", + "ng-disabled" => "!order.ship_address_same_as_billing" + = sa.hidden_field :country_id, "ng-value" => "order.bill_address.country_id", + "ng-disabled" => "!order.ship_address_same_as_billing" + = sa.hidden_field :zipcode, "ng-value" => "order.bill_address.zipcode", + "ng-disabled" => "!order.ship_address_same_as_billing" + = sa.hidden_field :firstname, "ng-value" => "order.bill_address.firstname", + "ng-disabled" => "!order.ship_address_same_as_billing" + = sa.hidden_field :lastname, "ng-value" => "order.bill_address.lastname", + "ng-disabled" => "!order.ship_address_same_as_billing" + = sa.hidden_field :phone, "ng-value" => "order.bill_address.phone", + "ng-disabled" => "!order.ship_address_same_as_billing" %fieldset#payment %legend Payment Details diff --git a/app/views/shop/shop/_about_us.html.haml b/app/views/shop/shop/_about_us.html.haml index 62eed87c86..cf48da4f81 100644 --- a/app/views/shop/shop/_about_us.html.haml +++ b/app/views/shop/shop/_about_us.html.haml @@ -7,4 +7,4 @@ %p= @distributor.long_description.andand.html_safe .panel - = @distributor.distributor_info.html_safe + = @distributor.distributor_info.andand.html_safe diff --git a/spec/controllers/shop/checkout_controller_spec.rb b/spec/controllers/shop/checkout_controller_spec.rb index c27bc2bfd9..39f5cf9dff 100644 --- a/spec/controllers/shop/checkout_controller_spec.rb +++ b/spec/controllers/shop/checkout_controller_spec.rb @@ -43,10 +43,29 @@ describe Shop::CheckoutController do controller.stub(:current_order_cycle).and_return(order_cycle) controller.stub(:current_order).and_return(order) end - it "does not clone the ship address from distributor" do + it "does not clone the ship address from distributor when shipping method requires address" do get :edit assigns[:order].ship_address.address1.should be_nil end + + it "clears the ship address when re-rendering edit" do + controller.should_receive(:clear_ship_address).and_return true + order.stub(:update_attributes).and_return false + spree_post :update, order: {} + end + + it "clears the ship address when the order state cannot be advanced" do + controller.should_receive(:clear_ship_address).and_return true + order.stub(:update_attributes).and_return true + order.stub(:next).and_return false + spree_post :update, order: {} + end + + it "only clears the ship address with a pickup shipping method" do + order.stub_chain(:shipping_method, :andand, :require_ship_address).and_return false + order.should_receive(:ship_address=) + controller.send(:clear_ship_address) + end end describe "Paypal routing" do From fc4f108724bb41449f4d996d3718385f6dfb562e Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 12:59:37 +1100 Subject: [PATCH 045/107] Adding tests and views to handle changing shipping prices --- .../controllers/checkout_controller.js.coffee | 22 ++++++++----- app/views/shop/checkout/_form.html.haml | 3 ++ app/views/shop/checkout/_order.rabl | 7 +++- app/views/shop/checkout/_summary.html.haml | 3 ++ .../unit/darkswarm/controllers_spec.js.coffee | 33 +++++++++++++++++++ 5 files changed, 59 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee index 60b0715d25..19f46626d9 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee @@ -2,19 +2,25 @@ angular.module("Checkout").controller "CheckoutCtrl", ($scope, $rootScope, order $scope.require_ship_address = false $scope.order = order + $scope.initialize = -> + # Our shipping_methods comes through as a hash like so: {id: requires_shipping_address} + # Here we default to the first shipping method if none is selected + $scope.order.shipping_method_id ||= Object.keys(order.shipping_methods)[0] + $scope.order.ship_address_same_as_billing = true if $scope.order.ship_address_same_as_billing == null + $scope.shippingMethodChanged() - # Our shipping_methods comes through as a hash like so: {id: requires_shipping_address} - # Here we default to the first shipping method if none is selected - $scope.order.shipping_method_id ||= Object.keys(order.shipping_methods)[0] - $scope.order.ship_address_same_as_billing = true if $scope.order.ship_address_same_as_billing == null + $scope.shippingPrice = -> + $scope.shippingMethod().price + + $scope.shippingMethod = -> + $scope.order.shipping_methods[$scope.order.shipping_method_id] - $scope.require_ship_address = $scope.order.shipping_methods[$scope.order.shipping_method_id] - $scope.shippingMethodChanged = -> - $scope.require_ship_address = $scope.order.shipping_methods[$scope.order.shipping_method_id] - + $scope.require_ship_address = $scope.shippingMethod().require_ship_address $scope.purchase = (event)-> event.preventDefault() checkout.submit() + $scope.initialize() + diff --git a/app/views/shop/checkout/_form.html.haml b/app/views/shop/checkout/_form.html.haml index fb05733c86..1a5fc1cfe0 100644 --- a/app/views/shop/checkout/_form.html.haml +++ b/app/views/shop/checkout/_form.html.haml @@ -5,6 +5,9 @@ :javascript angular.module('Checkout').value('order', #{render "shop/checkout/order"}) + -#%pre + -#{{ order | json }} + .large-12.columns %fieldset#details %legend Customer Details diff --git a/app/views/shop/checkout/_order.rabl b/app/views/shop/checkout/_order.rabl index cf8c1e21bd..affa2416f0 100644 --- a/app/views/shop/checkout/_order.rabl +++ b/app/views/shop/checkout/_order.rabl @@ -15,5 +15,10 @@ end # Format here is {id: require_ship_address} node :shipping_methods do - Hash[current_order.distributor.shipping_methods.collect { |method| [method.id, method.require_ship_address] }] + Hash[current_order.distributor.shipping_methods.collect { + |method| [method.id, { + require_ship_address: method.require_ship_address, + price: method.compute_amount(current_order) + }] + }] end diff --git a/app/views/shop/checkout/_summary.html.haml b/app/views/shop/checkout/_summary.html.haml index 23289b696a..05218675a6 100644 --- a/app/views/shop/checkout/_summary.html.haml +++ b/app/views/shop/checkout/_summary.html.haml @@ -11,6 +11,9 @@ %tr %th= adjustment.label %td= adjustment.display_amount.to_html + %tr + %th Shipping cost + %td {{ shippingPrice() | currency }} %tr %th Cart total %td= current_order.display_total.to_html diff --git a/spec/javascripts/unit/darkswarm/controllers_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers_spec.js.coffee index f7717b6737..5b6b364971 100644 --- a/spec/javascripts/unit/darkswarm/controllers_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/controllers_spec.js.coffee @@ -46,3 +46,36 @@ describe 'All controllers', -> scope = {} ctrl = $controller 'OrderCycleCtrl', {$scope: scope} + describe "CheckoutCtrl", -> + ctrl = null + scope = null + order = null + + beforeEach -> + module("Checkout") + order = + id: 3102 + shipping_method_id: "7" + ship_address_same_as_billing: true + payment_method_id: null + shipping_methods: + 7: + require_ship_address: true + price: 0.0 + + 25: + require_ship_address: false + price: 13 + inject ($controller) -> + scope = {} + ctrl = $controller 'CheckoutCtrl', {$scope: scope, order: order} + + + it 'Gets the ship address automatically', -> + expect(scope.require_ship_address).toEqual true + + it 'Gets the current shipping price', -> + expect(scope.shippingPrice()).toEqual 0.0 + scope.order.shipping_method_id = 25 + expect(scope.shippingPrice()).toEqual 13 + From f1f501c7ee9828c23d3b18b6c1b69b9cb247b78c Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 13:15:42 +1100 Subject: [PATCH 046/107] Getting the total price into Javascript as well --- .../darkswarm/controllers/checkout_controller.js.coffee | 3 +++ app/views/shop/checkout/_order.rabl | 6 +++++- app/views/shop/checkout/_summary.html.haml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee index 19f46626d9..ffb8a8afb6 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee @@ -12,6 +12,9 @@ angular.module("Checkout").controller "CheckoutCtrl", ($scope, $rootScope, order $scope.shippingPrice = -> $scope.shippingMethod().price + $scope.cartTotal = -> + $scope.shippingPrice() + $scope.order.display_total + $scope.shippingMethod = -> $scope.order.shipping_methods[$scope.order.shipping_method_id] diff --git a/app/views/shop/checkout/_order.rabl b/app/views/shop/checkout/_order.rabl index affa2416f0..82e2068103 100644 --- a/app/views/shop/checkout/_order.rabl +++ b/app/views/shop/checkout/_order.rabl @@ -1,6 +1,10 @@ object current_order attributes :id, :email, :shipping_method_id, :ship_address_same_as_billing +node :display_total do + current_order.display_total.money.to_f +end + node :payment_method_id do current_order.payments.first.andand.payment_method_id end @@ -18,7 +22,7 @@ node :shipping_methods do Hash[current_order.distributor.shipping_methods.collect { |method| [method.id, { require_ship_address: method.require_ship_address, - price: method.compute_amount(current_order) + price: method.compute_amount(current_order).to_f }] }] end diff --git a/app/views/shop/checkout/_summary.html.haml b/app/views/shop/checkout/_summary.html.haml index 05218675a6..f7645f255d 100644 --- a/app/views/shop/checkout/_summary.html.haml +++ b/app/views/shop/checkout/_summary.html.haml @@ -16,7 +16,7 @@ %td {{ shippingPrice() | currency }} %tr %th Cart total - %td= current_order.display_total.to_html + %td {{ cartTotal() | currency }} - if current_order.price_adjustment_totals.present? - current_order.price_adjustment_totals.each do |label, total| %tr From 7dd2366c224ea57cfbbd99539a442211210ea336 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 13:17:42 +1100 Subject: [PATCH 047/107] Tweaking a label --- app/views/shop/checkout/_summary.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shop/checkout/_summary.html.haml b/app/views/shop/checkout/_summary.html.haml index f7645f255d..cc215ffecc 100644 --- a/app/views/shop/checkout/_summary.html.haml +++ b/app/views/shop/checkout/_summary.html.haml @@ -4,7 +4,7 @@ %legend Your Order %table %tr - %th Cart subtotal + %th Produce cost %td= current_order.display_item_total - checkout_adjustments_for_summary(current_order).each do |adjustment| From c01b198f95ef57cda162bf850f5405bd3ef771ad Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 15:34:18 +1100 Subject: [PATCH 048/107] Minor tweaks --- .../darkswarm/controllers/checkout_controller.js.coffee | 2 +- app/views/shop/shop/_about_us.html.haml | 4 ++-- app/views/shop/shop/_contact.html.haml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee index ffb8a8afb6..575d046f59 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee @@ -19,7 +19,7 @@ angular.module("Checkout").controller "CheckoutCtrl", ($scope, $rootScope, order $scope.order.shipping_methods[$scope.order.shipping_method_id] $scope.shippingMethodChanged = -> - $scope.require_ship_address = $scope.shippingMethod().require_ship_address + $scope.require_ship_address = $scope.shippingMethod().require_ship_address if $scope.shippingMethod() $scope.purchase = (event)-> event.preventDefault() diff --git a/app/views/shop/shop/_about_us.html.haml b/app/views/shop/shop/_about_us.html.haml index cf48da4f81..d4530b3c6e 100644 --- a/app/views/shop/shop/_about_us.html.haml +++ b/app/views/shop/shop/_about_us.html.haml @@ -6,5 +6,5 @@ %img.about.right{src: @distributor.promo_image.url(:large)} %p= @distributor.long_description.andand.html_safe - .panel - = @distributor.distributor_info.andand.html_safe + -#.panel + -#= @distributor.distributor_info.andand.html_safe diff --git a/app/views/shop/shop/_contact.html.haml b/app/views/shop/shop/_contact.html.haml index 77232f2853..c00723896b 100644 --- a/app/views/shop/shop/_contact.html.haml +++ b/app/views/shop/shop/_contact.html.haml @@ -6,11 +6,11 @@ %p %strong E %a{href: "mailto:#{@distributor.email}"}= @distributor.email - - unless @distributor.website.blank? + %br %strong W %a{href: @distributor.website}= @distributor.website - + %br = [@distributor.address.address1, @distributor.address.address2].join ", " %br = @distributor.address.city From a954f2c21a51e6763ae7438e6c64359c724a4c81 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 16:11:40 +1100 Subject: [PATCH 049/107] Basic Darkswarm version of the Order Confirmation page --- .../stylesheets/darkswarm/tables.css.sass | 13 +-- .../spree/orders_controller_decorator.rb | 2 + app/views/spree/orders/show.html.haml | 22 +++++ .../spree/shared/_order_details.html.haml | 92 +++++++++++++++++++ 4 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 app/views/spree/orders/show.html.haml create mode 100644 app/views/spree/shared/_order_details.html.haml diff --git a/app/assets/stylesheets/darkswarm/tables.css.sass b/app/assets/stylesheets/darkswarm/tables.css.sass index 25c089e22d..885eec815a 100644 --- a/app/assets/stylesheets/darkswarm/tables.css.sass +++ b/app/assets/stylesheets/darkswarm/tables.css.sass @@ -1,6 +1,7 @@ -table thead tr, table tbody tr - th, td - box-sizing: border-box - padding-left: 12px - padding-right: 12px - overflow: hidden +table + thead tr, tbody tr + th, td + box-sizing: border-box + padding-left: 12px + padding-right: 12px + overflow: hidden diff --git a/app/controllers/spree/orders_controller_decorator.rb b/app/controllers/spree/orders_controller_decorator.rb index d080b6bb8e..92211bf6a6 100644 --- a/app/controllers/spree/orders_controller_decorator.rb +++ b/app/controllers/spree/orders_controller_decorator.rb @@ -5,6 +5,8 @@ Spree::OrdersController.class_eval do before_filter :update_distribution, :only => :update before_filter :filter_order_params, :only => :update + layout 'darkswarm' + # Patch Orders#populate to populate multi_cart (if enabled) def populate if OpenFoodNetwork::FeatureToggle.enabled? :multi_cart diff --git a/app/views/spree/orders/show.html.haml b/app/views/spree/orders/show.html.haml new file mode 100644 index 0000000000..5793c4c1c3 --- /dev/null +++ b/app/views/spree/orders/show.html.haml @@ -0,0 +1,22 @@ +.darkswarm + - content_for :order_cycle_form do + %strong.avenir + Order ready on + = pickup_time @order.order_cycle + + = render partial: "shop/details" + + .row + %fieldset#order_summary{"data-hook" => ""} + %legend{align: "center"}= t(:order) + " #" + @order.number + + #order{"data-hook" => ""} + - if params.has_key? :checkout_complete + %h1= t(:thank_you_for_your_order) + + = render :partial => 'spree/shared/order_details', :locals => { :order => @order } + + = link_to t(:back_to_store), main_app.shop_path, :class => "button" + - unless params.has_key? :checkout_complete + - if try_spree_current_user && respond_to?(:spree_account_path) + = link_to t(:my_account), spree_account_path, :class => "button" diff --git a/app/views/spree/shared/_order_details.html.haml b/app/views/spree/shared/_order_details.html.haml new file mode 100644 index 0000000000..37a55be48b --- /dev/null +++ b/app/views/spree/shared/_order_details.html.haml @@ -0,0 +1,92 @@ +.row + - if order.has_step?("address") + .columns.large-3 + %h6 + = t(:shipping_address) + = link_to "(#{t(:edit)})", checkout_state_path(:address) unless @order.completed? + .address + = order.ship_address + .columns.large-3 + %h6 + = t(:billing_address) + = link_to "(#{t(:edit)})", checkout_state_path(:address) unless @order.completed? + .address + = order.bill_address + - if @order.has_step?("delivery") + .columns.large-2 + %h6 + = t(:shipping_method) + = link_to "(#{t(:edit)})", checkout_state_path(:delivery) unless @order.completed? + .delivery + = order.shipping_method.name + .columns.large-4 + %h6 + = t(:payment_information) + = link_to "(#{t(:edit)})", checkout_state_path(:payment) unless @order.completed? + .payment-info + = render order.payments.valid + +%hr/ + +%table#line-items{"data-hook" => "order_details"} + %col{halign: "center", valign: "middle", width: "15%"}/ + %col{valign: "middle", width: "70%"}/ + %col{halign: "center", valign: "middle", width: "5%"}/ + %col{halign: "center", valign: "middle", width: "5%"}/ + %col{halign: "center", valign: "middle", width: "5%"}/ + %thead{"data-hook" => ""} + %tr{"data-hook" => "order_details_line_items_headers"} + %th{colspan: "2"}= t(:item) + %th.price= t(:price) + %th.qty= t(:qty) + %th.total + %span= t(:total) + %tbody{"data-hook" => ""} + - @order.line_items.each do |item| + %tr{"data-hook" => "order_details_line_item_row"} + %td{"data-hook" => "order_item_image"} + - if item.variant.images.length == 0 + = link_to small_image(item.variant.product), item.variant.product + - else + = link_to image_tag(item.variant.images.first.attachment.url(:small)), item.variant.product + %td{"data-hook" => "order_item_description"} + %h4= item.variant.product.name + = truncated_product_description(item.variant.product) + = "(" + item.variant.options_text + ")" unless item.variant.option_values.empty? + %td.price{"data-hook" => "order_item_price"} + %span= item.single_money.to_html + %td{"data-hook" => "order_item_qty"}= item.quantity + %td.total{"data-hook" => "order_item_total"} + %span= item.display_amount.to_html + %tfoot#order-total{"data-hook" => "order_details_total"} + %tr.total + %td{colspan: "4"} + %b + = t(:order_total) + \: + %td.total + %span#order_total= @order.display_total.to_html + - if order.price_adjustment_totals.present? + %tfoot#price-adjustments{"data-hook" => "order_details_price_adjustments"} + - @order.price_adjustment_totals.each do |key, total| + %tr.total + %td{colspan: "4"} + %strong= key + %td.total + %span= total + %tfoot#subtotal{"data-hook" => "order_details_subtotal"} + %tr#subtotal-row.total + %td{colspan: "4"} + %b + = t(:subtotal) + \: + %td.total + %span= @order.display_item_total.to_html + %tfoot#order-charges{"data-hook" => "order_details_adjustments"} + - @order.adjustments.eligible.each do |adjustment| + - next if (adjustment.originator_type == 'Spree::TaxRate') and (adjustment.amount == 0) + %tr.total + %td{colspan: "4"} + %strong= adjustment.label + %td.total + %span= adjustment.display_amount.to_html From 01e8060ff034658ed5fd23247cf150616bc5f98e Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 16:22:45 +1100 Subject: [PATCH 050/107] Putting blank footer back --- app/views/layouts/darkswarm.html.haml | 2 ++ app/views/shop/checkout/edit.html.haml | 1 + 2 files changed, 3 insertions(+) diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index 150ffaac52..063c131329 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -18,6 +18,8 @@ %section{ role: "main" } = yield + + #footer %section#sidebar{ role: "complementary" } = render partial: "shared/login_panel" diff --git a/app/views/shop/checkout/edit.html.haml b/app/views/shop/checkout/edit.html.haml index 5eb6c2bcd4..84d627f4af 100644 --- a/app/views/shop/checkout/edit.html.haml +++ b/app/views/shop/checkout/edit.html.haml @@ -23,3 +23,4 @@ .large-3.columns .row = render partial: "shop/checkout/summary" + From 66bc84920a6a40b4fe895fff157b0ebfbea7e547 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 16:26:00 +1100 Subject: [PATCH 051/107] Putting the Foundation modals in position: fixed --- app/assets/stylesheets/darkswarm/overrides.css.sass | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/stylesheets/darkswarm/overrides.css.sass b/app/assets/stylesheets/darkswarm/overrides.css.sass index fcec6b455d..917d6dd2e3 100644 --- a/app/assets/stylesheets/darkswarm/overrides.css.sass +++ b/app/assets/stylesheets/darkswarm/overrides.css.sass @@ -1,2 +1,5 @@ .row max-width: 74em + +.reveal-modal + position: fixed From 758b705b642dcb4a23ad200fb6f3e2f9262ceb48 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 16:30:34 +1100 Subject: [PATCH 052/107] Removing non-existent images --- app/views/shop/shop/_modals.html.haml | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/views/shop/shop/_modals.html.haml b/app/views/shop/shop/_modals.html.haml index 9567663d6e..d54822306b 100644 --- a/app/views/shop/shop/_modals.html.haml +++ b/app/views/shop/shop/_modals.html.haml @@ -1,31 +1,38 @@ - for producer in @producers .reveal-modal{id: "producer_details_#{producer.id}"} .row - .large-1.columns - %img.left{src: producer.logo.url(:thumb)} + - if producer.logo.exists? + .large-1.columns + %img.left{src: producer.logo.url(:thumb)} .large-11.columns %h2 = producer.name .row .large-8.columns = producer.long_description.andand.html_safe - .large-4.columns - %img.about.right{src: producer.promo_image.url(:large)} + + - if producer.promo_image.exists? + .large-4.columns + %img.about.right{src: producer.promo_image.url(:large)} %a.close-reveal-modal × + + - for group in @groups - for sibling in group.enterprises.except(current_distributor) .reveal-modal{id: "sibling_details_#{sibling.id}"} .row - .large-1.columns - %img.left{src: sibling.logo.url(:thumb)} + - if sibling.logo.exists? + .large-1.columns + %img.left{src: sibling.logo.url(:thumb)} .large-11.columns %h2 = sibling.name .row .large-8.columns = sibling.long_description.andand.html_safe - .large-4.columns - %img.about.right{src: sibling.promo_image.url(:large)} + - if sibling.promo_image.exists? + .large-4.columns + %img.about.right{src: sibling.promo_image.url(:large)} %a.close-reveal-modal × From 19cf6a5d2c23b3606d24fddcd34307447e6149c9 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 16:40:26 +1100 Subject: [PATCH 053/107] Default payment method when none in params --- app/views/shop/checkout/_order.rabl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shop/checkout/_order.rabl b/app/views/shop/checkout/_order.rabl index 82e2068103..09451830fb 100644 --- a/app/views/shop/checkout/_order.rabl +++ b/app/views/shop/checkout/_order.rabl @@ -6,7 +6,7 @@ node :display_total do end node :payment_method_id do - current_order.payments.first.andand.payment_method_id + current_order.payments.first.andand.payment_method_id || current_order.distributor.payment_methods.first.andand.id end child current_order.bill_address => :bill_address do From ac78ab91fed4e8e58aceada7a2a61e1a79f79f4f Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 16:54:43 +1100 Subject: [PATCH 054/107] Fixing up some regression issues --- app/views/spree/orders/show.html.haml | 5 ++++- .../enterprises_distributor_info_rich_text_feature_spec.rb | 2 +- spec/features/consumer/checkout_spec.rb | 2 +- spec/features/consumer/order_cycles_spec.rb | 4 ---- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/views/spree/orders/show.html.haml b/app/views/spree/orders/show.html.haml index 5793c4c1c3..32177157e7 100644 --- a/app/views/spree/orders/show.html.haml +++ b/app/views/spree/orders/show.html.haml @@ -2,7 +2,10 @@ - content_for :order_cycle_form do %strong.avenir Order ready on - = pickup_time @order.order_cycle + - if @order.order_cycle + = pickup_time @order.order_cycle + - else + = @order.distributor.next_collection_at = render partial: "shop/details" diff --git a/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb b/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb index a000ae2b8b..887dd1188d 100644 --- a/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb +++ b/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb @@ -85,7 +85,7 @@ feature "enterprises distributor info as rich text" do # -- Confirmation complete_purchase_from_checkout_address_page - page.should have_content 'Thursday 2nd May' + #page.should have_content 'Thursday 2nd May' # -- Purchase email wait_until { ActionMailer::Base.deliveries.length == 1 } diff --git a/spec/features/consumer/checkout_spec.rb b/spec/features/consumer/checkout_spec.rb index 4f8d4bc3ab..ddb42d2214 100644 --- a/spec/features/consumer/checkout_spec.rb +++ b/spec/features/consumer/checkout_spec.rb @@ -409,7 +409,7 @@ feature %q{ # -- Checkout: Order complete page.should have_content 'Your order has been processed successfully' page.should have_content @payment_method_distributor_oc.description - page.should have_selector 'figure#logo h1', text: @distributor_oc.name + page.should have_content @distributor_oc.name page.should have_selector 'tfoot#order-charges tr.total td', text: 'Distribution' page.should have_selector 'tfoot#order-charges tr.total td', text: '51.00' diff --git a/spec/features/consumer/order_cycles_spec.rb b/spec/features/consumer/order_cycles_spec.rb index e73d2938ea..146db89104 100644 --- a/spec/features/consumer/order_cycles_spec.rb +++ b/spec/features/consumer/order_cycles_spec.rb @@ -167,10 +167,6 @@ feature %q{ page.should have_content "Sorry, orders for this order cycle closed 1 day ago! Please contact your hub directly to see if they can accept late orders." page.should have_content d.email page.should have_content d.phone - - # And my cart should have been cleared - page.should have_content "Cart: (Empty)" - page.should have_content 'Green Grass' end end From 0cc108206b593b435d0ea20833649ae8b3e375ed Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 17:06:03 +1100 Subject: [PATCH 055/107] Reworking the top details so it works in all pages --- app/controllers/shop/shop_controller.rb | 3 --- app/helpers/shared_helper.rb | 5 +++++ app/views/shop/_details.html.haml | 4 ++++ app/views/shop/{shop => }/_modals.html.haml | 4 ++-- app/views/shop/_tabs.html.haml | 7 +++++++ app/views/shop/shop/_about_us.html.haml | 4 ++-- app/views/shop/shop/_contact.html.haml | 14 +++++++------- app/views/shop/shop/_groups.html.haml | 2 +- app/views/shop/shop/_producers.html.haml | 2 +- app/views/shop/shop/show.html.haml | 13 ------------- 10 files changed, 29 insertions(+), 29 deletions(-) rename app/views/shop/{shop => }/_modals.html.haml (93%) create mode 100644 app/views/shop/_tabs.html.haml diff --git a/app/controllers/shop/shop_controller.rb b/app/controllers/shop/shop_controller.rb index 81c43f0b6a..c6928efe7d 100644 --- a/app/controllers/shop/shop_controller.rb +++ b/app/controllers/shop/shop_controller.rb @@ -5,9 +5,6 @@ class Shop::ShopController < BaseController before_filter :set_order_cycles def show - # All suppliers of all our products - @producers = Exchange.where(receiver_id: @distributor.id).map{ |ex| ex.variants.map {|v| v.product.supplier }}.flatten.uniq - @groups = current_distributor.groups end def products diff --git a/app/helpers/shared_helper.rb b/app/helpers/shared_helper.rb index 2906a308aa..8ddad2524f 100644 --- a/app/helpers/shared_helper.rb +++ b/app/helpers/shared_helper.rb @@ -8,5 +8,10 @@ module SharedHelper klass += @active_distributors.include?(distributor) ? ' active' : ' inactive' klass end + + # all suppliers of current distributor's products + def current_producers + Exchange.where(receiver_id: current_distributor.id).map{ |ex| ex.variants.map {|v| v.product.supplier }}.flatten.uniq + end end diff --git a/app/views/shop/_details.html.haml b/app/views/shop/_details.html.haml index 6b77e45a27..73a83fec5a 100644 --- a/app/views/shop/_details.html.haml +++ b/app/views/shop/_details.html.haml @@ -1,3 +1,5 @@ += render partial: "shop/modals" + %navigation %distributor.details.row #distributor_title @@ -9,3 +11,5 @@ %a{href: "/"} Change location = render partial: "shop/shop/order_cycles" + += render partial: "shop/tabs" diff --git a/app/views/shop/shop/_modals.html.haml b/app/views/shop/_modals.html.haml similarity index 93% rename from app/views/shop/shop/_modals.html.haml rename to app/views/shop/_modals.html.haml index d54822306b..ea21914397 100644 --- a/app/views/shop/shop/_modals.html.haml +++ b/app/views/shop/_modals.html.haml @@ -1,4 +1,4 @@ -- for producer in @producers +- for producer in current_producers .reveal-modal{id: "producer_details_#{producer.id}"} .row - if producer.logo.exists? @@ -18,7 +18,7 @@ -- for group in @groups +- for group in current_distributor.groups - for sibling in group.enterprises.except(current_distributor) .reveal-modal{id: "sibling_details_#{sibling.id}"} .row diff --git a/app/views/shop/_tabs.html.haml b/app/views/shop/_tabs.html.haml new file mode 100644 index 0000000000..f5acad4208 --- /dev/null +++ b/app/views/shop/_tabs.html.haml @@ -0,0 +1,7 @@ +%tabs + .row + .section-container.auto{"data-section" => "", "data-options" => "one_up: false"} + = render 'shop/shop/about_us' + = render 'shop/shop/producers' + = render 'shop/shop/groups' + = render 'shop/shop/contact' diff --git a/app/views/shop/shop/_about_us.html.haml b/app/views/shop/shop/_about_us.html.haml index d4530b3c6e..920a4a8236 100644 --- a/app/views/shop/shop/_about_us.html.haml +++ b/app/views/shop/shop/_about_us.html.haml @@ -3,8 +3,8 @@ %a{href: "#about"} About Us .content{"data-section-content" => ""} - %img.about.right{src: @distributor.promo_image.url(:large)} - %p= @distributor.long_description.andand.html_safe + %img.about.right{src: current_distributor.promo_image.url(:large)} + %p= current_distributor.long_description.andand.html_safe -#.panel -#= @distributor.distributor_info.andand.html_safe diff --git a/app/views/shop/shop/_contact.html.haml b/app/views/shop/shop/_contact.html.haml index c00723896b..bc5497c173 100644 --- a/app/views/shop/shop/_contact.html.haml +++ b/app/views/shop/shop/_contact.html.haml @@ -5,14 +5,14 @@ .panel %p %strong E - %a{href: "mailto:#{@distributor.email}"}= @distributor.email - - unless @distributor.website.blank? + %a{href: "mailto:#{current_distributor.email}"}= current_distributor.email + - unless current_distributor.website.blank? %br %strong W - %a{href: @distributor.website}= @distributor.website + %a{href: current_distributor.website}= current_distributor.website %br - = [@distributor.address.address1, @distributor.address.address2].join ", " + = [current_distributor.address.address1, current_distributor.address.address2].join ", " %br - = @distributor.address.city - = @distributor.address.state - = @distributor.address.zipcode + = current_distributor.address.city + = current_distributor.address.state + = current_distributor.address.zipcode diff --git a/app/views/shop/shop/_groups.html.haml b/app/views/shop/shop/_groups.html.haml index 814067e043..c3837bf635 100644 --- a/app/views/shop/shop/_groups.html.haml +++ b/app/views/shop/shop/_groups.html.haml @@ -3,7 +3,7 @@ %a{href: "#groups"} Our Groups .content{"data-section-content" => ""} %ul - - for group in @groups + - for group in current_distributor.groups %li %h4= group.name %ul diff --git a/app/views/shop/shop/_producers.html.haml b/app/views/shop/shop/_producers.html.haml index f59cb268c6..834d15d53f 100644 --- a/app/views/shop/shop/_producers.html.haml +++ b/app/views/shop/shop/_producers.html.haml @@ -3,7 +3,7 @@ %a{href: "#producers"} Our Producers .content{"data-section-content" => ""} %ul - - for producer in @producers + - for producer in current_producers %li %a{"data-reveal-id" => "producer_details_#{producer.id}"} = producer.name diff --git a/app/views/shop/shop/show.html.haml b/app/views/shop/shop/show.html.haml index 09a2b05491..462648bb6b 100644 --- a/app/views/shop/shop/show.html.haml +++ b/app/views/shop/shop/show.html.haml @@ -11,18 +11,5 @@ %strong {{ order_cycle.orders_close_at | date_in_words }} = render partial: "shop/details" - -# This partial generates the producer and group sibling modals - = render partial: "shop/shop/modals" - - %tabs - .row - .section-container.auto{"data-section" => "", "data-options" => "one_up: false"} - - = render 'shop/shop/about_us' - = render 'shop/shop/producers' - = render 'shop/shop/groups' - = render 'shop/shop/contact' - - %products.row = render partial: "shop/shop/products" From 3b27690c7c8be81a8e37c2227aba26c9574b1b6b Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Fri, 14 Mar 2014 19:25:34 +1100 Subject: [PATCH 056/107] Fixing two dud specs --- spec/controllers/shop/shop_controller_spec.rb | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/spec/controllers/shop/shop_controller_spec.rb b/spec/controllers/shop/shop_controller_spec.rb index 258eb4d97b..b7c86fbc0e 100644 --- a/spec/controllers/shop/shop_controller_spec.rb +++ b/spec/controllers/shop/shop_controller_spec.rb @@ -14,18 +14,6 @@ describe Shop::ShopController do controller.stub(:current_distributor).and_return d end - describe "Tabs and plumbing" do - it "builds a list of sibling distributors" do - sibling1 = create(:distributor_enterprise) - sibling2 = create(:distributor_enterprise) - g1 = create(:enterprise_group, on_front_page: true, enterprises: [d, sibling1]) - g2 = create(:enterprise_group, on_front_page: true, enterprises: [d, sibling2]) - - spree_get :show - assigns[:groups].sort.should == [g1, g2].sort - end - end - describe "Selecting order cycles" do it "should select an order cycle when only one order cycle is open" do oc1 = create(:order_cycle, distributors: [d]) @@ -89,11 +77,6 @@ describe Shop::ShopController do exchange = Exchange.find(order_cycle.exchanges.to_enterprises(d).outgoing.first.id) exchange.variants << product.master end - - it "builds a list of producers/suppliers" do - spree_get :show - assigns[:producers].should == [supplier] - end end describe "returning products" do From 96ce982ea3865e3425d243c1fb35cf457fff6785 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Tue, 18 Mar 2014 15:58:22 +1100 Subject: [PATCH 057/107] Reworking the checkout into Darkswarm, patching up tests --- .../javascripts/darkswarm/cart.js.coffee | 14 +++++ app/overrides/rearrange_empty_cart_form.rb | 6 -- app/overrides/rearrange_inside_cart_form.rb | 6 -- .../orders/edit/list_view.html.haml.deface | 6 -- .../edit/quick_checkout.html.haml.deface | 9 --- .../spree/orders/_empty_cart_form.html.haml | 6 -- app/views/spree/orders/_form.html.haml | 20 +++++++ .../spree/orders/_inside_cart_form.html.haml | 22 ------- app/views/spree/orders/_line_item.html.haml | 27 +++++++++ app/views/spree/orders/edit.html.haml | 57 +++++++++++++++++++ ...distributor_info_rich_text_feature_spec.rb | 2 + spec/features/consumer/checkout_spec.rb | 49 +++------------- 12 files changed, 128 insertions(+), 96 deletions(-) create mode 100644 app/assets/javascripts/darkswarm/cart.js.coffee delete mode 100644 app/overrides/rearrange_empty_cart_form.rb delete mode 100644 app/overrides/rearrange_inside_cart_form.rb delete mode 100644 app/overrides/spree/orders/edit/list_view.html.haml.deface delete mode 100644 app/overrides/spree/orders/edit/quick_checkout.html.haml.deface delete mode 100644 app/views/spree/orders/_empty_cart_form.html.haml create mode 100644 app/views/spree/orders/_form.html.haml delete mode 100644 app/views/spree/orders/_inside_cart_form.html.haml create mode 100644 app/views/spree/orders/_line_item.html.haml create mode 100644 app/views/spree/orders/edit.html.haml diff --git a/app/assets/javascripts/darkswarm/cart.js.coffee b/app/assets/javascripts/darkswarm/cart.js.coffee new file mode 100644 index 0000000000..1d3e76290f --- /dev/null +++ b/app/assets/javascripts/darkswarm/cart.js.coffee @@ -0,0 +1,14 @@ +window.Cart = angular.module("Cart", ["ngResource"]).config ($httpProvider) -> + $httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content') + + +# TEMPORARILY handles the deletetion +$ -> + if ($ 'form#update-cart').is('*') + ($ 'form#update-cart a.delete').show().one 'click', -> + ($ this).parents('.line-item').first().find('input.line_item_quantity').val 0 + ($ this).parents('form').first().submit() + false + + ($ 'form#update-cart').submit -> + ($ 'form#update-cart #update-button').attr('disabled', true) diff --git a/app/overrides/rearrange_empty_cart_form.rb b/app/overrides/rearrange_empty_cart_form.rb deleted file mode 100644 index 813173f489..0000000000 --- a/app/overrides/rearrange_empty_cart_form.rb +++ /dev/null @@ -1,6 +0,0 @@ -# Remove column layout from cart form items so we can style it with CSS -Deface::Override.new(:virtual_path => "spree/orders/edit", - :replace => "#empty-cart[data-hook]", - :partial => "spree/orders/empty_cart_form", - :name => "rearrange_empty_cart_form", - :original => 'b5a751777e66ccbd45d7f1b980ecd201af94cb5b') \ No newline at end of file diff --git a/app/overrides/rearrange_inside_cart_form.rb b/app/overrides/rearrange_inside_cart_form.rb deleted file mode 100644 index fd4f873ad5..0000000000 --- a/app/overrides/rearrange_inside_cart_form.rb +++ /dev/null @@ -1,6 +0,0 @@ -# Remove column layout from cart form items so we can style it with CSS -Deface::Override.new(:virtual_path => "spree/orders/edit", - :replace => "[data-hook='inside_cart_form']", - :partial => "spree/orders/inside_cart_form", - :name => "rearrange_inside_cart_form", - :original => 'e30b0e749869c51f004242b0cb7be582b45e044e') diff --git a/app/overrides/spree/orders/edit/list_view.html.haml.deface b/app/overrides/spree/orders/edit/list_view.html.haml.deface deleted file mode 100644 index 86f90c6c8e..0000000000 --- a/app/overrides/spree/orders/edit/list_view.html.haml.deface +++ /dev/null @@ -1,6 +0,0 @@ -/ replace_contents "[id='clear_cart_link']" - -%p - = link_to "Continue Shopping", main_app.shop_path, class: "button primary checkout" - = t(:or) - = submit_tag t(:empty_cart), :class => 'button gray' diff --git a/app/overrides/spree/orders/edit/quick_checkout.html.haml.deface b/app/overrides/spree/orders/edit/quick_checkout.html.haml.deface deleted file mode 100644 index ea21298832..0000000000 --- a/app/overrides/spree/orders/edit/quick_checkout.html.haml.deface +++ /dev/null @@ -1,9 +0,0 @@ -/ replace_contents "[data-hook='cart_buttons']" - -= button_tag :class => 'primary', :id => 'update-button' do - = t(:update) -  -= link_to "Checkout", main_app.shop_checkout_path, class: "button checkout primary" - -%div - = link_to "Old Checkout", "/checkout", :id => 'checkout-link' diff --git a/app/views/spree/orders/_empty_cart_form.html.haml b/app/views/spree/orders/_empty_cart_form.html.haml deleted file mode 100644 index 1e9a792eec..0000000000 --- a/app/views/spree/orders/_empty_cart_form.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -#empty-cart{'data-hook' => ""} - = form_tag empty_cart_path, :method => :put do - %p#clear_cart_link{'data-hook' => ""} - = link_to t(:continue_shopping), products_path, :class => 'continue button' - = t(:or) - = submit_tag t(:empty_cart), :class => 'button' diff --git a/app/views/spree/orders/_form.html.haml b/app/views/spree/orders/_form.html.haml new file mode 100644 index 0000000000..ae2e392872 --- /dev/null +++ b/app/views/spree/orders/_form.html.haml @@ -0,0 +1,20 @@ += render :partial => 'spree/shared/error_messages', :locals => { :target => @order } + +%table#cart-detail{"data-hook" => ""} + %col{halign: "center", valign: "middle", width: "30%"}/ + %col{valign: "middle", width: "25%"}/ + %col{halign: "center", valign: "middle", width: "15%"}/ + %col{halign: "center", valign: "middle", width: "15%"}/ + %col{halign: "center", valign: "middle", width: "10%"}/ + %col{halign: "center", valign: "middle", width: "5%"}/ + %thead + %tr{"data-hook" => "cart_items_headers"} + %th.cart-item-description-header{colspan: "2"}= t(:item) + %th.cart-item-price-header= t(:price) + %th.cart-item-quantity-header= t(:qty) + %th.cart-item-total-header= t(:total) + %th.cart-item-delete-header + %tbody#line_items{"data-hook" => ""} + = order_form.fields_for :line_items do |item_form| + = render :partial => 'line_item', :locals => { :variant => item_form.object.variant, :line_item => item_form.object, :item_form => item_form } + = render "spree/orders/adjustments" unless @order.adjustments.eligible.blank? diff --git a/app/views/spree/orders/_inside_cart_form.html.haml b/app/views/spree/orders/_inside_cart_form.html.haml deleted file mode 100644 index 502546113f..0000000000 --- a/app/views/spree/orders/_inside_cart_form.html.haml +++ /dev/null @@ -1,22 +0,0 @@ -%div{'data-hook' => "inside_cart_form"} - %div{'data-hook' => "cart_items"} - = render :partial => 'form', :locals => { :order_form => order_form } - - #subtotal{'data-hook' => ""} - %h5 - Item total - \: - %span.order-total.item-total= number_to_currency @order.item_total - %h5 - Distribution total - \: - %span.order-total.distribution-total= order_distribution_subtotal(@order) - %h4 - Total - \: - %span.order-total.grand-total= @order.display_total - - .links{'data-hook' => "cart_buttons"} - = button_tag :class => 'primary', :id => 'update-button' do - = t(:update) - = link_to t(:checkout), checkout_path, :class => 'button checkout primary', :id => 'checkout-link' diff --git a/app/views/spree/orders/_line_item.html.haml b/app/views/spree/orders/_line_item.html.haml new file mode 100644 index 0000000000..bb7054655f --- /dev/null +++ b/app/views/spree/orders/_line_item.html.haml @@ -0,0 +1,27 @@ +%tr.line-item + %td.cart-item-image{"data-hook" => "cart_item_image"} + - if variant.images.length == 0 + = link_to small_image(variant.product), variant.product + - else + = link_to image_tag(variant.images.first.attachment.url(:small)), variant.product + + %td.cart-item-description{"data-hook" => "cart_item_description"} + %h4= link_to variant.product.name, product_path(variant.product) + = variant.options_text + - if @order.insufficient_stock_lines.include? line_item + %span.out-of-stock + = variant.in_stock? ? t(:insufficient_stock, :on_hand => variant.on_hand) : t(:out_of_stock) + %br + = line_item_description(variant) + + %td.cart-item-price{"data-hook" => "cart_item_price"} + = line_item.single_money.to_html + %td.cart-item-quantity{"data-hook" => "cart_item_quantity"} + = item_form.number_field :quantity, :min => 0, :class => "line_item_quantity", :size => 5 + %td.cart-item-total{"data-hook" => "cart_item_total"} + = line_item.display_amount.to_html unless line_item.quantity.nil? + + %td.cart-item-delete{"data-hook" => "cart_item_delete"} + {{ quantity }} + = link_to image_tag('icons/delete.png'), '#', :class => 'delete', + :id => "delete_#{dom_id(line_item)}" diff --git a/app/views/spree/orders/edit.html.haml b/app/views/spree/orders/edit.html.haml new file mode 100644 index 0000000000..3038412438 --- /dev/null +++ b/app/views/spree/orders/edit.html.haml @@ -0,0 +1,57 @@ +- @body_id = 'cart' +.darkswarm + - content_for :order_cycle_form do + %strong.avenir + Order ready on + - if @order.order_cycle + = pickup_time @order.order_cycle + - else + = @order.distributor.next_collection_at + + = render partial: "shop/details" + + %fieldset{"ng-app" => "Cart"} + - if @order.line_items.empty? + %div.row{"data-hook" => "empty_cart"} + %p= t(:your_cart_is_empty) + %p= link_to t(:continue_shopping), main_app.shop_path, :class => 'button continue' + + - else + %div{"data-hook" => "outside_cart_form"} + = form_for @order, :url => update_cart_path, :html => {:id => 'update-cart'} do |order_form| + %div{"data-hook" => "inside_cart_form"} + %div{"data-hook" => "cart_items"} + .row + = render :partial => 'form', :locals => { :order_form => order_form } + + #subtotal.row{'data-hook' => ""} + .columns.large-5 + %h5 + Item total + \: + %span.order-total.item-total= number_to_currency @order.item_total + .columns.large-5 + %h5 + Distribution total + \: + %span.order-total.distribution-total= order_distribution_subtotal(@order) + .columns.large-2 + %h4 + Total + \: + %span.order-total.grand-total= @order.display_total + + .links{'data-hook' => "cart_buttons"} + .row + .columns.large-1 + = button_tag :class => 'primary', :id => 'update-button' do + = t(:update) + .columns.large-2 + = link_to "Checkout", main_app.shop_checkout_path, class: "button checkout primary", id: "checkout-link" + + #empty-cart.columns.large-4{"data-hook" => ""} + = form_tag empty_cart_path, :method => :put do + #clear_cart_link{"data-hook" => ""} + = link_to "Continue Shopping", main_app.shop_path, class: "button primary checkout" + = t(:or) + = submit_tag t(:empty_cart), :class => 'button gray' diff --git a/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb b/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb index 887dd1188d..19ee905ae2 100644 --- a/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb +++ b/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb @@ -78,6 +78,7 @@ feature "enterprises distributor info as rich text" do # -- Checkout click_button 'Add To Cart' find('#checkout-link').click + visit "/checkout" within 'fieldset#shipping' do page.should have_content 'Chu ge sai yubi dan bisento tobi ashi yubi ge omote.' page.should have_content 'Thursday 2nd May' @@ -123,6 +124,7 @@ feature "enterprises distributor info as rich text" do # -- Checkout click_button 'Add To Cart' find('#checkout-link').click + visit "/checkout" within 'fieldset#shipping' do page.should have_content 'Chu ge sai yubi dan bisento tobi ashi yubi ge omote.' page.should have_content 'Friday 4th May' diff --git a/spec/features/consumer/checkout_spec.rb b/spec/features/consumer/checkout_spec.rb index ddb42d2214..c150582ae9 100644 --- a/spec/features/consumer/checkout_spec.rb +++ b/spec/features/consumer/checkout_spec.rb @@ -365,6 +365,9 @@ feature %q{ click_link 'Zucchini' click_button 'Add To Cart' find('#checkout-link').click + + # And manually visit the old checkout + visit "/checkout" # -- Checkout: Address fill_in_fields('order_bill_address_attributes_firstname' => 'Joe', @@ -451,9 +454,11 @@ feature %q{ # We perform login inline because: # a) It's a common user flow # b) It has been known to trigger errors with spree_last_address - fill_in 'spree_user_email', :with => 'someone@ofn.org' - fill_in 'spree_user_password', :with => 'passw0rd' - click_button 'Login' + within "#checkout_login" do + fill_in 'login_spree_user_email', :with => 'someone@ofn.org' + fill_in 'login_spree_user_password', :with => 'passw0rd' + click_button 'Login' + end # -- Checkout: Address page.should have_field 'order_bill_address_attributes_firstname', with: 'Joe' @@ -464,44 +469,6 @@ feature %q{ page.should have_field 'order_bill_address_attributes_phone', with: '12999911111' page.should have_select 'order_bill_address_attributes_state_id', selected: 'Victoria' page.should have_select 'order_bill_address_attributes_country_id', selected: 'Australia' - - # Distributor details should be displayed - within('fieldset#shipping') do - [@distributor_oc.name, - @distributor_oc.distributor_info, - @distributor_oc.next_collection_at - ].each do |value| - - page.should have_content value - end - end - - # Disabled until this form takes order cycles into account - # page.should have_selector "select#order_distributor_id option[value='#{@distributor_alternative.id}']" - - click_checkout_continue_button - - # -- Checkout: Delivery - order_charges = page.all("tbody#summary-order-charges tr").map {|row| row.all('td').map(&:text)}.take(2) - order_charges.should == [["Distribution:", "$51.00"]] - click_checkout_continue_button - - # -- Checkout: Payment - # Given the distributor I have selected for my order, I should only see payment methods valid for that distributor - page.should have_selector 'label', :text => @payment_method_distributor_oc.name - page.should_not have_selector 'label', :text => @payment_method_alternative.name - click_checkout_continue_button - - # -- Checkout: Order complete - page.should have_content 'Your order has been processed successfully' - page.should have_content @payment_method_distributor_oc.description - - page.should have_selector 'tfoot#order-charges tr.total td', text: 'Distribution' - page.should have_selector 'tfoot#order-charges tr.total td', text: '51.00' - - # -- Checkout: Email - email = ActionMailer::Base.deliveries.last - email.body.should =~ /Distribution[\s+]\$51.00/ end From 49789c865e8393323b27db0ba7b3f2a0981fb024 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Tue, 18 Mar 2014 16:47:28 +1100 Subject: [PATCH 058/107] No longer copying old ship address from pickup, adding phone to custom shipping address --- app/controllers/spree/checkout_controller_decorator.rb | 2 +- app/views/shop/checkout/_form.html.haml | 3 +++ spec/controllers/shop/checkout_controller_spec.rb | 8 ++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/controllers/spree/checkout_controller_decorator.rb b/app/controllers/spree/checkout_controller_decorator.rb index 3f83ca10ec..c0df48d456 100644 --- a/app/controllers/spree/checkout_controller_decorator.rb +++ b/app/controllers/spree/checkout_controller_decorator.rb @@ -45,7 +45,7 @@ Spree::CheckoutController.class_eval do past = Spree::Order.order("id desc").where(:email => email).where("state != 'cart'").limit(8) if order = past.detect(&:bill_address) bill_address = order.bill_address.clone if order.bill_address - ship_address = order.ship_address.clone if order.ship_address + ship_address = order.ship_address.clone if order.ship_address if order.shipping_method.require_ship_address end [bill_address, ship_address] diff --git a/app/views/shop/checkout/_form.html.haml b/app/views/shop/checkout/_form.html.haml index 1a5fc1cfe0..41f617a26f 100644 --- a/app/views/shop/checkout/_form.html.haml +++ b/app/views/shop/checkout/_form.html.haml @@ -106,6 +106,9 @@ = sa.text_field :firstname .large-6.columns = sa.text_field :lastname + .row + .large-6.columns + = sa.text_field :phone #ship_address_hidden{"ng-show" => "order.ship_address_same_as_billing"} = sa.hidden_field :address1, "ng-value" => "order.bill_address.address1", diff --git a/spec/controllers/shop/checkout_controller_spec.rb b/spec/controllers/shop/checkout_controller_spec.rb index 39f5cf9dff..2604fac5e7 100644 --- a/spec/controllers/shop/checkout_controller_spec.rb +++ b/spec/controllers/shop/checkout_controller_spec.rb @@ -37,6 +37,14 @@ describe Shop::CheckoutController do response.should be_success end + it "doesn't copy the previous shipping address from a pickup order" do + old_order = create(:order, bill_address: create(:address), ship_address: create(:address)) + old_order.shipping_method.should_receive(:require_ship_address).and_return(false) + Spree::Order.stub_chain(:order, :where, :where, :limit, :detect).and_return(old_order) + + controller.send(:find_last_used_addresses, "email").last.should == nil + end + describe "building the order" do before do controller.stub(:current_distributor).and_return(distributor) From 51b6ac3e68a98dca5be2463bae479bce0ee0a86c Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Tue, 18 Mar 2014 17:03:09 +1100 Subject: [PATCH 059/107] Fixing one regression bug --- app/controllers/spree/checkout_controller_decorator.rb | 2 +- spec/features/consumer/checkout_spec.rb | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/spree/checkout_controller_decorator.rb b/app/controllers/spree/checkout_controller_decorator.rb index c0df48d456..66ca7fb034 100644 --- a/app/controllers/spree/checkout_controller_decorator.rb +++ b/app/controllers/spree/checkout_controller_decorator.rb @@ -45,7 +45,7 @@ Spree::CheckoutController.class_eval do past = Spree::Order.order("id desc").where(:email => email).where("state != 'cart'").limit(8) if order = past.detect(&:bill_address) bill_address = order.bill_address.clone if order.bill_address - ship_address = order.ship_address.clone if order.ship_address if order.shipping_method.require_ship_address + ship_address = order.ship_address.clone if order.ship_address and order.shipping_method.andand.require_ship_address end [bill_address, ship_address] diff --git a/spec/features/consumer/checkout_spec.rb b/spec/features/consumer/checkout_spec.rb index c150582ae9..d606eb54fc 100644 --- a/spec/features/consumer/checkout_spec.rb +++ b/spec/features/consumer/checkout_spec.rb @@ -449,16 +449,16 @@ feature %q{ click_link 'Zucchini' click_button 'Add To Cart' find('#checkout-link').click + visit "/checkout" # Force to old checkout # -- Login # We perform login inline because: # a) It's a common user flow # b) It has been known to trigger errors with spree_last_address - within "#checkout_login" do - fill_in 'login_spree_user_email', :with => 'someone@ofn.org' - fill_in 'login_spree_user_password', :with => 'passw0rd' - click_button 'Login' - end + fill_in 'spree_user_email', :with => 'someone@ofn.org' + fill_in 'spree_user_password', :with => 'passw0rd' + click_button 'Login' + visit "/checkout" # Force to old checkout # -- Checkout: Address page.should have_field 'order_bill_address_attributes_firstname', with: 'Joe' From 90159325bfb5325f036d8906cc72927cf006fe38 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Tue, 18 Mar 2014 17:38:33 +1100 Subject: [PATCH 060/107] Patching a test --- spec/controllers/shop/checkout_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/shop/checkout_controller_spec.rb b/spec/controllers/shop/checkout_controller_spec.rb index 2604fac5e7..ed91818fbd 100644 --- a/spec/controllers/shop/checkout_controller_spec.rb +++ b/spec/controllers/shop/checkout_controller_spec.rb @@ -39,7 +39,7 @@ describe Shop::CheckoutController do it "doesn't copy the previous shipping address from a pickup order" do old_order = create(:order, bill_address: create(:address), ship_address: create(:address)) - old_order.shipping_method.should_receive(:require_ship_address).and_return(false) + old_order.shipping_method.stub_chain(:andand, :require_ship_address).and_return(false) Spree::Order.stub_chain(:order, :where, :where, :limit, :detect).and_return(old_order) controller.send(:find_last_used_addresses, "email").last.should == nil From d5b1cc33039a4dd951bf48d52418789a68e8e3e2 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 10:40:30 +1100 Subject: [PATCH 061/107] Swapping buttons --- app/views/spree/orders/edit.html.haml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/views/spree/orders/edit.html.haml b/app/views/spree/orders/edit.html.haml index 3038412438..c9b97b7331 100644 --- a/app/views/spree/orders/edit.html.haml +++ b/app/views/spree/orders/edit.html.haml @@ -43,15 +43,16 @@ .links{'data-hook' => "cart_buttons"} .row + #empty-cart.columns.large-9{"data-hook" => ""} + = form_tag empty_cart_path, :method => :put do + #clear_cart_link{"data-hook" => ""} + = link_to "Continue Shopping", main_app.shop_path, class: "button primary checkout" + = t(:or) + = submit_tag t(:empty_cart), :class => 'button gray' + .columns.large-1 = button_tag :class => 'primary', :id => 'update-button' do = t(:update) .columns.large-2 = link_to "Checkout", main_app.shop_checkout_path, class: "button checkout primary", id: "checkout-link" - #empty-cart.columns.large-4{"data-hook" => ""} - = form_tag empty_cart_path, :method => :put do - #clear_cart_link{"data-hook" => ""} - = link_to "Continue Shopping", main_app.shop_path, class: "button primary checkout" - = t(:or) - = submit_tag t(:empty_cart), :class => 'button gray' From a8547a9e64982cc383e3d5930f013b15f13863f1 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 10:45:40 +1100 Subject: [PATCH 062/107] Adjustments toggling --- app/assets/javascripts/darkswarm/cart.js.coffee | 11 +++++++++++ .../javascripts/store/checkout_adjustments.js.coffee | 2 +- app/views/spree/orders/_adjustments.html.haml | 11 +++++++++++ app/views/spree/orders/_form.html.haml | 2 ++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 app/views/spree/orders/_adjustments.html.haml diff --git a/app/assets/javascripts/darkswarm/cart.js.coffee b/app/assets/javascripts/darkswarm/cart.js.coffee index 1d3e76290f..66b607d94d 100644 --- a/app/assets/javascripts/darkswarm/cart.js.coffee +++ b/app/assets/javascripts/darkswarm/cart.js.coffee @@ -12,3 +12,14 @@ $ -> ($ 'form#update-cart').submit -> ($ 'form#update-cart #update-button').attr('disabled', true) + + +# Temporarily handles the cart showing stuff +$(document).ready -> + $('#cart_adjustments').hide() + + $('th.cart-adjustment-header').html('
Distribution...') + $('th.cart-adjustment-header a').click -> + $('#cart_adjustments').toggle() + $('th.cart-adjustment-header a').html('Distribution') + false diff --git a/app/assets/javascripts/store/checkout_adjustments.js.coffee b/app/assets/javascripts/store/checkout_adjustments.js.coffee index c443f7dd5d..35b9d7ff55 100644 --- a/app/assets/javascripts/store/checkout_adjustments.js.coffee +++ b/app/assets/javascripts/store/checkout_adjustments.js.coffee @@ -5,4 +5,4 @@ $(document).ready -> $('th.cart-adjustment-header a').click -> $('#cart_adjustments').toggle() $('th.cart-adjustment-header a').html('Distribution') - false \ No newline at end of file + false diff --git a/app/views/spree/orders/_adjustments.html.haml b/app/views/spree/orders/_adjustments.html.haml new file mode 100644 index 0000000000..21e971832a --- /dev/null +++ b/app/views/spree/orders/_adjustments.html.haml @@ -0,0 +1,11 @@ +%thead + %tr{"data-hook" => "cart_adjustments_headers"} + %th.cart-adjustment-header{colspan: "6"} + Distribution Fees + +%tbody#cart_adjustments{"data-hook" => ""} + - @order.adjustments.eligible.each do |adjustment| + %tr + %td{colspan: "4"}= adjustment.label + %td= adjustment.display_amount.to_html + %td diff --git a/app/views/spree/orders/_form.html.haml b/app/views/spree/orders/_form.html.haml index ae2e392872..9366e0025d 100644 --- a/app/views/spree/orders/_form.html.haml +++ b/app/views/spree/orders/_form.html.haml @@ -14,7 +14,9 @@ %th.cart-item-quantity-header= t(:qty) %th.cart-item-total-header= t(:total) %th.cart-item-delete-header + %tbody#line_items{"data-hook" => ""} = order_form.fields_for :line_items do |item_form| = render :partial => 'line_item', :locals => { :variant => item_form.object.variant, :line_item => item_form.object, :item_form => item_form } + = render "spree/orders/adjustments" unless @order.adjustments.eligible.blank? From 9c26d23cb5d0917fdbb8c35b39a4fcb34914e22a Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 10:48:49 +1100 Subject: [PATCH 063/107] Greying the buttons like a boss --- app/views/spree/orders/edit.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/spree/orders/edit.html.haml b/app/views/spree/orders/edit.html.haml index c9b97b7331..d91efc9483 100644 --- a/app/views/spree/orders/edit.html.haml +++ b/app/views/spree/orders/edit.html.haml @@ -46,12 +46,12 @@ #empty-cart.columns.large-9{"data-hook" => ""} = form_tag empty_cart_path, :method => :put do #clear_cart_link{"data-hook" => ""} - = link_to "Continue Shopping", main_app.shop_path, class: "button primary checkout" + = link_to "Continue Shopping", main_app.shop_path, class: "button secondary" = t(:or) - = submit_tag t(:empty_cart), :class => 'button gray' + = submit_tag t(:empty_cart), :class => 'button secondary' .columns.large-1 - = button_tag :class => 'primary', :id => 'update-button' do + = button_tag :class => 'secondary', :id => 'update-button' do = t(:update) .columns.large-2 = link_to "Checkout", main_app.shop_checkout_path, class: "button checkout primary", id: "checkout-link" From 8435dfabb114e46bf20ee8a3bdfe5eebd321a744 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 10:49:57 +1100 Subject: [PATCH 064/107] Changing the cart summary text --- app/views/shop/checkout/_summary.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/shop/checkout/_summary.html.haml b/app/views/shop/checkout/_summary.html.haml index cc215ffecc..8acd254d69 100644 --- a/app/views/shop/checkout/_summary.html.haml +++ b/app/views/shop/checkout/_summary.html.haml @@ -4,7 +4,7 @@ %legend Your Order %table %tr - %th Produce cost + %th Produce %td= current_order.display_item_total - checkout_adjustments_for_summary(current_order).each do |adjustment| @@ -12,7 +12,7 @@ %th= adjustment.label %td= adjustment.display_amount.to_html %tr - %th Shipping cost + %th Shipping %td {{ shippingPrice() | currency }} %tr %th Cart total From 01503632f23688f533711cecbfeabe90c2b0c5f2 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 10:52:26 +1100 Subject: [PATCH 065/107] Changing cart titles --- app/views/spree/orders/edit.html.haml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/spree/orders/edit.html.haml b/app/views/spree/orders/edit.html.haml index d91efc9483..095218cea7 100644 --- a/app/views/spree/orders/edit.html.haml +++ b/app/views/spree/orders/edit.html.haml @@ -27,17 +27,17 @@ #subtotal.row{'data-hook' => ""} .columns.large-5 %h5 - Item total + Product \: %span.order-total.item-total= number_to_currency @order.item_total - .columns.large-5 + .columns.large-4 %h5 - Distribution total + Distribution \: %span.order-total.distribution-total= order_distribution_subtotal(@order) - .columns.large-2 + .columns.large-3 %h4 - Total + Cart Total \: %span.order-total.grand-total= @order.display_total From 82c29683d61b2e327d07183284648bd7c1c683f1 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 11:06:18 +1100 Subject: [PATCH 066/107] Disabling enter on the search --- .../darkswarm/controllers/products_controller.js.coffee | 5 +++++ app/views/shop/shop/_products.html.haml | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee index 784aed539c..0a054d755d 100644 --- a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee @@ -3,6 +3,11 @@ angular.module("Shop").controller "ProductsCtrl", ($scope, $rootScope, Product, $scope.order_cycle = OrderCycle.order_cycle Product.update() + $scope.searchKeypress = (e)-> + code = e.keyCode || e.which + if code == 13 + e.preventDefault() + $scope.productPrice = (product) -> if product.variants.length > 0 prices = (v.price for v in product.variants) diff --git a/app/views/shop/shop/_products.html.haml b/app/views/shop/shop/_products.html.haml index 22bc7c7933..9941dffad8 100644 --- a/app/views/shop/shop/_products.html.haml +++ b/app/views/shop/shop/_products.html.haml @@ -1,7 +1,9 @@ %products{"ng-controller" => "ProductsCtrl", "ng-show" => "order_cycle.order_cycle_id != null"} = form_for :order, :url => populate_orders_path, html: {:class => "custom"} do - %input#search.text{"ng-model" => "query", placeholder: "Search"} + + %input#search.text{"ng-model" => "query", placeholder: "Search", "ng-keypress" => "searchKeypress($event)"} %input.button.right{type: :submit, value: "Add to Cart"} + %table %thead %th.name Item From df6cbc049f7320a8a819c671ec94ddeaf6184368 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 11:09:02 +1100 Subject: [PATCH 067/107] Making sure quantities don't get lost --- app/views/shop/shop/_products.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/shop/shop/_products.html.haml b/app/views/shop/shop/_products.html.haml index 9941dffad8..b9db90af95 100644 --- a/app/views/shop/shop/_products.html.haml +++ b/app/views/shop/shop/_products.html.haml @@ -39,7 +39,8 @@ min: 0, max: "{{product.on_demand && 9999 || product.count_on_hand }}", name: "variants[{{product.master.id}}]", - id: "variants_{{product.master.id}}"} + id: "variants_{{product.master.id}}", + "ng-model" => "product.quantity"} %td.group_buy %span{"ng-show" => "product.group_buy && (product.variants.length == 0)"} %input{type: :number, From 3df3afe068b706f7224f2c8f0aff03b773b701fc Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 11:09:45 +1100 Subject: [PATCH 068/107] Quantities for bulk not getting lost now --- app/views/shop/shop/_products.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/shop/shop/_products.html.haml b/app/views/shop/shop/_products.html.haml index b9db90af95..36193467e0 100644 --- a/app/views/shop/shop/_products.html.haml +++ b/app/views/shop/shop/_products.html.haml @@ -46,7 +46,8 @@ %input{type: :number, min: 0, max: "{{product.on_demand && 9999 || product.count_on_hand }}", - name: "variant_attributes[{{product.master.id}}][max_quantity]"} + name: "variant_attributes[{{product.master.id}}][max_quantity]", + "ng-model" => "product.max_quantity"} %td.price.text-right %small{"ng-show" => "(product.variants.length > 0)"} from {{ productPrice(product) | currency }} From abf1b17fe933a179ec24bfb5a0d1119dba7ec0a0 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 12:03:39 +1100 Subject: [PATCH 069/107] Taking payment methods with no distributor out of circulation --- app/models/spree/order_decorator.rb | 4 ++-- spec/models/spree/order_spec.rb | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index c744a3270a..40c06c3132 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -133,10 +133,10 @@ Spree::Order.class_eval do line_items.map { |li| li.variant } end - # Show payment methods with no distributor or for this distributor + # Show payment methods for this distributor def available_payment_methods @available_payment_methods ||= Spree::PaymentMethod.available(:front_end).select do |pm| - (self.distributor && (pm.distributors.include? self.distributor)) || pm.distributors.empty? + (self.distributor && (pm.distributors.include? self.distributor)) end end diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb index ff17188998..80a65b79cc 100644 --- a/spec/models/spree/order_spec.rb +++ b/spec/models/spree/order_spec.rb @@ -22,6 +22,19 @@ describe Spree::Order do end end + describe "Payment methods" do + let(:order) { build(:order, distributor: create(:distributor_enterprise)) } + let(:pm1) { create(:payment_method, distributors: [order.distributor])} + let(:pm2) { create(:payment_method, distributors: [])} + + it "finds the correct payment methods" do + Spree::PaymentMethod.stub(:available).and_return [pm1, pm2] + order.available_payment_methods.include?(pm2).should == false + order.available_payment_methods.include?(pm1).should == true + end + + end + describe "updating the distribution charge" do let(:order) { build(:order) } From b88571a6f0cfd21dce03270f846be314a568f2fe Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 14:14:51 +1100 Subject: [PATCH 070/107] Removing redundant spec --- .../features/consumer/browse_products_spec.rb | 95 ------------------- 1 file changed, 95 deletions(-) delete mode 100644 spec/features/consumer/browse_products_spec.rb diff --git a/spec/features/consumer/browse_products_spec.rb b/spec/features/consumer/browse_products_spec.rb deleted file mode 100644 index 8213e00035..0000000000 --- a/spec/features/consumer/browse_products_spec.rb +++ /dev/null @@ -1,95 +0,0 @@ -require 'spec_helper' - -feature %q{ - As a consumer - I want to browse products by distributor and order cycle - So that I can buy products that are available soon and close to me -} do - include AuthenticationWorkflow - include WebHelper - - describe "selecting a distributor" do - it "displays the distributor's details" do - # Given a distributor with a product - d = create(:distributor_enterprise, :name => 'Melb Uni Co-op', :description => '

Hello, world!

') - create(:product, :distributors => [d]) - - # When I select the distributor - visit spree.select_distributor_order_path(d) - - # Then I should see the name of the distributor that I've selected - page.should have_selector 'h1', :text => 'Melb Uni Co-op' - - # And I should see the distributor's long description - page.should have_selector 'div.enterprise-description', :text => 'Hello, world!' - end - - it "displays the distributor's name on the home page" do - # Given a distributor with a product - d = create(:distributor_enterprise, :name => 'Melb Uni Co-op', :description => '

Hello, world!

') - create_enterprise_group_for d - p1 = create(:product, :distributors => [d]) - - # When I select the distributor - visit spree.select_distributor_order_path(d) - visit spree.root_path - click_on "Melb Uni Co-op" - - # Then I should see the name of the distributor that I've selected - page.should have_content 'Melb Uni Co-op' - page.should_not have_selector 'div.distributor-description' - end - - it "splits the product listing by local/remote distributor", :future => true do - # Given two distributors, with a product under each, and each product under a taxon - taxonomy = Spree::Taxonomy.find_by_name('Products') || create(:taxonomy, :name => 'Products') - taxonomy_root = taxonomy.root - taxon = create(:taxon, :name => 'Taxon one', :parent_id => taxonomy_root.id) - d1 = create(:distributor_enterprise, :name => 'Green Grass') - d2 = create(:distributor_enterprise) - p1 = create(:product, :distributors => [d1], :taxons => [taxon]) - p2 = create(:product, :distributors => [d2], :taxons => [taxon]) - - # When I select the first distributor - visit spree.select_distributor_order_path(d1) - visit spree.root_path - - # Then I should see products split by local/remote distributor - # on the home page, the products page, the search results page and the taxon page - [spree.products_path, - spree.products_path(:keywords => 'Product'), - spree.nested_taxons_path(taxon.permalink) - ].each do |path| - - visit path - page.should have_selector '#products' - end - end - - describe "variant listing" do - it "shows only variants that are in the distributor and order cycle", js: true do - # Given a product with two variants - s = create(:supplier_enterprise) - d = create(:distributor_enterprise, name: 'Green Grass') - create_enterprise_group_for d - p = create(:simple_product, supplier: s) - v1 = create(:variant, product: p, is_master: false) - v2 = create(:variant, product: p, is_master: false) - - # And only one of those is distributed by an order cycle - oc = create(:simple_order_cycle, suppliers: [s], distributors: [d], variants: [v1]) - - # When I am in that order cycle - visit root_path - click_link d.name - visit enterprise_path d - - # And I view the product - click_link p.name - - # Then I should see only the relevant variant - page.all('#product-variants li input').count.should == 1 - end - end - end -end From 922724135b242295ea3d0866ba3d3d6f17ccdf08 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 14:15:54 +1100 Subject: [PATCH 071/107] Removing distributors spec --- spec/features/consumer/distributors_spec.rb | 110 -------------------- 1 file changed, 110 deletions(-) delete mode 100644 spec/features/consumer/distributors_spec.rb diff --git a/spec/features/consumer/distributors_spec.rb b/spec/features/consumer/distributors_spec.rb deleted file mode 100644 index 70b38dddbd..0000000000 --- a/spec/features/consumer/distributors_spec.rb +++ /dev/null @@ -1,110 +0,0 @@ -require 'spec_helper' - -feature %q{ - As a consumer - I want to see a list of distributors - So that I can shop by a particular distributor -} do - include AuthenticationWorkflow - include WebHelper - - scenario "viewing a list of distributors in the sidebar", :future => true do - # Given some distributors - d1 = create(:distributor_enterprise, :name => "Edible garden") - d2 = create(:distributor_enterprise) - d3 = create(:distributor_enterprise) - - # And some of those distributors have a product - create(:product, :distributors => [d1, d2]) - - # When I go to the home page - visit spree.root_path - - # and proceed to the shop front - click_on "Edible garden" - - # Then I should see a list containing the distributors that have products - page.should have_selector 'a', :text => d1.name - page.should have_selector 'a', :text => d2.name - page.should_not have_selector 'a', :text => d3.name - end - - scenario "viewing a list of distributors (with active products) in the sidebar when there's some inactive distributors", :future => true do - # Given some distributors - d1 = create(:distributor_enterprise, :name => "Edible garden") - d2 = create(:distributor_enterprise) - d3 = create(:distributor_enterprise) - d4 = create(:distributor_enterprise) - d5 = create(:distributor_enterprise) - d6 = create(:distributor_enterprise) - - # And some of those distributors have a product - create(:product, :distributors => [d1]) - create(:product, :distributors => [d3], :on_hand => 0) - - # And no limit set for the sidebar - sidebar_distributors_limit = false - - # When I go to the home page - visit spree.root_path - - # and proceed to the shop front - click_on "Edible garden" - - # Then I should see a list containing all the distributors that have active products in stock - page.should have_selector 'a', :text => d1.name - page.should_not have_selector 'a', :text => d2.name #has no products - page.should_not have_selector 'a', :text => d3.name #has no products on hand - - # And I should see '5 more' - distributors_more = Enterprise.is_distributor.distinct_count - Enterprise.is_distributor.with_distributed_active_products_on_hand.by_name.limit(sidebar_distributors_limit).length - page.should have_selector '#distributor_filter span.filter_more', :text => "#{distributors_more} more" - - # And I should (always) see a browse distributors button - page.should have_selector "#distributor_filter input[value='Browse All Distributors']" - end - - scenario "viewing a list of all distributors", :future => true do - # Given some distributors - d1 = create(:distributor_enterprise, :name => "Edible garden") - d2 = create(:distributor_enterprise) - d3 = create(:distributor_enterprise) - - # And some of those distributors have a product - create(:product, :distributors => [d1]) - create(:product, :distributors => [d3]) - - # When I go to the distributors listing page - visit spree.root_path - click_on "Edible garden" - click_button 'Browse All Distributors' - - # Then I should see a list containing all the distributors - page.should have_selector '#content a', :text => d1.name - page.should have_selector '#content a', :text => d2.name - page.should have_selector '#content a', :text => d3.name - end - - - scenario "viewing a distributor", :js => true do - # Given some distributors with products - d1 = create(:distributor_enterprise, :name => "Edible garden", :long_description => "

Hello, world!

") - d2 = create(:distributor_enterprise) - create_enterprise_group_for d1 - p1 = create(:product, :distributors => [d1]) - p2 = create(:product, :distributors => [d2]) - supplier = create(:supplier_enterprise) - order_cycle = create(:simple_order_cycle, suppliers: [supplier], distributors: [d1], variants: [p1.master]) - - # When I go to the first distributor page - visit spree.root_path - click_link d1.name - - # Then I should see the distributor details - page.should have_content d1.name - - # And I should see the first, but not the second product - page.should have_content p1.name - page.should_not have_content p2.name - end -end From acc9ebf83606211511ff2d15683954aaa35c5c60 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 14:22:37 +1100 Subject: [PATCH 072/107] Removing order cycle spec --- spec/features/consumer/order_cycles_spec.rb | 310 ------------------ .../consumer/shopping/shopping_spec.rb | 2 + 2 files changed, 2 insertions(+), 310 deletions(-) delete mode 100644 spec/features/consumer/order_cycles_spec.rb diff --git a/spec/features/consumer/order_cycles_spec.rb b/spec/features/consumer/order_cycles_spec.rb deleted file mode 100644 index 146db89104..0000000000 --- a/spec/features/consumer/order_cycles_spec.rb +++ /dev/null @@ -1,310 +0,0 @@ -require 'spec_helper' - -feature %q{ - As a consumer - I want to see a choice of order cycles and distributors - So that I can shop for a particular distributor and pickup date -} do - include AuthenticationWorkflow - include WebHelper - - background do - # Given some hubs and order cycles - create(:distributor_enterprise) - @d1 = create(:distributor_enterprise) - @d2 = create(:distributor_enterprise) - create(:product, distributors: [@d1, @d2]) - - @oc1 = create(:simple_order_cycle, orders_close_at: Time.zone.now + 1.week) - @oc2 = create(:simple_order_cycle, orders_close_at: Time.zone.now + 2.days) - create(:exchange, order_cycle: @oc1, sender: @oc1.coordinator, receiver: @d1) - create(:exchange, order_cycle: @oc2, sender: @oc2.coordinator, receiver: @d2) - end - - describe 'when order cycles is enabled' do - - background do - OrderCyclesHelper.class_eval do - def order_cycles_enabled? - true - end - end - end - - - scenario "selecting order cycle when multiple options are available", js: true do - d = create(:distributor_enterprise, name: 'Green Grass') - create_enterprise_group_for d - oc1 = create(:simple_order_cycle, name: 'oc 1', distributors: [d]) - oc2 = create(:simple_order_cycle, name: 'oc 2', distributors: [d]) - - # We find by ID because the scope returns read-only models - exchange = Exchange.find(oc1.exchanges.to_enterprises(d).outgoing.first.id) - exchange.update_attribute :pickup_time, "turtles" - - visit spree.root_path - click_link d.name - visit enterprise_path d - - page.should have_select 'order_order_cycle_id' - select_by_value oc1.id, from: 'order_order_cycle_id' - page.should have_content 'Your order will be ready on turtles' - end - - context "when there are no available order cycles" do - let(:d1) { create(:distributor_enterprise, name: 'Green Grass') } - let(:d2) { create(:distributor_enterprise, name: 'Blue Grass') } - before do - create_enterprise_group_for d1 - visit spree.root_path - end - - it "indicates there are no current order cycles" do - Timecop.freeze do - oc1 = create(:simple_order_cycle, name: 'oc 1', distributors: [d1], orders_close_at: 5.minutes.ago) - oc2 = create(:simple_order_cycle, name: 'oc 1', distributors: [d2], orders_close_at: 3.minutes.ago) - click_link d1.name - visit enterprise_path d1 - - page.should have_content "Orders are currently closed for this hub" - page.should have_content "The last cycle closed 5 minutes ago" - page.should have_content "Please contact your hub directly to see if they accept late orders, or wait until the next cycle opens." - page.should have_content d1.email - page.should have_content d1.phone - end - end - - context "displaying future order cycles" do - it "should show the time until the next order cycle opens" do - create(:simple_order_cycle, name: 'oc 1', distributors: [d1], orders_open_at: 10.days.from_now, orders_close_at: 11.days.from_now) - - click_link d1.name - visit enterprise_path d1 - - page.should have_content "The next order cycle opens in 10 days" - end - - it "should show nothing when there is no next order cycle" do - click_link d1.name - visit enterprise_path d1 - page.should_not have_content "The next order cycle opens" - page.should_not have_content "No products found" - end - end - end - - scenario "changing order cycle", js: true do - s = create(:supplier_enterprise) - d = create(:distributor_enterprise, name: 'Green Grass') - create_enterprise_group_for d - p = create(:simple_product, supplier: s) - oc = create(:simple_order_cycle, suppliers: [s], distributors: [d], variants: [p.master]) - - visit spree.root_path - click_link d.name - visit enterprise_path d - - click_link p.name - click_button 'Add To Cart' - visit enterprise_path d - - click_link 'Change Collection Date' - - - # Then we should be back at the landing page with a reset cart - page.should have_content 'Green Grass' - page.should have_content 'When do you want your order?' - # When we get taken back to select order cycle, there is no selected order cycle - # Therefore we should not see the no products info - page.should_not have_content "No products found" - - cart = Spree::Order.last - cart.distributor.should == d - cart.order_cycle.should be_nil - cart.line_items.should be_empty - end - - scenario "viewing order cycle and distributor choices", :future => true do - # When I go to the product listing page - visit spree.products_path - - # Then I should see a choice of hubs - page.should have_selector "#distribution-selection option[value='#{@d1.id}']", text: @d1.name - page.should have_selector "#distribution-selection option[value='#{@d2.id}']", text: @d2.name - - # And I should see a choice of order cycles with closing times - [{oc: @oc1, closing: '7 days'}, {oc: @oc2, closing: '2 days'}].each do |data| - within "tr.order-cycle-#{data[:oc].id}" do - page.should have_content data[:oc].name - page.should have_content data[:closing] - end - end - - # And I should see an indication of my current choices - page.should have_selector "#distribution-choice", text: 'You have not yet picked where you will get your order from.' - end - - scenario "order cycle expires mid-order" do - d = create(:distributor_enterprise, - name: 'Green Grass', email: 'd@example.com', phone: '1029 3847') - create_enterprise_group_for d - p = create(:simple_product) - oc = create(:simple_order_cycle, name: 'oc', distributors: [d], variants: [p.master]) - - # When I select an order cycle and add a product to my cart - visit spree.root_path - click_link 'Green Grass' - visit enterprise_path d - click_link p.name - click_button 'Add To Cart' - - # And the order cycle expires and I load a page - Timecop.travel(oc.orders_close_at + 1.day) do - - visit enterprise_path d - - # Then I should see an expiry message - page.should have_content "Sorry, orders for this order cycle closed 1 day ago! Please contact your hub directly to see if they can accept late orders." - page.should have_content d.email - page.should have_content d.phone - end - end - - - context "without javascript", :future => true do - scenario "selecting a distributor highlights valid order cycle choices" do - # When I go to the product listing page - visit spree.products_path - - # And I choose a distributor - select @d1.name, from: 'order_distributor_id' - click_button 'Choose Hub' - - # Then associated order cycles should be highlighted - page.should have_content "Your hub has been selected." - page.should have_selector '#distribution-choice', text: "Hub: #{@d1.name}" - within "#distribution-selection" do - page.should have_selector "tr.order-cycle-#{@oc1.id}.local" - page.should have_selector "tr.order-cycle-#{@oc2.id}.remote" - end - - # When I choose the other distributor - select @d2.name, from: 'order_distributor_id' - click_button 'Choose Hub' - - # Then associated order cycles should be highlighted - page.should have_content "Your hub has been selected." - page.should have_selector '#distribution-choice', text: "Hub: #{@d2.name}" - within '#distribution-selection' do - page.should have_selector "tr.order-cycle-#{@oc1.id}.remote" - page.should have_selector "tr.order-cycle-#{@oc2.id}.local" - end - end - - scenario "selecting an order cycle highlights valid distributor choices", :future => true do - # When I go to the product listing page - visit spree.products_path - - # And I choose an order cycle - choose @oc1.name - click_button 'Choose Order Cycle' - - # Then the associated distributor should be highlighted - page.should have_content "Your order cycle has been selected." - page.should have_selector '#distribution-choice', text: "Order Cycle: #{@oc1.name}" - within '#distribution-selection' do - page.should have_selector "option.local[value='#{@d1.id}']" - page.should have_selector "option.remote[value='#{@d2.id}']" - end - - # When I choose the other order cycle - choose @oc2.name - click_button 'Choose Order Cycle' - - # Then the associated distributor should be highlighted - page.should have_content "Your order cycle has been selected." - page.should have_selector '#distribution-choice', text: "Order Cycle: #{@oc2.name}" - within '#distribution-selection' do - page.should have_selector "option.remote[value='#{@d1.id}']" - page.should have_selector "option.local[value='#{@d2.id}']" - end - end - - scenario "selecing a remote order cycle clears the distributor" do - # When I go to the products listing page - visit spree.products_path - - # And I choose a distributor - select @d1.name, from: 'order_distributor_id' - click_button 'Choose Hub' - - # And I choose a remote order cycle - choose @oc2.name - click_button 'Choose Order Cycle' - - # Then my distributor should be cleared - page.should_not have_selector "option[value='#{@d1.id}'][selected='selected']" - end - - scenario "selecing a remote distributor clears the order cycle" do - # When I go to the products listing page - visit spree.products_path - - # And I choose an order cycle - choose @oc1.name - click_button 'Choose Order Cycle' - - # And I choose a remote distributor - select @d2.name, from: 'order_distributor_id' - click_button 'Choose Hub' - - # Then my order cycle should be cleared - page.should_not have_selector "input[value='#{@oc1.id}'][checked='checked']" - end - - scenario "selecting both an order cycle and distributor", :future => true do - # When I go to the products listing page - visit spree.products_path - - # And I choose an order cycle - choose @oc1.name - click_button 'Choose Order Cycle' - - # And I choose a distributor - select @d1.name, from: 'order_distributor_id' - click_button 'Choose Hub' - - # Then my order cycle and distributor should be set - within '#distribution-choice' do - page.should have_content "Hub: #{@d1.name}" - page.should have_content "Order Cycle: #{@oc1.name}" - end - - page.should have_selector "input[value='#{@oc1.id}'][checked='checked']" - page.should have_selector "option[value='#{@d1.id}'][selected='selected']" - end - end - end - - describe 'when order cycles is disabled' do - - background do - OrderCyclesHelper.class_eval do - def order_cycles_enabled? - false - end - end - end - - scenario "should not show order cycles in the product listing" do - # When I go to the product listing page - visit spree.products_path - - # Then I should not see any hubs - page.should_not have_selector "#distribution-selection" - - # And I should not display extra distribution details - page.should_not have_selector "#distribution-choice" - end - end -end diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index d42470be26..e6cb8c5d0c 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -306,6 +306,8 @@ feature "As a consumer I want to shop with a distributor", js: true do visit shop_path page.should have_content "The next cycle opens in 10 days" end + + it "shows nothing when there is no future order cycle" end end end From fae167884d1e2960632b195250ccbd82038f1bda Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 14:23:18 +1100 Subject: [PATCH 073/107] Removing the product spec --- spec/features/consumer/product_spec.rb | 55 -------------------------- 1 file changed, 55 deletions(-) delete mode 100644 spec/features/consumer/product_spec.rb diff --git a/spec/features/consumer/product_spec.rb b/spec/features/consumer/product_spec.rb deleted file mode 100644 index 9b45b21ef1..0000000000 --- a/spec/features/consumer/product_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -require 'spec_helper' - -feature %q{ - As a consumer - I want to see products - So that I can shop -} do - include AuthenticationWorkflow - include WebHelper - - scenario "viewing a product shows its supplier" do - # Given a product with a supplier and distributor - s = create(:supplier_enterprise) - d1 = create(:distributor_enterprise) - d2 = create(:distributor_enterprise) - p = create(:product, :supplier => s, :distributors => [d1]) - oc = create(:simple_order_cycle, :distributors => [d2], :variants => [p.master]) - - # When I view the product - visit spree.product_path p - - # Then I should see the product's supplier - page.should have_selector 'td', :text => s.name - end - - describe "viewing distributor details" do - context "without Javascript" do - it "displays a holding message when no distributor is selected" do - p = create(:product) - - visit spree.product_path p - - page.should have_selector '#product-distributor-details', :text => 'When you select a distributor for your order, their address and pickup times will be displayed here.' - end - - it "displays distributor details when one is selected" do - d = create(:distributor_enterprise) - p = create(:product, :distributors => [d]) - - visit spree.select_distributor_order_path(d) - visit spree.product_path p - - within '#product-distributor-details' do - [d.name, - d.distributor_info, - d.next_collection_at - ].each do |value| - - page.should have_content value - end - end - end - end - end -end From 9f36bc138336845550caba92e02663920e12fa02 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 14:25:13 +1100 Subject: [PATCH 074/107] Removing dud specs from suppliers spec --- spec/features/consumer/suppliers_spec.rb | 80 ------------------------ 1 file changed, 80 deletions(-) diff --git a/spec/features/consumer/suppliers_spec.rb b/spec/features/consumer/suppliers_spec.rb index 7c69c2e64b..2252b8e8c5 100644 --- a/spec/features/consumer/suppliers_spec.rb +++ b/spec/features/consumer/suppliers_spec.rb @@ -12,7 +12,6 @@ feature %q{ create(:distributor_enterprise, :name => "Edible garden") end - scenario "entering the site via a supplier's page" do # Given a supplier with some distributed products s = create(:supplier_enterprise) @@ -32,83 +31,4 @@ feature %q{ # Then that hub should be selected page.should have_content d.name end - - scenario "viewing a list of suppliers (with active products) in the sidebar when there's 5 or fewer", :future => true do - # Given some suppliers - s1 = create(:supplier_enterprise) - s2 = create(:supplier_enterprise) - s3 = create(:supplier_enterprise) - s4 = create(:supplier_enterprise) - s5 = create(:supplier_enterprise) - s6 = create(:supplier_enterprise) - - # And some of those suppliers have a product - create(:product, :supplier => s1) - create(:product, :supplier => s3, :on_hand => 0) - - # And no limit set for the sidebar - sidebar_suppliers_limit = false - - # When I go to the home page - visit spree.root_path - - # and proceed to the shop front - click_on "Edible garden" - - # Then I should see a list containing all the suppliers that have active products in stock - page.should have_selector 'a', :text => s1.name - page.should_not have_selector 'a', :text => s2.name #has no products - page.should_not have_selector 'a', :text => s3.name #has no products on hand - - # And I should see '5 more' - suppliers_more = Enterprise.is_primary_producer.distinct_count - Enterprise.is_primary_producer.with_supplied_active_products_on_hand.limit(sidebar_suppliers_limit).length - page.should have_selector '#supplier_filter span.filter_more', :text => "#{suppliers_more} more" - - # And I should (always) see a browse suppliers button - page.should have_selector "#supplier_filter input[value='Browse All Suppliers']" - end - - scenario "viewing a list of all suppliers", :future => true do - # Given some suppliers - s1 = create(:supplier_enterprise) - s2 = create(:supplier_enterprise) - s3 = create(:supplier_enterprise) - - # And some of those suppliers have a product - create(:product, :supplier => s1) - create(:product, :supplier => s3) - - # When I go to the suppliers listing page - visit spree.root_path - click_on "Edible garden" - click_button 'Browse All Suppliers' - - # Then I should see a list containing all the suppliers - page.should have_selector '#content a', :text => s1.name - page.should have_selector '#content a', :text => s2.name - page.should have_selector '#content a', :text => s3.name - end - - scenario "viewing products provided by a supplier", :future => true do - # Given a supplier with a product - s1 = create(:supplier_enterprise, :name => 'Murrnong', :long_description => "

Hello, world!

") - p1 = create(:product, :supplier => s1) - - # And a different supplier with another product - s2 = create(:supplier_enterprise, :name => 'Red Herring') - p2 = create(:product, :supplier => s2) - - # When I select the first supplier - visit spree.root_path - click_on "Edible garden" - click_link s1.name - - # Then I should see the supplier details - page.should have_selector 'h2', :text => s1.name - page.should have_selector 'div.enterprise-description', :text => 'Hello, world!' - - # And I should see the first, but not the second product - page.should have_content p1.name - page.should_not have_content p2.name - end end From dc8418cb4cab59ad0444b8f650d2ceb82509c5c8 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 14:29:46 +1100 Subject: [PATCH 075/107] Removing the taxonomy spec, with a comment about some possibly redundant spree overrides --- app/helpers/spree/base_helper_decorator.rb | 4 + spec/features/consumer/taxonomy_spec.rb | 167 --------------------- 2 files changed, 4 insertions(+), 167 deletions(-) delete mode 100644 spec/features/consumer/taxonomy_spec.rb diff --git a/app/helpers/spree/base_helper_decorator.rb b/app/helpers/spree/base_helper_decorator.rb index 8da5adaeb1..8abdace556 100644 --- a/app/helpers/spree/base_helper_decorator.rb +++ b/app/helpers/spree/base_helper_decorator.rb @@ -1,5 +1,9 @@ module Spree BaseHelper.class_eval do + + # TODO can we delete this? + # Spree code we are overriding to render sidebar + # No longer rendering sidebar def taxons_tree(root_taxon, current_taxon, max_level = 1) return '' if max_level < 1 || root_taxon.children.empty? content_tag :ul, :class => 'taxons-list' do diff --git a/spec/features/consumer/taxonomy_spec.rb b/spec/features/consumer/taxonomy_spec.rb deleted file mode 100644 index 8b0667ec45..0000000000 --- a/spec/features/consumer/taxonomy_spec.rb +++ /dev/null @@ -1,167 +0,0 @@ -require 'spec_helper' - -feature %q{ - As a consumer - I want to see product counts (for my chosen distributor) next to each taxon - So that I can locate products (at my chosen distributor) -} do - include AuthenticationWorkflow - include WebHelper - - background do - create(:distributor_enterprise, :name => "Edible garden") - end - - # How should this work with distributors/order cycles? - # - No distributor or OC selected - all shown - # - Distributor selected - any from that distributor in any OC - # - OC selected - any in that OC from any distributor - # - Both selected - filter for both - - # Also keep specs for distributors outside order cycles. - - scenario "viewing product counts when no distributor or order cycle is selected", :future => true do - # Given some taxons and some products - taxonomy = Spree::Taxonomy.find_by_name('Products') || create(:taxonomy, :name => 'Products') - taxonomy_root = taxonomy.root - - taxon_one = create(:taxon, :name => 'Taxon one', :parent_id => taxonomy_root.id) - taxon_two = create(:taxon, :name => 'Taxon two', :parent_id => taxonomy_root.id) - taxon_three = create(:taxon, :name => 'Taxon three', :parent_id => taxonomy_root.id) - - 1.times { create(:product, :taxons => [taxon_one]) } - 2.times { create(:product, :taxons => [taxon_two]) } - 3.times { create(:product, :taxons => [taxon_three]) } - - # When I visit the home page - visit spree.root_path - - # and proceed to the shop front - click_on "Edible garden" - - # Then I should see product counts next to the taxons - page.should have_selector 'nav#taxonomies li', :text => 'Taxon one (1)' - page.should have_selector 'nav#taxonomies li', :text => 'Taxon two (2)' - page.should have_selector 'nav#taxonomies li', :text => 'Taxon three (3)' - end - - - scenario "viewing product counts when a distributor is selected", :future => true do - # Given some taxons and some products under distributors - taxonomy = Spree::Taxonomy.find_by_name('Products') || create(:taxonomy, :name => 'Products') - taxonomy_root = taxonomy.root - - taxon_one = create(:taxon, :name => 'Taxon one', :parent_id => taxonomy_root.id) - taxon_two = create(:taxon, :name => 'Taxon two', :parent_id => taxonomy_root.id) - taxon_three = create(:taxon, :name => 'Taxon three', :parent_id => taxonomy_root.id) - - my_distributor = create(:distributor_enterprise, :name => 'My Distributor') - other_distributor = create(:distributor_enterprise, :name => 'Other Distributor') - - 1.times { create(:product, :taxons => [taxon_one], :distributors => [other_distributor]) } - 2.times { create(:product, :taxons => [taxon_two], :distributors => [other_distributor]) } - 2.times { create(:product, :taxons => [taxon_three], :distributors => [other_distributor]) } - 2.times { create(:product, :taxons => [taxon_three], :distributors => [my_distributor]) } - - p = create(:product, :taxons => [taxon_one]) - oc = create(:simple_order_cycle, distributors: [my_distributor], variants: [p.master]) - - # When I visit the home page and select my distributor - visit spree.select_distributor_order_path(my_distributor) - within('nav#filters') { click_link my_distributor.name } - page.should have_content 'You are shopping at My Distributor' - - # Then I should see distributor-scoped product counts next to the taxons - page.should have_selector 'nav#taxonomies li', :text => 'Taxon one (1)' - page.should have_selector 'nav#taxonomies li', :text => 'Taxon two (0)' - page.should have_selector 'nav#taxonomies li', :text => 'Taxon three (2)' - end - - - describe "selecting an order cycle" do - - before(:each) do - OrderCyclesHelper.class_eval do - def order_cycles_enabled? - true - end - end - end - - scenario "viewing product counts when an order cycle is selected", :future => true do - # Given some taxons and some products and some order cycles - taxonomy = Spree::Taxonomy.find_by_name('Products') || create(:taxonomy, :name => 'Products') - taxonomy_root = taxonomy.root - - taxon_one = create(:taxon, :name => 'Taxon one', :parent_id => taxonomy_root.id) - taxon_two = create(:taxon, :name => 'Taxon two', :parent_id => taxonomy_root.id) - taxon_three = create(:taxon, :name => 'Taxon three', :parent_id => taxonomy_root.id) - - supplier = create(:supplier_enterprise, :name => 'My Supplier') - distributor = create(:distributor_enterprise, :name => 'My Distributor') - - t1p1 = create(:product, :taxons => [taxon_one], :distributors => [distributor]) - t2p1 = create(:product, :taxons => [taxon_two], :distributors => [distributor]) - t2p2 = create(:product, :taxons => [taxon_two], :distributors => [distributor]) - t3p1 = create(:product, :taxons => [taxon_three], :distributors => [distributor]) - t3p2 = create(:product, :taxons => [taxon_three], :distributors => [distributor]) - - oc1 = create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], variants: [t1p1.master, t2p1.master, t2p2.master]) - oc2 = create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], variants: [t3p1.master, t3p2.master]) - - # When I visit the home page and select my order cycle - visit root_path - click_on "Edible garden" - choose oc2.name - click_button 'Choose Order Cycle' - page.should have_content 'Your order cycle has been selected.' - - # Then I should see order cycle-scoped product counts next to the taxons - page.should have_selector 'nav#taxonomies li', :text => 'Taxon one (0)' - page.should have_selector 'nav#taxonomies li', :text => 'Taxon two (0)' - page.should have_selector 'nav#taxonomies li', :text => 'Taxon three (2)' - end - - scenario "viewing product counts when both a distributor and an order cycle are selected", :future => true do - # Given some taxons and some products under distributors - taxonomy = Spree::Taxonomy.find_by_name('Products') || create(:taxonomy, :name => 'Products') - taxonomy_root = taxonomy.root - - taxon_one = create(:taxon, :name => 'Taxon one', :parent_id => taxonomy_root.id) - taxon_two = create(:taxon, :name => 'Taxon two', :parent_id => taxonomy_root.id) - taxon_three = create(:taxon, :name => 'Taxon three', :parent_id => taxonomy_root.id) - - supplier = create(:supplier_enterprise, :name => 'My Supplier') - my_distributor = create(:distributor_enterprise, :name => 'My Distributor') - other_distributor = create(:distributor_enterprise, :name => 'Other Distributor') - - p1 = create(:product, :taxons => [taxon_one]) - p2 = create(:product, :taxons => [taxon_two]) - p3 = create(:product, :taxons => [taxon_three]) - p4 = create(:product, :taxons => [taxon_one]) - p5 = create(:product, :taxons => [taxon_two]) - - oc1 = create(:simple_order_cycle, suppliers: [supplier]) - oc2 = create(:simple_order_cycle, suppliers: [supplier]) - create(:exchange, order_cycle: oc1, sender: oc1.coordinator, receiver: my_distributor, variants: [p1.master]) - create(:exchange, order_cycle: oc2, sender: oc2.coordinator, receiver: my_distributor, variants: [p2.master]) - create(:exchange, order_cycle: oc1, sender: oc1.coordinator, receiver: other_distributor, variants: [p3.master]) - create(:exchange, order_cycle: oc2, sender: oc2.coordinator, receiver: other_distributor, variants: [p4.master, p5.master]) - - # When I visit the home page and select my distributor and order cycle - visit spree.select_distributor_order_path(my_distributor) - within('nav#filters') { click_link my_distributor.name } - page.should have_content 'You are shopping at My Distributor' - visit root_path - click_on "Edible garden" - choose oc2.name - click_button 'Choose Order Cycle' - page.should have_content 'Your order cycle has been selected.' - - # Then I should see distributor- and order-cycle-scoped product counts next to the taxons - page.should have_selector 'nav#taxonomies li', :text => 'Taxon one (0)' - page.should have_selector 'nav#taxonomies li', :text => 'Taxon two (1)' - page.should have_selector 'nav#taxonomies li', :text => 'Taxon three (0)' - end - end -end From 09e6caf039ec80287b4bc17ecf643d34aee4679e Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 14:34:51 +1100 Subject: [PATCH 076/107] Archiving and disabling old checkout spec --- doc/outstanding_shopping_tests | 50 +++++++++++++++++++ .../features/consumer/checkout_spec.rb | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 doc/outstanding_shopping_tests rename spec/{ => archive}/features/consumer/checkout_spec.rb (99%) diff --git a/doc/outstanding_shopping_tests b/doc/outstanding_shopping_tests new file mode 100644 index 0000000000..66716a9c18 --- /dev/null +++ b/doc/outstanding_shopping_tests @@ -0,0 +1,50 @@ + + +Checkout: + it "displays correct distribution charges on checkout" + it "sends a confirmation email on successful checkout" + it "copies the addresses from a previous order" (controller test) + +Cart: + it "displays correct distribution charges on the cart" + +Shop: + it "shows nothing when there is no future order cycle" + scenario "order cycle expires mid-order" (see below) + + it "does not allow the user to add a product from a distributor that cannot supply the cart's products" + + add_to_cart_spec: + adapt: scenario "adding a product to the cart for a group buy" + adapt: scenario "adding a product with variants to the cart for a group buy" + adapt: scenario "adding a product to cart that is not a group buy does not show max quantity field" do + adapt: scenario "adding a product with a max quantity less than quantity results in max_quantity==quantity" do + + + +scenario "order cycle expires mid-order" do + d = create(:distributor_enterprise, + name: 'Green Grass', email: 'd@example.com', phone: '1029 3847') + create_enterprise_group_for d + p = create(:simple_product) + oc = create(:simple_order_cycle, name: 'oc', distributors: [d], variants: [p.master]) + + # When I select an order cycle and add a product to my cart + visit spree.root_path + click_link 'Green Grass' + visit enterprise_path d + click_link p.name + click_button 'Add To Cart' + + # And the order cycle expires and I load a page + Timecop.travel(oc.orders_close_at + 1.day) do + + visit enterprise_path d + + # Then I should see an expiry message + page.should have_content "Sorry, orders for this order cycle closed 1 day ago! Please contact your hub directly to see if they can accept late orders." + page.should have_content d.email + page.should have_content d.phone + end +end + diff --git a/spec/features/consumer/checkout_spec.rb b/spec/archive/features/consumer/checkout_spec.rb similarity index 99% rename from spec/features/consumer/checkout_spec.rb rename to spec/archive/features/consumer/checkout_spec.rb index d606eb54fc..2c42a023e7 100644 --- a/spec/features/consumer/checkout_spec.rb +++ b/spec/archive/features/consumer/checkout_spec.rb @@ -4,7 +4,7 @@ feature %q{ As a consumer I want to select a distributor for collection So that I can pick up orders from the closest possible location -} do +}, skip: true do include AuthenticationWorkflow include WebHelper From a1c8a35e79521197512947a179eef319301586a5 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 15:19:15 +1100 Subject: [PATCH 077/107] Disabling CMS specs --- spec/features/consumer/cms_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/consumer/cms_spec.rb b/spec/features/consumer/cms_spec.rb index de52f2973e..0f25bc549c 100644 --- a/spec/features/consumer/cms_spec.rb +++ b/spec/features/consumer/cms_spec.rb @@ -4,7 +4,7 @@ feature %q{ In order to learn about food As a user of the site I want to see static content pages -} do +}, skip: true do include AuthenticationWorkflow include WebHelper let(:d) { create(:distributor_enterprise, :name => 'Edible garden') } From 946913fc539e48121bedd2df75c087082a46f1ee Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 15:19:58 +1100 Subject: [PATCH 078/107] Better documentation --- doc/outstanding_shopping_tests | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/outstanding_shopping_tests b/doc/outstanding_shopping_tests index 66716a9c18..3d8974fb1c 100644 --- a/doc/outstanding_shopping_tests +++ b/doc/outstanding_shopping_tests @@ -15,6 +15,8 @@ Shop: it "does not allow the user to add a product from a distributor that cannot supply the cart's products" add_to_cart_spec: + AFTER DONE remove add_to_cart spec + MOVE to shopping_group_buy_spec.rb adapt: scenario "adding a product to the cart for a group buy" adapt: scenario "adding a product with variants to the cart for a group buy" adapt: scenario "adding a product to cart that is not a group buy does not show max quantity field" do From 49dc371219f8a56a1521b8f48af0eb87bd2ce1a2 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 15:25:36 +1100 Subject: [PATCH 079/107] Adding the missing bulk buy spec --- doc/outstanding_shopping_tests | 9 --------- spec/features/consumer/shopping/shopping_spec.rb | 12 ++++++++++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/doc/outstanding_shopping_tests b/doc/outstanding_shopping_tests index 3d8974fb1c..4bfcc0c2aa 100644 --- a/doc/outstanding_shopping_tests +++ b/doc/outstanding_shopping_tests @@ -13,15 +13,6 @@ Shop: scenario "order cycle expires mid-order" (see below) it "does not allow the user to add a product from a distributor that cannot supply the cart's products" - - add_to_cart_spec: - AFTER DONE remove add_to_cart spec - MOVE to shopping_group_buy_spec.rb - adapt: scenario "adding a product to the cart for a group buy" - adapt: scenario "adding a product with variants to the cart for a group buy" - adapt: scenario "adding a product to cart that is not a group buy does not show max quantity field" do - adapt: scenario "adding a product with a max quantity less than quantity results in max_quantity==quantity" do - scenario "order cycle expires mid-order" do diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index e6cb8c5d0c..67150f3f62 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -233,6 +233,7 @@ feature "As a consumer I want to shop with a distributor", js: true do describe "group buy products" do let(:oc) { create(:simple_order_cycle, distributors: [distributor]) } let(:product) { create(:simple_product, group_buy: true, on_hand: 15) } + let(:product2) { create(:simple_product, group_buy: false) } describe "without variants" do before do @@ -241,6 +242,7 @@ feature "As a consumer I want to shop with a distributor", js: true do it "should show group buy input" do page.should have_field "variant_attributes[#{product.master.id}][max_quantity]", :visible => true + page.should_not have_field "variant_attributes[#{product2.master.id}][max_quantity]", :visible => true end it "should save group buy data to ze cart" do @@ -252,6 +254,16 @@ feature "As a consumer I want to shop with a distributor", js: true do li.max_quantity.should == 9 li.quantity.should == 5 end + + scenario "adding a product with a max quantity less than quantity results in max_quantity==quantity" do + fill_in "variants[#{product.master.id}]", with: 5 + fill_in "variant_attributes[#{product.master.id}][max_quantity]", with: 1 + first("form.custom > input.button.right").click + page.should have_content product.name + li = Spree::Order.order(:created_at).last.line_items.order(:created_at).last + li.max_quantity.should == 5 + li.quantity.should == 5 + end end describe "with variants on the product" do From ba770d8eacd9dbf5fa9eb1d557d63c126301cc3f Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 15:28:48 +1100 Subject: [PATCH 080/107] Removing the add to cart spec --- spec/features/consumer/add_to_cart_spec.rb | 206 --------------------- 1 file changed, 206 deletions(-) delete mode 100644 spec/features/consumer/add_to_cart_spec.rb diff --git a/spec/features/consumer/add_to_cart_spec.rb b/spec/features/consumer/add_to_cart_spec.rb deleted file mode 100644 index 7e7650a9b9..0000000000 --- a/spec/features/consumer/add_to_cart_spec.rb +++ /dev/null @@ -1,206 +0,0 @@ -require 'spec_helper' - -feature %q{ - As a consumer - I want to choose a distributor when adding products to my cart - So that I can avoid making an order from many different distributors -} do - include AuthenticationWorkflow - include WebHelper - - context "with product distribution" do - scenario "adding a product to the cart with no distributor chosen" do - # Given a product and some distributors - d1 = create(:distributor_enterprise) - d2 = create(:distributor_enterprise) - p = create(:product, :distributors => [d1]) - create(:product, :distributors => [d2]) - - # When I add an item to my cart without choosing a distributor - visit spree.product_path p - click_button 'Add To Cart' - - # Then I should see an error message - page.should have_content "That product is not available from the chosen distributor or order cycle" - - # And the product should not have been added to my cart - Spree::Order.last.line_items.should be_empty - end - - context "adding a subsequent product to the cart" do - it "does not allow the user to add a product from a distributor that cannot supply the cart's products" do - # Given two products, each at a different distributor - d1 = create(:distributor_enterprise) - d2 = create(:distributor_enterprise) - p1 = create(:product, :distributors => [d1]) - p2 = create(:product, :distributors => [d2]) - - # When I add one of them to my cart - select_distribution d1 - visit spree.product_path p1 - click_button 'Add To Cart' - - # And I attempt to add the other - visit spree.product_path p2 - - # Then I should not be allowed to add the product - page.should_not have_selector "button#add-to-cart-button" - page.should have_content "Please complete your order at #{d1.name} before shopping with another distributor." - end - end - - describe 'with order cycles disabled' do - before(:each) do - OrderCyclesHelper.class_eval do - def order_cycles_enabled? - false - end - end - end - - scenario "should not show order cycle details when adding to cart" do - # Given a product and a distributor - d = create(:distributor_enterprise) - p = create(:product, :price => 12.34) - - # When I add an item to my cart - visit spree.product_path p - - page.should_not have_selector '#order_cycle_id option' - end - - end - end - - context "with order cycle distribution" do - before(:each) do - OrderCyclesHelper.class_eval do - def order_cycles_enabled? - true - end - end - end - - scenario "adding a product to the cart with no distribution chosen" do - # Given a product and some distributors - d1 = create(:distributor_enterprise) - d2 = create(:distributor_enterprise) - p1 = create(:product) - p2 = create(:product) - create(:simple_order_cycle, :distributors => [d1], :variants => [p1.master]) - create(:simple_order_cycle, :distributors => [d2], :variants => [p2.master]) - - # When I add an item to my cart without choosing a distributor or order cycle - visit spree.product_path p1 - click_button 'Add To Cart' - - # Then I should see an error message - page.should have_content "Please choose an order cycle for this order." - - # And the product should not have been added to my cart - Spree::Order.last.line_items.should be_empty - end - - scenario "adding the first product to the cart" do - # Given a product and a distributor - d = create(:distributor_enterprise) - p = create(:product, :price => 12.34) - oc = create(:simple_order_cycle, :distributors => [d], :variants => [p.master]) - - # When I add an item to my cart - select_distribution d, oc - visit spree.product_path p - click_button 'Add To Cart' - - # Then the correct totals should be displayed - page.should have_selector 'span.item-total', :text => '$12.34' - - # TODO: Test these when order cycle fees is implemented - # page.should have_selector 'span.distribution-total', :text => '$1.23' - # page.should have_selector 'span.grand-total', :text => '$13.57' - - # And the item should be in my cart - order = Spree::Order.last - line_item = order.line_items.first - line_item.product.should == p - - # And my order should have its distributor and order cycle set to the chosen ones - order.distributor.should == d - order.order_cycle.should == oc - end - end - - context "group buys" do - scenario "adding a product to the cart for a group buy" do - # Given a group buy product and a distributor - d = create(:distributor_enterprise) - p = create(:product, :distributors => [d], :group_buy => true) - - # When I add the item to my cart - select_distribution d - visit spree.product_path p - fill_in "variants_#{p.master.id}", :with => 2 - fill_in "variant_attributes_#{p.master.id}_max_quantity", :with => 3 - click_button 'Add To Cart' - - # Then the item should be in my cart with correct quantities - order = Spree::Order.last - li = order.line_items.first - li.product.should == p - li.quantity.should == 2 - li.max_quantity.should == 3 - end - - scenario "adding a product with variants to the cart for a group buy" do - # Given a group buy product with variants and a distributor - d = create(:distributor_enterprise) - p = create(:product, :distributors => [d], :group_buy => true) - create(:variant, :product => p) - - # When I add the item to my cart - select_distribution d - visit spree.product_path p - fill_in "quantity", :with => 2 - fill_in "max_quantity", :with => 3 - click_button 'Add To Cart' - - # Then the item should be in my cart with correct quantities - order = Spree::Order.last - li = order.line_items.first - li.product.should == p - li.quantity.should == 2 - li.max_quantity.should == 3 - end - - scenario "adding a product to cart that is not a group buy does not show max quantity field" do - # Given a group buy product and a distributor - d = create(:distributor_enterprise) - p = create(:product, :distributors => [d], :group_buy => false) - - # When I view the add to cart form, there should not be a max quantity field - visit spree.product_path p - - page.should_not have_selector "#variant_attributes_#{p.master.id}_max_quantity" - end - - scenario "adding a product with a max quantity less than quantity results in max_quantity==quantity" do - # Given a group buy product and a distributor - d = create(:distributor_enterprise) - p = create(:product, :distributors => [d], :group_buy => true) - - # When I add the item to my cart - select_distribution d - visit spree.product_path p - fill_in "variants_#{p.master.id}", :with => 2 - fill_in "variant_attributes_#{p.master.id}_max_quantity", :with => 1 - click_button 'Add To Cart' - - # Then the item should be in my cart with correct quantities - order = Spree::Order.last - li = order.line_items.first - li.product.should == p - li.quantity.should == 2 - li.max_quantity.should == 2 - end - end -end From 8e363e5fbba18311da77b8863b582565630ffe4f Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 15:40:57 +1100 Subject: [PATCH 081/107] Caching values for variants --- app/views/shop/shop/_variant.html.haml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/shop/shop/_variant.html.haml b/app/views/shop/shop/_variant.html.haml index 73b8b80f1e..3dade13410 100644 --- a/app/views/shop/shop/_variant.html.haml +++ b/app/views/shop/shop/_variant.html.haml @@ -7,12 +7,14 @@ value: nil, min: 0, max: "{{variant.on_demand && 9999 || variant.count_on_hand }}", - name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"} + name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}", + "ng-model" => "variant.quantity"} %td.group_buy %span{"ng-show" => "product.group_buy"} %input{type: :number, min: 0, max: "{{variant.on_demand && 9999 || variant.count_on_hand }}", - name: "variant_attributes[{{variant.id}}][max_quantity]"} + name: "variant_attributes[{{variant.id}}][max_quantity]", + "ng-model" => "variant.max_quantity"} %td.price.text-right {{ variant.price | currency }} From c6f1d43dbdf6999e5cf5e7466538594a76703cfa Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Wed, 19 Mar 2014 16:09:45 +1100 Subject: [PATCH 082/107] Reworking the shipping confirmation --- app/models/spree/shipping_method_decorator.rb | 2 +- ...rise_distribution_charges.html.haml.deface | 8 -------- .../spree/shared/_order_details.html.haml | 19 ++++++++++++------- 3 files changed, 13 insertions(+), 16 deletions(-) delete mode 100644 app/overrides/spree/shared/_order_details/summarise_distribution_charges.html.haml.deface diff --git a/app/models/spree/shipping_method_decorator.rb b/app/models/spree/shipping_method_decorator.rb index 88e9e626e7..a9628b3c73 100644 --- a/app/models/spree/shipping_method_decorator.rb +++ b/app/models/spree/shipping_method_decorator.rb @@ -35,6 +35,6 @@ Spree::ShippingMethod.class_eval do end def adjustment_label - 'Delivery' + 'Shipping' end end diff --git a/app/overrides/spree/shared/_order_details/summarise_distribution_charges.html.haml.deface b/app/overrides/spree/shared/_order_details/summarise_distribution_charges.html.haml.deface deleted file mode 100644 index c4e7ed5dfd..0000000000 --- a/app/overrides/spree/shared/_order_details/summarise_distribution_charges.html.haml.deface +++ /dev/null @@ -1,8 +0,0 @@ -/ replace_contents 'tfoot#order-charges' - -- checkout_adjustments_for_summary(@order).each do |adjustment| - %tr.total - %td{:colspan => "4"} - %strong= adjustment.label - %td.total - %span= adjustment.display_amount.to_html diff --git a/app/views/spree/shared/_order_details.html.haml b/app/views/spree/shared/_order_details.html.haml index 37a55be48b..559a0227f1 100644 --- a/app/views/spree/shared/_order_details.html.haml +++ b/app/views/spree/shared/_order_details.html.haml @@ -16,6 +16,7 @@ .columns.large-2 %h6 = t(:shipping_method) + \: = link_to "(#{t(:edit)})", checkout_state_path(:delivery) unless @order.completed? .delivery = order.shipping_method.name @@ -58,6 +59,7 @@ %td{"data-hook" => "order_item_qty"}= item.quantity %td.total{"data-hook" => "order_item_total"} %span= item.display_amount.to_html + %tfoot#order-total{"data-hook" => "order_details_total"} %tr.total %td{colspan: "4"} @@ -66,27 +68,30 @@ \: %td.total %span#order_total= @order.display_total.to_html + - if order.price_adjustment_totals.present? %tfoot#price-adjustments{"data-hook" => "order_details_price_adjustments"} - @order.price_adjustment_totals.each do |key, total| %tr.total %td{colspan: "4"} - %strong= key + %strong + = key + \: %td.total %span= total + %tfoot#subtotal{"data-hook" => "order_details_subtotal"} %tr#subtotal-row.total %td{colspan: "4"} %b - = t(:subtotal) - \: + Produce: %td.total %span= @order.display_item_total.to_html + %tfoot#order-charges{"data-hook" => "order_details_adjustments"} - - @order.adjustments.eligible.each do |adjustment| - - next if (adjustment.originator_type == 'Spree::TaxRate') and (adjustment.amount == 0) + - checkout_adjustments_for_summary(@order).reverse_each do |adjustment| %tr.total - %td{colspan: "4"} - %strong= adjustment.label + %td{:colspan => "4"} + %strong= adjustment.label + ":" %td.total %span= adjustment.display_amount.to_html From 510333288ce2d7d3f35a5c8462da632b348deaa4 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Sun, 2 Mar 2014 13:05:15 +1100 Subject: [PATCH 083/107] first steps of new design --- app/views/admin/enterprises/_form.html.haml | 89 +++++++-------------- 1 file changed, 31 insertions(+), 58 deletions(-) diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index 46e7494355..c6d4e3bfdb 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -5,63 +5,36 @@ %tr{"data-hook" => "name"} %td Name: %td= f.text_field :name - %tr{"data-hook" => "description"} - %td Description: - %td= f.text_field :description - %tr{'data-hook' => "long_description"} - %td Extended Description: - %td= f.text_area :long_description, :class => 'rich_text' - %tr{'data-hook' => "is_primary_producer"} - %td Primary Producer? - %td= f.check_box :is_primary_producer - %tr{'data-hook' => "is_distributor"} - %td Distributor? - %td= f.check_box :is_distributor - %tr{'data-hook' => "enterprise_group_ids"} - %td Groups - %td= f.collection_select :group_ids, EnterpriseGroup.all, :id, :name, {}, {class: "select2 fullwidth", multiple: true} - %tr{"data-hook" => "contact"} - %td Contact Person: - %td= f.text_field :contact - %tr{"data-hook" => "phone"} - %td Phone: - %td= f.text_field :phone - %tr{"data-hook" => "email"} - %td Email: - %td= f.text_field :email - %tr{"data-hook" => "website"} - %td Website: - %td= f.text_field :website - %tr{"data-hook" => "twitter"} - %td Twitter: - %td= f.text_field :twitter - %tr{"data-hook" => "abn"} - %td ABN: - %td= f.text_field :abn - %tr{"data-hook" => "acn"} - %td ACN: - %td= f.text_field :acn - %tr{"data-hook" => "logo"} - %td Logo: + %tr %td - = f.file_field :logo - = image_tag @object.logo.url - %tr{"data-hook" => "promo"} - %td Promo Image: %td - = f.file_field :promo_image - = image_tag @object.promo_image.url -%fieldset - %legend Address - %table - = f.fields_for :address do |address_form| - = render 'spree/admin/shared/address_form_simple', :f => address_form -%fieldset - %legend Pickup details - %table{"data-hook" => "distributors_pickup_details"} - %tr{"data-hook" => "next_collection_at"} - %td Next collection date/time: - %td= f.text_field :next_collection_at - %tr{"data-hook" => "pickup_times"} - %td Regular pickup times: - %td= f.text_field :pickup_times + / what about the data-hook? + / {'data-hook' => "is_primary_producer"} + = f.check_box :is_primary_producer + Producer + / %tr{'data-hook' => "is_distributor"} + / here should be a gap + = f.check_box :is_distributor + Distributor + / this should be at the right side + %a Tell me more? + %tr + %th Address + = f.fields_for :address do |af| + %tr + %td Address + %td= af.text_field :address1 + %td Address (cont.) + %td= af.text_field :address2 + %tr + %td Suburb + %td= af.text_field :city + %td Postcode + %td= af.text_field :zipcode + %tr + %td + %td + = af.collection_select(:state_id, af.object.country.states, :id, :name) + = af.collection_select(:country_id, available_countries, :id, :name) + + From 6f1d41e0200fdcf0f21dffe28ba6bd79d10c383b Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 4 Mar 2014 16:52:48 +1100 Subject: [PATCH 084/107] new selection of input fields for enterprises form, not all datafields working yet --- app/views/admin/enterprises/_form.html.haml | 47 +++++++++++++++++++++ app/views/admin/enterprises/new.html.erb | 1 + 2 files changed, 48 insertions(+) diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index c6d4e3bfdb..305ef8b2f8 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -36,5 +36,52 @@ %td = af.collection_select(:state_id, af.object.country.states, :id, :name) = af.collection_select(:country_id, available_countries, :id, :name) + %tr + %th Contact Details + %th + %th Enterprise Details + %th + %tr + %td Name + %td= f.text_field :contact + %td ABN / ACN + %td= f.text_field :abn + %tr + %td Email + %td= f.text_field :email + %td Website + %td= f.text_field :website + %tr + %td Phone + %td= f.text_field :phone + %td Facebook + %td= f.text_field :twitter + / TODO: facebook data field + %tr + %td + %td + %td Twitter + %td= f.text_field :twitter + %tr + %th About Us + %tr + %td Short Description + %td= f.text_field :description + %tr + %td About Us + %td= f.text_area :long_description, :class => 'rich_text' + %tr + %td How does it work? + %td= f.text_area :long_description, :class => 'rich_text' + / TODO: hub explanation data field + %tr + %td Logo + %td + = f.file_field :logo + = image_tag @object.logo.url + %td Promo + %td + = f.file_field :promo_image + = image_tag @object.promo_image.url diff --git a/app/views/admin/enterprises/new.html.erb b/app/views/admin/enterprises/new.html.erb index 92682f64d8..a78a75c81e 100644 --- a/app/views/admin/enterprises/new.html.erb +++ b/app/views/admin/enterprises/new.html.erb @@ -2,5 +2,6 @@ <%= form_for [main_app, :admin, @enterprise] do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> + <%= render :partial => 'spree/admin/shared/new_resource_links' %> <% end %> From 4c0885277d513414002aa475d330a6d5178d1d66 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 4 Mar 2014 19:15:03 +1100 Subject: [PATCH 085/107] started to redesign form with css --- app/views/admin/enterprises/_form.html.haml | 66 ++++++++++++++++----- app/views/admin/enterprises/new.html.erb | 2 +- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index 305ef8b2f8..5e105fdbaf 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -1,23 +1,57 @@ - content_for :head do = render 'shared/cms_elrte_head' +%style + div.enterprise-form { + border: 1px dashed grey; + margin-bottom: 1em; + padding: 1em; + } + fieldset { + font-size: 100%; + } + label { + text-transform:none; + margin-right: 1em; + } + input { + margin: 0.5em 0em; + } + label.leftcol { + display: inline-block; + width: 10em; + } + label.leftcol+input { + margin-right: 3em; + } + input#enterprise_is_primary_producer { + margin-left: 10em; + } + div.input-compound { + display: inline-block; + } + +%div.enterprise-form{data-hook: "distributors"} + %label.leftcol{for: "enterprise_name", 'data-hook' => "name"} Name + = f.text_field :name + %div + = f.check_box :is_primary_producer + %label{for: "enterprise_is_primary_producer", 'data-hook' => "is_primary_producer"} Producer + = f.check_box :is_distributor + %label{for: "enterprise_is_distributor", 'data-hook' => "is_distributor"} Hub + %a Tell me more? + = f.fields_for :address do |af| + %fieldset + %legend Address + %label.leftcol{for: "enterprise_address_attributes_address1"} Address + = af.text_field :address1 + %div.input-compound + %label.leftcol{for: "enterprise_address_attributes_address2"} Address (cont.) + = af.text_field :address2 + + + %table{"data-hook" => "distributors"} - %tr{"data-hook" => "name"} - %td Name: - %td= f.text_field :name - %tr - %td - %td - / what about the data-hook? - / {'data-hook' => "is_primary_producer"} - = f.check_box :is_primary_producer - Producer - / %tr{'data-hook' => "is_distributor"} - / here should be a gap - = f.check_box :is_distributor - Distributor - / this should be at the right side - %a Tell me more? %tr %th Address = f.fields_for :address do |af| diff --git a/app/views/admin/enterprises/new.html.erb b/app/views/admin/enterprises/new.html.erb index a78a75c81e..890c6009cd 100644 --- a/app/views/admin/enterprises/new.html.erb +++ b/app/views/admin/enterprises/new.html.erb @@ -2,6 +2,6 @@ <%= form_for [main_app, :admin, @enterprise] do |f| %> <%= render :partial => 'form', :locals => { :f => f } %> - + <%= render :partial => 'spree/admin/shared/new_resource_links' %> <% end %> From e9506d1571300dbda0989258dc7d009db86bf421 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 5 Mar 2014 16:26:59 +1100 Subject: [PATCH 086/107] complete form with columns. two things to ask Kerstin. --- app/views/admin/enterprises/_form.html.haml | 241 +++++++++++--------- 1 file changed, 131 insertions(+), 110 deletions(-) diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index 5e105fdbaf..dcdedfe830 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -2,120 +2,141 @@ = render 'shared/cms_elrte_head' %style - div.enterprise-form { - border: 1px dashed grey; - margin-bottom: 1em; - padding: 1em; - } - fieldset { - font-size: 100%; - } - label { - text-transform:none; - margin-right: 1em; - } - input { - margin: 0.5em 0em; - } - label.leftcol { - display: inline-block; - width: 10em; - } - label.leftcol+input { - margin-right: 3em; - } - input#enterprise_is_primary_producer { - margin-left: 10em; - } - div.input-compound { - display: inline-block; + div.row input[type=text], div.row select { + width: 100%; } -%div.enterprise-form{data-hook: "distributors"} - %label.leftcol{for: "enterprise_name", 'data-hook' => "name"} Name - = f.text_field :name - %div - = f.check_box :is_primary_producer - %label{for: "enterprise_is_primary_producer", 'data-hook' => "is_primary_producer"} Producer - = f.check_box :is_distributor - %label{for: "enterprise_is_distributor", 'data-hook' => "is_distributor"} Hub - %a Tell me more? +.enterprise-form + .row + .alpha.six.columns + .two.columns.alpha + = f.label :name + .four.columns.omega + = f.text_field :name + .row + .alpha.two.columns   + .omega.ten.columns + .alpha.two.columns + = f.check_box :is_primary_producer + = f.label :is_primary_producer, 'Producer' + .omega.two.columns + = f.check_box :is_distributor + = f.label :is_distributor, 'Hub' + .omega.two.columns + %a Tell me more? + / TODO: link for Tell me more = f.fields_for :address do |af| %fieldset %legend Address - %label.leftcol{for: "enterprise_address_attributes_address1"} Address - = af.text_field :address1 - %div.input-compound - %label.leftcol{for: "enterprise_address_attributes_address2"} Address (cont.) - = af.text_field :address2 - + .row + .six.columns.alpha + .alpha.two.columns + = af.label :address1 + .omega.four.columns + = af.text_field :address1 + .six.columns.omega + .alpha.two.columns + = af.label :address2 + .omega.four.columns + = af.text_field :address2 + .row + .six.columns.alpha + .alpha.two.columns + = af.label :city, 'Suburb' + .omega.four.columns + = af.text_field :city + .six.columns.omega + .alpha.two.columns + = af.label :zipcode, 'Postcode' + .omega.four.columns + = af.text_field :zipcode + .row + .alpha.two.columns   + .omega.three.columns + = af.collection_select(:state_id, af.object.country.states, :id, :name) + .row + .alpha.two.columns   + .omega.three.columns + = af.collection_select(:country_id, available_countries, :id, :name) + .row + .alpha.six.columns + %fieldset + %legend Contact Details + .row + .alpha.two.columns + = f.label :contact, 'Name' + .omega.four.columns + = f.text_field :contact + .row + .alpha.two.columns + = f.label :email + .omega.four.columns + = f.text_field :email + .row + .alpha.two.columns + = f.label :phone + .omega.four.columns + = f.text_field :phone + .omega.six.columns + %fieldset + %legend Enterprise Details + .row + .alpha.two.columns + = f.label :abn, 'ABN / ACN' + / TODO: figure out how to work with one field for both + / ABN has 11 digits + / ACN has 9 digits + .omega.four.columns + = f.text_field :abn + .row + .alpha.two.columns + = f.label :website + .omega.four.columns + = f.text_field :website + /.row + / .alpha.two.columns + / = f.label :twitter, 'Facebook' + / / TODO: facebook data field + / .omega.four.columns + / = f.text_field :twitter + .row + .alpha.two.columns + = f.label :twitter + .omega.four.columns + = f.text_field :twitter + %fieldset + %legend About Us + .row + .alpha.two.columns + = f.label :description, 'Short Description' + .omega.four.columns + = f.text_field :description + .row + .alpha.two.columns + = f.label :long_description, 'About Us' + %br + Tell us about yourself. This information appears on your public profile (under "About Us") + .omega.eight.columns + = f.text_area :long_description, class: 'rich_text', placeholder: 'Tell us about yourself. This information appears on your public profile (under "About Us")' + .row + .alpha.two.columns + = f.label :distributor_info, 'How does it work?' + %br + Hub only: Explain your distribution offer/s - this is more detailed information that the user can access by clicking on "How does it work?" + .omega.eight.columns + = f.text_area :distributor_info, class: 'rich_text', placeholder: 'Hub only: Explain your distribution offer/s - this is more detailed information that the user can access by clicking on "How does it work?"' + / TODO: how to use placeholder with richt text? + / TODO: editor breaks scrolling with arrow keys + .row + .alpha.two.columns + = f.label :logo + .omega.four.columns + = image_tag @object.logo.url + = f.file_field :logo + .omega.two.columns + = f.label :promo_image + .omega.four.columns + = image_tag @object.promo_image.url + = f.file_field :pro_image -%table{"data-hook" => "distributors"} - %tr - %th Address - = f.fields_for :address do |af| - %tr - %td Address - %td= af.text_field :address1 - %td Address (cont.) - %td= af.text_field :address2 - %tr - %td Suburb - %td= af.text_field :city - %td Postcode - %td= af.text_field :zipcode - %tr - %td - %td - = af.collection_select(:state_id, af.object.country.states, :id, :name) - = af.collection_select(:country_id, available_countries, :id, :name) - %tr - %th Contact Details - %th - %th Enterprise Details - %th - %tr - %td Name - %td= f.text_field :contact - %td ABN / ACN - %td= f.text_field :abn - %tr - %td Email - %td= f.text_field :email - %td Website - %td= f.text_field :website - %tr - %td Phone - %td= f.text_field :phone - %td Facebook - %td= f.text_field :twitter - / TODO: facebook data field - %tr - %td - %td - %td Twitter - %td= f.text_field :twitter - %tr - %th About Us - %tr - %td Short Description - %td= f.text_field :description - %tr - %td About Us - %td= f.text_area :long_description, :class => 'rich_text' - %tr - %td How does it work? - %td= f.text_area :long_description, :class => 'rich_text' - / TODO: hub explanation data field - %tr - %td Logo - %td - = f.file_field :logo - = image_tag @object.logo.url - %td Promo - %td - = f.file_field :promo_image - = image_tag @object.promo_image.url - - From 052782678e9ada3db9e1a96be212fc61c2a7a8f7 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 5 Mar 2014 16:49:39 +1100 Subject: [PATCH 087/107] working design. TODOs are optional now --- app/views/admin/enterprises/_form.html.haml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index dcdedfe830..55bc499aa7 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -1,6 +1,7 @@ - content_for :head do = render 'shared/cms_elrte_head' +/ TODO move this CSS %style div.row input[type=text], div.row select { width: 100%; @@ -23,8 +24,8 @@ = f.check_box :is_distributor = f.label :is_distributor, 'Hub' .omega.two.columns - %a Tell me more? - / TODO: link for Tell me more + %span.with-tip{'data-powertip' => "Select 'Producer' if you are producing food like a farmer. And select 'Hub' if you are selling to end customers. You can do both."} + %a Tell me more? = f.fields_for :address do |af| %fieldset %legend Address @@ -82,12 +83,17 @@ %legend Enterprise Details .row .alpha.two.columns - = f.label :abn, 'ABN / ACN' + = f.label :abn, 'ABN' / TODO: figure out how to work with one field for both / ABN has 11 digits / ACN has 9 digits .omega.four.columns = f.text_field :abn + .row + .alpha.two.columns + = f.label :acn, 'ACN' + .omega.four.columns + = f.text_field :acn .row .alpha.two.columns = f.label :website From 6689b0d93f1f897f56d8f63a0542c2aac2d5b5b4 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 5 Mar 2014 17:06:44 +1100 Subject: [PATCH 088/107] more tooltips --- app/views/admin/enterprises/_form.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index 55bc499aa7..3c9e81e151 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -115,8 +115,8 @@ .row .alpha.two.columns = f.label :description, 'Short Description' - .omega.four.columns - = f.text_field :description + .omega.ten.columns + = f.text_field :description, placeholder: 'Just one or two sentences' .row .alpha.two.columns = f.label :long_description, 'About Us' @@ -140,7 +140,7 @@ = image_tag @object.logo.url = f.file_field :logo .omega.two.columns - = f.label :promo_image + = f.label :promo_image, class: 'with-tip', 'data-powertip' => 'This image is displayed in "About Us"' .omega.four.columns = image_tag @object.promo_image.url = f.file_field :pro_image From aa7211dc7f7464da59fc549610c00e5b5c6af621 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 5 Mar 2014 17:13:46 +1100 Subject: [PATCH 089/107] more tooltips --- app/views/admin/enterprises/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index 3c9e81e151..89f35902ee 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -24,7 +24,7 @@ = f.check_box :is_distributor = f.label :is_distributor, 'Hub' .omega.two.columns - %span.with-tip{'data-powertip' => "Select 'Producer' if you are producing food like a farmer. And select 'Hub' if you are selling to end customers. You can do both."} + %span.with-tip{'data-powertip' => "Select 'Producer' if you are a primary producer of food. And select 'Hub' if you are selling to end customers. You can do both."} %a Tell me more? = f.fields_for :address do |af| %fieldset From cc2ae2f464add119f268b77988a8e68b0e339cd0 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 5 Mar 2014 17:33:26 +1100 Subject: [PATCH 090/107] moved fullwidth form css --- .../stylesheets/admin/openfoodnetwork.css.scss | 7 +++++++ app/views/admin/enterprises/_form.html.haml | 12 +++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index 2e03a9e2eb..14feebae0d 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -126,3 +126,10 @@ table#listing_enterprise_groups { text-align: left; } } + +.fullwidth_inputs { + input[type=text], select { + width: 100%; + } +} + diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index 89f35902ee..b1153aec2e 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -1,13 +1,7 @@ - content_for :head do = render 'shared/cms_elrte_head' -/ TODO move this CSS -%style - div.row input[type=text], div.row select { - width: 100%; - } - -.enterprise-form +.fullwidth_inputs .row .alpha.six.columns .two.columns.alpha @@ -30,12 +24,12 @@ %fieldset %legend Address .row - .six.columns.alpha + .alpha.six.columns .alpha.two.columns = af.label :address1 .omega.four.columns = af.text_field :address1 - .six.columns.omega + .omega.six.columns .alpha.two.columns = af.label :address2 .omega.four.columns From 9181f0243a2b1f501129400f6c59f8eba518301a Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 5 Mar 2014 18:15:01 +1100 Subject: [PATCH 091/107] added old input fields, enterprises test passing --- app/views/admin/enterprises/_form.html.haml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index b1153aec2e..b658636517 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -8,6 +8,11 @@ = f.label :name .four.columns.omega = f.text_field :name + .omega.six.columns + .two.columns.alpha + = f.label :group_ids, 'Groups' + .four.columns.omega + = f.collection_select :group_ids, EnterpriseGroup.all, :id, :name, {}, {class: "select2 fullwidth", multiple: true} .row .alpha.two.columns   .omega.ten.columns @@ -139,4 +144,19 @@ = image_tag @object.promo_image.url = f.file_field :pro_image + %fieldset + %legend Pickup details + .row + .alpha.six.columns + .alpha.two.columns + = f.label :next_collection_at, 'Next collection date/time' + .omega.four.columns + = f.text_field :next_collection_at + .omega.six.columns + .alpha.two.columns + = f.label :pickup_times, 'Regular pickup times' + .omega.four.columns + = f.text_field :pickup_times + + From 7d30d8e28fe06173f9d6e5e7b81d7815e1c17834 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Thu, 6 Mar 2014 18:35:14 +1100 Subject: [PATCH 092/107] old deface files deleted. spec for enterprise form updated --- .../enterprises/_form/add_distributor_info.html.haml.deface | 4 ---- .../_form/rename_extended_description.html.haml.deface | 3 --- .../enterprises_distributor_info_rich_text_feature_spec.rb | 4 ++-- 3 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 app/overrides/admin/enterprises/_form/add_distributor_info.html.haml.deface delete mode 100644 app/overrides/admin/enterprises/_form/rename_extended_description.html.haml.deface diff --git a/app/overrides/admin/enterprises/_form/add_distributor_info.html.haml.deface b/app/overrides/admin/enterprises/_form/add_distributor_info.html.haml.deface deleted file mode 100644 index 83ba814fdb..0000000000 --- a/app/overrides/admin/enterprises/_form/add_distributor_info.html.haml.deface +++ /dev/null @@ -1,4 +0,0 @@ -/ insert_after "[data-hook='long_description']" -%tr{"data-hook" => "distributor_info"} - %td Distributor Info: - %td= f.text_area :distributor_info, :class => 'rich_text' diff --git a/app/overrides/admin/enterprises/_form/rename_extended_description.html.haml.deface b/app/overrides/admin/enterprises/_form/rename_extended_description.html.haml.deface deleted file mode 100644 index 43c4db4b84..0000000000 --- a/app/overrides/admin/enterprises/_form/rename_extended_description.html.haml.deface +++ /dev/null @@ -1,3 +0,0 @@ -/ replace_contents "[data-hook='long_description']" -%td Profile Info: -%td= f.text_area :long_description, :class => 'rich_text' diff --git a/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb b/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb index 19ee905ae2..3855b80bee 100644 --- a/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb +++ b/spec/features/chili/enterprises_distributor_info_rich_text_feature_spec.rb @@ -32,8 +32,8 @@ feature "enterprises distributor info as rich text" do click_link 'New Enterprise' # Then I should see fields 'Profile Info' and 'Distributor Info' - page.should have_selector 'td', text: 'Profile Info:' - page.should have_selector 'td', text: 'Distributor Info:' + page.should have_content 'About Us' + page.should have_content 'How does it work' # When I fill out the form and create the enterprise fill_in 'enterprise_name', :with => 'Eaterprises' From f3789469b2fe0fe87695af8c50393f7a65053194 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Sun, 16 Mar 2014 17:32:22 +1100 Subject: [PATCH 093/107] Feedback from Kerstin. Resolved issue comments removed. Pickup details removed. --- app/views/admin/enterprises/_form.html.haml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index b658636517..a365ea4c73 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -83,9 +83,6 @@ .row .alpha.two.columns = f.label :abn, 'ABN' - / TODO: figure out how to work with one field for both - / ABN has 11 digits - / ACN has 9 digits .omega.four.columns = f.text_field :abn .row @@ -130,7 +127,6 @@ Hub only: Explain your distribution offer/s - this is more detailed information that the user can access by clicking on "How does it work?" .omega.eight.columns = f.text_area :distributor_info, class: 'rich_text', placeholder: 'Hub only: Explain your distribution offer/s - this is more detailed information that the user can access by clicking on "How does it work?"' - / TODO: how to use placeholder with richt text? / TODO: editor breaks scrolling with arrow keys .row .alpha.two.columns @@ -144,19 +140,3 @@ = image_tag @object.promo_image.url = f.file_field :pro_image - %fieldset - %legend Pickup details - .row - .alpha.six.columns - .alpha.two.columns - = f.label :next_collection_at, 'Next collection date/time' - .omega.four.columns - = f.text_field :next_collection_at - .omega.six.columns - .alpha.two.columns - = f.label :pickup_times, 'Regular pickup times' - .omega.four.columns - = f.text_field :pickup_times - - - From fcd9389fe396978767391373223130dc30ae4a57 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Tue, 18 Mar 2014 22:30:36 +1100 Subject: [PATCH 094/107] spec for pickup details in enterprise form removed --- spec/features/admin/enterprises_spec.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/spec/features/admin/enterprises_spec.rb b/spec/features/admin/enterprises_spec.rb index e593166236..88c6dc8cb4 100644 --- a/spec/features/admin/enterprises_spec.rb +++ b/spec/features/admin/enterprises_spec.rb @@ -84,9 +84,6 @@ feature %q{ select('Australia', :from => 'enterprise_address_attributes_country_id') select('Victoria', :from => 'enterprise_address_attributes_state_id') - fill_in 'enterprise_pickup_times', :with => 'Thursday, 22nd Feb, 6 - 9 PM. Friday, 23nd Feb, 6 - 9 PM' - fill_in 'enterprise_next_collection_at', :with => 'Thursday, 22nd Feb, 6 - 9 PM' - click_button 'Create' flash_message.should == 'Enterprise "Eaterprises" has been successfully created!' end @@ -124,9 +121,6 @@ feature %q{ select('Australia', :from => 'enterprise_address_attributes_country_id') select('Victoria', :from => 'enterprise_address_attributes_state_id') - fill_in 'enterprise_pickup_times', :with => 'Thursday, 22nd Feb, 6 - 9 PM. Friday, 23nd Feb, 6 - 9 PM' - fill_in 'enterprise_next_collection_at', :with => 'Thursday, 22nd Feb, 6 - 9 PM' - click_button 'Update' flash_message.should == 'Enterprise "Eaterprises" has been successfully updated!' From fd81b143b5b2e83ffe7d191b7b49168b6d4c00d2 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 14 Mar 2014 11:36:01 +1100 Subject: [PATCH 095/107] Sort suppliers and distributors by name on order cycle edit page --- app/views/admin/order_cycles/_form.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/order_cycles/_form.html.haml b/app/views/admin/order_cycles/_form.html.haml index f43f4e5a62..e2c0dd3ea9 100644 --- a/app/views/admin/order_cycles/_form.html.haml +++ b/app/views/admin/order_cycles/_form.html.haml @@ -25,7 +25,7 @@ %tr.products{'ng-show' => 'exchange.showProducts'} = render 'exchange_supplied_products_form' -= select_tag :new_supplier_id, options_from_collection_for_select(Enterprise.is_primary_producer.managed_by(spree_current_user), :id, :name), {'ng-model' => 'new_supplier_id'} += select_tag :new_supplier_id, options_from_collection_for_select(Enterprise.is_primary_producer.managed_by(spree_current_user).by_name, :id, :name), {'ng-model' => 'new_supplier_id'} = f.submit 'Add supplier', 'ng-click' => 'addSupplier($event)' @@ -50,7 +50,7 @@ %tr.products{'ng-show' => 'exchange.showProducts'} = render 'exchange_distributed_products_form' -= select_tag :new_distributor_id, options_from_collection_for_select(Enterprise.is_distributor.managed_by(spree_current_user), :id, :name), {'ng-model' => 'new_distributor_id'} += select_tag :new_distributor_id, options_from_collection_for_select(Enterprise.is_distributor.managed_by(spree_current_user).by_name, :id, :name), {'ng-model' => 'new_distributor_id'} = f.submit 'Add distributor', 'ng-click' => 'addDistributor($event)' .actions From 811b0cb36a38f3884002c1a925c96aee7fdd49ae Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 19 Mar 2014 13:17:11 +1100 Subject: [PATCH 096/107] Admin can remove a master variant from an order cycle when further variants have been added --- ..._exchange_supplied_products_form.html.haml | 8 ++++++ spec/features/admin/order_cycles_spec.rb | 28 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/app/views/admin/order_cycles/_exchange_supplied_products_form.html.haml b/app/views/admin/order_cycles/_exchange_supplied_products_form.html.haml index 092dfc6033..4523bd8337 100644 --- a/app/views/admin/order_cycles/_exchange_supplied_products_form.html.haml +++ b/app/views/admin/order_cycles/_exchange_supplied_products_form.html.haml @@ -1,10 +1,18 @@ / TODO: Unify this with exchange_distributed_products_form %td{:colspan => 3} .exchange-product{'ng-repeat' => 'product in enterprises[exchange.enterprise_id].supplied_products'} + .exchange-product-details = check_box_tag 'order_cycle_incoming_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', 1, 1, 'ng-hide' => 'product.variants', 'ng-model' => 'exchange.variants[product.master_id]', 'ofn-sync-distributions' => '{{ product.master_id }}', 'id' => 'order_cycle_incoming_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}' %img{'ng-src' => '{{ product.image_url }}'} {{ product.name }} + + -# When the master variant is in the order cycle but the product has variants, we want to + -# be able to remove the master variant, since it serves no purpose. Display a checkbox to do so. + .exchange-product-variant{'ng-show' => 'exchange.variants[product.master_id] && product.variants'} + = check_box_tag 'order_cycle_incoming_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', 1, 1, 'ng-model' => 'exchange.variants[product.master_id]', 'ofn-sync-distributions' => '{{ product.master_id }}', 'id' => 'order_cycle_incoming_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}' + Obsolete master + .exchange-product-variant{'ng-repeat' => 'variant in product.variants'} = check_box_tag 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', 1, 1, 'ng-model' => 'exchange.variants[variant.id]', 'ofn-sync-distributions' => '{{ variant.id }}', 'id' => 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}' {{ variant.label }} diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 5cadd11a29..6d5c9e17f2 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -391,7 +391,33 @@ feature %q{ end - context 'as an Enterprise user' do + scenario "removing a master variant from an order cycle when further variants have been added" do + # Given a product with a variant, with its master variant included in the order cycle + # (this usually happens when a product is added to an order cycle, then variants are added + # to the product after the fact) + s = create(:supplier_enterprise) + p = create(:simple_product, supplier: s) + v = create(:variant, product: p) + d = create(:distributor_enterprise) + oc = create(:simple_order_cycle, suppliers: [s], distributors: [d], variants: [p.master]) + exchange_ids = oc.exchanges.pluck :id + ExchangeVariant.where(exchange_id: exchange_ids, variant_id: p.master.id).should_not be_empty + + # When I go to the order cycle page and remove the obsolete master + login_to_admin_section + click_link 'Order Cycles' + click_link oc.name + within("table.exchanges tbody tr.supplier") { page.find('td.products input').click } + page.find("#order_cycle_incoming_exchange_0_variants_#{p.master.id}", visible: true).click # uncheck + click_button "Update" + + # Then the master variant should have been removed from all exchanges + page.should have_content "Your order cycle has been updated." + ExchangeVariant.where(exchange_id: exchange_ids, variant_id: p.master.id).should be_empty + end + + + context "as an enterprise user" do let(:supplier1) { create(:supplier_enterprise, name: 'First Supplier') } let(:supplier2) { create(:supplier_enterprise, name: 'Another Supplier') } From 638e922ae280480aaf2755850c593fa7939357fe Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 20 Mar 2014 10:56:46 +1100 Subject: [PATCH 097/107] Do not allow customer to purchase the obsolete master of a product --- app/controllers/shop/shop_controller.rb | 3 +- app/models/order_cycle.rb | 24 ++++++++++++ spec/models/order_cycle_spec.rb | 52 +++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/app/controllers/shop/shop_controller.rb b/app/controllers/shop/shop_controller.rb index c6928efe7d..1af6ceec87 100644 --- a/app/controllers/shop/shop_controller.rb +++ b/app/controllers/shop/shop_controller.rb @@ -9,9 +9,10 @@ class Shop::ShopController < BaseController def products unless @products = current_order_cycle.andand - .products_distributed_by(current_distributor).andand + .valid_products_distributed_by(current_distributor).andand .select { |p| p.has_stock_for_distribution?(current_order_cycle, current_distributor) }.andand .sort_by {|p| p.name } + render json: "", status: 404 end end diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 3d126b5cd7..a6707d9220 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -105,6 +105,16 @@ class OrderCycle < ActiveRecord::Base variants_distributed_by(distributor).map(&:product).uniq end + # If a product without variants is added to an order cycle, and then some variants are added + # to that product, then the master variant is still part of the order cycle, but customers + # should not be able to purchase it. + # This method filters out such products so that the customer cannot purchase them. + def valid_products_distributed_by(distributor) + variants = variants_distributed_by(distributor) + products = variants.map(&:product).uniq + products.reject { |p| product_has_only_obsolete_master_in_distribution?(p, variants) } + end + def products self.variants.map(&:product).uniq end @@ -214,6 +224,20 @@ class OrderCycle < ActiveRecord::Base fees end + + # -- Misc + + # If a product without variants is added to an order cycle, and then some variants are added + # to that product, then the master variant is still part of the order cycle, but customers + # should not be able to purchase it. + # This method is used by #valid_products_distributed_by to filter out such products so that + # the customer cannot purchase them. + def product_has_only_obsolete_master_in_distribution?(product, distributed_variants) + product.has_variants? && + distributed_variants.include?(product.master) && + (product.variants & distributed_variants).empty? + end + def exchanges_carrying(variant, distributor) exchanges.to_enterprises([coordinator, distributor]).with_variant(variant) end diff --git a/spec/models/order_cycle_spec.rb b/spec/models/order_cycle_spec.rb index cdfe6c5828..784d778682 100644 --- a/spec/models/order_cycle_spec.rb +++ b/spec/models/order_cycle_spec.rb @@ -222,6 +222,58 @@ describe OrderCycle do end end + describe "finding valid products distributed by a particular distributor" do + it "returns valid products but not invalid products" do + p_valid = create(:product) + p_invalid = create(:product) + v = create(:variant, product: p_invalid) + + d = create(:distributor_enterprise) + oc = create(:simple_order_cycle, distributors: [d], variants: [p_valid.master, p_invalid.master]) + oc.valid_products_distributed_by(d).should == [p_valid] + end + + describe "checking if a product has only an obsolete master variant in a distributution" do + it "returns true when so" do + master = double(:master) + unassociated_variant = double(:variant) + product = double(:product, :has_variants? => true, :master => master, :variants => []) + distributed_variants = [master, unassociated_variant] + + oc = OrderCycle.new + oc.send(:product_has_only_obsolete_master_in_distribution?, product, distributed_variants).should be_true + end + + it "returns false when the product doesn't have variants" do + master = double(:master) + product = double(:product, :has_variants? => false, :master => master, :variants => []) + distributed_variants = [master] + + oc = OrderCycle.new + oc.send(:product_has_only_obsolete_master_in_distribution?, product, distributed_variants).should be_false + end + + it "returns false when the master isn't distributed" do + master = double(:master) + product = double(:product, :has_variants? => true, :master => master, :variants => []) + distributed_variants = [] + + oc = OrderCycle.new + oc.send(:product_has_only_obsolete_master_in_distribution?, product, distributed_variants).should be_false + end + + it "returns false when the product has other variants distributed" do + master = double(:master) + variant = double(:variant) + product = double(:product, :has_variants? => true, :master => master, :variants => [variant]) + distributed_variants = [master, variant] + + oc = OrderCycle.new + oc.send(:product_has_only_obsolete_master_in_distribution?, product, distributed_variants).should be_false + end + end + end + describe "exchanges" do before(:each) do @oc = create(:simple_order_cycle) From af88d0f91b876a237f94992ba690b15e8e3a5f58 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 20 Mar 2014 11:07:13 +1100 Subject: [PATCH 098/107] Fix regression: Invisible checkbox causing ambiguity for capybara --- spec/features/admin/order_cycles_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 6d5c9e17f2..3708e03930 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -269,7 +269,7 @@ feature %q{ click_button 'Add supplier' page.all("table.exchanges tr.supplier td.products input").each { |e| e.click } - uncheck "order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}" + page.find("#order_cycle_incoming_exchange_1_variants_#{initial_variants.last.id}", visible: true).click # uncheck (with visible:true filter) check "order_cycle_incoming_exchange_2_variants_#{v1.id}" check "order_cycle_incoming_exchange_2_variants_#{v2.id}" From 4aace221ea9561e98aba03b8087646d67652c99c Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 20 Mar 2014 11:41:08 +1100 Subject: [PATCH 099/107] Enterprise user can reorder product properties and update product images --- app/models/spree/ability_decorator.rb | 4 ++-- spec/models/spree/ability_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 0fe8d22e59..83a4e5e0aa 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -15,8 +15,8 @@ class AbilityDecorator end can [:admin, :index, :read, :create, :edit, :update, :search, :destroy], Spree::Variant - can [:admin, :index, :read, :create, :edit, :destroy], Spree::ProductProperty - can [:admin, :index, :read, :create, :edit, :destroy], Spree::Image + can [:admin, :index, :read, :create, :edit, :update_positions, :destroy], Spree::ProductProperty + can [:admin, :index, :read, :create, :edit, :update, :destroy], Spree::Image can [:admin, :index, :read, :search], Spree::Taxon can [:admin, :index, :read, :create, :edit], Spree::Classification diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 86c24aa9ec..71160c7f87 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -48,11 +48,11 @@ module Spree end it "should be able to read/write their enterprises' product properties" do - should have_ability([:admin, :index, :read, :create, :edit, :destroy], for: Spree::ProductProperty) + should have_ability([:admin, :index, :read, :create, :edit, :update_positions, :destroy], for: Spree::ProductProperty) end it "should be able to read/write their enterprises' product images" do - should have_ability([:admin, :index, :read, :create, :edit, :destroy], for: Spree::Image) + should have_ability([:admin, :index, :read, :create, :edit, :update, :destroy], for: Spree::Image) end it "should be able to read Taxons (in order to create classifications)" do From 51fdf863299b94841aa464a3730186da288e492c Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 20 Mar 2014 15:18:21 +1100 Subject: [PATCH 100/107] Polish the new enterprise form --- app/views/admin/enterprises/_form.html.haml | 66 +++++++++++++-------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index a365ea4c73..2d3c728bf0 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -1,6 +1,13 @@ - content_for :head do = render 'shared/cms_elrte_head' +- content_for :page_title do + New Enterprise + +- content_for :page_actions do + %li= button_link_to "Back to enterprises list", main_app.admin_enterprises_path, icon: 'icon-arrow-left' + + .fullwidth_inputs .row .alpha.six.columns @@ -11,22 +18,25 @@ .omega.six.columns .two.columns.alpha = f.label :group_ids, 'Groups' + .with-tip{'data-powertip' => "Select any groups or regions that you are a member of. This will help customers find your enterprise."} + %a What's this? + .four.columns.omega = f.collection_select :group_ids, EnterpriseGroup.all, :id, :name, {}, {class: "select2 fullwidth", multiple: true} .row .alpha.two.columns   .omega.ten.columns - .alpha.two.columns - = f.check_box :is_primary_producer - = f.label :is_primary_producer, 'Producer' - .omega.two.columns - = f.check_box :is_distributor - = f.label :is_distributor, 'Hub' - .omega.two.columns - %span.with-tip{'data-powertip' => "Select 'Producer' if you are a primary producer of food. And select 'Hub' if you are selling to end customers. You can do both."} - %a Tell me more? + = f.check_box :is_primary_producer + = f.label :is_primary_producer, 'Producer' +   + = f.check_box :is_distributor + = f.label :is_distributor, 'Hub' + + .with-tip{'data-powertip' => "Select 'Producer' if you are a primary producer of food. Select 'Hub' if you want a shop-front. You can choose either or both."} + %a What's this? + = f.fields_for :address do |af| - %fieldset + %fieldset.no-border-bottom %legend Address .row .alpha.six.columns @@ -60,7 +70,7 @@ = af.collection_select(:country_id, available_countries, :id, :name) .row .alpha.six.columns - %fieldset + %fieldset.no-border-bottom %legend Contact Details .row .alpha.two.columns @@ -78,7 +88,7 @@ .omega.four.columns = f.text_field :phone .omega.six.columns - %fieldset + %fieldset.no-border-bottom %legend Enterprise Details .row .alpha.two.columns @@ -95,24 +105,24 @@ = f.label :website .omega.four.columns = f.text_field :website - /.row - / .alpha.two.columns - / = f.label :twitter, 'Facebook' - / / TODO: facebook data field - / .omega.four.columns - / = f.text_field :twitter + -# TODO: Facebook model field + -#.row + -# .alpha.two.columns + -# = f.label :facebook, 'Facebook' + -# .omega.four.columns + -# = f.text_field :facebook .row .alpha.two.columns = f.label :twitter .omega.four.columns = f.text_field :twitter - %fieldset + %fieldset.no-border-bottom %legend About Us .row .alpha.two.columns = f.label :description, 'Short Description' .omega.ten.columns - = f.text_field :description, placeholder: 'Just one or two sentences' + = f.text_field :description, placeholder: 'Tell us about your enterprise in one or two sentences' .row .alpha.two.columns = f.label :long_description, 'About Us' @@ -122,21 +132,27 @@ = f.text_area :long_description, class: 'rich_text', placeholder: 'Tell us about yourself. This information appears on your public profile (under "About Us")' .row .alpha.two.columns - = f.label :distributor_info, 'How does it work?' + = f.label :distributor_info, 'How does your hub work?' %br - Hub only: Explain your distribution offer/s - this is more detailed information that the user can access by clicking on "How does it work?" + %em (Hub only) + %br + Explain your distribution offer/s - this information appears on your public profile (under "How does it work?") .omega.eight.columns = f.text_area :distributor_info, class: 'rich_text', placeholder: 'Hub only: Explain your distribution offer/s - this is more detailed information that the user can access by clicking on "How does it work?"' / TODO: editor breaks scrolling with arrow keys .row .alpha.two.columns = f.label :logo + %br + 100 x 100 pixels .omega.four.columns - = image_tag @object.logo.url + = image_tag @object.logo.url if @object.logo.present? = f.file_field :logo .omega.two.columns = f.label :promo_image, class: 'with-tip', 'data-powertip' => 'This image is displayed in "About Us"' + .with-tip{'data-powertip' => 'This image is displayed on the right hand side of the "About Us" section of your public profile.'} + %a What's this? + .omega.four.columns - = image_tag @object.promo_image.url + = image_tag @object.promo_image.url if @object.promo_image.present? = f.file_field :pro_image - From 6e5c93526cbd4ad26e7ba3466122dc3419c8d872 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 20 Mar 2014 13:18:09 +1100 Subject: [PATCH 101/107] Moving everything to a single Angular application --- app/assets/javascripts/darkswarm/all.js.coffee | 2 +- app/assets/javascripts/darkswarm/cart.js.coffee | 5 ----- .../darkswarm/controllers/checkout_controller.js.coffee | 2 +- .../darkswarm/controllers/order_cycle_controller.js.coffee | 2 +- .../darkswarm/controllers/products_controller.js.coffee | 2 +- .../darkswarm/{checkout.js.coffee => darkswarm.js.coffee} | 2 +- .../javascripts/darkswarm/services/order_cycle.js.coffee | 2 +- app/assets/javascripts/darkswarm/services/product.js.coffee | 2 +- app/assets/javascripts/darkswarm/shop.js.coffee | 4 ---- app/views/layouts/darkswarm.html.haml | 2 +- app/views/shop/checkout/_form.html.haml | 2 +- app/views/shop/checkout/edit.html.haml | 2 +- app/views/shop/shop/_order_cycles.html.haml | 2 +- app/views/shop/shop/show.html.haml | 2 +- app/views/spree/orders/edit.html.haml | 2 +- 15 files changed, 13 insertions(+), 22 deletions(-) rename app/assets/javascripts/darkswarm/{checkout.js.coffee => darkswarm.js.coffee} (50%) diff --git a/app/assets/javascripts/darkswarm/all.js.coffee b/app/assets/javascripts/darkswarm/all.js.coffee index ba5c6b8086..ead3240bf3 100644 --- a/app/assets/javascripts/darkswarm/all.js.coffee +++ b/app/assets/javascripts/darkswarm/all.js.coffee @@ -8,7 +8,7 @@ # #= require ../shared/jquery.timeago #= require foundation -#= require ./shop +#= require ./darkswarm #= require_tree . $ -> diff --git a/app/assets/javascripts/darkswarm/cart.js.coffee b/app/assets/javascripts/darkswarm/cart.js.coffee index 66b607d94d..bb68d0d7ec 100644 --- a/app/assets/javascripts/darkswarm/cart.js.coffee +++ b/app/assets/javascripts/darkswarm/cart.js.coffee @@ -1,8 +1,3 @@ -window.Cart = angular.module("Cart", ["ngResource"]).config ($httpProvider) -> - $httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content') - - -# TEMPORARILY handles the deletetion $ -> if ($ 'form#update-cart').is('*') ($ 'form#update-cart a.delete').show().one 'click', -> diff --git a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee index 575d046f59..1d0604d76c 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee @@ -1,4 +1,4 @@ -angular.module("Checkout").controller "CheckoutCtrl", ($scope, $rootScope, order) -> +Darkswarm.controller "CheckoutCtrl", ($scope, $rootScope, order) -> $scope.require_ship_address = false $scope.order = order diff --git a/app/assets/javascripts/darkswarm/controllers/order_cycle_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/order_cycle_controller.js.coffee index d16d31603c..0f7f3cd17f 100644 --- a/app/assets/javascripts/darkswarm/controllers/order_cycle_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/order_cycle_controller.js.coffee @@ -1,4 +1,4 @@ -Shop.controller "OrderCycleCtrl", ($scope, $rootScope, OrderCycle) -> +Darkswarm.controller "OrderCycleCtrl", ($scope, $rootScope, OrderCycle) -> $scope.order_cycle = OrderCycle.order_cycle $scope.changeOrderCycle = -> OrderCycle.push_order_cycle() diff --git a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee index 0a054d755d..34ec81c5d2 100644 --- a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee @@ -1,4 +1,4 @@ -angular.module("Shop").controller "ProductsCtrl", ($scope, $rootScope, Product, OrderCycle) -> +Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Product, OrderCycle) -> $scope.data = Product.data $scope.order_cycle = OrderCycle.order_cycle Product.update() diff --git a/app/assets/javascripts/darkswarm/checkout.js.coffee b/app/assets/javascripts/darkswarm/darkswarm.js.coffee similarity index 50% rename from app/assets/javascripts/darkswarm/checkout.js.coffee rename to app/assets/javascripts/darkswarm/darkswarm.js.coffee index 65bd062d5d..12bc0023c6 100644 --- a/app/assets/javascripts/darkswarm/checkout.js.coffee +++ b/app/assets/javascripts/darkswarm/darkswarm.js.coffee @@ -1,2 +1,2 @@ -window.Checkout = angular.module("Checkout", ["ngResource", "filters"]).config ($httpProvider) -> +window.Darkswarm = angular.module("Darkswarm", ["ngResource", "filters"]).config ($httpProvider) -> $httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content') diff --git a/app/assets/javascripts/darkswarm/services/order_cycle.js.coffee b/app/assets/javascripts/darkswarm/services/order_cycle.js.coffee index 042ea960e4..84d09a9a13 100644 --- a/app/assets/javascripts/darkswarm/services/order_cycle.js.coffee +++ b/app/assets/javascripts/darkswarm/services/order_cycle.js.coffee @@ -1,4 +1,4 @@ -Shop.factory 'OrderCycle', ($resource, Product, orderCycleData) -> +Darkswarm.factory 'OrderCycle', ($resource, Product, orderCycleData) -> class OrderCycle @order_cycle = orderCycleData || {orders_close_at: ""} @push_order_cycle: -> diff --git a/app/assets/javascripts/darkswarm/services/product.js.coffee b/app/assets/javascripts/darkswarm/services/product.js.coffee index f8f139ad32..4140da20fa 100644 --- a/app/assets/javascripts/darkswarm/services/product.js.coffee +++ b/app/assets/javascripts/darkswarm/services/product.js.coffee @@ -1,4 +1,4 @@ -Shop.factory 'Product', ($resource) -> +Darkswarm.factory 'Product', ($resource) -> new class Product data: { products: null diff --git a/app/assets/javascripts/darkswarm/shop.js.coffee b/app/assets/javascripts/darkswarm/shop.js.coffee index 1163859bc9..70917a934d 100644 --- a/app/assets/javascripts/darkswarm/shop.js.coffee +++ b/app/assets/javascripts/darkswarm/shop.js.coffee @@ -1,7 +1,3 @@ -window.Shop = angular.module("Shop", ["ngResource", "filters"]).config ($httpProvider) -> - $httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content') - - angular.module("filters", []).filter "truncate", -> (text, length, end) -> text = text || "" diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index 063c131329..d6591cbf12 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -12,7 +12,7 @@ = render "layouts/bugherd_script" = csrf_meta_tags - %body.off-canvas + %body.off-canvas{"ng-app" => "Darkswarm"} = render partial: "shared/menu" = display_flash_messages diff --git a/app/views/shop/checkout/_form.html.haml b/app/views/shop/checkout/_form.html.haml index 41f617a26f..0445e28eed 100644 --- a/app/views/shop/checkout/_form.html.haml +++ b/app/views/shop/checkout/_form.html.haml @@ -3,7 +3,7 @@ = f_form_for current_order, url: main_app.shop_update_checkout_path, html: {name: "checkout", id: "checkout_form"} do |f| :javascript - angular.module('Checkout').value('order', #{render "shop/checkout/order"}) + angular.module('Darkswarm').value('order', #{render "shop/checkout/order"}) -#%pre -#{{ order | json }} diff --git a/app/views/shop/checkout/edit.html.haml b/app/views/shop/checkout/edit.html.haml index 84d627f4af..98cead9b9f 100644 --- a/app/views/shop/checkout/edit.html.haml +++ b/app/views/shop/checkout/edit.html.haml @@ -6,7 +6,7 @@ = render partial: "shop/details" - %checkout{"ng-app" => "Checkout"} + %checkout .row .large-9.columns - unless spree_current_user diff --git a/app/views/shop/shop/_order_cycles.html.haml b/app/views/shop/shop/_order_cycles.html.haml index aff1a3259d..d121bb5fb8 100644 --- a/app/views/shop/shop/_order_cycles.html.haml +++ b/app/views/shop/shop/_order_cycles.html.haml @@ -1,6 +1,6 @@ %ordercycle{"ng-controller" => "OrderCycleCtrl"} :javascript - angular.module('Shop').value('orderCycleData', #{render "shop/shop/order_cycle"}) + angular.module('Darkswarm').value('orderCycleData', #{render "shop/shop/order_cycle"}) - if @order_cycles.empty? Orders are currently closed for this hub diff --git a/app/views/shop/shop/show.html.haml b/app/views/shop/shop/show.html.haml index 462648bb6b..250942357c 100644 --- a/app/views/shop/shop/show.html.haml +++ b/app/views/shop/shop/show.html.haml @@ -1,4 +1,4 @@ -%shop.darkswarm{"ng-app" => "Shop"} +%shop.darkswarm - content_for :order_cycle_form do %strong.avenir Ready for %select.avenir#order_cycle_id{"ng-model" => "order_cycle.order_cycle_id", diff --git a/app/views/spree/orders/edit.html.haml b/app/views/spree/orders/edit.html.haml index 095218cea7..3ac65a197b 100644 --- a/app/views/spree/orders/edit.html.haml +++ b/app/views/spree/orders/edit.html.haml @@ -10,7 +10,7 @@ = render partial: "shop/details" - %fieldset{"ng-app" => "Cart"} + %fieldset - if @order.line_items.empty? %div.row{"data-hook" => "empty_cart"} %p= t(:your_cart_is_empty) From d954a8f4e4b1ba3673e2c3f42b01edffc604d4a7 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 20 Mar 2014 13:54:59 +1100 Subject: [PATCH 102/107] Starting on the sidebar --- .../controllers/sidebar_controller.js.coffee | 11 +++++++++++ app/views/layouts/darkswarm.html.haml | 10 +++++++--- app/views/shared/_login.html.haml | 4 ++-- 3 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/darkswarm/controllers/sidebar_controller.js.coffee diff --git a/app/assets/javascripts/darkswarm/controllers/sidebar_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/sidebar_controller.js.coffee new file mode 100644 index 0000000000..27ec2bf091 --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/sidebar_controller.js.coffee @@ -0,0 +1,11 @@ +window.SidebarCtrl = Darkswarm.controller "SidebarCtrl", ($scope, $location) -> + $scope.active_sidebar = $location.path() + + $scope.$watch -> + $location.path() + , -> + $scope.active_sidebar = $location.path() + + + $scope.visible = -> + $scope.active_sidebar != null and $scope.active_sidebar != "" diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index d6591cbf12..ba66b9446a 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -16,14 +16,18 @@ = render partial: "shared/menu" = display_flash_messages + %section#sidebar{ role: "complementary", "ng-controller" => "SidebarCtrl", "ng-show" => "visible()"} + {{ active_sidebar }} + TEST ME + %a{href: "#"} close + = render partial: "shared/login_panel" + = yield :sidebar + %section{ role: "main" } = yield #footer - %section#sidebar{ role: "complementary" } - = render partial: "shared/login_panel" - = yield :sidebar = yield :scripts diff --git a/app/views/shared/_login.html.haml b/app/views/shared/_login.html.haml index 67249aeb26..c46d0f424f 100644 --- a/app/views/shared/_login.html.haml +++ b/app/views/shared/_login.html.haml @@ -1,8 +1,8 @@ - if spree_current_user.nil? - %li#login-link= link_to "Login", "#sidebar", id: "sidebarLoginButton", class: "sidebar-button" + %li#login-link= link_to "Login", "#login", id: "sidebarLoginButton", class: "sidebar-button" %li#login-name.hide %li.divider - %li#sign-up-link= link_to "Sign Up", "#sidebar", id: "sidebarSignUpButton", class: "sidebar-button" + %li#sign-up-link= link_to "Sign Up", "#signup", id: "sidebarSignUpButton", class: "sidebar-button" %li#sign-out-link.hide= link_to "Sign Out", "/logout" - else %li#login-link.hide= link_to "Login", "#sidebar", id: "sidebarLoginButton", class: "sidebar-button" From 0cabc2eb4d2dc3a440cb56ba109a4d01b5978c7a Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 20 Mar 2014 14:22:52 +1100 Subject: [PATCH 103/107] Working sliding panel in basic form --- .../controllers/sidebar_controller.js.coffee | 6 +-- app/assets/stylesheets/darkswarm/all.scss | 2 - .../stylesheets/darkswarm/offcanvas.css | 50 +++++++++++++++++++ app/views/layouts/darkswarm.html.haml | 5 +- app/views/shared/_login_panel.html.haml | 5 +- app/views/shared/_menu.html.haml | 5 +- app/views/shared/_signed_in.html.haml | 5 ++ app/views/shared/_signed_out.html.haml | 5 ++ 8 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 app/assets/stylesheets/darkswarm/offcanvas.css create mode 100644 app/views/shared/_signed_in.html.haml create mode 100644 app/views/shared/_signed_out.html.haml diff --git a/app/assets/javascripts/darkswarm/controllers/sidebar_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/sidebar_controller.js.coffee index 27ec2bf091..cbf7a6710a 100644 --- a/app/assets/javascripts/darkswarm/controllers/sidebar_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/sidebar_controller.js.coffee @@ -1,11 +1,9 @@ window.SidebarCtrl = Darkswarm.controller "SidebarCtrl", ($scope, $location) -> - $scope.active_sidebar = $location.path() - $scope.$watch -> $location.path() , -> $scope.active_sidebar = $location.path() - $scope.visible = -> - $scope.active_sidebar != null and $scope.active_sidebar != "" + $scope.active = -> + return "active" if $scope.active_sidbar != null and $scope.active_sidebar != "" diff --git a/app/assets/stylesheets/darkswarm/all.scss b/app/assets/stylesheets/darkswarm/all.scss index aaec952184..44e8ea248c 100644 --- a/app/assets/stylesheets/darkswarm/all.scss +++ b/app/assets/stylesheets/darkswarm/all.scss @@ -7,5 +7,3 @@ *= require foundation *= require_tree . */ - - diff --git a/app/assets/stylesheets/darkswarm/offcanvas.css b/app/assets/stylesheets/darkswarm/offcanvas.css new file mode 100644 index 0000000000..d6837e3ed5 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/offcanvas.css @@ -0,0 +1,50 @@ +/* Off canvas layout CSS/JS provided by or adapted from work by Jason Weaver and Luke Wroblewski Requires globals.css grid.css */ +body.off-canvas { padding: 0; margin: 0; overflow: hidden} + +.container { width: 100%; } + +.row { overflow: hidden; } + +.row .row { overflow: visible; } + +.paneled .container { overflow: hidden; } + +.paneled .row { width: 100%; } + +[role="main"]:before { content: " "; position: absolute; z-index: -1; top: 0; left: -100%; width: 100%; height: 100%; } + +[role="complementary"], [role="main"] { width: 100%; padding: 0 15px; display: block; position: relative; z-index: 1; -webkit-transition: 0.25s all ease-in; -moz-transition: 0.25s all ease-in; -o-transition: 0.25s all ease-in; transition: 0.25s all ease-in; } + +.paneled [role="main"] { padding: 0; } + +.page-panel { width: 100%; padding: 0 15px; -webkit-transition: 0.3s margin ease-in-out; -moz-transition: 0.3s margin ease-in-out; -o-transition: 0.3s margin ease-in-out; transition: 0.3s margin ease-in-out; background: #fff; } + +#switchPanels { margin: 0 -15px; } + +.hide-extras [role="complementary"] { display: block; } + +[role="navigation"]#topMenu { -webkit-transition: 0.25s all ease-in; -moz-transition: 0.25s all ease-in; -o-transition: 0.25s all ease-in; transition: 0.25s all ease-in; } + +[role="navigation"]#topMenu ul { margin-top: 0; } + +[role="complementary"] { margin-left: -100%; width: 400px; float: left; z-index: 2; } + +[role="main"] { margin-left: 0; float: right; z-index: 1; position: relative; } + +.paneled [role="main"] { background: #fff; width: 500%; overflow: hidden; float: none; position: relative; left: 0; -webkit-transition: 0.15s all ease-in; -moz-transition: 0.15s all ease-in; -o-transition: 0.15s all ease-in; transition: 0.15s all ease-in; } + +.page-panel { min-height: 400px; float: left; margin: 0; width: 20%; } + +[role="complementary"].active { margin-left: 0; } +.active + [role="main"] { margin-right: -420px; } + +.active-menu [role="navigation"]#topMenu { margin-top: 0 !important; } + +@media all and (min-width: 768px) { menu-button, .sidebar-button { display: none; } + /*[role="complementary"] { width: 20%; margin-left: 0; float: left; padding: 0 15px; }*/ + [role="main"] { width: 100%; padding: 0 15px; } + .paneled [role="main"] { width: 100%; padding: 0; background: #f4f4f4; left: 0 !important; } + .page-panel { display: block; min-height: 800px; float: none; margin: 0; width: 100%; background: #f4f4f4; } + .hide-extras [role="main"] { width: 100%; } + .hide-extras [role="complementary"] { display: none; } + [role="navigation"]#topMenu { display: none; } } diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index ba66b9446a..5ceb406a58 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -16,10 +16,7 @@ = render partial: "shared/menu" = display_flash_messages - %section#sidebar{ role: "complementary", "ng-controller" => "SidebarCtrl", "ng-show" => "visible()"} - {{ active_sidebar }} - TEST ME - %a{href: "#"} close + %section#sidebar{ role: "complementary", "ng-controller" => "SidebarCtrl", "ng-class" => "active()"} = render partial: "shared/login_panel" = yield :sidebar diff --git a/app/views/shared/_login_panel.html.haml b/app/views/shared/_login_panel.html.haml index 8266eaf0b5..8859632fc4 100644 --- a/app/views/shared/_login_panel.html.haml +++ b/app/views/shared/_login_panel.html.haml @@ -1,5 +1,6 @@ .login-panel - #login-content.hide + %a{href: "#"} Close + #login-content = render "home/login" - #sign-up-content.hide + #sign-up-content = render "home/signup" diff --git a/app/views/shared/_menu.html.haml b/app/views/shared/_menu.html.haml index 65973942b3..1250158386 100644 --- a/app/views/shared/_menu.html.haml +++ b/app/views/shared/_menu.html.haml @@ -3,4 +3,7 @@ %ul.left %li= link_to image_tag("ofn_logo_small.png"), root_path %li.divider - = render partial: "shared/login" + - if spree_current_user.nil? + = render 'shared/signed_out' + - else + = render 'shared/signed_in' diff --git a/app/views/shared/_signed_in.html.haml b/app/views/shared/_signed_in.html.haml new file mode 100644 index 0000000000..a0137015b7 --- /dev/null +++ b/app/views/shared/_signed_in.html.haml @@ -0,0 +1,5 @@ +%li#login-link.hide= link_to "Login", "#sidebar", id: "sidebarLoginButton", class: "sidebar-button" +%li#login-name= link_to "#{spree_current_user.email}", "#" +%li.divider +%li#sign-up-link.hide= link_to "Sign Up", "#" +%li#sign-out-link= link_to "Sign Out", "/logout" diff --git a/app/views/shared/_signed_out.html.haml b/app/views/shared/_signed_out.html.haml new file mode 100644 index 0000000000..6c7c4e9d21 --- /dev/null +++ b/app/views/shared/_signed_out.html.haml @@ -0,0 +1,5 @@ +%li#login-link= link_to "Login", "#login", id: "sidebarLoginButton", class: "sidebar-button" +%li#login-name.hide +%li.divider +%li#sign-up-link= link_to "Sign Up", "#signup", id: "sidebarSignUpButton", class: "sidebar-button" +%li#sign-out-link.hide= link_to "Sign Out", "/logout" From d8a7860d5124a6e7965add967acd2152a01f22cb Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 20 Mar 2014 14:36:00 +1100 Subject: [PATCH 104/107] Getting some switching into place --- .../controllers/sidebar_controller.js.coffee | 3 +-- app/assets/stylesheets/darkswarm/offcanvas.css | 2 +- app/assets/stylesheets/darkswarm/sidepanel.css.sass | 13 +++++++++++++ app/views/shared/_login_panel.html.haml | 5 +++-- 4 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 app/assets/stylesheets/darkswarm/sidepanel.css.sass diff --git a/app/assets/javascripts/darkswarm/controllers/sidebar_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/sidebar_controller.js.coffee index cbf7a6710a..0aa5f11683 100644 --- a/app/assets/javascripts/darkswarm/controllers/sidebar_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/sidebar_controller.js.coffee @@ -4,6 +4,5 @@ window.SidebarCtrl = Darkswarm.controller "SidebarCtrl", ($scope, $location) -> , -> $scope.active_sidebar = $location.path() - $scope.active = -> - return "active" if $scope.active_sidbar != null and $scope.active_sidebar != "" + return "active" if $scope.active_sidebar != null and $scope.active_sidebar != "" diff --git a/app/assets/stylesheets/darkswarm/offcanvas.css b/app/assets/stylesheets/darkswarm/offcanvas.css index d6837e3ed5..3b823be7a3 100644 --- a/app/assets/stylesheets/darkswarm/offcanvas.css +++ b/app/assets/stylesheets/darkswarm/offcanvas.css @@ -1,5 +1,5 @@ /* Off canvas layout CSS/JS provided by or adapted from work by Jason Weaver and Luke Wroblewski Requires globals.css grid.css */ -body.off-canvas { padding: 0; margin: 0; overflow: hidden} +body.off-canvas { padding: 0; margin: 0;} .container { width: 100%; } diff --git a/app/assets/stylesheets/darkswarm/sidepanel.css.sass b/app/assets/stylesheets/darkswarm/sidepanel.css.sass new file mode 100644 index 0000000000..65b323a2e9 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/sidepanel.css.sass @@ -0,0 +1,13 @@ +@import "foundation/variables" +@import "foundation/components/global" +@import "foundation/components/buttons" +@import "foundation/components/panels" + + +#sidebar + margin-top: 1.875em + .login-panel + $bg: #222 + $padding: emCalc(20) + $adjust: true + @include panel($bg, $padding, $adjust) diff --git a/app/views/shared/_login_panel.html.haml b/app/views/shared/_login_panel.html.haml index 8859632fc4..333c872467 100644 --- a/app/views/shared/_login_panel.html.haml +++ b/app/views/shared/_login_panel.html.haml @@ -1,6 +1,7 @@ .login-panel %a{href: "#"} Close - #login-content + + #login-content{"ng-show" => "active_sidebar == '/login'"} = render "home/login" - #sign-up-content + #sign-up-content{"ng-show" => "active_sidebar == '/signup'"} = render "home/signup" From 7f88b8eb39b9f62e140aaa855fab6433dcec8d32 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 20 Mar 2014 14:43:25 +1100 Subject: [PATCH 105/107] Switching to separate controllers for ze sidebar --- .../darkswarm/controllers/login_sidebar_controller.js.coffee | 3 +++ .../controllers/signup_sidebar_controller.js.coffee | 3 +++ app/views/shared/_login_panel.html.haml | 5 ++--- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 app/assets/javascripts/darkswarm/controllers/login_sidebar_controller.js.coffee create mode 100644 app/assets/javascripts/darkswarm/controllers/signup_sidebar_controller.js.coffee diff --git a/app/assets/javascripts/darkswarm/controllers/login_sidebar_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/login_sidebar_controller.js.coffee new file mode 100644 index 0000000000..efd2d7e3e9 --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/login_sidebar_controller.js.coffee @@ -0,0 +1,3 @@ +window.LoginSidebarCtrl = Darkswarm.controller "LoginSidebarCtrl", ($scope) -> + $scope.active = -> + $scope.active_sidebar == '/login' diff --git a/app/assets/javascripts/darkswarm/controllers/signup_sidebar_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/signup_sidebar_controller.js.coffee new file mode 100644 index 0000000000..a577fc6b51 --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/signup_sidebar_controller.js.coffee @@ -0,0 +1,3 @@ +window.SignupSidebarCtrl = Darkswarm.controller "SignupSidebarCtrl", ($scope) -> + $scope.active = -> + $scope.active_sidebar == '/signup' diff --git a/app/views/shared/_login_panel.html.haml b/app/views/shared/_login_panel.html.haml index 333c872467..a553add506 100644 --- a/app/views/shared/_login_panel.html.haml +++ b/app/views/shared/_login_panel.html.haml @@ -1,7 +1,6 @@ .login-panel %a{href: "#"} Close - - #login-content{"ng-show" => "active_sidebar == '/login'"} + #login-content{"ng-controller" => "LoginSidebarCtrl", "ng-show" => "active()"} = render "home/login" - #sign-up-content{"ng-show" => "active_sidebar == '/signup'"} + #sign-up-content{"ng-controller" => "SignupSidebarCtrl", "ng-show" => "active()"} = render "home/signup" From dbc8e1bcf1950e2f2cfb4cae134a6896a8be1748 Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 20 Mar 2014 15:09:10 +1100 Subject: [PATCH 106/107] Reworking the tests for the new app --- .../checkout_controller_spec.js.coffee | 34 +++++++++++++++++ .../ordercycle_controller_spec.js.coffee | 18 +++++++++ .../products_controller_spec.js.coffee} | 38 +------------------ .../sidebar_controller_spec.js.coffee | 24 ++++++++++++ .../unit/darkswarm/order_cycle_spec.js.coffee | 4 +- .../unit/darkswarm/product_spec.js.coffee | 2 +- 6 files changed, 81 insertions(+), 39 deletions(-) create mode 100644 spec/javascripts/unit/darkswarm/controllers/checkout_controller_spec.js.coffee create mode 100644 spec/javascripts/unit/darkswarm/controllers/ordercycle_controller_spec.js.coffee rename spec/javascripts/unit/darkswarm/{controllers_spec.js.coffee => controllers/products_controller_spec.js.coffee} (58%) create mode 100644 spec/javascripts/unit/darkswarm/controllers/sidebar_controller_spec.js.coffee diff --git a/spec/javascripts/unit/darkswarm/controllers/checkout_controller_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers/checkout_controller_spec.js.coffee new file mode 100644 index 0000000000..fae814e4e8 --- /dev/null +++ b/spec/javascripts/unit/darkswarm/controllers/checkout_controller_spec.js.coffee @@ -0,0 +1,34 @@ +describe "CheckoutCtrl", -> + ctrl = null + scope = null + order = null + + beforeEach -> + module("Darkswarm") + order = + id: 3102 + shipping_method_id: "7" + ship_address_same_as_billing: true + payment_method_id: null + shipping_methods: + 7: + require_ship_address: true + price: 0.0 + + 25: + require_ship_address: false + price: 13 + inject ($controller) -> + scope = {} + ctrl = $controller 'CheckoutCtrl', {$scope: scope, order: order} + + + it 'Gets the ship address automatically', -> + expect(scope.require_ship_address).toEqual true + + it 'Gets the current shipping price', -> + expect(scope.shippingPrice()).toEqual 0.0 + scope.order.shipping_method_id = 25 + expect(scope.shippingPrice()).toEqual 13 + + diff --git a/spec/javascripts/unit/darkswarm/controllers/ordercycle_controller_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers/ordercycle_controller_spec.js.coffee new file mode 100644 index 0000000000..260f51982a --- /dev/null +++ b/spec/javascripts/unit/darkswarm/controllers/ordercycle_controller_spec.js.coffee @@ -0,0 +1,18 @@ +describe 'OrderCycleCtrl', -> + ctrl = null + scope = null + event = null + product_ctrl = null + OrderCycle = null + + beforeEach -> + module 'Darkswarm' + scope = {} + OrderCycle = + order_cycle: "test" + inject ($controller) -> + scope = {} + ctrl = $controller 'OrderCycleCtrl', {$scope: scope, OrderCycle: OrderCycle} + + it "puts the order cycle in scope", -> + expect(scope.order_cycle).toEqual "test" diff --git a/spec/javascripts/unit/darkswarm/controllers_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers/products_controller_spec.js.coffee similarity index 58% rename from spec/javascripts/unit/darkswarm/controllers_spec.js.coffee rename to spec/javascripts/unit/darkswarm/controllers/products_controller_spec.js.coffee index 5b6b364971..269c8385b8 100644 --- a/spec/javascripts/unit/darkswarm/controllers_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/controllers/products_controller_spec.js.coffee @@ -6,7 +6,7 @@ describe 'All controllers', -> Product = null beforeEach -> - module('Shop') + module('Darkswarm') Product = all: -> update: -> @@ -40,42 +40,8 @@ describe 'All controllers', -> OrderCycle = null beforeEach -> - module 'Shop' + module 'Darkswarm' scope = {} inject ($controller) -> scope = {} ctrl = $controller 'OrderCycleCtrl', {$scope: scope} - - describe "CheckoutCtrl", -> - ctrl = null - scope = null - order = null - - beforeEach -> - module("Checkout") - order = - id: 3102 - shipping_method_id: "7" - ship_address_same_as_billing: true - payment_method_id: null - shipping_methods: - 7: - require_ship_address: true - price: 0.0 - - 25: - require_ship_address: false - price: 13 - inject ($controller) -> - scope = {} - ctrl = $controller 'CheckoutCtrl', {$scope: scope, order: order} - - - it 'Gets the ship address automatically', -> - expect(scope.require_ship_address).toEqual true - - it 'Gets the current shipping price', -> - expect(scope.shippingPrice()).toEqual 0.0 - scope.order.shipping_method_id = 25 - expect(scope.shippingPrice()).toEqual 13 - diff --git a/spec/javascripts/unit/darkswarm/controllers/sidebar_controller_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers/sidebar_controller_spec.js.coffee new file mode 100644 index 0000000000..d9348021d7 --- /dev/null +++ b/spec/javascripts/unit/darkswarm/controllers/sidebar_controller_spec.js.coffee @@ -0,0 +1,24 @@ +describe "SidebarCtrl", -> + ctrl = null + scope = null + location = null + + beforeEach -> + module("Darkswarm") + location = + path: -> + "/test" + inject ($controller, $rootScope) -> + scope = $rootScope + ctrl = $controller 'SidebarCtrl', {$scope: scope, $location: location} + scope.$apply() + + it 'tracks the active sidebar from the $location', -> + expect(scope.active_sidebar).toEqual "/test" + + it 'is active when a location is set', -> + expect(scope.active()).toEqual "active" + + it 'is inactive no location is set', -> + scope.active_sidebar = null + expect(scope.active()).toEqual null diff --git a/spec/javascripts/unit/darkswarm/order_cycle_spec.js.coffee b/spec/javascripts/unit/darkswarm/order_cycle_spec.js.coffee index e7ef7f60b1..395b51512e 100644 --- a/spec/javascripts/unit/darkswarm/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/order_cycle_spec.js.coffee @@ -6,8 +6,8 @@ describe 'OrderCycle service', -> } beforeEach -> - angular.module('Shop').value('orderCycleData', {}) - module 'Shop', ($provide)-> + angular.module('Darkswarm').value('orderCycleData', {}) + module 'Darkswarm', ($provide)-> $provide.value "Product", mockProduct null # IMPORTANT # You must return null because module() is a bit dumb diff --git a/spec/javascripts/unit/darkswarm/product_spec.js.coffee b/spec/javascripts/unit/darkswarm/product_spec.js.coffee index fd2a8f4edb..7108d819af 100644 --- a/spec/javascripts/unit/darkswarm/product_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/product_spec.js.coffee @@ -3,7 +3,7 @@ describe 'Product service', -> Product = null beforeEach -> - module 'Shop' + module 'Darkswarm' inject ($injector, _$httpBackend_)-> Product = $injector.get("Product") $httpBackend = _$httpBackend_ From 250e1d236b96c096b726dff82f34c04cb1d6308b Mon Sep 17 00:00:00 2001 From: Will Marshall Date: Thu, 20 Mar 2014 16:56:47 +1100 Subject: [PATCH 107/107] Working login form --- .../login_sidebar_controller.js.coffee | 13 ++++++- .../javascripts/darkswarm/darkswarm.js.coffee | 3 ++ app/views/shared/_login_panel.html.haml | 34 ++++++++++++++++++- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/login_sidebar_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/login_sidebar_controller.js.coffee index efd2d7e3e9..5e6d051e93 100644 --- a/app/assets/javascripts/darkswarm/controllers/login_sidebar_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/login_sidebar_controller.js.coffee @@ -1,3 +1,14 @@ -window.LoginSidebarCtrl = Darkswarm.controller "LoginSidebarCtrl", ($scope) -> +window.LoginSidebarCtrl = Darkswarm.controller "LoginSidebarCtrl", ($scope, $http) -> + $scope.spree_user = { + remember_me: 0 + } + $scope.active = -> $scope.active_sidebar == '/login' + + $scope.submit = -> + $http.post("/user/spree_user/sign_in", {spree_user: $scope.spree_user}).success (data)-> + location.href = location.origin + location.pathname # Strips out hash fragments + .error (data) -> + $scope.errors = data.message + diff --git a/app/assets/javascripts/darkswarm/darkswarm.js.coffee b/app/assets/javascripts/darkswarm/darkswarm.js.coffee index 12bc0023c6..de8517124f 100644 --- a/app/assets/javascripts/darkswarm/darkswarm.js.coffee +++ b/app/assets/javascripts/darkswarm/darkswarm.js.coffee @@ -1,2 +1,5 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource", "filters"]).config ($httpProvider) -> $httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content') + $httpProvider.defaults.headers['common']['X-Requested-With'] = 'XMLHttpRequest' + $httpProvider.defaults.headers.common.Accept = "application/json, text/javascript, */*" + diff --git a/app/views/shared/_login_panel.html.haml b/app/views/shared/_login_panel.html.haml index a553add506..e2b3d561a8 100644 --- a/app/views/shared/_login_panel.html.haml +++ b/app/views/shared/_login_panel.html.haml @@ -1,6 +1,38 @@ .login-panel %a{href: "#"} Close #login-content{"ng-controller" => "LoginSidebarCtrl", "ng-show" => "active()"} - = render "home/login" + %h2 Login + %form{"ng-submit" => "submit()"} + .alert-box.alert{"ng-show" => "errors != null"} + {{ errors }} + .row + .large-12.columns + %label{for: "email"} Email + %input.title.input-text{name: "email", + type: "email", + tabindex: 1, + "ng-model" => "spree_user.email"} + .row + .large-12.columns + %label{for: "password"} Password + %input.title.input-text{name: "password", + type: "password", + autocomplete: "off", + tabindex: 2, + "ng-model" => "spree_user.password"} + .row + .large-12.columns + %label{for: "remember_me"} Remember Me + %input{name: "remember_me", + type: "checkbox", + value: "1", + "ng-model" => "spree_user.remember_me"} + .row + .large-12.columns + %input.button.primary{name: "commit", + tabindex: "3", + type: "submit", + value: "Login"} + #sign-up-content{"ng-controller" => "SignupSidebarCtrl", "ng-show" => "active()"} = render "home/signup"