From dd58f8fce7a10dd5e128b5e7c6754ffe84a61dbd Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 11 Jul 2022 19:29:38 -0500 Subject: [PATCH 01/16] Timeline: don't try to memoize getStatusIds --- app/soapbox/features/ui/components/timeline.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/soapbox/features/ui/components/timeline.tsx b/app/soapbox/features/ui/components/timeline.tsx index 53d1ad7f8..41b312ad3 100644 --- a/app/soapbox/features/ui/components/timeline.tsx +++ b/app/soapbox/features/ui/components/timeline.tsx @@ -9,6 +9,8 @@ import StatusList, { IStatusList } from 'soapbox/components/status_list'; import { useAppSelector, useAppDispatch } from 'soapbox/hooks'; import { makeGetStatusIds } from 'soapbox/selectors'; +const getStatusIds = makeGetStatusIds(); + const messages = defineMessages({ queue: { id: 'status_list.queue_label', defaultMessage: 'Click to see {count} new {count, plural, one {post} other {posts}}' }, }); @@ -25,7 +27,6 @@ const Timeline: React.FC = ({ ...rest }) => { const dispatch = useAppDispatch(); - const getStatusIds = useCallback(makeGetStatusIds, [])(); const lastStatusId = useAppSelector(state => (state.timelines.get(timelineId)?.items || ImmutableOrderedSet()).last() as string | undefined); const statusIds = useAppSelector(state => getStatusIds(state, { type: timelineId })); From 5c9cecf8c8dd43bdde3aa8eab14f8679ccb58de6 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 13 Jul 2022 09:42:58 -0500 Subject: [PATCH 02/16] Add PhoneInput component --- app/soapbox/components/ui/index.ts | 1 + .../components/ui/phone-input/phone-input.tsx | 34 +++++++++++++++++++ .../verification/steps/sms-verification.tsx | 12 +++---- 3 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 app/soapbox/components/ui/phone-input/phone-input.tsx diff --git a/app/soapbox/components/ui/index.ts b/app/soapbox/components/ui/index.ts index cd48426b6..042b26838 100644 --- a/app/soapbox/components/ui/index.ts +++ b/app/soapbox/components/ui/index.ts @@ -27,6 +27,7 @@ export { MenuList, } from './menu/menu'; export { default as Modal } from './modal/modal'; +export { default as PhoneInput } from './phone-input/phone-input'; export { default as ProgressBar } from './progress-bar/progress-bar'; export { default as Select } from './select/select'; export { default as Spinner } from './spinner/spinner'; diff --git a/app/soapbox/components/ui/phone-input/phone-input.tsx b/app/soapbox/components/ui/phone-input/phone-input.tsx new file mode 100644 index 000000000..c3ccae6cb --- /dev/null +++ b/app/soapbox/components/ui/phone-input/phone-input.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +import { formatPhoneNumber } from 'soapbox/utils/phone'; + +import Input from '../input/input'; + +interface IPhoneInput extends Pick, 'required'> { + /** Input phone number. */ + value?: string, + /** Change event handler taking the formatted input. */ + onChange?: (phone: string) => void, +} + +/** Internationalized phone input with country code picker. */ +const PhoneInput: React.FC = (props) => { + const { onChange, ...rest } = props; + + /** Pass the formatted phone to the handler. */ + const handleChange: React.ChangeEventHandler = ({ target }) => { + if (onChange) { + onChange(formatPhoneNumber(target.value)); + } + }; + + return ( + + ); +}; + +export default PhoneInput; diff --git a/app/soapbox/features/verification/steps/sms-verification.tsx b/app/soapbox/features/verification/steps/sms-verification.tsx index 4e24c8b24..87e8e49f2 100644 --- a/app/soapbox/features/verification/steps/sms-verification.tsx +++ b/app/soapbox/features/verification/steps/sms-verification.tsx @@ -5,9 +5,8 @@ import OtpInput from 'react-otp-input'; import snackbar from 'soapbox/actions/snackbar'; import { confirmPhoneVerification, requestPhoneVerification } from 'soapbox/actions/verification'; -import { Button, Form, FormGroup, Input, Text } from 'soapbox/components/ui'; +import { Button, Form, FormGroup, PhoneInput, Text } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import { formatPhoneNumber } from 'soapbox/utils/phone'; const Statuses = { IDLE: 'IDLE', @@ -30,10 +29,8 @@ const SmsVerification = () => { const isValid = validPhoneNumberRegex.test(phone); - const onChange = React.useCallback((event) => { - const formattedPhone = formatPhoneNumber(event.target.value); - - setPhone(formattedPhone); + const onChange = React.useCallback((phone: string) => { + setPhone(phone); }, []); const handleSubmit = React.useCallback((event) => { @@ -147,8 +144,7 @@ const SmsVerification = () => {
- Date: Wed, 13 Jul 2022 11:40:02 -0500 Subject: [PATCH 03/16] Refactor formatPhoneNumber to accept countryCode --- .../ui/phone-input/country-code-dropdown.tsx | 34 +++++++++++++++++ .../components/ui/phone-input/phone-input.tsx | 37 ++++++++++++++----- .../ui/components/modals/verify-sms-modal.tsx | 12 ++---- app/soapbox/utils/__tests__/phone.test.ts | 16 ++++---- app/soapbox/utils/phone.ts | 27 ++++++++++---- 5 files changed, 94 insertions(+), 32 deletions(-) create mode 100644 app/soapbox/components/ui/phone-input/country-code-dropdown.tsx diff --git a/app/soapbox/components/ui/phone-input/country-code-dropdown.tsx b/app/soapbox/components/ui/phone-input/country-code-dropdown.tsx new file mode 100644 index 000000000..e40709a0e --- /dev/null +++ b/app/soapbox/components/ui/phone-input/country-code-dropdown.tsx @@ -0,0 +1,34 @@ +import React from 'react'; + +import DropdownMenu from 'soapbox/containers/dropdown_menu_container'; +import { COUNTRY_CODES, CountryCode } from 'soapbox/utils/phone'; + +import type { Menu } from 'soapbox/components/dropdown_menu'; + +interface ICountryCodeDropdown { + countryCode: CountryCode, + onChange(countryCode: CountryCode): void, +} + +/** Dropdown menu to select a country code. */ +const CountryCodeDropdown: React.FC = ({ countryCode, onChange }) => { + + const handleMenuItem = (code: CountryCode) => { + return () => { + onChange(code); + }; + }; + + const menu: Menu = COUNTRY_CODES.map(code => ({ + text: <>{code}, + action: handleMenuItem(code), + })); + + return ( + + <>{countryCode} + + ); +}; + +export default CountryCodeDropdown; diff --git a/app/soapbox/components/ui/phone-input/phone-input.tsx b/app/soapbox/components/ui/phone-input/phone-input.tsx index c3ccae6cb..7b0f0a658 100644 --- a/app/soapbox/components/ui/phone-input/phone-input.tsx +++ b/app/soapbox/components/ui/phone-input/phone-input.tsx @@ -1,33 +1,52 @@ import React from 'react'; -import { formatPhoneNumber } from 'soapbox/utils/phone'; +import { CountryCode, formatPhoneNumber } from 'soapbox/utils/phone'; +import HStack from '../hstack/hstack'; import Input from '../input/input'; -interface IPhoneInput extends Pick, 'required'> { +import CountryCodeDropdown from './country-code-dropdown'; + +interface IPhoneInput extends Pick, 'required' | 'autoFocus'> { /** Input phone number. */ value?: string, + /** E164 country code. */ + countryCode?: CountryCode, /** Change event handler taking the formatted input. */ onChange?: (phone: string) => void, } /** Internationalized phone input with country code picker. */ const PhoneInput: React.FC = (props) => { - const { onChange, ...rest } = props; + const { countryCode = 1, value = '', onChange, ...rest } = props; + + const handleCountryChange = (code: CountryCode) => { + if (onChange) { + onChange(formatPhoneNumber(countryCode, value)); + } + }; /** Pass the formatted phone to the handler. */ const handleChange: React.ChangeEventHandler = ({ target }) => { if (onChange) { - onChange(formatPhoneNumber(target.value)); + onChange(formatPhoneNumber(countryCode, target.value)); } }; return ( - + + + + + ); }; diff --git a/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx b/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx index b89733486..10e97d3ca 100644 --- a/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx +++ b/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx @@ -6,11 +6,10 @@ import { verifyCredentials } from 'soapbox/actions/auth'; import { closeModal } from 'soapbox/actions/modals'; import snackbar from 'soapbox/actions/snackbar'; import { reConfirmPhoneVerification, reRequestPhoneVerification } from 'soapbox/actions/verification'; -import { FormGroup, Input, Modal, Stack, Text } from 'soapbox/components/ui'; +import { FormGroup, PhoneInput, Modal, Stack, Text } from 'soapbox/components/ui'; import { validPhoneNumberRegex } from 'soapbox/features/verification/steps/sms-verification'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { getAccessToken } from 'soapbox/utils/auth'; -import { formatPhoneNumber } from 'soapbox/utils/phone'; interface IVerifySmsModal { onClose: (type: string) => void, @@ -38,10 +37,8 @@ const VerifySmsModal: React.FC = ({ onClose }) => { const isValid = validPhoneNumberRegex.test(phone); - const onChange = useCallback((event: React.ChangeEvent) => { - const formattedPhone = formatPhoneNumber(event.target.value); - - setPhone(formattedPhone); + const onChange = useCallback((phone: string) => { + setPhone(phone); }, []); const handleSubmit = (event: React.MouseEvent) => { @@ -141,8 +138,7 @@ const VerifySmsModal: React.FC = ({ onClose }) => { case Statuses.READY: return ( - { it('Properly formats', () => { let number = ''; - expect(formatPhoneNumber(number)).toEqual(''); + expect(formatPhoneNumber(1, number)).toEqual(''); number = '5'; - expect(formatPhoneNumber(number)).toEqual('+1 (5'); + expect(formatPhoneNumber(1, number)).toEqual('+1 (5'); number = '55'; - expect(formatPhoneNumber(number)).toEqual('+1 (55'); + expect(formatPhoneNumber(1, number)).toEqual('+1 (55'); number = '555'; - expect(formatPhoneNumber(number)).toEqual('+1 (555'); + expect(formatPhoneNumber(1, number)).toEqual('+1 (555'); number = '55513'; - expect(formatPhoneNumber(number)).toEqual('+1 (555) 13'); + expect(formatPhoneNumber(1, number)).toEqual('+1 (555) 13'); number = '555135'; - expect(formatPhoneNumber(number)).toEqual('+1 (555) 135'); + expect(formatPhoneNumber(1, number)).toEqual('+1 (555) 135'); number = '5551350'; - expect(formatPhoneNumber(number)).toEqual('+1 (555) 135-0'); + expect(formatPhoneNumber(1, number)).toEqual('+1 (555) 135-0'); number = '5551350123'; - expect(formatPhoneNumber(number)).toEqual('+1 (555) 135-0123'); + expect(formatPhoneNumber(1, number)).toEqual('+1 (555) 135-0123'); }); }); diff --git a/app/soapbox/utils/phone.ts b/app/soapbox/utils/phone.ts index d112e66eb..913042a6d 100644 --- a/app/soapbox/utils/phone.ts +++ b/app/soapbox/utils/phone.ts @@ -1,3 +1,14 @@ +/** List of supported E164 country codes. */ +const COUNTRY_CODES = [ + 1, + 44, +] as const; + +/** Supported E164 country code. */ +type CountryCode = typeof COUNTRY_CODES[number]; + +/** Check whether a given value is a country code. */ +const isCountryCode = (value: any): value is CountryCode => COUNTRY_CODES.includes(value); function removeFormattingFromNumber(number = '') { if (number) { @@ -7,17 +18,14 @@ function removeFormattingFromNumber(number = '') { return number; } -function formatPhoneNumber(phoneNumber = '') { +function formatPhoneNumber(countryCode: CountryCode, phoneNumber = '') { let formattedPhoneNumber = ''; - let strippedPhone = removeFormattingFromNumber(phoneNumber); - if (strippedPhone.slice(0, 1) === '1') { - strippedPhone = strippedPhone.slice(1); - } + const strippedPhone = removeFormattingFromNumber(phoneNumber); for (let i = 0; i < strippedPhone.length && i < 10; i++) { const character = strippedPhone.charAt(i); if (i === 0) { - const prefix = '+1 ('; + const prefix = `+${countryCode} (`; formattedPhoneNumber += prefix + character; } else if (i === 3) { formattedPhoneNumber += `) ${character}`; @@ -30,4 +38,9 @@ function formatPhoneNumber(phoneNumber = '') { return formattedPhoneNumber; } -export { formatPhoneNumber }; +export { + COUNTRY_CODES, + CountryCode, + isCountryCode, + formatPhoneNumber, +}; From 85cf99f618297137a7c26121f878e58329b4f69e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 13 Jul 2022 12:16:54 -0500 Subject: [PATCH 04/16] CountryCodeDropdown: use button element --- app/soapbox/components/ui/phone-input/country-code-dropdown.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/components/ui/phone-input/country-code-dropdown.tsx b/app/soapbox/components/ui/phone-input/country-code-dropdown.tsx index e40709a0e..699237fe5 100644 --- a/app/soapbox/components/ui/phone-input/country-code-dropdown.tsx +++ b/app/soapbox/components/ui/phone-input/country-code-dropdown.tsx @@ -26,7 +26,7 @@ const CountryCodeDropdown: React.FC = ({ countryCode, onCh return ( - <>{countryCode} + ); }; From 0ed1c3ca8394c84efce43a7773172ea815b49358 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 13 Jul 2022 12:26:07 -0500 Subject: [PATCH 05/16] Improve display of PhoneInput --- .../components/ui/phone-input/country-code-dropdown.tsx | 4 ++-- app/soapbox/components/ui/phone-input/phone-input.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/soapbox/components/ui/phone-input/country-code-dropdown.tsx b/app/soapbox/components/ui/phone-input/country-code-dropdown.tsx index 699237fe5..3e0e7c778 100644 --- a/app/soapbox/components/ui/phone-input/country-code-dropdown.tsx +++ b/app/soapbox/components/ui/phone-input/country-code-dropdown.tsx @@ -20,13 +20,13 @@ const CountryCodeDropdown: React.FC = ({ countryCode, onCh }; const menu: Menu = COUNTRY_CODES.map(code => ({ - text: <>{code}, + text: <>+{code}, action: handleMenuItem(code), })); return ( - + ); }; diff --git a/app/soapbox/components/ui/phone-input/phone-input.tsx b/app/soapbox/components/ui/phone-input/phone-input.tsx index 7b0f0a658..2b140fc62 100644 --- a/app/soapbox/components/ui/phone-input/phone-input.tsx +++ b/app/soapbox/components/ui/phone-input/phone-input.tsx @@ -34,7 +34,7 @@ const PhoneInput: React.FC = (props) => { }; return ( - + Date: Wed, 13 Jul 2022 17:37:40 -0500 Subject: [PATCH 06/16] Simplify PhoneInput to only care about its own internal state --- .../components/ui/phone-input/phone-input.tsx | 51 +++++++++++++------ .../ui/components/modals/verify-sms-modal.tsx | 8 +-- .../verification/steps/sms-verification.tsx | 8 +-- app/soapbox/utils/__tests__/phone.test.ts | 16 +++--- app/soapbox/utils/phone.ts | 4 +- package.json | 1 + yarn.lock | 5 ++ 7 files changed, 59 insertions(+), 34 deletions(-) diff --git a/app/soapbox/components/ui/phone-input/phone-input.tsx b/app/soapbox/components/ui/phone-input/phone-input.tsx index 2b140fc62..a3efc5791 100644 --- a/app/soapbox/components/ui/phone-input/phone-input.tsx +++ b/app/soapbox/components/ui/phone-input/phone-input.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import { parsePhoneNumber } from 'libphonenumber-js'; +import React, { useState, useEffect } from 'react'; -import { CountryCode, formatPhoneNumber } from 'soapbox/utils/phone'; +import { CountryCode } from 'soapbox/utils/phone'; import HStack from '../hstack/hstack'; import Input from '../input/input'; @@ -8,31 +9,49 @@ import Input from '../input/input'; import CountryCodeDropdown from './country-code-dropdown'; interface IPhoneInput extends Pick, 'required' | 'autoFocus'> { - /** Input phone number. */ + /** E164 phone number. */ value?: string, - /** E164 country code. */ - countryCode?: CountryCode, - /** Change event handler taking the formatted input. */ - onChange?: (phone: string) => void, + /** Change handler which receives the E164 phone string. */ + onChange?: (phone: string | undefined) => void, + /** Country code that's selected on mount. */ + defaultCountryCode?: CountryCode, } /** Internationalized phone input with country code picker. */ const PhoneInput: React.FC = (props) => { - const { countryCode = 1, value = '', onChange, ...rest } = props; + const { value, onChange, defaultCountryCode = '1', ...rest } = props; + + const [countryCode, setCountryCode] = useState(defaultCountryCode); + const [nationalNumber, setNationalNumber] = useState(''); const handleCountryChange = (code: CountryCode) => { - if (onChange) { - onChange(formatPhoneNumber(countryCode, value)); - } + setCountryCode(code); }; - /** Pass the formatted phone to the handler. */ const handleChange: React.ChangeEventHandler = ({ target }) => { - if (onChange) { - onChange(formatPhoneNumber(countryCode, target.value)); - } + setNationalNumber(target.value); }; + // When the internal state changes, update the external state. + useEffect(() => { + if (onChange) { + try { + const opts = { defaultCallingCode: countryCode, extract: false } as any; + const result = parsePhoneNumber(nationalNumber, opts); + + if (!result.isPossible()) { + throw result; + } + + onChange(result.format('E.164')); + } catch (e) { + // The value returned is always a valid E164 string. + // If it's not valid, it'll return undefined. + onChange(undefined); + } + } + }, [countryCode, nationalNumber]); + return ( = (props) => { diff --git a/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx b/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx index 10e97d3ca..5b9d1deea 100644 --- a/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx +++ b/app/soapbox/features/ui/components/modals/verify-sms-modal.tsx @@ -31,13 +31,13 @@ const VerifySmsModal: React.FC = ({ onClose }) => { const isLoading = useAppSelector((state) => state.verification.isLoading); const [status, setStatus] = useState(Statuses.IDLE); - const [phone, setPhone] = useState(''); + const [phone, setPhone] = useState(); const [verificationCode, setVerificationCode] = useState(''); const [requestedAnother, setAlreadyRequestedAnother] = useState(false); - const isValid = validPhoneNumberRegex.test(phone); + const isValid = phone ? validPhoneNumberRegex.test(phone) : false; - const onChange = useCallback((phone: string) => { + const onChange = useCallback((phone?: string) => { setPhone(phone); }, []); @@ -57,7 +57,7 @@ const VerifySmsModal: React.FC = ({ onClose }) => { return; } - dispatch(reRequestPhoneVerification(phone)).then(() => { + dispatch(reRequestPhoneVerification(phone!)).then(() => { dispatch( snackbar.success( intl.formatMessage({ diff --git a/app/soapbox/features/verification/steps/sms-verification.tsx b/app/soapbox/features/verification/steps/sms-verification.tsx index 87e8e49f2..066d4332a 100644 --- a/app/soapbox/features/verification/steps/sms-verification.tsx +++ b/app/soapbox/features/verification/steps/sms-verification.tsx @@ -22,14 +22,14 @@ const SmsVerification = () => { const isLoading = useAppSelector((state) => state.verification.isLoading) as boolean; - const [phone, setPhone] = React.useState(''); + const [phone, setPhone] = React.useState(); const [status, setStatus] = React.useState(Statuses.IDLE); const [verificationCode, setVerificationCode] = React.useState(''); const [requestedAnother, setAlreadyRequestedAnother] = React.useState(false); - const isValid = validPhoneNumberRegex.test(phone); + const isValid = phone ? validPhoneNumberRegex.test(phone) : false; - const onChange = React.useCallback((phone: string) => { + const onChange = React.useCallback((phone?: string) => { setPhone(phone); }, []); @@ -49,7 +49,7 @@ const SmsVerification = () => { return; } - dispatch(requestPhoneVerification(phone)).then(() => { + dispatch(requestPhoneVerification(phone!)).then(() => { dispatch( snackbar.success( intl.formatMessage({ diff --git a/app/soapbox/utils/__tests__/phone.test.ts b/app/soapbox/utils/__tests__/phone.test.ts index db45729a6..2908601b8 100644 --- a/app/soapbox/utils/__tests__/phone.test.ts +++ b/app/soapbox/utils/__tests__/phone.test.ts @@ -3,27 +3,27 @@ import { formatPhoneNumber } from '../phone'; describe('Phone unit tests', () => { it('Properly formats', () => { let number = ''; - expect(formatPhoneNumber(1, number)).toEqual(''); + expect(formatPhoneNumber('1', number)).toEqual(''); number = '5'; - expect(formatPhoneNumber(1, number)).toEqual('+1 (5'); + expect(formatPhoneNumber('1', number)).toEqual('+1 (5'); number = '55'; - expect(formatPhoneNumber(1, number)).toEqual('+1 (55'); + expect(formatPhoneNumber('1', number)).toEqual('+1 (55'); number = '555'; - expect(formatPhoneNumber(1, number)).toEqual('+1 (555'); + expect(formatPhoneNumber('1', number)).toEqual('+1 (555'); number = '55513'; - expect(formatPhoneNumber(1, number)).toEqual('+1 (555) 13'); + expect(formatPhoneNumber('1', number)).toEqual('+1 (555) 13'); number = '555135'; - expect(formatPhoneNumber(1, number)).toEqual('+1 (555) 135'); + expect(formatPhoneNumber('1', number)).toEqual('+1 (555) 135'); number = '5551350'; - expect(formatPhoneNumber(1, number)).toEqual('+1 (555) 135-0'); + expect(formatPhoneNumber('1', number)).toEqual('+1 (555) 135-0'); number = '5551350123'; - expect(formatPhoneNumber(1, number)).toEqual('+1 (555) 135-0123'); + expect(formatPhoneNumber('1', number)).toEqual('+1 (555) 135-0123'); }); }); diff --git a/app/soapbox/utils/phone.ts b/app/soapbox/utils/phone.ts index 913042a6d..39dfdbcd7 100644 --- a/app/soapbox/utils/phone.ts +++ b/app/soapbox/utils/phone.ts @@ -1,7 +1,7 @@ /** List of supported E164 country codes. */ const COUNTRY_CODES = [ - 1, - 44, + '1', + '44', ] as const; /** Supported E164 country code. */ diff --git a/package.json b/package.json index 92f8a1600..bcd0a208c 100644 --- a/package.json +++ b/package.json @@ -133,6 +133,7 @@ "intl-pluralrules": "^1.3.1", "is-nan": "^1.2.1", "jsdoc": "~3.6.7", + "libphonenumber-js": "^1.10.8", "line-awesome": "^1.3.0", "localforage": "^1.10.0", "lodash": "^4.7.11", diff --git a/yarn.lock b/yarn.lock index 6d6828864..83bf71b1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7756,6 +7756,11 @@ li@^1.3.0: resolved "https://registry.yarnpkg.com/li/-/li-1.3.0.tgz#22c59bcaefaa9a8ef359cf759784e4bf106aea1b" integrity sha512-z34TU6GlMram52Tss5mt1m//ifRIpKH5Dqm7yUVOdHI+BQCs9qGPHFaCUTIzsWX7edN30aa2WrPwR7IO10FHaw== +libphonenumber-js@^1.10.8: + version "1.10.8" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.8.tgz#21925db0f16d4f1553dff2bbc62afdaeb03f21f0" + integrity sha512-MGgHrKRGE7sg7y0DikHybRDgTXcYv4HL+WwhDm5UAiChCNb5tcy5OEaU8XTTt5bDBwhZGCJNxoGMVBpZ4RfhIg== + lie@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" From ba98a8e82f1d52c1dfce472e367d3f42b1ab20b7 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 13 Jul 2022 18:08:59 -0500 Subject: [PATCH 07/16] PhoneInput: prettify input while typing --- .../components/ui/phone-input/phone-input.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/soapbox/components/ui/phone-input/phone-input.tsx b/app/soapbox/components/ui/phone-input/phone-input.tsx index a3efc5791..ec8584581 100644 --- a/app/soapbox/components/ui/phone-input/phone-input.tsx +++ b/app/soapbox/components/ui/phone-input/phone-input.tsx @@ -1,4 +1,4 @@ -import { parsePhoneNumber } from 'libphonenumber-js'; +import { parsePhoneNumber, AsYouType } from 'libphonenumber-js'; import React, { useState, useEffect } from 'react'; import { CountryCode } from 'soapbox/utils/phone'; @@ -29,7 +29,15 @@ const PhoneInput: React.FC = (props) => { }; const handleChange: React.ChangeEventHandler = ({ target }) => { - setNationalNumber(target.value); + // HACK: AsYouType is not meant to be used this way. But it works! + const asYouType = new AsYouType({ defaultCallingCode: countryCode }); + const formatted = asYouType.input(target.value); + + if (formatted === nationalNumber && target.value !== nationalNumber) { + setNationalNumber(target.value); + } else { + setNationalNumber(formatted); + } }; // When the internal state changes, update the external state. From 0591bf72a5d0836bd1bf29f30c99fc614b7a398a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 13 Jul 2022 18:10:16 -0500 Subject: [PATCH 08/16] PhoneInput: remove unnecessary handleCountryChange function --- app/soapbox/components/ui/phone-input/phone-input.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/soapbox/components/ui/phone-input/phone-input.tsx b/app/soapbox/components/ui/phone-input/phone-input.tsx index ec8584581..d41c49414 100644 --- a/app/soapbox/components/ui/phone-input/phone-input.tsx +++ b/app/soapbox/components/ui/phone-input/phone-input.tsx @@ -24,10 +24,6 @@ const PhoneInput: React.FC = (props) => { const [countryCode, setCountryCode] = useState(defaultCountryCode); const [nationalNumber, setNationalNumber] = useState(''); - const handleCountryChange = (code: CountryCode) => { - setCountryCode(code); - }; - const handleChange: React.ChangeEventHandler = ({ target }) => { // HACK: AsYouType is not meant to be used this way. But it works! const asYouType = new AsYouType({ defaultCallingCode: countryCode }); @@ -64,7 +60,7 @@ const PhoneInput: React.FC = (props) => { Date: Wed, 13 Jul 2022 18:45:42 -0500 Subject: [PATCH 09/16] PhoneInput: improve styles --- app/soapbox/components/ui/input/input.tsx | 2 +- .../ui/phone-input/country-code-dropdown.tsx | 8 +++++++- .../components/ui/phone-input/phone-input.tsx | 14 +++++++++----- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/app/soapbox/components/ui/input/input.tsx b/app/soapbox/components/ui/input/input.tsx index 488f2733e..d1cf7cf7c 100644 --- a/app/soapbox/components/ui/input/input.tsx +++ b/app/soapbox/components/ui/input/input.tsx @@ -52,7 +52,7 @@ const Input = React.forwardRef( }, []); return ( -
+
{icon ? (