From 1810aa41a4de01325f5a167ee817e168d3ac85cc Mon Sep 17 00:00:00 2001 From: Aastha Bist Date: Wed, 11 Dec 2024 08:38:53 +0530 Subject: [PATCH] fix: Revert Verify changes --- src/app/auth/disabled/disabled.page.html | 50 ++-- src/app/auth/disabled/disabled.page.scss | 127 +++++---- .../auth/new-password/new-password.module.ts | 2 - .../auth/new-password/new-password.page.html | 229 ++++++++------- .../auth/new-password/new-password.page.scss | 131 ++++----- .../new-password/new-password.page.spec.ts | 241 +++++++++------- .../auth/new-password/new-password.page.ts | 127 +++++---- .../pending-verification.page.html | 109 ++----- .../pending-verification.page.spec.ts | 159 ++++------- .../pending-verification.page.ts | 56 ++-- .../reset-password/reset-password.page.html | 99 +------ .../reset-password/reset-password.page.scss | 170 ----------- .../reset-password.page.spec.ts | 187 ++++-------- .../reset-password/reset-password.page.ts | 60 +--- src/app/auth/verify/verify.page.html | 21 +- src/app/auth/verify/verify.page.scss | 104 +++++-- src/app/auth/verify/verify.page.spec.ts | 28 +- src/app/auth/verify/verify.page.ts | 16 +- .../invited-user/invited-user.module.ts | 2 - .../invited-user/invited-user.page.html | 267 +++++++++--------- .../invited-user/invited-user.page.scss | 131 ++++----- .../invited-user/invited-user.page.spec.ts | 194 +++++++------ .../invited-user/invited-user.page.ts | 92 +++--- .../password-check-tooltip.component.scss | 30 +- .../password-check-tooltip.component.spec.ts | 22 ++ .../password-check-tooltip.component.ts | 9 +- src/app/shared/shared.module.ts | 3 - src/assets/svg/arrow-left.svg | 4 +- src/assets/svg/check-circle-outline.svg | 6 +- src/assets/svg/error-outlined.svg | 9 - src/assets/svg/eye-slash.svg | 7 - src/assets/svg/eye.svg | 4 - src/assets/svg/loader.svg | 4 +- src/global.scss | 2 +- src/theme/colors.scss | 1 - 35 files changed, 1154 insertions(+), 1549 deletions(-) delete mode 100644 src/app/auth/reset-password/reset-password.page.scss delete mode 100644 src/assets/svg/error-outlined.svg delete mode 100644 src/assets/svg/eye-slash.svg delete mode 100644 src/assets/svg/eye.svg diff --git a/src/app/auth/disabled/disabled.page.html b/src/app/auth/disabled/disabled.page.html index 6c94fb92aa..f78f04cfac 100644 --- a/src/app/auth/disabled/disabled.page.html +++ b/src/app/auth/disabled/disabled.page.html @@ -1,33 +1,25 @@ -
-
-
- -
-
-

Account disabled

-

- This account is no longer active. Please contact your company admin for details. -

-
-
-
- -
- - Back to Sign In -
-
+ + + + + + + +
+
Account Disabled
+ Stop +
+ This account is no longer active. Please contact your company admin for details.
+ + + + + Incorrect Account? + Try Signing in Again + + + diff --git a/src/app/auth/disabled/disabled.page.scss b/src/app/auth/disabled/disabled.page.scss index 4b402c85cd..5fdb92d550 100644 --- a/src/app/auth/disabled/disabled.page.scss +++ b/src/app/auth/disabled/disabled.page.scss @@ -1,80 +1,93 @@ -@import '../../../theme/colors.scss'; +$disabled-header: #220033; +$body-header: #000; +$body-subheader: #4a4a4a; +$password-icon: #b9beba; +$secondary-cta-border: #e0e0e0; -.disabled-user { - display: flex; - flex-direction: column; - justify-content: space-between; - height: 100%; +.disabled { + &--header { + min-height: 140px; + } + + &--header-container { + min-height: 140px; + background-color: $disabled-header; + } - &__content-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - flex-grow: 2; + &--header-logo-container { + text-align: center; } - &__arrow-icon { - fill: $pure-white; - margin-right: 8px; + &--header-logo { + max-width: 100px; } - &__cta-text { - font-size: 14px; - font-weight: 500; + &--body { + padding: 24px; + text-align: center; } - &__cta { - margin: 0 20px 24px 20px; + &--body-field { + width: 100%; } - &__cta-content { - display: flex; - align-items: center; - justify-content: center; + &--body-header { + font-size: 24px; + margin-top: 16px; + margin-bottom: 8px; + color: $body-header; + font-weight: 700; } - &__error-icon-container { - width: 60px; - height: 60px; - border-radius: 8px; - background: $pale-pink; - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 16px; + &--image { + max-width: 125px; + padding: 12px; } - &__error-icon { - width: 45px; - height: 45px; + &--subtext { + font-size: 16px; + color: $body-subheader; + margin-top: 8px; + margin-bottom: 24px; } - &__text { - display: flex; - align-items: center; - gap: 8px; - flex-direction: column; + &--primary-cta { + .mat-button-base { + width: 100%; + font-weight: 700; + min-height: 47px; + } } - &__header { - color: $black; - font-size: 20px; - font-style: normal; - font-weight: 500; - line-height: normal; - height: 26px; - margin: 0; + &--password-visibility-icon { + color: $password-icon; } - &__content { - color: $black-light; + &--secondary-cta { + .mat-button-base { + width: 100%; + font-weight: 700; + min-height: 47px; + letter-spacing: 1.6px; + border: 1px solid $secondary-cta-border; + } + margin-top: 24px; + } + + &--redirect { + margin: 16px; text-align: center; - font-size: 14px; - font-style: normal; - font-weight: 400; - line-height: 1.25; - height: 36px; - width: 274px; + font-size: 16px; + a { + text-decoration: none; + } + } + + &--edit-email { + text-align: end; + } + + &--greyed { + color: grey; } } diff --git a/src/app/auth/new-password/new-password.module.ts b/src/app/auth/new-password/new-password.module.ts index 452791efbb..ebe48b4b6e 100644 --- a/src/app/auth/new-password/new-password.module.ts +++ b/src/app/auth/new-password/new-password.module.ts @@ -17,7 +17,6 @@ import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { PopupComponent } from './popup/popup.component'; -import { SharedModule } from 'src/app/shared/shared.module'; @NgModule({ imports: [ @@ -31,7 +30,6 @@ import { SharedModule } from 'src/app/shared/shared.module'; ReactiveFormsModule, MatIconModule, MatButtonModule, - SharedModule, ], declarations: [NewPasswordPage, PopupComponent], }) diff --git a/src/app/auth/new-password/new-password.page.html b/src/app/auth/new-password/new-password.page.html index 0b8c56eee9..ad72afd5e7 100644 --- a/src/app/auth/new-password/new-password.page.html +++ b/src/app/auth/new-password/new-password.page.html @@ -1,106 +1,133 @@ -
-
- -
-
Reset Password
+ + + + + + + + +
+
Reset password
+
-
-
-
-
New password
-
- -
- -
-
- -
-
- Password cannot be empty -
-
- Please enter a valid password. -
-
-
-
-
Confirm new password
-
- -
- -
-
-
-
- Password cannot be empty -
-
- Passwords do not match -
-
-
+ + + + + Is between 12 to 32 characters +
+
+ + + + + Contains atleast 1 uppercase character +
+
+ + + + + Contains atleast 1 lowercase character +
+
+ + + + + Contains atleast 1 number +
+
+ + + + + Contains atleast 1 special character
-
-
- - Reset password - -
- - Back to Sign In +
Please enter a new password
+
+ + + + {{hide ? 'visibility_off' : 'visibility'}} + + +
+
+
-
+
+ + + + + Back to Sign in + + + diff --git a/src/app/auth/new-password/new-password.page.scss b/src/app/auth/new-password/new-password.page.scss index 23fcc7c757..e1afee8662 100644 --- a/src/app/auth/new-password/new-password.page.scss +++ b/src/app/auth/new-password/new-password.page.scss @@ -1,111 +1,84 @@ -@import '../../../theme/colors.scss'; +$new-password-header: #220033; +$form-header: #000; +$form-subheader: #4a4a4a; +$password-icon: #b9beba; +$secondary-cta-border: #e0e0e0; +$flash-yellow: #fde081; +$error: #f00; +$offline-title: #4a4a4a; +$offline-sub-title: #ababab; .new-password { - display: flex; - flex-direction: column; - padding: 24px 20px; - margin-top: 18px; - height: 100%; - justify-content: space-between; - - &__password-container { - display: flex; + &--header { + min-height: 140px; } - &__input-container { - margin-bottom: 24px; + &--header-container { + min-height: 140px; + background-color: $new-password-header; } - &__error { - color: $red; - font-size: 12px; + &--header-logo-container { + text-align: center; } - &__text { - border-bottom: 1px solid $grey-lighter; - - &__invalid { - border-bottom: 1px solid $red; - } + &--header-logo { + max-width: 100px; } - &__mandatory { - color: $brand-primary; - display: inline-block; - font-size: 14px; - font-weight: 400; + &--form { + padding: 24px; } - &__text-label { - margin: 0 8px 0 0; - max-width: 90%; - min-width: 120px; - font-size: 12px; - color: $black-light; - line-height: 16px; - white-space: nowrap; - font-weight: 400; + &--form-field { + width: 100%; } - &__text-input { - border: 0; - font-size: 14px; - font-weight: 400; - height: 18px; - line-height: 18px; - color: $blue-black; - width: 100%; - margin: 6px 0; - padding: 0; + &--form-header { + font-size: 24px; + margin-top: 16px; + margin-bottom: 8px; + color: $form-header; + font-weight: 700; } - &__password-icon { - width: 20px; - height: 20px; - fill: $black-light; + &--form-subheader { + font-size: 16px; + color: $form-subheader; + margin-top: 8px; + margin-bottom: 24px; } - &__password-icon-container { - display: flex; - align-items: center; + &--primary-cta { + margin-top: 12px; + .mat-button-base { + width: 100%; + font-weight: 700; + min-height: 47px; + } } - &__form-header { - font-size: 20px; - position: relative; - margin-bottom: 24px; - color: $black; + &--password-rules { + padding: 14px 0; } - &__save { + &--save { width: 100%; &__disabled { opacity: 0.2; } } - &__back-icon { - margin-bottom: 20px; - width: 28px; - height: 28px; - } - - &__cta-text { - font-size: 14px; - font-weight: 500; - } + &--validation { + vertical-align: middle; + font-size: 16px; - &__arrow-icon { - fill: $pure-white; - margin-right: 6px; - } + &__correct { + color: green; + } - &__cta-secondary { - display: flex; - padding-top: 18px; - align-items: center; - justify-content: center; - color: $blue-black; - flex-direction: row; + &__incorrect { + color: red; + } } } diff --git a/src/app/auth/new-password/new-password.page.spec.ts b/src/app/auth/new-password/new-password.page.spec.ts index c4da1499ba..f5db061720 100644 --- a/src/app/auth/new-password/new-password.page.spec.ts +++ b/src/app/auth/new-password/new-password.page.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; -import { IonicModule } from '@ionic/angular'; +import { IonicModule, PopoverController } from '@ionic/angular'; import { NewPasswordPage } from './new-password.page'; import { AuthService } from 'src/app/core/services/auth.service'; @@ -9,16 +9,14 @@ import { TrackingService } from 'src/app/core/services/tracking.service'; import { DeviceService } from 'src/app/core/services/device.service'; import { LoginInfoService } from 'src/app/core/services/login-info.service'; import { FormBuilder, FormControl, ReactiveFormsModule } from '@angular/forms'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { of } from 'rxjs'; import { apiEouRes } from 'src/app/core/mock-data/extended-org-user.data'; import { extendedDeviceInfoMockData } from 'src/app/core/mock-data/extended-device-info.data'; import { RouterTestingModule } from '@angular/router/testing'; import { getElementBySelector } from 'src/app/core/dom-helpers'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-properties.service'; -import { ToastMessageComponent } from 'src/app/shared/components/toast-message/toast-message.component'; +import { PopupComponent } from './popup/popup.component'; describe('NewPasswordPage', () => { let component: NewPasswordPage; @@ -26,23 +24,19 @@ describe('NewPasswordPage', () => { let authService: jasmine.SpyObj; let routerAuthService: jasmine.SpyObj; let loaderService: jasmine.SpyObj; + let popoverController: jasmine.SpyObj; let trackingService: jasmine.SpyObj; let deviceService: jasmine.SpyObj; let loginInfoService: jasmine.SpyObj; - let router: jasmine.SpyObj; - let matSnackBar: jasmine.SpyObj; - let snackbarPropertiesService: jasmine.SpyObj; beforeEach(waitForAsync(() => { const authServiceSpy = jasmine.createSpyObj('AuthService', ['refreshEou']); const routerAuthServiceSpy = jasmine.createSpyObj('RouterAuthService', ['resetPassword']); const loaderServiceSpy = jasmine.createSpyObj('LoaderService', ['showLoader', 'hideLoader']); + const popoverControllerSpy = jasmine.createSpyObj('PopoverController', ['create']); const trackingServiceSpy = jasmine.createSpyObj('TrackingService', ['onSignin', 'resetPassword', 'eventTrack']); const deviceServiceSpy = jasmine.createSpyObj('DeviceService', ['getDeviceInfo']); const loginInfoServiceSpy = jasmine.createSpyObj('LoginInfoService', ['addLoginInfo']); - const routerSpy = jasmine.createSpyObj('Router', ['navigate']); - const matSnackBarSpy = jasmine.createSpyObj('MatSnackBar', ['openFromComponent']); - const snackbarPropertiesServiceSpy = jasmine.createSpyObj('SnackbarPropertiesService', ['setSnackbarProperties']); TestBed.configureTestingModule({ declarations: [NewPasswordPage], @@ -52,12 +46,10 @@ describe('NewPasswordPage', () => { { provide: AuthService, useValue: authServiceSpy }, { provide: RouterAuthService, useValue: routerAuthServiceSpy }, { provide: LoaderService, useValue: loaderServiceSpy }, + { provide: PopoverController, useValue: popoverControllerSpy }, { provide: TrackingService, useValue: trackingServiceSpy }, { provide: DeviceService, useValue: deviceServiceSpy }, { provide: LoginInfoService, useValue: loginInfoServiceSpy }, - { provide: MatSnackBar, useValue: matSnackBarSpy }, - { provide: SnackbarPropertiesService, useValue: snackbarPropertiesServiceSpy }, - { provide: Router, useValue: routerSpy }, { provide: ActivatedRoute, useValue: { @@ -75,12 +67,10 @@ describe('NewPasswordPage', () => { authService = TestBed.inject(AuthService) as jasmine.SpyObj; routerAuthService = TestBed.inject(RouterAuthService) as jasmine.SpyObj; loaderService = TestBed.inject(LoaderService) as jasmine.SpyObj; + popoverController = TestBed.inject(PopoverController) as jasmine.SpyObj; trackingService = TestBed.inject(TrackingService) as jasmine.SpyObj; deviceService = TestBed.inject(DeviceService) as jasmine.SpyObj; loginInfoService = TestBed.inject(LoginInfoService) as jasmine.SpyObj; - router = TestBed.inject(Router) as jasmine.SpyObj; - matSnackBar = TestBed.inject(MatSnackBar) as jasmine.SpyObj; - snackbarPropertiesService = TestBed.inject(SnackbarPropertiesService) as jasmine.SpyObj; fixture.detectChanges(); })); @@ -88,23 +78,133 @@ describe('NewPasswordPage', () => { expect(component).toBeTruthy(); }); + describe('ngOnInit():', () => { + it('should initialize the form and observables', () => { + component.ngOnInit(); + + expect(component.fg).toBeDefined(); + expect(component.lengthValidationDisplay$).toBeDefined(); + expect(component.uppercaseValidationDisplay$).toBeDefined(); + expect(component.numberValidationDisplay$).toBeDefined(); + expect(component.specialCharValidationDisplay$).toBeDefined(); + expect(component.lowercaseValidationDisplay$).toBeDefined(); + }); + + it('should validate password length of 12 characters', () => { + const checkmarkIcon = getElementBySelector(fixture, '[data-testid="lengthValidation_correct"]'); + const passwordControl = component.fg.controls.password as FormControl; + + passwordControl.setValue('123456789012'); + expect(checkmarkIcon).toBeDefined(); + }); + + it('should validate password length of 32 characters', () => { + const checkmarkIcon = getElementBySelector(fixture, '[data-testid="lengthValidation_correct"]'); + const passwordControl = component.fg.controls.password as FormControl; + + passwordControl.setValue('1234567890123456789012345678901'); + expect(checkmarkIcon).toBeDefined(); + }); + + it('should not validate password length of less 12 characters', () => { + const closeIcon = getElementBySelector(fixture, '[data-testid="lengthValidation_incorrect"]'); + const passwordControl = component.fg.controls.password as FormControl; + + passwordControl.setValue('12345'); + expect(closeIcon).toBeDefined(); + }); + + it('should not validate password length of more 32 characters', () => { + const closeIcon = getElementBySelector(fixture, '[data-testid="lengthValidation_incorrect"]'); + const passwordControl = component.fg.controls.password as FormControl; + + passwordControl.setValue('12345678901234567890123456789012'); + expect(closeIcon).toBeDefined(); + }); + + it('should validate the presence of an uppercase letter in password', () => { + const checkmarkIcon = getElementBySelector(fixture, '[data-testid="uppercaseValidation_correct"]'); + const passwordControl = component.fg.controls.password as FormControl; + + passwordControl.setValue('PasswordWithUpperCase'); + expect(checkmarkIcon).toBeDefined(); + }); + + it('should not validate the absence of an uppercase letter in password', () => { + const closeIcon = getElementBySelector(fixture, '[data-testid="uppercaseValidation_incorrect"]'); + const passwordControl = component.fg.controls.password as FormControl; + + passwordControl.setValue('passwordwithoutuppercase'); + expect(closeIcon).toBeDefined(); + }); + + it('should validate the presence of a number in password', () => { + const checkmarkIcon = getElementBySelector(fixture, '[data-testid="numberValidation_correct"]'); + const passwordControl = component.fg.controls.password as FormControl; + + passwordControl.setValue('PasswordWithNumber123'); + expect(checkmarkIcon).toBeDefined(); + }); + + it('should not validate the absence of a number in password', () => { + const closeIcon = getElementBySelector(fixture, '[data-testid="numberValidation_incorrect"]'); + const passwordControl = component.fg.controls.password as FormControl; + + passwordControl.setValue('PasswordWithoutNumber'); + expect(closeIcon).toBeDefined(); + }); + + it('should validate the presence of a special character in password', () => { + const checkmarkIcon = getElementBySelector(fixture, '[data-testid="specialcharValidation_correct"]'); + const passwordControl = component.fg.controls.password as FormControl; + + passwordControl.setValue('PasswordWith@Special#Char'); + expect(checkmarkIcon).toBeDefined(); + }); + + it('should not validate the absence of a special character in password', () => { + const closeIcon = getElementBySelector(fixture, '[data-testid="specialcharValidation_incorrect"]'); + const passwordControl = component.fg.controls.password as FormControl; + + passwordControl.setValue('PasswordWithoutSpecialChar'); + expect(closeIcon).toBeDefined(); + }); + + it('should validate the presence of a lowercase letter in password', () => { + const checkmarkIcon = getElementBySelector(fixture, '[data-testid="lowercaseValidation_correct"]'); + const passwordControl = component.fg.controls.password as FormControl; + + passwordControl.setValue('PasswordWithLowerCase'); + expect(checkmarkIcon).toBeDefined(); + }); + + it('should not validate the absence of a lowercase letter in password', () => { + const closeIcon = getElementBySelector(fixture, '[data-testid="lowercaseValidation_incorrect"]'); + const passwordControl = component.fg.controls.password as FormControl; + + passwordControl.setValue('PASSWORDWITHOUTLOWERCASE'); + expect(closeIcon).toBeDefined(); + }); + }); + describe('changePassword', () => { const passwordValue = 'DummyPassword@123'; const refreshToken = 'token123'; const resetPasswordRes = { ...apiEouRes, refresh_token: refreshToken }; it('should change the password and show success message on success', fakeAsync(() => { - const message = 'Password changed successfully'; spyOn(component, 'trackLoginInfo'); routerAuthService.resetPassword.and.returnValue(of(resetPasswordRes)); authService.refreshEou.and.returnValue(of(apiEouRes)); + const popoverSpy = jasmine.createSpyObj('HTMLIonPopoverElement', ['present']); + popoverController.create.and.returnValue(popoverSpy); deviceService.getDeviceInfo.and.returnValue(of(extendedDeviceInfoMockData)); loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); component.fg.controls.password.setValue(passwordValue); fixture.detectChanges(); - const newPasswordButton = getElementBySelector(fixture, '.btn-primary') as HTMLButtonElement; + const newPasswordButton = getElementBySelector(fixture, '#new-password--btn-sign-in') as HTMLButtonElement; newPasswordButton.click(); tick(500); @@ -115,23 +215,27 @@ describe('NewPasswordPage', () => { expect(trackingService.onSignin).toHaveBeenCalledOnceWith('ajain@fyle.in'); expect(trackingService.resetPassword).toHaveBeenCalledTimes(1); expect(component.trackLoginInfo).toHaveBeenCalledTimes(1); - expect(snackbarPropertiesService.setSnackbarProperties).toHaveBeenCalledOnceWith('success', { message }); - expect(matSnackBar.openFromComponent).toHaveBeenCalledOnceWith(ToastMessageComponent, { - ...snackbarPropertiesService.setSnackbarProperties('success', { message }), - panelClass: ['msb-success'], + expect(popoverController.create).toHaveBeenCalledOnceWith({ + component: PopupComponent, + componentProps: { + header: 'Password changed successfully', + route: ['/', 'auth', 'switch_org'], + }, + cssClass: 'dialog-popover', }); })); it('should show error message on failure', fakeAsync(() => { - const message = 'Something went wrong. Please try after some time.'; spyOn(component, 'trackLoginInfo'); routerAuthService.resetPassword.and.rejectWith(); + const popoverSpy = jasmine.createSpyObj('HTMLIonPopoverElement', ['present']); + popoverController.create.and.returnValue(popoverSpy); loaderService.showLoader.and.resolveTo(); loaderService.hideLoader.and.resolveTo(); component.fg.controls.password.setValue(passwordValue); fixture.detectChanges(); - const newPasswordButton = getElementBySelector(fixture, '.btn-primary') as HTMLButtonElement; + const newPasswordButton = getElementBySelector(fixture, '#new-password--btn-sign-in') as HTMLButtonElement; newPasswordButton.click(); tick(500); @@ -142,9 +246,13 @@ describe('NewPasswordPage', () => { expect(trackingService.onSignin).not.toHaveBeenCalled(); expect(trackingService.resetPassword).not.toHaveBeenCalled(); expect(component.trackLoginInfo).not.toHaveBeenCalled(); - expect(matSnackBar.openFromComponent).toHaveBeenCalledOnceWith(ToastMessageComponent, { - ...snackbarPropertiesService.setSnackbarProperties('failure', { message }), - panelClass: ['msb-failure'], + expect(popoverController.create).toHaveBeenCalledOnceWith({ + component: PopupComponent, + componentProps: { + header: 'Setting new password failed. Please try again later.', + route: ['/', 'auth', 'sign_in'], + }, + cssClass: 'dialog-popover', }); })); }); @@ -162,81 +270,4 @@ describe('NewPasswordPage', () => { expect(trackingService.eventTrack).toHaveBeenCalledOnceWith('Added Login Info', { label: '5.50.0' }); expect(loginInfoService.addLoginInfo).toHaveBeenCalledOnceWith('5.50.0', mockDate); })); - - it('redirectToSignIn(): should navigate to the sign-in page', () => { - component.redirectToSignIn(); - // @ts-ignore - expect(component.router.navigate).toHaveBeenCalledOnceWith(['/', 'auth', 'sign_in']); // Should navigate to the correct route - }); - - describe('checkPasswordValidity():', () => { - it('should return null when isPasswordValid is true', () => { - component.isPasswordValid = true; - - const result = component.checkPasswordValidity(); - - expect(result).toBeNull(); // No errors - }); - - it('should return an error object when isPasswordValid is false', () => { - component.isPasswordValid = false; - - const result = component.checkPasswordValidity(); - - expect(result).toEqual({ invalidPassword: true }); // Error object - }); - }); - - describe('validatePasswordEquality():', () => { - it('should return null when password and confirmPassword match', () => { - component.fg.controls.password.setValue('StrongPassword@123'); - component.fg.controls.confirmPassword.setValue('StrongPassword@123'); - - const result = component.validatePasswordEquality(); - - expect(result).toBeNull(); // No errors - }); - - it('should return an error object when password and confirmPassword do not match', () => { - component.fg.controls.password.setValue('StrongPassword@123'); - component.fg.controls.confirmPassword.setValue('DifferentPassword@123'); - - const result = component.validatePasswordEquality(); - - expect(result).toEqual({ passwordMismatch: true }); - }); - - it('should return null when password or confirmPassword is empty', () => { - component.fg.controls.password.setValue(''); - component.fg.controls.confirmPassword.setValue(''); - - const result = component.validatePasswordEquality(); - - expect(result).toBeNull(); - }); - }); - - describe('onPasswordValid():', () => { - it('should set isPasswordValid to true when called with true', () => { - component.onPasswordValid(true); - expect(component.isPasswordValid).toBeTrue(); - }); - - it('should set isPasswordValid to false when called with false', () => { - component.onPasswordValid(false); - expect(component.isPasswordValid).toBeFalse(); - }); - }); - - describe('setPasswordTooltip():', () => { - it('should set showPasswordTooltip to true when called with true', () => { - component.setPasswordTooltip(true); - expect(component.showPasswordTooltip).toBeTrue(); - }); - - it('should set showPasswordTooltip to false when called with false', () => { - component.setPasswordTooltip(false); - expect(component.showPasswordTooltip).toBeFalse(); - }); - }); }); diff --git a/src/app/auth/new-password/new-password.page.ts b/src/app/auth/new-password/new-password.page.ts index b09d75752b..0d5295e798 100644 --- a/src/app/auth/new-password/new-password.page.ts +++ b/src/app/auth/new-password/new-password.page.ts @@ -1,17 +1,16 @@ import { Component, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms'; -import { finalize, switchMap, tap } from 'rxjs/operators'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { finalize, map, switchMap, tap } from 'rxjs/operators'; import { from, Observable } from 'rxjs'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { LoaderService } from 'src/app/core/services/loader.service'; import { RouterAuthService } from 'src/app/core/services/router-auth.service'; import { AuthService } from 'src/app/core/services/auth.service'; +import { PopoverController } from '@ionic/angular'; +import { PopupComponent } from './popup/popup.component'; import { TrackingService } from '../../core/services/tracking.service'; import { DeviceService } from '../../core/services/device.service'; import { LoginInfoService } from '../../core/services/login-info.service'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-properties.service'; -import { ToastMessageComponent } from 'src/app/shared/components/toast-message/toast-message.component'; @Component({ selector: 'app-new-password', @@ -31,41 +30,62 @@ export class NewPasswordPage implements OnInit { lowercaseValidationDisplay$: Observable; - isPasswordValid = false; - hide = false; - hideConfirmPassword = false; - - showPasswordTooltip = false; - constructor( private fb: FormBuilder, private activatedRoute: ActivatedRoute, private loaderService: LoaderService, private routerAuthService: RouterAuthService, private authService: AuthService, + private popoverController: PopoverController, private trackingService: TrackingService, private deviceService: DeviceService, - private loginInfoService: LoginInfoService, - private router: Router, - private matSnackBar: MatSnackBar, - private snackbarPropertiesService: SnackbarPropertiesService + private loginInfoService: LoginInfoService ) {} - ngOnInit(): void { + ngOnInit() { this.fg = this.fb.group({ - password: ['', [Validators.required, this.checkPasswordValidity]], - confirmPassword: ['', [Validators.required, this.validatePasswordEquality]], + password: [ + '', + Validators.compose([ + Validators.required, + Validators.minLength(12), + Validators.maxLength(32), + Validators.pattern(/[A-Z]/), + Validators.pattern(/[a-z]/), + Validators.pattern(/[0-9]/), + Validators.pattern(/[!@#$%^&*()+\-:;<=>{}|~?]/), + ]), + ], }); + + this.lengthValidationDisplay$ = this.fg.controls.password.valueChanges.pipe( + map((password) => password && password.length >= 12 && password.length <= 32) + ); + + this.uppercaseValidationDisplay$ = this.fg.controls.password.valueChanges.pipe( + map((password) => /[A-Z]/.test(password)) + ); + + this.numberValidationDisplay$ = this.fg.controls.password.valueChanges.pipe( + map((password) => /[0-9]/.test(password)) + ); + this.specialCharValidationDisplay$ = this.fg.controls.password.valueChanges.pipe( + map((password) => /[!@#$%^&*()+\-:;<=>{}|~?]/.test(password)) + ); + + this.lowercaseValidationDisplay$ = this.fg.controls.password.valueChanges.pipe( + map((password) => /[a-z]/.test(password)) + ); } - changePassword(): void { - const refreshToken = this.activatedRoute.snapshot.params.refreshToken as string; + changePassword() { + const refreshToken = this.activatedRoute.snapshot.params.refreshToken; from(this.loaderService.showLoader()) .pipe( - switchMap(() => this.routerAuthService.resetPassword(refreshToken, this.fg.controls.password.value as string)), + switchMap(() => this.routerAuthService.resetPassword(refreshToken, this.fg.controls.password.value)), switchMap(() => this.authService.refreshEou()), tap(async (eou) => { const email = eou.us.email; @@ -76,57 +96,36 @@ export class NewPasswordPage implements OnInit { finalize(() => from(this.loaderService.hideLoader())) ) .subscribe( - () => { - const toastMessageData = { - message: 'Password changed successfully', - }; - - this.matSnackBar.openFromComponent(ToastMessageComponent, { - ...this.snackbarPropertiesService.setSnackbarProperties('success', toastMessageData), - panelClass: ['msb-success'], + async () => { + const popup = await this.popoverController.create({ + component: PopupComponent, + componentProps: { + header: 'Password changed successfully', + route: ['/', 'auth', 'switch_org'], + }, + cssClass: 'dialog-popover', }); - this.router.navigate(['/', 'auth', 'switch_org']); + + await popup.present(); }, - () => { - const toastMessageData = { - message: 'Something went wrong. Please try after some time.', - }; - - this.matSnackBar.openFromComponent(ToastMessageComponent, { - ...this.snackbarPropertiesService.setSnackbarProperties('failure', toastMessageData), - panelClass: ['msb-failure'], + async () => { + const popup = await this.popoverController.create({ + component: PopupComponent, + componentProps: { + header: 'Setting new password failed. Please try again later.', + route: ['/', 'auth', 'sign_in'], + }, + cssClass: 'dialog-popover', }); - this.router.navigate(['/', 'auth', 'sign_in']); + + await popup.present(); } ); } - async trackLoginInfo(): Promise { + async trackLoginInfo() { const deviceInfo = await this.deviceService.getDeviceInfo().toPromise(); this.trackingService.eventTrack('Added Login Info', { label: deviceInfo.appVersion }); await this.loginInfoService.addLoginInfo(deviceInfo.appVersion, new Date()); } - - onPasswordValid(isValid: boolean): void { - this.isPasswordValid = isValid; - } - - redirectToSignIn(): void { - this.router.navigate(['/', 'auth', 'sign_in']); - } - - setPasswordTooltip(value: boolean): void { - this.showPasswordTooltip = value; - } - - checkPasswordValidity = (): ValidationErrors => (this.isPasswordValid ? null : { invalidPassword: true }); - - validatePasswordEquality = (): ValidationErrors => { - if (!this.fg) { - return null; - } - const password = this.fg.controls.password.value as string; - const confirmPassword = this.fg.controls.confirmPassword.value as string; - return password === confirmPassword ? null : { passwordMismatch: true }; - }; } diff --git a/src/app/auth/pending-verification/pending-verification.page.html b/src/app/auth/pending-verification/pending-verification.page.html index 0e95303fdf..0e31f3bb36 100644 --- a/src/app/auth/pending-verification/pending-verification.page.html +++ b/src/app/auth/pending-verification/pending-verification.page.html @@ -1,83 +1,26 @@ -
-
-
-
- -
-
-
The invitation has expired
-
- Enter your registered email address to receive a new invitation and set up your account on Fyle. -
-
-
-
Registered Email
- -
- Please enter a valid email. -
-
-
-
- - Send invite - -
- - Back to Sign In -
-
-
-
-
-
- -
-
-

