diff --git a/app/assets/javascripts/admin/addon/components/admin-user-field-item.hbs b/app/assets/javascripts/admin/addon/components/admin-user-field-item.hbs
index a2417599637..06835bc1ab1 100644
--- a/app/assets/javascripts/admin/addon/components/admin-user-field-item.hbs
+++ b/app/assets/javascripts/admin/addon/components/admin-user-field-item.hbs
@@ -1,134 +1,163 @@
-
- {{#if (or this.isEditing (not this.userField.id))}}
-
-
-
+{{#if (or this.isEditing (not @userField.id))}}
+
+
+
+
+
+ {{#each @fieldTypes as |fieldType|}}
+ {{fieldType.name}}
+ {{/each}}
+
+
-
-
-
+
+
+
-
-
-
+
+
+
- {{#if this.bufferedFieldType.hasOptions}}
-
-
-
- {{/if}}
+ {{#if
+ (or
+ (eq transientData.field_type "dropdown")
+ (eq transientData.field_type "multiselect")
+ )
+ }}
+
+
+
+
+
+ {{/if}}
-
-
+
+
+
+ {{i18n "admin.user_fields.requirement.optional.title"}}
+
+
+ {{i18n "admin.user_fields.requirement.for_all_users.title"}}
+ {{i18n
+ "admin.user_fields.requirement.for_all_users.description"
+ }}
+
+
+ {{i18n "admin.user_fields.requirement.on_signup.title"}}
+ {{i18n
+ "admin.user_fields.requirement.on_signup.description"
+ }}
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{else}}
+
+
+
+
+
+
+
+
+{{else}}
+
- {{this.userField.name}}
+ {{@userField.name}}
- {{html-safe
- this.userField.description
- }}
+ {{html-safe @userField.description}}
-
{{this.fieldName}}
+
{{@userField.fieldTypeName}}
{{this.flags}}
- {{/if}}
-
\ No newline at end of file
+
+{{/if}}
\ No newline at end of file
diff --git a/app/assets/javascripts/admin/addon/components/admin-user-field-item.js b/app/assets/javascripts/admin/addon/components/admin-user-field-item.js
index c95ac1e3b91..28fb4325c7e 100644
--- a/app/assets/javascripts/admin/addon/components/admin-user-field-item.js
+++ b/app/assets/javascripts/admin/addon/components/admin-user-field-item.js
@@ -1,4 +1,5 @@
-import Component from "@ember/component";
+import Component from "@glimmer/component";
+import { cached, tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { schedule } from "@ember/runloop";
import { service } from "@ember/service";
@@ -6,78 +7,102 @@ import { isEmpty } from "@ember/utils";
import { tagName } from "@ember-decorators/component";
import { Promise } from "rsvp";
import { popupAjaxError } from "discourse/lib/ajax-error";
-import { i18n, propertyEqual } from "discourse/lib/computed";
-import { bufferedProperty } from "discourse/mixins/buffered-content";
-import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
import UserField from "admin/models/user-field";
@tagName("")
-export default class AdminUserFieldItem extends Component.extend(
- bufferedProperty("userField")
-) {
+export default class AdminUserFieldItem extends Component {
@service adminCustomUserFields;
@service dialog;
- isEditing = false;
+ @tracked isEditing = false;
+ @tracked
+ editableDisabled = this.args.userField.requirement === "for_all_users";
- @propertyEqual("userField", "firstField") cantMoveUp;
- @propertyEqual("userField", "lastField") cantMoveDown;
-
- @i18n("admin.user_fields.description") userFieldsDescription;
-
- @discourseComputed("buffered.field_type")
- bufferedFieldType(fieldType) {
- return UserField.fieldTypeById(fieldType);
+ get fieldName() {
+ return UserField.fieldTypeById(this.fieldType)?.name;
}
- didInsertElement() {
- super.didInsertElement(...arguments);
-
- this._focusName();
+ get cantMoveUp() {
+ return this.args.userField.id === this.args.firstField?.id;
}
- _focusName() {
- schedule("afterRender", () => {
- document.querySelector(".user-field-name")?.focus();
- });
+ get cantMoveDown() {
+ return this.args.userField.id === this.args.lastField?.id;
}
- @discourseComputed("userField.field_type")
- fieldName(fieldType) {
- return UserField.fieldTypeById(fieldType)?.name;
+ get isNewRecord() {
+ return isEmpty(this.args.userField?.id);
}
- @discourseComputed(
- "userField.{editable,show_on_profile,show_on_user_card,searchable}"
- )
- flags(userField) {
- const ret = [];
- if (userField.editable) {
- ret.push(I18n.t("admin.user_fields.editable.enabled"));
- }
- if (userField.show_on_profile) {
- ret.push(I18n.t("admin.user_fields.show_on_profile.enabled"));
- }
- if (userField.show_on_user_card) {
- ret.push(I18n.t("admin.user_fields.show_on_user_card.enabled"));
- }
- if (userField.searchable) {
- ret.push(I18n.t("admin.user_fields.searchable.enabled"));
- }
+ get flags() {
+ const flags = [
+ "editable",
+ "show_on_profile",
+ "show_on_user_card",
+ "searchable",
+ ];
- return ret.join(", ");
+ return flags
+ .map((flag) => {
+ if (this.args.userField[flag]) {
+ return I18n.t(`admin.user_fields.${flag}.enabled`);
+ }
+ })
+ .filter(Boolean)
+ .join(", ");
}
- @discourseComputed("buffered.requirement")
- editableDisabled(requirement) {
- return requirement === "for_all_users";
+ @cached
+ get formData() {
+ return this.args.userField.getProperties(
+ "field_type",
+ "name",
+ "description",
+ "requirement",
+ "editable",
+ "show_on_profile",
+ "show_on_user_card",
+ "searchable",
+ "options",
+ ...this.adminCustomUserFields.additionalProperties
+ );
}
@action
- changeRequirementType(requirement) {
- this.buffered.set("requirement", requirement);
- this.buffered.set("editable", requirement === "for_all_users");
+ setRequirement(value, { set }) {
+ set("requirement", value);
+
+ if (value === "for_all_users") {
+ this.editableDisabled = true;
+ set("editable", true);
+ } else {
+ this.editableDisabled = false;
+ }
+ }
+
+ @action
+ async save(data) {
+ let confirm = true;
+
+ if (data.requirement === "for_all_users") {
+ confirm = await this._confirmChanges();
+ }
+
+ if (!confirm) {
+ return;
+ }
+
+ return this.args.userField
+ .save(data)
+ .then(() => {
+ if (this.isDestroying || this.isDestroyed) {
+ return;
+ }
+
+ this.isEditing = false;
+ })
+ .catch(popupAjaxError);
}
async _confirmChanges() {
@@ -90,57 +115,23 @@ export default class AdminUserFieldItem extends Component.extend(
});
}
- @action
- async save() {
- const attrs = this.buffered.getProperties(
- "name",
- "description",
- "field_type",
- "editable",
- "requirement",
- "show_on_profile",
- "show_on_user_card",
- "searchable",
- "options",
- ...this.adminCustomUserFields.additionalProperties
- );
-
- let confirm = true;
-
- if (attrs.requirement === "for_all_users") {
- confirm = await this._confirmChanges();
- }
-
- if (!confirm) {
- return;
- }
-
- return this.userField
- .save(attrs)
- .then(() => {
- if (this.isDestroying || this.isDestroyed) {
- return;
- }
-
- this.set("isEditing", false);
- this.commitBuffer();
- })
- .catch(popupAjaxError);
- }
-
@action
edit() {
- this.set("isEditing", true);
- this._focusName();
+ this.isEditing = true;
}
@action
cancel() {
- if (isEmpty(this.userField?.id)) {
- this.destroyAction(this.userField);
+ if (this.isNewRecord) {
+ this.args.destroyAction(this.args.userField);
} else {
- this.rollbackBuffer();
- this.set("isEditing", false);
+ this.isEditing = false;
}
}
+
+ _focusName() {
+ schedule("afterRender", () =>
+ document.querySelector(".user-field-name")?.focus()
+ );
+ }
}
diff --git a/app/assets/javascripts/admin/addon/components/value-list.js b/app/assets/javascripts/admin/addon/components/value-list.js
index ceb92ac3056..9c37faabae8 100644
--- a/app/assets/javascripts/admin/addon/components/value-list.js
+++ b/app/assets/javascripts/admin/addon/components/value-list.js
@@ -14,6 +14,7 @@ export default class ValueList extends Component {
newValue = "";
collection = null;
values = null;
+ onChange = null;
@reads("addKey") noneKey;
@@ -21,7 +22,7 @@ export default class ValueList extends Component {
super.didReceiveAttrs(...arguments);
if (this.inputType === "array") {
- this.set("collection", this.values || []);
+ this.set("collection", this.values ? [...this.values] : []);
return;
}
@@ -114,6 +115,11 @@ export default class ValueList extends Component {
}
_saveValues() {
+ if (this.onChange) {
+ this.onChange([...this.collection]);
+ return;
+ }
+
if (this.inputType === "array") {
this.set("values", this.collection);
return;
diff --git a/app/assets/javascripts/admin/addon/controllers/admin-user-fields.js b/app/assets/javascripts/admin/addon/controllers/admin-user-fields.js
index e760925fd36..6f50ed070ff 100644
--- a/app/assets/javascripts/admin/addon/controllers/admin-user-fields.js
+++ b/app/assets/javascripts/admin/addon/controllers/admin-user-fields.js
@@ -28,6 +28,7 @@ export default class AdminUserFieldsController extends Controller {
createField() {
const f = this.store.createRecord("user-field", {
field_type: "text",
+ requirement: "optional",
position: MAX_FIELDS,
});
this.model.pushObject(f);
diff --git a/app/assets/javascripts/admin/addon/models/user-field.js b/app/assets/javascripts/admin/addon/models/user-field.js
index b27f8b5aff1..da70af5706a 100644
--- a/app/assets/javascripts/admin/addon/models/user-field.js
+++ b/app/assets/javascripts/admin/addon/models/user-field.js
@@ -1,3 +1,4 @@
+import { tracked } from "@glimmer/tracking";
import EmberObject from "@ember/object";
import { i18n } from "discourse/lib/computed";
import RestModel from "discourse/models/rest";
@@ -19,6 +20,16 @@ export default class UserField extends RestModel {
static fieldTypeById(id) {
return this.fieldTypes().findBy("id", id);
}
+
+ @tracked field_type;
+ @tracked editable;
+ @tracked show_on_profile;
+ @tracked show_on_user_card;
+ @tracked searchable;
+
+ get fieldTypeName() {
+ return UserField.fieldTypes().find((ft) => ft.id === this.field_type).name;
+ }
}
class UserFieldType extends EmberObject {
diff --git a/app/assets/javascripts/admin/addon/templates/user-fields.hbs b/app/assets/javascripts/admin/addon/templates/user-fields.hbs
index 79b96ac39f8..f8d6fda47c6 100644
--- a/app/assets/javascripts/admin/addon/templates/user-fields.hbs
+++ b/app/assets/javascripts/admin/addon/templates/user-fields.hbs
@@ -1,27 +1,29 @@
-
-
{{i18n "admin.user_fields.title"}}
+
+
+
{{i18n "admin.user_fields.title"}}
-
{{i18n "admin.user_fields.help"}}
+
{{i18n "admin.user_fields.help"}}
- {{#if this.model}}
- {{#each this.sortedFields as |uf|}}
-
- {{/each}}
- {{/if}}
+ {{#if this.model}}
+ {{#each this.sortedFields as |uf|}}
+
+ {{/each}}
+ {{/if}}
-
+
+
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/tests/integration/components/admin-user-field-item-test.js b/app/assets/javascripts/discourse/tests/integration/components/admin-user-field-item-test.js
deleted file mode 100644
index 083e5d7b83d..00000000000
--- a/app/assets/javascripts/discourse/tests/integration/components/admin-user-field-item-test.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import { click, render } from "@ember/test-helpers";
-import { hbs } from "ember-cli-htmlbars";
-import { module, test } from "qunit";
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-import { exists, query } from "discourse/tests/helpers/qunit-helpers";
-import I18n from "discourse-i18n";
-
-module("Integration | Component | admin-user-field-item", function (hooks) {
- setupRenderingTest(hooks);
-
- test("user field without an id", async function (assert) {
- await render(hbs`
`);
-
- assert.ok(exists(".save"), "displays editing mode");
- });
-
- test("cancel action", async function (assert) {
- this.set("userField", { id: 1, field_type: "text" });
- this.set("isEditing", true);
- this.set("destroyAction", () => {});
- this.set("moveUpAction", () => {});
- this.set("moveDownAction", () => {});
-
- await render(hbs`
-
`);
-
- await click(".cancel");
- assert.ok(exists(".edit"));
- });
-
- test("edit action", async function (assert) {
- this.set("userField", { id: 1, field_type: "text" });
- this.set("destroyAction", () => {});
- this.set("moveUpAction", () => {});
- this.set("moveDownAction", () => {});
-
- await render(hbs`
-
`);
-
- await click(".edit");
- assert.ok(exists(".save"));
- });
-
- test("field attributes are rendered correctly", async function (assert) {
- this.set("userField", {
- id: 1,
- field_type: "text",
- name: "foo",
- description: "what is foo",
- show_on_profile: true,
- show_on_user_card: true,
- searchable: true,
- });
- this.set("destroyAction", () => {});
- this.set("moveUpAction", () => {});
- this.set("moveDownAction", () => {});
-
- await render(hbs`
-
`);
-
- assert.strictEqual(query(".name").innerText, this.userField.name);
- assert.strictEqual(
- query(".description").innerText,
- this.userField.description
- );
- assert.strictEqual(
- query(".field-type").innerText,
- I18n.t("admin.user_fields.field_types.text")
- );
-
- assert
- .dom(".user-field-flags")
- .hasText(
- `${I18n.t("admin.user_fields.show_on_profile.enabled")}, ${I18n.t(
- "admin.user_fields.show_on_user_card.enabled"
- )}, ${I18n.t("admin.user_fields.searchable.enabled")}`
- );
- });
-});
diff --git a/app/assets/stylesheets/common/admin/customize.scss b/app/assets/stylesheets/common/admin/customize.scss
index df2491f61fd..e306399a2d1 100644
--- a/app/assets/stylesheets/common/admin/customize.scss
+++ b/app/assets/stylesheets/common/admin/customize.scss
@@ -956,64 +956,16 @@ table.permalinks {
.user-fields {
h2 {
- margin-bottom: 10px;
+ margin-bottom: 1em;
}
.user-field {
- padding: 10px;
- margin-bottom: 10px;
+ padding-block: 0.5em;
+ margin-bottom: 1em;
border-bottom: 1px solid var(--primary-low);
.form-display {
width: 25%;
float: left;
}
- .form-element,
- .form-element-desc {
- float: left;
- min-height: 30px;
- padding: 0.25em 0;
- &.input-area {
- width: 75%;
- .value-list,
- .select-kit,
- input[type="text"] {
- width: 50%;
- }
-
- .value-list {
- .select-kit {
- width: 100%;
- }
- }
- label {
- font-weight: normal;
- padding: 0.5em 0;
- }
-
- .label-text {
- display: inline-flex;
- flex-direction: column;
- }
-
- .description {
- margin-top: 0.25em;
- color: var(--primary-medium);
- font-size: var(--font-down-1);
- line-height: var(--line-height-large);
- }
- }
- &.label-area {
- width: 25%;
- label {
- margin: 0.5em 1em 0 0;
- text-align: right;
- font-weight: bold;
- }
- }
- }
- .controls {
- float: right;
- text-align: right;
- }
.clearfix {
clear: both;
}
diff --git a/app/assets/stylesheets/common/form-kit/_checkbox-group.scss b/app/assets/stylesheets/common/form-kit/_checkbox-group.scss
index 7f7d62194e9..60102f6555c 100644
--- a/app/assets/stylesheets/common/form-kit/_checkbox-group.scss
+++ b/app/assets/stylesheets/common/form-kit/_checkbox-group.scss
@@ -1,5 +1,4 @@
.form-kit__checkbox-group {
display: flex;
- flex-direction: column;
- gap: 0.75em;
+ gap: 0em;
}
diff --git a/app/assets/stylesheets/common/form-kit/_control-custom-value-list.scss b/app/assets/stylesheets/common/form-kit/_control-custom-value-list.scss
new file mode 100644
index 00000000000..9328657334e
--- /dev/null
+++ b/app/assets/stylesheets/common/form-kit/_control-custom-value-list.scss
@@ -0,0 +1,7 @@
+.form-kit__control-custom {
+ .value-list {
+ .single-select.combobox {
+ width: 100%;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/common/form-kit/_index.scss b/app/assets/stylesheets/common/form-kit/_index.scss
index b2893085faf..82b07caabd0 100644
--- a/app/assets/stylesheets/common/form-kit/_index.scss
+++ b/app/assets/stylesheets/common/form-kit/_index.scss
@@ -20,6 +20,7 @@
@import "_control-select";
@import "_control-custom";
@import "_control-textarea";
+@import "_control-custom-value-list";
@import "_errors";
@import "_errors-summary";
@import "_field";
diff --git a/spec/system/admin_user_fields_spec.rb b/spec/system/admin_user_fields_spec.rb
index a3c2d5b8017..4d8d61612d2 100644
--- a/spec/system/admin_user_fields_spec.rb
+++ b/spec/system/admin_user_fields_spec.rb
@@ -23,7 +23,7 @@ describe "Admin User Fields", type: :system, js: true do
user_fields_page.add_field(name: "Occupation", description: "")
- expect(user_fields_page).to have_text(/Description can't be blank/)
+ expect(user_fields_page.form.field(:description)).to have_errors("Required")
end
it "makes sure new required fields are editable after signup" do
@@ -40,7 +40,7 @@ describe "Admin User Fields", type: :system, js: true do
user_fields_page.choose_requirement("optional")
- expect(form).to have_field(editable_label, checked: false, disabled: false)
+ expect(form).to have_field(editable_label, checked: true, disabled: false)
end
it "requires confirmation when applying required fields retroactively" do
diff --git a/spec/system/page_objects/components/form_kit.rb b/spec/system/page_objects/components/form_kit.rb
index 24fbd01bc88..93329bc8a00 100644
--- a/spec/system/page_objects/components/form_kit.rb
+++ b/spec/system/page_objects/components/form_kit.rb
@@ -67,6 +67,16 @@ module PageObjects
expect(self.value).to eq(expected_value)
end
+ def has_errors?(*messages)
+ within component do
+ messages.all? { |m| find(".form-kit__errors", text: m) }
+ end
+ end
+
+ def has_no_errors?
+ !has_css?(".form-kit__errors")
+ end
+
def control_type
component["data-control-type"]
end
diff --git a/spec/system/page_objects/pages/admin_user_fields.rb b/spec/system/page_objects/pages/admin_user_fields.rb
index 37692719c3d..664d51c04dd 100644
--- a/spec/system/page_objects/pages/admin_user_fields.rb
+++ b/spec/system/page_objects/pages/admin_user_fields.rb
@@ -8,6 +8,10 @@ module PageObjects
self
end
+ def form
+ PageObjects::Components::FormKit.new(".user-field .form-kit")
+ end
+
def choose_requirement(requirement)
form = page.find(".user-field")