Merge pull request #10575 from jibees/10559-allow-upload-of-custom-header-logo

White Label: can upload a custom header logo
This commit is contained in:
Filipe
2023-04-20 13:27:52 +01:00
committed by GitHub
20 changed files with 380 additions and 15 deletions

View File

@@ -12,5 +12,6 @@ angular.module("ofn.admin", [
"admin.orders"
]).config ($httpProvider, $locationProvider, $qProvider) ->
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"
# for the next line, you should also probably check file: app/assets/javascripts/admin/utils/utils.js.coffee
$locationProvider.hashPrefix('')
$qProvider.errorOnUnhandledRejections(false)

View File

@@ -1,3 +1,4 @@
angular.module("admin.utils", ["templates", "ngSanitize"]).config ($httpProvider, $locationProvider) ->
# for the next line, you should also probably check file: app/assets/javascripts/admin/admin_ofn.js.coffee
$locationProvider.hashPrefix('')
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"

View File

@@ -1,9 +1,10 @@
# frozen_string_literal: true
class ConfirmModalComponent < ModalComponent
def initialize(id:, confirm_actions: nil, controllers: nil, message: nil)
def initialize(id:, confirm_actions: nil, controllers: nil, message: nil, confirm_reflexes: nil)
super(id: id, close_button: true)
@confirm_actions = confirm_actions
@confirm_reflexes = confirm_reflexes
@controllers = controllers
@message = message
end

View File

@@ -7,4 +7,4 @@
.modal-actions
%input{ class: "button icon-plus #{close_button_class}", type: 'button', value: t('js.admin.modals.cancel'), "data-action": "click->modal#close" }
%input{ class: "button icon-plus primary", type: 'button', value: t('js.admin.modals.confirm'), "data-action": @confirm_actions }
%input{ class: "button icon-plus primary", type: 'button', value: t('js.admin.modals.confirm'), "data-action": @confirm_actions, "data-reflex": @confirm_reflexes }

View File

@@ -10,5 +10,12 @@ module WhiteLabel
# 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
# if the distributor has the hide_ofn_navigation preference
# set to false, there is no need to check the white_label_logo preference
return unless @hide_ofn_navigation
@white_label_logo = distributor.white_label_logo
@white_label_distributor = distributor
end
end

View File

@@ -15,6 +15,10 @@ class Enterprise < ApplicationRecord
medium: { resize_to_fill: [720, 156] },
large: { resize_to_fill: [1200, 260] },
}.freeze
WHITE_LABEL_LOGO_SIZES = {
default: { gravity: "Center", resize_to_fill: [217, 44] },
mobile: { gravity: "Center", resize_to_fill: [75, 26] },
}.freeze
VALID_INSTAGRAM_REGEX = %r{\A[a-zA-Z0-9._]{1,30}([^/-]*)\z}
searchable_attributes :sells, :is_primary_producer, :name
@@ -84,6 +88,7 @@ class Enterprise < ApplicationRecord
has_one_attached :logo
has_one_attached :promo_image
has_one_attached :terms_and_conditions
has_one_attached :white_label_logo
validates :logo,
processable_image: true,
@@ -302,6 +307,14 @@ class Enterprise < ApplicationRecord
)
end
def white_label_logo_url(name = :default)
return unless white_label_logo.variable?
Rails.application.routes.url_helpers.url_for(
white_label_logo.variant(WHITE_LABEL_LOGO_SIZES[name])
)
end
def website
strip_url self[:website]
end

View File

@@ -0,0 +1,12 @@
# frozen_string_literal: true
module EnterpriseConcern
extend ActiveSupport::Concern
included do
before_reflex do
@enterprise = Enterprise.find_by(permalink: params[:id])
authorize! :update, @enterprise
end
end
end

View File

