Skip to content

Commit

Permalink
resolves abhi9720/BankingPortal-API#13 Add methods to reset pin and p…
Browse files Browse the repository at this point in the history
…assword (#10)

* implement ui for forget password

* added validations for password

* account password reset implemented and validation added to form for password
  • Loading branch information
abhi9720 authored Jun 30, 2024
1 parent a0a1034 commit 7e4066d
Show file tree
Hide file tree
Showing 12 changed files with 360 additions and 41 deletions.
2 changes: 2 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { RegisterComponent } from './components/register/register.component';
import { OtpComponent } from './components/otp/otp.component';
import { NotfoundpageComponent } from './components/notfoundpage/notfoundpage.component';
import { ProfileComponent } from './components/profile/profile.component';
import { ResetPasswordComponent } from './components/reset-password/reset-password.component';

const routes: Routes = [
{ path: '', component: HomeComponent, pathMatch: 'full' }, // Root route (HomeComponent) without AuthGuard
Expand All @@ -32,6 +33,7 @@ const routes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent },
{ path: 'login/otp', component: OtpComponent },
{ path: 'forget-password', component: ResetPasswordComponent },
{ path: '**', component: NotfoundpageComponent }, // Handle 404 - Page Not Found
];

Expand Down
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { ApiService } from './services/api.service';
import { AuthService } from './services/auth.service';
import { LoadermodelService } from './services/loadermodel.service';
import { NgOtpInputModule } from 'ng-otp-input';
import { ResetPasswordComponent } from './components/reset-password/reset-password.component';