Invitation link sent

-

- A new invitation link has been sent to your registered email address. Check your inbox to continue setting up - your account. -

-
-
-
- -
- - Back to Sign In -
-
-
-
-
+ + + diff --git a/src/app/auth/pending-verification/pending-verification.page.spec.ts b/src/app/auth/pending-verification/pending-verification.page.spec.ts index 604f6b7f00..17271dc667 100644 --- a/src/app/auth/pending-verification/pending-verification.page.spec.ts +++ b/src/app/auth/pending-verification/pending-verification.page.spec.ts @@ -1,76 +1,48 @@ import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing'; import { IonicModule } from '@ionic/angular'; -import { ActivatedRoute, Router, RouterModule } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { PendingVerificationPage } from './pending-verification.page'; +import { PageState } from 'src/app/core/models/page-state.enum'; import { RouterAuthService } from 'src/app/core/services/router-auth.service'; import { of, throwError } from 'rxjs'; import { authResData1 } from 'src/app/core/mock-data/auth-reponse.data'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-properties.service'; -import { RouterTestingModule } from '@angular/router/testing'; -import { ToastMessageComponent } from 'src/app/shared/components/toast-message/toast-message.component'; -import { HttpErrorResponse } from '@angular/common/http'; -import { getElementRef } from 'src/app/core/dom-helpers'; describe('PendingVerificationPage', () => { let component: PendingVerificationPage; let fixture: ComponentFixture; let router: jasmine.SpyObj; - let routerAuthService: jasmine.SpyObj; - let matSnackBar: jasmine.SpyObj; - let snackbarPropertiesService: jasmine.SpyObj; let activatedRoute: jasmine.SpyObj; - let formBuilder: jasmine.SpyObj; - let fb: FormBuilder; + let routerAuthService: jasmine.SpyObj; beforeEach(waitForAsync(() => { const routerSpy = jasmine.createSpyObj('Router', ['navigate']); const routerAuthServiceSpy = jasmine.createSpyObj('RouterAuthService', ['resendVerificationLink']); - const matSnackBarSpy = jasmine.createSpyObj('MatSnackBar', ['openFromComponent']); - const snackbarPropertiesServiceSpy = jasmine.createSpyObj('SnackbarPropertiesService', ['setSnackbarProperties']); + TestBed.configureTestingModule({ declarations: [PendingVerificationPage], - imports: [IonicModule.forRoot(), RouterTestingModule, RouterModule, FormsModule, ReactiveFormsModule], + imports: [IonicModule.forRoot()], providers: [ - FormBuilder, - { - provide: RouterAuthService, - useValue: routerAuthServiceSpy, - }, - { - provide: Router, - useValue: routerSpy, - }, - { - provide: MatSnackBar, - useValue: matSnackBarSpy, - }, + { provide: Router, useValue: routerSpy }, { - provide: SnackbarPropertiesService, - useValue: snackbarPropertiesServiceSpy, + provide: ActivatedRoute, + useValue: { snapshot: { params: { orgId: 'orNVthTo2Zyo' } } }, }, { - provide: ActivatedRoute, - useValue: { snapshot: { params: { email: 'aastha.b@fyle.in' } } }, + provide: RouterAuthService, + useValue: routerAuthServiceSpy, }, ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], }).compileComponents(); fixture = TestBed.createComponent(PendingVerificationPage); component = fixture.componentInstance; router = TestBed.inject(Router) as jasmine.SpyObj; - routerAuthService = TestBed.inject(RouterAuthService) as jasmine.SpyObj; - matSnackBar = TestBed.inject(MatSnackBar) as jasmine.SpyObj; - snackbarPropertiesService = TestBed.inject(SnackbarPropertiesService) as jasmine.SpyObj; activatedRoute = TestBed.inject(ActivatedRoute) as jasmine.SpyObj; - fb = TestBed.inject(FormBuilder); - activatedRoute.snapshot.params.orgId = 'orNVthTo2Zyo'; - component.fg = fb.group({ - email: ['', Validators.compose([Validators.required, Validators.pattern('\\S+@\\S+\\.\\S{2,}')])], - }); + routerAuthService = TestBed.inject(RouterAuthService) as jasmine.SpyObj; fixture.detectChanges(); })); @@ -78,6 +50,33 @@ describe('PendingVerificationPage', () => { expect(component).toBeTruthy(); }); + it('should set hasTokenExpired to true if snapshot.params.hasTokenExpired is defined and true on ionViewWillEnter()', () => { + activatedRoute.snapshot.params.hasTokenExpired = true; + component.ionViewWillEnter(); + fixture.detectChanges(); + const pageTitle = fixture.debugElement.query(By.css('app-send-email')).nativeElement.title; + expect(component.hasTokenExpired).toBe(true); + expect(pageTitle).toEqual('Verification link expired'); + }); + + it('should set hasTokenExpired to false if snapshot.params.hasTokenExpired is defined and false on ionViewWillEnter()', () => { + activatedRoute.snapshot.params.hasTokenExpired = false; + component.ionViewWillEnter(); + fixture.detectChanges(); + const pageTitle = fixture.debugElement.query(By.css('app-send-email')).nativeElement.title; + expect(component.hasTokenExpired).toBe(false); + expect(pageTitle).toEqual('Please verify your email'); + }); + + it('should set hasTokenExpired to false and currentPageState to notSent if snapshot.params.hasTokenExpired is not defined on ionViewWillEnter()', () => { + component.ionViewWillEnter(); + fixture.detectChanges(); + const pageTitle = fixture.debugElement.query(By.css('app-send-email')).nativeElement.title; + expect(component.hasTokenExpired).toBe(false); + expect(component.currentPageState).toEqual(PageState.notSent); + expect(pageTitle).toEqual('Please verify your email'); + }); + it('resendVerificationLink(): should call routerAuthService and set PageState to success if API is successful', fakeAsync(() => { const data = { cluster_domain: authResData1.cluster_domain, @@ -87,80 +86,36 @@ describe('PendingVerificationPage', () => { tick(1000); expect(routerAuthService.resendVerificationLink).toHaveBeenCalledOnceWith('ajain@fyle.in', 'orNVthTo2Zyo'); expect(component.isLoading).toBeFalse(); + expect(component.currentPageState).toEqual(PageState.success); })); it('resendVerificationLink(): should call routerAuthService and call handleError if API is unsuccessful', fakeAsync(() => { - const error = { status: 500 } as HttpErrorResponse; + const error = new Error('An Error Occured'); routerAuthService.resendVerificationLink.and.returnValue(throwError(() => error)); spyOn(component, 'handleError'); component.resendVerificationLink('ajain@fyle.in'); tick(1000); expect(routerAuthService.resendVerificationLink).toHaveBeenCalledOnceWith('ajain@fyle.in', 'orNVthTo2Zyo'); expect(component.isLoading).toBeTrue(); + expect(component.currentPageState).not.toEqual(PageState.success); expect(component.handleError).toHaveBeenCalledOnceWith(error); })); - describe('handleError():', () => { - it('handleError(); should navigate to auth/disabled if status code is 422', () => { - const error = { - status: 422, - } as HttpErrorResponse; - component.handleError(error); - expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'auth', 'disabled']); - }); - - it('should display error message on other errors', () => { - const error = { status: 401 } as HttpErrorResponse; - const props = { - panelClass: ['msb-failure'], - }; - - matSnackBar.openFromComponent.and.callThrough(); - - component.handleError(error); - expect(matSnackBar.openFromComponent).toHaveBeenCalledOnceWith(ToastMessageComponent, { - ...props, - panelClass: ['msb-failure'], - }); - expect(snackbarPropertiesService.setSnackbarProperties).toHaveBeenCalledTimes(1); - }); - }); - - it('onGotoSignInClick(): should navigate to sign-in page', () => { - component.onGotoSignInClick(); - expect(router.navigate).toHaveBeenCalledWith(['/', 'auth', 'sign_in']); + it('handleError(); should navigate to auth/disabled if status code is 422', () => { + const error = { + status: 422, + }; + component.handleError(error); + expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'auth', 'disabled']); + expect(component.currentPageState).not.toEqual(PageState.failure); }); - describe('template', () => { - it('should render the form for entering email', () => { - component.isInvitationLinkSent = false; - fixture.detectChanges(); - - const formElement = fixture.debugElement.query(By.css('.pending-verification__form-container')); - expect(formElement).toBeTruthy(); - }); - - it('should display validation error for invalid email input', () => { - component.isInvitationLinkSent = false; - const emailControl = component.fg.controls.email; - emailControl.setValue('invalid-email'); - emailControl.markAsTouched(); - fixture.detectChanges(); - - const errorElement = getElementRef(fixture, '.pending-verification__error-message'); - expect(errorElement.nativeElement.textContent).toContain('Please enter a valid email.'); - }); - - it('should call resendVerificationLink with correct email when button is clicked', () => { - component.isInvitationLinkSent = false; - spyOn(component, 'resendVerificationLink'); - component.fg.controls.email.setValue('test@example.com'); - fixture.detectChanges(); - - const buttonElement = fixture.debugElement.query(By.css('ion-button')); - buttonElement.triggerEventHandler('click', null); - - expect(component.resendVerificationLink).toHaveBeenCalledWith('test@example.com'); - }); + it('handleError(); should set pagestate to failure if status code', () => { + const error = { + status: 422, + }; + component.handleError(error); + expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'auth', 'disabled']); + expect(component.currentPageState).not.toEqual(PageState.failure); }); }); diff --git a/src/app/auth/pending-verification/pending-verification.page.ts b/src/app/auth/pending-verification/pending-verification.page.ts index cf54a1723b..814941636a 100644 --- a/src/app/auth/pending-verification/pending-verification.page.ts +++ b/src/app/auth/pending-verification/pending-verification.page.ts @@ -1,71 +1,51 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { tap } from 'rxjs/operators'; import { RouterAuthService } from 'src/app/core/services/router-auth.service'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-properties.service'; -import { ToastMessageComponent } from 'src/app/shared/components/toast-message/toast-message.component'; -import { HttpErrorResponse } from '@angular/common/http'; +import { PageState } from 'src/app/core/models/page-state.enum'; @Component({ selector: 'app-pending-verification', templateUrl: './pending-verification.page.html', - styleUrls: ['./pending-verification.page.scss'], }) -export class PendingVerificationPage { - isLoading = false; +export class PendingVerificationPage implements OnInit { + currentPageState: PageState; - isInvitationLinkSent = false; + isLoading = false; - fg: FormGroup; + hasTokenExpired = false; constructor( - private formBuilder: FormBuilder, private routerAuthService: RouterAuthService, private router: Router, - private activatedRoute: ActivatedRoute, - private matSnackBar: MatSnackBar, - private snackbarProperties: SnackbarPropertiesService + private activatedRoute: ActivatedRoute ) {} - ionViewWillEnter(): void { - this.fg = this.formBuilder.group({ - email: ['', Validators.compose([Validators.required, Validators.pattern('\\S+@\\S+\\.\\S{2,}')])], - }); + ngOnInit() {} + + ionViewWillEnter() { + this.hasTokenExpired = this.activatedRoute.snapshot.params.hasTokenExpired || false; + this.currentPageState = PageState.notSent; } - resendVerificationLink(email: string): void { + resendVerificationLink(email: string) { this.isLoading = true; - const orgId = this.activatedRoute.snapshot.params.orgId as string; + const orgId = this.activatedRoute.snapshot.params.orgId; this.routerAuthService .resendVerificationLink(email, orgId) .pipe(tap(() => (this.isLoading = false))) .subscribe({ - next: () => { - this.isInvitationLinkSent = true; - }, - error: (err: HttpErrorResponse) => this.handleError(err), + next: () => (this.currentPageState = PageState.success), + error: (err) => this.handleError(err), }); } - handleError(err: HttpErrorResponse): void { + handleError(err: any) { if (err.status === 422) { this.router.navigate(['/', 'auth', 'disabled']); } else { - const toastMessageData = { - message: 'Something went wrong. Please try after some time.', - }; - - this.matSnackBar.openFromComponent(ToastMessageComponent, { - ...this.snackbarProperties.setSnackbarProperties('failure', toastMessageData), - panelClass: ['msb-failure'], - }); + this.currentPageState = PageState.failure; } } - - onGotoSignInClick(): void { - this.router.navigate(['/', 'auth', 'sign_in']); - } } diff --git a/src/app/auth/reset-password/reset-password.page.html b/src/app/auth/reset-password/reset-password.page.html index 2f39f7475b..e7ff210805 100644 --- a/src/app/auth/reset-password/reset-password.page.html +++ b/src/app/auth/reset-password/reset-password.page.html @@ -1,88 +1,11 @@ -
-
-
- -
-
Forgot Password
-
- Please enter your registered email address to receive instructions for resetting your password -
-
-
-
Registered Email
- -
- Please enter a valid email. -
-
-
-
- - Request reset link - -
-
-
-
-
- -
-
-

Check your email

-

- We've sent password recovery instructions to - {{resetEmail}} -

-
-
- Didn’t receive the email? - Resend - -
-
-
- -
- - Back to Sign In -
-
-
-
-
+ diff --git a/src/app/auth/reset-password/reset-password.page.scss b/src/app/auth/reset-password/reset-password.page.scss deleted file mode 100644 index 7150ac22c6..0000000000 --- a/src/app/auth/reset-password/reset-password.page.scss +++ /dev/null @@ -1,170 +0,0 @@ -@import '../../../theme/colors.scss'; - -.forgot-password { - height: 100%; - margin-top: 18px; - - &__form-container { - display: flex; - flex-direction: column; - padding: 24px 20px; - justify-content: space-between; - height: 100%; - } - - &__page-header { - font-size: 20px; - margin: 0 0 10px; - position: relative; - margin-bottom: 24px; - } - - &__disabled { - opacity: 0.2; - } - - &__sub-header { - font-size: 14px; - margin: 0 0 10px; - position: relative; - margin-bottom: 24px; - color: $black-light; - } - - &__error-message { - color: $red; - } - - &__input-container { - &__label { - margin: 0 8px 0 0; - font-size: 12px; - color: $black-light; - line-height: 1.3; - font-weight: 400; - } - - &__input { - border: 0; - font-size: 14px; - font-weight: 400; - line-height: 1.3; - color: $blue-black; - width: 100%; - padding: 6px 0; - border-bottom: 1px solid $grey-lighter; - } - - &__input:focus { - border-bottom: 1px solid $blue-black; - } - } - - &__content-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - flex-grow: 2; - } - - &__arrow-icon { - fill: $pure-white; - margin-right: 8px; - } - - &__cta-text { - font-size: 14px; - font-weight: 500; - } - - &__cta { - margin: 0 20px 24px 20px; - } - - &__cta-content { - display: flex; - align-items: center; - justify-content: center; - } - - &__success-message { - height: 100%; - display: flex; - flex-direction: column; - } - - &__success-icon-container { - width: 60px; - height: 60px; - border-radius: 8px; - background: $success-bg; - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 16px; - } - - &__success-icon { - width: 45px; - height: 45px; - fill: $green; - } - - &__text { - display: flex; - align-items: center; - gap: 8px; - flex-direction: column; - } - - &__header { - color: $black; - font-size: 20px; - font-style: normal; - font-weight: 500; - line-height: normal; - height: 26px; - margin: 0; - } - - &__resend-text { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - color: $black-light; - font-weight: 500; - gap: 2px; - - &__resend-link { - color: $brand-primary; - } - - &__spinner-icon { - color: $brand-primary; - height: 12px; - } - } - - &__content { - color: $black-light; - text-align: center; - font-size: 14px; - font-style: normal; - font-weight: 400; - line-height: 1.25; - height: 36px; - width: 274px; - - &__reset-email { - font-weight: 500; - } - } - - &__back-icon { - margin-bottom: 16px; - width: 28px; - height: 28px; - } -} diff --git a/src/app/auth/reset-password/reset-password.page.spec.ts b/src/app/auth/reset-password/reset-password.page.spec.ts index 547f76e434..55a30ea298 100644 --- a/src/app/auth/reset-password/reset-password.page.spec.ts +++ b/src/app/auth/reset-password/reset-password.page.spec.ts @@ -1,40 +1,54 @@ -import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; -import { ResetPasswordPage } from './reset-password.page'; -import { ReactiveFormsModule, FormsModule, FormBuilder, Validators, FormGroup } from '@angular/forms'; -import { By } from '@angular/platform-browser'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { IonicModule } from '@ionic/angular'; +import { ResetPasswordPage } from './reset-password.page'; import { RouterAuthService } from 'src/app/core/services/router-auth.service'; -import { ActivatedRoute, Router, RouterModule } from '@angular/router'; +import { Router, RouterModule } from '@angular/router'; +import { Location } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-properties.service'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { PageState } from 'src/app/core/models/page-state.enum'; -import { getElementRef } from 'src/app/core/dom-helpers'; -import { DebugElement } from '@angular/core'; import { of, throwError } from 'rxjs'; -import { ToastMessageComponent } from 'src/app/shared/components/toast-message/toast-message.component'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; describe('ResetPasswordPage', () => { let component: ResetPasswordPage; let fixture: ComponentFixture; let router: jasmine.SpyObj; let routerAuthService: jasmine.SpyObj; - let matSnackBar: jasmine.SpyObj; - let snackbarPropertiesService: jasmine.SpyObj; - let activatedRoute: jasmine.SpyObj; - let formBuilder: jasmine.SpyObj; - let fb: FormBuilder; + let location: jasmine.SpyObj; + + @Component({ + selector: 'app-send-email', + template: '
', + }) + class MockSendEmailComponent { + @Input() title: string; + + @Input() content: string; + + @Input() subcontent: string; + + @Input() ctaText: string; + + @Input() successTitle: string; + + @Input() successContent: string; + + @Input() sendEmailPageState: PageState; + + @Input() isLoading: boolean; + + @Output() sendEmail = new EventEmitter(); + } beforeEach(waitForAsync(() => { const routerSpy = jasmine.createSpyObj('Router', ['navigate']); + const locationSpy = jasmine.createSpyObj('Location', ['path']); const routerAuthServiceSpy = jasmine.createSpyObj('RouterAuthService', ['sendResetPassword']); - const matSnackBarSpy = jasmine.createSpyObj('MatSnackBar', ['openFromComponent']); - const snackbarPropertiesServiceSpy = jasmine.createSpyObj('SnackbarPropertiesService', ['setSnackbarProperties']); TestBed.configureTestingModule({ - declarations: [ResetPasswordPage], + declarations: [ResetPasswordPage, MockSendEmailComponent], imports: [IonicModule.forRoot(), RouterTestingModule, RouterModule, FormsModule, ReactiveFormsModule], providers: [ - FormBuilder, { provide: RouterAuthService, useValue: routerAuthServiceSpy, @@ -44,16 +58,8 @@ describe('ResetPasswordPage', () => { useValue: routerSpy, }, { - provide: MatSnackBar, - useValue: matSnackBarSpy, - }, - { - provide: SnackbarPropertiesService, - useValue: snackbarPropertiesServiceSpy, - }, - { - provide: ActivatedRoute, - useValue: { snapshot: { params: { email: 'aastha.b@fyle.in' } } }, + provide: Location, + useValue: locationSpy, }, ], }).compileComponents(); @@ -61,18 +67,12 @@ describe('ResetPasswordPage', () => { fixture = TestBed.createComponent(ResetPasswordPage); component = fixture.componentInstance; router = TestBed.inject(Router) as jasmine.SpyObj; + location = TestBed.inject(Location) as jasmine.SpyObj; routerAuthService = TestBed.inject(RouterAuthService) as jasmine.SpyObj; - matSnackBar = TestBed.inject(MatSnackBar) as jasmine.SpyObj; - snackbarPropertiesService = TestBed.inject(SnackbarPropertiesService) as jasmine.SpyObj; - activatedRoute = TestBed.inject(ActivatedRoute) as jasmine.SpyObj; - fb = TestBed.inject(FormBuilder); - component.fg = fb.group({ - email: [Validators.compose([Validators.required, Validators.pattern('\\S+@\\S+\\.\\S{2,}')])], - }); fixture.detectChanges(); })); - it('should create the component', () => { + it('should create', () => { expect(component).toBeTruthy(); }); @@ -81,86 +81,13 @@ describe('ResetPasswordPage', () => { expect(component.currentPageState).toEqual(PageState.notSent); }); - describe('template', () => { - it('should render the form in "notSent" state', () => { - component.currentPageState = component.PageState.notSent; - fixture.detectChanges(); - - const formElement = fixture.debugElement.query(By.css('.forgot-password__form-container')); - expect(formElement).toBeTruthy(); - }); - - it('should render the success message in "success" state', () => { - component.currentPageState = component.PageState.success; - component.resetEmail = 'test@example.com'; - fixture.detectChanges(); - - const successMessageElement = fixture.debugElement.query(By.css('.forgot-password__success-message')); - expect(successMessageElement).toBeTruthy(); - - const emailElement = fixture.debugElement.query(By.css('.forgot-password__content__reset-email')); - expect(emailElement.nativeElement.textContent).toContain('test@example.com'); - }); - - it('should display validation error for invalid email input', () => { - component.currentPageState = PageState.notSent; - const emailControl = component.fg.controls.email; - emailControl.setValue('invalid-email'); - emailControl.markAsTouched(); - fixture.detectChanges(); - - const errorElement = getElementRef(fixture, '.forgot-password__error-message'); - expect(errorElement.nativeElement.textContent).toContain('Please enter a valid email.'); - }); - - it('should call sendResetLink with correct email when button is clicked', () => { - component.currentPageState = PageState.notSent; - spyOn(component, 'sendResetLink'); - component.fg.controls.email.setValue('test@example.com'); - fixture.detectChanges(); - - const buttonElement = fixture.debugElement.query(By.css('ion-button')); - buttonElement.triggerEventHandler('click', null); - - expect(component.sendResetLink).toHaveBeenCalledWith('test@example.com'); - }); - - it('should display resend link if email is not sent', () => { - component.isEmailSentOnce = false; - component.isLoading = false; - component.currentPageState = component.PageState.success; - component.resetEmail = 'test@example.com'; - fixture.detectChanges(); - - const resendLink = fixture.debugElement.query(By.css('.forgot-password__resend-text__resend-link')); - expect(resendLink).toBeTruthy(); - }); - - it('should hide resend link and show spinner when loading', () => { - component.isEmailSentOnce = false; - component.isLoading = true; - component.currentPageState = component.PageState.success; - fixture.detectChanges(); - - const resendLink = getElementRef(fixture, '.forgot-password__resend-text__resend-link') as DebugElement; - expect(resendLink).toBeFalsy(); - - const spinner = getElementRef(fixture, 'ion-spinner') as DebugElement; - expect(spinner).toBeTruthy(); - }); - }); - describe('sendResetLink():', () => { - beforeEach(() => { - component.currentPageState = PageState.success; - }); - it('should send reset password link, change loading and page state', () => { routerAuthService.sendResetPassword.and.returnValue(of({})); const email = 'jay.b@fyle.in'; component.sendResetLink(email); - expect(component.isLoading).toBeFalse(); + expect(component.isLoading).toEqual(false); expect(component.currentPageState).toEqual(PageState.success); }); @@ -170,38 +97,28 @@ describe('ResetPasswordPage', () => { const email = 'jay.b@fyle.in'; component.sendResetLink(email); - expect(component.isLoading).toBeTrue(); + expect(component.isLoading).toEqual(true); expect(component.handleError).toHaveBeenCalledTimes(1); }); }); - describe('handleError(): ', () => { - it('should navigate to disabled page on 422 error', () => { - const error = { status: 422 }; - component.handleError(error); + describe('handleError():', () => { + it('should navigate to disabled auth', () => { + component.handleError({ + status: 422, + message: 'Error message', + }); - expect(router.navigate).toHaveBeenCalledWith(['/', 'auth', 'disabled']); + expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'auth', 'disabled']); }); - it('should display error message on other errors', () => { - const error = { status: 401 }; - const props = { - panelClass: ['msb-failure'], - }; - - matSnackBar.openFromComponent.and.callThrough(); - - component.handleError(error); - expect(matSnackBar.openFromComponent).toHaveBeenCalledOnceWith(ToastMessageComponent, { - ...props, - panelClass: ['msb-failure'], + it('should change page state if auth not disabled', () => { + component.handleError({ + status: 400, + message: 'Error message', }); - expect(snackbarPropertiesService.setSnackbarProperties).toHaveBeenCalledTimes(1); - }); - }); - it('onGotoSignInClick(): should navigate to sign-in page', () => { - component.onGotoSignInClick(); - expect(router.navigate).toHaveBeenCalledWith(['/', 'auth', 'sign_in']); + expect(component.currentPageState).toEqual(PageState.failure); + }); }); }); diff --git a/src/app/auth/reset-password/reset-password.page.ts b/src/app/auth/reset-password/reset-password.page.ts index 9207249fa4..09d288f93d 100644 --- a/src/app/auth/reset-password/reset-password.page.ts +++ b/src/app/auth/reset-password/reset-password.page.ts @@ -1,81 +1,43 @@ -import { Component } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; import { tap } from 'rxjs/operators'; import { RouterAuthService } from 'src/app/core/services/router-auth.service'; import { PageState } from 'src/app/core/models/page-state.enum'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { MatSnackBar } from '@angular/material/snack-bar'; -import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-properties.service'; -import { ToastMessageComponent } from 'src/app/shared/components/toast-message/toast-message.component'; @Component({ selector: 'app-reset-password', templateUrl: './reset-password.page.html', - styleUrls: ['./reset-password.page.scss'], }) -export class ResetPasswordPage { +export class ResetPasswordPage implements OnInit { currentPageState: PageState; isLoading = false; - fg: FormGroup; + constructor(private routerAuthService: RouterAuthService, private router: Router) {} - resetEmail: string; - - isEmailSentOnce: boolean; - - PageState: typeof PageState = PageState; - - constructor( - private formBuilder: FormBuilder, - private routerAuthService: RouterAuthService, - private router: Router, - private activatedRoute: ActivatedRoute, - private matSnackBar: MatSnackBar, - private snackbarProperties: SnackbarPropertiesService - ) {} - - ionViewWillEnter(): void { + ionViewWillEnter() { this.currentPageState = PageState.notSent; - const email = (this.activatedRoute.snapshot.params.email as string) || ''; - this.fg = this.formBuilder.group({ - email: [email, Validators.compose([Validators.required, Validators.pattern('\\S+@\\S+\\.\\S{2,}')])], - }); } - sendResetLink(email: string): void { - this.isLoading = true; - this.resetEmail = email; + ngOnInit() {} - if (this.currentPageState === PageState.success) { - this.isEmailSentOnce = true; - } + sendResetLink(email: string) { + this.isLoading = true; this.routerAuthService .sendResetPassword(email) .pipe(tap(() => (this.isLoading = false))) .subscribe({ next: () => (this.currentPageState = PageState.success), - error: (err: { status: number }) => this.handleError(err), + error: (err) => this.handleError(err), }); } - handleError(err: { status: number }): void { + handleError(err) { if (err.status === 422) { this.router.navigate(['/', 'auth', 'disabled']); } else { - const toastMessageData = { - message: 'Something went wrong. Please try after some time.', - }; - - this.matSnackBar.openFromComponent(ToastMessageComponent, { - ...this.snackbarProperties.setSnackbarProperties('failure', toastMessageData), - panelClass: ['msb-failure'], - }); + this.currentPageState = PageState.failure; } } - - onGotoSignInClick(): void { - this.router.navigate(['/', 'auth', 'sign_in']); - } } diff --git a/src/app/auth/verify/verify.page.html b/src/app/auth/verify/verify.page.html index a1fe1eb9f8..39f8fd5fa6 100644 --- a/src/app/auth/verify/verify.page.html +++ b/src/app/auth/verify/verify.page.html @@ -1,8 +1,21 @@ -
-
- + + + + + + + +
+
+ Verifying Identity + Verification Failed +
+
+ Checking your credentials.. +
+
+ Unable to verify your Fyle account. Please contact support by sending an email to support@fylehq.com
-
Accepting the invite...
diff --git a/src/app/auth/verify/verify.page.scss b/src/app/auth/verify/verify.page.scss index 6a21b379b0..568f784d4e 100644 --- a/src/app/auth/verify/verify.page.scss +++ b/src/app/auth/verify/verify.page.scss @@ -1,37 +1,83 @@ -@import '../../../theme/colors'; - -@mixin loader { - border-radius: 50%; - width: 14px; - height: 14px; - margin: 0 4px; - animation: spin 2s linear infinite; -} +$verify-header: #220033; +$form-header: #000; +$form-subheader: #4a4a4a; +$password-icon: #b9beba; +$secondary-cta-border: #e0e0e0; -@keyframes spin { - 0% { - transform: rotate(0deg); +.verify { + &--header { + min-height: 180px; } - 100% { - transform: rotate(360deg); + + &--header-container { + min-height: 180px; + background-color: $verify-header; } -} -.verify { - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - - &__verifying-loader { - @include loader; - &__icon { - fill: $brand-primary; + &--header-logo-container { + text-align: center; + } + + &--header-logo { + max-width: 100px; + } + + &--form { + padding: 24px; + } + + &--form-field { + width: 100%; + } + + &--form-header { + font-size: 24px; + margin-top: 16px; + margin-bottom: 8px; + color: $form-header; + font-weight: 700; + } + + &--form-subheader { + font-size: 16px; + color: $form-subheader; + margin-top: 24px; + margin-bottom: 24px; + } + + &--primary-cta { + .mat-button-base { + width: 100%; + font-weight: 700; + min-height: 47px; } - &__text { - margin-top: 8px; - color: $black; + } + + &--password-visibility-icon { + color: $password-icon; + } + + &--secondary-cta { + .mat-button-base { + width: 100%; + font-weight: 700; + min-height: 47px; + letter-spacing: 1.6px; + border: 1px solid $secondary-cta-border; + } + margin-top: 24px; + } + + &--redirect { + margin: 16px; + text-align: center; + font-size: 16px; + a { + text-decoration: none; } } + + &--edit-email { + text-align: end; + } } diff --git a/src/app/auth/verify/verify.page.spec.ts b/src/app/auth/verify/verify.page.spec.ts index eb25951295..b481a4cac1 100644 --- a/src/app/auth/verify/verify.page.spec.ts +++ b/src/app/auth/verify/verify.page.spec.ts @@ -78,6 +78,25 @@ describe('VerifyPage', () => { expect(trackingService.emailVerified).toHaveBeenCalledTimes(1); expect(trackingService.onSignin).toHaveBeenCalledOnceWith('ajain@fyle.in'); expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'auth', 'switch_org', { invite_link: true }]); + const verifyHeader = getElementBySelector(fixture, '#verify--header'); + const verifySubheader = getElementBySelector(fixture, '.verify--form-subheader'); + expect(getTextContent(verifyHeader)).toContain('Verifying Identity'); + expect(getTextContent(verifySubheader)).toContain('Checking your credentials..'); + }); + + it('should handle error when verification fails', () => { + const mockError = new Error('Verification failed'); + routerAuthService.emailVerify.and.returnValue(throwError(() => mockError)); + fixture.detectChanges(); + spyOn(component, 'handleError'); + component.ngOnInit(); + expect(component.handleError).toHaveBeenCalledOnceWith(mockError); + const verifyHeader = getElementBySelector(fixture, '#verify--header'); + const verifySubheader = getElementBySelector(fixture, '.verify--form-subheader'); + expect(getTextContent(verifyHeader)).toContain('Verification Failed'); + expect(getTextContent(verifySubheader)).toContain( + 'Unable to verify your Fyle account. Please contact support by sending an email to support@fylehq.com' + ); }); }); @@ -88,6 +107,7 @@ describe('VerifyPage', () => { }; component.handleError(error); expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'auth', 'disabled']); + expect(component.currentPageState).not.toEqual(VerifyPageState.error); }); it('should navigate to auth/pending_verification if status code is 440', () => { @@ -101,6 +121,7 @@ describe('VerifyPage', () => { 'pending_verification', { hasTokenExpired: true, orgId: 'orNVthTo2Zyo' }, ]); + expect(component.currentPageState).not.toEqual(VerifyPageState.error); }); it('should change the page status if error code is something else', () => { @@ -108,12 +129,7 @@ describe('VerifyPage', () => { status: 404, }; component.handleError(error); - expect(router.navigate).toHaveBeenCalledOnceWith([ - '/', - 'auth', - 'pending_verification', - { orgId: 'orNVthTo2Zyo' }, - ]); + expect(component.currentPageState).toEqual(VerifyPageState.error); }); }); }); diff --git a/src/app/auth/verify/verify.page.ts b/src/app/auth/verify/verify.page.ts index 1aebefd808..0a583c5fc4 100644 --- a/src/app/auth/verify/verify.page.ts +++ b/src/app/auth/verify/verify.page.ts @@ -4,6 +4,7 @@ import { RouterAuthService } from 'src/app/core/services/router-auth.service'; import { switchMap, tap } from 'rxjs/operators'; import { AuthService } from 'src/app/core/services/auth.service'; import { TrackingService } from '../../core/services/tracking.service'; +import { VerifyPageState } from './verify.enum'; @Component({ selector: 'app-verify', @@ -11,6 +12,8 @@ import { TrackingService } from '../../core/services/tracking.service'; styleUrls: ['./verify.page.scss'], }) export class VerifyPage implements OnInit { + currentPageState: VerifyPageState = VerifyPageState.verifying; + constructor( private activatedRoute: ActivatedRoute, private routerAuthService: RouterAuthService, @@ -19,6 +22,10 @@ export class VerifyPage implements OnInit { private trackingService: TrackingService ) {} + get PageStates(): typeof VerifyPageState { + return VerifyPageState; + } + ngOnInit(): void { const verificationCode = this.activatedRoute.snapshot.params.verification_code as string; this.routerAuthService @@ -32,18 +39,21 @@ export class VerifyPage implements OnInit { ) .subscribe({ next: () => this.router.navigate(['/', 'auth', 'switch_org', { invite_link: true }]), - error: (err: { status: number }) => this.handleError(err), + error: (err) => this.handleError(err), }); } - handleError(err: { status: number }): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + handleError(err: any): void { const orgId = this.activatedRoute.snapshot.params.org_id as string; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (err.status === 422) { this.router.navigate(['/', 'auth', 'disabled']); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access } else if (err.status === 440) { this.router.navigate(['/', 'auth', 'pending_verification', { hasTokenExpired: true, orgId }]); } else { - this.router.navigate(['/', 'auth', 'pending_verification', { orgId }]); + this.currentPageState = VerifyPageState.error; } } } diff --git a/src/app/post-verification/invited-user/invited-user.module.ts b/src/app/post-verification/invited-user/invited-user.module.ts index f735bc997e..fda77428df 100644 --- a/src/app/post-verification/invited-user/invited-user.module.ts +++ b/src/app/post-verification/invited-user/invited-user.module.ts @@ -15,7 +15,6 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { SharedModule } from 'src/app/shared/shared.module'; @NgModule({ imports: [ @@ -29,7 +28,6 @@ import { SharedModule } from 'src/app/shared/shared.module'; MatFormFieldModule, MatInputModule, MatSnackBarModule, - SharedModule, ], declarations: [InvitedUserPage], }) diff --git a/src/app/post-verification/invited-user/invited-user.page.html b/src/app/post-verification/invited-user/invited-user.page.html index 08be75da4a..3a571e9138 100644 --- a/src/app/post-verification/invited-user/invited-user.page.html +++ b/src/app/post-verification/invited-user/invited-user.page.html @@ -1,139 +1,140 @@ -
-
- + +
+
+
Setup your account?
+
Set a password and give your account a name
+
-
Join "{{ orgName }}" on Fyle
-
-
-
-
-
Full Name
- -
-
- Please enter your name -
-
-
-
-
Password
-
- -
- -
-
- -
-
- Password cannot be empty -
-
- Please enter a valid password. -
+ +
+ Full Name +
+ + + +
+ Password +
+ + +
+ {{hide ? 'Hide' : 'Show'}}
-
-
-
Confirm Password
-
- -
- -
-
-
-
- Password cannot be empty -
-
- Passwords do not match -
-
- +
+ +
+
+
+ + + + + Is between 12 to 32 characters +
+
+ + + + + Contains atleast 1 uppercase character +
+
+ + + + + Contains atleast 1 lowercase character +
+
+ + + + + Contains atleast 1 number +
+
+ + + + + Contains atleast 1 special character
+
+ +
-
- - Join - -
-
-
- offline-zer-state -
Looks like you are offline
-
- You need to be connected to Internet to be able to login to the app. Please check if your Internet connection is - working. +
+ offline-zer-state +
Looks like you are offline
+
+ You need to be connected to Internet to be able to login to the app. Please check if your Internet connection is + working. +
-
+ diff --git a/src/app/post-verification/invited-user/invited-user.page.scss b/src/app/post-verification/invited-user/invited-user.page.scss index bcbbafba6f..e398d7882d 100644 --- a/src/app/post-verification/invited-user/invited-user.page.scss +++ b/src/app/post-verification/invited-user/invited-user.page.scss @@ -1,114 +1,107 @@ -@import '../../../theme/colors.scss'; +$offline-title: #4a4a4a; +$offline-sub-title: #ababab; +$name: #aaa; +$header-text: #aaa; .invited-user { - display: flex; - flex-direction: column; - padding: 24px 20px; - height: 100%; - justify-content: space-between; - margin-top: 18px; - - &__password-container { - display: flex; - } - - &__input-container { - margin-bottom: 24px; + padding: 36px; + &--full-name { + font-size: 14px; + color: $header-text; + line-height: 1.5; + margin: 0 0 24px 20px; } - &__error { - color: $red; - font-size: 12px; + &--form-header-container { + margin-left: 20px; } - &__text { - border-bottom: 1px solid $grey-lighter; - - &__invalid { - border-bottom: 1px solid $red; + &--form-header { + font-size: 18px; + margin: 0 0 10px; + position: relative; + &::before { + color: #f90; + content: '∙'; + position: absolute; + font-size: 90px; + left: -48px; + top: -4px; } } - &__mandatory { - color: $brand-primary; - display: inline-block; - font-size: 14px; - font-weight: 400; - } - - &__text-label { - margin: 0 8px 0 0; - max-width: 90%; - min-width: 120px; - font-size: 12px; - color: $black-light; - line-height: 16px; - white-space: nowrap; - font-weight: 400; + &--form-sub-header { + color: $header-text; + margin: 0 0 10px; } - &__text-input { - border: 0; + &--form-label { + margin-top: 14px; + margin-left: 20px; + display: block; + margin-bottom: 8px; font-size: 14px; - font-weight: 400; - height: 18px; - line-height: 18px; - color: $blue-black; width: 100%; - margin: 6px 0; - padding: 0; + color: $header-text; + &::after { + content: '*'; + position: relative; + left: -4px; + } + &__invalid { + color: red; + } } - &__password-icon { - width: 20px; - height: 20px; - fill: $black-light; + &--form-field { + width: 100%; } - &__password-icon-container { - display: flex; - align-items: center; + &--password-visibility { + color: $header-text; + position: relative; + top: -7px; } - &__form-header { - font-size: 20px; - margin: 0 0 10px; - position: relative; - margin-bottom: 24px; - color: $black; + &--password-rules { + padding: 14px 0; } - &__save { + &--save { width: 100%; &__disabled { opacity: 0.2; } } - &__back-icon { - margin-bottom: 20px; - width: 28px; - height: 28px; + &--validation { + &__correct { + color: green; + } + + &__incorrect { + color: red; + } } } .invited-user-offline { margin-top: 75px; padding: 15px; - &__image { + &--image { max-width: 125px; } - &__title { + &--title { font-size: 16px; display: block; margin-bottom: 16px; - color: $grey-light-2; + color: $offline-title; line-height: 1.5; font-weight: 500; margin-bottom: 16px; } - &__sub-title { - color: $gray-3; + &--sub-title { + color: $offline-sub-title; display: block; font-size: 16px; font-weight: 300; diff --git a/src/app/post-verification/invited-user/invited-user.page.spec.ts b/src/app/post-verification/invited-user/invited-user.page.spec.ts index 927d44ff4f..eaa35e9ea9 100644 --- a/src/app/post-verification/invited-user/invited-user.page.spec.ts +++ b/src/app/post-verification/invited-user/invited-user.page.spec.ts @@ -6,7 +6,7 @@ import { ToastController } from '@ionic/angular'; import { OrgUserService } from 'src/app/core/services/org-user.service'; import { LoaderService } from 'src/app/core/services/loader.service'; import { AuthService } from 'src/app/core/services/auth.service'; -import { Router, UrlSerializer } from '@angular/router'; +import { Router } from '@angular/router'; import { TrackingService } from '../../core/services/tracking.service'; import { MatSnackBar } from '@angular/material/snack-bar'; import { SnackbarPropertiesService } from 'src/app/core/services/snackbar-properties.service'; @@ -23,7 +23,6 @@ import { cloneDeep } from 'lodash'; import { eouRes3 } from 'src/app/core/mock-data/extended-org-user.data'; import { OrgService } from 'src/app/core/services/org.service'; import { ToastMessageComponent } from 'src/app/shared/components/toast-message/toast-message.component'; -import { RouterTestingModule } from '@angular/router/testing'; describe('InvitedUserPage', () => { let component: InvitedUserPage; @@ -54,10 +53,9 @@ describe('InvitedUserPage', () => { const snackbarPropertiesSpy = jasmine.createSpyObj('SnackbarPropertiesService', ['setSnackbarProperties']); TestBed.configureTestingModule({ declarations: [InvitedUserPage], - imports: [IonicModule.forRoot(), MatIconTestingModule, RouterTestingModule], + imports: [IonicModule.forRoot(), MatIconTestingModule], providers: [ FormBuilder, - UrlSerializer, { provide: NetworkService, useValue: networkServiceSpy }, { provide: ToastController, useValue: toastController }, { provide: OrgUserService, useValue: orgUserServiceSpy }, @@ -84,7 +82,7 @@ describe('InvitedUserPage', () => { networkService.connectivityWatcher.and.returnValue(new EventEmitter()); networkService.isOnline.and.returnValue(of(true)); - authService.getEou.and.resolveTo(currentEouRes); + authService.getEou.and.returnValue(Promise.resolve(currentEouRes)); fixture = TestBed.createComponent(InvitedUserPage); component = fixture.componentInstance; component.isConnected$ = of(true); @@ -95,77 +93,129 @@ describe('InvitedUserPage', () => { expect(component).toBeTruthy(); }); - it('ngOnInit(): should set the fullname value from eou$ and setup network watcher', fakeAsync(() => { - networkService.isOnline.and.returnValue(of(true)); - const eventEmitterMock = new EventEmitter(); - networkService.connectivityWatcher.and.returnValue(eventEmitterMock); - component.eou$ = of(currentEouRes); - component.fg.controls.fullName.setValue('Abhishek Jain'); - component.ngOnInit(); - tick(500); - component.isConnected$.pipe(take(1)).subscribe((connectionStatus) => { - expect(connectionStatus).toBeTrue(); - }); - expect(component.fg.controls.fullName.value).toEqual(currentEouRes.us.full_name); - })); - - describe('checkPasswordValidity():', () => { - it('should return null when isPasswordValid is true', () => { - component.isPasswordValid = true; - - const result = component.checkPasswordValidity(); + describe('ngOnInit():', () => { + it('should set the fullname value from eou$ and setup network watcher', fakeAsync(() => { + networkService.isOnline.and.returnValue(of(true)); + const eventEmitterMock = new EventEmitter(); + networkService.connectivityWatcher.and.returnValue(eventEmitterMock); + component.eou$ = of(currentEouRes); + component.fg.controls.fullName.setValue('Abhishek Jain'); + component.ngOnInit(); + tick(500); + component.isConnected$.pipe(take(1)).subscribe((connectionStatus) => { + expect(connectionStatus).toEqual(true); + }); + expect(component.fg.controls.fullName.value).toEqual(currentEouRes.us.full_name); + })); - expect(result).toBeNull(); // No errors + it('should emit false value when there are less than 12 chars', (done) => { + const testCase = { input: 'qwert', expectedOutput: false }; + component.lengthValidationDisplay$.pipe(take(1)).subscribe((value) => { + expect(value).toEqual(testCase.expectedOutput); + done(); + }); + component.fg.controls.password.patchValue(testCase.input); }); - it('should return an error object when isPasswordValid is false', () => { - component.isPasswordValid = false; - - const result = component.checkPasswordValidity(); - - expect(result).toEqual({ invalidPassword: true }); // Error object + it('should emit false value when there are greater than 32 chars', (done) => { + const testCase = { input: 'Thisisaveryverylong12345@password', expectedOutput: false }; + component.lengthValidationDisplay$.pipe(take(1)).subscribe((value) => { + expect(value).toEqual(testCase.expectedOutput); + done(); + }); + component.fg.controls.password.patchValue(testCase.input); }); - }); - describe('validatePasswordEquality():', () => { - it('should return null when password and confirmPassword match', () => { - component.fg.controls.password.setValue('StrongPassword@123'); - component.fg.controls.confirmPassword.setValue('StrongPassword@123'); + it('should emit true value when there are between 12 and 32 chars', (done) => { + const testCase = { input: 'John_doe123@fyle', expectedOutput: true }; + component.lengthValidationDisplay$.pipe(take(1)).subscribe((value) => { + expect(value).toEqual(testCase.expectedOutput); + done(); + }); + component.fg.controls.password.patchValue(testCase.input); + }); - const result = component.validatePasswordEquality(); + it('should emit false value when there are no special chars', (done) => { + const testCase = { input: 'qwert', expectedOutput: false }; + component.specialCharValidationDisplay$.pipe(take(1)).subscribe((value) => { + expect(value).toEqual(testCase.expectedOutput); + done(); + }); + component.fg.controls.password.patchValue(testCase.input); + }); - expect(result).toBeNull(); // No errors + it('should emit true value when there are special chars', (done) => { + const testCase = { input: 'John_doe123@fyle', expectedOutput: true }; + component.specialCharValidationDisplay$.pipe(take(1)).subscribe((value) => { + expect(value).toEqual(testCase.expectedOutput); + done(); + }); + component.fg.controls.password.patchValue(testCase.input); }); - it('should return an error object when password and confirmPassword do not match', () => { - component.fg.controls.password.setValue('StrongPassword@123'); - component.fg.controls.confirmPassword.setValue('DifferentPassword@123'); + it('should emit false value when there are no numbers', (done) => { + const testCase = { input: 'qwert', expectedOutput: false }; + component.numberValidationDisplay$.pipe(take(1)).subscribe((value) => { + expect(value).toEqual(testCase.expectedOutput); + done(); + }); + component.fg.controls.password.patchValue(testCase.input); + }); - const result = component.validatePasswordEquality(); + it('should emit true value when there are numbers', (done) => { + const testCase = { input: 'John_doe123@fyle', expectedOutput: true }; + component.numberValidationDisplay$.pipe(take(1)).subscribe((value) => { + expect(value).toEqual(testCase.expectedOutput); + done(); + }); + component.fg.controls.password.patchValue(testCase.input); + }); - expect(result).toEqual({ passwordMismatch: true }); + it('should emit false value when there are no upper case letters', (done) => { + const testCase = { input: 'qwert', expectedOutput: false }; + component.uppercaseValidationDisplay$.pipe(take(1)).subscribe((value) => { + expect(value).toEqual(testCase.expectedOutput); + done(); + }); + component.fg.controls.password.patchValue(testCase.input); }); - it('should return null when password or confirmPassword is empty', () => { - component.fg.controls.password.setValue(''); - component.fg.controls.confirmPassword.setValue(''); + it('should emit true value when there are upper case letters', (done) => { + const testCase = { input: 'John_doe123@fyle', expectedOutput: true }; + component.uppercaseValidationDisplay$.pipe(take(1)).subscribe((value) => { + expect(value).toEqual(testCase.expectedOutput); + done(); + }); + component.fg.controls.password.patchValue(testCase.input); + }); - const result = component.validatePasswordEquality(); + it('should emit false value when there are no lower case letters', (done) => { + const testCase = { input: 'QWERT', expectedOutput: false }; + component.lowercaseValidationDisplay$.pipe(take(1)).subscribe((value) => { + expect(value).toEqual(testCase.expectedOutput); + done(); + }); + component.fg.controls.password.patchValue(testCase.input); + }); - expect(result).toBeNull(); + it('should emit true value when there are lower case letters', (done) => { + const testCase = { input: 'John_doe123@fyle', expectedOutput: true }; + component.lowercaseValidationDisplay$.pipe(take(1)).subscribe((value) => { + expect(value).toEqual(testCase.expectedOutput); + done(); + }); + component.fg.controls.password.patchValue(testCase.input); }); }); - describe('saveData():', () => { + describe('saveData', () => { it('should navigate to dashboard when form fields are valid', fakeAsync(() => { spyOn(component.fg, 'markAllAsTouched'); - component.isPasswordValid = true; component.fg.controls.fullName.setValue('John Doe'); component.fg.controls.password.setValue('StrongPassword@123'); - component.fg.controls.confirmPassword.setValue('StrongPassword@123'); component.eou$ = of(cloneDeep(currentEouRes)); - loaderService.showLoader.and.resolveTo(); - loaderService.hideLoader.and.resolveTo(); + loaderService.showLoader.and.returnValue(Promise.resolve()); + loaderService.hideLoader.and.returnValue(Promise.resolve()); authService.refreshEou.and.returnValue(of(eouRes3)); orgUserService.postUser.and.returnValue(of(postUserResponse)); orgUserService.markActive.and.returnValue(of(extendedOrgUserResponse)); @@ -183,7 +233,7 @@ describe('InvitedUserPage', () => { fixture.detectChanges(); tick(500); expect(component.fg.markAllAsTouched).toHaveBeenCalledTimes(1); - expect(component.fg.valid).toBeTrue(); + expect(component.fg.valid).toBe(true); expect(loaderService.showLoader).toHaveBeenCalledTimes(1); component.eou$.subscribe((exOrgUser) => { @@ -203,13 +253,12 @@ describe('InvitedUserPage', () => { spyOn(component.fg, 'markAllAsTouched'); component.fg.controls.fullName.setValue('John Doe'); component.fg.controls.password.setValue(''); - component.fg.controls.confirmPassword.setValue(''); const message = 'Please enter a valid password'; component.saveData(); tick(500); expect(component.fg.markAllAsTouched).toHaveBeenCalledTimes(1); - expect(component.fg.valid).toBeFalse(); + expect(component.fg.valid).toBe(false); expect(matSnackBar.openFromComponent).toHaveBeenCalledOnceWith(ToastMessageComponent, { ...snackbarProperties.setSnackbarProperties('failure', { message }), panelClass: ['msb-failure'], @@ -221,13 +270,12 @@ describe('InvitedUserPage', () => { spyOn(component.fg, 'markAllAsTouched'); component.fg.controls.fullName.setValue(''); component.fg.controls.password.setValue('StrongPassword@123'); - component.fg.controls.confirmPassword.setValue('StrongPassword@123'); const message = 'Please enter a valid name'; component.saveData(); tick(500); expect(component.fg.markAllAsTouched).toHaveBeenCalledTimes(1); - expect(component.fg.valid).toBeFalse(); + expect(component.fg.valid).toBe(false); expect(matSnackBar.openFromComponent).toHaveBeenCalledOnceWith(ToastMessageComponent, { ...snackbarProperties.setSnackbarProperties('failure', { message }), panelClass: ['msb-failure'], @@ -235,34 +283,4 @@ describe('InvitedUserPage', () => { expect(trackingService.showToastMessage).toHaveBeenCalledOnceWith({ ToastContent: message }); })); }); - - describe('onPasswordValid():', () => { - it('should set isPasswordValid to true when called with true', () => { - component.onPasswordValid(true); - expect(component.isPasswordValid).toBeTrue(); - }); - - it('should set isPasswordValid to false when called with false', () => { - component.onPasswordValid(false); - expect(component.isPasswordValid).toBeFalse(); - }); - }); - - describe('setPasswordTooltip():', () => { - it('should set showPasswordTooltip to true when called with true', () => { - component.setPasswordTooltip(true); - expect(component.showPasswordTooltip).toBeTrue(); - }); - - it('should set showPasswordTooltip to false when called with false', () => { - component.setPasswordTooltip(false); - expect(component.showPasswordTooltip).toBeFalse(); - }); - }); - - it('redirectToSignIn(): should navigate to the sign-in page', () => { - component.redirectToSignIn(); - // @ts-ignore - expect(component.router.navigate).toHaveBeenCalledOnceWith(['/', 'auth', 'sign_in']); // Should navigate to the correct route - }); }); diff --git a/src/app/post-verification/invited-user/invited-user.page.ts b/src/app/post-verification/invited-user/invited-user.page.ts index 9996ee48a6..ebb07a1809 100644 --- a/src/app/post-verification/invited-user/invited-user.page.ts +++ b/src/app/post-verification/invited-user/invited-user.page.ts @@ -1,8 +1,9 @@ -import { Component, EventEmitter, OnInit } from '@angular/core'; +import { Component, OnInit, EventEmitter } from '@angular/core'; import { Observable, noop, concat, from } from 'rxjs'; import { NetworkService } from 'src/app/core/services/network.service'; -import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms'; -import { switchMap, finalize, tap } from 'rxjs/operators'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { map, switchMap, finalize, tap } from 'rxjs/operators'; +import { ToastController } from '@ionic/angular'; import { OrgUserService } from 'src/app/core/services/org-user.service'; import { LoaderService } from 'src/app/core/services/loader.service'; import { AuthService } from 'src/app/core/services/auth.service'; @@ -27,12 +28,6 @@ export class InvitedUserPage implements OnInit { hide = true; - hideConfirmPassword = true; - - orgName: string; - - isPasswordValid = false; - lengthValidationDisplay$: Observable; uppercaseValidationDisplay$: Observable; @@ -43,15 +38,10 @@ export class InvitedUserPage implements OnInit { lowercaseValidationDisplay$: Observable; - showPasswordTooltip = false; - - arePasswordsEqual = false; - - isLoading = false; - constructor( private networkService: NetworkService, private fb: FormBuilder, + private toastController: ToastController, private orgUserService: OrgUserService, private loaderService: LoaderService, private authService: AuthService, @@ -61,36 +51,54 @@ export class InvitedUserPage implements OnInit { private snackbarProperties: SnackbarPropertiesService ) {} - ngOnInit(): void { + ngOnInit() { const networkWatcherEmitter = this.networkService.connectivityWatcher(new EventEmitter()); this.isConnected$ = concat(this.networkService.isOnline(), networkWatcherEmitter.asObservable()); this.isConnected$.subscribe(noop); this.fg = this.fb.group({ fullName: ['', Validators.compose([Validators.required, Validators.pattern(/[A-Za-z]/)])], - password: ['', [Validators.required, this.checkPasswordValidity]], - confirmPassword: ['', [Validators.required, this.validatePasswordEquality]], + password: [ + '', + Validators.compose([ + Validators.required, + Validators.minLength(12), + Validators.maxLength(32), + Validators.pattern(/[A-Z]/), + Validators.pattern(/[a-z]/), + Validators.pattern(/[0-9]/), + Validators.pattern(/[!@#$%^&*()+\-:;<=>{}|~?]/), + ]), + ], }); + this.lengthValidationDisplay$ = this.fg.controls.password.valueChanges.pipe( + map((password) => password && password.length >= 12 && password.length <= 32) + ); + + this.uppercaseValidationDisplay$ = this.fg.controls.password.valueChanges.pipe( + map((password) => /[A-Z]/.test(password)) + ); + + this.numberValidationDisplay$ = this.fg.controls.password.valueChanges.pipe( + map((password) => /[0-9]/.test(password)) + ); + this.specialCharValidationDisplay$ = this.fg.controls.password.valueChanges.pipe( + map((password) => /[!@#$%^&*()+\-:;<=>{}|~?]/.test(password)) + ); + + this.lowercaseValidationDisplay$ = this.fg.controls.password.valueChanges.pipe( + map((password) => /[a-z]/.test(password)) + ); + this.eou$ = from(this.authService.getEou()); this.eou$.subscribe((eou) => { this.fg.controls.fullName.setValue(eou?.us?.full_name); - this.orgName = eou.ou.org_name; }); } - onPasswordValid(isValid: boolean): void { - this.isPasswordValid = isValid; - this.fg.updateValueAndValidity(); - } - - setPasswordTooltip(value: boolean): void { - this.showPasswordTooltip = value; - } - - async saveData(): Promise { - this.isLoading = true; + async saveData() { this.fg.markAllAsTouched(); if (this.fg.valid) { from(this.loaderService.showLoader()) @@ -98,18 +106,15 @@ export class InvitedUserPage implements OnInit { switchMap(() => this.eou$), switchMap((eou) => { const user = eou.us; - user.full_name = this.fg.controls.fullName.value as string; - user.password = this.fg.controls.password.value as string; + user.full_name = this.fg.controls.fullName.value; + user.password = this.fg.controls.password.value; return this.orgUserService.postUser(user); }), tap(() => this.trackingService.setupComplete()), switchMap(() => this.authService.refreshEou()), switchMap(() => this.orgUserService.markActive()), tap(() => this.trackingService.activated()), - finalize(async () => { - this.isLoading = false; - return await this.loaderService.hideLoader(); - }) + finalize(async () => await this.loaderService.hideLoader()) ) .subscribe(() => { this.router.navigate(['/', 'enterprise', 'my_dashboard']); @@ -124,19 +129,4 @@ export class InvitedUserPage implements OnInit { this.trackingService.showToastMessage({ ToastContent: message }); } } - - redirectToSignIn(): void { - this.router.navigate(['/', 'auth', 'sign_in']); - } - - checkPasswordValidity = (): ValidationErrors => (this.isPasswordValid ? null : { invalidPassword: true }); - - validatePasswordEquality = (): ValidationErrors => { - if (!this.fg) { - return null; - } - const password = this.fg.controls.password.value as string; - const confirmPassword = this.fg.controls.confirmPassword.value as string; - return password === confirmPassword ? null : { passwordMismatch: true }; - }; } diff --git a/src/app/shared/components/password-check-tooltip/password-check-tooltip.component.scss b/src/app/shared/components/password-check-tooltip/password-check-tooltip.component.scss index 371cf572b2..a5173469e5 100644 --- a/src/app/shared/components/password-check-tooltip/password-check-tooltip.component.scss +++ b/src/app/shared/components/password-check-tooltip/password-check-tooltip.component.scss @@ -3,11 +3,9 @@ position: absolute; z-index: 1000; border-radius: 8px; - background: $pure-white; + background: $white; + box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25); color: $blue-black; - margin-top: 6px; - margin-left: 40%; - box-shadow: 0 4px 8px rgba(44, 48, 78, 0.1); .tooltip { display: inline-flex; @@ -16,30 +14,17 @@ align-items: flex-start; gap: 8px; border-radius: 8px; - background: $pure-white; + background: $white; - // Add the tooltip arrow &::before { - content: ''; position: absolute; bottom: 100%; - left: 10%; + left: 50%; transform: translateX(-50%); border-width: 8px; border-style: solid; - border-color: transparent transparent $pure-white transparent; - } - - &::after { - content: ''; - position: absolute; - bottom: calc(100% - 1px); - left: 10%; - transform: translateX(-50%); - border-width: 8px; - border-style: solid; - border-color: transparent transparent $pure-white transparent; - filter: drop-shadow(0px 2px 10px rgba(44, 48, 78, 0.1)); + border-color: transparent transparent $white transparent; + filter: drop-shadow(0 -2px 2px rgba(0, 0, 0, 0.1)); } &__text { @@ -55,9 +40,6 @@ margin: 0; font-size: 12px; line-height: 1.25; - display: flex; - flex-direction: column; - gap: 6px; &__check { display: flex; diff --git a/src/app/shared/components/password-check-tooltip/password-check-tooltip.component.spec.ts b/src/app/shared/components/password-check-tooltip/password-check-tooltip.component.spec.ts index 142ed15206..bbd2e56fd7 100644 --- a/src/app/shared/components/password-check-tooltip/password-check-tooltip.component.spec.ts +++ b/src/app/shared/components/password-check-tooltip/password-check-tooltip.component.spec.ts @@ -16,6 +16,28 @@ describe('PasswordCheckTooltipComponent', () => { fixture = TestBed.createComponent(PasswordCheckTooltipComponent); component = fixture.componentInstance; + component.passwordCriteria = [ + { + isValid: false, + message: '12 to 32 characters', + }, + { + isValid: false, + message: '1 uppercase character', + }, + { + isValid: false, + message: '1 lowercase character', + }, + { + isValid: false, + message: '1 number', + }, + { + isValid: false, + message: '1 special character', + }, + ]; fixture.detectChanges(); })); diff --git a/src/app/shared/components/password-check-tooltip/password-check-tooltip.component.ts b/src/app/shared/components/password-check-tooltip/password-check-tooltip.component.ts index c737a7ac7c..d86071ed2f 100644 --- a/src/app/shared/components/password-check-tooltip/password-check-tooltip.component.ts +++ b/src/app/shared/components/password-check-tooltip/password-check-tooltip.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, Output, EventEmitter, OnChanges, OnInit } from '@angular/core'; +import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core'; import { PasswordChecks } from './password-checks.model'; import { PasswordCriteria } from './password-criteria.model'; @@ -7,7 +7,7 @@ import { PasswordCriteria } from './password-criteria.model'; templateUrl: './password-check-tooltip.component.html', styleUrls: ['./password-check-tooltip.component.scss'], }) -export class PasswordCheckTooltipComponent implements OnChanges, OnInit { +export class PasswordCheckTooltipComponent implements OnChanges { @Input() password: string; @Output() isPasswordValid = new EventEmitter(); @@ -53,10 +53,6 @@ export class PasswordCheckTooltipComponent implements OnChanges, OnInit { this.validatePassword(); } - ngOnInit(): void { - this.updatePasswordCriteria(); - } - validatePassword(): void { if (!this.password) { Object.keys(this.passwordChecks).forEach((key) => { @@ -65,6 +61,7 @@ export class PasswordCheckTooltipComponent implements OnChanges, OnInit { this.isPasswordValid.emit(false); return; } + const specialCharRegex = /[!@#$%^&*()+\-:;<=>{}|~?]/; this.passwordChecks.lengthValid = this.password.length >= 12 && this.password.length <= 32; diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index d925924a96..eefd188e61 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -151,7 +151,6 @@ import { PromoteOptInModalComponent } from './components/promote-opt-in-modal/pr import { ProfileOptInCardComponent } from './components/profile-opt-in-card/profile-opt-in-card.component'; import { DashboardOptInComponent } from './components/dashboard-opt-in/dashboard-opt-in.component'; import { MobileNumberCardComponent } from './components/mobile-number-card/mobile-number-card.component'; -import { PasswordCheckTooltipComponent } from './components/password-check-tooltip/password-check-tooltip.component'; import { ExactCurrencyPipe } from './pipes/exact-currency.pipe'; @NgModule({ @@ -202,7 +201,6 @@ import { ExactCurrencyPipe } from './pipes/exact-currency.pipe'; FyConnectionComponent, FyCriticalPolicyViolationComponent, PopupAlertComponent, - PasswordCheckTooltipComponent, CreateNewReportComponentV2, ExpensesCardComponent, ExpensesCardComponentV2, @@ -351,7 +349,6 @@ import { ExactCurrencyPipe } from './pipes/exact-currency.pipe'; FyConnectionComponent, FyCriticalPolicyViolationComponent, PopupAlertComponent, - PasswordCheckTooltipComponent, CreateNewReportComponentV2, ExpensesCardComponent, ExpensesCardComponentV2, diff --git a/src/assets/svg/arrow-left.svg b/src/assets/svg/arrow-left.svg index c85f296cf8..95a000f74b 100644 --- a/src/assets/svg/arrow-left.svg +++ b/src/assets/svg/arrow-left.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/assets/svg/check-circle-outline.svg b/src/assets/svg/check-circle-outline.svg index 92ec73d47e..a4a3fa561b 100644 --- a/src/assets/svg/check-circle-outline.svg +++ b/src/assets/svg/check-circle-outline.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/src/assets/svg/error-outlined.svg b/src/assets/svg/error-outlined.svg deleted file mode 100644 index 58b21cbc3e..0000000000 --- a/src/assets/svg/error-outlined.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/assets/svg/eye-slash.svg b/src/assets/svg/eye-slash.svg deleted file mode 100644 index 3354018719..0000000000 --- a/src/assets/svg/eye-slash.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/assets/svg/eye.svg b/src/assets/svg/eye.svg deleted file mode 100644 index e7a9e3a497..0000000000 --- a/src/assets/svg/eye.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/assets/svg/loader.svg b/src/assets/svg/loader.svg index a2238bb0cf..7fec1b9c12 100644 --- a/src/assets/svg/loader.svg +++ b/src/assets/svg/loader.svg @@ -1,3 +1,3 @@ - - + + diff --git a/src/global.scss b/src/global.scss index 8e93c955c8..6ca6647cbe 100644 --- a/src/global.scss +++ b/src/global.scss @@ -825,7 +825,7 @@ ref - https://stackoverflow.com/a/31162426 font-size: 14px !important; line-height: 18px; color: $pure-white; - font-weight: 500 !important; + font-weight: 400 !important; height: 52px !important; text-transform: inherit; width: 100%; diff --git a/src/theme/colors.scss b/src/theme/colors.scss index 2aaaf51780..fe5aca541f 100644 --- a/src/theme/colors.scss +++ b/src/theme/colors.scss @@ -42,7 +42,6 @@ $highlight-green: #66bb6a !default; $highlight-blue: #5c98e5 !default; $pink-shadow: #d7d7d799 !default; -$success-bg: #e9f5ed !default; $black-shadow: rgba(0, 0, 0, 0.1) !default; $shadow-lg: rgba(44, 48, 78, 0.1);