Added the logo upload to the admin -> customization -> config -> images section. (#15729)

Co-authored-by: Michael Kohl <citizen428@forem.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Suzanne Aitchison <suzanne@forem.com>
Co-authored-by: Mac Siri <mac@forem.com>
Co-authored-by: Ridhwana <Ridhwana.Khan16@gmail.com>
This commit is contained in:
Nick Taylor 2021-12-15 14:10:27 -05:00 committed by GitHub
parent cde4e08534
commit 6df9309284
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 154 additions and 30 deletions

View file

@ -15,8 +15,7 @@ module Admin
checked_terms_and_conditions: current_user.checked_terms_and_conditions,
)
@max_file_size = LogoUploader::MAX_FILE_SIZE
@logo_allowed_types = (LogoUploader::CONTENT_TYPE_ALLOWLIST +
LogoUploader::EXTENSION_ALLOWLIST.map { |extension| ".#{extension}" }).join(",")
@logo_allowed_types = LogoUploader::ALLOWED_TYPES
end
def create

View file

@ -6,6 +6,7 @@ module Admin
SPECIAL_PARAMS_TO_ADD = %w[
credit_prices_in_cents
meta_keywords
logo
].freeze
def create

View file

@ -10,6 +10,8 @@ module Admin
layout "admin"
def show
@logo_allowed_types = LogoUploader::ALLOWED_TYPES
@logo_max_file_size = LogoUploader::MAX_FILE_SIZE
@confirmation_text =
"My username is @#{current_user.username} and this action is 100% safe and appropriate."
end

View file

