From ad3f4bf88fe626a6d3cd6cb098a86c58e393f40a Mon Sep 17 00:00:00 2001 From: aastha Date: Mon, 9 Dec 2024 06:02:00 +0530 Subject: [PATCH] feat: Redesign reset password page --- .../auth/new-password/new-password.module.ts | 2 + .../auth/new-password/new-password.page.html | 227 ++++++++---------- .../auth/new-password/new-password.page.scss | 136 +++++++---- .../new-password/new-password.page.spec.ts | 26 +- .../auth/new-password/new-password.page.ts | 127 +++++----- 5 files changed, 250 insertions(+), 268 deletions(-) diff --git a/src/app/auth/new-password/new-password.module.ts b/src/app/auth/new-password/new-password.module.ts index ebe48b4b6e..452791efbb 100644 --- a/src/app/auth/new-password/new-password.module.ts +++ b/src/app/auth/new-password/new-password.module.ts @@ -17,6 +17,7 @@ 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: [ @@ -30,6 +31,7 @@ import { PopupComponent } from './popup/popup.component'; 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 ad72afd5e7..f432b0043b 100644 --- a/src/app/auth/new-password/new-password.page.html +++ b/src/app/auth/new-password/new-password.page.html @@ -1,133 +1,104 @@ - - - - - - - - -
-
Reset password
-
+
+
+ +
+
Reset Password
- - - - - 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 +
+
+
+
New password
+
+ +
+ +
+
+ +
+
+ Password cannot be empty +
+
+ Please enter a valid password. +
+
+
+
+
Confirm new password
+
+ +
+ +
+
+
+
+ Password cannot be empty +
+
+ Passwords do not match +
+
+
-
Please enter a new password
-
- - - - {{hide ? 'visibility_off' : 'visibility'}} - - -
-
- +
+
+ Reset password +
+ + Back to Sign In
- - - - - - 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 e1afee8662..d6ddd4feb4 100644 --- a/src/app/auth/new-password/new-password.page.scss +++ b/src/app/auth/new-password/new-password.page.scss @@ -1,84 +1,116 @@ -$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; +@import '../../../theme/colors.scss'; .new-password { - &--header { - min-height: 140px; + display: flex; + flex-direction: column; + padding: 24px 20px; + height: 100%; + justify-content: space-between; + + &__password-container { + display: flex; } - &--header-container { - min-height: 140px; - background-color: $new-password-header; + &__input-container { + margin-bottom: 24px; } - &--header-logo-container { - text-align: center; + &__error { + color: $red; + font-size: 12px; } - &--header-logo { - max-width: 100px; + &__text { + border-bottom: 1px solid $grey-lighter; + + &__invalid { + border-bottom: 1px solid $red; + } } - &--form { - padding: 24px; + &__mandatory { + color: $brand-primary; + display: inline-block; + font-size: 14px; + font-weight: 400; } - &--form-field { - width: 100%; + &__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-header { - font-size: 24px; - margin-top: 16px; - margin-bottom: 8px; - color: $form-header; - font-weight: 700; + &__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-subheader { - font-size: 16px; - color: $form-subheader; - margin-top: 8px; - margin-bottom: 24px; + &__password-icon { + width: 20px; + height: 20px; + fill: $black-light; } - &--primary-cta { - margin-top: 12px; - .mat-button-base { - width: 100%; - font-weight: 700; - min-height: 47px; - } + &__password-icon-container { + display: flex; + align-items: center; } - &--password-rules { - padding: 14px 0; + &__form-header { + font-size: 20px; + margin: 0 0 10px; + position: relative; + margin-bottom: 24px; + color: $black; } - &--save { + &__save { width: 100%; &__disabled { opacity: 0.2; } } - &--validation { - vertical-align: middle; - font-size: 16px; + &__back-icon { + margin-bottom: 20px; + width: 28px; + height: 28px; + } - &__correct { - color: green; - } + &__cta-text { + font-size: 14px; + font-weight: 500; + } - &__incorrect { - color: red; - } + &__arrow-icon { + fill: $pure-white; + margin-right: 8px; + } + + &__cta-secondary { + display: flex; + padding-top: 12px; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 24px; + align-self: stretch; + color: $blue-black; + display: flex; + flex-direction: row; + gap: 6px; } } 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 f5db061720..f27c9dc51d 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, PopoverController } from '@ionic/angular'; +import { IonicModule } from '@ionic/angular'; import { NewPasswordPage } from './new-password.page'; import { AuthService } from 'src/app/core/services/auth.service'; @@ -24,7 +24,6 @@ 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; @@ -33,7 +32,6 @@ describe('NewPasswordPage', () => { 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']); @@ -46,7 +44,6 @@ 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 }, @@ -67,7 +64,6 @@ 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; @@ -196,8 +192,6 @@ describe('NewPasswordPage', () => { 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(); @@ -215,21 +209,11 @@ describe('NewPasswordPage', () => { expect(trackingService.onSignin).toHaveBeenCalledOnceWith('ajain@fyle.in'); expect(trackingService.resetPassword).toHaveBeenCalledTimes(1); expect(component.trackLoginInfo).toHaveBeenCalledTimes(1); - 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(() => { 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(); @@ -246,14 +230,6 @@ describe('NewPasswordPage', () => { expect(trackingService.onSignin).not.toHaveBeenCalled(); expect(trackingService.resetPassword).not.toHaveBeenCalled(); expect(component.trackLoginInfo).not.toHaveBeenCalled(); - expect(popoverController.create).toHaveBeenCalledOnceWith({ - component: PopupComponent, - componentProps: { - header: 'Setting new password failed. Please try again later.', - route: ['/', 'auth', 'sign_in'], - }, - cssClass: 'dialog-popover', - }); })); }); diff --git a/src/app/auth/new-password/new-password.page.ts b/src/app/auth/new-password/new-password.page.ts index 0d5295e798..daecee279b 100644 --- a/src/app/auth/new-password/new-password.page.ts +++ b/src/app/auth/new-password/new-password.page.ts @@ -1,16 +1,17 @@ import { Component, OnInit } from '@angular/core'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { finalize, map, switchMap, tap } from 'rxjs/operators'; +import { FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms'; +import { finalize, switchMap, tap } from 'rxjs/operators'; import { from, Observable } from 'rxjs'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } 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', @@ -30,62 +31,41 @@ 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 loginInfoService: LoginInfoService, + private router: Router, + private matSnackBar: MatSnackBar, + private snackbarPropertiesService: SnackbarPropertiesService ) {} - ngOnInit() { + ngOnInit(): void { this.fg = this.fb.group({ - 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(/[!@#$%^&*()+\-:;<=>{}|~?]/), - ]), - ], + password: ['', [Validators.required, this.customPasswordValidator]], + confirmPassword: ['', [Validators.required, this.validatePasswordEquality]], }); - - 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() { - const refreshToken = this.activatedRoute.snapshot.params.refreshToken; + changePassword(): void { + const refreshToken = this.activatedRoute.snapshot.params.refreshToken as string; from(this.loaderService.showLoader()) .pipe( - switchMap(() => this.routerAuthService.resetPassword(refreshToken, this.fg.controls.password.value)), + switchMap(() => this.routerAuthService.resetPassword(refreshToken, this.fg.controls.password.value as string)), switchMap(() => this.authService.refreshEou()), tap(async (eou) => { const email = eou.us.email; @@ -96,36 +76,57 @@ export class NewPasswordPage implements OnInit { finalize(() => from(this.loaderService.hideLoader())) ) .subscribe( - async () => { - const popup = await this.popoverController.create({ - component: PopupComponent, - componentProps: { - header: 'Password changed successfully', - route: ['/', 'auth', 'switch_org'], - }, - cssClass: 'dialog-popover', + () => { + const toastMessageData = { + message: 'Password changed successfully', + }; + + this.matSnackBar.openFromComponent(ToastMessageComponent, { + ...this.snackbarPropertiesService.setSnackbarProperties('success', toastMessageData), + panelClass: ['msb-success'], }); - - await popup.present(); + this.router.navigate(['/', 'auth', 'switch_org']); }, - 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', + () => { + const toastMessageData = { + message: 'Something went wrong. Please try after some time.', + }; + + this.matSnackBar.openFromComponent(ToastMessageComponent, { + ...this.snackbarPropertiesService.setSnackbarProperties('failure', toastMessageData), + panelClass: ['msb-failure'], }); - - await popup.present(); + this.router.navigate(['/', 'auth', 'sign_in']); } ); } - async trackLoginInfo() { + async trackLoginInfo(): Promise { 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; + } + + customPasswordValidator = (): 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 }; + }; }