diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index 48d5cf060a..2c8b7f7cfe 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -5,6 +5,7 @@ require 'open_food_network/address_finder' class CheckoutController < ::BaseController include OrderStockCheck include OrderCompletion + include WhiteLabel layout 'darkswarm' @@ -27,6 +28,8 @@ class CheckoutController < ::BaseController before_action :associate_user before_action :check_authorization + before_action :hide_ofn_navigation, only: :edit + helper 'spree/orders' def edit; end diff --git a/app/controllers/concerns/white_label.rb b/app/controllers/concerns/white_label.rb new file mode 100644 index 0000000000..180d138237 --- /dev/null +++ b/app/controllers/concerns/white_label.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module WhiteLabel + extend ActiveSupport::Concern + include EnterprisesHelper + + def hide_ofn_navigation(distributor = current_distributor) + return false unless OpenFoodNetwork::FeatureToggle.enabled?(:white_label) + + # if the distributor has the hide_ofn_navigation preference set to true + # then we should hide the OFN navigation + @hide_ofn_navigation = distributor.hide_ofn_navigation + end +end diff --git a/app/controllers/enterprises_controller.rb b/app/controllers/enterprises_controller.rb index 2fc7f43b44..058c6528ed 100644 --- a/app/controllers/enterprises_controller.rb +++ b/app/controllers/enterprises_controller.rb @@ -7,6 +7,7 @@ class EnterprisesController < BaseController helper Spree::ProductsHelper include OrderCyclesHelper include SerializerHelper + include WhiteLabel protect_from_forgery except: :check_permalink @@ -14,6 +15,7 @@ class EnterprisesController < BaseController prepend_before_action :set_order_cycles, :require_distributor_chosen, :reset_order, only: :shop before_action :clean_permalink, only: :check_permalink + before_action :hide_ofn_navigation, only: :shop respond_to :js, only: :permalink_checker diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index e33bd0038b..c1a63c2c44 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -10,6 +10,7 @@ class SplitCheckoutController < ::BaseController include CheckoutCallbacks include OrderCompletion include CablecarResponses + include WhiteLabel helper 'terms_and_conditions' helper 'checkout' @@ -18,6 +19,7 @@ class SplitCheckoutController < ::BaseController helper OrderHelper before_action :set_checkout_redirect + before_action :hide_ofn_navigation, only: [:edit, :update] def edit redirect_to_step_based_on_order unless params[:step] diff --git a/app/controllers/spree/orders_controller.rb b/app/controllers/spree/orders_controller.rb index 95e9390f9b..e0a8d2281b 100644 --- a/app/controllers/spree/orders_controller.rb +++ b/app/controllers/spree/orders_controller.rb @@ -5,6 +5,7 @@ module Spree include OrderCyclesHelper include Rails.application.routes.url_helpers include CablecarResponses + include WhiteLabel layout 'darkswarm' @@ -14,7 +15,8 @@ module Spree respond_to :html, :json before_action :check_authorization - before_action :set_current_order, only: :update + before_action :set_order_from_params, only: :show + before_action :set_current_order, only: [:edit, :update] before_action :filter_order_params, only: :update prepend_before_action :require_order_authentication, only: :show @@ -23,10 +25,12 @@ module Spree before_action :check_hub_ready_for_checkout, only: :edit before_action :check_at_least_one_line_item, only: :update - def show - @order = Spree::Order.find_by!(number: params[:id]) + before_action only: [:show, :edit] do + hide_ofn_navigation(@order.distributor) end + def show; end + def empty if @order = current_order @order.empty! @@ -37,7 +41,6 @@ module Spree # Patching to redirect to shop if order is empty def edit - @order = current_order(true) @insufficient_stock_lines = @order.insufficient_stock_lines @unavailable_order_variants = OrderCycleDistributedVariants. new(current_order_cycle, current_distributor).unavailable_order_variants(@order) @@ -106,6 +109,10 @@ module Spree private + def set_order_from_params + @order = Spree::Order.find_by!(number: params[:id]) + end + def set_current_order @order = current_order(true) end diff --git a/app/helpers/admin/enterprises_helper.rb b/app/helpers/admin/enterprises_helper.rb index 25dae316f8..f36661544a 100644 --- a/app/helpers/admin/enterprises_helper.rb +++ b/app/helpers/admin/enterprises_helper.rb @@ -23,6 +23,14 @@ module Admin show_enterprise_fees = can?(:manage_enterprise_fees, enterprise) && (is_shop || enterprise.is_primary_producer) + build_enterprise_side_menu_items(is_shop, show_properties, show_shipping_methods, + show_payment_methods, show_enterprise_fees) + end + + private + + def build_enterprise_side_menu_items(is_shop, show_properties, show_shipping_methods, + show_payment_methods, show_enterprise_fees) [ { name: 'primary_details', icon_class: "icon-home", show: true, selected: 'selected' }, { name: 'address', icon_class: "icon-map-marker", show: true }, @@ -41,8 +49,14 @@ module Admin { name: 'inventory_settings', icon_class: "icon-list-ol", show: is_shop }, { name: 'tag_rules', icon_class: "icon-random", show: is_shop }, { name: 'shop_preferences', icon_class: "icon-shopping-cart", show: is_shop }, - { name: 'users', icon_class: "icon-user", show: true } - ] + { name: 'users', icon_class: "icon-user", show: true }, + ] + [add_white_label_if_feature_activated].compact + end + + def add_white_label_if_feature_activated + return nil unless OpenFoodNetwork::FeatureToggle.enabled?(:white_label) + + { name: 'white_label', icon_class: "icon-leaf", show: true } end # rubocop:enable Metrics/MethodLength end diff --git a/app/serializers/api/admin/enterprise_serializer.rb b/app/serializers/api/admin/enterprise_serializer.rb index 2071024f3e..9afed8e532 100644 --- a/app/serializers/api/admin/enterprise_serializer.rb +++ b/app/serializers/api/admin/enterprise_serializer.rb @@ -14,7 +14,7 @@ module Api :logo, :promo_image, :terms_and_conditions, :terms_and_conditions_file_name, :terms_and_conditions_updated_at, :preferred_invoice_order_by_supplier, :preferred_product_low_stock_display, - :visible + :visible, :hide_ofn_navigation has_one :owner, serializer: Api::Admin::UserSerializer has_many :users, serializer: Api::Admin::UserSerializer diff --git a/app/serializers/api/enterprise_shopfront_serializer.rb b/app/serializers/api/enterprise_shopfront_serializer.rb index ac0fed09f2..12ca4d1b79 100644 --- a/app/serializers/api/enterprise_shopfront_serializer.rb +++ b/app/serializers/api/enterprise_shopfront_serializer.rb @@ -9,7 +9,8 @@ module Api :instagram, :linkedin, :twitter, :facebook, :is_primary_producer, :is_distributor, :phone, :whatsapp_phone, :whatsapp_url, :visible, :email_address, :hash, :logo, :promo_image, :path, :category, :active, :producers, :orders_close_at, :hubs, - :taxons, :supplied_taxons, :pickup, :delivery, :preferred_product_low_stock_display + :taxons, :supplied_taxons, :pickup, :delivery, :preferred_product_low_stock_display, + :hide_ofn_navigation has_one :address, serializer: Api::AddressSerializer has_many :supplied_properties, serializer: Api::PropertySerializer diff --git a/app/services/permitted_attributes/enterprise.rb b/app/services/permitted_attributes/enterprise.rb index 5aff8fbb76..c72a24cfa7 100644 --- a/app/services/permitted_attributes/enterprise.rb +++ b/app/services/permitted_attributes/enterprise.rb @@ -34,7 +34,8 @@ module PermittedAttributes :preferred_shopfront_producer_order, :preferred_shopfront_order_cycle_order, :show_customer_names_to_suppliers, :preferred_shopfront_product_sorting_method, :preferred_invoice_order_by_supplier, - :preferred_product_low_stock_display + :preferred_product_low_stock_display, + :hide_ofn_navigation ] end end diff --git a/app/views/admin/enterprises/form/_white_label.html.haml b/app/views/admin/enterprises/form/_white_label.html.haml new file mode 100644 index 0000000000..b083ec7700 --- /dev/null +++ b/app/views/admin/enterprises/form/_white_label.html.haml @@ -0,0 +1,6 @@ +.row + .three.columns.alpha + = f.label :hide_ofn_navigation, t('.hide_ofn_navigation') + .three.columns + = f.check_box :hide_ofn_navigation + diff --git a/app/views/shared/menu/_large_menu.html.haml b/app/views/shared/menu/_large_menu.html.haml index d8cb828ca6..ec64ecdcf5 100644 --- a/app/views/shared/menu/_large_menu.html.haml +++ b/app/views/shared/menu/_large_menu.html.haml @@ -10,14 +10,15 @@ = t 'powered_by' %a{href: '/'} = t 'title' - %ul.nav-main-menu - - [*1..7].each do |menu_number| - - menu_name = "menu_#{menu_number}" - - if ContentConfig[menu_name].present? - %li - %a{href: t("#{menu_name}_url") } - %span.nav-primary - = t "#{menu_name}_title" + - unless @hide_ofn_navigation + %ul.nav-main-menu + - [*1..7].each do |menu_number| + - menu_name = "menu_#{menu_number}" + - if ContentConfig[menu_name].present? + %li + %a{href: t("#{menu_name}_url") } + %span.nav-primary + = t "#{menu_name}_title" %ul.nav-icons-menu - if OpenFoodNetwork::I18nConfig.selectable_locales.count > 1 = render 'shared/menu/language_selector' diff --git a/config/locales/en.yml b/config/locales/en.yml index 50b456fd18..eb520908a0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1155,6 +1155,9 @@ en: net_value: Net Value add_new: Add New no_voucher_yet: No Vouchers yet + white_label: + legend: "White Label" + hide_ofn_navigation: "Hide OFN navigation" actions: edit_profile: Settings properties: Properties @@ -1394,6 +1397,7 @@ en: shop_preferences: "Shop Preferences" users: "Users" vouchers: Vouchers + white_label: "White Label" enterprise_group: primary_details: "Primary Details" users: "Users" diff --git a/db/migrate/20230329080357_add_hide_ofn_navigation_to_enterprises.rb b/db/migrate/20230329080357_add_hide_ofn_navigation_to_enterprises.rb new file mode 100644 index 0000000000..3fd3906634 --- /dev/null +++ b/db/migrate/20230329080357_add_hide_ofn_navigation_to_enterprises.rb @@ -0,0 +1,5 @@ +class AddHideOfnNavigationToEnterprises < ActiveRecord::Migration[7.0] + def change + add_column :enterprises, :hide_ofn_navigation, :boolean, null: false, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 6e7f939670..04c2560ce1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_03_15_031807) do +ActiveRecord::Schema[7.0].define(version: 2023_03_29_080357) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" enable_extension "plpgsql" @@ -224,6 +224,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_15_031807) do t.boolean "show_customer_names_to_suppliers", default: false, null: false t.string "visible", limit: 255, default: "public", null: false t.string "whatsapp_phone", limit: 255 + t.boolean "hide_ofn_navigation", default: false, null: false t.index ["address_id"], name: "index_enterprises_on_address_id" t.index ["is_primary_producer", "sells"], name: "index_enterprises_on_is_primary_producer_and_sells" t.index ["name"], name: "index_enterprises_on_name", unique: true diff --git a/lib/open_food_network/feature_toggle.rb b/lib/open_food_network/feature_toggle.rb index ebebffb1de..b6a3048209 100644 --- a/lib/open_food_network/feature_toggle.rb +++ b/lib/open_food_network/feature_toggle.rb @@ -40,6 +40,9 @@ module OpenFoodNetwork "vouchers" => <<~DESC, Add voucher functionality. Voucher can be managed via Enterprise settings. DESC + "white_label" => <<~DESC, + Customize shopfront (shop, cart, checkout) and emails without OFN branding. + DESC }.freeze # Move your feature entry from CURRENT_FEATURES to RETIRED_FEATURES when diff --git a/spec/controllers/spree/orders_controller_spec.rb b/spec/controllers/spree/orders_controller_spec.rb index c6ce2a43e6..44b5873661 100644 --- a/spec/controllers/spree/orders_controller_spec.rb +++ b/spec/controllers/spree/orders_controller_spec.rb @@ -91,6 +91,7 @@ describe Spree::OrdersController, type: :controller do it "redirects to shop when order is empty" do allow(controller).to receive(:current_distributor).and_return(distributor) + allow(distributor).to receive(:hide_ofn_navigation).and_return false allow(controller).to receive(:current_order_cycle).and_return(order_cycle) allow(controller).to receive(:current_order).and_return order allow(order).to receive_message_chain(:line_items, :empty?).and_return true diff --git a/spec/system/admin/enterprises_spec.rb b/spec/system/admin/enterprises_spec.rb index d377f51635..5613a6da02 100644 --- a/spec/system/admin/enterprises_spec.rb +++ b/spec/system/admin/enterprises_spec.rb @@ -584,5 +584,46 @@ describe ' end end end + + context "white label settings" do + context "when the feature is enabled" do + before do + Flipper.enable(:white_label) + visit edit_admin_enterprise_path(distributor1) + + within(".side_menu") do + click_link "White Label" + end + end + + it "set the hide_ofn_navigation preference for the current shop" do + check "Hide OFN navigation" + click_button 'Update' + expect(flash_message).to eq('Enterprise "First Distributor" has been successfully updated!') + expect(distributor1.reload.hide_ofn_navigation).to be true + + visit edit_admin_enterprise_path(distributor1) + within(".side_menu") do + click_link "White Label" + end + + uncheck "Hide OFN navigation" + click_button 'Update' + expect(flash_message).to eq('Enterprise "First Distributor" has been successfully updated!') + expect(distributor1.reload.hide_ofn_navigation).to be false + end + end + + context "when the feature is disabled" do + before do + Flipper.disable(:white_label) + visit edit_admin_enterprise_path(distributor1) + end + + it "does not show the white label settings" do + expect(page).not_to have_link "White Label" + end + end + end end end diff --git a/spec/system/consumer/white_label_spec.rb b/spec/system/consumer/white_label_spec.rb new file mode 100644 index 0000000000..24e930725d --- /dev/null +++ b/spec/system/consumer/white_label_spec.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +require 'system_helper' + +describe 'White label setting' do + include AuthenticationHelper + include ShopWorkflow + + let!(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) } + let!(:shipping_method) { create(:shipping_method, distributors: [distributor]) } + let(:product) { + create(:taxed_product, supplier: create(:supplier_enterprise), price: 10, + zone: create(:zone_with_member), tax_rate_amount: 0.1) + } + let!(:order_cycle) { + create(:simple_order_cycle, distributors: [distributor], + coordinator: create(:distributor_enterprise), + variants: [product.variants.first]) + } + let!(:order) { create(:order, distributor: distributor, order_cycle: order_cycle) } + + let(:ofn_navigation) { 'ul.nav-main-menu' } + + shared_examples "does not hide the OFN navigation" do + it "does not hide the OFN navigation when visiting the shop" do + visit main_app.enterprise_shop_path(distributor) + expect(page).to have_selector ofn_navigation + end + + it "does not hide the OFN navigation when visiting root path" do + visit main_app.root_path + expect(page).to have_selector ofn_navigation + end + + it "does not hide the OFN navigation when visiting cart path" do + visit main_app.cart_path + expect(page).to have_selector ofn_navigation + end + end + + context "when the white label feature is activated" do + before do + Flipper.enable(:white_label) + end + + context "manage the hide_ofn_navigation preference" do + context "when the preference is set to true" do + before do + distributor.update_attribute(:hide_ofn_navigation, true) + end + + shared_examples "hides the OFN navigation when needed only" do + it "hides the OFN navigation when visiting the shop" do + visit main_app.enterprise_shop_path(distributor) + expect(page).to have_no_selector ofn_navigation + end + + it "does not hide the OFN navigation when visiting root path" do + visit main_app.root_path + expect(page).to have_selector ofn_navigation + end + end + + context "without order or current distributor" do + it_behaves_like "hides the OFN navigation when needed only" + end + + context "when the user has an order ready to checkout" do + before do + order.update_attribute(:state, 'cart') + order.line_items << create(:line_item, variant: product.variants.first) + set_order(order) + end + + shared_examples "hides the OFN navigation when needed only for the checkout" do + it_behaves_like "hides the OFN navigation when needed only" + + it "hides the OFN navigation when visiting cart path" do + visit main_app.cart_path + expect(page).to have_no_selector ofn_navigation + end + + it "hides the OFN navigation when visiting checkout path" do + visit checkout_path + expect(page).to have_content "Checkout now" + expect(page).to have_content "Order ready for " + expect(page).to have_no_selector ofn_navigation + end + end + + context "when the split checkout is disabled" do + it_behaves_like "hides the OFN navigation when needed only for the checkout" + end + + context "when the split checkout is enabled" do + before do + Flipper.enable(:split_checkout) + end + + it_behaves_like "hides the OFN navigation when needed only for the checkout" + end + end + + context "when the user has a complete order" do + let(:complete_order) { + create(:order_with_credit_payment, + user: nil, + email: "guest@user.com", + distributor: distributor, + order_cycle: order_cycle) + } + before do + set_order(complete_order) + end + + shared_examples "hides the OFN navigation when needed only for the order confirmation" do + it "hides" do + visit order_path(complete_order, order_token: complete_order.token) + expect(page).to have_no_selector ofn_navigation + end + end + + context "when the current distributor is the distributor of the order" do + before do + allow_any_instance_of(EnterprisesHelper).to receive(:current_distributor). + and_return(distributor) + end + + it_behaves_like "hides the OFN navigation when needed only for the order confirmation" + end + + context "when the user has a current distributor that is not the distributor's order" do + let!(:another_distributor) { create(:distributor_enterprise) } + before do + another_distributor.update_attribute(:hide_ofn_navigation, false) + allow_any_instance_of(EnterprisesHelper).to receive(:current_distributor). + and_return(another_distributor) + end + + it_behaves_like "hides the OFN navigation when needed only for the order confirmation" + end + end + + context "when the user has a current distributor" do + before do + allow_any_instance_of(EnterprisesHelper).to receive(:current_distributor). + and_return(distributor) + end + + it_behaves_like "hides the OFN navigation when needed only" + end + end + + context "when the preference is set to false" do + before do + distributor.update_attribute(:hide_ofn_navigation, false) + set_order(order) + allow_any_instance_of(EnterprisesHelper).to receive(:current_distributor). + and_return(distributor) + end + + it_behaves_like "does not hide the OFN navigation" + end + end + end + + context "when the white label feature is deactivated" do + before do + Flipper.disable(:white_label) + end + + it_behaves_like "does not hide the OFN navigation" + end +end