Using a Form Object that will persist for the Creator Settings Form (#15684)

* WIP: add a creatore settings form

* WIP: updat the controller to use the Creator Settings FOREM

* feat: use the creator settings form for the new action

* feat: add some default values for the new action

* a note about form data

* update the initiaize function to set some default values

* feat: update the form to use the model data

* feat: permit adn use the attributes within creator_settings_form

* update the flash error

* refactor: require and permit parameters

* chore: use booleans, set defaults and validate the form

* spec: update all the creator_settings tests

* chore: remove comment

* refactor: use self

* feat: aggregate failures'

* chore: remove the logo uploader in the controller

* refactor: update error handling

* feat: update the wasy the controller handles success and error

* chore: remove the resource errors

* feat: show flash message on new line

* fix: use a redirect so that we can get back to /new

* refactor: pass these values through as they seem to be caching whne setting them as default

* chore: change default values

* spec: update tests

* Fix CreatorSettingsForm specs

* fix: use a boolean for public

* spec: add another test for the success var

* fix: radio button labels to correspond + cyress specs

* spec: update based on new changes

* spec: update the params and the expected output

* spec: update the comments and status

* feat: no need for the initialize as we use Active Record Attributes

* feat: update the tac and coc to be persisted when ticked

* fix: amend spec

* blank space

* Message

Co-authored-by: Michael Kohl <me@citizen428.net>
This commit is contained in:
Ridhwana 2021-12-10 17:07:40 +02:00 committed by GitHub
parent 7511ee1fe2
commit e250b46ed2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 201 additions and 105 deletions

View file

@ -475,7 +475,9 @@ function initializePodcastPlayback() {
function handlePodcastMessages(event) {
const message = JSON.parse(event.detail);
if (message.namespace !== 'podcast') { return }
if (message.namespace !== 'podcast') {
return;
}
var currentState = currentAudioState();
switch (message.action) {
@ -506,7 +508,7 @@ function initializePodcastPlayback() {
if (deviceType !== 'web') {
Runtime.podcastMessage = (msg) => {
window.ForemMobile.injectNativeMessage('podcast', msg);
}
};
}
}

View file

@ -4,6 +4,14 @@ module Admin
invite_only_mode logo primary_brand_color_hex public].freeze
def new
@creator_settings_form = CreatorSettingsForm.new(
community_name: ::Settings::Community.community_name,
public: ::Settings::UserExperience.public,
invite_only_mode: ::Settings::Authentication.invite_only_mode,
primary_brand_color_hex: ::Settings::UserExperience.primary_brand_color_hex,
checked_code_of_conduct: current_user.checked_code_of_conduct,
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(",")
@ -11,27 +19,21 @@ module Admin
def create
extra_authorization
ActiveRecord::Base.transaction do
::Settings::Community.community_name = settings_params[:community_name]
::Settings::UserExperience.primary_brand_color_hex = settings_params[:primary_brand_color_hex]
::Settings::Authentication.invite_only_mode = settings_params[:invite_only]
::Settings::UserExperience.public = settings_params[:public]
if settings_params[:logo]
logo_uploader = upload_logo(settings_params[:logo])
::Settings::General.original_logo = logo_uploader.url
::Settings::General.resized_logo = logo_uploader.resized_logo.url
end
end
@creator_settings_form = CreatorSettingsForm.new(settings_params)
current_user.update!(
saw_onboarding: true,
checked_code_of_conduct: settings_params[:checked_code_of_conduct],
checked_terms_and_conditions: settings_params[:checked_terms_and_conditions],
checked_code_of_conduct: @creator_settings_form.checked_code_of_conduct,
checked_terms_and_conditions: @creator_settings_form.checked_terms_and_conditions,
)
redirect_to root_path
rescue StandardError => e
flash.now[:error] = e.message
render new_admin_creator_setting_path
@creator_settings_form.save
if @creator_settings_form.success
current_user.update!(saw_onboarding: true)
redirect_to root_path
else
flash[:error] = @creator_settings_form.errors.full_messages
redirect_to new_admin_creator_setting_path
end
end
private
@ -41,13 +43,7 @@ module Admin
end
def settings_params
params.permit(ALLOWED_PARAMS)
end
def upload_logo(image)
LogoUploader.new.tap do |uploader|
uploader.store!(image)
end
params.require(:creator_settings_form).permit(ALLOWED_PARAMS)
end
end
end

View file

@ -0,0 +1,53 @@
class CreatorSettingsForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :checked_code_of_conduct, :boolean, default: false
attribute :checked_terms_and_conditions, :boolean, default: false
attribute :community_name, :string
attribute :invite_only_mode, :boolean
attribute :logo
attribute :primary_brand_color_hex, :string
attribute :public, :boolean
validates :community_name,
:primary_brand_color_hex, presence: true
validates :checked_code_of_conduct, inclusion: { in: [true, false] }
validates :checked_terms_and_conditions, inclusion: { in: [true, false] }
validates :invite_only_mode, inclusion: { in: [true, false] }
validates :public, inclusion: { in: [true, false] }
attr_accessor :success
def save
if valid?
begin
::Settings::Community.community_name = community_name
::Settings::UserExperience.primary_brand_color_hex = primary_brand_color_hex
::Settings::Authentication.invite_only_mode = invite_only_mode
::Settings::UserExperience.public = public
if logo
logo_uploader = upload_logo(logo)
::Settings::General.original_logo = logo_uploader.url
::Settings::General.resized_logo = logo_uploader.resized_logo.url
end
@success = true
rescue StandardError => e
errors.add(:base, e.message)
@success = false
end
else
@success = false
end
end
private
def upload_logo(image)
LogoUploader.new.tap do |uploader|
uploader.store!(image)
end
end
end

View file

@ -52,15 +52,8 @@ export class Navigation extends Component {
}
render() {
const {
next,
prev,
hideNext,
hidePrev,
disabled,
canSkip,
className,
} = this.props;
const { next, prev, hideNext, hidePrev, disabled, canSkip, className } =
this.props;
return (
<nav
class={`onboarding-navigation${

View file

@ -10,7 +10,7 @@ module Settings
setting(
:community_name,
type: :string,
default: ApplicationConfig["COMMUNITY_NAME"] || "New Forem",
default: ApplicationConfig["COMMUNITY_NAME"],
validates: {
format: {
with: /\A[^[<|>]]+\Z/,

View file

@ -21,7 +21,7 @@ module Settings
}
# a non-public forem will redirect all unauthenticated pages to the registration page.
# a public forem could have more fine-grained authentication (listings ar private etc.) in future
setting :public, type: :boolean, default: 0
setting :public, type: :boolean, default: true
setting :tag_feed_minimum_score, type: :integer, default: 0
setting :default_locale, type: :string, default: "en"
setting :display_in_directory, type: :boolean, default: true

View file

@ -1,16 +1,16 @@
<div class="crayons-field mt-6 align-left">
<%= label_tag :community_name, class: "crayons-field__label" do %>
<%= f.label :community_name, class: "crayons-field__label" do %>
Community name
<span class="crayons-field__required crayons-tooltip" data-tooltip="This will set the primary name for your Forem" aria-describedby="community-name-subtitle"></span>
<p id="community-name-subtitle" class="crayons-field__description">Used as the primary name for your Forem.</p>
<% end %>
<%= text_field_tag :community_name, "", placeholder: "Climbing Life", class: "crayons-textfield", required: true %>
<%= 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 } %>
<%= render partial: "admin/shared/logo_upload", locals: { allowed_types: @logo_allowed_types, max_file_size: @max_file_size, f: f } %>
<div class="crayons-field mt-6 align-left">
<%= label_tag :primary_brand_color_hex, class: "crayons-field__label" do %>
<%= f.label :primary_brand_color_hex, class: "crayons-field__label" do %>
Brand color
<span class="crayons-field__required crayons-tooltip" data-tooltip="This will set the accent color for buttons, links, etc. on your Forem" aria-describedby="color-selector-subtitle"></span>
<p id="color-selector-subtitle" class="crayons-field__description">This will be the "accent" color used for buttons, links, etc.</p>
@ -18,22 +18,20 @@
<div class="flex w-100 m:w-50 crayons-field">
<div class="flex">
<%= text_field_tag :primary_brand_color_hex,
::Settings::UserExperience.primary_brand_color_hex,
pattern: "^#+([a-fA-F0-9]{6})$",
title: "Provide a valid HEX Color or pick your color from the color picker.",
placeholder: ::Settings::UserExperience.primary_brand_color_hex,
class: "crayons-textfield js-color-field",
"data-action": "change->creator-settings#handleValidationsAndUpdates",
"data-creator-settings-target": "brandColor",
"aria-describedby": "color-contrast-error" %>
<%= color_field_tag :primary_brand_color_hex,
::Settings::UserExperience.primary_brand_color_hex,
pattern: "^#+([a-fA-F0-9]{6})$",
placeholder: ::Settings::UserExperience.primary_brand_color_hex,
class: "crayons-color-selector js-color-field ml-2",
required: true,
"data-action": "change->creator-settings#handleValidationsAndUpdates" %>
<%= f.text_field :primary_brand_color_hex,
pattern: "^#+([a-fA-F0-9]{6})$",
title: "Provide a valid HEX Color or pick your color from the color picker.",
placeholder: ::Settings::UserExperience.primary_brand_color_hex,
class: "crayons-textfield js-color-field",
"data-action": "change->creator-settings#handleValidationsAndUpdates",
"data-creator-settings-target": "brandColor",
"aria-describedby": "color-contrast-error" %>
<%= f.color_field :primary_brand_color_hex,
pattern: "^#+([a-fA-F0-9]{6})$",
placeholder: ::Settings::UserExperience.primary_brand_color_hex,
class: "crayons-color-selector js-color-field ml-2",
required: true,
"data-action": "change->creator-settings#handleValidationsAndUpdates" %>
</div>
</div>
<div id="color-contrast-error" data-creator-settings-target="colorContrastError" aria-live="polite" class="mt-1 color-accent-danger"></div>
@ -44,12 +42,12 @@
<legend class="crayons-field__label mb-2">Who can join this community?</legend>
<div class="flex gap-3">
<div>
<%= radio_button_tag :invite_only_mode, "0", false, class: "crayons-radio" %>
<label for="invite_only_mode_0">Everyone</label>
<%= f.radio_button :invite_only_mode, false, class: "crayons-radio", role: "radio" %>
<%= label_tag "creator_settings_form_invite_only_mode_false", "Everyone" %>
</div>
<div>
<%= radio_button_tag :invite_only_mode, "1", true, class: "crayons-radio" %>
<label for="invite_only_mode_1">Invite only</label>
<%= f.radio_button :invite_only_mode, true, class: "crayons-radio", role: "radio" %>
<%= label_tag "creator_settings_form_invite_only_mode_true", "Members Only" %>
</div>
</div>
</fieldset>
@ -60,12 +58,12 @@
<legend class="crayons-field__label mb-2">Who can view content in this community?</legend>
<div class="flex gap-3">
<div>
<%= radio_button_tag :public, "1", true, class: "crayons-radio" %>
<label for="public_0">Everyone</label>
<%= f.radio_button :public, true, class: "crayons-radio", role: "radio" %>
<%= label_tag "creator_settings_form_public_true", "Everyone" %>
</div>
<div>
<%= radio_button_tag :public, "0", false, class: "crayons-radio" %>
<label for="public_1">Members only</label>
<%= f.radio_button :public, false, class: "crayons-radio", role: "radio" %>
<%= label_tag "creator_settings_form_public_false", "Members Only" %>
</div>
</div>
</fieldset>
@ -77,13 +75,11 @@
<div>
<p class="crayons-field__description">You will have the opportunity to establish the Code of Conduct and Terms and Conditions for the users of your Forem during the setup process.</p>
<div class="mb-2">
<%= hidden_field_tag :checked_code_of_conduct, "0" %>
<%= check_box_tag :checked_code_of_conduct, "1", false, class: "crayons-checkbox", required: true %>
<%= f.check_box :checked_code_of_conduct, class: "crayons-checkbox", required: true %>
<label for="checked_code_of_conduct">I agree to uphold our <a href="/code-of-conduct" target="code-of-conduct">Code of Conduct</a>.</label>
</div>
<div>
<%= hidden_field_tag :checked_terms_and_conditions, "0" %>
<%= check_box_tag :checked_terms_and_conditions, "1", false, class: "crayons-checkbox", required: true %>
<%= f.check_box :checked_terms_and_conditions, class: "crayons-checkbox", required: true %>
<label for="checked_terms_and_conditions">I agree to our <a href="/terms" target="terms-and-conditions">Terms and Conditions</a>.</label>
</div>
</div>

View file

@ -7,37 +7,23 @@
<div aria-live="assertive">
<% if flash[:error] %>
<div class="crayons-notice crayons-notice--danger mb-6" role="alert">
<%= flash[:error] %>
<% flash[:error].each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<% end %>
</div>
<%= form_tag(admin_creator_settings_path, method: "post", multipart: true, class: "relative z-elevate p-4", "data-action": "submit->creator-settings#formValidations") do %>
<% if defined?(resource) && resource&.errors&.any? %>
<div class="crayons-card crayons-card--secondary crayons-notice crayons-notice--danger" role="alert" data-testid="signup-errors">
<div class="crayons-card__header">
<h1 class="crayons-card__headline">
Whoops, we found <%= pluralize(resource.errors.size, "problem") %>
</h1>
</div>
<div class="crayons-card__body">
<ul>
<% resource.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
</div>
<% end %>
<%= form_with url: admin_creator_settings_path, model: @creator_settings_form, local: true, method: "post", multipart: true, class: "relative z-elevate p-4", "data-action": "submit->creator-settings#formValidations" do |f| %>
<div class="align-center">
<p class="fs-3xl fw-bold">Lovely! Let's set up your Forem.</p>
<p class="color-base-70 fs-xl">No stress, you can always change it later.</p>
</div>
<br>
<%= render "form" %>
<%= render "form", creator_settings_form: @creator_settings_form, f: f %>
<div class="crayons-field mt-6 align-left">
<%= submit_tag "Finish", class: "crayons-btn btn--primary" %>
<%= f.submit "Finish", class: "crayons-btn btn--primary" %>
</div>
<% if @help_url %>
<%= render "shared/help_icon" %>

View file

@ -1,10 +1,10 @@
<div class="crayons-field mt-6 align-left" data-controller="logo-upload">
<%= label_tag :logo, class: "crayons-field__label" do %>
<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 %>
<div class="flex flex-1 gap-4">
<%= file_field_tag :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" }, aria: { describedby: "logo-subtitle" } %>
<div data-logo-upload-target="previewLogo">
</div>
</div>

View file

@ -161,6 +161,7 @@ describe('Creator Settings Page', () => {
);
cy.findByRole('textbox', { name: /community name/i })
.clear()
.focus()
.type('Climbing Life')
.should('have.css', 'border-color', rgbColor);

View file

@ -0,0 +1,64 @@
require "rails_helper"
RSpec.describe CreatorSettingsForm, type: :model do
describe "validations" do
it { is_expected.to validate_presence_of(:community_name) }
it { is_expected.to validate_presence_of(:primary_brand_color_hex) }
end
describe "attributes" do
it "has the correct attribute names" do
expect(described_class.attribute_names).to eq(%w[checked_code_of_conduct checked_terms_and_conditions
community_name invite_only_mode logo
primary_brand_color_hex public])
end
it "sets default values", :aggregate_failures do
attributes = described_class.new.attributes
expect(attributes["checked_code_of_conduct"]).to be(false)
expect(attributes["checked_terms_and_conditions"]).to be(false)
end
end
describe "initializer" do
it "updates the values when we pass an attribute as a param", :aggregate_failures do
attributes = described_class.new(
primary_brand_color_hex: "#0a0a0a",
invite_only_mode: false,
).attributes
expect(attributes["primary_brand_color_hex"]).to eq("#0a0a0a")
expect(attributes["invite_only_mode"]).to eq(false)
end
end
describe "#save" do
let(:current_user) { create(:user) }
# rubocop:disable RSpec/ExampleLength
it "saves the updated attributes to the correct Settings values", :aggregate_failures do
# NOTE: override the profile migration hack from rails_helper.rb
# TODO: remove this once we remove it in rails_helper.rb
allow(Settings::UserExperience).to receive(:public).and_call_original
creator_settings_form = described_class.new(
checked_code_of_conduct: true,
checked_terms_and_conditions: true,
community_name: "Climbing Life",
invite_only_mode: false,
public: false,
primary_brand_color_hex: "#a81adb",
)
expect(creator_settings_form.valid?).to be(true)
creator_settings_form.save
expect(creator_settings_form.success).to be(true)
expect(Settings::Community.community_name).to eq("Climbing Life")
expect(Settings::UserExperience.primary_brand_color_hex).to eq("#a81adb")
expect(Settings::UserExperience.public).to eq(false)
expect(Settings::Authentication.invite_only_mode).to eq(false)
expect(current_user.checked_code_of_conduct).to eq(true)
expect(current_user.checked_terms_and_conditions).to eq(true)
end
# rubocop:enable RSpec/ExampleLength
end
end

View file

@ -1,14 +1,18 @@
require "rails_helper"
RSpec.describe "/creator_settings/new", type: :request do
let!(:creator) { create(:user, :creator) }
let!(:current_user) { create(:user, :creator) }
let!(:non_admin_user) { create(:user) }
let(:params) do
{ community_name: "Climbing Life",
logo_svg: "https://dummyimage.com/300x300.png",
primary_brand_color_hex: "000000",
public: true,
invite_only: false }
{ creator_settings_form:
{
checked_code_of_conduct: true,
checked_terms_and_conditions: true,
community_name: "Climbing Life",
invite_only_mode: false,
primary_brand_color_hex: "#000000",
public: true
} }
end
before do
@ -18,7 +22,7 @@ RSpec.describe "/creator_settings/new", type: :request do
describe "GET /admin/creator_settings/new" do
before do
sign_in creator
sign_in current_user
get new_admin_creator_setting_path
end
@ -46,16 +50,17 @@ RSpec.describe "/creator_settings/new", type: :request do
describe "POST /admin/creator_settings/new" do
before do
sign_in creator
sign_in current_user
get new_admin_creator_setting_path
end
it "allows a creator to successfully fill out the creator setup form", :aggregate_failures do
post admin_creator_settings_path, params: params
expect(creator.saw_onboarding).to eq(true)
expect(creator.checked_code_of_conduct).to eq(true)
expect(creator.checked_terms_and_conditions).to eq(true)
expect(response).to have_http_status(:ok)
expect(current_user.saw_onboarding).to eq(true)
expect(current_user.checked_code_of_conduct).to eq(true)
expect(current_user.checked_terms_and_conditions).to eq(true)
expect(response).to redirect_to(:root).and have_http_status(:found)
end
end
end