@@ -0,0 +1,24 @@
# frozen_string_literal: true
class WhiteLabelReflex < ApplicationReflex
include EnterpriseConcern
delegate :view_context, to: :controller
def remove_logo
@enterprise.update!(white_label_logo: nil)
f = ActionView::Helpers::FormBuilder.new(:enterprise, @enterprise, view_context, {})
html = with_locale {
render(partial: "admin/enterprises/form/white_label",
locals: { f: f, enterprise: @enterprise })
}
morph "#white_label_panel", html
flash[:success] = with_locale {
I18n.t("admin.enterprises.form.white_label.remove_logo_success")
}
cable_ready.dispatch_event(name: "modal:close")
morph "#flashes", render(partial: "shared/flashes", locals: { flashes: flash })
end
end

View File

@@ -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, :hide_ofn_navigation
:visible, :hide_ofn_navigation, :white_label_logo
has_one :owner, serializer: Api::Admin::UserSerializer
has_many :users, serializer: Api::Admin::UserSerializer
@@ -29,6 +29,10 @@ module Api
attachment_urls(object.promo_image, Enterprise::PROMO_IMAGE_SIZES)
end
def white_label_logo
attachment_urls(object.white_label_logo, Enterprise::WHITE_LABEL_LOGO_SIZES)
end
def terms_and_conditions
return unless object.terms_and_conditions.attached?

View File

@@ -10,7 +10,7 @@ module Api
: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,
:hide_ofn_navigation
:hide_ofn_navigation, :white_label_logo
has_one :address, serializer: Api::AddressSerializer
has_many :supplied_properties, serializer: Api::PropertySerializer
@@ -49,6 +49,10 @@ module Api
enterprise.promo_image_url(:large)
end
def white_label_logo
enterprise.white_label_logo_url
end
def path
enterprise_shop_path(enterprise)
end

View File

@@ -35,7 +35,7 @@ module PermittedAttributes
:show_customer_names_to_suppliers, :preferred_shopfront_product_sorting_method,
:preferred_invoice_order_by_supplier,
:preferred_product_low_stock_display,
:hide_ofn_navigation
:hide_ofn_navigation, :white_label_logo,
]
end
end

View File

