diff --git a/.erb-lint.yml b/.erb-lint.yml
index cf33d296b7c..d5cedd40c18 100644
--- a/.erb-lint.yml
+++ b/.erb-lint.yml
@@ -58,7 +58,9 @@ linters:
- '*/app/views/users/rules_of_use/*'
- '*/app/views/users/service_provider_inactive/*'
- '*/app/views/users/service_provider_revoke/*'
- - '*/app/views/users/shared/*'
+ - '*/app/views/users/shared/_otp_delivery_preference_selection.html.erb'
+ - '*/app/views/users/shared/_otp_make_default_number.html.erb'
+ - '*/app/views/users/shared/_phone_number_edit.html.erb'
- '*/app/views/users/totp_setup/*'
- '*/app/views/users/two_factor_authentication_setup/*'
- '*/app/views/users/verify_password/*'
diff --git a/app/javascript/app/form-field-format.js b/app/javascript/app/form-field-format.js
deleted file mode 100644
index 9cdba9905be..00000000000
--- a/app/javascript/app/form-field-format.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import Cleave from 'cleave.js';
-
-/* eslint-disable no-new */
-function formatForm() {
- if (document.querySelector('.dob')) {
- new Cleave('.dob', {
- date: true,
- datePattern: ['m', 'd', 'Y'],
- });
- }
-
- if (document.querySelector('.personal-key')) {
- new Cleave('.personal-key', {
- blocks: [4, 4, 4, 4],
- delimiter: '-',
- });
- }
-
- if (document.querySelector('.backup-code')) {
- new Cleave('.backup-code', {
- blocks: [4, 4, 4],
- delimiter: '-',
- });
- }
-
- if (document.querySelector('.zipcode')) {
- new Cleave('.zipcode', {
- numericOnly: true,
- blocks: [5, 4],
- delimiter: '-',
- delimiterLazyShow: true,
- });
- }
-}
-
-document.addEventListener('DOMContentLoaded', formatForm);
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 0c49af61b31..802a1112228 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -1,8 +1,6 @@
require('../app/components/index');
require('../app/utils/index');
require('../app/pw-toggle');
-require('../app/form-field-format');
require('../app/print-personal-key');
require('../app/i18n-dropdown');
require('../app/accessible-forms');
-require('../app/ssn-field');
diff --git a/app/javascript/packs/form-validation.js b/app/javascript/packs/form-validation.js
index 0a3fd242d26..d9f79c7129a 100644
--- a/app/javascript/packs/form-validation.js
+++ b/app/javascript/packs/form-validation.js
@@ -3,7 +3,7 @@ import { loadPolyfills } from '@18f/identity-polyfill';
/** @typedef {{t:(key:string)=>string, key:(key:string)=>string}} LoginGovI18n */
/** @typedef {{LoginGov:{I18n:LoginGovI18n}}} LoginGovGlobal */
-const PATTERN_TYPES = ['dob', 'personal-key', 'ssn', 'state_id_number', 'zipcode'];
+const PATTERN_TYPES = ['personal-key', 'ssn', 'zipcode'];
/**
* Given a submit event, disables all submit buttons within the target form.
@@ -41,10 +41,8 @@ function checkInputValidity(event) {
} else if (input.validity.patternMismatch) {
PATTERN_TYPES.forEach((type) => {
if (input.classList.contains(type)) {
- // i18n-tasks-use t('idv.errors.pattern_mismatch.dob')
// i18n-tasks-use t('idv.errors.pattern_mismatch.personal_key')
// i18n-tasks-use t('idv.errors.pattern_mismatch.ssn')
- // i18n-tasks-use t('idv.errors.pattern_mismatch.state_id_number')
// i18n-tasks-use t('idv.errors.pattern_mismatch.zipcode')
input.setCustomValidity(I18n.t(`idv.errors.pattern_mismatch.${I18n.key(type)}`));
}
diff --git a/app/javascript/packs/formatted-fields.js b/app/javascript/packs/formatted-fields.js
new file mode 100644
index 00000000000..07605584a45
--- /dev/null
+++ b/app/javascript/packs/formatted-fields.js
@@ -0,0 +1,23 @@
+import Cleave from 'cleave.js';
+
+const SELECTOR_CONFIGS = {
+ '.personal-key': {
+ blocks: [4, 4, 4, 4],
+ delimiter: '-',
+ },
+ '.backup-code': {
+ blocks: [4, 4, 4],
+ delimiter: '-',
+ },
+ '.zipcode': {
+ numericOnly: true,
+ blocks: [5, 4],
+ delimiter: '-',
+ delimiterLazyShow: true,
+ },
+};
+
+Object.entries(SELECTOR_CONFIGS)
+ .map(([selector, config]) => [document.querySelector(selector), config])
+ .filter(([element]) => element)
+ .forEach(([element, config]) => new Cleave(element, config));
diff --git a/app/javascript/app/ssn-field.js b/app/javascript/packs/ssn-field.js
similarity index 100%
rename from app/javascript/app/ssn-field.js
rename to app/javascript/packs/ssn-field.js
diff --git a/app/views/idv/address/new.html.erb b/app/views/idv/address/new.html.erb
index 2072586c35d..6e13a990eca 100644
--- a/app/views/idv/address/new.html.erb
+++ b/app/views/idv/address/new.html.erb
@@ -49,3 +49,4 @@
<%= render 'idv/doc_auth/back', step: 'verify' %>
+<%= javascript_packs_tag_once('formatted-fields') %>
diff --git a/app/views/idv/doc_auth/_ssn_init.html.erb b/app/views/idv/doc_auth/_ssn_init.html.erb
index 110da425170..a66baf8e84c 100644
--- a/app/views/idv/doc_auth/_ssn_init.html.erb
+++ b/app/views/idv/doc_auth/_ssn_init.html.erb
@@ -24,16 +24,7 @@
) do |f| %>
-
- <%= f.input(
- :ssn,
- as: :password,
- label: t('idv.form.ssn_label_html'),
- required: true,
- pattern: '^\d{3}-?\d{2}-?\d{4}$',
- maxlength: 11,
- input_html: { aria: { invalid: false }, class: 'ssn ssn-toggle', value: '' }
- ) %>
+ <%= render 'shared/ssn_field', f: f %>
diff --git a/app/views/idv/doc_auth/_ssn_update.html.erb b/app/views/idv/doc_auth/_ssn_update.html.erb
index 796f6d2fbd1..e4b78383f48 100644
--- a/app/views/idv/doc_auth/_ssn_update.html.erb
+++ b/app/views/idv/doc_auth/_ssn_update.html.erb
@@ -17,16 +17,7 @@
) do |f| %>
-
- <%= f.input(
- :ssn,
- as: :password,
- label: t('idv.form.ssn_label_html'),
- required: true,
- pattern: '^\d{3}-?\d{2}-?\d{4}$',
- maxlength: 11,
- input_html: { aria: { invalid: false }, class: 'ssn ssn-toggle', value: '' }
- ) %>
+ <%= render 'shared/ssn_field', f: f %>
@@ -39,4 +30,3 @@
<% end %>
<%= render 'idv/doc_auth/back', action: 'cancel_update_ssn' %>
-
diff --git a/app/views/idv/gpo/_new_address.html.erb b/app/views/idv/gpo/_new_address.html.erb
index 003f5f5d733..e2cae1457e9 100644
--- a/app/views/idv/gpo/_new_address.html.erb
+++ b/app/views/idv/gpo/_new_address.html.erb
@@ -30,3 +30,4 @@
<% end %>
+<%= javascript_packs_tag_once('formatted-fields') %>
diff --git a/app/views/partials/backup_code/_entry_fields.html.erb b/app/views/partials/backup_code/_entry_fields.html.erb
index 9592e55904c..f6ba24cbadf 100644
--- a/app/views/partials/backup_code/_entry_fields.html.erb
+++ b/app/views/partials/backup_code/_entry_fields.html.erb
@@ -9,3 +9,4 @@
aria: { invalid: false },
spellcheck: 'false' } %>
+<%= javascript_packs_tag_once('formatted-fields') %>
diff --git a/app/views/partials/personal_key/_entry_fields.html.erb b/app/views/partials/personal_key/_entry_fields.html.erb
index 6a7b8a67db0..eb3f744fca9 100644
--- a/app/views/partials/personal_key/_entry_fields.html.erb
+++ b/app/views/partials/personal_key/_entry_fields.html.erb
@@ -11,3 +11,4 @@
aria: { invalid: false },
spellcheck: 'false' } %>
+<%= javascript_packs_tag_once('formatted-fields') %>
diff --git a/app/views/shared/_personal_key_input.html.erb b/app/views/shared/_personal_key_input.html.erb
index 6a8e2a26f3b..a851e7b13b2 100644
--- a/app/views/shared/_personal_key_input.html.erb
+++ b/app/views/shared/_personal_key_input.html.erb
@@ -8,3 +8,4 @@
aria-invalid="false"
spellcheck="false"
type="text" />
+<%= javascript_packs_tag_once('formatted-fields') %>
diff --git a/app/views/shared/_ssn_field.html.erb b/app/views/shared/_ssn_field.html.erb
new file mode 100644
index 00000000000..77b06f83834
--- /dev/null
+++ b/app/views/shared/_ssn_field.html.erb
@@ -0,0 +1,16 @@
+<%#
+locals:
+* f: from validated_form_for
+%>
+
+<%# maxlength set and includes '-' delimiters to work around cleave bug %>
+<%= f.input(
+ :ssn,
+ as: :password,
+ label: t('idv.form.ssn_label_html'),
+ required: true,
+ pattern: '^\d{3}-?\d{2}-?\d{4}$',
+ maxlength: 11,
+ input_html: { aria: { invalid: false }, class: 'ssn ssn-toggle', value: '' }
+ ) %>
+<%= javascript_packs_tag_once('ssn-field') %>
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index 9342d46d6ae..348c6d32ad2 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -99,7 +99,6 @@ ignore_unused:
- 'errors.messages.*'
- 'forms.two_factor_choice.legend'
- 'forms.two_factor_recovery_choice.legend'
- - 'idv.errors.pattern_mismatch.*'
- 'jobs.sms_otp_sender_job.login_message'
- 'jobs.sms_otp_sender_job.verify_message'
- 'service_providers.*'
diff --git a/config/locales/idv/en.yml b/config/locales/idv/en.yml
index b6da1715c74..43b2ef59104 100644
--- a/config/locales/idv/en.yml
+++ b/config/locales/idv/en.yml
@@ -30,11 +30,9 @@ en:
incorrect_password: The password you entered is not correct.
mail_limit_reached: You have requested too much mail in the last month.
pattern_mismatch:
- dob: Your date of birth must be entered in as mm/dd/yyyy
personal_key: 'Please enter your personal key for this account. Example:
ABC1-DEF2-G3HI-J456'
ssn: 'Your Social Security Number must be entered in as ###-##-####'
- state_id_number: Your ID number cannot be more than 25 characters.
zipcode: 'Your zipcode must be entered in as #####-####'
unsupported_otp_delivery_method: Select a method to receive a code.
failure:
diff --git a/config/locales/idv/es.yml b/config/locales/idv/es.yml
index e990e9d77de..2cc46cfe61c 100644
--- a/config/locales/idv/es.yml
+++ b/config/locales/idv/es.yml
@@ -32,11 +32,9 @@ es:
incorrect_password: La contraseña que ingresó no es correcta.
mail_limit_reached: Usted ha solicitado demasiado correo en el último mes.
pattern_mismatch:
- dob: Su fecha de nacimiento debe ser ingresada en este formato mes/día/año.
personal_key: 'Introduzca su clave personal para esta cuenta. Ejemplo:
ABC1-DEF2-G3HI-J456'
ssn: 'Su número de Seguro Social debe ser ingresado como ### - ## - ####'
- state_id_number: Su número de ID no puede tener más de 25 caracteres
zipcode: 'Su código postal debe ser ingresado como #####-####'
unsupported_otp_delivery_method: Seleccione una manera de recibir un código.
failure:
diff --git a/config/locales/idv/fr.yml b/config/locales/idv/fr.yml
index 30a6a9054d6..dfb5d8f85f0 100644
--- a/config/locales/idv/fr.yml
+++ b/config/locales/idv/fr.yml
@@ -34,12 +34,10 @@ fr:
incorrect_password: Le mot de passe que vous avez inscrit est incorrect.
mail_limit_reached: Vous avez demandé trop de lettres au cours du dernier mois.
pattern_mismatch:
- dob: 'Votre date de naissance doit être inscrite de cette façon: mm/jj/aaaa'
personal_key: 'Veuillez inscrire votre clé personnelle pour ce compte, par
exemple : ABC1-DEF2-G3HI-J456'
ssn: 'Votre numéro de sécurité sociale doit être inscrit de cette façon :
###-##-####'
- state_id_number: Votre numéro d’identification ne peut excéder 25 caractères
zipcode: 'Votre code ZIP doit être inscrit de cette façon : #####-####'
unsupported_otp_delivery_method: Sélectionnez une méthode pour recevoir un code.
failure: