Skip to content

Commit

Permalink
Merge pull request #1568 from 18F/jmhooper-phone-number-format
Browse files Browse the repository at this point in the history
Update international code as user types phone
  • Loading branch information
jmhooper authored Jul 26, 2017
2 parents 480aade + 0733a6d commit 16fef6e
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 24 deletions.
5 changes: 3 additions & 2 deletions app/assets/javascripts/app/form-field-format.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PhoneFormatter, SocialSecurityNumberFormatter, TextField } from 'field-kit';
import { SocialSecurityNumberFormatter, TextField } from 'field-kit';
import DateFormatter from './modules/date-formatter';
import InternationalPhoneFormatter from './modules/international-phone-formatter';
import NumericFormatter from './modules/numeric-formatter';
import PersonalKeyFormatter from './modules/personal-key-formatter';
import ZipCodeFormatter from './modules/zip-code-formatter';
Expand All @@ -13,7 +14,7 @@ function formatForm() {
['.home_equity_line', new NumericFormatter()],
['.mfa', new NumericFormatter()],
['.mortgage', new NumericFormatter()],
['.phone', new PhoneFormatter()],
['.phone', new InternationalPhoneFormatter()],
['.personal-key', new PersonalKeyFormatter()],
['.ssn', new SocialSecurityNumberFormatter()],
['.zipcode', new ZipCodeFormatter()],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Formatter } from 'field-kit';
import { asYouType as AsYouType } from 'libphonenumber-js';

const fixCountryCodeSpacing = (text, countryCode) => {
// If the text is `+123456`, make it `+123 456`
if (text[countryCode.length + 1] !== ' ') {
return text.replace(`+${countryCode}`, `+${countryCode} `);
}
return text;
};

const getFormattedTextData = (text) => {
if (text === '1') {
text = '+1';
}

const asYouType = new AsYouType('US');
let formattedText = asYouType.input(text);
const countryCode = asYouType.country_phone_code;

if (asYouType.country_phone_code) {
formattedText = fixCountryCodeSpacing(formattedText, countryCode);
}

return {
text: formattedText,
template: asYouType.template,
countryCode,
};
};

const cursorPosition = (formattedTextData) => {
// If the text is `(23 )` the cursor goes after the 3
const match = formattedTextData.text.match(/\d[^\d]*$/);
if (match) {
return match.index + 1;
}
return formattedTextData.text.length + 1;
};

class InternationalPhoneFormatter extends Formatter {
format(text) {
const formattedTextData = getFormattedTextData(text);
return super.format(formattedTextData.text);
}

// eslint-disable-next-line class-methods-use-this
parse(text) {
return text.replace(/[^\d+]/g, '');
}

isChangeValid(change, error) {
const formattedTextData = getFormattedTextData(change.proposed.text);
const previousFormattedTextData = getFormattedTextData(change.current.text);

if (previousFormattedTextData.template &&
!formattedTextData.template &&
change.inserted.text.length === 1
) {
return false;
}

change.proposed.text = formattedTextData.text;
change.proposed.selectedRange.start = cursorPosition(formattedTextData);
return super.isChangeValid(change, error);
}
}

export default InternationalPhoneFormatter;
57 changes: 53 additions & 4 deletions app/assets/javascripts/app/phone-internationalization.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { PhoneFormatter } from 'field-kit';

const INTERNATIONAL_CODE_REGEX = /^\+(\d+) |^1 /;

const I18n = window.LoginGov.I18n;
const phoneFormatter = new PhoneFormatter();

Expand All @@ -18,7 +20,7 @@ const areaCodeFromUSPhone = (phone) => {
};

const selectedInternationCodeOption = () => {
const dropdown = document.querySelector('#two_factor_setup_form_international_code');
const dropdown = document.querySelector('[data-international-phone-form] .international-code');
return dropdown.item(dropdown.selectedIndex);
};

Expand Down Expand Up @@ -50,9 +52,14 @@ const unsupportedPhoneOTPDeliveryWarningMessage = (phone) => {
};