@@ -1,6 +1,23 @@
- @object ||= enterprise
.row
.three.columns.alpha
= f.label :hide_ofn_navigation, t('.hide_ofn_navigation')
.three.columns
= f.check_box :hide_ofn_navigation
= f.check_box :hide_ofn_navigation, { "data-controller": "checkbox-display", "data-checkbox-display-target-id-value": "white_label_logo" }
.row{id: "white_label_logo"}
.three.columns.alpha
= f.label :white_label_logo, t('.upload_logo')
.three.columns
= image_tag @object.white_label_logo_url if @object.white_label_logo.present?
= f.file_field :white_label_logo, accept: "image/*"
- if @object.white_label_logo.variable?
%button.button.small{ type: "button", "data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "remove_logo" }
= t('.remove_logo')
- if @object.white_label_logo.variable?
= render ConfirmModalComponent.new(id: "remove_logo", confirm_reflexes: "click->WhiteLabel#remove_logo" ) do
.margin-bottom-30
= t('.remove_logo_confirm')

View File

@@ -3,7 +3,10 @@
%ul.nav-logo
%li.ofn-logo
%a{href: main_app.root_path}
%img{src: ContentConfig.url_for(:logo)}
- if @white_label_logo&.variable?
= image_tag @white_label_distributor.white_label_logo_url(:default)
- else
%img{src: ContentConfig.url_for(:logo)}
%li.powered-by
%img{src: '/favicon.ico'}
%span

View File

@@ -6,7 +6,10 @@
%section.left
.ofn-logo
%a{href: main_app.root_path}
%img{src: ContentConfig.url_for(:logo_mobile), srcset: ContentConfig.url_for(:logo_mobile_svg), width: "75", height: "26"}
- if @white_label_logo&.variable?
= image_tag @white_label_distributor.white_label_logo_url(:mobile)
- else
%img{src: ContentConfig.url_for(:logo_mobile), srcset: ContentConfig.url_for(:logo_mobile_svg), width: "75", height: "26"}
%section.right{"ng-cloak" => true}
%span.cart-span{"ng-class" => "{ dirty: Cart.dirty || Cart.empty(), 'pure-dirty': Cart.dirty }"}

View File

@@ -0,0 +1,37 @@
import { Controller } from "stimulus";
export default class extends Controller {
static values = { targetId: String };
connect() {
this.element.addEventListener("change", this.change.bind(this));
if (this.element.checked) {
this.showTarget();
} else {
this.hideTarget();
}
}
disconnect() {
this.element.removeEventListener("change", this.change.bind(this));
}
change(event) {
// if the checkbox is checked, display the target
if (this.element.checked) {
this.showTarget();
} else {
this.hideTarget();
}
}
showTarget() {
let target = document.getElementById(this.targetIdValue);
target.style.display = "block";
}
hideTarget() {
let target = document.getElementById(this.targetIdValue);
target.style.display = "none";
}
}

View File

@@ -1161,6 +1161,10 @@ en:
white_label:
legend: "White Label"
hide_ofn_navigation: "Hide OFN navigation"
upload_logo: "Logo used in shopfront"
remove_logo: "Remove logo"
remove_logo_confirm: "Are you sure you want to remove this logo?"
remove_logo_success: "Logo removed"
actions:
edit_profile: Settings
properties: Properties

View File

@@ -0,0 +1,39 @@
/**
* @jest-environment jsdom
*/
import { Application } from "stimulus";
import checkbox_display_controller from "../../../app/webpacker/controllers/checkbox_display_controller";
describe("CheckboxDisplayController", () => {
beforeAll(() => {
const application = Application.start();
application.register("checkbox-display", checkbox_display_controller);
});
describe("#toggle", () => {
beforeEach(() => {
document.body.innerHTML = `<div >
<input type="checkbox" id="checkbox" data-controller="checkbox-display" data-checkbox-display-target-id-value="content" />
<div id="content">
content
</div>
</div>`;
});
it("show the content if the checkbox is checked, hide content either", () => {
const checkbox = document.getElementById("checkbox");
const content = document.getElementById("content");
expect(content.style.display).toBe("none");
checkbox.click();
expect(content.style.display).toBe("block");
checkbox.click();
expect(content.style.display).toBe("none");
});
});
});

View File

@@ -60,4 +60,17 @@ describe Api::Admin::EnterpriseSerializer do
end
end
end
context "for white label logo" do
let(:enterprise) { create(:distributor_enterprise, white_label_logo: black_logo_file) }
context "when there is a white label logo" do
it "includes URLs of image versions" do
serializer = Api::Admin::EnterpriseSerializer.new(enterprise)
expect(serializer.as_json[:white_label_logo]).to be_present
expect(serializer.as_json[:white_label_logo][:default]).to match(/logo-black\.png$/)
expect(serializer.as_json[:white_label_logo][:mobile]).to match(/logo-black\.png$/)
end
end
end
end

View File

@@ -10,6 +10,7 @@ describe '
include AuthenticationHelper
include ShopWorkflow
include UIComponentHelper
include FileHelper
it "viewing an enterprise" do
e = create(:enterprise)
@@ -645,6 +646,80 @@ describe '
expect(page).not_to have_link "White Label"
end
end
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
expect(page).not_to have_content "LOGO USED IN SHOPFRONT"
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
expect(page).to have_content "LOGO USED IN SHOPFRONT"
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
context "when white label is active via `hide_ofn_navigation`" do
before do
distributor1.update_attribute(:hide_ofn_navigation, true)
visit edit_admin_enterprise_path(distributor1)
within(".side_menu") do
click_link "White Label"
end
end
it "can updload the white label logo for the current shop" do
attach_file "enterprise_white_label_logo", white_logo_path
click_button 'Update'
expect(flash_message)
.to eq('Enterprise "First Distributor" has been successfully updated!')
expect(distributor1.reload.white_label_logo_blob.filename).to eq("logo-white.png")
end
context "when enterprise has a white label logo" do
before do
distributor1.update white_label_logo: white_logo_file
visit edit_admin_enterprise_path(distributor1)
within(".side_menu") do
click_link "White Label"
end
end
it "can remove the white label logo for the current shop" do
expect(page).to have_selector("img[src*='logo-white.png']")
expect(distributor1.white_label_logo).to be_attached
click_button "Remove"
within ".reveal-modal" do
click_button "Confirm"
end
expect(flash_message).to match(/Logo removed/)
distributor1.reload
expect(distributor1.white_label_logo).to_not be_attached
end
end
end
end
end
end
end

View File

@@ -5,6 +5,7 @@ require 'system_helper'
describe 'White label setting' do
include AuthenticationHelper
include ShopWorkflow
include FileHelper
let!(:distributor) { create(:distributor_enterprise, with_payment_and_shipping: true) }
let!(:shipping_method) { create(:shipping_method, distributors: [distributor]) }
@@ -18,6 +19,13 @@ describe 'White label setting' do
variants: [product.variants.first])
}
let!(:order) { create(:order, distributor: distributor, order_cycle: order_cycle) }
let(:complete_order) {
create(:order_with_credit_payment,
user: nil,
email: "guest@user.com",
distributor: distributor,
order_cycle: order_cycle)
}
let(:ofn_navigation) { 'ul.nav-main-menu' }
@@ -102,13 +110,6 @@ describe 'White label setting' do
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
@@ -162,6 +163,112 @@ describe 'White label setting' do
it_behaves_like "does not hide the OFN navigation"
end
end
context "manage the white_label_logo preference" do
context "when the distributor has no logo" do
before do
distributor.update_attribute(:hide_ofn_navigation, true)
end
shared_examples "shows/hide the right logos" do
it "shows the OFN logo on shop page" do
expect(page).to have_selector "img[src*='/default_images/ofn-logo.png']"
end
end
context "on shop page" do
before do
visit main_app.enterprise_shop_path(distributor)
end
it_behaves_like "shows/hide the right logos"
end
context "on cart page" do
before do
order.update_attribute(:state, 'cart')
order.line_items << create(:line_item, variant: product.variants.first)
set_order(order)
visit main_app.cart_path
end
it_behaves_like "shows/hide the right logos"
end
context "on checkout page" do
before do
order.update_attribute(:state, 'cart')
order.line_items << create(:line_item, variant: product.variants.first)
set_order(order)
visit checkout_path
end
it_behaves_like "shows/hide the right logos"
end
context "on order confirmation page" do
before do
visit order_path(complete_order, order_token: complete_order.token)
end
it_behaves_like "shows/hide the right logos"
end
end
context "when the distributor has a logo" do
before do
distributor.update_attribute(:hide_ofn_navigation, true)
distributor.update white_label_logo: white_logo_file
end
shared_examples "shows/hide the right logos" do
it "shows the white label logo on shop page" do
expect(page).to have_selector "img[src*='/logo-white.png']"
end
it "does not show the OFN logo on shop page" do
expect(page).not_to have_selector "img[src*='/default_images/ofn-logo.png']"
end
end
context "on shop page" do
before do
visit main_app.enterprise_shop_path(distributor)
end
it_behaves_like "shows/hide the right logos"
end
context "on cart page" do
before do
order.update_attribute(:state, 'cart')
order.line_items << create(:line_item, variant: product.variants.first)
set_order(order)
visit main_app.cart_path
end
it_behaves_like "shows/hide the right logos"
end
context "on checkout page" do
before do
order.update_attribute(:state, 'cart')
order.line_items << create(:line_item, variant: product.variants.first)
set_order(order)
visit checkout_path
end
it_behaves_like "shows/hide the right logos"
end
context "on order confirmation page" do
before do
visit order_path(complete_order, order_token: complete_order.token)
end
it_behaves_like "shows/hide the right logos"
end
end
end
end
context "when the white label feature is deactivated" do