@ -102,6 +102,8 @@ export default class ConfigController extends Controller {
async updateConfigurationSettings(event) {
event.preventDefault();
let errored = false;
try {
const body = new FormData(event.target);
const response = await fetch(event.target.action, {
@ -117,9 +119,35 @@ export default class ConfigController extends Controller {
const outcome = await response.json();
errored = outcome.error != null;
displaySnackbar(outcome.message ?? outcome.error);
} catch (err) {
displaySnackbar(err.message);
errored = true;
displaySnackbar('An error occurred. Please try again.');
} finally {
// Only update the site logo in the header if the new logo is uploaded successfully.
if (!errored && event.target.elements.settings_general_logo) {
this.updateLogo();
}
}
}
/**
* Updates the site logo in the header with the same URL as the preview logo.
*/
updateLogo() {
const previewLogo = document.querySelector(
'#logo-upload-preview .site-logo__img',
);
if (!previewLogo) {
return;
}
for (const logo of document.querySelectorAll('.site-logo__img')) {
if (logo !== previewLogo) {
logo.src = previewLogo.src;
}
}
}

View file

@ -31,7 +31,7 @@ export class LogoUploadController extends Controller {
const imageURL = reader.result;
const image = document.createElement('img');
image.src = imageURL;
image.className = 'site-logo';
image.className = 'site-logo__img';
// The logo preview image is purely visual so no need to communicate this to assistive technology.
image.alt = 'preview of logo selected';
@ -43,10 +43,14 @@ export class LogoUploadController extends Controller {
target: { width, height },
} = event;
this.previewLogoTarget.replaceChild(
image,
this.previewLogoTarget.firstChild,
);
if (this.previewLogoTarget.firstElementChild) {
this.previewLogoTarget.replaceChild(
image,
this.previewLogoTarget.firstElementChild,
);
} else {
this.previewLogoTarget.appendChild(image);
}
const maxLogoPreviewWidth = parseInt(
getComputedStyle(image).getPropertyValue('--max-width'),

View file

@ -5,7 +5,15 @@ module Settings
TAG_PARAMS = %w[sidebar_tags suggested_tags].freeze
def self.call(settings)
cleaned_params = clean_params(settings)
params_to_clean = settings.except(:logo)
if settings[:logo].present?
logo_uploader = upload_logo(settings[:logo])
logo_settings = { original_logo: logo_uploader.url, resized_logo: logo_uploader.resized_logo.url }
params_to_clean = params_to_clean.merge(logo_settings)
end
cleaned_params = clean_params(params_to_clean)
result = ::Settings::Upsert.call(cleaned_params, ::Settings::General)
return result unless result.success?
@ -29,6 +37,14 @@ module Settings
tags = Settings::General.suggested_tags + Settings::General.sidebar_tags
Tag.find_or_create_all_with_like_by_name(tags)
end
def self.upload_logo(image)
LogoUploader.new.tap do |uploader|
uploader.store!(image)
end
end
private_class_method :upload_logo
end
end
end

View file

@ -4,6 +4,7 @@ class LogoUploader < BaseUploader
EXTENSION_ALLOWLIST = %w[png jpg jpeg jpe].freeze
IMAGE_TYPE_ALLOWLIST = %i[png jpg jpeg jpe].freeze
CONTENT_TYPE_ALLOWLIST = %w[image/png image/jpg image/jpeg].freeze
ALLOWED_TYPES = (CONTENT_TYPE_ALLOWLIST + EXTENSION_ALLOWLIST.map { |extension| ".#{extension}" }).join(",")
def store_dir
STORE_DIRECTORY

View file

@ -7,7 +7,7 @@
<%= f.text_field :community_name, placeholder: "Climbing Life", class: "crayons-textfield", required: true %>
</div>
<%= render partial: "admin/shared/logo_upload", locals: { allowed_types: @logo_allowed_types, max_file_size: @max_file_size, f: f } %>
<%= render partial: "admin/shared/logo_upload", locals: { allowed_types: @logo_allowed_types, max_file_size: @max_file_size, f: f, logo: Settings::General.resized_logo } %>
<div class="crayons-field mt-6 align-left">
<%= f.label :primary_brand_color_hex, class: "crayons-field__label" do %>

View file

@ -1,3 +1,3 @@
<% if current_user.has_role?(:super_admin) %>
<%= f.submit "Update Settings", class: "crayons-btn mt-4", data: { disable_with: false } %>
<%= f.submit "Update Settings", class: "crayons-btn mt-4", aria: { label: local_assigns[:aria_label] }, data: { disable_with: false } %>
<% end %>

View file

@ -45,22 +45,26 @@
<% end %>
</div>
<div class="crayons-field">
<%= admin_config_label :logo_svg %>
<%= admin_config_description Constants::Settings::General::DETAILS[:logo_svg][:description] %>
<%= f.text_area :logo_svg,
class: "crayons-textfield",
value: Settings::General.logo_svg,
rows: 6,
placeholder: Constants::Settings::General::DETAILS[:logo_svg][:placeholder] %>
<% if Settings::General.logo_svg.present? %>
<div class="site-logo">
<%= Settings::General.logo_svg.html_safe %>
</div>
<% end %>
</div>
<% if FeatureFlag.enabled?(:creator_onboarding) %>
<%= render partial: "admin/shared/logo_upload", locals: { f: f, allowed_types: logo_allowed_types, max_file_size: logo_max_file_size, logo: Settings::General.resized_logo } %>
<% else %>
<div class="crayons-field">
<%= admin_config_label :logo_svg %>
<%= admin_config_description Constants::Settings::General::DETAILS[:logo_svg][:description] %>
<%= f.text_area :logo_svg,
class: "crayons-textfield",
value: Settings::General.logo_svg,
rows: 6,
placeholder: Constants::Settings::General::DETAILS[:logo_svg][:placeholder] %>
<% if Settings::General.logo_svg.present? %>
<div class="site-logo">
<%= Settings::General.logo_svg.html_safe %>
</div>
<% end %>
</div>
<% end %>
</fieldset>
<%= render "update_setting_button", f: f %>
<%= render "update_setting_button", f: f, aria_label: "Update image settings" %>
</div>
</div>
<% end %>

View file

@ -66,7 +66,7 @@
<%= render partial: "forms/emails" %>
<%= render partial: "forms/email_digest_frequency" %>
<%= render partial: "forms/google_analytics" %>
<%= render partial: "forms/images" %>
<%= render partial: "forms/images", locals: { logo_allowed_types: @logo_allowed_types, logo_max_file_size: @logo_max_file_size } %>
<%= render partial: "forms/mascot" %>
<%= render partial: "forms/meta_keywords" %>
<%= render partial: "forms/monetization" %>

View file

@ -1,11 +1,14 @@
<div class="crayons-field mt-6 align-left" data-controller="logo-upload">
<%= f.label :logo, class: "crayons-field__label" do %>
Logo
<p id="logo-subtitle" class="crayons-field__description">Your logo will display in the upper left hand corner of your Forem. Upload a PNG or JPEG file.</p>
<% end %>
<p id="logo-subtitle" class="crayons-field__description">Your logo will display in the upper left hand corner of your Forem. Upload a PNG or JPEG file.</p>
<div class="flex flex-1 gap-4">
<%= f.file_field :logo, accept: allowed_types.to_s, aria_describedby: "logo-subtitle", data: { "max-file-size-mb": max_file_size.to_s, action: "change->logo-upload#previewLogo" }, aria: { describedby: "logo-subtitle" } %>
<div data-logo-upload-target="previewLogo">
<%= f.file_field :logo, accept: allowed_types.to_s, aria_describedby: "logo-subtitle", data: { "max-file-size-mb": max_file_size.to_s, action: "change->logo-upload#previewLogo" } %>
<div data-logo-upload-target="previewLogo" id="logo-upload-preview">
<% if logo.present? %>
<img class="site-logo__img" src="<%= logo %>" alt="preview of logo selected">
<% end %>
</div>
</div>
</div>

View file

@ -168,3 +168,69 @@ describe('Creator Settings Page', () => {
});
});
});
describe('Admin -> Customization -> Config -> Images', () => {
// NOTE: These tests are here for the moment as we still haven't figured out how to enable
// feature flags in the context of E2E tests.
// These test should really live in the seeded flows for admin.
beforeEach(() => {
cy.testSetup();
cy.fixture('users/creatorUser.json').as('creator');
cy.get('@creator').then((creator) => {
cy.loginCreator(creator);
});
});
it('should upload an image from the admin -> customization -> config -> images section', () => {
cy.visit(`/admin/customization/config`);
cy.findByRole('heading', { name: /Images/i }).click();
cy.findByLabelText(/^Logo$/i).attachFile('/images/admin-image.png');
cy.findByRole('button', { name: /Update image settings/i }).click();
cy.findByTestId('snackbar')
.should('be.visible')
.should('have.text', 'Successfully updated settings.');
cy.findByRole('img', { name: /preview of logo selected/i }).then(
([previewImage]) => {
cy.findAllByRole('img', { name: /DEV\(local\)/i }).then((images) => {
// Some images being picked up are SVGs which we don't want to check
const logoImages = [...images].filter(
(image) => image.tagName === 'IMG' && image !== previewImage,
);
// Ensure any site logos have te same URL as the preview logo
for (const image of logoImages) {
cy.get(image).should('have.attr', 'src', previewImage.src);
}
});
},
);
});
it('should not upload an image from the admin -> customization -> config -> images section', () => {
cy.visit(`/admin/customization/config`);
cy.findByRole('heading', { name: /Images/i }).click();
cy.findByRole('button', { name: /Update image settings/i }).click();
cy.findByTestId('snackbar')
.should('be.visible')
.should('have.text', 'Successfully updated settings.');
cy.findByRole('img', { name: /preview of logo selected/i }).should(
'not.exist',
);
cy.findAllByRole('img', { name: /DEV\(local\)/i }).then((images) => {
// Some images being picked up are SVGs which we don't want to check
const logoImages = [...images].filter((image) => image.tagName === 'IMG');
// Ensure that the logo URL hasn't been changed.
for (const image of logoImages) {
cy.get(image).should('have.attr', 'src', '');
}
});
});
});