const updateOTPDeliveryMethods = () => {
const phoneInput = document.querySelector('#two_factor_setup_form_phone');
const phoneRadio = document.querySelector('#two_factor_setup_form_otp_delivery_preference_voice');
const smsRadio = document.querySelector('#two_factor_setup_form_otp_delivery_preference_sms');

if (!phoneRadio || !smsRadio) {
return;
}

const phoneInput = document.querySelector('[data-international-phone-form] .phone');
const phoneLabel = phoneRadio.parentNode.parentNode;
const deliveryMethodHint = document.querySelector('#otp_delivery_preference_instruction');
const optPhoneLabelInfo = document.querySelector('#otp_phone_label_info');
Expand All @@ -74,14 +81,56 @@ const updateOTPDeliveryMethods = () => {
}
};

const internationalCodeFromPhone = (phone) => {
const match = phone.match(INTERNATIONAL_CODE_REGEX);
if (match) {
return match[1] || match[2];
}
return '1';
};

const updateInternationalCodeSelection = () => {
const phoneInput = document.querySelector('[data-international-phone-form] .phone');
const phone = phoneInput.value;
const internationalCode = internationalCodeFromPhone(phone);
const option = document.querySelector(`[data-country-code='${internationalCode}']`);
if (option) {
const dropdown = document.querySelector('[data-international-phone-form] .international-code');
dropdown.value = option.value;
}
};

const updateInternationalCodeInPhone = (phone, newCode) => {
if (phone.match(/^\+[^d+]$/)) {
phone = phone.replace(/^\+[^d+]$/, '');
}
if (phone.match(INTERNATIONAL_CODE_REGEX)) {
return phone.replace(INTERNATIONAL_CODE_REGEX, `+${newCode} `);
}
return `+${newCode} ${phone}`;
};

const updateInternationalCodeInput = () => {
const phoneInput = document.querySelector('[data-international-phone-form] .phone');
const phone = phoneInput.value;
const inputInternationalCode = internationalCodeFromPhone(phone);
const selectedInternationalCode = selectedInternationCodeOption().dataset.countryCode;

if (inputInternationalCode !== selectedInternationalCode) {
phoneInput.value = updateInternationalCodeInPhone(phone, selectedInternationalCode);
}
};

document.addEventListener('DOMContentLoaded', () => {
const phoneInput = document.querySelector('#two_factor_setup_form_phone');
const codeInput = document.querySelector('#two_factor_setup_form_international_code');
const phoneInput = document.querySelector('[data-international-phone-form] .phone');
const codeInput = document.querySelector('[data-international-phone-form] .international-code');
if (phoneInput) {
phoneInput.addEventListener('keyup', updateOTPDeliveryMethods);
phoneInput.addEventListener('keyup', updateInternationalCodeSelection);
}
if (codeInput) {
codeInput.addEventListener('change', updateOTPDeliveryMethods);
codeInput.addEventListener('change', updateInternationalCodeInput);
updateOTPDeliveryMethods();
}
});
8 changes: 6 additions & 2 deletions app/views/users/phones/edit.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@


