From 04a714426f980146c3dfa23551c2d7581d9a31b7 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 6 May 2016 12:37:53 +1000 Subject: [PATCH 001/109] Integrate from Spree fork: Fix spree issues #3531 and #2210 (patch provided by leiyangyou) --- app/controllers/base_controller.rb | 1 + .../spree/admin/base_controller_decorator.rb | 2 ++ .../respond_with_decorator.rb | 26 +++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 lib/spree/core/controller_helpers/respond_with_decorator.rb diff --git a/app/controllers/base_controller.rb b/app/controllers/base_controller.rb index 58bf679dc3..a3b645d522 100644 --- a/app/controllers/base_controller.rb +++ b/app/controllers/base_controller.rb @@ -1,3 +1,4 @@ +require 'spree/core/controller_helpers/respond_with_decorator' require 'open_food_network/tag_rule_applicator' class BaseController < ApplicationController diff --git a/app/controllers/spree/admin/base_controller_decorator.rb b/app/controllers/spree/admin/base_controller_decorator.rb index 9888967f58..d5e62469cb 100644 --- a/app/controllers/spree/admin/base_controller_decorator.rb +++ b/app/controllers/spree/admin/base_controller_decorator.rb @@ -1,3 +1,5 @@ +require 'spree/core/controller_helpers/respond_with_decorator' + Spree::Admin::BaseController.class_eval do before_filter :warn_invalid_order_cycles diff --git a/lib/spree/core/controller_helpers/respond_with_decorator.rb b/lib/spree/core/controller_helpers/respond_with_decorator.rb new file mode 100644 index 0000000000..4910591b9c --- /dev/null +++ b/lib/spree/core/controller_helpers/respond_with_decorator.rb @@ -0,0 +1,26 @@ +module ActionController + class Base + def respond_with(*resources, &block) + raise "In order to use respond_with, first you need to declare the formats your " << + "controller responds to in the class level" if self.class.mimes_for_respond_to.empty? + + if collector = retrieve_collector_from_mimes(&block) + options = resources.size == 1 ? {} : resources.extract_options! + + # Fix spree issues #3531 and #2210 (patch provided by leiyangyou) + if defined_response = collector.response and !(Spree::BaseController.spree_responders[self.class.to_s.to_sym].try(:[], action_name.to_sym)) + if action = options.delete(:action) + render :action => action + else + defined_response.call + end + else + # The action name is needed for processing + options.merge!(:action_name => action_name.to_sym) + # If responder is not specified then pass in Spree::Responder + (options.delete(:responder) || Spree::Responder).call(self, resources, options) + end + end + end + end +end From 9989b76b7dd6f96e978db9668a2ab7062b8a0821 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 6 May 2016 16:42:28 +1000 Subject: [PATCH 002/109] Integrate from Spree fork: Calculators work against LineItems --- .../flat_percent_item_total_decorator.rb | 9 +++++++++ .../spree/calculator/flexi_rate_decorator.rb | 19 +++++++++++++++++++ .../spree/calculator/per_item_decorator.rb | 15 +++++++++++++++ app/models/spree/calculator_decorator.rb | 18 ++++++++++++++++++ .../flat_percent_item_total_spec.rb | 10 ++++++++++ .../spree/calculator/flexi_rate_spec.rb | 17 +++++++++++++++++ spec/models/spree/calculator/per_item_spec.rb | 12 ++++++++++++ .../spree/calculator/price_sack_spec.rb | 17 +++++++++++++++++ 8 files changed, 117 insertions(+) create mode 100644 app/models/spree/calculator/flat_percent_item_total_decorator.rb create mode 100644 app/models/spree/calculator/flexi_rate_decorator.rb create mode 100644 app/models/spree/calculator/per_item_decorator.rb create mode 100644 app/models/spree/calculator_decorator.rb create mode 100644 spec/models/spree/calculator/flat_percent_item_total_spec.rb create mode 100644 spec/models/spree/calculator/flexi_rate_spec.rb create mode 100644 spec/models/spree/calculator/per_item_spec.rb create mode 100644 spec/models/spree/calculator/price_sack_spec.rb diff --git a/app/models/spree/calculator/flat_percent_item_total_decorator.rb b/app/models/spree/calculator/flat_percent_item_total_decorator.rb new file mode 100644 index 0000000000..9012261dab --- /dev/null +++ b/app/models/spree/calculator/flat_percent_item_total_decorator.rb @@ -0,0 +1,9 @@ +module Spree + Calculator::FlatPercentItemTotal.class_eval do + def compute(object) + item_total = line_items_for(object).map(&:amount).sum + value = item_total * BigDecimal(self.preferred_flat_percent.to_s) / 100.0 + (value * 100).round.to_f / 100 + end + end +end diff --git a/app/models/spree/calculator/flexi_rate_decorator.rb b/app/models/spree/calculator/flexi_rate_decorator.rb new file mode 100644 index 0000000000..c4ef6183c2 --- /dev/null +++ b/app/models/spree/calculator/flexi_rate_decorator.rb @@ -0,0 +1,19 @@ +module Spree + Calculator::FlexiRate.class_eval do + def compute(object) + sum = 0 + max = self.preferred_max_items.to_i + items_count = line_items_for(object).map(&:quantity).sum + items_count.times do |i| + # check max value to avoid divide by 0 errors + if (max == 0 && i == 0) || (max > 0) && (i % max == 0) + sum += self.preferred_first_item.to_f + else + sum += self.preferred_additional_item.to_f + end + end + + sum + end + end +end diff --git a/app/models/spree/calculator/per_item_decorator.rb b/app/models/spree/calculator/per_item_decorator.rb new file mode 100644 index 0000000000..ebaaf6fc1c --- /dev/null +++ b/app/models/spree/calculator/per_item_decorator.rb @@ -0,0 +1,15 @@ +module Spree + Calculator::PerItem.class_eval do + def compute(object=nil) + return 0 if object.nil? + self.preferred_amount * line_items_for(object).reduce(0) do |sum, value| + if matching_products.blank? || matching_products.include?(value.product) + value_to_add = value.quantity + else + value_to_add = 0 + end + sum + value_to_add + end + end + end +end diff --git a/app/models/spree/calculator_decorator.rb b/app/models/spree/calculator_decorator.rb new file mode 100644 index 0000000000..f8be4d00ef --- /dev/null +++ b/app/models/spree/calculator_decorator.rb @@ -0,0 +1,18 @@ +module Spree + Calculator.class_eval do + + + private + + # Given an object which might be an Order or a LineItem (amongst + # others), return a collection of line items. + def line_items_for(object) + if object.respond_to? :line_items + object.line_items + else + [object] + end + end + + end +end diff --git a/spec/models/spree/calculator/flat_percent_item_total_spec.rb b/spec/models/spree/calculator/flat_percent_item_total_spec.rb new file mode 100644 index 0000000000..02b6e5d1ae --- /dev/null +++ b/spec/models/spree/calculator/flat_percent_item_total_spec.rb @@ -0,0 +1,10 @@ + describe Spree::Calculator::FlatPercentItemTotal do + let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new } + let(:line_item) { mock_model(Spree::LineItem, amount: 10) } + + before { calculator.stub :preferred_flat_percent => 10 } + + it "should compute amount correctly for a single line item" do + calculator.compute(line_item).should == 1.0 + end +end diff --git a/spec/models/spree/calculator/flexi_rate_spec.rb b/spec/models/spree/calculator/flexi_rate_spec.rb new file mode 100644 index 0000000000..a242429dca --- /dev/null +++ b/spec/models/spree/calculator/flexi_rate_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Spree::Calculator::FlexiRate do + let(:calculator) { Spree::Calculator::FlexiRate.new } + let(:line_item) { mock_model(Spree::LineItem, amount: 10, quantity: 4) } + + describe "computing for a single line item" do + it "returns the first item rate" do + calculator.stub preferred_first_item: 1.0 + calculator.compute(line_item).round(2).should == 1.0 + end + end + + it "allows creation of new object with all the attributes" do + Spree::Calculator::FlexiRate.new(preferred_first_item: 1, preferred_additional_item: 1, preferred_max_items: 1) + end +end diff --git a/spec/models/spree/calculator/per_item_spec.rb b/spec/models/spree/calculator/per_item_spec.rb new file mode 100644 index 0000000000..09c2fb02f6 --- /dev/null +++ b/spec/models/spree/calculator/per_item_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe Spree::Calculator::PerItem do + let(:calculator) { Spree::Calculator::PerItem.new(preferred_amount: 10) } + let(:shipping_calculable) { double(:calculable) } + let(:line_item) { double(:line_item, quantity: 5, product: double(:product)) } + + it "correctly calculates on a single line item object" do + calculator.stub(calculable: shipping_calculable) + calculator.compute(line_item).to_f.should == 50 # 5 x 10 + end +end diff --git a/spec/models/spree/calculator/price_sack_spec.rb b/spec/models/spree/calculator/price_sack_spec.rb new file mode 100644 index 0000000000..b34771c720 --- /dev/null +++ b/spec/models/spree/calculator/price_sack_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Spree::Calculator::PriceSack do + let(:calculator) do + calculator = Spree::Calculator::PriceSack.new + calculator.preferred_minimal_amount = 5 + calculator.preferred_normal_amount = 10 + calculator.preferred_discount_amount = 1 + calculator + end + + let(:line_item) { stub_model(Spree::LineItem, price: 1, quantity: 2) } + + it "computes with a line item object" do + calculator.compute(line_item) + end +end From ab707cf3125a81f4821344c20fef78590c0a4031 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 13 May 2016 17:49:55 +1000 Subject: [PATCH 003/109] Bundle incremental Spree upgrade --- Gemfile | 2 +- Gemfile.lock | 86 +++++++++++++++++++++++++++------------------------- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/Gemfile b/Gemfile index f28e8a8baa..5048bb12ac 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ gem 'i18n', '~> 0.6.11' gem 'nokogiri', '>= 1.6.7.1' gem 'pg' -gem 'spree', github: 'openfoodfoundation/spree', branch: '1-3-stable' +gem 'spree', github: 'openfoodfoundation/spree', branch: 'spree-upgrade-step1c' gem 'spree_i18n', github: 'spree/spree_i18n', branch: '1-3-stable' gem 'spree_auth_devise', github: 'spree/spree_auth_devise', branch: '1-3-stable' diff --git a/Gemfile.lock b/Gemfile.lock index a645196fc5..73af3c3eb1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -23,45 +23,48 @@ GIT GIT remote: git://github.com/openfoodfoundation/spree.git - revision: 6e3edfe40a5de8eba0095b2c5f3db9ea54c3afda - branch: 1-3-stable + revision: a4c439570b77afa50f9e36299811f293232bd281 + branch: spree-upgrade-step1c specs: - spree (1.3.6.beta) - spree_api (= 1.3.6.beta) - spree_cmd (= 1.3.6.beta) - spree_core (= 1.3.6.beta) - spree_promo (= 1.3.6.beta) - spree_sample (= 1.3.6.beta) - spree_api (1.3.6.beta) - spree_core (= 1.3.6.beta) + spree (1.3.99) + spree_api (= 1.3.99) + spree_cmd (= 1.3.99) + spree_core (= 1.3.99) + spree_dash (= 1.3.99) + spree_promo (= 1.3.99) + spree_sample (= 1.3.99) + spree_api (1.3.99) + spree_core (= 1.3.99) versioncake (= 0.4.0) - spree_cmd (1.3.6.beta) + spree_cmd (1.3.99) thor (>= 0.14.6) - spree_core (1.3.6.beta) - activemerchant (~> 1.34) + spree_core (1.3.99) + activemerchant (~> 1.50.0) acts_as_list (= 0.1.4) awesome_nested_set (= 2.1.5) aws-sdk (~> 1.11.1) cancan (= 1.6.8) deface (>= 0.9.0) ffaker (~> 1.15.0) - highline (= 1.6.18) - jquery-rails (~> 2.2.0) + highline (= 1.6.11) + jquery-rails (~> 2.0) json (>= 1.5.5) - kaminari (= 0.14.1) - money (= 5.1.1) + kaminari (= 0.13.0) + money (= 5.0.0) paperclip (~> 3.0) rabl (= 0.7.2) - rails (~> 3.2.16) + rails (~> 3.2.13) ransack (= 0.7.2) - select2-rails (= 3.5.9.3) - state_machine (= 1.1.2) + select2-rails (~> 3.2) + state_machine (= 1.2.0) stringex (~> 1.3.2) - truncate_html (~> 0.5.5) - spree_promo (1.3.6.beta) - spree_core (= 1.3.6.beta) - spree_sample (1.3.6.beta) - spree_core (= 1.3.6.beta) + spree_dash (1.3.99) + httparty (~> 0.8.1) + spree_core (= 1.3.99) + spree_promo (1.3.99) + spree_core (= 1.3.99) + spree_sample (1.3.99) + spree_core (= 1.3.99) GIT remote: git://github.com/spree/deface.git @@ -123,8 +126,8 @@ GEM sprockets (~> 2.2.1) active_model_serializers (0.8.3) activemodel (>= 3.0) - activemerchant (1.57.0) - activesupport (>= 3.2.14, < 5.1) + activemerchant (1.50.0) + activesupport (>= 3.2.14, < 5.0.0) builder (>= 2.1.2, < 4.0.0) i18n (>= 0.6.9) nokogiri (~> 1.4) @@ -167,9 +170,7 @@ GEM bcrypt-ruby (3.1.5) bcrypt (>= 3.1.3) blockenspiel (0.4.5) - bugsnag (1.5.2) - httparty (>= 0.6, < 1.0) - multi_json (~> 1.0) + bugsnag (4.1.0) builder (3.0.4) byebug (2.7.0) columnize (~> 0.3) @@ -189,7 +190,7 @@ GEM climate_control (0.0.3) activesupport (>= 3.0) cliver (0.3.2) - cocaine (0.5.7) + cocaine (0.5.8) climate_control (>= 0.0.3, < 1.0) coderay (1.0.9) coffee-rails (3.2.2) @@ -404,12 +405,12 @@ GEM zeus haml (4.0.4) tilt - highline (1.6.18) + highline (1.6.11) hike (1.2.3) http_parser.rb (0.5.3) - httparty (0.13.1) - json (~> 1.8) - multi_xml (>= 0.5.2) + httparty (0.8.3) + multi_json (~> 1.0) + multi_xml i18n (0.6.11) immigrant (0.1.6) activerecord (>= 3.0) @@ -417,16 +418,17 @@ GEM inflecto (0.0.2) ipaddress (0.8.0) journey (1.0.4) - jquery-rails (2.2.2) + jquery-rails (2.3.0) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) json (1.8.3) json_spec (1.1.1) multi_json (~> 1.0) rspec (~> 2.0) - kaminari (0.14.1) + kaminari (0.13.0) actionpack (>= 3.0.0) activesupport (>= 3.0.0) + railties (>= 3.0.0) kgio (2.9.3) knapsack (1.5.1) rake @@ -494,7 +496,7 @@ GEM activesupport (>= 2.3.14) multi_json (~> 1.0) rack (1.4.7) - rack-cache (1.2) + rack-cache (1.6.1) rack (>= 0.4) rack-livereload (0.3.15) rack @@ -521,7 +523,7 @@ GEM rdoc (~> 3.4) thor (>= 0.14.6, < 2.0) raindrops (0.13.0) - rake (10.4.2) + rake (11.1.2) ransack (0.7.2) actionpack (~> 3.0) activerecord (~> 3.0) @@ -571,7 +573,7 @@ GEM railties (~> 3.2.0) sass (>= 3.1.10) tilt (~> 1.3) - select2-rails (3.5.9.3) + select2-rails (3.5.10) thor (~> 0.14) shoulda-matchers (1.1.0) activesupport (>= 3.0.0) @@ -583,7 +585,7 @@ GEM multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - state_machine (1.1.2) + state_machine (1.2.0) stringex (1.3.3) therubyracer (0.12.0) libv8 (~> 3.16.14.0) @@ -601,7 +603,7 @@ GEM sprockets (>= 2.0.0) turn (0.8.3) ansi - tzinfo (0.3.44) + tzinfo (0.3.49) uglifier (2.7.1) execjs (>= 0.3.0) json (>= 1.8.0) From 0d4c4f20df985799c4c3810ea2f58f3a7c35061e Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 13 May 2016 12:30:10 +1000 Subject: [PATCH 004/109] Fix missing EnterpriseFee::Calculator error Conflicts: app/models/enterprise_fee.rb --- app/models/enterprise_fee.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/enterprise_fee.rb b/app/models/enterprise_fee.rb index d1d0816340..0fc44ac496 100644 --- a/app/models/enterprise_fee.rb +++ b/app/models/enterprise_fee.rb @@ -16,6 +16,10 @@ class EnterpriseFee < ActiveRecord::Base calculated_adjustments + # Class name is mis-inferred outside of Spree namespace + has_one :calculator, as: :calculable, dependent: :destroy, class_name: 'Spree::Calculator' + + attr_accessible :enterprise_id, :fee_type, :name, :tax_category_id, :calculator_type, :inherits_tax_category FEE_TYPES = %w(packing transport admin sales fundraising) From a0b740f52df995f9da8d60df99fcab363e0486e8 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 20 May 2016 11:01:54 +1000 Subject: [PATCH 005/109] Generalise fix for missing EnterpriseFee::Calculator to any use of calculated_adjustments --- app/models/enterprise_fee.rb | 3 --- config/initializers/spree.rb | 1 + lib/spree/core/calculated_adjustments_decorator.rb | 14 ++++++++++++++ .../enterprise_fee_applicator_spec.rb | 1 + 4 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 lib/spree/core/calculated_adjustments_decorator.rb diff --git a/app/models/enterprise_fee.rb b/app/models/enterprise_fee.rb index 0fc44ac496..ff118cf0aa 100644 --- a/app/models/enterprise_fee.rb +++ b/app/models/enterprise_fee.rb @@ -16,9 +16,6 @@ class EnterpriseFee < ActiveRecord::Base calculated_adjustments - # Class name is mis-inferred outside of Spree namespace - has_one :calculator, as: :calculable, dependent: :destroy, class_name: 'Spree::Calculator' - attr_accessible :enterprise_id, :fee_type, :name, :tax_category_id, :calculator_type, :inherits_tax_category diff --git a/config/initializers/spree.rb b/config/initializers/spree.rb index 30182ef63a..f439e779cf 100644 --- a/config/initializers/spree.rb +++ b/config/initializers/spree.rb @@ -8,6 +8,7 @@ require 'spree/product_filters' +require 'spree/core/calculated_adjustments_decorator' require "#{Rails.root}/app/models/spree/payment_method_decorator" require "#{Rails.root}/app/models/spree/gateway_decorator" diff --git a/lib/spree/core/calculated_adjustments_decorator.rb b/lib/spree/core/calculated_adjustments_decorator.rb new file mode 100644 index 0000000000..6a42eb68c5 --- /dev/null +++ b/lib/spree/core/calculated_adjustments_decorator.rb @@ -0,0 +1,14 @@ +module Spree + module Core + module CalculatedAdjustments + module ClassMethods + def calculated_adjustments_with_explicit_class_name + calculated_adjustments_without_explicit_class_name + # Class name is mis-inferred outside of Spree namespace + has_one :calculator, as: :calculable, dependent: :destroy, class_name: 'Spree::Calculator' + end + alias_method_chain :calculated_adjustments, :explicit_class_name + end + end + end +end diff --git a/spec/lib/open_food_network/enterprise_fee_applicator_spec.rb b/spec/lib/open_food_network/enterprise_fee_applicator_spec.rb index 9ad1d222b3..b1a9074efb 100644 --- a/spec/lib/open_food_network/enterprise_fee_applicator_spec.rb +++ b/spec/lib/open_food_network/enterprise_fee_applicator_spec.rb @@ -1,3 +1,4 @@ +require 'spec_helper' require 'open_food_network/enterprise_fee_applicator' module OpenFoodNetwork From 376c4c3e0ef7e8f5d150aea114fbd6c72b7b6320 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 20 May 2016 11:02:08 +1000 Subject: [PATCH 006/109] Fix factories - base_product and base_variant removed --- spec/factories.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/spec/factories.rb b/spec/factories.rb index 9875d22652..d4193c11aa 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -319,10 +319,6 @@ end FactoryGirl.modify do - factory :base_product do - unit_value 1 - unit_description '' - end factory :product do primary_taxon { Spree::Taxon.first || FactoryGirl.create(:taxon) } end @@ -336,12 +332,15 @@ FactoryGirl.modify do primary_taxon { Spree::Taxon.first || FactoryGirl.create(:taxon) } on_hand 3 + unit_value 1 + unit_description '' + variant_unit 'weight' variant_unit_scale 1 variant_unit_name '' end - factory :base_variant do + factory :variant do unit_value 1 unit_description '' end From d8907701047fbd5cd14ce1304ce4b074f5b3e5b0 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 20 May 2016 11:02:27 +1000 Subject: [PATCH 007/109] Order state transition condition removed in Spree --- app/models/spree/order_decorator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index f6128a736d..c62f114177 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -35,7 +35,7 @@ Spree::Order.class_eval do order.payment_required? } go_to_state :confirm, :if => lambda { |order| order.confirmation_required? } - go_to_state :complete, :if => lambda { |order| (order.payment_required? && order.has_unprocessed_payments?) || !order.payment_required? } + go_to_state :complete remove_transition :from => :delivery, :to => :confirm end From 32d2adc8a24f4352e8c6a0b179fe9ef351295f6d Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 20 May 2016 11:41:04 +1000 Subject: [PATCH 008/109] Fix mailers - provide from address --- app/mailers/spree/base_mailer_decorator.rb | 7 +++++++ app/mailers/spree/order_mailer_decorator.rb | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/app/mailers/spree/base_mailer_decorator.rb b/app/mailers/spree/base_mailer_decorator.rb index 339abd8901..949f51d2f0 100644 --- a/app/mailers/spree/base_mailer_decorator.rb +++ b/app/mailers/spree/base_mailer_decorator.rb @@ -6,6 +6,13 @@ Spree::BaseMailer.class_eval do layout 'mailer' protected + + def from_address + Spree::MailMethod.current.andand.preferred_mails_from || + 'test@example.com' + end + + def roadie_options # This lets us specify assets using relative paths in email templates super.merge(url_options: {host: URI(spree.root_url).host }) diff --git a/app/mailers/spree/order_mailer_decorator.rb b/app/mailers/spree/order_mailer_decorator.rb index 016b61efa4..4f81762c0d 100644 --- a/app/mailers/spree/order_mailer_decorator.rb +++ b/app/mailers/spree/order_mailer_decorator.rb @@ -3,6 +3,13 @@ Spree::OrderMailer.class_eval do helper CheckoutHelper helper SpreeCurrencyHelper + def cancel_email(order, resend = false) + @order = find_order(order) + subject = (resend ? "[#{t(:resend).upcase}] " : '') + subject += "#{Spree::Config[:site_name]} #{t('order_mailer.cancel_email.subject')} ##{order.number}" + mail(to: order.email, from: from_address, subject: subject) + end + def confirm_email_for_customer(order, resend = false) find_order(order) # Finds an order instance from an id subject = (resend ? "[#{t(:resend).upcase}] " : '') @@ -31,4 +38,8 @@ Spree::OrderMailer.class_eval do :subject => subject, :reply_to => @order.distributor.email) end + + def find_order(order) + order.respond_to?(:id) ? order : Spree::Order.find(order) + end end From 1765ba0422a47df2ea88fac4fb3e2585a4199f4d Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 20 May 2016 11:44:16 +1000 Subject: [PATCH 009/109] Fix spec dependent on product on_demand --- spec/models/spree/product_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 10e67e5900..2e188244a9 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -585,10 +585,10 @@ module Spree describe "finding products in stock for a particular distribution" do it "returns on-demand products" do p = create(:simple_product, on_demand: true) - p.master.update_attribute(:count_on_hand, 0) + p.variants.first.update_attributes!(count_on_hand: 0, on_demand: true) d = create(:distributor_enterprise) oc = create(:simple_order_cycle, distributors: [d]) - oc.exchanges.outgoing.first.variants << p.master + oc.exchanges.outgoing.first.variants << p.variants.first p.should have_stock_for_distribution(oc, d) end From 7f7ee25e2746b07e99ae9205e46de8a0413fc5d0 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 20 May 2016 12:26:58 +1000 Subject: [PATCH 010/109] Money accessors changed to dollars/cents --- lib/spree/money_decorator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/spree/money_decorator.rb b/lib/spree/money_decorator.rb index 3479bbd9a2..fc249151f4 100644 --- a/lib/spree/money_decorator.rb +++ b/lib/spree/money_decorator.rb @@ -6,7 +6,7 @@ Spree::Money.class_eval do end def rounded - @options[:no_cents] = true if @money.amount % 1 == 0 + @options[:no_cents] = true if @money.dollars % 1 == 0 to_s end end From 764219b9ed19d5500a256b0ee20dcf8b361a31bc Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 20 May 2016 12:27:15 +1000 Subject: [PATCH 011/109] Fix OrderMailer#find_order --- app/mailers/spree/order_mailer_decorator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/mailers/spree/order_mailer_decorator.rb b/app/mailers/spree/order_mailer_decorator.rb index 4f81762c0d..13f014d5a0 100644 --- a/app/mailers/spree/order_mailer_decorator.rb +++ b/app/mailers/spree/order_mailer_decorator.rb @@ -40,6 +40,6 @@ Spree::OrderMailer.class_eval do end def find_order(order) - order.respond_to?(:id) ? order : Spree::Order.find(order) + @order = order.respond_to?(:id) ? order : Spree::Order.find(order) end end From 8f0bc367d04c279935cf9712e032ebdb6865f357 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 20 May 2016 12:27:45 +1000 Subject: [PATCH 012/109] Include missing helpers --- app/controllers/application_controller.rb | 5 +++++ app/controllers/base_controller.rb | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7dfd404b69..c5eb745c05 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -20,6 +20,11 @@ class ApplicationController < ActionController::Base private + def action + params[:action].to_sym + end + + def require_distributor_chosen unless @distributor = current_distributor redirect_to spree.root_path diff --git a/app/controllers/base_controller.rb b/app/controllers/base_controller.rb index a3b645d522..36084c0225 100644 --- a/app/controllers/base_controller.rb +++ b/app/controllers/base_controller.rb @@ -3,7 +3,11 @@ require 'open_food_network/tag_rule_applicator' class BaseController < ApplicationController include Spree::Core::ControllerHelpers + include Spree::Core::ControllerHelpers::Auth + include Spree::Core::ControllerHelpers::Common + include Spree::Core::ControllerHelpers::Order include Spree::Core::ControllerHelpers::RespondWith + include EnterprisesHelper include OrderCyclesHelper From 1497d2c3bb1b868d31c45f23162868438ec53c87 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 20 May 2016 16:26:20 +1000 Subject: [PATCH 013/109] Remove missing call --- app/controllers/spree/orders_controller_decorator.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/spree/orders_controller_decorator.rb b/app/controllers/spree/orders_controller_decorator.rb index 2c0ff8562c..dd4f2eba81 100644 --- a/app/controllers/spree/orders_controller_decorator.rb +++ b/app/controllers/spree/orders_controller_decorator.rb @@ -40,7 +40,6 @@ Spree::OrdersController.class_eval do if @order.update_attributes(params[:order]) @order.line_items = @order.line_items.select {|li| li.quantity > 0 } - @order.restart_checkout_flow render :edit and return unless apply_coupon_code From 257441c9be4c4b384c175fa35b1459a625fe24f8 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 20 May 2016 16:26:54 +1000 Subject: [PATCH 014/109] Re-add object-level auth to Spree::Admin::ResourceController --- .../spree/admin/resource_controller_decorator.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 app/controllers/spree/admin/resource_controller_decorator.rb diff --git a/app/controllers/spree/admin/resource_controller_decorator.rb b/app/controllers/spree/admin/resource_controller_decorator.rb new file mode 100644 index 0000000000..cb789d7330 --- /dev/null +++ b/app/controllers/spree/admin/resource_controller_decorator.rb @@ -0,0 +1,16 @@ +module AuthorizeOnLoadResource + def load_resource + super + + if member_action? + # If we don't have access, clear the object + unless can? action, @object + instance_variable_set("@#{object_name}", nil) + end + + authorize! action, @object + end + end +end + +Spree::Admin::ResourceController.send(:prepend, AuthorizeOnLoadResource) From a3b91dabe5fbd2e810ce7f6330c88a057d6eb753 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 20 May 2016 16:27:23 +1000 Subject: [PATCH 015/109] TEMP: Remove override for no-longer-present method --- .../admin/navigation_helper_decorator.rb | 16 +++++------ spec/helpers/navigation_helper_spec.rb | 28 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app/helpers/spree/admin/navigation_helper_decorator.rb b/app/helpers/spree/admin/navigation_helper_decorator.rb index eb210ef482..d7d0bd2370 100644 --- a/app/helpers/spree/admin/navigation_helper_decorator.rb +++ b/app/helpers/spree/admin/navigation_helper_decorator.rb @@ -4,14 +4,14 @@ module Spree # Make it so that the Reports admin tab can be enabled/disabled through the cancan # :report resource, since it does not have a corresponding resource class (unlike # eg. Spree::Product). - def klass_for_with_sym_fallback(name) - klass = klass_for_without_sym_fallback(name) - klass ||= name.singularize.to_sym - klass = :overview if klass == :dashboard - klass = Spree::Order if klass == :bulk_order_management - klass - end - alias_method_chain :klass_for, :sym_fallback + # def klass_for_with_sym_fallback(name) + # klass = klass_for_without_sym_fallback(name) + # klass ||= name.singularize.to_sym + # klass = :overview if klass == :dashboard + # klass = Spree::Order if klass == :bulk_order_management + # klass + # end + # alias_method_chain :klass_for, :sym_fallback end end end diff --git a/spec/helpers/navigation_helper_spec.rb b/spec/helpers/navigation_helper_spec.rb index 76fe9573f2..e90d46cd57 100644 --- a/spec/helpers/navigation_helper_spec.rb +++ b/spec/helpers/navigation_helper_spec.rb @@ -3,23 +3,23 @@ require 'spec_helper' module Spree module Admin describe NavigationHelper do - describe "klass_for" do - it "returns the class when present" do - helper.klass_for('products').should == Spree::Product - end + # describe "klass_for" do + # it "returns the class when present" do + # helper.klass_for('products').should == Spree::Product + # end - it "returns a symbol when there's no available class" do - helper.klass_for('reports').should == :report - end + # it "returns a symbol when there's no available class" do + # helper.klass_for('reports').should == :report + # end - it "returns :overview for the dashboard" do - helper.klass_for('dashboard').should == :overview - end + # it "returns :overview for the dashboard" do + # helper.klass_for('dashboard').should == :overview + # end - it "returns Spree::Order for bulk_order_management" do - helper.klass_for('bulk_order_management').should == Spree::Order - end - end + # it "returns Spree::Order for bulk_order_management" do + # helper.klass_for('bulk_order_management').should == Spree::Order + # end + # end end end end From 675332033600daaadb5067cf622795cb1effc161 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 8 Jun 2016 16:27:54 +1000 Subject: [PATCH 016/109] Do not stomp address on create --- app/controllers/admin/enterprises_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index 377186b604..d88d30cb16 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -115,8 +115,8 @@ module Admin def build_resource_with_address enterprise = build_resource_without_address - enterprise.address = Spree::Address.new - enterprise.address.country = Spree::Country.find_by_id(Spree::Config[:default_country_id]) + enterprise.address ||= Spree::Address.new + enterprise.address.country ||= Spree::Country.find_by_id(Spree::Config[:default_country_id]) enterprise end alias_method_chain :build_resource, :address From a6a0bdb063df92df03917df45420e31bd3797029 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 8 Jun 2016 14:44:47 +1000 Subject: [PATCH 017/109] Sanitize values before they're used --- app/controllers/admin/enterprises_controller.rb | 6 ++++-- app/models/enterprise.rb | 2 ++ spec/controllers/admin/enterprises_controller_spec.rb | 8 +++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index d88d30cb16..8b7efa9e1f 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -2,6 +2,10 @@ require 'open_food_network/referer_parser' module Admin class EnterprisesController < ResourceController + # These need to run before #load_resource so that @object is initialised with sanitised values + prepend_before_filter :override_owner, only: :create + prepend_before_filter :override_sells, only: :create + before_filter :load_enterprise_set, :only => :index before_filter :load_countries, :except => [:index, :register, :check_permalink] before_filter :load_methods_and_fees, :only => [:edit, :update] @@ -9,8 +13,6 @@ module Admin before_filter :load_taxons, :only => [:new, :edit, :update, :create] before_filter :check_can_change_sells, only: :update before_filter :check_can_change_bulk_sells, only: :bulk_update - before_filter :override_owner, only: :create - before_filter :override_sells, only: :create before_filter :check_can_change_owner, only: :update before_filter :check_can_change_bulk_owner, only: :bulk_update before_filter :check_can_change_managers, only: :update diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 80414d5116..5460ecc94d 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -78,6 +78,7 @@ class Enterprise < ActiveRecord::Base validate :enforce_ownership_limit, if: lambda { owner_id_changed? && !owner_id.nil? } validates_length_of :description, :maximum => 255 + before_save :confirmation_check, if: lambda { email_changed? } before_validation :initialize_permalink, if: lambda { permalink.nil? } @@ -93,6 +94,7 @@ class Enterprise < ActiveRecord::Base after_rollback :restore_permalink + scope :by_name, order('name') scope :visible, where(visible: true) scope :confirmed, where('confirmed_at IS NOT NULL') diff --git a/spec/controllers/admin/enterprises_controller_spec.rb b/spec/controllers/admin/enterprises_controller_spec.rb index 05498010bb..94282e6c2c 100644 --- a/spec/controllers/admin/enterprises_controller_spec.rb +++ b/spec/controllers/admin/enterprises_controller_spec.rb @@ -28,6 +28,7 @@ module Admin spree_put :create, enterprise_params enterprise = Enterprise.find_by_name 'zzz' + response.should redirect_to edit_admin_enterprise_path enterprise distributor_manager.enterprise_roles.where(enterprise_id: enterprise).first.should be end @@ -37,15 +38,17 @@ module Admin spree_put :create, enterprise_params enterprise = Enterprise.find_by_name 'zzz' + response.should redirect_to edit_admin_enterprise_path enterprise admin_user.enterprise_roles.where(enterprise_id: enterprise).should be_empty end - it "overrides the owner_id submitted by the user unless current_user is super admin" do + it "overrides the owner_id submitted by the user (when not super admin)" do controller.stub spree_current_user: distributor_manager enterprise_params[:enterprise][:owner_id] = user spree_put :create, enterprise_params enterprise = Enterprise.find_by_name 'zzz' + response.should redirect_to edit_admin_enterprise_path enterprise distributor_manager.enterprise_roles.where(enterprise_id: enterprise).first.should be end @@ -58,6 +61,7 @@ module Admin spree_put :create, enterprise_params enterprise = Enterprise.find_by_name 'zzz' + response.should redirect_to edit_admin_enterprise_path enterprise enterprise.sells.should == 'any' end @@ -68,6 +72,7 @@ module Admin spree_put :create, enterprise_params enterprise = Enterprise.find_by_name 'zzz' + response.should redirect_to edit_admin_enterprise_path enterprise enterprise.sells.should == 'none' end @@ -80,6 +85,7 @@ module Admin spree_put :create, enterprise_params enterprise = Enterprise.find_by_name 'zzz' + response.should redirect_to edit_admin_enterprise_path enterprise enterprise.sells.should == 'none' end end From 0e354f8fc10913900debabab83dc81dd2820ab67 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 8 Jun 2016 14:56:30 +1000 Subject: [PATCH 018/109] Remove unused currency config vars: decimal_mark, thousands_separator --- app/serializers/api/currency_config_serializer.rb | 14 +++----------- .../filters/localize_currency_spec.js.coffee | 5 ----- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/app/serializers/api/currency_config_serializer.rb b/app/serializers/api/currency_config_serializer.rb index ab17af8715..100a4a0b84 100644 --- a/app/serializers/api/currency_config_serializer.rb +++ b/app/serializers/api/currency_config_serializer.rb @@ -1,5 +1,5 @@ class Api::CurrencyConfigSerializer < ActiveModel::Serializer - attributes :currency, :display_currency, :symbol, :symbol_position, :hide_cents, :decimal_mark, :thousands_separator + attributes :currency, :display_currency, :symbol, :symbol_position, :hide_cents def currency Spree::Config[:currency] @@ -13,20 +13,12 @@ class Api::CurrencyConfigSerializer < ActiveModel::Serializer ::Money.new(1, Spree::Config[:currency]).symbol end - def symbol_position + def symbol_position Spree::Config[:currency_symbol_position] end - def hide_cents + def hide_cents Spree::Config[:hide_cents] end - def decimal_mark - Spree::Config[:currency_decimal_mark] - end - - def thousands_separator - Spree::Config[:currency_thousands_separator] - end - end diff --git a/spec/javascripts/unit/darkswarm/filters/localize_currency_spec.js.coffee b/spec/javascripts/unit/darkswarm/filters/localize_currency_spec.js.coffee index 0d21c7de6c..2a690ed0a7 100644 --- a/spec/javascripts/unit/darkswarm/filters/localize_currency_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/filters/localize_currency_spec.js.coffee @@ -7,9 +7,6 @@ describe 'convert number to localised currency ', -> symbol_position: "before" currency: "D" hide_cents: "false" - # Not used yet... - # decimal_mark: "." - # thousands_separator: "," module 'Darkswarm' module ($provide)-> $provide.value "currencyConfig", currencyconfig @@ -38,5 +35,3 @@ describe 'convert number to localised currency ', -> it "can hide cents", -> currencyconfig.hide_cents = "true" expect(filter(5)).toEqual "$5" - - From 225e43629346a9fe1194961622afc75f90d45c99 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 8 Jun 2016 14:56:41 +1000 Subject: [PATCH 019/109] Update included tax param before loading resource --- app/controllers/spree/admin/adjustments_controller_decorator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/spree/admin/adjustments_controller_decorator.rb b/app/controllers/spree/admin/adjustments_controller_decorator.rb index 12c9ae2c60..d3059b4804 100644 --- a/app/controllers/spree/admin/adjustments_controller_decorator.rb +++ b/app/controllers/spree/admin/adjustments_controller_decorator.rb @@ -1,7 +1,7 @@ module Spree module Admin AdjustmentsController.class_eval do - before_filter :set_included_tax, only: [:create, :update] + prepend_before_filter :set_included_tax, only: [:create, :update] before_filter :set_default_tax_rate, only: :edit From fdd6400cb8271fb0bd839f1e638cfaa6b227144c Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 8 Jun 2016 15:14:24 +1000 Subject: [PATCH 020/109] Fix perms for API soft delete --- app/models/spree/ability_decorator.rb | 4 ++-- spec/controllers/spree/api/variants_controller_spec.rb | 2 +- spec/models/spree/ability_spec.rb | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index c2bd99bcdb..b10c781fac 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -110,12 +110,12 @@ class AbilityDecorator def add_product_management_abilities(user) # Enterprise User can only access products that they are a supplier for can [:create], Spree::Product - can [:admin, :read, :update, :product_distributions, :bulk_edit, :bulk_update, :clone, :destroy], Spree::Product do |product| + can [:admin, :read, :update, :product_distributions, :bulk_edit, :bulk_update, :clone, :delete, :destroy], Spree::Product do |product| OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include? product.supplier end can [:create], Spree::Variant - can [:admin, :index, :read, :edit, :update, :search, :destroy], Spree::Variant do |variant| + can [:admin, :index, :read, :edit, :update, :search, :delete, :destroy], Spree::Variant do |variant| OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include? variant.product.supplier end diff --git a/spec/controllers/spree/api/variants_controller_spec.rb b/spec/controllers/spree/api/variants_controller_spec.rb index 5fb9f2f2a0..39c6439999 100644 --- a/spec/controllers/spree/api/variants_controller_spec.rb +++ b/spec/controllers/spree/api/variants_controller_spec.rb @@ -47,7 +47,7 @@ module Spree spree_delete :soft_delete, {variant_id: variant.to_param, product_id: product.to_param, format: :json} response.status.should == 204 lambda { variant.reload }.should_not raise_error - variant.deleted_at.should_not be_nil + variant.deleted_at.should be_present end it "is denied access to soft deleting another enterprises' variant" do diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 44321353fe..0057d377e7 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -149,13 +149,13 @@ module Spree it "should be able to read/write their enterprises' products and variants" do should have_ability([:admin, :read, :update, :product_distributions, :bulk_edit, :bulk_update, :clone, :destroy], for: p1) - should have_ability([:admin, :index, :read, :edit, :update, :search, :destroy], for: p1.master) + should have_ability([:admin, :index, :read, :edit, :update, :search, :destroy, :delete], for: p1.master) end it "should be able to read/write related enterprises' products and variants with manage_products permission" do er_ps should have_ability([:admin, :read, :update, :product_distributions, :bulk_edit, :bulk_update, :clone, :destroy], for: p_related) - should have_ability([:admin, :index, :read, :edit, :update, :search, :destroy], for: p_related.master) + should have_ability([:admin, :index, :read, :edit, :update, :search, :destroy, :delete], for: p_related.master) end it "should not be able to read/write other enterprises' products and variants" do @@ -173,7 +173,7 @@ module Spree it "should be able to read/write their enterprises' product variants" do should have_ability([:create], for: Spree::Variant) - should have_ability([:admin, :index, :read, :create, :edit, :search, :update, :destroy], for: p1.master) + should have_ability([:admin, :index, :read, :create, :edit, :search, :update, :destroy, :delete], for: p1.master) end it "should not be able to read/write other enterprises' product variants" do From d3a3b2da9a4d8e36727e36be5216d7efdbd85e92 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 8 Jun 2016 15:32:56 +1000 Subject: [PATCH 021/109] Pin jquery-rails to 2.1.4 (older version) to prevent missing $.browser errors --- Gemfile | 2 +- Gemfile.lock | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 5048bb12ac..e2c656bbdd 100644 --- a/Gemfile +++ b/Gemfile @@ -86,7 +86,7 @@ end gem "foundation-rails" gem 'foundation_rails_helper', github: 'willrjmarshall/foundation_rails_helper', branch: "rails3" -gem 'jquery-rails' +gem 'jquery-rails', '2.1.4' gem 'css_splitter' diff --git a/Gemfile.lock b/Gemfile.lock index 73af3c3eb1..afb7f53f2f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -418,7 +418,7 @@ GEM inflecto (0.0.2) ipaddress (0.8.0) journey (1.0.4) - jquery-rails (2.3.0) + jquery-rails (2.1.4) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) json (1.8.3) @@ -684,7 +684,7 @@ DEPENDENCIES haml i18n (~> 0.6.11) immigrant - jquery-rails + jquery-rails (= 2.1.4) json_spec knapsack letter_opener @@ -730,5 +730,8 @@ DEPENDENCIES wicked_pdf wkhtmltopdf-binary +RUBY VERSION + ruby 2.1.5p273 + BUNDLED WITH 1.12.5 From 6546d2763b4d22c55e7dd8942e16f9312366e39b Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 8 Jun 2016 16:21:33 +1000 Subject: [PATCH 022/109] Add Spree::Money#to_html (from Spree 2.0) --- lib/spree/money_decorator.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/spree/money_decorator.rb b/lib/spree/money_decorator.rb index fc249151f4..755041df0e 100644 --- a/lib/spree/money_decorator.rb +++ b/lib/spree/money_decorator.rb @@ -9,4 +9,15 @@ Spree::Money.class_eval do @options[:no_cents] = true if @money.dollars % 1 == 0 to_s end + + def to_html(options = { :html => true }) + output = @money.format(@options.merge(options)) + if options[:html] + # 1) prevent blank, breaking spaces + # 2) prevent escaping of HTML character entities + output = output.sub(" ", " ").html_safe + end + output + end + end From 8ce917a4222207edb106f15a0acf36807839b893 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 8 Jun 2016 16:45:29 +1000 Subject: [PATCH 023/109] Work around click obscuration --- spec/features/admin/enterprises_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/admin/enterprises_spec.rb b/spec/features/admin/enterprises_spec.rb index b436c0c3ee..865ce15b57 100644 --- a/spec/features/admin/enterprises_spec.rb +++ b/spec/features/admin/enterprises_spec.rb @@ -77,7 +77,7 @@ feature %q{ visit '/admin/enterprises' within "tr.enterprise-#{@enterprise.id}" do - all("a", text: 'Edit Profile').first.click + first("a", text: 'Edit Profile').trigger 'click' end fill_in 'enterprise_name', :with => 'Eaterprises' From d8f8c0df166d0703d45f797c1a9db02926a40198 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 10 Jun 2016 10:34:09 +1000 Subject: [PATCH 024/109] Fix API auth: Need Spree::Api::UsersController for authorise_api action to work --- app/controllers/spree/api/users_controller.rb | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 app/controllers/spree/api/users_controller.rb diff --git a/app/controllers/spree/api/users_controller.rb b/app/controllers/spree/api/users_controller.rb new file mode 100644 index 0000000000..74f83f6709 --- /dev/null +++ b/app/controllers/spree/api/users_controller.rb @@ -0,0 +1,7 @@ +module Spree + module Api + class UsersController < Spree::Api::BaseController + respond_to :json + end + end +end From 48acf80c85719f6eb97662655c2972d865f3a85c Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 10 Jun 2016 11:02:49 +1000 Subject: [PATCH 025/109] Fix tabbing --- lib/open_food_network/referer_parser.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/open_food_network/referer_parser.rb b/lib/open_food_network/referer_parser.rb index b90ef21829..20be289f44 100644 --- a/lib/open_food_network/referer_parser.rb +++ b/lib/open_food_network/referer_parser.rb @@ -5,13 +5,11 @@ module OpenFoodNetwork end def self.parse_uri(string) - begin - # TODO: make this operation obsolete by fixing URLs generated by AngularJS - string.sub!('##', '#') - URI(string) - rescue URI::InvalidURIError - nil - end + # TODO: make this operation obsolete by fixing URLs generated by AngularJS + string.sub!('##', '#') + URI(string) + rescue URI::InvalidURIError + nil end end end From f33df883a060669298dd6be8069a57513fa679c1 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 10 Jun 2016 11:24:23 +1000 Subject: [PATCH 026/109] Fix link_to_remove_fields - does not immediately delete the field --- app/assets/javascripts/admin/util.js.erb | 16 ++++++++++++---- app/controllers/admin/enterprises_controller.rb | 7 ++++--- app/helpers/spree/admin/base_helper_decorator.rb | 16 ---------------- spec/features/admin/enterprises_spec.rb | 13 +++++++------ 4 files changed, 23 insertions(+), 29 deletions(-) diff --git a/app/assets/javascripts/admin/util.js.erb b/app/assets/javascripts/admin/util.js.erb index bc963d4612..4a8c526414 100644 --- a/app/assets/javascripts/admin/util.js.erb +++ b/app/assets/javascripts/admin/util.js.erb @@ -30,8 +30,16 @@ show_flash_error = function(message) { } $(document).ready(function(){ - $('a.close').click(function(event){ - event.preventDefault(); - $(this).parent().slideUp(250); - }); + $('a.close').click(function(event){ + event.preventDefault(); + $(this).parent().slideUp(250); + }); + + // Spree locates hidden with prev(), which with our current version of jQuery + // does not locate the hidden field, resulting in the delete failing. This + // handler updates the hidden field, fixing the problem. + $('body').on('click', 'a.remove_fields', function() { + $(this).next("input[type=hidden]").val("1"); + return false; + }); }); diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index 8b7efa9e1f..f2c9552c4a 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -271,9 +271,10 @@ module Admin # Overriding method on Spree's resource controller def location_after_save referer_path = OpenFoodNetwork::RefererParser::path(request.referer) - refered_from_edit = referer_path =~ /\/edit$/ - if params[:enterprise].key?(:producer_properties_attributes) && !refered_from_edit - main_app.admin_enterprises_path + refered_from_producer_properties = referer_path =~ /\/producer_properties$/ + + if refered_from_producer_properties + main_app.admin_enterprise_producer_properties_path(@enterprise) else main_app.edit_admin_enterprise_path(@enterprise) end diff --git a/app/helpers/spree/admin/base_helper_decorator.rb b/app/helpers/spree/admin/base_helper_decorator.rb index 86e77431ba..ff9e2b76a7 100644 --- a/app/helpers/spree/admin/base_helper_decorator.rb +++ b/app/helpers/spree/admin/base_helper_decorator.rb @@ -1,22 +1,6 @@ module Spree module Admin module BaseHelper - # Add url option to pass in link URL - def link_to_remove_fields(name, f, options = {}) - name = '' if options[:no_text] - options[:class] = '' unless options[:class] - options[:class] += 'no-text' if options[:no_text] - - url = if f.object.persisted? - options[:url] || [:admin, f.object] - else - '#' - end - - link_to_with_icon('icon-trash', name, url, :class => "remove_fields #{options[:class]}", :data => {:action => 'remove'}, :title => t(:remove)) + f.hidden_field(:_destroy) - end - - def preference_field_tag_with_files(name, value, options) if options[:type] == :file file_field_tag name, preference_field_options(options) diff --git a/spec/features/admin/enterprises_spec.rb b/spec/features/admin/enterprises_spec.rb index 865ce15b57..8b5a08a7cc 100644 --- a/spec/features/admin/enterprises_spec.rb +++ b/spec/features/admin/enterprises_spec.rb @@ -210,8 +210,8 @@ feature %q{ fill_in 'enterprise_producer_properties_attributes_0_value', with: "NASAA 12345" click_button 'Update' - # Then I should be returned to the enterprises page - page.should have_selector '#listing_enterprises a', text: s.name + # Then I should remain on the producer properties page + expect(current_path).to eq main_app.admin_enterprise_producer_properties_path(s) # And the producer should have the property s.producer_properties(true).count.should == 1 @@ -233,8 +233,8 @@ feature %q{ fill_in 'enterprise_producer_properties_attributes_0_value', with: "Shininess" click_button 'Update' - # Then I should be returned to the enterprises - page.should have_selector '#listing_enterprises a', text: s.name + # Then I should remain on the producer properties page + expect(current_path).to eq main_app.admin_enterprise_producer_properties_path(s) # And the property should be updated s.producer_properties(true).count.should == 1 @@ -254,9 +254,10 @@ feature %q{ # And I remove the property page.should have_field 'enterprise_producer_properties_attributes_0_property_name', with: 'Certified Organic' within("#spree_producer_property_#{pp.id}") { page.find('a.remove_fields').click } + click_button 'Update' # Then the property should have been removed - page.should_not have_selector '#progress' + expect(current_path).to eq main_app.admin_enterprise_producer_properties_path(s) page.should_not have_field 'enterprise_producer_properties_attributes_0_property_name', with: 'Certified Organic' s.producer_properties(true).should be_empty end @@ -438,7 +439,7 @@ feature %q{ end within("#spree_producer_property_#{pp.id}") { page.find('a.remove_fields').click } - page.should_not have_selector '#progress' + click_button 'Update' supplier1.producer_properties(true).should be_empty end end From a1535d6c04dc5edf9a42d76ec7d13d39ab2feb80 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 10 Jun 2016 12:12:36 +1000 Subject: [PATCH 027/109] Catch flash messages after fadeout --- spec/support/request/web_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/request/web_helper.rb b/spec/support/request/web_helper.rb index 23f9757c5b..bca12c46ab 100644 --- a/spec/support/request/web_helper.rb +++ b/spec/support/request/web_helper.rb @@ -78,7 +78,7 @@ module WebHelper end def flash_message - find('.flash').text.strip + find('.flash', visible: false).text.strip end def errors From 20e6b703d939a81b721adbf3fe54e1540254b37d Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 10 Jun 2016 12:13:02 +1000 Subject: [PATCH 028/109] Rewrite alias_method to alias_method_chain --- .../spree/admin/products_controller_decorator.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/controllers/spree/admin/products_controller_decorator.rb b/app/controllers/spree/admin/products_controller_decorator.rb index 5b1bb347a1..7929aa1df6 100644 --- a/app/controllers/spree/admin/products_controller_decorator.rb +++ b/app/controllers/spree/admin/products_controller_decorator.rb @@ -8,7 +8,6 @@ Spree::Admin::ProductsController.class_eval do before_filter :load_spree_api_key, :only => [:bulk_edit, :variant_overrides] before_filter :strip_new_properties, only: [:create, :update] - alias_method :location_after_save_original, :location_after_save respond_to :json, :only => :clone @@ -53,14 +52,17 @@ Spree::Admin::ProductsController.class_eval do protected - def location_after_save + + def location_after_save_with_bulk_edit referer_path = OpenFoodNetwork::RefererParser::path(request.referer) + if referer_path == '/admin/products/bulk_edit' bulk_edit_admin_products_url else - location_after_save_original + location_after_save_without_bulk_edit end end + alias_method_chain :location_after_save, :bulk_edit def collection # This method is copied directly from the spree product controller, except where we narrow the search below with the managed_by search to support From 7a68cc7da177e815c584fc43a83b5003aad6ec7f Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 10 Jun 2016 12:14:47 +1000 Subject: [PATCH 029/109] Remove spec for product distributions --- spec/features/admin/products_spec.rb | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/spec/features/admin/products_spec.rb b/spec/features/admin/products_spec.rb index 2aafc82b02..7f391c1bc4 100644 --- a/spec/features/admin/products_spec.rb +++ b/spec/features/admin/products_spec.rb @@ -56,22 +56,6 @@ feature %q{ product.group_buy.should be_false product.master.option_values.map(&:name).should == ['5kg'] product.master.options_text.should == "5kg" - - # Distributors - visit spree.product_distributions_admin_product_path(product) - - check @distributors[0].name - select2_select @enterprise_fees[0].name, :from => 'product_product_distributions_attributes_0_enterprise_fee_id' - check @distributors[2].name - select2_select @enterprise_fees[2].name, :from => 'product_product_distributions_attributes_2_enterprise_fee_id' - - click_button 'Update' - - product.reload - product.distributors.should match_array [@distributors[0], @distributors[2]] - - - product.product_distributions.map { |pd| pd.enterprise_fee }.should match_array [@enterprise_fees[0], @enterprise_fees[2]] end scenario "making a product into a group buy product" do From 4117b32ebd913cb8baf9c7fa374a962ff58be811 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 10 Jun 2016 12:15:35 +1000 Subject: [PATCH 030/109] Fix specs: We had no permission to update the specified supplier (how did this ever work?) --- spec/features/admin/products_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/features/admin/products_spec.rb b/spec/features/admin/products_spec.rb index 7f391c1bc4..445844d97b 100644 --- a/spec/features/admin/products_spec.rb +++ b/spec/features/admin/products_spec.rb @@ -39,6 +39,7 @@ feature %q{ click_button 'Create' + expect(current_path).to eq spree.bulk_edit_admin_products_path flash_message.should == 'Product "A new product !!!" has been successfully created!' product = Spree::Product.find_by_name('A new product !!!') product.supplier.should == @supplier @@ -169,7 +170,7 @@ feature %q{ scenario "deleting product properties", js: true do # Given a product with a property - p = create(:simple_product, supplier: @supplier) + p = create(:simple_product, supplier: @supplier2) p.set_property('fooprop', 'fooval') # When I navigate to the product properties page @@ -179,11 +180,12 @@ feature %q{ # And I delete the property page.all('a.remove_fields').first.click - wait_until { p.reload.property('fooprop').nil? } + click_button 'Update' # Then the property should have been deleted page.should_not have_field 'product_product_properties_attributes_0_property_name', with: 'fooprop' page.should_not have_field 'product_product_properties_attributes_0_value', with: 'fooval' + expect(p.reload.property('fooprop')).to be_nil end From 7d79fffa33426dfba1f7775a3abaf968333f5bef Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 10 Jun 2016 12:21:10 +1000 Subject: [PATCH 031/109] Reinstate Spree::PaymentMethod::DISPLAY (removed in Spree but used by us) --- app/models/spree/payment_method_decorator.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/spree/payment_method_decorator.rb b/app/models/spree/payment_method_decorator.rb index 4a718dfc16..fb5faaef4c 100644 --- a/app/models/spree/payment_method_decorator.rb +++ b/app/models/spree/payment_method_decorator.rb @@ -1,4 +1,6 @@ Spree::PaymentMethod.class_eval do + Spree::PaymentMethod::DISPLAY = [:both, :front_end, :back_end] + acts_as_taggable has_and_belongs_to_many :distributors, join_table: 'distributors_payment_methods', :class_name => 'Enterprise', association_foreign_key: 'distributor_id' From 0e013501078cde7e4d118257e285e1830001983e Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 10 Jun 2016 15:22:38 +1000 Subject: [PATCH 032/109] Use jquery-migrate instead of downgrading jQuery to access $.browser --- Gemfile | 3 ++- Gemfile.lock | 11 +++++++---- app/assets/javascripts/admin/all.js | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index e2c656bbdd..6e48dd97d5 100644 --- a/Gemfile +++ b/Gemfile @@ -86,7 +86,8 @@ end gem "foundation-rails" gem 'foundation_rails_helper', github: 'willrjmarshall/foundation_rails_helper', branch: "rails3" -gem 'jquery-rails', '2.1.4' +gem 'jquery-rails' +gem 'jquery-migrate-rails' gem 'css_splitter' diff --git a/Gemfile.lock b/Gemfile.lock index afb7f53f2f..d346f7aa03 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -418,7 +418,8 @@ GEM inflecto (0.0.2) ipaddress (0.8.0) journey (1.0.4) - jquery-rails (2.1.4) + jquery-migrate-rails (1.2.1) + jquery-rails (2.3.0) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) json (1.8.3) @@ -451,8 +452,9 @@ GEM mini_portile2 (2.0.0) momentjs-rails (2.5.1) railties (>= 3.1) - money (5.1.1) - i18n (~> 0.6.0) + money (5.0.0) + i18n (~> 0.4) + json multi_json (1.12.1) multi_xml (0.5.5) newrelic_rpm (3.12.0.288) @@ -684,7 +686,8 @@ DEPENDENCIES haml i18n (~> 0.6.11) immigrant - jquery-rails (= 2.1.4) + jquery-migrate-rails + jquery-rails json_spec knapsack letter_opener diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 0923e5721c..102cb237ca 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -6,6 +6,7 @@ // //= require jquery +//= require jquery-migrate-min //= require jquery_ujs //= require jquery-ui //= require shared/jquery-ui-timepicker-addon From 3e565ad7cbbc2719bcfeff75d956bfc85f914f53 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 31 Aug 2016 15:40:21 +1000 Subject: [PATCH 033/109] Mark admin enterprises form dirty when property removed --- .../controllers/enterprise_controller.js.coffee | 2 +- app/helpers/spree/admin/base_helper_decorator.rb | 14 ++++++++++++++ .../_producer_property_fields.html.haml | 2 +- spec/features/admin/enterprises_spec.rb | 3 +++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee index f21c96a27e..0a68ce9d94 100644 --- a/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprises/controllers/enterprise_controller.js.coffee @@ -27,7 +27,7 @@ angular.module("admin.enterprises") # Provide a callback for generating warning messages displayed before leaving the page. This is passed in # from a directive "nav-check" in the page - if we pass it here it will be called in the test suite, - # and on all new uses of this contoller, and we might not want that . + # and on all new uses of this contoller, and we might not want that. enterpriseNavCallback = -> if $scope.Enterprise.$dirty "Your changes to the enterprise are not saved yet." diff --git a/app/helpers/spree/admin/base_helper_decorator.rb b/app/helpers/spree/admin/base_helper_decorator.rb index ff9e2b76a7..9430ab8733 100644 --- a/app/helpers/spree/admin/base_helper_decorator.rb +++ b/app/helpers/spree/admin/base_helper_decorator.rb @@ -9,6 +9,20 @@ module Spree end end alias_method_chain :preference_field_tag, :files + + + # Add support for options[:html], allowing additional HTML attributes + def link_to_remove_fields(name, f, options = {}) + name = '' if options[:no_text] + options[:class] = '' unless options[:class] + options[:class] += 'no-text with-tip' if options[:no_text] + + html_options = {class: "remove_fields #{options[:class]}", data: {action: 'remove'}, title: t(:remove)} + html_options.merge!(options[:html]) + + link_to_with_icon('icon-trash', name, '#', html_options) + f.hidden_field(:_destroy) + end + end end end diff --git a/app/views/admin/producer_properties/_producer_property_fields.html.haml b/app/views/admin/producer_properties/_producer_property_fields.html.haml index 314cbfc31a..6fe0b1d7ad 100644 --- a/app/views/admin/producer_properties/_producer_property_fields.html.haml +++ b/app/views/admin/producer_properties/_producer_property_fields.html.haml @@ -12,4 +12,4 @@ = f.text_field :value, :class => 'autocomplete' %td.actions - unless @enterprise.producer_properties.empty? - = link_to_remove_fields t(:remove), f, no_text: true, url: (f.object.persisted? && main_app.admin_enterprise_producer_property_path(@enterprise, f.object)) + = link_to_remove_fields t(:remove), f, no_text: true, url: (f.object.persisted? && main_app.admin_enterprise_producer_property_path(@enterprise, f.object)), html: {"onclick" => "if(typeof(enterprise_form) != 'undefined') { angular.element(enterprise_form).scope().setFormDirty() }".html_safe} diff --git a/spec/features/admin/enterprises_spec.rb b/spec/features/admin/enterprises_spec.rb index 8b5a08a7cc..c051c9eade 100644 --- a/spec/features/admin/enterprises_spec.rb +++ b/spec/features/admin/enterprises_spec.rb @@ -439,7 +439,10 @@ feature %q{ end within("#spree_producer_property_#{pp.id}") { page.find('a.remove_fields').click } + click_button 'Update' + + expect(page).to have_content 'Enterprise "First Supplier" has been successfully updated!' supplier1.producer_properties(true).should be_empty end end From 46fcf7b62e14cc68298fbabb70dec16366b232ad Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 31 Aug 2016 16:29:48 +1000 Subject: [PATCH 034/109] Fix enterprise group save error - initialise address correctly --- app/models/enterprise_group.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/enterprise_group.rb b/app/models/enterprise_group.rb index 63eb63f0b3..8b96ca9163 100644 --- a/app/models/enterprise_group.rb +++ b/app/models/enterprise_group.rb @@ -58,14 +58,14 @@ class EnterpriseGroup < ActiveRecord::Base } def set_unused_address_fields - address.firstname = address.lastname = 'unused' if address.present? + address.firstname = address.lastname = 'unused' end def set_undefined_address_fields - return unless address.present? address.phone.present? || address.phone = 'undefined' address.address1.present? || address.address1 = 'undefined' address.city.present? || address.city = 'undefined' + address.state.present? || address.state = address.country.states.first address.zipcode.present? || address.zipcode = 'undefined' end From d574b8943be318d63328cb08841e7d2d8b5a6b42 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 31 Aug 2016 17:21:06 +1000 Subject: [PATCH 035/109] WIP: Cherry-pick b2d82b6 - Using ofnSelect2 instead of plain select on BOM --- .../services/enterprises.js.coffee | 5 ---- .../directives/ofn-select2.js.coffee | 2 ++ .../services/dereferencer.js.coffee | 3 ++- .../line_items_controller.js.coffee | 14 +++++------ .../filters/select_filter.js.coffee | 6 ++--- .../admin/orders/bulk_management.html.haml | 6 ++--- .../services/enterprises_spec.js.coffee | 25 ++++++------------- .../line_items_controller_spec.js.coffee | 18 ++++++------- 8 files changed, 33 insertions(+), 46 deletions(-) diff --git a/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee b/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee index 80db943689..44dfb503a5 100644 --- a/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee +++ b/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee @@ -4,16 +4,11 @@ angular.module("admin.enterprises").factory 'Enterprises', ($q, EnterpriseResour pristineByID: {} index: (params={}, callback=null) -> - includeBlank = !!params['includeBlank'] - delete params['includeBlank'] EnterpriseResource.index(params, (data) => for enterprise in data @enterprisesByID[enterprise.id] = enterprise @pristineByID[enterprise.id] = angular.copy(enterprise) - (callback || angular.noop)(data) - - data.unshift(blankOption()) if includeBlank data ) diff --git a/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee index d65341e6fe..af21ff2dc9 100644 --- a/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee +++ b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee @@ -12,8 +12,10 @@ angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout, $timeout -> scope.text ||= 'name' scope.filter ||= -> true + scope.data.unshift(scope.blank) if scope.blank? && typeof scope.blank is "object" + item.name = $sanitize(item.name) for item in scope.data element.select2 minimumResultsForSearch: scope.minSearch || 0 diff --git a/app/assets/javascripts/admin/index_utils/services/dereferencer.js.coffee b/app/assets/javascripts/admin/index_utils/services/dereferencer.js.coffee index 4793c63034..fba6ef7908 100644 --- a/app/assets/javascripts/admin/index_utils/services/dereferencer.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/dereferencer.js.coffee @@ -3,7 +3,8 @@ angular.module("admin.indexUtils").factory 'Dereferencer', -> dereference: (array, data)-> if array for object, i in array - array[i] = data[object.id] + match = data[object.id] + array[i] = match if match? dereferenceAttr: (array, attr, data)-> if array diff --git a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee index f105ebb0e5..f5d11fda58 100644 --- a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee +++ b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee @@ -15,13 +15,13 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, LineItems.allSaved() || confirm(t("unsaved_changes_warning")) $scope.resetSelectFilters = -> - $scope.distributorFilter = blankOption().id - $scope.supplierFilter = blankOption().id - $scope.orderCycleFilter = blankOption().id + $scope.distributorFilter = 0 + $scope.supplierFilter = 0 + $scope.orderCycleFilter = 0 $scope.quickSearch = "" $scope.refreshData = -> - unless !$scope.orderCycleFilter? || $scope.orderCycleFilter == "0" + unless !$scope.orderCycleFilter? || $scope.orderCycleFilter == 0 $scope.startDate = OrderCycles.orderCyclesByID[$scope.orderCycleFilter].first_order $scope.endDate = OrderCycles.orderCyclesByID[$scope.orderCycleFilter].last_order @@ -29,9 +29,9 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, RequestMonitor.load $scope.lineItems = LineItems.index("q[order][state_not_eq]": "canceled", "q[order][completed_at_not_null]": "true", "q[order][completed_at_gt]": "#{parseDate($scope.startDate)}", "q[order][completed_at_lt]": "#{parseDate($scope.endDate)}") unless $scope.initialized - RequestMonitor.load $scope.distributors = Enterprises.index(includeBlank: true, action: "for_line_items", ams_prefix: "basic", "q[sells_in][]": ["own", "any"]) - RequestMonitor.load $scope.orderCycles = OrderCycles.index(includeBlank: true, ams_prefix: "basic", as: "distributor", "q[orders_close_at_gt]": "#{daysFromToday(-90)}") - RequestMonitor.load $scope.suppliers = Enterprises.index(includeBlank: true, action: "for_line_items", ams_prefix: "basic", "q[is_primary_producer_eq]": "true") + RequestMonitor.load $scope.distributors = Enterprises.index(action: "for_line_items", ams_prefix: "basic", "q[sells_in][]": ["own", "any"]) + RequestMonitor.load $scope.orderCycles = OrderCycles.index(ams_prefix: "basic", as: "distributor", "q[orders_close_at_gt]": "#{daysFromToday(-90)}") + RequestMonitor.load $scope.suppliers = Enterprises.index(action: "for_line_items", ams_prefix: "basic", "q[is_primary_producer_eq]": "true") RequestMonitor.load $q.all([$scope.orders.$promise, $scope.distributors.$promise, $scope.orderCycles.$promise]).then -> Dereferencer.dereferenceAttr $scope.orders, "distributor", Enterprises.enterprisesByID diff --git a/app/assets/javascripts/admin/line_items/filters/select_filter.js.coffee b/app/assets/javascripts/admin/line_items/filters/select_filter.js.coffee index 5195057663..6b6428c010 100644 --- a/app/assets/javascripts/admin/line_items/filters/select_filter.js.coffee +++ b/app/assets/javascripts/admin/line_items/filters/select_filter.js.coffee @@ -2,7 +2,7 @@ angular.module("admin.lineItems").filter "selectFilter", (blankOption, RequestMo return (lineItems,selectedSupplier,selectedDistributor,selectedOrderCycle) -> filtered = [] unless RequestMonitor.loading - filtered.push lineItem for lineItem in lineItems when (angular.equals(selectedSupplier,"0") || lineItem.supplier.id == selectedSupplier) && - (angular.equals(selectedDistributor,"0") || lineItem.order.distributor.id == selectedDistributor) && - (angular.equals(selectedOrderCycle,"0") || lineItem.order.order_cycle.id == selectedOrderCycle) + filtered.push lineItem for lineItem in lineItems when (angular.equals(selectedSupplier,0) || lineItem.supplier.id == selectedSupplier) && + (angular.equals(selectedDistributor,0) || lineItem.order.distributor.id == selectedDistributor) && + (angular.equals(selectedOrderCycle,0) || lineItem.order.order_cycle.id == selectedOrderCycle) filtered diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 520d2376a4..70de7fdd89 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -31,17 +31,17 @@ %label{ :for => 'supplier_filter' } = t("admin.producer") %br - %select{ :class => "three columns alpha", :id => 'supplier_filter', 'select2-min-search' => 5, 'ng-model' => 'supplierFilter', 'ng-options' => 's.id as s.name for s in suppliers' } + %input#supplier_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'suppliers', blank: "{ id: 0, name: 'All' }", ng: { model: 'supplierFilter' } } .filter_select{ :class => "three columns" } %label{ :for => 'distributor_filter' } = t("admin.shop") %br - %select{ :class => "three columns alpha", :id => 'distributor_filter', 'select2-min-search' => 5, 'ng-model' => 'distributorFilter', 'ng-options' => 'd.id as d.name for d in distributors'} + %input#distributor_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'distributors', blank: "{ id: 0, name: 'All' }", ng: { model: 'distributorFilter' } } .filter_select{ :class => "three columns" } %label{ :for => 'order_cycle_filter' } = t("admin.order_cycle") %br - %select{ :class => "three columns alpha", :id => 'order_cycle_filter', 'select2-min-search' => 5, 'ng-model' => 'orderCycleFilter', 'ng-options' => 'oc.id as oc.name for oc in orderCycles', 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()'} + %input#order_cycle_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'orderCycles', blank: "{ id: 0, name: 'All' }", on: { selecting: "confirmRefresh" }, ng: { model: 'orderCycleFilter', change: 'refreshData()' } } .filter_clear{ :class => "two columns omega" } %label{ :for => 'clear_all_filters' } %br diff --git a/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee index 702ef2cc1c..5c81f72d97 100644 --- a/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee @@ -37,25 +37,14 @@ describe "Enterprises service", -> expect(result).toDeepEqual response describe "when params are passed", -> - describe "where includeBlank param is truthy", -> - beforeEach -> - params = {includeBlank: true, someParam: 'someVal'} - $httpBackend.expectGET('/admin/enterprises.json?someParam=someVal').respond 200, response - result = Enterprises.index(params) - $httpBackend.flush() + beforeEach -> + params = { someParam: 'someVal'} + $httpBackend.expectGET('/admin/enterprises.json?someParam=someVal').respond 200, response + result = Enterprises.index(params) + $httpBackend.flush() - it "returns an array of enterprises, with a blank option appended to the beginning", -> - expect(result).toDeepEqual [{id: '0', name: 'All'} ,{ id: 5, name: 'Enterprise 1'}] - - describe "where includeBlank param is falsey", -> - beforeEach -> - params = {includeBlank: false, someParam: 'someVal'} - $httpBackend.expectGET('/admin/enterprises.json?someParam=someVal').respond 200, response - result = Enterprises.index(params) - $httpBackend.flush() - - it "returns an array of enterprises, with a blank option appended to the beginning", -> - expect(result).toDeepEqual response + it "returns an array of enterprises", -> + expect(result).toDeepEqual response describe "#save", -> diff --git a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee index a1c291f5b0..665d41934a 100644 --- a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee @@ -63,17 +63,17 @@ describe "LineItemsCtrl", -> $timeout.flush() describe "initialisation", -> - it "gets suppliers, adds a blank option as the first in the list", -> - expect(scope.suppliers).toDeepEqual [ { id : '0', name : 'All' }, supplier ] + it "gets suppliers", -> + expect(scope.suppliers).toDeepEqual [supplier ] - it "gets distributors, adds a blank option as the first in the list", -> - expect(scope.distributors).toDeepEqual [ { id : '0', name : 'All' }, distributor ] + it "gets distributors", -> + expect(scope.distributors).toDeepEqual [ distributor ] it "stores enterprises in an list that is accessible by id", -> expect(Enterprises.enterprisesByID[1]).toDeepEqual supplier - it "gets order cycles, adds a blank option as the first in the list", -> - expect(scope.orderCycles).toDeepEqual [ { id : '0', name : 'All' }, orderCycle ] + it "gets order cycles", -> + expect(scope.orderCycles).toDeepEqual [ orderCycle ] it "gets orders, with dereferenced order cycles and distributors", -> expect(scope.orders).toDeepEqual [ { id: 9, order_cycle: orderCycle, distributor: distributor, number: "R123456" } ] @@ -85,9 +85,9 @@ describe "LineItemsCtrl", -> expect(scope.RequestMonitor.loading).toBe false it "resets the select filters", -> - expect(scope.distributorFilter).toBe '0' - expect(scope.supplierFilter).toBe '0' - expect(scope.orderCycleFilter).toBe '0' + expect(scope.distributorFilter).toBe 0 + expect(scope.supplierFilter).toBe 0 + expect(scope.orderCycleFilter).toBe 0 expect(scope.quickSearch).toBe = "" it "resets the form state to pristine", -> From db93b744908558545ea88e784509bf974d24336a Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 31 Aug 2016 17:21:49 +1000 Subject: [PATCH 036/109] Refactoring ofnSelect2, works with multiple, and with promised data --- .../directives/ofn-select2.js.coffee | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee index af21ff2dc9..6890589b9d 100644 --- a/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee +++ b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee @@ -3,21 +3,42 @@ angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout, restrict: 'C' scope: data: "=" - minSearch: "@?" - text: "@?" + minSearch: "@" + text: "@" blank: "=?" filter: "=?" onSelecting: "=?" + multiple: '@' link: (scope, element, attrs, ngModel) -> $timeout -> - scope.text ||= 'name' - scope.filter ||= -> true + scope.text ?= 'name' + scope.multiple ?= false + scope.filter ?= -> true + if scope.data.$promise + scope.data.$promise.then -> init() + else + init() + + element.on "select2-opening", scope.onSelecting || angular.noop + + attrs.$observe 'disabled', (value) -> + element.select2('enable', !value) + + ngModel.$formatters.push (value) -> + element.select2('val', value) + value + + ngModel.$parsers.push (value) -> + return value.split(",") if scope.multiple + value + + init = -> scope.data.unshift(scope.blank) if scope.blank? && typeof scope.blank is "object" - item.name = $sanitize(item.name) for item in scope.data element.select2 + multiple: scope.multiple minimumResultsForSearch: scope.minSearch || 0 data: -> filtered = $filter('filter')(scope.data,scope.filter) @@ -26,12 +47,3 @@ angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout, item[scope.text] formatResult: (item) -> item[scope.text] - - element.on "select2-opening", scope.onSelecting || angular.noop - - attrs.$observe 'disabled', (value) -> - element.select2('enable', !value) - - ngModel.$formatters.push (value) -> - element.select2('val', value) - value From f4034b10650ec2c559b5b812d71ef4202df744e1 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 16 Sep 2016 11:52:42 +1000 Subject: [PATCH 037/109] Fix spec --- app/helpers/spree/admin/base_helper_decorator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/spree/admin/base_helper_decorator.rb b/app/helpers/spree/admin/base_helper_decorator.rb index 9430ab8733..5398cee35f 100644 --- a/app/helpers/spree/admin/base_helper_decorator.rb +++ b/app/helpers/spree/admin/base_helper_decorator.rb @@ -18,7 +18,7 @@ module Spree options[:class] += 'no-text with-tip' if options[:no_text] html_options = {class: "remove_fields #{options[:class]}", data: {action: 'remove'}, title: t(:remove)} - html_options.merge!(options[:html]) + html_options.merge!(options[:html]) if options.key? :html link_to_with_icon('icon-trash', name, '#', html_options) + f.hidden_field(:_destroy) end From 5913004e140fc461aaaeb141bb123ca7449e8e2c Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Wed, 9 Nov 2016 11:47:57 +1100 Subject: [PATCH 038/109] Fix datepicker error parsing date --- config/locales/en.yml | 3 +++ spec/features/admin/order_cycles_spec.rb | 25 ++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index ba2b1acab6..2211243bda 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1074,6 +1074,9 @@ Please follow the instructions there to make your enterprise visible on the Open validation_msg_is_associated_with_an_exising_customer: "is associated with an existing customer" spree: + date_picker: + format: ! '%Y-%m-%d' + js_format: 'yy-mm-dd' zipcode: Postcode shipment_states: backorder: backorder diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 74b2e88453..212fafd026 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -409,11 +409,11 @@ feature %q{ end - scenario "updating many order cycle opening/closing times at once" do + scenario "updating many order cycle opening/closing times at once", js: true do # Given three order cycles oc1 = create(:simple_order_cycle) oc2 = create(:simple_order_cycle) - oc3 = create(:simple_order_cycle) + oc3 = create(:simple_order_cycle, orders_open_at: Time.zone.local(2040, 12, 12, 12, 12, 12)) # When I go to the order cycles page login_to_admin_section @@ -430,7 +430,28 @@ feature %q{ all('input').last.set '2040-12-01 12:00:03' end + # And I fill in a time using the datepicker within("tr.order-cycle-#{oc3.id}") do + # When I trigger the datepicker + find('img.ui-datepicker-trigger', match: :first).click + end + + within("#ui-datepicker-div") do + # Then it should display the correct date/time + expect(page).to have_selector 'span.ui-datepicker-month', text: 'DECEMBER' + expect(page).to have_selector 'span.ui-datepicker-year', text: '2040' + expect(page).to have_selector 'a.ui-state-active', text: '12' + + # When I fill in a new date/time + click_link '1' + click_button 'Done' + end + + within("tr.order-cycle-#{oc3.id}") do + # Then that date/time should appear on the form + expect(all('input').first.value).to eq '2040-12-01 00:00' + + # Manually fill out time all('input').first.set '2040-12-01 12:00:04' all('input').last.set '2040-12-01 12:00:05' end From c0c8b07addf153c963289f1159f74ef9677b335c Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 11 Nov 2016 17:11:39 +1100 Subject: [PATCH 039/109] Let shopping tabs listen to URL changes When clicking on a shopping tab like "contact", it changed the URL. But changing the URL did not change the tab. Listening to URL changes enables manual manipulation of the URL and simple links to "#/contact" to open the contact tab. --- .../controllers/shopping_tabs_controller.js.coffee | 6 +++++- app/views/shop/_messages.html.haml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/shopping_tabs_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/shopping_tabs_controller.js.coffee index 8daac0212c..b99d169c5b 100644 --- a/app/assets/javascripts/darkswarm/controllers/shopping_tabs_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/shopping_tabs_controller.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.controller "ShoppingTabsCtrl", ($scope, $controller, Navigation) -> +Darkswarm.controller "ShoppingTabsCtrl", ($scope, $controller, Navigation, $location) -> angular.extend this, $controller('TabsCtrl', {$scope: $scope}) $scope.tabs = @@ -6,3 +6,7 @@ Darkswarm.controller "ShoppingTabsCtrl", ($scope, $controller, Navigation) -> producers: { active: Navigation.isActive('/producers') } contact: { active: Navigation.isActive('/contact') } groups: { active: Navigation.isActive('/groups') } + + $scope.$on '$locationChangeStart', (event, url) -> + tab = $location.path().replace(/^\//, '') + $scope.tabs[tab]?.active = true diff --git a/app/views/shop/_messages.html.haml b/app/views/shop/_messages.html.haml index 9e5d3b8e82..8debaff82e 100644 --- a/app/views/shop/_messages.html.haml +++ b/app/views/shop/_messages.html.haml @@ -10,7 +10,7 @@ register: ('' + t('.register') + '').html_safe} - else = t '.require_customer_html', - {contact: ('' + t('.contact') + '').html_safe, + {contact: link_to(t('.contact'), '#contact'), enterprise: current_distributor.name} - elsif @order_cycles and @order_cycles.empty? - if current_distributor.preferred_shopfront_closed_message.present? From d8ce0e7d58d0b68dcb42d2dc888792bbaccebc45 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 24 Nov 2016 13:35:04 +1100 Subject: [PATCH 040/109] A user without enterprises does not have access to the dashboard, so it's not meaningful to spec --- spec/features/admin/overview_spec.rb | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/spec/features/admin/overview_spec.rb b/spec/features/admin/overview_spec.rb index 590c8528e8..ebe6a1d379 100644 --- a/spec/features/admin/overview_spec.rb +++ b/spec/features/admin/overview_spec.rb @@ -3,31 +3,18 @@ require 'spec_helper' feature %q{ As a backend user I want to be given information about the state of my enterprises, products and order cycles -} , js: true do +}, js: true do include AuthenticationWorkflow include AuthorizationHelpers include WebHelper - stub_authorization! - context "as an enterprise user" do - before :each do + before do @enterprise_user = create_enterprise_user Spree::Admin::OverviewController.any_instance.stub(:spree_current_user).and_return @enterprise_user quick_login_as @enterprise_user end - context "with no enterprises" do - it "prompts the user to create a new enteprise" do - visit '/admin' - page.should have_selector ".dashboard_item#enterprises h3", text: "My Enterprises" - page.should have_selector ".dashboard_item#enterprises .list-item", text: "You don't have any enterprises yet" - page.should have_selector ".dashboard_item#enterprises .button.bottom", text: "CREATE A NEW ENTERPRISE" - page.should_not have_selector ".dashboard_item#products" - page.should_not have_selector ".dashboard_item#order_cycles" - end - end - context "with an enterprise" do let(:d1) { create(:distributor_enterprise) } From b7e9ffc9da3ce78610dc92ee0c95c9698da0661f Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 24 Nov 2016 13:35:49 +1100 Subject: [PATCH 041/109] Fix enterprise user being denied access to admin when spree dash configured (as on production) --- app/models/spree/ability_decorator.rb | 2 ++ spec/features/admin/overview_spec.rb | 42 ++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index b10c781fac..13efc7e109 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -57,6 +57,7 @@ class AbilityDecorator def add_group_management_abilities(user) can [:admin, :index], :overview + can [:admin, :sync], :analytic can [:admin, :index], EnterpriseGroup can [:read, :edit, :update], EnterpriseGroup do |group| user.owned_groups.include? group @@ -69,6 +70,7 @@ class AbilityDecorator can [:create, :search], nil can [:admin, :index], :overview + can [:admin, :sync], :analytic can [:admin, :index, :read, :create, :edit, :update_positions, :destroy], ProducerProperty diff --git a/spec/features/admin/overview_spec.rb b/spec/features/admin/overview_spec.rb index ebe6a1d379..30f2cc0336 100644 --- a/spec/features/admin/overview_spec.rb +++ b/spec/features/admin/overview_spec.rb @@ -110,5 +110,45 @@ feature %q{ end end end + + context "with the spree dash configured" do + let(:d1) { create(:distributor_enterprise) } + + before do + stub_jirafe + @enterprise_user.enterprise_roles.build(enterprise: d1).save + end + + around do |example| + with_dash_configured { example.run } + end + + it "has permission to sync analytics" do + visit '/admin' + expect(page).to have_content d1.name + end + end end -end \ No newline at end of file + + private + + def stub_jirafe + stub_request(:post, "https://api.jirafe.com/v1/applications/abc123/resources?token="). + to_return(:status => 200, :body => "", :headers => {}) + end + + def with_dash_configured(&block) + Spree::Dash::Config.preferred_app_id = 'abc123' + Spree::Dash::Config.preferred_site_id = 'abc123' + Spree::Dash::Config.preferred_token = 'abc123' + expect(Spree::Dash::Config.configured?).to be true + + block.call + + ensure + Spree::Dash::Config.preferred_app_id = nil + Spree::Dash::Config.preferred_site_id = nil + Spree::Dash::Config.preferred_token = nil + expect(Spree::Dash::Config.configured?).to be false + end +end From 63e815c7fc0d19c427cae72e930bf703570a9568 Mon Sep 17 00:00:00 2001 From: Levent Ali Date: Thu, 24 Nov 2016 08:31:01 +0000 Subject: [PATCH 042/109] Correct spelling of further in translations --- config/locales/en-US.yml | 2 +- config/locales/en.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/en-US.yml b/config/locales/en-US.yml index e86100ba93..9281c7a76b 100644 --- a/config/locales/en-US.yml +++ b/config/locales/en-US.yml @@ -911,7 +911,7 @@ Please follow the instructions there to make your enterprise visible on the Open spree_admin_single_enterprise_alert_mail_confirmation: "Please confirm the email address for" spree_admin_single_enterprise_alert_mail_sent: "We've sent an email to" spree_admin_overview_action_required: "Action Required" - spree_admin_overview_check_your_inbox: "Please check you inbox for furher instructions. Thanks!" + spree_admin_overview_check_your_inbox: "Please check you inbox for further instructions. Thanks!" change_package: "Change Package" spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" your_profil_live: "Your profile live" diff --git a/config/locales/en.yml b/config/locales/en.yml index 2211243bda..4bda952631 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -979,7 +979,7 @@ Please follow the instructions there to make your enterprise visible on the Open spree_admin_single_enterprise_alert_mail_confirmation: "Please confirm the email address for" spree_admin_single_enterprise_alert_mail_sent: "We've sent an email to" spree_admin_overview_action_required: "Action Required" - spree_admin_overview_check_your_inbox: "Please check you inbox for furher instructions. Thanks!" + spree_admin_overview_check_your_inbox: "Please check you inbox for further instructions. Thanks!" change_package: "Change Package" spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" your_profil_live: "Your profile live" From 631b19084a90884d32dcce0239eca559097831bc Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 25 Nov 2016 09:37:05 +1100 Subject: [PATCH 043/109] Fix intermittent failure via FK when deleted taxon is primary taxon on p2 --- spec/models/spree/taxon_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/spree/taxon_spec.rb b/spec/models/spree/taxon_spec.rb index 8fb4b22dc5..3a26c9b2aa 100644 --- a/spec/models/spree/taxon_spec.rb +++ b/spec/models/spree/taxon_spec.rb @@ -7,7 +7,7 @@ module Spree let!(:t2) { create(:taxon) } describe "callbacks" do - let!(:p2) { create(:simple_product, taxons: [t1]) } + let!(:p2) { create(:simple_product, taxons: [t1], primary_taxon: t2) } it "refreshes the products cache on save" do expect(OpenFoodNetwork::ProductsCache).to receive(:product_changed).with(p2) From 338d3cbc386715c592914675c3cf1ffeb2d197f6 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 19 Oct 2016 15:49:27 +1100 Subject: [PATCH 044/109] Delete old commented code --- .../panels/exchange_distributed_products.html.haml | 3 --- .../admin/enterprises/form/_properties.html.haml | 11 ----------- app/views/shared/_copyright.html.haml | 3 --- app/views/spree/checkout/payment/_paypal.html.haml | 1 + 4 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 app/views/shared/_copyright.html.haml diff --git a/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml index 4c6a86e926..44080e3fe0 100644 --- a/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml +++ b/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml @@ -14,9 +14,6 @@ .exchange-product{'ng-repeat' => 'product in supplied_products | filter:productSuppliedToOrderCycle | visibleProducts:exchange:order_cycle.visible_variants_for_outgoing_exchanges | orderBy:"name"' } .exchange-product-details %label - -# MASTER_VARIANTS: No longer required - -# = check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', 1, 1, 'ng-hide' => 'product.variants.length > 0', 'ng-model' => 'exchange.variants[product.master_id]', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', - -# 'ng-disabled' => 'product.variants.length > 0 || !order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(product.master_id) < 0' %img{'ng-src' => '{{ product.image_url }}'} .name {{ product.name }} .supplier {{ product.supplier_name }} diff --git a/app/views/admin/enterprises/form/_properties.html.haml b/app/views/admin/enterprises/form/_properties.html.haml index 795a104f1a..e3271435db 100644 --- a/app/views/admin/enterprises/form/_properties.html.haml +++ b/app/views/admin/enterprises/form/_properties.html.haml @@ -1,12 +1 @@ = render 'admin/producer_properties/form', f: f - -// :javascript -// var properties = #{raw(@properties.to_json)}; -// -// $("#producer_properties input.autocomplete").live("keydown", function() { -// already_auto_completed = $(this).is('ac_input'); -// if (!already_auto_completed) { -// $(this).autocomplete({source: properties}); -// $(this).focus(); -// } -// }); diff --git a/app/views/shared/_copyright.html.haml b/app/views/shared/_copyright.html.haml deleted file mode 100644 index 66ee8d9b4e..0000000000 --- a/app/views/shared/_copyright.html.haml +++ /dev/null @@ -1,3 +0,0 @@ --##copyright.text-center - -#%img.copyright{src: "/assets/logo.png", alt: "Open Food Network"} - -#© Copyright 2013 Open Food Foundation diff --git a/app/views/spree/checkout/payment/_paypal.html.haml b/app/views/spree/checkout/payment/_paypal.html.haml index e69de29bb2..1cc8aa25a7 100644 --- a/app/views/spree/checkout/payment/_paypal.html.haml +++ b/app/views/spree/checkout/payment/_paypal.html.haml @@ -0,0 +1 @@ +-# This file intentionally overrides the view in the spree_paypal_express gem From cbbb047fc118bc52f38bc42c34f2c75dbd868bb3 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 18 Sep 2016 11:06:59 +1000 Subject: [PATCH 045/109] Expunge all mentions of includeBlank and blankOption from angular services --- .../services/enterprises.js.coffee | 2 +- .../line_items_controller.js.coffee | 2 +- .../filters/select_filter.js.coffee | 2 +- .../services/order_cycles.js.coffee | 5 +--- .../utils/services/blank_option.js.coffee | 2 -- .../services/order_cycles_spec.js.coffee | 25 ++++++------------- 6 files changed, 11 insertions(+), 27 deletions(-) delete mode 100644 app/assets/javascripts/admin/utils/services/blank_option.js.coffee diff --git a/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee b/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee index 44dfb503a5..57c6dc4b1a 100644 --- a/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee +++ b/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.enterprises").factory 'Enterprises', ($q, EnterpriseResource, blankOption) -> +angular.module("admin.enterprises").factory 'Enterprises', ($q, EnterpriseResource) -> new class Enterprises enterprisesByID: {} pristineByID: {} diff --git a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee index f5d11fda58..1f0d6031b4 100644 --- a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee +++ b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, StatusMessage, Columns, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, blankOption, VariantUnitManager, RequestMonitor) -> +angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, StatusMessage, Columns, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, VariantUnitManager, RequestMonitor) -> $scope.initialized = false $scope.RequestMonitor = RequestMonitor $scope.filteredLineItems = [] diff --git a/app/assets/javascripts/admin/line_items/filters/select_filter.js.coffee b/app/assets/javascripts/admin/line_items/filters/select_filter.js.coffee index 6b6428c010..fb91acdeb3 100644 --- a/app/assets/javascripts/admin/line_items/filters/select_filter.js.coffee +++ b/app/assets/javascripts/admin/line_items/filters/select_filter.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.lineItems").filter "selectFilter", (blankOption, RequestMonitor) -> +angular.module("admin.lineItems").filter "selectFilter", (RequestMonitor) -> return (lineItems,selectedSupplier,selectedDistributor,selectedOrderCycle) -> filtered = [] unless RequestMonitor.loading diff --git a/app/assets/javascripts/admin/order_cycles/services/order_cycles.js.coffee b/app/assets/javascripts/admin/order_cycles/services/order_cycles.js.coffee index a182f19f12..71076c42c3 100644 --- a/app/assets/javascripts/admin/order_cycles/services/order_cycles.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/services/order_cycles.js.coffee @@ -1,11 +1,9 @@ -angular.module("admin.orderCycles").factory 'OrderCycles', ($q, OrderCycleResource, blankOption) -> +angular.module("admin.orderCycles").factory 'OrderCycles', ($q, OrderCycleResource) -> new class OrderCycles orderCyclesByID: {} pristineByID: {} index: (params={}, callback=null) -> - includeBlank = !!params['includeBlank'] - delete params['includeBlank'] OrderCycleResource.index(params, (data) => for orderCycle in data @orderCyclesByID[orderCycle.id] = orderCycle @@ -13,7 +11,6 @@ angular.module("admin.orderCycles").factory 'OrderCycles', ($q, OrderCycleResour (callback || angular.noop)(data) - data.unshift(blankOption()) if includeBlank data ) diff --git a/app/assets/javascripts/admin/utils/services/blank_option.js.coffee b/app/assets/javascripts/admin/utils/services/blank_option.js.coffee deleted file mode 100644 index f4089aa20d..0000000000 --- a/app/assets/javascripts/admin/utils/services/blank_option.js.coffee +++ /dev/null @@ -1,2 +0,0 @@ -angular.module("admin.utils").value "blankOption", -> - { id: "0", name: "All" } diff --git a/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee b/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee index aebdb19f6f..e4d51d508c 100644 --- a/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee @@ -37,25 +37,14 @@ describe "OrderCycles service", -> expect(result).toDeepEqual response describe "when no params are passed", -> - describe "where includeBlank param is truthy", -> - beforeEach -> - params = {includeBlank: true, someParam: 'someVal'} - $httpBackend.expectGET('/admin/order_cycles.json?someParam=someVal').respond 200, response - result = OrderCycles.index(params) - $httpBackend.flush() + beforeEach -> + params = { someParam: 'someVal'} + $httpBackend.expectGET('/admin/order_cycles.json?someParam=someVal').respond 200, response + result = OrderCycles.index(params) + $httpBackend.flush() - it "returns an array of orderCycles", -> - expect(result).toDeepEqual [{id: '0', name: 'All'} ,{ id: 5, name: 'OrderCycle 1'}] - - describe "where includeBlank param is falsey", -> - beforeEach -> - params = {includeBlank: false, someParam: 'someVal'} - $httpBackend.expectGET('/admin/order_cycles.json?someParam=someVal').respond 200, response - result = OrderCycles.index(params) - $httpBackend.flush() - - it "returns an array of orderCycles", -> - expect(result).toDeepEqual response + it "returns an array of orderCycles", -> + expect(result).toDeepEqual response describe "#save", -> From 3de69987e61b82710ff905da1d32e6f505cdcf32 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sat, 20 Aug 2016 19:57:31 +1000 Subject: [PATCH 046/109] Changing convention for angular resource services to generic 'byID' object and 'all' array --- .../enterprises/services/enterprises.js.coffee | 4 ++-- .../controllers/line_items_controller.js.coffee | 12 ++++++------ .../line_items/services/line_items.js.coffee | 12 ++++++------ .../order_cycles/services/order_cycles.js.coffee | 15 ++++++++++++--- .../admin/orders/services/orders.js.coffee | 4 ++-- .../admin/taxons/services/taxons.js.coffee | 15 ++++++++------- .../services/enterprises_spec.js.coffee | 4 ++-- .../line_items_controller_spec.js.coffee | 2 +- .../line_items/services/line_items_spec.js.coffee | 12 ++++++------ .../services/order_cycles_spec.js.coffee | 4 ++-- .../admin/orders/services/orders_spec.js.coffee | 4 ++-- 11 files changed, 49 insertions(+), 39 deletions(-) diff --git a/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee b/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee index 57c6dc4b1a..f3b8111253 100644 --- a/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee +++ b/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee @@ -1,12 +1,12 @@ angular.module("admin.enterprises").factory 'Enterprises', ($q, EnterpriseResource) -> new class Enterprises - enterprisesByID: {} + byID: {} pristineByID: {} index: (params={}, callback=null) -> EnterpriseResource.index(params, (data) => for enterprise in data - @enterprisesByID[enterprise.id] = enterprise + @byID[enterprise.id] = enterprise @pristineByID[enterprise.id] = angular.copy(enterprise) (callback || angular.noop)(data) data diff --git a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee index 1f0d6031b4..72e17d0931 100644 --- a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee +++ b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee @@ -22,8 +22,8 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $scope.refreshData = -> unless !$scope.orderCycleFilter? || $scope.orderCycleFilter == 0 - $scope.startDate = OrderCycles.orderCyclesByID[$scope.orderCycleFilter].first_order - $scope.endDate = OrderCycles.orderCyclesByID[$scope.orderCycleFilter].last_order + $scope.startDate = OrderCycles.byID[$scope.orderCycleFilter].first_order + $scope.endDate = OrderCycles.byID[$scope.orderCycleFilter].last_order RequestMonitor.load $scope.orders = Orders.index("q[state_not_eq]": "canceled", "q[completed_at_not_null]": "true", "q[completed_at_gt]": "#{parseDate($scope.startDate)}", "q[completed_at_lt]": "#{parseDate($scope.endDate)}") RequestMonitor.load $scope.lineItems = LineItems.index("q[order][state_not_eq]": "canceled", "q[order][completed_at_not_null]": "true", "q[order][completed_at_gt]": "#{parseDate($scope.startDate)}", "q[order][completed_at_lt]": "#{parseDate($scope.endDate)}") @@ -34,12 +34,12 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, RequestMonitor.load $scope.suppliers = Enterprises.index(action: "for_line_items", ams_prefix: "basic", "q[is_primary_producer_eq]": "true") RequestMonitor.load $q.all([$scope.orders.$promise, $scope.distributors.$promise, $scope.orderCycles.$promise]).then -> - Dereferencer.dereferenceAttr $scope.orders, "distributor", Enterprises.enterprisesByID - Dereferencer.dereferenceAttr $scope.orders, "order_cycle", OrderCycles.orderCyclesByID + Dereferencer.dereferenceAttr $scope.orders, "distributor", Enterprises.byID + Dereferencer.dereferenceAttr $scope.orders, "order_cycle", OrderCycles.byID RequestMonitor.load $q.all([$scope.orders.$promise, $scope.suppliers.$promise, $scope.lineItems.$promise]).then -> - Dereferencer.dereferenceAttr $scope.lineItems, "supplier", Enterprises.enterprisesByID - Dereferencer.dereferenceAttr $scope.lineItems, "order", Orders.ordersByID + Dereferencer.dereferenceAttr $scope.lineItems, "supplier", Enterprises.byID + Dereferencer.dereferenceAttr $scope.lineItems, "order", Orders.byID $scope.bulk_order_form.$setPristine() StatusMessage.clear() unless $scope.initialized diff --git a/app/assets/javascripts/admin/line_items/services/line_items.js.coffee b/app/assets/javascripts/admin/line_items/services/line_items.js.coffee index e78389c559..5804bb2996 100644 --- a/app/assets/javascripts/admin/line_items/services/line_items.js.coffee +++ b/app/assets/javascripts/admin/line_items/services/line_items.js.coffee @@ -1,23 +1,23 @@ angular.module("admin.lineItems").factory 'LineItems', ($q, LineItemResource) -> new class LineItems - lineItemsByID: {} + byID: {} pristineByID: {} index: (params={}, callback=null) -> LineItemResource.index params, (data) => @resetData() for lineItem in data - @lineItemsByID[lineItem.id] = lineItem + @byID[lineItem.id] = lineItem @pristineByID[lineItem.id] = angular.copy(lineItem) (callback || angular.noop)(data) resetData: -> - @lineItemsByID = {} + @byID = {} @pristineByID = {} saveAll: -> - for id, lineItem of @lineItemsByID + for id, lineItem of @byID lineItem.errors = {} # removes errors when line_item has been returned to original state @save(lineItem) if !@isSaved(lineItem) @@ -34,7 +34,7 @@ angular.module("admin.lineItems").factory 'LineItems', ($q, LineItemResource) -> deferred.promise allSaved: -> - for id, lineItem of @lineItemsByID + for id, lineItem of @byID return false unless @isSaved(lineItem) true @@ -54,7 +54,7 @@ angular.module("admin.lineItems").factory 'LineItems', ($q, LineItemResource) -> deferred = $q.defer() lineItem.$delete({id: lineItem.id, orders: "orders", order_number: lineItem.order.number}) .then( (data) => - delete @lineItemsByID[lineItem.id] + delete @byID[lineItem.id] delete @pristineByID[lineItem.id] (callback || angular.noop)(data) deferred.resolve(data) diff --git a/app/assets/javascripts/admin/order_cycles/services/order_cycles.js.coffee b/app/assets/javascripts/admin/order_cycles/services/order_cycles.js.coffee index 71076c42c3..4716dc1757 100644 --- a/app/assets/javascripts/admin/order_cycles/services/order_cycles.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/services/order_cycles.js.coffee @@ -1,12 +1,21 @@ -angular.module("admin.orderCycles").factory 'OrderCycles', ($q, OrderCycleResource) -> +angular.module("admin.orderCycles").factory 'OrderCycles', ($q, $injector, OrderCycleResource) -> new class OrderCycles - orderCyclesByID: {} + all: [] + byID: {} pristineByID: {} + constructor: -> + if $injector.has('orderCycles') + for orderCycle in $injector.get('orderCycles') + @all.push orderCycle + @byID[orderCycle.id] = orderCycle + @pristineByID[orderCycle.id] = angular.copy(orderCycle) + + index: (params={}, callback=null) -> OrderCycleResource.index(params, (data) => for orderCycle in data - @orderCyclesByID[orderCycle.id] = orderCycle + @byID[orderCycle.id] = orderCycle @pristineByID[orderCycle.id] = angular.copy(orderCycle) (callback || angular.noop)(data) diff --git a/app/assets/javascripts/admin/orders/services/orders.js.coffee b/app/assets/javascripts/admin/orders/services/orders.js.coffee index a7b5bc1b68..238afda8e0 100644 --- a/app/assets/javascripts/admin/orders/services/orders.js.coffee +++ b/app/assets/javascripts/admin/orders/services/orders.js.coffee @@ -1,12 +1,12 @@ angular.module("admin.orders").factory 'Orders', ($q, OrderResource) -> new class Orders - ordersByID: {} + byID: {} pristineByID: {} index: (params={}, callback=null) -> OrderResource.index params, (data) => for order in data - @ordersByID[order.id] = order + @byID[order.id] = order @pristineByID[order.id] = angular.copy(order) (callback || angular.noop)(data) diff --git a/app/assets/javascripts/admin/taxons/services/taxons.js.coffee b/app/assets/javascripts/admin/taxons/services/taxons.js.coffee index 62daf77901..48fd503980 100644 --- a/app/assets/javascripts/admin/taxons/services/taxons.js.coffee +++ b/app/assets/javascripts/admin/taxons/services/taxons.js.coffee @@ -1,19 +1,20 @@ angular.module("admin.taxons").factory "Taxons", (taxons, $filter) -> new class Taxons - taxons: taxons - taxonsByID: {} + all: [] + byID: {} constructor: -> - for taxon in @taxons - @taxonsByID[taxon.id] = taxon + for taxon in taxons + @all.push taxon + @byID[taxon.id] = taxon # For finding a single Taxon findByID: (id) -> - @taxonsByID[id] + @byID[id] # For finding multiple Taxons represented by comma delimited string findByIDs: (ids) -> - @taxonsByID[taxon_id] for taxon_id in ids.split(",") when @taxonsByID[taxon_id] + @byID[taxon_id] for taxon_id in ids.split(",") when @byID[taxon_id] findByTerm: (term) -> - $filter('filter')(@taxons, term) + $filter('filter')(@all, term) diff --git a/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee b/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee index 5c81f72d97..8c088b1715 100644 --- a/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee +++ b/spec/javascripts/unit/admin/enterprises/services/enterprises_spec.js.coffee @@ -26,9 +26,9 @@ describe "Enterprises service", -> result = Enterprises.index() $httpBackend.flush() - it "stores returned data in @enterprisesByID, with ids as keys", -> + it "stores returned data in @byID, with ids as keys", -> # EnterpriseResource returns instances of Resource rather than raw objects - expect(Enterprises.enterprisesByID).toDeepEqual { 5: response[0] } + expect(Enterprises.byID).toDeepEqual { 5: response[0] } it "stores returned data in @pristineByID, with ids as keys", -> expect(Enterprises.pristineByID).toDeepEqual { 5: response[0] } diff --git a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee index 665d41934a..23c42efde0 100644 --- a/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/line_items/controllers/line_items_controller_spec.js.coffee @@ -70,7 +70,7 @@ describe "LineItemsCtrl", -> expect(scope.distributors).toDeepEqual [ distributor ] it "stores enterprises in an list that is accessible by id", -> - expect(Enterprises.enterprisesByID[1]).toDeepEqual supplier + expect(Enterprises.byID[1]).toDeepEqual supplier it "gets order cycles", -> expect(scope.orderCycles).toDeepEqual [ orderCycle ] diff --git a/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee b/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee index ef428377c0..3de04003e5 100644 --- a/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee +++ b/spec/javascripts/unit/admin/line_items/services/line_items_spec.js.coffee @@ -23,9 +23,9 @@ describe "LineItems service", -> result = LineItems.index() $httpBackend.flush() - it "stores returned data in @lineItemsByID, with ids as keys", -> + it "stores returned data in @byID, with ids as keys", -> # LineItemResource returns instances of Resource rather than raw objects - expect(LineItems.lineItemsByID).toDeepEqual { 5: response[0] } + expect(LineItems.byID).toDeepEqual { 5: response[0] } it "stores returned data in @pristineByID, with ids as keys", -> expect(LineItems.pristineByID).toDeepEqual { 5: response[0] } @@ -114,14 +114,14 @@ describe "LineItems service", -> beforeEach -> lineItem = new LineItemResource({ id: 15, order: { number: '12345678'} }) LineItems.pristineByID[15] = lineItem - LineItems.lineItemsByID[15] = lineItem + LineItems.byID[15] = lineItem $httpBackend.expectDELETE('/admin/orders/12345678/line_items/15.json').respond 200, { id: 15, name: 'LineItem 1'} LineItems.delete(lineItem, callback).then( -> resolved = true).catch( -> rejected = true) $httpBackend.flush() it "updates the pristine copy of the lineItem", -> expect(LineItems.pristineByID[15]).toBeUndefined() - expect(LineItems.lineItemsByID[15]).toBeUndefined() + expect(LineItems.byID[15]).toBeUndefined() it "runs the callback", -> expect(callback).toHaveBeenCalled() @@ -139,14 +139,14 @@ describe "LineItems service", -> beforeEach -> lineItem = new LineItemResource({ id: 15, order: { number: '12345678'} }) LineItems.pristineByID[15] = lineItem - LineItems.lineItemsByID[15] = lineItem + LineItems.byID[15] = lineItem $httpBackend.expectDELETE('/admin/orders/12345678/line_items/15.json').respond 422, { error: 'obj' } LineItems.delete(lineItem, callback).then( -> resolved = true).catch( -> rejected = true) $httpBackend.flush() it "does not update the pristine copy of the lineItem", -> expect(LineItems.pristineByID[15]).toBeDefined() - expect(LineItems.lineItemsByID[15]).toBeDefined() + expect(LineItems.byID[15]).toBeDefined() it "does not run the callback", -> expect(callback).not.toHaveBeenCalled() diff --git a/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee b/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee index e4d51d508c..f2ac4983f2 100644 --- a/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/services/order_cycles_spec.js.coffee @@ -26,9 +26,9 @@ describe "OrderCycles service", -> result = OrderCycles.index() $httpBackend.flush() - it "stores returned data in @orderCyclesByID, with ids as keys", -> + it "stores returned data in @byID, with ids as keys", -> # OrderCycleResource returns instances of Resource rather than raw objects - expect(OrderCycles.orderCyclesByID).toDeepEqual { 5: response[0] } + expect(OrderCycles.byID).toDeepEqual { 5: response[0] } it "stores returned data in @pristineByID, with ids as keys", -> expect(OrderCycles.pristineByID).toDeepEqual { 5: response[0] } diff --git a/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee b/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee index c3d6646144..3d41ded40a 100644 --- a/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee +++ b/spec/javascripts/unit/admin/orders/services/orders_spec.js.coffee @@ -23,9 +23,9 @@ describe "Orders service", -> result = Orders.index() $httpBackend.flush() - it "stores returned data in @ordersByID, with ids as keys", -> + it "stores returned data in @byID, with ids as keys", -> # OrderResource returns instances of Resource rather than raw objects - expect(Orders.ordersByID).toDeepEqual { 5: response[0] } + expect(Orders.byID).toDeepEqual { 5: response[0] } it "stores returned data in @pristineByID, with ids as keys", -> expect(Orders.pristineByID).toDeepEqual { 5: response[0] } From 3678d4d01817e9f3925cd1debbea73f8c1340cc1 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 21 Aug 2016 21:07:46 +1000 Subject: [PATCH 047/109] Creating new 'resources' module for holding interdependent ngResource services --- app/assets/javascripts/admin/all.js | 1 + app/assets/javascripts/admin/index_utils/index_utils.js.coffee | 2 +- .../javascripts/admin/order_cycles/order_cycles.js.coffee | 2 +- app/assets/javascripts/admin/resources/resources.js.coffee | 1 + .../resources}/enterprise_resource.js.coffee | 2 +- .../resources}/line_item_resource.js.coffee | 2 +- .../resources}/order_cycle_resource.js.coffee | 2 +- .../services => resources/resources}/order_resource.js.coffee | 2 +- .../{enterprises => resources}/services/enterprises.js.coffee | 2 +- .../{line_items => resources}/services/line_items.js.coffee | 2 +- .../{order_cycles => resources}/services/order_cycles.js.coffee | 2 +- .../admin/{orders => resources}/services/orders.js.coffee | 2 +- 12 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 app/assets/javascripts/admin/resources/resources.js.coffee rename app/assets/javascripts/admin/{enterprises/services => resources/resources}/enterprise_resource.js.coffee (75%) rename app/assets/javascripts/admin/{line_items/services => resources/resources}/line_item_resource.js.coffee (85%) rename app/assets/javascripts/admin/{order_cycles/services => resources/resources}/order_cycle_resource.js.coffee (64%) rename app/assets/javascripts/admin/{orders/services => resources/resources}/order_resource.js.coffee (66%) rename app/assets/javascripts/admin/{enterprises => resources}/services/enterprises.js.coffee (93%) rename app/assets/javascripts/admin/{line_items => resources}/services/line_items.js.coffee (96%) rename app/assets/javascripts/admin/{order_cycles => resources}/services/order_cycles.js.coffee (93%) rename app/assets/javascripts/admin/{orders => resources}/services/orders.js.coffee (92%) diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 102cb237ca..e71405ae46 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -37,6 +37,7 @@ //= require ./order_cycles/order_cycles //= require ./payment_methods/payment_methods //= require ./products/products +//= require ./resources/resources //= require ./shipping_methods/shipping_methods //= require ./side_menu/side_menu //= require ./tag_rules/tag_rules diff --git a/app/assets/javascripts/admin/index_utils/index_utils.js.coffee b/app/assets/javascripts/admin/index_utils/index_utils.js.coffee index 1ea74e614b..a956fd955c 100644 --- a/app/assets/javascripts/admin/index_utils/index_utils.js.coffee +++ b/app/assets/javascripts/admin/index_utils/index_utils.js.coffee @@ -1 +1 @@ -angular.module("admin.indexUtils", ['ngResource', 'ngSanitize', 'templates', 'admin.utils']).config ($httpProvider) -> $httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content"); $httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"; \ No newline at end of file +angular.module("admin.indexUtils", ['admin.resources', 'ngSanitize', 'templates', 'admin.utils']).config ($httpProvider) -> $httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content"); $httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"; \ No newline at end of file diff --git a/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee b/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee index 6ea3f76984..99dcbfe4b7 100644 --- a/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee @@ -1,4 +1,4 @@ -angular.module('admin.orderCycles', ['ngResource', 'admin.utils', 'admin.indexUtils', 'ngTagsInput']) +angular.module('admin.orderCycles', ['admin.utils', 'admin.indexUtils', 'ngTagsInput']) .config ($httpProvider) -> $httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content') diff --git a/app/assets/javascripts/admin/resources/resources.js.coffee b/app/assets/javascripts/admin/resources/resources.js.coffee new file mode 100644 index 0000000000..9e89237732 --- /dev/null +++ b/app/assets/javascripts/admin/resources/resources.js.coffee @@ -0,0 +1 @@ +angular.module("admin.resources", ['ngResource']) diff --git a/app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee b/app/assets/javascripts/admin/resources/resources/enterprise_resource.js.coffee similarity index 75% rename from app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee rename to app/assets/javascripts/admin/resources/resources/enterprise_resource.js.coffee index b522f5be3b..7cdd5b7bce 100644 --- a/app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee +++ b/app/assets/javascripts/admin/resources/resources/enterprise_resource.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.enterprises").factory 'EnterpriseResource', ($resource) -> +angular.module("admin.resources").factory 'EnterpriseResource', ($resource) -> ignoredAttrs = -> ["$$hashKey", "producer", "package", "producerError", "packageError", "status"] diff --git a/app/assets/javascripts/admin/line_items/services/line_item_resource.js.coffee b/app/assets/javascripts/admin/resources/resources/line_item_resource.js.coffee similarity index 85% rename from app/assets/javascripts/admin/line_items/services/line_item_resource.js.coffee rename to app/assets/javascripts/admin/resources/resources/line_item_resource.js.coffee index 60ca925753..4301f8df82 100644 --- a/app/assets/javascripts/admin/line_items/services/line_item_resource.js.coffee +++ b/app/assets/javascripts/admin/resources/resources/line_item_resource.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.lineItems").factory 'LineItemResource', ($resource) -> +angular.module("admin.resources").factory 'LineItemResource', ($resource) -> $resource('/admin/:orders/:order_number/line_items/:id.json', {}, { 'index': method: 'GET' diff --git a/app/assets/javascripts/admin/order_cycles/services/order_cycle_resource.js.coffee b/app/assets/javascripts/admin/resources/resources/order_cycle_resource.js.coffee similarity index 64% rename from app/assets/javascripts/admin/order_cycles/services/order_cycle_resource.js.coffee rename to app/assets/javascripts/admin/resources/resources/order_cycle_resource.js.coffee index 4a5df3c44a..cd5be31acd 100644 --- a/app/assets/javascripts/admin/order_cycles/services/order_cycle_resource.js.coffee +++ b/app/assets/javascripts/admin/resources/resources/order_cycle_resource.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.orderCycles").factory 'OrderCycleResource', ($resource) -> +angular.module("admin.resources").factory 'OrderCycleResource', ($resource) -> $resource('/admin/order_cycles/:id/:action.json', {}, { 'index': method: 'GET' diff --git a/app/assets/javascripts/admin/orders/services/order_resource.js.coffee b/app/assets/javascripts/admin/resources/resources/order_resource.js.coffee similarity index 66% rename from app/assets/javascripts/admin/orders/services/order_resource.js.coffee rename to app/assets/javascripts/admin/resources/resources/order_resource.js.coffee index ab360a2fc9..317b384485 100644 --- a/app/assets/javascripts/admin/orders/services/order_resource.js.coffee +++ b/app/assets/javascripts/admin/resources/resources/order_resource.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.orders").factory 'OrderResource', ($resource) -> +angular.module("admin.resources").factory 'OrderResource', ($resource) -> $resource('/admin/orders/:id/:action.json', {}, { 'index': method: 'GET' diff --git a/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee b/app/assets/javascripts/admin/resources/services/enterprises.js.coffee similarity index 93% rename from app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee rename to app/assets/javascripts/admin/resources/services/enterprises.js.coffee index f3b8111253..5cdebe02cd 100644 --- a/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee +++ b/app/assets/javascripts/admin/resources/services/enterprises.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.enterprises").factory 'Enterprises', ($q, EnterpriseResource) -> +angular.module("admin.resources").factory 'Enterprises', ($q, EnterpriseResource) -> new class Enterprises byID: {} pristineByID: {} diff --git a/app/assets/javascripts/admin/line_items/services/line_items.js.coffee b/app/assets/javascripts/admin/resources/services/line_items.js.coffee similarity index 96% rename from app/assets/javascripts/admin/line_items/services/line_items.js.coffee rename to app/assets/javascripts/admin/resources/services/line_items.js.coffee index 5804bb2996..d790c826a3 100644 --- a/app/assets/javascripts/admin/line_items/services/line_items.js.coffee +++ b/app/assets/javascripts/admin/resources/services/line_items.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.lineItems").factory 'LineItems', ($q, LineItemResource) -> +angular.module("admin.resources").factory 'LineItems', ($q, LineItemResource) -> new class LineItems byID: {} pristineByID: {} diff --git a/app/assets/javascripts/admin/order_cycles/services/order_cycles.js.coffee b/app/assets/javascripts/admin/resources/services/order_cycles.js.coffee similarity index 93% rename from app/assets/javascripts/admin/order_cycles/services/order_cycles.js.coffee rename to app/assets/javascripts/admin/resources/services/order_cycles.js.coffee index 4716dc1757..ad86a7899e 100644 --- a/app/assets/javascripts/admin/order_cycles/services/order_cycles.js.coffee +++ b/app/assets/javascripts/admin/resources/services/order_cycles.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.orderCycles").factory 'OrderCycles', ($q, $injector, OrderCycleResource) -> +angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCycleResource) -> new class OrderCycles all: [] byID: {} diff --git a/app/assets/javascripts/admin/orders/services/orders.js.coffee b/app/assets/javascripts/admin/resources/services/orders.js.coffee similarity index 92% rename from app/assets/javascripts/admin/orders/services/orders.js.coffee rename to app/assets/javascripts/admin/resources/services/orders.js.coffee index 238afda8e0..69a56b77ca 100644 --- a/app/assets/javascripts/admin/orders/services/orders.js.coffee +++ b/app/assets/javascripts/admin/resources/services/orders.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.orders").factory 'Orders', ($q, OrderResource) -> +angular.module("admin.resources").factory 'Orders', ($q, OrderResource) -> new class Orders byID: {} pristineByID: {} From 1770a67cd9f4ec7c5a4b3c492ded56aeec6f0522 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Sun, 18 Sep 2016 13:42:52 +1000 Subject: [PATCH 048/109] Adding #load function to services for resources, for generic loading of data into byID and pristineByID --- .../resources/services/enterprises.js.coffee | 12 ++++++----- .../resources/services/line_items.js.coffee | 12 ++++++----- .../resources/services/order_cycles.js.coffee | 21 ++++++++----------- .../admin/resources/services/orders.js.coffee | 10 +++++---- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/app/assets/javascripts/admin/resources/services/enterprises.js.coffee b/app/assets/javascripts/admin/resources/services/enterprises.js.coffee index 5cdebe02cd..0b7fa6e870 100644 --- a/app/assets/javascripts/admin/resources/services/enterprises.js.coffee +++ b/app/assets/javascripts/admin/resources/services/enterprises.js.coffee @@ -4,13 +4,15 @@ angular.module("admin.resources").factory 'Enterprises', ($q, EnterpriseResource pristineByID: {} index: (params={}, callback=null) -> - EnterpriseResource.index(params, (data) => - for enterprise in data - @byID[enterprise.id] = enterprise - @pristineByID[enterprise.id] = angular.copy(enterprise) + EnterpriseResource.index params, (data) => + @load(data) (callback || angular.noop)(data) data - ) + + load: (enterprises) -> + for enterprise in enterprises + @byID[enterprise.id] = enterprise + @pristineByID[enterprise.id] = angular.copy(enterprise) save: (enterprise) -> deferred = $q.defer() diff --git a/app/assets/javascripts/admin/resources/services/line_items.js.coffee b/app/assets/javascripts/admin/resources/services/line_items.js.coffee index d790c826a3..182ef81e6a 100644 --- a/app/assets/javascripts/admin/resources/services/line_items.js.coffee +++ b/app/assets/javascripts/admin/resources/services/line_items.js.coffee @@ -5,17 +5,19 @@ angular.module("admin.resources").factory 'LineItems', ($q, LineItemResource) -> index: (params={}, callback=null) -> LineItemResource.index params, (data) => - @resetData() - for lineItem in data - @byID[lineItem.id] = lineItem - @pristineByID[lineItem.id] = angular.copy(lineItem) - + @load(data) (callback || angular.noop)(data) resetData: -> @byID = {} @pristineByID = {} + load: (lineItems) -> + @resetData() + for lineItem in lineItems + @byID[lineItem.id] = lineItem + @pristineByID[lineItem.id] = angular.copy(lineItem) + saveAll: -> for id, lineItem of @byID lineItem.errors = {} # removes errors when line_item has been returned to original state diff --git a/app/assets/javascripts/admin/resources/services/order_cycles.js.coffee b/app/assets/javascripts/admin/resources/services/order_cycles.js.coffee index ad86a7899e..728b173e35 100644 --- a/app/assets/javascripts/admin/resources/services/order_cycles.js.coffee +++ b/app/assets/javascripts/admin/resources/services/order_cycles.js.coffee @@ -6,22 +6,19 @@ angular.module("admin.resources").factory 'OrderCycles', ($q, $injector, OrderCy constructor: -> if $injector.has('orderCycles') - for orderCycle in $injector.get('orderCycles') - @all.push orderCycle - @byID[orderCycle.id] = orderCycle - @pristineByID[orderCycle.id] = angular.copy(orderCycle) - + @load($injector.get('orderCycles')) index: (params={}, callback=null) -> - OrderCycleResource.index(params, (data) => - for orderCycle in data - @byID[orderCycle.id] = orderCycle - @pristineByID[orderCycle.id] = angular.copy(orderCycle) - + OrderCycleResource.index params, (data) => + @load(data) (callback || angular.noop)(data) - data - ) + + load: (orderCycles) -> + for orderCycle in orderCycles + @all.push orderCycle + @byID[orderCycle.id] = orderCycle + @pristineByID[orderCycle.id] = angular.copy(orderCycle) save: (order_cycle) -> deferred = $q.defer() diff --git a/app/assets/javascripts/admin/resources/services/orders.js.coffee b/app/assets/javascripts/admin/resources/services/orders.js.coffee index 69a56b77ca..da3f409149 100644 --- a/app/assets/javascripts/admin/resources/services/orders.js.coffee +++ b/app/assets/javascripts/admin/resources/services/orders.js.coffee @@ -5,12 +5,14 @@ angular.module("admin.resources").factory 'Orders', ($q, OrderResource) -> index: (params={}, callback=null) -> OrderResource.index params, (data) => - for order in data - @byID[order.id] = order - @pristineByID[order.id] = angular.copy(order) - + @load(data) (callback || angular.noop)(data) + load: (orders) -> + for order in orders + @byID[order.id] = order + @pristineByID[order.id] = angular.copy(order) + save: (order) -> deferred = $q.defer() order.$update({id: order.number}) From dbbd52cace348462b9fc07c3fdbab1dc45ae6482 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 28 Oct 2016 11:44:26 +1100 Subject: [PATCH 049/109] Fixing broken taxons filter on bulk product edit Was referencing Taxons.taxons instead of Taxons.all --- app/assets/javascripts/admin/bulk_product_update.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index 7a9d2677b9..d464a49bf4 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -22,7 +22,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout $scope.producers = producers - $scope.taxons = Taxons.taxons + $scope.taxons = Taxons.all $scope.tax_categories = tax_categories $scope.filterProducers = [{id: "0", name: ""}].concat $scope.producers $scope.filterTaxons = [{id: "0", name: ""}].concat $scope.taxons From 268c8dbcddf5c02a91ff7340fd4fdca45833c72c Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 25 Nov 2016 09:59:32 +1100 Subject: [PATCH 050/109] Moving Customers and CustomerResource services to admin.resource module --- .../customers_controller.js.coffee | 3 +-- .../resources}/customer_resource.js.coffee | 2 +- .../services/customers.js.coffee | 24 +++++++++++++------ .../customers_controller_spec.js.coffee | 15 ++---------- .../services/customers_spec.js.coffee | 23 ++++++++++++++++++ 5 files changed, 44 insertions(+), 23 deletions(-) rename app/assets/javascripts/admin/{customers/services => resources/resources}/customer_resource.js.coffee (86%) rename app/assets/javascripts/admin/{customers => resources}/services/customers.js.coffee (58%) create mode 100644 spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee diff --git a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee index 9e956a1977..2506e994ac 100644 --- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee +++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee @@ -3,8 +3,8 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, $q, $filt $scope.availableCountries = availableCountries $scope.RequestMonitor = RequestMonitor $scope.submitAll = pendingChanges.submitAll - $scope.add = Customers.add $scope.customerLimit = 20 + $scope.customers = Customers.all $scope.columns = Columns.columns $scope.confirmRefresh = (event) -> @@ -16,7 +16,6 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, $q, $filt Customers.index({enterprise_id: $scope.shop_id}).then (data) -> pendingChanges.removeAll() $scope.customers_form.$setPristine() - $scope.customers = data $scope.shop_id = shops[0].id if shops.length == 1 diff --git a/app/assets/javascripts/admin/customers/services/customer_resource.js.coffee b/app/assets/javascripts/admin/resources/resources/customer_resource.js.coffee similarity index 86% rename from app/assets/javascripts/admin/customers/services/customer_resource.js.coffee rename to app/assets/javascripts/admin/resources/resources/customer_resource.js.coffee index 3904d0333d..0a67586374 100644 --- a/app/assets/javascripts/admin/customers/services/customer_resource.js.coffee +++ b/app/assets/javascripts/admin/resources/resources/customer_resource.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.customers").factory 'CustomerResource', ($resource) -> +angular.module("admin.resources").factory 'CustomerResource', ($resource) -> $resource('/admin/customers/:id.json', {}, { 'index': method: 'GET' diff --git a/app/assets/javascripts/admin/customers/services/customers.js.coffee b/app/assets/javascripts/admin/resources/services/customers.js.coffee similarity index 58% rename from app/assets/javascripts/admin/customers/services/customers.js.coffee rename to app/assets/javascripts/admin/resources/services/customers.js.coffee index f4c6f6f3cd..6b73f5c4db 100644 --- a/app/assets/javascripts/admin/customers/services/customers.js.coffee +++ b/app/assets/javascripts/admin/resources/services/customers.js.coffee @@ -1,19 +1,24 @@ -angular.module("admin.customers").factory "Customers", ($q, InfoDialog, RequestMonitor, CustomerResource, CurrentShop) -> +angular.module("admin.resources").factory "Customers", ($q, InfoDialog, RequestMonitor, CustomerResource, CurrentShop) -> new class Customers - customers: [] + all: [] + byID: {} + pristineByID: {} add: (email) -> params = enterprise_id: CurrentShop.shop.id email: email CustomerResource.create params, (customer) => - @customers.unshift customer if customer.id + if customer.id + @all.unshift customer + @byID[customer.id] = customer + @pristineByID[customer.id] = angular.copy(customer) remove: (customer) -> params = id: customer.id CustomerResource.destroy params, => - i = @customers.indexOf customer - @customers.splice i, 1 unless i < 0 + i = @all.indexOf customer + @all.splice i, 1 unless i < 0 , (response) => errors = response.data.errors if errors? @@ -22,14 +27,19 @@ angular.module("admin.customers").factory "Customers", ($q, InfoDialog, RequestM InfoDialog.open 'error', "Could not delete customer: #{customer.email}" index: (params) -> - request = CustomerResource.index(params, (data) => @customers = data) + request = CustomerResource.index(params, (data) => @load(data)) RequestMonitor.load(request.$promise) request.$promise + load: (customers) -> + for customer in customers + @all.push customer + @byID[customer.id] = customer + @pristineByID[customer.id] = angular.copy(customer) + update: (address, customer, addressType) -> params = id: customer.id customer: "#{addressType}_attributes": address CustomerResource.update params - diff --git a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee index 5f3deff550..ce4b967f90 100644 --- a/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/customers/controllers/customers_controller_spec.js.coffee @@ -62,28 +62,17 @@ describe "CustomersCtrl", -> as = scope.findByCode('b') expect(as).toDeepEqual [] - describe "scope.add", -> - it "creates a new customer", -> - email = "customer@example.org" - newCustomer = {id: 6, email: email} - customers.unshift(newCustomer) - http.expectPOST('/admin/customers.json?email=' + email + '&enterprise_id=3').respond 200, newCustomer - scope.add(email) - http.flush() - expect(scope.customers).toDeepEqual customers - describe "scope.deleteCustomer", -> beforeEach -> spyOn(window, 'confirm').and.returnValue(true) it "deletes a customer", -> - expect(scope.customers.length).toBe 2 + expect(scope.customers.length).toBe 1 customer = scope.customers[0] http.expectDELETE('/admin/customers/' + customer.id + '.json').respond 200 scope.deleteCustomer(customer) http.flush() - expect(scope.customers.length).toBe 1 - expect(scope.customers[0]).not.toDeepEqual customer + expect(scope.customers.length).toBe 0 describe "scope.findTags", -> tags = [ diff --git a/spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee b/spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee new file mode 100644 index 0000000000..569701d29c --- /dev/null +++ b/spec/javascripts/unit/admin/customers/services/customers_spec.js.coffee @@ -0,0 +1,23 @@ +describe "Customers", -> + Customers = CurrentShop = customers = $httpBackend = null + + beforeEach -> + module 'admin.customers' + + jasmine.addMatchers + toDeepEqual: (util, customEqualityTesters) -> + compare: (actual, expected) -> + { pass: angular.equals(actual, expected) } + + inject ($q, _$httpBackend_, _Customers_, _CurrentShop_) -> + Customers = _Customers_ + + describe "scope.add", -> + it "creates a new customer", inject ($httpBackend, CurrentShop) -> + email = "customer@example.org" + newCustomer = {id: 6, email: email} + CurrentShop.shop = { id: 3 } + $httpBackend.expectPOST('/admin/customers.json?email=' + email + '&enterprise_id=3').respond 200, newCustomer + Customers.add(email) + $httpBackend.flush() + expect(Customers.all).toDeepEqual [newCustomer] From 7498b7f098361ab7dd1e83a50a773b49dd82733f Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 25 Nov 2016 10:09:07 +1100 Subject: [PATCH 051/109] Moving PaymentMethods service to admin.resource module --- .../payment_methods_controller.js.coffee | 2 +- .../services/payment_methods.js.coffee | 8 -------- .../resources/services/payment_methods.js.coffee | 16 ++++++++++++++++ 3 files changed, 17 insertions(+), 9 deletions(-) delete mode 100644 app/assets/javascripts/admin/payment_methods/services/payment_methods.js.coffee create mode 100644 app/assets/javascripts/admin/resources/services/payment_methods.js.coffee diff --git a/app/assets/javascripts/admin/payment_methods/controllers/payment_methods_controller.js.coffee b/app/assets/javascripts/admin/payment_methods/controllers/payment_methods_controller.js.coffee index ddad6cd259..2def86a25a 100644 --- a/app/assets/javascripts/admin/payment_methods/controllers/payment_methods_controller.js.coffee +++ b/app/assets/javascripts/admin/payment_methods/controllers/payment_methods_controller.js.coffee @@ -1,3 +1,3 @@ angular.module("admin.paymentMethods").controller "paymentMethodsCtrl", ($scope, PaymentMethods) -> $scope.findPaymentMethodByID = (id) -> - $scope.PaymentMethod = PaymentMethods.findByID(id) + $scope.PaymentMethod = PaymentMethods.byID[id] diff --git a/app/assets/javascripts/admin/payment_methods/services/payment_methods.js.coffee b/app/assets/javascripts/admin/payment_methods/services/payment_methods.js.coffee deleted file mode 100644 index c31a20d96f..0000000000 --- a/app/assets/javascripts/admin/payment_methods/services/payment_methods.js.coffee +++ /dev/null @@ -1,8 +0,0 @@ -angular.module("admin.paymentMethods") - .factory "PaymentMethods", (paymentMethods) -> - new class PaymentMethods - paymentMethods: paymentMethods - - findByID: (id) -> - for paymentMethod in @paymentMethods - return paymentMethod if paymentMethod.id is id diff --git a/app/assets/javascripts/admin/resources/services/payment_methods.js.coffee b/app/assets/javascripts/admin/resources/services/payment_methods.js.coffee new file mode 100644 index 0000000000..f84f3a165d --- /dev/null +++ b/app/assets/javascripts/admin/resources/services/payment_methods.js.coffee @@ -0,0 +1,16 @@ +angular.module("admin.resources") + .factory "PaymentMethods", ($injector) -> + new class PaymentMethods + paymentMethods: [] + byID: {} + pristineByID: {} + + constructor: -> + if $injector.has('paymentMethods') + @load($injector.get('paymentMethods')) + + load: (paymentMethods) -> + for paymentMethod in paymentMethods + @paymentMethods.push paymentMethod + @byID[paymentMethod.id] = paymentMethod + @pristineByID[paymentMethod.id] = angular.copy(paymentMethod) From 5e214a32b3a3b5f429a63a526a036a6de1bbca2e Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 25 Nov 2016 10:16:08 +1100 Subject: [PATCH 052/109] Moving ShippingMethods service to admin.resource module --- .../services/shipping_methods.js.coffee | 16 ++++++++++++++++ .../shipping_methods_controller.js.coffee | 2 +- .../services/shipping_methods.js.coffee | 8 -------- 3 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 app/assets/javascripts/admin/resources/services/shipping_methods.js.coffee delete mode 100644 app/assets/javascripts/admin/shipping_methods/services/shipping_methods.js.coffee diff --git a/app/assets/javascripts/admin/resources/services/shipping_methods.js.coffee b/app/assets/javascripts/admin/resources/services/shipping_methods.js.coffee new file mode 100644 index 0000000000..9fde8c9d06 --- /dev/null +++ b/app/assets/javascripts/admin/resources/services/shipping_methods.js.coffee @@ -0,0 +1,16 @@ +angular.module("admin.resources") + .factory "ShippingMethods", ($injector) -> + new class ShippingMethods + shippingMethods: [] + byID: {} + pristineByID: {} + + constructor: -> + if $injector.has('shippingMethods') + @load($injector.get('shippingMethods')) + + load: (shippingMethods) -> + for shippingMethod in shippingMethods + @shippingMethods.push shippingMethod + @byID[shippingMethod.id] = shippingMethod + @pristineByID[shippingMethod.id] = angular.copy(shippingMethod) diff --git a/app/assets/javascripts/admin/shipping_methods/controllers/shipping_methods_controller.js.coffee b/app/assets/javascripts/admin/shipping_methods/controllers/shipping_methods_controller.js.coffee index 9efd2a5fea..c9b85ea76e 100644 --- a/app/assets/javascripts/admin/shipping_methods/controllers/shipping_methods_controller.js.coffee +++ b/app/assets/javascripts/admin/shipping_methods/controllers/shipping_methods_controller.js.coffee @@ -1,3 +1,3 @@ angular.module("admin.shippingMethods").controller "shippingMethodsCtrl", ($scope, ShippingMethods) -> $scope.findShippingMethodByID = (id) -> - $scope.ShippingMethod = ShippingMethods.findByID(id) + $scope.ShippingMethod = ShippingMethods.byID[id] diff --git a/app/assets/javascripts/admin/shipping_methods/services/shipping_methods.js.coffee b/app/assets/javascripts/admin/shipping_methods/services/shipping_methods.js.coffee deleted file mode 100644 index c691f5dae5..0000000000 --- a/app/assets/javascripts/admin/shipping_methods/services/shipping_methods.js.coffee +++ /dev/null @@ -1,8 +0,0 @@ -angular.module("admin.shippingMethods") - .factory "ShippingMethods", (shippingMethods) -> - new class ShippingMethods - shippingMethods: shippingMethods - - findByID: (id) -> - for shippingMethod in @shippingMethods - return shippingMethod if shippingMethod.id is id From 27283c50b828d5a1697705782479ad9f10496bdf Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 25 Nov 2016 12:43:48 +1100 Subject: [PATCH 053/109] Customers service clears array fo each #index request --- .../javascripts/admin/resources/services/customers.js.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/admin/resources/services/customers.js.coffee b/app/assets/javascripts/admin/resources/services/customers.js.coffee index 6b73f5c4db..3783097f56 100644 --- a/app/assets/javascripts/admin/resources/services/customers.js.coffee +++ b/app/assets/javascripts/admin/resources/services/customers.js.coffee @@ -27,6 +27,7 @@ angular.module("admin.resources").factory "Customers", ($q, InfoDialog, RequestM InfoDialog.open 'error', "Could not delete customer: #{customer.email}" index: (params) -> + @clear() request = CustomerResource.index(params, (data) => @load(data)) RequestMonitor.load(request.$promise) request.$promise @@ -43,3 +44,6 @@ angular.module("admin.resources").factory "Customers", ($q, InfoDialog, RequestM customer: "#{addressType}_attributes": address CustomerResource.update params + + clear: -> + @all.length = 0 From f4f38b41833b66f6a815b6247ba12cea16319f1f Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 14 Oct 2016 09:56:40 +1100 Subject: [PATCH 054/109] Re-apply shop property filters - including performance regression This reverts commit 7d149ed198a18ce602cf3b35eff93f55645df851. --- .../darkswarm/filters/properties.js.coffee | 8 ++- .../darkswarm/filters/properties_of.js.coffee | 12 ++-- .../darkswarm/_shop-filters.css.sass | 2 +- app/helpers/injection_helper.rb | 2 +- app/helpers/serializer_helper.rb | 6 ++ app/models/producer_property.rb | 8 ++- app/models/spree/property_decorator.rb | 8 ++- app/models/spree/taxon_decorator.rb | 27 ++++---- app/serializers/api/enterprise_serializer.rb | 48 ++++++++------ app/serializers/api/property_serializer.rb | 6 ++ app/views/groups/_hub_filters.html.haml | 21 ------- app/views/groups/show.html.haml | 28 ++++----- app/views/home/_filters.html.haml | 22 ------- app/views/producers/_filters.html.haml | 2 +- app/views/producers/index.html.haml | 2 +- app/views/shop/products/_filters.html.haml | 2 +- app/views/{home => shops}/_fat.html.haml | 0 app/views/shops/_filters.html.haml | 34 ++++++++++ app/views/{home => shops}/_hubs.html.haml | 6 +- .../{home => shops}/_hubs_table.html.haml | 6 +- app/views/{home => shops}/_skinny.html.haml | 0 app/views/shops/index.html.haml | 4 +- config/locales/en.yml | 1 + .../enterprise_injection_data.rb | 8 ++- spec/features/consumer/groups_spec.rb | 39 ++++++++++++ spec/features/consumer/shops_spec.rb | 63 ++++++++++++++++++- spec/models/producer_property_spec.rb | 22 ++++--- spec/models/spree/property_spec.rb | 34 ++++++---- spec/models/spree/taxon_spec.rb | 17 +++-- .../serializers/enterprise_serializer_spec.rb | 3 +- 30 files changed, 299 insertions(+), 142 deletions(-) create mode 100644 app/helpers/serializer_helper.rb delete mode 100644 app/views/groups/_hub_filters.html.haml delete mode 100644 app/views/home/_filters.html.haml rename app/views/{home => shops}/_fat.html.haml (100%) create mode 100644 app/views/shops/_filters.html.haml rename app/views/{home => shops}/_hubs.html.haml (89%) rename app/views/{home => shops}/_hubs_table.html.haml (67%) rename app/views/{home => shops}/_skinny.html.haml (100%) diff --git a/app/assets/javascripts/darkswarm/filters/properties.js.coffee b/app/assets/javascripts/darkswarm/filters/properties.js.coffee index 1453cac053..fe7ed71fa3 100644 --- a/app/assets/javascripts/darkswarm/filters/properties.js.coffee +++ b/app/assets/javascripts/darkswarm/filters/properties.js.coffee @@ -1,13 +1,17 @@ Darkswarm.filter 'properties', -> # Filter anything that responds to object.supplied_properties - (objects, ids) -> + (objects, ids, source) -> objects ||= [] ids ?= [] + + source ||= 'properties' + return [] unless source in ['properties', 'supplied_properties', 'distributed_properties'] + if ids.length == 0 # No properties selected, pass all objects through. objects else objects.filter (obj) -> - properties = obj.supplied_properties || obj.properties + properties = obj[source] properties.some (property) -> property.id in ids diff --git a/app/assets/javascripts/darkswarm/filters/properties_of.js.coffee b/app/assets/javascripts/darkswarm/filters/properties_of.js.coffee index 7eac12bf1e..f3f4f51b5e 100644 --- a/app/assets/javascripts/darkswarm/filters/properties_of.js.coffee +++ b/app/assets/javascripts/darkswarm/filters/properties_of.js.coffee @@ -1,12 +1,12 @@ Darkswarm.filter 'propertiesOf', -> - (objects) -> + (objects, source) -> + source ||= 'properties' + return {} unless source in ['properties', 'supplied_properties', 'distributed_properties'] + properties = {} for object in objects - if object.supplied_properties? - for property in object.supplied_properties - properties[property.id] = property - else - for property in object.properties + if object[source]? + for property in object[source] properties[property.id] = property properties diff --git a/app/assets/stylesheets/darkswarm/_shop-filters.css.sass b/app/assets/stylesheets/darkswarm/_shop-filters.css.sass index 3c4f9a514d..07fea7a0b1 100644 --- a/app/assets/stylesheets/darkswarm/_shop-filters.css.sass +++ b/app/assets/stylesheets/darkswarm/_shop-filters.css.sass @@ -96,7 +96,7 @@ // content. Ensure that the dropdown appears above the content. .filter-row position: relative - z-index: 100 + z-index: 90 .filter-shopfront &.taxon-selectors, &.property-selectors diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index 6db2e583bc..dad9cf11f9 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -45,7 +45,7 @@ module InjectionHelper end def inject_properties - inject_json_ams "properties", Spree::Property.all, Api::IdNameSerializer + inject_json_ams "properties", Spree::Property.all, Api::PropertySerializer end def inject_currency_config diff --git a/app/helpers/serializer_helper.rb b/app/helpers/serializer_helper.rb new file mode 100644 index 0000000000..2e48e3741d --- /dev/null +++ b/app/helpers/serializer_helper.rb @@ -0,0 +1,6 @@ +module SerializerHelper + def ids_to_objs(ids) + return [] if ids.blank? + ids.map { |id| {id: id} } + end +end diff --git a/app/models/producer_property.rb b/app/models/producer_property.rb index bf1f083936..b0bb1b6e51 100644 --- a/app/models/producer_property.rb +++ b/app/models/producer_property.rb @@ -8,14 +8,18 @@ class ProducerProperty < ActiveRecord::Base after_destroy :refresh_products_cache_from_destroy - scope :sold_by, ->(shop) { + scope :ever_sold_by, ->(shop) { joins(producer: {supplied_products: {variants: {exchanges: :order_cycle}}}). merge(Exchange.outgoing). merge(Exchange.to_enterprise(shop)). - merge(OrderCycle.active). select('DISTINCT producer_properties.*') } + scope :currently_sold_by, ->(shop) { + ever_sold_by(shop). + merge(OrderCycle.active) + } + def property_name property.name if property diff --git a/app/models/spree/property_decorator.rb b/app/models/spree/property_decorator.rb index 50e450a03b..81577cef4e 100644 --- a/app/models/spree/property_decorator.rb +++ b/app/models/spree/property_decorator.rb @@ -8,14 +8,18 @@ module Spree where('spree_product_properties.product_id IN (?)', enterprise.supplied_product_ids) } - scope :sold_by, ->(shop) { + scope :ever_sold_by, ->(shop) { joins(products: {variants: {exchanges: :order_cycle}}). merge(Exchange.outgoing). merge(Exchange.to_enterprise(shop)). - merge(OrderCycle.active). select('DISTINCT spree_properties.*') } + scope :currently_sold_by, ->(shop) { + ever_sold_by(shop). + merge(OrderCycle.active) + } + after_save :refresh_products_cache diff --git a/app/models/spree/taxon_decorator.rb b/app/models/spree/taxon_decorator.rb index a051de98fa..1878a20e49 100644 --- a/app/models/spree/taxon_decorator.rb +++ b/app/models/spree/taxon_decorator.rb @@ -32,21 +32,24 @@ Spree::Taxon.class_eval do end # Find all the taxons of distributed products for each enterprise, indexed by enterprise. + # May return :all taxons (distributed in open and closed order cycles), + # or :current taxons (distributed in an open order cycle). + # # Format: {enterprise_id => [taxon_id, ...]} - def self.distributed_taxons - taxons = {} + def self.distributed_taxons(which_taxons=:all) + # TODO: Why can't we merge(Spree::Product.with_order_cycles_inner) here? + taxons = Spree::Taxon. + joins(products: {variants_including_master: {exchanges: :order_cycle}}). + merge(Exchange.outgoing). + select('spree_taxons.*, exchanges.receiver_id AS enterprise_id') - Spree::Taxon. - joins(:products). - merge(Spree::Product.with_order_cycles_outer). - where('o_exchanges.incoming = ?', false). - select('spree_taxons.*, o_exchanges.receiver_id AS enterprise_id'). - each do |t| - taxons[t.enterprise_id.to_i] ||= Set.new - taxons[t.enterprise_id.to_i] << t.id - end + taxons = taxons.merge(OrderCycle.active) if which_taxons == :current - taxons + taxons.inject({}) do |ts, t| + ts[t.enterprise_id.to_i] ||= Set.new + ts[t.enterprise_id.to_i] << t.id + ts + end end diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index f68c8be679..3537bcdd24 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -20,7 +20,10 @@ class Api::EnterpriseSerializer < ActiveModel::Serializer end class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer + include SerializerHelper + attributes :orders_close_at, :active + attributes :taxons, :supplied_taxons has_many :supplied_properties, serializer: Api::PropertySerializer has_many :distributed_properties, serializer: Api::PropertySerializer @@ -42,15 +45,37 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer def distributed_properties # This results in 3 queries per enterprise - product_properties = Spree::Property.sold_by(object) - ids = ProducerProperty.sold_by(object).pluck(:property_id) - producer_properties = Spree::Property.where(id: ids) + + if active + product_properties = Spree::Property.currently_sold_by(object) + producer_property_ids = ProducerProperty.currently_sold_by(object).pluck(:property_id) + + else + product_properties = Spree::Property.ever_sold_by(object) + producer_property_ids = ProducerProperty.ever_sold_by(object).pluck(:property_id) + end + + producer_properties = Spree::Property.where(id: producer_property_ids) OpenFoodNetwork::PropertyMerge.merge product_properties, producer_properties end + + def taxons + if active + ids_to_objs options[:data].current_distributed_taxons[object.id] + else + ids_to_objs options[:data].all_distributed_taxons[object.id] + end + end + + def supplied_taxons + ids_to_objs options[:data].supplied_taxons[object.id] + end end class Api::CachedEnterpriseSerializer < ActiveModel::Serializer + include SerializerHelper + cached #delegate :cache_key, to: :object @@ -65,16 +90,7 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer :email_address, :hash, :logo, :promo_image, :path, :pickup, :delivery, :icon, :icon_font, :producer_icon_font, :category, :producers, :hubs - attributes :taxons, :supplied_taxons - has_one :address, serializer: Api::AddressSerializer - def taxons - ids_to_objs options[:data].distributed_taxons[object.id] - end - - def supplied_taxons - ids_to_objs options[:data].supplied_taxons[object.id] - end def pickup services = options[:data].shipping_method_services[object.id] @@ -153,12 +169,4 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer } icon_fonts[object.category] end - - - private - - def ids_to_objs(ids) - return [] if ids.blank? - ids.map { |id| {id: id} } - end end diff --git a/app/serializers/api/property_serializer.rb b/app/serializers/api/property_serializer.rb index 7da4fce990..cdb3f2bae3 100644 --- a/app/serializers/api/property_serializer.rb +++ b/app/serializers/api/property_serializer.rb @@ -1,3 +1,9 @@ class Api::PropertySerializer < ActiveModel::Serializer attributes :id, :name, :presentation + + # Client-side we don't care about the property name. Send the presentation + # since this is what we want to show to the user. + def name + object.presentation + end end diff --git a/app/views/groups/_hub_filters.html.haml b/app/views/groups/_hub_filters.html.haml deleted file mode 100644 index 71924d5e85..0000000000 --- a/app/views/groups/_hub_filters.html.haml +++ /dev/null @@ -1,21 +0,0 @@ -.row - = render partial: 'shared/components/filter_controls' - = render partial: 'shared/components/show_profiles' - -.row.animate-show{"ng-show" => "filtersActive"} - .small-12.columns - .row.filter-box - .small-12.large-9.columns - %h5.tdhead - .light - = t :hubs_filter_by - = t :hubs_filter_type - %filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{"selector-set" => "filterSelectors", objects: "group_hubs | searchEnterprises:query | shipping:shippingTypes | showHubProfiles:show_profiles | taxonsOf", "active-selectors" => "activeTaxons"} - .small-12.large-3.columns - %h5.tdhead - .light - = t :hubs_filter_by - = t :hubs_filter_delivery - %shipping-type-selector - -= render partial: 'shared/components/filter_box' diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index ef9e281155..90b74c0b42 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -60,23 +60,23 @@ .small-12.columns %h1 = t :groups_producers - = render partial: "shared/components/enterprise_search" - = render partial: "producers/filters" + = render "shared/components/enterprise_search" + = render "producers/filters" .row .small-12.columns .active_table %producer.active_table_node.row.animate-repeat{id: "{{producer.path}}", - "ng-repeat" => "producer in filteredEnterprises = (group_producers | searchEnterprises:query | taxons:activeTaxons | properties:activeProperties)", + "ng-repeat" => "producer in filteredEnterprises = (group_producers | searchEnterprises:query | taxons:activeTaxons | properties:activeProperties:'supplied_properties')", "ng-controller" => "GroupEnterpriseNodeCtrl", "ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !producer.active}", id: "{{producer.hash}}"} .small-12.columns - = render partial: 'producers/skinny' - = render partial: 'producers/fat' + = render "producers/skinny" + = render "producers/fat" - = render partial: 'shared/components/enterprise_no_results' + = render 'shared/components/enterprise_no_results' %tab{heading: t(:groups_hubs), active: "tabs.hubs.active", @@ -87,24 +87,24 @@ %h1 = t :groups_hubs - = render partial: "shared/components/enterprise_search" - = render partial: "hub_filters" + = render "shared/components/enterprise_search" + = render "shops/filters", resource: "group_hubs", property_filters: "| searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles" .row .small-12.columns .active_table %hub.active_table_node.row.animate-repeat{id: "{{hub.hash}}", - "ng-repeat" => "hub in filteredEnterprises = (group_hubs | searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+orders_close_at'])", + "ng-repeat" => "hub in filteredEnterprises = (group_hubs | searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | properties:activeProperties:'distributed_properties' | orderBy:['-active', '+orders_close_at'])", "ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}", "ng-controller" => "GroupEnterpriseNodeCtrl"} .small-12.columns - = render partial: 'home/skinny' - = render partial: 'home/fat' + = render 'shops/skinny' + = render 'shops/fat' - = render partial: 'shared/components/enterprise_no_results' + = render 'shared/components/enterprise_no_results' .small-12.medium-12.large-3.columns - = render partial: 'contact' + = render 'contact' .small-12.columns.pad-top .row.pad-top @@ -122,4 +122,4 @@ %p   -= render partial: "shared/footer" += render "shared/footer" diff --git a/app/views/home/_filters.html.haml b/app/views/home/_filters.html.haml deleted file mode 100644 index bfd13fdf54..0000000000 --- a/app/views/home/_filters.html.haml +++ /dev/null @@ -1,22 +0,0 @@ -.row - = render partial: 'shared/components/filter_controls' - -# .small-12.medium-6.columns   - = render partial: 'shared/components/show_profiles' - -.row.animate-show{"ng-show" => "filtersActive"} - .small-12.columns - .row.filter-box - .small-12.large-9.columns - %h5.tdhead - .light - = t :hubs_filter_by - = t :hubs_filter_type - %filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{ "selector-set" => "filterSelectors", objects: "visibleMatches | visible | taxonsOf", "active-selectors" => "activeTaxons" } - .small-12.large-3.columns - %h5.tdhead - .light - = t :hubs_filter_by - = t :hubs_filter_delivery - %shipping-type-selector - -= render partial: 'shared/components/filter_box' diff --git a/app/views/producers/_filters.html.haml b/app/views/producers/_filters.html.haml index 771ed932c9..17f260196a 100644 --- a/app/views/producers/_filters.html.haml +++ b/app/views/producers/_filters.html.haml @@ -18,6 +18,6 @@ = t :producers_filter = t :producers_filter_property .filter-shopfront.property-selectors - %single-line-selectors{ selectors: "filterSelectors", objects: "producers_to_filter | searchEnterprises:query | propertiesOf", "active-selectors" => "activeProperties"} + %filter-selector{ "selector-set" => "filterSelectors", objects: "producers_to_filter | searchEnterprises:query | taxons:activeTaxons | propertiesOf:'supplied_properties'", "active-selectors" => "activeProperties"} = render partial: 'shared/components/filter_box' diff --git a/app/views/producers/index.html.haml b/app/views/producers/index.html.haml index 04048ef77e..af6c2e4fd1 100644 --- a/app/views/producers/index.html.haml +++ b/app/views/producers/index.html.haml @@ -16,7 +16,7 @@ .small-12.columns .active_table %producer.active_table_node.row.animate-repeat{id: "{{producer.path}}", - "ng-repeat" => "producer in filteredEnterprises = (Enterprises.producers | visible | searchEnterprises:query | taxons:activeTaxons | properties:activeProperties)", + "ng-repeat" => "producer in filteredEnterprises = (Enterprises.producers | visible | searchEnterprises:query | taxons:activeTaxons | properties:activeProperties:'supplied_properties')", "ng-controller" => "ProducerNodeCtrl", "ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !producer.active}", id: "{{producer.hash}}"} diff --git a/app/views/shop/products/_filters.html.haml b/app/views/shop/products/_filters.html.haml index 7efaba6cc9..10c7c20deb 100644 --- a/app/views/shop/products/_filters.html.haml +++ b/app/views/shop/products/_filters.html.haml @@ -1,5 +1,5 @@ .filter-shopfront.taxon-selectors.text-right - %single-line-selectors{ selectors: "taxonSelectors", objects: "Products.products | products:query | properties: activeProperties | taxonsOf", "active-selectors" => "activeTaxons"} + %single-line-selectors{ selectors: "taxonSelectors", objects: "Products.products | products:query | properties:activeProperties | taxonsOf", "active-selectors" => "activeTaxons"} .filter-shopfront.property-selectors.text-right %single-line-selectors{ selectors: "propertySelectors", objects: "Products.products | products:query | taxons:activeTaxons | propertiesOf", "active-selectors" => "activeProperties"} diff --git a/app/views/home/_fat.html.haml b/app/views/shops/_fat.html.haml similarity index 100% rename from app/views/home/_fat.html.haml rename to app/views/shops/_fat.html.haml diff --git a/app/views/shops/_filters.html.haml b/app/views/shops/_filters.html.haml new file mode 100644 index 0000000000..42e2b2ec28 --- /dev/null +++ b/app/views/shops/_filters.html.haml @@ -0,0 +1,34 @@ +- resource ||= "visibleMatches" +- property_filters ||= "| filter:filterExpression | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles" + +.row + = render 'shared/components/filter_controls' + -# .small-12.medium-6.columns   + = render 'shared/components/show_profiles' + +.row.animate-show.filter-row{"ng-show" => "filtersActive"} + .small-12.columns + .row.filter-box + .small-12.large-9.columns + %h5.tdhead + .light + = t :hubs_filter_by + = t :hubs_filter_type + + %filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{ "selector-set" => "filterSelectors", objects: "#{resource} | visible | taxonsOf", "active-selectors" => "activeTaxons" } + .small-12.large-3.columns + %h5.tdhead + .light + = t :hubs_filter_by + = t :hubs_filter_delivery + %shipping-type-selector + + .small-12.large-12.columns + %h5.tdhead + .light + = t :hubs_filter_by + = t :hubs_filter_property + .filter-shopfront.property-selectors + %filter-selector{ "selector-set" => "filterSelectors", objects: "#{resource} #{property_filters} | propertiesOf:'distributed_properties'", "active-selectors" => "activeProperties"} + += render 'shared/components/filter_box' diff --git a/app/views/home/_hubs.html.haml b/app/views/shops/_hubs.html.haml similarity index 89% rename from app/views/home/_hubs.html.haml rename to app/views/shops/_hubs.html.haml index e899270efc..6087d2330c 100644 --- a/app/views/home/_hubs.html.haml +++ b/app/views/shops/_hubs.html.haml @@ -7,14 +7,14 @@ = t :hubs_intro = render "shared/components/enterprise_search" - = render "home/filters" + = render "filters" .row .small-12.columns .name-matches{"ng-show" => "nameMatchesFiltered.length > 0"} %h2 = t :hubs_matches - = render "home/hubs_table", enterprises: "nameMatches" + = render "hubs_table", enterprises: "nameMatches" .distance-matches{"ng-if" => "nameMatchesFiltered.length == 0 || distanceMatchesShown"} %h2{"ng-show" => "nameMatchesFiltered.length > 0 || query.length > 0"} @@ -22,7 +22,7 @@ %span{"ng-show" => "nameMatchesFiltered.length > 0"} {{ nameMatchesFiltered[0].name }}... %span{"ng-hide" => "nameMatchesFiltered.length > 0"} {{ query }}... - = render "home/hubs_table", enterprises: "distanceMatches" + = render "hubs_table", enterprises: "distanceMatches" .show-distance-matches{"ng-show" => "nameMatchesFiltered.length > 0 && !distanceMatchesShown"} %a{href: "", "ng-click" => "showDistanceMatches()"} diff --git a/app/views/home/_hubs_table.html.haml b/app/views/shops/_hubs_table.html.haml similarity index 67% rename from app/views/home/_hubs_table.html.haml rename to app/views/shops/_hubs_table.html.haml index edf9eb5ec8..6d5803b477 100644 --- a/app/views/home/_hubs_table.html.haml +++ b/app/views/shops/_hubs_table.html.haml @@ -1,10 +1,10 @@ .active_table - %hub.active_table_node.row{"ng-repeat" => "hub in #{enterprises}Filtered = (#{enterprises} | filter:filterExpression | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+distance', '+orders_close_at'])", + %hub.active_table_node.row{"ng-repeat" => "hub in #{enterprises}Filtered = (#{enterprises} | filter:filterExpression | taxons:activeTaxons | properties:activeProperties:'distributed_properties' | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+distance', '+orders_close_at'])", "ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}", "ng-controller" => "HubNodeCtrl", id: "{{hub.hash}}"} .small-12.columns - = render 'home/skinny' - = render 'home/fat' + = render 'skinny' + = render 'fat' = render 'shared/components/enterprise_no_results', enterprises: "#{enterprises}Filtered" diff --git a/app/views/home/_skinny.html.haml b/app/views/shops/_skinny.html.haml similarity index 100% rename from app/views/home/_skinny.html.haml rename to app/views/shops/_skinny.html.haml diff --git a/app/views/shops/index.html.haml b/app/views/shops/index.html.haml index 1b3cf547a8..7e288f4157 100644 --- a/app/views/shops/index.html.haml +++ b/app/views/shops/index.html.haml @@ -10,5 +10,5 @@ %p.text-big = t :shops_text -= render partial: "home/hubs" -= render partial: "shared/footer" += render "hubs" += render "shared/footer" diff --git a/config/locales/en.yml b/config/locales/en.yml index 2211243bda..010cd4d586 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -513,6 +513,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using hubs_filter_by: "Filter by" hubs_filter_type: "Type" hubs_filter_delivery: "Delivery" + hubs_filter_property: "Property" hubs_matches: "Did you mean?" hubs_intro: Shop in your local area hubs_distance: Closest to diff --git a/lib/open_food_network/enterprise_injection_data.rb b/lib/open_food_network/enterprise_injection_data.rb index 87516007c6..938c8b3aab 100644 --- a/lib/open_food_network/enterprise_injection_data.rb +++ b/lib/open_food_network/enterprise_injection_data.rb @@ -20,8 +20,12 @@ module OpenFoodNetwork @supplied_taxons ||= Spree::Taxon.supplied_taxons end - def distributed_taxons - @distributed_taxons ||= Spree::Taxon.distributed_taxons + def all_distributed_taxons + @all_distributed_taxons ||= Spree::Taxon.distributed_taxons(:all) + end + + def current_distributed_taxons + @current_distributed_taxons ||= Spree::Taxon.distributed_taxons(:current) end end end diff --git a/spec/features/consumer/groups_spec.rb b/spec/features/consumer/groups_spec.rb index 039322b379..4a5f166d7d 100644 --- a/spec/features/consumer/groups_spec.rb +++ b/spec/features/consumer/groups_spec.rb @@ -54,4 +54,43 @@ feature 'Groups', js: true do end end end + + describe "shops" do + describe "filtering by product property" do + let!(:group) { create(:enterprise_group, enterprises: [d1, d2], on_front_page: true) } + let!(:order_cycle) { create(:simple_order_cycle, distributors: [d1, d2], coordinator: create(:distributor_enterprise)) } + let(:producer) { create(:supplier_enterprise) } + let(:d1) { create(:distributor_enterprise) } + let(:d2) { create(:distributor_enterprise) } + let(:p1) { create(:simple_product, supplier: producer) } + let(:p2) { create(:simple_product, supplier: create(:supplier_enterprise)) } + let(:ex_d1) { order_cycle.exchanges.outgoing.where(receiver_id: d1).first } + let(:ex_d2) { order_cycle.exchanges.outgoing.where(receiver_id: d2).first } + + before do + producer.set_producer_property 'Organic', 'NASAA 12345' + p2.set_property 'Local', 'XYZ 123' + + ex_d1.variants << p1.variants.first + ex_d2.variants << p2.variants.first + + visit group_path(group, anchor: "/hubs") + end + + it "filters" do + toggle_filters + + toggle_filter 'Organic' + + expect(page).to have_content d1.name + expect(page).not_to have_content d2.name + + toggle_filter 'Organic' + toggle_filter 'Local' + + expect(page).not_to have_content d1.name + expect(page).to have_content d2.name + end + end + end end diff --git a/spec/features/consumer/shops_spec.rb b/spec/features/consumer/shops_spec.rb index 2a65a73aef..f8f54a197b 100644 --- a/spec/features/consumer/shops_spec.rb +++ b/spec/features/consumer/shops_spec.rb @@ -47,7 +47,7 @@ feature 'Shops', js: true do it "should show closed shops after clicking the button" do create(:simple_product, distributors: [d1, d2]) visit shops_path - click_link_and_ensure("Show closed shops", -> { page.has_selector? 'hub.inactive' }) + click_link_and_ensure("Show Closed Shops", -> { page.has_selector? 'hub.inactive' }) page.should have_selector 'hub.inactive', text: d2.name end @@ -56,6 +56,67 @@ feature 'Shops', js: true do expect(page).to have_current_path enterprise_shop_path(distributor) end + describe "filtering by product property" do + let!(:order_cycle) { create(:simple_order_cycle, distributors: [d1, d2], coordinator: create(:distributor_enterprise)) } + let!(:p1) { create(:simple_product, supplier: producer) } + let!(:p2) { create(:simple_product, supplier: create(:supplier_enterprise)) } + let(:ex_d1) { order_cycle.exchanges.outgoing.where(receiver_id: d1).first } + let(:ex_d2) { order_cycle.exchanges.outgoing.where(receiver_id: d2).first } + + before do + p2.set_property 'Local', 'XYZ 123' + + ex_d1.variants << p1.variants.first + ex_d2.variants << p2.variants.first + + visit shops_path + end + + it "filters" do + toggle_filters + + toggle_filter 'Organic' + + expect(page).to have_content d1.name + expect(page).not_to have_content d2.name + + toggle_filter 'Organic' + toggle_filter 'Local' + + expect(page).not_to have_content d1.name + expect(page).to have_content d2.name + end + end + + describe "taxon badges" do + let!(:closed_oc) { create(:closed_order_cycle, distributors: [shop], variants: [p_closed.variants.first]) } + let!(:p_closed) { create(:simple_product, taxons: [taxon_closed]) } + let(:shop) { create(:distributor_enterprise) } + let(:taxon_closed) { create(:taxon, name: 'Closed') } + + describe "open shops" do + let!(:open_oc) { create(:open_order_cycle, distributors: [shop], variants: [p_open.variants.first]) } + let!(:p_open) { create(:simple_product, taxons: [taxon_open]) } + let(:taxon_open) { create(:taxon, name: 'Open') } + + it "shows taxons for open order cycles only" do + visit shops_path + expand_active_table_node shop.name + expect(page).to have_selector '.fat-taxons', text: 'Open' + expect(page).not_to have_selector '.fat-taxons', text: 'Closed' + end + end + + describe "closed shops" do + it "shows taxons for any order cycle" do + visit shops_path + click_link 'Show Closed Shops' + expand_active_table_node shop.name + expect(page).to have_selector '.fat-taxons', text: 'Closed' + end + end + end + describe "property badges" do let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) } let(:product) { create(:simple_product, supplier: producer) } diff --git a/spec/models/producer_property_spec.rb b/spec/models/producer_property_spec.rb index 2d0009ce12..0de7aae19b 100644 --- a/spec/models/producer_property_spec.rb +++ b/spec/models/producer_property_spec.rb @@ -8,7 +8,7 @@ describe ProducerProperty do producer.set_producer_property 'Organic Certified', 'NASAA 54321' end - describe ".sold_by" do + describe ".currently_sold_by and .ever_sold_by" do let!(:shop) { create(:distributor_enterprise) } let!(:oc) { create(:simple_order_cycle, distributors: [shop], variants: [product.variants.first]) } let(:product) { create(:simple_product, supplier: producer) } @@ -22,7 +22,8 @@ describe ProducerProperty do describe "with an associated producer property" do it "returns the producer property" do - expect(ProducerProperty.sold_by(shop)).to eq [pp] + expect(ProducerProperty.currently_sold_by(shop)).to eq [pp] + expect(ProducerProperty.ever_sold_by(shop)).to eq [pp] end end @@ -30,7 +31,8 @@ describe ProducerProperty do let!(:exchange) { create(:exchange, order_cycle: oc, incoming: true, sender: producer_other, receiver: oc.coordinator) } it "doesn't return the producer property" do - expect(ProducerProperty.sold_by(shop)).not_to include pp_other + expect(ProducerProperty.currently_sold_by(shop)).not_to include pp_other + expect(ProducerProperty.ever_sold_by(shop)).not_to include pp_other end end @@ -40,7 +42,8 @@ describe ProducerProperty do let!(:exchange) { create(:exchange, order_cycle: oc, incoming: false, sender: oc.coordinator, receiver: shop_other, variants: [product_other.variants.first]) } it "doesn't return the producer property" do - expect(ProducerProperty.sold_by(shop)).not_to include pp_other + expect(ProducerProperty.currently_sold_by(shop)).not_to include pp_other + expect(ProducerProperty.ever_sold_by(shop)).not_to include pp_other end end @@ -49,8 +52,12 @@ describe ProducerProperty do oc.update_attributes! orders_close_at: 1.week.ago end - it "doesn't return the producer property" do - expect(ProducerProperty.sold_by(shop)).not_to include pp + it "doesn't return the producer property for .currently_sold_by" do + expect(ProducerProperty.currently_sold_by(shop)).not_to include pp + end + + it "returns the producer property for .ever_sold_by" do + expect(ProducerProperty.ever_sold_by(shop)).to include pp end end @@ -59,7 +66,8 @@ describe ProducerProperty do let!(:oc) { create(:simple_order_cycle, distributors: [shop], variants: [product.variants.first, product2.variants.first]) } it "doesn't return duplicates" do - expect(ProducerProperty.sold_by(shop).to_a.count).to eq 1 + expect(ProducerProperty.currently_sold_by(shop).to_a.count).to eq 1 + expect(ProducerProperty.ever_sold_by(shop).to_a.count).to eq 1 end end end diff --git a/spec/models/spree/property_spec.rb b/spec/models/spree/property_spec.rb index 6e6ed2d780..29046a8782 100644 --- a/spec/models/spree/property_spec.rb +++ b/spec/models/spree/property_spec.rb @@ -31,42 +31,53 @@ module Spree end end - describe ".sold_by" do + describe ".currently_sold_by and .ever_sold_by" do let!(:shop) { create(:distributor_enterprise) } let!(:shop_other) { create(:distributor_enterprise) } let!(:product) { create(:simple_product) } let!(:product_other_ex) { create(:simple_product) } let!(:product_no_oc) { create(:simple_product) } - let!(:product_closed_oc) { create(:simple_product) } let!(:oc) { create(:simple_order_cycle, distributors: [shop], variants: [product.variants.first]) } - let!(:oc_closed) { create(:closed_order_cycle, distributors: [shop], variants: [product_closed_oc.variants.first]) } let!(:exchange_other_shop) { create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: shop_other, variants: [product_other_ex.variants.first]) } let(:property) { product.properties.last } let(:property_other_ex) { product_other_ex.properties.last } let(:property_no_oc) { product_no_oc.properties.last } - let(:property_closed_oc) { product_closed_oc.properties.last } before do product.set_property 'Organic', 'NASAA 12345' product_other_ex.set_property 'Biodynamic', 'ASDF 12345' product_no_oc.set_property 'Shiny', 'Very' - product_closed_oc.set_property 'Spiffy', 'Ooh yeah' end it "returns the property" do - expect(Property.sold_by(shop)).to eq [property] + expect(Property.currently_sold_by(shop)).to eq [property] + expect(Property.ever_sold_by(shop)).to eq [property] end it "doesn't return the property from another exchange" do - expect(Property.sold_by(shop)).not_to include property_other_ex + expect(Property.currently_sold_by(shop)).not_to include property_other_ex + expect(Property.ever_sold_by(shop)).not_to include property_other_ex end it "doesn't return the property with no order cycle" do - expect(Property.sold_by(shop)).not_to include property_no_oc + expect(Property.currently_sold_by(shop)).not_to include property_no_oc + expect(Property.ever_sold_by(shop)).not_to include property_no_oc end - it "doesn't return the property from a closed order cycle" do - expect(Property.sold_by(shop)).not_to include property_closed_oc + describe "closed order cyces" do + let!(:product_closed_oc) { create(:simple_product) } + let!(:oc_closed) { create(:closed_order_cycle, distributors: [shop], variants: [product_closed_oc.variants.first]) } + let(:property_closed_oc) { product_closed_oc.properties.last } + + before { product_closed_oc.set_property 'Spiffy', 'Ooh yeah' } + + it "doesn't return the property for .currently_sold_by" do + expect(Property.currently_sold_by(shop)).not_to include property_closed_oc + end + + it "returns the property for .ever_sold_by" do + expect(Property.ever_sold_by(shop)).to include property_closed_oc + end end context "with another product in the order cycle" do @@ -78,7 +89,8 @@ module Spree end it "doesn't return duplicates" do - expect(Property.sold_by(shop).to_a.count).to eq 1 + expect(Property.currently_sold_by(shop).to_a.count).to eq 1 + expect(Property.ever_sold_by(shop).to_a.count).to eq 1 end end end diff --git a/spec/models/spree/taxon_spec.rb b/spec/models/spree/taxon_spec.rb index 3a26c9b2aa..a031823bee 100644 --- a/spec/models/spree/taxon_spec.rb +++ b/spec/models/spree/taxon_spec.rb @@ -29,13 +29,18 @@ module Spree end end - describe "finding all distributed taxons" do - let!(:oc) { create(:simple_order_cycle, distributors: [e], variants: [p1.master]) } - let!(:s) { create(:supplier_enterprise) } - let!(:p1) { create(:simple_product, supplier: s, taxons: [t1, t2]) } + describe "finding distributed taxons" do + let!(:oc_open) { create(:open_order_cycle, distributors: [e], variants: [p_open.variants.first]) } + let!(:oc_closed) { create(:closed_order_cycle, distributors: [e], variants: [p_closed.variants.first]) } + let!(:p_open) { create(:simple_product, primary_taxon: t1) } + let!(:p_closed) { create(:simple_product, primary_taxon: t2) } - it "finds taxons" do - Taxon.distributed_taxons.should == {e.id => Set.new(p1.taxons.map(&:id))} + it "finds all distributed taxons" do + expect(Taxon.distributed_taxons(:all)).to eq({e.id => Set.new([t1.id, t2.id])}) + end + + it "finds currently distributed taxons" do + expect(Taxon.distributed_taxons(:current)).to eq({e.id => Set.new([t1.id])}) end end end diff --git a/spec/serializers/enterprise_serializer_spec.rb b/spec/serializers/enterprise_serializer_spec.rb index f5eff4f771..bea2672157 100644 --- a/spec/serializers/enterprise_serializer_spec.rb +++ b/spec/serializers/enterprise_serializer_spec.rb @@ -6,7 +6,8 @@ describe Api::EnterpriseSerializer do let(:taxon) { create(:taxon) } let(:data) { OpenStruct.new(earliest_closing_times: {}, active_distributors: [], - distributed_taxons: {enterprise.id => [123]}, + all_distributed_taxons: {enterprise.id => [123]}, + current_distributed_taxons: {enterprise.id => [123]}, supplied_taxons: {enterprise.id => [456]}, shipping_method_services: {}, relatives: {enterprise.id => {producers: [123], distributors: [456]}}) } From e8848451a54bf392211c773c6462aa2d897d1db2 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 28 Oct 2016 16:26:27 +1100 Subject: [PATCH 055/109] Spacing --- spec/models/enterprise_caching_spec.rb | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/spec/models/enterprise_caching_spec.rb b/spec/models/enterprise_caching_spec.rb index 72b356df21..58317ca2df 100644 --- a/spec/models/enterprise_caching_spec.rb +++ b/spec/models/enterprise_caching_spec.rb @@ -9,8 +9,9 @@ describe Enterprise do describe "with a supplied product" do let(:product) { create(:simple_product, supplier: enterprise) } let!(:classification) { create(:classification, taxon: taxon, product: product) } + it "touches enterprise when a classification on that product changes" do - expect{classification.save!}.to change{enterprise.updated_at} + expect { classification.save! }.to change { enterprise.updated_at } end end @@ -18,37 +19,41 @@ describe Enterprise do let(:product) { create(:simple_product) } let!(:oc) { create(:simple_order_cycle, distributors: [enterprise], variants: [product.master]) } let!(:classification) { create(:classification, taxon: taxon, product: product) } + it "touches enterprise when a classification on that product changes" do - expect{classification.save!}.to change{enterprise.reload.updated_at} + expect { classification.save! }.to change { enterprise.reload.updated_at } end end describe "with relatives" do let(:child_enterprise) { create(:supplier_enterprise) } let!(:er) { create(:enterprise_relationship, parent: enterprise, child: child_enterprise) } + it "touches enterprise when enterprise relationship is updated" do - expect{er.save!}.to change {enterprise.reload.updated_at } + expect { er.save! }.to change { enterprise.reload.updated_at } end end - + describe "with shipping methods" do let(:sm) { create(:shipping_method) } + before do enterprise.shipping_methods << sm end + it "touches enterprise when distributor_shipping_method is updated" do expect { enterprise.distributor_shipping_methods.first.save! - }.to change {enterprise.reload.updated_at} + }.to change { enterprise.reload.updated_at } end it "touches enterprise when shipping method is updated" do - expect{sm.save!}.to change {enterprise.reload.updated_at } + expect { sm.save! }.to change { enterprise.reload.updated_at } end end - + it "touches enterprise when address is updated" do - expect{enterprise.address.save!}.to change {enterprise.reload.updated_at } + expect{ enterprise.address.save! }.to change { enterprise.reload.updated_at } end end end From bd11c6ce14a7dc4d0c4fac5b4dfc608860346c8a Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 28 Oct 2016 16:31:42 +1100 Subject: [PATCH 056/109] New hash style --- app/models/exchange.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/models/exchange.rb b/app/models/exchange.rb index 3d0e6417e6..ce152da6d6 100644 --- a/app/models/exchange.rb +++ b/app/models/exchange.rb @@ -2,18 +2,18 @@ class Exchange < ActiveRecord::Base acts_as_taggable belongs_to :order_cycle - belongs_to :sender, :class_name => 'Enterprise' - belongs_to :receiver, :class_name => 'Enterprise' - belongs_to :payment_enterprise, :class_name => 'Enterprise' + belongs_to :sender, class_name: 'Enterprise' + belongs_to :receiver, class_name: 'Enterprise' + belongs_to :payment_enterprise, class_name: 'Enterprise' - has_many :exchange_variants, :dependent => :destroy - has_many :variants, :through => :exchange_variants + has_many :exchange_variants, dependent: :destroy + has_many :variants, through: :exchange_variants - has_many :exchange_fees, :dependent => :destroy - has_many :enterprise_fees, :through => :exchange_fees + has_many :exchange_fees, dependent: :destroy + has_many :enterprise_fees, through: :exchange_fees validates_presence_of :order_cycle, :sender, :receiver - validates_uniqueness_of :sender_id, :scope => [:order_cycle_id, :receiver_id, :incoming] + validates_uniqueness_of :sender_id, scope: [:order_cycle_id, :receiver_id, :incoming] after_save :refresh_products_cache after_destroy :refresh_products_cache_from_destroy From 1ea4f4274c5daa7d663c26cf25ee2392c8f548b9 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 28 Oct 2016 16:33:27 +1100 Subject: [PATCH 057/109] Add enterprise cache invalidation for order cycle changes --- app/models/exchange.rb | 2 +- spec/models/enterprise_caching_spec.rb | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/models/exchange.rb b/app/models/exchange.rb index ce152da6d6..f60c7289b6 100644 --- a/app/models/exchange.rb +++ b/app/models/exchange.rb @@ -3,7 +3,7 @@ class Exchange < ActiveRecord::Base belongs_to :order_cycle belongs_to :sender, class_name: 'Enterprise' - belongs_to :receiver, class_name: 'Enterprise' + belongs_to :receiver, class_name: 'Enterprise', touch: true belongs_to :payment_enterprise, class_name: 'Enterprise' has_many :exchange_variants, dependent: :destroy diff --git a/spec/models/enterprise_caching_spec.rb b/spec/models/enterprise_caching_spec.rb index 58317ca2df..5eceba9160 100644 --- a/spec/models/enterprise_caching_spec.rb +++ b/spec/models/enterprise_caching_spec.rb @@ -17,12 +17,17 @@ describe Enterprise do describe "with a distributed product" do let(:product) { create(:simple_product) } - let!(:oc) { create(:simple_order_cycle, distributors: [enterprise], variants: [product.master]) } + let(:oc) { create(:simple_order_cycle, distributors: [enterprise], variants: [product.variants.first]) } let!(:classification) { create(:classification, taxon: taxon, product: product) } it "touches enterprise when a classification on that product changes" do + oc expect { classification.save! }.to change { enterprise.reload.updated_at } end + + it "touches enterprise when the product's variant is added to order cycle" do + expect { oc }.to change { enterprise.reload.updated_at } + end end describe "with relatives" do From d93fe3cf2cffabbb1e35934a3990bde2d64089ba Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 28 Oct 2016 16:34:01 +1100 Subject: [PATCH 058/109] Cache enterprise supplied and distributed taxons --- app/serializers/api/enterprise_serializer.rb | 31 ++++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index 3537bcdd24..c7b45937f6 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -23,7 +23,6 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer include SerializerHelper attributes :orders_close_at, :active - attributes :taxons, :supplied_taxons has_many :supplied_properties, serializer: Api::PropertySerializer has_many :distributed_properties, serializer: Api::PropertySerializer @@ -59,18 +58,6 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer OpenFoodNetwork::PropertyMerge.merge product_properties, producer_properties end - - def taxons - if active - ids_to_objs options[:data].current_distributed_taxons[object.id] - else - ids_to_objs options[:data].all_distributed_taxons[object.id] - end - end - - def supplied_taxons - ids_to_objs options[:data].supplied_taxons[object.id] - end end class Api::CachedEnterpriseSerializer < ActiveModel::Serializer @@ -90,6 +77,8 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer :email_address, :hash, :logo, :promo_image, :path, :pickup, :delivery, :icon, :icon_font, :producer_icon_font, :category, :producers, :hubs + attributes :taxons, :supplied_taxons + has_one :address, serializer: Api::AddressSerializer def pickup @@ -132,6 +121,22 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer ids_to_objs(relatives.andand[:distributors]) end + def taxons + if active + ids_to_objs options[:data].current_distributed_taxons[object.id] + else + ids_to_objs options[:data].all_distributed_taxons[object.id] + end + end + + def supplied_taxons + ids_to_objs options[:data].supplied_taxons[object.id] + end + + def active + options[:data].active_distributors.andand.include? object + end + # Map svg icons. def icon icons = { From 22080a9a0800b48427965473262d5db342988daa Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 28 Oct 2016 17:21:58 +1100 Subject: [PATCH 059/109] Property / ProducerProperty changes update supplier enterprise cache --- app/models/producer_property.rb | 2 +- app/models/spree/product_property_decorator.rb | 3 +++ spec/models/enterprise_caching_spec.rb | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/models/producer_property.rb b/app/models/producer_property.rb index b0bb1b6e51..a21b5eb5a3 100644 --- a/app/models/producer_property.rb +++ b/app/models/producer_property.rb @@ -1,5 +1,5 @@ class ProducerProperty < ActiveRecord::Base - belongs_to :producer, class_name: 'Enterprise' + belongs_to :producer, class_name: 'Enterprise', touch: true belongs_to :property, class_name: 'Spree::Property' default_scope order("#{self.table_name}.position") diff --git a/app/models/spree/product_property_decorator.rb b/app/models/spree/product_property_decorator.rb index 0f3329c03d..47414e92dc 100644 --- a/app/models/spree/product_property_decorator.rb +++ b/app/models/spree/product_property_decorator.rb @@ -1,8 +1,11 @@ module Spree ProductProperty.class_eval do + belongs_to :product, class_name: "Spree::Product", touch: true + after_save :refresh_products_cache after_destroy :refresh_products_cache + def refresh_products_cache product.refresh_products_cache end diff --git a/spec/models/enterprise_caching_spec.rb b/spec/models/enterprise_caching_spec.rb index 5eceba9160..65ca32bb21 100644 --- a/spec/models/enterprise_caching_spec.rb +++ b/spec/models/enterprise_caching_spec.rb @@ -9,10 +9,25 @@ describe Enterprise do describe "with a supplied product" do let(:product) { create(:simple_product, supplier: enterprise) } let!(:classification) { create(:classification, taxon: taxon, product: product) } + let(:property) { product.product_properties.last } + let(:producer_property) { enterprise.producer_properties.last } + + before do + product.set_property 'Organic', 'NASAA 12345' + enterprise.set_producer_property 'Biodynamic', 'ASDF 4321' + end it "touches enterprise when a classification on that product changes" do expect { classification.save! }.to change { enterprise.updated_at } end + + it "touches enterprise when a property on that product changes" do + expect { property.save! }.to change { enterprise.reload.updated_at } + end + + it "touches enterprise when a producer property on that product changes" do + expect { producer_property.save! }.to change { enterprise.reload.updated_at } + end end describe "with a distributed product" do From 9b656eaf4f3e951f60057dca5b9ebe21a1e0dccf Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 28 Oct 2016 17:26:35 +1100 Subject: [PATCH 060/109] Property / ProducerProperty changes update distributor enterprise cache --- app/models/enterprise.rb | 5 ++++ spec/models/enterprise_caching_spec.rb | 38 ++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 5460ecc94d..40642d12cb 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -87,6 +87,7 @@ class Enterprise < ActiveRecord::Base before_validation :set_unused_address_fields after_validation :geocode_address + after_touch :touch_distributors after_create :relate_to_owners_enterprises # TODO: Later versions of devise have a dedicated after_confirmation callback, so use that after_update :welcome_after_confirm, if: lambda { confirmation_token_changed? && confirmation_token.nil? } @@ -469,4 +470,8 @@ class Enterprise < ActiveRecord::Base def initialize_permalink self.permalink = Enterprise.find_available_permalink(name) end + + def touch_distributors + Enterprise.distributing_product(self.supplied_products).each(&:touch) + end end diff --git a/spec/models/enterprise_caching_spec.rb b/spec/models/enterprise_caching_spec.rb index 65ca32bb21..047fc6ab02 100644 --- a/spec/models/enterprise_caching_spec.rb +++ b/spec/models/enterprise_caching_spec.rb @@ -5,6 +5,7 @@ describe Enterprise do describe "is touched when a(n)" do let(:enterprise) { create(:distributor_enterprise, updated_at: 1.week.ago) } let(:taxon) { create(:taxon) } + let(:supplier2) { create(:supplier_enterprise) } describe "with a supplied product" do let(:product) { create(:simple_product, supplier: enterprise) } @@ -28,16 +29,47 @@ describe Enterprise do it "touches enterprise when a producer property on that product changes" do expect { producer_property.save! }.to change { enterprise.reload.updated_at } end + + it "touches enterprise when the supplier of a product changes" do + expect { + product.update_attributes!(supplier: supplier2) + }.to change { enterprise.updated_at } + end end describe "with a distributed product" do let(:product) { create(:simple_product) } let(:oc) { create(:simple_order_cycle, distributors: [enterprise], variants: [product.variants.first]) } + let(:supplier) { product.supplier } let!(:classification) { create(:classification, taxon: taxon, product: product) } + let(:property) { product.product_properties.last } + let(:producer_property) { supplier.producer_properties.last } - it "touches enterprise when a classification on that product changes" do - oc - expect { classification.save! }.to change { enterprise.reload.updated_at } + before do + product.set_property 'Organic', 'NASAA 12345' + supplier.set_producer_property 'Biodynamic', 'ASDF 4321' + end + + context "with an order cycle" do + before { oc } + + it "touches enterprise when a classification on that product changes" do + expect { classification.save! }.to change { enterprise.reload.updated_at } + end + + it "touches enterprise when a property on that product changes" do + expect { property.save! }.to change { enterprise.reload.updated_at } + end + + it "touches enterprise when a producer property on that product changes" do + expect { producer_property.save! }.to change { enterprise.reload.updated_at } + end + + it "touches enterprise when the supplier of a product changes" do + expect { + product.update_attributes!(supplier: supplier2) + }.to change { enterprise.reload.updated_at } + end end it "touches enterprise when the product's variant is added to order cycle" do From 6030e9a2941b58d2407d3e02b00a8e935a0a67a2 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 28 Oct 2016 17:27:07 +1100 Subject: [PATCH 061/109] Cache enterprise supplied and distributed properties and producer properties --- app/serializers/api/enterprise_serializer.rb | 55 ++++++++++---------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb index c7b45937f6..e3829174a2 100644 --- a/app/serializers/api/enterprise_serializer.rb +++ b/app/serializers/api/enterprise_serializer.rb @@ -23,8 +23,6 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer include SerializerHelper attributes :orders_close_at, :active - has_many :supplied_properties, serializer: Api::PropertySerializer - has_many :distributed_properties, serializer: Api::PropertySerializer def orders_close_at options[:data].earliest_closing_times[object.id] @@ -33,31 +31,6 @@ class Api::UncachedEnterpriseSerializer < ActiveModel::Serializer def active options[:data].active_distributors.andand.include? object end - - def supplied_properties - # This results in 3 queries per enterprise - product_properties = Spree::Property.applied_by(object) - producer_properties = object.properties - - OpenFoodNetwork::PropertyMerge.merge product_properties, producer_properties - end - - def distributed_properties - # This results in 3 queries per enterprise - - if active - product_properties = Spree::Property.currently_sold_by(object) - producer_property_ids = ProducerProperty.currently_sold_by(object).pluck(:property_id) - - else - product_properties = Spree::Property.ever_sold_by(object) - producer_property_ids = ProducerProperty.ever_sold_by(object).pluck(:property_id) - end - - producer_properties = Spree::Property.where(id: producer_property_ids) - - OpenFoodNetwork::PropertyMerge.merge product_properties, producer_properties - end end class Api::CachedEnterpriseSerializer < ActiveModel::Serializer @@ -81,6 +54,9 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer has_one :address, serializer: Api::AddressSerializer + has_many :supplied_properties, serializer: Api::PropertySerializer + has_many :distributed_properties, serializer: Api::PropertySerializer + def pickup services = options[:data].shipping_method_services[object.id] services ? services[:pickup] : false @@ -133,6 +109,31 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer ids_to_objs options[:data].supplied_taxons[object.id] end + def supplied_properties + # This results in 3 queries per enterprise + product_properties = Spree::Property.applied_by(object) + producer_properties = object.properties + + OpenFoodNetwork::PropertyMerge.merge product_properties, producer_properties + end + + def distributed_properties + # This results in 3 queries per enterprise + + if active + product_properties = Spree::Property.currently_sold_by(object) + producer_property_ids = ProducerProperty.currently_sold_by(object).pluck(:property_id) + + else + product_properties = Spree::Property.ever_sold_by(object) + producer_property_ids = ProducerProperty.ever_sold_by(object).pluck(:property_id) + end + + producer_properties = Spree::Property.where(id: producer_property_ids) + + OpenFoodNetwork::PropertyMerge.merge product_properties, producer_properties + end + def active options[:data].active_distributors.andand.include? object end From dabac50128d438ffe7aeef8176aad2f6b9299f47 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 28 Oct 2016 17:44:29 +1100 Subject: [PATCH 062/109] Replace Enterprise.distributing_product and Enterprise.distributing_any_product_of with Enterprise.distributing_products --- app/models/enterprise.rb | 9 ++------- app/models/spree/product_decorator.rb | 2 +- app/views/spree/products/_source.html.haml | 2 +- .../distribution_change_validator.rb | 4 ++-- .../distribution_change_validator_spec.rb | 12 ++++++------ spec/models/enterprise_spec.rb | 14 ++++++-------- 6 files changed, 18 insertions(+), 25 deletions(-) diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 40642d12cb..05ec3596f2 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -162,12 +162,7 @@ class Enterprise < ActiveRecord::Base select('DISTINCT enterprises.*') } - scope :distributing_product, lambda { |product| - with_distributed_products_outer.with_order_cycles_and_exchange_variants_outer. - where('product_distributions.product_id = ? OR spree_variants.product_id = ?', product, product). - select('DISTINCT enterprises.*') - } - scope :distributing_any_product_of, lambda { |products| + scope :distributing_products, lambda { |products| with_distributed_products_outer.with_order_cycles_and_exchange_variants_outer. where('product_distributions.product_id IN (?) OR spree_variants.product_id IN (?)', products, products). select('DISTINCT enterprises.*') @@ -472,6 +467,6 @@ class Enterprise < ActiveRecord::Base end def touch_distributors - Enterprise.distributing_product(self.supplied_products).each(&:touch) + Enterprise.distributing_products(self.supplied_products).each(&:touch) end end diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index f8c177ceac..835a875dbf 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -215,7 +215,7 @@ Spree::Product.class_eval do end def touch_distributors - Enterprise.distributing_product(self).each(&:touch) + Enterprise.distributing_products(self).each(&:touch) end def add_primary_taxon_to_taxons diff --git a/app/views/spree/products/_source.html.haml b/app/views/spree/products/_source.html.haml index 24f66f2add..dfcd091d67 100644 --- a/app/views/spree/products/_source.html.haml +++ b/app/views/spree/products/_source.html.haml @@ -12,7 +12,7 @@ %tbody - order = current_order(false) - validator = DistributionChangeValidator.new(order) - - Enterprise.distributing_product(@product).each do |distributor| + - Enterprise.distributing_products(@product).each do |distributor| - if !order.nil? && distributor == order.distributor %tr.odd %td diff --git a/lib/open_food_network/distribution_change_validator.rb b/lib/open_food_network/distribution_change_validator.rb index 6f7edc2c0f..ba6d21b247 100644 --- a/lib/open_food_network/distribution_change_validator.rb +++ b/lib/open_food_network/distribution_change_validator.rb @@ -1,5 +1,5 @@ class DistributionChangeValidator - + def initialize order @order = order end @@ -29,7 +29,7 @@ class DistributionChangeValidator end def available_distributors_for(product) - distributors = Enterprise.distributing_product(product) + distributors = Enterprise.distributing_products(product) if @order.andand.line_items.present? distributors = available_distributors(distributors) diff --git a/spec/lib/open_food_network/distribution_change_validator_spec.rb b/spec/lib/open_food_network/distribution_change_validator_spec.rb index 7e2a4b3274..8e324f961e 100644 --- a/spec/lib/open_food_network/distribution_change_validator_spec.rb +++ b/spec/lib/open_food_network/distribution_change_validator_spec.rb @@ -87,7 +87,7 @@ describe DistributionChangeValidator do enterprise_with_some_variants.stub(:distributed_variants) { [variant1, variant3] } # Only some variants enterprise_with_some_plus_extras = double(:enterprise) enterprise_with_some_plus_extras.stub(:distributed_variants) { [variant1, variant2, variant3, variant4] } # Only some variants, plus extras - + subject.available_distributors([enterprise_with_some_variants]).should_not include enterprise_with_some_variants subject.available_distributors([enterprise_with_some_plus_extras]).should_not include enterprise_with_some_plus_extras end @@ -97,10 +97,10 @@ describe DistributionChangeValidator do order.stub(:line_item_variants) { line_item_variants } enterprise = double(:enterprise) enterprise.stub(:distributed_variants) { [variant1, variant2, variant3, variant4, variant5] } # Excess variants - + subject.available_distributors([enterprise]).should == [enterprise] end - + it "matches no enterprises when none are provided" do subject.available_distributors([]).should == [] end @@ -201,7 +201,7 @@ describe DistributionChangeValidator do describe "finding available distributors for a product" do it "returns enterprises distributing the product when there's no order" do subject = DistributionChangeValidator.new(nil) - Enterprise.stub(:distributing_product).and_return([1, 2, 3]) + Enterprise.stub(:distributing_products).and_return([1, 2, 3]) subject.should_receive(:available_distributors).never subject.available_distributors_for(product).should == [1, 2, 3] @@ -209,7 +209,7 @@ describe DistributionChangeValidator do it "returns enterprises distributing the product when there's no order items" do order.stub(:line_items) { [] } - Enterprise.stub(:distributing_product).and_return([1, 2, 3]) + Enterprise.stub(:distributing_products).and_return([1, 2, 3]) subject.should_receive(:available_distributors).never subject.available_distributors_for(product).should == [1, 2, 3] @@ -217,7 +217,7 @@ describe DistributionChangeValidator do it "filters by available distributors when there are order items" do order.stub(:line_items) { [1, 2, 3] } - Enterprise.stub(:distributing_product).and_return([1, 2, 3]) + Enterprise.stub(:distributing_products).and_return([1, 2, 3]) subject.should_receive(:available_distributors).and_return([2]) subject.available_distributors_for(product).should == [2] diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb index b8360094ec..8c16ac39eb 100644 --- a/spec/models/enterprise_spec.rb +++ b/spec/models/enterprise_spec.rb @@ -542,40 +542,38 @@ describe Enterprise do end end - describe "distributing_product" do + describe "distributing_products" do it "returns enterprises distributing via a product distribution" do d = create(:distributor_enterprise) p = create(:product, distributors: [d]) - Enterprise.distributing_product(p).should == [d] + Enterprise.distributing_products(p).should == [d] end it "returns enterprises distributing via an order cycle" do d = create(:distributor_enterprise) p = create(:product) oc = create(:simple_order_cycle, distributors: [d], variants: [p.master]) - Enterprise.distributing_product(p).should == [d] + Enterprise.distributing_products(p).should == [d] end - end - describe "distributing_any_product_of" do it "returns enterprises distributing via a product distribution" do d = create(:distributor_enterprise) p = create(:product, distributors: [d]) - Enterprise.distributing_any_product_of([p]).should == [d] + Enterprise.distributing_products([p]).should == [d] end it "returns enterprises distributing via an order cycle" do d = create(:distributor_enterprise) p = create(:product) oc = create(:simple_order_cycle, distributors: [d], variants: [p.master]) - Enterprise.distributing_any_product_of([p]).should == [d] + Enterprise.distributing_products([p]).should == [d] end it "does not return duplicate enterprises" do d = create(:distributor_enterprise) p1 = create(:product, distributors: [d]) p2 = create(:product, distributors: [d]) - Enterprise.distributing_any_product_of([p1, p2]).should == [d] + Enterprise.distributing_products([p1, p2]).should == [d] end end From 44a301edb1e691ed02a819ab50b0ba38f5aae655 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 4 Nov 2016 16:04:13 +1100 Subject: [PATCH 063/109] When touching distributing enterprises, do not touch self -> infinite recursion --- app/models/enterprise.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 05ec3596f2..840477185f 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -467,6 +467,8 @@ class Enterprise < ActiveRecord::Base end def touch_distributors - Enterprise.distributing_products(self.supplied_products).each(&:touch) + Enterprise.distributing_products(self.supplied_products). + where('enterprises.id != ?', self.id). + each(&:touch) end end From c4318030d31d0a8074ac951544c33d6f7a99dfc8 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 4 Nov 2016 16:52:43 +1100 Subject: [PATCH 064/109] Fix spec: Second visit doesn't actually reload the page --- spec/features/consumer/shops_spec.rb | 169 ++++++++++++++------------- 1 file changed, 85 insertions(+), 84 deletions(-) diff --git a/spec/features/consumer/shops_spec.rb b/spec/features/consumer/shops_spec.rb index f8f54a197b..c9e8063e72 100644 --- a/spec/features/consumer/shops_spec.rb +++ b/spec/features/consumer/shops_spec.rb @@ -55,109 +55,110 @@ feature 'Shops', js: true do follow_active_table_node distributor.name expect(page).to have_current_path enterprise_shop_path(distributor) end + end - describe "filtering by product property" do - let!(:order_cycle) { create(:simple_order_cycle, distributors: [d1, d2], coordinator: create(:distributor_enterprise)) } - let!(:p1) { create(:simple_product, supplier: producer) } - let!(:p2) { create(:simple_product, supplier: create(:supplier_enterprise)) } - let(:ex_d1) { order_cycle.exchanges.outgoing.where(receiver_id: d1).first } - let(:ex_d2) { order_cycle.exchanges.outgoing.where(receiver_id: d2).first } + describe "filtering by product property" do + let!(:order_cycle) { create(:simple_order_cycle, distributors: [d1, d2], coordinator: create(:distributor_enterprise)) } + let!(:p1) { create(:simple_product, supplier: producer) } + let!(:p2) { create(:simple_product, supplier: create(:supplier_enterprise)) } + let(:ex_d1) { order_cycle.exchanges.outgoing.where(receiver_id: d1).first } + let(:ex_d2) { order_cycle.exchanges.outgoing.where(receiver_id: d2).first } - before do - p2.set_property 'Local', 'XYZ 123' + before do + ex_d1.variants << p1.variants.first + ex_d2.variants << p2.variants.first - ex_d1.variants << p1.variants.first - ex_d2.variants << p2.variants.first + p2.set_property 'Local', 'XYZ 123' + visit shops_path + end + + it "filters" do + toggle_filters + + toggle_filter 'Organic' + + expect(page).to have_content d1.name + expect(page).not_to have_content d2.name + + toggle_filter 'Organic' + toggle_filter 'Local' + + expect(page).not_to have_content d1.name + expect(page).to have_content d2.name + end + end + + describe "taxon badges" do + let!(:closed_oc) { create(:closed_order_cycle, distributors: [shop], variants: [p_closed.variants.first]) } + let!(:p_closed) { create(:simple_product, taxons: [taxon_closed]) } + let(:shop) { create(:distributor_enterprise) } + let(:taxon_closed) { create(:taxon, name: 'Closed') } + + describe "open shops" do + let!(:open_oc) { create(:open_order_cycle, distributors: [shop], variants: [p_open.variants.first]) } + let!(:p_open) { create(:simple_product, taxons: [taxon_open]) } + let(:taxon_open) { create(:taxon, name: 'Open') } + + it "shows taxons for open order cycles only" do visit shops_path - end - - it "filters" do - toggle_filters - - toggle_filter 'Organic' - - expect(page).to have_content d1.name - expect(page).not_to have_content d2.name - - toggle_filter 'Organic' - toggle_filter 'Local' - - expect(page).not_to have_content d1.name - expect(page).to have_content d2.name + expand_active_table_node shop.name + expect(page).to have_selector '.fat-taxons', text: 'Open' + expect(page).not_to have_selector '.fat-taxons', text: 'Closed' end end - describe "taxon badges" do - let!(:closed_oc) { create(:closed_order_cycle, distributors: [shop], variants: [p_closed.variants.first]) } - let!(:p_closed) { create(:simple_product, taxons: [taxon_closed]) } - let(:shop) { create(:distributor_enterprise) } - let(:taxon_closed) { create(:taxon, name: 'Closed') } - - describe "open shops" do - let!(:open_oc) { create(:open_order_cycle, distributors: [shop], variants: [p_open.variants.first]) } - let!(:p_open) { create(:simple_product, taxons: [taxon_open]) } - let(:taxon_open) { create(:taxon, name: 'Open') } - - it "shows taxons for open order cycles only" do - visit shops_path - expand_active_table_node shop.name - expect(page).to have_selector '.fat-taxons', text: 'Open' - expect(page).not_to have_selector '.fat-taxons', text: 'Closed' - end - end - - describe "closed shops" do - it "shows taxons for any order cycle" do - visit shops_path - click_link 'Show Closed Shops' - expand_active_table_node shop.name - expect(page).to have_selector '.fat-taxons', text: 'Closed' - end + describe "closed shops" do + it "shows taxons for any order cycle" do + visit shops_path + click_link 'Show Closed Shops' + expand_active_table_node shop.name + expect(page).to have_selector '.fat-taxons', text: 'Closed' end end + end - describe "property badges" do - let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) } - let(:product) { create(:simple_product, supplier: producer) } + describe "property badges" do + let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) } + let(:product) { create(:simple_product, supplier: producer) } - before do - product.set_property 'Local', 'XYZ 123' - end + before do + product.set_property 'Local', 'XYZ 123' + end - it "shows property badges" do - # Given a shop with a product with a property - # And the product's producer has a producer property + it "shows property badges" do + # Given a shop with a product with a property + # And the product's producer has a producer property - # When I go to the shops path - visit shops_path + # When I go to the shops path + visit shops_path - # And I open the shop - expand_active_table_node distributor.name + # And I open the shop + expand_active_table_node distributor.name - # Then I should see both properties - expect(page).to have_content 'Local' # Product property + # Then I should see both properties + expect(page).to have_content 'Local' # Product property + expect(page).to have_content 'Organic' # Producer property + end + end + + describe "hub producer modal" do + let!(:product) { create(:simple_product, supplier: producer, taxons: [taxon]) } + let!(:taxon) { create(:taxon, name: 'Fruit') } + let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) } + + it "shows hub producer modals" do + visit shops_path + expand_active_table_node distributor.name + expect(page).to have_content producer.name + open_enterprise_modal producer + modal_should_be_open_for producer + + within ".reveal-modal" do + expect(page).to have_content 'Fruit' # Taxon expect(page).to have_content 'Organic' # Producer property end end - - describe "hub producer modal" do - let!(:product) { create(:simple_product, supplier: producer, taxons: [taxon]) } - let!(:taxon) { create(:taxon, name: 'Fruit') } - let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product.variants.first]) } - - it "shows hub producer modals" do - expand_active_table_node distributor.name - expect(page).to have_content producer.name - open_enterprise_modal producer - modal_should_be_open_for producer - - within ".reveal-modal" do - expect(page).to have_content 'Fruit' # Taxon - expect(page).to have_content 'Organic' # Producer property - end - end - end end describe "viewing closed shops by URL" do From da9a3ce9f33d578ed32834c918f1665e4fcea9a4 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 25 Nov 2016 10:41:04 +1100 Subject: [PATCH 065/109] Refresh products cache when product is deleted (cf. destroyed) --- app/models/spree/product_decorator.rb | 6 ++++-- lib/open_food_network/products_cache.rb | 9 +++++++++ .../open_food_network/products_cache_spec.rb | 20 +++++++++++++++++++ spec/models/spree/product_spec.rb | 5 +++++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 835a875dbf..d7f5651a3c 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -187,9 +187,11 @@ Spree::Product.class_eval do def delete_with_delete_from_order_cycles transaction do - delete_without_delete_from_order_cycles + OpenFoodNetwork::ProductsCache.product_deleted(self) do + ExchangeVariant.where('exchange_variants.variant_id IN (?)', self.variants_including_master_and_deleted).destroy_all - ExchangeVariant.where('exchange_variants.variant_id IN (?)', self.variants_including_master_and_deleted).destroy_all + delete_without_delete_from_order_cycles + end end end alias_method_chain :delete, :delete_from_order_cycles diff --git a/lib/open_food_network/products_cache.rb b/lib/open_food_network/products_cache.rb index f6ef15829f..fc3caec111 100644 --- a/lib/open_food_network/products_cache.rb +++ b/lib/open_food_network/products_cache.rb @@ -28,6 +28,15 @@ module OpenFoodNetwork end end + def self.product_deleted(product, &block) + exchanges = exchanges_featuring_variants(product.reload.variants).to_a + + block.call + + exchanges.each do |exchange| + refresh_cache exchange.receiver, exchange.order_cycle + end + end def self.variant_override_changed(variant_override) exchanges_featuring_variants(variant_override.variant, distributor: variant_override.hub).each do |exchange| diff --git a/spec/lib/open_food_network/products_cache_spec.rb b/spec/lib/open_food_network/products_cache_spec.rb index a395873f45..d48f131087 100644 --- a/spec/lib/open_food_network/products_cache_spec.rb +++ b/spec/lib/open_food_network/products_cache_spec.rb @@ -89,6 +89,26 @@ module OpenFoodNetwork end end + describe "when a product is deleted" do + let(:product) { create(:simple_product) } + let(:variant) { create(:variant, product: product) } + let(:distributor) { create(:distributor_enterprise) } + let!(:oc) { create(:open_order_cycle, distributors: [distributor], variants: [variant]) } + + it "refreshes the cache based on exchanges the variant was in before destruction" do + expect(ProductsCache).to receive(:refresh_cache).with(distributor, oc) + product.delete + end + + it "performs the cache refresh after the product has been removed from the order cycle" do + expect(ProductsCache).to receive(:refresh_cache).with(distributor, oc) do + expect(product.reload.deleted_at).not_to be_nil + end + + product.delete + end + end + describe "when a variant override changes" do let(:variant) { create(:variant) } let(:d1) { create(:distributor_enterprise) } diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 2e188244a9..4d126bd80f 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -170,6 +170,11 @@ module Spree product.save end + it "refreshes the products cache on delete" do + expect(OpenFoodNetwork::ProductsCache).to receive(:product_deleted).with(product) + product.delete + end + # On destroy, all distributed variants are refreshed by a Variant around_destroy # callback, so we don't need to do anything on the product model. end From 57363e2da55878add81c794a510ff7e72e7541d0 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 25 Nov 2016 10:53:08 +1100 Subject: [PATCH 066/109] When a product is deleted, touch the supplier and distributors --- app/models/spree/product_decorator.rb | 4 ++++ spec/models/spree/product_spec.rb | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index d7f5651a3c..40c6a6910b 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -188,6 +188,10 @@ Spree::Product.class_eval do def delete_with_delete_from_order_cycles transaction do OpenFoodNetwork::ProductsCache.product_deleted(self) do + # Touch supplier and distributors as we would on #destroy + self.supplier.touch + touch_distributors + ExchangeVariant.where('exchange_variants.variant_id IN (?)', self.variants_including_master_and_deleted).destroy_all delete_without_delete_from_order_cycles diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 4d126bd80f..568d701ec9 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -177,6 +177,21 @@ module Spree # On destroy, all distributed variants are refreshed by a Variant around_destroy # callback, so we don't need to do anything on the product model. + + describe "touching affected enterprises when the product is deleted" do + let(:product) { create(:simple_product) } + let(:supplier) { product.supplier } + let(:distributor) { create(:distributor_enterprise) } + let!(:oc) { create(:simple_order_cycle, distributors: [distributor], variants: [product.variants.first]) } + + it "touches the supplier" do + expect { product.delete }.to change { supplier.reload.updated_at } + end + + it "touches all distributors" do + expect { product.delete }.to change { distributor.reload.updated_at } + end + end end describe "scopes" do From 6795237a2d61390bc2f0d82f4252e8eddbe66dc2 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 25 Nov 2016 11:18:39 +1100 Subject: [PATCH 067/109] Put timecop into safe mode and fix leaky Timecop.freeze --- Gemfile.lock | 2 +- config/initializers/timecop.rb | 1 + spec/models/spree/product_spec.rb | 7 ++++--- 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 config/initializers/timecop.rb diff --git a/Gemfile.lock b/Gemfile.lock index d346f7aa03..0dc7a067ee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -594,7 +594,7 @@ GEM ref thor (0.19.1) tilt (1.4.1) - timecop (0.6.2.2) + timecop (0.8.1) timers (1.1.0) treetop (1.4.15) polyglot diff --git a/config/initializers/timecop.rb b/config/initializers/timecop.rb new file mode 100644 index 0000000000..f14c7d622d --- /dev/null +++ b/config/initializers/timecop.rb @@ -0,0 +1 @@ +Timecop.safe_mode = true diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 568d701ec9..4f63fe882b 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -33,9 +33,10 @@ module Spree end it "defaults available_on to now" do - Timecop.freeze - product = Product.new - product.available_on.should == Time.zone.now + Timecop.freeze do + product = Product.new + product.available_on.should == Time.zone.now + end end describe "tax category" do From 071ba5285d31d0e5167d81ad4c0801eb797e58c1 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 25 Nov 2016 14:28:05 +1100 Subject: [PATCH 068/109] Put timecop config in spec support --- {config/initializers => spec/support}/timecop.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {config/initializers => spec/support}/timecop.rb (100%) diff --git a/config/initializers/timecop.rb b/spec/support/timecop.rb similarity index 100% rename from config/initializers/timecop.rb rename to spec/support/timecop.rb From d4fd66461e65edcd92d5cecdd30147f85c5bd437 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 25 Nov 2016 14:37:47 +1100 Subject: [PATCH 069/109] Add retry to flaky specs --- spec/features/consumer/shopping/checkout_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb index 7bd540169f..16bdb27ec1 100644 --- a/spec/features/consumer/shopping/checkout_spec.rb +++ b/spec/features/consumer/shopping/checkout_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' -feature "As a consumer I want to check out my cart", js: true do +feature "As a consumer I want to check out my cart", js: true, retry: 3 do include AuthenticationWorkflow include ShopWorkflow include CheckoutWorkflow From ee905cad5fcb66e1e06562c859f031765e619e33 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Tue, 29 Nov 2016 15:07:35 +0000 Subject: [PATCH 070/109] Translation updates --- config/locales/en-GB.yml | 63 ++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index f76c59352d..df0a3a1c1e 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -6,7 +6,7 @@ en-GB: shipment_state: Shipment State devise: failure: - invalid: + invalid: | Invalid email or password. Were you a guest last time? Perhaps you need to create an account or reset your password. enterprise_confirmations: @@ -23,7 +23,7 @@ en-GB: title: Open Food Network welcome_to: 'Welcome to ' site_meta_description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can seriously change the world." - search_by_name: Search by name... + search_by_name: Search by name, town, county or postcode... producers: 'UK Producers' producers_join: UK producers are now welcome to join Open Food Network UK. charges_sales_tax: Charges VAT? @@ -90,6 +90,18 @@ en-GB: add_a_new_customer_for: Add a new customer for %{shop_name} code: Code duplicate_code: "This code is used already." + bill_address: "Billing Address" + ship_address: "Shipping Address" + update_address_success: 'Address updated successfully.' + update_address_error: 'Sorry! Please input all of the required fields!' + edit_bill_address: 'Edit Billing Address' + edit_ship_address: 'Edit Shipping Address' + required_fileds: 'Required fields are denoted with an asterisk' + select_country: 'Select Country' + select_state: 'Select County' + edit: 'Edit' + update_address: 'Update Address' + confirm_delete: 'Sure to delete?' products: bulk_edit: unit: Unit @@ -152,11 +164,15 @@ en-GB: status: Status manage: Manage form: - primary_details: - shopfront_requires_login: "Shopfront requires login?" - shopfront_requires_login_tip: "Choose whether customers must login to view the shopfront." + shop_preferences: + shopfront_requires_login: "Publicly visible shopfront?" + shopfront_requires_login_tip: "Choose whether customers must login to view the shopfront or if it's visible to everybody." shopfront_requires_login_false: "Public" - shopfront_requires_login_true: "Require customers to login" + shopfront_requires_login_true: "Visible to registered customers only" + allow_guest_orders: "Guest orders" + allow_guest_orders_tip: "Allow checkout as guest or require a registered user." + allow_guest_orders_false: "Require login to order" + allow_guest_orders_true: "Allow guest checkout" home: hubs: show_closed_shops: "Show closed shops" @@ -308,7 +324,9 @@ en-GB: checkout_as_guest: "Checkout as guest" checkout_details: "Your details" checkout_billing: "Billing info" + checkout_default_bill_address: "Save as default billing address" checkout_shipping: Shipping info + checkout_default_ship_address: "Save as default shipping address" checkout_method_free: Free checkout_address_same: Shipping address same as billing address? checkout_ready_for: "Ready for:" @@ -347,6 +365,7 @@ en-GB: email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here: %{link}" email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration." email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. %{link}" + join_community: "Join the community" email_help: "If you have any difficulties, check out our FAQs, browse the forum or post a 'Support' topic and someone will help you out!" email_confirmation_greeting: "Hi, %{contact}!" email_confirmation_profile_created: "A profile for %{name} has been successfully created! To activate your Profile we need to confirm this email address." @@ -388,7 +407,7 @@ en-GB: email_signup_email: Your login email is email_signup_shop_html: "You can start shopping online now at %{link}." email_signup_text: "Thanks for joining the network. If you are a customer, we look forward to introducing you to many fantastic farmers, wonderful food hubs and delicious food! If you are a producer or food enterprise, we are excited to have you as a part of the network." - email_signup_help_html: "We welcome all your questions and feedback; you can use the Send Feedback button on the site or email us at" + email_signup_help_html: "We welcome all your questions and feedback; you can use the Send Feedback button on the site or email us at %{email}" producer_mail_greeting: "Dear" producer_mail_text_before: "We now have all the consumer orders for the next food delivery." producer_mail_order_text: "Here is a summary of the orders for your products:" @@ -529,8 +548,8 @@ en-GB: sell_hubs_detail: "Set up a profile for your food enterprise or organisation on the OFN. At any time you can upgrade your profile to a multi-producer shop." sell_groups_detail: "Set up a tailored directory of enterprises (producers and other food enterprises) for your region or for your organisation." sell_user_guide: "Find out more in our user guide." - sell_listing_price: "Listing on OFN is free. Opening and running a shop on OFN is free for six months and always free for small enterprises. We charge just 2% of sales for enterprises turning over more than £5000/year." - sell_embed: "We can also embed an OFN shop in your own customised website or build a customised local food network website for your region." + sell_listing_price: "Selling through OFN UK is free!\n\nOFN UK is a Platform Cooperative - part of the Solidarity or Sharing Economy. Our goal is to support the distribution of as much local food as possible. As such OFN UK asks users to 'Pay What You Feel' toward the running costs and maintenance. We think 2% of sales is fair for all the services that OFN UK provide. But more importantly, we think that getting good local food to people at a fair price is more important than paying for software. So pay what you feel our services are worth to your enterprise." + sell_embed: "We collectively budget for new feature development from the international OFN community. This way the huge cost of good software development can be shared. If you want a new feature, chances are someone in France, South Africa, Australia, India or Brazil might want it too! Use the Community Forum to suggest features you'd like to see." sell_ask_services: "Ask us about OFN services." shops_title: Shops shops_headline: Shopping, transformed. @@ -706,30 +725,30 @@ en-GB: registration_type_producer: "Yes, I'm a producer" registration_type_no_producer: "No, I'm not a producer" registration_type_error: "Please choose one. Are you are producer?" - registration_type_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it." + registration_type_producer_help: "Producers make things to eat and/or drink. You're a producer if you might grow it, raise it, brew it, bake it or ferment it." registration_type_no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." create_profile: "Create profile" registration_images_headline: "Thanks!" - registration_images_description: "Let's upload some pretty pictures so your profile looks great! :)" + registration_images_description: "Let's upload some pictures so your profile looks great! :)" registration_detail_headline: "Let's get started" - registration_detail_enterprise: "Woot! First we need to know a little bit about your enterprise:" - registration_detail_producer: "Woot! First we need to know a little bit about your farm:" + registration_detail_enterprise: "First we need to know a little bit about your enterprise:" + registration_detail_producer: "First we need to know a little bit about your farm:" registration_detail_name_enterprise: "Enterprise name:" registration_detail_name_producer: "Farm name:" - registration_detail_name_placeholder: "e.g. Charlie's Awesome Farm" + registration_detail_name_placeholder: "e.g. Charlie's Farm" registration_detail_name_error: "Please choose a unique name for your enterprise" registration_detail_address1: "Address line 1:" - registration_detail_address1_placeholder: "e.g. 123 Cranberry Drive" + registration_detail_address1_placeholder: "e.g. 123 Apple Drive" registration_detail_address1_error: "Please enter an address" registration_detail_address2: "Address line 2:" - registration_detail_suburb: "Suburb:" - registration_detail_suburb_placeholder: "e.g. Northcote" - registration_detail_suburb_error: "Please enter a suburb" + registration_detail_suburb: "Town:" + registration_detail_suburb_placeholder: "e.g. Taunton" + registration_detail_suburb_error: "Please enter a town" registration_detail_postcode: "Postcode:" - registration_detail_postcode_placeholder: "e.g. 3070" + registration_detail_postcode_placeholder: "e.g. TA1 1AA" registration_detail_postcode_error: "Postcode required" - registration_detail_state: "State:" - registration_detail_state_error: "State required" + registration_detail_state: "County:" + registration_detail_state_error: "County required" registration_detail_country: "Country:" registration_detail_country_error: "Please select a country" fees: "Fees" @@ -797,6 +816,7 @@ en-GB: tax_category: "Tax Category" calculator: "Calculator" calculator_values: "Calculator values" + flat_percent_per_item: "Flat Percent (per item)" new_order_cycles: "New Order Cycles" select_a_coordinator_for_your_order_cycle: "Select a coordinator for your order cycle" edit_order_cycle: "Edit Order Cycle" @@ -947,6 +967,7 @@ en-GB: validation_msg_tax_category_cant_be_blank: "^Tax Category can't be blank" validation_msg_is_associated_with_an_exising_customer: "is associated with an existing customer" spree: + zipcode: Postcode shipment_states: backorder: backorder partial: partial From d0509b54bfb4e2802659b8c17de14fce7cd74f32 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Wed, 30 Nov 2016 18:30:20 +1100 Subject: [PATCH 071/109] Disabling override the adds coupon field to cart page --- .../orders/edit/promo_cart_coupon_code_field.html.haml.deface | 1 + 1 file changed, 1 insertion(+) create mode 100644 app/overrides/spree/orders/edit/promo_cart_coupon_code_field.html.haml.deface diff --git a/app/overrides/spree/orders/edit/promo_cart_coupon_code_field.html.haml.deface b/app/overrides/spree/orders/edit/promo_cart_coupon_code_field.html.haml.deface new file mode 100644 index 0000000000..dca5725b49 --- /dev/null +++ b/app/overrides/spree/orders/edit/promo_cart_coupon_code_field.html.haml.deface @@ -0,0 +1 @@ +/ disabled From 6cd8289b2709c575ac669b3e780c647cab5a7e39 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 2 Dec 2016 16:50:54 +1100 Subject: [PATCH 072/109] Correct more typos --- config/locales/en-GB.yml | 4 ++-- config/locales/en-US.yml | 4 ++-- config/locales/en.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index f76c59352d..541070f2fc 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -11,7 +11,7 @@ en-GB: Were you a guest last time? Perhaps you need to create an account or reset your password. enterprise_confirmations: enterprise: - confirmed: Thankyou, your email address has been confirmed. + confirmed: Thank you, your email address has been confirmed. not_confirmed: Your email address could not be confirmed. Perhaps you have already completed this step? confirmation_sent: "Confirmation email sent!" confirmation_not_sent: "Could not send a confirmation email." @@ -855,7 +855,7 @@ en-GB: spree_admin_single_enterprise_alert_mail_confirmation: "Please confirm the email address for" spree_admin_single_enterprise_alert_mail_sent: "We've sent an email to" spree_admin_overview_action_required: "Action Required" - spree_admin_overview_check_your_inbox: "Please check you inbox for further instructions. Thanks!" + spree_admin_overview_check_your_inbox: "Please check your inbox for further instructions. Thanks!" change_package: "Change Package" spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" your_profil_live: "Your profile live" diff --git a/config/locales/en-US.yml b/config/locales/en-US.yml index 9281c7a76b..f7e4b29abf 100644 --- a/config/locales/en-US.yml +++ b/config/locales/en-US.yml @@ -29,7 +29,7 @@ en-US: Were you a guest last time? Perhaps you need to create an account or reset your password. enterprise_confirmations: enterprise: - confirmed: Thankyou, your email address has been confirmed. + confirmed: Thank you, your email address has been confirmed. not_confirmed: Your email address could not be confirmed. Perhaps you have already completed this step? confirmation_sent: "Confirmation email sent!" confirmation_not_sent: "Could not send a confirmation email." @@ -911,7 +911,7 @@ Please follow the instructions there to make your enterprise visible on the Open spree_admin_single_enterprise_alert_mail_confirmation: "Please confirm the email address for" spree_admin_single_enterprise_alert_mail_sent: "We've sent an email to" spree_admin_overview_action_required: "Action Required" - spree_admin_overview_check_your_inbox: "Please check you inbox for further instructions. Thanks!" + spree_admin_overview_check_your_inbox: "Please check your inbox for further instructions. Thanks!" change_package: "Change Package" spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" your_profil_live: "Your profile live" diff --git a/config/locales/en.yml b/config/locales/en.yml index 4bda952631..ef8245bfd1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -28,7 +28,7 @@ en: Were you a guest last time? Perhaps you need to create an account or reset your password. enterprise_confirmations: enterprise: - confirmed: Thankyou, your email address has been confirmed. + confirmed: Thank you, your email address has been confirmed. not_confirmed: Your email address could not be confirmed. Perhaps you have already completed this step? confirmation_sent: "Confirmation email sent!" confirmation_not_sent: "Could not send a confirmation email." @@ -979,7 +979,7 @@ Please follow the instructions there to make your enterprise visible on the Open spree_admin_single_enterprise_alert_mail_confirmation: "Please confirm the email address for" spree_admin_single_enterprise_alert_mail_sent: "We've sent an email to" spree_admin_overview_action_required: "Action Required" - spree_admin_overview_check_your_inbox: "Please check you inbox for further instructions. Thanks!" + spree_admin_overview_check_your_inbox: "Please check your inbox for further instructions. Thanks!" change_package: "Change Package" spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" your_profil_live: "Your profile live" From 3a69c958ef3bf83b79cded98f659ce05f8a90a38 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 30 Nov 2016 16:49:06 +1100 Subject: [PATCH 073/109] Hide super admin menu items for enterprise users Imported temporarily missing Spree functionality. This patch becomes obsolete with another Spree upgrade. --- .../admin/navigation_helper_decorator.rb | 20 +++++++++++++++++++ spec/features/admin/authentication_spec.rb | 1 + 2 files changed, 21 insertions(+) diff --git a/app/helpers/spree/admin/navigation_helper_decorator.rb b/app/helpers/spree/admin/navigation_helper_decorator.rb index d7d0bd2370..5d0716b9f8 100644 --- a/app/helpers/spree/admin/navigation_helper_decorator.rb +++ b/app/helpers/spree/admin/navigation_helper_decorator.rb @@ -1,6 +1,15 @@ module Spree module Admin module NavigationHelper + # TEMP: import method until it is re-introduced into Spree. + def klass_for(name) + model_name = name.to_s + + ["Spree::#{model_name.classify}", model_name.classify, model_name.gsub('_', '/').classify].find do |t| + t.safe_constantize + end.try(:safe_constantize) + end + # Make it so that the Reports admin tab can be enabled/disabled through the cancan # :report resource, since it does not have a corresponding resource class (unlike # eg. Spree::Product). @@ -12,6 +21,17 @@ module Spree # klass # end # alias_method_chain :klass_for, :sym_fallback + + # TEMP: override method until it is fixed in Spree. + def tab_with_cancan_check(*args) + options = {:label => args.first.to_s} + if args.last.is_a?(Hash) + options = options.merge(args.last) + end + return '' if klass = klass_for(options[:label]) and cannot?(:admin, klass) + tab_without_cancan_check(*args) + end + alias_method_chain :tab, :cancan_check end end end diff --git a/spec/features/admin/authentication_spec.rb b/spec/features/admin/authentication_spec.rb index fe2eb156c5..5cdbbbd6de 100644 --- a/spec/features/admin/authentication_spec.rb +++ b/spec/features/admin/authentication_spec.rb @@ -18,6 +18,7 @@ feature "Authentication", js: true do click_login_button expect(page).to have_content "DASHBOARD" expect(page).to have_current_path spree.admin_path + expect(page).to have_no_content "CONFIGURATION" end end From a2a6ce1b3e5bb49fc5bb31b4160742bbcb17f08f Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 30 Nov 2016 16:53:35 +1100 Subject: [PATCH 074/109] Revert "TEMP: Remove override for no-longer-present method" This reverts commit a3b91dabe5fbd2e810ce7f6330c88a057d6eb753. Conflicts: app/helpers/spree/admin/navigation_helper_decorator.rb --- .../admin/navigation_helper_decorator.rb | 16 +++++------ spec/helpers/navigation_helper_spec.rb | 28 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app/helpers/spree/admin/navigation_helper_decorator.rb b/app/helpers/spree/admin/navigation_helper_decorator.rb index 5d0716b9f8..57154b1479 100644 --- a/app/helpers/spree/admin/navigation_helper_decorator.rb +++ b/app/helpers/spree/admin/navigation_helper_decorator.rb @@ -13,14 +13,14 @@ module Spree # Make it so that the Reports admin tab can be enabled/disabled through the cancan # :report resource, since it does not have a corresponding resource class (unlike # eg. Spree::Product). - # def klass_for_with_sym_fallback(name) - # klass = klass_for_without_sym_fallback(name) - # klass ||= name.singularize.to_sym - # klass = :overview if klass == :dashboard - # klass = Spree::Order if klass == :bulk_order_management - # klass - # end - # alias_method_chain :klass_for, :sym_fallback + def klass_for_with_sym_fallback(name) + klass = klass_for_without_sym_fallback(name) + klass ||= name.singularize.to_sym + klass = :overview if klass == :dashboard + klass = Spree::Order if klass == :bulk_order_management + klass + end + alias_method_chain :klass_for, :sym_fallback # TEMP: override method until it is fixed in Spree. def tab_with_cancan_check(*args) diff --git a/spec/helpers/navigation_helper_spec.rb b/spec/helpers/navigation_helper_spec.rb index e90d46cd57..76fe9573f2 100644 --- a/spec/helpers/navigation_helper_spec.rb +++ b/spec/helpers/navigation_helper_spec.rb @@ -3,23 +3,23 @@ require 'spec_helper' module Spree module Admin describe NavigationHelper do - # describe "klass_for" do - # it "returns the class when present" do - # helper.klass_for('products').should == Spree::Product - # end + describe "klass_for" do + it "returns the class when present" do + helper.klass_for('products').should == Spree::Product + end - # it "returns a symbol when there's no available class" do - # helper.klass_for('reports').should == :report - # end + it "returns a symbol when there's no available class" do + helper.klass_for('reports').should == :report + end - # it "returns :overview for the dashboard" do - # helper.klass_for('dashboard').should == :overview - # end + it "returns :overview for the dashboard" do + helper.klass_for('dashboard').should == :overview + end - # it "returns Spree::Order for bulk_order_management" do - # helper.klass_for('bulk_order_management').should == Spree::Order - # end - # end + it "returns Spree::Order for bulk_order_management" do + helper.klass_for('bulk_order_management').should == Spree::Order + end + end end end end From 6e6efea328383010be1524c4d760dbc3c4e8669f Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 8 Dec 2016 10:27:41 +1100 Subject: [PATCH 075/109] Fix groups link not appearing on menu --- .../spree/admin/navigation_helper_decorator.rb | 1 + spec/features/admin/enterprise_groups_spec.rb | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/app/helpers/spree/admin/navigation_helper_decorator.rb b/app/helpers/spree/admin/navigation_helper_decorator.rb index 57154b1479..460f10ffcf 100644 --- a/app/helpers/spree/admin/navigation_helper_decorator.rb +++ b/app/helpers/spree/admin/navigation_helper_decorator.rb @@ -18,6 +18,7 @@ module Spree klass ||= name.singularize.to_sym klass = :overview if klass == :dashboard klass = Spree::Order if klass == :bulk_order_management + klass = EnterpriseGroup if klass == :group klass end alias_method_chain :klass_for, :sym_fallback diff --git a/spec/features/admin/enterprise_groups_spec.rb b/spec/features/admin/enterprise_groups_spec.rb index 2adb96c0a5..d973dfd557 100644 --- a/spec/features/admin/enterprise_groups_spec.rb +++ b/spec/features/admin/enterprise_groups_spec.rb @@ -105,6 +105,17 @@ feature %q{ end context "as an enterprise user" do + let(:user) { create_enterprise_user } + let!(:enterprise) { create(:distributor_enterprise, owner: user) } + let!(:group) { create(:enterprise_group, name: 'My Group', owner: user) } + + it "lets me access enterprise groups" do + quick_login_as user + visit spree.admin_path + click_link 'Groups' + expect(page).to have_content 'My Group' + end + xit "should show me only enterprises I manage when creating a new enterprise group" end end From 43726a0b237226d6f557a1d5b71db31e59f0d7f0 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 8 Dec 2016 10:38:01 +1100 Subject: [PATCH 076/109] Fix inventory link not appearing on menu --- app/helpers/spree/admin/navigation_helper_decorator.rb | 1 + spec/features/admin/variant_overrides_spec.rb | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/helpers/spree/admin/navigation_helper_decorator.rb b/app/helpers/spree/admin/navigation_helper_decorator.rb index 460f10ffcf..8398df7de3 100644 --- a/app/helpers/spree/admin/navigation_helper_decorator.rb +++ b/app/helpers/spree/admin/navigation_helper_decorator.rb @@ -19,6 +19,7 @@ module Spree klass = :overview if klass == :dashboard klass = Spree::Order if klass == :bulk_order_management klass = EnterpriseGroup if klass == :group + klass = VariantOverride if klass == :Inventory klass end alias_method_chain :klass_for, :sym_fallback diff --git a/spec/features/admin/variant_overrides_spec.rb b/spec/features/admin/variant_overrides_spec.rb index 1cb559a653..d21c0998e5 100644 --- a/spec/features/admin/variant_overrides_spec.rb +++ b/spec/features/admin/variant_overrides_spec.rb @@ -29,7 +29,9 @@ feature %q{ permissions_list: [:add_to_order_cycle]) } # This er should not confer ability to create VOs for hub2 it "displays a list of hub choices (ie. only those managed by the user)" do - visit '/admin/inventory' + visit spree.admin_path + click_link 'Products' + click_link 'Inventory' page.should have_select2 'hub_id', options: [hub.name] # Selects the hub automatically when only one is available end From bbcaef20a8440807e9ec2c0f0c6f66833bdcb785 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 9 Dec 2016 11:24:58 +1100 Subject: [PATCH 077/109] Add unit specs for Spree::Admin::NavigationHelper --- spec/helpers/navigation_helper_spec.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/helpers/navigation_helper_spec.rb b/spec/helpers/navigation_helper_spec.rb index 76fe9573f2..360dd57495 100644 --- a/spec/helpers/navigation_helper_spec.rb +++ b/spec/helpers/navigation_helper_spec.rb @@ -19,6 +19,14 @@ module Spree it "returns Spree::Order for bulk_order_management" do helper.klass_for('bulk_order_management').should == Spree::Order end + + it "returns EnterpriseGroup for group" do + helper.klass_for('group').should == EnterpriseGroup + end + + it "returns VariantOverride for Inventory" do + helper.klass_for('Inventory').should == VariantOverride + end end end end From cad3464f561235fedf5e97a4d05adf855c6c0d96 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Fri, 9 Dec 2016 11:40:55 +0000 Subject: [PATCH 078/109] Adjusted BOM spec to reflect currency symbol changes --- app/views/spree/admin/orders/bulk_management.html.haml | 4 ++-- spec/features/admin/bulk_order_management_spec.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 70de7fdd89..5f6764e8d9 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -151,7 +151,7 @@ %th.final_weight_volume{ 'ng-show' => 'columns.final_weight_volume.visible' } = t("admin.orders.bulk_management.weight_volume") %th.price{ 'ng-show' => 'columns.price.visible' } - = t("admin.price") + = t("admin.price (#{currency_symbol})") %th.actions %th.actions = t("admin.orders.bulk_management.ask") @@ -178,7 +178,7 @@ %input.show-dirty{ :type => 'number', :name => 'final_weight_volume', :id => 'final_weight_volume', ng: { model: "line_item.final_weight_volume", readonly: "unitValueLessThanZero(line_item)", change: "weightAdjustedPrice(line_item)", required: "true", class: '{"update-error": line_item.errors.final_weight_volume}' }, min: 0, 'ng-pattern' => '/[1-9]+/' } %span.error{ ng: { bind: 'line_item.errors.final_weight_volume' } } %td.price{ 'ng-show' => 'columns.price.visible' } - %input.show-dirty{ :type => 'text', :name => 'price', :id => 'price', :ng => { value: 'line_item.price * line_item.quantity | currency', readonly: "true", class: '{"update-error": line_item.errors.price}' } } + %input.show-dirty{ :type => 'text', :name => 'price', :id => 'price', :ng => { value: 'line_item.price * line_item.quantity | currency:""', readonly: "true", class: '{"update-error": line_item.errors.price}' } } %span.error{ ng: { bind: 'line_item.errors.price' } } %td.actions %a{ href: "/admin/orders/{{line_item.order.number}}/edit", :class => "edit-order icon-edit no-text", 'confirm-link-click' => 'confirmRefresh()' } diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index ef4fc13e39..74c10c7b72 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -162,9 +162,9 @@ feature %q{ # hide dropdown find("div#columns-dropdown", :text => "COLUMNS").click within "tr#li_#{li1.id}" do - expect(page).to have_field "price", with: "$50.00" + expect(page).to have_field "price", with: "50.00" fill_in "final_weight_volume", :with => 2000 - expect(page).to have_field "price", with: "$100.00" + expect(page).to have_field "price", with: "100.00" end click_button "Save Changes" expect(page).to have_no_selector "#save-bar" @@ -181,9 +181,9 @@ feature %q{ find("div#columns-dropdown div.menu div.menu_item", text: "Price").click find("div#columns-dropdown", :text => "COLUMNS").click within "tr#li_#{li1.id}" do - expect(page).to have_field "price", with: "$#{format("%.2f",li1.price * 5)}" + expect(page).to have_field "price", with: "#{format("%.2f",li1.price * 5)}" fill_in "quantity", :with => 6 - expect(page).to have_field "price", with: "$#{format("%.2f",li1.price * 6)}" + expect(page).to have_field "price", with: "#{format("%.2f",li1.price * 6)}" end end end From 066f42070a2163d4431607d65ad951523dbb89e4 Mon Sep 17 00:00:00 2001 From: Matt-Yorkley Date: Fri, 9 Dec 2016 14:12:00 +0000 Subject: [PATCH 079/109] Fixed confusing wysiwyg line breaks display discrepency --- app/assets/stylesheets/admin/openfoodnetwork.css.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index 1532ea8520..f8035304f5 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -203,6 +203,9 @@ text-angular { .ta-scroll-window > .ta-bind { max-height: 400px; min-height: 100px; + p { + margin-bottom: 1.5rem; + } } .ta-scroll-window.form-control { min-height: 100px; From 7d7197da58faf0b7b385474fe4b531482050e20f Mon Sep 17 00:00:00 2001 From: Matt-Yorkley Date: Fri, 9 Dec 2016 14:31:34 +0000 Subject: [PATCH 080/109] Reordered enterprises submenu --- .../enterprises/controllers/side_menu_controller.js.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/javascripts/admin/enterprises/controllers/side_menu_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/side_menu_controller.js.coffee index 913ff59d3e..fa346e100f 100644 --- a/app/assets/javascripts/admin/enterprises/controllers/side_menu_controller.js.coffee +++ b/app/assets/javascripts/admin/enterprises/controllers/side_menu_controller.js.coffee @@ -6,7 +6,6 @@ angular.module("admin.enterprises") $scope.menu.setItems [ { name: 'primary_details', label: t('primary_details'), icon_class: "icon-home" } - { name: 'users', label: t('users'), icon_class: "icon-user" } { name: 'address', label: t('address'), icon_class: "icon-map-marker" } { name: 'contact', label: t('contact'), icon_class: "icon-phone" } { name: 'social', label: t('social'), icon_class: "icon-twitter" } @@ -20,11 +19,11 @@ angular.module("admin.enterprises") { name: 'inventory_settings', label: t('inventory_settings'), icon_class: "icon-list-ol", show: "enterpriseIsShop()" } { name: 'tag_rules', label: t('tag_rules'), icon_class: "icon-random", show: "enterpriseIsShop()" } { name: 'shop_preferences', label: t('shop_preferences'), icon_class: "icon-shopping-cart", show: "enterpriseIsShop()" } + { name: 'users', label: t('users'), icon_class: "icon-user" } ] $scope.select(0) - $scope.showItem = (item) -> if item.show? $parse(item.show)($scope) From 45dc1341d3e34fa9c2e2b72c0f2e88e1e36bb1e3 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Fri, 9 Dec 2016 14:41:16 +0000 Subject: [PATCH 081/109] translation updates --- config/locales/en-GB.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index df0a3a1c1e..c71ddff3cd 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -43,7 +43,7 @@ en-GB: monthly_cap_excl_tax: "monthly cap (excl. VAT)" capped_at_cap: "capped at %{cap}" per_month: "per month" - free: "free" + free: "pay what you can" free_trial: "free trial" plus_tax: "plus VAT" total_monthly_bill_incl_tax: "Total Monthly Bill (Incl. VAT)" From 0a67876815dc61bdf00d833279d5d1e875c66a1d Mon Sep 17 00:00:00 2001 From: Matt-Yorkley Date: Fri, 9 Dec 2016 18:47:16 +0000 Subject: [PATCH 082/109] Update angular-file-uploader-rails to v1.1.6 --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 6e48dd97d5..f8407decdc 100644 --- a/Gemfile +++ b/Gemfile @@ -50,7 +50,7 @@ gem 'gmaps4rails' gem 'spinjs-rails' gem 'rack-ssl', :require => 'rack/ssl' gem 'custom_error_message', :github => 'jeremydurham/custom-err-msg' -gem 'angularjs-file-upload-rails', '~> 1.1.0' +gem 'angularjs-file-upload-rails', '~> 1.1.6' gem 'roadie-rails', '~> 1.0.3' gem 'figaro' gem 'blockenspiel' diff --git a/Gemfile.lock b/Gemfile.lock index 0dc7a067ee..824f8a0c04 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -154,7 +154,7 @@ GEM railties (>= 3.1) sprockets (~> 2) tilt - angularjs-file-upload-rails (1.1.0) + angularjs-file-upload-rails (1.1.6) angularjs-rails (1.5.5) ansi (1.4.2) arel (3.0.3) @@ -649,7 +649,7 @@ DEPENDENCIES acts-as-taggable-on (~> 3.4) andand angular-rails-templates (~> 0.2.0) - angularjs-file-upload-rails (~> 1.1.0) + angularjs-file-upload-rails (~> 1.1.6) angularjs-rails (= 1.5.5) atomic awesome_print @@ -737,4 +737,4 @@ RUBY VERSION ruby 2.1.5p273 BUNDLED WITH - 1.12.5 + 1.13.6 From eef308c4f6cb322a616db477012ae27812ae1aa7 Mon Sep 17 00:00:00 2001 From: Rafael Braz Date: Wed, 14 Dec 2016 19:39:37 -0200 Subject: [PATCH 083/109] create a pr-br translate file (#1266) --- config/locales/pt-BR.yml | 987 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 987 insertions(+) create mode 100644 config/locales/pt-BR.yml diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml new file mode 100644 index 0000000000..80d83fdb3a --- /dev/null +++ b/config/locales/pt-BR.yml @@ -0,0 +1,987 @@ +pt-BR: + activerecord: + attributes: + spree/order: + payment_state: Situação do pagamento + shipment_state: Situação do Envio + devise: + failure: + invalid: | + Email ou senha incorretos. + Você entrou como visitante da última vez? Talvez você precise criar uma conta ou criar uma nova senha. + enterprise_confirmations: + enterprise: + confirmed: Obrigado, seu endereço de email foi confirmado. + not_confirmed: Não foi possível confirmar seu endereço de email. Talvez você já tenha completado esse passo? + confirmation_sent: "Email de confirmação enviado!" + confirmation_not_sent: "Não foi possível enviar a confirmação de email." + enterprise_mailer: + confirmation_instructions: + subject: "Favor confirmar o endereço de email para %{enterprise}" + welcome: + subject: "%{enterprise} está agora em %{sitename}" + title: Open Food Network + welcome_to: 'Bem vindo a' + site_meta_description: "A gente começa por quem produz os alimentos, contando suas histórias e de seus cultivos, passando por quem distribui de modo justo e sem desperdício, até chegar em quem acredita que suas decisões de consumo podem..." + search_by_name: Procurar por nome ou localidade + producers: 'Produtores Nacionais' + producers_join: Produtores nacionais estão convidados a se unirem à Open Food Network. + print_invoice: "Imprimir fatura" + send_invoice: "Enviar fatura" + resend_confirmation: "Enviar confirmação novamente" + view_order: "Ver pedido" + edit_order: "Editar pedido" + ship_order: "Enviar pedido" + cancel_order: "Cancelar pedido" + confirm_send_invoice: "Uma fatura desse pedido será enviada ao cliente. Tem certeza que deseja continuar?" + confirm_resend_order_confirmation: "Tem certeza que deseja enviar novamente o pedido de confirmação do email?" + invoice: "Fatura" + percentage_of_sales: "%{percentage} de vendas" + percentage_of_turnover: " \"Percentual de faturamento\"" + monthly_cap_excl_tax: "limite mensal (taxas exclusas)" + capped_at_cap: "limitado em %{cap}" + per_month: "ao mês" + free: "grátis" + free_trial: "faça um teste grátis" + plus_tax: "mais taxas" + total_monthly_bill_incl_tax: "Total mensal (taxas inclusas)" + min_bill_turnover_desc: "uma vez que o volume de negócio exceder %{mbt_amount}" + business_model_configuration: "Configuração do modelo de negócios" + say_no: "Não" + say_yes: "Sim" + then: então + sort_order_cycles_on_shopfront_by: "Ordenar Ciclo de Pedidos no Mercado Por" + admin: + date: Data + email: Email + name: Nome + on_hand: Disponível + on_demand: Sob Encomenda + on_demand?: Sob Encomenda + order_cycle: Ciclo de Pedidos + phone: Telefone + price: Preço + producer: Produtor + product: Produto + quantity: Quantidade + shop: Loja + tags: Tags + variant: Variante + quick_search: Busca Rápida + clear_all: apagar tudo + start_date: "Data de Início" + end_date: "Data de Término" + columns: Colunas + actions: Ações + viewing: "Visualizando: %{current_view_name}" + whats_this: O que é isso + tag_has_rules: "Regras existentes para essa tag: %{num}" + has_one_rule: "possui uma regra" + has_n_rules: "tem %{num} regras" + customers: + index: + add_customer: "Adicionar Cliente" + new_customer: "Novo Cliente" + customer_placeholder: "cliente@exemplo.org" + valid_email_error: Favor usar um endereço de email válido + add_a_new_customer_for: Adicionar novo cliente para %{shop_name} + code: Código + duplicate_code: "Esse código já está sendo usado." + bill_address: "Endereço de Fatura" + ship_address: "Endereço de Entrega" + update_address_success: 'Endereço atualizado com sucesso' + update_address_error: 'Favor preencher todos os campos obrigatórios' + edit_bill_address: 'Editar Endereço de Fatura' + edit_ship_address: 'Editar Endereço de Entrega' + required_fileds: 'Os campos obrigatórios estão marcados com um asterisco' + select_country: 'Selecionar País' + select_state: 'Selecionar Estado' + edit: 'Editar' + update_address: 'Atualizar Endereço' + confirm_delete: 'Certeza que quer excluir?' + products: + bulk_edit: + unit: Unidade + display_as: Mostrar Como + category: Categoria + tax_category: Categoria de taxa + inherits_properties?: Herdar Propriedades? + available_on: Disponível Em + av_on: "Disp. Em" + variant_overrides: + index: + title: Inventário + description: Utilize essa página para gerenciar inventários para seus empreendimentos. Qualquer detalhe de produtos especificado aqui irá substituir o que foi especificado na página de 'Produtos' + enable_reset?: Ativar Restauração do Estoque + inherit?: Herdar? + add: Adicionar + hide: Esconder + select_a_shop: Selecionar Uma Loja + review_now: Avaliar Agora + new_products_alert_message: Há %{new_product_count} novos produtos disponíveis para serem adicionados ao seu inventário. + currently_empty: Seu inventário está vazio no momento + no_matching_products: enhum produto correspondente encontrado no seu inventário + no_hidden_products: Nenhum produto foi ocultado nesse inventário + no_matching_hidden_products: Nenhum produto oculto corresponde à sua busca + no_new_products: Nenhum novo produto está disponível para ser adicionado a este inventário + no_matching_new_products: Nenhum novo produto corresponde à sua busca + inventory_powertip: Este é seu inventário de produtos. Para adicionar produtos ao seu inventário, selecione 'Novos Produtos' no menu Exibição + hidden_powertip: Estes produtos foram escondidos no seu inventário e não estarão disponíveis para serem adicionados à sua loja. Você pode clicar em 'Adicionar' para adicionar um produto a seu inventário. + new_powertip: 'Estes produtos estão disponíveis para serem adicionados ao seu inventário. Clique em ''Adicionar'' para adicionar um produto em seu inventário, ou ''Ocultar'' para escondê-lo. Você pode voltar atrás quando quiser! ' + orders: + bulk_management: + tip: "Use essa página para alterar a quantidade de produtos dentre múltiplos pedidos. Produtos podem ser removidos completamente dos pedidos, se necessário" + shared: "Recurso Compartilhado?" + order_no: "Pedido nº" + order_date: "Data do Pedido" + max: "Máximo" + product_unit: "Produto: Unidade" + weight_volume: "Peso/Volume" + ask: "Perguntar?" + page_title: "Gestão de Pedidos a Granel" + actions_delete: "Deletar Selecionado" + loading: "Carregando pedidos" + no_results: "Nenhum pedido encontrado. " + group_buy_unit_size: "Tamanho de Unidade para Compras de Grupo" + total_qtt_ordered: "Quantidade Total do Pedido" + max_qtt_ordered: "Quantidade Máxima do Pedido" + current_fulfilled_units: "Unidades Completadas no Momento" + max_fulfilled_units: "Máximo de Unidades Completadas " + order_error: "Alguns erros devem ser corrigidos antes de atualizar os pedidos.\nOs campos com bordas vermelhas contém erros." + variants_without_unit_value: "AVISO: Algumas variantes não possuem unidade de valor" + order_cycles: + edit: + choose_products_from: "Escolha Produtos De:" + enterprises: + index: + producer?: Produtor? + package: Embalagem + status: Status + manage: Administrar + form: + shop_preferences: + shopfront_requires_login: "Mercado visível publicamente?" + shopfront_requires_login_tip: "Escolha se os clientes precisarão fazer o login para ver os produtos do mercado, ou se eles estarão visíveis para todos." + shopfront_requires_login_false: "Publico" + shopfront_requires_login_true: "Disponível somente para clientes registrados" + allow_guest_orders: "Pedidos dos convidados" + allow_guest_orders_tip: "Permitir o checkout como convidado, ou requisitar um usuário registrado. " + allow_guest_orders_false: "Requisitar o registro para fazer pedidos" + allow_guest_orders_true: "Permitir checkout de convidados" + home: + hubs: + show_closed_shops: "Mostrar lojas fechadas" + hide_closed_shops: "Esconder lojas fechadas" + show_on_map: "Mostrar todas no mapa" + shared: + register_call: + selling_on_ofn: "Interessado em participar da Open Food Network?" + register: "Registre-se aqui" + shop: + messages: + login: "Entrar" + register: "registro" + contact: "contato" + require_customer_login: "Essa loja é somente para clientes." + require_login_html: "Fazer o %{login} se você já possui uma conta. Caso contrário, %{register} para se tornar cliente. " + require_customer_html: "Favor %{contact} %{enterprise} para se tornar um cliente. " + invoice_column_item: "Ítem" + invoice_column_qty: "Quantidade" + invoice_column_price: "Preço" + logo: "Logo (640x130)" + logo_mobile: "logo mobile (75x26)" + logo_mobile_svg: "Logo mobile (svg)" + home_hero: "Imagem de capa" + home_show_stats: "Mostrar estatísticas" + footer_logo: "Logo (220x76)" + footer_facebook_url: "URL Facebook" + footer_twitter_url: "URL Twitter" + footer_instagram_url: "URL Instagram" + footer_linkedin_url: "URL Linkedln " + footer_googleplus_url: "URL Google Plus" + footer_pinterest_url: "URL Pinterest" + footer_email: "Email" + footer_links_md: "Links" + footer_about_url: "URL Sobre" + footer_tos_url: "URL Termos de Serviço" + name: Nome + first_name: Primeiro Nome + last_name: Último Nome + email: Email + phone: Telefon + next: Próximo + address: Endereço + address2: Complemento + city: Cidade + state: Estado + postcode: CEP + country: País + unauthorized: Não autorizado + terms_of_service: "Termos de Serviço" + on_demand: Sob encomenda + none: Nenhum + label_shops: "Lojas" + label_map: "Mapa" + label_producers: "Produtores" + label_groups: "Grupos" + label_about: "Sobre" + label_connect: "Conectar" + label_learn: "Aprender" + label_shopping: "Compras" + label_login: "Entrar" + label_logout: "Sair" + label_signup: "Registre-se" + label_administration: "Administração" + label_admin: "Admin" + label_account: "Conta" + label_more: "Mostrar mais" + label_less: "Mostrar menos" + label_notices: "Avisos" + items: "itens" + cart_headline: "seu carrinho de compras" + total: "Total" + checkout: "Checkout" + cart_updating: "Atualizando carrinho" + cart_empty: "Carrinho vazio" + cart_edit: "Edite seu carrinho" + card_number: Número do Cartão + card_securitycode: "Código de Segurança" + card_expiry_date: Data de Vencimento + ofn_cart_headline: "Carrinho atual para:" + ofn_cart_distributor: "Distribuidor:" + ofn_cart_oc: "Ciclo de pedidos" + ofn_cart_from: "De:" + ofn_cart_to: "Para:" + ofn_cart_product: "Produto:" + ofn_cart_quantitiy: "Quantidade:" + ofn_cart_send: "Me compre" + ie_warning_headline: "Seu navegador está desatualizado :-(" + ie_warning_text: "Para a melhor experiência na Open Food Network, recomendamos que você atualize seu navegador:" + ie_warning_chrome: Baixar Chrome + ie_warning_firefox: Baixar Firefox + ie_warning_ie: Atualizar Internet Explorer + ie_warning_other: "Não consegue atualizar o navegador? Tente acessar a OFN pelo smartphone :-)" + footer_global_headline: "OFN Global" + footer_global_home: "Início" + footer_global_news: "Novidades" + footer_global_about: "Sobre" + footer_global_contact: "Contato" + footer_sites_headline: "Páginas OFN" + footer_sites_developer: "Desenvolvedores" + footer_sites_community: "Comunidade" + footer_sites_userguide: "Guia do Usuário" + footer_secure: "Seguro e confiável." + footer_secure_text: "A Open Food Network utiliza a criptografia SSL (2048 bit RSA) para manter suas informações em segurança. Nossos servidores não guardam os detalhes do seu cartão de crédito e os pagamentos são processados por serviços compatíveis com PCI." + footer_contact_headline: "Mantenha contato" + footer_contact_email: "Envie-nos um email" + footer_nav_headline: "Navegar" + footer_join_headline: "Junte-se a nós" + footer_join_body: "Criar uma lista de ofertas, mercado ou cooperativa na Open Food Network" + footer_join_cta: "Quero saber mais!" + footer_legal_call: "Leia nosso" + footer_legal_tos: "Termos e condições" + footer_legal_visit: "Nos encontre em " + footer_legal_text_html: "A Open Food Network é uma plataforma grátis e open-source. Nosso conteúdo está sob licensa %{content_license} e nosso código %{code_license}." + home_shop: Compre Agora + brandstory_headline: "Alimentos, com liberdade" + brandstory_intro: "Às vezes, a melhor maneira de consertar o sistema é construir um novo..." + brandstory_part1: "A gente começa por quem produz os alimentos, contando suas histórias e de seus cultivos, passando por quem distribui de modo justo e sem desperdício, até chegar em quem acredita que suas decisões de consumo podem..." + brandstory_part2: "Precisamos de uma ferramenta para empoderar a todos que produzem, vendem e compram comida. Uma maneira de contar histórias, e controlar toda a logística. " + brandstory_part3: "Por isso construímos um mercado online, transparente, capaz de criar conexões verdadeiras. O código é aberto, e pode ser modificado para melhor se adaptar as particularidades de cada canto do planeta. " + brandstory_part4: "Queremos de volta o controle sobre os alimentos que consumimos." + brandstory_part5_strong: "Bem vindos à Open Food Network" + brandstory_part6: "Todos amamos comida. Agora a gente também pode amar nosso sistema alimentar. " + learn_body: "Conheça novos modelos, histórias e fornecedores para dar suporte à sua iniciativa, mercado ou organização. Encontre oportunidades para aprender com quem faz parte do seu setor. " + learn_cta: "Inspire-se" + connect_body: "Procure em nossa lista por produtores, distribuidores e cooperativas para encontrar um comércio justo, perto de você. Registre seu negócio na OFN para que os consumidores possam te encontrar. Junte-se à comunidade para trocar experiências e resolver problemas, juntos. " + connect_cta: "Explore" + system_headline: "Comprar, funciona assim:" + system_step1: "1. Busca" + system_step1_text: "Escolha entre diversos mercados independentes por alimentos locais, da estação. Procure por região, tipo de alimentos, ou se você prefere retirar ou receber em casa. " + system_step2: "2. Compra" + system_step2_text: "Transforme seu comércio com fornecedores de alimentos locais. Conheça as histórias por trás do seu produto, e daqueles que o fazem!" + system_step3: "3. Coleta / Entrega" + system_step3_text: "Espere que sua compre chegue até você, ou retire nos pontos de entrega determinados pelo seu fornecedor. Simples assim!" + cta_headline: "A feira que incentiva a economia local." + cta_label: "Estou pronto" + stats_headline: "Estamos criando um novo sistema alimentar" + stats_producers: "produtores" + stats_shops: "lojas" + stats_shoppers: "compradores" + stats_orders: "pedidos" + checkout_title: Fechar pedido + checkout_now: Fechar pedido agora + checkout_order_ready: Pedido pronto para + checkout_hide: Ocultar + checkout_expand: Expandir + checkout_headline: "Ok, pronto para fechar o pedido?" + checkout_as_guest: "Fechar pedido como convidado" + checkout_details: "Seus detalhes" + checkout_billing: "Informações para fatura" + checkout_default_bill_address: "Salvar como endereço de faturamento padrão" + checkout_shipping: Informações para envio + checkout_default_ship_address: "Salvar como endereço para entrega padrão" + checkout_method_free: Grátis + checkout_address_same: O endereço de entrega é o mesmo endereço da fatura? + checkout_ready_for: "Pronto para" + checkout_instructions: "Algum comentário ou instruções especiais?" + checkout_payment: Pagamento + checkout_send: Fechar pedido agora + checkout_your_order: Seu pedido + checkout_cart_total: Total do carrinho + checkout_shipping_price: Envio + checkout_total_price: Total + checkout_back_to_cart: "De volta para o Carrinho" + order_paid: PAGO + order_not_paid: NÃO PAGO + order_total: Total do pedido + order_payment: "Pagando com:" + order_billing_address: Endereço de fatura + order_delivery_on: Entrega em + order_delivery_address: Endereço para entrega + order_special_instructions: "Suas anotações" + order_pickup_time: Pronto para retirada + order_pickup_instructions: Instruções para retirada + order_produce: Produtos + order_total_price: Total + order_includes_tax: (inclui taxas) + order_payment_paypal_successful: Seu pagamento via Paypal foi processado com sucesso. + order_hub_info: Informações do distribuidor + unsaved_changes_warning: "Existem modificações não salvas que serão perdidas se você continuar" + unsaved_changes_error: "Campos com bordas vermelhas contem erros. " + products: "Produtos" + products_in: "em %{oc}" + products_at: "em %{distributor}" + products_elsewhere: "Produtos encontrados em outros lugares" + email_welcome: "Bem vindo" + email_confirmed: "Obrigado por confirmar seu endereço" + email_registered: "é agora parte de" + email_userguide_html: "O Guia de Usuário com suporte detalhado para gerenciar sua loja está aqui:" + email_admin_html: "Você pode gerenciar sua conta entrando no %{link} ou clicando na engrenagem no canto superior direito da página, e selecionando Administração." + email_community_html: "Também temos um fórum online para discussão sobre a plataforma e as dificuldades únicas de se negociar alimentos. Você está convidade a participar. Estamos evoluindo constantemente e suas idéias vão nos ajudar a melhorar.\n%{link}" + join_community: "Faça parte da comunidade" + email_help: "Se tiver quaisquer dificuldades, leia nosso FAQs, navegue no fórum ou crie um post no tópico 'Support' e alguém irá te ajudar!" + email_confirmation_greeting: "Olá, %{contact}!" + email_confirmation_profile_created: "Um perfil para %{name} foi criado com sucesso!\nPara ativar seu Perfil precisamos que você confirme seu endereço de email." + email_confirmation_click_link: "Clique no link abaixo para confirma seu email e continuar criando seu perfil." + email_confirmation_link_label: "Confirme esse endereço de email »" + email_confirmation_help_html: "Depois de confirmar seu email você pode acessar a conta de administração desse empreendimento. Veja o %{link} para descobrir mais sobre %{sitename} e para começar a utilizar seu perfil ou loja online." + email_confirmation_userguide: "Guia do Usuário" + email_social: "Conecte-se com a gente:" + email_contact: "Envie-nos um email:" + email_signoff: "Olá," + email_signature: "%{sitename} Equipe" + email_confirm_customer_greeting: "Olá %{name}, " + email_confirm_customer_intro_html: "Obrigado por comprar com %{distributor}!" + email_confirm_customer_number_html: "Confirmação de pedido #%{number}" + email_confirm_customer_details_html: "Aqui estão os detalhes de pedido de %{distributor}:" + email_confirm_customer_signoff: "Atenciosamente," + email_confirm_shop_greeting: "Olá %{name}, " + email_confirm_shop_order_html: "Parabéns! Você tem um novo pedido para %{distributor}:" + email_confirm_shop_number_html: "Confirmação de pedido #%{number}" + email_order_summary_item: "Item" + email_order_summary_quantity: "Qtd" + email_order_summary_price: "Preço" + email_order_summary_subtotal: "Subtotal:" + email_order_summary_total: "Total:" + email_payment_paid: PAGO + email_payment_not_paid: NÃO PAGO + email_payment_summary: Resumo do pagamento + email_payment_method: "Pagando com:" + email_shipping_delivery_details: Detalhes da entrega + email_shipping_delivery_time: "Entrega " + email_shipping_delivery_address: "Endereço de entrega:" + email_shipping_collection_details: Detalhes para retirada + email_shipping_collection_time: "Pronto para retirada" + email_shipping_collection_instructions: "Instruções para retirada:" + email_special_instructions: "Suas anotações" + email_signup_greeting: Olá! + email_signup_welcome: "Bem vindo a %{sitename}!" + email_signup_login: Seu login + email_signup_email: 'O seu email de login é ' + email_signup_shop_html: "Você pode começar a comprar agora em %{link}." + email_signup_text: "Obrigado por se juntar à rede. Se você é um cliente, esperamos te apresentar a vários produtores fantásticos, incríveis distribuidores e comidas deliciosas! Se você é um produtor ou empreendedor, estamos felizes em ter você como parte de nossa rede" + email_signup_help_html: "Dúvidas e comentários são sempre benvindos; você pode usar o botão Enviar Comentário no site, ou enviar um e-mail para %{email}" + producer_mail_greeting: "Querido" + producer_mail_text_before: "Agora temos todos os pedidos do cliente para a próxima entrega." + producer_mail_order_text: "Aqui está um resumo de pedidos para seus produtos:" + producer_mail_delivery_instructions: "Instruções para retirada/entrega do estoque:" + producer_mail_signoff: "Obrigado e até breve" + shopping_oc_closed: Fechado para pedidos + shopping_oc_closed_description: "Favor aguardar até que o próximo ciclo seja aberto (ou entre em contato diretamente para saber se podemos aceitar pedidos atrasados)" + shopping_oc_last_closed: "A último ciclo fechou a %{distance_of_time} atrás" + shopping_oc_next_open: "A próximo ciclo abre em %{distance_of_time}" + shopping_tabs_about: "Sobre %{distributor}" + shopping_tabs_contact: "Contato" + shopping_contact_address: "Endereço" + shopping_contact_web: "Contato" + shopping_contact_social: "Seguir" + shopping_groups_part_of: "é parte de:" + shopping_producers_of_hub: "produtores de %{hub}:" + enterprises_next_closing: "Próximo fechamento de pedido" + enterprises_ready_for: "Pronto para" + enterprises_choose: "Escolha para quando você quer seu pedido:" + hubs_buy: "Compre por:" + hubs_shopping_here: "Comprando aqui" + hubs_orders_closed: "Fechado para pedidos" + hubs_profile_only: "Somente perfil" + hubs_delivery_options: "Opções de entrega" + hubs_pickup: "Coleta" + hubs_delivery: "Entrega" + hubs_producers: "Nosso produtores" + hubs_filter_by: "Filtrar por" + hubs_filter_type: "Tipo" + hubs_filter_delivery: "Entrega" + hubs_matches: "Você quis dizer?" + hubs_intro: Compre na sua região + hubs_distance: Mais próximo a + hubs_distance_filter: "Mostrar lojas próximas a %{location}" + products_clear_all: Apagar tudo + products_showing: "Mostrando:" + products_with: com + products_search: "Procurar por produto ou produtor" + products_loading: "Carregando produtos..." + products_updating_cart: "Atualizando carrinho..." + products_cart_empty: "Carrinho vazio" + products_edit_cart: "Edite seu carrinho" + products_from: de + products_change: "Nenhuma modificação a ser salva." + products_update_error: "Falha ao salvar, com os seguintes erros:" + products_update_error_msg: "Falha ao salvar." + products_update_error_data: "Falha no salvamento devido a dados inválidos:" + products_changes_saved: "Modificações salvas." + search_no_results_html: "Desculpe, nenhum resultado encontrado para %{query}. Que tal tentar outra busca?" + components_profiles_popover: "Perfis não possuem mercaods na Open Food Network, mas pode ter suas próprias lojas físicas ou online em outro endereço" + components_profiles_show: "Mostrar perfis" + components_filters_nofilters: "Nenhum filtro" + components_filters_clearfilters: "Eliminar filtros" + groups_title: Grupos + groups_headline: Grupos / Regiões + groups_text: "Todo produtor é único. Todo negócio tem algo de diferente para oferecer. Nossos grupos são coletivos de produtores, armazéns e distribuidores que compartilham algo em comum, como localização, mercado ou filosofia. Isso faz com que sua experiência de compra fique mais fácil. Explore a curadoria feita por cada um de nosso grupos. " + groups_search: "Procurar por nome ou localidade" + groups_no_groups: "Nenhum grupo encontrado. " + groups_about: "Sobre nós" + groups_producers: "Nosso produtores" + groups_hubs: "Nossas" + groups_contact_web: Contato + groups_contact_social: Seguir + groups_contact_address: Endereço + groups_contact_email: Envie-nos um email + groups_contact_website: Visite nosso website + groups_contact_facebook: Siga + groups_signup_title: Inscreva-se como grupo + groups_signup_headline: Inscrição de grupos + groups_signup_intro: "Somos uma plataforma incrível para marketing colaborativo: a maneira mais fácil para que seus membros alcancem novos mercados. Não temos fins lucrativos, somos simples e acessíveis." + groups_signup_email: Envie-nos um email + groups_signup_motivation1: Transformamos o sistema alimentar de maneira justa. + groups_signup_motivation2: 'Trabalhos para isso. Somos uma organização global, sem fins lucrativos, baseada em código open source. Jogamos limpo, sem mistério. ' + groups_signup_motivation3: 'Sabemos que você tem grandes planos, e queremos ajudar. Podemos compartilhar conhecimento, redes e recurso. Sabemos que ninguém muda nada sozinho, por isso queremos você como parceiro. ' + groups_signup_motivation4: Vamos até onde você está. + groups_signup_motivation5: 'Seja uma cooperativa de produtores, distribuidores, indústria ou governo local. ' + groups_signup_motivation6: 'Seja qual for o seu papel, estamos aqui para ajudar. Entre em contato com a gente e conte-nos sobre suas ideias e projetos. ' + groups_signup_motivation7: 'Queremos dar sentido para os movimentos por boa comida. ' + groups_signup_motivation8: 'Se você precisa engajar sua rede de contatos, nós oferecemos a plataforma para isso. Conectamos todos os agentes e setores envolvidos no sistema alimentar. ' + groups_signup_motivation9: 'Se você precisa de recursos, nós te conectamos a uma rede global de parceiros. ' + groups_signup_pricing: Conta de Grupos + groups_signup_studies: Estudos de Caso + groups_signup_contact: Pronto para discutir? + groups_signup_contact_text: "Entre em contato para descobrir o que a OFN pode fazer por você" + groups_signup_detail: "Aqui está o detalhe. " + login_invalid: "Email ou senha inválidos" + modal_hubs: "Centrais de Alimentos" + modal_hubs_abstract: Nossas centrais de alimentos são o ponto de contato entre você e as pessoas que produzem sua comida! + modal_hubs_content1: 'Você pode procurar pelo mercado mais próximo, por localização ou por nome. Alguns distribuidores possuem múltiplos pontos de entrega, onde você pode retirar suas compras, e outros ainda entregam na sua casa. Cada mercado é um ponto de venda independente, e por isso as ofertas e maneira de operar podem variar de um para outro. ' + modal_hubs_content2: Você só pode comprar em uma central de alimentos por vez. + modal_groups: "Grupos / Regiões" + modal_groups_content1: Essas são as organizações e relações entre as centrais que constroem a Open Food Network + modal_groups_content2: Alguns grupos estão organizados por localização, outros por similaridades não geográficas. + modal_how: "Como funciona" + modal_how_shop: Compra na Open Food Network + modal_how_shop_explained: Procure por um mercado próximo e comece suas compras! Em cada mercado você pode ver, em detalhe, quais produtos são oferecidos (você só pode comprar em um mercado de cada vez). + modal_how_pickup: 'Custos de coleta e entrega. ' + modal_how_pickup_explained: 'Alguns mercados entregam na sua casa, outros oferecem um local para que você mesmo retire os produtos. É possível ver quais opções estão disponíveis no perfil individual de cada um, e fazer sua escolha no momento do checkout. Provavelmente será cobrada uma taxa de entrega, que pode variar de mercado para mercado. ' + modal_how_more: Saiba mais + modal_how_more_explained: "Para saber mais sobre a Open Food Network, como funciona, e se envolver:" + modal_producers: "Produtores" + modal_producers_explained: "Nosso produtores são quem disponibilizam toda a oferta da Open Food Network" + producers_about: Sobre nós + producers_buy: Compre por + producers_contact: Contato + producers_contact_phone: Ligue + producers_contact_social: Seguir + producers_buy_at_html: "Compre por produtos oferecidos por %{enterprise} em:" + producers_filter: Filtrar por + producers_filter_type: Tipo + producers_filter_property: Propriedades + producers_title: Produtores + producers_headline: Encontre produtores locais + producers_signup_title: Inscreva-se como produtor + producers_signup_headline: Mais liberdade para quem produz comida. + producers_signup_motivation: Comercialize seus produtos e conte sua história para um mercado novo e diferenciado. Economize tempo e dinheiro em comunicação e logística. + producers_signup_send: Cadastre-se agora + producers_signup_enterprise: Contas de Empreendimentos + producers_signup_studies: Histórias de nossos produtores + producers_signup_cta_headline: Cadastre-se agora! + producers_signup_cta_action: Cadastre-se agora + producers_signup_detail: Aqui está o detalhe/ + products_item: Ítem + products_description: Descrição + products_variant: Variante + products_quantity: Quantidade + products_available: Disponível? + products_producer: "Produtor" + products_price: "Preço" + register_title: Registro + sell_title: "\bRegistrar" + sell_headline: "Fazer parte da Open Food Network!" + sell_motivation: "Mostre seus produtos deliciosos." + sell_producers: "Produtores" + sell_hubs: "Centrais" + sell_groups: "Grupos" + sell_producers_detail: "Crie um perfil para seu negócio em apenas alguns minutos. A qualquer momento você poderá fazer se tornar um mercado online e vender seus produtos diretamente ao consumidor." + sell_hubs_detail: "Crie um perfil para seu negócio ou organização na OFN. A qualquer momento você poderá fazer um upgrade para um mercado de multi-produtores. " + sell_groups_detail: "Organize um lista personalizada de negócios (cultivos, cooperativas, comércios, etc.) para sua região ou organização. " + sell_user_guide: "Saiba mais acessando nosso guia. " + sell_listing_price: "Criar um perfil na OFN é grátis. Abrir e gerenciar um mercado na OFN é grátis. Organizar um grupo de empresas na OFN é grátis." + sell_embed: "Você também pode embutir um mercado da OFN no seu próprio site, ou construir um site específico para a sua região. " + sell_ask_services: "Pergunte-nos sobre nossos serviços." + shops_title: Lojas + shops_headline: 'Compras, transformadas. ' + shops_text: 'A colheita é feita em ciclos, a comida é produzida em ciclos, e nós fazemos nossos pedidos em ciclos. Se você encontrar um ciclo de pedidos fechado, volte em breve para tentar novamente. ' + shops_signup_title: Registre-se como uma central + shops_signup_headline: 'Um mercado de alimentos sem tamanho. ' + shops_signup_motivation: 'Seja qual for seu perfil, nos oferecemos suporte. Somos sem fins lucrativos, independentes, e open source. ' + shops_signup_action: Cadastre-se agora + shops_signup_pricing: Contas de Empreendimentos + shops_signup_stories: Histórias de nossas centrais. + shops_signup_help: Estamos prontos para ajudar + shops_signup_help_text: Você precisa de mais retorno, novos compradores e parceiros de logística. Você precisa contar a sua história. + shops_signup_detail: Aqui está o detalhe. + orders_fees: Taxas... + orders_edit_title: Carrinho de compras + orders_edit_headline: seu carrinho de compras + orders_edit_time: Pedido pronto para + orders_edit_continue: Continuar comprando + orders_edit_checkout: Fechar pedido + orders_form_empty_cart: "Carrinho vazio" + orders_form_subtotal: Subtotal dos produtos + orders_form_admin: Administração e manejo + orders_form_total: Total + orders_oc_expired_headline: Este ciclo de pedidos está fechado para pedidos + orders_oc_expired_text: "Desculpe, este ciclo de pedidos fechou há %{time} atrás. Favor entrar em contato diretamente com sua central para saber se podem aceitar pedidos atrasados." + orders_oc_expired_text_others_html: "Desculpe, este ciclo de pedidos fechou há %{time} atrás. Favor entrar em contato diretamente com sua central para saber se podem aceitar pedidos atrasados %{link}." + orders_oc_expired_text_link: "ou veja os outros ciclos de pedidos disponíveis nessa central" + orders_oc_expired_email: "Email:" + orders_oc_expired_phone: "Telefone:" + orders_show_title: Confimação de Pedido + orders_show_time: Pedido pronto em + orders_show_number: Confirmação de pedido + products_cart_distributor_choice: "Distribuidor para seu pedido:" + products_cart_distributor_change: "O distribuidor para este pedido será trocado para %{name} se você adicionar este produto no carrinho." + products_cart_distributor_is: "O distribuidor para este pedido é %{name}." + products_distributor_error: "Favor completar seu pedido no %{link} antes de comprar com outro distribuidor." + products_oc: "Ciclo de pedido para seu pedido:" + products_oc_change: "O ciclo de pedido para esse pedido será trocada para %{name} se você adicionar este produto ao carrinho." + products_oc_is: "O ciclo de pedido para este pedido é %{name}." + products_oc_error: "Favor completar seu pedido no %{link} antes de comprar em outro ciclo de pedido." + products_oc_current: "seu ciclo de pedido atual" + products_max_quantity: Quantidade máxima + products_distributor: Distribuidor + products_distributor_info: Quando você selecionar um distribuidor para seu pedido, o endereço e data para retirada serão exibidos aqui. + shop_trial_length: "Duração de Avaliação da Loja (dias)" + shop_trial_expires_in: "O período de avaliação do mercado termina em " + shop_trial_expired_notice: "Boa notícia! Decidimos extender o período avaliação do mercado até segunda ordem. " + password: Senha + remember_me: Lembre-me + are_you_sure: "Tem certeza?" + orders_open: Pedidos abertos + closing: "Fechando" + going_back_to_home_page: "Voltando à pagina inicial" + creating: Criando + updating: Atualizando + failed_to_create_enterprise: "Falha ao criar seu empreendimento" + failed_to_create_enterprise_unknown: "Falha ao criar seu empreendimento. \nFavor verificar se todos os campos foram preenchidos corretamente." + failed_to_update_enterprise_unknown: "Falha ao atualizar seu empreendimento. \nFavor verificar se todos os campos foram preenchidos corretamente." + order_not_saved_yet: "Seu pedido ainda não foi salvo. Aguarde um momento. " + filter_by: "Filtrar por" + hide_filters: "Esconder filtros" + one_filter_applied: "1 filtro aplicado" + x_filters_applied: "filtros aplicados" + submitting_order: "Processando seu pedido: favor aguardar" + confirm_hub_change: "Tem certeza? Isso irá mudar a central selecionada e remover todos os ítens do carrinho de compras." + confirm_oc_change: "Tem certeza? Isso irá mudar o ciclo de pedidos selecionado e remover todos os ítens do carrinho de compras." + location_placeholder: "Digite uma localidade..." + error_required: "Não pode ser vazio" + error_number: "Precisa ser um número" + error_email: "Precisa ser um endereço de email" + item_handling_fees: "Taxas de Manejo do Produto (incluídas no total do produto)" + january: "Janeiro" + february: "Fevereiro" + march: "Março" + april: "Abril" + may: "Maio" + june: "Junho" + july: "Julho" + august: "Agosto " + september: "Setembro" + october: "Outubro" + november: "Novembro" + december: "Dezembro" + email_not_found: "Endereço de email não encontrado" + email_required: "Você precisa providenciar um endereço de email" + logging_in: "Fazendo o login, aguarde um momento" + signup_email: "Seu email" + choose_password: "Escolha uma senha" + confirm_password: "Confirme a senha" + action_signup: "Cadastre-se agora" + welcome_to_ofn: "Bem-vindo à Open Food Network!" + signup_or_login: "Faça seu cadastro ou login para começar" + have_an_account: "Já possui um conta?" + action_login: "Entrar agora" + forgot_password: "Esqueceu sua senha?" + password_reset_sent: "Um email foi enviado com instruções para resetar sua senha!" + reset_password: "Resetar password" + who_is_managing_enterprise: "Quem é responsável por gerenciar %{enterprise}? " + enterprise_contact: "Contato Principal" + enterprise_contact_required: "Você precisa adicionar um contato principal" + enterprise_email_address: "Endereço de e-mail" + enterprise_phone: "n" + back: "Voltar" + continue: "Continuar" + limit_reached_headline: "Oh não!" + limit_reached_message: "Você chegou ao limite!" + limit_reached_text: "Você chegou ao limite para o número de empreendimentos permitidos" + limit_reached_action: "Voltar à página principal" + select_promo_image: "Passo 3. Selecionar Imagem Promocional" + promo_image_tip: "Tamanho preferencial: 1200x260px" + promo_image_label: "Escolher uma imagem promocional" + action_or: "OU" + promo_image_drag: "Arraste e solte sua imagem aqui" + review_promo_image: "Passo 4. Avalie Sua Imagem Promocional" + review_promo_image_tip: "Dica: para melhores resultados, sua imagem deve preencher o espaço disponível" + promo_image_placeholder: "Seu logo aparecerá aqui para avaliação assim que for carregado" + uploading: "Carregando..." + select_logo: "Passo 1. Selecionar imagem de perfil" + logo_tip: "Dica: Imagens quadradas funcionam melhor, com mínimo de 300x300px" + logo_label: "Escolha uma imagem de perfil" + logo_drag: "Arraste e solte sua imagem aqui" + review_logo: "Passo 2. Avalie sua imagem de perfil" + review_logo_tip: "Dica: para melhores resultados, sua imagem deve preencher o espaço disponível" + logo_placeholder: "Seu logo aparecerá aqui para avaliação assim que for carregado`" + enterprise_about_headline: "Boa!" + enterprise_about_message: "Vamos inserir mais detalhes sobre" + enterprise_success: "Sucesso! %{enterprise} foi adicionada a Open Food Network" + enterprise_registration_exit_message: "Se você fechar esse guia a qualquer momento, você precisa clicar no link de confirmação que você recebeu no email. Isso te levará até sua interface de administração onde você pode continuar construindo seu perfil." + enterprise_description: "Descrição Curta" + enterprise_description_placeholder: "Uma pequena frase descrevendo sua empresa" + enterprise_long_desc: "Descrição completa" + enterprise_long_desc_placeholder: "Essa é a oportunidade de contar a história da sua empresa. Sugerimos um parágrafo, no máximo, 600 caracteres ou 150 palavras. " + enterprise_long_desc_length: "%{num} caracteres / recomendamos até 600" + enterprise_tax_required: "Você precisa fazer uma seleção." + enterprise_final_step: "Último passo!" + enterprise_social_text: "Como as pessoas podem encontrar o/a %{enterprise} online?" + website: "Website" + website_placeholder: "eg. openfoodnetwork.com.br" + facebook: "Facebook" + facebook_placeholder: "ex. www.facebook.com/suapagina" + linkedin: "LinkedIn" + linkedin_placeholder: "ex. www.linkedin.com/seunome" + twitter: "Twitter" + twitter_placeholder: "ex. @conta_twitter" + instagram: "Instagram" + instagram_placeholder: "ex. @conta_instagram" + registration_greeting: "Olá!" + registration_intro: "Você pode criar um perfil para seu Produtor ou Distribuidor" + registration_action: "Vamos começar!" + registration_checklist: "Você vai precisar" + registration_time: "5-10 minutos" + registration_enterprise_address: "Endereço do empreendimento" + registration_contact_details: "Informações para contato" + registration_logo: "Sua imagem de perfil" + registration_promo_image: "Imagem horizontal para seu perfil" + registration_about_us: "'Sobre Nós'" + registration_outcome_headline: "O que eu ganho?" + registration_outcome1_html: "Seu perfil ajuda as pessoas a te encontrarem e entrarem em contato com você na Open Food Network" + registration_outcome2: "Use este espaço para contar a história da sua empresa, e ajudar a criar conexões entre sua presença física e virtual." + registration_outcome3: "Esse é também o primeiro passo para começar a comercializar na Open Food Network, ou abrir uma loja online" + registration_finished_headline: "Pronto!" + registration_finished_thanks: "Obrigado por preencher os detalhes para %{enterprise}." + registration_finished_login: "Você pode alterar ou atualizar suas informações a qualquer momento fazendo o login na Open Food Network e entrando na seção 'Admin'." + registration_finished_activate: "Ativar %{enterprise}." + registration_finished_activate_instruction_html: "Enviamos um email de confirmação para %{email}. Favor seguir as instruções do email para que seu negócio seja visível na Open Food Network. " + registration_finished_action: "Página principal" + registration_type_headline: "Último passo para adicionar %{enterprise}!" + registration_type_question: "Você é um produtor?" + registration_type_producer: "Sim, sou um produtor" + registration_type_no_producer: "Não, não sou um produtor" + registration_type_error: "Favor escolher um. Você é um produtor?" + registration_type_producer_help: "Produtores fazem coisas deliciosas de comer e beber. " + registration_type_no_producer_help: "Se você não é um produtor, você provavelmente é alguém que vende e distribui comida. Você pode ser uma central, cooperativa, grupo de compras, lojista, etc." + create_profile: "Crir perfil" + registration_images_headline: "Obrigado!" + registration_images_description: "Vamos adicionar umas belas imagens para seu perfil ficar lindão!" + registration_detail_headline: "Vamos Começar" + registration_detail_enterprise: "Primeiro precisamos saber mais sobre sua empresa:" + registration_detail_producer: "Primeiro precisamos saber mais sobre sua produção:" + registration_detail_name_enterprise: "Nome da Atividade:" + registration_detail_name_producer: "Nome da Produção" + registration_detail_name_placeholder: "ex. Fazenda da Nina" + registration_detail_name_error: "Escolha um nome para sua empresa" + registration_detail_address1: "Linha de endereço 1:" + registration_detail_address1_placeholder: "ex. Rua Mármore, 123" + registration_detail_address1_error: "Favor inserir um endereço" + registration_detail_address2: "Linha de endereço 2:" + registration_detail_suburb: "Município" + registration_detail_suburb_placeholder: "ex. Contagem" + registration_detail_suburb_error: "Favor inserir um município" + registration_detail_postcode: "Código postal" + registration_detail_postcode_placeholder: "ex. 3070" + registration_detail_postcode_error: "Código postal requisitado" + registration_detail_state: "Estado" + registration_detail_state_error: "É obrigatório inserir o Estado" + registration_detail_country: "País" + registration_detail_country_error: "Favor inserir um país" + fees: "Taxas" + item_cost: "Custo da unidade" + bulk: "Atacado" + shop_variant_quantity_min: "mín." + shop_variant_quantity_max: "max." + follow: "Seguir" + shop_for_products_html: "Compre produtos da %{enterprise} em:" + change_shop: "Mudar loja para:" + shop_at: "Compre agora em:" + price_breakdown: "Preço detalhado" + admin_fee: "Taxa de manejo" + sales_fee: "Taxa de venda" + packing_fee: "Taxa de embalagem" + transport_fee: "Taxa de transporte" + fundraising_fee: "Taxa de poupança" + price_graph: "Gráfico de preços" + included_tax: "Taxas incluídas" + balance: "Balanço" + transaction: "Transação" + transaction_date: "DataData" + payment_state: "Status do Pagamento" + shipping_state: "Status da entrega" + value: "Valor" + balance_due: "saldo devedor" + credit: "Crédito" + Paid: "Pago" + Ready: "Pronto" + you_have_no_orders_yet: "Você ainda não tem pedidos" + running_balance: "Balanço corrente" + outstanding_balance: "Saldo devedor" + admin_entreprise_relationships: "Relações da empresa" + admin_entreprise_relationships_everything: "Tudo" + admin_entreprise_relationships_permits: "permite" + admin_entreprise_relationships_seach_placeholder: "Busca" + admin_entreprise_relationships_button_create: "Criar" + admin_entreprise_groups: "Grupos de empresas" + admin_entreprise_groups_name: "Nome" + admin_entreprise_groups_owner: "Dono" + admin_entreprise_groups_on_front_page: "Na página inicial?" + admin_entreprise_groups_entreprise: "Empreendimentos" + admin_entreprise_groups_data_powertip: "Usuário responsável pelo grupo" + admin_entreprise_groups_data_powertip_logo: "Esse é o logo do grupo" + admin_entreprise_groups_data_powertip_promo_image: "Essa é a imagem que aparecerá no topo do perfil do Grupo" + admin_entreprise_groups_contact: "Contato" + admin_entreprise_groups_contact_city: "Município" + admin_entreprise_groups_contact_city_placeholder: "ex. Contagem" + admin_entreprise_groups_contact_zipcode: "Código Postal " + admin_entreprise_groups_contact_zipcode_placeholder: "ex. 3078" + admin_entreprise_groups_contact_state_id: "Estadi" + admin_entreprise_groups_contact_country_id: "País" + admin_entreprise_groups_web: "Recursos Web" + admin_entreprise_groups_web_twitter: "ex. @nome_perfil" + admin_entreprise_groups_web_website_placeholder: "ex. www.cogumelos.com.br" + admin_order_cycles: "Ciclo de Pedidos do Administrador" + open: "Aberto" + close: "Fechado" + supplier: "Fornecedor" + coordinator: "Coordenador" + distributor: "Distribuidor" + fee_type: "Tipo de Taxa" + tax_category: "Categoria de taxa" + calculator: "Calculadora" + calculator_values: "Valores da calculadora" + flat_percent_per_item: "Percentual (por unidade)" + new_order_cycles: "Novo Ciclo de Pedidos" + select_a_coordinator_for_your_order_cycle: "Escolher um coordenador para novo ciclo de pedidos" + edit_order_cycle: "Editar Ciclo de Pedidos" + roles: "Papeis" + update: "Atualizar" + add_producer_property: "Adicionar produtor" + admin_settings: "Preferências do Sistema" + update_invoice: "Atualizar faturas" + finalise_invoice: "Finalizar Faturas" + finalise_user_invoices: "Finalizar Faturas de Usuário" + finalise_user_invoice_explained: "Usar este botão para finalizar todos as faturas no sistema para o calendário do mês anterior. " + manually_run_task: "Rodar Tarefa Manualmente" + update_user_invoices: "Atualizar Fatura de Usuário" + update_user_invoice_explained: "Usar este botão para atualizar imediamente todas as faturas no sistema para o mês atual, para cada usuário da empresa no sistema. Essa tarefa pode ser automatizada para rodar automaticamente toda noite. " + auto_finalise_invoices: "Finalizar automaticamente as faturas no dia 2 de cada mês, à 1:30am" + auto_update_invoices: "Atualizar automaticamente faturas toda as noites, a 1:00 am" + in_progress: "Em andamento" + started_at: "Começou em " + queued: "Aguardando" + scheduled_for: "Agendado para" + customers: "Clientes" + please_select_hub: "Favor selecionar uma Central" + loading_customers: "Carregando Clientes" + no_customers_found: "Nenhum cliente encontrado" + go: "Ir" + hub: "Central" + accounts_administration_distributor: "distribuidor de administração de contas" + accounts_and_billing: "Contas e Faturamento" + producer: "Produtor" + product: "Produto" + price: "Preço" + on_hand: "Disponível" + save_changes: "Salvar Modificações" + spree_admin_overview_enterprises_header: "Minhas Empresas" + spree_admin_overview_enterprises_footer: "GERENCIAR MINHAS EMPRESAS" + spree_admin_enterprises_hubs_name: "Nome" + spree_admin_enterprises_create_new: "CRIAR NOVA" + spree_admin_enterprises_shipping_methods: "Métodos de Entrega" + spree_admin_enterprises_fees: "Taxas da Empresa" + spree_admin_enterprises_none_create_a_new_enterprise: "CRIAR NOVA EMPRESA" + spree_admin_enterprises_none_text: "Você ainda não possui nenhuma empresa" + spree_admin_enterprises_producers_name: "Nome" + spree_admin_enterprises_producers_total_products: "Total de Produtos" + spree_admin_enterprises_producers_active_products: "Produtos Ativos" + spree_admin_enterprises_tabs_hubs: "CENTRAIS" + spree_admin_enterprises_tabs_producers: "PRODUTORES" + spree_admin_enterprises_producers_manage_order_cycles: "GERENCIAR CICLO DE PEDIDOS" + spree_admin_enterprises_producers_manage_products: "GERENCIAR PRODUTOS" + spree_admin_enterprises_producers_orders_cycle_text: "Você ainda não possui nenhum ciclo de pedidos ativo." + spree_admin_enterprises_any_active_products_text: "Você ainda não tem nenhum produto ativo." + spree_admin_enterprises_create_new_product: "CRIAR UM NOVO PRODUTO" + spree_admin_order_cycles: "Ciclo de Pedidos" + spree_admin_order_cycles_tip: "O ciclo de pedido determina quando e onde seus produtos estarão disponíveis para clientes." + dashbord: "Painel" + spree_admin_single_enterprise_alert_mail_confirmation: "Favor confirmar o endereço de email para" + spree_admin_single_enterprise_alert_mail_sent: "Enviamos um e-mail para" + spree_admin_overview_action_required: "Medida Solicitada" + spree_admin_overview_check_your_inbox: "Favor verificar sua caixa de entrada para maiores informações. Obrigado!" + change_package: "Modificar Embalagem" + spree_admin_single_enterprise_hint: "Dica: Para permitir que as pessoas te encontrem, ative sua visibilidade em" + your_profil_live: "Seu perfil online" + on_ofn_map: "O mapa da Open Food Network" + see: "Ver" + live: "online" + manage: "Gerenciar" + resend: "Re-enviar" + add_and_manage_products: "Adicionar e gerenciar produtos" + add_and_manage_order_cycles: "Adicionar e enviar ciclos de pedidos" + manage_order_cycles: "Gerenciar ciclos de pedidos" + manage_products: "Gerenciar produtos" + edit_profile_details: "Editar detalhes de perfil " + edit_profile_details_etc: "Modificar a descrição dos seu perfil, imagem, etc." + order_cycle: "Ciclo de Pedidos" + remove_tax: "Remover taxa" + tax_settings: "Configurações de Taxas" + products_require_tax_category: "produtos necessitam uma categoria de taxa" + admin_shared_address_1: "Endereço" + admin_shared_address_2: "Endereço (cont.)" + admin_share_city: "Cidade" + admin_share_zipcode: "Código Postal " + admin_share_country: "País" + admin_share_state: "Estadi" + hub_sidebar_hubs: "Centrais" + hub_sidebar_none_available: "Nada Disponível" + hub_sidebar_manage: "Gerenciar" + hub_sidebar_at_least: "Ao menos uma central deve ser selecionada" + hub_sidebar_blue: "azul" + hub_sidebar_red: "vermelho" + shop_trial_in_progress: "O período de avaliação do mercado termina em %{days}." + shop_trial_expired: "Boa notícia! Decidimos extender a avaliação da loja até segunda ordem. " + report_customers_distributor: "Distribuidor" + report_customers_supplier: "Fornecedor" + report_customers_cycle: "Ciclo de Pedidos" + report_customers_type: "Relatar Tipo" + report_customers_csv: "Fazer download como csv" + report_producers: "Produtores:" + report_type: "Relatar Tipo:" + report_hubs: "Centrais:" + report_payment: "Métodos de Pagamento" + report_distributor: "Distribuidor:" + report_payment_by: 'Pagamentos Por Tipo' + report_itemised_payment: 'Totais dos Pagamentos Discriminados' + report_payment_totals: 'Totais dos Pagamanetos' + report_all: 'todos' + report_order_cycle: "Ciclo de Pedidos:" + report_entreprises: "Empresas:" + report_users: "Usuários:" + initial_invoice_number: "Número de recibo inicial:" + invoice_date: "Data de recibo:" + due_date: "Data limite:" + account_code: "Código de conta:" + equals: "Igual a:" + contains: "contém:" + discount: "Desconto" + filter_products: "Filtrar Produtos" + delete_product_variant: "A última variante não pode ser deletada!" + progress: "progresso" + saving: "Salvando.." + success: "Sucesso" + failure: "falha" + unsaved_changes_confirmation: "Modificações não salvas serão perdidas. Continuar mesmo assim?" + one_product_unsaved: "Modificações para um produto permanecem não salvas." + products_unsaved: "Modificações para %{n} produtos permanecem não salvas." + is_already_manager: "já é um gestor!" + no_change_to_save: "Nenhuma modificação a ser salva" + add_manager: "Adicionar um gestor" + users: "Usuários" + about: "Sobre" + images: "Imagens" + web: "Web" + primary_details: "Detalhes principais" + adrdress: "Endereço" + contact: "Contato" + social: "Social" + business_details: "Detalhes do negócio" + properties: "Propriedades" + shipping_methods: "Métodos de Entrega" + payment_methods: "Métodos de Pagamento" + payment_method_fee: "Taxa de transação" + enterprise_fees: "Taxas da Empresa" + inventory_settings: "Configurações de Inventário" + tag_rules: "Regras para tag" + shop_preferences: "Preferências da Loja" + validation_msg_relationship_already_established: "^Esse relacionamento já foi estabelecido." + validation_msg_at_least_one_hub: "^Pelo menos uma central deve ser selecionada" + validation_msg_product_category_cant_be_blank: "^A Categoria do Produto deve ser preenchida" + validation_msg_tax_category_cant_be_blank: "^A Categoria da taxa deve ser preenchida" + validation_msg_is_associated_with_an_exising_customer: "está associado com um cliente existente" + spree: + zipcode: CEP + shipment_states: + backorder: atrasos + partial: parcial + pending: pendente + ready: pronto + shipped: enviado + payment_states: + balance_due: saldo devedor + completed: completado + checkout: fechar pedido + credit_owed: crédito devido + failed: falha + paid: pago + pending: pendente + processing: processando + void: vazio + invalid: inváliod + order_state: + address: endereço + adjustments: ajustes + awaiting_return: aguardando retorno + canceled: cancelado + cart: carrinho + complete: completo + confirm: confirmado + delivery: entrega + payment: pagamento + resumed: retomado + returned: retornado + skrill: skrill From c8f0502e71a912e17f89d66adeafe67d2ef2f66b Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 16 Dec 2016 14:32:48 +1100 Subject: [PATCH 084/109] Apply connect_learn_homepage feature toggle to mobile menu --- app/views/shared/menu/_mobile_menu.html.haml | 21 ++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/views/shared/menu/_mobile_menu.html.haml b/app/views/shared/menu/_mobile_menu.html.haml index 1ae4439cd9..066c3a8034 100644 --- a/app/views/shared/menu/_mobile_menu.html.haml +++ b/app/views/shared/menu/_mobile_menu.html.haml @@ -40,16 +40,17 @@ %span.nav-primary %i.ofn-i_036-producers = t 'label_producers' - %li.li-menu - %a{href: "https://openfoodnetwork.org/au/connect/"} - %span.nav-primary - %i.ofn-i_035-groups - = t 'label_connect' - %li.li-menu - %a{href: "https://openfoodnetwork.org/au/learn/"} - %span.nav-primary - %i.ofn-i_013-help - = t 'label_learn' + - if feature? :connect_learn_homepage + %li.li-menu + %a{href: "https://openfoodnetwork.org/au/connect/"} + %span.nav-primary + %i.ofn-i_035-groups + = t 'label_connect' + %li.li-menu + %a{href: "https://openfoodnetwork.org/au/learn/"} + %span.nav-primary + %i.ofn-i_013-help + = t 'label_learn' %li - if spree_current_user.nil? From 9793450ed0e4446236479cb5886faab41d9bba61 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Fri, 16 Dec 2016 14:40:13 +1100 Subject: [PATCH 085/109] Make mobile menu mimic the large menu --- app/views/shared/menu/_mobile_menu.html.haml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/views/shared/menu/_mobile_menu.html.haml b/app/views/shared/menu/_mobile_menu.html.haml index 066c3a8034..69757a02a6 100644 --- a/app/views/shared/menu/_mobile_menu.html.haml +++ b/app/views/shared/menu/_mobile_menu.html.haml @@ -51,6 +51,17 @@ %span.nav-primary %i.ofn-i_013-help = t 'label_learn' + - else + %li.li-menu + %a{href: main_app.groups_path} + %span.nav-primary + %i.ofn-i_035-groups + = t 'label_groups' + %li.li-menu + %a{href: ContentConfig.footer_about_url} + %span.nav-primary + %i.ofn-i_013-help + = t 'label_about' %li - if spree_current_user.nil? From 0b2281dfe2efaf876eb569bd2bd7c3574549460e Mon Sep 17 00:00:00 2001 From: Matt-Yorkley Date: Sat, 17 Dec 2016 16:07:39 +0000 Subject: [PATCH 086/109] Adjustment for chrome browser --- app/assets/stylesheets/admin/openfoodnetwork.css.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index f8035304f5..4b5703d317 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -203,6 +203,7 @@ text-angular { .ta-scroll-window > .ta-bind { max-height: 400px; min-height: 100px; + outline: none; p { margin-bottom: 1.5rem; } From 6d9bae8ef91e2835e2e20c0dbf9b343b107d481a Mon Sep 17 00:00:00 2001 From: Matt-Yorkley Date: Sat, 17 Dec 2016 20:39:13 +0000 Subject: [PATCH 087/109] Checkout layout adjustments for mobile view --- app/views/checkout/_payment.html.haml | 4 ++-- app/views/checkout/_shipping.html.haml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/checkout/_payment.html.haml b/app/views/checkout/_payment.html.haml index 8d387145d5..928578158f 100644 --- a/app/views/checkout/_payment.html.haml +++ b/app/views/checkout/_payment.html.haml @@ -16,7 +16,7 @@ -# TODO render this in Angular instead of server-side -# The problem being how to render the partials .row - .small-6.columns + .small-12.medium-12.large-6.columns - available_payment_methods.each do |method| .row .small-12.columns @@ -34,6 +34,6 @@ .row{"ng-if" => "order.payment_method_id == #{method.id}"} .small-12.columns = render partial: "spree/checkout/payment/#{method.method_type}", :locals => { :payment_method => method } - .small-12.columns.medium-6.columns.large-6.columns + .small-12.medium-12.large-6.columns #distributor_address.panel{"ng-show" => "Checkout.paymentMethod().description"} %span.pre-wrap {{ Checkout.paymentMethod().description }} diff --git a/app/views/checkout/_shipping.html.haml b/app/views/checkout/_shipping.html.haml index 4e6e63d666..6cba461be0 100644 --- a/app/views/checkout/_shipping.html.haml +++ b/app/views/checkout/_shipping.html.haml @@ -13,7 +13,7 @@ "ng-class" => "{valid: shipping.$valid, open: accordion.shipping}"} = render 'checkout/accordion_heading' - .small-12.columns.medium-6.columns.large-6.columns + .small-12.columns.medium-12.columns.large-6.columns %label{"ng-repeat" => "method in ShippingMethods.shipping_methods"} %input{type: :radio, required: true, @@ -38,7 +38,7 @@ %input{type: :checkbox, "ng-model" => "Checkout.default_ship_address"} = t :checkout_default_ship_address - .small-12.columns.medium-6.columns.large-6.columns + .small-12.columns.medium-12.columns.large-6.columns #distributor_address.panel{"ng-show" => "Checkout.shippingMethod().description"} %span{ style: "white-space: pre-wrap;" }{{ Checkout.shippingMethod().description }} %br/ From 7531c8cbc9eb0010e88c44db3966611c29ee792e Mon Sep 17 00:00:00 2001 From: Matt-Yorkley Date: Mon, 19 Dec 2016 11:10:12 +0000 Subject: [PATCH 088/109] Removed dashes --- app/views/checkout/_authentication.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/checkout/_authentication.html.haml b/app/views/checkout/_authentication.html.haml index 45d977d8e3..d157901059 100644 --- a/app/views/checkout/_authentication.html.haml +++ b/app/views/checkout/_authentication.html.haml @@ -9,7 +9,7 @@ %button.primary.expand{"auth" => "login"} = t :label_login .small-2.columns.text-center - %p.pad-top= "-#{t :action_or}-" + %p.pad-top= "#{t :action_or}" .small-5.columns.text-center %button.neutral-btn.dark.expand{"ng-click" => "enabled = true"} = t :checkout_as_guest From d77f775c4cf1158d97daa4d360c4d72abb94559d Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Mon, 19 Dec 2016 15:06:43 +0000 Subject: [PATCH 089/109] Moving commits from 1241 to trigger build on UK staging --- .../filters/filter_products.js.coffee | 2 +- .../darkswarm/services/products.js.coffee | 3 +- .../consumer/shopping/shopping_spec.rb | 34 ++++++++++++++++++- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/darkswarm/filters/filter_products.js.coffee b/app/assets/javascripts/darkswarm/filters/filter_products.js.coffee index 4cc325e483..18402edc02 100644 --- a/app/assets/javascripts/darkswarm/filters/filter_products.js.coffee +++ b/app/assets/javascripts/darkswarm/filters/filter_products.js.coffee @@ -4,5 +4,5 @@ Darkswarm.filter 'products', (Matcher) -> text ?= "" return products if text == "" products.filter (product) => - propertiesToMatch = [product.name, product.supplier.name, product.primary_taxon.name] + propertiesToMatch = [product.name, product.variant_names, product.supplier.name, product.primary_taxon.name] Matcher.match propertiesToMatch, text diff --git a/app/assets/javascripts/darkswarm/services/products.js.coffee b/app/assets/javascripts/darkswarm/services/products.js.coffee index b120cab737..63e015e5ba 100644 --- a/app/assets/javascripts/darkswarm/services/products.js.coffee +++ b/app/assets/javascripts/darkswarm/services/products.js.coffee @@ -25,7 +25,6 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Pro prices = (v.price for v in product.variants) product.price = Math.min.apply(null, prices) product.hasVariants = product.variants?.length > 0 - product.primaryImage = product.images[0]?.small_url if product.images product.primaryImageOrMissing = product.primaryImage || "/assets/noimage/small.png" product.largeImage = product.images[0]?.large_url if product.images @@ -45,5 +44,7 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Pro if product.variants product.variants = for variant in product.variants variant = Variants.register variant + if product.name != variant.name_to_display + product.variant_names += variant.name_to_display variant.product = product variant diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index b449b4d083..e11f929f6a 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -142,7 +142,9 @@ feature "As a consumer I want to shop with a distributor", js: true do describe "after selecting an order cycle with products visible" do let(:variant1) { create(:variant, product: product, price: 20) } - let(:variant2) { create(:variant, product: product, price: 30) } + let(:variant2) { create(:variant, product: product, price: 30, display_name: "Badgers") } + let(:product2) { create(:simple_product, supplier: supplier, name: "Meercats") } + let(:variant3) { create(:variant, product: product2, price: 40, display_name: "Ferrets") } let(:exchange) { Exchange.find(oc1.exchanges.to_enterprises(distributor).outgoing.first.id) } before do @@ -150,6 +152,7 @@ feature "As a consumer I want to shop with a distributor", js: true do add_variant_to_order_cycle(exchange, variant) add_variant_to_order_cycle(exchange, variant1) add_variant_to_order_cycle(exchange, variant2) + add_variant_to_order_cycle(exchange, variant3) order.order_cycle = oc1 end @@ -171,6 +174,35 @@ feature "As a consumer I want to shop with a distributor", js: true do # Product price should be listed as the lesser of these page.should have_price "$43.00" end + + it "filters search results properly" do + visit shop_path + select "frogs", :from => "order_cycle_id" + + fill_in "search", with: "74576345634XXXXXX" + page.should have_content "Sorry, no results found" + page.should_not have_content product2.name + + fill_in "search", with: "Meer" # For product named "Meercats" + page.should have_content product2.name + page.should_not have_content product.name + end + + it "returns search results for products where the search term matches one of the product's variant names" do + visit shop_path + select "frogs", :from => "order_cycle_id" + + fill_in "search", with: "Badg" # For variant with display_name "Badgers" + + within('div.pad-top') do + page.should have_content product.name + page.should have_content variant2.display_name + page.should_not have_content product2.name + page.should_not have_content variant3.display_name + end + + end + end describe "group buy products" do From 7cd7e7367b5a7eef3d82c619c0afeb8486bba55a Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Thu, 22 Dec 2016 12:09:44 +0000 Subject: [PATCH 090/109] Adding UK TOS to repo --- public/Terms-of-ServiceUK.pdf | Bin 0 -> 156523 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/Terms-of-ServiceUK.pdf diff --git a/public/Terms-of-ServiceUK.pdf b/public/Terms-of-ServiceUK.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e69c013f238b9d7189e85493d046ee2026f057c3 GIT binary patch literal 156523 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

-oc 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 literal 0 HcmV?d00001 From 6ae1272281e2142c666401883f8864185045419b Mon Sep 17 00:00:00 2001 From: Levent Ali Date: Wed, 26 Oct 2016 16:59:01 +0100 Subject: [PATCH 091/109] Mark order cycle form as dirty when removing fees Resolves an issue where removing coordinator/exchange fees wasn't allowing users to save the OC. Fixes #1165 --- .../javascripts/admin/order_cycles/controllers/edit.js.coffee | 2 ++ spec/javascripts/unit/order_cycle_spec.js.coffee | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee index c0818acafa..0e1a0ab45c 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee @@ -68,6 +68,7 @@ angular.module('admin.orderCycles') $scope.removeCoordinatorFee = ($event, index) -> $event.preventDefault() OrderCycle.removeCoordinatorFee(index) + $scope.order_cycle_form.$dirty = true $scope.addExchangeFee = ($event, exchange) -> $event.preventDefault() @@ -76,6 +77,7 @@ angular.module('admin.orderCycles') $scope.removeExchangeFee = ($event, exchange, index) -> $event.preventDefault() OrderCycle.removeExchangeFee(exchange, index) + $scope.order_cycle_form.$dirty = true $scope.removeDistributionOfVariant = (variant_id) -> OrderCycle.removeDistributionOfVariant(variant_id) diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index 5fec8b93f5..ac7e52a366 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -300,6 +300,7 @@ describe 'OrderCycle controllers', -> scope.removeCoordinatorFee(event, 0) expect(event.preventDefault).toHaveBeenCalled() expect(OrderCycle.removeCoordinatorFee).toHaveBeenCalledWith(0) + expect(scope.order_cycle_form.$dirty).toEqual true it 'Adds exchange fees', -> scope.addExchangeFee(event) @@ -310,6 +311,7 @@ describe 'OrderCycle controllers', -> scope.removeExchangeFee(event, 'exchange', 0) expect(event.preventDefault).toHaveBeenCalled() expect(OrderCycle.removeExchangeFee).toHaveBeenCalledWith('exchange', 0) + expect(scope.order_cycle_form.$dirty).toEqual true it 'Removes distribution of a variant', -> scope.removeDistributionOfVariant('variant') From f62c5831308d6ba74a42f1af9c9cf00f55ab4c8d Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Tue, 3 Jan 2017 16:11:56 +0000 Subject: [PATCH 092/109] uk translation updates --- config/locales/en-GB.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index bda8867858..76cf001e47 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -1,4 +1,4 @@ -en-GB: +en_GB: activerecord: attributes: spree/order: @@ -11,7 +11,7 @@ en-GB: Were you a guest last time? Perhaps you need to create an account or reset your password. enterprise_confirmations: enterprise: - confirmed: Thank you, your email address has been confirmed. + confirmed: Thankyou, your email address has been confirmed. not_confirmed: Your email address could not be confirmed. Perhaps you have already completed this step? confirmation_sent: "Confirmation email sent!" confirmation_not_sent: "Could not send a confirmation email." @@ -522,14 +522,14 @@ en-GB: producers_title: Producers producers_headline: Find local producers producers_signup_title: Sign up as a producer - producers_signup_headline: Food producers, empowered. - producers_signup_motivation: Sell your food and tell your stories to diverse new markets. Save time and money on every overhead. We support innovation without the risk. We've levelled the playing field. - producers_signup_send: Join now - producers_signup_enterprise: Enterprise Accounts + producers_signup_headline: Food producers. Meet OFN. + producers_signup_motivation: Sell your food and tell your stories. Save time and money on every overhead. We support innovation without the risk. We're levelling the playing field. + producers_signup_send: Register + producers_signup_enterprise: Why the Open Food Network? producers_signup_studies: Stories from our producers. producers_signup_cta_headline: Join now! - producers_signup_cta_action: Join now - producers_signup_detail: Here's the detail. + producers_signup_cta_action: Register + producers_signup_detail: Got a question? products_item: Item products_description: Description products_variant: Variant @@ -537,7 +537,7 @@ en-GB: products_available: Available? products_producer: "Producer" products_price: "Price" - register_title: Register + register_title: Find out more sell_title: "Register" sell_headline: "Get on the Open Food Network!" sell_motivation: "Showcase your beautiful food." @@ -875,7 +875,7 @@ en-GB: spree_admin_single_enterprise_alert_mail_confirmation: "Please confirm the email address for" spree_admin_single_enterprise_alert_mail_sent: "We've sent an email to" spree_admin_overview_action_required: "Action Required" - spree_admin_overview_check_your_inbox: "Please check your inbox for further instructions. Thanks!" + spree_admin_overview_check_your_inbox: "Please check you inbox for further instructions. Thanks!" change_package: "Change Package" spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under" your_profil_live: "Your profile live" From d17f9dc504aaac8fe2a2d00e6558436a75d1d835 Mon Sep 17 00:00:00 2001 From: Lynne Davis Date: Tue, 3 Jan 2017 16:35:01 +0000 Subject: [PATCH 093/109] update --- config/locales/en-GB.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 76cf001e47..92eba244eb 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -1,4 +1,4 @@ -en_GB: +en-GB: activerecord: attributes: spree/order: From bd1cd527d38efa5ecba2f725160eb277e2d3e1bd Mon Sep 17 00:00:00 2001 From: elf Pavlik Date: Tue, 3 Jan 2017 20:57:10 -0600 Subject: [PATCH 094/109] configurable google maps api key --- app/views/layouts/darkswarm.html.haml | 2 +- config/application.yml.example | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index d3f0a78087..6998b99ed7 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -13,7 +13,7 @@ %link{href: "https://fonts.googleapis.com/css?family=Roboto:400,300italic,400italic,300,700,700italic|Oswald:300,400,700", rel: "stylesheet", type: "text/css"} = yield :scripts - %script{src: "//maps.googleapis.com/maps/api/js?libraries=places,geometry"} + %script{src: "//maps.googleapis.com/maps/api/js?libraries=places,geometry&key=#{ENV['GOOGLE_MAPS_API_KEY']}"} = split_stylesheet_link_tag "darkswarm/all" = javascript_include_tag "darkswarm/all" diff --git a/config/application.yml.example b/config/application.yml.example index 0b1871466b..b3b21c39ad 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -21,3 +21,5 @@ CURRENCY: AUD # # DISCOURSE_URL must be the URL of your Discourse instance. #DISCOURSE_URL: "https://noticeboard.openfoodnetwork.org.au" + +GOOGLE_MAPS_API_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx From 764c95488c28ed00221ccd68e8e09969fdfac027 Mon Sep 17 00:00:00 2001 From: elf Pavlik Date: Sat, 7 Jan 2017 09:27:37 -0600 Subject: [PATCH 095/109] made api key optional (google maps) --- app/views/layouts/darkswarm.html.haml | 2 +- config/application.yml.example | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml index 6998b99ed7..20a47be970 100644 --- a/app/views/layouts/darkswarm.html.haml +++ b/app/views/layouts/darkswarm.html.haml @@ -13,7 +13,7 @@ %link{href: "https://fonts.googleapis.com/css?family=Roboto:400,300italic,400italic,300,700,700italic|Oswald:300,400,700", rel: "stylesheet", type: "text/css"} = yield :scripts - %script{src: "//maps.googleapis.com/maps/api/js?libraries=places,geometry&key=#{ENV['GOOGLE_MAPS_API_KEY']}"} + %script{src: "//maps.googleapis.com/maps/api/js?libraries=places,geometry#{ ENV['GOOGLE_MAPS_API_KEY'] ? '&key=' + ENV['GOOGLE_MAPS_API_KEY'] : ''} "} = split_stylesheet_link_tag "darkswarm/all" = javascript_include_tag "darkswarm/all" diff --git a/config/application.yml.example b/config/application.yml.example index b3b21c39ad..5b38a6da60 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -22,4 +22,5 @@ CURRENCY: AUD # DISCOURSE_URL must be the URL of your Discourse instance. #DISCOURSE_URL: "https://noticeboard.openfoodnetwork.org.au" -GOOGLE_MAPS_API_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +# see: https://developers.google.com/maps/documentation/javascript/get-api-key +# GOOGLE_MAPS_API_KEY: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx From 5cb2194f5e6541caf29aabbb579635c9a9a1baae Mon Sep 17 00:00:00 2001 From: Paul Mackay Date: Tue, 10 Jan 2017 11:39:17 +0000 Subject: [PATCH 096/109] #1027: Add sitemap.xml generation --- app/controllers/sitemap_controller.rb | 11 +++++++++++ app/views/sitemap/index.xml.haml | 18 ++++++++++++++++++ config/routes.rb | 2 ++ spec/features/consumer/sitemap_spec.rb | 12 ++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 app/controllers/sitemap_controller.rb create mode 100644 app/views/sitemap/index.xml.haml create mode 100644 spec/features/consumer/sitemap_spec.rb diff --git a/app/controllers/sitemap_controller.rb b/app/controllers/sitemap_controller.rb new file mode 100644 index 0000000000..f5ce44ceca --- /dev/null +++ b/app/controllers/sitemap_controller.rb @@ -0,0 +1,11 @@ +class SitemapController < ApplicationController + layout nil + + def index + headers['Content-Type'] = 'application/xml' + @pages = ['shops', 'map', 'producers', 'groups'] + @enterprises = Enterprise.is_hub + @groups = EnterpriseGroup.all + respond_to :xml + end +end diff --git a/app/views/sitemap/index.xml.haml b/app/views/sitemap/index.xml.haml new file mode 100644 index 0000000000..3c07cc6ebc --- /dev/null +++ b/app/views/sitemap/index.xml.haml @@ -0,0 +1,18 @@ +!!! XML +%urlset{xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9"} + - for page in @pages + %url + %loc= root_url + page + %changefreq monthly + + - for enterprise in @enterprises + %url + %loc= enterprise_shop_url(enterprise) + %lastmod= enterprise.updated_at.strftime('%Y-%m-%d') + %changefreq weekly + + - for group in @groups + %url + %loc= group_url(group) + %lastmod= enterprise.updated_at.strftime('%Y-%m-%d') + %changefreq yearly diff --git a/config/routes.rb b/config/routes.rb index 69319ae84a..607e6308a2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -169,6 +169,8 @@ Openfoodnetwork::Application.routes.draw do end end + get 'sitemap.xml', to: 'sitemap#index', defaults: { format: 'xml' } + # Mount Spree's routes mount Spree::Core::Engine, :at => '/' diff --git a/spec/features/consumer/sitemap_spec.rb b/spec/features/consumer/sitemap_spec.rb new file mode 100644 index 0000000000..6ec913de32 --- /dev/null +++ b/spec/features/consumer/sitemap_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +feature 'sitemap' do + let(:enterprise) { create(:distributor_enterprise) } + let!(:group) { create(:enterprise_group, enterprises: [enterprise], on_front_page: true) } + + it "renders sitemap" do + visit '/sitemap.xml' + expect(page).to have_content enterprise_shop_url(enterprise) + expect(page).to have_content group_url(group) + end +end From fc400741b4491d2041ba30d32c0727563fedf2ac Mon Sep 17 00:00:00 2001 From: Paul Mackay Date: Tue, 10 Jan 2017 11:44:57 +0000 Subject: [PATCH 097/109] Remove lastmod for groups --- app/views/sitemap/index.xml.haml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/sitemap/index.xml.haml b/app/views/sitemap/index.xml.haml index 3c07cc6ebc..f961b75899 100644 --- a/app/views/sitemap/index.xml.haml +++ b/app/views/sitemap/index.xml.haml @@ -14,5 +14,4 @@ - for group in @groups %url %loc= group_url(group) - %lastmod= enterprise.updated_at.strftime('%Y-%m-%d') %changefreq yearly From 88dea0f2b8c60d846d97dd591000b504d2e171b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Turbelin?= Date: Tue, 13 Dec 2016 16:53:32 +0100 Subject: [PATCH 098/109] Adding i18n keys on Admin side (Rails views) --- app/views/admin/account/show.html.haml | 2 - .../edit.html.haml | 2 +- app/views/admin/cache_settings/show.html.haml | 8 +- app/views/admin/contents/edit.html.haml | 3 +- .../admin/enterprise_fees/index.html.haml | 14 +- .../admin/enterprise_groups/index.html.haml | 2 +- .../admin/enterprises/_actions.html.haml | 16 +- .../admin/enterprises/_admin_index.html.haml | 14 +- .../enterprises/_change_type_form.html.haml | 46 ++-- .../_enterprise_user_index.html.haml | 4 +- .../admin/enterprises/_new_form.html.haml | 63 +++-- .../admin/enterprises/_ng_form.html.haml | 4 +- app/views/admin/enterprises/edit.html.haml | 7 +- .../enterprises/form/_about_us.html.haml | 8 +- .../admin/enterprises/form/_address.html.haml | 21 +- .../form/_business_details.html.haml | 12 +- .../admin/enterprises/form/_contact.html.haml | 16 +- .../form/_enterprise_fees.html.haml | 10 +- .../admin/enterprises/form/_images.html.haml | 10 +- .../form/_inventory_settings.html.haml | 11 +- .../form/_payment_methods.html.haml | 14 +- .../form/_primary_details.html.haml | 54 ++-- .../form/_shipping_methods.html.haml | 12 +- .../form/_shop_preferences.html.haml | 8 +- .../admin/enterprises/form/_social.html.haml | 2 +- .../enterprises/form/_tag_rules.html.haml | 10 +- .../admin/enterprises/form/_users.html.haml | 27 +- .../form/tag_rules/_default_rules.html.haml | 6 +- app/views/admin/enterprises/index.html.haml | 6 +- app/views/admin/enterprises/new.html.haml | 6 +- app/views/admin/enterprises/welcome.html.haml | 8 +- .../order_cycles/_advanced_settings.html.haml | 16 +- .../order_cycles/_coordinator_fees.html.haml | 4 +- app/views/admin/order_cycles/_form.html.haml | 39 ++- .../_name_and_timing_form.html.haml | 8 +- app/views/admin/order_cycles/_row.html.haml | 8 +- .../admin/order_cycles/_simple_form.html.haml | 14 +- app/views/admin/order_cycles/edit.html.haml | 10 +- app/views/admin/order_cycles/index.html.haml | 2 +- app/views/admin/order_cycles/new.html.haml | 2 +- .../admin/producer_properties/_form.html.haml | 4 +- .../admin/producer_properties/index.html.haml | 5 +- .../admin/shared/_user_guide_link.html.haml | 2 +- .../_loading_flash.html.haml | 2 +- .../variant_overrides/_show_more.html.haml | 6 +- config/locales/en.yml | 258 +++++++++++++++++- config/locales/fr.yml | 258 ++++++++++++++++++ 47 files changed, 784 insertions(+), 280 deletions(-) diff --git a/app/views/admin/account/show.html.haml b/app/views/admin/account/show.html.haml index 808402ed16..b052b5b0a0 100644 --- a/app/views/admin/account/show.html.haml +++ b/app/views/admin/account/show.html.haml @@ -1,8 +1,6 @@ - - content_for :page_title do = t(:account) - - if @invoices.empty? %h4= t(:no_invoices_to_display) diff --git a/app/views/admin/accounts_and_billing_settings/edit.html.haml b/app/views/admin/accounts_and_billing_settings/edit.html.haml index 72ce7920a9..83cfbbb76e 100644 --- a/app/views/admin/accounts_and_billing_settings/edit.html.haml +++ b/app/views/admin/accounts_and_billing_settings/edit.html.haml @@ -9,7 +9,7 @@ %fieldset.no-border-bottom %legend - =t :admin_settings + = t(:admin_settings) = form_for @settings, as: :settings, url: main_app.admin_accounts_and_billing_settings_path, :method => :put do |f| .row{ ng: { app: 'admin.accounts_and_billing_settings' } } .twelve.columns.alpha.omega diff --git a/app/views/admin/cache_settings/show.html.haml b/app/views/admin/cache_settings/show.html.haml index 79b3e5acaf..f2069f0e55 100644 --- a/app/views/admin/cache_settings/show.html.haml +++ b/app/views/admin/cache_settings/show.html.haml @@ -4,10 +4,10 @@ %table.index %thead %tr - %th Distributor - %th Order Cycle - %th Status - %th Diff + %th= t('.distributor') + %th= t('.order_cycle') + %th= t('.status') + %th= t('.diff') %tbody - @results.each do |result| %tr diff --git a/app/views/admin/contents/edit.html.haml b/app/views/admin/contents/edit.html.haml index 82751abebc..e0bdb0adc0 100644 --- a/app/views/admin/contents/edit.html.haml +++ b/app/views/admin/contents/edit.html.haml @@ -1,8 +1,7 @@ = render 'spree/admin/shared/configuration_menu' - content_for :page_title do - Content - + = t('.title') = form_tag main_app.admin_content_path, method: :put, multipart: true do #preferences diff --git a/app/views/admin/enterprise_fees/index.html.haml b/app/views/admin/enterprise_fees/index.html.haml index ec62fa5d36..3a2dae96b1 100644 --- a/app/views/admin/enterprise_fees/index.html.haml +++ b/app/views/admin/enterprise_fees/index.html.haml @@ -1,5 +1,5 @@ = content_for :page_title do - =t :Enterprise_Fees + = t('.title') = ng_form_for @enterprise_fee_set, :url => main_app.bulk_update_admin_enterprise_fees_path, :html => {'ng-app' => 'admin.enterpriseFees', 'ng-controller' => 'enterpriseFeesCtrl'} do |enterprise_fee_set_form| = hidden_field_tag 'enterprise_id', @enterprise.id if @enterprise @@ -12,17 +12,17 @@ %thead %tr %th - =t'Enterprise' + = t('.enterprise') %th - =t'fee_type' + = t('.fee_type') %th - =t'name' + = t('.name') %th - =t'tax_category' + = t('.tax_category') %th - =t'calculator' + = t('.calculator') %th - =t'calculator_values' + = t('.calculator_values') %th.actions %tbody = enterprise_fee_set_form.ng_fields_for :collection do |f| diff --git a/app/views/admin/enterprise_groups/index.html.haml b/app/views/admin/enterprise_groups/index.html.haml index 747eff0b94..2540c64980 100644 --- a/app/views/admin/enterprise_groups/index.html.haml +++ b/app/views/admin/enterprise_groups/index.html.haml @@ -3,7 +3,7 @@ - if admin_user? = content_for :page_actions do - %li= button_link_to "New Enterprise Group", main_app.new_admin_enterprise_group_path + %li= button_link_to t('.new_button'), main_app.new_admin_enterprise_group_path %table.index#listing_enterprise_groups %thead diff --git a/app/views/admin/enterprises/_actions.html.haml b/app/views/admin/enterprises/_actions.html.haml index 5bcfc7a512..d5b3ae14a9 100644 --- a/app/views/admin/enterprises/_actions.html.haml +++ b/app/views/admin/enterprises/_actions.html.haml @@ -1,4 +1,4 @@ -= link_to_with_icon('icon-edit', 'Edit Profile', main_app.edit_admin_enterprise_path(enterprise), class: 'edit') += link_to_with_icon('icon-edit', t('.edit_profile'), main_app.edit_admin_enterprise_path(enterprise), class: 'edit') %br/ - if can? :destroy, enterprise @@ -6,27 +6,27 @@ %br/ - if enterprise.is_primary_producer - = link_to_with_icon 'icon-dashboard', 'Properties', main_app.admin_enterprise_producer_properties_path(enterprise_id: enterprise) + = link_to_with_icon 'icon-dashboard', t('.properties'), main_app.admin_enterprise_producer_properties_path(enterprise_id: enterprise) (#{enterprise.producer_properties.count}) %br/ - if enterprise.is_distributor - if can?(:admin, Spree::PaymentMethod) && can?(:manage_payment_methods, enterprise) - = link_to_with_icon 'icon-chevron-right', 'Payment Methods', spree.admin_payment_methods_path(enterprise_id: enterprise.id) + = link_to_with_icon 'icon-chevron-right', t('.payment_methods'), spree.admin_payment_methods_path(enterprise_id: enterprise.id) (#{enterprise.payment_methods.count}) - if enterprise.payment_methods.count == 0 - %span.icon-exclamation-sign{"ofn-with-tip" => "This enterprise has no payment methods", style: "font-size: 16px;color: #DA5354"} + %span.icon-exclamation-sign{"ofn-with-tip" => t('.payment_methods_tip'), style: "font-size: 16px;color: #DA5354"} %br/ - if can?(:admin, Spree::ShippingMethod) && can?(:manage_shipping_methods, enterprise) - = link_to_with_icon 'icon-plane', 'Shipping Methods', spree.admin_shipping_methods_path(enterprise_id: enterprise.id) + = link_to_with_icon 'icon-plane', t('.shipping_methods'), spree.admin_shipping_methods_path(enterprise_id: enterprise.id) (#{enterprise.shipping_methods.count}) - if enterprise.shipping_methods.count == 0 - %span.icon-exclamation-sign{"ofn-with-tip" => "This enterprise has shipping methods", style: "font-size: 16px;color: #DA5354"} + %span.icon-exclamation-sign{"ofn-with-tip" => t('.shipping_methods_tip'), style: "font-size: 16px;color: #DA5354"} %br/ - if can?(:admin, EnterpriseFee) && can?(:manage_enterprise_fees, enterprise) - = link_to_with_icon 'icon-money', 'Enterprise Fees', main_app.admin_enterprise_fees_path(enterprise_id: enterprise.id) + = link_to_with_icon 'icon-money', t('.enterprise_fees'), main_app.admin_enterprise_fees_path(enterprise_id: enterprise.id) (#{enterprise.enterprise_fees.count}) - if enterprise.enterprise_fees.count == 0 - %span.icon-warning-sign{"ofn-with-tip" => "This enterprise has no fees", style: "font-size: 16px;color: orange"} + %span.icon-warning-sign{"ofn-with-tip" => t('.enterprise_fees_tip'), style: "font-size: 16px;color: orange"} diff --git a/app/views/admin/enterprises/_admin_index.html.haml b/app/views/admin/enterprises/_admin_index.html.haml index 2359f45f78..5e1eb4c92c 100644 --- a/app/views/admin/enterprises/_admin_index.html.haml +++ b/app/views/admin/enterprises/_admin_index.html.haml @@ -15,13 +15,13 @@ %col{style: "width: 25%;"}/ %thead %tr{"data-hook" => "enterprises_header"} - %th Name - %th Role + %th= t('.name') + %th= t('.role') - if spree_current_user.admin? - %th Sells - %th Visible? + %th= t('.sells') + %th= t('.visible') - if spree_current_user.admin? - %th Owner + %th= t('.owner') %th %tbody = f.fields_for :collection do |enterprise_form| @@ -30,7 +30,7 @@ %td= link_to enterprise.name, main_app.edit_admin_enterprise_path(enterprise) %td = enterprise_form.check_box :is_primary_producer - Producer + = t('.producer') - if spree_current_user.admin? %td= enterprise_form.select :sells, Enterprise::SELLS, {}, class: 'select2 fullwidth' %td= enterprise_form.check_box :visible @@ -41,4 +41,4 @@ - if @enterprises.empty? %tr %td{colspan: "4"}= t(:none) - = f.submit 'Update' + = f.submit t(:update) diff --git a/app/views/admin/enterprises/_change_type_form.html.haml b/app/views/admin/enterprises/_change_type_form.html.haml index ac5ca55821..a7c07ab87b 100644 --- a/app/views/admin/enterprises/_change_type_form.html.haml +++ b/app/views/admin/enterprises/_change_type_form.html.haml @@ -12,36 +12,36 @@ .basic_producer.option.one-third.column.alpha %a.full-width.button.selector{ ng: { click: "sells='none'", class: "{selected: sells=='none'}" } } .top - %h3 Producer Profile - %p Connect through OFN - .bottom ALWAYS FREE + %h3= t('.producer_profile') + %p= t('.connect_ofn') + .bottom= t('.always_free') %p.description - Add your products to Open Food Network, allowing hubs to stock your products in their stores. + = t('.producer_description_text') .producer_shop.option.one-third.column %a.full-width.button.selector{ ng: { click: "sells='own'", class: "{selected: sells=='own'}" } } .top - %h3 Producer Shop - %p Sell your own produce + %h3= t('.producer_shop') + %p= t('.sell_your_produce') .bottom %monthly-pricing-description{ joiner: "newline" } %p.description - Sell your products directly to customers through your very own Open Food Network shopfront. + = t('.producer_description_text') %br %br - A Producer Shop is for your produce only, if you want to sell produce grown/produced off site, select 'Producer Hub'. + = t('.producer_description_text2') .full_hub.option.one-third.column.omega %a.full-width.button.selector{ ng: { click: "sells='any'", class: "{selected: sells=='any'}" } } .top - %h3 Producer Hub - %p Sell produce from self and others + %h3= t('.producer_hub') + %p= t('.producer_hub_text') .bottom %monthly-pricing-description{ joiner: "newline" } %p.description - Your enterprise is the backbone of your local food system. You can sell your own produce as well as produce aggregated from other enterprises through your shopfront on the Open Food Network. + = t('.producer_hub_description_text') -# %p.description -# Test out having your own shopfront with full access to all Shopfront features for 30 days. @@ -55,38 +55,38 @@ .shop_profile.option.six.columns %a.full-width.button.selector{ ng: { click: "sells='none'", class: "{selected: sells=='none'}" } } .top - %h3 Profile Only - %p Get a listing - .bottom ALWAYS FREE + %h3= t('.profile') + %p= t('.get_listing') + .bottom= t('.always_free') %p.description - People can find and contact you on the Open Food Network. Your enterprise will be visible on the map, and will be searchable in listings. + = t('.profile_description_text') .full_hub.option.six.columns %a.full-width.button.selector{ ng: { click: "sells='any'", class: "{selected: sells=='any'}" } } .top - %h3 Hub Shop - %p Sell produce from others + %h3= t('.hub_shop') + %p= t('.hub_shop_text') .bottom %monthly-pricing-description{ joiner: "newline" } %p.description - Your enterprise is the backbone of your local food system. You aggregate produce from other enterprises and can sell it through your shop on the Open Food Network. + = t('.hub_shop_description_text') .two.columns.omega   .row .sixteen.columns.alpha %span.error{ ng: { show: "(change_type.sells.$error.required || change_type.sells.$error.pattern) && submitted" } } - Please choose one of the options above. + = t('.choose_option') - if @enterprise.sells == 'unspecified' && @enterprise.shop_trial_start_date.nil? -if free_use? - %input.button.big{ type: 'submit', value: 'Select and continue', ng: { click: "submit(change_type)" } } + %input.button.big{ type: 'submit', value: t(:select_continue), ng: { click: "submit(change_type)" } } - else - trial_length = Spree::Config[:shop_trial_length_days] %input.button.big{ type: 'submit', value: "Start #{trial_length}-Day Shop Trial", ng: { click: "submit(change_type)", show: "sells=='own' || sells=='any'" } } - %input.button.big{ type: 'submit', value: 'Select and continue', ng: { click: "submit(change_type)", hide: "sells=='own' || sells=='any'" } } + %input.button.big{ type: 'submit', value: t(:select_continue), ng: { click: "submit(change_type)", hide: "sells=='own' || sells=='any'" } } - elsif @enterprise.sells == 'unspecified' - %input.button.big{ type: 'submit', value: 'Select and continue', ng: { click: "submit(change_type)" } } + %input.button.big{ type: 'submit', value: t(:select_continue), ng: { click: "submit(change_type)" } } - else - %input.button.big{ type: 'submit', value: 'Change now', ng: { click: "submit(change_type)" } } + %input.button.big{ type: 'submit', value: t('.change_now'), ng: { click: "submit(change_type)" } } %br   %hr diff --git a/app/views/admin/enterprises/_enterprise_user_index.html.haml b/app/views/admin/enterprises/_enterprise_user_index.html.haml index d89970946d..4b777e127b 100644 --- a/app/views/admin/enterprises/_enterprise_user_index.html.haml +++ b/app/views/admin/enterprises/_enterprise_user_index.html.haml @@ -2,7 +2,7 @@ .row{ 'ng-hide' => '!loaded' } .controls{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px;" } .four.columns.alpha - %input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Search By Name' } + %input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => t('.search_placeholder') } .six.columns   -# = render 'admin/shared/bulk_actions_dropdown' .three.columns   @@ -41,7 +41,7 @@ %i.icon-status{ ng: { class: "enterprise.status" } } %td.manage{ ng: { show: 'columns.manage.visible' } } %a.button.fullwidth{ ng: { href: '{{::enterprise.edit_path}}' } } - Manage + = t('.manage') %i.icon-arrow-right %tr.panel-row{ object: "enterprise", panels: "{producer: 'enterprise_producer', package: 'enterprise_package', status: 'enterprise_status'}" } diff --git a/app/views/admin/enterprises/_new_form.html.haml b/app/views/admin/enterprises/_new_form.html.haml index ba8fce56ae..f04e55acd6 100644 --- a/app/views/admin/enterprises/_new_form.html.haml +++ b/app/views/admin/enterprises/_new_form.html.haml @@ -1,98 +1,97 @@ .row .three.columns.alpha - = f.label :name + = f.label :name, t('admin.enterprises.form.primary_details.name') %span.required * .nine.columns.omega - = f.text_field :name, { placeholder: "eg. Professor Plum's Biodynamic Truffles", class: "fullwidth" } + = f.text_field :name, { placeholder: t('admin.enterprises.form.primary_details.name_placeholder'), class: "fullwidth" } - if spree_current_user.admin? .row .three.columns.alpha - =f.label :owner_id, 'Owner' + =f.label :owner_id, t('.owner') %span.required * - %div{'ofn-with-tip' => "The primary user responsible for this enterprise."} - %a What's this? + %div{'ofn-with-tip' => t('.owner_tip')} + %a= t('admin.whats_this') .nine.columns.omega - owner_email = @enterprise.andand.owner.andand.email || "" = f.hidden_field :owner_id, class: "select2 fullwidth", 'user-select' => 'Enterprise.owner' .row .three.columns.alpha - %label Primary Producer? - %div{'ofn-with-tip' => "Select 'Producer' if you are a primary producer of food."} - %a What's this? + %label= t('admin.enterprises.form.primary_details.primary_producer') + %div{'ofn-with-tip' => t('admin.enterprises.form.primary_details.primary_producer_tip')} + %a= t('admin.whats_this') .five.columns.omega = f.check_box :is_primary_producer, 'ng-model' => 'Enterprise.is_primary_producer'   - = f.label :is_primary_producer, 'I am a Producer' + = f.label :is_primary_producer, t('.i_am_producer') - if spree_current_user.admin? .row .alpha.eleven.columns .three.columns.alpha - = f.label :sells, 'Sells' - %div{'ofn-with-tip' => "None - enterprise does not sell to customers directly.
Own - Enterprise sells own products to customers.
Any - Enterprise can sell own or other enterprises products.
"} + = f.label :sells, t('admin.enterprises.form.primary_details.sells') + %div{'ofn-with-tip' => t('admin.enterprises.form.primary_details.sells_tip')} %a What's this? .two.columns = f.radio_button :sells, "none", 'ng-model' => 'Enterprise.sells'   - = f.label :sells, "None", value: "none" + = f.label :sells, t('admin.enterprises.form.primary_details.none'), value: "none" .two.columns = f.radio_button :sells, "own", 'ng-model' => 'Enterprise.sells'   - = f.label :sells, "Own", value: "own" + = f.label :sells, t('admin.enterprises.form.primary_details.own'), value: "own" .four.columns.omega = f.radio_button :sells, "any", 'ng-model' => 'Enterprise.sells'   - = f.label :sells, "Any", value: "any" - + = f.label :sells, t('admin.enterprises.form.primary_details.any'), value: "any" .row .alpha.three.columns - = f.label :contact, 'Contact Name' + = f.label :contact, t('.contact_name') .omega.nine.columns - = f.text_field :contact, { placeholder: "eg. Gustav Plum"} + = f.text_field :contact, { placeholder: t('admin.enterprises.form.contact.name_placeholder')} .row .alpha.three.columns - = f.label :email_address + = f.label :email_address, t('admin.enterprises.form.contact.email_address') .omega.nine.columns - = f.text_field :email_address, { placeholder: "eg. gustav@truffles.com", "ng-model" => "Enterprise.email_address" } + = f.text_field :email_address, { placeholder: t('admin.enterprises.form.contact.email_address_placeholder'), "ng-model" => "Enterprise.email_address" } .row .alpha.three.columns - = f.label :phone + = f.label :phone, t('admin.enterprises.form.contact.phone') .omega.nine.columns - = f.text_field :phone, { placeholder: "eg. 98 7654 3210"} + = f.text_field :phone, { placeholder: t('admin.enterprises.form.contact.phone_placeholder')} .row .alpha.three.columns - = f.label :website + = f.label :website, t('admin.enterprises.form.contact.website') .omega.nine.columns - = f.text_field :website, { placeholder: "eg. www.truffles.com"} + = f.text_field :website, { placeholder: t('admin.enterprises.form.contact.website_placeholder')} = f.fields_for :address do |af| .row .three.columns.alpha - = af.label :address1 + = af.label :address1, t(:address) %span.required * .nine.columns.omega - = af.text_field :address1, { placeholder: "eg. 123 High Street"} + = af.text_field :address1, { placeholder: t(:address_placeholder)} .row .alpha.three.columns - = af.label :address2 + = af.label :address2, t(:address2) .nine.columns.omega = af.text_field :address2 .row .three.columns.alpha - = af.label :city, 'Suburb' + = af.label :city, t(:city) \/ - = af.label :zipcode, 'Postcode' + = af.label :zipcode, t(:postcode) %span.required * .four.columns - = af.text_field :city, { placeholder: "eg. Northcote"} + = af.text_field :city, { placeholder: t(:city_placeholder)} .five.columns.omega - = af.text_field :zipcode, { placeholder: "eg. 3070"} + = af.text_field :zipcode, { placeholder: t(:postcode_placeholder)} .row .three.columns.alpha - = af.label :state_id, 'State' + = af.label :state_id, t(:state) \/ - = af.label :country_id, 'Country' + = af.label :country_id, t(:country) .four.columns = af.collection_select :state_id, af.object.country.states, :id, :name, {}, :class => "select2 fullwidth" .five.columns.omega diff --git a/app/views/admin/enterprises/_ng_form.html.haml b/app/views/admin/enterprises/_ng_form.html.haml index ca38b7eca4..17b649bcc6 100644 --- a/app/views/admin/enterprises/_ng_form.html.haml +++ b/app/views/admin/enterprises/_ng_form.html.haml @@ -8,8 +8,8 @@ } do |f| %save-bar{ dirty: "enterprise_form.$dirty", persist: "true" } - %input.red{ type: "button", value: "Update", ng: { click: "submit()", disabled: "!enterprise_form.$dirty" } } - %input{ type: "button", ng: { value: "enterprise_form.$dirty ? 'Cancel' : 'Close'", click: "cancel('#{main_app.admin_enterprises_path}')" } } + %input.red{ type: "button", value: t(:update), ng: { click: "submit()", disabled: "!enterprise_form.$dirty" } } + %input{ type: "button", ng: { value: "enterprise_form.$dirty ? '#{t(:cancel)}' : '#{t(:close)}'", click: "cancel('#{main_app.admin_enterprises_path}')" } } diff --git a/app/views/admin/enterprises/edit.html.haml b/app/views/admin/enterprises/edit.html.haml index 5d3a623f40..2e53f3e2c0 100644 --- a/app/views/admin/enterprises/edit.html.haml +++ b/app/views/admin/enterprises/edit.html.haml @@ -1,12 +1,11 @@ -= render :partial => 'spree/shared/error_messages', :locals => { :target => @enterprise } += render partial: 'spree/shared/error_messages', locals: { target: @enterprise } - content_for :page_title do - Editing: + = t('.editing') = @enterprise.name - content_for :page_actions do - %li= button_link_to "Back to enterprises list", main_app.admin_enterprises_path, icon: 'icon-arrow-left' - + %li= button_link_to t('.back_link'), main_app.admin_enterprises_path, icon: 'icon-arrow-left' = render 'admin/enterprises/form_data' diff --git a/app/views/admin/enterprises/form/_about_us.html.haml b/app/views/admin/enterprises/form/_about_us.html.haml index a04437dad0..32550b711b 100644 --- a/app/views/admin/enterprises/form/_about_us.html.haml +++ b/app/views/admin/enterprises/form/_about_us.html.haml @@ -1,11 +1,11 @@ .row .alpha.three.columns - = f.label :description, 'Short Description' + = f.label :description, t('.desc_short') .omega.eight.columns - = f.text_field :description, maxlength: 255, placeholder: 'Tell us about your enterprise in one or two sentences' + = f.text_field :description, maxlength: 255, placeholder: t('.desc_short_placeholder') .row .alpha.three.columns - = f.label :long_description, 'About Us' + = f.label :long_description, t('.desc_long') .omega.eight.columns -# textAngular toolbar options, add to the ta-toolbar array below and separate into groups with extra ],[ if needed: -# ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'pre', 'quote'], @@ -14,4 +14,4 @@ -# ['html', 'insertImage', 'insertLink', 'insertVideo'] %text-angular{'ng-model' => 'Enterprise.long_description', 'id' => 'enterprise_long_description', 'name' => 'enterprise[long_description]', 'class' => 'text-angular', 'ta-toolbar' => "[['h1','h2','h3','h4','p'],['bold','italics','underline','clear'],['insertLink']]", - 'placeholder' => 'Tell customers about yourself. This information appears on your public profile.'} \ No newline at end of file + 'placeholder' => t('.desc_long_placeholder')} \ No newline at end of file diff --git a/app/views/admin/enterprises/form/_address.html.haml b/app/views/admin/enterprises/form/_address.html.haml index 68ae7d68bf..a1485d485f 100644 --- a/app/views/admin/enterprises/form/_address.html.haml +++ b/app/views/admin/enterprises/form/_address.html.haml @@ -1,34 +1,35 @@ -# redo denoting required fields in the whole project .row - Required fields are denoted with an asterisk ( + = t(:required_fields) + ( %span.required * ) .row .three.columns.alpha - = af.label :address1 + = af.label :address1, t(:address) %span.required * .eight.columns.omega - = af.text_field :address1, { placeholder: "eg. 123 High Street"} + = af.text_field :address1, { placeholder: t(:address_placeholder) } .row .alpha.three.columns - = af.label :address2 + = af.label :address2, t(:address2) .eight.columns.omega = af.text_field :address2 .row .three.columns.alpha - = af.label :city, 'Suburb' + = af.label :city, t(:city) \/ - = af.label :zipcode, 'Postcode' + = af.label :zipcode, t(:postcode) %span.required * .four.columns - = af.text_field :city, { placeholder: "eg. Northcote"} + = af.text_field :city, { placeholder: t(:city_placeholder) } .four.columns.omega - = af.text_field :zipcode, { placeholder: "eg. 3070"} + = af.text_field :zipcode, { placeholder: t(:postcode_placeholder) } .row .three.columns.alpha - = af.label :state_id, 'State' + = af.label :state_id, t(:state) \/ - = af.label :country_id, 'Country' + = af.label :country_id, t(:country) %span.required * .four.columns = af.collection_select :state_id, af.object.country.states, :id, :name, {}, :class => "select2 fullwidth" diff --git a/app/views/admin/enterprises/form/_business_details.html.haml b/app/views/admin/enterprises/form/_business_details.html.haml index 92f727d8d3..4015e51282 100644 --- a/app/views/admin/enterprises/form/_business_details.html.haml +++ b/app/views/admin/enterprises/form/_business_details.html.haml @@ -1,14 +1,14 @@ .row .alpha.three.columns - = f.label :abn, 'ABN' + = f.label :abn, t('.abn') .omega.eight.columns - = f.text_field :abn, { placeholder: "eg. 99 123 456 789"} + = f.text_field :abn, { placeholder: t('.abn_placeholder') } .row .alpha.three.columns - = f.label :acn, 'ACN' + = f.label :acn, t('.acn') .omega.eight.columns - = f.text_field :acn, { placeholder: "eg. 123 456 789"} + = f.text_field :acn, { placeholder: t('.acn_placeholder') } .row .three.columns.alpha @@ -16,8 +16,8 @@ .two.columns = f.radio_button :charges_sales_tax, true   - = f.label :charges_sales_tax, "Yes", :value => "true" + = f.label :charges_sales_tax, t(:say_yes), value: 'true' .five.columns.omega = f.radio_button :charges_sales_tax, false   - = f.label :charges_sales_tax, "No", :value => "false" + = f.label :charges_sales_tax, t(:say_no), value: 'false' diff --git a/app/views/admin/enterprises/form/_contact.html.haml b/app/views/admin/enterprises/form/_contact.html.haml index 28bbd6014b..1a7fa792f1 100644 --- a/app/views/admin/enterprises/form/_contact.html.haml +++ b/app/views/admin/enterprises/form/_contact.html.haml @@ -1,20 +1,20 @@ .row .alpha.three.columns - = f.label :contact, 'Name' + = f.label :contact, t('.name') .omega.eight.columns - = f.text_field :contact, { placeholder: "eg. Gustav Plum"} + = f.text_field :contact, { placeholder: t('.name_placeholder') } .row .alpha.three.columns - = f.label :email_address + = f.label :email_address, t('.email_address') .omega.eight.columns - = f.text_field :email_address, { placeholder: "eg. gustav@truffles.com" } + = f.text_field :email_address, { placeholder: t('.email_address_placeholder') } .row .alpha.three.columns - = f.label :phone + = f.label :phone, t('.phone') .omega.eight.columns - = f.text_field :phone, { placeholder: "eg. 98 7654 3210"} + = f.text_field :phone, { placeholder: t('.phone_placeholder') } .row .alpha.three.columns - = f.label :website + = f.label :website, t('.website') .omega.eight.columns - = f.text_field :website, { placeholder: "eg. www.truffles.com"} + = f.text_field :website, { placeholder: t('.website_placeholder') } diff --git a/app/views/admin/enterprises/form/_enterprise_fees.html.haml b/app/views/admin/enterprises/form/_enterprise_fees.html.haml index 53e48f1be9..dcae9afbc4 100644 --- a/app/views/admin/enterprises/form/_enterprise_fees.html.haml +++ b/app/views/admin/enterprises/form/_enterprise_fees.html.haml @@ -2,8 +2,8 @@ %table %thead %tr - %th Name - %th Fee Type + %th= t('.name') + %th= t('.fee_type') -# %th Calculator -# %th Calculator Values %tbody @@ -16,15 +16,15 @@ %br %div %a.button{ href: "#{main_app.admin_enterprise_fees_path}"} - Manage Enterprise Fees + = t('.manage_fees') %i.icon-arrow-right - else %p.text-center - You don't have any enterprise fees yet. + = t('.no_fees_yet') %br .text-center %a.button{ href: "#{main_app.admin_enterprise_fees_path}"} - Create One Now + = t('.create_button') %i.icon-arrow-right diff --git a/app/views/admin/enterprises/form/_images.html.haml b/app/views/admin/enterprises/form/_images.html.haml index c03be2caa3..97d31dabcc 100644 --- a/app/views/admin/enterprises/form/_images.html.haml +++ b/app/views/admin/enterprises/form/_images.html.haml @@ -8,11 +8,13 @@ = f.file_field :logo .row .alpha.three.columns - = f.label :promo_image, 'ofn-with-tip' => 'This image is displayed in "About Us"' + = f.label :promo_image, 'ofn-with-tip' => t('.promo_image_placeholder') %br/ - %span{ style: 'font-weight:bold' } PLEASE NOTE: - Any promo image uploaded here will be cropped to 1200 x 260. - The promo image is displayed at the top of an enterprise's profile page and pop-ups. + %span{ style: 'font-weight:bold' }= t('.promo_image_note1') + %br + = t('.promo_image_note2') + %br + = t('.promo_image_note3') .omega.eight.columns = image_tag @object.promo_image(:large) if @object.promo_image.present? diff --git a/app/views/admin/enterprises/form/_inventory_settings.html.haml b/app/views/admin/enterprises/form/_inventory_settings.html.haml index 0df0fdb35f..c055a1231f 100644 --- a/app/views/admin/enterprises/form/_inventory_settings.html.haml +++ b/app/views/admin/enterprises/form/_inventory_settings.html.haml @@ -3,16 +3,17 @@ -# their inventory if they so choose .row .alpha.eleven.columns - You may opt to manage stock levels and prices in via your + = t('.text1') = succeed "." do %strong - %a{href: main_app.admin_inventory_path } inventory - If you are using the inventory tool, you can select whether new products added by your suppliers need to be added to your inventory before they can be stocked. If you are not using your inventory to manage your products you should select the 'recommended' option below: + %a{href: main_app.admin_inventory_path }= t('.inventory') + %br + = t('.text2') .row .alpha.eleven.columns .five.columns.alpha = radio_button :enterprise, :preferred_product_selection_from_inventory_only, "0", { 'ng-model' => 'Enterprise.preferred_product_selection_from_inventory_only' } - = label :enterprise, :preferred_product_selection_from_inventory_only, "New products can be put into my shopfront (recommended)" + = label :enterprise, :preferred_product_selection_from_inventory_only, t('.preferred_product_selection_from_inventory_only_yes') .six.columns.omega = radio_button :enterprise, :preferred_product_selection_from_inventory_only, "1", { 'ng-model' => 'Enterprise.preferred_product_selection_from_inventory_only' } - = label :enterprise, :preferred_product_selection_from_inventory_only, "New products must be added to my inventory before they can be put into my shopfront" + = label :enterprise, :preferred_product_selection_from_inventory_only, t('.preferred_product_selection_from_inventory_only_no') diff --git a/app/views/admin/enterprises/form/_payment_methods.html.haml b/app/views/admin/enterprises/form/_payment_methods.html.haml index 57d9426d03..efcde16dc0 100644 --- a/app/views/admin/enterprises/form/_payment_methods.html.haml +++ b/app/views/admin/enterprises/form/_payment_methods.html.haml @@ -2,32 +2,32 @@ %table %thead %tr - %th Name - %th Applies? + %th= t('.name') + %th= t('.applies') %th %tbody - @payment_methods.each do |payment_method| %tr{ ng: { controller: 'paymentMethodsCtrl', init: "findPaymentMethodByID(#{payment_method.id})" } } %td= payment_method.name %td= f.check_box :payment_method_ids, { multiple: true, 'ng-model' => 'PaymentMethod.selected' }, payment_method.id, nil - %td= link_to "Edit", edit_admin_payment_method_path(payment_method) + %td= link_to t(:edit), edit_admin_payment_method_path(payment_method) %br .row .six.columns.alpha %a.button{ href: "#{admin_payment_methods_path}"} - Manage Payment Methods + = t('.manage') %i.icon-arrow-right .five.columns.omega.text-right %a.button{ href: "#{new_admin_payment_method_path}"} - Create New Payment Method + = t('.create_button') %i.icon-plus - else %p.text-center - You don't have any payment methods yet. + = t('.no_method_yet') %br .text-center %a.button{ href: "#{new_admin_payment_method_path}"} - Create One Now + = t('.create_one_button') %i.icon-arrow-right diff --git a/app/views/admin/enterprises/form/_primary_details.html.haml b/app/views/admin/enterprises/form/_primary_details.html.haml index 52b2ffebce..aad869288e 100644 --- a/app/views/admin/enterprises/form/_primary_details.html.haml +++ b/app/views/admin/enterprises/form/_primary_details.html.haml @@ -1,61 +1,61 @@ .row .alpha.eleven.columns .three.columns.alpha - = f.label :name + = f.label :name, t('.name') %span.required * .eight.columns.omega - = f.text_field :name, { placeholder: "eg. Professor Plum's Biodynamic Truffles" } + = f.text_field :name, { placeholder: t('.name_placeholder') } - if @groups.present? .row .alpha.eleven.columns .three.columns.alpha - = f.label :group_ids, 'Groups' - %div{'ofn-with-tip' => "Select any groups or regions that you are a member of. This will help customers find your enterprise."} - %a What's this? + = f.label :group_ids, t('.groups') + %div{'ofn-with-tip' => t('.groups_tip')} + %a= t('admin.whats_this') .eight.columns.omega - = f.collection_select :group_ids, @groups, :id, :name, {}, class: "select2 fullwidth", multiple: true, placeholder: "Start typing to search available groups..." + = f.collection_select :group_ids, @groups, :id, :name, {}, class: "select2 fullwidth", multiple: true, placeholder: t('.groups_placeholder') .row .three.columns.alpha - %label Primary Producer - %div{'ofn-with-tip' => "Select 'Producer' if you are a primary producer of food."} - %a What's this? + %label= t('.primary_producer') + %div{'ofn-with-tip' => t('.primary_producer_tip')} + %a= t('admin.whats_this') .five.columns.omega = f.check_box :is_primary_producer, 'ng-model' => 'Enterprise.is_primary_producer' - = f.label :is_primary_producer, 'Producer' + = f.label :is_primary_producer, t('.producer') - if spree_current_user.admin? .row .alpha.eleven.columns .three.columns.alpha - = f.label :sells, 'Sells' - %div{'ofn-with-tip' => "None - enterprise does not sell to customers directly.
Own - Enterprise sells own products to customers.
Any - Enterprise can sell own or other enterprises products.
"} - %a What's this? + = f.label :sells, t('.sells') + %div{'ofn-with-tip' => t('.sells_tip')} + %a= t('admin.whats_this') .two.columns = f.radio_button :sells, "none", 'ng-model' => 'Enterprise.sells' - = f.label :sells, "None", value: "none" + = f.label :sells, t('.none'), value: "none" .two.columns = f.radio_button :sells, "own", 'ng-model' => 'Enterprise.sells' - = f.label :sells, "Own", value: "own" + = f.label :sells, t('.own'), value: "own" .four.columns.omega = f.radio_button :sells, "any", 'ng-model' => 'Enterprise.sells' - = f.label :sells, "Any", value: "any" + = f.label :sells, t('.any'), value: "any" .row .three.columns.alpha - %label Visible in search? - %div{'ofn-with-tip' => "Determines whether this enterprise will be visible to customers when searching the site."} - %a What's this? + %label= t('.visible_in_search') + %div{'ofn-with-tip' => t('.visible_in_search_tip')} + %a= t('admin.whats_this') .two.columns = f.radio_button :visible, true - = f.label :visible, "Visible", :value => "true" + = f.label :visible, t('.visible'), value: 'true' .five.columns.omega = f.radio_button :visible, false - = f.label :visible, "Not Visible", :value => "false" + = f.label :visible, t('.not_visible'), value: 'false' .permalink{ ng: { controller: "permalinkCtrl" } } .row{ ng: { show: "Enterprise.sells == 'own' || Enterprise.sells == 'any'" } } .three.columns.alpha - = f.label :permalink, 'Permalink (no spaces)' - %div{'ofn-with-tip' => "This permalink is used to create the url to your shop: #{spree.root_url}your-shop-name/shop"} - %a What's this? + = f.label :permalink, t('.permalink') + %div{'ofn-with-tip' => t('.permalink_tip', link: spree.root_url)} + %a= t('admin.whats_this') .six.columns = f.text_field :permalink, { 'ng-model' => "Enterprise.permalink", placeholder: "eg. your-shop-name", 'ng-model-options' => "{ updateOn: 'default blur', debounce: {'default': 300, 'blur': 0} }" } .two.columns.omega @@ -65,9 +65,9 @@ %i{ ng: { class: "{'icon-ok-sign': availability == 'Available', 'icon-remove-sign': availability == 'Unavailable'}" } } .row{ ng: { show: "Enterprise.sells == 'own' || Enterprise.sells == 'any'" } } .three.columns.alpha - %label Link to shop front - %div{'ofn-with-tip' => "A direct link to your shopfront on the Open Food Network."} - %a What's this? + %label= t('.link_to_front') + %div{'ofn-with-tip' => t('.link_to_front_tip')} + %a= t('admin.whats_this') .eight.columns.omega = surround spree.root_url, "/shop" do {{Enterprise.permalink}} diff --git a/app/views/admin/enterprises/form/_shipping_methods.html.haml b/app/views/admin/enterprises/form/_shipping_methods.html.haml index 5ccc22a720..db7daa5b98 100644 --- a/app/views/admin/enterprises/form/_shipping_methods.html.haml +++ b/app/views/admin/enterprises/form/_shipping_methods.html.haml @@ -2,8 +2,8 @@ %table %thead %tr - %th Name - %th Applies? + %th= t('.name') + %th= t('.applies') %th %tbody - @shipping_methods.each do |shipping_method| @@ -15,19 +15,19 @@ .row .six.columns.alpha %a.button{ href: "#{admin_shipping_methods_path}"} - Manage Shipping Methods + = t('.manage') %i.icon-arrow-right .five.columns.omega.text-right %a.button{ href: "#{new_admin_shipping_method_path}"} - Create New Shipping Method + = t('.create_button') %i.icon-plus - else %p.text-center - You don't have any shipping methods yet. + = t('.no_method_yet') %br .text-center %a.button{ href: "#{new_admin_shipping_method_path}"} - Create One Now + = t('.create_one_button') %i.icon-arrow-right diff --git a/app/views/admin/enterprises/form/_shop_preferences.html.haml b/app/views/admin/enterprises/form/_shop_preferences.html.haml index 6573e4f3b4..0e5a1096f0 100644 --- a/app/views/admin/enterprises/form/_shop_preferences.html.haml +++ b/app/views/admin/enterprises/form/_shop_preferences.html.haml @@ -5,7 +5,7 @@ .eight.columns.omega %text-angular{'ng-model' => 'Enterprise.preferred_shopfront_message', 'id' => 'enterprise_preferred_shopfront_message', 'name' => 'enterprise[preferred_shopfront_message]', 'class' => 'text-angular', 'ta-toolbar' => "[['h1','h2','h3','h4','p'],['bold','italics','underline','clear'],['insertLink']]", - 'placeholder' => 'An optional explanation for customers detailing how your shopfront works, to be displayed above the product list on your shop page.'} + 'placeholder' => t('.shopfront_message_placeholder')} .row .alpha.eleven.columns .three.columns.alpha @@ -13,7 +13,7 @@ .eight.columns.omega %text-angular{'ng-model' => 'Enterprise.preferred_shopfront_closed_message', 'id' => 'enterprise_preferred_shopfront_closed_message', 'name' => 'enterprise[preferred_shopfront_closed_message]', 'class' => 'text-angular', 'ta-toolbar' => "[['h1','h2','h3','h4','p'],['bold','italics','underline','clear'],['insertLink']]", - 'placeholder' => 'A message which provides a more detailed explanation about why your shop is closed and/or when customers can expect it to open again. This is displayed on your shop only when you have no active order cycles (ie. shop is closed).'} + 'placeholder' => t('.shopfront_closed_message_placeholder')} .row .alpha.eleven.columns .three.columns.alpha @@ -29,10 +29,10 @@ = f.label "enterprise_preferred_shopfront_order_cycle_order", t(:sort_order_cycles_on_shopfront_by) .three.columns = radio_button :enterprise, :preferred_shopfront_order_cycle_order, :orders_open_at, { 'ng-model' => 'Enterprise.preferred_shopfront_order_cycle_order' } - = label :enterprise, :preferred_shopfront_order_cycle_order_orders_open_at, "Open Date" + = label :enterprise, :preferred_shopfront_order_cycle_order_orders_open_at, t('.open_date') .five.columns.omega = radio_button :enterprise, :preferred_shopfront_order_cycle_order, :orders_close_at, { 'ng-model' => 'Enterprise.preferred_shopfront_order_cycle_order' } - = label :enterprise, :preferred_shopfront_order_cycle_order_orders_close_at, "Close Date" + = label :enterprise, :preferred_shopfront_order_cycle_order_orders_close_at, t('.close_date') .row .alpha.eleven.columns .three.columns.alpha diff --git a/app/views/admin/enterprises/form/_social.html.haml b/app/views/admin/enterprises/form/_social.html.haml index 1909a6fc4a..11939739c1 100644 --- a/app/views/admin/enterprises/form/_social.html.haml +++ b/app/views/admin/enterprises/form/_social.html.haml @@ -17,4 +17,4 @@ .alpha.three.columns = f.label :twitter .omega.eight.columns - = f.text_field :twitter, { placeholder: "eg. @the_prof" } \ No newline at end of file + = f.text_field :twitter, { placeholder: t('.twitter_placeholder') } \ No newline at end of file diff --git a/app/views/admin/enterprises/form/_tag_rules.html.haml b/app/views/admin/enterprises/form/_tag_rules.html.haml index e58f7c5a62..822ea3f1ff 100644 --- a/app/views/admin/enterprises/form/_tag_rules.html.haml +++ b/app/views/admin/enterprises/form/_tag_rules.html.haml @@ -2,7 +2,7 @@ .eleven.columns.alpha.omega %ofn-sortable{ axis: "y", handle: ".header", items: '.customer_tag', position: "tagGroup.position", after: { sort: "updateRuleCounts()" } } .no_tags{ ng: { show: "tagGroups.length == 0" } } - No tags apply to this enterprise yet + = t('.no_tags_yet') = render 'admin/enterprises/form/tag_rules/default_rules' -# = render 'customer_tags' .customer_tag{ id: "tg_{{tagGroup.position}}", ng: { repeat: "tagGroup in tagGroups" } } @@ -14,14 +14,14 @@ %tr %td %h5 - For customers tagged: + = t('.for_customers_tagged') %td %tags-with-translation{ object: "tagGroup", max: 1, on: { tag: { added: "updateTagsRulesFor(tagGroup)", removed: "updateTagsRulesFor(tagGroup)" } } } .no_rules{ ng: { show: "tagGroup.rules.length == 0" } } - No rules apply to this tag yet + = t('.no_rules_yet') .tag_rule{ ng: { repeat: "rule in tagGroup.rules" } } .add_rule.text-center - %input.button.icon-plus{ type: 'button', value: "+ Add A New Rule", "add-new-rule-to" => "addNewRuleTo", "tag-group" => "tagGroup", "new-tag-rule-dialog" => true } + %input.button.icon-plus{ type: 'button', value: t('.add_new_rule'), "add-new-rule-to" => "addNewRuleTo", "tag-group" => "tagGroup", "new-tag-rule-dialog" => true } .add_tag - %input.button.red.icon-plus{ type: 'button', value: "+ Add A New Tag", ng: { click: 'addNewTag()' } } + %input.button.red.icon-plus{ type: 'button', value: t('.add_new_tag'), ng: { click: 'addNewTag()' } } diff --git a/app/views/admin/enterprises/form/_users.html.haml b/app/views/admin/enterprises/form/_users.html.haml index 42e25fad0a..eea13440a6 100644 --- a/app/views/admin/enterprises/form/_users.html.haml +++ b/app/views/admin/enterprises/form/_users.html.haml @@ -4,19 +4,18 @@ -if @enterprise.pending_any_confirmation? .alert-box - email = @enterprise.confirmed? ? @enterprise.unconfirmed_email : @enterprise.email - Email confirmation is pending. - We've sent a confirmation email to + = t('.email_confirmation_text') %strong= "#{email}." - = link_to('Resend', main_app.enterprise_confirmation_path(enterprise: { id: @enterprise.id, email: email } ), method: :post) + = link_to(t('.resend'), main_app.enterprise_confirmation_path(enterprise: { id: @enterprise.id, email: email } ), method: :post) %a.close{ href: "#" } × .row .three.columns.alpha - =f.label :owner_id, 'Owner' + =f.label :owner_id, t('.owner') - if full_permissions %span.required * - %div{'ofn-with-tip' => "The primary user responsible for this enterprise."} - %a What's this? + %div{'ofn-with-tip' => t('.owner_tip')} + %a= t('admin.whats_this') .eight.columns.omega - if full_permissions = f.hidden_field :owner_id, class: "select2 fullwidth", 'user-select' => 'Enterprise.owner' @@ -25,29 +24,29 @@ .row .three.columns.alpha - = f.label :email, 'Notifications' + = f.label :email, t('.notifications') - if full_permissions %span.required * - .with-tip{'data-powertip' => "Notifications about orders will be send to this email address."} - %a What's this? + .with-tip{'data-powertip' => t('.notifications_tip')} + %a= t('admin.whats_this') .eight.columns.omega - if full_permissions - = f.text_field :email, { placeholder: "eg. gustav@truffles.com", "ng-model" => "Enterprise.email" } + = f.text_field :email, { placeholder: t('.notifications_placeholder'), "ng-model" => "Enterprise.email" } - else = @enterprise.email .row{ ng: { hide: "pristineEmail == null || pristineEmail == Enterprise.email"} } .alpha.three.columns   .omega.eight.columns - Note: A new email address may need to be confirmed prior to use + = t('.notifications_note') .row .three.columns.alpha - =f.label :user_ids, 'Managers' + =f.label :user_ids, t('.managers') - if full_permissions %span.required * - %div{'ofn-with-tip' => "The other users with permission to manage this enterprise."} - %a What's this? + %div{'ofn-with-tip' => t('.managers_tip')} + %a= t('admin.whats_this') .eight.columns.omega - if full_permissions %table diff --git a/app/views/admin/enterprises/form/tag_rules/_default_rules.html.haml b/app/views/admin/enterprises/form/tag_rules/_default_rules.html.haml index 09c78f9c8c..a6e149d238 100644 --- a/app/views/admin/enterprises/form/tag_rules/_default_rules.html.haml +++ b/app/views/admin/enterprises/form/tag_rules/_default_rules.html.haml @@ -6,10 +6,10 @@ %tr %td %h5 - By Default + = t('.by_default') %i.text-big.icon-question-sign.help-modal{ template: 'admin/modals/tag_rule_help.html' } .no_rules{ ng: { show: "defaultTagGroup.rules.length == 0" } } - No default rules apply yet + = t('.no_rules_yet') .tag_rule{ ng: { repeat: "rule in defaultTagGroup.rules" } } .add_rule.text-center - %input.button.icon-plus{ type: 'button', value: "+ Add A New Default Rule", "add-new-rule-to" => "addNewRuleTo", "tag-group" => "defaultTagGroup", "new-tag-rule-dialog" => true } + %input.button.icon-plus{ type: 'button', value: t('.add_new_button'), "add-new-rule-to" => "addNewRuleTo", "tag-group" => "defaultTagGroup", "new-tag-rule-dialog" => true } diff --git a/app/views/admin/enterprises/index.html.haml b/app/views/admin/enterprises/index.html.haml index b84659738c..a233bc3146 100644 --- a/app/views/admin/enterprises/index.html.haml +++ b/app/views/admin/enterprises/index.html.haml @@ -1,19 +1,19 @@ - content_for :page_title do - Enterprises + = t('.title') - content_for :page_actions do = render 'admin/shared/user_guide_link' - if spree_current_user.can_own_more_enterprises? %li#new_product_link - = button_link_to "New Enterprise", main_app.new_admin_enterprise_path, :icon => 'icon-plus', :id => 'admin_new_enterprise_link' + = button_link_to t('.new_enterprise'), main_app.new_admin_enterprise_path, icon: 'icon-plus', id: 'admin_new_enterprise_link' = admin_inject_monthly_bill_description = admin_inject_column_preferences module: 'admin.enterprises', action: "enterprises_index" = render 'admin/shared/enterprises_sub_menu' -= render :partial => 'spree/shared/error_messages', :locals => { :target => @enterprise_set } += render partial: 'spree/shared/error_messages', locals: { target: @enterprise_set } - if spree_current_user.admin? = render 'admin_index' diff --git a/app/views/admin/enterprises/new.html.haml b/app/views/admin/enterprises/new.html.haml index 4413acfc2c..4676d117fb 100644 --- a/app/views/admin/enterprises/new.html.haml +++ b/app/views/admin/enterprises/new.html.haml @@ -1,10 +1,10 @@ -= render :partial => 'spree/shared/error_messages', :locals => { :target => @enterprise } += render partial: 'spree/shared/error_messages', locals: { target: @enterprise } - content_for :page_title do - New Enterprise + = t('.title') - content_for :page_actions do - %li= button_link_to "Back to enterprises list", main_app.admin_enterprises_path, icon: 'icon-arrow-left' + %li= button_link_to t('.back_link'), main_app.admin_enterprises_path, icon: 'icon-arrow-left' -# Form diff --git a/app/views/admin/enterprises/welcome.html.haml b/app/views/admin/enterprises/welcome.html.haml index cfe5593d99..8f70211e88 100644 --- a/app/views/admin/enterprises/welcome.html.haml +++ b/app/views/admin/enterprises/welcome.html.haml @@ -1,13 +1,13 @@ #welcome_page.sixteen.columns.alpha %header - %h1 Welcome to the Open Food Network! + %h1= t('.welcome_title') %p - You have successfully created a + = t('.welcome_text') %strong = "#{"producer " if @enterprise.is_primary_producer}profile" %section - %h2 Next step - %p Choose your starting point: + %h2= t('.next_step') + %p= t('.choose_starting_point') = render partial: "change_type_form" diff --git a/app/views/admin/order_cycles/_advanced_settings.html.haml b/app/views/admin/order_cycles/_advanced_settings.html.haml index f8f71d0b93..bfbd6e9c91 100644 --- a/app/views/admin/order_cycles/_advanced_settings.html.haml +++ b/app/views/admin/order_cycles/_advanced_settings.html.haml @@ -1,22 +1,22 @@ .row .alpha.omega.sixteen.columns - %h3 Advanced Settings + %h3= t('.title') = form_for [main_app, :admin, @order_cycle] do |f| .row .six.columns.alpha = f.label "enterprise_preferred_product_selection_from_coordinator_inventory_only", t('admin.order_cycles.edit.choose_products_from') - .with-tip{'data-powertip' => "You can opt to restrict all available products (both incoming and outgoing), to only those in #{@order_cycle.coordinator.name}'s inventory."} - %a What's this? + .with-tip{'data-powertip' => t('.choose_product_tip', inventory: @order_cycle.coordinator.name)} + %a= t('admin.whats_this') .four.columns = f.radio_button :preferred_product_selection_from_coordinator_inventory_only, true - = f.label :preferred_product_selection_from_coordinator_inventory_only, "Coordinator's Inventory Only" + = f.label :preferred_product_selection_from_coordinator_inventory_only, t('.preferred_product_selection_from_coordinator_inventory_only_here') .six.columns.omega = f.radio_button :preferred_product_selection_from_coordinator_inventory_only, false - = f.label :preferred_product_selection_from_coordinator_inventory_only, "All Available Products" + = f.label :preferred_product_selection_from_coordinator_inventory_only, t('.preferred_product_selection_from_coordinator_inventory_only_all') .row .sixteen.columns.alpha.omega.text-center - %input{ type: 'submit', value: 'Save and Reload Page' } - or - %a{ href: "#", onClick: "toggleSettings()" } Close + %input{ type: 'submit', value: t('.save_reload') } + = t(:or) + %a{ href: "#", onClick: "toggleSettings()" }= t(:close) diff --git a/app/views/admin/order_cycles/_coordinator_fees.html.haml b/app/views/admin/order_cycles/_coordinator_fees.html.haml index 86e0d0a7af..a22deee37f 100644 --- a/app/views/admin/order_cycles/_coordinator_fees.html.haml +++ b/app/views/admin/order_cycles/_coordinator_fees.html.haml @@ -1,6 +1,6 @@ %ol.coordinator-fees %li{'ng-repeat' => 'enterprise_fee in order_cycle.coordinator_fees'} = select_tag 'order_cycle_coordinator_fee_{{ $index }}_id', nil, {'ng-model' => 'enterprise_fee.id', 'ng-options' => 'enterprise_fee.id as enterprise_fee.name for enterprise_fee in enterpriseFeesForEnterprise(order_cycle.coordinator_id)'} - = link_to 'Remove', '#', {'id' => 'order_cycle_coordinator_fee_{{ $index }}_remove', 'ng-click' => 'removeCoordinatorFee($event, $index)'} + = link_to t(:remove), '#', {'id' => 'order_cycle_coordinator_fee_{{ $index }}_remove', 'ng-click' => 'removeCoordinatorFee($event, $index)'} -= f.submit 'Add coordinator fee', 'ng-click' => 'addCoordinatorFee($event)' += f.submit t('.add'), 'ng-click' => 'addCoordinatorFee($event)' diff --git a/app/views/admin/order_cycles/_form.html.haml b/app/views/admin/order_cycles/_form.html.haml index ebe9e45905..f075c2cf33 100644 --- a/app/views/admin/order_cycles/_form.html.haml +++ b/app/views/admin/order_cycles/_form.html.haml @@ -3,55 +3,54 @@ -if Enterprise.managed_by(spree_current_user).include? @order_cycle.coordinator = render 'coordinator_fees', f: f -%h2 Incoming +%h2= t('.incoming') %table.exchanges %thead %tr - %th Supplier + %th= t('.supplier') %th - Products + =t('.products') = surround '(', ')' do %a{href: '#', 'ng-click' => "OrderCycle.toggleAllProducts('incoming')"} - %span{'ng-show' => "OrderCycle.showProducts['incoming']"} Collapse all - %span{'ng-hide' => "OrderCycle.showProducts['incoming']"} Expand all - %th Receival details - %th Fees + %span{'ng-show' => "OrderCycle.showProducts['incoming']"}= t(:collapse_all) + %span{'ng-hide' => "OrderCycle.showProducts['incoming']"}= t(:expand_all) + %th= t('.receival_details') + %th= t('.fees') %th.actions %tbody.panel-ctrl{ object: 'exchange', 'ng-repeat' => 'exchange in order_cycle.incoming_exchanges'} - = render 'exchange_form', :f => f, :type => 'supplier' + = render 'exchange_form', f: f, type: 'supplier' - if Enterprise.managed_by(spree_current_user).include? @order_cycle.coordinator = render 'add_exchange_form', f: f, type: 'supplier' -%h2 Outgoing +%h2= t('.outgoing') %table.exchanges %thead %tr - %th Distributor + %th= t('.distributor') %th - Products + = t('.products') = surround '(', ')' do %a{href: '#', 'ng-click' => "OrderCycle.toggleAllProducts('outgoing')"} - %span{'ng-show' => "OrderCycle.showProducts['outgoing']"} Collapse all - %span{'ng-hide' => "OrderCycle.showProducts['outgoing']"} Expand all - %th{ ng: { if: 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' } } Tags - %th Pickup / Delivery details + %span{'ng-show' => "OrderCycle.showProducts['outgoing']"}= t(:collapse_all) + %span{'ng-hide' => "OrderCycle.showProducts['outgoing']"}= t(:expand_all) + %th{ ng: { if: 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' } } + = t('.tags') + %th= t('.delivery_details') %th Fees %th.actions %tbody.panel-ctrl{ object: 'exchange', 'ng-repeat' => 'exchange in order_cycle.outgoing_exchanges'} - = render 'exchange_form', :f => f, :type => 'distributor' - + = render 'exchange_form', f: f, type: 'distributor' - if Enterprise.managed_by(spree_current_user).include? @order_cycle.coordinator = render 'add_exchange_form', f: f, type: 'distributor' .actions - %span{'ng-hide' => 'loaded()'} Loading... - + %span{'ng-hide' => 'loaded()'}= t(:loading) - unless Rails.env.production? #order-cycles-debug - %h2 Debug information + %h2= t('.debug_info') %pre loaded = {{ loaded() | json }} %hr/ diff --git a/app/views/admin/order_cycles/_name_and_timing_form.html.haml b/app/views/admin/order_cycles/_name_and_timing_form.html.haml index 39fa19f287..d081120210 100644 --- a/app/views/admin/order_cycles/_name_and_timing_form.html.haml +++ b/app/views/admin/order_cycles/_name_and_timing_form.html.haml @@ -1,13 +1,13 @@ .row .alpha.two.columns - = f.label :name + = f.label :name, t('.name') .six.columns.omega - if viewing_as_coordinator_of?(@order_cycle) = f.text_field :name, 'ng-model' => 'order_cycle.name', 'required' => true, 'ng-disabled' => '!loaded()' - else {{ order_cycle.name }} .two.columns - = f.label :orders_open_at, 'Orders open' + = f.label :orders_open_at, t('.orders_open') .omega.six.columns - if viewing_as_coordinator_of?(@order_cycle) = f.text_field :orders_open_at, 'datetimepicker' => 'order_cycle.orders_open_at', 'ng-model' => 'order_cycle.orders_open_at', 'ng-disabled' => '!loaded()' @@ -16,11 +16,11 @@ .row .alpha.two.columns - = f.label :coordinator + = f.label :coordinator, t('.coordinator') .six.columns.omega = @order_cycle.coordinator.name .two.columns - = f.label :orders_close_at, 'Orders close' + = f.label :orders_close, t('.orders_close') .six.columns.omega - if viewing_as_coordinator_of?(@order_cycle) = f.text_field :orders_close_at, 'datetimepicker' => 'order_cycle.orders_close_at', 'ng-model' => 'order_cycle.orders_close_at', 'ng-disabled' => '!loaded()' diff --git a/app/views/admin/order_cycles/_row.html.haml b/app/views/admin/order_cycles/_row.html.haml index d75a97bbb5..cbfde7f546 100644 --- a/app/views/admin/order_cycles/_row.html.haml +++ b/app/views/admin/order_cycles/_row.html.haml @@ -13,7 +13,7 @@ - if suppliers.count > 3 %span{'ofn-with-tip' => supplier_list} = suppliers.count - suppliers + = t('.suppliers') - else = supplier_list %td= order_cycle.coordinator.name @@ -23,12 +23,12 @@ - if distributors.count > 3 %span{'ofn-with-tip' => distributor_list} = distributors.count - distributors + = t('.distributors') - else = distributor_list %td.products - %span= "#{order_cycle.variants.count} variants" + %span= "#{order_cycle.variants.count} #{t('.variants')}" %td.actions = link_to '', main_app.edit_admin_order_cycle_path(order_cycle), class: 'edit-order-cycle icon-edit no-text' @@ -36,4 +36,4 @@ = link_to '', main_app.clone_admin_order_cycle_path(order_cycle), class: 'clone-order-cycle icon-copy no-text' - if can_delete?(order_cycle) %td.actions - = link_to '', main_app.admin_order_cycle_path(order_cycle), class: 'delete-order-cycle icon-trash no-text', :method => :delete, data: { confirm: "Are you sure?" } + = link_to '', main_app.admin_order_cycle_path(order_cycle), class: 'delete-order-cycle icon-trash no-text', :method => :delete, data: { confirm: t(:are_you_sure) } diff --git a/app/views/admin/order_cycles/_simple_form.html.haml b/app/views/admin/order_cycles/_simple_form.html.haml index a7a0363cd3..15303b1f5d 100644 --- a/app/views/admin/order_cycles/_simple_form.html.haml +++ b/app/views/admin/order_cycles/_simple_form.html.haml @@ -2,23 +2,23 @@ .row .alpha.two.columns - = label_tag 'Ready for' + = label_tag t('.ready_for') .six.columns - = text_field_tag 'order_cycle_outgoing_exchange_0_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_0_pickup_time', 'placeholder' => 'Date / time', 'ng-model' => 'outgoing_exchange.pickup_time', 'size' => 30 + = text_field_tag 'order_cycle_outgoing_exchange_0_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_0_pickup_time', 'placeholder' => t('.ready_for_placeholder'), 'ng-model' => 'outgoing_exchange.pickup_time', 'size' => 30 .two.columns - = label_tag 'Customer instructions' + = label_tag t('.customer_instructions') .six.columns.omega - = text_field_tag 'order_cycle_outgoing_exchange_0_pickup_instructions', '', 'id' => 'order_cycle_outgoing_exchange_0_pickup_instructions', 'placeholder' => 'Pick-up or delivery notes', 'ng-model' => 'outgoing_exchange.pickup_instructions', 'size' => 30 + = text_field_tag 'order_cycle_outgoing_exchange_0_pickup_instructions', '', 'id' => 'order_cycle_outgoing_exchange_0_pickup_instructions', 'placeholder' => t('.customer_instructions_placeholder'), 'ng-model' => 'outgoing_exchange.pickup_instructions', 'size' => 30 -= label_tag 'Products' += label_tag t('.products') %table.exchanges %tbody{ng: {repeat: "exchange in order_cycle.incoming_exchanges"}} %tr.products %td{ ng: { include: "'admin/panels/exchange_supplied_products.html'" } } %br/ -= label_tag 'Fees' += label_tag t('.fees') = render 'coordinator_fees', f: f .actions - %span{'ng-hide' => 'loaded()'} Loading... + %span{'ng-hide' => 'loaded()'}= t(:loading) diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml index 4712566dfd..34f64c1ee7 100644 --- a/app/views/admin/order_cycles/edit.html.haml +++ b/app/views/admin/order_cycles/edit.html.haml @@ -12,26 +12,24 @@ - if can? :notify_producers, @order_cycle %li - = button_to "Notify producers", main_app.notify_producers_admin_order_cycle_path, :id => 'admin_notify_producers', :confirm => 'Are you sure?' + = button_to "Notify producers", main_app.notify_producers_admin_order_cycle_path, :id => 'admin_notify_producers', :confirm => t(:are_you_sure) %li %button#toggle_settings{ onClick: 'toggleSettings()' } - Advanced Settings + = t('.advanced_settings') %i.icon-chevron-down - #advanced_settings{ hidden: true } = render partial: "/admin/order_cycles/advanced_settings" %h1 = t :edit_order_cycle - - ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl' = form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" } - %input.red{ type: "button", value: "Update", ng: { click: "submit($event, null)", disabled: "!order_cycle_form.$dirty" } } - %input.red{ type: "button", value: "Update and Close", ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty" } } + %input.red{ type: "button", value: t(:update), ng: { click: "submit($event, null)", disabled: "!order_cycle_form.$dirty" } } + %input.red{ type: "button", value: t('.update_and_close'), ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty" } } %input{ type: "button", ng: { value: "order_cycle_form.$dirty ? 'Cancel' : 'Close'", click: "cancel('#{main_app.admin_order_cycles_path}')" } } - if order_cycles_simple_form diff --git a/app/views/admin/order_cycles/index.html.haml b/app/views/admin/order_cycles/index.html.haml index 824aa66ee8..989983b5b0 100644 --- a/app/views/admin/order_cycles/index.html.haml +++ b/app/views/admin/order_cycles/index.html.haml @@ -3,7 +3,7 @@ = content_for :page_actions do %li#new_order_cycle_link - = button_link_to t(:new_order_cycle), main_app.new_admin_order_cycle_path, :icon => 'icon-plus', :id => 'admin_new_order_cycle_link' + = button_link_to t(:new_order_cycle), main_app.new_admin_order_cycle_path, icon: 'icon-plus', id: 'admin_new_order_cycle_link' - if @show_more %li = button_link_to t(:label_less), main_app.admin_order_cycles_path diff --git a/app/views/admin/order_cycles/new.html.haml b/app/views/admin/order_cycles/new.html.haml index 4e5a95544c..6bc0946f01 100644 --- a/app/views/admin/order_cycles/new.html.haml +++ b/app/views/admin/order_cycles/new.html.haml @@ -7,7 +7,7 @@ = form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f| %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" } - %input.red{ type: "button", value: "Create", ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty" } } + %input.red{ type: "button", value: t(:create), ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty" } } %input{ type: "button", ng: { value: "order_cycle_form.$dirty ? 'Cancel' : 'Close'", click: "cancel('#{main_app.admin_order_cycles_path}')" } } - if order_cycles_simple_form diff --git a/app/views/admin/producer_properties/_form.html.haml b/app/views/admin/producer_properties/_form.html.haml index dcd83f8ec4..249301ae40 100644 --- a/app/views/admin/producer_properties/_form.html.haml +++ b/app/views/admin/producer_properties/_form.html.haml @@ -4,8 +4,8 @@ %table.index.sortable{"data-hook" => "", "data-sortable-link" => main_app.update_positions_admin_enterprise_producer_properties_url(@enterprise)} %thead %tr{"data-hook" => "producer_properties_header"} - %th{colspan: "2"} Property - %th Value + %th{colspan: "2"}= t('.property') + %th= t('.value') %th.actions %tbody#producer_properties{"data-hook" => ""} = f.fields_for :producer_properties do |pp_form| diff --git a/app/views/admin/producer_properties/index.html.haml b/app/views/admin/producer_properties/index.html.haml index d898860cb1..c66f02a32e 100644 --- a/app/views/admin/producer_properties/index.html.haml +++ b/app/views/admin/producer_properties/index.html.haml @@ -1,17 +1,14 @@ - content_for :page_title do = "#{@enterprise.name}:" - Producer Properties - + = t('.title') - content_for :page_actions do %ul.tollbar.inline-menu %li = link_to_add_fields t(:add_producer_property), 'tbody#producer_properties', class: 'icon-plus button' - = render 'spree/shared/error_messages', target: @enterprise - = form_for @enterprise, url: main_app.admin_enterprise_path(@enterprise), method: :put do |f| = render 'form', f: f diff --git a/app/views/admin/shared/_user_guide_link.html.haml b/app/views/admin/shared/_user_guide_link.html.haml index 6081422aba..cd1b0dba86 100644 --- a/app/views/admin/shared/_user_guide_link.html.haml +++ b/app/views/admin/shared/_user_guide_link.html.haml @@ -1 +1 @@ -= button_link_to "User Guide", "http://www.openfoodnetwork.org/platform/user-guide/", :icon => 'icon-external-link', target: '_blank' += button_link_to t('.user_guide'), "http://www.openfoodnetwork.org/platform/user-guide/", icon: 'icon-external-link', target: '_blank' diff --git a/app/views/admin/variant_overrides/_loading_flash.html.haml b/app/views/admin/variant_overrides/_loading_flash.html.haml index c543a5517c..2baa04a473 100644 --- a/app/views/admin/variant_overrides/_loading_flash.html.haml +++ b/app/views/admin/variant_overrides/_loading_flash.html.haml @@ -1,3 +1,3 @@ %div.sixteen.columns.alpha.omega#loading{ ng: { cloak: true, if: 'hub_id && products.length == 0 && RequestMonitor.loading' } } %img.spinner{ src: "/assets/spinning-circles.svg" } - %h1 LOADING INVENTORY + %h1= t('.loading_inventory') diff --git a/app/views/admin/variant_overrides/_show_more.html.haml b/app/views/admin/variant_overrides/_show_more.html.haml index 21e927e443..5e8274d99c 100644 --- a/app/views/admin/variant_overrides/_show_more.html.haml +++ b/app/views/admin/variant_overrides/_show_more.html.haml @@ -1,5 +1,5 @@ -# %show-more.text-center{ data: "filteredProducts", limit: "productLimit", increment: "10" } .text-center{ ng: { show: "filteredProducts.length > productLimit" } } - %input{ type: 'button', value: 'Show More', ng: { click: 'productLimit = productLimit + 10' } } - or - %input{ type: 'button', value: "Show All ({{ filteredProducts.length - productLimit }} More)", ng: { click: 'productLimit = filteredProducts.length' } } + %input{ type: 'button', value: t(:show_more), ng: { click: 'productLimit = productLimit + 10' } } + = t(:or) + %input{ type: 'button', value: "#{t(:show_all)} ({{ filteredProducts.length - productLimit }} More)", ng: { click: 'productLimit = filteredProducts.length' } } diff --git a/config/locales/en.yml b/config/locales/en.yml index 6f04c26284..949060ab36 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -70,9 +70,17 @@ en: say_no: "No" say_yes: "Yes" then: then - sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By" - + required_fields: Required fields are denoted with an asterisk + select_continue: Select and Continue + remove: Remove + or: or + collapse_all: Collapse all + expand_all: Expand all + loading: Loading... + show_more: Show more + show_all: Show all + cancel: Cancel admin: # Common properties / models @@ -131,6 +139,31 @@ en: update_address: 'Update Address' confirm_delete: 'Sure to delete?' + cache_settings: + show: + distributor: Distributor + order_cycle: Order Cycle + status: Status + diff: Diff + + contents: + edit: + title: Content + + enterprise_fees: + index: + title: Entreprise Fees + enterprise: Enterprise + fee_type: Fee Type + name: Name + tax_category: Tax Category + calculator: Calculator + calculator_values: Calculator Values + + enterprise_groups: + index: + new_button: New Enterprise Group + products: bulk_edit: unit: Unit @@ -142,6 +175,8 @@ en: av_on: "Av. On" variant_overrides: + loading_flash: + loading_inventory: LOADING INVENTORY index: title: Inventory description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page @@ -198,6 +233,82 @@ en: status: Status manage: Manage form: + about_us: + desc_short: Short Description + desc_short_placeholder: Tell us about your enterprise in one or two sentences + desc_long: About Us + desc_long_placeholder: Tell customers about yourself. This information appears on your public profile. + business_details: + abn: ABN + abn_placeholder: eg. 99 123 456 789 + acn: ACN + acn_placeholder: eg. 123 456 789 + contact: + name: Fullname + name_placeholder: eg. Gustav Plum + email_address: Email Address + email_address_placeholder: eg. gustav@truffles.com + phone: Phone + phone_placeholder: eg. 98 7654 3210 + website: Website + website_placeholder: eg. www.truffles.com + enterprise_fees: + name: Name + fee_type: Fee Type + manage_fees: Manage Enterprise Fees + no_fees_yet: You don't have any enterprise fees yet. + create_button: Create One Now + images: + logo: Logo + promo_image_placeholder: 'This image is displayed in "About Us"' + promo_image_note1: 'PLEASE NOTE:' + promo_image_note2: Any promo image uploaded here will be cropped to 1200 x 260. + promo_image_note3: The promo image is displayed at the top of an enterprise's profile page and pop-ups. + inventory_settings: + text1: You may opt to manage stock levels and prices in via your + inventory: inventory + text2: "If you are using the inventory tool, you can select whether new products \ + added by your suppliers need to be added to your inventory before they can be \ + stocked. If you are not using your inventory to manage your products you should \ + select the 'recommended' option below:" + preferred_product_selection_from_inventory_only_yes: New products can be put into my shopfront (recommended) + preferred_product_selection_from_inventory_only_no: New products must be added to my inventory before they can be put into my shopfront + payment_methods: + name: Name + applies: Applies? + manage: Manage Payment Methods + not_method_yet: You don't have any payment methods yet. + create_button: Create New Payment Method + create_one_button: Create One Now + primary_details: + name: Name + name_placeholder: eg. Professor Plum's Biodynamic Truffles + groups: Groups + groups_tip: Select any groups or regions that you are a member of. This will help customers find your enterprise. + groups_placeholder: Start typing to search available groups... + primary_producer: Primary Producer? + primary_producer_tip: Select 'Producer' if you are a primary producer of food. + producer: Producer + any: Any + none: None + own: Own + sells: Sells + sells_tip: "None - enterprise does not sell to customers directly.
Own - Enterprise sells own products to customers.
Any - Enterprise can sell own or other enterprises products.
" + visible_in_seach: Visible in search? + visible_in_seach_tip: Determines whether this enterprise will be visible to customers when searching the site. + visible: Visible + not_visible: Not visible + permalink: Permalink (no spaces) + permalink_tip: "This permalink is used to create the url to your shop: {{link}} your-shop-name/shop" + link_to_front: Link to shop front + link_to_front_tip: A direct link to your shopfront on the Open Food Network. + shipping_methods: + name: Name + applies: Applies? + manage: Manage Shipping Methods + create_button: Create New Shipping Method + create_one_button: Create One Now + no_method_yet: You don't have any shipping methods yet. shop_preferences: shopfront_requires_login: "Publicly visible shopfront?" shopfront_requires_login_tip: "Choose whether customers must login to view the shopfront or if it's visible to everybody." @@ -207,6 +318,145 @@ en: allow_guest_orders_tip: "Allow checkout as guest or require a registered user." allow_guest_orders_false: "Require login to order" allow_guest_orders_true: "Allow guest checkout" + shopfront_message_placeholder: \ + An optional explanation for customers detailing how your shopfront works, \ + to be displayed above the product list on your shop page. + shopfront_closed_message_placeholder: \ + A message which provides a more detailed explanation about why your shop is \ + closed and/or when customers can expect it to open again. This is displayed \ + on your shop only when you have no active order cycles (ie. shop is closed). + open_date: Open Date + close_date: Close Date + social: + twitter_placeholder: eg. @the_prof + tag_rules: + default_rules: + by_default: By Default + no_rules_yet: No default rules apply yet + add_new_button: '+ Add A New Default Rule' + no_tags_yet: No tags apply to this enterprise yet + no_rules_yet: No rules apply to this tag yet + for_customers_tagged: 'For customers tagged:' + add_new_rule: '+ Add A New Rule' + add_new_tag: '+ Add A New Tag' + users: + email_confirmation_text: Email confirmation is pending.
We've sent a confirmation email to + resend: Resend + owner: 'Owner' + owner_tip: The primary user responsible for this enterprise. + notifications: Notifications + notifications_tip: Notifications about orders will be send to this email address. + notifications_placeholder: eg. gustav@truffles.com + notifications_note: 'Note: A new email address may need to be confirmed prior to use' + managers: Managers + managers_tip: The other users with permission to manage this enterprise. + actions: + edit_profile: Edit Profile + properties: Properties + payment_methods: Payment Methods + payment_methods_tip: This enterprise has no payment methods + shipping_methods: Shipping Methods + shipping_methods_tip: This enterprise has shipping methods + enterprise_fees: Enterprise Fees + enterprise_fees_tip: This enterprise has no fees + admin_index: + name: Name + role: Role + sells: Sells + visible: Visible? + owner: Owner + producer: Producer + change_type_form: + producer_profile: Producer Profile + connect_ofn: Connect through OFN + always_free: ALWAYS FREE + producer_description_text: Add your products to Open Food Network, allowing hubs to stock your products in their stores. + producer_shop: Producer Shop + sell_your_produce: Sell your own produce + sell_description_text: Sell your products directly to customers through your very own Open Food Network shopfront. + sell_description_text2: A Producer Shop is for your produce only, if you want to sell produce grown/produced off site, select 'Producer Hub'. + producer_hub: Producer Hub + producer_hub_text: Sell produce from self and others + producer_hub_description_text: Your enterprise is the backbone of your local food system. You can sell your own produce as well as produce aggregated from other enterprises through your shopfront on the Open Food Network. + profile: Profile Only + get_listing: Get a listing + profile_description_text: People can find and contact you on the Open Food Network. Your enterprise will be visible on the map, and will be searchable in listings. + hub_shop: Hub Shop + hub_shop_text: Sell produce from others + hub_shop_description_text: Your enterprise is the backbone of your local food system. You aggregate produce from other enterprises and can sell it through your shop on the Open Food Network. + choose_option: Please choose one of the options above. + change_now: Change now + enterprise_user_index: + search_placeholder: Search By Name + manage: Manage + new_form: + owner: Owner + owner_tip: The primary user responsible for this enterprise. + i_am_producer: I am a Producer + contact_name: Contact Name + edit: + editing: 'Editing:' + back_link: Back to enterprises list + index: + title: Enterprises + new_enterprise: New Enterprise + new: + title: New Enterprise + back_link: Back to enterprises list + welcome: + welcome_title: Welcome to the Open Food Network! + welcome_text: You have successfully created a + next_step: Next step + choose_starting_point: 'Choose your starting point:' + order_cycles: + advanced_settings: + title: Advanced Settings + choose_product_tip: You can opt to restrict all available products (both incoming and outgoing), to only those in {{inventory}}'s inventory. + preferred_product_selection_from_coordinator_inventory_only_here: Coordinator's Inventory Only + preferred_product_selection_from_coordinator_inventory_only_all: All Available Products + save_reload: Save and Reload Page + coordinator_fees: + add: Add coordinator fee + form: + incoming: Incoming + supplier: Supplier + products: Products + receival_details: Receival details + fees: Fees + outgoing: Outgoing + distributor: Distributor + products: Products + tags: Tags + delivery_detaisl: Pickup / Delivery details + debug_info: Debug information + name_and_timing_form: + name: Name + orders_open: Orders open at + coordinator: Coordinator + order_closes: Orders close + row: + suppliers: suppliers + distributors: distributors + variants: variants + simple_form: + ready_for: Ready for + ready_for_placeholder: Date / time + customer_instructions: Customer instructions + customer_instructions_placeholder: Pick-up or delivery notes + products: Products + fees: Fees + edit: + advanced_settings: Advanced Settings + update_and_close: Update and Close + producer_properties: + form: + property: Property + value: Value + index: + title: Producer Properties + shared: + user_guide_link: + user_guide: User Guide home: hubs: @@ -256,10 +506,14 @@ en: phone: Phone next: Next address: Address + address_placeholder: eg. 123 High Street address2: Address (contd.) city: City + city_placeholder: eg. Northcote state: State postcode: Postcode + postcode_placeholder: eg. 3070 + state: State country: Country unauthorized: Unauthorized terms_of_service: "Terms of service" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 0594d8a112..74fdc54336 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -53,6 +53,17 @@ fr: say_yes: "Oui" then: puis sort_order_cycles_on_shopfront_by: "Trier les cycles de vente par" + required_fields: "Les champs obligatoires sont annotés d'une astérisque" + select_continue: Sélectionner et Continuer + remove: Supprimer + or: ou + collapse_all: Réduire + expand_all: Etendre + loading: Chargement... + show_more: Voir plus + show_all: Voir tout + cancel: Annuler + admin: date: Date email: Email @@ -100,6 +111,27 @@ fr: select_country: 'Choisir le pays' select_state: 'Choisir le département' edit: 'Modifier' + cache_settings: + show: + distributor: Distributeur + order_cycle: Cycle de vente + status: Statut + diff: Diff + contents: + edit: + title: Contenu + enterprise_fees: + index: + title: Marges de l'entreprise + enterprise: Entreprise + fee_type: Type de marge + name: Nom + tax_category: TVA appliquée + calculator: Calculateur + calculator_values: Valeurs applicables + enterprise_groups: + index: + new_button: Nouveau groupe d'entreprise products: bulk_edit: unit: Unité @@ -110,6 +142,8 @@ fr: available_on: Disponible sur av_on: "Disp. sur" variant_overrides: + loading_flash: + loading_inventory: LOADING INVENTORY index: title: Stock description: Utilisez cette page pour gérer le catalogue de votre entreprise. Les détails produits saisis ici remplaceront ceux de la page "Produit" pour votre entreprise uniquement. @@ -162,11 +196,231 @@ fr: status: Statut manage: Gérer form: + about_us: + desc_short: Description courte + desc_short_placeholder: Dites nous à propos de votre entreprise en 2 lignes + desc_long: Description publique + desc_long_placeholder: Décrivez-vous à vos clients. Cette information apparaîtra sur votre profil public. + business_details: + abn: ABN + abn_placeholder: "ex: 99 123 456 789" + acn: ACN + acn_placeholder: "ex: 123 456 789" + contact: + name: Prénom/Nom + name_placeholder: "ex: Maurice Batot" + email_address: Email Address + email_address_placeholder: "ex: maurice@tomates.fr" + phone: Téléphone + phone_placeholder: "ex: 98 7654 3210" + website: Site Web + website_placeholder: "ex: www.tomates.fr" + enterprise_fees: + name: Nom + fee_type: Type de marge + manage_fees: Gérer vos marges + no_fees_yet: Vous n'avez pas encore de marges définies. + create_button: Créer une nouvelle marge + images: + logo: Logo + promo_image_placeholder: 'Cette image est affichée dans la partie "A Propos"' + promo_image_note1: 'NOTES :' + promo_image_note2: Toutes les images uploadées ici seront coupées pour 1200x260 pixels. + promo_image_note3: L'image promotionelle est affichée en haut de la page profil de l'entreprise et sur les pop-ups. + inventory_settings: + text1: Vous pouvez gérer votre stock et vos prix via votre + inventory: inventaire + text2: "If you are using the inventory tool, you can select whether new products \ + added by your suppliers need to be added to your inventory before they can be \ + stocked. If you are not using your inventory to manage your products you should \ + select the 'recommended' option below:" + preferred_product_selection_from_inventory_only_yes: Les nouveaux produits peuvent être mis dans mon magasin (Recommendé) + preferred_product_selection_from_inventory_only_no: Les nouveaux produits doivent être ajouté dans mon magasin avant de pouvoir être ajouté dans mon magasin + payment_methods: + name: Nom + applies: Applies? + manage: Gérer les moyens de paiement + not_method_yet: Vous n'avez encore aucun moyen de paiement. + create_button: Ajouter un nouveau moyen de paiement + create_one_button: Ajouter un nouveau moyen de paiement primary_details: + name: Nom + name_placeholder: 'ex: Les Oranges Bleues' + groups: Groupes + groups_tip: Select any groups or regions that you are a member of. This will help customers find your enterprise. + groups_placeholder: Commencer à taper dans rechercher les groupes disponibles... + primary_producer: Primary Producer + primary_producer_tip: Select 'Producer' if you are a primary producer of food. + producer: Producteur + any: Any + none: None + own: Own + sells: Sells + sells_tip: "None - enterprise does not sell to customers directly.
Own - Enterprise sells own products to customers.
Any - Enterprise can sell own or other enterprises products.
" + visible_in_seach: Visible dans la recherche? + visible_in_seach_tip: Determines whether this enterprise will be visible to customers when searching the site. + visible: Visible + not_visible: Pas visible + permalink: Lien (pas d'espace) + permalink_tip: "This permalink is used to create the url to your shop: {{link}} your-shop-name/shop" + link_to_front: Link to shop front + link_to_front_tip: A direct link to your shopfront on the Open Food Network. + shipping_methods: + name: Nom + applies: Applies? + manage: Gérer les moyens de livraison + create_button: Ajouter un nouveau moyen de livraison + create_one_button: Ajouter un nouveau moyen de livraison + no_method_yet: Vous n'avez encore aucun moyen de livraison défini. + shop_preferences: shopfront_requires_login: "Cette boutique exige un login?" shopfront_requires_login_tip: "Les acheteurs doivent-ils se connecter pour accéder à la boutique?" shopfront_requires_login_false: "Public" shopfront_requires_login_true: "Demander aux acheteurs de se connecter" + allow_guest_orders: "Guest orders" + allow_guest_orders_tip: "Allow checkout as guest or require a registered user." + allow_guest_orders_false: "Require login to order" + allow_guest_orders_true: "Allow guest checkout" + shopfront_message_placeholder: \ + An optional explanation for customers detailing how your shopfront works, \ + to be displayed above the product list on your shop page. + shopfront_closed_message_placeholder: \ + A message which provides a more detailed explanation about why your shop is \ + closed and/or when customers can expect it to open again. This is displayed \ + on your shop only when you have no active order cycles (ie. shop is closed). + open_date: Open Date + close_date: Close Date + social: + twitter_placeholder: 'ex: @lesorangesbleues' + tag_rules: + default_rules: + by_default: Par défaut + no_rules_yet: Aucune règle encore ajoutée + add_new_button: + Ajouter une nouvelle règle + no_tags_yet: Aucun tag encore défini pour cette entreprise + no_rules_yet: Aucun régle encore définie pour ce tag + for_customers_tagged: 'For customers tagged:' + add_new_rule: '+ Ajouter une nouvelle régle' + add_new_tag: '+ Ajouter un nouveau tag' + users: + email_confirmation_text: Email confirmation is pending.
We've sent a confirmation email to + resend: Renvoyer + owner: Propriétaire + owner_tip: Le premier utilisateur responsable pour l'entreprise. + notifications: Notifications + notifications_tip: Notifications about orders will be send to this email address. + notifications_placeholder: 'ex: gustav@truffles.com' + notifications_note: 'Note: A new email address may need to be confirmed prior to use' + managers: Managers + managers_tip: The other users with permission to manage this enterprise. + actions: + edit_profile: Mettre à jour le profil + properties: Propriétés + payment_methods: Méthodes de paiement + payment_methods_tip: Cette entreprise n'a pas de méthode de paiement + shipping_methods: Méthodes de livraison + shipping_methods_tip: Cette entreprise n'a pas de méthode de livraisons + enterprise_fees: Enterprise Fees + enterprise_fees_tip: This enterprise has no fees + admin_index: + name: Nom + role: Role + sells: Sells + visible: Visible? + owner: Owner + producer: Producer + change_type_form: + producer_profile: Producer Profile + connect_ofn: Connect through OFN + always_free: ALWAYS FREE + producer_description_text: Add your products to Open Food Network, allowing hubs to stock your products in their stores. + producer_shop: Producer Shop + sell_your_produce: Sell your own produce + sell_description_text: Sell your products directly to customers through your very own Open Food Network shopfront. + sell_description_text2: A Producer Shop is for your produce only, if you want to sell produce grown/produced off site, select 'Producer Hub'. + producer_hub: Producer Hub + producer_hub_text: Sell produce from self and others + producer_hub_description_text: Your enterprise is the backbone of your local food system. You can sell your own produce as well as produce aggregated from other enterprises through your shopfront on the Open Food Network. + profile: Profile Only + get_listing: Get a listing + profile_description_text: People can find and contact you on the Open Food Network. Your enterprise will be visible on the map, and will be searchable in listings. + hub_shop: Hub Shop + hub_shop_text: Sell produce from others + hub_shop_description_text: Your enterprise is the backbone of your local food system. You aggregate produce from other enterprises and can sell it through your shop on the Open Food Network. + choose_option: Please choose one of the options above. + change_now: Change now + enterprise_user_index: + search_placeholder: Search By Name + manage: Manage + new_form: + owner: Owner + owner_tip: The primary user responsible for this enterprise. + i_am_producer: I am a Producer + contact_name: Nom du contact + edit: + editing: 'Editing:' + back_link: Retour à la liste d'entreprise + index: + title: Enterprises + new_enterprise: Ajouter une nouvelle entreprise + new: + title: Nouvelle entreprise + back_link: Retour à la liste d'entreprise + welcome: + welcome_title: Bienvenue chez Open Food France ! + welcome_text: Vous avez bien créé une + next_step: Etape suivante + choose_starting_point: 'Choose your starting point:' + order_cycles: + advanced_settings: + title: Advanced Settings + choose_product_tip: You can opt to restrict all available products (both incoming and outgoing), to only those in {{inventory}}'s inventory. + preferred_product_selection_from_coordinator_inventory_only_here: Coordinator's Inventory Only + preferred_product_selection_from_coordinator_inventory_only_all: All Available Products + save_reload: Save and Reload Page + coordinator_fees: + add: Add coordinator fee + form: + incoming: Incoming + supplier: Supplier + products: Products + receival_details: Receival details + fees: Fees + outgoing: Outgoing + distributor: Distributor + products: Products + tags: Tags + delivery_detaisl: Pickup / Delivery details + debug_info: Debug information + name_and_timing_form: + name: Name + orders_open: Orders open + coordinator: Coordinator + order_closes: Orders close + row: + suppliers: suppliers + distributors: distributors + variants: variants + simple_form: + ready_for: Ready for + ready_for_placeholder: Date / time + customer_instructions: Customer instructions + customer_instructions_placeholder: Pick-up or delivery notes + products: Products + fees: Fees + edit: + advanced_settings: Advanced Settings + update_and_close: Update and Close + producer_properties: + form: + property: Propriété + value: Valeur + index: + title: Producer Properties + shared: + user_guide_link: + user_guide: Guide l'utilisateur + home: hubs: show_closed_shops: "Aficher les boutiques fermées" @@ -211,10 +465,14 @@ fr: phone: Téléphone next: Suivant address: Adresse + address_placeholder: "ex: 16, rue du Marché" address2: Adresse (suite) city: Ville + city_placeholder: "ex: Lille" state: Département postcode: Code postal + postcode_placeholder: "ex: 59000" + state: Région country: Pays unauthorized: Non authorisé terms_of_service: "Conditions d'utilisation" From a9f01c0f0df23f890b0112ea10141aead5c0b88f Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 11 Jan 2017 11:15:44 +1100 Subject: [PATCH 099/109] Fix typos in en.yml --- config/locales/en.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 949060ab36..da7f06a076 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -244,7 +244,7 @@ en: acn: ACN acn_placeholder: eg. 123 456 789 contact: - name: Fullname + name: Name name_placeholder: eg. Gustav Plum email_address: Email Address email_address_placeholder: eg. gustav@truffles.com @@ -294,8 +294,8 @@ en: own: Own sells: Sells sells_tip: "None - enterprise does not sell to customers directly.
Own - Enterprise sells own products to customers.
Any - Enterprise can sell own or other enterprises products.
" - visible_in_seach: Visible in search? - visible_in_seach_tip: Determines whether this enterprise will be visible to customers when searching the site. + visible_in_search: Visible in search? + visible_in_search_tip: Determines whether this enterprise will be visible to customers when searching the site. visible: Visible not_visible: Not visible permalink: Permalink (no spaces) @@ -667,6 +667,8 @@ en: order_payment_paypal_successful: Your payment via PayPal has been processed successfully. order_hub_info: Hub Info + bom_tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required." + unsaved_changes_warning: "Unsaved changes exist and will be lost if you continue." unsaved_changes_error: "Fields with red borders contain errors." From e899633affc38c5dff89d5cab7cd7898635b0624 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Wed, 11 Jan 2017 14:48:05 +1100 Subject: [PATCH 100/109] Merge duplicate locale entry admin.enterprises.index --- config/locales/en.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index da7f06a076..1b31b5c054 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -228,7 +228,9 @@ en: enterprises: index: - producer?: Producer? + title: Enterprises + new_enterprise: New Enterprise + producer?: "Producer?" package: Package status: Status manage: Manage @@ -397,9 +399,6 @@ en: edit: editing: 'Editing:' back_link: Back to enterprises list - index: - title: Enterprises - new_enterprise: New Enterprise new: title: New Enterprise back_link: Back to enterprises list From d80f080af55997aa2ddfb5ddcda7326f5ac7de1b Mon Sep 17 00:00:00 2001 From: Paul Mackay Date: Thu, 12 Jan 2017 10:18:30 +0000 Subject: [PATCH 101/109] #1027: Use url helpers for basic pages Set groups change frequency to monthly. --- app/controllers/sitemap_controller.rb | 2 +- app/views/sitemap/index.xml.haml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/sitemap_controller.rb b/app/controllers/sitemap_controller.rb index f5ce44ceca..103b6e93ee 100644 --- a/app/controllers/sitemap_controller.rb +++ b/app/controllers/sitemap_controller.rb @@ -3,7 +3,7 @@ class SitemapController < ApplicationController def index headers['Content-Type'] = 'application/xml' - @pages = ['shops', 'map', 'producers', 'groups'] + @page_urls = [shops_url, map_url, producers_url, groups_url] @enterprises = Enterprise.is_hub @groups = EnterpriseGroup.all respond_to :xml diff --git a/app/views/sitemap/index.xml.haml b/app/views/sitemap/index.xml.haml index f961b75899..dac6032a15 100644 --- a/app/views/sitemap/index.xml.haml +++ b/app/views/sitemap/index.xml.haml @@ -1,8 +1,8 @@ !!! XML %urlset{xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9"} - - for page in @pages + - for page_url in @page_urls %url - %loc= root_url + page + %loc= page_url %changefreq monthly - for enterprise in @enterprises @@ -14,4 +14,4 @@ - for group in @groups %url %loc= group_url(group) - %changefreq yearly + %changefreq monthly From 44970a13bb482d4c5fc9c87ed9bb6b72a8605ad2 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 13 Jan 2017 10:21:25 +1100 Subject: [PATCH 102/109] Fix translation of email confirmation notice The `_html` suffix was missing leading to `
` being displayed in plain text. The new version also integrates the email address as a variable in the translation. --- app/views/admin/enterprises/form/_users.html.haml | 3 +-- config/locales/en.yml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/views/admin/enterprises/form/_users.html.haml b/app/views/admin/enterprises/form/_users.html.haml index eea13440a6..a0b5b7e662 100644 --- a/app/views/admin/enterprises/form/_users.html.haml +++ b/app/views/admin/enterprises/form/_users.html.haml @@ -4,8 +4,7 @@ -if @enterprise.pending_any_confirmation? .alert-box - email = @enterprise.confirmed? ? @enterprise.unconfirmed_email : @enterprise.email - = t('.email_confirmation_text') - %strong= "#{email}." + = t('.email_confirmation_notice_html', {email: "#{email}".html_safe}) = link_to(t('.resend'), main_app.enterprise_confirmation_path(enterprise: { id: @enterprise.id, email: email } ), method: :post) %a.close{ href: "#" } × diff --git a/config/locales/en.yml b/config/locales/en.yml index 1b31b5c054..902d32e35c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -342,7 +342,7 @@ en: add_new_rule: '+ Add A New Rule' add_new_tag: '+ Add A New Tag' users: - email_confirmation_text: Email confirmation is pending.
We've sent a confirmation email to + email_confirmation_notice_html: "Email confirmation is pending. We've sent a confirmation email to %{email}." resend: Resend owner: 'Owner' owner_tip: The primary user responsible for this enterprise. From 651626eb4ffd220f0a2d71cb2f0e8f56b20c541e Mon Sep 17 00:00:00 2001 From: Paul Mackay Date: Sat, 14 Jan 2017 11:47:21 +0000 Subject: [PATCH 103/109] #1365: Remove /test dir as it is not used --- test/fixtures/.gitkeep | 0 test/functional/.gitkeep | 0 test/integration/.gitkeep | 0 test/performance/browsing_test.rb | 12 ------------ test/test_helper.rb | 13 ------------- test/unit/.gitkeep | 0 6 files changed, 25 deletions(-) delete mode 100644 test/fixtures/.gitkeep delete mode 100644 test/functional/.gitkeep delete mode 100644 test/integration/.gitkeep delete mode 100644 test/performance/browsing_test.rb delete mode 100644 test/test_helper.rb delete mode 100644 test/unit/.gitkeep diff --git a/test/fixtures/.gitkeep b/test/fixtures/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/functional/.gitkeep b/test/functional/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/integration/.gitkeep b/test/integration/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/performance/browsing_test.rb b/test/performance/browsing_test.rb deleted file mode 100644 index 3fea27b916..0000000000 --- a/test/performance/browsing_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'test_helper' -require 'rails/performance_test_help' - -class BrowsingTest < ActionDispatch::PerformanceTest - # Refer to the documentation for all available options - # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] - # :output => 'tmp/performance', :formats => [:flat] } - - def test_homepage - get '/' - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb deleted file mode 100644 index 7d57a78563..0000000000 --- a/test/test_helper.rb +++ /dev/null @@ -1,13 +0,0 @@ -ENV["RAILS_ENV"] = "test" -require_relative '../config/environment' -require 'rails/test_help' - -class ActiveSupport::TestCase - # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. - # - # Note: You'll currently still have to declare fixtures explicitly in integration tests - # -- they do not yet inherit this setting - fixtures :all - - # Add more helper methods to be used by all tests here... -end diff --git a/test/unit/.gitkeep b/test/unit/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 From 02e5ba4dfdefc2500289884a036d6c3c8fc580df Mon Sep 17 00:00:00 2001 From: Paul Mackay Date: Sun, 15 Jan 2017 12:11:53 +0000 Subject: [PATCH 104/109] Convert .sass files to .scss format --- app/assets/stylesheets/admin/alert.css.sass | 15 - app/assets/stylesheets/admin/alert.css.scss | 24 + ...nimations.css.sass => animations.css.scss} | 34 +- .../admin/change_type_form.css.sass | 72 --- .../admin/change_type_form.css.scss | 103 +++++ .../admin/components/save_bar.sass | 14 - .../admin/components/save_bar.scss | 19 + .../admin/components/trial_progess_bar.sass | 9 - .../admin/components/trial_progess_bar.scss | 10 + .../admin/dashboard-single-ent.css.sass | 33 -- .../admin/dashboard-single-ent.css.scss | 45 ++ .../stylesheets/admin/dashboard_item.css.sass | 159 ------- .../stylesheets/admin/dashboard_item.css.scss | 239 ++++++++++ .../stylesheets/admin/relationships.css.sass | 23 - .../stylesheets/admin/relationships.css.scss | 31 ++ .../stylesheets/admin/side_menu.css.sass | 18 - .../stylesheets/admin/side_menu.css.scss | 29 ++ .../stylesheets/admin/sidebar-item.css.sass | 77 ---- .../stylesheets/admin/sidebar-item.css.scss | 121 ++++++ .../admin/variant_overrides.css.sass | 6 - .../admin/variant_overrides.css.scss | 8 + app/assets/stylesheets/admin/welcome.css.sass | 16 - app/assets/stylesheets/admin/welcome.css.scss | 24 + .../darkswarm/_shop-filters.css.sass | 125 ------ .../darkswarm/_shop-filters.css.scss | 173 ++++++++ .../darkswarm/_shop-inputs.css.sass | 85 ---- .../darkswarm/_shop-inputs.css.scss | 102 +++++ .../darkswarm/_shop-modals.css.sass | 6 - .../darkswarm/_shop-modals.css.scss | 9 + .../darkswarm/_shop-popovers.css.sass | 112 ----- .../darkswarm/_shop-popovers.css.scss | 152 +++++++ .../darkswarm/_shop-product-rows.css.sass | 123 ------ .../darkswarm/_shop-product-rows.css.scss | 182 ++++++++ .../darkswarm/_shop-product-thumb.css.sass | 59 --- .../darkswarm/_shop-product-thumb.css.scss | 81 ++++ .../darkswarm/_shop-taxon-flag.css.sass | 31 -- .../darkswarm/_shop-taxon-flag.css.scss | 45 ++ .../stylesheets/darkswarm/account.css.sass | 70 --- .../stylesheets/darkswarm/account.css.scss | 106 +++++ .../darkswarm/active_table.css.sass | 114 ----- .../darkswarm/active_table.css.scss | 160 +++++++ .../darkswarm/active_table_search.css.sass | 117 ----- .../darkswarm/active_table_search.css.scss | 164 +++++++ .../{angular.css.sass => angular.css.scss} | 5 +- .../stylesheets/darkswarm/animations.sass | 217 --------- .../stylesheets/darkswarm/animations.scss | 271 ++++++++++++ .../stylesheets/darkswarm/big-input.sass | 93 ---- .../stylesheets/darkswarm/big-input.scss | 127 ++++++ .../stylesheets/darkswarm/branding.css.sass | 38 -- .../stylesheets/darkswarm/branding.css.scss | 37 ++ app/assets/stylesheets/darkswarm/footer.sass | 78 ---- app/assets/stylesheets/darkswarm/footer.scss | 127 ++++++ .../stylesheets/darkswarm/forms.css.sass | 5 - .../stylesheets/darkswarm/forms.css.scss | 6 + .../stylesheets/darkswarm/groups.css.sass | 108 ----- .../stylesheets/darkswarm/groups.css.scss | 169 ++++++++ .../stylesheets/darkswarm/header.css.sass | 21 - .../stylesheets/darkswarm/header.css.scss | 31 ++ .../stylesheets/darkswarm/home_panes.css.sass | 156 ------- .../stylesheets/darkswarm/home_panes.css.scss | 225 ++++++++++ .../darkswarm/home_tagline.css.sass | 33 -- .../darkswarm/home_tagline.css.scss | 47 ++ .../stylesheets/darkswarm/hub_node.css.sass | 168 ------- .../stylesheets/darkswarm/hub_node.css.scss | 255 +++++++++++ .../stylesheets/darkswarm/hubs.css.sass | 13 - .../stylesheets/darkswarm/hubs.css.scss | 18 + .../stylesheets/darkswarm/ie_warning.sass | 32 -- .../stylesheets/darkswarm/ie_warning.scss | 40 ++ .../stylesheets/darkswarm/images.css.sass | 47 -- .../stylesheets/darkswarm/images.css.scss | 58 +++ .../stylesheets/darkswarm/lists.css.sass | 7 - .../stylesheets/darkswarm/lists.css.scss | 9 + app/assets/stylesheets/darkswarm/loading.sass | 83 ---- app/assets/stylesheets/darkswarm/loading.scss | 106 +++++ app/assets/stylesheets/darkswarm/map.css.sass | 50 --- app/assets/stylesheets/darkswarm/map.css.scss | 64 +++ .../stylesheets/darkswarm/menu.css.sass | 137 ------ .../stylesheets/darkswarm/menu.css.scss | 186 ++++++++ app/assets/stylesheets/darkswarm/mixins.sass | 192 -------- app/assets/stylesheets/darkswarm/mixins.scss | 253 +++++++++++ .../darkswarm/modal-enterprises.css.sass | 165 ------- .../darkswarm/modal-enterprises.css.scss | 212 +++++++++ .../darkswarm/modal-login.css.sass | 13 - .../darkswarm/modal-login.css.scss | 13 + .../stylesheets/darkswarm/modals.css.sass | 54 --- .../stylesheets/darkswarm/modals.css.scss | 64 +++ .../stylesheets/darkswarm/overrides.css.sass | 2 - .../stylesheets/darkswarm/overrides.css.scss | 3 + .../stylesheets/darkswarm/page_alert.css.sass | 58 --- .../stylesheets/darkswarm/page_alert.css.scss | 77 ++++ .../darkswarm/producer_node.css.sass | 97 ----- .../darkswarm/producer_node.css.scss | 145 +++++++ .../stylesheets/darkswarm/producers.css.sass | 14 - .../stylesheets/darkswarm/producers.css.scss | 24 + .../darkswarm/product_table.css.sass | 4 - .../darkswarm/product_table.css.scss | 6 + .../darkswarm/registration.css.sass | 161 ------- .../darkswarm/registration.css.scss | 239 ++++++++++ .../stylesheets/darkswarm/shop.css.sass | 111 ----- .../stylesheets/darkswarm/shop.css.scss | 146 +++++++ .../darkswarm/shopping-cart.css.sass | 70 --- .../darkswarm/shopping-cart.css.scss | 98 +++++ .../stylesheets/darkswarm/sidebar.css.sass | 31 -- .../stylesheets/darkswarm/sidebar.css.scss | 41 ++ .../stylesheets/darkswarm/signup.css.sass | 119 ----- .../stylesheets/darkswarm/signup.css.scss | 188 ++++++++ .../stylesheets/darkswarm/tables.css.sass | 7 - .../stylesheets/darkswarm/tables.css.scss | 10 + .../stylesheets/darkswarm/tabs.css.sass | 110 ----- .../stylesheets/darkswarm/tabs.css.scss | 161 +++++++ .../stylesheets/darkswarm/taxons.css.sass | 52 --- .../stylesheets/darkswarm/taxons.css.scss | 81 ++++ .../stylesheets/darkswarm/typography.css.sass | 125 ------ .../stylesheets/darkswarm/typography.css.scss | 167 +++++++ app/assets/stylesheets/darkswarm/ui.css.sass | 97 ----- app/assets/stylesheets/darkswarm/ui.css.scss | 128 ++++++ .../stylesheets/darkswarm/variables.css.sass | 32 -- .../stylesheets/darkswarm/variables.css.scss | 31 ++ app/assets/stylesheets/mail/email.css.sass | 312 ------------- app/assets/stylesheets/mail/email.css.scss | 410 ++++++++++++++++++ 120 files changed, 6123 insertions(+), 4412 deletions(-) delete mode 100644 app/assets/stylesheets/admin/alert.css.sass create mode 100644 app/assets/stylesheets/admin/alert.css.scss rename app/assets/stylesheets/admin/{animations.css.sass => animations.css.scss} (56%) delete mode 100644 app/assets/stylesheets/admin/change_type_form.css.sass create mode 100644 app/assets/stylesheets/admin/change_type_form.css.scss delete mode 100644 app/assets/stylesheets/admin/components/save_bar.sass create mode 100644 app/assets/stylesheets/admin/components/save_bar.scss delete mode 100644 app/assets/stylesheets/admin/components/trial_progess_bar.sass create mode 100644 app/assets/stylesheets/admin/components/trial_progess_bar.scss delete mode 100644 app/assets/stylesheets/admin/dashboard-single-ent.css.sass create mode 100644 app/assets/stylesheets/admin/dashboard-single-ent.css.scss delete mode 100644 app/assets/stylesheets/admin/dashboard_item.css.sass create mode 100644 app/assets/stylesheets/admin/dashboard_item.css.scss delete mode 100644 app/assets/stylesheets/admin/relationships.css.sass create mode 100644 app/assets/stylesheets/admin/relationships.css.scss delete mode 100644 app/assets/stylesheets/admin/side_menu.css.sass create mode 100644 app/assets/stylesheets/admin/side_menu.css.scss delete mode 100644 app/assets/stylesheets/admin/sidebar-item.css.sass create mode 100644 app/assets/stylesheets/admin/sidebar-item.css.scss delete mode 100644 app/assets/stylesheets/admin/variant_overrides.css.sass create mode 100644 app/assets/stylesheets/admin/variant_overrides.css.scss delete mode 100644 app/assets/stylesheets/admin/welcome.css.sass create mode 100644 app/assets/stylesheets/admin/welcome.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/_shop-filters.css.sass create mode 100644 app/assets/stylesheets/darkswarm/_shop-filters.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/_shop-inputs.css.sass create mode 100644 app/assets/stylesheets/darkswarm/_shop-inputs.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/_shop-modals.css.sass create mode 100644 app/assets/stylesheets/darkswarm/_shop-modals.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/_shop-popovers.css.sass create mode 100644 app/assets/stylesheets/darkswarm/_shop-popovers.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/_shop-product-rows.css.sass create mode 100644 app/assets/stylesheets/darkswarm/_shop-product-rows.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/_shop-product-thumb.css.sass create mode 100644 app/assets/stylesheets/darkswarm/_shop-product-thumb.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/_shop-taxon-flag.css.sass create mode 100644 app/assets/stylesheets/darkswarm/_shop-taxon-flag.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/account.css.sass create mode 100644 app/assets/stylesheets/darkswarm/account.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/active_table.css.sass create mode 100644 app/assets/stylesheets/darkswarm/active_table.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/active_table_search.css.sass create mode 100644 app/assets/stylesheets/darkswarm/active_table_search.css.scss rename app/assets/stylesheets/darkswarm/{angular.css.sass => angular.css.scss} (71%) delete mode 100644 app/assets/stylesheets/darkswarm/animations.sass create mode 100644 app/assets/stylesheets/darkswarm/animations.scss delete mode 100644 app/assets/stylesheets/darkswarm/big-input.sass create mode 100644 app/assets/stylesheets/darkswarm/big-input.scss delete mode 100644 app/assets/stylesheets/darkswarm/branding.css.sass create mode 100644 app/assets/stylesheets/darkswarm/branding.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/footer.sass create mode 100644 app/assets/stylesheets/darkswarm/footer.scss delete mode 100644 app/assets/stylesheets/darkswarm/forms.css.sass create mode 100644 app/assets/stylesheets/darkswarm/forms.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/groups.css.sass create mode 100644 app/assets/stylesheets/darkswarm/groups.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/header.css.sass create mode 100644 app/assets/stylesheets/darkswarm/header.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/home_panes.css.sass create mode 100644 app/assets/stylesheets/darkswarm/home_panes.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/home_tagline.css.sass create mode 100644 app/assets/stylesheets/darkswarm/home_tagline.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/hub_node.css.sass create mode 100644 app/assets/stylesheets/darkswarm/hub_node.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/hubs.css.sass create mode 100644 app/assets/stylesheets/darkswarm/hubs.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/ie_warning.sass create mode 100644 app/assets/stylesheets/darkswarm/ie_warning.scss delete mode 100644 app/assets/stylesheets/darkswarm/images.css.sass create mode 100644 app/assets/stylesheets/darkswarm/images.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/lists.css.sass create mode 100644 app/assets/stylesheets/darkswarm/lists.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/loading.sass create mode 100644 app/assets/stylesheets/darkswarm/loading.scss delete mode 100644 app/assets/stylesheets/darkswarm/map.css.sass create mode 100644 app/assets/stylesheets/darkswarm/map.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/menu.css.sass create mode 100644 app/assets/stylesheets/darkswarm/menu.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/mixins.sass create mode 100644 app/assets/stylesheets/darkswarm/mixins.scss delete mode 100644 app/assets/stylesheets/darkswarm/modal-enterprises.css.sass create mode 100644 app/assets/stylesheets/darkswarm/modal-enterprises.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/modal-login.css.sass create mode 100644 app/assets/stylesheets/darkswarm/modal-login.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/modals.css.sass create mode 100644 app/assets/stylesheets/darkswarm/modals.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/overrides.css.sass create mode 100644 app/assets/stylesheets/darkswarm/overrides.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/page_alert.css.sass create mode 100644 app/assets/stylesheets/darkswarm/page_alert.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/producer_node.css.sass create mode 100644 app/assets/stylesheets/darkswarm/producer_node.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/producers.css.sass create mode 100644 app/assets/stylesheets/darkswarm/producers.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/product_table.css.sass create mode 100644 app/assets/stylesheets/darkswarm/product_table.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/registration.css.sass create mode 100644 app/assets/stylesheets/darkswarm/registration.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/shop.css.sass create mode 100644 app/assets/stylesheets/darkswarm/shop.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/shopping-cart.css.sass create mode 100644 app/assets/stylesheets/darkswarm/shopping-cart.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/sidebar.css.sass create mode 100644 app/assets/stylesheets/darkswarm/sidebar.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/signup.css.sass create mode 100644 app/assets/stylesheets/darkswarm/signup.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/tables.css.sass create mode 100644 app/assets/stylesheets/darkswarm/tables.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/tabs.css.sass create mode 100644 app/assets/stylesheets/darkswarm/tabs.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/taxons.css.sass create mode 100644 app/assets/stylesheets/darkswarm/taxons.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/typography.css.sass create mode 100644 app/assets/stylesheets/darkswarm/typography.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/ui.css.sass create mode 100644 app/assets/stylesheets/darkswarm/ui.css.scss delete mode 100644 app/assets/stylesheets/darkswarm/variables.css.sass create mode 100644 app/assets/stylesheets/darkswarm/variables.css.scss delete mode 100644 app/assets/stylesheets/mail/email.css.sass create mode 100644 app/assets/stylesheets/mail/email.css.scss diff --git a/app/assets/stylesheets/admin/alert.css.sass b/app/assets/stylesheets/admin/alert.css.sass deleted file mode 100644 index d86cb38f55..0000000000 --- a/app/assets/stylesheets/admin/alert.css.sass +++ /dev/null @@ -1,15 +0,0 @@ -.alert - border: 3px solid #919191 - border-radius: 6px - margin-bottom: 20px - color: #919191 - padding: 5px 10px - h6 - color: #919191 - .message - font-weight: bold - &:hover - border-color: #DA5354 - color: #DA5354 - h6 - color: #DA5354 diff --git a/app/assets/stylesheets/admin/alert.css.scss b/app/assets/stylesheets/admin/alert.css.scss new file mode 100644 index 0000000000..b516867a96 --- /dev/null +++ b/app/assets/stylesheets/admin/alert.css.scss @@ -0,0 +1,24 @@ +.alert { + border: 3px solid #919191; + border-radius: 6px; + margin-bottom: 20px; + color: #919191; + padding: 5px 10px; + + h6 { + color: #919191; + } + + .message { + font-weight: bold; + } + + &:hover { + border-color: #DA5354; + color: #DA5354; + + h6 { + color: #DA5354; + } + } +} diff --git a/app/assets/stylesheets/admin/animations.css.sass b/app/assets/stylesheets/admin/animations.css.scss similarity index 56% rename from app/assets/stylesheets/admin/animations.css.sass rename to app/assets/stylesheets/admin/animations.css.scss index 88284c7487..bbc766ecfd 100644 --- a/app/assets/stylesheets/admin/animations.css.sass +++ b/app/assets/stylesheets/admin/animations.css.scss @@ -1,10 +1,14 @@ -@-webkit-keyframes slideInUp - 0% - -webkit-transform: translateY(20px) - transform: translateY(20px) - 100% - -webkit-transform: translateY(0) - transform: translateY(0) +@-webkit-keyframes slideInUp { + 0% { + -webkit-transform: translateY(20px); + transform: translateY(20px); + } + + 100% { + -webkit-transform: translateY(0); + transform: translateY(0); + } +} // @-webkit-keyframes slideOutDown // 0% @@ -14,13 +18,14 @@ // -webkit-transform: translateY(20px) // transform: translateY(20px) -.animate-show - -webkit-animation-name: slideInUp - animation-name: slideInUp - -webkit-animation-duration: 0.3s - animation-duration: 0.3s - -webkit-animation-fill-mode: both - animation-fill-mode: both +.animate-show { + -webkit-animation-name: slideInUp; + animation-name: slideInUp; + -webkit-animation-duration: 0.3s; + animation-duration: 0.3s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + // line-height: 20px // opacity: 1 @@ -33,3 +38,4 @@ // animation-fill-mode: both // // line-height: 20px // // opacity: 1 +} diff --git a/app/assets/stylesheets/admin/change_type_form.css.sass b/app/assets/stylesheets/admin/change_type_form.css.sass deleted file mode 100644 index bf67f89696..0000000000 --- a/app/assets/stylesheets/admin/change_type_form.css.sass +++ /dev/null @@ -1,72 +0,0 @@ -@import ../darkswarm/branding -@import ../darkswarm/mixins - -#change_type - section - margin: 2em 0 0 0 - &, & * - color: #5498da - - .description - background-color: #eff5fc - margin-top: -2em - padding: 4em 2em 2em 1em - @media all and (max-width: 786px) - margin-bottom: 2em - - .admin-cta - border: 1px solid #5498da - @include border-radius(3px) - text-align: center - padding: 1em - - .error - display: block - color: #f57e80 - border: 1px solid #f57e80 - background-color: #fde6e7 - @include border-radius(3px) - margin-bottom: 1em - padding: 0.5em - - a.selector - position: relative - border: 2px solid black - text-align: center - width: 100% - cursor: pointer - &, & * - color: white - &:after, &:before - top: 100% - left: 50% - border: solid transparent - content: " " - height: 0 - width: 0 - position: absolute - pointer-events: none - &:after - border-color: rgba(136, 183, 213, 0) - border-top-color: #5498da - border-width: 12px - margin-left: -12px - &:hover - &:after - border-top-color: #9fc820 - &:before - border-color: rgba(84, 152, 218, 0) - border-top-color: black - border-width: 15px - margin-left: -15px - .bottom - background: repeating-linear-gradient(60deg, rgba(84, 152, 218, 0), rgba(84, 152, 218, 0) 5px, rgba(255, 255, 255, 0.25) 5px, rgba(255, 255, 255, 0.25) 10px) - margin-top: 1em - margin-left: -15px - margin-right: -15px - padding: 5px - text-transform: uppercase - &.selected - background-color: black - &:after, &:hover &:after - border-top-color: black \ No newline at end of file diff --git a/app/assets/stylesheets/admin/change_type_form.css.scss b/app/assets/stylesheets/admin/change_type_form.css.scss new file mode 100644 index 0000000000..0153d701e8 --- /dev/null +++ b/app/assets/stylesheets/admin/change_type_form.css.scss @@ -0,0 +1,103 @@ +@import "../darkswarm/branding"; +@import "../darkswarm/mixins"; + +#change_type { + section { + margin: 2em 0 0 0; + + &, & * { + color: #5498da; + } + } + + .description { + background-color: #eff5fc; + margin-top: -2em; + padding: 4em 2em 2em 1em; + + @media all and (max-width: 786px) { + margin-bottom: 2em; + } + } + + .admin-cta { + border: 1px solid #5498da; + + @include border-radius(3px); + + text-align: center; + padding: 1em; + } + + .error { + display: block; + color: #f57e80; + border: 1px solid #f57e80; + background-color: #fde6e7; + + @include border-radius(3px); + + margin-bottom: 1em; + padding: 0.5em; + } + + a.selector { + position: relative; + border: 2px solid black; + text-align: center; + width: 100%; + cursor: pointer; + + &, & * { + color: white; + } + + &:after, &:before { + top: 100%; + left: 50%; + border: solid transparent; + content: " "; + height: 0; + width: 0; + position: absolute; + pointer-events: none; + } + + &:after { + border-color: rgba(136, 183, 213, 0); + border-top-color: #5498da; + border-width: 12px; + margin-left: -12px; + } + + &:hover { + &:after { + border-top-color: #9fc820; + } + } + + &:before { + border-color: rgba(84, 152, 218, 0); + border-top-color: black; + border-width: 15px; + margin-left: -15px; + } + + .bottom { + background: repeating-linear-gradient(60deg, rgba(84, 152, 218, 0), rgba(84, 152, 218, 0) 5px, rgba(255, 255, 255, 0.25) 5px, rgba(255, 255, 255, 0.25) 10px); + margin-top: 1em; + margin-left: -15px; + margin-right: -15px; + padding: 5px; + text-transform: uppercase; + } + + &.selected { + background-color: black; + + &:after, &:hover &:after { + border-top-color: black; + } + } + } +} diff --git a/app/assets/stylesheets/admin/components/save_bar.sass b/app/assets/stylesheets/admin/components/save_bar.sass deleted file mode 100644 index f453c5ddfc..0000000000 --- a/app/assets/stylesheets/admin/components/save_bar.sass +++ /dev/null @@ -1,14 +0,0 @@ -#save-bar - position: fixed - width: 100% - z-index: 100 - bottom: 0px - left: 0 - padding: 8px 8px - font-weight: bold - background-color: #eff5fc - color: #5498da - h5 - color: #5498da - input - margin-right: 5px diff --git a/app/assets/stylesheets/admin/components/save_bar.scss b/app/assets/stylesheets/admin/components/save_bar.scss new file mode 100644 index 0000000000..4b63a50bc8 --- /dev/null +++ b/app/assets/stylesheets/admin/components/save_bar.scss @@ -0,0 +1,19 @@ +#save-bar { + position: fixed; + width: 100%; + z-index: 100; + bottom: 0px; + left: 0; + padding: 8px 8px; + font-weight: bold; + background-color: #eff5fc; + color: #5498da; + + h5 { + color: #5498da; + } + + input { + margin-right: 5px; + } +} diff --git a/app/assets/stylesheets/admin/components/trial_progess_bar.sass b/app/assets/stylesheets/admin/components/trial_progess_bar.sass deleted file mode 100644 index cb6a648273..0000000000 --- a/app/assets/stylesheets/admin/components/trial_progess_bar.sass +++ /dev/null @@ -1,9 +0,0 @@ -#trial_progress_bar - position: fixed - left: 0px - bottom: 0px - width: 100vw - padding: 8px 10px - font-weight: bold - background-color: #5498da - color: white diff --git a/app/assets/stylesheets/admin/components/trial_progess_bar.scss b/app/assets/stylesheets/admin/components/trial_progess_bar.scss new file mode 100644 index 0000000000..049c1f1f3d --- /dev/null +++ b/app/assets/stylesheets/admin/components/trial_progess_bar.scss @@ -0,0 +1,10 @@ +#trial_progress_bar { + position: fixed; + left: 0px; + bottom: 0px; + width: 100vw; + padding: 8px 10px; + font-weight: bold; + background-color: #5498da; + color: white; +} diff --git a/app/assets/stylesheets/admin/dashboard-single-ent.css.sass b/app/assets/stylesheets/admin/dashboard-single-ent.css.sass deleted file mode 100644 index 567999b43d..0000000000 --- a/app/assets/stylesheets/admin/dashboard-single-ent.css.sass +++ /dev/null @@ -1,33 +0,0 @@ -@import ../darkswarm/mixins - -.alert-box - position: relative - display: block - background-color: #eff5dc - border: 1px solid #9fc820 - color: #666 - margin-top: 1em - margin-bottom: 1em - @include border-radius(3px) - transition: opacity 300ms ease-out - padding: 0.77778em 1.33333em 0.77778em 0.77778em - a.close - position: absolute - right: 5px - top: 0px - font-size: 1.5em - - -.dashboard_item.single-ent - .header - padding: 0.77778em 1.33333em 0.77778em 0.77778em - height: auto !important - .list - .button.bottom - width: 100% - -.button.big - width: 100% - font-size: 1rem - @include border-radius(25px) - padding: 15px \ No newline at end of file diff --git a/app/assets/stylesheets/admin/dashboard-single-ent.css.scss b/app/assets/stylesheets/admin/dashboard-single-ent.css.scss new file mode 100644 index 0000000000..b9aca09481 --- /dev/null +++ b/app/assets/stylesheets/admin/dashboard-single-ent.css.scss @@ -0,0 +1,45 @@ +@import "../darkswarm/mixins"; + +.alert-box { + position: relative; + display: block; + background-color: #eff5dc; + border: 1px solid #9fc820; + color: #666; + margin-top: 1em; + margin-bottom: 1em; + + @include border-radius(3px); + + transition: opacity 300ms ease-out; + padding: 0.77778em 1.33333em 0.77778em 0.77778em; + + a.close { + position: absolute; + right: 5px; + top: 0px; + font-size: 1.5em; + } +} + +.dashboard_item.single-ent { + .header { + padding: 0.77778em 1.33333em 0.77778em 0.77778em; + height: auto !important; + } + + .list { + .button.bottom { + width: 100%; + } + } +} + +.button.big { + width: 100%; + font-size: 1rem; + + @include border-radius(25px); + + padding: 15px; +} diff --git a/app/assets/stylesheets/admin/dashboard_item.css.sass b/app/assets/stylesheets/admin/dashboard_item.css.sass deleted file mode 100644 index 4bb4855660..0000000000 --- a/app/assets/stylesheets/admin/dashboard_item.css.sass +++ /dev/null @@ -1,159 +0,0 @@ -div.dashboard_item - margin-bottom: 30px - - .centered - text-align: center - - .text-icon - margin-top: 8px - display: block - font-size: 16px - font-weight: bold - color: #fff - padding: 0px 6px - border-radius: 10px - &.green - background-color: #9fc820 - &.red - background-color: #DA5354 - &.orange - background-color: #DA7F52 - - div.header - height: 50px - border-radius: 6px 6px 0px 0px - border: 1px solid #5498da - position: relative - - a[ofn-with-tip] - position: absolute - right: 5px - bottom: 5px - - &.red - border-color: #DA5354 - border-width: 3px - h3 - color: #DA5354 - - &.orange - border-color: #DA7F52 - border-width: 3px - h3 - color: #DA7F52 - - h3.alpha - height: 100% - padding: 10px 5px 0px 3% - - a - border-radius: 0px 4px 0px 0px - margin-left: 8px - height: 100% - padding: 15px 2px 0px 2px - - .tabs - height: 30px - border: solid #5498da - border-width: 0px 0px 1px 0px - margin-top: 3px - div.dashboard_tab - cursor: pointer - height: 30px - color: #fff - background-color: #5498da - padding: 5px 5px 0px 5px - text-align: center - font-weight: bold - border: solid #5498da - border-width: 1px 1px 0px 1px - &:hover - background-color: #9fc820 - &.selected - color: #5498da - background-color: #fff - - .list - max-height: 250px - overflow-y: auto - overflow-x: hidden - - .list-title - border: solid #5498da - border-width: 0px 1px 0px 1px - span - font-size: 105% - padding: 10px 0px - font-weight: bold - span.alpha - padding: 10px 2px 10px 5% - - .list-item - border: solid #5498da - border-width: 0px 1px 0px 1px - height: 38px - span.alpha - font-weight: bold - margin-left: -3px - padding: 10px 2px 0px 5% - span.omega - padding-right: 13px - margin-right: -3px - text-align: right - .icon-arrow-right - padding-top: 6px - font-size: 20px - .icon-warning-sign - color: #DA7F52 - font-size: 30px - .icon-remove-sign - color: #DA5354 - font-size: 30px - .icon-ok-sign - color: #9fc820 - font-size: 30px - &.orange - color: #DA7F52 - border: solid #DA7F52 - &.red - color: #DA5354 - border: solid #DA5354 - &.orange, &.red - border-width: 0px 3px 0px 3px - &.even - background-color: #fff - &.odd - background-color: #eff5fc - &.even, &.odd - &:hover - color: #ffffff - background-color: #9fc820 - .icon-arrow-right - color: #fff - .icon-remove-sign - color: #fff - .icon-warning-sign - color: #fff - .icon-ok-sign - color: #fff - .text-icon - &.green - color: #9fc820 - background-color: #fff - - a.button - color: #fff - font-size: 110% - font-weight: bold - text-align: center - &.orange - background-color: #DA7F52 - &.blue - background-color: #5498da - &.red - background-color: #DA5354 - &:hover - background-color: #9fc820 - &.bottom - border-radius: 0px 0px 6px 6px - padding: 15px 15px diff --git a/app/assets/stylesheets/admin/dashboard_item.css.scss b/app/assets/stylesheets/admin/dashboard_item.css.scss new file mode 100644 index 0000000000..f314500031 --- /dev/null +++ b/app/assets/stylesheets/admin/dashboard_item.css.scss @@ -0,0 +1,239 @@ +div.dashboard_item { + margin-bottom: 30px; + + .centered { + text-align: center; + } + + .text-icon { + margin-top: 8px; + display: block; + font-size: 16px; + font-weight: bold; + color: #fff; + padding: 0px 6px; + border-radius: 10px; + + &.green { + background-color: #9fc820; + } + + &.red { + background-color: #DA5354; + } + + &.orange { + background-color: #DA7F52; + } + } + + div.header { + height: 50px; + border-radius: 6px 6px 0px 0px; + border: 1px solid #5498da; + position: relative; + + a[ofn-with-tip] { + position: absolute; + right: 5px; + bottom: 5px; + } + + &.red { + border-color: #DA5354; + border-width: 3px; + + h3 { + color: #DA5354; + } + } + + &.orange { + border-color: #DA7F52; + border-width: 3px; + + h3 { + color: #DA7F52; + } + } + + h3.alpha { + height: 100%; + padding: 10px 5px 0px 3%; + } + + a { + border-radius: 0px 4px 0px 0px; + margin-left: 8px; + height: 100%; + padding: 15px 2px 0px 2px; + } + } + + .tabs { + height: 30px; + border: solid #5498da; + border-width: 0px 0px 1px 0px; + margin-top: 3px; + + div.dashboard_tab { + cursor: pointer; + height: 30px; + color: #fff; + background-color: #5498da; + padding: 5px 5px 0px 5px; + text-align: center; + font-weight: bold; + border: solid #5498da; + border-width: 1px 1px 0px 1px; + + &:hover { + background-color: #9fc820; + } + + &.selected { + color: #5498da; + background-color: #fff; + } + } + } + + .list { + max-height: 250px; + overflow-y: auto; + overflow-x: hidden; + } + + .list-title { + border: solid #5498da; + border-width: 0px 1px 0px 1px; + + span { + font-size: 105%; + padding: 10px 0px; + font-weight: bold; + } + + span.alpha { + padding: 10px 2px 10px 5%; + } + } + + .list-item { + border: solid #5498da; + border-width: 0px 1px 0px 1px; + height: 38px; + + span.alpha { + font-weight: bold; + margin-left: -3px; + padding: 10px 2px 0px 5%; + } + + span.omega { + padding-right: 13px; + margin-right: -3px; + text-align: right; + } + + .icon-arrow-right { + padding-top: 6px; + font-size: 20px; + } + + .icon-warning-sign { + color: #DA7F52; + font-size: 30px; + } + + .icon-remove-sign { + color: #DA5354; + font-size: 30px; + } + + .icon-ok-sign { + color: #9fc820; + font-size: 30px; + } + + &.orange { + color: #DA7F52; + border: solid #DA7F52; + } + + &.red { + color: #DA5354; + border: solid #DA5354; + } + + &.orange, &.red { + border-width: 0px 3px 0px 3px; + } + + &.even { + background-color: #fff; + } + + &.odd { + background-color: #eff5fc; + } + + &.even, &.odd { + &:hover { + color: #ffffff; + background-color: #9fc820; + + .icon-arrow-right { + color: #fff; + } + + .icon-remove-sign { + color: #fff; + } + + .icon-warning-sign { + color: #fff; + } + + .icon-ok-sign { + color: #fff; + } + + .text-icon { + &.green { + color: #9fc820; + background-color: #fff; + } + } + } + } + } + + a.button { + color: #fff; + font-size: 110%; + font-weight: bold; + text-align: center; + + &.orange { + background-color: #DA7F52; + } + + &.blue { + background-color: #5498da; + } + + &.red { + background-color: #DA5354; + } + + &:hover { + background-color: #9fc820; + } + + &.bottom { + border-radius: 0px 0px 6px 6px; + padding: 15px 15px; + } + } +} diff --git a/app/assets/stylesheets/admin/relationships.css.sass b/app/assets/stylesheets/admin/relationships.css.sass deleted file mode 100644 index a5b8879125..0000000000 --- a/app/assets/stylesheets/admin/relationships.css.sass +++ /dev/null @@ -1,23 +0,0 @@ -// TODO: Provide -moz- and -o- directives -@-webkit-keyframes alert-flash - 0% - background-color: #f9f1ae - - 100% - background-color: #fff - - -table#enterprise-relationships, table#enterprise-roles - ul - list-style-type: none - - th.actions, td.actions - width: 16% - .errors - color: #f00 - - tr.ng-enter - -webkit-animation-name: alert-flash - -webkit-animation-duration: 1200ms - -webkit-animation-iteration-count: 1 - -webkit-animation-timing-function: ease-in-out diff --git a/app/assets/stylesheets/admin/relationships.css.scss b/app/assets/stylesheets/admin/relationships.css.scss new file mode 100644 index 0000000000..103da85e47 --- /dev/null +++ b/app/assets/stylesheets/admin/relationships.css.scss @@ -0,0 +1,31 @@ +// TODO: Provide -moz- and -o- directives +@-webkit-keyframes alert-flash { + 0% { + background-color: #f9f1ae; + } + + 100% { + background-color: #fff; + } +} + +table#enterprise-relationships, table#enterprise-roles { + ul { + list-style-type: none; + } + + th.actions, td.actions { + width: 16%; + + .errors { + color: #f00; + } + } + + tr.ng-enter { + -webkit-animation-name: alert-flash; + -webkit-animation-duration: 1200ms; + -webkit-animation-iteration-count: 1; + -webkit-animation-timing-function: ease-in-out; + } +} diff --git a/app/assets/stylesheets/admin/side_menu.css.sass b/app/assets/stylesheets/admin/side_menu.css.sass deleted file mode 100644 index c218cfbcf0..0000000000 --- a/app/assets/stylesheets/admin/side_menu.css.sass +++ /dev/null @@ -1,18 +0,0 @@ -.side_menu - border-right: 2px solid #f6f6f6 - border-top: 2px solid #f6f6f6 - .menu_item - display: block - padding: 8px 15px - font-size: 120% - cursor: pointer - text-transform: uppercase - &:nth-child(odd) - background-color: #ebf3fb - &:nth-child(even) - background-color: #ffffff - &:hover - background-color: #eaf0f5 - &.selected - background-color: #5498da - color: #ffffff diff --git a/app/assets/stylesheets/admin/side_menu.css.scss b/app/assets/stylesheets/admin/side_menu.css.scss new file mode 100644 index 0000000000..921b9dee52 --- /dev/null +++ b/app/assets/stylesheets/admin/side_menu.css.scss @@ -0,0 +1,29 @@ +.side_menu { + border-right: 2px solid #f6f6f6; + border-top: 2px solid #f6f6f6; + + .menu_item { + display: block; + padding: 8px 15px; + font-size: 120%; + cursor: pointer; + text-transform: uppercase; + + &:nth-child(odd) { + background-color: #ebf3fb; + } + + &:nth-child(even) { + background-color: #ffffff; + } + + &:hover { + background-color: #eaf0f5; + } + + &.selected { + background-color: #5498da; + color: #ffffff; + } + } +} diff --git a/app/assets/stylesheets/admin/sidebar-item.css.sass b/app/assets/stylesheets/admin/sidebar-item.css.sass deleted file mode 100644 index d25d43ab29..0000000000 --- a/app/assets/stylesheets/admin/sidebar-item.css.sass +++ /dev/null @@ -1,77 +0,0 @@ -div.sidebar_item - margin-bottom: 30px - - .centered - text-align: center - - div.header - font-size: 105% - color: #fff - padding: 10px 0px - position: relative - &.blue - background-color: #5498da - &.red - background-color: #DA5354 - - .list - max-height: 400px - overflow-y: auto - overflow-x: hidden - &.red - color: #DA5354 - .list-item - border: solid #DA5354 - border-width: 0px 3px 0px 3px - a.alpha, span.alpha - margin-left: -3px - &.odd - background-color: #fcf6ef - &:hover - background-color: #9fc820 - a - color: #DA5354 - - .list-item - .icon-arrow-right - padding-top: 6px - font-size: 20px - border: solid #5498da - border-width: 0px 1px 0px 1px - a.alpha, span.alpha - font-weight: bold - margin-left: -1px - padding: 10px 2px 10px 5% - overflow: hidden - text-overflow: ellipsis - span.omega - padding: 8px 18px 8px 0px - margin-right: -3px - text-align: right - .icon-remove-sign - color: #DA5354 - font-size: 18px - &.even - background-color: #fff - &.odd - background-color: #eff5fc - &.even, &.odd - &:hover - color: #ffffff - background-color: #9fc820 - a - color: #ffffff - - a.button - color: #fff - padding: 15px 15px - font-weight: bold - text-align: center - border-radius: 0px - &.blue - background-color: #5498da - &.red - background-color: #DA5354 - &:hover - background-color: #9fc820 - diff --git a/app/assets/stylesheets/admin/sidebar-item.css.scss b/app/assets/stylesheets/admin/sidebar-item.css.scss new file mode 100644 index 0000000000..a3baa7b6e0 --- /dev/null +++ b/app/assets/stylesheets/admin/sidebar-item.css.scss @@ -0,0 +1,121 @@ +div.sidebar_item { + margin-bottom: 30px; + + .centered { + text-align: center; + } + + div.header { + font-size: 105%; + color: #fff; + padding: 10px 0px; + position: relative; + + &.blue { + background-color: #5498da; + } + + &.red { + background-color: #DA5354; + } + } + + .list { + max-height: 400px; + overflow-y: auto; + overflow-x: hidden; + + &.red { + color: #DA5354; + + .list-item { + border: solid #DA5354; + border-width: 0px 3px 0px 3px; + + a.alpha, span.alpha { + margin-left: -3px; + } + + &.odd { + background-color: #fcf6ef; + + &:hover { + background-color: #9fc820; + } + } + } + + a { + color: #DA5354; + } + } + } + + .list-item { + .icon-arrow-right { + padding-top: 6px; + font-size: 20px; + } + + border: solid #5498da; + border-width: 0px 1px 0px 1px; + + a.alpha, span.alpha { + font-weight: bold; + margin-left: -1px; + padding: 10px 2px 10px 5%; + overflow: hidden; + text-overflow: ellipsis; + } + + span.omega { + padding: 8px 18px 8px 0px; + margin-right: -3px; + text-align: right; + } + + .icon-remove-sign { + color: #DA5354; + font-size: 18px; + } + + &.even { + background-color: #fff; + } + + &.odd { + background-color: #eff5fc; + } + + &.even, &.odd { + &:hover { + color: #ffffff; + background-color: #9fc820; + + a { + color: #ffffff; + } + } + } + } + + a.button { + color: #fff; + padding: 15px 15px; + font-weight: bold; + text-align: center; + border-radius: 0px; + + &.blue { + background-color: #5498da; + } + + &.red { + background-color: #DA5354; + } + + &:hover { + background-color: #9fc820; + } + } +} diff --git a/app/assets/stylesheets/admin/variant_overrides.css.sass b/app/assets/stylesheets/admin/variant_overrides.css.sass deleted file mode 100644 index 0488c1d56b..0000000000 --- a/app/assets/stylesheets/admin/variant_overrides.css.sass +++ /dev/null @@ -1,6 +0,0 @@ -.variant-override-unit - float: right - font-style: italic - -button.hide:hover - background-color: #DA5354 diff --git a/app/assets/stylesheets/admin/variant_overrides.css.scss b/app/assets/stylesheets/admin/variant_overrides.css.scss new file mode 100644 index 0000000000..d4e3e4aaac --- /dev/null +++ b/app/assets/stylesheets/admin/variant_overrides.css.scss @@ -0,0 +1,8 @@ +.variant-override-unit { + float: right; + font-style: italic; +} + +button.hide:hover { + background-color: #DA5354; +} diff --git a/app/assets/stylesheets/admin/welcome.css.sass b/app/assets/stylesheets/admin/welcome.css.sass deleted file mode 100644 index 5d14fc1436..0000000000 --- a/app/assets/stylesheets/admin/welcome.css.sass +++ /dev/null @@ -1,16 +0,0 @@ -@import ../darkswarm/mixins - -#welcome_page - header - text-align: center - padding: 4em 2em - @include fullbg - background-image: url('/assets/home/tagline-bg.jpg') - background-repeat: no-repeat - background-position: center center - margin-bottom: 2em - p - text-transform: uppercase - font-weight: 300 - &, & * - color: white diff --git a/app/assets/stylesheets/admin/welcome.css.scss b/app/assets/stylesheets/admin/welcome.css.scss new file mode 100644 index 0000000000..fe31a07396 --- /dev/null +++ b/app/assets/stylesheets/admin/welcome.css.scss @@ -0,0 +1,24 @@ +@import "../darkswarm/mixins"; + +#welcome_page { + header { + text-align: center; + padding: 4em 2em; + + @include fullbg; + + background-image: url("/assets/home/tagline-bg.jpg"); + background-repeat: no-repeat; + background-position: center center; + margin-bottom: 2em; + + p { + text-transform: uppercase; + font-weight: 300; + } + + &, & * { + color: white; + } + } +} diff --git a/app/assets/stylesheets/darkswarm/_shop-filters.css.sass b/app/assets/stylesheets/darkswarm/_shop-filters.css.sass deleted file mode 100644 index 07fea7a0b1..0000000000 --- a/app/assets/stylesheets/darkswarm/_shop-filters.css.sass +++ /dev/null @@ -1,125 +0,0 @@ -@import mixins -@import branding -@import big-input -@import animations - -@mixin filter-selector($base-clr, $border-clr, $hover-clr) - &.inline-block, ul.inline-block - display: inline-block - - li - display: inline-block - @include border-radius(0) - padding: 0 - margin: 0 0 0.25rem 0.25rem - &:hover, &:focus - background: transparent - &.active - box-shadow: none - - a, a.button - display: block - padding-top: 0.5rem - @include border-radius(0.5em) - border: 1px solid $border-clr - padding: 0.5em 0.625em - font-size: 0.875em - color: $base-clr - font-size: 0.75em - background: white - margin: 0 - i - padding-left: 0.25rem - - render-svg - &, & svg - width: 1rem - height: 1rem - float: left - padding-right: 0.25rem - path - @include csstrans - fill: $base-clr - - &:hover, &:focus - border-color: $hover-clr - color: $hover-clr - render-svg - svg - path - fill: $hover-clr - - &.disabled - opacity: 0.6 - - &:hover, &:focus - border-color: $border-clr - color: $base-clr - render-svg - svg - path - fill: $base-clr - - - &.active, &.active:hover, &.active:focus - border: 1px solid $base-clr - background: $base-clr - color: white - render-svg - svg - path - fill: white - - -// Alert when search, taxon, filter is triggered - -.alert-box.search-alert - background-color: $clr-yellow-light - border-color: $clr-yellow-light - color: #777 - font-size: 0.75rem - padding: 0.5rem 0.75rem - - span.applied-properties - color: #333 - - span.applied-taxons - color: $clr-blue - - span.applied-search - color: $clr-brick - - span.filter-label - opacity: 0.75 - -// singleLineSelectors directive provides a drop-down that can overlap -// content. Ensure that the dropdown appears above the content. -.filter-row - position: relative - z-index: 90 - -.filter-shopfront - &.taxon-selectors, &.property-selectors - background: transparent - - single-line-selectors - overflow-x: hidden - white-space: nowrap - - .f-dropdown - overflow-x: auto - white-space: normal - - ul - margin: 0 - display: inline-block - ul, ul li - list-style: none - - // Shopfront taxons - &.taxon-selectors - @include filter-selector($clr-blue, $clr-blue-light, $clr-blue-bright) - - // Shopfront properties - &.property-selectors - @include filter-selector(#666, #ccc, #777) diff --git a/app/assets/stylesheets/darkswarm/_shop-filters.css.scss b/app/assets/stylesheets/darkswarm/_shop-filters.css.scss new file mode 100644 index 0000000000..16bca77cbc --- /dev/null +++ b/app/assets/stylesheets/darkswarm/_shop-filters.css.scss @@ -0,0 +1,173 @@ +@import "mixins"; +@import "branding"; +@import "big-input"; +@import "animations"; + +@mixin filter-selector($base-clr, $border-clr, $hover-clr) { + &.inline-block, ul.inline-block { + display: inline-block; + } + + li { + display: inline-block; + + @include border-radius(0); + + padding: 0; + margin: 0 0 0.25rem 0.25rem; + + &:hover, &:focus { + background: transparent; + } + + &.active { + box-shadow: none; + } + + a, a.button { + display: block; + padding-top: 0.5rem; + + @include border-radius(0.5em); + + border: 1px solid $border-clr; + padding: 0.5em 0.625em; + font-size: 0.875em; + color: $base-clr; + font-size: 0.75em; + background: white; + margin: 0; + + i { + padding-left: 0.25rem; + } + + render-svg { + &, & svg { + width: 1rem; + height: 1rem; + float: left; + padding-right: 0.25rem; + + path { + @include csstrans; + + fill: $base-clr; + } + } + } + + &:hover, &:focus { + border-color: $hover-clr; + color: $hover-clr; + + render-svg { + svg { + path { + fill: $hover-clr; + } + } + } + } + + &.disabled { + opacity: 0.6; + + &:hover, &:focus { + border-color: $border-clr; + color: $base-clr; + + render-svg { + svg { + path { + fill: $base-clr; + } + } + } + } + } + + &.active, &.active:hover, &.active:focus { + border: 1px solid $base-clr; + background: $base-clr; + color: white; + + render-svg { + svg { + path { + fill: white; + } + } + } + } + } + } +} + +// Alert when search, taxon, filter is triggered + +.alert-box.search-alert { + background-color: $clr-yellow-light; + border-color: $clr-yellow-light; + color: #777; + font-size: 0.75rem; + padding: 0.5rem 0.75rem; + + span.applied-properties { + color: #333; + } + + span.applied-taxons { + color: $clr-blue; + } + + span.applied-search { + color: $clr-brick; + } + + span.filter-label { + opacity: 0.75; + } +} + +// singleLineSelectors directive provides a drop-down that can overlap +// content. Ensure that the dropdown appears above the content. +.filter-row { + position: relative; + z-index: 90; +} + +.filter-shopfront { + &.taxon-selectors, &.property-selectors { + background: transparent; + + single-line-selectors { + overflow-x: hidden; + white-space: nowrap; + + .f-dropdown { + overflow-x: auto; + white-space: normal; + } + } + + ul { + margin: 0; + display: inline-block; + } + + ul, ul li { + list-style: none; + } + } + + // Shopfront taxons + &.taxon-selectors { + @include filter-selector($clr-blue, $clr-blue-light, $clr-blue-bright); + } + + // Shopfront properties + &.property-selectors { + @include filter-selector(#666, #ccc, #777); + } +} diff --git a/app/assets/stylesheets/darkswarm/_shop-inputs.css.sass b/app/assets/stylesheets/darkswarm/_shop-inputs.css.sass deleted file mode 100644 index b191913020..0000000000 --- a/app/assets/stylesheets/darkswarm/_shop-inputs.css.sass +++ /dev/null @@ -1,85 +0,0 @@ -@import mixins -@import variables -@import branding -@import big-input - - -.darkswarm - // #search - @include placeholder(rgba(0,0,0,0.4), #777) - - input#search - @include medium-input(rgba(0,0,0,0.3), #777, $clr-brick) - - // ordering - product - - input - @include border-radius(0) - @include csstrans - margin: 0 - width: 10rem - display: inline - @include box-shadow(none) - border-color: #b3b3b3 - text-align: right - - @media all and (max-width: 1024px) - width: 8rem - - @media all and (max-width: 768px) - width: 7rem - - @media all and (max-width: 640px) - float: left !important - font-size: 0.75rem - padding-left: 0.25rem - padding-right: 0.25rem - - @media all and (max-width: 480px) - width: 5.8rem - - &:hover - @include box-shadow(none) - border-color: #888 - background-color: #fafafa - - &:active, &:focus, &.active - @include box-shadow(none) - background-color: #f9f4b9 - border-color: #666 - - - // BULK - - input.bulk - width: 5rem - - @media all and (max-width: 1024px) - width: 4rem - - @media all and (max-width: 768px) - width: 3.5rem - - @media all and (max-width: 480px) - width: 2.8rem - - input.bulk.first - border-right: 0 - - input.bulk.second - border-left: 0 - - .bulk-input-container - float: right - - @media all and (max-width: 640px) - float: left !important - - .bulk-input - float: left - - - - - diff --git a/app/assets/stylesheets/darkswarm/_shop-inputs.css.scss b/app/assets/stylesheets/darkswarm/_shop-inputs.css.scss new file mode 100644 index 0000000000..55f3309477 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/_shop-inputs.css.scss @@ -0,0 +1,102 @@ +@import "mixins"; +@import "variables"; +@import "branding"; +@import "big-input"; + +.darkswarm { + // #search + @include placeholder(rgba(0, 0, 0, 0.4), #777); + + input#search { + @include medium-input(rgba(0, 0, 0, 0.3), #777, $clr-brick); + } + + // ordering + product { + input { + @include border-radius(0); + + @include csstrans; + + margin: 0; + width: 10rem; + display: inline; + + @include box-shadow(none); + + border-color: #b3b3b3; + text-align: right; + + @media all and (max-width: 1024px) { + width: 8rem; + } + + @media all and (max-width: 768px) { + width: 7rem; + } + + @media all and (max-width: 640px) { + float: left !important; + font-size: 0.75rem; + padding-left: 0.25rem; + padding-right: 0.25rem; + } + + @media all and (max-width: 480px) { + width: 5.8rem; + } + + &:hover { + @include box-shadow(none); + + border-color: #888; + background-color: #fafafa; + } + + &:active, &:focus, &.active { + @include box-shadow(none); + + background-color: #f9f4b9; + border-color: #666; + } + } + + // BULK + + input.bulk { + width: 5rem; + + @media all and (max-width: 1024px) { + width: 4rem; + } + + @media all and (max-width: 768px) { + width: 3.5rem; + } + + @media all and (max-width: 480px) { + width: 2.8rem; + } + } + + input.bulk.first { + border-right: 0; + } + + input.bulk.second { + border-left: 0; + } + + .bulk-input-container { + float: right; + + @media all and (max-width: 640px) { + float: left !important; + } + + .bulk-input { + float: left; + } + } + } +} diff --git a/app/assets/stylesheets/darkswarm/_shop-modals.css.sass b/app/assets/stylesheets/darkswarm/_shop-modals.css.sass deleted file mode 100644 index 405c1bc701..0000000000 --- a/app/assets/stylesheets/darkswarm/_shop-modals.css.sass +++ /dev/null @@ -1,6 +0,0 @@ - -.product-header - h1, h2, h3, h4, h5, h6 - margin: 0 - hr - margin: 0.5em 0 \ No newline at end of file diff --git a/app/assets/stylesheets/darkswarm/_shop-modals.css.scss b/app/assets/stylesheets/darkswarm/_shop-modals.css.scss new file mode 100644 index 0000000000..6215e22c9f --- /dev/null +++ b/app/assets/stylesheets/darkswarm/_shop-modals.css.scss @@ -0,0 +1,9 @@ +.product-header { + h1, h2, h3, h4, h5, h6 { + margin: 0; + } + + hr { + margin: 0.5em 0; + } +} diff --git a/app/assets/stylesheets/darkswarm/_shop-popovers.css.sass b/app/assets/stylesheets/darkswarm/_shop-popovers.css.sass deleted file mode 100644 index 38b77dd756..0000000000 --- a/app/assets/stylesheets/darkswarm/_shop-popovers.css.sass +++ /dev/null @@ -1,112 +0,0 @@ -@import mixins -@import branding - -// .darkswarm -// product - -ordercycle - .joyride-tip-guide - background-color: $clr-brick - .joyride-nub.right - border-color: $clr-brick !important - border-top-color: transparent !important - border-right-color: transparent !important - border-bottom-color: transparent !important - p - margin: 0 - font-weight: 700 - -// Pop over -// Foundation overrides -.joyride-tip-guide.price_breakdown - // JS needs to be tweaked to adjust for left alignment - this is dynamic can't rewrite in CSS - background-color: #999 - color: #1f1f1f - margin-left: -8px - @include box-shadow(0 1px 2px 0 rgba(0,0,0,0.7)) - - .joyride-content-wrapper - padding: 1.125rem 1.25rem 1.5rem - padding: 1rem - margin: 1% - width: 98% - background-color: white - - h1, h2, h3, h4, h5, h6 - color: #1f1f1f - - .joyride-nub.right - top: 38px - border-color: #999 !important - border-top-color: transparent !important - border-right-color: transparent !important - border-bottom-color: transparent !important - - .progress - background-color: #13bf85 - padding: 0 - border: none - color: white - font-size: 0.75rem - font-style: oblique - line-height: 1 - height: auto - .right - padding: 0.5rem 0.25rem 0 0 - .meter - background-color: #0b8c61 - padding: 0.5rem 0.25rem - border-right: 1px solid #539f92 - - .expanded - ul, li - list-style: none - margin: 0 - font-size: 0.875rem - li - background-color: #13bf85 - padding: 0 0.25rem - margin-bottom: 2px - color: white - li.cost - background-color: #0b8c61 - li:last-child - margin-bottom: 0.75rem - - -button.graph-button - // z-index: 9999999 - border: 1px solid transparent - padding: 0 - margin: 0 - display: inline - background-color: rgba(255,255,255,0.5) - padding: 4px - @include box-shadow(none) - @include border-radius(999rem) - &:before - @include icon-font - content: '\e639' - color: #999 - &:focus - border: 1px solid #e0e0e0 - background-color: rgba(255,255,255,1) - &:before - color: #666 - &:hover, &:active - background-color: rgba(255,255,255,1) - border: 1px solid transparent - &:before - color: $clr-brick-bright - @media all and (max-width: 768px) - // Hide for small - display: none - -button.graph-button.open - border: 1px solid transparent - background-color: $clr-brick-bright - &:before - content: '\e608' - color: white - - diff --git a/app/assets/stylesheets/darkswarm/_shop-popovers.css.scss b/app/assets/stylesheets/darkswarm/_shop-popovers.css.scss new file mode 100644 index 0000000000..92bfa84a30 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/_shop-popovers.css.scss @@ -0,0 +1,152 @@ +@import "mixins"; +@import "branding"; + +// .darkswarm +// product + +ordercycle { + .joyride-tip-guide { + background-color: $clr-brick; + + .joyride-nub.right { + border-color: $clr-brick !important; + border-top-color: transparent !important; + border-right-color: transparent !important; + border-bottom-color: transparent !important; + } + + p { + margin: 0; + font-weight: 700; + } + } +} + +// Pop over +// Foundation overrides +.joyride-tip-guide.price_breakdown { + // JS needs to be tweaked to adjust for left alignment - this is dynamic can't rewrite in CSS + background-color: #999; + color: #1f1f1f; + margin-left: -8px; + + @include box-shadow(0 1px 2px 0 rgba(0, 0, 0, 0.7)); + + .joyride-content-wrapper { + padding: 1.125rem 1.25rem 1.5rem; + padding: 1rem; + margin: 1%; + width: 98%; + background-color: white; + } + + h1, h2, h3, h4, h5, h6 { + color: #1f1f1f; + } + + .joyride-nub.right { + top: 38px; + border-color: #999 !important; + border-top-color: transparent !important; + border-right-color: transparent !important; + border-bottom-color: transparent !important; + } + + .progress { + background-color: #13bf85; + padding: 0; + border: none; + color: white; + font-size: 0.75rem; + font-style: oblique; + line-height: 1; + height: auto; + + .right { + padding: 0.5rem 0.25rem 0 0; + } + + .meter { + background-color: #0b8c61; + padding: 0.5rem 0.25rem; + border-right: 1px solid #539f92; + } + } + + .expanded { + ul, li { + list-style: none; + margin: 0; + font-size: 0.875rem; + } + + li { + background-color: #13bf85; + padding: 0 0.25rem; + margin-bottom: 2px; + color: white; + } + + li.cost { + background-color: #0b8c61; + } + + li:last-child { + margin-bottom: 0.75rem; + } + } +} + +button.graph-button { + // z-index: 9999999 + border: 1px solid transparent; + padding: 0; + margin: 0; + display: inline; + background-color: rgba(255, 255, 255, 0.5); + padding: 4px; + + @include box-shadow(none); + + @include border-radius(999rem); + + &:before { + @include icon-font; + + content: ""; + color: #999; + } + + &:focus { + border: 1px solid #e0e0e0; + background-color: rgba(255, 255, 255, 1); + + &:before { + color: #666; + } + } + + &:hover, &:active { + background-color: rgba(255, 255, 255, 1); + border: 1px solid transparent; + + &:before { + color: $clr-brick-bright; + } + } + + @media all and (max-width: 768px) { + // Hide for small + display: none; + } +} + +button.graph-button.open { + border: 1px solid transparent; + background-color: $clr-brick-bright; + + &:before { + content: ""; + color: white; + } +} diff --git a/app/assets/stylesheets/darkswarm/_shop-product-rows.css.sass b/app/assets/stylesheets/darkswarm/_shop-product-rows.css.sass deleted file mode 100644 index c0e4fb27aa..0000000000 --- a/app/assets/stylesheets/darkswarm/_shop-product-rows.css.sass +++ /dev/null @@ -1,123 +0,0 @@ -@import branding.css.sass -@import animations.sass - -.darkswarm - products - product - // GENERAL LAYOUT - .row - .columns - padding-top: 0em - padding-bottom: 0em - display: table - line-height: 1.1 - // outline: 1px solid red - - @media all and (max-width: 768px) - font-size: 0.875rem - - @media all and (max-width: 640px) - font-size: 0.75rem - - .table-cell - display: table-cell - vertical-align: middle - height: 37px - - // ROW VARIANTS - .row.variants - margin-left: 0 - margin-right: 0 - background-color: #ECECEC - &:hover, &:focus, &:active - background-color: $clr-brick-light - &:nth-of-type(even) - background-color: #f9f9f9 - &:hover, &:focus, &:active - background-color: $clr-brick-ultra-light - - &.out-of-stock - opacity: 0.2 - - // Variant name - .variant-name - padding-left: 7.9375rem - @media all and (max-width: 768px) - padding-left: 4.9375rem - - .variant-name - @media all and (max-width: 640px) - background: #333 - color: white - padding-left: 0.9375rem - font-weight: bold - .table-cell - height: 27px - - // Variant unit - .variant-unit - padding-left: 0rem - padding-right: 0rem - color: #888 - font-size: 0.875rem - overflow: hidden - - @media all and (max-width: 768px) - font-size: 0.75rem - - // Variant price - .variant-price - padding-left: 0.25rem - padding-right: 0.25rem - @media all and (max-width: 640px) - text-align: right - - // Total price - .total-price - padding-left: 0rem - color: $disabled-med - .filled - color: $med-drk-grey - @media all and (max-width: 640px) - background: #777 - color: $disabled-med - .filled - color: white - .table-cell - height: 27px - - // ROW SUMMARY - .row.summary - margin-left: 0 - margin-right: 0 - background: #fff - - .columns - padding-top: 1em - padding-bottom: 1em - line-height: 1 - - @media all and (max-width: 768px) - padding-top: 0.65rem - padding-bottom: 0.65rem - - .summary-header - padding-left: 7.9375rem - @media all and (max-width: 768px) - padding-left: 4.9375rem - @media all and (max-width: 640px) - padding-left: 0.9375rem - small - font-size: 80% - h3 - font-size: 1.5rem - margin: 0 - h3 a - color: #222 - i - @include csstrans - font-size: 0.6em - &:hover, &:focus, &:active - color: $clr-brick - i - font-size: 0.8em diff --git a/app/assets/stylesheets/darkswarm/_shop-product-rows.css.scss b/app/assets/stylesheets/darkswarm/_shop-product-rows.css.scss new file mode 100644 index 0000000000..d98457cd73 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/_shop-product-rows.css.scss @@ -0,0 +1,182 @@ +@import "branding"; +@import "animations"; + +.darkswarm { + products { + product { + // GENERAL LAYOUT + .row { + .columns { + padding-top: 0em; + padding-bottom: 0em; + display: table; + line-height: 1.1; + + // outline: 1px solid red + + @media all and (max-width: 768px) { + font-size: 0.875rem; + } + + @media all and (max-width: 640px) { + font-size: 0.75rem; + } + } + + .table-cell { + display: table-cell; + vertical-align: middle; + height: 37px; + } + } + + // ROW VARIANTS + .row.variants { + margin-left: 0; + margin-right: 0; + background-color: #ECECEC; + + &:hover, &:focus, &:active { + background-color: $clr-brick-light; + } + + &:nth-of-type(even) { + background-color: #f9f9f9; + + &:hover, &:focus, &:active { + background-color: $clr-brick-ultra-light; + } + } + + &.out-of-stock { + opacity: 0.2; + } + + // Variant name + .variant-name { + padding-left: 7.9375rem; + + @media all and (max-width: 768px) { + padding-left: 4.9375rem; + } + } + + .variant-name { + @media all and (max-width: 640px) { + background: #333; + color: white; + padding-left: 0.9375rem; + font-weight: bold; + + .table-cell { + height: 27px; + } + } + } + + // Variant unit + .variant-unit { + padding-left: 0rem; + padding-right: 0rem; + color: #888; + font-size: 0.875rem; + overflow: hidden; + + @media all and (max-width: 768px) { + font-size: 0.75rem; + } + } + + // Variant price + .variant-price { + padding-left: 0.25rem; + padding-right: 0.25rem; + + @media all and (max-width: 640px) { + text-align: right; + } + } + + // Total price + .total-price { + padding-left: 0rem; + color: $disabled-med; + + .filled { + color: $med-drk-grey; + } + + @media all and (max-width: 640px) { + background: #777; + color: $disabled-med; + + .filled { + color: white; + } + + .table-cell { + height: 27px; + } + } + } + } + + // ROW SUMMARY + .row.summary { + margin-left: 0; + margin-right: 0; + background: #fff; + + .columns { + padding-top: 1em; + padding-bottom: 1em; + line-height: 1; + + @media all and (max-width: 768px) { + padding-top: 0.65rem; + padding-bottom: 0.65rem; + } + } + + .summary-header { + padding-left: 7.9375rem; + + @media all and (max-width: 768px) { + padding-left: 4.9375rem; + } + + @media all and (max-width: 640px) { + padding-left: 0.9375rem; + } + + small { + font-size: 80%; + } + + h3 { + font-size: 1.5rem; + margin: 0; + } + + h3 a { + color: #222; + + i { + @include csstrans; + + font-size: 0.6em; + } + + &:hover, &:focus, &:active { + color: $clr-brick; + + i { + font-size: 0.8em; + } + } + } + } + } + } + } +} diff --git a/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.sass b/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.sass deleted file mode 100644 index 701e8005b0..0000000000 --- a/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.sass +++ /dev/null @@ -1,59 +0,0 @@ -@import branding -@import animations.sass - -.darkswarm - products - product - .product-thumb - @include csstrans - position: absolute - top: 2px - left: 0px - width: 7rem - height: 7rem - float: left - display: block - z-index: 1 - background-color: white - overflow: hidden - i - @include csstrans - transition-delay: 150ms - position: absolute - left: 45% - top: 45% - z-index: 99999 - color: white - font-size: 1rem - opacity: 0 - img - @include csstrans - opacity: 1 - @include transform-scale(scale(1)) - - &:hover, &:focus, &:active - background-color: $clr-brick - i - left: 32% - top: 30% - font-size: 3rem - opacity: 1 - img - opacity: 0.5 - @include transform-scale(scale(1.1)) - - @media all and (max-width: 768px) - top: 2px - width: 4rem - height: 4rem - &:hover, &:focus, &:active - i - left: 30% - top: 30% - font-size: 2rem - @media all and (max-width: 640px) - display: none - width: 0rem - height: 0rem - - diff --git a/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.scss b/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.scss new file mode 100644 index 0000000000..8a75467c9c --- /dev/null +++ b/app/assets/stylesheets/darkswarm/_shop-product-thumb.css.scss @@ -0,0 +1,81 @@ +@import "branding"; +@import "animations"; + +.darkswarm { + products { + product { + .product-thumb { + @include csstrans; + + position: absolute; + top: 2px; + left: 0px; + width: 7rem; + height: 7rem; + float: left; + display: block; + z-index: 1; + background-color: white; + overflow: hidden; + + i { + @include csstrans; + + transition-delay: 150ms; + position: absolute; + left: 45%; + top: 45%; + z-index: 99999; + color: white; + font-size: 1rem; + opacity: 0; + } + + img { + @include csstrans; + + opacity: 1; + + @include transform-scale(scale(1)); + } + + &:hover, &:focus, &:active { + background-color: $clr-brick; + + i { + left: 32%; + top: 30%; + font-size: 3rem; + opacity: 1; + } + + img { + opacity: 0.5; + + @include transform-scale(scale(1.1)); + } + } + + @media all and (max-width: 768px) { + top: 2px; + width: 4rem; + height: 4rem; + + &:hover, &:focus, &:active { + i { + left: 30%; + top: 30%; + font-size: 2rem; + } + } + } + + @media all and (max-width: 640px) { + display: none; + width: 0rem; + height: 0rem; + } + } + } + } +} diff --git a/app/assets/stylesheets/darkswarm/_shop-taxon-flag.css.sass b/app/assets/stylesheets/darkswarm/_shop-taxon-flag.css.sass deleted file mode 100644 index 7a6b04cbc2..0000000000 --- a/app/assets/stylesheets/darkswarm/_shop-taxon-flag.css.sass +++ /dev/null @@ -1,31 +0,0 @@ -.darkswarm - products - product - .taxon-flag - background: transparent url("/assets/flag.svg") top center no-repeat - background-size: 34px 39px - min-height: 40px - width: 34px - margin-top: -1.1rem - padding-top: 0.25rem - z-index: 999999 - @media all and (max-width: 480px) - background-size: 28px 32px - min-height: 32px - width: 28px - - render-svg - svg - width: 24px - height: 24px - path - fill: #999 - - @media all and (max-width: 768px) - margin-top: -0.85rem - - @media all and (max-width: 480px) - render-svg - svg - width: 18px - height: 18px diff --git a/app/assets/stylesheets/darkswarm/_shop-taxon-flag.css.scss b/app/assets/stylesheets/darkswarm/_shop-taxon-flag.css.scss new file mode 100644 index 0000000000..6a2c993600 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/_shop-taxon-flag.css.scss @@ -0,0 +1,45 @@ +.darkswarm { + products { + product { + .taxon-flag { + background: transparent url("/assets/flag.svg") top center no-repeat; + background-size: 34px 39px; + min-height: 40px; + width: 34px; + margin-top: -1.1rem; + padding-top: 0.25rem; + z-index: 999999; + + @media all and (max-width: 480px) { + background-size: 28px 32px; + min-height: 32px; + width: 28px; + } + + render-svg { + svg { + width: 24px; + height: 24px; + + path { + fill: #999; + } + } + } + + @media all and (max-width: 768px) { + margin-top: -0.85rem; + } + + @media all and (max-width: 480px) { + render-svg { + svg { + width: 18px; + height: 18px; + } + } + } + } + } + } +} diff --git a/app/assets/stylesheets/darkswarm/account.css.sass b/app/assets/stylesheets/darkswarm/account.css.sass deleted file mode 100644 index 0f3de5af57..0000000000 --- a/app/assets/stylesheets/darkswarm/account.css.sass +++ /dev/null @@ -1,70 +0,0 @@ -@import branding -@import mixins - -.orders - @include sidepaddingSm - @include panepadding - padding-top: 10px - - h3 - padding-top: 2em - - a - color: $clr-brick - &:hover, &:active, &:focus - color: $clr-brick-med-bright - - img - display: block - width: 80px - height: auto - - i.ofn-i_059-producer, i.ofn-i_060-producer-reversed - font-size: 3rem - display: inline-block - margin-right: 0.25rem - float: left - color: $clr-turquoise - - .credit - color: green - - .debit - color: $clr-brick - - .invalid - color: $ofn-grey - .credit - color: $ofn-grey - .debit - color: $ofn-grey - - .distributor-balance.paid - visibility: hidden - - .transaction-group - - - table - border-radius: 0.5em 0.5em 0 0 - tr:nth-of-type(even) - background: transparent // clear previous - tbody.odd - tr - background-color: #f9f9f9 - border: none - // Column widths for order table - .order1 - width: 20% - .order2 - width: 20% - .order3 - width: 20% - .order4 - width: 10% - .order5 - width: 10% - .order6 - width: 10% - .order7 - width: 10% diff --git a/app/assets/stylesheets/darkswarm/account.css.scss b/app/assets/stylesheets/darkswarm/account.css.scss new file mode 100644 index 0000000000..0d44ea1eaa --- /dev/null +++ b/app/assets/stylesheets/darkswarm/account.css.scss @@ -0,0 +1,106 @@ +@import "branding"; +@import "mixins"; + +.orders { + @include sidepaddingSm; + + @include panepadding; + + padding-top: 10px; + + h3 { + padding-top: 2em; + } + + a { + color: $clr-brick; + + &:hover, &:active, &:focus { + color: $clr-brick-med-bright; + } + } + + img { + display: block; + width: 80px; + height: auto; + } + + i.ofn-i_059-producer, i.ofn-i_060-producer-reversed { + font-size: 3rem; + display: inline-block; + margin-right: 0.25rem; + float: left; + color: $clr-turquoise; + } + + .credit { + color: green; + } + + .debit { + color: $clr-brick; + } + + .invalid { + color: $ofn-grey; + + .credit { + color: $ofn-grey; + } + + .debit { + color: $ofn-grey; + } + } + + .distributor-balance.paid { + visibility: hidden; + } + + .transaction-group {} + + table { + border-radius: 0.5em 0.5em 0 0; + + tr:nth-of-type(even) { + background: transparent; + } + + tbody.odd { + tr { + background-color: #f9f9f9; + border: none; + } + } + } + + // Column widths for order table + .order1 { + width: 20%; + } + + .order2 { + width: 20%; + } + + .order3 { + width: 20%; + } + + .order4 { + width: 10%; + } + + .order5 { + width: 10%; + } + + .order6 { + width: 10%; + } + + .order7 { + width: 10%; + } +} diff --git a/app/assets/stylesheets/darkswarm/active_table.css.sass b/app/assets/stylesheets/darkswarm/active_table.css.sass deleted file mode 100644 index b8280d22ca..0000000000 --- a/app/assets/stylesheets/darkswarm/active_table.css.sass +++ /dev/null @@ -1,114 +0,0 @@ -@import branding -@import mixins -@import "compass/css3/user-interface" - - -.no-results - font-size: 1.875rem - -.active_table - margin: 2em 0em - @include user-select(none) - .active_table_row - display: block - cursor: pointer - &:first-child - padding: 1rem 0 - -.active_table .active_table_node - margin-top: 0.25rem - display: block - border: 0 - - @media all and (max-width: 640px) - margin-bottom: 1rem - - //Generic text link style - a:hover, a:active, a:focus - color: $dark-grey - span - text-decoration: underline - - span.margin-top - margin-top: 0.5rem - display: inline-block - - // Generic text resize - @media all and (max-width: 640px) - &, & * - font-size: 0.875rem - fat > div label - &, & * - font-size: 0.75rem - - // Inherits from active_table - .active_table_row - border: 1px solid transparent - @include border-radius(0.5em) - - // Foundation overrides - margin-left: 0 - margin-right: 0 - - &, & > a.row - display: block - - .active_table_row.link - border: 0 - - &.open - // .active_table_row:nth-child(2) - - .active_table_row - border-left: 1px solid $disabled-bright - border-right: 1px solid $disabled-bright - border-top: 0 - border-bottom: 0 - @include border-radius(0) - - .active_table_row:first-child - border-top: 1px solid $disabled-bright - color: $dark-grey - @include border-radius-mixed(0.5em, 0.5em, 0, 0) - &:hover, &:active, &:focus - // color: $dark-grey - - .active_table_row:last-child - border-bottom: 1px solid $disabled-bright - @include border-radius-mixed(0, 0, 0.5em, 0.5em) - - - //Open row sections - .fat > div - border-top: 1px solid #aaa - @media all and (max-width: 640px) - margin-top: 1em - - ul, ol - font-size: 0.875rem - - [class*="block-grid-"] > li - padding-bottom: 0.25rem !important - - label - text-transform: uppercase - font-size: 0.75rem - margin-top: 0.25rem - margin-bottom: 0.25rem - color: #777 - - p.trans-sentence - text-transform: capitalize - - &.closed - &:hover, &:active, &:focus - .active_table_row.closed - border: 1px solid $disabled-bright - - &.current - &.closed - &, & * - color: $dark-grey - &.open - .active_table_row:first-child - color: $dark-grey diff --git a/app/assets/stylesheets/darkswarm/active_table.css.scss b/app/assets/stylesheets/darkswarm/active_table.css.scss new file mode 100644 index 0000000000..e343eb07d9 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/active_table.css.scss @@ -0,0 +1,160 @@ +@import "branding"; +@import "mixins"; +@import "compass/css3/user-interface"; + +.no-results { + font-size: 1.875rem; +} + +.active_table { + margin: 2em 0em; + + @include user-select(none); + + .active_table_row { + display: block; + cursor: pointer; + + &:first-child { + padding: 1rem 0; + } + } +} + +.active_table .active_table_node { + margin-top: 0.25rem; + display: block; + border: 0; + + @media all and (max-width: 640px) { + margin-bottom: 1rem; + } + + //Generic text link style + a:hover, a:active, a:focus { + color: $dark-grey; + + span { + text-decoration: underline; + } + } + + span.margin-top { + margin-top: 0.5rem; + display: inline-block; + } + + // Generic text resize + @media all and (max-width: 640px) { + &, & * { + font-size: 0.875rem; + } + + fat > div label { + &, & * { + font-size: 0.75rem; + } + } + } + + // Inherits from active_table + .active_table_row { + border: 1px solid transparent; + + @include border-radius(0.5em); + + // Foundation overrides + margin-left: 0; + margin-right: 0; + + &, & > a.row { + display: block; + } + } + + .active_table_row.link { + border: 0; + } + + &.open { + // .active_table_row:nth-child(2) + + .active_table_row { + border-left: 1px solid $disabled-bright; + border-right: 1px solid $disabled-bright; + border-top: 0; + border-bottom: 0; + + @include border-radius(0); + } + + .active_table_row:first-child { + border-top: 1px solid $disabled-bright; + color: $dark-grey; + + @include border-radius-mixed(0.5em, 0.5em, 0, 0); + + &:hover, &:active, &:focus { + // color: $dark-grey + + } + } + + .active_table_row:last-child { + border-bottom: 1px solid $disabled-bright; + + @include border-radius-mixed(0, 0, 0.5em, 0.5em); + } + + //Open row sections + .fat > div { + border-top: 1px solid #aaa; + + @media all and (max-width: 640px) { + margin-top: 1em; + } + + ul, ol { + font-size: 0.875rem; + } + + [class*="block-grid-"] > li { + padding-bottom: 0.25rem !important; + } + + label { + text-transform: uppercase; + font-size: 0.75rem; + margin-top: 0.25rem; + margin-bottom: 0.25rem; + color: #777; + } + } + + p.trans-sentence { + text-transform: capitalize; + } + } + + &.closed { + &:hover, &:active, &:focus { + .active_table_row.closed { + border: 1px solid $disabled-bright; + } + } + } + + &.current { + &.closed { + &, & * { + color: $dark-grey; + } + } + + &.open { + .active_table_row:first-child { + color: $dark-grey; + } + } + } +} diff --git a/app/assets/stylesheets/darkswarm/active_table_search.css.sass b/app/assets/stylesheets/darkswarm/active_table_search.css.sass deleted file mode 100644 index 49981895d0..0000000000 --- a/app/assets/stylesheets/darkswarm/active_table_search.css.sass +++ /dev/null @@ -1,117 +0,0 @@ -@import mixins -@import branding -@import big-input -@import animations - -// Filter-box -.row .row.filter-box - margin-left: 0 - margin-right: 0 - -.row.filter-box:first-child - border: 1px solid $clr-blue-light - @include border-radius(0.25em) - margin-top: 2px - @media all and (max-width: 640px) - margin-bottom: 1em - -.row.filter-box.clear-filters - background: transparent - margin-top: 1em - -products .filter-box - background: #f7f7f7 - -.filter-box - background: rgba(245,245,245,0.6) - .tdhead - padding: 0.25rem 0.5rem - margin-top: 0.9rem - color: $clr-blue - border-bottom: 1px solid $clr-blue-light - - // OVERRIDES - [class*="block-grid-"] - margin: 0 0 0.5rem 0 - [class*="block-grid-"] > li - padding-bottom: 0.5rem !important - - li - @include border-radius(12px) - padding-top: 0.5rem - margin-bottom: 0.25rem - &:hover, &:focus - background: rgba(255,255,255,0.25) - li.active - background: white - @include box-shadow(inset 0 1px 3px 0 rgba(143,48,29,0.5)) - - li.active a - color: $clr-brick - render-svg - svg - path - fill: $clr-brick - &:hover, &:focus - border-color: $clr-brick-bright - - li a - @include csstrans - display: table - table-layout: fixed - text-transform: capitalize - overflow: visible - line-height: 1 - color: $med-drk-grey - font-size: 0.875rem - span - display: table-cell - vertical-align: middle - text-align: left - i - display: block - font-size: 1.5rem - margin: 0 0.2rem 0 0 - - &:hover, &:focus - color: $clr-brick-bright - - render-svg - svg - path - fill: $clr-brick-bright - - &:active, &.active - color: $clr-brick - render-svg - svg - path - fill: $clr-brick - - render-svg - display: block - width: 1.5rem - height: 1.5rem - margin: 0 0.2rem 0 0 - padding: 0 - svg - width: 1.5rem - height: 1.5rem - path - fill: #666 - -.button.filterbtn - margin-bottom: 0 !important - min-width: 160px - -#active-table-search - position: relative - @include placeholder(rgba(0,0,0,0.4), #777) - input[type="text"] - @include big-input(rgba(0,0,0,0.3), #777, $clr-brick) - - - - - - diff --git a/app/assets/stylesheets/darkswarm/active_table_search.css.scss b/app/assets/stylesheets/darkswarm/active_table_search.css.scss new file mode 100644 index 0000000000..4159126538 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/active_table_search.css.scss @@ -0,0 +1,164 @@ +@import "mixins"; +@import "branding"; +@import "big-input"; +@import "animations"; + +// Filter-box +.row .row.filter-box { + margin-left: 0; + margin-right: 0; +} + +.row.filter-box:first-child { + border: 1px solid $clr-blue-light; + + @include border-radius(0.25em); + + margin-top: 2px; + + @media all and (max-width: 640px) { + margin-bottom: 1em; + } +} + +.row.filter-box.clear-filters { + background: transparent; + margin-top: 1em; +} + +products .filter-box { + background: #f7f7f7; +} + +.filter-box { + background: rgba(245, 245, 245, 0.6); + + .tdhead { + padding: 0.25rem 0.5rem; + margin-top: 0.9rem; + color: $clr-blue; + border-bottom: 1px solid $clr-blue-light; + } + + // OVERRIDES + [class*="block-grid-"] { + margin: 0 0 0.5rem 0; + } + + [class*="block-grid-"] > li { + padding-bottom: 0.5rem !important; + } + + li { + @include border-radius(12px); + + padding-top: 0.5rem; + margin-bottom: 0.25rem; + + &:hover, &:focus { + background: rgba(255, 255, 255, 0.25); + } + } + + li.active { + background: white; + + @include box-shadow(inset 0 1px 3px 0 rgba(143, 48, 29, 0.5)); + } + + li.active a { + color: $clr-brick; + + render-svg { + svg { + path { + fill: $clr-brick; + } + } + } + + &:hover, &:focus { + border-color: $clr-brick-bright; + } + } + + li a { + @include csstrans; + + display: table; + table-layout: fixed; + text-transform: capitalize; + overflow: visible; + line-height: 1; + color: $med-drk-grey; + font-size: 0.875rem; + + span { + display: table-cell; + vertical-align: middle; + text-align: left; + } + + i { + display: block; + font-size: 1.5rem; + margin: 0 0.2rem 0 0; + } + + &:hover, &:focus { + color: $clr-brick-bright; + + render-svg { + svg { + path { + fill: $clr-brick-bright; + } + } + } + } + + &:active, &.active { + color: $clr-brick; + + render-svg { + svg { + path { + fill: $clr-brick; + } + } + } + } + + render-svg { + display: block; + width: 1.5rem; + height: 1.5rem; + margin: 0 0.2rem 0 0; + padding: 0; + + svg { + width: 1.5rem; + height: 1.5rem; + + path { + fill: #666; + } + } + } + } +} + +.button.filterbtn { + margin-bottom: 0 !important; + min-width: 160px; +} + +#active-table-search { + position: relative; + + @include placeholder(rgba(0, 0, 0, 0.4), #777); + + input[type="text"] { + @include big-input(rgba(0, 0, 0, 0.3), #777, $clr-brick); + } +} diff --git a/app/assets/stylesheets/darkswarm/angular.css.sass b/app/assets/stylesheets/darkswarm/angular.css.scss similarity index 71% rename from app/assets/stylesheets/darkswarm/angular.css.sass rename to app/assets/stylesheets/darkswarm/angular.css.scss index 0430dd1390..bbb1d61c40 100644 --- a/app/assets/stylesheets/darkswarm/angular.css.sass +++ b/app/assets/stylesheets/darkswarm/angular.css.scss @@ -1,3 +1,4 @@ // https://docs.angularjs.org/api/ng/directive/ngCloak -[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak - display: none !important +[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { + display: none !important; +} diff --git a/app/assets/stylesheets/darkswarm/animations.sass b/app/assets/stylesheets/darkswarm/animations.sass deleted file mode 100644 index d452b69d39..0000000000 --- a/app/assets/stylesheets/darkswarm/animations.sass +++ /dev/null @@ -1,217 +0,0 @@ -@import mixins - -// ANIMATION FUNCTIONS - - -// -@-webkit-keyframes slideInDown - 0% - opacity: 0 - -webkit-transform: translateY(-20px) - transform: translateY(-20px) - 100% - -webkit-transform: translateY(0) - transform: translateY(0) - -@keyframes slideInDown - 0% - opacity: 0 - -webkit-transform: translateY(-20px) - -ms-transform: translateY(-20px) - transform: translateY(-20px) - 100% - -webkit-transform: translateY(0) - -ms-transform: translateY(0) - transform: translateY(0) - - - -@-webkit-keyframes slideOutUp - 0% - -webkit-transform: translateY(0) - transform: translateY(0) - 100% - opacity: 0 - -webkit-transform: translateY(-20px) - transform: translateY(-20px) - - -@keyframes slideOutUp - 0% - -webkit-transform: translateY(0) - -ms-transform: translateY(0) - transform: translateY(0) - -@-webkit-keyframes fadeIn - 0% - opacity: 0 - 100% - opacity: 1 - - -@keyframes fadeIn - 0% - opacity: 0 - 100% - opacity: 1 - -@-webkit-keyframes spin - 0% - -webkit-transform: rotate(0deg) - transform: rotate(0deg) - 100% - -webkit-transform: rotate(359deg) - transform: rotate(359deg) -@keyframes spin - 0% - -webkit-transform: rotate(0deg) - transform: rotate(0deg) - 100% - -webkit-transform: rotate(359deg) - transform: rotate(359deg) - -// ANIMATION CLASSES - -.fade - opacity: 0 - -webkit-transition: opacity .15s linear - transition: opacity .15s linear - -.fade.in - opacity: 1 - -.reveal-modal.fade - -webkit-transition: -webkit-transform .2s ease-out - -moz-transition: -moz-transform .2s ease-out - -o-transition: -o-transform .2s ease-out - transition: transform .2s ease-out - -webkit-transform: translate(0, -25%) - -ms-transform: translate(0, -25%) - transform: translate(0, -25%) - -.reveal-modal.in - -webkit-transform: translate(0, 0) - -ms-transform: translate(0, 0) - transform: translate(0, 0) - -.reveal-modal-bg.fade - filter: alpha(opacity = 0) - opacity: 0 - -.reveal-modal-bg.in - filter: alpha(opacity = 50) - opacity: .5 - - -.animate-repeat - &.ng-move, &.ng-enter, &.ng-leave - -webkit-transition: all 300ms linear - transition: all 300ms linear - - &.ng-leave - opacity: 1 - &.ng-leave-active - opacity: 0 - - &.ng-enter - opacity: 0 - &.ng-enter-active - opacity: 1 - -product.animate-repeat - &.ng-leave - border-color: rgba(153, 153, 153, 1) - &.ng-leave-active - border-color: rgba(153, 153, 153, 0) - - &.ng-enter - border-color: rgba(153, 153, 153, 0) - &.ng-enter-active - border-color: rgba(153, 153, 153, 1) - -.animate-show - -webkit-animation-name: slideInDown - animation-name: slideInDown - -webkit-animation-duration: 0.5s - animation-duration: 0.5s - -webkit-animation-fill-mode: both - animation-fill-mode: both - // line-height: 20px - // opacity: 1 - -.animate-show.ng-hide-add, -.animate-show.ng-hide-remove - // display: block !important - -.animate-show.ng-hide - -webkit-animation-name: slideOutUp - animation-name: slideOutUp - -webkit-animation-duration: 0.15s - animation-duration: 0.15s - -webkit-animation-fill-mode: both - animation-fill-mode: both - // line-height: 0 - // opacity: 0 - // padding: 0 10px - -.row.animate-show ~ .row - -webkit-animation-name: fadeIn - animation-name: fadeIn - -webkit-animation-duration: 0.5s - animation-duration: 0.5s - -webkit-animation-fill-mode: both - animation-fill-mode: both - -// - -.animate-slide - max-height: 500px - opacity: 1 !important - -webkit-transition: all 300ms ease-in-out - -moz-transition: all 300ms ease-in-out - -o-transition: all 300ms ease-in-out - transition: all 300ms ease-in-out - - &.ng-hide - overflow: hidden - max-height: 0 - opacity: 0 !important - - // &.ng-hide-add-active, &.ng-hide-remove-active - - &.ng-hide-add, &.ng-hide-remove - /* IMPORTANT: this needs to be here to make it visible during the animation - since the .ng-hide class is already on the element rendering - it as hidden. */ - display: block !important - - -.animate-show - opacity: 1 !important - -webkit-transition: all 300ms ease-in-out - -moz-transition: all 300ms ease-in-out - -o-transition: all 300ms ease-in-out - transition: all 300ms ease-in-out - - &.ng-hide - opacity: 0 !important - - // &.ng-hide-add-active, &.ng-hide-remove-active - - &.ng-hide-add, &.ng-hide-remove - /* IMPORTANT: this needs to be here to make it visible during the animation - since the .ng-hide class is already on the element rendering - it as hidden. */ - display: block !important - - - - - -@mixin csstrans - -webkit-transition: all 300ms ease - -moz-transition: all 300ms ease - -ms-transition: all 300ms ease - -o-transition: all 300ms ease - transition: all 300ms ease - -webkit-transform-style: preserve-3d diff --git a/app/assets/stylesheets/darkswarm/animations.scss b/app/assets/stylesheets/darkswarm/animations.scss new file mode 100644 index 0000000000..f9c7c1967f --- /dev/null +++ b/app/assets/stylesheets/darkswarm/animations.scss @@ -0,0 +1,271 @@ +@import "mixins"; + +// ANIMATION FUNCTIONS + +// +@-webkit-keyframes slideInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-20px); + transform: translateY(-20px); + } + + 100% { + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +@keyframes slideInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-20px); + -ms-transform: translateY(-20px); + transform: translateY(-20px); + } + + 100% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +@-webkit-keyframes slideOutUp { + 0% { + -webkit-transform: translateY(0); + transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-20px); + transform: translateY(-20px); + } +} + +@keyframes slideOutUp { + 0% { + -webkit-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} + +@-webkit-keyframes fadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes fadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@-webkit-keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} + +@keyframes spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} + +// ANIMATION CLASSES + +.fade { + opacity: 0; + -webkit-transition: opacity 0.15s linear; + transition: opacity 0.15s linear; +} + +.fade.in { + opacity: 1; +} + +.reveal-modal.fade { + -webkit-transition: -webkit-transform 0.2s ease-out; + -moz-transition: -moz-transform 0.2s ease-out; + -o-transition: -o-transform 0.2s ease-out; + transition: transform 0.2s ease-out; + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + transform: translate(0, -25%); +} + +.reveal-modal.in { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); +} + +.reveal-modal-bg.fade { + filter: alpha(opacity = 0); + opacity: 0; +} + +.reveal-modal-bg.in { + filter: alpha(opacity = 50); + opacity: 0.5; +} + +.animate-repeat { + &.ng-move, &.ng-enter, &.ng-leave { + -webkit-transition: all 300ms linear; + transition: all 300ms linear; + } + + &.ng-leave { + opacity: 1; + + &.ng-leave-active { + opacity: 0; + } + } + + &.ng-enter { + opacity: 0; + + &.ng-enter-active { + opacity: 1; + } + } +} + +product.animate-repeat { + &.ng-leave { + border-color: rgba(153, 153, 153, 1); + + &.ng-leave-active { + border-color: rgba(153, 153, 153, 0); + } + } + + &.ng-enter { + border-color: rgba(153, 153, 153, 0); + + &.ng-enter-active { + border-color: rgba(153, 153, 153, 1); + } + } +} + +.animate-show { + -webkit-animation-name: slideInDown; + animation-name: slideInDown; + -webkit-animation-duration: 0.5s; + animation-duration: 0.5s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + + // line-height: 20px + // opacity: 1 +} + +.animate-show.ng-hide-add, +.animate-show.ng-hide-remove { + // display: block !important +} + +.animate-show.ng-hide { + -webkit-animation-name: slideOutUp; + animation-name: slideOutUp; + -webkit-animation-duration: 0.15s; + animation-duration: 0.15s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + + // line-height: 0 + // opacity: 0 + // padding: 0 10px +} + +.row.animate-show ~ .row { + -webkit-animation-name: fadeIn; + animation-name: fadeIn; + -webkit-animation-duration: 0.5s; + animation-duration: 0.5s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +// + +.animate-slide { + max-height: 500px; + opacity: 1 !important; + -webkit-transition: all 300ms ease-in-out; + -moz-transition: all 300ms ease-in-out; + -o-transition: all 300ms ease-in-out; + transition: all 300ms ease-in-out; + + &.ng-hide { + overflow: hidden; + max-height: 0; + opacity: 0 !important; + } + + // &.ng-hide-add-active, &.ng-hide-remove-active + + &.ng-hide-add, &.ng-hide-remove { + /* IMPORTANT: this needs to be here to make it visible during the animation + * since the .ng-hide class is already on the element rendering + * it as hidden. */ + display: block !important; + } +} + +.animate-show { + opacity: 1 !important; + -webkit-transition: all 300ms ease-in-out; + -moz-transition: all 300ms ease-in-out; + -o-transition: all 300ms ease-in-out; + transition: all 300ms ease-in-out; + + &.ng-hide { + opacity: 0 !important; + } + + // &.ng-hide-add-active, &.ng-hide-remove-active + + &.ng-hide-add, &.ng-hide-remove { + /* IMPORTANT: this needs to be here to make it visible during the animation + * since the .ng-hide class is already on the element rendering + * it as hidden. */ + display: block !important; + } +} + +@mixin csstrans { + -webkit-transition: all 300ms ease; + -moz-transition: all 300ms ease; + -ms-transition: all 300ms ease; + -o-transition: all 300ms ease; + transition: all 300ms ease; + -webkit-transform-style: preserve-3d; +} diff --git a/app/assets/stylesheets/darkswarm/big-input.sass b/app/assets/stylesheets/darkswarm/big-input.sass deleted file mode 100644 index 0660fda460..0000000000 --- a/app/assets/stylesheets/darkswarm/big-input.sass +++ /dev/null @@ -1,93 +0,0 @@ -@import typography -@import branding -@import animations -@import mixins - -//Big search used in active table search \\ - -@mixin big-input($input, $inputhvr, $inputactv) - @include headingFont - @include csstrans - @include border-radius(0.5rem) - background: rgba(255,255,255,0.1) - border: 2px solid $input - font-size: 2rem - box-shadow: 0 - padding: 0.5rem 1rem - height: auto - width: 100% - margin-bottom: 0.5rem - box-shadow: none - color: $inputactv - @media all and (max-width: 640px) - font-size: 1.25rem - - &:hover - @include box-shadow(0 1px 1px 0 rgba(255,255,255,0.25)) - border: 2px solid $inputhvr - color: $inputactv - - &:active, &:focus, &.active - border: 2px solid $inputactv - color: $inputactv - background: white - background: rgba(255,255,255,0.5) - text-shadow: 0 0 10px #ffffff - padding: 1rem - letter-spacing: 0.02rem - outline: none - -@mixin big-input-static - outline: 0 - &:active, &:focus, &.active - padding: 0.75rem 1rem 0.35rem 1rem - letter-spacing: 0 - -@mixin medium-input($input, $inputhvr, $inputactv) - @include headingFont - @include csstrans - @include border-radius(0.5rem) - background: rgba(255,255,255,0.1) - border: 2px solid $input - font-size: 0.875rem - box-shadow: 0 - padding: 0.5rem 0.625rem 0.375rem - height: auto - width: 100% - margin-bottom: 0.5rem - box-shadow: none - color: $inputactv - - &:hover - @include box-shadow(0 1px 1px 0 rgba(255,255,255,0.25)) - border: 2px solid $inputhvr - color: $inputactv - - &:active, &:focus, &.active - border: 2px solid $inputactv - color: $inputactv - background: white - background: rgba(255,255,255,0.5) - text-shadow: 0 0 10px #ffffff - outline: none - -@mixin placeholder($placeholder, $placeholderhvr) - ::-webkit-input-placeholder - color: $placeholder - :-moz-placeholder - color: $placeholder - ::-moz-placeholder - color: $placeholder - :-ms-input-placeholder - color: $placeholder - - &:hover - ::-webkit-input-placeholder - color: $placeholderhvr - :-moz-placeholder - color: $placeholderhvr - ::-moz-placeholder - color: $placeholderhvr - :-ms-input-placeholder - color: $placeholderhvr - diff --git a/app/assets/stylesheets/darkswarm/big-input.scss b/app/assets/stylesheets/darkswarm/big-input.scss new file mode 100644 index 0000000000..7b9ecdac8d --- /dev/null +++ b/app/assets/stylesheets/darkswarm/big-input.scss @@ -0,0 +1,127 @@ +@import "typography"; +@import "branding"; +@import "animations"; +@import "mixins"; + +//Big search used in active table search \\ + +@mixin big-input($input, $inputhvr, $inputactv) { + @include headingFont; + + @include csstrans; + + @include border-radius(0.5rem); + + background: rgba(255, 255, 255, 0.1); + border: 2px solid $input; + font-size: 2rem; + box-shadow: 0; + padding: 0.5rem 1rem; + height: auto; + width: 100%; + margin-bottom: 0.5rem; + box-shadow: none; + color: $inputactv; + + @media all and (max-width: 640px) { + font-size: 1.25rem; + } + + &:hover { + @include box-shadow(0 1px 1px 0 rgba(255, 255, 255, 0.25)); + + border: 2px solid $inputhvr; + color: $inputactv; + } + + &:active, &:focus, &.active { + border: 2px solid $inputactv; + color: $inputactv; + background: white; + background: rgba(255, 255, 255, 0.5); + text-shadow: 0 0 10px #ffffff; + padding: 1rem; + letter-spacing: 0.02rem; + outline: none; + } +} + +@mixin big-input-static { + outline: 0; + + &:active, &:focus, &.active { + padding: 0.75rem 1rem 0.35rem 1rem; + letter-spacing: 0; + } +} + +@mixin medium-input($input, $inputhvr, $inputactv) { + @include headingFont; + + @include csstrans; + + @include border-radius(0.5rem); + + background: rgba(255, 255, 255, 0.1); + border: 2px solid $input; + font-size: 0.875rem; + box-shadow: 0; + padding: 0.5rem 0.625rem 0.375rem; + height: auto; + width: 100%; + margin-bottom: 0.5rem; + box-shadow: none; + color: $inputactv; + + &:hover { + @include box-shadow(0 1px 1px 0 rgba(255, 255, 255, 0.25)); + + border: 2px solid $inputhvr; + color: $inputactv; + } + + &:active, &:focus, &.active { + border: 2px solid $inputactv; + color: $inputactv; + background: white; + background: rgba(255, 255, 255, 0.5); + text-shadow: 0 0 10px #ffffff; + outline: none; + } +} + +@mixin placeholder($placeholder, $placeholderhvr) { + ::-webkit-input-placeholder { + color: $placeholder; + } + + -moz-placeholder: { + color: $placeholder; + }; + + ::-moz-placeholder { + color: $placeholder; + } + + -ms-input-placeholder: { + color: $placeholder; + }; + + &:hover { + ::-webkit-input-placeholder { + color: $placeholderhvr; + } + + -moz-placeholder: { + color: $placeholderhvr; + }; + + ::-moz-placeholder { + color: $placeholderhvr; + } + + -ms-input-placeholder: { + color: $placeholderhvr; + }; + } +} diff --git a/app/assets/stylesheets/darkswarm/branding.css.sass b/app/assets/stylesheets/darkswarm/branding.css.sass deleted file mode 100644 index 76614599cf..0000000000 --- a/app/assets/stylesheets/darkswarm/branding.css.sass +++ /dev/null @@ -1,38 +0,0 @@ -$ofn-brand: #f27052 -// e.g. australia, uk, norway specific color - -$ofn-grey: #808184 - - -// old colors: - -$clr-brick: #c1122b -$clr-brick-light: #f5e6e7 -$clr-brick-light-trans: rgba(245, 230, 231, 0.9) -$clr-brick-ultra-light: #faf5f6 -$clr-brick-bright: #eb4c46 -$clr-brick-med-bright: #e5a2a0 -$clr-brick-light-bright: #f5c4c9 - -$clr-turquoise: #0b8c61 -$clr-turquoise-light: #ceefe4 -$clr-turquoise-ultra-light: #e8f9f4 -$clr-turquoise-bright: #23a877 - -$clr-blue: #0096ad -$clr-blue-light: #85d9e5 -$clr-blue-bright: #14b6cc - -$clr-yellow-light: #faf6c7 - -$disabled-light: #e5e5e5 -$disabled-bright: #ccc -$disabled-med: #b3b3b3 -$disabled-dark: #999 -$disabled-v-dark: #808080 -$med-grey: #666 -$med-drk-grey: #444 -$dark-grey: #333 -$light-grey: #ddd -$black: #000 - diff --git a/app/assets/stylesheets/darkswarm/branding.css.scss b/app/assets/stylesheets/darkswarm/branding.css.scss new file mode 100644 index 0000000000..c9a68b513b --- /dev/null +++ b/app/assets/stylesheets/darkswarm/branding.css.scss @@ -0,0 +1,37 @@ +$ofn-brand: #f27052; + +// e.g. australia, uk, norway specific color + +$ofn-grey: #808184; + +// old colors: + +$clr-brick: #c1122b; +$clr-brick-light: #f5e6e7; +$clr-brick-light-trans: rgba(245, 230, 231, 0.9); +$clr-brick-ultra-light: #faf5f6; +$clr-brick-bright: #eb4c46; +$clr-brick-med-bright: #e5a2a0; +$clr-brick-light-bright: #f5c4c9; + +$clr-turquoise: #0b8c61; +$clr-turquoise-light: #ceefe4; +$clr-turquoise-ultra-light: #e8f9f4; +$clr-turquoise-bright: #23a877; + +$clr-blue: #0096ad; +$clr-blue-light: #85d9e5; +$clr-blue-bright: #14b6cc; + +$clr-yellow-light: #faf6c7; + +$disabled-light: #e5e5e5; +$disabled-bright: #ccc; +$disabled-med: #b3b3b3; +$disabled-dark: #999; +$disabled-v-dark: #808080; +$med-grey: #666; +$med-drk-grey: #444; +$dark-grey: #333; +$light-grey: #ddd; +$black: #000; diff --git a/app/assets/stylesheets/darkswarm/footer.sass b/app/assets/stylesheets/darkswarm/footer.sass deleted file mode 100644 index 80681cedfb..0000000000 --- a/app/assets/stylesheets/darkswarm/footer.sass +++ /dev/null @@ -1,78 +0,0 @@ -@import branding -@import mixins -@import animations - -footer - .row - p a - font-size: 0.875rem - a, a * - @include csstrans - color: white - &:hover, &:active, &:focus - color: rgba(white, 1) - text-decoration: underline - - .footer-global - background-color: $ofn-grey - padding-top: 60px - padding-bottom: 40px - .logo - width: 200px - height: 200px - background: $ofn-grey - @include border-radius(120px) - margin: -140px auto 0 auto - img - margin-top: 36px - - .alert-box - background-color: transparent - border: none - padding: 0 - .alert-cta - @include csstrans - width: 100% - border: 1px solid rgba($dark-grey, 0.35) - background-image: url("/assets/tile-wide.png") - background-position: center center - background-color: #bbb - padding: 12px 0 8px 0 - display: block - &, & * - @include csstrans - color: #333 - strong - letter-spacing: 0.5px - a:hover, a:active, a:focus - text-decoration: none - border-color: white - &, & * - color: rgba(white, 1) - .row - &, p, h1, h2, h3, h4, h5, h6 - color: $disabled-bright - - .footer-local - background: lighten($dark-grey, 3%) - @include panepadding - .row - &, p, h1, h2, h3, h4, h5, h6 - color: $disabled-med - p.secure-icon i - font-size: 10rem - color: rgba(white, 0.1) - p.secure-text - color: rgba($disabled-med, 0.35) - .social-icons - margin-bottom: 0.25rem - margin-top: 0.75rem - a - i - font-size: 1.5rem - color: white - &:hover, &:active, &:focus - text-decoration: none - i - color: lighten($dark-grey, 60%) - text-shadow: 2px 2px 0 black diff --git a/app/assets/stylesheets/darkswarm/footer.scss b/app/assets/stylesheets/darkswarm/footer.scss new file mode 100644 index 0000000000..56176a1b4d --- /dev/null +++ b/app/assets/stylesheets/darkswarm/footer.scss @@ -0,0 +1,127 @@ +@import "branding"; +@import "mixins"; +@import "animations"; + +footer { + .row { + p a { + font-size: 0.875rem; + } + + a, a * { + @include csstrans; + + color: white; + + &:hover, &:active, &:focus { + color: rgba(white, 1); + text-decoration: underline; + } + } + } + + .footer-global { + background-color: $ofn-grey; + padding-top: 60px; + padding-bottom: 40px; + + .logo { + width: 200px; + height: 200px; + background: $ofn-grey; + + @include border-radius(120px); + + margin: (-140px) auto 0 auto; + + img { + margin-top: 36px; + } + } + + .alert-box { + background-color: transparent; + border: none; + padding: 0; + + .alert-cta { + @include csstrans; + + width: 100%; + border: 1px solid rgba($dark-grey, 0.35); + background-image: url("/assets/tile-wide.png"); + background-position: center center; + background-color: #bbb; + padding: 12px 0 8px 0; + display: block; + + &, & * { + @include csstrans; + + color: #333; + } + + strong { + letter-spacing: 0.5px; + } + + a:hover, a:active, a:focus { + text-decoration: none; + border-color: white; + + &, & * { + color: rgba(white, 1); + } + } + } + } + + .row { + &, p, h1, h2, h3, h4, h5, h6 { + color: $disabled-bright; + } + } + } + + .footer-local { + background: lighten($dark-grey, 3%); + + @include panepadding; + + .row { + &, p, h1, h2, h3, h4, h5, h6 { + color: $disabled-med; + } + + p.secure-icon i { + font-size: 10rem; + color: rgba(white, 0.1); + } + + p.secure-text { + color: rgba($disabled-med, 0.35); + } + + .social-icons { + margin-bottom: 0.25rem; + margin-top: 0.75rem; + + a { + i { + font-size: 1.5rem; + color: white; + } + + &:hover, &:active, &:focus { + text-decoration: none; + + i { + color: lighten($dark-grey, 60%); + text-shadow: 2px 2px 0 black; + } + } + } + } + } + } +} diff --git a/app/assets/stylesheets/darkswarm/forms.css.sass b/app/assets/stylesheets/darkswarm/forms.css.sass deleted file mode 100644 index 7047159601..0000000000 --- a/app/assets/stylesheets/darkswarm/forms.css.sass +++ /dev/null @@ -1,5 +0,0 @@ -@import mixins -@import branding - -fieldset - border: 0 \ No newline at end of file diff --git a/app/assets/stylesheets/darkswarm/forms.css.scss b/app/assets/stylesheets/darkswarm/forms.css.scss new file mode 100644 index 0000000000..9ca7f6f391 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/forms.css.scss @@ -0,0 +1,6 @@ +@import "mixins"; +@import "branding"; + +fieldset { + border: 0; +} diff --git a/app/assets/stylesheets/darkswarm/groups.css.sass b/app/assets/stylesheets/darkswarm/groups.css.sass deleted file mode 100644 index 7243112b67..0000000000 --- a/app/assets/stylesheets/darkswarm/groups.css.sass +++ /dev/null @@ -1,108 +0,0 @@ -@import branding -@import mixins -@import typography - -// Search page -#groups - @include groupsbg - @include sidepaddingSm - @include panepadding - h1, p.text - font-weight: 300 - h1 - font-size: 350% - a > .group-name - &:hover, &:focus, &:active - text-decoration: underline - - .groups-icons - text-align: right - a - font-size: 1.5em - - .groups-header - border: 2px solid $clr-brick-light-bright - @include border-radius-mixed(0.5em, 0.5em, 0, 0) - margin: -1rem 0 1rem - padding: 1rem 0.9375rem - @media screen and (min-width: 640px) - border: 0 none - @include border-radius(0) - margin: 0 - padding: 0 - -.group - padding-bottom: 0.5em - .row div - font-size: 110% - .row a - vertical-align: middle - .ofn-i_035-groups - font-size: 120% - vertical-align: middle - -// Individual Page -#group-page - .group-logo, .group-header - text-align: center - .group-logo - padding-bottom: 1em - max-height: 200px - .group-name - border-bottom: 1px solid #ccc - @media screen and (min-width: 768px) - .group-logo, .group-header - text-align: left - .group-logo - max-height: 120px - float: left - padding-right: 1em - background-color: white - - // Tabs - .tabs dd a // Mobile first - padding: 0.25rem 0.45rem 0rem - font-size: 0.75rem - border: none - margin-bottom: -2px - margin-right: 2px - text-transform: capitalize - @include headingFont - @include border-radius(1em 0.25em 0 0) - @include gradient($disabled-light, $disabled-bright) - @media screen and (min-width: 768px) - .tabs dd a - padding: 0.5rem 1rem 0.25em - font-size: 0.875rem - @include border-radius(1.5em 0.25em 0 0) - @media screen and (min-width: 1024px) - .tabs dd a - padding: 0.75rem 1.5rem 0.5em - font-size: 1rem - @include border-radius(2em 0.25em 0 0) - .tabs dd.active a - @include gradient(white, white) - margin-bottom: -1px - border-top: 1px solid $light-grey - border-left: 1px solid $light-grey - border-right: 1px solid $light-grey - border-bottom: 0 - .tabs-content - border-top: 1px solid $light-grey - border-left: 1px solid $light-grey - border-right: 1px solid $light-grey - border-bottom: 1px solid $light-grey - padding: 1.5em - - // Producers tab - .producers - background-image: none - background-color: initial - .active_table .active_table_node a.is_distributor, .active_table .active_table_node a.is_distributor i.ofn-i_059-producer - color: $clr-turquoise - padding: 0 - // Hubs tab - .hubs - background-image: none - padding-top: 0 - padding-bottom: 0 diff --git a/app/assets/stylesheets/darkswarm/groups.css.scss b/app/assets/stylesheets/darkswarm/groups.css.scss new file mode 100644 index 0000000000..b6ba8087c7 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/groups.css.scss @@ -0,0 +1,169 @@ +@import "branding"; +@import "mixins"; +@import "typography"; + +// Search page +#groups { + @include groupsbg; + + @include sidepaddingSm; + + @include panepadding; + + h1, p.text { + font-weight: 300; + } + + h1 { + font-size: 350%; + } + + a > .group-name { + &:hover, &:focus, &:active { + text-decoration: underline; + } + } + + .groups-icons { + text-align: right; + + a { + font-size: 1.5em; + } + } + + .groups-header { + border: 2px solid $clr-brick-light-bright; + + @include border-radius-mixed(0.5em, 0.5em, 0, 0); + + margin: (-1rem) 0 1rem; + padding: 1rem 0.9375rem; + + @media screen and (min-width: 640px) { + border: 0 none; + + @include border-radius(0); + + margin: 0; + padding: 0; + } + } +} + +.group { + padding-bottom: 0.5em; + + .row div { + font-size: 110%; + } + + .row a { + vertical-align: middle; + } + + .ofn-i_035-groups { + font-size: 120%; + vertical-align: middle; + } +} + +// Individual Page +#group-page { + .group-logo, .group-header { + text-align: center; + } + + .group-logo { + padding-bottom: 1em; + max-height: 200px; + } + + .group-name { + border-bottom: 1px solid #ccc; + } + + @media screen and (min-width: 768px) { + .group-logo, .group-header { + text-align: left; + } + + .group-logo { + max-height: 120px; + float: left; + padding-right: 1em; + background-color: white; + } + } + + // Tabs + .tabs dd a { + padding: 0.25rem 0.45rem 0rem; + font-size: 0.75rem; + border: none; + margin-bottom: -2px; + margin-right: 2px; + text-transform: capitalize; + + @include headingFont; + + @include border-radius(1em 0.25em 0 0); + + @include gradient($disabled-light, $disabled-bright); + } + + @media screen and (min-width: 768px) { + .tabs dd a { + padding: 0.5rem 1rem 0.25em; + font-size: 0.875rem; + + @include border-radius(1.5em 0.25em 0 0); + } + } + + @media screen and (min-width: 1024px) { + .tabs dd a { + padding: 0.75rem 1.5rem 0.5em; + font-size: 1rem; + + @include border-radius(2em 0.25em 0 0); + } + } + + .tabs dd.active a { + @include gradient(white, white); + + margin-bottom: -1px; + border-top: 1px solid $light-grey; + border-left: 1px solid $light-grey; + border-right: 1px solid $light-grey; + border-bottom: 0; + } + + .tabs-content { + border-top: 1px solid $light-grey; + border-left: 1px solid $light-grey; + border-right: 1px solid $light-grey; + border-bottom: 1px solid $light-grey; + padding: 1.5em; + } + + // Producers tab + .producers { + background-image: none; + background-color: initial; + + .active_table .active_table_node a.is_distributor, .active_table .active_table_node a.is_distributor i.ofn-i_059-producer { + color: $clr-turquoise; + } + + padding: 0; + } + + // Hubs tab + .hubs { + background-image: none; + padding-top: 0; + padding-bottom: 0; + } +} diff --git a/app/assets/stylesheets/darkswarm/header.css.sass b/app/assets/stylesheets/darkswarm/header.css.sass deleted file mode 100644 index 523afca824..0000000000 --- a/app/assets/stylesheets/darkswarm/header.css.sass +++ /dev/null @@ -1,21 +0,0 @@ -@import variables - -nav.top-bar - margin-bottom: 0px - a.icon - &:hover - text-decoration: none - height: $topbar-height - color: white - i - font-size: 29px - line-height: $topbar-height - span - font-size: 13px - display: inline-block - line-height: $topbar-height - height: $topbar-height - vertical-align: top - -body > section[role='main'] - padding: 0px diff --git a/app/assets/stylesheets/darkswarm/header.css.scss b/app/assets/stylesheets/darkswarm/header.css.scss new file mode 100644 index 0000000000..6d04b31f0f --- /dev/null +++ b/app/assets/stylesheets/darkswarm/header.css.scss @@ -0,0 +1,31 @@ +@import "variables"; + +nav.top-bar { + margin-bottom: 0px; + + a.icon { + &:hover { + text-decoration: none; + } + + height: $topbar-height; + color: white; + + i { + font-size: 29px; + line-height: $topbar-height; + } + + span { + font-size: 13px; + display: inline-block; + line-height: $topbar-height; + height: $topbar-height; + vertical-align: top; + } + } +} + +body > section[role='main'] { + padding: 0px; +} diff --git a/app/assets/stylesheets/darkswarm/home_panes.css.sass b/app/assets/stylesheets/darkswarm/home_panes.css.sass deleted file mode 100644 index 4bb9c19aa6..0000000000 --- a/app/assets/stylesheets/darkswarm/home_panes.css.sass +++ /dev/null @@ -1,156 +0,0 @@ -@import branding -@import mixins -@import typography -@import animations -@import variables - -// Styling for big panes on homepage \\ -#panes - .pane - .row - @include panepadding - padding-top: 75px - padding-bottom: 75px - &.header - padding-bottom: 0 - &.content - padding-top: 0 - - -// Background styles \\ -#system.pane - background-color: white - -#brand-story.pane, #cta.pane, #shops.pane, #sell.pane - @include tiledPane - -#connect.pane - @include groupsbg - -#learn.pane - @include hubsbg - -#stats.pane - background-image: url("/assets/home/background-blurred-oranges.jpg") - background-position: center center - background-color: $ofn-grey - @include fullbg - @include paneWhiteText - - -// Content styles \\ -#brand-story.pane - .row - h2 - font-weight: 300 - font-size: 88px - p - @include bodyFont - font-size: 1.5rem - font-weight: 300 - @media all and (max-width: 768px) - h2 - font-size: 52px - p - font-size: 1.3rem - - a.text-vbig i - font-size: 75px - -#system.pane - .row .row - padding-bottom: 0 - @media all and (max-width: 640px) - .row .row - padding: 0 - - .home-icon-box - background-image: url("/assets/ofn-o.png") - background-position: center center - background-repeat: no-repeat - background-size: auto 100% - padding: 3rem 0 - text-align: center - margin-top: 2rem - @media all and (min-width: 642px) - margin-top: 0 - i - font-size: 4rem - a - display: block - width: 64px - height: 64px - margin: 0 auto - background-color: $brand-colour - background-position: center center - background-repeat: no-repeat - background-size: auto 100% - &.search - background-image: url("/assets/icon-mask-magnifier.png") - &.shop - background-image: url("/assets/icon-mask-apple.png") - &.pick-up-delivery - background-image: url("/assets/icon-mask-truck.png") - - h2 - font-size: 70px - font-weight: 300 - color: $brand-colour - @media all and (max-width: 640px) - font-size: 45px - - .home-icon-box-bottom - margin-top: 1rem - width: 100% - padding-left: 1rem - padding-right: 1rem - @media all and (min-width: 480px) - padding-left: 3rem - padding-right: 3rem - @media all and (min-width: 642px) - padding-left: 1rem - padding-right: 1rem - h4 - color: $brand-colour - border-bottom: 2px solid lighten($brand-colour, 20%) - text-align: center - padding: 1rem 0 - margin: 1.5rem 0 - -#cta.pane, #stats.pane - h2 - font-weight: 300 - font-size: 45px - margin-bottom: 2rem - @media all and (max-width: 830px) - font-size: 35px - -#stats.pane - .row.header - padding-bottom: 2rem - - h4 - font-weight: 300 - text-transform: uppercase - margin: 1.5rem 0 - display: inline-block - strong - display: block - font-weight: normal - font-size: 75px - -#shops.pane - @include paneWhiteText - h2 - margin-bottom: 2rem - font-size: 4.4rem - font-weight: 300 - -#shops-signup.pane - background-color: $brand-colour - -#sell-detail.pane - margin: 50px 0 - .row - padding-top: 25px - padding-bottom: 25px diff --git a/app/assets/stylesheets/darkswarm/home_panes.css.scss b/app/assets/stylesheets/darkswarm/home_panes.css.scss new file mode 100644 index 0000000000..92053189e2 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/home_panes.css.scss @@ -0,0 +1,225 @@ +@import "branding"; +@import "mixins"; +@import "typography"; +@import "animations"; +@import "variables"; + +// Styling for big panes on homepage \\ +#panes { + .pane { + .row { + @include panepadding; + + padding-top: 75px; + padding-bottom: 75px; + + &.header { + padding-bottom: 0; + } + + &.content { + padding-top: 0; + } + } + } +} + +// Background styles \\ +#system.pane { + background-color: white; +} + +#brand-story.pane, #cta.pane, #shops.pane, #sell.pane { + @include tiledPane; +} + +#connect.pane { + @include groupsbg; +} + +#learn.pane { + @include hubsbg; +} + +#stats.pane { + background-image: url("/assets/home/background-blurred-oranges.jpg"); + background-position: center center; + background-color: $ofn-grey; + + @include fullbg; + + @include paneWhiteText; +} + +// Content styles \\ +#brand-story.pane { + .row { + h2 { + font-weight: 300; + font-size: 88px; + } + + p { + @include bodyFont; + + font-size: 1.5rem; + font-weight: 300; + } + + @media all and (max-width: 768px) { + h2 { + font-size: 52px; + } + + p { + font-size: 1.3rem; + } + } + } + + a.text-vbig i { + font-size: 75px; + } +} + +#system.pane { + .row .row { + padding-bottom: 0; + } + + @media all and (max-width: 640px) { + .row .row { + padding: 0; + } + } + + .home-icon-box { + background-image: url("/assets/ofn-o.png"); + background-position: center center; + background-repeat: no-repeat; + background-size: auto 100%; + padding: 3rem 0; + text-align: center; + margin-top: 2rem; + + @media all and (min-width: 642px) { + margin-top: 0; + } + + i { + font-size: 4rem; + } + + a { + display: block; + width: 64px; + height: 64px; + margin: 0 auto; + background-color: $brand-colour; + background-position: center center; + background-repeat: no-repeat; + background-size: auto 100%; + + &.search { + background-image: url("/assets/icon-mask-magnifier.png"); + } + + &.shop { + background-image: url("/assets/icon-mask-apple.png"); + } + + &.pick-up-delivery { + background-image: url("/assets/icon-mask-truck.png"); + } + } + } + + h2 { + font-size: 70px; + font-weight: 300; + color: $brand-colour; + + @media all and (max-width: 640px) { + font-size: 45px; + } + } + + .home-icon-box-bottom { + margin-top: 1rem; + width: 100%; + padding-left: 1rem; + padding-right: 1rem; + + @media all and (min-width: 480px) { + padding-left: 3rem; + padding-right: 3rem; + } + + @media all and (min-width: 642px) { + padding-left: 1rem; + padding-right: 1rem; + } + + h4 { + color: $brand-colour; + border-bottom: 2px solid lighten($brand-colour, 20%); + text-align: center; + padding: 1rem 0; + margin: 1.5rem 0; + } + } +} + +#cta.pane, #stats.pane { + h2 { + font-weight: 300; + font-size: 45px; + margin-bottom: 2rem; + + @media all and (max-width: 830px) { + font-size: 35px; + } + } +} + +#stats.pane { + .row.header { + padding-bottom: 2rem; + } + + h4 { + font-weight: 300; + text-transform: uppercase; + margin: 1.5rem 0; + display: inline-block; + + strong { + display: block; + font-weight: normal; + font-size: 75px; + } + } +} + +#shops.pane { + @include paneWhiteText; + + h2 { + margin-bottom: 2rem; + font-size: 4.4rem; + font-weight: 300; + } +} + +#shops-signup.pane { + background-color: $brand-colour; +} + +#sell-detail.pane { + margin: 50px 0; + + .row { + padding-top: 25px; + padding-bottom: 25px; + } +} diff --git a/app/assets/stylesheets/darkswarm/home_tagline.css.sass b/app/assets/stylesheets/darkswarm/home_tagline.css.sass deleted file mode 100644 index fadaae70b0..0000000000 --- a/app/assets/stylesheets/darkswarm/home_tagline.css.sass +++ /dev/null @@ -1,33 +0,0 @@ -@import branding -@import mixins -@import variables - -// Styling for brand intro / tagline on homepage - -#tagline - width: 100% - &:before - content: "" - @include fullbg - background-color: $ofn-grey - background-image: url("/assets/home/home.jpg") - position: fixed - left: 0 - right: 0 - bottom: 0 - z-index: -1 - width: 100% - height: 100% - - h1 - margin-top: 2rem - @media all and (min-width: 768px) - margin-top: 10rem - img - max-width: 45% - @media all and (min-height: 500px) - max-width: 80% - - margin-bottom: 2rem - @media all and (min-width: 768px) - margin-bottom: 5rem diff --git a/app/assets/stylesheets/darkswarm/home_tagline.css.scss b/app/assets/stylesheets/darkswarm/home_tagline.css.scss new file mode 100644 index 0000000000..b278f79e25 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/home_tagline.css.scss @@ -0,0 +1,47 @@ +@import "branding"; +@import "mixins"; +@import "variables"; + +// Styling for brand intro / tagline on homepage + +#tagline { + width: 100%; + + &:before { + content: ""; + + @include fullbg; + + background-color: $ofn-grey; + background-image: url("/assets/home/home.jpg"); + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: -1; + width: 100%; + height: 100%; + } + + h1 { + margin-top: 2rem; + + @media all and (min-width: 768px) { + margin-top: 10rem; + } + + img { + max-width: 45%; + + @media all and (min-height: 500px) { + max-width: 80%; + } + + margin-bottom: 2rem; + + @media all and (min-width: 768px) { + margin-bottom: 5rem; + } + } + } +} diff --git a/app/assets/stylesheets/darkswarm/hub_node.css.sass b/app/assets/stylesheets/darkswarm/hub_node.css.sass deleted file mode 100644 index 4d175681f2..0000000000 --- a/app/assets/stylesheets/darkswarm/hub_node.css.sass +++ /dev/null @@ -1,168 +0,0 @@ -@import branding -@import mixins - -.hubs - .active_table .active_table_node - - //Prevents ugly overflows on hub title - .columns.skinny-head - white-space: nowrap - overflow-x: hidden - overflow-y: visible - - //Generic text link style - a, a * - color: $clr-brick - &:hover, &:active, &:focus - color: $clr-brick-bright - - //Hub and Producer icons - i.ofn-i_063-hub, i.ofn-i_064-hub-reversed, i.ofn-i_059-producer, i.ofn-i_060-producer-reversed - font-size: 2rem - display: inline-block - margin-right: 0.25rem - float: left - - //Closed & Open column - .open_closed - i - font-size: 2rem - float: right - margin-left: 0.5rem - - //Hub Link - @media all and (max-width: 640px) - a.hub - display: block - - //Hub Name - span.hub-name-listing - font-weight: 700 - &:after - content: ">>" - display: inline-block - margin-left: 5px - - //CLOSED row - &.closed - .active_table_row - border: 1px solid transparent - @media all and (max-width: 640px) - border-color: $clr-brick-light - &:hover, &:active, &:focus - border-color: $clr-brick-light-bright - - //OPEN row - &.open - .active_table_row, .active_table_row:first-child, .active_table_row:last-child - border-color: $clr-brick-light-bright - - &.open, &.closed - @media all and (max-width: 640px) - .active_table_row:first-child .skinny-head - background-color: $clr-brick-light - @include border-radius-mixed(0.5em, 0.5em, 0, 0) - margin-top: -1rem - margin-bottom: 1rem - padding-top: 1rem - padding-bottom: 1rem - label - margin-top: 1rem - - //Padding second row - &.open - .active_table_row:nth-child(2) - padding-bottom: 0.75rem - - - .producers-list - li.more-producers-link - .less - display: none - a:hover - text-decoration: underline - li.additional-producer - display: none - &.show-more-producers - li.additional-producer - display: block - li.more-producers-link - .more - display: none - .less - display: block - - //CURRENT hub (shows selected hub) - &.current - //overwrites active_table - &.closed, &.open - a, a * - color: $clr-brick - .active_table_row - border-color: $clr-brick - .active_table_row:first-child, .active_table_row:last-child - background-color: $clr-brick-light-trans - opacity: 1 - &:hover, &:focus, &:active - opacity: 0.9 - border-color: $clr-brick-bright - @media all and (max-width: 640px) - .active_table_row:first-child .skinny-head - background-color: rgba(255,255,255,0.85) - - - //INACTIVE - closed hub - &.inactive - &.closed, &.open - &, & * - color: $disabled-med - a, a * - color: $disabled-dark - &.closed - .active_table_row, .active_table_row:first-child, .active_table_row:last-child - &:hover, &:active, &:focus - border-color: $disabled-bright - &.open - .active_table_row, .active_table_row:first-child, .active_table_row:last-child - // border-color: $disabled-bright - &, &:hover, &:active, &:focus - border-color: $disabled-bright - - &.closed, &.open - - // & Current hub - &.current - .active_table_row, .active_table_row:first-child, .active_table_row:last-child - a, a * - color: $med-grey - border-color: $disabled-dark - background-color: rgba(220,220,220,0.5) - &:hover, &:focus, &:active - border-color: $disabled-dark - - // Small devices - @media all and (max-width: 640px) - .active_table_row:first-child .skinny-head - background-color: $disabled-bright - - // Small devices - @media all and (max-width: 640px) - .active_table_row, .active_table_row:first-child, .active_table_row:last-child - border-color: $disabled-bright - background-color: transparent - &:hover, &:focus, &:active - border-color: $disabled-bright - opacity: 0.85 - .active_table_row:first-child .skinny-head - background-color: $disabled-light - - //Is Profile - profile node - &.inactive.is_profile - &.closed, &.open - .active_table_row - &:hover, &:active, &:focus - border-color: transparent - cursor: auto - @media all and (max-width: 640px) - border-color: transparent - diff --git a/app/assets/stylesheets/darkswarm/hub_node.css.scss b/app/assets/stylesheets/darkswarm/hub_node.css.scss new file mode 100644 index 0000000000..5df4f68b01 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/hub_node.css.scss @@ -0,0 +1,255 @@ +@import "branding"; +@import "mixins"; + +.hubs { + .active_table .active_table_node { + //Prevents ugly overflows on hub title + .columns.skinny-head { + white-space: nowrap; + overflow-x: hidden; + overflow-y: visible; + } + + //Generic text link style + a, a * { + color: $clr-brick; + + &:hover, &:active, &:focus { + color: $clr-brick-bright; + } + } + + //Hub and Producer icons + i.ofn-i_063-hub, i.ofn-i_064-hub-reversed, i.ofn-i_059-producer, i.ofn-i_060-producer-reversed { + font-size: 2rem; + display: inline-block; + margin-right: 0.25rem; + float: left; + } + + //Closed & Open column + .open_closed { + i { + font-size: 2rem; + float: right; + margin-left: 0.5rem; + } + } + + //Hub Link + @media all and (max-width: 640px) { + a.hub { + display: block; + } + } + + //Hub Name + span.hub-name-listing { + font-weight: 700; + + &:after { + content: ">>"; + display: inline-block; + margin-left: 5px; + } + } + + //CLOSED row + &.closed { + .active_table_row { + border: 1px solid transparent; + + @media all and (max-width: 640px) { + border-color: $clr-brick-light; + } + + &:hover, &:active, &:focus { + border-color: $clr-brick-light-bright; + } + } + } + + //OPEN row + &.open { + .active_table_row, .active_table_row:first-child, .active_table_row:last-child { + border-color: $clr-brick-light-bright; + } + } + + &.open, &.closed { + @media all and (max-width: 640px) { + .active_table_row:first-child .skinny-head { + background-color: $clr-brick-light; + + @include border-radius-mixed(0.5em, 0.5em, 0, 0); + + margin-top: -1rem; + margin-bottom: 1rem; + padding-top: 1rem; + padding-bottom: 1rem; + } + + label { + margin-top: 1rem; + } + } + } + + //Padding second row + &.open { + .active_table_row:nth-child(2) { + padding-bottom: 0.75rem; + } + } + + .producers-list { + li.more-producers-link { + .less { + display: none; + } + + a:hover { + text-decoration: underline; + } + } + + li.additional-producer { + display: none; + } + + &.show-more-producers { + li.additional-producer { + display: block; + } + + li.more-producers-link { + .more { + display: none; + } + + .less { + display: block; + } + } + } + } + + //CURRENT hub (shows selected hub) + &.current { + //overwrites active_table + &.closed, &.open { + a, a * { + color: $clr-brick; + } + + .active_table_row { + border-color: $clr-brick; + } + + .active_table_row:first-child, .active_table_row:last-child { + background-color: $clr-brick-light-trans; + opacity: 1; + + &:hover, &:focus, &:active { + opacity: 0.9; + border-color: $clr-brick-bright; + } + } + + @media all and (max-width: 640px) { + .active_table_row:first-child .skinny-head { + background-color: rgba(255, 255, 255, 0.85); + } + } + } + } + + //INACTIVE - closed hub + &.inactive { + &.closed, &.open { + &, & * { + color: $disabled-med; + } + + a, a * { + color: $disabled-dark; + } + } + + &.closed { + .active_table_row, .active_table_row:first-child, .active_table_row:last-child { + &:hover, &:active, &:focus { + border-color: $disabled-bright; + } + } + } + + &.open { + .active_table_row, .active_table_row:first-child, .active_table_row:last-child { + // border-color: $disabled-bright + &, &:hover, &:active, &:focus { + border-color: $disabled-bright; + } + } + } + + &.closed, &.open { + // & Current hub + &.current { + .active_table_row, .active_table_row:first-child, .active_table_row:last-child { + a, a * { + color: $med-grey; + } + + border-color: $disabled-dark; + background-color: rgba(220, 220, 220, 0.5); + + &:hover, &:focus, &:active { + border-color: $disabled-dark; + } + } + + // Small devices + @media all and (max-width: 640px) { + .active_table_row:first-child .skinny-head { + background-color: $disabled-bright; + } + } + } + + // Small devices + @media all and (max-width: 640px) { + .active_table_row, .active_table_row:first-child, .active_table_row:last-child { + border-color: $disabled-bright; + background-color: transparent; + + &:hover, &:focus, &:active { + border-color: $disabled-bright; + opacity: 0.85; + } + } + + .active_table_row:first-child .skinny-head { + background-color: $disabled-light; + } + } + } + } + + //Is Profile - profile node + &.inactive.is_profile { + &.closed, &.open { + .active_table_row { + &:hover, &:active, &:focus { + border-color: transparent; + cursor: auto; + } + + @media all and (max-width: 640px) { + border-color: transparent; + } + } + } + } + } +} diff --git a/app/assets/stylesheets/darkswarm/hubs.css.sass b/app/assets/stylesheets/darkswarm/hubs.css.sass deleted file mode 100644 index 5946d6ee77..0000000000 --- a/app/assets/stylesheets/darkswarm/hubs.css.sass +++ /dev/null @@ -1,13 +0,0 @@ -@import branding -@import mixins - -#hubs - background-color: lighten($ofn-grey, 43%) - @include panepadding - @include sidepaddingSm - - .name-matches, .distance-matches - margin-top: 4em - - .more-controls - text-align: center diff --git a/app/assets/stylesheets/darkswarm/hubs.css.scss b/app/assets/stylesheets/darkswarm/hubs.css.scss new file mode 100644 index 0000000000..9845ce9ad8 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/hubs.css.scss @@ -0,0 +1,18 @@ +@import "branding"; +@import "mixins"; + +#hubs { + background-color: lighten($ofn-grey, 43%); + + @include panepadding; + + @include sidepaddingSm; + + .name-matches, .distance-matches { + margin-top: 4em; + } + + .more-controls { + text-align: center; + } +} diff --git a/app/assets/stylesheets/darkswarm/ie_warning.sass b/app/assets/stylesheets/darkswarm/ie_warning.sass deleted file mode 100644 index ede86a3083..0000000000 --- a/app/assets/stylesheets/darkswarm/ie_warning.sass +++ /dev/null @@ -1,32 +0,0 @@ -#ie-warning - margin-bottom: 0 - padding-bottom: 2rem - .ie-msg - background: rgba(255,255,255,0.15) - padding: 0.5rem - margin-bottom: 1rem - margin-top: 1rem - .browserbtn - text-align: center - margin-bottom: 1rem - a - color: white - font-size: 1rem - filter: alpha(opacity=70) - opacity: 0.7 - &:hover, &:active, &:focus - filter: alpha(opacity=100) - opacity: 1 - - a.browserlogo - display: block - width: 100% - i - font-size: 5rem - color: white - text-align: center - display: block - - - - diff --git a/app/assets/stylesheets/darkswarm/ie_warning.scss b/app/assets/stylesheets/darkswarm/ie_warning.scss new file mode 100644 index 0000000000..0ed3940734 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/ie_warning.scss @@ -0,0 +1,40 @@ +#ie-warning { + margin-bottom: 0; + padding-bottom: 2rem; + + .ie-msg { + background: rgba(255, 255, 255, 0.15); + padding: 0.5rem; + margin-bottom: 1rem; + margin-top: 1rem; + } + + .browserbtn { + text-align: center; + margin-bottom: 1rem; + + a { + color: white; + font-size: 1rem; + filter: alpha(opacity = 70); + opacity: 0.7; + + &:hover, &:active, &:focus { + filter: alpha(opacity = 100); + opacity: 1; + } + } + + a.browserlogo { + display: block; + width: 100%; + } + } + + i { + font-size: 5rem; + color: white; + text-align: center; + display: block; + } +} diff --git a/app/assets/stylesheets/darkswarm/images.css.sass b/app/assets/stylesheets/darkswarm/images.css.sass deleted file mode 100644 index b89cbb21fc..0000000000 --- a/app/assets/stylesheets/darkswarm/images.css.sass +++ /dev/null @@ -1,47 +0,0 @@ -@import mixins -@import variables -@import branding - -.product-img - padding: 5px - margin-bottom: 10px - outline: 1px solid #ccc - @include box-shadow(0 1px 2px 1px rgba(0,0,0,0.15)) - // placeholder for when no product images - &.placeholder - opacity: 0.35 - @media all and (max-width: 1024px) - display: none - -.hero-img - outline: 1px solid $disabled-bright - border-color: transparent - @include box-shadow(none) - width: 100% - min-height: 80px - height: inherit - max-height: 260px - overflow: hidden - @media all and (max-width: 640px) - min-height: 68px - -.hero-img-small - background-color: #333 - width: 100% - // min-height: 60px - height: inherit - overflow: hidden - margin: 0 0 1rem 0 !important - -.producer-logo - max-width: 220px - -@media only screen and (max-width: 1024px) - .product-img - margin-top: 2em - margin-bottom: 1em - - - - - diff --git a/app/assets/stylesheets/darkswarm/images.css.scss b/app/assets/stylesheets/darkswarm/images.css.scss new file mode 100644 index 0000000000..2a9f931f6e --- /dev/null +++ b/app/assets/stylesheets/darkswarm/images.css.scss @@ -0,0 +1,58 @@ +@import "mixins"; +@import "variables"; +@import "branding"; + +.product-img { + padding: 5px; + margin-bottom: 10px; + outline: 1px solid #ccc; + + @include box-shadow(0 1px 2px 1px rgba(0, 0, 0, 0.15)); + + // placeholder for when no product images + &.placeholder { + opacity: 0.35; + + @media all and (max-width: 1024px) { + display: none; + } + } +} + +.hero-img { + outline: 1px solid $disabled-bright; + border-color: transparent; + + @include box-shadow(none); + + width: 100%; + min-height: 80px; + height: inherit; + max-height: 260px; + overflow: hidden; + + @media all and (max-width: 640px) { + min-height: 68px; + } +} + +.hero-img-small { + background-color: #333; + width: 100%; + + // min-height: 60px + height: inherit; + overflow: hidden; + margin: 0 0 1rem 0 !important; +} + +.producer-logo { + max-width: 220px; +} + +@media only screen and (max-width: 1024px) { + .product-img { + margin-top: 2em; + margin-bottom: 1em; + } +} diff --git a/app/assets/stylesheets/darkswarm/lists.css.sass b/app/assets/stylesheets/darkswarm/lists.css.sass deleted file mode 100644 index 244d0dd862..0000000000 --- a/app/assets/stylesheets/darkswarm/lists.css.sass +++ /dev/null @@ -1,7 +0,0 @@ - -body ol - list-style-type: none - margin-left: 0em - padding-top: 1em - li - margin-left: 0 diff --git a/app/assets/stylesheets/darkswarm/lists.css.scss b/app/assets/stylesheets/darkswarm/lists.css.scss new file mode 100644 index 0000000000..51d54744ca --- /dev/null +++ b/app/assets/stylesheets/darkswarm/lists.css.scss @@ -0,0 +1,9 @@ +body ol { + list-style-type: none; + margin-left: 0em; + padding-top: 1em; + + li { + margin-left: 0; + } +} diff --git a/app/assets/stylesheets/darkswarm/loading.sass b/app/assets/stylesheets/darkswarm/loading.sass deleted file mode 100644 index d2950f07fd..0000000000 --- a/app/assets/stylesheets/darkswarm/loading.sass +++ /dev/null @@ -1,83 +0,0 @@ -@import "compass/css3/user-interface" - -#loading - .reveal-modal-bg - z-index: 101 - background: rgba(0, 0, 0, 0.85) - #message - width: 100% - text-align: center - position: absolute - z-index: 102 - margin: auto - top: 0 - left: 0 - bottom: 0 - right: 0 - @include user-select(none) - h1 - -webkit-animation: blurFadeIn 0.8s ease-in-out 0s - color: white - font-size: 1.15rem - position: fixed - text-align: center - left: 0 - right: 0 - margin: 0 auto - top: 55% - width: 100% - overflow: visible - - .loader - position: fixed - margin: 0 auto - left: 0 - right: 0 - top: 50% - margin-top: -30px - width: 60px - height: 60px - list-style: none - - li - background-color: #FFFFFF - width: 10px - height: 10px - float: right - margin-right: 5px - box-shadow: 0px 100px 20px rgba(0, 0, 0, 0.2) - - li:first-child - -webkit-animation: loadbars 0.6s cubic-bezier(0.645, 0.045, 0.355, 1) infinite 0s - - li:nth-child(2) - -webkit-animation: loadbars 0.6s ease-in-out infinite -0.2s - - li:nth-child(3) - -webkit-animation: loadbars 0.6s ease-in-out infinite -0.4s - -@-webkit-keyframes blurFadeIn - 0% - opacity: 0 - text-shadow: 0px 0px 10px #fff - -webkit-transform: scale(1.3) - 50% - opacity: 0.5 - text-shadow: 0px 0px 5px #fff - -webkit-transform: scale(1.1) - 100% - opacity: 1 - text-shadow: 0px 0px 0px #fff - -webkit-transform: scale(1) - -@-webkit-keyframes 'loadbars' - 0% - height: 10px - margin-top: 25px - 50% - height: 50px - margin-top: 0px - 100% - height: 10px - margin-top: 25px - diff --git a/app/assets/stylesheets/darkswarm/loading.scss b/app/assets/stylesheets/darkswarm/loading.scss new file mode 100644 index 0000000000..32a3d6b9b4 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/loading.scss @@ -0,0 +1,106 @@ +@import "compass/css3/user-interface"; + +#loading { + .reveal-modal-bg { + z-index: 101; + background: rgba(0, 0, 0, 0.85); + } + + #message { + width: 100%; + text-align: center; + position: absolute; + z-index: 102; + margin: auto; + top: 0; + left: 0; + bottom: 0; + right: 0; + + @include user-select(none); + + h1 { + -webkit-animation: blurFadeIn 0.8s ease-in-out 0s; + color: white; + font-size: 1.15rem; + position: fixed; + text-align: center; + left: 0; + right: 0; + margin: 0 auto; + top: 55%; + width: 100%; + overflow: visible; + } + } + + .loader { + position: fixed; + margin: 0 auto; + left: 0; + right: 0; + top: 50%; + margin-top: -30px; + width: 60px; + height: 60px; + list-style: none; + + li { + background-color: #FFFFFF; + width: 10px; + height: 10px; + float: right; + margin-right: 5px; + box-shadow: 0px 100px 20px rgba(0, 0, 0, 0.2); + } + + li:first-child { + -webkit-animation: loadbars 0.6s cubic-bezier(0.645, 0.045, 0.355, 1) infinite 0s; + } + + li:nth-child(2) { + -webkit-animation: loadbars 0.6s ease-in-out infinite -0.2s; + } + + li:nth-child(3) { + -webkit-animation: loadbars 0.6s ease-in-out infinite -0.4s; + } + } +} + +@-webkit-keyframes blurFadeIn { + 0% { + opacity: 0; + text-shadow: 0px 0px 10px #fff; + -webkit-transform: scale(1.3); + } + + 50% { + opacity: 0.5; + text-shadow: 0px 0px 5px #fff; + -webkit-transform: scale(1.1); + } + + 100% { + opacity: 1; + text-shadow: 0px 0px 0px #fff; + -webkit-transform: scale(1); + } +} + +@-webkit-keyframes 'loadbars' { + 0% { + height: 10px; + margin-top: 25px; + } + + 50% { + height: 50px; + margin-top: 0px; + } + + 100% { + height: 10px; + margin-top: 25px; + } +} diff --git a/app/assets/stylesheets/darkswarm/map.css.sass b/app/assets/stylesheets/darkswarm/map.css.sass deleted file mode 100644 index e2a25d61c9..0000000000 --- a/app/assets/stylesheets/darkswarm/map.css.sass +++ /dev/null @@ -1,50 +0,0 @@ -// Place all the styles related to the map controller here. -// They will automatically be included in application.css. -// You can use Sass (SCSS) here: http://sass-lang.com/ -@import big-input - -.map-container - width: 100% - map, .angular-google-map-container, google-map, .angular-google-map - display: block - height: 100% - width: 100% - - // https://github.com/zurb/foundation/issues/112 - img - max-width: none - height: auto - - #pac-input - @include big-input(#888, #333, $clr-brick) - @include big-input-static - font-size: 1.5rem - background: rgba(255,255,255,0.85) - width: 50% - margin-top: 1.2rem - margin-left: 1rem - @media all and (max-width: 768px) - width: 80% - &:active, &:focus, &.active - background: rgba(255,255,255, 1) - -.map-footer - position: fixed - z-index: 2 - width: 100% - height: 23px - left: 80px - right: 0 - bottom: 6px - margin: 0 - padding: 6px - font-size: 14px - font-weight: bold - text-shadow: 2px 2px #aaa - color: #fff - - a, a:hover, a:active, a:focus - color: #fff - - @media all and (max-width: 1025px) - left: 0px \ No newline at end of file diff --git a/app/assets/stylesheets/darkswarm/map.css.scss b/app/assets/stylesheets/darkswarm/map.css.scss new file mode 100644 index 0000000000..d880b3025b --- /dev/null +++ b/app/assets/stylesheets/darkswarm/map.css.scss @@ -0,0 +1,64 @@ +// Place all the styles related to the map controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ +@import "big-input"; + +.map-container { + width: 100%; + + map, .angular-google-map-container, google-map, .angular-google-map { + display: block; + height: 100%; + width: 100%; + } + + // https://github.com/zurb/foundation/issues/112 + img { + max-width: none; + height: auto; + } + + #pac-input { + @include big-input(#888, #333, $clr-brick); + + @include big-input-static; + + font-size: 1.5rem; + background: rgba(255, 255, 255, 0.85); + width: 50%; + margin-top: 1.2rem; + margin-left: 1rem; + + @media all and (max-width: 768px) { + width: 80%; + } + + &:active, &:focus, &.active { + background: rgba(255, 255, 255, 1); + } + } +} + +.map-footer { + position: fixed; + z-index: 2; + width: 100%; + height: 23px; + left: 80px; + right: 0; + bottom: 6px; + margin: 0; + padding: 6px; + font-size: 14px; + font-weight: bold; + text-shadow: 2px 2px #aaa; + color: #fff; + + a, a:hover, a:active, a:focus { + color: #fff; + } + + @media all and (max-width: 1025px) { + left: 0px; + } +} diff --git a/app/assets/stylesheets/darkswarm/menu.css.sass b/app/assets/stylesheets/darkswarm/menu.css.sass deleted file mode 100644 index 41b86f72ff..0000000000 --- a/app/assets/stylesheets/darkswarm/menu.css.sass +++ /dev/null @@ -1,137 +0,0 @@ -@import compass -@import branding -@import mixins -@import typography -@import variables - - -nav - @include textpress - text-shadow: none - - // Create center style for nav ul (foundation provides left and right) - text-align: center - .top-bar-section - // Avoid menu items blocking logo - li:not(.has-form), li:not(.has-form) a:not(.button), li:not(.has-form) a:not(.button):hover - background-color: transparent - - ul.center - display: inline-block - // By default, we center between the left and right uls, but we want to be centered - // relative to the whole page. The difference in width between the other uls is 74px, - // so we offset by that amount here. - margin-left: -74px - - .joyride-tip-guide .button - text-shadow: none - - // Default overrides - big menu - .top-bar-section .has-dropdown > a - padding-right: $topbar-height / 3 !important - - i.ofn-i_022-cog - font-size: 24px - line-height: $topbar-height - - .top-bar-section .has-dropdown > a:after - display: none - - .top-bar-section ul li > a - font-size: 0.75rem - height: $topbar-height - opacity: 0.8 - &:hover, &:focus, &:active - opacity: 1 - @include transition(all 0.3s ease-in-out) - - .top-bar-section ul li.ofn-logo > a - display: table-cell - vertical-align: middle - opacity: 1 - - .nav-branded - color: $brand-colour - span - font-size: 13px - .nav-primary - @include headingFont - font-size: 0.875rem - font-weight: 300 - ul .nav-primary - text-transform: uppercase - ul.dropdown - border: 1px solid $smoke - border-top: none - - -// Mobile Menu - -.tab-bar - background-color: white - -.off-canvas-wrap.move-right .tab-bar .menu-icon - @include box-shadow(inset 0 0 6px 2px rgba(0,0,0,0.5)) - -.off-canvas-wrap .tab-bar .menu-icon - @include box-shadow(none) - -.off-canvas-wrap.move-right .tab-bar .menu-icon span - -moz-box-shadow: 0 0px 0 1px #666, 0 7px 0 1px #666, 0 14px 0 1px #666 - -webkit-box-shadow: 0 0px 0 1px #666, 0 7px 0 1px #666, 0 14px 0 1px #666 - box-shadow: 0 0px 0 1px #666, 0 7px 0 1px #666, 0 14px 0 1px #666 - -.tab-bar .menu-icon span::after - box-shadow: 0 0 0 1px black, 0 7px 0 1px black, 0 14px 0 1px black - -.tab-bar .ofn-logo - padding: 9px 0 0 9px - -.left-off-canvas-menu - background-color: white - -.off-canvas-wrap.move-right ul.off-canvas-list - font-size: 0.875rem - - .li-menu - @include headingFont - font-size: 1rem - a - color: rgba(0, 0, 0, 0.9) - li a - color: rgba(0, 0, 0, 0.9) - &:hover - background-color: transparent - color: $brand-colour - @include transition(all 0.3s ease-in-out) - -.off-canvas-wrap.move-right ul.off-canvas-list i - font-size: 1.5rem - margin-right: 0.25rem - - - -// Responsive - -@media screen and (max-width: 1450px) - nav .top-bar-section - ul li a, .has-dropdown > a - padding: 0 $topbar-height / 8 !important - - ul.center - margin-left: -24px - - -@media screen and (min-width: 1025px) - body.off-canvas - // padding required to placehold for fixed menu bar - padding-top: $topbar-height - - -@media screen and (max-width: 1025px) - body.off-canvas - // padding required to placehold for fixed menu bar - padding-top: 0 - section.right - .nav-branded - padding: 0 1em diff --git a/app/assets/stylesheets/darkswarm/menu.css.scss b/app/assets/stylesheets/darkswarm/menu.css.scss new file mode 100644 index 0000000000..801ccb57a4 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/menu.css.scss @@ -0,0 +1,186 @@ +@import "compass"; +@import "branding"; +@import "mixins"; +@import "typography"; +@import "variables"; + +nav { + @include textpress; + + text-shadow: none; + + // Create center style for nav ul (foundation provides left and right) + text-align: center; + + .top-bar-section { + // Avoid menu items blocking logo + li:not(.has-form), li:not(.has-form) a:not(.button), li:not(.has-form) a:not(.button):hover { + background-color: transparent; + } + + ul.center { + display: inline-block; + + // By default, we center between the left and right uls, but we want to be centered + // relative to the whole page. The difference in width between the other uls is 74px, + // so we offset by that amount here. + margin-left: -74px; + } + } + + .joyride-tip-guide .button { + text-shadow: none; + } + + // Default overrides - big menu + .top-bar-section .has-dropdown > a { + padding-right: ($topbar-height / 3) !important; + + i.ofn-i_022-cog { + font-size: 24px; + line-height: $topbar-height; + } + } + + .top-bar-section .has-dropdown > a:after { + display: none; + } + + .top-bar-section ul li > a { + font-size: 0.75rem; + height: $topbar-height; + opacity: 0.8; + + &:hover, &:focus, &:active { + opacity: 1; + } + + @include transition(all 0.3s ease-in-out); + } + + .top-bar-section ul li.ofn-logo > a { + display: table-cell; + vertical-align: middle; + opacity: 1; + } + + .nav-branded { + color: $brand-colour; + + span { + font-size: 13px; + } + } + + .nav-primary { + @include headingFont; + + font-size: 0.875rem; + font-weight: 300; + } + + ul .nav-primary { + text-transform: uppercase; + } + + ul.dropdown { + border: 1px solid $smoke; + border-top: none; + } +} + +// Mobile Menu + +.tab-bar { + background-color: white; +} + +.off-canvas-wrap.move-right .tab-bar .menu-icon { + @include box-shadow(inset 0 0 6px 2px rgba(0, 0, 0, 0.5)); +} + +.off-canvas-wrap .tab-bar .menu-icon { + @include box-shadow(none); +} + +.off-canvas-wrap.move-right .tab-bar .menu-icon span { + -moz-box-shadow: 0 0px 0 1px #666, 0 7px 0 1px #666, 0 14px 0 1px #666; + -webkit-box-shadow: 0 0px 0 1px #666, 0 7px 0 1px #666, 0 14px 0 1px #666; + box-shadow: 0 0px 0 1px #666, 0 7px 0 1px #666, 0 14px 0 1px #666; +} + +.tab-bar .menu-icon span::after { + box-shadow: 0 0 0 1px black, 0 7px 0 1px black, 0 14px 0 1px black; +} + +.tab-bar .ofn-logo { + padding: 9px 0 0 9px; +} + +.left-off-canvas-menu { + background-color: white; +} + +.off-canvas-wrap.move-right ul.off-canvas-list { + font-size: 0.875rem; + + .li-menu { + @include headingFont; + + font-size: 1rem; + + a { + color: rgba(0, 0, 0, 0.9); + } + } + + li a { + color: rgba(0, 0, 0, 0.9); + + &:hover { + background-color: transparent; + color: $brand-colour; + } + + @include transition(all 0.3s ease-in-out); + } +} + +.off-canvas-wrap.move-right ul.off-canvas-list i { + font-size: 1.5rem; + margin-right: 0.25rem; +} + +// Responsive + +@media screen and (max-width: 1450px) { + nav .top-bar-section { + ul li a, .has-dropdown > a { + padding: 0 ($topbar-height / 8) !important; + } + + ul.center { + margin-left: -24px; + } + } +} + +@media screen and (min-width: 1025px) { + body.off-canvas { + // padding required to placehold for fixed menu bar + padding-top: $topbar-height; + } +} + +@media screen and (max-width: 1025px) { + body.off-canvas { + // padding required to placehold for fixed menu bar + padding-top: 0; + } + + section.right { + .nav-branded { + padding: 0 1em; + } + } +} diff --git a/app/assets/stylesheets/darkswarm/mixins.sass b/app/assets/stylesheets/darkswarm/mixins.sass deleted file mode 100644 index d35c5aa1a7..0000000000 --- a/app/assets/stylesheets/darkswarm/mixins.sass +++ /dev/null @@ -1,192 +0,0 @@ -// Note this mixin file is used in ADMIN and FRONTEND - -@import branding - - -// Generic \\ - -@mixin tiledPane - background-image: url("/assets/tile-wide.png") - background-color: $brand-colour - background-position: center center - @include paneWhiteText - -@mixin panepadding - padding-top: 100px - padding-bottom: 100px - -@mixin paneWhiteText - &, & * - color: white - -@mixin sidepaddingSm - padding-left: 10px - padding-right: 10px - @media all and (min-width: 768px) - padding-left: 20px - padding-right: 20px - @media all and (min-width: 1024px) - padding-left: 50px - padding-right: 50px - @media all and (min-width: 1200px) - padding-left: 100px - padding-right: 100px - -@mixin sidepaddingBg - padding-left: 20px - padding-right: 20px - @media all and (min-width: 768px) - padding-left: 40px - padding-right: 40px - @media all and (min-width: 1024px) - padding-left: 100px - padding-right: 100px - @media all and (min-width: 1200px) - padding-left: 200px - padding-right: 200px - -@mixin disabled - color: $disabled-bright - -@mixin box-shadow($box-shadow) - -moz-box-shadow: $box-shadow - -webkit-box-shadow: $box-shadow - box-shadow: $box-shadow - -@mixin elipse-shadow($elipse-shadow) - content: "" - position: absolute - z-index: -1 - -webkit-box-shadow: $elipse-shadow - box-shadow: $elipse-shadow - bottom: -12% - left: 10% - right: 10% - width: 80% - height: 10% - -moz-border-radius: 100% - border-radius: 100% - -@mixin border-radius($border-radius) - -webkit-border-radius: $border-radius - border-radius: $border-radius - -@mixin border-radius-mixed($border-radius-TL, $border-radius-TR, $border-radius-BR, $border-radius-BL) - -webkit-border-radius: $border-radius-TL $border-radius-TR $border-radius-BR $border-radius-BL - border-radius: $border-radius-TL $border-radius-TR $border-radius-BR $border-radius-BL - -@mixin transform-translate($translate) - -ms-transform: $translate - -webkit-transform: $translate - transform: $translate - -@mixin transform-scale($scale) - -moz-transform: $scale - -webkit-transform: $scale - -o-transform: $scale - -ms-transform: $scale - transform: $scale - -// Typography \\ - -@mixin textpress - text-shadow: 0 -1px 1px #111111, 0 1px 2px #222222 - -@mixin textsoftpress - text-shadow: 0 0 3px rgba(0,0,0,0.35) - -// TO USE icon-font -// Assign to :before or :after element -// Assign content: "string" - -@mixin icon-font - font-family: "OFN" - display: inline-block - font-weight: normal - font-style: normal - font-variant: normal - text-transform: none - - -// Background options \\ - -@mixin darkbg - background-color: $clr-brick - &, & * - color: white - a - color: $clr-brick-ultra-light - &:hover - text-decoration: none - color: $clr-brick-light - -@mixin lightbg - background-color: $clr-brick-light - &, & * - color: black - a - color: $clr-brick - &:hover - text-decoration: none - color: $clr-brick-bright - -@mixin turqbg - background-color: $clr-turquoise-light - &, & * - color: $clr-turquoise - a - color: $clr-turquoise - &:hover - text-decoration: none - color: $clr-turquoise-bright - -@mixin fullbg - background-position: center center - background-repeat: no-repeat - -webkit-background-size: cover - -moz-background-size: cover - -o-background-size: cover - background-size: cover - -@mixin fullwidthbg - background-position: center top - background-repeat: no-repeat - background-size: 100% auto - -@mixin gradient($gradient-clr1, $gradient-clr2) - background: $gradient-clr1 - // Old browsers - background: -moz-linear-gradient(top, $gradient-clr1 0%, $gradient-clr2 100%) - // FF3.6+ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, $gradient-clr1), color-stop(100%, $gradient-clr2)) - // Chrome,Safari4+ - background: -webkit-linear-gradient(top, $gradient-clr1 0%, $gradient-clr2 100%) - // Chrome10+,Safari5.1+ - background: -o-linear-gradient(top, $gradient-clr1 0%, $gradient-clr2 100%) - // Opera 11.10+ - background: -ms-linear-gradient(top, $gradient-clr1 0%, $gradient-clr2 100%) - // IE10+ - background: linear-gradient(to bottom, $gradient-clr1 0%, $gradient-clr2 100%) - // W3C - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='$gradient-clr1', endColorstr='$gradient-clr2',GradientType=0 ) - // IE6-8 - - -@mixin producersbg - background-color: lighten($clr-turquoise, 68%) - background-image: url("/assets/producers.svg") - background-position: center 50px - background-repeat: no-repeat - background-size: 922px 763px - -@mixin hubsbg - background-color: $brand-colour - background-image: url("/assets/hubs-bg.jpg") - background-position: center center - -@mixin groupsbg - background-color: lighten($clr-brick, 56%) - background-image: url("/assets/groups.svg") - background-position: center 50px - background-repeat: no-repeat - background-size: 922px 922px diff --git a/app/assets/stylesheets/darkswarm/mixins.scss b/app/assets/stylesheets/darkswarm/mixins.scss new file mode 100644 index 0000000000..a473a014bf --- /dev/null +++ b/app/assets/stylesheets/darkswarm/mixins.scss @@ -0,0 +1,253 @@ +// Note this mixin file is used in ADMIN and FRONTEND + +@import "branding"; + +// Generic \\ + +@mixin tiledPane { + background-image: url("/assets/tile-wide.png"); + background-color: $brand-colour; + background-position: center center; + + @include paneWhiteText; +} + +@mixin panepadding { + padding-top: 100px; + padding-bottom: 100px; +} + +@mixin paneWhiteText { + &, & * { + color: white; + } +} + +@mixin sidepaddingSm { + padding-left: 10px; + padding-right: 10px; + + @media all and (min-width: 768px) { + padding-left: 20px; + padding-right: 20px; + } + + @media all and (min-width: 1024px) { + padding-left: 50px; + padding-right: 50px; + } + + @media all and (min-width: 1200px) { + padding-left: 100px; + padding-right: 100px; + } +} + +@mixin sidepaddingBg { + padding-left: 20px; + padding-right: 20px; + + @media all and (min-width: 768px) { + padding-left: 40px; + padding-right: 40px; + } + + @media all and (min-width: 1024px) { + padding-left: 100px; + padding-right: 100px; + } + + @media all and (min-width: 1200px) { + padding-left: 200px; + padding-right: 200px; + } +} + +@mixin disabled { + color: $disabled-bright; +} + +@mixin box-shadow($box-shadow) { + -moz-box-shadow: $box-shadow; + -webkit-box-shadow: $box-shadow; + box-shadow: $box-shadow; +} + +@mixin elipse-shadow($elipse-shadow) { + content: ""; + position: absolute; + z-index: -1; + -webkit-box-shadow: $elipse-shadow; + box-shadow: $elipse-shadow; + bottom: -12%; + left: 10%; + right: 10%; + width: 80%; + height: 10%; + -moz-border-radius: 100%; + border-radius: 100%; +} + +@mixin border-radius($border-radius) { + -webkit-border-radius: $border-radius; + border-radius: $border-radius; +} + +@mixin border-radius-mixed($border-radius-TL, $border-radius-TR, $border-radius-BR, $border-radius-BL) { + -webkit-border-radius: $border-radius-TL $border-radius-TR $border-radius-BR $border-radius-BL; + border-radius: $border-radius-TL $border-radius-TR $border-radius-BR $border-radius-BL; +} + +@mixin transform-translate($translate) { + -ms-transform: $translate; + -webkit-transform: $translate; + transform: $translate; +} + +@mixin transform-scale($scale) { + -moz-transform: $scale; + -webkit-transform: $scale; + -o-transform: $scale; + -ms-transform: $scale; + transform: $scale; +} + +// Typography \\ + +@mixin textpress { + text-shadow: 0 -1px 1px #111111, 0 1px 2px #222222; +} + +@mixin textsoftpress { + text-shadow: 0 0 3px rgba(0, 0, 0, 0.35); +} + +// TO USE icon-font +// Assign to :before or :after element +// Assign content: "string" + +@mixin icon-font { + font-family: "OFN"; + display: inline-block; + font-weight: normal; + font-style: normal; + font-variant: normal; + text-transform: none; +} + +// Background options \\ + +@mixin darkbg { + background-color: $clr-brick; + + &, & * { + color: white; + } + + a { + color: $clr-brick-ultra-light; + + &:hover { + text-decoration: none; + color: $clr-brick-light; + } + } +} + +@mixin lightbg { + background-color: $clr-brick-light; + + &, & * { + color: black; + } + + a { + color: $clr-brick; + + &:hover { + text-decoration: none; + color: $clr-brick-bright; + } + } +} + +@mixin turqbg { + background-color: $clr-turquoise-light; + + &, & * { + color: $clr-turquoise; + } + + a { + color: $clr-turquoise; + + &:hover { + text-decoration: none; + color: $clr-turquoise-bright; + } + } +} + +@mixin fullbg { + background-position: center center; + background-repeat: no-repeat; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; +} + +@mixin fullwidthbg { + background-position: center top; + background-repeat: no-repeat; + background-size: 100% auto; +} + +@mixin gradient($gradient-clr1, $gradient-clr2) { + background: $gradient-clr1; + + // Old browsers + background: -moz-linear-gradient(top, $gradient-clr1 0%, $gradient-clr2 100%); + + // FF3.6+ + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, $gradient-clr1), color-stop(100%, $gradient-clr2)); + + // Chrome,Safari4+ + background: -webkit-linear-gradient(top, $gradient-clr1 0%, $gradient-clr2 100%); + + // Chrome10+,Safari5.1+ + background: -o-linear-gradient(top, $gradient-clr1 0%, $gradient-clr2 100%); + + // Opera 11.10+ + background: -ms-linear-gradient(top, $gradient-clr1 0%, $gradient-clr2 100%); + + // IE10+ + background: linear-gradient(to bottom, $gradient-clr1 0%, $gradient-clr2 100%); + + // W3C + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='$gradient-clr1', endColorstr='$gradient-clr2',GradientType=0 ); + + // IE6-8 +} + +@mixin producersbg { + background-color: lighten($clr-turquoise, 68%); + background-image: url("/assets/producers.svg"); + background-position: center 50px; + background-repeat: no-repeat; + background-size: 922px 763px; +} + +@mixin hubsbg { + background-color: $brand-colour; + background-image: url("/assets/hubs-bg.jpg"); + background-position: center center; +} + +@mixin groupsbg { + background-color: lighten($clr-brick, 56%); + background-image: url("/assets/groups.svg"); + background-position: center 50px; + background-repeat: no-repeat; + background-size: 922px 922px; +} diff --git a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass deleted file mode 100644 index e4724a13f5..0000000000 --- a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass +++ /dev/null @@ -1,165 +0,0 @@ -@import branding -@import mixins - - -// Generic styles for use - -.modal-centered - text-align: center - p - margin-bottom: 0 - -.modal-header, p.modal-header - text-align: center - text-transform: uppercase - font-size: 1rem - font-weight: 400 - color: $disabled-dark - border-bottom: 1px solid $light-grey - margin-top: 0.75rem - margin-bottom: 0.5rem - -// Enterprise promo image and text - -.highlight - position: relative - -.highlight-top - padding-top: 0.75rem - padding-bottom: 0.75rem - background-color: rgba(255,255,255,0.65) - position: absolute - bottom: 0 - width: 100% - border: 0 - outline: 0 - @media only screen and (max-width: 640px) - padding-top: 0.5rem - padding-bottom: 0.35rem - - h3, p - margin-top: 0 - margin-bottom: 0 - padding-bottom: 0 - line-height: 1 - - h3 > i - color: $clr-brick - - p - line-height: 2.4 - @media all and (max-width: 640px) - line-height: 1.4 - - h3 a:hover span - border-bottom: 1px solid $clr-brick-bright - - .is_producer - &, & * - color: $clr-turquoise - - -// ABOUT Enterprise - -.about-container - min-height: 20px - margin-bottom: 0.5rem - overflow-x: hidden - border-bottom: 1px solid $light-grey - @include box-shadow(0 2px 2px -2px $light-grey) - - .enterprise-logo, img - float: left - margin-right: 2rem - margin-bottom: 0.5rem - max-width: 180px - max-height: 180px - -// CONTACT Enterprise - -.contact-container - a:hover - text-decoration: underline - -// FOLLOW Enterprise - -.follow-icons - // text-align: center - span - display: inline-block - margin: 0 0.25rem 0.75rem 0.25rem - i - font-size: 2rem - a - color: #999 - &:hover, &:active, &:focus - color: #666 - - -// CALL TO ACTION - hub click throughs - -.cta-container - background-color: #ececec - padding-top: 0.75rem - - label - text-transform: uppercase - font-size: 0.875rem - margin-bottom: 0.75rem - 5rem - color: $dark-grey - - label.right - color: $disabled-dark - span - text-transform: capitalize - - .cta-hub - margin-right: 2rem - margin-top: 0rem - margin-bottom: 0.75rem - display: inline-block - - &.secondary - color: $disabled-dark - - &.primary - color: $clr-brick - - i.ofn-i_033-open-sign, i.ofn-i_032-closed-sign - margin-right: 0.1rem - font-size: 2rem - padding: 0.15rem 0.25rem 0.35rem 0.25rem - float: left - - .hub-name - margin-top: 0.75rem - margin-bottom: 0.2rem - font-weight: 400 - display: inline-block - border-bottom: 1px solid transparent - .button-address - margin-top: 0.5rem - margin-bottom: 0.2rem - margin-left: 0.1rem - text-transform: uppercase - font-weight: 300 - font-style: italic - font-size: 0.75rem - display: inline-block - border-bottom: 1px solid transparent - @media all and (max-width: 640px) - display: none - - &:hover, &:focus, &:active - &.secondary - color: #666 - .hub-name, .button-address - border-bottom: 1px solid #999 - &.primary - color: $clr-brick-bright - .hub-name, .button-address - border-bottom: 1px solid $clr-brick-bright - - - diff --git a/app/assets/stylesheets/darkswarm/modal-enterprises.css.scss b/app/assets/stylesheets/darkswarm/modal-enterprises.css.scss new file mode 100644 index 0000000000..7b37c46955 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/modal-enterprises.css.scss @@ -0,0 +1,212 @@ +@import "branding"; +@import "mixins"; + +// Generic styles for use + +.modal-centered { + text-align: center; + + p { + margin-bottom: 0; + } +} + +.modal-header, p.modal-header { + text-align: center; + text-transform: uppercase; + font-size: 1rem; + font-weight: 400; + color: $disabled-dark; + border-bottom: 1px solid $light-grey; + margin-top: 0.75rem; + margin-bottom: 0.5rem; +} + +// Enterprise promo image and text + +.highlight { + position: relative; +} + +.highlight-top { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + background-color: rgba(255, 255, 255, 0.65); + position: absolute; + bottom: 0; + width: 100%; + border: 0; + outline: 0; + + @media only screen and (max-width: 640px) { + padding-top: 0.5rem; + padding-bottom: 0.35rem; + } + + h3, p { + margin-top: 0; + margin-bottom: 0; + padding-bottom: 0; + line-height: 1; + } + + h3 > i { + color: $clr-brick; + } + + p { + line-height: 2.4; + + @media all and (max-width: 640px) { + line-height: 1.4; + } + } + + h3 a:hover span { + border-bottom: 1px solid $clr-brick-bright; + } + + .is_producer { + &, & * { + color: $clr-turquoise; + } + } +} + +// ABOUT Enterprise + +.about-container { + min-height: 20px; + margin-bottom: 0.5rem; + overflow-x: hidden; + border-bottom: 1px solid $light-grey; + + @include box-shadow(0 2px 2px -2px $light-grey); + + .enterprise-logo, img { + float: left; + margin-right: 2rem; + margin-bottom: 0.5rem; + max-width: 180px; + max-height: 180px; + } +} + +// CONTACT Enterprise + +.contact-container { + a:hover { + text-decoration: underline; + } +} + +// FOLLOW Enterprise + +.follow-icons { + // text-align: center + span { + display: inline-block; + margin: 0 0.25rem 0.75rem 0.25rem; + } + + i { + font-size: 2rem; + } + + a { + color: #999; + + &:hover, &:active, &:focus { + color: #666; + } + } +} + +// CALL TO ACTION - hub click throughs + +.cta-container { + background-color: #ececec; + padding-top: 0.75rem; + + label { + text-transform: uppercase; + font-size: 0.875rem; + margin-bottom: 0.75rem; + + 5rem {} + + color: $dark-grey; + } + + label.right { + color: $disabled-dark; + + span { + text-transform: capitalize; + } + } + + .cta-hub { + margin-right: 2rem; + margin-top: 0rem; + margin-bottom: 0.75rem; + display: inline-block; + + &.secondary { + color: $disabled-dark; + } + + &.primary { + color: $clr-brick; + } + + i.ofn-i_033-open-sign, i.ofn-i_032-closed-sign { + margin-right: 0.1rem; + font-size: 2rem; + padding: 0.15rem 0.25rem 0.35rem 0.25rem; + float: left; + } + + .hub-name { + margin-top: 0.75rem; + margin-bottom: 0.2rem; + font-weight: 400; + display: inline-block; + border-bottom: 1px solid transparent; + } + + .button-address { + margin-top: 0.5rem; + margin-bottom: 0.2rem; + margin-left: 0.1rem; + text-transform: uppercase; + font-weight: 300; + font-style: italic; + font-size: 0.75rem; + display: inline-block; + border-bottom: 1px solid transparent; + + @media all and (max-width: 640px) { + display: none; + } + } + + &:hover, &:focus, &:active { + &.secondary { + color: #666; + + .hub-name, .button-address { + border-bottom: 1px solid #999; + } + } + + &.primary { + color: $clr-brick-bright; + + .hub-name, .button-address { + border-bottom: 1px solid $clr-brick-bright; + } + } + } + } +} diff --git a/app/assets/stylesheets/darkswarm/modal-login.css.sass b/app/assets/stylesheets/darkswarm/modal-login.css.sass deleted file mode 100644 index 61108145bb..0000000000 --- a/app/assets/stylesheets/darkswarm/modal-login.css.sass +++ /dev/null @@ -1,13 +0,0 @@ - -// Styling for login modal to style tabs -.reveal-modal.login-modal - border-bottom-color: #efefef - -.login-modal - background: #efefef - .tabs-content - background: #fff - padding-top: 10px - - - \ No newline at end of file diff --git a/app/assets/stylesheets/darkswarm/modal-login.css.scss b/app/assets/stylesheets/darkswarm/modal-login.css.scss new file mode 100644 index 0000000000..4b87a7d1d9 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/modal-login.css.scss @@ -0,0 +1,13 @@ +// Styling for login modal to style tabs +.reveal-modal.login-modal { + border-bottom-color: #efefef; +} + +.login-modal { + background: #efefef; + + .tabs-content { + background: #fff; + padding-top: 10px; + } +} diff --git a/app/assets/stylesheets/darkswarm/modals.css.sass b/app/assets/stylesheets/darkswarm/modals.css.sass deleted file mode 100644 index 5844a8b600..0000000000 --- a/app/assets/stylesheets/darkswarm/modals.css.sass +++ /dev/null @@ -1,54 +0,0 @@ -@import branding -@import mixins - -dialog, .reveal-modal - border: none - outline: none - padding: 30px 20px 0 20px - border-bottom: 30px solid white - overflow-y: scroll - overflow-x: hidden - min-height: 260px - // Not working yet - want a nice gradient shadow when there is overflow - needs JS too - // &:after - // @include elipse-shadow(0 0 40px rgba(0, 0, 0, 0.8)) - - - // Reveal.js break point: - // @media only screen and (max-width: 40.063em) - - // Small - when modal IS full screen - @media only screen and (max-width: 640px) - max-height: initial - // This is needed to make the height not the height of whole content page - min-height: 100% - position: absolute !important - top: 0 - left: 0 - - // Medium and up - when modal IS NOT full screen - @media only screen and (min-width: 641px) - top: 10% - max-height: 80% - -.reveal-modal-bg - background-color: rgba(0,0,0,0.85) - position: fixed - -dialog .close-reveal-modal, .reveal-modal .close-reveal-modal - right: 0.25rem - top: 0.25rem - background-color: rgba(205,205,205,0.65) - text-shadow: none - font-size: 2rem - padding: 0.45rem - color: #666 - z-index: 9999999 - @include border-radius(999999rem) - &:hover, &:active, &:focus - background-color: rgba(205,205,205,1) - color: #333 - -// Prevent body from scrolling when a modal is open -body.modal-open - overflow: hidden diff --git a/app/assets/stylesheets/darkswarm/modals.css.scss b/app/assets/stylesheets/darkswarm/modals.css.scss new file mode 100644 index 0000000000..122d840f2a --- /dev/null +++ b/app/assets/stylesheets/darkswarm/modals.css.scss @@ -0,0 +1,64 @@ +@import "branding"; +@import "mixins"; + +dialog, .reveal-modal { + border: none; + outline: none; + padding: 30px 20px 0 20px; + border-bottom: 30px solid white; + overflow-y: scroll; + overflow-x: hidden; + min-height: 260px; + + // Not working yet - want a nice gradient shadow when there is overflow - needs JS too + // &:after + // @include elipse-shadow(0 0 40px rgba(0, 0, 0, 0.8)) + + // Reveal.js break point: + // @media only screen and (max-width: 40.063em) + + // Small - when modal IS full screen + @media only screen and (max-width: 640px) { + max-height: initial; + + // This is needed to make the height not the height of whole content page + min-height: 100%; + position: absolute !important; + top: 0; + left: 0; + } + + // Medium and up - when modal IS NOT full screen + @media only screen and (min-width: 641px) { + top: 10%; + max-height: 80%; + } +} + +.reveal-modal-bg { + background-color: rgba(0, 0, 0, 0.85); + position: fixed; +} + +dialog .close-reveal-modal, .reveal-modal .close-reveal-modal { + right: 0.25rem; + top: 0.25rem; + background-color: rgba(205, 205, 205, 0.65); + text-shadow: none; + font-size: 2rem; + padding: 0.45rem; + color: #666; + z-index: 9999999; + + @include border-radius(999999rem); + + &:hover, &:active, &:focus { + background-color: rgba(205, 205, 205, 1); + color: #333; + } +} + +// Prevent body from scrolling when a modal is open +body.modal-open { + overflow: hidden; +} diff --git a/app/assets/stylesheets/darkswarm/overrides.css.sass b/app/assets/stylesheets/darkswarm/overrides.css.sass deleted file mode 100644 index fcec6b455d..0000000000 --- a/app/assets/stylesheets/darkswarm/overrides.css.sass +++ /dev/null @@ -1,2 +0,0 @@ -.row - max-width: 74em diff --git a/app/assets/stylesheets/darkswarm/overrides.css.scss b/app/assets/stylesheets/darkswarm/overrides.css.scss new file mode 100644 index 0000000000..ccad1b5d35 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/overrides.css.scss @@ -0,0 +1,3 @@ +.row { + max-width: 74em; +} diff --git a/app/assets/stylesheets/darkswarm/page_alert.css.sass b/app/assets/stylesheets/darkswarm/page_alert.css.sass deleted file mode 100644 index 7e66d35518..0000000000 --- a/app/assets/stylesheets/darkswarm/page_alert.css.sass +++ /dev/null @@ -1,58 +0,0 @@ -@import branding -@import animations -@import "compass/css3/transition" - -// Basic style \\ -.page-alert - .alert-box - height: 55px - overflow: hidden - border: 1px solid rgba($dark-grey, 0.35) - border-left: none - border-right: none - background-color: #bbb - background-image: url("/assets/tile-wide.png") - background-position: center center - padding: 12px 0 8px 0 - margin: 0 - - h6 - @media all and (max-width: 480px) - font-size: 10px - line-height: 24px - - .alert-cta - &, & * - @include csstrans - color: #333 - strong - letter-spacing: 0.5px - a:hover, a:active, a:focus - &, & * - text-decoration: none - color: white - - -// Show-hide animation \\ -.off-canvas-wrap .inner-wrap, .off-canvas-wrap .inner-wrap .fixed, nav.tab-bar - @include transition(all, 1000ms, ease-in-out) - - &.move-down - margin-top: 55px - @include transition(all, 1000ms, ease-in-out) - - -.off-canvas-wrap .inner-wrap .page-alert.fixed - top: -55px - z-index: 1 - // TODO: Compass to disable transition - -moz-transition: none - -webkit-transition: none - -o-transition: color 0 ease-in - transition: none - -.off-canvas-wrap.move-right .inner-wrap.move-down - .page-alert - top: -55px * 2 - .left-off-canvas-menu - top: -55px diff --git a/app/assets/stylesheets/darkswarm/page_alert.css.scss b/app/assets/stylesheets/darkswarm/page_alert.css.scss new file mode 100644 index 0000000000..e7bdcd53a6 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/page_alert.css.scss @@ -0,0 +1,77 @@ +@import "branding"; +@import "animations"; +@import "compass/css3/transition"; + +// Basic style \\ +.page-alert { + .alert-box { + height: 55px; + overflow: hidden; + border: 1px solid rgba($dark-grey, 0.35); + border-left: none; + border-right: none; + background-color: #bbb; + background-image: url("/assets/tile-wide.png"); + background-position: center center; + padding: 12px 0 8px 0; + margin: 0; + + h6 { + @media all and (max-width: 480px) { + font-size: 10px; + line-height: 24px; + } + } + + .alert-cta { + &, & * { + @include csstrans; + + color: #333; + } + + strong { + letter-spacing: 0.5px; + } + + a:hover, a:active, a:focus { + &, & * { + text-decoration: none; + color: white; + } + } + } + } +} + +// Show-hide animation \\ +.off-canvas-wrap .inner-wrap, .off-canvas-wrap .inner-wrap .fixed, nav.tab-bar { + @include transition(all, 1000ms, ease-in-out); + + &.move-down { + margin-top: 55px; + + @include transition(all, 1000ms, ease-in-out); + } +} + +.off-canvas-wrap .inner-wrap .page-alert.fixed { + top: -55px; + z-index: 1; + + // TODO: Compass to disable transition + -moz-transition: none; + -webkit-transition: none; + -o-transition: color 0 ease-in; + transition: none; +} + +.off-canvas-wrap.move-right .inner-wrap.move-down { + .page-alert { + top: -55px * 2; + } + + .left-off-canvas-menu { + top: -55px; + } +} diff --git a/app/assets/stylesheets/darkswarm/producer_node.css.sass b/app/assets/stylesheets/darkswarm/producer_node.css.sass deleted file mode 100644 index 24eb0467c2..0000000000 --- a/app/assets/stylesheets/darkswarm/producer_node.css.sass +++ /dev/null @@ -1,97 +0,0 @@ -@import branding -@import mixins - -.producers - .active_table .active_table_node - - // Header row - @media all and (max-width: 640px) - .skinny-head - background-color: $clr-turquoise-light - @include border-radius-mixed(0.5em, 0.5em, 0, 0) - margin-top: -1rem - margin-bottom: 1rem - padding-top: 1rem - padding-bottom: 1rem - .follow-icons - &, & * - font-size: 1.5rem - - - // Producer icons - i.ofn-i_059-producer, i.ofn-i_060-producer-reversed - font-size: 2rem - display: inline-block - margin-right: 0.25rem - float: left - color: $clr-turquoise - - a - &:hover, &:active, &:focus - color: $clr-turquoise-bright - span - text-decoration: underline - - &.is_distributor, &.is_distributor i.ofn-i_059-producer, &.is_distributor i.ofn-i_060-producer-reversed - color: $clr-brick - &:hover, &:active, &:focus - color: $clr-brick-bright - - a.cta-hub - &:hover, &:focus, &:active - &.secondary - color: #666 - .hub-name, .button-address - border-bottom: 1px solid #999 - &.primary - color: $clr-brick-bright - .hub-name, .button-address - border-bottom: 1px solid $clr-brick-bright - - p.word-wrap - margin-bottom: 0 - &:last-child - margin-bottom: 1rem - - .fat-taxons - background-color: $clr-turquoise-light - - .fat-properties - background-color: $clr-turquoise-ultra-light - border: 1px solid $clr-turquoise-light - - .producer-name - color: $clr-turquoise - - //Open row - &.open - .active_table_row - border-left: 1px solid $clr-turquoise-bright - border-right: 1px solid $clr-turquoise-bright - background-color: rgba(206,239,228,0.4) - .cta-container - background: none - .columns img - padding: 1rem 0 - max-height: 160px - width: auto - &.left - padding: 0.25rem 1rem 0.25rem 0 - &.right - padding: 0.25rem 0.5rem 0.25rem 2rem - - .active_table_row:first-child - border-top: 1px solid $clr-turquoise-bright - - .active_table_row:last-child - border-bottom: 1px solid $clr-turquoise-bright - cursor: auto - - //Closed row - &.closed - .active_table_row.closed - border: 1px solid transparent - @media all and (max-width: 640px) - border-color: $clr-turquoise-light - &:hover, &:active, &:focus - border-color: $clr-turquoise diff --git a/app/assets/stylesheets/darkswarm/producer_node.css.scss b/app/assets/stylesheets/darkswarm/producer_node.css.scss new file mode 100644 index 0000000000..921d8df544 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/producer_node.css.scss @@ -0,0 +1,145 @@ +@import "branding"; +@import "mixins"; + +.producers { + .active_table .active_table_node { + // Header row + @media all and (max-width: 640px) { + .skinny-head { + background-color: $clr-turquoise-light; + + @include border-radius-mixed(0.5em, 0.5em, 0, 0); + + margin-top: -1rem; + margin-bottom: 1rem; + padding-top: 1rem; + padding-bottom: 1rem; + } + + .follow-icons { + &, & * { + font-size: 1.5rem; + } + } + } + + // Producer icons + i.ofn-i_059-producer, i.ofn-i_060-producer-reversed { + font-size: 2rem; + display: inline-block; + margin-right: 0.25rem; + float: left; + color: $clr-turquoise; + } + + a { + &:hover, &:active, &:focus { + color: $clr-turquoise-bright; + + span { + text-decoration: underline; + } + } + + &.is_distributor, &.is_distributor i.ofn-i_059-producer, &.is_distributor i.ofn-i_060-producer-reversed { + color: $clr-brick; + + &:hover, &:active, &:focus { + color: $clr-brick-bright; + } + } + } + + a.cta-hub { + &:hover, &:focus, &:active { + &.secondary { + color: #666; + + .hub-name, .button-address { + border-bottom: 1px solid #999; + } + } + + &.primary { + color: $clr-brick-bright; + + .hub-name, .button-address { + border-bottom: 1px solid $clr-brick-bright; + } + } + } + } + + p.word-wrap { + margin-bottom: 0; + + &:last-child { + margin-bottom: 1rem; + } + } + + .fat-taxons { + background-color: $clr-turquoise-light; + } + + .fat-properties { + background-color: $clr-turquoise-ultra-light; + border: 1px solid $clr-turquoise-light; + } + + .producer-name { + color: $clr-turquoise; + } + + //Open row + &.open { + .active_table_row { + border-left: 1px solid $clr-turquoise-bright; + border-right: 1px solid $clr-turquoise-bright; + background-color: rgba(206, 239, 228, 0.4); + + .cta-container { + background: none; + } + + .columns img { + padding: 1rem 0; + max-height: 160px; + width: auto; + + &.left { + padding: 0.25rem 1rem 0.25rem 0; + } + + &.right { + padding: 0.25rem 0.5rem 0.25rem 2rem; + } + } + } + + .active_table_row:first-child { + border-top: 1px solid $clr-turquoise-bright; + } + + .active_table_row:last-child { + border-bottom: 1px solid $clr-turquoise-bright; + cursor: auto; + } + } + + //Closed row + &.closed { + .active_table_row.closed { + border: 1px solid transparent; + + @media all and (max-width: 640px) { + border-color: $clr-turquoise-light; + } + + &:hover, &:active, &:focus { + border-color: $clr-turquoise; + } + } + } + } +} diff --git a/app/assets/stylesheets/darkswarm/producers.css.sass b/app/assets/stylesheets/darkswarm/producers.css.sass deleted file mode 100644 index caa31f224d..0000000000 --- a/app/assets/stylesheets/darkswarm/producers.css.sass +++ /dev/null @@ -1,14 +0,0 @@ -@import branding -@import mixins - -.producers - @include producersbg - @include sidepaddingSm - @include panepadding - a - color: $clr-turquoise - &:hover, &:active, &:focus - color: $clr-turquoise-bright - a.button.primary - &:hover, &:active, &:focus - color: white diff --git a/app/assets/stylesheets/darkswarm/producers.css.scss b/app/assets/stylesheets/darkswarm/producers.css.scss new file mode 100644 index 0000000000..e1a8c7a21b --- /dev/null +++ b/app/assets/stylesheets/darkswarm/producers.css.scss @@ -0,0 +1,24 @@ +@import "branding"; +@import "mixins"; + +.producers { + @include producersbg; + + @include sidepaddingSm; + + @include panepadding; + + a { + color: $clr-turquoise; + + &:hover, &:active, &:focus { + color: $clr-turquoise-bright; + } + } + + a.button.primary { + &:hover, &:active, &:focus { + color: white; + } + } +} diff --git a/app/assets/stylesheets/darkswarm/product_table.css.sass b/app/assets/stylesheets/darkswarm/product_table.css.sass deleted file mode 100644 index 9b0a7b4598..0000000000 --- a/app/assets/stylesheets/darkswarm/product_table.css.sass +++ /dev/null @@ -1,4 +0,0 @@ -.product_table - .row - border: 1px solid black - padding: 8px inherit diff --git a/app/assets/stylesheets/darkswarm/product_table.css.scss b/app/assets/stylesheets/darkswarm/product_table.css.scss new file mode 100644 index 0000000000..1d98464d4d --- /dev/null +++ b/app/assets/stylesheets/darkswarm/product_table.css.scss @@ -0,0 +1,6 @@ +.product_table { + .row { + border: 1px solid black; + padding: 8px inherit; + } +} diff --git a/app/assets/stylesheets/darkswarm/registration.css.sass b/app/assets/stylesheets/darkswarm/registration.css.sass deleted file mode 100644 index ba56701600..0000000000 --- a/app/assets/stylesheets/darkswarm/registration.css.sass +++ /dev/null @@ -1,161 +0,0 @@ -@import branding -@import mixins - -#registration-modal - header - text-align: center - @media all and (max-width: 64em) - text-align: left - .container - background-color: #ffffff - - i - font-size: 150% - - .field - margin-bottom: 1em - - .chunky - padding: 8px - font-size: 1rem - margin: 0 - width: 100% - - label.indent-checkbox - display: block - padding-left: 20px - text-indent: -17px - input - margin: 0px - - label - margin-bottom: 3px - - ol, ul, p - font-size: 0.875rem - ol, ul - padding: 0 - margin: 0 - ol - list-style-type: decimal - - .highlight-box - background: white - padding: 1rem 1.2rem - @media all and (max-width: 64em) - margin-top: 1rem - - #progress-bar - margin-bottom: 15px - .item - font-size: 0.75rem - padding: 10px 0px - text-transform: uppercase - text-align: center - background-color: $clr-blue - border: 2px solid $clr-blue - color: #fff - .item.active - background-color: $disabled-light - border: 2px solid $clr-blue - color: $clr-blue - font-weight: 700 - @include box-shadow(inset 0 0 1px 0 #fff) - - - .image-select - label - font-size: 18px - padding: 21px 0px - #logo-select - display: none - - #image-over - font-size: 18px - padding: 41px 0px - border: 3px dashed #494949 - text-align: center - font-weight: bold - color: #494949 - &.nv-file-over - background-color: #78cd91 - - #or - text-align: center - font-weight: bold - font-size: 18px - padding: 21px 0px - &.horizontal - padding: 41px 0px - - #image-placeholder - font-size: 18px - font-weight: bold - color: #373737 - background-color: #f1f1f1 - text-align: center - border: 3px dashed #494949 - margin-left: auto - margin-right: auto - .spinner - width: 100px - &.logo - .message - padding-top: 6em - .loading - padding-top: 4em - width: 306px - height: 306px - &.promo - .message - padding-top: 4em - .loading - padding-top: 1em - width: 726px - height: 166px - - -#registration-type - #enterprise-types - a.btnpanel - display: block - padding: 1rem - margin-bottom: 1rem - background-color: #efefef - color: black - text-align: center - border: 1px solid transparent - i - font-size: 3rem - h4 - margin-top: 1rem - - &:hover - background-color: #fff - - &#producer-panel:hover - border: 1px solid $clr-turquoise - &, & * - color: $clr-turquoise - - &#hub-panel:hover, &#both-panel:hover - border: 1px solid $clr-brick - &, & * - color: $clr-brick - - &.selected - &, & * - color: #fff - &#hub-panel, &#both-panel - background-color: $clr-brick-bright - &:hover - &, & * - color: white - &#producer-panel - background-color: $clr-turquoise-bright - &:hover - &, & * - color: white - p - clear: both - font-size: 0.875rem diff --git a/app/assets/stylesheets/darkswarm/registration.css.scss b/app/assets/stylesheets/darkswarm/registration.css.scss new file mode 100644 index 0000000000..1f80038f18 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/registration.css.scss @@ -0,0 +1,239 @@ +@import "branding"; +@import "mixins"; + +#registration-modal { + header { + text-align: center; + + @media all and (max-width: 64em) { + text-align: left; + } + } + + .container { + background-color: #ffffff; + } + + i { + font-size: 150%; + } + + .field { + margin-bottom: 1em; + } + + .chunky { + padding: 8px; + font-size: 1rem; + margin: 0; + width: 100%; + } + + label.indent-checkbox { + display: block; + padding-left: 20px; + text-indent: -17px; + + input { + margin: 0px; + } + } + + label { + margin-bottom: 3px; + } + + ol, ul, p { + font-size: 0.875rem; + } + + ol, ul { + padding: 0; + margin: 0; + } + + ol { + list-style-type: decimal; + } + + .highlight-box { + background: white; + padding: 1rem 1.2rem; + + @media all and (max-width: 64em) { + margin-top: 1rem; + } + } + + #progress-bar { + margin-bottom: 15px; + + .item { + font-size: 0.75rem; + padding: 10px 0px; + text-transform: uppercase; + text-align: center; + background-color: $clr-blue; + border: 2px solid $clr-blue; + color: #fff; + } + + .item.active { + background-color: $disabled-light; + border: 2px solid $clr-blue; + color: $clr-blue; + font-weight: 700; + + @include box-shadow(inset 0 0 1px 0 #fff); + } + } + + .image-select { + label { + font-size: 18px; + padding: 21px 0px; + } + + #logo-select { + display: none; + } + } + + #image-over { + font-size: 18px; + padding: 41px 0px; + border: 3px dashed #494949; + text-align: center; + font-weight: bold; + color: #494949; + + &.nv-file-over { + background-color: #78cd91; + } + } + + #or { + text-align: center; + font-weight: bold; + font-size: 18px; + padding: 21px 0px; + + &.horizontal { + padding: 41px 0px; + } + } + + #image-placeholder { + font-size: 18px; + font-weight: bold; + color: #373737; + background-color: #f1f1f1; + text-align: center; + border: 3px dashed #494949; + margin-left: auto; + margin-right: auto; + + .spinner { + width: 100px; + } + + &.logo { + .message { + padding-top: 6em; + } + + .loading { + padding-top: 4em; + } + + width: 306px; + height: 306px; + } + + &.promo { + .message { + padding-top: 4em; + } + + .loading { + padding-top: 1em; + } + + width: 726px; + height: 166px; + } + } +} + +#registration-type { + #enterprise-types { + a.btnpanel { + display: block; + padding: 1rem; + margin-bottom: 1rem; + background-color: #efefef; + color: black; + text-align: center; + border: 1px solid transparent; + + i { + font-size: 3rem; + } + + h4 { + margin-top: 1rem; + } + + &:hover { + background-color: #fff; + } + + &#producer-panel:hover { + border: 1px solid $clr-turquoise; + + &, & * { + color: $clr-turquoise; + } + } + + &#hub-panel:hover, &#both-panel:hover { + border: 1px solid $clr-brick; + + &, & * { + color: $clr-brick; + } + } + + &.selected { + &, & * { + color: #fff; + } + + &#hub-panel, &#both-panel { + background-color: $clr-brick-bright; + + &:hover { + &, & * { + color: white; + } + } + } + + &#producer-panel { + background-color: $clr-turquoise-bright; + + &:hover { + &, & * { + color: white; + } + } + } + } + + p { + clear: both; + font-size: 0.875rem; + } + } + } +} diff --git a/app/assets/stylesheets/darkswarm/shop.css.sass b/app/assets/stylesheets/darkswarm/shop.css.sass deleted file mode 100644 index 5cfcef46b8..0000000000 --- a/app/assets/stylesheets/darkswarm/shop.css.sass +++ /dev/null @@ -1,111 +0,0 @@ -@import mixins -@import animations -@import variables -@import branding -@import big-input - -// Shop partials -@import shop-inputs -@import shop-navigation -@import shop-product-rows -@import shop-taxon-flag -@import shop-popovers - -.darkswarm - products - display: block - padding-top: 20px - @media all and (max-width: 768px) - input.button.right - float: left - - @media all and (max-width: 480px) - .add_to_cart - margin-top: 2rem - - form - input.small.button.primary.right.add_to_cart - &.dirty - padding-left: 3.2rem - i.cart-spinner - position: absolute - top: 14px - right: 146px - color: white - font-size: 1.2em - // Necessary to be below Z index of cart popover: - z-index: 98 - -webkit-animation: spin 2s infinite linear - animation: spin 2s infinite linear - - product - @include csstrans - border-bottom: 1px solid #e5e5e5 - border-top: 1px solid #e5e5e5 - padding-bottom: 1px - margin-bottom: 20px !important - position: relative - display: block - color: $med-drk-grey - - &:hover, &:focus, &:active - border-bottom: 1px solid $clr-brick-med-bright - border-top: 1px solid $clr-brick-med-bright - - // BULK - .bulk-buy - font-size: 0.875rem - @media all and (max-width: 768px) - font-size: 0.75rem - - .bulk-buy, .bulk-buy i - color: #888 - - .inline - display: inline - - .spinner - width: 100px - margin-bottom: 20px - - // ICONS - i - font-size: 0.75em - padding-right: 0.9375rem - @media all and (max-width: 640px) - padding-right: 0.25rem - - i.ofn-i_056-bulk - font-size: 1rem - padding-right: 0rem - - i.ofn-i_036-producers - padding-left: 0.2rem - padding-right: 0rem - font-size: 0.8rem - - .alert-box.shopfront-message - border: 2px solid $clr-turquoise - border-radius: 5px - background-color: $clr-turquoise-light - color: $clr-turquoise - a - color: #0096ad - &:hover, &:focus, &:active - text-decoration: none - color: #4aadbd - - - .shopfront_closed_message, .shopfront_hidden_message - padding: 15px - border-radius: 5px - - .shopfront_closed_message - border: 2px solid #eb4c46 - - .shopfront_closed_message - margin: 2em 0em - - .shopfront_hidden_message - border: 2px solid #db4 - margin: 2em 0em diff --git a/app/assets/stylesheets/darkswarm/shop.css.scss b/app/assets/stylesheets/darkswarm/shop.css.scss new file mode 100644 index 0000000000..33ccbb31e7 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/shop.css.scss @@ -0,0 +1,146 @@ +@import "mixins"; +@import "animations"; +@import "variables"; +@import "branding"; +@import "big-input"; + +// Shop partials +@import "shop-inputs"; +@import "shop-navigation"; +@import "shop-product-rows"; +@import "shop-taxon-flag"; +@import "shop-popovers"; + +.darkswarm { + products { + display: block; + padding-top: 20px; + + @media all and (max-width: 768px) { + input.button.right { + float: left; + } + } + + @media all and (max-width: 480px) { + .add_to_cart { + margin-top: 2rem; + } + } + + form { + input.small.button.primary.right.add_to_cart { + &.dirty { + padding-left: 3.2rem; + } + } + + i.cart-spinner { + position: absolute; + top: 14px; + right: 146px; + color: white; + font-size: 1.2em; + + // Necessary to be below Z index of cart popover: + z-index: 98; + -webkit-animation: spin 2s infinite linear; + animation: spin 2s infinite linear; + } + } + + product { + @include csstrans; + + border-bottom: 1px solid #e5e5e5; + border-top: 1px solid #e5e5e5; + padding-bottom: 1px; + margin-bottom: 20px !important; + position: relative; + display: block; + color: $med-drk-grey; + + &:hover, &:focus, &:active { + border-bottom: 1px solid $clr-brick-med-bright; + border-top: 1px solid $clr-brick-med-bright; + } + + // BULK + .bulk-buy { + font-size: 0.875rem; + + @media all and (max-width: 768px) { + font-size: 0.75rem; + } + } + + .bulk-buy, .bulk-buy i { + color: #888; + } + + .inline { + display: inline; + } + + .spinner { + width: 100px; + margin-bottom: 20px; + } + + // ICONS + i { + font-size: 0.75em; + padding-right: 0.9375rem; + + @media all and (max-width: 640px) { + padding-right: 0.25rem; + } + } + + i.ofn-i_056-bulk { + font-size: 1rem; + padding-right: 0rem; + } + + i.ofn-i_036-producers { + padding-left: 0.2rem; + padding-right: 0rem; + font-size: 0.8rem; + } + } + } + + .alert-box.shopfront-message { + border: 2px solid $clr-turquoise; + border-radius: 5px; + background-color: $clr-turquoise-light; + color: $clr-turquoise; + + a { + color: #0096ad; + + &:hover, &:focus, &:active { + text-decoration: none; + color: #4aadbd; + } + } + } + + .shopfront_closed_message, .shopfront_hidden_message { + padding: 15px; + border-radius: 5px; + } + + .shopfront_closed_message { + border: 2px solid #eb4c46; + } + + .shopfront_closed_message { + margin: 2em 0em; + } + + .shopfront_hidden_message { + border: 2px solid #db4; + margin: 2em 0em; + } +} diff --git a/app/assets/stylesheets/darkswarm/shopping-cart.css.sass b/app/assets/stylesheets/darkswarm/shopping-cart.css.sass deleted file mode 100644 index 6fd48e0970..0000000000 --- a/app/assets/stylesheets/darkswarm/shopping-cart.css.sass +++ /dev/null @@ -1,70 +0,0 @@ -@import mixins -@import branding -@import "compass/css3/user-interface" - -.cart - @include user-select(none) - .cart-span, .cart-span a - display: inline-block - .cart-span - float: left - .joyride-tip-guide - display: block - right: 10px - top: 55px - width: 480px - - @media screen and (min-width: 641px) - overflow-y: auto - max-height: calc(95vh - 55px) - - @media screen and (max-width: 640px) - width: 96% - - .joyride-nub - right: 22px !important - left: auto - - table - width: 100% - border: none - border-spacing: 0px - margin-bottom: 5px - - tr.total-cart - color: #fff - background-color: #424242 - td - color: #fff - tr.product-cart - background-color: #333333 - border-top: 1px solid #424242 - td - padding: 4px 12px - color: #fff - .buttons - margin-bottom: 0.1em - .button - height: auto - top: 0px - -// Shopping cart -#cart-detail - .cart-item-delete - a.delete - font-size: 1.125em - -.item-thumb-image - display: none - @media screen and (min-width: 640px) - display: inline-block - float: left - padding-right: 0.5em - width: 36px - height: 36px - - - -#edit-cart - button, .button - margin: 0 diff --git a/app/assets/stylesheets/darkswarm/shopping-cart.css.scss b/app/assets/stylesheets/darkswarm/shopping-cart.css.scss new file mode 100644 index 0000000000..25ed0ba930 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/shopping-cart.css.scss @@ -0,0 +1,98 @@ +@import "mixins"; +@import "branding"; +@import "compass/css3/user-interface"; + +.cart { + @include user-select(none); + + .cart-span, .cart-span a { + display: inline-block; + } + + .cart-span { + float: left; + } + + .joyride-tip-guide { + display: block; + right: 10px; + top: 55px; + width: 480px; + + @media screen and (min-width: 641px) { + overflow-y: auto; + max-height: calc(95vh - 55px); + } + + @media screen and (max-width: 640px) { + width: 96%; + } + + .joyride-nub { + right: 22px !important; + left: auto; + } + + table { + width: 100%; + border: none; + border-spacing: 0px; + margin-bottom: 5px; + + tr.total-cart { + color: #fff; + background-color: #424242; + + td { + color: #fff; + } + } + + tr.product-cart { + background-color: #333333; + border-top: 1px solid #424242; + + td { + padding: 4px 12px; + color: #fff; + } + } + } + + .buttons { + margin-bottom: 0.1em; + + .button { + height: auto; + top: 0px; + } + } + } +} + +// Shopping cart +#cart-detail { + .cart-item-delete { + a.delete { + font-size: 1.125em; + } + } +} + +.item-thumb-image { + display: none; + + @media screen and (min-width: 640px) { + display: inline-block; + float: left; + padding-right: 0.5em; + width: 36px; + height: 36px; + } +} + +#edit-cart { + button, .button { + margin: 0; + } +} diff --git a/app/assets/stylesheets/darkswarm/sidebar.css.sass b/app/assets/stylesheets/darkswarm/sidebar.css.sass deleted file mode 100644 index 4addd035dc..0000000000 --- a/app/assets/stylesheets/darkswarm/sidebar.css.sass +++ /dev/null @@ -1,31 +0,0 @@ -// OMG -// We can't import foundation components? -// See https://github.com/zurb/foundation/issues/3855#issuecomment-30372252 - -@import "variables" -@import "components/global" -@import "components/buttons" -@import "components/panels" - - -#sidebar - margin-top: 1.875em - $bg: #222 - $padding: emCalc(20) - $adjust: true - $adjust: true - @include panel($bg, $padding, $adjust) - .content - background: white - - .tabs dd a - padding: 0.5em 1em - - #account - dl - @include clearfix - dt, dd - display: inline-block - - p > strong - display: block diff --git a/app/assets/stylesheets/darkswarm/sidebar.css.scss b/app/assets/stylesheets/darkswarm/sidebar.css.scss new file mode 100644 index 0000000000..18718062da --- /dev/null +++ b/app/assets/stylesheets/darkswarm/sidebar.css.scss @@ -0,0 +1,41 @@ +// OMG +// We can't import foundation components? +// See https://github.com/zurb/foundation/issues/3855#issuecomment-30372252 + +@import "variables"; +@import "components/global"; +@import "components/buttons"; +@import "components/panels"; + +#sidebar { + margin-top: 1.875em; + + $bg: #222; + $padding: emCalc(20); + $adjust: true; + $adjust: true; + + @include panel($bg, $padding, $adjust); + + .content { + background: white; + } + + .tabs dd a { + padding: 0.5em 1em; + } + + #account { + dl { + @include clearfix; + } + + dt, dd { + display: inline-block; + } + + p > strong { + display: block; + } + } +} diff --git a/app/assets/stylesheets/darkswarm/signup.css.sass b/app/assets/stylesheets/darkswarm/signup.css.sass deleted file mode 100644 index 12c01d0325..0000000000 --- a/app/assets/stylesheets/darkswarm/signup.css.sass +++ /dev/null @@ -1,119 +0,0 @@ -@import branding -@import mixins -@import typography -@import animations -@import variables - - -#producer-signup.pane, #shops-signup.pane - @include tiledPane - - h2 - margin-bottom: 2rem - font-size: 4.4rem - font-weight: 300 - -#producer-details.pane, #hub-details.pane, .groups-details.pane - background-color: lighten($ofn-grey, 44%) - - -#producer-case-studies, #shops-case-studies - @include hubsbg - padding-top: 100px - padding-bottom: 100px - -webkit-filter: brightness(1.1) - filter: brightness(1.1) - h2 - color: $brand-colour - font-size: 3rem - .case-study - background-color: rgba(255, 255, 255, 0.5) - padding: 1rem - margin-top: 2rem - text-align: center - .case-study-img - background-color: white - margin-bottom: 1rem - @media all and (min-width: 768px) - float: right - margin-left: 2rem - @media all and (min-width: 640px) - text-align: left - h4, a - color: $brand-colour - a - &, & * - @include csstrans - opacity: 1 - &:hover, &:focus, &:active - &, & * - opacity: 0.75 - - -// Signup tables \\ -table.signup-table - width: 100% - border: 0 - -table.signup-table.hubs-table, table.signup-table.producers-table - tr - td - background-color: white - border-bottom: 1px solid rgba($ofn-grey, 0.3) - td:nth-child(2) - background-color: lighten($ofn-grey, 46%) - td:nth-child(3) - background-color: lighten($ofn-grey, 41%) - td:last-child - &, & i - color: $brand-colour - border-bottom: 1px solid rgba($brand-colour, 0.3) - background-color: lighten($brand-colour, 48%) - thead - background-color: transparent - tr - td - border-bottom: 1px solid transparent - td:nth-child(1) - background-color: transparent - td:nth-child(2) - background: lighten($ofn-grey, 44%) - td:nth-child(3) - background: lighten($ofn-grey, 38%) - td:last-child - &, & * - color: white - background: $brand-colour - h5 - text-transform: uppercase - color: $ofn-grey - font-weight: 400 - font-size: 0.875rem - margin-bottom: 0.25em - - tfoot - background-color: transparent - tr - td - border-bottom: 1px solid transparent - td:nth-child(1) - background-color: transparent - td:nth-child(2) - background: lighten($ofn-grey, 44%) - td:nth-child(3) - background: lighten($ofn-grey, 38%) - td:last-child - &, & * - color: white - background: $brand-colour - h2 - .text-small - text-transform: uppercase - display: inline-block - font-weight: 400 - line-height: 1.5 - @include headingFont - -// Detail \\ -.enterprise-type-flowchart - float: right diff --git a/app/assets/stylesheets/darkswarm/signup.css.scss b/app/assets/stylesheets/darkswarm/signup.css.scss new file mode 100644 index 0000000000..206d7d6656 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/signup.css.scss @@ -0,0 +1,188 @@ +@import "branding"; +@import "mixins"; +@import "typography"; +@import "animations"; +@import "variables"; + +#producer-signup.pane, #shops-signup.pane { + @include tiledPane; + + h2 { + margin-bottom: 2rem; + font-size: 4.4rem; + font-weight: 300; + } +} + +#producer-details.pane, #hub-details.pane, .groups-details.pane { + background-color: lighten($ofn-grey, 44%); +} + +#producer-case-studies, #shops-case-studies { + @include hubsbg; + + padding-top: 100px; + padding-bottom: 100px; + -webkit-filter: brightness(1.1); + filter: brightness(1.1); + + h2 { + color: $brand-colour; + font-size: 3rem; + } + + .case-study { + background-color: rgba(255, 255, 255, 0.5); + padding: 1rem; + margin-top: 2rem; + text-align: center; + + .case-study-img { + background-color: white; + margin-bottom: 1rem; + + @media all and (min-width: 768px) { + float: right; + margin-left: 2rem; + } + } + + @media all and (min-width: 640px) { + text-align: left; + } + + h4, a { + color: $brand-colour; + } + + a { + &, & * { + @include csstrans; + + opacity: 1; + } + + &:hover, &:focus, &:active { + &, & * { + opacity: 0.75; + } + } + } + } +} + +// Signup tables \\ +table.signup-table { + width: 100%; + border: 0; +} + +table.signup-table.hubs-table, table.signup-table.producers-table { + tr { + td { + background-color: white; + border-bottom: 1px solid rgba($ofn-grey, 0.3); + } + + td:nth-child(2) { + background-color: lighten($ofn-grey, 46%); + } + + td:nth-child(3) { + background-color: lighten($ofn-grey, 41%); + } + + td:last-child { + &, & i { + color: $brand-colour; + } + + border-bottom: 1px solid rgba($brand-colour, 0.3); + background-color: lighten($brand-colour, 48%); + } + } + + thead { + background-color: transparent; + + tr { + td { + border-bottom: 1px solid transparent; + } + + td:nth-child(1) { + background-color: transparent; + } + + td:nth-child(2) { + background: lighten($ofn-grey, 44%); + } + + td:nth-child(3) { + background: lighten($ofn-grey, 38%); + } + + td:last-child { + &, & * { + color: white; + } + + background: $brand-colour; + } + } + + h5 { + text-transform: uppercase; + color: $ofn-grey; + font-weight: 400; + font-size: 0.875rem; + margin-bottom: 0.25em; + } + } + + tfoot { + background-color: transparent; + + tr { + td { + border-bottom: 1px solid transparent; + } + + td:nth-child(1) { + background-color: transparent; + } + + td:nth-child(2) { + background: lighten($ofn-grey, 44%); + } + + td:nth-child(3) { + background: lighten($ofn-grey, 38%); + } + + td:last-child { + &, & * { + color: white; + } + + background: $brand-colour; + } + } + + h2 { + .text-small { + text-transform: uppercase; + display: inline-block; + font-weight: 400; + line-height: 1.5; + + @include headingFont; + } + } + } +} + +// Detail \\ +.enterprise-type-flowchart { + float: right; +} diff --git a/app/assets/stylesheets/darkswarm/tables.css.sass b/app/assets/stylesheets/darkswarm/tables.css.sass deleted file mode 100644 index 885eec815a..0000000000 --- a/app/assets/stylesheets/darkswarm/tables.css.sass +++ /dev/null @@ -1,7 +0,0 @@ -table - thead tr, tbody tr - th, td - box-sizing: border-box - padding-left: 12px - padding-right: 12px - overflow: hidden diff --git a/app/assets/stylesheets/darkswarm/tables.css.scss b/app/assets/stylesheets/darkswarm/tables.css.scss new file mode 100644 index 0000000000..b2632d52f1 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/tables.css.scss @@ -0,0 +1,10 @@ +table { + thead tr, tbody tr { + th, td { + box-sizing: border-box; + padding-left: 12px; + padding-right: 12px; + overflow: hidden; + } + } +} diff --git a/app/assets/stylesheets/darkswarm/tabs.css.sass b/app/assets/stylesheets/darkswarm/tabs.css.sass deleted file mode 100644 index 12134a19e1..0000000000 --- a/app/assets/stylesheets/darkswarm/tabs.css.sass +++ /dev/null @@ -1,110 +0,0 @@ -@import typography -@import mixins -@import branding - -// Foundation overrides -#tabs .tabs-content > .content p - max-width: 100% !important - -.tabs-content > .content - padding-top: 0 !important - -// Tabs styling - -#tabs - background: url("/assets/gray_jean.png") top left repeat - @include box-shadow(inset 0 2px 3px 0 rgba(0,0,0,0.15)) - display: block - color: $dark-grey - .header - text-align: center - text-transform: uppercase - color: $dark-grey - border-bottom: 1px solid $disabled-dark - margin-top: 0.75rem - margin-bottom: 0.75rem - padding-bottom: 0.25rem - font-size: 0.875rem - - .panel - border-color: $clr-brick-bright - background-color: rgba(255, 255, 255, 0) - - dl dd - - text-align: center - @media all and (max-width: 640px) - text-align: left - - a - @include headingFont - background: transparent - text-transform: uppercase - line-height: 1 - font-size: 0.875em - text-shadow: 0 -1px 1px #ffffff - padding: 1em - border: none - &:hover, &:focus, &:active - color: $clr-brick-bright - &, &:hover - background: none - - @media all and (max-width: 640px) - padding: 0.35em 0 0.65em 0 - text-shadow: none - - // inactive nav link - dl - dd - border-top: 4px solid transparent - - a:after - padding-left: 8px - content: "\e633" - visibility: hidden - @include icon-font - @media all and (max-width: 640px) - content: "\e633" - dd:hover - a:after - visibility: visible - - // active nav link - - dl - dd.active - border-top: 4px solid $clr-brick - @media all and (max-width: 640px) - border-top: 4px solid transparent - background-color: $clr-brick - a - color: $clr-brick - @media all and (max-width: 640px) - color: white - a:after - content: "\e634" - visibility: visible - @include icon-font - @media all and (max-width: 640px) - content: "\e633" - - // content revealed in accordion - - .tabs-content - margin-bottom: 0 - & > .content - background: none - border: none - img - margin: 0px 0px 0px 40px - p - max-width: 555px - @media all and (max-width: 768px) - height: auto !important - ul - list-style-type: none - padding-left: none - .panel - padding-bottom: 1.25em - diff --git a/app/assets/stylesheets/darkswarm/tabs.css.scss b/app/assets/stylesheets/darkswarm/tabs.css.scss new file mode 100644 index 0000000000..55b55f1189 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/tabs.css.scss @@ -0,0 +1,161 @@ +@import "typography"; +@import "mixins"; +@import "branding"; + +// Foundation overrides +#tabs .tabs-content > .content p { + max-width: 100% !important; +} + +.tabs-content > .content { + padding-top: 0 !important; +} + +// Tabs styling + +#tabs { + background: url("/assets/gray_jean.png") top left repeat; + + @include box-shadow(inset 0 2px 3px 0 rgba(0, 0, 0, 0.15)); + + display: block; + color: $dark-grey; + + .header { + text-align: center; + text-transform: uppercase; + color: $dark-grey; + border-bottom: 1px solid $disabled-dark; + margin-top: 0.75rem; + margin-bottom: 0.75rem; + padding-bottom: 0.25rem; + font-size: 0.875rem; + } + + .panel { + border-color: $clr-brick-bright; + background-color: rgba(255, 255, 255, 0); + } + + dl dd { + text-align: center; + + @media all and (max-width: 640px) { + text-align: left; + } + + a { + @include headingFont; + + background: transparent; + text-transform: uppercase; + line-height: 1; + font-size: 0.875em; + text-shadow: 0 -1px 1px #ffffff; + padding: 1em; + border: none; + + &:hover, &:focus, &:active { + color: $clr-brick-bright; + } + + &, &:hover { + background: none; + } + + @media all and (max-width: 640px) { + padding: 0.35em 0 0.65em 0; + text-shadow: none; + } + } + } + + // inactive nav link + dl { + dd { + border-top: 4px solid transparent; + + a:after { + padding-left: 8px; + content: ""; + visibility: hidden; + + @include icon-font; + + @media all and (max-width: 640px) { + content: ""; + } + } + } + + dd:hover { + a:after { + visibility: visible; + } + } + } + + // active nav link + + dl { + dd.active { + border-top: 4px solid $clr-brick; + + @media all and (max-width: 640px) { + border-top: 4px solid transparent; + background-color: $clr-brick; + } + + a { + color: $clr-brick; + + @media all and (max-width: 640px) { + color: white; + } + } + + a:after { + content: ""; + visibility: visible; + + @include icon-font; + + @media all and (max-width: 640px) { + content: ""; + } + } + } + } + + // content revealed in accordion + + .tabs-content { + margin-bottom: 0; + + & > .content { + background: none; + border: none; + + img { + margin: 0px 0px 0px 40px; + } + + p { + max-width: 555px; + + @media all and (max-width: 768px) { + height: auto !important; + } + } + + ul { + list-style-type: none; + padding-left: none; + } + + .panel { + padding-bottom: 1.25em; + } + } + } +} diff --git a/app/assets/stylesheets/darkswarm/taxons.css.sass b/app/assets/stylesheets/darkswarm/taxons.css.sass deleted file mode 100644 index 1d1e5b652f..0000000000 --- a/app/assets/stylesheets/darkswarm/taxons.css.sass +++ /dev/null @@ -1,52 +0,0 @@ -@import branding -@import mixins - -.fat-taxons, .fat-properties - display: inline-block - line-height: 1 - margin-right: 0.5rem - margin-bottom: 0.35rem - text-transform: capitalize - font-weight: 300 - font-size: 0.875rem - background: rgba(215,215,215,0.5) - color: #555 - @include border-radius(3px) - padding: 0.25rem 0.5rem 0.35rem 0.35rem - render-svg - svg - width: 16px - height: 16px - path - fill: #555 - &, & * - display: inline-block - color: #555 - -.inactive - .fat-taxons - render-svg - svg - path - fill: $disabled-dark - -.product-header - render-svg - svg - width: 32px - height: 32px - path - fill: black - @media all and (max-width: 640px) - render-svg - svg - width: 24px - height: 24px - -.summary-header - render-svg - svg - width: 18px - height: 18px - path - fill: $clr-brick \ No newline at end of file diff --git a/app/assets/stylesheets/darkswarm/taxons.css.scss b/app/assets/stylesheets/darkswarm/taxons.css.scss new file mode 100644 index 0000000000..a5db89b324 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/taxons.css.scss @@ -0,0 +1,81 @@ +@import "branding"; +@import "mixins"; + +.fat-taxons, .fat-properties { + display: inline-block; + line-height: 1; + margin-right: 0.5rem; + margin-bottom: 0.35rem; + text-transform: capitalize; + font-weight: 300; + font-size: 0.875rem; + background: rgba(215, 215, 215, 0.5); + color: #555; + + @include border-radius(3px); + + padding: 0.25rem 0.5rem 0.35rem 0.35rem; + + render-svg { + svg { + width: 16px; + height: 16px; + + path { + fill: #555; + } + } + } + + &, & * { + display: inline-block; + color: #555; + } +} + +.inactive { + .fat-taxons { + render-svg {} + + svg { + path { + fill: $disabled-dark; + } + } + } +} + +.product-header { + render-svg { + svg { + width: 32px; + height: 32px; + + path { + fill: black; + } + } + } + + @media all and (max-width: 640px) { + render-svg { + svg { + width: 24px; + height: 24px; + } + } + } +} + +.summary-header { + render-svg { + svg { + width: 18px; + height: 18px; + + path { + fill: $clr-brick; + } + } + } +} diff --git a/app/assets/stylesheets/darkswarm/typography.css.sass b/app/assets/stylesheets/darkswarm/typography.css.sass deleted file mode 100644 index 8c151db832..0000000000 --- a/app/assets/stylesheets/darkswarm/typography.css.sass +++ /dev/null @@ -1,125 +0,0 @@ -@import branding - -@mixin headingFont - font-family: 'Oswald', sans-serif - -@mixin bodyFont - font-family: 'Roboto', Arial, sans-serif - -$headingFont: 'Oswald' -$bodyFont: 'Roboto' - -body - @include bodyFont - font-weight: 400 -a - color: $clr-brick - &:hover, &:focus, &:active - text-decoration: none - color: $clr-brick-bright - -.text-vbig - font-size: 2rem - font-weight: 300 - -.text-big - font-size: 1.5rem - font-weight: 300 - -small, .small - font-size: 0.75rem - -.text-small - font-size: 0.875rem - margin-bottom: 0.5rem - font-family: $bodyFont - &, & * - font-size: 0.875rem - -.text-normal - font-weight: 400 - font-family: $bodyFont - -.text-skinny - font-weight: 300 - font-family: $bodyFont - -.word-wrap - word-wrap: break-word - -.pre-wrap - white-space: pre-wrap - -.pre-line - white-space: pre-line - -.light - color: #999 - display: inline - -.center - text-align: center - -.turquoise - color: $clr-turquoise - -.brick - color: $clr-brick - -.hr-light - border-color: rgba(#ddd, 0.25) - -h1, h2, h3, h4, h5, h6 - @include headingFont - padding: 0px - -.inline-header - display: inline-block - margin: 0px - -ul.bullet-list, ul.check-list - margin: 0 0 0 1.25em !important - li - list-style: none - line-height: 1.5 - height: inherit - li:before - content: "\e609" - font-family: "OFN" - margin-left: -1.25em - display: inline-block - font-weight: normal - font-style: normal - font-variant: normal - text-transform: none - -ul.check-list - li:before - content: "\e632" - -.light-grey - color: #666666 - -.pad - padding: 1em - -.pad-top - padding-top: 1em - -.not-bold - font-weight: normal - -.footer-pad - padding-bottom: 100px - - -// These selectors match the default Foundation selectors -// For clean overriden magic -table tr th, table tr td - color: #333333 -table thead tr th, table thead tr td, table tfoot tr th, table tfoot tr td - color: #333333 - -span.email - direction: rtl - unicode-bidi: bidi-override diff --git a/app/assets/stylesheets/darkswarm/typography.css.scss b/app/assets/stylesheets/darkswarm/typography.css.scss new file mode 100644 index 0000000000..0ac7cbb673 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/typography.css.scss @@ -0,0 +1,167 @@ +@import "branding"; + +@mixin headingFont { + font-family: "Oswald", sans-serif; +} + +@mixin bodyFont { + font-family: "Roboto", Arial, sans-serif; +} + +$headingFont: "Oswald"; +$bodyFont: "Roboto"; + +body { + @include bodyFont; + + font-weight: 400; +} + +a { + color: $clr-brick; + + &:hover, &:focus, &:active { + text-decoration: none; + color: $clr-brick-bright; + } +} + +.text-vbig { + font-size: 2rem; + font-weight: 300; +} + +.text-big { + font-size: 1.5rem; + font-weight: 300; +} + +small, .small { + font-size: 0.75rem; +} + +.text-small { + font-size: 0.875rem; + margin-bottom: 0.5rem; + font-family: $bodyFont; + + &, & * { + font-size: 0.875rem; + } +} + +.text-normal { + font-weight: 400; + font-family: $bodyFont; +} + +.text-skinny { + font-weight: 300; + font-family: $bodyFont; +} + +.word-wrap { + word-wrap: break-word; +} + +.pre-wrap { + white-space: pre-wrap; +} + +.pre-line { + white-space: pre-line; +} + +.light { + color: #999; + display: inline; +} + +.center { + text-align: center; +} + +.turquoise { + color: $clr-turquoise; +} + +.brick { + color: $clr-brick; +} + +.hr-light { + border-color: rgba(#ddd, 0.25); +} + +h1, h2, h3, h4, h5, h6 { + @include headingFont; + + padding: 0px; +} + +.inline-header { + display: inline-block; + margin: 0px; +} + +ul.bullet-list, ul.check-list { + margin: 0 0 0 1.25em !important; + + li { + list-style: none; + line-height: 1.5; + height: inherit; + } + + li:before { + content: ""; + font-family: "OFN"; + margin-left: -1.25em; + display: inline-block; + font-weight: normal; + font-style: normal; + font-variant: normal; + text-transform: none; + } +} + +ul.check-list { + li:before { + content: ""; + } +} + +.light-grey { + color: #666666; +} + +.pad { + padding: 1em; +} + +.pad-top { + padding-top: 1em; +} + +.not-bold { + font-weight: normal; +} + +.footer-pad { + padding-bottom: 100px; +} + +// These selectors match the default Foundation selectors +// For clean overriden magic +table tr th, table tr td { + color: #333333; +} + +table thead tr th, table thead tr td, table tfoot tr th, table tfoot tr td { + color: #333333; +} + +span.email { + direction: rtl; + unicode-bidi: bidi-override; +} diff --git a/app/assets/stylesheets/darkswarm/ui.css.sass b/app/assets/stylesheets/darkswarm/ui.css.sass deleted file mode 100644 index 5c049a2bba..0000000000 --- a/app/assets/stylesheets/darkswarm/ui.css.sass +++ /dev/null @@ -1,97 +0,0 @@ -@import foundation/components/buttons -@import branding -@import mixins -@import typography - -// Button class extensions - -.neutral-btn - @include button - @include border-radius(0.5em) - font-family: $bodyFont - background-color: transparent - border: 2px solid rgba(200, 200, 200, 1) - color: #999 - -.neutral-btn:hover, .neutral-btn:active, .neutral-btn:focus - background-color: rgba(200, 200, 200, 0.2) - border: 2px solid rgba(200, 200, 200, 0.8) - -.neutral-btn.dark - border-color: #000 - color: #000 - -.neutral-btn.dark:hover, .neutral-btn.dark:active, .neutral-btn.dark:focus - background-color: rgba(0, 0, 0, 0.1) - border: 2px solid rgba(0, 0, 0, 0.8) - text-shadow: 0 1px 0 #fff - -.neutral-btn.light - border-color: #fff - color: #fff - -.neutral-btn.light:hover, .neutral-btn.light:active, .neutral-btn.light:focus - background-color: rgba(255, 255, 255, 0.2) - border: 2px solid rgba(255, 255, 255, 0.8) - text-shadow: 0 1px 0 rgba(0,0,0,0.2) - -.neutral-btn.turquoise - border-color: $clr-turquoise - color: $clr-turquoise - -.neutral-btn.turquoise:hover, .neutral-btn.turquoise:active, .neutral-btn.turquoise:focus - background-color: rgba(0, 0, 0, 0.1) - // text-shadow: 0 1px 0 #fff - -// Rewrite foundation's .primary button style - -.button, button - @include border-radius(0.5em) - outline: none // Turn off blue highlight on chrome - -.button.primary, button.primary - font-family: $bodyFont - background: $clr-brick - color: white - -.button.primary:hover, .button.primary:active, .button.primary:focus, button.primary:hover, button.primary:active, button.primary:focus - background: $clr-brick-bright - text-shadow: 0 1px 0 $clr-brick - -button.success, .button.success - background: #0096ad - -.button.success:hover, .button.success:active, .button.success:focus, button.success:hover, button.success:active, button.success:focus - background: #14b6cc - -.button.help-btn - @include border-radius(999999px) - &.tiny - padding: 0rem - margin: 0 - float: right - i - font-size: 1.25rem - -.profile-checkbox - display: inline-block - label - margin: 0 0.2rem - float: right - -// Transparent button -.button.transparent - @include headingFont - font-size: 20px - text-transform: uppercase - background: rgba(0, 0, 0, 0.3) - border: 3px solid #fff - text-shadow: none - &:hover - background: rgba(0, 0, 0, 0.6) - color: #fff - -// Responsive -@media screen and (min-width: 768px) - [role="main"] - padding: 0 diff --git a/app/assets/stylesheets/darkswarm/ui.css.scss b/app/assets/stylesheets/darkswarm/ui.css.scss new file mode 100644 index 0000000000..1d1b9e166a --- /dev/null +++ b/app/assets/stylesheets/darkswarm/ui.css.scss @@ -0,0 +1,128 @@ +@import "foundation/components/buttons"; +@import "branding"; +@import "mixins"; +@import "typography"; + +// Button class extensions + +.neutral-btn { + @include button; + + @include border-radius(0.5em); + + font-family: $bodyFont; + background-color: transparent; + border: 2px solid rgba(200, 200, 200, 1); + color: #999; +} + +.neutral-btn:hover, .neutral-btn:active, .neutral-btn:focus { + background-color: rgba(200, 200, 200, 0.2); + border: 2px solid rgba(200, 200, 200, 0.8); +} + +.neutral-btn.dark { + border-color: #000; + color: #000; +} + +.neutral-btn.dark:hover, .neutral-btn.dark:active, .neutral-btn.dark:focus { + background-color: rgba(0, 0, 0, 0.1); + border: 2px solid rgba(0, 0, 0, 0.8); + text-shadow: 0 1px 0 #fff; +} + +.neutral-btn.light { + border-color: #fff; + color: #fff; +} + +.neutral-btn.light:hover, .neutral-btn.light:active, .neutral-btn.light:focus { + background-color: rgba(255, 255, 255, 0.2); + border: 2px solid rgba(255, 255, 255, 0.8); + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2); +} + +.neutral-btn.turquoise { + border-color: $clr-turquoise; + color: $clr-turquoise; +} + +.neutral-btn.turquoise:hover, .neutral-btn.turquoise:active, .neutral-btn.turquoise:focus { + background-color: rgba(0, 0, 0, 0.1); + + // text-shadow: 0 1px 0 #fff +} + +// Rewrite foundation's .primary button style + +.button, button { + @include border-radius(0.5em); + + outline: none; +} + +.button.primary, button.primary { + font-family: $bodyFont; + background: $clr-brick; + color: white; +} + +.button.primary:hover, .button.primary:active, .button.primary:focus, button.primary:hover, button.primary:active, button.primary:focus { + background: $clr-brick-bright; + text-shadow: 0 1px 0 $clr-brick; +} + +button.success, .button.success { + background: #0096ad; +} + +.button.success:hover, .button.success:active, .button.success:focus, button.success:hover, button.success:active, button.success:focus { + background: #14b6cc; +} + +.button.help-btn { + @include border-radius(999999px); + + &.tiny { + padding: 0rem; + margin: 0; + float: right; + } + + i { + font-size: 1.25rem; + } +} + +.profile-checkbox { + display: inline-block; + + label { + margin: 0 0.2rem; + float: right; + } +} + +// Transparent button +.button.transparent { + @include headingFont; + + font-size: 20px; + text-transform: uppercase; + background: rgba(0, 0, 0, 0.3); + border: 3px solid #fff; + text-shadow: none; + + &:hover { + background: rgba(0, 0, 0, 0.6); + color: #fff; + } +} + +// Responsive +@media screen and (min-width: 768px) { + [role="main"] { + padding: 0; + } +} diff --git a/app/assets/stylesheets/darkswarm/variables.css.sass b/app/assets/stylesheets/darkswarm/variables.css.sass deleted file mode 100644 index ea87d11fc4..0000000000 --- a/app/assets/stylesheets/darkswarm/variables.css.sass +++ /dev/null @@ -1,32 +0,0 @@ -@import "foundation/functions" -@import "foundation/components/global" - -// Brand guide colours: -// International: #81c26e -// Australia: #f35746 -// Africa: #f35e32 -// South Africa: #f9a72b -// Norway: #4b83cc -// Scandanavia: #0c8bbc -// UK: #e6373f - -$brand-colour: #f27052 - - -// Topbar -$topbar-height: rem-calc(75) -$topbar-link-padding: $topbar-height / 3 - -$topbar-bg: $white -$topbar-bg-color: $topbar-bg - -$topbar-link-color: $black -$topbar-link-color-hover: $brand-colour -$topbar-link-color-active: $black -$topbar-link-color-active-hover: $white -$topbar-link-bg-hover: $white - -$topbar-dropdown-link-color: $black -$topbar-dropdown-bg: $white -$topbar-dropdown-link-bg: $white -$topbar-dropdown-link-bg-hover: $white diff --git a/app/assets/stylesheets/darkswarm/variables.css.scss b/app/assets/stylesheets/darkswarm/variables.css.scss new file mode 100644 index 0000000000..12d77f0d09 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/variables.css.scss @@ -0,0 +1,31 @@ +@import "foundation/functions"; +@import "foundation/components/global"; + +// Brand guide colours: +// International: #81c26e +// Australia: #f35746 +// Africa: #f35e32 +// South Africa: #f9a72b +// Norway: #4b83cc +// Scandanavia: #0c8bbc +// UK: #e6373f + +$brand-colour: #f27052; + +// Topbar +$topbar-height: rem-calc(75); +$topbar-link-padding: $topbar-height / 3; + +$topbar-bg: $white; +$topbar-bg-color: $topbar-bg; + +$topbar-link-color: $black; +$topbar-link-color-hover: $brand-colour; +$topbar-link-color-active: $black; +$topbar-link-color-active-hover: $white; +$topbar-link-bg-hover: $white; + +$topbar-dropdown-link-color: $black; +$topbar-dropdown-bg: $white; +$topbar-dropdown-link-bg: $white; +$topbar-dropdown-link-bg-hover: $white; diff --git a/app/assets/stylesheets/mail/email.css.sass b/app/assets/stylesheets/mail/email.css.sass deleted file mode 100644 index e9eabd4edb..0000000000 --- a/app/assets/stylesheets/mail/email.css.sass +++ /dev/null @@ -1,312 +0,0 @@ -/* ------------------------------------- - * GLOBAL - *------------------------------------- - -* - margin: 0 - padding: 0 - font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif - -img - max-width: 100% - -.collapse - margin: 0 - padding: 0 - -body - -webkit-font-smoothing: antialiased - -webkit-text-size-adjust: none - width: 100%!important - height: 100% - -/* ------------------------------------- - * ELEMENTS - *------------------------------------- - -a - color: #0096ad - -.btn - text-decoration: none - color: #FFF - background-color: #666 - padding: 10px 16px - font-weight: bold - margin-right: 10px - text-align: center - cursor: pointer - display: inline-block - -p.callout - padding: 15px - background-color: #e1f0f5 - margin-bottom: 15px - -.callout a - font-weight: bold - color: #0096ad - -table.social - background-color: #ebebeb - &.white-bg - background-color: white!important - border: 1px solid #ebebeb - -table.order-summary - border-collapse: separate - border-spacing: 0px 10px - tbody tr td - padding-left: 5px - padding-right: 5px - thead tr th - background-color: #f2f2f2 - border-bottom: 1px solid black - padding-left: 5px - padding-right: 5px - h4 - margin-top: 15px - tfoot - tr:first-child td - border-top: 1px solid black - padding-top: 5px - tr td - padding-left: 5px - padding-right: 5px - -.text-right - text-align: right - -.social .soc-btn - padding: 3px 7px - font-size: 12px - margin-bottom: 10px - text-decoration: none - color: #FFF - font-weight: bold - display: block - text-align: center - -a - &.fb - background-color: #3B5998!important - &.tw - background-color: #1daced!important - &.gp - background-color: #DB4A39!important - &.li - background-color: #0073b2!important - &.ms - background-color: #000!important - -.sidebar .soc-btn - display: block - width: 100% - -img.float-right - float: right - display: block - -/* ------------------------------------- - * HEADER - *------------------------------------- - -table.head-wrap - width: 100% - -.header.container table td - &.logo - padding: 15px - &.label - padding: 15px - padding-left: 0px - -/* ------------------------------------- - * BODY - *------------------------------------- - -table - &.body-wrap - width: 100% - &.footer-wrap - width: 100% - clear: both!important - -/* ------------------------------------- - * FOOTER - *------------------------------------- - -.footer-wrap .container td.content p - border-top: 1px solid rgb(215, 215, 215) - padding-top: 15px - font-size: 10px - font-weight: bold - -/* ------------------------------------- - * TYPOGRAPHY - *------------------------------------- - -h1, h2, h3, h4, h5, h6 - font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif - line-height: 1.1 - margin-bottom: 15px - color: #000 - -h1 small, h2 small, h3 small, h4 small, h5 small, h6 small - font-size: 60% - color: #6f6f6f - line-height: 0 - text-transform: none - -h1 - font-weight: 200 - font-size: 44px - -h2 - font-weight: 200 - font-size: 37px - -h3 - font-weight: 500 - font-size: 27px - -h4 - font-weight: 500 - font-size: 23px - -h5 - font-weight: 900 - font-size: 17px - -h6 - font-weight: 900 - font-size: 14px - text-transform: uppercase - color: #999 - -.collapse - margin: 0!important - -p, ul - margin-bottom: 10px - font-weight: normal - font-size: 14px - line-height: 1.6 - -p - &.lead - font-size: 17px - &.last - margin-bottom: 0px - -ul - li - margin-left: 5px - list-style-position: inside - &.sidebar - background: #ebebeb - display: block - list-style-type: none - li - display: block - margin: 0 - a - text-decoration: none - color: #666 - padding: 10px 16px - /* font-weight:bold; - margin-right: 10px - /* text-align:center; - cursor: pointer - border-bottom: 1px solid #777777 - border-top: 1px solid #FFFFFF - display: block - margin: 0 - &.last - border-bottom-width: 0px - h1, h2, h3, h4, h5, h6, p - margin-bottom: 0!important - -/* ------------------------------------- - * SIDEBAR - *------------------------------------- - -/* --------------------------------------------------- - * RESPONSIVENESS - * Nuke it from orbit. It's the only way to be sure. - *------------------------------------------------------ - -/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something - -.container - display: block!important - max-width: 600px!important - margin: 0 auto!important - /* makes it centered - clear: both!important - -/* This should also be a block element, so that it will fill 100% of the .container - -.content - padding: 15px - max-width: 600px - margin: 0 auto - display: block - table - width: 100% - -/* Let's make sure tables in the content area are 100% wide - -/* Odds and ends - -.column - width: 300px - float: left - tr td - padding: 15px - -.pad - tr td - padding: 15px - -.column-wrap - padding: 0!important - margin: 0 auto - max-width: 600px!important - -.column table - width: 100% - -.social .column - width: 280px - min-width: 279px - float: left - -/* Be sure to place a .clear element after each set of columns, just to be safe - -.clear - display: block - clear: both - -/* ------------------------------------------- - * PHONE - * For clients that support media queries. - * Nothing fancy. - *-------------------------------------------- -@media only screen and (max-width: 600px) - a[class="btn"] - display: block!important - margin-bottom: 10px!important - background-image: none!important - margin-right: 0!important - div[class="column"] - width: auto!important - float: none!important - table.social div[class="column"] - width: auto!important - img.float-right - float: none!important - -.inline-header - display: inline-block - margin: 0px diff --git a/app/assets/stylesheets/mail/email.css.scss b/app/assets/stylesheets/mail/email.css.scss new file mode 100644 index 0000000000..0b2f641b4f --- /dev/null +++ b/app/assets/stylesheets/mail/email.css.scss @@ -0,0 +1,410 @@ +/* ------------------------------------- + * GLOBAL + *------------------------------------- */ + +* { + margin: 0; + padding: 0; + font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif; +} + +img { + max-width: 100%; +} + +.collapse { + margin: 0; + padding: 0; +} + +body { + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; + width: 100% !important; + height: 100%; +} + +/* ------------------------------------- + * ELEMENTS + *------------------------------------- */ + +a { + color: #0096ad; +} + +.btn { + text-decoration: none; + color: #FFF; + background-color: #666; + padding: 10px 16px; + font-weight: bold; + margin-right: 10px; + text-align: center; + cursor: pointer; + display: inline-block; +} + +p.callout { + padding: 15px; + background-color: #e1f0f5; + margin-bottom: 15px; +} + +.callout a { + font-weight: bold; + color: #0096ad; +} + +table.social { + background-color: #ebebeb; + + &.white-bg { + background-color: white !important; + border: 1px solid #ebebeb; + } +} + +table.order-summary { + border-collapse: separate; + border-spacing: 0px 10px; + + tbody tr td { + padding-left: 5px; + padding-right: 5px; + } + + thead tr th { + background-color: #f2f2f2; + border-bottom: 1px solid black; + padding-left: 5px; + padding-right: 5px; + + h4 { + margin-top: 15px; + } + } + + tfoot { + tr:first-child td { + border-top: 1px solid black; + padding-top: 5px; + } + + tr td { + padding-left: 5px; + padding-right: 5px; + } + } +} + +.text-right { + text-align: right; +} + +.social .soc-btn { + padding: 3px 7px; + font-size: 12px; + margin-bottom: 10px; + text-decoration: none; + color: #FFF; + font-weight: bold; + display: block; + text-align: center; +} + +a { + &.fb { + background-color: #3B5998 !important; + } + + &.tw { + background-color: #1daced !important; + } + + &.gp { + background-color: #DB4A39 !important; + } + + &.li { + background-color: #0073b2 !important; + } + + &.ms { + background-color: #000 !important; + } +} + +.sidebar .soc-btn { + display: block; + width: 100%; +} + +img.float-right { + float: right; + display: block; +} + +/* ------------------------------------- + * HEADER + *------------------------------------- */ + +table.head-wrap { + width: 100%; +} + +.header.container table td { + &.logo { + padding: 15px; + } + + &.label { + padding: 15px; + padding-left: 0px; + } +} + +/* ------------------------------------- + * BODY + *------------------------------------- */ + +table { + &.body-wrap { + width: 100%; + } + + &.footer-wrap { + width: 100%; + clear: both !important; + } +} + +/* ------------------------------------- + * FOOTER + *------------------------------------- */ + +.footer-wrap .container td.content p { + border-top: 1px solid rgb(215, 215, 215); + padding-top: 15px; + font-size: 10px; + font-weight: bold; +} + +/* ------------------------------------- + * TYPOGRAPHY + *------------------------------------- */ + +h1, h2, h3, h4, h5, h6 { + font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; + line-height: 1.1; + margin-bottom: 15px; + color: #000; +} + +h1 small, h2 small, h3 small, h4 small, h5 small, h6 small { + font-size: 60%; + color: #6f6f6f; + line-height: 0; + text-transform: none; +} + +h1 { + font-weight: 200; + font-size: 44px; +} + +h2 { + font-weight: 200; + font-size: 37px; +} + +h3 { + font-weight: 500; + font-size: 27px; +} + +h4 { + font-weight: 500; + font-size: 23px; +} + +h5 { + font-weight: 900; + font-size: 17px; +} + +h6 { + font-weight: 900; + font-size: 14px; + text-transform: uppercase; + color: #999; +} + +.collapse { + margin: 0 !important; +} + +p, ul { + margin-bottom: 10px; + font-weight: normal; + font-size: 14px; + line-height: 1.6; +} + +p { + &.lead { + font-size: 17px; + } + + &.last { + margin-bottom: 0px; + } +} + +ul { + li { + margin-left: 5px; + list-style-position: inside; + } + + &.sidebar { + background: #ebebeb; + display: block; + list-style-type: none; + + li { + display: block; + margin: 0; + + a { + text-decoration: none; + color: #666; + padding: 10px 16px; + + /* font-weight:bold; */ + margin-right: 10px; + + /* text-align:center; */ + cursor: pointer; + border-bottom: 1px solid #777777; + border-top: 1px solid #FFFFFF; + display: block; + margin: 0; + + &.last { + border-bottom-width: 0px; + } + + h1, h2, h3, h4, h5, h6, p { + margin-bottom: 0 !important; + } + } + } + } +} + +/* ------------------------------------- + * SIDEBAR + *------------------------------------- */ + +/* --------------------------------------------------- + * RESPONSIVENESS + * Nuke it from orbit. It's the only way to be sure. + *------------------------------------------------------ */ + +/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */ + +.container { + display: block !important; + max-width: 600px !important; + margin: 0 auto !important; + + /* makes it centered */ + clear: both !important; +} + +/* This should also be a block element, so that it will fill 100% of the .container */ + +.content { + padding: 15px; + max-width: 600px; + margin: 0 auto; + display: block; + + table { + width: 100%; + } +} + +/* Let's make sure tables in the content area are 100% wide */ + +/* Odds and ends */ + +.column { + width: 300px; + float: left; + + tr td { + padding: 15px; + } +} + +.pad { + tr td { + padding: 15px; + } +} + +.column-wrap { + padding: 0 !important; + margin: 0 auto; + max-width: 600px !important; +} + +.column table { + width: 100%; +} + +.social .column { + width: 280px; + min-width: 279px; + float: left; +} + +/* Be sure to place a .clear element after each set of columns, just to be safe */ + +.clear { + display: block; + clear: both; +} + +/* ------------------------------------------- + * PHONE + * For clients that support media queries. + * Nothing fancy. + *-------------------------------------------- */ +@media only screen and (max-width: 600px) { + a[class="btn"] { + display: block !important; + margin-bottom: 10px !important; + background-image: none !important; + margin-right: 0 !important; + } + + div[class="column"] { + width: auto !important; + float: none !important; + } + + table.social div[class="column"] { + width: auto !important; + } + + img.float-right { + float: none !important; + } +} + +.inline-header { + display: inline-block; + margin: 0px; +} From 017916b1933e4d6cd0b09acbca6bb8c87d7b1eb4 Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Thu, 5 Jan 2017 12:17:24 +1100 Subject: [PATCH 105/109] Enterprise distributing_products scope uses INNER JOINS instead of OUTER JOINS --- app/models/enterprise.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 840477185f..e9acbd51f6 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -163,10 +163,18 @@ class Enterprise < ActiveRecord::Base } scope :distributing_products, lambda { |products| - with_distributed_products_outer.with_order_cycles_and_exchange_variants_outer. - where('product_distributions.product_id IN (?) OR spree_variants.product_id IN (?)', products, products). - select('DISTINCT enterprises.*') + # TODO: remove this when we pull out product distributions + pds = joins("INNER JOIN product_distributions ON product_distributions.distributor_id = enterprises.id"). + where("product_distributions.product_id IN (?)", products).select('DISTINCT enterprises.id') + + exs = joins("INNER JOIN exchanges ON (exchanges.receiver_id = enterprises.id AND exchanges.incoming = 'f')"). + joins('INNER JOIN exchange_variants ON (exchange_variants.exchange_id = exchanges.id)'). + joins('INNER JOIN spree_variants ON (spree_variants.id = exchange_variants.variant_id)'). + where('spree_variants.product_id IN (?)', products).select('DISTINCT enterprises.id') + + where(id: pds | exs) } + scope :managed_by, lambda { |user| if user.has_spree_role?('admin') scoped From 165b437f315c249da56cc698e1db1b53f05f2d92 Mon Sep 17 00:00:00 2001 From: Paul Mackay Date: Fri, 20 Jan 2017 15:59:22 +0000 Subject: [PATCH 106/109] #1226: fix OSM URL to use HTTPS --- .../javascripts/darkswarm/directives/map_osm_tiles.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/darkswarm/directives/map_osm_tiles.js.coffee b/app/assets/javascripts/darkswarm/directives/map_osm_tiles.js.coffee index 0dcda8f59a..deb7186d95 100644 --- a/app/assets/javascripts/darkswarm/directives/map_osm_tiles.js.coffee +++ b/app/assets/javascripts/darkswarm/directives/map_osm_tiles.js.coffee @@ -15,7 +15,7 @@ Darkswarm.directive 'mapOsmTiles', ($timeout) -> if x < 0 x = tilesPerGlobe + x # Wrap y (latitude) in a like manner if you want to enable vertical infinite scroll - 'http://tile.openstreetmap.org/' + zoom + '/' + x + '/' + coord.y + '.png' + 'https://a.tile.openstreetmap.org/' + zoom + '/' + x + '/' + coord.y + '.png' tileSize: new google.maps.Size(256, 256) name: 'OpenStreetMap' maxZoom: 18 From 940ca7ade1739bda52071481a32debabcdc9c648 Mon Sep 17 00:00:00 2001 From: OliverUK Date: Fri, 27 Jan 2017 15:02:38 +0000 Subject: [PATCH 107/109] translate ABN and ACN to company number, charity number --- config/locales/en-GB.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 92eba244eb..041cdc614b 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -548,7 +548,7 @@ en-GB: sell_hubs_detail: "Set up a profile for your food enterprise or organisation on the OFN. At any time you can upgrade your profile to a multi-producer shop." sell_groups_detail: "Set up a tailored directory of enterprises (producers and other food enterprises) for your region or for your organisation." sell_user_guide: "Find out more in our user guide." - sell_listing_price: "Selling through OFN UK is free!\n\nOFN UK is a Platform Cooperative - part of the Solidarity or Sharing Economy. Our goal is to support the distribution of as much local food as possible. As such OFN UK asks users to 'Pay What You Feel' toward the running costs and maintenance. We think 2% of sales is fair for all the services that OFN UK provide. But more importantly, we think that getting good local food to people at a fair price is more important than paying for software. So pay what you feel our services are worth to your enterprise." + sell_listing_price: "Sell through OFN UK. Pay what you think is fair.\n\nOur goal is to support the distribution of as much local food as possible. As such OFN UK asks users to 'Pay What You Feel' toward the running costs and maintenance. All we ask is that you do pay something. " sell_embed: "We collectively budget for new feature development from the international OFN community. This way the huge cost of good software development can be shared. If you want a new feature, chances are someone in France, South Africa, Australia, India or Brazil might want it too! Use the Community Forum to suggest features you'd like to see." sell_ask_services: "Ask us about OFN services." shops_title: Shops @@ -683,9 +683,9 @@ en-GB: enterprise_long_desc: "Long Description" enterprise_long_desc_placeholder: "This is your opportunity to tell the story of your enterprise - what makes you different and wonderful? We'd suggest keeping your description to under 600 characters or 150 words." enterprise_long_desc_length: "%{num} characters / up to 600 recommended" - enterprise_abn: "ABN" + enterprise_abn: "Company Number" enterprise_abn_placeholder: "eg. 99 123 456 789" - enterprise_acn: "ACN" + enterprise_acn: "Charity Number" enterprise_acn_placeholder: "eg. 123 456 789" enterprise_tax_required: "You need to make a selection." enterprise_final_step: "Final step!" From 67730e82c3083a481059cd8183d71317b082adca Mon Sep 17 00:00:00 2001 From: Lynne Date: Mon, 30 Jan 2017 12:04:24 +0000 Subject: [PATCH 108/109] Update en-GB.yml --- config/locales/en-GB.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 041cdc614b..c1954c4956 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -548,7 +548,7 @@ en-GB: sell_hubs_detail: "Set up a profile for your food enterprise or organisation on the OFN. At any time you can upgrade your profile to a multi-producer shop." sell_groups_detail: "Set up a tailored directory of enterprises (producers and other food enterprises) for your region or for your organisation." sell_user_guide: "Find out more in our user guide." - sell_listing_price: "Sell through OFN UK. Pay what you think is fair.\n\nOur goal is to support the distribution of as much local food as possible. As such OFN UK asks users to 'Pay What You Feel' toward the running costs and maintenance. All we ask is that you do pay something. " + sell_listing_price: "Sell through OFN UK. Pay what you think is fair. Our goal is to support the distribution of as much local food as possible. As such OFN UK asks users to 'Pay What You Feel' toward the running costs and maintenance. All we ask is that you do pay something. " sell_embed: "We collectively budget for new feature development from the international OFN community. This way the huge cost of good software development can be shared. If you want a new feature, chances are someone in France, South Africa, Australia, India or Brazil might want it too! Use the Community Forum to suggest features you'd like to see." sell_ask_services: "Ask us about OFN services." shops_title: Shops From 6d19613ecc943261717c8c53ccf13e852f9830ca Mon Sep 17 00:00:00 2001 From: Lynne Date: Mon, 30 Jan 2017 12:53:01 +0000 Subject: [PATCH 109/109] Revert "translate ABN and ACN to company number, charity number" --- config/locales/en-GB.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index c1954c4956..92eba244eb 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -548,7 +548,7 @@ en-GB: sell_hubs_detail: "Set up a profile for your food enterprise or organisation on the OFN. At any time you can upgrade your profile to a multi-producer shop." sell_groups_detail: "Set up a tailored directory of enterprises (producers and other food enterprises) for your region or for your organisation." sell_user_guide: "Find out more in our user guide." - sell_listing_price: "Sell through OFN UK. Pay what you think is fair. Our goal is to support the distribution of as much local food as possible. As such OFN UK asks users to 'Pay What You Feel' toward the running costs and maintenance. All we ask is that you do pay something. " + sell_listing_price: "Selling through OFN UK is free!\n\nOFN UK is a Platform Cooperative - part of the Solidarity or Sharing Economy. Our goal is to support the distribution of as much local food as possible. As such OFN UK asks users to 'Pay What You Feel' toward the running costs and maintenance. We think 2% of sales is fair for all the services that OFN UK provide. But more importantly, we think that getting good local food to people at a fair price is more important than paying for software. So pay what you feel our services are worth to your enterprise." sell_embed: "We collectively budget for new feature development from the international OFN community. This way the huge cost of good software development can be shared. If you want a new feature, chances are someone in France, South Africa, Australia, India or Brazil might want it too! Use the Community Forum to suggest features you'd like to see." sell_ask_services: "Ask us about OFN services." shops_title: Shops @@ -683,9 +683,9 @@ en-GB: enterprise_long_desc: "Long Description" enterprise_long_desc_placeholder: "This is your opportunity to tell the story of your enterprise - what makes you different and wonderful? We'd suggest keeping your description to under 600 characters or 150 words." enterprise_long_desc_length: "%{num} characters / up to 600 recommended" - enterprise_abn: "Company Number" + enterprise_abn: "ABN" enterprise_abn_placeholder: "eg. 99 123 456 789" - enterprise_acn: "Charity Number" + enterprise_acn: "ACN" enterprise_acn_placeholder: "eg. 123 456 789" enterprise_tax_required: "You need to make a selection." enterprise_final_step: "Final step!"