feat(console): phone number validation with flags (#5139)

Formats the phonenumber according to the preselected country
This commit is contained in:
Miguel Cabrerizo
2023-02-02 09:36:43 +01:00
committed by GitHub
parent e9d5d1dcaf
commit 5704c44117
284 changed files with 306 additions and 65 deletions

View File

@@ -32,10 +32,12 @@
"codemirror": "^5.65.8",
"cors": "^2.8.5",
"file-saver": "^2.0.5",
"flag-icons": "^6.6.6",
"google-proto-files": "^3.0.2",
"google-protobuf": "^3.21.2",
"grpc-web": "^1.4.1",
"libphonenumber-js": "^1.10.15",
"i18n-iso-countries": "^7.5.0",
"libphonenumber-js": "^1.10.19",
"material-design-icons-iconfont": "^6.1.1",
"moment": "^2.29.4",
"ngx-color": "^8.0.3",
@@ -6728,6 +6730,11 @@
"integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==",
"dev": true
},
"node_modules/diacritics": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz",
"integrity": "sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA=="
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -7929,6 +7936,11 @@
"node": ">=8"
}
},
"node_modules/flag-icons": {
"version": "6.6.6",
"resolved": "https://registry.npmjs.org/flag-icons/-/flag-icons-6.6.6.tgz",
"integrity": "sha512-4lHDKxldnQ7q617pf9Dx9nAetT+9zcMpUexbRrc9kjLw9KJgZ83zA5Dky3Vv7ZDzUjAiZ46x/cy5P0HnEnqA2A=="
},
"node_modules/flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@@ -8688,6 +8700,17 @@
"ms": "^2.0.0"
}
},
"node_modules/i18n-iso-countries": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.5.0.tgz",
"integrity": "sha512-PtfKJNWLVhhU0KBX/8asmywjAcuyQk07mmmMwxFJcddTNBJJ1yvpY2qxVmyxbtVF+9+6eg9phgpv83XPUKU5CA==",
"dependencies": {
"diacritics": "1.3.0"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -10194,9 +10217,9 @@
}
},
"node_modules/libphonenumber-js": {
"version": "1.10.15",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.15.tgz",
"integrity": "sha512-sLeVLmWX17VCKKulc+aDIRHS95TxoTsKMRJi5s5gJdwlqNzMWcBCtSHHruVyXjqfi67daXM2SnLf2juSrdx5Sg=="
"version": "1.10.19",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.19.tgz",
"integrity": "sha512-MDZ1zLIkfSDZV5xBta3nuvbEOlsnKCPe4z5r3hyup/AXveevkl9A1eSWmLhd2FX4k7pJDe4MrLeQsux0HI/VWg=="
},
"node_modules/license-webpack-plugin": {
"version": "4.0.2",

View File

@@ -36,10 +36,12 @@
"codemirror": "^5.65.8",
"cors": "^2.8.5",
"file-saver": "^2.0.5",
"flag-icons": "^6.6.6",
"google-proto-files": "^3.0.2",
"google-protobuf": "^3.21.2",
"grpc-web": "^1.4.1",
"libphonenumber-js": "^1.10.15",
"i18n-iso-countries": "^7.5.0",
"libphonenumber-js": "^1.10.19",
"material-design-icons-iconfont": "^6.1.1",
"moment": "^2.29.4",
"ngx-color": "^8.0.3",

View File

@@ -18,6 +18,7 @@ import { ServiceWorkerModule } from '@angular/service-worker';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { AuthConfig, OAuthModule, OAuthStorage } from 'angular-oauth2-oidc';
import { from, Observable } from 'rxjs';
import * as i18nIsoCountries from 'i18n-iso-countries';
import { AuthGuard } from 'src/app/guards/auth.guard';
import { RoleGuard } from 'src/app/guards/role.guard';
import { UserGuard } from 'src/app/guards/user.guard';
@@ -58,10 +59,15 @@ import { ThemeService } from './services/theme.service';
import { ToastService } from './services/toast.service';
registerLocaleData(localeDe);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/de.json'));
registerLocaleData(localeZh);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/zh.json'));
registerLocaleData(localeFr);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/fr.json'));
registerLocaleData(localeIt);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/it.json'));
registerLocaleData(localeEn);
i18nIsoCountries.registerLocale(require('i18n-iso-countries/langs/en.json'));
export class WebpackTranslateLoader implements TranslateLoader {
getTranslation(lang: string): Observable<any> {

View File

@@ -139,14 +139,27 @@
<p class="user-create-section">{{ 'USER.CREATE.ADDRESSANDPHONESECTION' | translate }}</p>
<div class="phone-grid">
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.COUNTRY' | translate }}</cnsl-label>
<mat-select [(value)]="selected" (selectionChange)="setCountryCallingCode()" data-cy="country-calling-code">
<mat-select-trigger> <span class="fi fi-{{ selected?.countryCode | lowercase }}"></span></mat-select-trigger>
<mat-option *ngFor="let country of countryPhoneCodes" [value]="country">
<span class="fi fi-{{ country.countryCode | lowercase }}"></span>
<span class="phone-country-name">{{ country.countryName }}</span>
<span class="phone-country-code">+{{ country.countryCallingCode }}</span>
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field>
<cnsl-label>{{ 'USER.PROFILE.PHONE' | translate }}</cnsl-label>
<input cnslInput formControlName="phone" />
<input cnslInput formControlName="phone" matTooltip="{{ 'USER.PROFILE.PHONE_HINT' | translate }}" />
<span cnslError *ngIf="phone?.invalid && phone?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
</div>
</div>
<div class="user-create-btn-container">
<button
data-e2e="create-button"

View File

@@ -53,3 +53,22 @@
}
}
}
.phone-grid {
max-width: 22rem;
display: grid;
grid-template-columns: auto 1fr;
column-gap: 1rem;
@media only screen and (max-width: 500px) {
grid-template-columns: 1fr;
}
}
.phone-country-name {
padding: 0 0.5em;
}
.phone-country-code {
color: darkgrey;
}

