From 69193c4bd55cdde9740850d6349830fed79aa154 Mon Sep 17 00:00:00 2001 From: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com> Date: Thu, 6 Jun 2024 11:17:06 -0600 Subject: [PATCH] DEV: Add a callback to the validation of user custom fields in the signup form (#27369) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 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. Screenshot 2024-06-06 at 11 08 40 AM # Other - Add `addCustomUserFieldValidationCallback` to the plugin api - Bump plugin api version - Update plugin api changelog - Add tests --- .../discourse/app/lib/plugin-api.gjs | 30 ++++++++- .../app/mixins/user-fields-validation.js | 12 ++++ .../create-account-user-fields-test.js | 66 +++++++++++++++++++ docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md | 4 ++ 4 files changed, 111 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.gjs b/app/assets/javascripts/discourse/app/lib/plugin-api.gjs index 161ad6f1de6..a5cb16fd7b0 100644 --- a/app/assets/javascripts/discourse/app/lib/plugin-api.gjs +++ b/app/assets/javascripts/discourse/app/lib/plugin-api.gjs @@ -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 diff --git a/app/assets/javascripts/discourse/app/mixins/user-fields-validation.js b/app/assets/javascripts/discourse/app/mixins/user-fields-validation.js index c2ceaf26f34..17d10d154f4 100644 --- a/app/assets/javascripts/discourse/app/mixins/user-fields-validation.js +++ b/app/assets/javascripts/discourse/app/mixins/user-fields-validation.js @@ -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); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js b/app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js index 2a40101c5f4..04f2c90ee6d 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/create-account-user-fields-test.js @@ -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" + ); + }); }); diff --git a/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md b/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md index d8c0173cd15..a73f972f65d 100644 --- a/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md +++ b/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md @@ -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.