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:
parent
cde4e08534
commit
6df9309284
13 changed files with 154 additions and 30 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ module Admin
|
|||
SPECIAL_PARAMS_TO_ADD = %w[
|
||||
credit_prices_in_cents
|
||||
meta_keywords
|
||||
logo
|
||||
].freeze
|
||||
|
||||
def create
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 %>
|
||||
|
|
|
|||
|
|
@ -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 %>
|
||||
|
|
|
|||
|
|
@ -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 %>
|
||||
|
|
|
|||
|
|
@ -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" %>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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', '');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue