diff --git a/assets/javascripts/discourse/connectors/after-admin-user-fields/custom-user-fields.gjs b/assets/javascripts/discourse/connectors/after-admin-user-fields/custom-user-fields.gjs index e487f6f..d8c09b2 100644 --- a/assets/javascripts/discourse/connectors/after-admin-user-fields/custom-user-fields.gjs +++ b/assets/javascripts/discourse/connectors/after-admin-user-fields/custom-user-fields.gjs @@ -1,7 +1,10 @@ import Component from "@glimmer/component"; import { tracked } from "@glimmer/tracking"; import { Input } from "@ember/component"; +import { fn } from "@ember/helper"; +import { on } from "@ember/modifier"; import { inject as service } from "@ember/service"; +import withEventValue from "discourse/helpers/with-event-value"; import { withPluginApi } from "discourse/lib/plugin-api"; import i18n from "discourse-common/helpers/i18n"; import AdminFormRow from "admin/components/admin-form-row"; @@ -18,9 +21,12 @@ export default class CustomUserFields extends Component { constructor() { super(...arguments); withPluginApi("1.21.0", (api) => { - ["has_custom_validation", "show_values", "target_user_field_ids"].forEach( - (property) => api.includeUserFieldPropertyOnSave(property) - ); + [ + "has_custom_validation", + "show_values", + "target_user_field_ids", + "value_validation_regex", + ].forEach((property) => api.includeUserFieldPropertyOnSave(property)); }); } @@ -37,6 +43,28 @@ export default class CustomUserFields extends Component { {{#if @outletArgs.buffered.has_custom_validation}} + + +
+ + {{i18n + "discourse_authentication_validations.value_validation_regex.description" + }} + +
+ @@ -59,6 +87,7 @@ export default class CustomUserFields extends Component { @content={{this.userFieldsMinusCurrent}} @valueProperty="id" @value={{@outletArgs.buffered.target_user_field_ids}} + class="target-user-field-ids-input" />
diff --git a/assets/javascripts/discourse/initializers/user-field-validation.js b/assets/javascripts/discourse/initializers/user-field-validation.js new file mode 100644 index 0000000..6886378 --- /dev/null +++ b/assets/javascripts/discourse/initializers/user-field-validation.js @@ -0,0 +1,35 @@ +import EmberObject from "@ember/object"; +import { withPluginApi } from "discourse/lib/plugin-api"; +import I18n from "discourse-i18n"; + +export default { + name: "user-field-validation", + initialize() { + withPluginApi("1.33.0", (api) => { + api.addCustomUserFieldValidationCallback((userField) => { + if ( + userField.field.has_custom_validation && + userField.field.value_validation_regex && + userField.value + ) { + if ( + !new RegExp(userField.field.value_validation_regex).test( + userField.value + ) + ) { + return EmberObject.create({ + failed: true, + reason: I18n.t( + "discourse_authentication_validations.value_validation_error_message", + { + user_field_name: userField.field.name, + } + ), + element: userField.field.element, + }); + } + } + }); + }); + }, +}; diff --git a/assets/stylesheets/common/admin/common.scss b/assets/stylesheets/common/admin/common.scss new file mode 100644 index 0000000..42b0497 --- /dev/null +++ b/assets/stylesheets/common/admin/common.scss @@ -0,0 +1,4 @@ +.show-values-input, +.target-user-field-ids-input { + margin: 0.4em 0; +} diff --git a/assets/stylesheets/common/common.scss b/assets/stylesheets/common/common.scss deleted file mode 100644 index e69de29..0000000 diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index e605ea8..a8fc8e6 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -13,3 +13,8 @@ en: target_user_field_ids: label: "Target User Fields" description: "The User Field(s) that will be made visible" + value_validation_regex: + label: "Value Validation Regex" + description: "The regular expression that the value must match (only applicable to text fields)" + value_validation_error_message: | + Please enter a valid %{user_field_name} diff --git a/db/migrate/20240605190725_add_value_validation_regex_to_user_field.rb b/db/migrate/20240605190725_add_value_validation_regex_to_user_field.rb new file mode 100644 index 0000000..e6a922a --- /dev/null +++ b/db/migrate/20240605190725_add_value_validation_regex_to_user_field.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddValueValidationRegexToUserField < ActiveRecord::Migration[7.0] + def change + add_column :user_fields, :value_validation_regex, :string, default: "", null: true + end +end diff --git a/plugin.rb b/plugin.rb index 72416ba..a20dac7 100644 --- a/plugin.rb +++ b/plugin.rb @@ -10,6 +10,8 @@ enabled_site_setting :discourse_authentication_validations_enabled +register_asset "stylesheets/common/admin/common.scss" + module ::DiscourseAuthenticationValidations PLUGIN_NAME = "discourse-authentication-validations" end @@ -20,9 +22,15 @@ module ::DiscourseAuthenticationValidations add_to_serializer(:user_field, :has_custom_validation) { object.has_custom_validation } add_to_serializer(:user_field, :show_values) { object.show_values } add_to_serializer(:user_field, :target_user_field_ids) { object.target_user_field_ids } + add_to_serializer(:user_field, :value_validation_regex) { object.value_validation_regex } register_modifier(:admin_user_fields_columns) do |columns| - columns.push(:has_custom_validation, :show_values, :target_user_field_ids) + columns.push( + :has_custom_validation, + :show_values, + :target_user_field_ids, + :value_validation_regex, + ) columns end end diff --git a/spec/system/value_validation_regex_user_field_spec.rb b/spec/system/value_validation_regex_user_field_spec.rb new file mode 100644 index 0000000..d23e600 --- /dev/null +++ b/spec/system/value_validation_regex_user_field_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +RSpec.describe "Discourse Authentication Validation - Value Validation Regex - User Field", + type: :system do + let(:signup_modal) { PageObjects::Modals::Signup.new } + fab!(:user_field_with_value_validation_regex) do + Fabricate( + :user_field, + name: "phone number", + field_type: "text", + editable: true, + required: true, + has_custom_validation: true, + show_values: [], + target_user_field_ids: [], + value_validation_regex: "^\\d{2} \\d{4} \\d{4}$", # 00 0000 0000 + ) + end + + fab!(:user_field_without_value_validation_regex) do + Fabricate( + :user_field, + name: "unvalidated phone number", + field_type: "text", + editable: true, + required: true, + has_custom_validation: true, + show_values: [], + target_user_field_ids: [], + value_validation_regex: nil, + ) + end + + before { SiteSetting.discourse_authentication_validations_enabled = true } + + context "when a user field has a value_validation_regex" do + it "displays a validation error message when the target user field input is incorrect" do + signup_modal.open + signup_modal.fill_custom_field("phone-number", "not a phone number") + signup_modal.click_create_account + + expect(signup_modal).to have_text( + I18n.t( + "js.discourse_authentication_validations.value_validation_error_message", + { user_field_name: user_field_with_value_validation_regex.name }, + ), + ) + end + + it "displays the default error message for no value" do + signup_modal.open + signup_modal.click_create_account + + expect(signup_modal).not_to have_text( + I18n.t( + "js.discourse_authentication_validations.value_validation_error_message", + { user_field_name: user_field_with_value_validation_regex.name }, + ), + ) + expect(signup_modal).to have_text( + I18n.t("js.user_fields.required", { name: user_field_with_value_validation_regex.name }), + ) + end + + it "clears the validation error message when the target user field input is correct" do + signup_modal.open + signup_modal.fill_custom_field("phone-number", "not a phone number") + signup_modal.click_create_account + + signup_modal.fill_custom_field("phone-number", "11 2222 3333") + signup_modal.click_create_account + + expect(signup_modal).not_to have_text( + I18n.t( + "js.discourse_authentication_validations.value_validation_error_message", + { user_field_name: user_field_with_value_validation_regex.name }, + ), + ) + end + end + + context "when a user field does not have a value_validation_regex" do + it "does not display a validation error message" do + signup_modal.open + signup_modal.fill_custom_field("unvalidated-phone-number", "not a phone number") + signup_modal.click_create_account + + expect(signup_modal).not_to have_text( + I18n.t( + "js.discourse_authentication_validations.value_validation_error_message", + { user_field_name: user_field_without_value_validation_regex.name }, + ), + ) + end + end + + context "when a user field has a value_validation_regex and has_custom_validation is false" do + it "does not display a validation error message" do + user_field_with_value_validation_regex.update!(has_custom_validation: false) + signup_modal.open + signup_modal.fill_custom_field("phone-number", "not a phone number") + signup_modal.click_create_account + + expect(signup_modal).not_to have_text( + I18n.t( + "js.discourse_authentication_validations.value_validation_error_message", + { user_field_name: user_field_with_value_validation_regex.name }, + ), + ) + end + end +end diff --git a/test/javascripts/acceptance/custom-user-fields-test.js b/test/javascripts/acceptance/admin-page-custom-user-fields-test.js similarity index 96% rename from test/javascripts/acceptance/custom-user-fields-test.js rename to test/javascripts/acceptance/admin-page-custom-user-fields-test.js index 8bcbc27..a641b3c 100644 --- a/test/javascripts/acceptance/custom-user-fields-test.js +++ b/test/javascripts/acceptance/admin-page-custom-user-fields-test.js @@ -30,7 +30,7 @@ const userFields = { }; acceptance( - "Discourse Authentication Validations - Custom User Fields", + "Discourse Authentication Validations - Admin Page - Custom User Fields", function (needs) { needs.user(); needs.settings({