diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7b394140d5..ec1a08947e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -13,21 +13,11 @@ Provide context for others to understand it. --> #### Release notes - + - - - - -Changelog Category: Added | Changed | Deprecated | Removed | Fixed | Security - - - -#### Discourse thread - + +Changelog Category: User facing changes | Technical changes diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index a919e0dedc..dba500e10f 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -167,7 +167,6 @@ Layout/LineLength: - spec/features/admin/enterprises/index_spec.rb - spec/features/admin/enterprises_spec.rb - spec/features/admin/enterprise_user_spec.rb - - spec/features/admin/image_settings_spec.rb - spec/features/admin/multilingual_spec.rb - spec/features/admin/order_cycles/complex_creating_specific_time_spec.rb - spec/features/admin/order_cycles/complex_editing_multiple_product_pages_spec.rb @@ -345,7 +344,6 @@ Metrics/AbcSize: - app/controllers/cart_controller.rb - app/controllers/discourse_sso_controller.rb - app/controllers/enterprises_controller.rb - - app/controllers/spree/admin/image_settings_controller.rb - app/controllers/spree/admin/orders_controller.rb - app/controllers/spree/admin/orders/customer_details_controller.rb - app/controllers/spree/admin/overview_controller.rb @@ -665,7 +663,6 @@ Metrics/MethodLength: - app/controllers/api/variants_controller.rb - app/controllers/cart_controller.rb - app/controllers/shop_controller.rb - - app/controllers/spree/admin/image_settings_controller.rb - app/controllers/spree/admin/orders_controller.rb - app/controllers/spree/admin/orders/customer_details_controller.rb - app/controllers/spree/admin/payment_methods_controller.rb @@ -814,42 +811,17 @@ Metrics/ModuleLength: - app/helpers/spree/admin/navigation_helper.rb - app/models/spree/order/checkout.rb - app/models/spree/payment/processing.rb - - engines/catalog/spec/services/catalog/product_import/products_reset_strategy_spec.rb - - engines/order_management/spec/services/order_management/order/updater_spec.rb - - engines/order_management/spec/services/order_management/stock/package_spec.rb - - engines/order_management/spec/services/order_management/subscriptions/estimator_spec.rb - - engines/order_management/spec/services/order_management/subscriptions/form_spec.rb - engines/order_management/spec/services/order_management/subscriptions/proxy_order_syncer_spec.rb - - engines/order_management/spec/services/order_management/subscriptions/summarizer_spec.rb - engines/order_management/spec/services/order_management/subscriptions/validator_spec.rb - - engines/order_management/spec/services/order_management/subscriptions/variants_list_spec.rb - lib/open_food_network/column_preference_defaults.rb - spec/controllers/admin/order_cycles_controller_spec.rb - - spec/controllers/api/order_cycles_controller_spec.rb - spec/controllers/api/orders_controller_spec.rb - - spec/controllers/spree/admin/payment_methods_controller_spec.rb - - spec/lib/open_food_network/address_finder_spec.rb - - spec/lib/open_food_network/customers_report_spec.rb - - spec/lib/open_food_network/enterprise_fee_calculator_spec.rb - - spec/lib/open_food_network/order_cycle_form_applicator_spec.rb - spec/lib/open_food_network/order_cycle_permissions_spec.rb - - spec/lib/open_food_network/order_grouper_spec.rb - - spec/lib/open_food_network/packing_report_spec.rb - - spec/lib/open_food_network/permissions_spec.rb - - spec/lib/open_food_network/products_and_inventory_report_spec.rb - - spec/lib/open_food_network/scope_variant_to_hub_spec.rb - - spec/lib/open_food_network/tag_rule_applicator_spec.rb - - spec/lib/open_food_network/user_balance_calculator_spec.rb - - spec/lib/open_food_network/users_and_enterprises_report_spec.rb - spec/models/spree/adjustment_spec.rb - spec/models/spree/credit_card_spec.rb - spec/models/spree/line_item_spec.rb - spec/models/spree/product_spec.rb - - spec/models/spree/shipping_method_spec.rb - spec/models/spree/variant_spec.rb - - spec/services/permissions/order_spec.rb - - spec/services/variant_units/option_value_namer_spec.rb - - spec/support/request/stripe_helper.rb Metrics/ParameterLists: Max: 5 diff --git a/.rubocop_specs.yml b/.rubocop_specs.yml new file mode 100644 index 0000000000..8ea54cd55c --- /dev/null +++ b/.rubocop_specs.yml @@ -0,0 +1,7 @@ +inherit_from: + - .rubocop.yml + +# This rubocop config file is only used for specs +# Here we allow specs to be 300 lines long +Metrics/ModuleLength: + Max: 300 diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2c867c2ec1..93648258a3 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -691,7 +691,6 @@ Style/FrozenStringLiteralComment: - 'app/controllers/spree/admin/base_controller.rb' - 'app/controllers/spree/admin/countries_controller.rb' - 'app/controllers/spree/admin/general_settings_controller.rb' - - 'app/controllers/spree/admin/image_settings_controller.rb' - 'app/controllers/spree/admin/images_controller.rb' - 'app/controllers/spree/admin/invoices_controller.rb' - 'app/controllers/spree/admin/mail_methods_controller.rb' @@ -726,7 +725,6 @@ Style/FrozenStringLiteralComment: - 'app/controllers/user_passwords_controller.rb' - 'app/controllers/user_registrations_controller.rb' - 'app/helpers/admin/enterprises_helper.rb' - - 'app/helpers/admin/image_settings_helper.rb' - 'app/helpers/admin/injection_helper.rb' - 'app/helpers/admin/orders_helper.rb' - 'app/helpers/admin/subscriptions_helper.rb' @@ -1123,7 +1121,6 @@ Style/FrozenStringLiteralComment: - 'spec/controllers/shops_controller_spec.rb' - 'spec/controllers/spree/admin/adjustments_controller_spec.rb' - 'spec/controllers/spree/admin/base_controller_spec.rb' - - 'spec/controllers/spree/admin/image_settings_controller_spec.rb' - 'spec/controllers/spree/admin/invoices_controller_spec.rb' - 'spec/controllers/spree/admin/mail_methods_controller_spec.rb' - 'spec/controllers/spree/admin/orders/customer_details_controller_spec.rb' @@ -1168,7 +1165,6 @@ Style/FrozenStringLiteralComment: - 'spec/features/admin/bulk_product_update_spec.rb' - 'spec/features/admin/configuration/content_spec.rb' - 'spec/features/admin/configuration/general_settings_spec.rb' - - 'spec/features/admin/configuration/image_settings_spec.rb' - 'spec/features/admin/configuration/mail_methods_spec.rb' - 'spec/features/admin/configuration/states_spec.rb' - 'spec/features/admin/configuration/tax_categories_spec.rb' @@ -1184,7 +1180,6 @@ Style/FrozenStringLiteralComment: - 'spec/features/admin/enterprises/index_spec.rb' - 'spec/features/admin/enterprises_spec.rb' - 'spec/features/admin/external_services_spec.rb' - - 'spec/features/admin/image_settings_spec.rb' - 'spec/features/admin/multilingual_spec.rb' - 'spec/features/admin/overview_spec.rb' - 'spec/features/admin/payment_method_spec.rb' diff --git a/DOCKER.md b/DOCKER.md deleted file mode 100644 index 15fa75df5a..0000000000 --- a/DOCKER.md +++ /dev/null @@ -1,65 +0,0 @@ -### Docker - -It is possible to setup the Open Food Network app easily with Docker and Docker Compose. -The objective is to spare configuration time, in order to help people testing the app and contribute to it. -It can also be used as documentation. It is not perfect but it is used in many other projects and many devs are used to it nowadays. - -### Install Docker - -Please check the documentation here, https://docs.docker.com/install/ to install Docker. - -For Docker Compose, information are here: https://docs.docker.com/compose/install/. - -Better to have at least 2GB free on your computer in order to download images and create containers for Open Food Network app. - - -### Use Docker with Open Food Network - -Open a terminal with a shell. - -Clone the repository. If you're planning on contributing code to the project (which we [LOVE](CONTRIBUTING.md)), it is a good idea to begin by forking this repo using the Fork button in the top-right corner of this screen. You should then be able to use git clone to copy your fork onto your local machine. - -```sh -$ git clone https://github.com/YOUR_GITHUB_USERNAME_HERE/openfoodnetwork -``` - -Otherwise, if you just want to get things running, clone from the OFN main repo: - -```sh -$ git clone git@github.com:openfoodfoundation/openfoodnetwork.git -``` - -Go at the root of the app: - -```sh -$ cd openfoodnetwork -``` - -Download the Docker images and build the containers: - -```sh -$ docker-compose build -``` - -Setup the database and seed it with sample data: -```sh -$ docker-compose run web bundle exec rake db:reset -$ docker-compose run web bundle exec rake db:test:prepare -$ docker-compose run web bundle exec rake ofn:sample_data -``` - -Finally, run the app with all the required containers: - -```sh -$ docker-compose up -``` - -The default admin user is 'ofn@example.com' with 'ofn123' password. -Check the app in the browser at `http://localhost:3000`. - -You will then get the trace of the containers in the terminal. You can stop the containers using Ctrl-C in the terminal. - -You can find some useful tips and commands [here](https://github.com/openfoodfoundation/openfoodnetwork/wiki/Docker:-useful-tips-and-commands). - -### Troubleshooting -If you are using Windows and having issues related to the ruby-build not finding a definition for the ruby version, you may need to follow these commands [here](https://stackoverflow.com/questions/2517190/how-do-i-force-git-to-use-lf-instead-of-crlf-under-windows/33424884#33424884) to fix your local git config related to line breaks. diff --git a/Dockerfile b/Dockerfile index d45df4780a..d5fe7270ac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,8 +27,11 @@ RUN sh -c "echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' apt-get update && \ apt-get install -yqq --no-install-recommends postgresql-client-9.5 libpq-dev -# Install node -RUN apt-get install -y nodejs +# Install node & yarn +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ + echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ + apt-get update && \ + apt-get install -y nodejs yarn # Install Chrome RUN wget --quiet -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ @@ -43,5 +46,9 @@ RUN wget https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.z # Copy code and install app dependencies COPY . /usr/src/app/ + +# Install front-end dependencies +RUN yarn install + # Run bundler install in parallel with the amount of available CPUs RUN bundle install --jobs="$(nproc)" diff --git a/Gemfile b/Gemfile index 95db8c699a..38e15fb73e 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ ruby "2.3.7" git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" } gem 'i18n', '~> 0.6.11' -gem 'i18n-js', '~> 3.7.1' +gem 'i18n-js', '~> 3.8.0' gem 'rails', '~> 4.0.13' gem 'rails-i18n', '~> 4.0' gem 'rails_safe_tasks', '~> 1.0' diff --git a/Gemfile.lock b/Gemfile.lock index adda24eddd..83e30d89dc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -109,7 +109,7 @@ GEM activesupport (= 4.0.13) arel (~> 4.0.0) activerecord-deprecated_finders (1.0.4) - activerecord-import (1.0.6) + activerecord-import (1.0.7) activerecord (>= 3.2) activerecord-postgresql-adapter (0.0.1) pg @@ -204,7 +204,7 @@ GEM activerecord (>= 3.2.0, < 5.0) fog (~> 1.0) rails (>= 3.2.0, < 5.0) - ddtrace (0.41.0) + ddtrace (0.42.0) msgpack debugger-linecache (1.2.0) delayed_job (4.1.8) @@ -421,7 +421,7 @@ GEM mime-types (~> 3.0) multi_xml (>= 0.5.2) i18n (0.6.11) - i18n-js (3.7.1) + i18n-js (3.8.0) i18n (>= 0.6.6) immigrant (0.3.6) activerecord (>= 3.0) @@ -685,7 +685,7 @@ GEM nokogiri (~> 1.6) rubyzip (>= 1.3.0) selenium-webdriver (>= 3.0, < 4.0) - webmock (3.9.2) + webmock (3.9.3) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -754,7 +754,7 @@ DEPENDENCIES highline (= 1.6.18) httparty (~> 0.18) i18n (~> 0.6.11) - i18n-js (~> 3.7.1) + i18n-js (~> 3.8.0) immigrant jquery-migrate-rails jquery-rails (= 3.1.5) diff --git a/app/assets/images/flag.svg b/app/assets/images/flag.svg deleted file mode 100644 index bac244c981..0000000000 --- a/app/assets/images/flag.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/app/assets/images/ofn-logo.png b/app/assets/images/ofn-logo.png new file mode 100644 index 0000000000..6058b26a75 Binary files /dev/null and b/app/assets/images/ofn-logo.png differ diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 212518b010..564c099ccd 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -27,14 +27,14 @@ //= require lodash.underscore.js // spree -//= require spree +//= require admin/spree/spree //= require admin/spree/spree-select2 //= require modernizr //= require equalize //= require css_browser_selector_dev //= require responsive-tables //= require admin/spree_paypal_express -//= require admin/handlebar_extensions +//= require handlebars // OFN specific //= require_tree ../templates/admin diff --git a/app/assets/javascripts/admin/spree/base.js.erb b/app/assets/javascripts/admin/spree/base.js.erb index 1ddd4a8dd3..79a8dfb6f8 100644 --- a/app/assets/javascripts/admin/spree/base.js.erb +++ b/app/assets/javascripts/admin/spree/base.js.erb @@ -116,7 +116,8 @@ $.fn.radioControlsVisibilityOfElement = function(dependentElementSelector){ } $(document).ready(function() { - if (typeof Spree !== 'undefined') { + if (typeof Spree !== 'undefined' && + typeof Spree.translations !== 'undefined') { handle_date_picker_fields = function(){ $('.datepicker').datepicker({ dateFormat: Spree.translations.date_picker, diff --git a/app/assets/javascripts/admin/spree/image_settings.js.erb b/app/assets/javascripts/admin/spree/image_settings.js.erb deleted file mode 100644 index d8c0b2a635..0000000000 --- a/app/assets/javascripts/admin/spree/image_settings.js.erb +++ /dev/null @@ -1,59 +0,0 @@ -$(document).ready(function() { - - if ($('input#preferences_use_s3[type="checkbox"]:checked').length > 0) { - $('#s3_settings, #s3_headers').show(); - } - - // Toggle display of S3 settings based on value of use_s3 checkbox - $('input#preferences_use_s3[type="checkbox"]').click(function() { - $('#s3_settings, #s3_headers').toggle(); - }); - - $(document).on('click', '.destroy_style', function(e) { - e.preventDefault(); - $(this).parent().remove(); - }); - - $(document).on('click', '.destroy_new_attachment_styles', function(e) { - e.preventDefault(); - $(this).closest('.new_attachment_styles').remove(); - }); - - $(document).on('click', '.destroy_new_s3_headers', function(e) { - e.preventDefault(); - $(this).closest('.new_s3_headers').remove(); - }); - - // Handle adding new styles - var styles_hash_index = 1; - $(document).on('click', '.add_new_style', function(e) { - e.preventDefault(); - $('#new-styles').append(generate_html_for_hash("new_attachment_styles", styles_hash_index)); - }); - - // Handle adding new headers - var headers_hash_index = 1; - $(document).on('click', '.add_header', function(e) { - e.preventDefault(); - $('#headers_list').append(generate_html_for_hash("new_s3_headers", headers_hash_index)); - }); - - // Generates html for new paperclip styles form fields - generate_html_for_hash = function(hash_name, index) { - var html = '
'; - html += '
'; - html += ''; - html += '
'; - html += '
' - html += ''; - html += ''; - html += '
' - html += '   ' + Spree.translations.destroy + ''; - html += '
'; - - index += 1; - return html; - }; -}); diff --git a/app/assets/javascripts/admin/spree/spree.js.coffee b/app/assets/javascripts/admin/spree/spree.js.coffee new file mode 100644 index 0000000000..c905ff48e7 --- /dev/null +++ b/app/assets/javascripts/admin/spree/spree.js.coffee @@ -0,0 +1,13 @@ +#= require jsuri + +class window.Spree + # Helper function to take a URL and add query parameters to it + @url: (uri, query) -> + if uri.path == undefined + uri = new Uri(uri) + if query + $.each query, (key, value) -> + uri.addQueryParam(key, value) + if Spree.api_key + uri.addQueryParam('token', Spree.api_key) + return uri diff --git a/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee index fb8ac8d288..5a723fb8b1 100644 --- a/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee @@ -1,6 +1,6 @@ Darkswarm.controller "ProductNodeCtrl", ($scope, $modal, FilterSelectorsService) -> $scope.enterprise = $scope.product.supplier # For the modal, so it's consistent + $scope.productPropertySelectors = FilterSelectorsService.createSelectors() $scope.triggerProductModal = -> - $scope.productPropertySelectors = FilterSelectorsService.createSelectors() $modal.open(templateUrl: "product_modal.html", scope: $scope) diff --git a/app/assets/javascripts/darkswarm/controllers/shop_variant_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/shop_variant_controller.js.coffee new file mode 100644 index 0000000000..adc25f4ad7 --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/shop_variant_controller.js.coffee @@ -0,0 +1,39 @@ +Darkswarm.controller "ShopVariantCtrl", ($scope, $modal, Cart) -> + $scope.$watchGroup [ + 'variant.line_item.quantity', + 'variant.line_item.max_quantity' + ], (new_value, old_value) -> + return if old_value[0] == null && new_value[0] == null + Cart.adjust($scope.variant.line_item) + + $scope.add = (quantity) -> + item = $scope.variant.line_item + item.quantity += quantity + if $scope.variant.product.group_buy + if item.quantity < 1 || item.max_quantity < item.quantity + item.max_quantity = item.quantity + + $scope.addMax = (quantity) -> + item = $scope.variant.line_item + item.max_quantity += quantity + if item.max_quantity < item.quantity + item.quantity = item.max_quantity + + $scope.canAdd = (quantity) -> + wantedQuantity = $scope.variant.line_item.quantity + quantity + $scope.quantityValid(wantedQuantity) + + $scope.canAddMax = (quantity) -> + variant = $scope.variant + wantedQuantity = variant.line_item.max_quantity + quantity + $scope.quantityValid(wantedQuantity) && variant.line_item.quantity > 0 + + $scope.quantityValid = (quantity) -> + variant = $scope.variant + minimum = 0 + maximum = variant.on_demand && Infinity || variant.on_hand + quantity >= minimum && quantity <= maximum + + $scope.addBulk = (quantity) -> + $scope.add(quantity) + $modal.open(templateUrl: "bulk_buy_modal.html", scope: $scope, windowClass: "product-bulk-modal") diff --git a/app/assets/javascripts/darkswarm/directives/price_breakdown.js.coffee b/app/assets/javascripts/darkswarm/directives/price_breakdown.js.coffee index 8b0beb17a5..1b4609ff4d 100644 --- a/app/assets/javascripts/darkswarm/directives/price_breakdown.js.coffee +++ b/app/assets/javascripts/darkswarm/directives/price_breakdown.js.coffee @@ -2,7 +2,7 @@ Darkswarm.directive "priceBreakdown", ($tooltip)-> # We use the $tooltip service from Angular foundation to give us boilerplate # Subsequently we patch the scope, template and restrictions tooltip = $tooltip 'priceBreakdown', 'priceBreakdown', 'click' - tooltip.scope = + tooltip.scope = variant: "=" tooltip.templateUrl = "price_breakdown_button.html" tooltip.replace = true @@ -15,6 +15,3 @@ Darkswarm.directive 'priceBreakdownPopup', -> replace: true templateUrl: 'price_breakdown.html' scope: false - - link: (scope, elem, attrs) -> - scope.expanded = false unless scope.expanded? diff --git a/app/assets/javascripts/darkswarm/directives/price_percentage.js.coffee b/app/assets/javascripts/darkswarm/directives/price_percentage.js.coffee deleted file mode 100644 index 35140598c4..0000000000 --- a/app/assets/javascripts/darkswarm/directives/price_percentage.js.coffee +++ /dev/null @@ -1,10 +0,0 @@ -Darkswarm.directive "pricePercentage", -> - restrict: 'E' - replace: true - templateUrl: 'price_percentage.html' - scope: - percentage: '=' - - link: (scope, elem, attrs) -> - elem.find(".meter").css - width: "#{scope.percentage}%" diff --git a/app/assets/javascripts/darkswarm/directives/shop_variant.js.coffee b/app/assets/javascripts/darkswarm/directives/shop_variant.js.coffee index 84615e3125..1872b55dfd 100644 --- a/app/assets/javascripts/darkswarm/directives/shop_variant.js.coffee +++ b/app/assets/javascripts/darkswarm/directives/shop_variant.js.coffee @@ -4,10 +4,4 @@ Darkswarm.directive "shopVariant", -> templateUrl: 'shop_variant.html' scope: variant: '=' - controller: ($scope, Cart) -> - $scope.$watchGroup [ - 'variant.line_item.quantity', - 'variant.line_item.max_quantity' - ], (new_value, old_value) -> - return if old_value[0] == null && new_value[0] == null - Cart.adjust($scope.variant.line_item) + controller: 'ShopVariantCtrl' diff --git a/app/assets/javascripts/darkswarm/services/variants.js.coffee b/app/assets/javascripts/darkswarm/services/variants.js.coffee index a650db5cd7..f5ba1af757 100644 --- a/app/assets/javascripts/darkswarm/services/variants.js.coffee +++ b/app/assets/javascripts/darkswarm/services/variants.js.coffee @@ -10,7 +10,6 @@ Darkswarm.factory 'Variants', -> extend: (variant)-> variant.extended_name = @extendedVariantName(variant) - variant.base_price_percentage = Math.round(variant.price / variant.price_with_fees * 100) variant.line_item ||= @lineItemFor(variant) # line_item may have been initialised in Cart#constructor variant.line_item.total_price = variant.price_with_fees * variant.line_item.quantity variant diff --git a/app/assets/javascripts/templates/bulk_buy_modal.html.haml b/app/assets/javascripts/templates/bulk_buy_modal.html.haml new file mode 100644 index 0000000000..9625c1bd8f --- /dev/null +++ b/app/assets/javascripts/templates/bulk_buy_modal.html.haml @@ -0,0 +1,37 @@ +.row + .columns.small-12 + %h3{"ng-bind" => "::variant.extended_name"} + +.row.variant-bulk-buy-price-summary + .columns.small-6 + .variant-unit {{ ::variant.unit_to_display }} + .columns.small-6 + {{ variant.line_item.total_price | localizeCurrency }} + +.row + .columns.small-6 + .variant-bulk-buy-quantity-label + {{ "js.shopfront.bulk_buy_modal.min_quantity" | t }} + %div + %button.bulk-buy-add.variant-quantity{type: "button", ng: {click: "add(-1)", disabled: "!canAdd(-1)"}}> + -# U+FF0D Fullwidth Hyphen-Minus + - + %span.bulk-buy.variant-quantity> + {{ variant.line_item.quantity }} + %button.bulk-buy-add.variant-quantity{type: "button", ng: {click: "add(1)", disabled: "!canAdd(1)"}} + -# U+FF0B Fullwidth Plus Sign + + + .columns.small-6 + .variant-bulk-buy-quantity-label + {{ "js.shopfront.bulk_buy_modal.max_quantity" | t }} + %div + %button.bulk-buy-add.variant-quantity{type: "button", ng: {click: "addMax(-1)", disabled: "!canAddMax(-1)"}}> + -# U+FF0D Fullwidth Hyphen-Minus + - + %span.bulk-buy.variant-quantity> + {{ variant.line_item.max_quantity }} + %button.bulk-buy-add.variant-quantity{type: "button", ng: {click: "addMax(1)", disabled: "!canAddMax(1)"}} + -# U+FF0B Fullwidth Plus Sign + + + +%ng-include{src: "'partials/close.html'"} diff --git a/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml index 8ba787f20b..0d6563bc2a 100644 --- a/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml +++ b/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml @@ -1,14 +1,16 @@ .small-5.medium-3.large-3.columns.text-right{"ng-if" => "::!variant.product.group_buy"} - %input{type: :number, - integer: true, - value: nil, - min: 0, - placeholder: "0", - "ofn-disable-scroll" => true, - "ng-debounce" => "500", - onwheel: "this.blur()", - "ng-model" => "variant.line_item.quantity", - "ofn-on-hand" => "{{variant.on_demand && 9999 || variant.on_hand }}", - "ng-disabled" => "!variant.on_demand && variant.on_hand == 0", - name: "variants[{{::variant.id}}]", id: "variants_{{::variant.id}}"} + %button.add-variant{type: "button", ng: {if: "!variant.line_item.quantity", click: "add(1)", disabled: "!canAdd(1)"}} + {{ "js.shopfront.variant.add_to_cart" | t }} + %button.variant-quantity{type: "button", ng: {if: "variant.line_item.quantity", click: "add(-1)"}}> + -# U+FF0D Fullwidth Hyphen-Minus + - + %button.variant-quantity{type: "button", ng: {if: "variant.line_item.quantity", click: "add(1)", disabled: "!canAdd(1)"}} + -# U+FF0B Fullwidth Plus Sign + + + %br + .variant-quantity-display{ng: {class: "{visible: variant.line_item.quantity}"}} + {{ "js.shopfront.variant.quantity_in_cart" | t:{quantity: variant.line_item.quantity || 0} }} + %input{type: :hidden, + name: "variants[{{::variant.id}}]", + ng: {model: "variant.line_item.quantity"}} diff --git a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml index c0cc46ac13..18f46e5490 100644 --- a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml +++ b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml @@ -1,28 +1,17 @@ .small-5.medium-3.large-3.columns.text-right{"ng-if" => "::variant.product.group_buy"} - %span.bulk-input-container - %span.bulk-input - %input.bulk.first{type: :number, - value: nil, - integer: true, - min: 0, - "ng-model" => "variant.line_item.quantity", - placeholder: "{{::'shop_variant_quantity_min' | t}}", - "ofn-disable-scroll" => true, - "ng-debounce" => "500", - onwheel: "this.blur()", - "ofn-on-hand" => "{{variant.on_demand && 9999 || variant.on_hand }}", - name: "variants[{{::variant.id}}]", id: "variants_{{::variant.id}}"} - %span.bulk-input - %input.bulk.second{type: :number, - "ng-disabled" => "!variant.line_item.quantity", - integer: true, - min: 0, - "ng-model" => "variant.line_item.max_quantity", - placeholder: "{{::'shop_variant_quantity_max' | t}}", - "ofn-disable-scroll" => true, - "ng-debounce" => "500", - onwheel: "this.blur()", - min: "{{variant.line_item.quantity}}", - name: "variant_attributes[{{::variant.id}}][max_quantity]", - id: "variants_{{::variant.id}}_max"} + %button.add-variant{type: "button", ng: {if: "!variant.line_item.quantity", click: "addBulk(1)", disabled: "!canAdd(1)"}} + {{ "js.shopfront.variant.add_to_cart" | t }} + %button.bulk-buy.variant-quantity{type: "button", ng: {if: "variant.line_item.quantity", click: "addBulk(0)"}}> + {{ variant.line_item.quantity }} + %button.bulk-buy.variant-quantity{type: "button", ng: {if: "variant.line_item.quantity", click: "addBulk(0)"}} + {{ variant.line_item.max_quantity || "-" }} + %br + .variant-quantity-display{ng: {class: "{visible: variant.line_item.quantity}"}} + {{ "js.shopfront.variant.in_cart" | t }} + %input{type: :hidden, + name: "variants[{{::variant.id}}]", + ng: {model: "variant.line_item.quantity"}} + %input{type: :hidden, + name: "variants[{{::variant.id}}]", + ng: {model: "variant.line_item.max_quantity"}} diff --git a/app/assets/javascripts/templates/price_breakdown.html.haml b/app/assets/javascripts/templates/price_breakdown.html.haml index 48e0a8d5e0..bd4c3bf88f 100644 --- a/app/assets/javascripts/templates/price_breakdown.html.haml +++ b/app/assets/javascripts/templates/price_breakdown.html.haml @@ -1,37 +1,28 @@ -.joyride-tip-guide.price_breakdown{"ng-class" => "{ in: tt_isOpen, fade: tt_animation }"} - %span.joyride-nub.right +.joyride-tip-guide.price_breakdown{ng: {class: "{ in: tt_isOpen, fade: tt_animation }", show: "tt_isOpen"}} + %span.joyride-nub.top + .background{ng: {click: "tt_isOpen = false"}} .joyride-content-wrapper - .collapsed{"ng-show" => "!expanded"} - %price-percentage{percentage: 'variant.base_price_percentage'} - %a{"ng-click" => "expanded = !expanded"} - %span{"ng-bind" => "::'price_breakdown' | t"} - %i.ofn-i_005-caret-down - - .expanded{"ng-show" => "expanded"} - %ul - %li.cost - .right {{ ::variant.price | localizeCurrency }} - %span{"ng-bind" => "::'item_cost' | t"} - %li.admin-fee{"ng-if" => "::variant.fees.admin"} - .right {{ ::variant.fees.admin | localizeCurrency }} - %span{"ng-bind" => "::'admin_fee' | t"} - %li.sales-fee{"ng-if" => "::variant.fees.sales"} - .right {{ ::variant.fees.sales | localizeCurrency }} - %span{"ng-bind" => "::'sales_fee' | t"} - %li.packing-fee{"ng-if" => "::variant.fees.packing"} - .right {{ ::variant.fees.packing | localizeCurrency }} - %span{"ng-bind" => "::'packing_fee' | t"} - %li.transport-fee{"ng-if" => "::variant.fees.transport"} - .right {{ ::variant.fees.transport | localizeCurrency }} - %span{"ng-bind" => "::'transport_fee' | t"} - %li.fundraising-fee{"ng-if" => "::variant.fees.fundraising"} - .right {{ ::variant.fees.fundraising | localizeCurrency }} - %span{"ng-bind" => "::'fundraising_fee' | t"} - %li.total - %strong - .right = {{ ::variant.price_with_fees | localizeCurrency }} -   - - %a{"ng-click" => "expanded = !expanded"} - %span{"ng-bind" => "::'price_graph' | t"} - %i.ofn-i_006-caret-up + %h6 {{ "js.shopfront.price_breakdown" | t }} + %ul + %li + .right {{ ::variant.price | localizeCurrency }} + %span{"ng-bind" => "::'item_cost' | t"} + %li{"ng-if" => "::variant.fees.admin"} + .right {{ ::variant.fees.admin | localizeCurrency }} + %span{"ng-bind" => "::'admin_fee' | t"} + %li{"ng-if" => "::variant.fees.sales"} + .right {{ ::variant.fees.sales | localizeCurrency }} + %span{"ng-bind" => "::'sales_fee' | t"} + %li{"ng-if" => "::variant.fees.packing"} + .right {{ ::variant.fees.packing | localizeCurrency }} + %span{"ng-bind" => "::'packing_fee' | t"} + %li{"ng-if" => "::variant.fees.transport"} + .right {{ ::variant.fees.transport | localizeCurrency }} + %span{"ng-bind" => "::'transport_fee' | t"} + %li{"ng-if" => "::variant.fees.fundraising"} + .right {{ ::variant.fees.fundraising | localizeCurrency }} + %span{"ng-bind" => "::'fundraising_fee' | t"} + %li + %strong + .right = {{ ::variant.price_with_fees | localizeCurrency }} +   diff --git a/app/assets/javascripts/templates/price_percentage.html.haml b/app/assets/javascripts/templates/price_percentage.html.haml deleted file mode 100644 index f524103643..0000000000 --- a/app/assets/javascripts/templates/price_percentage.html.haml +++ /dev/null @@ -1,4 +0,0 @@ -.progress - .right {{::'fees' | t}} - .meter - {{::'item_cost' | t}} diff --git a/app/assets/javascripts/templates/shop_variant.html.haml b/app/assets/javascripts/templates/shop_variant.html.haml index 71ca3c33ec..1435f33399 100644 --- a/app/assets/javascripts/templates/shop_variant.html.haml +++ b/app/assets/javascripts/templates/shop_variant.html.haml @@ -1,31 +1,16 @@ .variants.row - .small-12.medium-4.large-4.columns.variant-name - .table-cell - .inline {{ ::variant.name_to_display }} - .bulk-buy.inline{"ng-if" => "::variant.product.group_buy"} - %i.ofn-i_056-bulk>< - %em>< - \ {{::'bulk' | t}} + .small-4.medium-4.large-6.columns.variant-name + .inline{"ng-if" => "::variant.display_name"} {{ ::variant.display_name }} + .variant-unit {{ ::variant.unit_to_display }} + .small-3.medium-3.large-2.columns.variant-price + %price-breakdown{"price-breakdown" => "_", variant: "variant", + "price-breakdown-append-to-body" => "true", + "price-breakdown-placement" => "bottom", + "price-breakdown-animation" => true} + {{ variant.price_with_fees | localizeCurrency }} + .medium-2.large-1.columns.total-price + %span{"ng-class" => "{filled: variant.line_item.total_price}"} + {{ variant.line_item.total_price | localizeCurrency }} %ng-include{src: "'partials/shop_variant_no_group_buy.html'"} %ng-include{src: "'partials/shop_variant_with_group_buy.html'"} - - .small-3.medium-1.large-1.columns.variant-unit - .table-cell - %em {{ ::variant.unit_to_display }} - - .small-4.medium-2.large-2.columns.variant-price - .table-cell.price - %i.ofn-i_009-close - {{ variant.price_with_fees | localizeCurrency }} - - -# Now in a template in app/assets/javascripts/templates ! - %price-breakdown{"price-breakdown" => "_", variant: "variant", - "price-breakdown-append-to-body" => "true", - "price-breakdown-placement" => "left", - "price-breakdown-animation" => true} - - .small-12.medium-2.large-2.columns.total-price.text-right - .table-cell - %strong{"ng-class" => "{filled: variant.line_item.total_price}"} - {{ variant.line_item.total_price | localizeCurrency }} diff --git a/app/assets/stylesheets/admin/all.scss b/app/assets/stylesheets/admin/all.scss index 6d5992f8d4..65d555e561 100644 --- a/app/assets/stylesheets/admin/all.scss +++ b/app/assets/stylesheets/admin/all.scss @@ -35,7 +35,6 @@ @import 'plugins/font-awesome'; @import 'plugins/select2'; -@import 'sections/image_settings'; @import 'sections/orders'; @import 'sections/products'; diff --git a/app/assets/stylesheets/admin/hacks/ie.scss b/app/assets/stylesheets/admin/hacks/ie.scss index ba5cd61501..4eff79ed84 100644 --- a/app/assets/stylesheets/admin/hacks/ie.scss +++ b/app/assets/stylesheets/admin/hacks/ie.scss @@ -29,11 +29,6 @@ html.ie { z-index: 0; } } - - // Fix margin-top for destroy paperclip styles background - .destroy_new_attachment_styles { - margin-top: 17px !important; - } } // IE8 Hacks diff --git a/app/assets/stylesheets/admin/sections/image_settings.scss b/app/assets/stylesheets/admin/sections/image_settings.scss deleted file mode 100644 index dc7a29b1b2..0000000000 --- a/app/assets/stylesheets/admin/sections/image_settings.scss +++ /dev/null @@ -1,3 +0,0 @@ -.destroy_style, .destroy_header { - float: right; -} diff --git a/app/assets/stylesheets/admin/shared/layout.scss b/app/assets/stylesheets/admin/shared/layout.scss index 4288e88266..17af56a59b 100644 --- a/app/assets/stylesheets/admin/shared/layout.scss +++ b/app/assets/stylesheets/admin/shared/layout.scss @@ -71,12 +71,6 @@ display: none; } -// For block grids -.frameless { - margin-left: -10px; - margin-right: -10px; -} - // Header //--------------------------------------------------- #header { diff --git a/app/assets/stylesheets/darkswarm/_shop-inputs.scss b/app/assets/stylesheets/darkswarm/_shop-inputs.scss index 59a3bd0c67..7de6a9355a 100644 --- a/app/assets/stylesheets/darkswarm/_shop-inputs.scss +++ b/app/assets/stylesheets/darkswarm/_shop-inputs.scss @@ -6,93 +6,110 @@ .darkswarm { // #search @include placeholder(rgba(0, 0, 0, 0.4), #777); +} - // ordering - product { - input { - @include border-radius(0); +.reveal-modal.product-bulk-modal { + width: 26em; +} - @include csstrans; +// Components to add variants to cart and change quantities +// +// They are not nested so that they can be used in modals. +button.add-variant, button.variant-quantity { + height: 2.5rem; + border-radius: 0; + background-color: $orange-500; + color: white; + // Override foundation button styles: + font-size: 1rem; + margin: 0; + padding: 0; + transition: none; - margin: 0; - width: 10rem; - display: inline; - - @include box-shadow(none); - - border-color: #b3b3b3; - text-align: right; - - @include breakpoint(desktop) { - width: 8rem; - } - - @include breakpoint(tablet) { - width: 7rem; - } - - @include breakpoint(phablet) { - float: left !important; - font-size: 0.75rem; - padding-left: 0.25rem; - padding-right: 0.25rem; - } - - @include breakpoint(mobile) { - 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; - } + &:hover { + background-color: $orange-600; + } + &[disabled] { + &:hover, &:focus { + background-color: $orange-500; } + } + &:nth-of-type(1) { + border-bottom-left-radius: 0.25em; + border-top-left-radius: 0.25em; + } + &:nth-last-of-type(1) { + border-top-right-radius: 0.25em; + border-bottom-right-radius: 0.25em; + } +} - // BULK +button.add-variant { + min-width: 6rem; + padding: 0 1em; - input.bulk { - width: 5rem; - - @include breakpoint(desktop) { - width: 4rem; - } - - @include breakpoint(tablet) { - width: 3.5rem; - } - - @include breakpoint(mobile) { - width: 2.8rem; - } - } - - input.bulk.first { - border-right: 0; - } - - input.bulk.second { - border-left: 0; - } - - .bulk-input-container { - float: right; - - @include breakpoint(phablet) { - float: left !important; - } - - .bulk-input { - float: left; - } + &[disabled] { + &:hover, &:focus { + background-color: $orange-500; } } } + +button.variant-quantity { + width: 3rem; + + &:nth-of-type(1):not(.bulk-buy):not(.bulk-buy-add) { + border-right: .1em solid $orange-400; + } +} + +.variant-quantity-display { + display: inline-block; + font-size: 0.875em; + margin-top: 0.25em; + text-align: center; + width: 6rem; + visibility: hidden; + + &.visible { + visibility: visible; + } +} + +button.bulk-buy.variant-quantity { + background-color: transparent; + border: .1em solid $grey-200; + color: inherit; +} + +button.bulk-buy-add.variant-quantity { + width: 2.5rem; + + &[disabled] { + background-color: $grey-400; + + &:hover, &:focus { + background-color: $grey-400; + } + } +} + +span.bulk-buy.variant-quantity { + border: .1em solid $grey-200; + height: 2.5rem; + display: inline-block; + min-width: 3em; + padding: .5em; + text-align: center; + vertical-align: top; +} + +.variant-bulk-buy-price-summary { + color: $disabled-med; + margin-bottom: 1em; +} + +.variant-bulk-buy-quantity-label { + font-size: 0.875rem; + margin-bottom: .5em; +} diff --git a/app/assets/stylesheets/darkswarm/_shop-navigation.scss b/app/assets/stylesheets/darkswarm/_shop-navigation.scss index d3febdb28f..8ed5400ed9 100644 --- a/app/assets/stylesheets/darkswarm/_shop-navigation.scss +++ b/app/assets/stylesheets/darkswarm/_shop-navigation.scss @@ -178,7 +178,7 @@ shop ordercycle { } shop navigation ordercycle { - margin-top: 3em; + margin-top: 3.4em; padding: 1em; height: 7.6em; position: absolute; diff --git a/app/assets/stylesheets/darkswarm/_shop-popovers.scss b/app/assets/stylesheets/darkswarm/_shop-popovers.scss index dd5aa5a177..d91f870330 100644 --- a/app/assets/stylesheets/darkswarm/_shop-popovers.scss +++ b/app/assets/stylesheets/darkswarm/_shop-popovers.scss @@ -4,86 +4,69 @@ // Pop over // Foundation overrides .joyride-tip-guide.price_breakdown { + width: 16rem; + max-width: 65%; // 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; + margin-left: -7.4rem; + margin-top: 0.1rem; - @include box-shadow(0 1px 2px 0 rgba(0, 0, 0, 0.7)); + @include box-shadow(0 2px 8px 0 rgba(0, 0, 0, 0.35)); .joyride-content-wrapper { - padding: 1.125rem 1.25rem 1.5rem; - padding: 1rem; - margin: 1%; + padding: .625rem; 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; + background-color: $grey-800; 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; - } + h6 { + font-size: 0.937rem; + line-height: 1; + border-bottom: .1em solid $grey-700; + padding: 0 0 .625em 0; + margin-bottom: 2px; + } - li { - background-color: #13bf85; - padding: 0 0.25rem; - margin-bottom: 2px; - color: white; - } + .joyride-nub.top { + left: 7.4rem; + z-index: -1; + } - li.cost { - background-color: #0b8c61; - } + .background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + cursor: pointer; + } - li:last-child { - margin-bottom: 0.75rem; - } + ul, li { + list-style: none; + margin: 0; + font-size: 0.875rem; + } + + li { + line-height: 1; + border-bottom: .1em solid $grey-700; + padding: 0.625rem 0; + margin-bottom: 2px; + } + + li:last-child { + border-bottom: 0; + padding-bottom: 0; } } button.graph-button { // z-index: 9999999 - border: 1px solid transparent; padding: 0; margin: 0; - display: inline; + display: inline-block; background-color: rgba(255, 255, 255, 0.5); - padding: 4px; @include box-shadow(none); @@ -97,7 +80,6 @@ button.graph-button { } &:focus { - border: 1px solid #e0e0e0; background-color: rgba(255, 255, 255, 1); &:before { @@ -107,22 +89,15 @@ button.graph-button { &:hover, &:active { background-color: rgba(255, 255, 255, 1); - border: 1px solid transparent; &:before { - color: $clr-brick-bright; + color: $teal-500; } } - - @include breakpoint(tablet) { - // Hide for small - display: none; - } } button.graph-button.open { - border: 1px solid transparent; - background-color: $clr-brick-bright; + background-color: $teal-500; &:before { content: ""; diff --git a/app/assets/stylesheets/darkswarm/_shop-product-rows.scss b/app/assets/stylesheets/darkswarm/_shop-product-rows.scss index b0ea1faf39..feb9f54e5d 100644 --- a/app/assets/stylesheets/darkswarm/_shop-product-rows.scss +++ b/app/assets/stylesheets/darkswarm/_shop-product-rows.scss @@ -10,91 +10,57 @@ .columns { padding-top: 0em; padding-bottom: 0em; - display: table; line-height: 1.1; - - // outline: 1px solid red - - @include breakpoint(tablet) { - font-size: 0.875rem; - } - - @include breakpoint(phablet) { - font-size: 0.75rem; - } } + } - .table-cell { - display: table-cell; - vertical-align: middle; - height: 37px; + .shop-variants { + // product-thumb width + 1rem + padding-left: calc(22.222% + 1rem); + + @include breakpoint(phablet) { + padding-left: 0; + clear: left; } } // 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; - } - } + margin: 0 0 1em 0; &.out-of-stock { opacity: 0.2; } + .variant-name, + .total-price { + padding-top: .74em; + } + .variant-price { + padding-top: .65em; + } + // Variant name .variant-name { - padding-left: 7.9375rem; + padding-left: 0; + padding-right: 0; - @include breakpoint(tablet) { - padding-left: 4.9375rem; - } - } - - .variant-name { @include breakpoint(phablet) { - background: #333; - color: white; - padding-left: 0.9375rem; - font-weight: bold; - - .table-cell { - height: 27px; - } + padding-left: 0.5rem; } - } - // Variant unit - .variant-unit { - padding-left: 0rem; - padding-right: 0rem; - color: #888; - font-size: 0.875rem; - overflow: hidden; - - @include breakpoint(tablet) { - font-size: 0.75rem; + & > *:nth-child(n + 2) { + color: $grey-550; + font-size: 0.875rem; + font-style: italic; + line-height: normal; } } // Variant price .variant-price { - padding-left: 0.25rem; - padding-right: 0.25rem; - @include breakpoint(phablet) { - text-align: right; + padding-left: 1rem; } } @@ -108,24 +74,16 @@ } @include breakpoint(phablet) { - background: #777; - color: $disabled-med; - - .filled { - color: white; - } - - .table-cell { - height: 27px; - } + display: none; } } } // ROW SUMMARY - .row.summary { + .summary { margin-left: 0; margin-right: 0; + margin-bottom: 1.25em; background: #fff; .columns { @@ -140,39 +98,77 @@ } .summary-header { - padding-left: 7.9375rem; - - @include breakpoint(tablet) { - padding-left: 4.9375rem; - } + // product-thumb width + 1rem + padding-left: calc(22.222% + 1rem); + padding-right: 1rem; @include breakpoint(phablet) { - padding-left: 0.9375rem; + padding-left: calc(33.333% + 1rem); } - small { - font-size: 80%; + .product-producer { + color: $grey-550; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-style: italic; + + a { + color: $teal-500; + + &:hover, &:focus, &:active { + color: $teal-600; + text-decoration: underline; + } + } } h3 { - font-size: 1.5rem; - margin: 0; + font-size: 1.3rem; + margin-top: 0.75rem; + margin-bottom: 0.6rem; } h3 a { - color: #222; - - i { - @include csstrans; - - font-size: 0.6em; - } + color: $orange-500; &:hover, &:focus, &:active { - color: $clr-brick; + color: $orange-600; + text-decoration: underline; + } + } - i { - font-size: 0.8em; + .product-description { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: 0.75rem; + } + + .product-properties { + margin: .5em 0; + + li { + margin: 0 0.25rem 0.25rem 0; + + a { + padding: 0.1em 0.625em; + + cursor: auto; + + &.has-tip { + cursor: pointer; + font-weight: normal; + } + &:hover, &:focus { + border-color: #ccc; + } + } + + // Foundation doesn't show the nub on mobile. + // Repeating the style to show it here. + .nub { + border-color: transparent transparent #333333 transparent; } } } diff --git a/app/assets/stylesheets/darkswarm/_shop-product-thumb.scss b/app/assets/stylesheets/darkswarm/_shop-product-thumb.scss index c9f8794295..9b51d7f881 100644 --- a/app/assets/stylesheets/darkswarm/_shop-product-thumb.scss +++ b/app/assets/stylesheets/darkswarm/_shop-product-thumb.scss @@ -1,80 +1,32 @@ -@import "mixins"; -@import "branding"; -@import "animations"; - .darkswarm { products { product { .product-thumb { - @include csstrans; - - position: absolute; - top: 2px; - left: 0px; - width: 7rem; - height: 7rem; + // Desktop: the product summary is nine columns wide. Use two + // for the image. 100% / 9 * 2 = 22.222% <= 192px + width: calc(22.222%); 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)); - } - } - - @include breakpoint(tablet) { - top: 2px; - width: 4rem; - height: 4rem; - - &:hover, &:focus, &:active { - i { - left: 30%; - top: 30%; - font-size: 2rem; - } - } - } + // Mobile: the summary has full twelve columns and the image + // should take four of them. 100% / 12 * 4 = 33.333% <= 227px @include breakpoint(phablet) { - display: none; - width: 0rem; - height: 0rem; + width: calc(33.333%); + } + + // Make this an anchor for the bulk label. + position: relative; + + .product-thumb__bulk-label { + background-color: $grey-700; + color: white; + position: absolute; + right: 0; + top: .8em; + padding: .25em .5em; + } + + &:hover { + filter: brightness(96%); } } } diff --git a/app/assets/stylesheets/darkswarm/_shop-taxon-flag.scss b/app/assets/stylesheets/darkswarm/_shop-taxon-flag.scss deleted file mode 100644 index b6b237215e..0000000000 --- a/app/assets/stylesheets/darkswarm/_shop-taxon-flag.scss +++ /dev/null @@ -1,47 +0,0 @@ -@import "mixins"; - -.darkswarm { - products { - product { - .taxon-flag { - background: transparent image-url("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; - - @include breakpoint(mobile) { - background-size: 28px 32px; - min-height: 32px; - width: 28px; - } - - render-svg { - svg { - width: 24px; - height: 24px; - - path { - fill: #999; - } - } - } - - @include breakpoint(tablet) { - margin-top: -0.85rem; - } - - @include breakpoint(mobile) { - render-svg { - svg { - width: 18px; - height: 18px; - } - } - } - } - } - } -} diff --git a/app/assets/stylesheets/darkswarm/branding.scss b/app/assets/stylesheets/darkswarm/branding.scss index de705bbfa3..b29e81acf9 100644 --- a/app/assets/stylesheets/darkswarm/branding.scss +++ b/app/assets/stylesheets/darkswarm/branding.scss @@ -11,7 +11,6 @@ $ofn-grey: #808184; $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; @@ -48,6 +47,7 @@ $grey-200: #ddd; $grey-300: #ccc; $grey-400: #bbb; $grey-500: #999; +$grey-550: #888; $grey-600: #777; $grey-650: #666; $grey-700: #555; @@ -56,6 +56,7 @@ $grey-800: #333; $teal-300: #80d3df; $teal-400: #4cb5c5; $teal-500: #0096ad; +$teal-600: #007a9a; $orange-400: #ff9466; $orange-450: #f4704c; diff --git a/app/assets/stylesheets/darkswarm/embedded_shopfront.scss b/app/assets/stylesheets/darkswarm/embedded_shopfront.scss index 210825a008..2db5181cdd 100644 --- a/app/assets/stylesheets/darkswarm/embedded_shopfront.scss +++ b/app/assets/stylesheets/darkswarm/embedded_shopfront.scss @@ -59,13 +59,6 @@ body.embedded { line-height: $large-menu-height; height: $large-menu-height; vertical-align: top; - - &.cart { - div.joyride-tip-guide { // Cart Dropdown - top: 75px; - overflow: visible; - } - } } } diff --git a/app/assets/stylesheets/darkswarm/mixins.scss b/app/assets/stylesheets/darkswarm/mixins.scss index 43d291e14e..90febd6009 100644 --- a/app/assets/stylesheets/darkswarm/mixins.scss +++ b/app/assets/stylesheets/darkswarm/mixins.scss @@ -142,57 +142,6 @@ // 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; @@ -202,12 +151,6 @@ 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; diff --git a/app/assets/stylesheets/darkswarm/shop.scss b/app/assets/stylesheets/darkswarm/shop.scss index 6f411db6e9..8312f6c360 100644 --- a/app/assets/stylesheets/darkswarm/shop.scss +++ b/app/assets/stylesheets/darkswarm/shop.scss @@ -8,10 +8,30 @@ @import "shop-inputs"; @import "shop-navigation"; @import "shop-product-rows"; -@import "shop-taxon-flag"; @import "shop-popovers"; .darkswarm { + .product-listing { + @include breakpoint(desktop) { + padding-right: 0; + padding-left: 0; + } + + .row.full { + width: 100%; + margin: 0; + + .columns.full { + padding-right: 0; + padding-left: 0; + } + } + } + + .filter-header { + margin-top: 1.1em; + } + products { display: block; @@ -51,32 +71,18 @@ product { @include csstrans; - border-bottom: 1px solid #e5e5e5; - border-top: 1px solid #e5e5e5; + // Avoid margin collapsing which breaks the flush alignment of the first + // image. + overflow: auto; + + &:nth-child(n + 2) { + border-top: 1px solid $grey-100; + } 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; - - @include breakpoint(tablet) { - font-size: 0.75rem; - } - } - - .bulk-buy, .bulk-buy i { - color: #888; - } - .inline { display: inline; } @@ -85,27 +91,6 @@ width: 100px; margin-bottom: 20px; } - - // ICONS - i { - font-size: 0.75em; - padding-right: 0.9375rem; - - @include breakpoint(phablet) { - 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; - } } } diff --git a/app/assets/stylesheets/darkswarm/shop_search.scss b/app/assets/stylesheets/darkswarm/shop_search.scss index 4b141cd021..28e4269b4c 100644 --- a/app/assets/stylesheets/darkswarm/shop_search.scss +++ b/app/assets/stylesheets/darkswarm/shop_search.scss @@ -6,7 +6,6 @@ background-color: $grey-100; height: 5em; padding: 1em 0; - margin-bottom: 1em; position: relative; z-index: 5; @@ -50,7 +49,7 @@ button { background-color: $grey-600; - margin-left: 1em; + margin: 0 0 0 1em; height: 3em; width: 7em; padding: 0; diff --git a/app/assets/stylesheets/darkswarm/ui.scss b/app/assets/stylesheets/darkswarm/ui.scss index d6b0988ad6..a9a43b0b5f 100644 --- a/app/assets/stylesheets/darkswarm/ui.scss +++ b/app/assets/stylesheets/darkswarm/ui.scss @@ -159,11 +159,6 @@ a.button.large { display: flex; } -.no-gutter { - padding-right: 0; - padding-left: 0; -} - .disable-scroll { overflow: hidden; } diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index 4277dc2ff4..f1b4887f6a 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -73,7 +73,7 @@ module Admin if @enterprise.update(attributes) flash[:success] = I18n.t(:enterprise_register_success_notice, enterprise: @enterprise.name) - redirect_to admin_dashboard_path + redirect_to spree.admin_dashboard_path else flash[:error] = I18n.t(:enterprise_register_error, enterprise: @enterprise.name) render :welcome, layout: "spree/layouts/bare_admin" diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index c47b112359..b25dd490f2 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'open_food_network/address_finder' +require 'spree/core/gateway_error' class CheckoutController < Spree::StoreController layout 'darkswarm' @@ -47,8 +48,6 @@ class CheckoutController < Spree::StoreController params_adapter = Checkout::FormDataAdapter.new(permitted_params, @order, spree_current_user) return action_failed unless @order.update(params_adapter.params[:order]) - fire_event('spree.checkout.update') - checkout_workflow(params_adapter.shipping_method_id) rescue Spree::Core::GatewayError => e rescue_from_spree_gateway_error(e) @@ -150,7 +149,7 @@ class CheckoutController < Spree::StoreController def handle_redirect_from_stripe if OrderWorkflow.new(@order).next && order_complete? checkout_succeeded - redirect_to(order_path(@order)) && return + redirect_to(spree.order_path(@order)) && return else checkout_failed end @@ -210,10 +209,10 @@ class CheckoutController < Spree::StoreController def update_succeeded_response respond_to do |format| format.html do - respond_with(@order, location: order_path(@order)) + respond_with(@order, location: spree.order_path(@order)) end format.json do - render json: { path: order_path(@order) }, status: :ok + render json: { path: spree.order_path(@order) }, status: :ok end end end diff --git a/app/controllers/spree/admin/general_settings_controller.rb b/app/controllers/spree/admin/general_settings_controller.rb index dcb360bcc9..73fbe619d3 100644 --- a/app/controllers/spree/admin/general_settings_controller.rb +++ b/app/controllers/spree/admin/general_settings_controller.rb @@ -17,7 +17,7 @@ module Spree end flash[:success] = Spree.t(:successfully_updated, resource: Spree.t(:general_settings)) - redirect_to edit_admin_general_settings_path + redirect_to spree.edit_admin_general_settings_path end end end diff --git a/app/controllers/spree/admin/image_settings_controller.rb b/app/controllers/spree/admin/image_settings_controller.rb deleted file mode 100644 index 476dd401a8..0000000000 --- a/app/controllers/spree/admin/image_settings_controller.rb +++ /dev/null @@ -1,79 +0,0 @@ -module Spree - module Admin - class ImageSettingsController < Spree::Admin::BaseController - def edit - @styles = ActiveSupport::JSON.decode(Spree::Config[:attachment_styles]) - @headers = ActiveSupport::JSON.decode(Spree::Config[:s3_headers]) - end - - def update - update_styles(params) - update_headers(params) if Spree::Config[:use_s3] - - Spree::Config.set(params[:preferences]) - update_paperclip_settings - - respond_to do |format| - format.html { - flash[:success] = Spree.t(:image_settings_updated) - redirect_to spree.edit_admin_image_settings_path - } - end - end - - private - - def update_styles(params) - if params[:new_attachment_styles].present? - params[:new_attachment_styles].each do |_index, style| - params[:attachment_styles][style[:name]] = style[:value] if style[:value].present? - end - end - - styles = params[:attachment_styles] - - Spree::Config[:attachment_styles] = ActiveSupport::JSON.encode(styles) unless styles.nil? - end - - def update_headers(params) - if params[:new_s3_headers].present? - params[:new_s3_headers].each do |_index, header| - params[:s3_headers][header[:name]] = header[:value] if header[:value].present? - end - end - - headers = params[:s3_headers] - - Spree::Config[:s3_headers] = ActiveSupport::JSON.encode(headers) unless headers.nil? - end - - def update_paperclip_settings - if Spree::Config[:use_s3] - s3_creds = { access_key_id: Spree::Config[:s3_access_key], - secret_access_key: Spree::Config[:s3_secret], - bucket: Spree::Config[:s3_bucket] } - Spree::Image.attachment_definitions[:attachment][:storage] = :s3 - Spree::Image.attachment_definitions[:attachment][:s3_credentials] = s3_creds - Spree::Image.attachment_definitions[:attachment][:s3_headers] = - ActiveSupport::JSON.decode(Spree::Config[:s3_headers]) - Spree::Image.attachment_definitions[:attachment][:bucket] = Spree::Config[:s3_bucket] - else - Spree::Image.attachment_definitions[:attachment].delete :storage - end - - Spree::Image.attachment_definitions[:attachment][:styles] = - ActiveSupport::JSON.decode(Spree::Config[:attachment_styles]).symbolize_keys! - Spree::Image.attachment_definitions[:attachment][:path] = Spree::Config[:attachment_path] - Spree::Image.attachment_definitions[:attachment][:default_url] = - Spree::Config[:attachment_default_url] - Spree::Image.attachment_definitions[:attachment][:default_style] = - Spree::Config[:attachment_default_style] - - # Spree stores attachent definitions in JSON. This converts the style name and format to - # strings. However, when paperclip encounters these, it doesn't recognise the format. - # Here we solve that problem by converting format and style name to symbols. - Spree::Image.reformat_styles - end - end - end -end diff --git a/app/controllers/spree/admin/images_controller.rb b/app/controllers/spree/admin/images_controller.rb index c73e4e6826..b16e643508 100644 --- a/app/controllers/spree/admin/images_controller.rb +++ b/app/controllers/spree/admin/images_controller.rb @@ -61,7 +61,7 @@ module Spree private def location_after_save - admin_product_images_url(@product) + spree.admin_product_images_url(@product) end def load_data diff --git a/app/controllers/spree/admin/mail_methods_controller.rb b/app/controllers/spree/admin/mail_methods_controller.rb index 70d573f319..d3618488ed 100644 --- a/app/controllers/spree/admin/mail_methods_controller.rb +++ b/app/controllers/spree/admin/mail_methods_controller.rb @@ -23,7 +23,7 @@ module Spree rescue StandardError => e flash[:error] = Spree.t('admin.mail_methods.testmail.error') % { e: e } ensure - redirect_to edit_admin_mail_methods_url + redirect_to spree.edit_admin_mail_methods_url end private diff --git a/app/controllers/spree/admin/orders/customer_details_controller.rb b/app/controllers/spree/admin/orders/customer_details_controller.rb index 8ad0c7ea28..56502d54a0 100644 --- a/app/controllers/spree/admin/orders/customer_details_controller.rb +++ b/app/controllers/spree/admin/orders/customer_details_controller.rb @@ -27,7 +27,7 @@ module Spree @order.shipments.map(&:refresh_rates) flash[:success] = Spree.t('customer_details_updated') - redirect_to admin_order_customer_path(@order) + redirect_to spree.admin_order_customer_path(@order) else render action: :edit end diff --git a/app/controllers/spree/admin/orders_controller.rb b/app/controllers/spree/admin/orders_controller.rb index 0e3b28d630..56dc8b0ed7 100644 --- a/app/controllers/spree/admin/orders_controller.rb +++ b/app/controllers/spree/admin/orders_controller.rb @@ -29,7 +29,7 @@ module Spree @order = Order.create @order.created_by = spree_current_user @order.save - redirect_to edit_admin_order_url(@order) + redirect_to spree.edit_admin_order_url(@order) end def edit @@ -48,16 +48,16 @@ module Spree if @order.line_items.empty? @order.errors.add(:line_items, Spree.t('errors.messages.blank')) end - return redirect_to(edit_admin_order_path(@order), + return redirect_to(spree.edit_admin_order_path(@order), flash: { error: @order.errors.full_messages.join(', ') }) end @order.update! if @order.complete? - redirect_to edit_admin_order_path(@order) + redirect_to spree.edit_admin_order_path(@order) else # Jump to next step if order is not complete - redirect_to admin_order_customer_path(@order) + redirect_to spree.admin_order_customer_path(@order) end end @@ -91,7 +91,9 @@ module Spree Spree::OrderMailer.invoice_email(@order.id, pdf).deliver flash[:success] = t('admin.orders.invoice_email_sent') - respond_with(@order) { |format| format.html { redirect_to edit_admin_order_path(@order) } } + respond_with(@order) { |format| + format.html { redirect_to spree.edit_admin_order_path(@order) } + } end def print @@ -131,7 +133,9 @@ module Spree flash[:error] = t(:must_have_valid_business_number, enterprise_name: @order.distributor.name) - respond_with(@order) { |format| format.html { redirect_to edit_admin_order_path(@order) } } + respond_with(@order) { |format| + format.html { redirect_to spree.edit_admin_order_path(@order) } + } end def load_distribution_choices diff --git a/app/controllers/spree/admin/payment_methods_controller.rb b/app/controllers/spree/admin/payment_methods_controller.rb index 950b97340b..72408fef26 100644 --- a/app/controllers/spree/admin/payment_methods_controller.rb +++ b/app/controllers/spree/admin/payment_methods_controller.rb @@ -22,7 +22,7 @@ module Spree if @payment_method.save invoke_callbacks(:create, :after) flash[:success] = Spree.t(:successfully_created, resource: Spree.t(:payment_method)) - redirect_to edit_admin_payment_method_path(@payment_method) + redirect_to spree.edit_admin_payment_method_path(@payment_method) else invoke_callbacks(:create, :fails) respond_with(@payment_method) @@ -43,7 +43,7 @@ module Spree if @payment_method.update(params_for_update) invoke_callbacks(:update, :after) flash[:success] = Spree.t(:successfully_updated, resource: Spree.t(:payment_method)) - redirect_to edit_admin_payment_method_path(@payment_method) + redirect_to spree.edit_admin_payment_method_path(@payment_method) else invoke_callbacks(:update, :fails) respond_with(@payment_method) @@ -122,7 +122,7 @@ module Spree return if valid_payment_methods.include?(params[:payment_method][:type]) flash[:error] = Spree.t(:invalid_payment_provider) - redirect_to new_admin_payment_method_path + redirect_to spree.new_admin_payment_method_path end def load_hubs diff --git a/app/controllers/spree/admin/payments_controller.rb b/app/controllers/spree/admin/payments_controller.rb index bfdb4fd2ff..0b5732caa1 100644 --- a/app/controllers/spree/admin/payments_controller.rb +++ b/app/controllers/spree/admin/payments_controller.rb @@ -12,7 +12,7 @@ module Spree def index @payments = @order.payments - redirect_to new_admin_order_payment_url(@order) if @payments.empty? + redirect_to spree.new_admin_order_payment_url(@order) if @payments.empty? end def new @@ -25,7 +25,7 @@ module Spree begin unless @payment.save - redirect_to admin_order_payments_path(@order) + redirect_to spree.admin_order_payments_path(@order) return end @@ -35,16 +35,16 @@ module Spree @payment.process! flash[:success] = flash_message_for(@payment, :successfully_created) - redirect_to admin_order_payments_path(@order) + redirect_to spree.admin_order_payments_path(@order) else OrderWorkflow.new(@order).complete! flash[:success] = Spree.t(:new_order_completed) - redirect_to edit_admin_order_url(@order) + redirect_to spree.edit_admin_order_url(@order) end rescue Spree::Core::GatewayError => e flash[:error] = e.message.to_s - redirect_to new_admin_order_payment_path(@order) + redirect_to spree.new_admin_order_payment_path(@order) end end @@ -118,7 +118,7 @@ module Spree return if @order.payment? || @order.complete? flash[:notice] = Spree.t(:fill_in_customer_info) - redirect_to edit_admin_order_customer_url(@order) + redirect_to spree.edit_admin_order_customer_url(@order) end def load_order diff --git a/app/controllers/spree/admin/products_controller.rb b/app/controllers/spree/admin/products_controller.rb index 0556c25a1a..42d4006680 100644 --- a/app/controllers/spree/admin/products_controller.rb +++ b/app/controllers/spree/admin/products_controller.rb @@ -29,9 +29,9 @@ module Spree if @object.save flash[:success] = flash_message_for(@object, :successfully_created) if params[:button] == "add_another" - redirect_to new_admin_product_path + redirect_to spree.new_admin_product_path else - redirect_to admin_products_path + redirect_to spree.admin_products_path end else render :new @@ -70,7 +70,7 @@ module Spree flash[:success] = flash_message_for(@object, :successfully_updated) end - redirect_to edit_admin_product_url(@object, @url_filters) + redirect_to spree.edit_admin_product_url(@object, @url_filters) end end @@ -97,7 +97,7 @@ module Spree Spree.t('notice_messages.product_not_cloned') end - redirect_to edit_admin_product_url(@new) + redirect_to spree.edit_admin_product_url(@new) end def group_buy_options diff --git a/app/controllers/spree/admin/shipping_methods_controller.rb b/app/controllers/spree/admin/shipping_methods_controller.rb index b91f8432a9..14f7654177 100644 --- a/app/controllers/spree/admin/shipping_methods_controller.rb +++ b/app/controllers/spree/admin/shipping_methods_controller.rb @@ -74,7 +74,7 @@ module Spree end def location_after_save - edit_admin_shipping_method_path(@shipping_method) + spree.edit_admin_shipping_method_path(@shipping_method) end def load_data diff --git a/app/controllers/spree/admin/states_controller.rb b/app/controllers/spree/admin/states_controller.rb index 09d7244439..56f077b0ea 100644 --- a/app/controllers/spree/admin/states_controller.rb +++ b/app/controllers/spree/admin/states_controller.rb @@ -14,7 +14,7 @@ module Spree protected def location_after_save - admin_country_states_url(@country) + spree.admin_country_states_url(@country) end def collection diff --git a/app/controllers/spree/admin/tax_settings_controller.rb b/app/controllers/spree/admin/tax_settings_controller.rb index a66ad219ff..8be30677e0 100644 --- a/app/controllers/spree/admin/tax_settings_controller.rb +++ b/app/controllers/spree/admin/tax_settings_controller.rb @@ -6,7 +6,7 @@ module Spree respond_to do |format| format.html { - redirect_to edit_admin_tax_settings_path + redirect_to spree.edit_admin_tax_settings_path } end end diff --git a/app/controllers/spree/admin/taxonomies_controller.rb b/app/controllers/spree/admin/taxonomies_controller.rb index fb735ec4fa..66497eda2a 100644 --- a/app/controllers/spree/admin/taxonomies_controller.rb +++ b/app/controllers/spree/admin/taxonomies_controller.rb @@ -11,9 +11,9 @@ module Spree def location_after_save if @taxonomy.created_at == @taxonomy.updated_at - edit_admin_taxonomy_url(@taxonomy) + spree.edit_admin_taxonomy_url(@taxonomy) else - admin_taxonomies_url + spree.admin_taxonomies_url end end diff --git a/app/controllers/spree/admin/taxons_controller.rb b/app/controllers/spree/admin/taxons_controller.rb index 0b171567d2..1b9b980da2 100644 --- a/app/controllers/spree/admin/taxons_controller.rb +++ b/app/controllers/spree/admin/taxons_controller.rb @@ -15,9 +15,9 @@ module Spree respond_with(@taxon) do |format| format.html do if redirect_to @taxonomy - edit_admin_taxonomy_url(@taxonomy) + spree.edit_admin_taxonomy_url(@taxonomy) else - admin_taxonomies_url + spree.admin_taxonomies_url end end end @@ -95,7 +95,7 @@ module Spree end respond_with(@taxon) do |format| - format.html { redirect_to edit_admin_taxonomy_url(@taxonomy) } + format.html { redirect_to spree.edit_admin_taxonomy_url(@taxonomy) } format.json { render json: @taxon.to_json } end end diff --git a/app/controllers/spree/admin/users_controller.rb b/app/controllers/spree/admin/users_controller.rb index e6ffeaa6cd..3241a889f1 100644 --- a/app/controllers/spree/admin/users_controller.rb +++ b/app/controllers/spree/admin/users_controller.rb @@ -60,14 +60,14 @@ module Spree if @user.generate_spree_api_key! flash[:success] = t('spree.api.key_generated') end - redirect_to edit_admin_user_path(@user) + redirect_to spree.edit_admin_user_path(@user) end def clear_api_key if @user.clear_spree_api_key! flash[:success] = t('spree.api.key_cleared') end - redirect_to edit_admin_user_path(@user) + redirect_to spree.edit_admin_user_path(@user) end protected diff --git a/app/controllers/spree/admin/variants_controller.rb b/app/controllers/spree/admin/variants_controller.rb index 302b3e62a5..bb1f8284ac 100644 --- a/app/controllers/spree/admin/variants_controller.rb +++ b/app/controllers/spree/admin/variants_controller.rb @@ -61,17 +61,21 @@ module Spree @url_filters = ::ProductFilters.new.extract(request.query_parameters) @variant = Spree::Variant.find(params[:id]) - flash[:success] = if VariantDeleter.new.delete(@variant) - Spree.t('notice_messages.variant_deleted') - else - Spree.t('notice_messages.variant_not_deleted') - end + flash[:success] = delete_variant - redirect_to admin_product_variants_url(params[:product_id], @url_filters) + redirect_to spree.admin_product_variants_url(params[:product_id], @url_filters) end protected + def delete_variant + if VariantDeleter.new.delete(@variant) + Spree.t('notice_messages.variant_deleted') + else + Spree.t('notice_messages.variant_not_deleted') + end + end + def create_before option_values = params[:new_variant] option_values.andand.each_value { |id| @object.option_values << OptionValue.find(id) } diff --git a/app/controllers/spree/base_controller.rb b/app/controllers/spree/base_controller.rb index 21a3d6fe5d..b2e4628bc3 100644 --- a/app/controllers/spree/base_controller.rb +++ b/app/controllers/spree/base_controller.rb @@ -3,8 +3,8 @@ require 'cancan' require 'spree/core/controller_helpers/auth' require 'spree/core/controller_helpers/respond_with' -require 'spree/core/controller_helpers/common' require 'spree/core/controller_helpers/ssl' +require 'spree/core/controller_helpers/common' module Spree class BaseController < ApplicationController diff --git a/app/controllers/spree/credit_cards_controller.rb b/app/controllers/spree/credit_cards_controller.rb index a1c2e60ea5..9b4c2d971d 100644 --- a/app/controllers/spree/credit_cards_controller.rb +++ b/app/controllers/spree/credit_cards_controller.rb @@ -48,10 +48,10 @@ module Spree else flash[:error] = I18n.t(:card_could_not_be_removed) end - redirect_to account_path(anchor: 'cards') + redirect_to spree.account_path(anchor: 'cards') rescue Stripe::CardError flash[:error] = I18n.t(:card_could_not_be_removed) - redirect_to account_path(anchor: 'cards') + redirect_to spree.account_path(anchor: 'cards') end private diff --git a/app/controllers/spree/orders_controller.rb b/app/controllers/spree/orders_controller.rb index 898ec93f99..9703c8def8 100644 --- a/app/controllers/spree/orders_controller.rb +++ b/app/controllers/spree/orders_controller.rb @@ -78,11 +78,7 @@ module Spree discard_empty_line_items with_open_adjustments { update_totals_and_taxes } - if @order == current_order - fire_event('spree.order.contents_changed') - else - @order.update_distribution_charge! - end + @order.update_distribution_charge! respond_with(@order) do |format| format.html do @@ -90,7 +86,7 @@ module Spree @order.next_transition.run_callbacks if @order.cart? redirect_to checkout_state_path(@order.checkout_steps.first) elsif @order.complete? - redirect_to order_path(@order) + redirect_to spree.order_path(@order) else redirect_to main_app.cart_path end @@ -157,7 +153,7 @@ module Spree else flash[:error] = I18n.t(:orders_could_not_cancel) end - redirect_to request.referer || order_path(@order) + redirect_to request.referer || spree.order_path(@order) end private @@ -221,7 +217,7 @@ module Spree if items.empty? flash[:error] = I18n.t(:orders_cannot_remove_the_final_item) - redirect_to order_path(order_to_update) + redirect_to spree.order_path(order_to_update) end end diff --git a/app/controllers/spree/user_passwords_controller.rb b/app/controllers/spree/user_passwords_controller.rb index 4e7fc6e824..6358d16f0b 100644 --- a/app/controllers/spree/user_passwords_controller.rb +++ b/app/controllers/spree/user_passwords_controller.rb @@ -7,7 +7,7 @@ require "spree/core/controller_helpers/ssl" module Spree class UserPasswordsController < Devise::PasswordsController - helper 'spree/base', 'spree/store' + helper 'spree/base' include Spree::Core::ControllerHelpers::Auth include Spree::Core::ControllerHelpers::Common diff --git a/app/controllers/spree/user_registrations_controller.rb b/app/controllers/spree/user_registrations_controller.rb index 1a2d740da7..64c3681d81 100644 --- a/app/controllers/spree/user_registrations_controller.rb +++ b/app/controllers/spree/user_registrations_controller.rb @@ -7,7 +7,7 @@ require "spree/core/controller_helpers/ssl" module Spree class UserRegistrationsController < Devise::RegistrationsController - helper 'spree/base', 'spree/store' + helper 'spree/base' include Spree::Core::ControllerHelpers::Auth include Spree::Core::ControllerHelpers::Common diff --git a/app/controllers/spree/user_sessions_controller.rb b/app/controllers/spree/user_sessions_controller.rb index c17eafb892..f090a281ca 100644 --- a/app/controllers/spree/user_sessions_controller.rb +++ b/app/controllers/spree/user_sessions_controller.rb @@ -7,7 +7,7 @@ require "spree/core/controller_helpers/ssl" module Spree class UserSessionsController < Devise::SessionsController - helper 'spree/base', 'spree/store' + helper 'spree/base' include Spree::Core::ControllerHelpers::Auth include Spree::Core::ControllerHelpers::Common diff --git a/app/helpers/admin/image_settings_helper.rb b/app/helpers/admin/image_settings_helper.rb deleted file mode 100644 index e337161298..0000000000 --- a/app/helpers/admin/image_settings_helper.rb +++ /dev/null @@ -1,21 +0,0 @@ -module Admin - module ImageSettingsHelper - def admin_image_settings_format_options - [['Unchanged', ''], ['PNG', 'png'], ['JPEG', 'jpg']] - end - - def admin_image_settings_geometry_from_style(style) - geometry, _format = admin_image_settings_split_style style - geometry - end - - def admin_image_settings_format_from_style(style) - _geometry, format = admin_image_settings_split_style style - format - end - - def admin_image_settings_split_style(style) - [style, nil].flatten[0..1] - end - end -end diff --git a/app/mailers/spree/user_mailer.rb b/app/mailers/spree/user_mailer.rb index a93aff05a5..a53db460a0 100644 --- a/app/mailers/spree/user_mailer.rb +++ b/app/mailers/spree/user_mailer.rb @@ -8,10 +8,12 @@ module Spree def reset_password_instructions(user, token, _opts = {}) @edit_password_reset_url = spree. edit_spree_user_password_url(reset_password_token: token) + subject = "#{Spree::Config[:site_name]} " \ + "#{I18n.t('spree.user_mailer.reset_password_instructions.subject')}" - mail(to: user.email, from: from_address, - subject: Spree::Config[:site_name] + ' ' + - I18n.t(:subject, scope: [:devise, :mailer, :reset_password_instructions])) + I18n.with_locale valid_locale(user) do + mail(to: user.email, from: from_address, subject: subject) + end end # This is a OFN specific email, not from Devise::Mailer diff --git a/app/models/enterprise_group.rb b/app/models/enterprise_group.rb index 0bf0319bde..5bb20d3821 100644 --- a/app/models/enterprise_group.rb +++ b/app/models/enterprise_group.rb @@ -1,5 +1,6 @@ require 'open_food_network/locking' require 'open_food_network/permalink_generator' +require 'spree/core/s3_support' class EnterpriseGroup < ActiveRecord::Base include PermalinkGenerator diff --git a/app/models/spree/app_configuration.rb b/app/models/spree/app_configuration.rb new file mode 100644 index 0000000000..d56ca5a6ac --- /dev/null +++ b/app/models/spree/app_configuration.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +# This is the primary location for defining spree preferences +# +# This file allows us to add global configuration variables, which +# we can allow to be modified in the UI by adding appropriate form +# elements to existing or new configuration pages. +# +# The expectation is that this is created once and stored in +# the spree environment +# +# setters: +# a.color = :blue +# a[:color] = :blue +# a.set :color = :blue +# a.preferred_color = :blue +# +# getters: +# a.color +# a[:color] +# a.get :color +# a.preferred_color +# +module Spree + class AppConfiguration < Preferences::Configuration + # Should state/state_name be required + preference :address_requires_state, :boolean, default: true + preference :admin_interface_logo, :string, default: 'ofn-logo.png' + preference :admin_products_per_page, :integer, default: 10 + # Should only be true if you don't need to track inventory + preference :allow_backorder_shipping, :boolean, default: false + preference :allow_checkout_on_gateway_error, :boolean, default: false + preference :allow_guest_checkout, :boolean, default: true + preference :allow_ssl_in_development_and_test, :boolean, default: false + preference :allow_ssl_in_production, :boolean, default: true + preference :allow_ssl_in_staging, :boolean, default: true + # Automatically capture the credit card (as opposed to just authorize and capture later) + preference :auto_capture, :boolean, default: false + # Replace with the name of a zone if you would like to limit the countries + preference :checkout_zone, :string, default: nil + preference :currency, :string, default: "USD" + preference :currency_decimal_mark, :string, default: "." + preference :currency_symbol_position, :string, default: "before" + preference :currency_thousands_separator, :string, default: "," + preference :display_currency, :boolean, default: false + preference :default_country_id, :integer + preference :default_meta_description, :string, default: 'OFN demo site' + preference :default_meta_keywords, :string, default: 'ofn, demo' + preference :default_seo_title, :string, default: '' + preference :hide_cents, :boolean, default: false + preference :layout, :string, default: 'darkswarm' + preference :logo, :string, default: 'ofn-logo.png' + + # Maximum nesting level in taxons menu + preference :max_level_in_taxons_menu, :integer, default: 1 + preference :orders_per_page, :integer, default: 15 + preference :prices_inc_tax, :boolean, default: false + preference :products_per_page, :integer, default: 12 + preference :redirect_https_to_http, :boolean, default: false + preference :require_master_price, :boolean, default: true + preference :shipment_inc_vat, :boolean, default: false + # Request instructions/info for shipping + preference :shipping_instructions, :boolean, default: false + # Displays variant full price or difference with product price. + preference :show_variant_full_price, :boolean, default: false + preference :show_products_without_price, :boolean, default: false + preference :show_raw_product_description, :boolean, default: false + preference :site_name, :string, default: 'OFN Demo Site' + preference :site_url, :string, default: 'demo.openfoodnetwork.org' + preference :tax_using_ship_address, :boolean, default: true + # Determines whether to track on_hand values for variants / products. + preference :track_inventory_levels, :boolean, default: true + + # Preferences related to image settings + preference :attachment_default_url, :string, + default: '/spree/products/:id/:style/:basename.:extension' + preference :attachment_path, :string, + default: ':rails_root/public/spree/products/:id/:style/:basename.:extension' + preference :attachment_url, :string, + default: '/spree/products/:id/:style/:basename.:extension' + preference :attachment_styles, :string, + default: "{\"mini\":\"48x48>\",\"small\":\"100x100>\",\"product\":\"240x240>\",\"large\":\"600x600>\"}" + preference :attachment_default_style, :string, default: 'product' + preference :s3_access_key, :string + preference :s3_bucket, :string + preference :s3_secret, :string + preference :s3_headers, :string, default: "{\"Cache-Control\":\"max-age=31557600\"}" + preference :use_s3, :boolean, default: false # Use S3 for images rather than the file system + preference :s3_protocol, :string + preference :s3_host_alias, :string + + # Default mail headers settings + preference :enable_mail_delivery, :boolean, default: false + preference :mails_from, :string, default: 'ofn@example.com' + preference :mail_bcc, :string, default: 'ofn@example.com' + preference :intercept_email, :string, default: nil + + # Default smtp settings + preference :override_actionmailer_config, :boolean, default: true + preference :mail_host, :string, default: 'localhost' + preference :mail_domain, :string, default: 'localhost' + preference :mail_port, :integer, default: 25 + preference :secure_connection_type, :string, + default: Core::MailSettings::SECURE_CONNECTION_TYPES[0] + preference :mail_auth_type, :string, default: Core::MailSettings::MAIL_AUTH[0] + preference :smtp_username, :string + preference :smtp_password, :string + + # Embedded Shopfronts + preference :enable_embedded_shopfronts, :boolean, default: false + preference :embedded_shopfronts_whitelist, :text, default: nil + + # Legal Preferences + preference :footer_tos_url, :string, default: "/Terms-of-service.pdf" + preference :enterprises_require_tos, :boolean, default: false + preference :privacy_policy_url, :string, default: nil + preference :cookies_consent_banner_toggle, :boolean, default: false + preference :cookies_policy_matomo_section, :boolean, default: false + + # Tax Preferences + preference :products_require_tax_category, :boolean, default: false + preference :shipping_tax_rate, :decimal, default: 0 + + # Monitoring + preference :last_job_queue_heartbeat_at, :string, default: nil + + # External services + preference :bugherd_api_key, :string, default: nil + preference :matomo_url, :string, default: nil + preference :matomo_site_id, :string, default: nil + preference :matomo_tag_manager_url, :string, default: nil + + # Invoices & Receipts + preference :enable_invoices?, :boolean, default: true + preference :invoice_style2?, :boolean, default: false + preference :enable_receipt_printing?, :boolean, default: false + + # Stripe Connect + preference :stripe_connect_enabled, :boolean, default: false + + # Number localization + preference :enable_localized_number?, :boolean, default: false + + # Enable cache + preference :enable_products_cache?, :boolean, + default: (Rails.env.production? || Rails.env.staging?) + end +end diff --git a/app/models/spree/app_configuration_decorator.rb b/app/models/spree/app_configuration_decorator.rb deleted file mode 100644 index a039ddbd45..0000000000 --- a/app/models/spree/app_configuration_decorator.rb +++ /dev/null @@ -1,44 +0,0 @@ -Spree::AppConfiguration.class_eval do - # This file decorates the existing preferences file defined by Spree. - # It allows us to add our own global configuration variables, which - # we can allow to be modified in the UI by adding appropriate form - # elements to existing or new configuration pages. - - # Embedded Shopfronts - preference :enable_embedded_shopfronts, :boolean, default: false - preference :embedded_shopfronts_whitelist, :text, default: nil - - # Legal Preferences - preference :footer_tos_url, :string, default: "/Terms-of-service.pdf" - preference :enterprises_require_tos, :boolean, default: false - preference :privacy_policy_url, :string, default: nil - preference :cookies_consent_banner_toggle, :boolean, default: false - preference :cookies_policy_matomo_section, :boolean, default: false - - # Tax Preferences - preference :products_require_tax_category, :boolean, default: false - preference :shipping_tax_rate, :decimal, default: 0 - - # Monitoring - preference :last_job_queue_heartbeat_at, :string, default: nil - - # External services - preference :bugherd_api_key, :string, default: nil - preference :matomo_url, :string, default: nil - preference :matomo_site_id, :string, default: nil - preference :matomo_tag_manager_url, :string, default: nil - - # Invoices & Receipts - preference :enable_invoices?, :boolean, default: true - preference :invoice_style2?, :boolean, default: false - preference :enable_receipt_printing?, :boolean, default: false - - # Stripe Connect - preference :stripe_connect_enabled, :boolean, default: false - - # Number localization - preference :enable_localized_number?, :boolean, default: false - - # Enable cache - preference :enable_products_cache?, :boolean, default: (Rails.env.production? || Rails.env.staging?) -end diff --git a/app/models/spree/gateway.rb b/app/models/spree/gateway.rb index 99d57826fb..4b7533c2d8 100644 --- a/app/models/spree/gateway.rb +++ b/app/models/spree/gateway.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +require 'concerns/payment_method_distributors' +require 'spree/core/delegate_belongs_to' + module Spree class Gateway < PaymentMethod include PaymentMethodDistributors diff --git a/app/models/spree/image.rb b/app/models/spree/image.rb index 3ece2769aa..c42d147ca0 100644 --- a/app/models/spree/image.rb +++ b/app/models/spree/image.rb @@ -5,13 +5,19 @@ module Spree validates_attachment_presence :attachment validate :no_attachment_errors + # This is where the styles are used in the app: + # - mini: used in the BackOffice: Bulk Product Edit page and Order Cycle edit page + # - small: used in the FrontOffice: Product List page + # - product: used in the BackOffice: Product Image upload modal in the Bulk Product Edit page + # and Product image edit page + # - large: used in the FrontOffice: product modal has_attached_file :attachment, - styles: { mini: '48x48>', small: '100x100>', - product: '240x240>', large: '600x600>' }, + styles: { mini: "48x48#", small: "227x227#", + product: "240x240>", large: "600x600>" }, default_style: :product, url: '/spree/products/:id/:style/:basename.:extension', path: ':rails_root/public/spree/products/:id/:style/:basename.:extension', - convert_options: { all: '-strip -auto-orient -colorspace RGB' } + convert_options: { all: '-strip -auto-orient -colorspace sRGB' } # save the w,h of the original image (from which others can be calculated) # we need to look at the write-queue for images which have not been saved yet @@ -20,15 +26,6 @@ module Spree include Spree::Core::S3Support supports_s3 :attachment - Spree::Image.attachment_definitions[:attachment][:styles] = - ActiveSupport::JSON.decode(Spree::Config[:attachment_styles]).symbolize_keys! - Spree::Image.attachment_definitions[:attachment][:path] = Spree::Config[:attachment_path] - Spree::Image.attachment_definitions[:attachment][:url] = Spree::Config[:attachment_url] - Spree::Image.attachment_definitions[:attachment][:default_url] = - Spree::Config[:attachment_default_url] - Spree::Image.attachment_definitions[:attachment][:default_style] = - Spree::Config[:attachment_default_style] - # used by admin products autocomplete def mini_url attachment.url(:mini, false) @@ -51,26 +48,26 @@ module Spree false end - # Spree stores attachent definitions in JSON. This converts the style name and format to - # strings. However, when paperclip encounters these, it doesn't recognise the format. - # Here we solve that problem by converting format and style name to symbols. - # See also: ImageSettingsController decorator. - # - # eg. {'mini' => ['48x48>', 'png']} is converted to {mini: ['48x48>', :png]} - def self.format_styles(styles) - styles_a = styles.map do |name, style| - style[1] = style[1].to_sym if style.is_a? Array - [name.to_sym, style] + def self.set_attachment_attributes(attribute_name, attribute_value) + attachment_definitions[:attachment][attribute_name] = attribute_value + end + + def self.set_s3_attachment_definitions + if Spree::Config[:use_s3] + set_attachment_attributes(:storage, :s3) + set_attachment_attributes(:s3_credentials, s3_credentials) + set_attachment_attributes(:s3_headers, + ActiveSupport::JSON.decode(Spree::Config[:s3_headers])) + set_attachment_attributes(:bucket, Spree::Config[:s3_bucket]) + else + attachment_definitions[:attachment].delete :storage end - - Hash[styles_a] end - def self.reformat_styles - Spree::Image.attachment_definitions[:attachment][:styles] = - format_styles(Spree::Image.attachment_definitions[:attachment][:styles]) + def self.s3_credentials + { access_key_id: Spree::Config[:s3_access_key], + secret_access_key: Spree::Config[:s3_secret], + bucket: Spree::Config[:s3_bucket] } end - - reformat_styles end end diff --git a/app/models/spree/order.rb b/app/models/spree/order.rb index 00dfb64d4f..c2a0a98582 100644 --- a/app/models/spree/order.rb +++ b/app/models/spree/order.rb @@ -6,11 +6,6 @@ require 'open_food_network/feature_toggle' require 'open_food_network/tag_rule_applicator' require 'concerns/order_shipment' -ActiveSupport::Notifications - .subscribe('spree.order.contents_changed') do |_name, _start, _finish, _id, payload| - payload[:order].reload.update_distribution_charge! -end - module Spree class Order < ActiveRecord::Base prepend OrderShipment diff --git a/app/models/spree/payment_method.rb b/app/models/spree/payment_method.rb index 9c41fbefa2..a521122d67 100644 --- a/app/models/spree/payment_method.rb +++ b/app/models/spree/payment_method.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +require 'concerns/payment_method_distributors' +require 'spree/core/calculated_adjustments' + module Spree class PaymentMethod < ActiveRecord::Base include Spree::Core::CalculatedAdjustments @@ -99,7 +102,7 @@ module Spree self.class.include Spree::Core::CalculatedAdjustments end - self.calculator ||= Calculator::FlatRate.new(preferred_amount: 0) + self.calculator ||= ::Calculator::FlatRate.new(preferred_amount: 0) end def has_distributor?(distributor) diff --git a/app/models/spree/preference.rb b/app/models/spree/preference.rb new file mode 100644 index 0000000000..b877f460ce --- /dev/null +++ b/app/models/spree/preference.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Spree + class Preference < ActiveRecord::Base + serialize :value + + validates :key, presence: true + validates :value_type, presence: true + + scope :valid, -> { + where(Spree::Preference.arel_table[:key].not_eq(nil)). + where(Spree::Preference.arel_table[:value_type].not_eq(nil)) + } + + # The type conversions here should match + # the ones in spree::preferences::preferrable#convert_preference_value + def value + if self[:value_type].present? + case self[:value_type].to_sym + when :string, :text + self[:value].to_s + when :password + self[:value].to_s + when :decimal + BigDecimal(self[:value].to_s).round(2, BigDecimal::ROUND_HALF_UP) + when :integer + self[:value].to_i + when :boolean + !(self[:value].to_s =~ /^[t|1]/i).nil? + else + self[:value].is_a?(String) ? YAML.safe_load(self[:value]) : self[:value] + end + else + self[:value] + end + end + + def raw_value + self[:value] + end + end +end diff --git a/app/models/spree/preferences/configuration.rb b/app/models/spree/preferences/configuration.rb new file mode 100644 index 0000000000..d1a514484d --- /dev/null +++ b/app/models/spree/preferences/configuration.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +# This takes the preferrable methods and adds some +# syntatic sugar to access the preferences +# +# class App < Configuration +# preference :color, :string +# end +# +# a = App.new +# +# setters: +# a.color = :blue +# a[:color] = :blue +# a.set :color = :blue +# a.preferred_color = :blue +# +# getters: +# a.color +# a[:color] +# a.get :color +# a.preferred_color +# +# +module Spree + module Preferences + class Configuration + include Spree::Preferences::Preferable + + def configure + yield(self) if block_given? + end + + def preference_cache_key(name) + [ENV['RAILS_CACHE_ID'], self.class.name, name].flatten.join('::').underscore + end + + def reset + preferences.each do |name, _value| + set_preference name, preference_default(name) + end + end + + alias :[] :get_preference + alias :[]= :set_preference + + alias :get :get_preference + + def set(*args) + options = args.extract_options! + options.each do |name, value| + set_preference name, value + end + + return unless args.size == 2 + + set_preference args[0], args[1] + end + + def method_missing(method, *args) + name = method.to_s.gsub('=', '') + if has_preference? name + if method.to_s =~ /=$/ + set_preference(name, args.first) + else + get_preference name + end + else + super + end + end + end + end +end diff --git a/app/models/spree/preferences/preferable.rb b/app/models/spree/preferences/preferable.rb new file mode 100644 index 0000000000..76f4e6aad7 --- /dev/null +++ b/app/models/spree/preferences/preferable.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +# The preference_cache_key is used to determine if the preference +# can be set. The default behavior is to return nil if there is no +# id value. On ActiveRecords, new objects will have their preferences +# saved to a pending hash until it is persisted. +# +# class_attributes are inheritied unless you reassign them in +# the subclass, so when you inherit a Preferable class, the +# inherited hook will assign a new hash for the subclass definitions +# and copy all the definitions allowing the subclass to add +# additional defintions without affecting the base +module Spree + module Preferences + module Preferable + def self.included(base) + base.class_eval do + extend Spree::Preferences::PreferableClassMethods + + if respond_to?(:after_create) + after_create do |obj| + obj.save_pending_preferences + end + end + + if respond_to?(:after_destroy) + after_destroy do |obj| + obj.clear_preferences + end + end + end + end + + def get_preference(name) + has_preference! name + __send__ self.class.preference_getter_method(name) + end + alias :preferred :get_preference + alias :prefers? :get_preference + + def set_preference(name, value) + has_preference! name + __send__ self.class.preference_setter_method(name), value + end + + def preference_type(name) + has_preference! name + __send__ self.class.preference_type_getter_method(name) + end + + def preference_default(name) + has_preference! name + __send__ self.class.preference_default_getter_method(name) + end + + def preference_description(name) + has_preference! name + __send__ self.class.preference_description_getter_method(name) + end + + def has_preference!(name) + raise NoMethodError, "#{name} preference not defined" unless has_preference? name + end + + def has_preference?(name) + respond_to? self.class.preference_getter_method(name) + end + + def preferences + prefs = {} + methods.grep(/^prefers_.*\?$/).each do |pref_method| + prefs[pref_method.to_s.gsub(/prefers_|\?/, '').to_sym] = __send__(pref_method) + end + prefs + end + + def preference_cache_key(name) + return unless id + + [ENV["RAILS_CACHE_ID"], self.class.name, name, id].join('::').underscore + end + + def save_pending_preferences + return unless @pending_preferences + + @pending_preferences.each do |name, value| + set_preference(name, value) + end + end + + def clear_preferences + preferences.keys.each { |pref| preference_store.delete preference_cache_key(pref) } + end + + private + + def add_pending_preference(name, value) + @pending_preferences ||= {} + @pending_preferences[name] = value + end + + def get_pending_preference(name) + return unless @pending_preferences + + @pending_preferences[name] + end + + def convert_preference_value(value, type) + case type + when :string, :text + value.to_s + when :password + value.to_s + when :decimal + BigDecimal(value.to_s).round(2, BigDecimal::ROUND_HALF_UP) + when :integer + value.to_i + when :boolean + if value.is_a?(FalseClass) || + value.nil? || + value == 0 || + value =~ /^(f|false|0)$/i || + (value.respond_to?(:empty?) && value.empty?) + false + else + true + end + else + value + end + end + + def preference_store + Spree::Preferences::Store.instance + end + end + end +end diff --git a/app/models/spree/preferences/preferable_class_methods.rb b/app/models/spree/preferences/preferable_class_methods.rb new file mode 100644 index 0000000000..dd9369d3a0 --- /dev/null +++ b/app/models/spree/preferences/preferable_class_methods.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +module Spree + module Preferences + module PreferableClassMethods + def preference(name, type, *args) + options = args.extract_options! + options.assert_valid_keys(:default, :description) + default = options[:default] + description = options[:description] || name + + # cache_key will be nil for new objects, then if we check if there + # is a pending preference before going to default + define_method preference_getter_method(name) do + # perference_cache_key will only be nil/false for new records + # + if preference_cache_key(name) + preference_store.get(preference_cache_key(name), default) + else + get_pending_preference(name) || default + end + end + alias_method prefers_getter_method(name), preference_getter_method(name) + + define_method preference_setter_method(name) do |value| + value = convert_preference_value(value, type) + if preference_cache_key(name) + preference_store.set preference_cache_key(name), value, type + else + add_pending_preference(name, value) + end + end + alias_method prefers_setter_method(name), preference_setter_method(name) + + define_method preference_default_getter_method(name) do + default + end + + define_method preference_type_getter_method(name) do + type + end + + define_method preference_description_getter_method(name) do + description + end + end + + def remove_preference(name) + if method_defined? preference_getter_method(name) + remove_method preference_getter_method(name) + end + if method_defined? preference_setter_method(name) + remove_method preference_setter_method(name) + end + if method_defined? prefers_getter_method(name) + remove_method prefers_getter_method(name) + end + if method_defined? prefers_setter_method(name) + remove_method prefers_setter_method(name) + end + if method_defined? preference_default_getter_method(name) + remove_method preference_default_getter_method(name) + end + if method_defined? preference_type_getter_method(name) + remove_method preference_type_getter_method(name) + end + if method_defined? preference_description_getter_method(name) + remove_method preference_description_getter_method(name) + end + end + + def preference_getter_method(name) + "preferred_#{name}".to_sym + end + + def preference_setter_method(name) + "preferred_#{name}=".to_sym + end + + def prefers_getter_method(name) + "prefers_#{name}?".to_sym + end + + def prefers_setter_method(name) + "prefers_#{name}=".to_sym + end + + def preference_default_getter_method(name) + "preferred_#{name}_default".to_sym + end + + def preference_type_getter_method(name) + "preferred_#{name}_type".to_sym + end + + def preference_description_getter_method(name) + "preferred_#{name}_description".to_sym + end + end + end +end diff --git a/app/models/spree/preferences/store.rb b/app/models/spree/preferences/store.rb new file mode 100644 index 0000000000..cb30d31eaa --- /dev/null +++ b/app/models/spree/preferences/store.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +# Use singleton class Spree::Preferences::Store.instance to access +# +# StoreInstance has a persistence flag that is on by default, +# but we disable database persistence in testing to speed up tests +# + +require 'singleton' + +module Spree + module Preferences + class StoreInstance + attr_accessor :persistence + + def initialize + @cache = Rails.cache + @persistence = true + end + + def set(key, value, type) + @cache.write(key, value) + persist(key, value, type) + end + + def exist?(key) + @cache.exist?(key) || + should_persist? && Spree::Preference.where(key: key).exists? + end + + def get(key, fallback = nil) + # return the retrieved value, if it's in the cache + # use unless nil? incase the value is actually boolean false + # + unless (val = @cache.read(key)).nil? + return val + end + + if should_persist? + # If it's not in the cache, maybe it's in the database, but + # has been cleared from the cache + + # does it exist in the database? + if Spree::Preference.table_exists? && preference = Spree::Preference.find_by(key: key) + # it does exist, so let's put it back into the cache + @cache.write(preference.key, preference.value) + + # and return the value + return preference.value + end + end + + unless fallback.nil? + # cache fallback so we won't hit the db above on + # subsequent queries for the same key + # + @cache.write(key, fallback) + end + + fallback + end + + def delete(key) + @cache.delete(key) + destroy(key) + end + + def clear_cache + @cache.clear + end + + private + + def persist(cache_key, value, type) + return unless should_persist? + + preference = Spree::Preference.where(key: cache_key).first_or_initialize + preference.value = value + preference.value_type = type + preference.save + end + + def destroy(cache_key) + return unless should_persist? + + preference = Spree::Preference.find_by(key: cache_key) + preference&.destroy + end + + def should_persist? + @persistence && Spree::Preference.connected? && Spree::Preference.table_exists? + end + end + + class Store < StoreInstance + include Singleton + end + end +end diff --git a/app/serializers/api/product_serializer.rb b/app/serializers/api/product_serializer.rb index 733904c5b6..9223a797d8 100644 --- a/app/serializers/api/product_serializer.rb +++ b/app/serializers/api/product_serializer.rb @@ -18,7 +18,7 @@ class Api::ProductSerializer < ActiveModel::Serializer # return an unformatted descripton def description - strip_tags object.description + strip_tags object.description&.strip end # return a sanitized html description diff --git a/app/views/shop/products/_form.html.haml b/app/views/shop/products/_form.html.haml index ffbc7bdf25..9506cd4bcd 100644 --- a/app/views/shop/products/_form.html.haml +++ b/app/views/shop/products/_form.html.haml @@ -4,26 +4,39 @@ = render partial: "shop/products/searchbar" .row - .small-12.columns - .footer-pad.small-12.columns.no-gutter - .row - .medium-12.large-10.columns - = render partial: "shop/products/search_feedback" + .footer-pad.small-12.columns.product-listing + .row.full + .medium-12.large-9.columns.full + = render partial: "shop/products/search_feedback" - %div.pad-top{ "infinite-scroll" => "loadMore()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'Products.loading' } - %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in Products.products track by product.id", "id" => "product-{{ product.id }}"} - = render "shop/products/summary" + %div.pad-top{ "infinite-scroll" => "loadMore()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'Products.loading' } + %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in Products.products track by product.id", "id" => "product-{{ product.id }}"} + = render "shop/products/summary" + .shop-variants %shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants | orderBy: ['name_to_display','unit_value'] track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.on_hand == 0}"} - %product{"ng-show" => "Products.loading"} - .row.summary - .small-12.columns.text-center - = t :products_loading - .row - .small-12.columns.text-center - %img.spinner{ src: image_path("spinning-circles.svg") } + %product{"ng-show" => "Products.loading"} + .summary + .small-12.columns.text-center + = t :products_loading + .row.full + .small-12.columns.text-center + %img.spinner{ src: image_path("spinning-circles.svg") } - .hide-for-medium-down.large-2.columns + .hide-for-medium-down.large-1.columns + -# Space between products and filters +   + .hide-for-medium-down.large-2.columns + %h5.filter-header + = t(:products_filter_by) + %span{ng: {show: 'filtersCount()' }} + = "({{ filtersCount() }} #{t(:products_filter_selected)})" + + = render partial: "shop/products/filters" + + .expanding-sidebar.shop-filters-sidebar.hide-for-large-up{ng: {show: 'showFilterSidebar', class: "{'shown': showFilterSidebar}"}} + .background{ng: {click: 'toggleFilterSidebar()'}} + .sidebar %h5 = t(:products_filter_by) %span{ng: {show: 'filtersCount()' }} @@ -31,18 +44,8 @@ = render partial: "shop/products/filters" - .expanding-sidebar.shop-filters-sidebar.hide-for-large-up{ng: {show: 'showFilterSidebar', class: "{'shown': showFilterSidebar}"}} - .background{ng: {click: 'toggleFilterSidebar()'}} - .sidebar - %h5 - = t(:products_filter_by) - %span{ng: {show: 'filtersCount()' }} - = "({{ filtersCount() }} #{t(:products_filter_selected)})" - - = render partial: "shop/products/filters" - - .sidebar-footer - %button.large.dark.left{type: 'button', ng: {click: 'clearFilters()'}} - = t(:products_filter_clear) - %button.large.bright.right{type: 'button', ng: {click: 'toggleFilterSidebar()'}} - = t(:products_filter_done) + .sidebar-footer + %button.large.dark.left{type: 'button', ng: {click: 'clearFilters()'}} + = t(:products_filter_clear) + %button.large.bright.right{type: 'button', ng: {click: 'toggleFilterSidebar()'}} + = t(:products_filter_done) diff --git a/app/views/shop/products/_summary.html.haml b/app/views/shop/products/_summary.html.haml index 3c12a68db8..c87d26ff46 100644 --- a/app/views/shop/products/_summary.html.haml +++ b/app/views/shop/products/_summary.html.haml @@ -1,20 +1,20 @@ .product-thumb %a{"ng-click" => "triggerProductModal()"} - %i.ofn-i_057-expand - %img{"ng-src" => "{{::product.primaryImageOrMissing}}", "ng-click" => "triggerProductModal()"} + %span.product-thumb__bulk-label{"ng-if" => "::product.group_buy"} + = t(".bulk") + %img{"ng-src" => "{{::product.primaryImageOrMissing}}"} -.row.summary - .small-10.medium-10.large-11.columns.summary-header +.summary + .summary-header %h3 %a{"ng-click" => "triggerProductModal()", href: 'javascript:void(0)'} %span{"ng-bind" => "::product.name"} - %i.ofn-i_057-expand - %small - %em - = t :products_from + %p.product-description{ng: {bind: "::product.description", click: "triggerProductModal()", show: "product.description.length"}} + .product-producer + = t :products_from %span %enterprise-modal - %i.ofn-i_036-producers %span{"ng-bind" => "::enterprise.name"} - .small-2.medium-2.large-1.columns.text-center - .taxon-flag + + .product-properties.filter-shopfront.property-selectors + %filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" } diff --git a/app/views/spree/admin/image_settings/edit.html.haml b/app/views/spree/admin/image_settings/edit.html.haml deleted file mode 100644 index 4345755792..0000000000 --- a/app/views/spree/admin/image_settings/edit.html.haml +++ /dev/null @@ -1,81 +0,0 @@ -= render :partial => 'spree/admin/shared/configuration_menu' - -- content_for :page_title do - = Spree.t(:image_settings) - -= form_tag admin_image_settings_path, :method => :put do - - destroy = Spree.t(:destroy) - - %fieldset.no-border-top - %fieldset.no-border-bottom - %legend{:align => "center"}= Spree.t(:general_settings) - .field - .warning.note= Spree.t(:image_settings_warning) - - .field{"data-hook" => "attachment_path"} - = label_tag 'preferences[attachment_path]', Spree.t(:attachment_path) - = preference_field_tag 'preferences[attachment_path]', Spree::Config[:attachment_path], :type => :string - .alpha.eight.columns - .field{"data-hook" => "attachment_default_url"} - = label_tag 'preferences[attachment_default_url]', Spree.t(:attachment_default_url) - = preference_field_tag 'preferences[attachment_default_url]', Spree::Config[:attachment_default_url], :type => :string - .alpha.eight.columns - .field{"data-hook" => "attachment_url"} - = label_tag 'preferences[attachment_url]', Spree.t(:attachment_url) - = preference_field_tag 'preferences[attachment_url]', Spree::Config[:attachment_url], :type => :string - .omega.four.columns - .field{"data-hook" => "attachment_default_style"} - = label_tag 'preferences[attachment_default_style]', Spree.t(:attachment_default_style) - = collection_select 'preferences', 'attachment_default_style', @styles, :first, :first, {:selected => Spree::Config[:attachment_default_style] }, :class => 'select2 fullwidth' - .clear - .field{"data-hook" => "use_s3"} - = preference_field_tag 'preferences[use_s3]', Spree::Config[:use_s3], :type => :boolean - = label_tag 'preferences[use_s3]', Spree.t(:use_s3) - - %fieldset#attachment_styles.no-border-bottom{"data-hook" => "attachment_styles"} - %legend{:align => "center"}= Spree.t(:attachment_styles) - #styles_list.row.frameless - - @styles.each_with_index do |(style_name, style_value), index| - .field.three.columns - = label_tag "attachment_styles[#{style_name}]", style_name - %a.destroy_style{:alt => t(:destroy), :href => "#", :title => t(:destroy)} - %i.icon-trash - = text_field_tag "attachment_styles[#{style_name}][]", admin_image_settings_geometry_from_style(style_value), :class => 'fullwidth' - %br/ - - current_format = admin_image_settings_format_from_style(style_value) || '' - = select_tag "attachment_styles[#{style_name}][]", options_for_select(admin_image_settings_format_options, current_format), :class => 'fullwidth', :id => "attachment_styles_format_#{style_name}" - #new-styles.row.frameless - .field - = link_to_with_icon 'icon-plus', Spree.t(:add_new_style), '#', :class => 'add_new_style button' - - .row - #s3_settings.alpha.six.columns{"data-hook" => "s3_settings", :style => "display: none"} - %fieldset.no-border-bottom - %legend{:align => "center"}= Spree.t(:configure_s3) - .field{"data-hook" => "s3_access_key"} - = label_tag 'preferences[s3_access_key]', Spree.t(:s3_access_key) - = preference_field_tag 'preferences[s3_access_key]', Spree::Config[:s3_access_key], :type => :string - .field{"data-hook" => "s3_secret"} - = label_tag 'preferences[s3_secret]', Spree.t(:s3_secret) - = preference_field_tag 'preferences[s3_secret]', Spree::Config[:s3_secret], :type => :string - .field{"data-hook" => "s3_bucket"} - = label_tag 'preferences[s3_bucket]', Spree.t(:s3_bucket) - = preference_field_tag 'preferences[s3_bucket]', Spree::Config[:s3_bucket], :type => :string - .field{"data-hook" => "s3_protocol"} - = label_tag 'preferences[s3_protocol]', Spree.t(:s3_protocol) - = preference_field_tag 'preferences[s3_protocol]', Spree::Config[:s3_protocol], :type => :string - - #s3_headers.omega.six.columns{"data-hook" => "s3_headers", :style => "display: none"} - %fieldset.no-border-bottom - %legend{:align => "center"}= Spree.t(:s3_headers) - #headers_list - - @headers.each do |header_name, header_value| - .field - = label_tag "s3_headers[#{header_name}]", header_name - %a.destroy_header.with-tip{:alt => destroy, :href => "#", :title => destroy} - %i.icon-trash - = text_field_tag "s3_headers[#{header_name}]", header_value, :class => 'fullwidth' - = link_to_with_icon 'icon-plus', Spree.t(:add_new_header), '#', :class => 'add_header button' - - .form-buttons.filter-actions.actions{"data-hook" => "buttons"} - = button Spree.t('actions.update'), 'icon-refresh' diff --git a/app/views/spree/admin/orders/customer_details/_address_form.html.haml b/app/views/spree/admin/orders/customer_details/_address_form.html.haml index 18bd0970b6..da8d943c18 100644 --- a/app/views/spree/admin/orders/customer_details/_address_form.html.haml +++ b/app/views/spree/admin/orders/customer_details/_address_form.html.haml @@ -16,10 +16,6 @@ %div{class: "field"} = f.label :lastname, Spree.t(:last_name) + ':' = f.text_field :lastname, class: 'fullwidth' - - if Spree::Config[:company] - %div{class: "field"} - = f.label :company, Spree.t(:company) + ':' - = f.text_field :company, class: 'fullwidth' %div{class: "field"} = f.label :address1, Spree.t(:street_address) + ':' = f.text_field :address1, class: 'fullwidth' diff --git a/app/views/spree/admin/shared/_configuration_menu.html.haml b/app/views/spree/admin/shared/_configuration_menu.html.haml index c5f302f0db..3e5790beb5 100644 --- a/app/views/spree/admin/shared/_configuration_menu.html.haml +++ b/app/views/spree/admin/shared/_configuration_menu.html.haml @@ -7,7 +7,6 @@ = configurations_sidebar_menu_item Spree.t(:general_settings), edit_admin_general_settings_path - if Spree::Config[:override_actionmailer_config] = configurations_sidebar_menu_item Spree.t(:mail_method_settings), edit_admin_mail_methods_path - = configurations_sidebar_menu_item Spree.t(:image_settings), edit_admin_image_settings_path = configurations_sidebar_menu_item Spree.t(:tax_categories), admin_tax_categories_path = configurations_sidebar_menu_item Spree.t(:tax_rates), admin_tax_rates_path = configurations_sidebar_menu_item Spree.t(:tax_settings), edit_admin_tax_settings_path diff --git a/app/views/spree/admin/shared/_routes.html.erb b/app/views/spree/admin/shared/_routes.html.erb index 15d562e4ef..438d257af9 100644 --- a/app/views/spree/admin/shared/_routes.html.erb +++ b/app/views/spree/admin/shared/_routes.html.erb @@ -1,4 +1,10 @@