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({