View File

@@ -1,10 +1,9 @@
import { Location } from '@angular/common';
import { ChangeDetectorRef, Component, OnDestroy, ViewChild } from '@angular/core';
import { ChangeDetectorRef, Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, ValidatorFn, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import parsePhoneNumber from 'libphonenumber-js';
import { parsePhoneNumber, CountryCode } from 'libphonenumber-js';
import { Subject } from 'rxjs';
import { debounceTime, takeUntil } from 'rxjs/operators';
import { AddHumanUserRequest } from 'src/app/proto/generated/zitadel/management_pb';
import { Domain } from 'src/app/proto/generated/zitadel/org_pb';
import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
@@ -14,6 +13,8 @@ import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from '../../validators';
import { CountryCallingCodesService, CountryPhoneCode } from 'src/app/services/country-calling-codes.service';
import { formatPhone } from 'src/app/utils/formatPhone';
function passwordConfirmValidator(c: AbstractControl): any {
if (!c.parent || !c) {
@@ -40,10 +41,12 @@ function passwordConfirmValidator(c: AbstractControl): any {
templateUrl: './user-create.component.html',
styleUrls: ['./user-create.component.scss'],
})
export class UserCreateComponent implements OnDestroy {
export class UserCreateComponent implements OnInit, OnDestroy {
public user: AddHumanUserRequest.AsObject = new AddHumanUserRequest().toObject();
public genders: Gender[] = [Gender.GENDER_FEMALE, Gender.GENDER_MALE, Gender.GENDER_UNSPECIFIED];
public languages: string[] = ['de', 'en', 'it', 'fr'];
public selected: CountryPhoneCode | undefined;
public countryPhoneCodes: CountryPhoneCode[] = [];
public userForm!: UntypedFormGroup;
public pwdForm!: UntypedFormGroup;
private destroyed$: Subject<void> = new Subject();
@@ -63,6 +66,7 @@ export class UserCreateComponent implements OnDestroy {
private mgmtService: ManagementService,
private changeDetRef: ChangeDetectorRef,
private _location: Location,
private countryCallingCodesService: CountryCallingCodesService,
breadcrumbService: BreadcrumbService,
) {
breadcrumbService.setBreadcrumb([
@@ -151,17 +155,6 @@ export class UserCreateComponent implements OnDestroy {
});
}
});
this.userForm.controls['phone'].valueChanges.pipe(takeUntil(this.destroyed$), debounceTime(300)).subscribe((value) => {
const phoneNumber = parsePhoneNumber(value ?? '', 'CH');
if (phoneNumber) {
const formmatted = phoneNumber.formatInternational();
const country = phoneNumber.country;
if (this.phone && country && this.phone.value && this.phone.value !== formmatted) {
this.phone.setValue(formmatted);
}
}
});
}
public createUser(): void {
@@ -190,7 +183,10 @@ export class UserCreateComponent implements OnDestroy {
}
if (this.phone && this.phone.value) {
humanReq.setPhone(new AddHumanUserRequest.Phone().setPhone(this.phone.value));
// Try to parse number and format it according to country
const phoneNumber = formatPhone(this.phone.value);
this.selected = this.countryPhoneCodes.find((code) => code.countryCode === phoneNumber.country);
humanReq.setPhone(new AddHumanUserRequest.Phone().setPhone(phoneNumber.phone));
}
this.mgmtService
@@ -206,6 +202,18 @@ export class UserCreateComponent implements OnDestroy {
});
}
public setCountryCallingCode(): void {
let value = (this.phone?.value as string) || '';
this.phone?.setValue('+' + this.selected?.countryCallingCode + ' ' + value.replace(/\+[0-9]*\s/, ''));
}
ngOnInit(): void {
// Set default selected country for phone numbers
const defaultCountryCallingCode = 'CH';
this.countryPhoneCodes = this.countryCallingCodesService.getCountryCallingCodes();
this.selected = this.countryPhoneCodes.find((code) => code.countryCode === defaultCountryCallingCode);
}
ngOnDestroy(): void {
this.destroyed$.next();
this.destroyed$.complete();

View File

@@ -18,9 +18,11 @@ import { PasswordComplexityViewModule } from 'src/app/modules/password-complexit
import { UserCreateRoutingModule } from './user-create-routing.module';
import { UserCreateComponent } from './user-create.component';
import { CountryCallingCodesService } from 'src/app/services/country-calling-codes.service';
@NgModule({
declarations: [UserCreateComponent],
providers: [CountryCallingCodesService],
imports: [
UserCreateRoutingModule,
CommonModule,

View File

@@ -21,6 +21,7 @@ import { Buffer } from 'buffer';
import { EditDialogComponent, EditDialogType } from './edit-dialog/edit-dialog.component';
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
import { LoginPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { formatPhone } from 'src/app/utils/formatPhone';
@Component({
selector: 'cnsl-auth-user-detail',
@@ -271,6 +272,9 @@ export class AuthUserDetailComponent implements OnDestroy {
public savePhone(phone: string): void {
if (this.user?.human) {
// Format phone before save (add +)
phone = formatPhone(phone).phone;
this.userService
.setMyPhone(phone)
.then(() => {

View File

@@ -3,12 +3,31 @@
</h1>
<p class="desc cnsl-secondary-text">{{ data.descriptionKey | translate }}</p>
<div mat-dialog-content>
<cnsl-form-field class="formfield">
<cnsl-label
>{{ data.labelKey | translate }} <span *ngIf="isPhone && phoneCountry">({{ phoneCountry }})</span>
</cnsl-label>
<input [formControl]="valueControl" cnslInput (keydown.enter)="valueControl.valid ? closeDialogWithValue() : null" />
<div class="phone-grid">
<cnsl-form-field *ngIf="isPhone">
<cnsl-label>{{ 'USER.PROFILE.COUNTRY' | translate }}</cnsl-label>
<mat-select [(value)]="selected" (selectionChange)="setCountryCallingCode()">
<mat-select-trigger> <span class="fi fi-{{ selected?.countryCode | lowercase }}"></span></mat-select-trigger>
<mat-option *ngFor="let country of countryPhoneCodes" [value]="country">
<span class="fi fi-{{ country.countryCode | lowercase }}"></span>
<span class="phone-country-name">{{ country.countryName }}</span>
<span class="phone-country-code">+{{ country.countryCallingCode }}</span>
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field>
<cnsl-label>{{ data.labelKey | translate }}</cnsl-label>
<input
cnslInput
[formControl]="valueControl"
matTooltip="{{ 'USER.PROFILE.PHONE_HINT' | translate }}"
[matTooltipDisabled]="!isPhone"
/>
<span cnslError *ngIf="valueControl?.invalid && valueControl?.errors?.required">
{{ 'USER.VALIDATION.REQUIRED' | translate }}
</span>
</cnsl-form-field>
</div>
<ng-container *ngIf="data.type === EditDialogType.EMAIL && data.isVerifiedTextKey">
<mat-checkbox class="verified-checkbox" [(ngModel)]="isVerified">

View File

@@ -22,3 +22,22 @@
border-radius: 0.5rem;
}
}
.phone-grid {
max-width: 22rem;
display: grid;
grid-template-columns: auto 1fr;
column-gap: 1rem;
@media only screen and (max-width: 500px) {
grid-template-columns: 1fr;
}
}
.phone-country-name {
padding: 0 0.5em;
}
.phone-country-code {
color: darkgrey;
}

View File

@@ -1,10 +1,12 @@
import { Component, Inject } from '@angular/core';
import { Component, Inject, OnInit } from '@angular/core';
import { UntypedFormControl, Validators } from '@angular/forms';
import {
MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA,
MatLegacyDialogRef as MatDialogRef,
} from '@angular/material/legacy-dialog';
import { parsePhoneNumber } from 'libphonenumber-js';
import { CountryCode, parsePhoneNumber } from 'libphonenumber-js';
import { CountryCallingCodesService, CountryPhoneCode } from 'src/app/services/country-calling-codes.service';
import { formatPhone } from 'src/app/utils/formatPhone';
export enum EditDialogType {
PHONE = 1,
@@ -16,39 +18,37 @@ export enum EditDialogType {
templateUrl: './edit-dialog.component.html',
styleUrls: ['./edit-dialog.component.scss'],
})
export class EditDialogComponent {
export class EditDialogComponent implements OnInit {
public isPhone: boolean = false;
public isVerified: boolean = false;
public phoneCountry: string = 'CH';
public valueControl: UntypedFormControl = new UntypedFormControl(['', [Validators.required]]);
public EditDialogType: any = EditDialogType;
constructor(public dialogRef: MatDialogRef<EditDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: any) {
public selected: CountryPhoneCode | undefined;
public countryPhoneCodes: CountryPhoneCode[] = [];
constructor(
public dialogRef: MatDialogRef<EditDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
private countryCallingCodesService: CountryCallingCodesService,
) {
this.valueControl.setValue(data.value);
if (data.type === EditDialogType.PHONE) {
this.isPhone = true;
}
this.valueControl.valueChanges.subscribe((value) => {
if (value && value.length > 1) {
this.changeValue(value);
}
});
}
private changeValue(changedValue: string): void {
if (this.isPhone && changedValue) {
try {
const phoneNumber = parsePhoneNumber(changedValue ?? '', 'CH');
if (phoneNumber) {
const formmatted = phoneNumber.formatInternational();
this.phoneCountry = phoneNumber.country || '';
if (formmatted !== this.valueControl.value) {
this.valueControl.setValue(formmatted);
}
}
} catch (error) {
console.error(error);
public setCountryCallingCode(): void {
let value = (this.valueControl?.value as string) || '';
this.valueControl?.setValue('+' + this.selected?.countryCallingCode + ' ' + value.replace(/\+[0-9]*\s/, ''));
}
ngOnInit(): void {
if (this.isPhone) {
// Get country phone codes and set selected flag to guessed country or default country
this.countryPhoneCodes = this.countryCallingCodesService.getCountryCallingCodes();
const phoneNumber = formatPhone(this.valueControl?.value);
this.selected = this.countryPhoneCodes.find((code) => code.countryCode === phoneNumber.country);
this.valueControl.setValue(phoneNumber.phone);
}
}

View File

@@ -36,7 +36,7 @@
<div class="contact-method-row">
<div class="left">
<span class="label cnsl-secondary-text">{{ 'USER.PHONE' | translate }}</span>
<span class="name">{{ human.phone?.phone ? human.phone?.phone : ('USER.PHONEEMPTY' | translate) }}</span>
<cnsl-phone-detail [phone]="human.phone?.phone"></cnsl-phone-detail>
<span *ngIf="human.phone?.isPhoneVerified" class="contact-state verified">{{ 'USER.PHONEVERIFIED' | translate }}</span>
<div *ngIf="human.phone?.phone && !human.phone?.isPhoneVerified" class="block">
<span class="contact-state notverified">{{ 'USER.NOTVERIFIED' | translate }}</span>

View File

@@ -0,0 +1,4 @@
<span class="name">
<span *ngIf="country" class="fi fi-{{ country | lowercase }} margin-right"></span>
{{ phone ? phone : ('USER.PHONEEMPTY' | translate) }}
</span>

View File

@@ -0,0 +1,3 @@
.margin-right {
margin-right: 0.5em;
}

View File

@@ -0,0 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { PhoneDetailComponent } from './phone-detail.component';
describe('PhoneDetailComponent', () => {
let component: PhoneDetailComponent;
let fixture: ComponentFixture<PhoneDetailComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [PhoneDetailComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PhoneDetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,24 @@
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { formatPhone } from 'src/app/utils/formatPhone';
@Component({
selector: 'cnsl-phone-detail',
templateUrl: './phone-detail.component.html',
styleUrls: ['./phone-detail.component.scss'],
})
export class PhoneDetailComponent implements OnChanges {
@Input() phone: string | undefined;
public country: string | undefined;
ngOnChanges(changes: SimpleChanges): void {
if (changes.phone.currentValue) {
const phoneNumber = formatPhone(changes.phone.currentValue);
if (this.phone !== phoneNumber.phone) {
this.phone = phoneNumber.phone;
this.country = phoneNumber.country;
}
} else {
this.country = undefined;
}
}
}

View File

@@ -58,6 +58,9 @@ import { UserMfaComponent } from './user-detail/user-mfa/user-mfa.component';
import { MachineSecretDialogComponent } from './user-detail/machine-secret-dialog/machine-secret-dialog.component';
import { MetadataModule } from 'src/app/modules/metadata/metadata.module';
import { QRCodeModule } from 'angularx-qrcode';
import { PhoneDetailComponent } from './phone-detail/phone-detail.component';
import { CountryCallingCodesService } from 'src/app/services/country-calling-codes.service';
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
@NgModule({
declarations: [
@@ -76,8 +79,10 @@ import { QRCodeModule } from 'angularx-qrcode';
DialogU2FComponent,
DialogPasswordlessComponent,
AuthFactorDialogComponent,
PhoneDetailComponent,
MachineSecretDialogComponent,
],
providers: [CountryCallingCodesService],
imports: [
ChangesModule,
CommonModule,
@@ -122,6 +127,7 @@ import { QRCodeModule } from 'angularx-qrcode';
InputModule,
MachineKeysModule,
InfoSectionModule,
MatSelectModule,
],
})
export class UserDetailModule {}

View File

@@ -21,6 +21,7 @@ import { Buffer } from 'buffer';
import { EditDialogComponent, EditDialogType } from '../auth-user-detail/edit-dialog/edit-dialog.component';
import { ResendEmailDialogComponent } from '../auth-user-detail/resend-email-dialog/resend-email-dialog.component';
import { LoginPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
import { formatPhone } from 'src/app/utils/formatPhone';
import { MachineSecretDialogComponent } from './machine-secret-dialog/machine-secret-dialog.component';
const GENERAL: SidenavSetting = { id: 'general', i18nKey: 'USER.SETTINGS.GENERAL' };
@@ -355,6 +356,9 @@ export class UserDetailComponent implements OnInit {
public savePhone(phone: string): void {
if (this.user.id && phone) {
// Format phone before save (add +)
phone = formatPhone(phone).phone;
this.mgmtUserService
.updateHumanPhone(this.user.id, phone)
.then(() => {

View File

@@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { CountryCode, getCountries, getCountryCallingCode } from 'libphonenumber-js';
import * as i18nIsoCountries from 'i18n-iso-countries';
export interface CountryPhoneCode {
countryCode: string;
countryName: string;
countryCallingCode: string;
}
@Injectable()
export class CountryCallingCodesService {
constructor(private translateService: TranslateService) {}
public getCountryCallingCodes(): CountryPhoneCode[] {
const currentLang = this.translateService.currentLang ?? 'en';
const countryPhoneCodes = getCountries()
.filter((code: CountryCode) => i18nIsoCountries.getName(code.toString(), currentLang))
.map((code: CountryCode) => {
return <CountryPhoneCode>{
countryCode: code,
countryName: i18nIsoCountries.getName(code.toString(), currentLang),
countryCallingCode: getCountryCallingCode(code),
};
})
.sort((a, b) => a.countryName.localeCompare(b.countryName));
return countryPhoneCodes;
}
}

View File

@@ -0,0 +1,19 @@
import { CountryCode, parsePhoneNumber } from 'libphonenumber-js';
export function formatPhone(phone: string): { phone: string; country: CountryCode } {
const defaultCountry = 'CH';
if (phone) {
try {
const phoneNumber = parsePhoneNumber(phone, defaultCountry);
const country = phoneNumber.country ?? defaultCountry;
if (phoneNumber) {
return { phone: phoneNumber.formatInternational(), country };
}
} catch (error) {
console.error(error);
}
}
return { phone, country: defaultCountry };
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 735 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 646 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 955 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 B

Some files were not shown because too many files have changed in this diff Show More