mirror of
https://github.com/discourse/discourse.git
synced 2025-01-24 09:46:16 +08:00
FEATURE: Add option to hide full name input at signup (#30471)
This commit replaces the `full_name_required` setting with a new `full_name_requirement` setting to allow more flexibility with the name field in the signup form. The new setting has 2 options, "Required at signup" and "Optional at signup", which are equivalent to the true/false possibilities of the old setting, and a third option "Hidden at signup" that hides the name field from the signup form, making it effectively optional too. New sites will have the "Hidden at signup" option as the default option, and existing site will continue to use the option that maps to their current configuration. Internal topic: t/136746.
This commit is contained in:
parent
b728b74c49
commit
3187606d34
app
assets/javascripts/discourse
app
components
controllers
mixins
templates
tests
models
serializers
services
config
db/migrate
lib
spec
lib/validators
requests/api/schemas/json
services
system
|
@ -6,15 +6,9 @@ import TextField from "discourse/components/text-field";
|
|||
import valueEntered from "discourse/helpers/value-entered";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class SidebarEditNavigationMenuTagsModal extends Component {
|
||||
export default class FullnameInput extends Component {
|
||||
@service siteSettings;
|
||||
|
||||
get showFullname() {
|
||||
return (
|
||||
this.siteSettings.full_name_required || this.siteSettings.enable_names
|
||||
);
|
||||
}
|
||||
|
||||
get showFullnameInstructions() {
|
||||
return (
|
||||
this.siteSettings.show_signup_form_full_name_instructions &&
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if this.fullnameRequired}}
|
||||
{{#if (and this.showFullname this.fullnameRequired)}}
|
||||
<FullnameInput
|
||||
@nameValidation={{this.nameValidation}}
|
||||
@nameTitle={{this.nameTitle}}
|
||||
|
|
|
@ -134,12 +134,14 @@ export default class CreateAccount extends Component.extend(
|
|||
|
||||
@discourseComputed
|
||||
showFullname() {
|
||||
return this.siteSettings.enable_names;
|
||||
return (
|
||||
this.siteSettings.enable_names && this.site.full_name_visible_in_signup
|
||||
);
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
fullnameRequired() {
|
||||
return this.siteSettings.full_name_required;
|
||||
return this.site.full_name_required_for_signup;
|
||||
}
|
||||
|
||||
@discourseComputed(
|
||||
|
|
|
@ -157,12 +157,14 @@ export default class InvitesShowController extends Controller.extend(
|
|||
|
||||
@discourseComputed
|
||||
showFullname() {
|
||||
return this.siteSettings.enable_names;
|
||||
return (
|
||||
this.siteSettings.enable_names && this.site.full_name_visible_in_signup
|
||||
);
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
fullnameRequired() {
|
||||
return this.siteSettings.full_name_required;
|
||||
return this.site.full_name_required_for_signup;
|
||||
}
|
||||
|
||||
@discourseComputed(
|
||||
|
|
|
@ -66,7 +66,7 @@ export default class AccountController extends Controller {
|
|||
@discourseComputed()
|
||||
nameInstructions() {
|
||||
return i18n(
|
||||
this.siteSettings.full_name_required
|
||||
this.site.full_name_required_for_signup
|
||||
? "user.name.instructions_required"
|
||||
: "user.name.instructions"
|
||||
);
|
||||
|
|
|
@ -117,12 +117,14 @@ export default class SignupPageController extends Controller.extend(
|
|||
|
||||
@discourseComputed
|
||||
showFullname() {
|
||||
return this.siteSettings.enable_names;
|
||||
return (
|
||||
this.siteSettings.enable_names && this.site.full_name_visible_in_signup
|
||||
);
|
||||
}
|
||||
|
||||
@discourseComputed
|
||||
fullnameRequired() {
|
||||
return this.siteSettings.full_name_required;
|
||||
return this.site.full_name_required_for_signup;
|
||||
}
|
||||
|
||||
@discourseComputed(
|
||||
|
|
|
@ -6,7 +6,7 @@ import { i18n } from "discourse-i18n";
|
|||
export default Mixin.create({
|
||||
get nameTitle() {
|
||||
return i18n(
|
||||
this.siteSettings.full_name_required
|
||||
this.site.full_name_required_for_signup
|
||||
? "user.name.title"
|
||||
: "user.name.title_optional"
|
||||
);
|
||||
|
@ -15,7 +15,7 @@ export default Mixin.create({
|
|||
// Validate the name.
|
||||
nameValidation: computed("accountName", "forceValidationReason", function () {
|
||||
const { accountName, forceValidationReason } = this;
|
||||
if (this.siteSettings.full_name_required && isEmpty(accountName)) {
|
||||
if (this.site.full_name_required_for_signup && isEmpty(accountName)) {
|
||||
return EmberObject.create({
|
||||
failed: true,
|
||||
ok: false,
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
{{#if this.fullnameRequired}}
|
||||
{{#if (and this.showFullname this.fullnameRequired)}}
|
||||
<FullnameInput
|
||||
@nameValidation={{this.nameValidation}}
|
||||
@nameTitle={{this.nameTitle}}
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if this.fullnameRequired}}
|
||||
{{#if (and this.showFullname this.fullnameRequired)}}
|
||||
<FullnameInput
|
||||
@nameValidation={{this.nameValidation}}
|
||||
@nameTitle={{this.nameTitle}}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { click, fillIn, visit } from "@ember/test-helpers";
|
|||
import { test } from "qunit";
|
||||
import sinon from "sinon";
|
||||
import LoginMethod from "discourse/models/login-method";
|
||||
import Site from "discourse/models/site";
|
||||
import pretender, {
|
||||
parsePostData,
|
||||
response,
|
||||
|
@ -135,10 +136,12 @@ acceptance("Create Account", function () {
|
|||
});
|
||||
});
|
||||
|
||||
acceptance("Create Account - full_name_required", function (needs) {
|
||||
needs.settings({ full_name_required: true });
|
||||
acceptance("Create Account - full name requirement", function () {
|
||||
test("full name required", async function (assert) {
|
||||
const site = Site.current();
|
||||
site.set("full_name_required_for_signup", true);
|
||||
site.set("full_name_visible_in_signup", true);
|
||||
|
||||
test("full_name_required", async function (assert) {
|
||||
await visit("/");
|
||||
await click("header .sign-up-button");
|
||||
|
||||
|
@ -168,4 +171,66 @@ acceptance("Create Account - full_name_required", function (needs) {
|
|||
|
||||
assert.verifySteps(["request"]);
|
||||
});
|
||||
|
||||
test("full name hidden at signup", async function (assert) {
|
||||
const site = Site.current();
|
||||
site.set("full_name_required_for_signup", false);
|
||||
site.set("full_name_visible_in_signup", false);
|
||||
|
||||
await visit("/");
|
||||
await click("header .sign-up-button");
|
||||
|
||||
assert.dom("#new-account-name").doesNotExist();
|
||||
|
||||
await fillIn("#new-account-email", "z@z.co");
|
||||
await fillIn("#new-account-username", "good-tuna");
|
||||
await fillIn("#new-account-password", "cool password bro");
|
||||
|
||||
pretender.post("/u", (request) => {
|
||||
assert.step("request");
|
||||
const data = parsePostData(request.requestBody);
|
||||
assert.strictEqual(data.password, "cool password bro");
|
||||
assert.strictEqual(data.email, "z@z.co");
|
||||
assert.strictEqual(data.username, "good-tuna");
|
||||
return response({ success: true });
|
||||
});
|
||||
|
||||
await click(".d-modal__footer .btn-primary");
|
||||
assert
|
||||
.dom(".d-modal__footer .btn-primary")
|
||||
.isDisabled("create account is disabled");
|
||||
|
||||
assert.verifySteps(["request"]);
|
||||
});
|
||||
|
||||
test("full name optional at signup", async function (assert) {
|
||||
const site = Site.current();
|
||||
site.set("full_name_required_for_signup", false);
|
||||
site.set("full_name_visible_in_signup", true);
|
||||
|
||||
await visit("/");
|
||||
await click("header .sign-up-button");
|
||||
|
||||
assert.dom("#new-account-name").exists();
|
||||
|
||||
await fillIn("#new-account-email", "z@z.co");
|
||||
await fillIn("#new-account-username", "good-tuna");
|
||||
await fillIn("#new-account-password", "cool password bro");
|
||||
|
||||
pretender.post("/u", (request) => {
|
||||
assert.step("request");
|
||||
const data = parsePostData(request.requestBody);
|
||||
assert.strictEqual(data.password, "cool password bro");
|
||||
assert.strictEqual(data.email, "z@z.co");
|
||||
assert.strictEqual(data.username, "good-tuna");
|
||||
return response({ success: true });
|
||||
});
|
||||
|
||||
await click(".d-modal__footer .btn-primary");
|
||||
assert
|
||||
.dom(".d-modal__footer .btn-primary")
|
||||
.isDisabled("create account is disabled");
|
||||
|
||||
assert.verifySteps(["request"]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { click, currentURL, fillIn, visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
import PreloadStore from "discourse/lib/preload-store";
|
||||
import Site from "discourse/models/site";
|
||||
import pretender, { response } from "discourse/tests/helpers/create-pretender";
|
||||
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
@ -50,9 +51,7 @@ function preloadInvite({
|
|||
PreloadStore.store("invite_info", info);
|
||||
}
|
||||
|
||||
acceptance("Invite accept", function (needs) {
|
||||
needs.settings({ full_name_required: true });
|
||||
|
||||
acceptance("Invite accept", function () {
|
||||
test("email invite link", async function (assert) {
|
||||
PreloadStore.store("invite_info", {
|
||||
invited_by: {
|
||||
|
@ -164,12 +163,38 @@ acceptance("Invite accept", function (needs) {
|
|||
assert.dom(".invites-show .btn-primary").isEnabled("submit is enabled");
|
||||
});
|
||||
|
||||
test("invite name is required only if full name is required", async function (assert) {
|
||||
test("invite name optional", async function (assert) {
|
||||
const site = Site.current();
|
||||
site.set("full_name_required_for_signup", false);
|
||||
site.set("full_name_visible_in_signup", true);
|
||||
|
||||
preloadInvite();
|
||||
await visit("/invites/my-valid-invite-token");
|
||||
assert.dom("#new-account-name").exists();
|
||||
assert
|
||||
.dom(".name-input .required")
|
||||
.doesNotExist("Full name is implicitly required");
|
||||
.dom(".name-input.name-required")
|
||||
.doesNotExist("full name is not required");
|
||||
});
|
||||
|
||||
test("invite name hidden", async function (assert) {
|
||||
const site = Site.current();
|
||||
site.set("full_name_required_for_signup", false);
|
||||
site.set("full_name_visible_in_signup", false);
|
||||
|
||||
preloadInvite();
|
||||
await visit("/invites/my-valid-invite-token");
|
||||
assert.dom("#new-account-name").doesNotExist();
|
||||
});
|
||||
|
||||
test("invite name required", async function (assert) {
|
||||
const site = Site.current();
|
||||
site.set("full_name_required_for_signup", true);
|
||||
site.set("full_name_visible_in_signup", true);
|
||||
|
||||
preloadInvite();
|
||||
await visit("/invites/my-valid-invite-token");
|
||||
assert.dom("#new-account-name").exists();
|
||||
assert.dom(".name-input.name-required").exists("full name is required");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -60,6 +60,8 @@ export default {
|
|||
anonymous_top_menu_items: ["latest", "hot", "categories"],
|
||||
uncategorized_category_id: 17,
|
||||
is_readonly: false,
|
||||
full_name_required_for_signup: false,
|
||||
full_name_visible_in_signup: true,
|
||||
categories: [
|
||||
{
|
||||
id: 3,
|
||||
|
|
21
app/models/full_name_requirement.rb
Normal file
21
app/models/full_name_requirement.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "enum_site_setting"
|
||||
|
||||
class FullNameRequirement < EnumSiteSetting
|
||||
def self.valid_value?(val)
|
||||
values.any? { |v| v[:value] == val }
|
||||
end
|
||||
|
||||
def self.values
|
||||
@values ||= [
|
||||
{ name: "full_name_requirement.required_at_signup", value: "required_at_signup" },
|
||||
{ name: "full_name_requirement.optional_at_signup", value: "optional_at_signup" },
|
||||
{ name: "full_name_requirement.hidden_at_signup", value: "hidden_at_signup" },
|
||||
]
|
||||
end
|
||||
|
||||
def self.translate_names?
|
||||
true
|
||||
end
|
||||
end
|
|
@ -48,6 +48,8 @@ class SiteSerializer < ApplicationSerializer
|
|||
:system_user_avatar_template,
|
||||
:lazy_load_categories,
|
||||
:valid_flag_applies_to_types,
|
||||
:full_name_required_for_signup,
|
||||
:full_name_visible_in_signup,
|
||||
)
|
||||
|
||||
has_many :archetypes, embed: :objects, serializer: ArchetypeSerializer
|
||||
|
@ -381,6 +383,14 @@ class SiteSerializer < ApplicationSerializer
|
|||
scope.is_admin?
|
||||
end
|
||||
|
||||
def full_name_required_for_signup
|
||||
SiteSetting.full_name_requirement == "required_at_signup"
|
||||
end
|
||||
|
||||
def full_name_visible_in_signup
|
||||
SiteSetting.full_name_requirement != "hidden_at_signup"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ordered_flags(flags)
|
||||
|
|
|
@ -29,7 +29,7 @@ class UserAnonymizer
|
|||
|
||||
@user.reload
|
||||
@user.password = SecureRandom.hex
|
||||
@user.name = SiteSetting.full_name_required ? @user.username : nil
|
||||
@user.name = SiteSetting.full_name_requirement == "required_at_signup" ? @user.username : nil
|
||||
@user.date_of_birth = nil
|
||||
@user.title = nil
|
||||
@user.uploaded_avatar_id = nil
|
||||
|
|
|
@ -2539,6 +2539,10 @@ en:
|
|||
categories_boxes: "Boxes with Subcategories"
|
||||
categories_boxes_with_topics: "Boxes with Featured Topics"
|
||||
subcategories_with_featured_topics: "Subcategories with Featured Topics"
|
||||
full_name_requirement:
|
||||
required_at_signup: "Required at signup"
|
||||
optional_at_signup: "Optional at signup"
|
||||
hidden_at_signup: "Hidden at signup"
|
||||
|
||||
shortcut_modifier_key:
|
||||
shift: "Shift"
|
||||
|
|
|
@ -2517,7 +2517,7 @@ en:
|
|||
svg_icon_subset: "Add additional FontAwesome icons that you would like to include in your assets. Use prefix 'fa-' for solid icons, 'far-' for regular icons and 'fab-' for brand icons."
|
||||
max_prints_per_hour_per_user: "Maximum number of /print page impressions (set to 0 to disable printing)"
|
||||
|
||||
full_name_required: "Full name is a required field of a user's profile."
|
||||
full_name_requirement: "Make the full name field a required, optional, or optional but hidden field in the signup form."
|
||||
enable_names: "Show the user's full name on their profile, user card, and emails. Disable to hide full name everywhere."
|
||||
display_name_on_posts: "Show a user's full name on their posts in addition to their @username."
|
||||
show_time_gap_days: "If two posts are made this many days apart, display the time gap in the topic."
|
||||
|
|
|
@ -707,9 +707,10 @@ users:
|
|||
logout_redirect:
|
||||
client: true
|
||||
default: ""
|
||||
full_name_required:
|
||||
client: true
|
||||
default: false
|
||||
full_name_requirement:
|
||||
type: enum
|
||||
default: hidden_at_signup
|
||||
enum: "FullNameRequirement"
|
||||
enable_names:
|
||||
client: true
|
||||
default: true
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ChangeFullNameRequiredSetting < ActiveRecord::Migration[7.2]
|
||||
def up
|
||||
old_setting = DB.query_single(<<~SQL).first
|
||||
SELECT value
|
||||
FROM site_settings
|
||||
WHERE name = 'full_name_required'
|
||||
SQL
|
||||
|
||||
new_setting = nil
|
||||
if old_setting
|
||||
new_setting = old_setting == "t" ? "required_at_signup" : "optional_at_signup"
|
||||
elsif Migration::Helpers.existing_site?
|
||||
new_setting = "optional_at_signup"
|
||||
end
|
||||
|
||||
DB.exec(<<~SQL)
|
||||
DELETE FROM site_settings WHERE name = 'full_name_required'
|
||||
SQL
|
||||
|
||||
DB.exec(<<~SQL, value: new_setting) if new_setting
|
||||
INSERT INTO site_settings
|
||||
(name, data_type, value, created_at, updated_at)
|
||||
VALUES
|
||||
('full_name_requirement', 7, :value, NOW(), NOW())
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
end
|
|
@ -116,7 +116,8 @@ module DiscourseDev
|
|||
puts "Once site is running use https://localhost:9292/user/#{username}/become to access the account in development"
|
||||
end
|
||||
|
||||
admin.name = ask("Full name: ") if SiteSetting.full_name_required
|
||||
admin.name = ask("Full name: ") if SiteSetting.full_name_requirement ==
|
||||
"required_at_signup"
|
||||
saved = admin.save
|
||||
|
||||
if saved
|
||||
|
|
|
@ -81,7 +81,8 @@ task "admin:create" => :environment do
|
|||
admin.password = password
|
||||
end
|
||||
|
||||
admin.name = ask("Full name: ") if SiteSetting.full_name_required && admin.name.blank?
|
||||
admin.name = ask("Full name: ") if SiteSetting.full_name_requirement == "required_at_signup" &&
|
||||
admin.name.blank?
|
||||
|
||||
# save/update user account
|
||||
saved = admin.save
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
class UserFullNameValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
record.errors.add(attribute, :blank) if SiteSetting.full_name_required && !record.name.present?
|
||||
if SiteSetting.full_name_requirement == "required_at_signup" && !record.name.present?
|
||||
record.errors.add(attribute, :blank)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ RSpec.describe UserFullNameValidator do
|
|||
let(:record) { Fabricate.build(:user, name: @name) }
|
||||
|
||||
context "when name is not required" do
|
||||
before { SiteSetting.full_name_required = false }
|
||||
before { SiteSetting.full_name_requirement = "optional_at_signup" }
|
||||
|
||||
it "allows no name" do
|
||||
@name = nil
|
||||
|
@ -23,7 +23,7 @@ RSpec.describe UserFullNameValidator do
|
|||
end
|
||||
|
||||
context "when name is required" do
|
||||
before { SiteSetting.full_name_required = true }
|
||||
before { SiteSetting.full_name_requirement = "required_at_signup" }
|
||||
|
||||
it "adds error for nil name" do
|
||||
@name = nil
|
||||
|
|
|
@ -861,6 +861,12 @@
|
|||
},
|
||||
"navigation_menu_site_top_tags": {
|
||||
"type": "array"
|
||||
},
|
||||
"full_name_required_for_signup": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"full_name_visible_in_signup": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
@ -893,6 +899,8 @@
|
|||
"categories",
|
||||
"archetypes",
|
||||
"user_fields",
|
||||
"auth_providers"
|
||||
"auth_providers",
|
||||
"full_name_required_for_signup",
|
||||
"full_name_visible_in_signup"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ RSpec.describe AnonymousShadowCreator do
|
|||
end
|
||||
|
||||
it "works even when names are required" do
|
||||
SiteSetting.full_name_required = true
|
||||
SiteSetting.full_name_requirement = "required_at_signup"
|
||||
|
||||
expect { AnonymousShadowCreator.get(user) }.to_not raise_error
|
||||
end
|
||||
|
|
|
@ -76,7 +76,7 @@ RSpec.describe UserAnonymizer do
|
|||
end
|
||||
|
||||
context "when Site Settings do not require full name" do
|
||||
before { SiteSetting.full_name_required = false }
|
||||
before { SiteSetting.full_name_requirement = "optional_at_signup" }
|
||||
|
||||
it "resets profile to default values" do
|
||||
user.update!(name: "Bibi", date_of_birth: 19.years.ago, title: "Super Star")
|
||||
|
@ -127,7 +127,7 @@ RSpec.describe UserAnonymizer do
|
|||
end
|
||||
|
||||
context "when Site Settings require full name" do
|
||||
before { SiteSetting.full_name_required = true }
|
||||
before { SiteSetting.full_name_requirement = "required_at_signup" }
|
||||
|
||||
it "changes name to anonymized username" do
|
||||
prev_username = user.username
|
||||
|
|
|
@ -335,6 +335,8 @@ shared_examples "social authentication scenarios" do |signup_page_object, login_
|
|||
end
|
||||
|
||||
describe "Social authentication", type: :system do
|
||||
before { SiteSetting.full_name_requirement = "optional_at_signup" }
|
||||
|
||||
context "when desktop" do
|
||||
include_examples "social authentication scenarios",
|
||||
PageObjects::Modals::Signup.new,
|
||||
|
|
Loading…
Reference in New Issue
Block a user