@NgModule({
declarations: [
Expand Down Expand Up @@ -70,6 +71,7 @@ import { NgOtpInputModule } from 'ng-otp-input';
TransactionComponent,
MonthlyTransactionChartComponent,
DonwloadtransactionsComponent,
ResetPasswordComponent
],
imports: [
RouterModule,
Expand Down
13 changes: 10 additions & 3 deletions src/app/components/login/login.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,16 @@ <h2 class="my-3 text-center text-3xl font-extrabold text-gray-900">Login</h2>
class="group relative w-full flex justify-center py-2 px-4 bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-3 px-6 rounded focus:outline-none focus:shadow-outline disabled:opacity-50 disabled:cursor-not-allowed">Login</button>
</div>
</form>
<a class="block mt-4 font-semibold leading-6 text-indigo-600 hover:text-indigo-500" routerLink="/login/otp">Login
via
Otp</a>

<div class="flex justify-between">
<a class="block mt-4 font-semibold leading-6 text-indigo-600 hover:text-indigo-500" routerLink="/login/otp">Login
via
Otp</a>
<a class="block mt-4 font-semibold leading-6 text-indigo-600 hover:text-indigo-500" routerLink="/forget-password">Forget Password?
</a>

</div>

</div>
</div>
</div>
22 changes: 14 additions & 8 deletions src/app/components/otp/otp.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { environment } from 'src/environment/environment';

import { Component, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { finalize } from 'rxjs';

@Component({
selector: 'app-otp',
Expand All @@ -21,15 +22,15 @@ export class OtpComponent {
private authService: AuthService,
private toastService: ToastService,
private router: Router,
private loader: LoadermodelService // Inject the LoaderService here
private loader: LoadermodelService
) { }

ngOnInit() {
// Check if the accountNumber exists in sessionStorage (on page refresh)
const storedAccountNumber = sessionStorage.getItem('accountNumber');
if (storedAccountNumber) {
this.accountNumber = storedAccountNumber;
this.otpGenerated = true; // If account number exists, it means OTP is generated
this.otpGenerated = true;
}
}

Expand All @@ -53,16 +54,18 @@ export class OtpComponent {

generateOTP() {
this.loader.show('Generating OTP...'); // Show the loader before making the API call
this.authService.generateOTP(this.accountNumber).subscribe({
this.authService.generateOTP(this.accountNumber).pipe(
finalize(() => {
this.loader.hide(); // Hide the loader after API call completes (success or error)
})
).subscribe({
next: (response: any) => {
this.loader.hide(); // Hide the loader on API response
this.toastService.success(response.message + ', Check Email');
this.otpGenerated = true;
// Save the account number in sessionStorage
sessionStorage.setItem('accountNumber', this.accountNumber);
},
error: (error: any) => {
this.loader.hide(); // Hide the loader on API error
this.toastService.error(error.error);
console.error(error);
},
Expand All @@ -76,17 +79,20 @@ export class OtpComponent {
otp: this.otp,
};

this.authService.verifyOTP(otpVerificationRequest).subscribe({
this.authService.verifyOTP(otpVerificationRequest).pipe(
finalize(() => {
// Hide the loader after API call completes (success or error)
this.loader.hide();
})
).subscribe({
next: (response: any) => {
this.loader.hide(); // Hide the loader on API response
console.log(response);
this.toastService.success('Account LoggedIn');
const token = response.token;
localStorage.setItem(this.authTokenName, token);
this.router.navigate(['/dashboard']);
},
error: (error: any) => {
this.loader.hide(); // Hide the loader on API error
this.toastService.error(error.error);
console.error(error);
},
Expand Down
35 changes: 26 additions & 9 deletions src/app/components/register/register.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ <h2 class="my-3 text-center text-3xl font-extrabold text-gray-900">
/>
<div
*ngIf="f.password.invalid && f.password.touched"
class="text-red-500 text-sm mt-1"
class="block text-red-500 font-medium rounded-lg mt-2 text-sm"
>
<div *ngIf="f.password.errors?.required">
Password is required.
Expand All @@ -130,6 +130,8 @@ <h2 class="my-3 text-center text-3xl font-extrabold text-gray-900">
Password must be at most 127 characters long.
</div>
</div>


</div>
<div class="col-span-1">
<label for="confirmPassword" class="block text-sm font-bold mb-2"
Expand All @@ -144,7 +146,7 @@ <h2 class="my-3 text-center text-3xl font-extrabold text-gray-900">
/>
<div
*ngIf="f.confirmPassword.invalid && f.confirmPassword.touched"
class="text-red-500 text-sm mt-1"
class="block text-red-500 font-medium rounded-lg mt-2 text-sm"
>
<div *ngIf="f.confirmPassword.errors?.required">
Confirm Password is required.
Expand All @@ -155,6 +157,21 @@ <h2 class="my-3 text-center text-3xl font-extrabold text-gray-900">
</div>
</div>
</div>

<ul class="text-red-400 text-sm list-disc" *ngIf="f.password?.dirty">
<li class="block text-red-500 font-medium rounded-lg mt-2" *ngIf="!f.password?.value?.match('^(?=.*[A-Z])')">At least oneuppercase
letter.</li>
<li class="block text-red-500 font-medium rounded-lg mt-2" *ngIf="!f.password?.value?.match('(?=.*[a-z])')">At least one lowercase
letter.</li>
<li class="block text-red-500 font-medium rounded-lg mt-2" *ngIf="!f.password?.value?.match('(.*[0-9].*)')">At least one
digit.</li>
<li class="block text-red-500 font-medium rounded-lg mt-2" *ngIf="!f.password?.value?.match('(?=.*[!@#$%^&*])')">At least one
special
character.</li>
<li class="block text-red-500 font-medium rounded-lg mt-2" *ngIf="!f.password?.value?.match('.{8,}')">At least 8 characters
long.</li>
</ul>

<div class="mt-4 flex items-center justify-center">
<button
type="submit"
Expand All @@ -167,19 +184,19 @@ <h2 class="my-3 text-center text-3xl font-extrabold text-gray-900">
</form>
</div>
<div
class="rounded-lg border border-gray-300 p-8"
class="max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow"
*ngIf="showRegistrationData"
>
<h2 class="text-3xl font-semibold mb-6">Registration Successful!</h2>
<p>Name: {{ registrationData.name }}</p>
<p>Email: {{ registrationData.email }}</p>
<p>Account Number: {{ registrationData.accountNumber }}</p>
<p>Branch: {{ registrationData.branch }}</p>
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900 dark:text-white text-center">Registration Successful!</h5>
<p class="mb-3 font-normal text-gray-700">Name: <span class="font-medium">{{ registrationData.name || '-' }}</span></p>
<p class="mb-3 font-normal text-gray-700">Email:<span class="font-medium"> {{ registrationData.email }}</span></p>
<p class="mb-3 font-normal text-gray-700">Account Number: :<span class="font-medium">{{ registrationData.accountNumber }}</span></p>
<p class="mb-3 font-normal text-gray-700">Branch: <span class="font-medium">{{ registrationData.branch }}</span></p>
<div class="mt-4 flex items-center justify-center">
<a
routerLink="/login"
type="button"
class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline cursor-pointer"
(click)="showRegistrationData = false"
>Continue to Login</a
>
Expand Down
23 changes: 3 additions & 20 deletions src/app/components/register/register.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,9 @@ import { invalidPhoneNumber } from 'src/app/services/country-code.service';

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { passwordMismatch, StrongPasswordRegx } from 'src/app/util/formutil';

function passwordMismatch(
controlName: string,
matchingControlName: string
): any {
return (formGroup: FormGroup) => {
const control = formGroup.controls[controlName];
const matchingControl = formGroup.controls[matchingControlName];

if (matchingControl.errors && !matchingControl.errors.passwordMismatch) {
return;
}

if (control.value !== matchingControl.value) {
matchingControl.setErrors({ passwordMismatch: true });
} else {
matchingControl.setErrors(null);
}
};
}

@Component({
selector: 'app-register',
Expand All @@ -35,12 +18,11 @@ export class RegisterComponent implements OnInit {
registerForm!: FormGroup;
showRegistrationData = false;
registrationData: any;
print = console;

constructor(
private authService: AuthService,
private _toastService: ToastService
) {}
) { }

ngOnInit() {
this.registerForm = new FormGroup(
Expand All @@ -54,6 +36,7 @@ export class RegisterComponent implements OnInit {
Validators.required,
Validators.minLength(8),
Validators.maxLength(127),
Validators.pattern(StrongPasswordRegx)
]),
confirmPassword: new FormControl('', Validators.required),
},
Expand Down
11 changes: 11 additions & 0 deletions src/app/components/reset-password/reset-password.component.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
:host {

display: flex;
flex: 1;
}

.coverparentspace {
flex: 1;
display: flex;
flex-direction: column;
}
90 changes: 90 additions & 0 deletions src/app/components/reset-password/reset-password.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<div class="h-screen flex items-center justify-center bg-gray-100 coverparentspace">
<div class="max-w-md w-full">
<div *ngIf="!showNewPasswordForm">
<!-- Step 1: Enter Email or Account Number -->
<div class="border-none md:border border-gray-300 px-4 py-3 mb-8 bg-white rounded-lg shadow-none md:shadow-md">
<h2 class="my-3 text-center text-3xl font-extrabold text-gray-900">Reset Password</h2>
<form [formGroup]="resetPasswordForm" (ngSubmit)="sendOtp()">
<!-- Display OTP input if OTP is sent successfully -->
<ng-container *ngIf="!otpSentSuccessfully; else otpInputSection">
<div class="mb-4">
<label for="identifier" class="block text-sm font-bold mb-2">Email or Account Number:</label>
<input formControlName="identifier" type="text" id="identifier"
class="w-full px-3 py-2 border rounded-lg shadow-sm focus:outline-none focus:border-indigo-500" />
<div *ngIf="resetPasswordForm.get('identifier')?.invalid && resetPasswordForm.get('identifier')?.touched"
class="text-red-500 text-sm mt-1">
Email or Account Number is required.
</div>
</div>
<div class="flex items-center justify-between">
<button type="submit" [disabled]="resetPasswordForm.invalid"
class="group relative w-full flex justify-center py-2 px-4 bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-3 px-6 rounded focus:outline-none focus:shadow-outline disabled:opacity-50 disabled:cursor-not-allowed">Send OTP</button>
</div>
</ng-container>

<!-- OTP input section -->
<ng-template #otpInputSection>
<div class="mb-4">
<label for="otp" class="block text-sm font-bold mb-2">Enter OTP:</label>
<ng-otp-input #ngOtpInput (onInputChange)="onOtpChange($event)" [config]="config"></ng-otp-input>
<input formControlName="otp" type="hidden" id="otp" [value]="resetPasswordForm.get('otp')?.value">
</div>
<div class="flex items-center justify-between">
<button type="button" (click)="verifyOtp()"
class="group relative w-full flex justify-center py-2 px-4 bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-3 px-6 rounded focus:outline-none focus:shadow-outline disabled:opacity-50 disabled:cursor-not-allowed">Verify OTP</button>
</div>
</ng-template>
</form>
<a class="block mt-4 font-semibold leading-6 text-indigo-600 hover:text-indigo-500" routerLink="/login">Back to Login</a>
</div>
</div>

<div *ngIf="showNewPasswordForm">
<!-- Step 3: Enter New Password -->
<div class="border-none md:border border-gray-300 px-4 py-3 mb-8 bg-white rounded-lg shadow-none md:shadow-md">
<h2 class="my-3 text-center text-3xl font-extrabold text-gray-900">Reset Password</h2>
<form [formGroup]="newPasswordForm" (ngSubmit)="resetPassword()">
<div class="mb-4">
<label for="newPassword" class="block text-sm font-bold mb-2">New Password:</label>
<input formControlName="newPassword" type="password" id="newPassword"
class="w-full px-3 py-2 border rounded-lg shadow-sm focus:outline-none focus:border-indigo-500" />
<!-- <div *ngIf="newPasswordForm.get('newPassword')?.invalid && newPasswordForm.get('newPassword')?.touched"
class="text-red-500 text-sm mt-1">
New Password is required.
</div> -->
</div>
<div class="mb-4">
<label for="confirmPassword" class="block text-sm font-bold mb-2">Confirm Password:</label>
<input formControlName="confirmPassword" type="password" id="confirmPassword"
class="w-full px-3 py-2 border rounded-lg shadow-sm focus:outline-none focus:border-indigo-500" />

<div *ngIf="newPasswordForm.get('confirmPassword')?.invalid && newPasswordForm.get('confirmPassword')?.touched"
class="text-red-500 text-sm mt-1">
Confirm Password is required and must match the New Password.
</div>

<div
*ngIf="f.newPassword.invalid && f.newPassword.touched"
class="text-red-500 text-sm mt-1"
>
<div *ngIf="f.newPassword.errors?.required">
Password is required.
</div>
<div *ngIf="f.newPassword.errors?.minlength">
Password must be at least 8 characters long.
</div>
<div *ngIf="f.newPassword.errors?.maxlength">
Password must be at most 127 characters long.
</div>
</div>

</div>
<div class="flex items-center justify-between">
<button type="submit" [disabled]="newPasswordForm.invalid"
class="group relative w-full flex justify-center py-2 px-4 bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-3 px-6 rounded focus:outline-none focus:shadow-outline disabled:opacity-50 disabled:cursor-not-allowed">Reset Password</button>
</div>
</form>
</div>
</div>
</div>
</div>
23 changes: 23 additions & 0 deletions src/app/components/reset-password/reset-password.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { ResetPasswordComponent } from './reset-password.component';

describe('ResetPasswordComponent', () => {
let component: ResetPasswordComponent;
let fixture: ComponentFixture<ResetPasswordComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ResetPasswordComponent]
})
.compileComponents();

fixture = TestBed.createComponent(ResetPasswordComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Loading

0 comments on commit 7e4066d

Please sign in to comment.