From 8340f140354dcff85e1c30f0245f8ce81f889c7f Mon Sep 17 00:00:00 2001 From: atif118-mfsys Date: Wed, 24 Dec 2025 14:37:28 +0500 Subject: [PATCH 1/3] working on setting up http services as per standard --- package.json | 2 + src/app/app.component.html | 1 + src/app/app.component.ts | 3 +- src/app/app.http.uri.service.ts | 125 +++++++++ src/app/app.routes.ts | 4 +- src/app/app.uri.ts | 84 ++++++ src/app/authenticate/authenticate.ts | 23 ++ src/app/authenticate/login/login.component.ts | 37 +-- src/app/services/authenticate.service.ts | 119 ++++++++ src/app/services/button-management.service.ts | 56 ++++ src/app/services/credential.service.ts | 79 ++++++ src/app/services/encryption.service.ts | 54 ++++ src/app/services/i18n.service.ts | 87 ++++++ .../components/header/header.component.ts | 6 +- .../notifications.component.html | 4 + .../notifications.component.scss | 88 ++++++ .../notifications.component.spec.ts | 23 ++ .../notifications/notifications.component.ts | 21 ++ src/app/shared/guards/authentication.guard.ts | 40 +++ src/app/shared/guards/gauth.guard.ts | 26 -- .../shared/interceptors/auth.interceptor.ts | 190 +++++++------ src/app/shared/services/http.service.ts | 256 ++++++------------ .../shared/services/notification.service.ts | 40 +++ src/app/utils/app.constants.ts | 8 + src/app/utils/enums.ts | 42 +++ src/app/utils/uri-enums.ts | 5 + src/assets/data/app.uri.json | 25 ++ src/assets/i18n/Arabic.json | 4 +- src/assets/i18n/English.json | 4 +- src/environments/environment.prod.ts | 5 +- src/environments/environment.ts | 5 +- 31 files changed, 1149 insertions(+), 317 deletions(-) create mode 100644 src/app/app.http.uri.service.ts create mode 100644 src/app/app.uri.ts create mode 100644 src/app/authenticate/authenticate.ts create mode 100644 src/app/services/authenticate.service.ts create mode 100644 src/app/services/button-management.service.ts create mode 100644 src/app/services/credential.service.ts create mode 100644 src/app/services/encryption.service.ts create mode 100644 src/app/services/i18n.service.ts create mode 100644 src/app/shared/components/notifications/notifications.component.html create mode 100644 src/app/shared/components/notifications/notifications.component.scss create mode 100644 src/app/shared/components/notifications/notifications.component.spec.ts create mode 100644 src/app/shared/components/notifications/notifications.component.ts create mode 100644 src/app/shared/guards/authentication.guard.ts delete mode 100644 src/app/shared/guards/gauth.guard.ts create mode 100644 src/app/shared/services/notification.service.ts create mode 100644 src/app/utils/uri-enums.ts create mode 100644 src/assets/data/app.uri.json diff --git a/package.json b/package.json index dab2037..1f0edd1 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ "@ng-select/ng-select": "^14.8.0", "@ngx-translate/core": "^17.0.0", "@ngx-translate/http-loader": "^16.0.1", + "@types/crypto-js": "^4.2.2", "bootstrap": "^5.3.8", + "crypto-js": "^4.2.0", "express": "^4.18.2", "ngx-spinner": "^19.0.0", "ngx-toastr": "^19.1.0", diff --git a/src/app/app.component.html b/src/app/app.component.html index 6a33028..c48ec10 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,2 +1,3 @@ + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index adf7a8b..4207d60 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -5,10 +5,11 @@ import { StorageService } from './shared/services/storage.service'; import { isPlatformBrowser } from '@angular/common'; import { directions, supportedLanguages } from './utils/enums'; import { LoaderComponent } from './shared/components/loader/loader.component'; +import { NotificationsComponent } from './shared/components/notifications/notifications.component'; @Component({ selector: 'app-root', - imports: [RouterOutlet, LoaderComponent], + imports: [RouterOutlet, LoaderComponent, NotificationsComponent], templateUrl: './app.component.html', styleUrl: './app.component.scss' }) diff --git a/src/app/app.http.uri.service.ts b/src/app/app.http.uri.service.ts new file mode 100644 index 0000000..5f50d48 --- /dev/null +++ b/src/app/app.http.uri.service.ts @@ -0,0 +1,125 @@ +import { HttpHeaders, HttpParams } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { filter, Observable, Observer } from 'rxjs'; +import { HttpService } from './shared/services/http.service'; +import { URIService } from './app.uri'; +import { URIKey } from '../app/utils/uri-enums'; + +@Injectable( + { providedIn: 'root' } +) +export class HttpURIService { + + constructor(private http: HttpService, private uriService: URIService) { + } + + requestGET(uriKey: URIKey | string, params?: HttpParams): Observable { + return new Observable((observer: Observer) => { + this.uriService.canSubscribe.pipe(filter(val => val)).subscribe(val => { + let uri: string = this.uriService.getURIForRequest(uriKey as URIKey) + this.http.requestGET(uri, params).subscribe(t => { + observer.next(t), + observer.complete() + }, + error => { + console.error(error); + observer.next(error), + observer.complete() + }); + }); + }); + } + + requestPOST(uriKey: URIKey | string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable { + return new Observable((observer: Observer) => { + this.uriService.canSubscribe.pipe(filter(val => val)).subscribe(val => { + let uri: string = this.uriService.getURIForRequest(uriKey as URIKey) + this.http.requestPOST(uri, body, headers, params).subscribe( + t => { + observer.next(t), + observer.complete() + }, + error => { + console.error(error); + observer.next(error), + observer.complete() + } + ); + }); + }); + } + + requestPOSTMultipart(uriKey: URIKey | string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable { + return new Observable((observer: Observer) => { + this.uriService.canSubscribe.pipe(filter(val => val)).subscribe(val => { + let uri: string = this.uriService.getURIForRequest(uriKey as URIKey) + this.http.requestPOSTMultipart(uri, body, headers, params).subscribe( + t => { + observer.next(t), + observer.complete() + }, + error => { + console.error(error); + observer.next(error), + observer.complete() + } + ); + }); + }); + } + + requestDELETE(uriKey: URIKey | string, params: HttpParams): Observable { + return new Observable((observer: Observer) => { + this.uriService.canSubscribe.pipe(filter(val => val)).subscribe(val => { + let uri: string = this.uriService.getURIForRequest(uriKey as URIKey) + this.http.requestDELETE(uri, params).subscribe(t => { + observer.next(t), + observer.complete() + }, + error => { + console.error(error); + observer.next(error), + observer.complete() + } + ); + }); + }); + } + + requestPATCH(uriKey: URIKey | string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable { + return new Observable((observer: Observer) => { + this.uriService.canSubscribe.pipe(filter(val => val)).subscribe(val => { + let uri: string = this.uriService.getURIForRequest(uriKey as URIKey) + this.http.requestPATCH(uri, body, headers, params).subscribe(t => { + observer.next(t), + observer.complete() + }, + error => { + console.error(error); + observer.next(error), + observer.complete() + } + ); + }); + }); + } + + requestPUT(uriKey: URIKey | string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable { + return new Observable((observer: Observer) => { + this.uriService.canSubscribe.pipe(filter(val => val)).subscribe(val => { + let uri: string = this.uriService.getURIForRequest(uriKey as URIKey) + this.http.requestPUT(uri, body, headers, params).subscribe(t => { + observer.next(t), + observer.complete() + }, + error => { + console.error(error); + observer.next(error), + observer.complete() + } + ); + }); + }); + } +} + diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 3d30b61..6038e1b 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -2,7 +2,7 @@ import { Routes } from '@angular/router'; import { LoginComponent } from './authenticate/login/login.component'; import { ChangePasswordComponent } from './user-management/change-password/change-password.component'; import { FullLayoutComponent } from './full-layout/full-layout.component'; -import { GAuth } from './shared/guards/gauth.guard'; +import { AuthenticationGuard } from './shared/guards/authentication.guard'; export const routes: Routes = [ { @@ -21,7 +21,7 @@ export const routes: Routes = [ { path: 'home', component: FullLayoutComponent, - canActivate: [GAuth], + canActivate: [AuthenticationGuard], children: [ { path: '', diff --git a/src/app/app.uri.ts b/src/app/app.uri.ts new file mode 100644 index 0000000..1adaaac --- /dev/null +++ b/src/app/app.uri.ts @@ -0,0 +1,84 @@ + +import { HttpClient } from '@angular/common/http'; +import { Injectable, OnInit } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { environment } from '../environments/environment'; +import { URIKey } from './utils/uri-enums'; +@Injectable({ + providedIn: 'root', +}) +export class URIService { + canSubscribe: BehaviorSubject; + uriMap: Map; + + constructor(private http: HttpClient) { + this.canSubscribe = new BehaviorSubject(false); + this.uriMap = new Map(); + this.loadURIs(); + } + + loadURIs(): void { + this.http.get('assets/data/app.uri.json') + .subscribe(data => { + for (const item of data) { + const baseURI = environment.moduleHost.get(item.Id) as string; + if (baseURI) { + for (const module of item.Modules) { + for (const page of module.Pages) { + const uri = `${baseURI}${module.URI}${page.URI}`; + const key = URIKey[page.UUID as keyof typeof URIKey]; + if (key !== undefined) { + this.uriMap.set(key, uri); + } + } + } + } + } + this.canSubscribe.next(true); + }); + } + + + getURI(key: URIKey): string | undefined { + return this.uriMap.get(key); + } + + getURIForRequest(key: URIKey): string { + let uri = this.getURI(key as URIKey); + + if (uri != undefined) { + return uri; + } + else { + let arr = key.split("/"); + if (arr.length) { + let db = arr[0]; + let baseurl = environment.moduleHost.get(db.toUpperCase() + "_DOMAIN_URI"); + if (baseurl != undefined) { + uri = (baseurl).concat("/").concat(key); + return uri; + } + } + } + return key; + } +} + + +export interface URIInfo { + Id: string; + URI: string; + Modules: URIModule[]; +} + +interface URIModule { + Id: string; + URI: string; + Pages: URIPage[]; +} + +interface URIPage { + Id: string; + URI: string; + UUID: string; +} \ No newline at end of file diff --git a/src/app/authenticate/authenticate.ts b/src/app/authenticate/authenticate.ts new file mode 100644 index 0000000..d6279dd --- /dev/null +++ b/src/app/authenticate/authenticate.ts @@ -0,0 +1,23 @@ +export interface AuthenticationToken { + token: string; +} + +export interface AuthenticationResponse extends AuthenticationToken { + authenticated: boolean + porOrgacode: string; + userId: string; + userType: string; + password: string; + userHomevac: string; +} + +export class UserCredentials { + porOrgacode!: string; + userId!: string; + password!: string; + token!: string; +} + +export class AuthenticationRequest { + isInProgress: boolean = false; +} \ No newline at end of file diff --git a/src/app/authenticate/login/login.component.ts b/src/app/authenticate/login/login.component.ts index 1703c88..53eca3d 100644 --- a/src/app/authenticate/login/login.component.ts +++ b/src/app/authenticate/login/login.component.ts @@ -4,14 +4,15 @@ import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angula import { CONSTANTS } from '../../utils/app.constants'; import { PasswordHideShowComponent } from '../../shared/components/password-hide-show/password-hide-show.component'; -import { AuthService } from '../../services/auth.service'; import { Router } from '@angular/router'; import { MiscService } from '../../shared/services/misc.service'; import { User } from '../../models/user'; import { CommonModule, isPlatformBrowser } from '@angular/common'; import { StorageService } from '../../shared/services/storage.service'; -import { directions, supportedLanguages } from '../../utils/enums'; +import { directions, FormConstants, HiddenValues, supportedLanguages } from '../../utils/enums'; import { environment } from '../../../environments/environment'; +import { AuthenticationService } from '../../services/authenticate.service'; +import { UserCredentials } from '../authenticate'; @Component({ selector: 'app-login', @@ -28,10 +29,11 @@ export class LoginComponent { passwordType: string = 'password'; direction: string = ''; supportedLanguages = supportedLanguages; + ucred: UserCredentials = new UserCredentials(); @ViewChild(PasswordHideShowComponent) passwordHideShow?: PasswordHideShowComponent; constructor( - private authService: AuthService, + private authService: AuthenticationService, private translateService: TranslateService, private router: Router, private miscService: MiscService, @@ -92,27 +94,16 @@ export class LoginComponent { }) } - async login() { - let User: User = { - Username: this.loginForm.get("USER_ID")?.value, - Password: this.loginForm.get("PASSWORD")?.value - } - let response = await this.authService.login(User); - if ((await response["errorMessage"] == undefined)) { - if (await response) { - if (!this.authService.firstLogin) { - this.router.navigate(['home/dashboard']); - this.miscService.handleSuccess(this.translateService.instant('loginSuccess')); - } - else { - this.router.navigate(['/changepassword']); - this.miscService.handleSuccess(this.translateService.instant('passwordChangeRequired')); - } - } - } - else { - this.miscService.handleError(await response["errorMessage"]) + login() { + if (this.loginForm.valid) { + this.ucred.porOrgacode = HiddenValues.POR_ORGACODE; + this.ucred.password = this.loginForm.get(FormConstants.PASSWORD)?.value; + this.ucred.userId = this.loginForm.get(FormConstants.USER_ID)?.value; + this.authService.authenticate(this.ucred).subscribe( (res: any) => { + + }); } + } onLangChange() { diff --git a/src/app/services/authenticate.service.ts b/src/app/services/authenticate.service.ts new file mode 100644 index 0000000..bcee077 --- /dev/null +++ b/src/app/services/authenticate.service.ts @@ -0,0 +1,119 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { BehaviorSubject, Observable, Observer } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { ErrorMessages, FormConstants, HiddenValues, SuccessMessages } from '../utils/enums'; +import { CredentialService } from './credential.service'; +import { AuthenticationToken, UserCredentials } from '../authenticate/authenticate'; +import { HttpURIService } from '../app.http.uri.service'; +import { URIKey } from '../utils/uri-enums'; +import { I18NService } from './i18n.service'; +import { StorageService } from '../shared/services/storage.service'; +import { ButtonManagementService } from './button-management.service'; + +@Injectable( + { providedIn: 'root' } +) +export class AuthenticationService { + showLicenseInfo: boolean = false; + reset: boolean = false; + + public onAuthenticationComplete: BehaviorSubject = new BehaviorSubject(false); + + constructor(private buttonManagementService: ButtonManagementService, private httpService: HttpURIService, private router: Router, private credentialService: CredentialService, private i18nService: I18NService, private storageService: StorageService) { + } + + authenticate(uCreds: UserCredentials) : Observable { + const observable = new Observable((observer: Observer) => { + + if (this.storageService.getItem('user') != null) { + this.i18nService.error(ErrorMessages.ALREADY_LOGGED_IN,[]); + return; + } + this.credentialService.setPorOrgacode(uCreds.porOrgacode); + this.credentialService.setUserId(uCreds.userId); + this.credentialService.setPassword(uCreds.password); + this.storageService.setItem(FormConstants.POR_ORGACODE, uCreds.porOrgacode); + this.storageService.setItem(FormConstants.USER_ID, uCreds.userId); + this.storageService.setItem(FormConstants.PASSWORD, uCreds.password); + this.httpService.requestPOST(URIKey.USER_LOGIN_URI, uCreds).subscribe((data: any) => { + if (!(data instanceof HttpErrorResponse)) { + data.authenticated = true; + this.i18nService.success(SuccessMessages.LOGIN_SUCCESSFULLY, []); + this.storageService.setItem('user', JSON.stringify(data)); + this.credentialService.setToken(data.token); + this.credentialService.setUserType(data.userType); + if(data.permission){ + this.storageService.setItem('permission', data.permission); + this.credentialService.setPermission(JSON.parse(data.permission)); + } + else{ + this.storageService.setItem('permission', '[]'); + this.credentialService.setPermission([]); + } + this.buttonManagementService.setButtonPermissions(this.credentialService.getPermission(), this.isSuperAdminUser()); + if(data.user.isFirstLogin){ + this.router.navigate(["/changepassword"]); + } else { + this.router.navigate(["/home/dashboard"]); + } + this.onAuthenticationComplete.next(true); + observer.complete(); + } + else { + this.onAuthenticationComplete.next(false); + observer.error(false); + } + }); + }); + return observable; + + } + + isAuthenticated(): boolean { + if (this.storageService && this.storageService.getItem('user') != null) { + let cachedUser = JSON.parse(this.storageService.getItem('user') || '{}'); + return cachedUser.authenticated; + } + return false; + } + + isOrganizartionUser(){ + if (this.storageService && this.storageService.getItem('user') != null) { + let cachedUser = JSON.parse(this.storageService.getItem('user') || '{}'); + return cachedUser.userType === HiddenValues.ORGANIZATION_USER; + } + return false; + } + + isSuperAdminUser(){ + if (this.storageService && this.storageService.getItem('user') != null) { + let cachedUser = JSON.parse(this.storageService.getItem('user') || '{}'); + return cachedUser.userType === HiddenValues.SUPERADMIN_USER; + } + return false; + } + + refreshToken() { + let uCreds: UserCredentials = { porOrgacode: this.credentialService.getPorOrgacode(), userId: this.credentialService.getUserId(), password: this.credentialService.getPassword(), token: this.credentialService.getToken() }; + return this.httpService.requestPOST(URIKey.USER_REFRESH_TOKEN, uCreds).pipe( + tap(response => { + this.credentialService.setToken(response.token); + let cachedUser = JSON.parse(this.storageService.getItem('user') || '{}'); + cachedUser.token = response.token; + this.storageService.setItem('user', JSON.stringify(cachedUser)); + }) + ); + } + + logout() { + let defaultPermission: string = this.storageService.getItem("defaultPermission") || "{}"; + this.storageService.clear(); + this.storageService.setItem("defaultPermission", defaultPermission) + this.credentialService.resetService(); + this.router.navigate(['/login']); + + } + +} \ No newline at end of file diff --git a/src/app/services/button-management.service.ts b/src/app/services/button-management.service.ts new file mode 100644 index 0000000..e3ef074 --- /dev/null +++ b/src/app/services/button-management.service.ts @@ -0,0 +1,56 @@ +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; +import { PermissionNode } from "../utils/app.constants"; +import { HttpURIService } from "../app.http.uri.service"; +import { StorageService } from "../shared/services/storage.service"; + +@Injectable({ + providedIn: 'root' +}) +export class ButtonManagementService { + buttonPermissions: any = {}; + defaultPermission: any = {}; + + constructor(private httpService: HttpURIService, private storageService: StorageService){ + this.defaultPermissions().subscribe((data: PermissionNode[]) => { + this.defaultPermission = data; + this.storageService.setItem("defaultPermission", JSON.stringify(data)); + }); + + } + + traverse(nodes: any, isSuperAdmin: boolean) { + if (!Array.isArray(nodes)) return; + + nodes.forEach(node => { + // Check if the node has buttons + if (node.buttons && Array.isArray(node.buttons) && node.buttons.length > 0) { + // Create an object for this node's buttons + this.buttonPermissions[node.name] = {}; + // Map each button's name to its checked value + node.buttons.forEach((button:any) => { + this.buttonPermissions[node.name][button.name] = isSuperAdmin? true : button.checked; + }); + } + // Recursively traverse children + if (node.children && Array.isArray(node.children)) { + this.traverse(node.children, isSuperAdmin); + } + }); + } + + setButtonPermissions(permission: any, isSuperAdmin: boolean){ + if (isSuperAdmin){ + if (Object.keys(this.defaultPermission).length === 0){ + this.defaultPermission = JSON.parse(this.storageService.getItem("defaultPermission") || '{}'); + } + this.traverse(this.defaultPermission, isSuperAdmin); + } else{ + this.traverse(permission, isSuperAdmin); + } + } + + defaultPermissions(): Observable { + return this.httpService.requestGET('assets/data/sideMenu.json'); + } +} \ No newline at end of file diff --git a/src/app/services/credential.service.ts b/src/app/services/credential.service.ts new file mode 100644 index 0000000..446861d --- /dev/null +++ b/src/app/services/credential.service.ts @@ -0,0 +1,79 @@ +import { Injectable } from '@angular/core'; +import { StorageService } from '../shared/services/storage.service'; + +@Injectable( + { providedIn: 'root' } +) +export class CredentialService { + + private porOrgacode!: string; + private userId!: string; + private userType!: string; + private password!: string; + private token!: string; + private userHomevac!: string; + private permission: any[] = []; + + constructor(private storageService: StorageService){ + + } + + getToken(): string { + return this.token; + } + + setToken(token: string) { + this.token = token; + } + + getPorOrgacode(): string { + return this.porOrgacode; + } + + setPorOrgacode(porOrgacode: string) { + this.porOrgacode = porOrgacode + } + + getUserId(): string { + return this.userId + } + + setUserId(userId: string) { + this.userId = userId; + } + + getUserType(): string { + return this.userType + } + + setUserType(userType: string) { + this.userType = userType; + } + + getPassword(): string { + return this.password; + } + + setPassword(password: string) { + this.password = password; + } + + getPermission(): any[] { + return this.permission; + } + + setPermission(permission: any[]) { + this.permission = permission; + } + + + resetService() { + this.setPorOrgacode(""); + this.setUserId(""); + this.setUserType(""); + this.setPassword(""); + this.setToken(""); + this.setPermission([]); + } + +} \ No newline at end of file diff --git a/src/app/services/encryption.service.ts b/src/app/services/encryption.service.ts new file mode 100644 index 0000000..75e5dbc --- /dev/null +++ b/src/app/services/encryption.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@angular/core'; +import { AES, enc, pad} from 'crypto-js'; + +@Injectable({ + providedIn: 'root' +}) +export class EncryptionService { + + + constructor() { } + + encryptData(request: any): object { + const fromKey1: number = Math.floor(Math.random() * 5) + 1; + const fromKey2: number = Math.floor(Math.random() * 5) + 1; + const key1: string = this.generateRandomKey(16); + const key2: string = this.generateRandomKey(16); + + try { + let input: string; + if (typeof request === 'string') { + input = request; + } else { + input = JSON.stringify(request); + } + + // Encryption + const encrypted: string = AES.encrypt(input, enc.Utf8.parse(key1), { + iv: enc.Utf8.parse(key2), + padding: pad.Pkcs7 + }).toString(); + + const dataBeforeKey1 = encrypted.substring(0, fromKey1); + let encryptedDataSubstring = encrypted.substring(fromKey1); + const dataBeforeAfterKey2 = encryptedDataSubstring.substring(encryptedDataSubstring.length - fromKey2); + encryptedDataSubstring = encryptedDataSubstring.substring(0, encryptedDataSubstring.length - fromKey2); + const encryptedRequestData = `1${fromKey1}${dataBeforeKey1}${key1}${encryptedDataSubstring}${key2}${dataBeforeAfterKey2}${fromKey2}1`; + + const encryptedDataObject = { data: encryptedRequestData }; + return encryptedDataObject; + } catch (error) { + console.error(error); + return {}; + } + } + + generateRandomKey(length: number): string { + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let key = ''; + for (let i = 0; i < length; i++) { + key += characters.charAt(Math.floor(Math.random() * characters.length)); + } + return key; + } +} diff --git a/src/app/services/i18n.service.ts b/src/app/services/i18n.service.ts new file mode 100644 index 0000000..f634a1f --- /dev/null +++ b/src/app/services/i18n.service.ts @@ -0,0 +1,87 @@ +import { Injectable } from "@angular/core"; +import { TranslateService } from "@ngx-translate/core"; +import { BehaviorSubject, Observable } from "rxjs"; +import { MESSAGEKEY } from "../utils/enums"; +import { NotificationService } from "../shared/services/notification.service"; + +@Injectable( + { providedIn: 'root' } +) +export class I18NService { + + public translationsMerge: BehaviorSubject = new BehaviorSubject(false); + + constructor(private translate: TranslateService, private notificationService: NotificationService) { + + + } + + success(code: string, customMessage: any[], defaultText?: string): any { + let params = this.keyValueParamter(customMessage); + + this.getMessageTranslate(MESSAGEKEY.SUCCESS, code, params).subscribe((res) => { + this.notificationService.success((res[code] == code && defaultText != undefined) ? defaultText : res[code]); + }); + } + + error(code: string, customMessage: any[], defaultText?: string): any { + let params = this.keyValueParamter(customMessage); + this.getMessageTranslate(MESSAGEKEY.ERROR, code, params).subscribe((res) => { + this.notificationService.error((res[code] == code && defaultText != undefined) ? defaultText : res[code]); + }); + } + + info(code: string, customMessage: any[]): any { + let params = this.keyValueParamter(customMessage); + this.getMessageTranslate(MESSAGEKEY.INFO, code, params).subscribe((res) => { + this.notificationService.info(res[code]); + }); + } + + warn(code: string, customMessage: any[]): any { + let params = this.keyValueParamter(customMessage); + this.getMessageTranslate(MESSAGEKEY.WARN, code, params).subscribe((res) => { + this.notificationService.warning(res[code]); + }); + } + + notification(code: string, customMessage: any[]): string { + let params = this.keyValueParamter(customMessage); + let notification: string = ""; + this.getMessageTranslate(MESSAGEKEY.NOTIFICATION, code, params).subscribe((res) => { + notification = res[code] as string; + }); + return notification; + } + + + keyValueParamter(params: object[]): Object { + // Create an object to hold the key-value pairs + const keyValueObject: { [key: string]: string } = {}; + + // Populate the object + for (let i = 0; i < params?.length; i++) { + keyValueObject[`value${i + 1}`] = String(params[i]); + } + + return keyValueObject; + } + + getMessageTranslate(type: string, code: string, params: any): Observable { + if (type == MESSAGEKEY.SUCCESS) { + return this.translate.get([code, "SUC_APP_F_SUM"], params) + } else if (type == MESSAGEKEY.ERROR) { + return this.translate.get([code, "ERR_APP_F_SUM"], params) + } else if (type == MESSAGEKEY.WARN) { + return this.translate.get([code, "WRN_APP_F_SUM"], params) + } else if (type == MESSAGEKEY.INFO) { + return this.translate.get([code, "INF_APP_F_SUM"], params) + } else if (type == MESSAGEKEY.NOTIFICATION) { + return this.translate.get([code, "NTF_APP_F_SUM"], params) + } else { + return this.translate.get([code, code], params) + } + } + + +} \ No newline at end of file diff --git a/src/app/shared/components/header/header.component.ts b/src/app/shared/components/header/header.component.ts index 1d9e8e6..b6dd633 100644 --- a/src/app/shared/components/header/header.component.ts +++ b/src/app/shared/components/header/header.component.ts @@ -3,8 +3,8 @@ import { ActivatedRoute, Router } from '@angular/router'; import { SidebarService } from '../../../services/sidebar.service'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { StorageService } from '../../services/storage.service'; -import { AuthService } from '../../../services/auth.service'; import { isPlatformBrowser, formatDate, CommonModule } from '@angular/common'; +import { AuthenticationService } from '../../../services/authenticate.service'; @Component({ selector: 'app-header', @@ -34,12 +34,10 @@ export class HeaderComponent { vacName: any; allVacs: any; constructor( - private router: Router, public sidebarService: SidebarService, - private translate: TranslateService, @Inject(PLATFORM_ID) private platformId: Object, private storageService: StorageService, - public authService: AuthService + public authService: AuthenticationService ) { this.isDropdownVisible = false; this.isVacDropdownVisible = false; diff --git a/src/app/shared/components/notifications/notifications.component.html b/src/app/shared/components/notifications/notifications.component.html new file mode 100644 index 0000000..09559e0 --- /dev/null +++ b/src/app/shared/components/notifications/notifications.component.html @@ -0,0 +1,4 @@ +
+ {{ notification.message }} + +
\ No newline at end of file diff --git a/src/app/shared/components/notifications/notifications.component.scss b/src/app/shared/components/notifications/notifications.component.scss new file mode 100644 index 0000000..6444de6 --- /dev/null +++ b/src/app/shared/components/notifications/notifications.component.scss @@ -0,0 +1,88 @@ +.notification { + position: fixed; + top: 20px; + right: 20px; + max-width: 400px; + padding: 16px 48px 16px 20px; + border-radius: 6px; + z-index: 10000; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + background-color: #ffffff; + color: #333333; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-size: 14px; + line-height: 1.4; + transition: opacity 0.4s ease, transform 0.3s ease; + animation: fadeIn 0.3s ease forwards; + border-left: 8px solid #c9c9c9; + display: flex; + align-items: center; + justify-content: space-between; + + &.success { + background-color: #e8f5e9; + border-color: #4caf50; + color: #2e7d32; + } + + &.error { + background-color: #ffebee; + border-color: #f44336; + color: #c62828; + } + + &.warning { + background-color: #fffde7; + border-color: #ffeb3b; + color: #fbc02d; + } + + &.info { + background-color: #e3f2fd; + border-color: #2196f3; + color: #1976d2; + } + + &.fade-out { + animation: fadeOut 0.4s ease forwards; + } +} + +.close-btn { + position: absolute; + top: 12px; + right: 16px; + width: 20px; + height: 20px; + background: none; + border: none; + font-size: 20px; + color: inherit; + cursor: pointer; + transition: color 0.3s; + line-height: 1; + font-weight: bold; + + &:hover { + color: #000000; + } +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateX(100px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes fadeOut { + to { + opacity: 0; + transform: translateX(100px); + } +} \ No newline at end of file diff --git a/src/app/shared/components/notifications/notifications.component.spec.ts b/src/app/shared/components/notifications/notifications.component.spec.ts new file mode 100644 index 0000000..108e3f2 --- /dev/null +++ b/src/app/shared/components/notifications/notifications.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NotificationsComponent } from './notifications.component'; + +describe('NotificationsComponent', () => { + let component: NotificationsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NotificationsComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(NotificationsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/notifications/notifications.component.ts b/src/app/shared/components/notifications/notifications.component.ts new file mode 100644 index 0000000..4608fe9 --- /dev/null +++ b/src/app/shared/components/notifications/notifications.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; +import { Observable } from 'rxjs'; +import { NotificationService } from '../../services/notification.service'; +import { CommonModule } from '@angular/common'; + +@Component({ + selector: 'app-notifications', + imports: [CommonModule], + templateUrl: './notifications.component.html', + styleUrl: './notifications.component.scss' +}) +export class NotificationsComponent { +notifications$: Observable<{ type: string; message: string; } | null> + + constructor(private notificationService: NotificationService) { + this.notifications$ = this.notificationService.notifications$; + } + clearNotification() { + this.notificationService.clearNotification(); + } +} diff --git a/src/app/shared/guards/authentication.guard.ts b/src/app/shared/guards/authentication.guard.ts new file mode 100644 index 0000000..2e21bbe --- /dev/null +++ b/src/app/shared/guards/authentication.guard.ts @@ -0,0 +1,40 @@ +import { LocationStrategy } from '@angular/common'; +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { AuthenticationResponse } from '../../authenticate/authenticate'; +import { AuthenticationService } from '../../services/authenticate.service'; +import { CredentialService } from '../../services/credential.service'; +import { FormConstants } from '../../utils/enums'; +import { ButtonManagementService } from '../../services/button-management.service'; + + +@Injectable( + { providedIn: 'root' } +) +export class AuthenticationGuard implements CanActivate { + + constructor(private router: Router, private authService: AuthenticationService, private location: LocationStrategy, private credentialService: CredentialService,private buttonManagementService: ButtonManagementService) { } + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { + if (typeof window !== 'undefined' && window.localStorage) { + let data = JSON.parse(window.localStorage.getItem('user') || '{}') as AuthenticationResponse; + let permission = JSON.parse(window.localStorage.getItem('permission') || '[]'); + if (this.authService.isAuthenticated()) { + this.credentialService.setPorOrgacode(window.localStorage.getItem(FormConstants.POR_ORGACODE) || ''); + this.credentialService.setUserId(window.localStorage.getItem(FormConstants.USER_ID) || ''); + this.credentialService.setPassword(window.localStorage.getItem(FormConstants.PASSWORD) || ''); + this.credentialService.setToken(data.token); + this.credentialService.setUserType(data.userType); + this.credentialService.setPermission(permission); + this.buttonManagementService.setButtonPermissions(this.credentialService.getPermission(), this.authService.isSuperAdminUser()); + this.authService.onAuthenticationComplete.next(true); + return true; + } else { + this.authService.logout(); + return false; + } + } + return false; + } + +} \ No newline at end of file diff --git a/src/app/shared/guards/gauth.guard.ts b/src/app/shared/guards/gauth.guard.ts deleted file mode 100644 index 92e1e66..0000000 --- a/src/app/shared/guards/gauth.guard.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; -import { CanActivate, Router } from '@angular/router'; -import { isPlatformBrowser } from '@angular/common'; - -import { AuthService } from '../../services/auth.service'; - - -@Injectable({ - providedIn: 'root' -}) -export class GAuth implements CanActivate { - - constructor(private authservice: AuthService, private router: Router, @Inject(PLATFORM_ID) private platformId: object) { } - - canActivate() { - if (this.authservice.IsLoggedIn()){ - return true; - } - else { - if(isPlatformBrowser(this.platformId)) - this.router.navigate(['login']); - return false; - } - } - -} diff --git a/src/app/shared/interceptors/auth.interceptor.ts b/src/app/shared/interceptors/auth.interceptor.ts index b9d139e..4f9e110 100644 --- a/src/app/shared/interceptors/auth.interceptor.ts +++ b/src/app/shared/interceptors/auth.interceptor.ts @@ -1,113 +1,133 @@ import { Injectable, Injector } from '@angular/core'; import {HttpRequest,HttpHandler,HttpEvent,HttpInterceptor,HttpErrorResponse} from '@angular/common/http'; import { BehaviorSubject, Observable, throwError } from 'rxjs'; -import { AuthService } from '../../services/auth.service'; -import { catchError, filter, switchMap, take, timeout } from 'rxjs/operators'; -import { MiscService } from '../services/misc.service'; -import { ServerException } from '../../services/app.server.response'; +import { catchError, filter, switchMap, take } from 'rxjs/operators'; import { ErrorMessages } from '../../utils/enums'; +import { environment } from '../../../environments/environment'; +import { CredentialService } from '../../services/credential.service'; +import { EncryptionService } from '../../services/encryption.service'; +import { NotificationService } from '../services/notification.service'; +import { I18NService } from '../../services/i18n.service'; +import { AuthenticationService } from '../../services/authenticate.service'; @Injectable() export class AuthInterceptor implements HttpInterceptor { - private isRefreshing = false; - token: any; - private refreshTokenSubject: BehaviorSubject = new BehaviorSubject(null); - private logoutChannel = new BroadcastChannel('logout_channel'); - - constructor(private injector: Injector,private authService:AuthService) {} + private isRefreshing = false; + private refreshTokenSubject: BehaviorSubject = new BehaviorSubject(null); + private enableEncryption = environment.enableEncryption; + constructor(private injector: Injector, private credentialService: CredentialService, private encryptionService: EncryptionService, private notificationService: NotificationService, private i18nService: I18NService) {} intercept(request: HttpRequest, handler: HttpHandler): Observable> { - this.logoutChannel.onmessage = async (event) => { - if (event.data === 'logout') { - localStorage.clear(); - window.location.href = '/#/auth'; - } - }; - if (this.authService.getToken()) { - request = this.addToken(request, this.authService.getToken()); + if (this.credentialService.getPorOrgacode()!= undefined){ + request = this.setDefaultHeaders(request); + } + // FOR BIOMETRIC SECUGEN WE BYPASS THESE URIS AS SECUGEN DRIVERS IS USING LOCAL ENDPOINTS. + if (this.credentialService.getToken()&& !request.url.endsWith("/SGIFPCapture")&& !request.url.endsWith("/CreateTemplate")&& !request.url.endsWith("/verifyUserBiometric")) { + request = this.addToken(request, this.credentialService.getToken()); + } + if(this.enableEncryption && (request.method === "POST" || request.method === "PATCH" ) && !request.url.endsWith("/SGIFPCapture")&& !request.url.endsWith("/createTemplate")&& !request.url.endsWith("/verifyUserBiometric")) + { + request = this.setEncryptionHeader(request); + request = this.encryptRequestBody(request); } return handler.handle(request).pipe(catchError(error => { - if (error instanceof HttpErrorResponse && error.status === 401) { - return this.handleAuthError(request, handler); - } else { - this.handleServerError(error); - return throwError(error); - } - }))as Observable>; - } - private handleAuthError(request: HttpRequest, handler: HttpHandler) { - if (!this.isRefreshing) { - this.isRefreshing = true; - this.refreshTokenSubject.next(null); - let authService: AuthService = this.injector.get(AuthService); - return authService.refreshToken().pipe( - switchMap((response: any) => { - this.isRefreshing = false; - this.refreshTokenSubject.next(response.token); - return handler.handle(this.addToken(request, response.token)).pipe(catchError(error => { + if (error instanceof HttpErrorResponse && error.status === 401) { + return this.handleAuthError(request, handler); + } else { this.handleServerError(error); return throwError(error); - })); - })); + } + })); + +} + +private encryptRequestBody(request: HttpRequest): HttpRequest { + if (Object.keys(request.body).length > 0) { + const encryptedData: object = this.encryptionService.encryptData(request.body); + const encryptedRequest: any = request.clone({ body: encryptedData }); + return encryptedRequest; + } + return request; +} + +private handleAuthError(request: HttpRequest, handler: HttpHandler) { + if (!this.isRefreshing) { + this.isRefreshing = true; + this.refreshTokenSubject.next(null); + let authService: AuthenticationService = this.injector.get(AuthenticationService); + return authService.refreshToken().pipe( + switchMap((response: any) => { + this.isRefreshing = false; + this.refreshTokenSubject.next(response.token); + return handler.handle(this.addToken(request, response.token)).pipe(catchError(error => { + this.handleServerError(error); + return throwError(error); + })); + })); } else { - return this.refreshTokenSubject.pipe( - filter(token => token != null), - take(1), - switchMap(token => { - return handler.handle(this.addToken(request, token)); - })); + return this.refreshTokenSubject.pipe( + filter(token => token != null), + take(1), + switchMap(token => { + return handler.handle(this.addToken(request, token)); + })); } - } - private handleServerError(error: HttpErrorResponse) { +} + +private handleServerError(error: HttpErrorResponse) { let url: string = error.url as string; let moduleName: string = ""; - if (url != null && url != undefined) { - moduleName = url.split(':').length > 2 ? - url.split(':')[2].split('/')[1] : - url.split('/')[3]; + if (url != null && url != undefined) { + moduleName = url.split(':').length>2 ? + url.split(':')[2].split('/')[1]: + url.split('/')[3]; } - let authService: AuthService = this.injector.get(AuthService); - let miscService: MiscService = this.injector.get(MiscService); + let authService: AuthenticationService = this.injector.get(AuthenticationService); switch (error.status) { - case 400: - let errorResponse: ServerException = error as ServerException; - if (errorResponse.error && errorResponse.error.errorCode != null) { - errorResponse.error.arguments.forEach((argument, index) => { - if (miscService.getErrorMessageTranslation(argument) != argument) { - errorResponse.error.arguments[index] = miscService.getErrorMessageTranslation(argument); + case 400: + let errorResponse:any = error ; + if (errorResponse.error && errorResponse.error.errorCode != null) { + this.i18nService.error(errorResponse.error.errorCode, errorResponse.error.arguments); + } else { + this.i18nService.error(ErrorMessages.BAD_REQUEST,[moduleName.toUpperCase()]); } - }); - miscService.handleError(errorResponse.error.errorCode); - } else { - miscService.handleError(ErrorMessages.BAD_REQUEST, [moduleName.toUpperCase()]); - } - break; - - case 401: - miscService.handleError(ErrorMessages.UNAUTHORIZED_REQUEST,[error.error.message]); - authService.logout(); - break; + break; + case 401: + this.i18nService.error(ErrorMessages.UNAUTHORIZED_REQUEST,[]); + authService.logout(); + break; - case 403: - miscService.handleError(ErrorMessages.FORBIDDEN_REQUEST,[]); - authService.logout(); - break; + case 403: + this.i18nService.error(ErrorMessages.FORBIDDEN_REQUEST,[]); + authService.logout(); + break; - case 500: - miscService.handleError(ErrorMessages.INTERNAL_SERVER_ERROR,[]); - break; + case 500: + this.i18nService.error(ErrorMessages.INTERNAL_SERVER_ERROR,[moduleName.toUpperCase()]); + break; - case 0: - miscService.handleError(ErrorMessages.CONNECTION_ERROR,[]); - break; + case 0: + this.i18nService.error(ErrorMessages.CONNECTION_ERROR,[moduleName.toUpperCase()]); + break; } - } - private addToken(request: HttpRequest, token: string) { +} + +private addToken(request: HttpRequest, token: string) { return request.clone({ - setHeaders: { - 'Authorization': `Bearer ${token}` - } + setHeaders: { + 'Authorization': `Bearer ${token}` + } }); - } +} + +private setDefaultHeaders(request: HttpRequest): HttpRequest { + const modifiedHeaders = request.headers.set('userId', this.credentialService.getUserId()) + .append('porOrgacode', this.credentialService.getPorOrgacode()) + return request.clone({ headers: modifiedHeaders }); +} +private setEncryptionHeader(request: HttpRequest): HttpRequest { + const modifiedHeaders = request.headers.set('X-Encrypted', this.enableEncryption.toString()); + return request.clone({ headers: modifiedHeaders }); +} } diff --git a/src/app/shared/services/http.service.ts b/src/app/shared/services/http.service.ts index 75f6b18..b778c1e 100644 --- a/src/app/shared/services/http.service.ts +++ b/src/app/shared/services/http.service.ts @@ -1,192 +1,108 @@ +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { CONSTANTS } from '../../utils/app.constants'; -import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http'; -import { MiscService } from './misc.service'; -import { TranslateService } from '@ngx-translate/core'; -import { environment } from '../../../environments/environment'; -import { APP_URL_KEY } from '../../utils/enums'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { FormConstants, HiddenValues } from '../../utils/enums'; -@Injectable({ - providedIn: 'root' -}) +@Injectable( + { providedIn: 'root' } +) export class HttpService { - - API_PATH = environment.apiPath.get(APP_URL_KEY.API_PATH) + private loadingSubject = new BehaviorSubject(false); + loading$ = this.loadingSubject.asObservable(); - constructor(private translateService: TranslateService, - private miscService: MiscService, private http: HttpClient - ) { } - - getRequest(url: string, queryParams?: HttpParams, pathParams?: any, showLoader = true, options?: any) { - - const custId: any = localStorage.getItem("userId"); - let headers = new HttpHeaders().set("Content-Type", "application/json"); - - const authorization = "Bearer " + localStorage.getItem('token'); - headers = headers.set("Authorization", authorization); - headers = headers.set("cmpUserId", custId); - let apiUrl = this.API_PATH + url; - this.miscService.showLoader(); - apiUrl = this.replaceParamsWithValues(apiUrl, pathParams); - - try { - let response = this.http.get(apiUrl, { headers: headers }); - - if (!(response instanceof HttpErrorResponse)) { - if (showLoader) { - this.miscService.hideLoader(); - } - return response; - } - else { - this.miscService.handleError(this.translateService.instant('ServerError')); - return null; - } - } - catch (e) { - console.log(e); - return null; + private setLoading(loading: boolean) { + this.loadingSubject.next(loading); } - } + + constructor(private http: HttpClient) { - postRequest(url: string, data?: any, pathParams?: any, isMultipart = false, showLoader = true) { - const custId = localStorage.getItem("userId"); - let headers = new HttpHeaders().set("Content-Type", "application/json"); - let apiUrl = this.API_PATH + url; - this.miscService.showLoader(); - headers = headers.set("Authorization", "Bearer " + localStorage.getItem('token')); - if (custId != null) { - headers = headers.set("cmpUserId", custId); } - apiUrl = this.replaceParamsWithValues(apiUrl, pathParams); - let formData = data; - formData = this.setCommonParams(formData); - if (isMultipart) { - formData = this.convertToFormData(data); - } - try { - let response = this.http.post(apiUrl, formData, { headers: headers }); - - if (!(response instanceof HttpErrorResponse)) { - if (showLoader) { - this.miscService.hideLoader(); + requestPOST(url: string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable { + this.setLoading(true); + if (headers == undefined) { + headers = new HttpHeaders().set('Content-Type', 'application/json'); } - return response; - } - else { - this.miscService.handleError(this.translateService.instant('ServerError')); - return null; - } - } - catch (e) { - console.log(e); - return null; - } - } - - putRequest(url: string, data: any, pathParams?: any, isMultipart = false, showLoader = true) { - const custId: any = localStorage.getItem("userId"); - let headers = new HttpHeaders().set("Content-Type", "application/json"); - headers = headers.set("cmpUserId", custId); - headers = headers.set("Authorization", "Bearer " + localStorage.getItem('token')); - let apiUrl = this.API_PATH + url; - apiUrl = this.replaceParamsWithValues(apiUrl, pathParams); - this.miscService.showLoader(); - let formData = data; - formData = this.setCommonParams(formData); - if (isMultipart) { - formData = this.convertToFormData(data); - } - - try { - let response = this.http.put(apiUrl, formData, { headers: headers }); - - if (!(response instanceof HttpErrorResponse)) { - if (showLoader) { - this.miscService.hideLoader(); + headers = headers.set(FormConstants.POR_ORGACODE, HiddenValues.POR_ORGACODE).set(FormConstants.CHANNEL_CODE, HiddenValues.CHANNEL_CODE) + if (params == undefined) { + return this.http.post(url, body, { headers: headers }) + } else { + url = this.substituePathVariables(url, params); + return this.http.post(url, body, { params: params, headers: headers }); } - return response; - } - else { - this.miscService.handleError(this.translateService.instant('ServerError')); - return null; - } } - catch (e) { - console.log(e); - return null; + requestPOSTMultipart(url: string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable { + this.setLoading(true); + if (headers == undefined) { + headers = new HttpHeaders(); + } + headers = headers.set(FormConstants.POR_ORGACODE, HiddenValues.POR_ORGACODE).set(FormConstants.CHANNEL_CODE, HiddenValues.CHANNEL_CODE) + if (params == undefined) { + return this.http.post(url, body, { headers: headers }) + } else { + url = this.substituePathVariables(url, params); + return this.http.post(url, body, { params: params, headers: headers }); + } } - } - - deleteRequest(url: any, data: any, pathParams?: any, isMultipart = false, showLoader = true) { - const custId: any = localStorage.getItem("userId"); - let apiUrl = this.API_PATH + url; - let headers = new HttpHeaders().set("Content-Type", "application/json"); - headers = headers.set("Authorization", "Bearer " + localStorage.getItem('token')); - headers = headers.set("cmpUserId", custId); - apiUrl = this.replaceParamsWithValues(apiUrl, pathParams); + requestGET(url: string, reqParams?: HttpParams): Observable { + this.setLoading(true); + let httpHeaders: HttpHeaders = new HttpHeaders(); + httpHeaders = httpHeaders.set(FormConstants.POR_ORGACODE,HiddenValues.POR_ORGACODE).set(FormConstants.CHANNEL_CODE,HiddenValues.CHANNEL_CODE); + if (reqParams == undefined) { + return this.http.get(url, { headers: httpHeaders }) + } else { + url = this.substituePathVariables(url, reqParams); + return this.http.get(url, { params: reqParams, headers: httpHeaders }); + } - this.miscService.showLoader(); - let formData = data; - formData = this.setCommonParams(formData); - if (isMultipart) { - formData = this.convertToFormData(data); } - try { - let response = this.http.delete(apiUrl, { headers: headers, body: formData }); - - if (!(response instanceof HttpErrorResponse)) { - if (showLoader) { - this.miscService.hideLoader(); + requestDELETE(url: string, reqParams: HttpParams): Observable { + this.setLoading(true); + url = this.substituePathVariables(url, reqParams); + let httpHeaders: HttpHeaders = new HttpHeaders().set(FormConstants.POR_ORGACODE,HiddenValues.POR_ORGACODE).set(FormConstants.CHANNEL_CODE,HiddenValues.CHANNEL_CODE); + if (reqParams.keys().length > 0) { + return this.http.delete(url, { params: reqParams, headers: httpHeaders }); + } + else { + return this.http.delete(url, { headers: httpHeaders }); } - return response; - } - else { - this.miscService.handleError(this.translateService.instant('ServerError')); - return null; - } - } - catch (e) { - console.log(e); - return null; } - } - addQueryParamsToUrl(url: any, queryParams: any) { - if (queryParams && Object.keys(queryParams).length > 0) { - let toReturn = url + '?'; - Object.keys(queryParams).forEach((key, index, arr) => { - toReturn += `${key}=${queryParams[key]}`; - toReturn += index === arr.length - 1 ? '' : '&'; - }); - return toReturn; + + requestPATCH(url: string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable { + this.setLoading(true); + if (headers == undefined) { + headers = new HttpHeaders().set('Content-Type', 'application/json'); + } + if (params != undefined) { + url = this.substituePathVariables(url, params); + } + headers = headers.set(FormConstants.POR_ORGACODE,HiddenValues.POR_ORGACODE).set(FormConstants.CHANNEL_CODE, HiddenValues.CHANNEL_CODE); + return this.http.patch(url, body, { headers: headers, params: params }); } - return url; - } - convertToFormData(data: any) { - const formData = new FormData(); - Object.keys(data).forEach((k) => { - formData.append(k, data[k]); - }); - return formData; - } - replaceParamsWithValues(url: any, data: any) { - data = data ?? {}; - data['porOrgacode'] = CONSTANTS.POR_ORGACODE; - if (data && Object.keys(data).length > 0) { - Object.keys(data).forEach((k) => { - url = url.replace(`{${k}}`, data[k]); - }); + requestPUT(url: string, body: any, headers?: HttpHeaders, params?: HttpParams): Observable { + this.setLoading(true); + if (headers == undefined) { + headers = new HttpHeaders().set('Content-Type', 'application/json'); + } + if (params != undefined) { + url = this.substituePathVariables(url, params); + } + headers = headers.set(FormConstants.POR_ORGACODE,HiddenValues.POR_ORGACODE).set(FormConstants.CHANNEL_CODE, HiddenValues.CHANNEL_CODE); + return this.http.put(url, body, { headers: headers, params: params }); } - return url; - } - setCommonParams(data: any = {}) { - data = data ?? {}; - data['porOrgacode'] = CONSTANTS.POR_ORGACODE; - return data; - } -} + private substituePathVariables(url: string, params: HttpParams): string { + params.keys().forEach(param => { + let pathVariable: string = `{${param}}`; + let pathValue = params.get(param); + if (url.includes(pathVariable) && pathValue != null) { + url = url.replace(pathVariable, pathValue); + params.delete(param); + } + }); + return url; + } +} \ No newline at end of file diff --git a/src/app/shared/services/notification.service.ts b/src/app/shared/services/notification.service.ts new file mode 100644 index 0000000..56ca38b --- /dev/null +++ b/src/app/shared/services/notification.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, timer } from 'rxjs'; +import { switchMap, startWith } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class NotificationService { + private notificationSubject = new BehaviorSubject<{ type: string, message: string } | null>(null); + notifications$ = this.notificationSubject.asObservable(); + + success(message: string) { + this.notify('success', 'Success: ' + message); + } + + error(message: string) { + this.notify('error', 'Error: ' + message); + } + + warning(message: string) { + this.notify('warning', 'Warning: ' + message); + } + + info(message: string) { + this.notify('info', 'Info: ' + message); + } + + private notify(type: string, message: string) { + this.notificationSubject.next({ type, message }); + + // Automatically clear notification after 3 seconds using RxJS timer + this.notifications$.pipe( + startWith(null), + switchMap(() => timer(5000)) + ).subscribe(() => this.notificationSubject.next(null)); + } + clearNotification() { + this.notificationSubject.next(null); + } +} diff --git a/src/app/utils/app.constants.ts b/src/app/utils/app.constants.ts index cab96e5..57635f7 100644 --- a/src/app/utils/app.constants.ts +++ b/src/app/utils/app.constants.ts @@ -7,3 +7,11 @@ export const pageSizeOptions = [ { label: '10 items', value: 10 }, { label: '20 items', value: 20 } ]; + +export interface PermissionNode { + name: string; + checked: boolean; + expanded: boolean; + children?: PermissionNode[]; + buttons?: PermissionNode[]; + } diff --git a/src/app/utils/enums.ts b/src/app/utils/enums.ts index 076b4cc..961aa22 100644 --- a/src/app/utils/enums.ts +++ b/src/app/utils/enums.ts @@ -4,6 +4,7 @@ export enum ErrorMessages{ BAD_REQUEST = "ERR_APP_B_0003", FORBIDDEN_REQUEST = "ERR_APP_B_0004", UNAUTHORIZED_REQUEST = "ERR_APP_B_0005", + ALREADY_LOGGED_IN = "ALREADY_LOGGED_IN", } export enum supportedLanguages{ @@ -24,4 +25,45 @@ export enum selectedGatewayType{ SYRIATEL = 'Syriatel', TWILIO = 'Twilio', JAZZ = 'Jazz' +} + +export enum FormConstants{ + POR_ORGACODE = "POR_ORGACODE", + USER_ID = "USER_ID", + PASSWORD = "PASSWORD", + CHANNEL_CODE = "CHANNEL_CODE" +} + +export enum HiddenValues { + POR_ORGACODE = "0005", + CHANNEL_CODE = "01", + ORGANIZATION_USER = "O", + VAC_USER = "V", + SUPERADMIN_USER = "S", + DEFAULT_PASSWORD = "12345678", + REVOLVING_FUND_PRODUCT = "101", + INTERNAL_LENDING_PRODUCT = "102", + REVOLVING = "R", + CREDIT = "C", + CASH_GL = "11100001" +} + +export enum SuccessMessages { + +SAVED_SUCESSFULLY = "SUC_APP_F_0001", +LOGIN_SUCCESSFULLY = "LOGIN_SUCCESSFULLY", +TRANSACTION_SUCCESSFUL = "TRANSACTION_SUCCESSFUL", +SAVED_SUCCESSFULLY = "SAVED_SUCCESSFULLY", +RECORD_DELETED_SUCCESSFULY = "RECORD_DELETED_SUCCESSFULY", +ACCOUNT_CLOSED_SUCCESSFULLY = "ACCOUNT_CLOSED_SUCCESSFULLY", +SUCCESS_MESSAGE = "SUCCESS_MESSAGE" +} + +export enum MESSAGEKEY { + SUCCESS = "SUCCESS", + ERROR = "ERROR", + WARN = "WARN", + INFO = "INFO", + NOTIFICATION = "NOTIFICATION", + FORM = "FORM" } \ No newline at end of file diff --git a/src/app/utils/uri-enums.ts b/src/app/utils/uri-enums.ts new file mode 100644 index 0000000..87b31be --- /dev/null +++ b/src/app/utils/uri-enums.ts @@ -0,0 +1,5 @@ + +export enum URIKey { + USER_LOGIN_URI = "USER_LOGIN_URI", + USER_REFRESH_TOKEN = "USER_REFRESH_TOKEN" +} \ No newline at end of file diff --git a/src/assets/data/app.uri.json b/src/assets/data/app.uri.json new file mode 100644 index 0000000..6442eac --- /dev/null +++ b/src/assets/data/app.uri.json @@ -0,0 +1,25 @@ +[ + { + "Id": "ACONNECT_DOMAIN_URI", + "URI": "", + "Modules": [ + { + "Id": "ACONNECT_URI", + "URI": "/aconnect", + "Pages": [ + { + "Id": "ENTITY_USER_LOGIN_URI", + "URI": "/authentication/login", + "UUID": "USER_LOGIN_URI" + }, + { + "Id": "ENTITY_USER_REFRESH_TOKEN", + "URI": "/refreshtoken", + "UUID": "USER_REFRESH_TOKEN" + } + ] + } + ] + } + +] \ No newline at end of file diff --git a/src/assets/i18n/Arabic.json b/src/assets/i18n/Arabic.json index 62bcfcf..f01d0ec 100644 --- a/src/assets/i18n/Arabic.json +++ b/src/assets/i18n/Arabic.json @@ -212,5 +212,7 @@ "totalItems": "السجلات", "record": "سِجِلّ", "previous": "سابق", - "next": "التالي" + "next": "التالي", + "LOGIN_SUCCESSFULLY":"تم تسجيل الدخول بنجاح", + "ALREADY_LOGGED_IN": "المستخدم مسجل دخوله بالفعل" } \ No newline at end of file diff --git a/src/assets/i18n/English.json b/src/assets/i18n/English.json index 23d2076..35b7bcb 100644 --- a/src/assets/i18n/English.json +++ b/src/assets/i18n/English.json @@ -211,5 +211,7 @@ "totalItems": "Records", "record": "Record", "previous": "Previous", - "next": "Next" + "next": "Next", + "LOGIN_SUCCESSFULLY":"Login SucessFully", + "ALREADY_LOGGED_IN": "User Already Logged In" } \ No newline at end of file diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index ff7051f..758cb57 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -3,7 +3,8 @@ export const environment = { versionNumber: '1.0.0.0', buildNumber: '1.0', buildDate: '27-11-2025', - apiPath: new Map([ - ["API_PATH", "http://localhost:8080/aconnect"] + enableEncryption: true, + moduleHost: new Map([ + ["ACONNECT_DOMAIN_URI", "http://localhost:8080"] ]) }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 18dfbfe..c22709a 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -3,7 +3,8 @@ export const environment = { versionNumber: '1.0.0.0', buildNumber: '1.0', buildDate: '27-11-2025', - apiPath: new Map([ - ["API_PATH", "http://localhost:8080/aconnect"] + enableEncryption: false, + moduleHost: new Map([ + ["ACONNECT_DOMAIN_URI", "http://localhost:8080"] ]) }; -- 2.32.0 From 1c0b19e33c1ab829e50235973f4ffcc6e462c52b Mon Sep 17 00:00:00 2001 From: atif118-mfsys Date: Wed, 24 Dec 2025 17:11:37 +0500 Subject: [PATCH 2/3] added activity guard and added some translations --- src/app/app.routes.ts | 15 ++++++ src/app/services/authenticate.service.ts | 8 ---- src/app/shared/guards/activity.guard.ts | 59 ++++++++++++++++++++++++ src/app/utils/enums.ts | 11 +++-- src/assets/i18n/Arabic.json | 8 +++- src/assets/i18n/English.json | 8 +++- 6 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 src/app/shared/guards/activity.guard.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 6038e1b..3a9c3b2 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -3,6 +3,7 @@ import { LoginComponent } from './authenticate/login/login.component'; import { ChangePasswordComponent } from './user-management/change-password/change-password.component'; import { FullLayoutComponent } from './full-layout/full-layout.component'; import { AuthenticationGuard } from './shared/guards/authentication.guard'; +// import { ActivityGuard } from './shared/guards/activity.guard'; export const routes: Routes = [ { @@ -37,6 +38,9 @@ export const routes: Routes = [ }, { path: 'permissions', + // will need this guard in future when permissions are implemented. + // commenting them for now. + // canActivate: [ActivityGuard], loadComponent: () => import('./user-permissions/user-permissions.component').then( m => m.UserPermissionsComponent @@ -44,6 +48,7 @@ export const routes: Routes = [ }, { path: 'smsLogger', + // canActivate: [ActivityGuard], loadComponent: () => import('./sms-banking/sms-banking.component').then( m => m.SmsBankingComponent @@ -51,6 +56,7 @@ export const routes: Routes = [ }, { path: 'smsGateway', + // canActivate: [ActivityGuard], loadComponent: () => import('./sms-gateway/sms-gateway.component').then( m => m.SmsGatewayComponent @@ -58,6 +64,7 @@ export const routes: Routes = [ }, { path: 'loggerManager', + // canActivate: [ActivityGuard], loadComponent: () => import('./logging/logging.component').then( m => m.LoggingComponent @@ -65,6 +72,7 @@ export const routes: Routes = [ }, { path: 'analysis', + // canActivate: [ActivityGuard], loadComponent: () => import('./data-analysis/data-analysis.component').then( m => m.DataAnalysisComponent @@ -72,6 +80,7 @@ export const routes: Routes = [ }, { path: 'ibUnblockUser', + // canActivate: [ActivityGuard], loadComponent: () => import('./ib-support/ib-unblock-user/ib-unblock-user.component').then( m => m.IbUnblockUserComponent @@ -79,6 +88,7 @@ export const routes: Routes = [ }, { path: 'feedbackSetup', + // canActivate: [ActivityGuard], loadComponent: () => import('./ib-support/feedback-setup/feedback-setup.component').then( m => m.FeedbackSetupComponent @@ -86,6 +96,7 @@ export const routes: Routes = [ }, { path: 'purposeSetup', + // canActivate: [ActivityGuard], loadComponent: () => import('./ib-support/tran-purpose-setup/tran-purpose-setup.component').then( m => m.TranPurposeSetupComponent @@ -93,6 +104,7 @@ export const routes: Routes = [ }, { path: 'thirdPartyRegistration', + // canActivate: [ActivityGuard], loadComponent: () => import('./user-management/third-party-registration/third-party-registration.component').then( m => m.ThirdPartyRegistrationComponent @@ -100,6 +112,7 @@ export const routes: Routes = [ }, { path: 'setupUser', + // canActivate: [ActivityGuard], loadComponent: () => import('./user-management/setup-user/setup-user.component').then( m => m.SetupUserComponent @@ -107,6 +120,7 @@ export const routes: Routes = [ }, { path: 'resetPassword', + // canActivate: [ActivityGuard], loadComponent: () => import('./user-management/reset-password/reset-password.component').then( m => m.ResetPasswordComponent @@ -114,6 +128,7 @@ export const routes: Routes = [ }, { path: 'changePassword', + // canActivate: [ActivityGuard], loadComponent: () => import('./user-management/change-password/change-password.component').then( m => m.ChangePasswordComponent diff --git a/src/app/services/authenticate.service.ts b/src/app/services/authenticate.service.ts index bcee077..d766c40 100644 --- a/src/app/services/authenticate.service.ts +++ b/src/app/services/authenticate.service.ts @@ -79,14 +79,6 @@ export class AuthenticationService { return false; } - isOrganizartionUser(){ - if (this.storageService && this.storageService.getItem('user') != null) { - let cachedUser = JSON.parse(this.storageService.getItem('user') || '{}'); - return cachedUser.userType === HiddenValues.ORGANIZATION_USER; - } - return false; - } - isSuperAdminUser(){ if (this.storageService && this.storageService.getItem('user') != null) { let cachedUser = JSON.parse(this.storageService.getItem('user') || '{}'); diff --git a/src/app/shared/guards/activity.guard.ts b/src/app/shared/guards/activity.guard.ts new file mode 100644 index 0000000..1e85545 --- /dev/null +++ b/src/app/shared/guards/activity.guard.ts @@ -0,0 +1,59 @@ +import { LocationStrategy } from '@angular/common'; +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { AuthenticationService } from '../../services/authenticate.service'; +import { I18NService } from '../../services/i18n.service'; +import { ErrorMessages, FormConstants } from '../../utils/enums'; +import { CredentialService } from '../../services/credential.service'; + + +@Injectable( + { providedIn: 'root' } +) +export class ActivityGuard implements CanActivate { + + constructor(private router: Router, private authService: AuthenticationService, private i18nService: I18NService, private credentialService: CredentialService) { } + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { + if (typeof window !== 'undefined' && window.localStorage) { + let permissions = JSON.parse(window.localStorage.getItem('permission') || '[]'); + if (this.authService.isAuthenticated()) { + if (this.authService.isSuperAdminUser()){ + return true; + } + let routeLink = (state.url.split('?'))[0]; + if (this.isRouteAuthorized(routeLink, route.queryParams, permissions)) { + return true; + } + this.i18nService.error(ErrorMessages.ACCESS_DENIED, []); + window.localStorage.setItem('currentSubModule','dashboard'); + this.router.navigate(["/home/dashboard"]); + return false; + } else { + this.authService.logout(); + return false; + } + } + return false; + } + + isRouteAuthorized(routerLink: string, queryParams: any, permissions: any): boolean { + let routePermissions : any = {} + let permissionName : any = {} + permissions.forEach((permission: any) => { + routePermissions[permission.route] = permission.checked; + permissionName[permission.name] = permission.checked; + if(permission.children.length>0){ + permission.children.forEach((child: any)=>{ + routePermissions[child.route] = child.checked; + permissionName[child.name] = child.checked; + }) + } + }); + if(routePermissions[routerLink]){ + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/src/app/utils/enums.ts b/src/app/utils/enums.ts index 961aa22..f43ca1b 100644 --- a/src/app/utils/enums.ts +++ b/src/app/utils/enums.ts @@ -1,10 +1,11 @@ export enum ErrorMessages{ - INTERNAL_SERVER_ERROR = "ERR_APP_B_0001", - CONNECTION_ERROR = "ERR_APP_B_0002", - BAD_REQUEST = "ERR_APP_B_0003", - FORBIDDEN_REQUEST = "ERR_APP_B_0004", - UNAUTHORIZED_REQUEST = "ERR_APP_B_0005", + INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR", + CONNECTION_ERROR = "CONNECTION_ERROR", + BAD_REQUEST = "BAD_REQUEST", + FORBIDDEN_REQUEST = "FORBIDDEN_REQUEST", + UNAUTHORIZED_REQUEST = "UNAUTHORIZED_REQUEST", ALREADY_LOGGED_IN = "ALREADY_LOGGED_IN", + ACCESS_DENIED = "ACCESS_DENIED", } export enum supportedLanguages{ diff --git a/src/assets/i18n/Arabic.json b/src/assets/i18n/Arabic.json index f01d0ec..10d017a 100644 --- a/src/assets/i18n/Arabic.json +++ b/src/assets/i18n/Arabic.json @@ -214,5 +214,11 @@ "previous": "سابق", "next": "التالي", "LOGIN_SUCCESSFULLY":"تم تسجيل الدخول بنجاح", - "ALREADY_LOGGED_IN": "المستخدم مسجل دخوله بالفعل" + "ALREADY_LOGGED_IN": "المستخدم مسجل دخوله بالفعل", + "ACCESS_DENIED" : "تم الرفض", + "INTERNAL_SERVER_ERROR": "خطأ في الخادم الداخلي", + "CONNECTION_ERROR": "خطأ في الاتصال", + "BAD_REQUEST": "اقتراح غير جيد", + "FORBIDDEN_REQUEST": "طلب ممنوع", + "UNAUTHORIZED_REQUEST": "طلب غير مصرح به" } \ No newline at end of file diff --git a/src/assets/i18n/English.json b/src/assets/i18n/English.json index 35b7bcb..2fdac12 100644 --- a/src/assets/i18n/English.json +++ b/src/assets/i18n/English.json @@ -213,5 +213,11 @@ "previous": "Previous", "next": "Next", "LOGIN_SUCCESSFULLY":"Login SucessFully", - "ALREADY_LOGGED_IN": "User Already Logged In" + "ALREADY_LOGGED_IN": "User Already Logged In", + "ACCESS_DENIED" : "Access Denied", + "INTERNAL_SERVER_ERROR": "Internal Server Error", + "CONNECTION_ERROR": "Connection Error", + "BAD_REQUEST": "Bad Request", + "FORBIDDEN_REQUEST": "Forbidden Request", + "UNAUTHORIZED_REQUEST": "Unauthorized Request" } \ No newline at end of file -- 2.32.0 From 966f0d59bcf1b281eb1233c5c257fe5bef139ab2 Mon Sep 17 00:00:00 2001 From: atif118-mfsys Date: Wed, 24 Dec 2025 17:25:50 +0500 Subject: [PATCH 3/3] fixed and revamped first time change password screen logic --- .../components/side-nav/side-nav.component.html | 2 +- .../components/side-nav/side-nav.component.ts | 7 +------ .../change-password/change-password.component.html | 1 + .../change-password/change-password.component.ts | 14 +++++++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/app/shared/components/side-nav/side-nav.component.html b/src/app/shared/components/side-nav/side-nav.component.html index a4d7e2b..b8f4024 100644 --- a/src/app/shared/components/side-nav/side-nav.component.html +++ b/src/app/shared/components/side-nav/side-nav.component.html @@ -30,7 +30,7 @@
  • - + {{ 'changePassword' | translate }}
  • diff --git a/src/app/shared/components/side-nav/side-nav.component.ts b/src/app/shared/components/side-nav/side-nav.component.ts index eaa0d29..975a608 100644 --- a/src/app/shared/components/side-nav/side-nav.component.ts +++ b/src/app/shared/components/side-nav/side-nav.component.ts @@ -100,10 +100,5 @@ export class SideNavComponent { this.storageService.setItem('currentSubModule', lastRoutePart); } } - - navigateToChangePassword() { - this.router.navigate(['/home/changePassword'], { - state: { fromMenu: true } - }); -} + } diff --git a/src/app/user-management/change-password/change-password.component.html b/src/app/user-management/change-password/change-password.component.html index 32f643f..219142f 100644 --- a/src/app/user-management/change-password/change-password.component.html +++ b/src/app/user-management/change-password/change-password.component.html @@ -59,6 +59,7 @@ +
    diff --git a/src/app/user-management/change-password/change-password.component.ts b/src/app/user-management/change-password/change-password.component.ts index c0a1770..4eae8cc 100644 --- a/src/app/user-management/change-password/change-password.component.ts +++ b/src/app/user-management/change-password/change-password.component.ts @@ -41,12 +41,16 @@ passwordType2: string = 'password'; } ngOnInit(): void { - const fromMenu = history.state?.['fromMenu']; - if(fromMenu){ + this.checkIfFirstTimeChangePasswordOrNot(); + } + + checkIfFirstTimeChangePasswordOrNot(){ + let currentUser: any = JSON.parse(this.storageService.getItem('user')!) + if(currentUser.user.isFirstLogin){ + this.isFirstLogin = true; + } + else{ this.isFirstLogin = false; - }else{ - const firstLoginFlag = this.storageService.getItem('firstLogin'); - this.isFirstLogin = firstLoginFlag === 'true'; } } -- 2.32.0