diff --git a/app/controllers/admin/creator_settings_controller.rb b/app/controllers/admin/creator_settings_controller.rb index 6b1b22510..9c49d05b6 100644 --- a/app/controllers/admin/creator_settings_controller.rb +++ b/app/controllers/admin/creator_settings_controller.rb @@ -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 diff --git a/app/controllers/admin/settings/general_settings_controller.rb b/app/controllers/admin/settings/general_settings_controller.rb index 5d9b20586..be7747e72 100644 --- a/app/controllers/admin/settings/general_settings_controller.rb +++ b/app/controllers/admin/settings/general_settings_controller.rb @@ -6,6 +6,7 @@ module Admin SPECIAL_PARAMS_TO_ADD = %w[ credit_prices_in_cents meta_keywords + logo ].freeze def create diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index e319e49ce..1fc655c74 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -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 diff --git a/app/javascript/admin/controllers/config_controller.js b/app/javascript/admin/controllers/config_controller.js index a77144512..06fdf317f 100644 --- a/app/javascript/admin/controllers/config_controller.js +++ b/app/javascript/admin/controllers/config_controller.js @@ -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; + } } } diff --git a/app/javascript/admin/controllers/logo_upload_controller.js b/app/javascript/admin/controllers/logo_upload_controller.js index d254bdb6c..7d5ff00f9 100644 --- a/app/javascript/admin/controllers/logo_upload_controller.js +++ b/app/javascript/admin/controllers/logo_upload_controller.js @@ -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'), diff --git a/app/services/settings/general/upsert.rb b/app/services/settings/general/upsert.rb index 6bdbf9bd6..318aa7f35 100644 --- a/app/services/settings/general/upsert.rb +++ b/app/services/settings/general/upsert.rb @@ -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 diff --git a/app/uploaders/logo_uploader.rb b/app/uploaders/logo_uploader.rb index 2ee5ff33a..fd8e12410 100644 --- a/app/uploaders/logo_uploader.rb +++ b/app/uploaders/logo_uploader.rb @@ -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 diff --git a/app/views/admin/creator_settings/_form.html.erb b/app/views/admin/creator_settings/_form.html.erb index 79278f3ce..b428e0878 100644 --- a/app/views/admin/creator_settings/_form.html.erb +++ b/app/views/admin/creator_settings/_form.html.erb @@ -7,7 +7,7 @@ <%= f.text_field :community_name, placeholder: "Climbing Life", class: "crayons-textfield", required: true %> -<%= 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 } %>
<%= f.label :primary_brand_color_hex, class: "crayons-field__label" do %> diff --git a/app/views/admin/settings/_update_setting_button.html.erb b/app/views/admin/settings/_update_setting_button.html.erb index c466434c5..2b8fe6fad 100644 --- a/app/views/admin/settings/_update_setting_button.html.erb +++ b/app/views/admin/settings/_update_setting_button.html.erb @@ -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 %> diff --git a/app/views/admin/settings/forms/_images.html.erb b/app/views/admin/settings/forms/_images.html.erb index cbc538bc6..b373d2179 100644 --- a/app/views/admin/settings/forms/_images.html.erb +++ b/app/views/admin/settings/forms/_images.html.erb @@ -45,22 +45,26 @@ <% end %>
-
- <%= 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? %> - - <% end %> -
+ <% 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 %> +
+ <%= 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? %> + + <% end %> +
+ <% end %> - <%= render "update_setting_button", f: f %> + <%= render "update_setting_button", f: f, aria_label: "Update image settings" %> <% end %> diff --git a/app/views/admin/settings/show.html.erb b/app/views/admin/settings/show.html.erb index daca45c81..d5beff0a8 100644 --- a/app/views/admin/settings/show.html.erb +++ b/app/views/admin/settings/show.html.erb @@ -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" %> diff --git a/app/views/admin/shared/_logo_upload.html.erb b/app/views/admin/shared/_logo_upload.html.erb index 5ca9dd6e1..48f52ffbe 100644 --- a/app/views/admin/shared/_logo_upload.html.erb +++ b/app/views/admin/shared/_logo_upload.html.erb @@ -1,11 +1,14 @@
<%= f.label :logo, class: "crayons-field__label" do %> Logo -

Your logo will display in the upper left hand corner of your Forem. Upload a PNG or JPEG file.

<% end %> +

Your logo will display in the upper left hand corner of your Forem. Upload a PNG or JPEG file.

- <%= 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" } %> -
+ <%= 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" } %> +
+ <% if logo.present? %> + preview of logo selected + <% end %>
diff --git a/cypress/integration/creatorOnboardingFlows/creatorSettings.spec.js b/cypress/integration/creatorOnboardingFlows/creatorSettings.spec.js index ff3cdb7a9..425f61683 100644 --- a/cypress/integration/creatorOnboardingFlows/creatorSettings.spec.js +++ b/cypress/integration/creatorOnboardingFlows/creatorSettings.spec.js @@ -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', ''); + } + }); + }); +});