mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-27 01:43:22 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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, */*"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
12
app/reflexes/concerns/enterprise_concern.rb
Normal file
12
app/reflexes/concerns/enterprise_concern.rb
Normal 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
|
||||
24
app/reflexes/white_label_reflex.rb
Normal file
24
app/reflexes/white_label_reflex.rb
Normal 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
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }"}
|
||||
|
||||
37
app/webpacker/controllers/checkbox_display_controller.js
Normal file
37
app/webpacker/controllers/checkbox_display_controller.js
Normal 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";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user