{
- // Should execute before onFocus
event.preventDefault();
setType(type === 'password' ? 'text' : 'password');
}}
diff --git a/packages/ui/src/components/Input/PhoneInput.test.tsx b/packages/ui/src/components/Input/PhoneInput.test.tsx
new file mode 100644
index 000000000..64672afa1
--- /dev/null
+++ b/packages/ui/src/components/Input/PhoneInput.test.tsx
@@ -0,0 +1,80 @@
+import { render, fireEvent } from '@testing-library/react';
+import React from 'react';
+
+import { defaultCountryCallingCode, countryList } from '@/hooks/use-phone-number';
+
+import PhoneInput from './PhoneInput';
+
+describe('Phone Input Field UI Component', () => {
+ const onChange = jest.fn();
+
+ beforeEach(() => {
+ onChange.mockClear();
+ });
+
+ it('render empty PhoneInput', () => {
+ const { queryByText, container } = render(
+
+ );
+ expect(queryByText(`+${defaultCountryCallingCode}`)).toBeNull();
+ expect(container.querySelector('input')?.value).toBe('');
+ });
+
+ it('render with country list', () => {
+ const { queryByText, container } = render(
+
+ );
+
+ const countryCode = queryByText(`+${defaultCountryCallingCode}`);
+ expect(countryCode).not.toBeNull();
+
+ const selector = container.querySelector('select');
+ expect(selector).not.toBeNull();
+
+ if (selector) {
+ fireEvent.change(selector, { target: { value: '1' } });
+ expect(onChange).toBeCalledWith({ countryCallingCode: '1' });
+ }
+ });
+
+ it('render input update', () => {
+ const { container } = render(
+
+ );
+
+ const inputField = container.querySelector('input');
+ expect(inputField?.value).toBe('911');
+
+ if (inputField) {
+ fireEvent.change(inputField, { target: { value: '110' } });
+ expect(onChange).toBeCalledWith({ nationalNumber: '110' });
+ fireEvent.focus(inputField);
+ }
+ });
+
+ it('render input clear', () => {
+ const { container } = render(
+
+ );
+
+ const inputField = container.querySelector('input');
+
+ if (inputField) {
+ fireEvent.focus(inputField);
+ }
+
+ const clearButton = container.querySelector('svg');
+ expect(clearButton).not.toBeNull();
+
+ if (clearButton) {
+ fireEvent.mouseDown(clearButton);
+ expect(onChange).toBeCalledWith({ nationalNumber: '' });
+ }
+ });
+});
diff --git a/packages/ui/src/components/Input/PhoneInput.tsx b/packages/ui/src/components/Input/PhoneInput.tsx
new file mode 100644
index 000000000..0430fcfa3
--- /dev/null
+++ b/packages/ui/src/components/Input/PhoneInput.tsx
@@ -0,0 +1,111 @@
+import classNames from 'classnames';
+import React, { useState, useMemo, useRef } from 'react';
+
+import { CountryCallingCode, CountryMetaData } from '@/hooks/use-phone-number';
+
+import { ClearIcon, DownArrowIcon } from '../Icons';
+import * as styles from './index.module.scss';
+import * as phoneInputStyles from './phoneInput.module.scss';
+
+export type Props = {
+ name: string;
+ autoComplete?: AutoCompleteType;
+ isDisabled?: boolean;
+ className?: string;
+ placeholder?: string;
+ countryCallingCode?: CountryCallingCode;
+ nationalNumber: string;
+ countryList?: CountryMetaData[];
+ hasError?: boolean;
+ onChange: (value: { countryCallingCode?: CountryCallingCode; nationalNumber?: string }) => void;
+};
+
+const PhoneInput = ({
+ name,
+ autoComplete,
+ isDisabled,
+ className,
+ placeholder,
+ countryCallingCode,
+ nationalNumber,
+ countryList,
+ hasError = false,
+ onChange,
+}: Props) => {
+ const [onFocus, setOnFocus] = useState(false);
+ const inputReference = useRef
(null);
+
+ const countrySelector = useMemo(() => {
+ if (!countryCallingCode || !countryList) {
+ return null;
+ }
+
+ return (
+
+ {`+${countryCallingCode}`}
+
+
+
+ );
+ }, [countryCallingCode, countryList, onChange]);
+
+ return (
+
+ {countrySelector}
+ {
+ setOnFocus(true);
+ }}
+ onBlur={() => {
+ setOnFocus(false);
+ }}
+ onChange={({ target: { value } }) => {
+ onChange({ nationalNumber: value });
+ }}
+ />
+ {nationalNumber && onFocus && (
+ {
+ event.preventDefault();
+ onChange({ nationalNumber: '' });
+ }}
+ />
+ )}
+
+ );
+};
+
+export default PhoneInput;
diff --git a/packages/ui/src/components/Input/index.module.scss b/packages/ui/src/components/Input/index.module.scss
index 889f6b7a8..367807f8d 100644
--- a/packages/ui/src/components/Input/index.module.scss
+++ b/packages/ui/src/components/Input/index.module.scss
@@ -2,38 +2,46 @@
.wrapper {
position: relative;
-}
-
-.input {
- width: 100%;
- padding: _.unit(3) _.unit(12) _.unit(3) _.unit(5);
+ @include _.flex-row;
+ padding: 0 _.unit(4);
border-radius: _.unit(2);
border: _.border();
background: var(--color-control-background);
color: var(--color-font-primary);
- caret-color: var(--color-primary);
- font: var(--font-control);
- transition: var(--transition-default-control);
- &::placeholder {
- color: var(--color-font-tertiary-3);
+ > *:not(:first-child) {
+ margin-left: _.unit(1);
}
- &:focus {
+ &.focus {
border: _.border(var(--color-control-focus));
}
-}
-.error,
-.error:focus {
- border: _.border(var(--color-error));
+ &.error {
+ border: _.border(var(--color-error));
+ }
+
+ input {
+ flex: 1;
+ border: none;
+ background: none;
+ padding: _.unit(3) 0;
+ caret-color: var(--color-primary);
+ font: var(--font-control);
+ transition: var(--transition-default-control);
+
+ &::placeholder {
+ color: var(--color-font-tertiary-3);
+ }
+
+ &:-webkit-autofill {
+ box-shadow: 0 0 0 30px var(--color-control-background) inset;
+ transition: background-color 5000s ease-in-out 0s;
+ }
+ }
}
.actionButton {
- position: absolute;
- right: _.unit(5);
- bottom: 50%;
- transform: translateY(50%);
fill: var(--color-neutral-70);
&.highlight {
diff --git a/packages/ui/src/components/Input/index.test.tsx b/packages/ui/src/components/Input/index.test.tsx
index ea0346ba9..79d9a632c 100644
--- a/packages/ui/src/components/Input/index.test.tsx
+++ b/packages/ui/src/components/Input/index.test.tsx
@@ -12,7 +12,6 @@ describe('Input Field UI Component', () => {
const inputEle = container.querySelector('input');
expect(inputEle).not.toBeNull();
expect(inputEle?.value).toEqual(text);
- expect(container.querySelector('svg')).not.toBeNull();
if (inputEle) {
fireEvent.change(inputEle, { target: { value: 'update' } });
@@ -22,11 +21,19 @@ describe('Input Field UI Component', () => {
test('click on clear button', () => {
const { container } = render();
+ const inputField = container.querySelector('input');
+
+ expect(container.querySelector('svg')).toBeNull();
+
+ if (inputField) {
+ fireEvent.focus(inputField);
+ }
+
const clearIcon = container.querySelector('svg');
expect(clearIcon).not.toBeNull();
if (clearIcon) {
- fireEvent.click(clearIcon);
+ fireEvent.mouseDown(clearIcon);
expect(onChange).toBeCalledWith('');
}
});
diff --git a/packages/ui/src/components/Input/index.tsx b/packages/ui/src/components/Input/index.tsx
index fc914d2c7..d7025452e 100644
--- a/packages/ui/src/components/Input/index.tsx
+++ b/packages/ui/src/components/Input/index.tsx
@@ -1,5 +1,5 @@
import classNames from 'classnames';
-import React, { useState, useRef } from 'react';
+import React, { useState } from 'react';
import { ClearIcon } from '../Icons';
import * as styles from './index.module.scss';
@@ -30,15 +30,19 @@ const Input = ({
onChange,
}: Props) => {
const [onFocus, setOnFocus] = useState(false);
- const inputReference = useRef(null);
return (
-
+
{
- // Should execute before onFocus
event.preventDefault();
onChange('');
}}
diff --git a/packages/ui/src/components/Input/phoneInput.module.scss b/packages/ui/src/components/Input/phoneInput.module.scss
new file mode 100644
index 000000000..dba0103cd
--- /dev/null
+++ b/packages/ui/src/components/Input/phoneInput.module.scss
@@ -0,0 +1,26 @@
+@use '@/scss/underscore' as _;
+
+.countryCodeSelector {
+ color: var(--color-font-primary);
+ font: var(--font-control);
+ border: none;
+ background: none;
+ width: auto;
+ @include _.flex-row;
+ position: relative;
+
+ > select {
+ appearance: none;
+ border: none;
+ outline: none;
+ background: none;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ font-size: 0;
+ }
+
+ > svg {
+ fill: var(--color-primary);
+ }
+}
diff --git a/packages/ui/src/containers/PhoneInputProvider/index.test.tsx b/packages/ui/src/containers/PhoneInputProvider/index.test.tsx
new file mode 100644
index 000000000..f6d77b589
--- /dev/null
+++ b/packages/ui/src/containers/PhoneInputProvider/index.test.tsx
@@ -0,0 +1,79 @@
+import { render, fireEvent } from '@testing-library/react';
+import React from 'react';
+
+import { defaultCountryCallingCode } from '@/hooks/use-phone-number';
+
+import PhoneInputProvider from '.';
+
+describe('Phone Input Provider', () => {
+ const onChange = jest.fn();
+
+ beforeEach(() => {
+ onChange.mockClear();
+ });
+
+ it('render with empty input', () => {
+ const { queryByText } = render(
+
+ );
+
+ expect(queryByText(`+${defaultCountryCallingCode}`)).not.toBeNull();
+ });
+
+ it('render with input', () => {
+ const { queryByText, container } = render(
+
+ );
+
+ expect(queryByText('+1')).not.toBeNull();
+ expect(container.querySelector('input')?.value).toEqual('911');
+ });
+
+ it('update country code', () => {
+ const { container } = render(
+
+ );
+
+ const selector = container.querySelector('select');
+
+ if (selector) {
+ fireEvent.change(selector, { target: { value: '86' } });
+ expect(onChange).toBeCalledWith('+86911');
+ }
+ });
+
+ it('update national code', () => {
+ const { container } = render(
+
+ );
+
+ const input = container.querySelector('input');
+
+ if (input) {
+ fireEvent.change(input, { target: { value: '119' } });
+ expect(onChange).toBeCalledWith('+1119');
+ }
+ });
+
+ it('clear national code', () => {
+ const { container } = render(
+
+ );
+
+ const input = container.querySelector('input');
+
+ if (!input) {
+ return;
+ }
+
+ fireEvent.focus(input);
+
+ const clearButton = container.querySelectorAll('svg');
+ expect(clearButton).toHaveLength(2);
+
+ if (clearButton[1]) {
+ fireEvent.mouseDown(clearButton[1]);
+ expect(onChange).toBeCalledWith('+1');
+ }
+ });
+});
diff --git a/packages/ui/src/containers/PhoneInputProvider/index.tsx b/packages/ui/src/containers/PhoneInputProvider/index.tsx
new file mode 100644
index 000000000..4869a43b7
--- /dev/null
+++ b/packages/ui/src/containers/PhoneInputProvider/index.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+
+import PhoneInput from '@/components/Input/PhoneInput';
+import usePhoneNumber, { countryList } from '@/hooks/use-phone-number';
+
+export type Props = {
+ name: string;
+ autoComplete?: AutoCompleteType;
+ isDisabled?: boolean;
+ className?: string;
+ placeholder?: string;
+ value: string;
+ onChange: (value: string) => void;
+};
+
+const PhoneInputProvider = ({ value, onChange, ...inputProps }: Props) => {
+ // TODO: error message
+ const {
+ error,
+ phoneNumber: { countryCallingCode, nationalNumber, interacted },
+ setPhoneNumber,
+ } = usePhoneNumber(value, onChange);
+
+ return (
+
{
+ setPhoneNumber((phoneNumber) => ({ ...phoneNumber, ...data, interacted: true }));
+ }}
+ />
+ );
+};
+
+export default PhoneInputProvider;
diff --git a/packages/ui/src/hooks/use-phone-number.ts b/packages/ui/src/hooks/use-phone-number.ts
new file mode 100644
index 000000000..7a9a09729
--- /dev/null
+++ b/packages/ui/src/hooks/use-phone-number.ts
@@ -0,0 +1,128 @@
+/**
+ * Provide PhoneNumber Format support
+ * Reference [libphonenumber-js](https://gitlab.com/catamphetamine/libphonenumber-js)
+ */
+
+import {
+ parsePhoneNumber as _parsePhoneNumber,
+ getCountries,
+ getCountryCallingCode,
+ CountryCallingCode,
+ CountryCode,
+ E164Number,
+ ParseError,
+} from 'libphonenumber-js';
+import { useState, useEffect } from 'react';
+// Should not need the react-phone-number-input package, but we use its locale country name for now
+import en from 'react-phone-number-input/locale/en.json';
+
+export type { CountryCallingCode } from 'libphonenumber-js';
+
+/**
+ * TODO: Get Default Country Code
+ */
+const defaultCountryCode: CountryCode = 'CN';
+
+export const defaultCountryCallingCode: CountryCallingCode =
+ getCountryCallingCode(defaultCountryCode);
+
+/**
+ * Provide Country Code Options
+ * TODO: Country Name i18n
+ */
+export type CountryMetaData = {
+ countryCode: CountryCode;
+ countryCallingCode: CountryCallingCode;
+ countryName?: string;
+};
+
+export const countryList: CountryMetaData[] = getCountries().map((code) => {
+ const callingCode = getCountryCallingCode(code);
+ const countryName = en[code];
+
+ return {
+ countryCode: code,
+ countryCallingCode: callingCode,
+ countryName,
+ };
+});
+
+type PhoneNumberData = {
+ countryCallingCode: string;
+ nationalNumber: string;
+};
+
+// Add interact status to prevent the initial onUpdate useEffect call
+type PhoneNumberState = PhoneNumberData & { interacted: boolean };
+
+const parseE164Number = (value: string): E164Number | '' => {
+ if (!value || value.startsWith('+')) {
+ return value;
+ }
+
+ return `+${value}`;
+};
+
+export const parsePhoneNumber = (value: string): [ParseError?, PhoneNumberData?] => {
+ try {
+ const phoneNumber = _parsePhoneNumber(parseE164Number(value));
+ const { countryCallingCode, nationalNumber } = phoneNumber;
+
+ return [undefined, { countryCallingCode, nationalNumber }];
+ } catch (error: unknown) {
+ if (error instanceof ParseError) {
+ return [error];
+ }
+ throw error;
+ }
+};
+
+const usePhoneNumber = (value: string, onChangeCallback: (value: string) => void) => {
+ // TODO: phoneNumber format based on country
+
+ const [phoneNumber, setPhoneNumber] = useState({
+ countryCallingCode: defaultCountryCallingCode,
+ nationalNumber: '',
+ interacted: false,
+ });
+ const [error, setError] = useState();
+
+ useEffect(() => {
+ // Only run on data initialization
+ if (phoneNumber.interacted) {
+ return;
+ }
+
+ const [parseError, result] = parsePhoneNumber(value);
+ setError(parseError);
+
+ if (result) {
+ const { countryCallingCode, nationalNumber } = result;
+ setPhoneNumber((previous) => ({
+ ...previous,
+ countryCallingCode,
+ nationalNumber,
+ }));
+ }
+ }, [phoneNumber.interacted, value]);
+
+ useEffect(() => {
+ // Only run after data initialization
+ if (!phoneNumber.interacted) {
+ return;
+ }
+
+ const { countryCallingCode, nationalNumber } = phoneNumber;
+ const [parseError] = parsePhoneNumber(`${countryCallingCode}${nationalNumber}`);
+ setError(parseError);
+ onChangeCallback(`+${countryCallingCode}${nationalNumber}`);
+ }, [onChangeCallback, phoneNumber]);
+
+ return {
+ error,
+ phoneNumber,
+ setPhoneNumber,
+ };
+};
+
+export default usePhoneNumber;
diff --git a/packages/ui/src/include.d/dom.d.ts b/packages/ui/src/include.d/dom.d.ts
index d6c0c1615..ce3570903 100644
--- a/packages/ui/src/include.d/dom.d.ts
+++ b/packages/ui/src/include.d/dom.d.ts
@@ -68,7 +68,8 @@ type AutoCompleteType =
| 'bday-year'
| 'sex'
| 'url'
- | 'photo';
+ | 'photo'
+ | 'mobile';
// TO-DO: remove me
interface Body {
diff --git a/packages/ui/src/scss/_underscore.scss b/packages/ui/src/scss/_underscore.scss
index fe29ed9d6..d84efcb45 100644
--- a/packages/ui/src/scss/_underscore.scss
+++ b/packages/ui/src/scss/_underscore.scss
@@ -9,6 +9,12 @@
justify-content: center;
}
+@mixin flex-row {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
@mixin image-align-center {
object-fit: contain;
object-position: center;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b6df382b6..c77347418 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -288,7 +288,9 @@ importers:
i18next-browser-languagedetector: ^6.1.3
identity-obj-proxy: ^3.0.0
jest: ^27.5.1
+ jest-transform-stub: ^2.0.0
ky: ^0.29.0
+ libphonenumber-js: ^1.9.49
lint-staged: ^11.1.1
parcel: ^2.3.2
postcss: ^8.4.6
@@ -297,6 +299,7 @@ importers:
react: ^17.0.2
react-dom: ^17.0.2
react-i18next: ^11.15.4
+ react-phone-number-input: ^3.1.46
react-router-dom: ^5.2.0
stylelint: ^13.13.1
ts-jest: ^27.0.5
@@ -308,9 +311,11 @@ importers:
i18next: 21.6.11
i18next-browser-languagedetector: 6.1.3
ky: 0.29.0
+ libphonenumber-js: 1.9.49
react: 17.0.2
react-dom: 17.0.2_react@17.0.2
react-i18next: 11.15.4_3fb644aa30122a07f960d67fa51d6dc1
+ react-phone-number-input: 3.1.46_react-dom@17.0.2+react@17.0.2
react-router-dom: 5.3.0_react@17.0.2
devDependencies:
'@jest/types': 27.5.1
@@ -328,6 +333,7 @@ importers:
eslint: 8.10.0
identity-obj-proxy: 3.0.0
jest: 27.5.1
+ jest-transform-stub: 2.0.0
lint-staged: 11.2.6
parcel: 2.3.2_postcss@8.4.6
postcss: 8.4.6
@@ -2216,27 +2222,6 @@ packages:
write-file-atomic: 3.0.3
dev: true
- /@monaco-editor/loader/1.2.0_monaco-editor@0.32.1:
- resolution: {integrity: sha512-cJVCG/T/KxXgzYnjKqyAgsKDbH9mGLjcXxN6AmwumBwa2rVFkwvGcUj1RJtD0ko4XqLqJxwqsN/Z/KURB5f1OQ==}
- peerDependencies:
- monaco-editor: '>= 0.21.0 < 1'
- dependencies:
- monaco-editor: 0.32.1
- state-local: 1.0.7
- dev: false
-
- /@monaco-editor/react/4.3.1_e62f1489d5efe674a41c3f8d6971effe:
- resolution: {integrity: sha512-f+0BK1PP/W5I50hHHmwf11+Ea92E5H1VZXs+wvKplWUWOfyMa1VVwqkJrXjRvbcqHL+XdIGYWhWNdi4McEvnZg==}
- peerDependencies:
- monaco-editor: '>= 0.25.0 < 1'
- react: ^16.8.0 || ^17.0.0
- react-dom: ^16.8.0 || ^17.0.0
- dependencies:
- '@monaco-editor/loader': 1.2.0_monaco-editor@0.32.1
- monaco-editor: 0.32.1
- prop-types: 15.8.1
- react: 17.0.2
- react-dom: 17.0.2_react@17.0.2
/@logto/browser/0.1.2:
resolution: {integrity: sha512-sTJjnx00BXYEChCbbO/LPs0x0wE1bDSHniFi+u93cynyEHgoT5yjMnH4N39NhrpmRdkXxOxaIkXmyAT1nSmYzQ==}
requiresBuild: true
@@ -2271,6 +2256,29 @@ packages:
react: 17.0.2
dev: false
+ /@monaco-editor/loader/1.2.0_monaco-editor@0.32.1:
+ resolution: {integrity: sha512-cJVCG/T/KxXgzYnjKqyAgsKDbH9mGLjcXxN6AmwumBwa2rVFkwvGcUj1RJtD0ko4XqLqJxwqsN/Z/KURB5f1OQ==}
+ peerDependencies:
+ monaco-editor: '>= 0.21.0 < 1'
+ dependencies:
+ monaco-editor: 0.32.1
+ state-local: 1.0.7
+ dev: false
+
+ /@monaco-editor/react/4.3.1_e62f1489d5efe674a41c3f8d6971effe:
+ resolution: {integrity: sha512-f+0BK1PP/W5I50hHHmwf11+Ea92E5H1VZXs+wvKplWUWOfyMa1VVwqkJrXjRvbcqHL+XdIGYWhWNdi4McEvnZg==}
+ peerDependencies:
+ monaco-editor: '>= 0.25.0 < 1'
+ react: ^16.8.0 || ^17.0.0
+ react-dom: ^16.8.0 || ^17.0.0
+ dependencies:
+ '@monaco-editor/loader': 1.2.0_monaco-editor@0.32.1
+ monaco-editor: 0.32.1
+ prop-types: 15.8.1
+ react: 17.0.2
+ react-dom: 17.0.2_react@17.0.2
+ dev: false
+
/@nodelib/fs.scandir/2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -5033,6 +5041,10 @@ packages:
yaml: 1.10.2
dev: true
+ /country-flag-icons/1.4.21:
+ resolution: {integrity: sha512-bA9jDr+T5li7EsKdDx0xVnO0bdMdoT8IA3BNbeT2XSWUygR1okhiZ2+eYiC1EKLrFZhI4aEHni2w03lUlOjogg==}
+ dev: false
+
/crack-json/1.3.0:
resolution: {integrity: sha512-JfZ9NPLsU9ejTYgZ7fM+5TIMfTwROTxpi2Twh597GxmiVDwIGZSjaor+zsQBKZ0mmCKOFb9EZZLVeKNf/5UaGg==}
engines: {node: '>=8.0'}
@@ -7243,6 +7255,12 @@ packages:
resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==}
dev: false
+ /input-format/0.3.7:
+ resolution: {integrity: sha512-hgwiCjV7MnhFvX4Hwrvk7hB2a2rcB2CQb7Ex7GlK1ISbEXuLtflwBUnadFSA1rVNDPFh9yWBaJJ4/o1XkzhPIg==}
+ dependencies:
+ prop-types: 15.8.1
+ dev: false
+
/inquirer/7.3.3:
resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==}
engines: {node: '>=8.0.0'}
@@ -8477,6 +8495,10 @@ packages:
- supports-color
dev: true
+ /jest-transform-stub/2.0.0:
+ resolution: {integrity: sha512-lspHaCRx/mBbnm3h4uMMS3R5aZzMwyNpNIJLXj4cEsV0mIUtS4IjYJLSoyjRCtnxb6RIGJ4NL2quZzfIeNhbkg==}
+ dev: true
+
/jest-util/27.4.2:
resolution: {integrity: sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
@@ -9016,6 +9038,10 @@ packages:
- supports-color
dev: true
+ /libphonenumber-js/1.9.49:
+ resolution: {integrity: sha512-/wEOIONcVboFky+lWlCaF7glm1FhBz11M5PHeCApA+xDdVfmhKjHktHS8KjyGxouV5CSXIr4f3GvLSpJa4qMSg==}
+ dev: false
+
/lilconfig/2.0.4:
resolution: {integrity: sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==}
engines: {node: '>=10'}
@@ -11923,6 +11949,21 @@ packages:
warning: 4.0.3
dev: false
+ /react-phone-number-input/3.1.46_react-dom@17.0.2+react@17.0.2:
+ resolution: {integrity: sha512-afYl7BMy/0vMqWtzsZBmOgiPdqQAGyPO/Z3auorFs4K/zgFSBq3YoaASleodBkeRO/PygJ4ML8Wnb4Ce+3dlVQ==}
+ peerDependencies:
+ react: '>=0.16.8'
+ react-dom: '>=0.16.8'
+ dependencies:
+ classnames: 2.3.1
+ country-flag-icons: 1.4.21
+ input-format: 0.3.7
+ libphonenumber-js: 1.9.49
+ prop-types: 15.8.1
+ react: 17.0.2
+ react-dom: 17.0.2_react@17.0.2
+ dev: false
+
/react-refresh/0.9.0:
resolution: {integrity: sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==}
engines: {node: '>=0.10.0'}