diff --git a/app/assets/javascripts/discourse/app/static/wizard/components/fields/index.js b/app/assets/javascripts/discourse/app/static/wizard/components/fields/index.js index deb2152be4e..6127a0281dc 100644 --- a/app/assets/javascripts/discourse/app/static/wizard/components/fields/index.js +++ b/app/assets/javascripts/discourse/app/static/wizard/components/fields/index.js @@ -2,6 +2,7 @@ import Checkbox from "./checkbox"; import Checkboxes from "./checkboxes"; import Dropdown from "./dropdown"; import Image from "./image"; +import Radio from "./radio"; import StylingPreview from "./styling-preview"; import Text from "./text"; @@ -12,4 +13,5 @@ export default { dropdown: Dropdown, image: Image, text: Text, + radio: Radio, }; diff --git a/app/assets/javascripts/discourse/app/static/wizard/components/fields/radio.gjs b/app/assets/javascripts/discourse/app/static/wizard/components/fields/radio.gjs new file mode 100644 index 00000000000..51f6db63547 --- /dev/null +++ b/app/assets/javascripts/discourse/app/static/wizard/components/fields/radio.gjs @@ -0,0 +1,72 @@ +import Component from "@glimmer/component"; +import { hash } from "@ember/helper"; +import { on } from "@ember/modifier"; +import { action, set } from "@ember/object"; +import PluginOutlet from "discourse/components/plugin-outlet"; +import concatClass from "discourse/helpers/concat-class"; +import withEventValue from "discourse/helpers/with-event-value"; +import icon from "discourse-common/helpers/d-icon"; + +export default class Radio extends Component { + constructor() { + super(...arguments); + + this._setSelected(); + } + + get field() { + return this.args.field; + } + + @action + selectionChanged(input) { + this.field.value = input; + this._setSelected(); + } + + _setSelected() { + for (let choice of this.field.choices) { + set(choice, "selected", this.field.value === choice.id); + } + } + + + + {{#each @field.choices as |c|}} + + + + + + {{#if c.icon}} + {{icon c.icon}} + {{/if}} + {{c.label}} + + + + + + + {{/each}} + + +} diff --git a/app/assets/javascripts/discourse/app/static/wizard/components/wizard-field.gjs b/app/assets/javascripts/discourse/app/static/wizard/components/wizard-field.gjs index a2f96fddfe7..b2bd553a47d 100644 --- a/app/assets/javascripts/discourse/app/static/wizard/components/wizard-field.gjs +++ b/app/assets/javascripts/discourse/app/static/wizard/components/wizard-field.gjs @@ -3,6 +3,7 @@ import { assert } from "@ember/debug"; import { hash } from "@ember/helper"; import { dasherize } from "@ember/string"; import { htmlSafe } from "@ember/template"; +import { or } from "truth-helpers"; import PluginOutlet from "discourse/components/plugin-outlet"; import fields from "./fields"; @@ -42,7 +43,7 @@ export default class WizardFieldComponent extends Component { - {{#if @field.label}} + {{#if (or @field.label @field.description)}} {{@field.label}} diff --git a/app/assets/stylesheets/wizard.scss b/app/assets/stylesheets/wizard.scss index 4082bc7a9c6..3b95ddba997 100644 --- a/app/assets/stylesheets/wizard.scss +++ b/app/assets/stylesheets/wizard.scss @@ -504,7 +504,8 @@ body.wizard { &__description { color: var(--primary-high); - font-size: var(--font-down-1); + font-size: var(--font-0); + font-weight: normal; margin: 0.25em 0 0.5em 0; a { @@ -630,6 +631,58 @@ body.wizard { top: 2px; } + &__radio { + position: absolute; + visibility: hidden; + } + + &__radio-choices { + align-items: stretch; + display: flex; + gap: 1em; + @include breakpoint(mobile-extra-large) { + flex-direction: column; + } + } + + &__radio-choice { + flex-basis: 0; + flex-grow: 1; + display: flex; + + &.--selected { + .wizard-container__label { + background-color: var(--tertiary-very-low); + border-color: var(--tertiary-high); + border-width: 2px; + margin: 0; + } + } + + .wizard-container__label { + border-radius: 4px; + border: 1px solid var(--primary-low-mid); + flex-grow: 1; + margin: 1px 0; + } + + label { + align-content: center; + cursor: pointer; + display: flex; + flex-direction: column; + flex-wrap: wrap; + font-weight: normal; + padding: 1em; + text-align: center; + + .svg-icon { + margin-bottom: 0.5em; + width: 100%; + } + } + } + label .svg-icon { top: 2px; } diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 4460512cd66..24d1489856c 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -5345,18 +5345,32 @@ en: label: "Language" privacy: - title: "Member experience" + title: "Member access" fields: login_required: - placeholder: "Private" - extra_description: "Only logged in users can access this community" + label: "Visibility" + description: "Is your community public or private?" + choices: + public: + label: "Public" + private: + label: "Private" invite_only: - placeholder: "Invite only" - extra_description: "Users must be invited by trusted users or staff, otherwise users can sign up on their own" + label: "Registration" + description: "How can members join this community?" + choices: + sign_up: + label: "Sign up" + invite_only: + label: "Invite only" must_approve_users: - placeholder: "Require approval" - extra_description: "Users must be approved by staff" + description: "Do you want to approve member accounts?" + choices: + "no": + label: "No, new members can join immediately" + "yes": + label: "Yes, new members must be approved by moderators" chat_enabled: placeholder: "Enable chat" extra_description: "Engage with your members in real time" diff --git a/lib/wizard/builder.rb b/lib/wizard/builder.rb index 35aa5721d49..07eddc89ab4 100644 --- a/lib/wizard/builder.rb +++ b/lib/wizard/builder.rb @@ -59,41 +59,38 @@ class Wizard @wizard.append_step("privacy") do |step| step.emoji = "hugs" + step.add_field( id: "login_required", - type: "checkbox", - icon: "unlock", - value: SiteSetting.login_required, - ) + type: "radio", + value: SiteSetting.login_required ? "private" : "public", + ) do |field| + field.add_choice("public") + field.add_choice("private") + end step.add_field( id: "invite_only", - type: "checkbox", - icon: "user-plus", - value: SiteSetting.invite_only, - ) + type: "radio", + value: SiteSetting.invite_only ? "invite_only" : "sign_up", + ) do |field| + field.add_choice("sign_up", icon: "user-plus") + field.add_choice("invite_only", icon: "paper-plane") + end step.add_field( id: "must_approve_users", - type: "checkbox", - icon: "user-shield", - value: SiteSetting.must_approve_users, - ) - - if defined?(::Chat) - step.add_field( - id: "chat_enabled", - type: "checkbox", - icon: "d-chat", - value: SiteSetting.chat_enabled, - ) + type: "radio", + value: SiteSetting.must_approve_users ? "yes" : "no", + ) do |field| + field.add_choice("no") + field.add_choice("yes") end step.on_update do |updater| - updater.update_setting(:login_required, updater.fields[:login_required]) - updater.update_setting(:invite_only, updater.fields[:invite_only]) - updater.update_setting(:must_approve_users, updater.fields[:must_approve_users]) - updater.update_setting(:chat_enabled, updater.fields[:chat_enabled]) if defined?(::Chat) + updater.update_setting(:login_required, updater.fields[:login_required] == "private") + updater.update_setting(:invite_only, updater.fields[:invite_only] == "invite_only") + updater.update_setting(:must_approve_users, updater.fields[:must_approve_users] == "yes") end end diff --git a/lib/wizard/step.rb b/lib/wizard/step.rb index ff675d18dfb..09e059e1478 100644 --- a/lib/wizard/step.rb +++ b/lib/wizard/step.rb @@ -14,6 +14,7 @@ class Wizard field = Field.new(attrs) field.step = self @fields << field + yield field if block_given? field end diff --git a/spec/lib/wizard/step_updater_spec.rb b/spec/lib/wizard/step_updater_spec.rb index 177865354f5..639f3605d2c 100644 --- a/spec/lib/wizard/step_updater_spec.rb +++ b/spec/lib/wizard/step_updater_spec.rb @@ -51,9 +51,9 @@ RSpec.describe Wizard::StepUpdater do updater = wizard.create_updater( "privacy", - login_required: false, - invite_only: false, - must_approve_users: false, + login_required: "public", + invite_only: "sign_up", + must_approve_users: "no", ) updater.update expect(updater.success?).to eq(true) @@ -67,9 +67,9 @@ RSpec.describe Wizard::StepUpdater do updater = wizard.create_updater( "privacy", - login_required: true, - invite_only: true, - must_approve_users: true, + login_required: "private", + invite_only: "invite_only", + must_approve_users: "yes", ) updater.update expect(updater.success?).to eq(true) diff --git a/spec/lib/wizard/wizard_builder_spec.rb b/spec/lib/wizard/wizard_builder_spec.rb index 7a428aa0496..5289b9c05b2 100644 --- a/spec/lib/wizard/wizard_builder_spec.rb +++ b/spec/lib/wizard/wizard_builder_spec.rb @@ -82,15 +82,11 @@ RSpec.describe Wizard::Builder do count = defined?(::Chat) ? 4 : 3 expect(fields.length).to eq(count) expect(login_required_field.id).to eq("login_required") - expect(login_required_field.value).to eq(true) + expect(login_required_field.value).to eq("private") expect(invite_only_field.id).to eq("invite_only") - expect(invite_only_field.value).to eq(false) + expect(invite_only_field.value).to eq("sign_up") expect(must_approve_users_field.id).to eq("must_approve_users") - expect(must_approve_users_field.value).to eq(true) - if defined?(::Chat) - expect(chat_enabled_field.id).to eq("chat_enabled") - expect(chat_enabled_field.value).to eq(true) - end + expect(must_approve_users_field.value).to eq("yes") end end diff --git a/spec/serializers/wizard_serializer_spec.rb b/spec/serializers/wizard_serializer_spec.rb index d135a7a23c2..48c958e886f 100644 --- a/spec/serializers/wizard_serializer_spec.rb +++ b/spec/serializers/wizard_serializer_spec.rb @@ -61,13 +61,13 @@ RSpec.describe WizardSerializer do expect(privacy_step).to_not be_nil login_required_field = privacy_step["fields"].find { |f| f["id"] == "login_required" } - expect(login_required_field["value"]).to eq(true) + expect(login_required_field["value"]).to eq("private") invite_only_field = privacy_step["fields"].find { |f| f["id"] == "invite_only" } - expect(invite_only_field["value"]).to eq(true) + expect(invite_only_field["value"]).to eq("invite_only") must_approve_users_field = privacy_step["fields"].find { |f| f["id"] == "must_approve_users" } - expect(must_approve_users_field["value"]).to eq(true) + expect(must_approve_users_field["value"]).to eq("yes") end end end diff --git a/spec/system/page_objects/pages/wizard.rb b/spec/system/page_objects/pages/wizard.rb index 3df9c0b666d..6fe3eb41187 100644 --- a/spec/system/page_objects/pages/wizard.rb +++ b/spec/system/page_objects/pages/wizard.rb @@ -6,6 +6,14 @@ module PageObjects def click_jump_in find(".jump-in").click end + + def go_to_next_step + find(".wizard-container__button.next").click + end + + def select_access_option(label) + find(".wizard-container__radio-choice", text: label).click + end end end end diff --git a/spec/system/wizard_spec.rb b/spec/system/wizard_spec.rb index 1d1fae26938..f33b7721898 100644 --- a/spec/system/wizard_spec.rb +++ b/spec/system/wizard_spec.rb @@ -9,6 +9,34 @@ describe "Wizard", type: :system do before { sign_in(admin) } + it "lets user configure member access" do + visit("/wizard/steps/privacy") + + expect(page).to have_css( + ".wizard-container__radio-choice.--selected", + text: I18n.t("wizard.step.privacy.fields.login_required.choices.public.label"), + ) + + wizard_page.select_access_option("Private") + + expect(page).to have_css( + ".wizard-container__radio-choice.--selected", + text: I18n.t("wizard.step.privacy.fields.login_required.choices.private.label"), + ) + + wizard_page.go_to_next_step + + expect(page).to have_current_path("/wizard/steps/ready") + expect(SiteSetting.login_required).to eq(true) + + visit("/wizard/steps/privacy") + + expect(page).to have_css( + ".wizard-container__radio-choice.--selected", + text: I18n.t("wizard.step.privacy.fields.login_required.choices.private.label"), + ) + end + it "redirects to latest when wizard is completed" do visit("/wizard/steps/ready") wizard_page.click_jump_in