h1.h3.my0 = t('headings.edit_info.phone')
= simple_form_for(@update_user_phone_form, url: manage_phone_path,
= simple_form_for(@update_user_phone_form,
data: { unsupported_area_codes: @unsupported_area_codes,
international_phone_form: true },
url: manage_phone_path,
html: { autocomplete: 'off', method: :put, role: 'form' }) do |f|
= f.input :international_code,
collection: international_phone_codes,
include_blank: false
include_blank: false,
input_html: { class: 'international-code' }
= f.input :phone, as: :tel, required: true, input_html: { class: 'phone', value: nil },
label: t('account.index.phone')
= f.button :submit, t('forms.buttons.submit.confirm_change'), class: 'mt2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ p.mt-tiny.mb0
= t('devise.two_factor_authentication.otp_setup_html')
= simple_form_for(@two_factor_setup_form,
html: { autocomplete: 'off', role: 'form' },
data: { unsupported_area_codes: @unsupported_area_codes },
data: { unsupported_area_codes: @unsupported_area_codes,
international_phone_form: true },
method: :patch,
url: phone_setup_path) do |f|
.clearfix
.sm-col.sm-col-8
= f.input :international_code,
collection: international_phone_codes,
include_blank: false
include_blank: false,
input_html: { class: 'international-code' }
= f.label :phone, class: 'block'
strong.left = t('devise.two_factor_authentication.otp_phone_label')
span#otp_phone_label_info.ml1.italic
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"field-kit": "^2.1.0",
"focus-trap": "^2.3.0",
"hint.css": "^2.3.2",
"libphonenumber-js": "^0.4.23",
"normalize.css": "^4.2.0",
"sinon": "^1.17.7",
"zxcvbn": "^4.4.2"
Expand Down
2 changes: 1 addition & 1 deletion spec/features/idv/phone_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
fill_in 'Phone', with: ''
find('#idv_phone_form_phone').native.send_keys('abcd1234')

expect(find('#idv_phone_form_phone').value).to eq '1 (234) '
expect(find('#idv_phone_form_phone').value).to eq '+1 234'
end

def complete_idv_profile_with_phone(phone)
Expand Down
19 changes: 18 additions & 1 deletion spec/features/two_factor_authentication/sign_in_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
scenario 'disables the phone option and displays a warning with js', :js do
sign_in_before_2fa
select 'Turkey +90', from: 'International code'
fill_in 'Phone', with: '555-555-5000'
fill_in 'Phone', with: '+90 312 213 29 65'
phone_radio_button = page.find(
'#two_factor_setup_form_otp_delivery_preference_voice',
visible: :all
Expand All @@ -112,6 +112,23 @@
)
expect(phone_radio_button).to_not be_disabled
end

scenario 'updates international code as user types', :js do
sign_in_before_2fa
fill_in 'Phone', with: '+81 54 354 3643'

expect(page.find('#two_factor_setup_form_international_code').value).to eq 'JP'

fill_in 'Phone', with: '5376'
select 'Morocco +212', from: 'International code'

expect(find('#two_factor_setup_form_phone').value).to eq '+212 5376'

fill_in 'Phone', with: '54354'
select 'Japan +81', from: 'International code'

expect(find('#two_factor_setup_form_phone').value).to include '+81'
end
end
end

Expand Down
48 changes: 36 additions & 12 deletions spec/features/users/user_edit_spec.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,47 @@
require 'rails_helper'

feature 'User edit' do
scenario 'user sees error message if form is submitted without email', js: true, idv_job: true do
sign_in_and_2fa_user
context 'editing email' do
before do
sign_in_and_2fa_user
visit manage_email_path
end

visit manage_email_path
fill_in 'Email', with: ''
click_button 'Update'
scenario 'user sees error message if form is submitted without email', :js, idv_job: true do
fill_in 'Email', with: ''
click_button 'Update'

expect(page).to have_content t('valid_email.validations.email.invalid')
expect(page).to have_content t('valid_email.validations.email.invalid')
end
end

scenario 'user sees error message if form is submitted without phone number', js: true do
sign_in_and_2fa_user
context 'editing 2FA phone number' do
before do
sign_in_and_2fa_user
visit manage_phone_path
end

visit manage_phone_path
fill_in 'Phone', with: ''
click_button t('forms.buttons.submit.confirm_change')
scenario 'user sees error message if form is submitted without phone number', js: true do
fill_in 'Phone', with: ''
click_button t('forms.buttons.submit.confirm_change')

expect(page).to have_content t('errors.messages.improbable_phone')
expect(page).to have_content t('errors.messages.improbable_phone')
end

scenario 'updates international code as user types', :js do
fill_in 'Phone', with: '+81 54 354 3643'

expect(page.find('#update_user_phone_form_international_code').value).to eq 'JP'

fill_in 'Phone', with: '5376'
select 'Morocco +212', from: 'International code'

expect(find('#update_user_phone_form_phone').value).to eq '+212 5376'

fill_in 'Phone', with: '54354'
select 'Japan +81', from: 'International code'

expect(find('#update_user_phone_form_phone').value).to include '+81'
end
end
end

0 comments on commit 16fef6e

Please sign in to comment.