DEV: Add a callback to the validation of user custom fields in the signup form (#27369)

# Description

Add `addCustomUserFieldValidationCallback` to the user fields validation mixin. This allows you to add a custom validation when checking the validity of custom user field values in the signup form on submit. 

```js
addCustomUserFieldValidationCallback((userField) => {
  if (userField.field.name === "my custom user field" && userField.value === "foo") {
    return EmberObject.create({
      failed: true,
      reason: I18n.t("value_can_not_be_foo"),
      element: userField.field.element,
    });
  }
});
```

In the case your custom validation deems an input value `failed`, you return an EmberObject with the fields `failed: true`, `reason`, and `element`.

```js
return EmberObject.create({
  failed: true,
  reason: I18n.t("value_can_not_be_foo"),
  element: userField.field.element,
});
```

which will then display your custom `reason` to the user attached to the given user custom field input and will not submit the signup form.

<img width="288" alt="Screenshot 2024-06-06 at 11 08 40 AM" src="https://github.com/discourse/discourse/assets/50783505/11168fb8-8806-43f0-9417-73991bbd1178">

# Other

- Add `addCustomUserFieldValidationCallback` to the plugin api
- Bump plugin api version
- Update plugin api changelog
- Add tests
This commit is contained in:
Isaac Janzen 2024-06-06 11:17:06 -06:00 committed by GitHub
parent dbd16776fa
commit 69193c4bd5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 111 additions and 1 deletions

View File

@ -96,6 +96,7 @@ import { includeAttributes } from "discourse/lib/transform-post";
import { registerUserMenuTab } from "discourse/lib/user-menu/tab";
import { replaceFormatter } from "discourse/lib/utilities";
import { addCardClickListenerSelector } from "discourse/mixins/card-contents-base";
import { addCustomUserFieldValidationCallback } from "discourse/mixins/user-fields-validation";
import Composer, {
registerCustomizationCallback,
} from "discourse/models/composer";
@ -152,7 +153,7 @@ import { modifySelectKit } from "select-kit/mixins/plugin-api";
// docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
// using the format described at https://keepachangelog.com/en/1.0.0/.
export const PLUGIN_API_VERSION = "1.32.0";
export const PLUGIN_API_VERSION = "1.33.0";
const DEPRECATED_HEADER_WIDGETS = [
"header",
@ -1288,6 +1289,33 @@ class PluginApi {
addTopicParticipantClassesCallback(callback);
}
/**
* Adds a callback when validating the value of a custom user field in the signup form.
*
* If the validation is intended to fail, the callback should return an Ember Object with the
* following properties: `failed`, `reason`, and `element`.
*
* In the case of a failed validation, the `reason` will be displayed to the user
* and the form will not be submitted.
*
*
* Example:
*
* addCustomUserFieldValidationCallback((userField) => {
* if (userField.field.name === "my custom user field" && userField.value === "foo") {
* return EmberObject.create({
* failed: true,
* reason: I18n.t("value_can_not_be_foo"),
* element: userField.field.element,
* });
* }
* });
**/
addCustomUserFieldValidationCallback(callback) {
addCustomUserFieldValidationCallback(callback);
}
/**
*
* Adds a callback to be executed on the "transformed" post that is passed to the post

View File

@ -4,6 +4,11 @@ import { isEmpty } from "@ember/utils";
import discourseComputed, { on } from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
const addCustomUserFieldValidationCallbacks = [];
export function addCustomUserFieldValidationCallback(callback) {
addCustomUserFieldValidationCallbacks.push(callback);
}
export default Mixin.create({
@on("init")
_createUserFields() {
@ -55,6 +60,13 @@ export default Mixin.create({
});
}
addCustomUserFieldValidationCallbacks.map((callback) => {
const customUserFieldValidationObject = callback(userField);
if (customUserFieldValidationObject) {
validation = customUserFieldValidationObject;
}
});
userField.set("validation", validation);
});

View File

@ -1,9 +1,27 @@
import EmberObject from "@ember/object";
import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers";
import { test } from "qunit";
import { withPluginApi } from "discourse/lib/plugin-api";
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import I18n from "discourse-i18n";
const CUSTOM_VALIDATION_REASON = "bad choice";
acceptance("Create Account - User Fields", function (needs) {
needs.hooks.beforeEach(function () {
withPluginApi("1.33.0", (api) => {
api.addCustomUserFieldValidationCallback((userField) => {
if (userField.field.id === 37 && userField.value !== "red") {
return EmberObject.create({
failed: true,
reason: CUSTOM_VALIDATION_REASON,
element: userField.field.element,
});
}
});
});
});
needs.site({
user_fields: [
{
@ -24,6 +42,12 @@ acceptance("Create Account - User Fields", function (needs) {
field_type: "text",
required: false,
},
{
id: 37,
name: "What is your favorite color?",
field_type: "text",
required: true,
},
],
});
@ -84,4 +108,46 @@ acceptance("Create Account - User Fields", function (needs) {
.dom(".user-field-whats-your-dad-like .tip.bad")
.exists("shows same as password error");
});
test("allows for custom validations of user fields", async function (assert) {
await visit("/");
await click("header .sign-up-button");
await fillIn(".user-field-what-is-your-favorite-color input", "blue");
assert
.dom(".user-field-what-is-your-favorite-color .tip.bad")
.doesNotExist(
"it does not show error message until the form is submitted"
);
await click(".d-modal__footer .btn-primary");
assert
.dom(".user-field-what-is-your-favorite-color .tip.bad")
.hasText(CUSTOM_VALIDATION_REASON, "shows custom error message");
});
test("it does not submit the form when custom validation fails", async function (assert) {
await visit("/");
await click("header .sign-up-button");
// incorrect value for custom validation
await fillIn(".user-field-what-is-your-favorite-color input", "blue");
await fillIn("#new-account-name", "Dr. Good Tuna");
await fillIn("#new-account-password", "cool password bro");
await fillIn("#new-account-email", "good.tuna@test.com");
await fillIn("#new-account-username", "goodtuna");
await fillIn(".user-field input[type=text]:nth-of-type(1)", "Barky");
await click(".user-field input[type=checkbox]");
await click(".d-modal__footer .btn-primary");
assert
.dom(".user-field-what-is-your-favorite-color .tip.bad")
.hasText(
CUSTOM_VALIDATION_REASON,
"shows custom error message, and the form is not submitted"
);
});
});

View File

@ -7,6 +7,10 @@ in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.33.0] - 2024-06-06
- Added `addCustomUserFieldValidationCallback` which allows to set a callback to change the validation and user facing message when attempting to save the signup form.
## [1.32.0] - 2024-05-16
- Added `registerHomeLogoHrefCallback` which allows to set a callback to change the home logo URL.