diff --git a/app.config.ts b/app.config.ts index 665969215ae063def606833bc5bfb37f9110112f..dc39b0fc17bc3c32d8997ea8a6820efb85e0fd8d 100644 --- a/app.config.ts +++ b/app.config.ts @@ -9,21 +9,16 @@ import { ApplicationConfig } from '@angular/core'; import { provideProtractorTestingSupport } from '@angular/platform-browser'; import { provideRouter } from '@angular/router'; -import { - provideHttpClient, - withInterceptorsFromDi, -} from '@angular/common/http'; -import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { provideAnimations } from '@angular/platform-browser/animations'; import routeConfig from 'src/app/routes'; -import { DevAuthInterceptor } from 'src/app/core/interceptors/dev-auth-interceptor.service'; +import { devAuthInterceptor } from 'src/app/core/interceptors/dev-auth-interceptor.service'; export const appConfig: ApplicationConfig = { providers: [ provideProtractorTestingSupport(), provideRouter(routeConfig), provideAnimations(), - { provide: HTTP_INTERCEPTORS, useClass: DevAuthInterceptor, multi: true }, - provideHttpClient(withInterceptorsFromDi()), + provideHttpClient(withInterceptors([devAuthInterceptor])), ], }; diff --git a/src/app/core/config/api-config.ts b/src/app/core/config/api-config.ts index 61d8c994ad971303ca2ceee97b0f7a27df063243..e5521b928fb993736cb060b8cc4e61f1f3c49a8f 100644 --- a/src/app/core/config/api-config.ts +++ b/src/app/core/config/api-config.ts @@ -35,13 +35,13 @@ export const apiConfig = { urlGetSolutionDescription: (revisionId: string, selectedCatalogId: string) => `/api/solution/revision/${revisionId}/${selectedCatalogId}/description`, urlGetSolutionRatings: '/api/solutions/ratings/', - urlPreferredTag: 'api/preferredTags', - urlCreateFavorite: 'api/solution/createFavorite', - urlDeleteFavorite: 'api/solution/deleteFavorite', + urlPreferredTag: '/api/preferredTags', + urlCreateFavorite: '/api/solution/createFavorite', + urlDeleteFavorite: '/api/solution/deleteFavorite', urlPrivateCatalogsList: '/api/catalogs', urlModelTypes: '/api/filter/modeltype', urlSearchSolutions: '/api/searchSolutionBykeyword', - urlFavoriteSolution: 'api/solution/getFavoriteSolutions', + urlFavoriteSolution: '/api/solution/getFavoriteSolutions', urlUserAccountDetails: '/api/users/userAccountDetails', getRelatedMySolutions: '/api/getRelatedMySolutions', urlUpdateViewCount: '/api/solution/updateViewCount', diff --git a/src/app/core/interceptors/dev-auth-interceptor.service.ts b/src/app/core/interceptors/dev-auth-interceptor.service.ts index f249311a68494227bdac5fd4873c4405aa1b5c42..4af97ec9a21c02abf020053fcf6852b07d3579e8 100644 --- a/src/app/core/interceptors/dev-auth-interceptor.service.ts +++ b/src/app/core/interceptors/dev-auth-interceptor.service.ts @@ -1,33 +1,34 @@ -import { Injectable } from '@angular/core'; +import { inject } from '@angular/core'; import { - HttpEvent, - HttpHandler, - HttpInterceptor, + HttpHandlerFn, + HttpInterceptorFn, HttpRequest, } from '@angular/common/http'; -import { Observable } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; import { environment } from 'src/environments/environment'; import { JwtTokenService } from '../services/auth/jwt-token.service'; -@Injectable() -export class DevAuthInterceptor implements HttpInterceptor { - constructor(protected jwtTokenService: JwtTokenService) {} - intercept( - request: HttpRequest<unknown>, - next: HttpHandler, - ): Observable<HttpEvent<unknown>> { - // if the system is not in production, change the bearer for each http request - if (!environment.production) { - const modifiedReq = request.clone({ - headers: request.headers.set( - 'Authorization', - `Bearer ${this.jwtTokenService.getToken()}`, - ), - }); - return next.handle(modifiedReq); - } +export const devAuthInterceptor: HttpInterceptorFn = ( + request: HttpRequest<unknown>, + next: HttpHandlerFn, +) => { + const jwtTokenService = inject(JwtTokenService); - // if there is nothing to intercept, pass the request. - return next.handle(request); + if (environment.production) { + return next(request); } -} + + return jwtTokenService.getToken().pipe( + switchMap((token) => { + if (token) { + // Ensure token is a non-null string + const authReq = request.clone({ + headers: request.headers.set('Authorization', `Bearer ${token}`), + }); + return next(authReq); + } + // If no token, forward the original request + return next(request.clone()); + }), + ); +}; diff --git a/src/app/core/services/auth/auth-guard.service.ts b/src/app/core/services/auth/auth-guard.service.ts index b2f67b5cc1d9db7bcf10e8d9e6d8c398b1053d47..e6ec0c782a263a5b709abd3dbee3d7b3a6ea3868 100644 --- a/src/app/core/services/auth/auth-guard.service.ts +++ b/src/app/core/services/auth/auth-guard.service.ts @@ -13,7 +13,7 @@ export const authGuard = (next: ActivatedRouteSnapshot) => { } else { return service.isAuthenticatedUser() ? true - : createUrlTreeFromSnapshot(next, ['/', 'login']); + : createUrlTreeFromSnapshot(next, ['/', '']); } }; @@ -21,5 +21,5 @@ export const adminAuthGuard = (next: ActivatedRouteSnapshot) => { const service = inject(AuthService); return service.isAuthenticatedUser() ? true - : createUrlTreeFromSnapshot(next, ['/', 'login']); + : createUrlTreeFromSnapshot(next, ['/', '']); }; diff --git a/src/app/core/services/auth/auth.service.ts b/src/app/core/services/auth/auth.service.ts index 4ad2bd8395af8458b557f3ea6df2ce1efa2afc07..65f9406d252fcdac3df714f4a6d9a0aacca64d55 100644 --- a/src/app/core/services/auth/auth.service.ts +++ b/src/app/core/services/auth/auth.service.ts @@ -27,10 +27,14 @@ export class AuthService { } } - loginUser(authToken: string) { - this.authToken = authToken; + loginUser(authToken: string): { + isAuthenticated: boolean; + authToken: string; + } { this.isAuthenticated = true; + this.authToken = authToken; this.localStorageService.setItem(this.authSecretKey, authToken); + return { authToken, isAuthenticated: this.isAuthenticated }; } getAuthToken() { diff --git a/src/app/core/services/auth/jwt-token.service.ts b/src/app/core/services/auth/jwt-token.service.ts index 1b831001c6b7c8bc8b752f2a017d0e177e6056ef..48e9746b3f3a412d556d7e1df003d579525bdd41 100644 --- a/src/app/core/services/auth/jwt-token.service.ts +++ b/src/app/core/services/auth/jwt-token.service.ts @@ -1,5 +1,7 @@ import { Injectable } from '@angular/core'; import { jwtDecode } from 'jwt-decode'; +import { BehaviorSubject, Observable, map } from 'rxjs'; +import { UserDetails } from 'src/app/shared/models'; interface DecodedToken { sub: string; @@ -15,44 +17,50 @@ interface DecodedToken { providedIn: 'root', }) export class JwtTokenService { - private jwtToken: string | null = null; - private decodedToken: DecodedToken | null = null; + private jwtToken: BehaviorSubject<string | null> = new BehaviorSubject< + string | null + >(null); + private decodedToken: BehaviorSubject<DecodedToken | null> = + new BehaviorSubject<DecodedToken | null>(null); constructor() {} setToken(token: string): void { - if (token) { - this.jwtToken = token; - this.decodedToken = jwtDecode<DecodedToken>(token); + this.jwtToken.next(token); // Store the raw token + try { + const decoded: DecodedToken = jwtDecode<DecodedToken>(token); // Decode the token + this.decodedToken.next(decoded); // Store the decoded token + } catch (error) { + console.error('Failed to decode token', error); + this.decodedToken.next(null); // Reset or handle decoding error } } - getToken(): string | null { - return this.jwtToken; + getToken(): Observable<string | null> { + return this.jwtToken.asObservable(); } - getDecodedToken(): DecodedToken | null { - return this.decodedToken; + getDecodedToken(): Observable<DecodedToken | null> { + return this.decodedToken.asObservable(); } - getUserSub(): string | null { - return this.decodedToken?.sub || null; - } - - getUserName(): string | null { - return this.decodedToken?.mlpuser.firstName || null; - } - - getEmailId(): string | null { - return this.decodedToken?.mlpuser.email || null; - } - - getUserId(): string | null { - return this.decodedToken?.mlpuser.userId || null; + getUserDetailsObservable(): Observable<UserDetails | null> { + return this.getDecodedToken().pipe( + map((token) => + token + ? { + username: token.sub, + userId: token.mlpuser.userId, + firstName: token.mlpuser.firstName, + emailId: token.mlpuser.email, + } + : null, + ), + ); } getExpiryTime(): number | null { - return this.decodedToken?.exp || null; + return this.decodedToken.getValue()?.exp || null; } isTokenExpired(): boolean { diff --git a/src/app/core/services/auth/local-login.service.ts b/src/app/core/services/auth/local-login.service.ts index 13686fd1fb5f80a8aea98906eade761a0fd4d10f..fd7bdc74c7d90caced46ff014e39cc53be5066bb 100644 --- a/src/app/core/services/auth/local-login.service.ts +++ b/src/app/core/services/auth/local-login.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { AuthService } from './auth.service'; import { HttpSharedService } from '../../http-shared/http-shared.service'; import { apiConfig } from 'src/app/core/config'; -import { map } from 'rxjs'; +import { Observable, map } from 'rxjs'; import { UserDetails } from '../../../shared/models'; @Injectable({ @@ -14,17 +14,19 @@ export class LocalLoginService { private httpSharedService: HttpSharedService, ) {} - login(username: string, password: string) { + login( + username: string, + password: string, + ): Observable<{ + isAuthenticated: boolean; + authToken: string; + }> { const url = apiConfig.apiBackendURL + apiConfig.authorization; return this.httpSharedService .post(url, undefined, { request_body: { username: username, password: password }, }) - .pipe( - map((response) => { - this.authService.loginUser(response.jwtToken); - }), - ); + .pipe(map((response) => this.authService.loginUser(response.jwtToken))); } requestPasswordReset(email: string) { const url = apiConfig.apiBackendURL + apiConfig.forgetPasswordURL; diff --git a/src/app/core/services/private-catalogs.service.ts b/src/app/core/services/private-catalogs.service.ts index d223b4cee5e82c3955e516be9f185cfc8ce4da48..bb2394aecf1b84a95f36c8f0b10ba3918f4a9381 100644 --- a/src/app/core/services/private-catalogs.service.ts +++ b/src/app/core/services/private-catalogs.service.ts @@ -1,9 +1,9 @@ import { Injectable } from '@angular/core'; import { apiConfig } from '../config'; -import { Observable, catchError, tap } from 'rxjs'; +import { Observable, catchError, map, tap } from 'rxjs'; import { HttpSharedService } from '../http-shared/http-shared.service'; import { HttpClient, HttpEvent, HttpRequest } from '@angular/common/http'; -import { JwtTokenService } from './auth/jwt-token.service'; +import { PublicSolution } from 'src/app/shared/models'; @Injectable({ providedIn: 'root', @@ -12,7 +12,6 @@ export class PrivateCatalogsService { constructor( private readonly _httpSharedService: HttpSharedService, private http: HttpClient, - private jwtTokenService: JwtTokenService, ) {} getPrivateCatalogs(pageNumber: number, requestResultSize: number) { @@ -38,12 +37,12 @@ export class PrivateCatalogsService { ); } - getFavoriteSolutions(userId: string) { + getFavoriteSolutions(userId: string): Observable<PublicSolution[]> { const url = apiConfig.apiBackendURL + apiConfig.urlFavoriteSolution + '/' + userId; - return this._httpSharedService.post(url, undefined).pipe( - tap(), + return this._httpSharedService.get(url, undefined).pipe( + map((res) => res.response_body), catchError((error) => { throw error; }), @@ -156,7 +155,6 @@ export class PrivateCatalogsService { versionId: string, licenseProfile: any, ): Observable<HttpEvent<any>> { - console.log('user Id', this.jwtTokenService.getUserId()); const url = apiConfig.apiBackendURL + apiConfig.urlUploadLicenseProfile + @@ -181,4 +179,32 @@ export class PrivateCatalogsService { return this.http.request(req); } + + createFavorite(dataObj: any) { + const urlCreateFavorite = + apiConfig.apiBackendURL + apiConfig.urlCreateFavorite; + + return this._httpSharedService + .post(urlCreateFavorite, undefined, dataObj) + .pipe( + tap(), + catchError((error) => { + throw error; + }), + ); + } + + deleteFavorite(dataObj: any) { + const urlDeleteFavorite = + apiConfig.apiBackendURL + apiConfig.urlDeleteFavorite; + + return this._httpSharedService + .post(urlDeleteFavorite, undefined, dataObj) + .pipe( + tap(), + catchError((error) => { + throw error; + }), + ); + } } diff --git a/src/app/core/services/public-solutions.service.ts b/src/app/core/services/public-solutions.service.ts index c10a8f60856481f833d9d67136bd6d4a73194369..98ce70191ac90db60ce65a3e1e7c4369dc1746e0 100644 --- a/src/app/core/services/public-solutions.service.ts +++ b/src/app/core/services/public-solutions.service.ts @@ -20,7 +20,7 @@ import { }) export class PublicSolutionsService { constructor( - private readonly _httpSharedService: HttpSharedService, + public _httpSharedService: HttpSharedService, private http: HttpClient, ) {} @@ -98,7 +98,6 @@ export class PublicSolutionsService { }, }; const url = apiConfig.apiBackendURL + apiConfig.urlPublicPortalSolutions; - return this._httpSharedService.post(url, undefined, body).pipe( tap(), map((res) => ({ @@ -111,34 +110,6 @@ export class PublicSolutionsService { ); } - createFavorite(dataObj: any) { - const urlCreateFavorite = - apiConfig.apiBackendURL + apiConfig.urlCreateFavorite; - - return this._httpSharedService - .post(urlCreateFavorite, undefined, dataObj) - .pipe( - tap(), - catchError((error) => { - throw error; - }), - ); - } - - deleteFavorite(dataObj: any) { - const urlDeleteFavorite = - apiConfig.apiBackendURL + apiConfig.urlDeleteFavorite; - - return this._httpSharedService - .post(urlDeleteFavorite, undefined, dataObj) - .pipe( - tap(), - catchError((error) => { - throw error; - }), - ); - } - getSolutionDetails( solutionId: string, revisionId: string, diff --git a/src/app/core/services/storage/browser-storage.service.ts b/src/app/core/services/storage/browser-storage.service.ts index 88d7c0625570e97e3f24fff1a6920f0ff8c4c1c3..6adc6e01f7406f999ca60fadc68175b911fcb72b 100644 --- a/src/app/core/services/storage/browser-storage.service.ts +++ b/src/app/core/services/storage/browser-storage.service.ts @@ -1,17 +1,42 @@ import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { UserDetails } from 'src/app/shared/models'; @Injectable({ providedIn: 'root', }) export class BrowserStorageService { + private userDetailSource = new BehaviorSubject<UserDetails | null>( + this.loadInitialUserDetails(), + ); + private authTokenSource = new BehaviorSubject<string | null>( + localStorage.getItem('auth_token') || null, + ); constructor() {} - setUserDetail(detail: string): void { + private loadInitialUserDetails(): UserDetails | null { + const userDetailString = + localStorage.getItem('userDetail') || + sessionStorage.getItem('userDetail'); + return userDetailString ? JSON.parse(userDetailString) : null; + } + + getUserDetails(): Observable<UserDetails | null> { + return this.userDetailSource.asObservable(); + } + + setUserDetail(userDetail: UserDetails): void { const storage = sessionStorage.getItem('rm') ? sessionStorage : localStorage; - storage.setItem('userDetail', detail); - document.cookie = 'userDetail=' + detail; + storage.setItem('userDetail', JSON.stringify(userDetail)); + this.userDetailSource.next(userDetail); + } + + removeUserDetail(): void { + sessionStorage.removeItem('userDetail'); + localStorage.removeItem('userDetail'); + this.userDetailSource.next(null); } setUserRole(role: string): void { @@ -30,12 +55,6 @@ export class BrowserStorageService { document.cookie = 'authToken= Bearer ' + token; } - getUserDetail(): string | null { - return ( - sessionStorage.getItem('userDetail') ?? localStorage.getItem('userDetail') - ); - } - getUserRole(): string | null { return ( sessionStorage.getItem('userRole') ?? localStorage.getItem('userRole') @@ -87,11 +106,6 @@ export class BrowserStorageService { ); } - removeUserDetail(): void { - sessionStorage.removeItem('userDetail'); - localStorage.removeItem('userDetail'); - } - clearUserRole(): void { sessionStorage.setItem('userRole', ''); localStorage.setItem('userRole', ''); diff --git a/src/app/features/dashboard/navbar/navbar.component.html b/src/app/features/dashboard/navbar/navbar.component.html index 9e2b34918d7d0816345ad8395d1046400e716f5b..68866d5f89602199e0d2d860fb4eac6d1e85211c 100644 --- a/src/app/features/dashboard/navbar/navbar.component.html +++ b/src/app/features/dashboard/navbar/navbar.component.html @@ -3,7 +3,7 @@ <mat-icon>menu</mat-icon> </button> <button mat-button [matMenuTriggerFor]="menu" style="margin-left: auto"> - {{ jwtTokenService.getUserName() || "Username" }} + {{ firstName$ | async }} </button> <mat-menu #menu="matMenu"> <button mat-menu-item>Account settings</button> diff --git a/src/app/features/dashboard/navbar/navbar.component.ts b/src/app/features/dashboard/navbar/navbar.component.ts index e97dfb1f33d5e23eaab56eb4c019c2eb26b1a415..2edb3e40dc460eaf9ba89c7abcee0f24fd0e6e4c 100644 --- a/src/app/features/dashboard/navbar/navbar.component.ts +++ b/src/app/features/dashboard/navbar/navbar.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { Component, Input, signal } from '@angular/core'; +import { Component, Input, OnInit, signal } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { MatToolbarModule } from '@angular/material/toolbar'; @@ -8,8 +8,8 @@ import { MatDialog } from '@angular/material/dialog'; import { LocalLoginComponent } from '../../login/local-login/local-login.component'; import { AuthService } from 'src/app/core/services/auth/auth.service'; import { Router } from '@angular/router'; -import { JwtTokenService } from 'src/app/core/services/auth/jwt-token.service'; import { BrowserStorageService } from 'src/app/core/services/storage/browser-storage.service'; +import { Observable, map } from 'rxjs'; @Component({ selector: 'gp-navbar', templateUrl: './navbar.component.html', @@ -25,14 +25,18 @@ import { BrowserStorageService } from 'src/app/core/services/storage/browser-sto }) export class NavbarComponent { @Input() collapsed = signal(false); + firstName$: Observable<string | undefined>; constructor( private authService: AuthService, public dialog: MatDialog, private router: Router, - protected jwtTokenService: JwtTokenService, private browserStorageService: BrowserStorageService, - ) {} + ) { + this.firstName$ = this.browserStorageService + .getUserDetails() + .pipe(map((details) => details?.firstName)); + } openDialog() { this.dialog.open(LocalLoginComponent); @@ -44,8 +48,4 @@ export class NavbarComponent { this.authService.logout(); this.router.navigate(['/']); } - - ngOnInit(): void { - console.log('decoded token', this.jwtTokenService.getUserName()); - } } diff --git a/src/app/features/landing-page/landing-page.component.ts b/src/app/features/landing-page/landing-page.component.ts index 10395afbcae98b43893d87be04b623b906497853..b44e6e26356971c5af14921842be54a05697416d 100644 --- a/src/app/features/landing-page/landing-page.component.ts +++ b/src/app/features/landing-page/landing-page.component.ts @@ -20,11 +20,4 @@ import { RouterModule } from '@angular/router'; RouterModule, ], }) -export class LandingPageComponent { - constructor() {} - showHomeContent = true; - - showMarketplace() { - this.showHomeContent = false; - } -} +export class LandingPageComponent {} diff --git a/src/app/features/login/local-login/local-login.component.scss b/src/app/features/login/local-login/local-login.component.scss index 0a422ab23cfd889aac04ea7e13bcea1a7688faa3..32034d6729e78d9a4298c6278fa0a189bbee8381 100644 --- a/src/app/features/login/local-login/local-login.component.scss +++ b/src/app/features/login/local-login/local-login.component.scss @@ -28,7 +28,6 @@ .dialog-header { display: flex; - flex-direction: column; justify-content: space-between; align-items: center; width: 100%; diff --git a/src/app/features/login/local-login/local-login.component.ts b/src/app/features/login/local-login/local-login.component.ts index d8ca716da959ed6b83dc65746cb2e34d3f2eba45..29c94aa1141aac315f2b83d0d0a915236f57d596 100644 --- a/src/app/features/login/local-login/local-login.component.ts +++ b/src/app/features/login/local-login/local-login.component.ts @@ -1,5 +1,5 @@ import { CommonModule } from '@angular/common'; -import { Input, Component, Output, EventEmitter } from '@angular/core'; +import { Component, OnInit, OnDestroy, Optional } from '@angular/core'; import { FormBuilder, @@ -19,7 +19,7 @@ import { import { AuthService } from 'src/app/core/services/auth/auth.service'; import { LocalLoginService } from 'src/app/core/services/auth/local-login.service'; -import { Router } from '@angular/router'; +import { NavigationStart, Router } from '@angular/router'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { @@ -27,7 +27,7 @@ import { MatCheckboxModule, } from '@angular/material/checkbox'; import { MatDividerModule } from '@angular/material/divider'; -import { Subject, combineLatest, takeUntil } from 'rxjs'; +import { Subject, switchMap, takeUntil, tap } from 'rxjs'; import { ForgetPasswordComponent } from '../forget-password/forget-password.component'; import { SignupComponent } from '../../signup/signup.component'; import { BrowserStorageService } from 'src/app/core/services/storage/browser-storage.service'; @@ -52,79 +52,77 @@ import { JwtTokenService } from 'src/app/core/services/auth/jwt-token.service'; templateUrl: './local-login.component.html', styleUrl: './local-login.component.scss', }) -export class LocalLoginComponent { +export class LocalLoginComponent implements OnInit, OnDestroy { + private onDestroy = new Subject<void>(); + isSubmitted = false; + enableSubmit = false; + isLoading = false; + form: FormGroup; + hidePassword = true; + constructor( private authService: AuthService, private localLoginService: LocalLoginService, - private dialogRef: MatDialogRef<LocalLoginComponent>, + @Optional() private dialogRef: MatDialogRef<LocalLoginComponent>, private formBuilder: FormBuilder, private router: Router, public dialog: MatDialog, private browserStorageService: BrowserStorageService, private jwtTokenService: JwtTokenService, ) { - combineLatest([ - this.controls['username'].valueChanges, - this.controls['password'].valueChanges, - ]).subscribe(([username, password]) => { - this.enableSubmit = username && password; + this.form = this.formBuilder.group({ + username: ['', Validators.required], + password: ['', Validators.required], + remember: [false], + }); + } + ngOnInit(): void { + this.form.valueChanges.pipe(takeUntil(this.onDestroy)).subscribe(() => { + this.checkFormValidity(); }); } - form: FormGroup = this.formBuilder.group({ - username: [undefined, Validators.required], - password: [undefined, Validators.required], - remember: [false], - }); + checkFormValidity() { + const { username, password } = this.form.value; + this.enableSubmit = username && password; + } get controls() { return this.form.controls; } - isSubmitted = false; - - hidePassword = true; - enableSubmit = false; - isLoading = false; - - onDestroy = new Subject<void>(); - - submit() { + submit(): void { this.isSubmitted = true; this.isLoading = true; - - if (this.form.valid && !this.isForgetPasswordForm()) { + if (this.form.valid) { + const { username, password, remember } = this.form.value; this.localLoginService - .login(this.form.value.username, this.form.value.password) - .pipe(takeUntil(this.onDestroy)) - .subscribe( - (result: any) => { - let localStore = []; + .login(username, password) + .pipe( + tap((result) => { + this.jwtTokenService.setToken(result.authToken); + }), + switchMap(() => this.jwtTokenService.getUserDetailsObservable()), + takeUntil(this.onDestroy), + ) + .subscribe({ + next: (userDetails) => { this.isLoading = false; - - const userDetails = { - username: this.form.value.username, - password: this.form.value.password, - }; - - if (!sessionStorage.getItem('rm')) - sessionStorage.setItem('rm', this.form.value.remember); - - localStore.push(userDetails.username); - localStore.push(this.jwtTokenService.getUserId()); - localStore.push(this.jwtTokenService.getUserName()); - this.browserStorageService.setUserDetail( - JSON.stringify(localStore), - ); - - this.dialogRef.close(); + this.browserStorageService.setUserDetail({ + userId: userDetails?.userId, + firstName: userDetails?.firstName || '', + username: userDetails?.username || '', + emailId: userDetails?.emailId || '', + }); + sessionStorage.setItem('rm', remember ? 'true' : 'false'); this.router.navigate(['/dashboard/home']); + if (this.dialogRef) this.dialogRef.close(); }, - (error) => { - // Handle login error + error: (error) => { this.isLoading = false; + console.error('Login failed:', error); }, - ); + }); } } @@ -134,6 +132,7 @@ export class LocalLoginComponent { ngOnDestroy() { this.onDestroy.next(); + this.onDestroy.complete(); } OnRememberMe(event: MatCheckboxChange) { diff --git a/src/app/features/marketplace/marketplace.component.html b/src/app/features/marketplace/marketplace.component.html index acadd3750037969d118b5f5623a9e0f5bca5ea88..b40fe121e3c128c55d03afa30ad23049dd359e2a 100644 --- a/src/app/features/marketplace/marketplace.component.html +++ b/src/app/features/marketplace/marketplace.component.html @@ -1,6 +1,32 @@ <section class="pageheadsection mob-pageheadsection1"> <div class="mdl-grid mdl-grid.mdl-grid--no-spacing"> - <div class="headline"><span>Marketplace | </span></div> + <div> + <div class="headline"> + <span>Marketplace | </span> + </div> + <div + style="display: flex; flex-direction: row; align-items: center" + *ngIf="loginUserId" + > + <button + class="favorite-button" + mat-raised-button + [color]="!isOnMyFavoriteSelected ? 'primary' : ''" + (click)="onAllCatalogsClick()" + > + All catalogs + </button> + <button + class="favorite-button" + mat-raised-button + [color]="isOnMyFavoriteSelected ? 'primary' : ''" + (click)="onMyFavoriteCatalogsClick()" + > + My favorite catalogs + </button> + </div> + </div> + <div style="display: flex; flex-direction: row; gap: 6px"> <a class="link-home" (click)="onHomeClick()">Home</a> <span class="link-marketplace">/</span> @@ -20,7 +46,7 @@ </mat-sidenav> <mat-sidenav-content> <div style="padding: 20px; display: flex; flex-direction: row"> - <div style="flex: 1"> + <div style="flex: 1" *ngIf="!isLoading"> @if (totalItems > 0) { <div style="margin: 24px; font-size: 14px; font-weight: normal"> <span style="color: #2e2f2f" @@ -34,21 +60,21 @@ } </div> - <div class="gp-sort-by"> + <div class="gp-sort-by" *ngIf="!isLoading"> <gp-sort-by [sortByOptions]="sortByOptions" [selectedSortBy]="sortByFilter" (sortBySelected)="updateFieldToDirectionMap($event)" ></gp-sort-by> </div> - <div class="gp-card-list-view"> + <div class="gp-card-list-view" *ngIf="!isLoading"> <gp-solutions-toggle-view [viewTile]="viewTile" (viewTileChange)="onViewTileChange($event)" ></gp-solutions-toggle-view> </div> </div> - <ng-container *ngIf="publicSolutions; else loadingState"> + <ng-container *ngIf="publicSolutions && !isLoading; else loadingState"> <ng-container *ngIf="publicSolutions.length > 0; else noCatalogs"> <div *ngIf="!viewTile; else gridView"> <!-- Horizontal list view content --> @@ -70,6 +96,8 @@ [item]="item" [items]="publicSolutions" [isMarketPlacePage]="true" + [isFavoriteSolution]="favoriteSolutionsMap[item.solutionId]" + (favoriteClicked)="updateFavorite(item.solutionId)" ></gp-card-item> </div> </mat-grid-tile> @@ -79,15 +107,16 @@ </ng-container> <ng-template #noCatalogs> <div class="loading-data"> - <p>Loading catalogs...</p> + <p>No result found...</p> </div> </ng-template> </ng-container> <ng-template #loadingState> - <p>No result found</p> + <div class="loading-data"> + <p>Loading data ...</p> + </div> </ng-template> @if (totalItems > 0) { - l <mat-paginator [length]="totalItems" [pageSize]="pageSize" diff --git a/src/app/features/marketplace/marketplace.component.scss b/src/app/features/marketplace/marketplace.component.scss index fc7dc7b445837f04be7daab317c426a51def6bb7..6b486b0e9e2026ade874e7dd184f89b75a7e6d63 100644 --- a/src/app/features/marketplace/marketplace.component.scss +++ b/src/app/features/marketplace/marketplace.component.scss @@ -37,10 +37,11 @@ mat-sidenav { } mat-sidenav-content { - height: 100%; + height: 77vh; overflow: auto; .loading-data { - height: 100vh; + height: 75vh; + width: 100%; overflow: hidden !important; display: flex; align-items: center; @@ -108,6 +109,7 @@ mat-sidenav-content { text-decoration: none; font-weight: 600; font-size: 13px; + cursor: pointer; } .link-marketplace { @@ -119,3 +121,12 @@ mat-sidenav-content { .grid-list { padding: 20px; } + +.favorite-button { + border-radius: 1px !important; + height: 30px; + font-weight: 600; + font-family: "Open Sans", sans-serif; + font-size: 14px; + padding: 0 16px; +} diff --git a/src/app/features/marketplace/marketplace.component.ts b/src/app/features/marketplace/marketplace.component.ts index 5b680d0f3a51dfbe6b70c15e13ffa1340ecdc231..069ef68c804a05a52bbb2a7e33681d0d0a4171ea 100644 --- a/src/app/features/marketplace/marketplace.component.ts +++ b/src/app/features/marketplace/marketplace.component.ts @@ -30,7 +30,10 @@ import { SortByComponent } from 'src/app/shared/components/sort-by/sort-by.compo import { FiltersService } from 'src/app/core/services/filters.service'; import { SolutionsToggleViewComponent } from 'src/app/shared/components/card-list-view/solutions-toggle-view.component'; import { ListItemComponent } from 'src/app/shared/components/list-item/list-item.component'; -import { JwtTokenService } from 'src/app/core/services/auth/jwt-token.service'; +import { BrowserStorageService } from 'src/app/core/services/storage/browser-storage.service'; +import { Observable, Subscription, map } from 'rxjs'; +import { PrivateCatalogsService } from 'src/app/core/services/private-catalogs.service'; +import { MatButtonModule } from '@angular/material/button'; @Component({ selector: 'gp-marketplace', @@ -46,14 +49,18 @@ import { JwtTokenService } from 'src/app/core/services/auth/jwt-token.service'; SortByComponent, SolutionsToggleViewComponent, ListItemComponent, + MatButtonModule, ], templateUrl: './marketplace.component.html', styleUrl: './marketplace.component.scss', providers: [{ provide: MatPaginatorIntl, useValue: CustomPaginator() }], }) export class MarketplaceComponent { - loginUserID: string = ''; + userId$: Observable<string | undefined>; + private destroy$: Subscription = new Subscription(); + loginUserId!: string; publicSolutions!: PublicSolution[]; + favoriteSolutions!: PublicSolution[]; pageSizeOptions = [25, 50, 100]; pageSize = 25; pageIndex = 0; @@ -82,51 +89,111 @@ export class MarketplaceComponent { viewTile: boolean = true; private isFirstLoad = true; + favoriteSolutionsMap: { [key: string]: boolean } = {}; + isLoading: boolean = false; + isOnMyFavoriteSelected = false; constructor( private publicSolutionsService: PublicSolutionsService, + private privateCatalogsService: PrivateCatalogsService, private filtersService: FiltersService, private router: Router, - private jwtTokenService: JwtTokenService, - ) {} + private browserStorageService: BrowserStorageService, + ) { + this.userId$ = this.browserStorageService + .getUserDetails() + .pipe(map((details) => details?.userId)); + } - async loadPublicSolutions(): Promise<void> { + loadPublicSolutions() { + this.isLoading = true; try { - const response1 = await this.publicSolutionsService + const response1 = this.publicSolutionsService .getPublicCatalogs() - .toPromise(); - - const catalogs: Catalog[] = response1 ?? []; - this.catalogIds = catalogs?.map((catalog: Catalog) => catalog.catalogId); - const requestPayload: PublicSolutionsRequestPayload = { - catalogIds: this.catalogIds, - modelTypeCodes: this.selectedCategories, - tags: this.selectedTags, - nameKeyword: this.searchKeyWord, - sortBy: this.sortByFilter, - pageRequest: { - fieldToDirectionMap: this.fieldToDirectionMap, - page: this.pageIndex, - size: this.pageSize, - }, - }; - const response2 = await this.publicSolutionsService - .getPublicSolutionsOfPublicCatalogs(requestPayload) - .toPromise(); - this.publicSolutions = response2?.publicSolutions ?? []; - this.totalItems = response2?.totalElements ?? 0; - if (this.isFirstLoad) - this.tags = this.extractTagsFromPublicSolutions(this.publicSolutions); + .subscribe((catalogs) => { + this.catalogIds = catalogs?.map( + (catalog: Catalog) => catalog.catalogId, + ); + + const requestPayload: PublicSolutionsRequestPayload = { + catalogIds: this.catalogIds, + modelTypeCodes: this.selectedCategories, + tags: this.selectedTags, + nameKeyword: this.searchKeyWord, + sortBy: this.sortByFilter, + pageRequest: { + fieldToDirectionMap: this.fieldToDirectionMap, + page: this.pageIndex, + size: this.pageSize, + }, + }; + + this.publicSolutionsService + .getPublicSolutionsOfPublicCatalogs(requestPayload) + .subscribe((publicSolutions) => { + this.publicSolutions = publicSolutions.publicSolutions ?? []; + this.totalItems = publicSolutions?.totalElements ?? 0; + if (this.isFirstLoad) + this.tags = this.extractTagsFromPublicSolutions( + this.publicSolutions, + ); + this.isLoading = false; + }); + }); } catch (error) { // Handle errors console.error('Error:', error); + this.isLoading = false; + } + } + + updateFavorite(solutionId: string): void { + const dataObj = { + request_body: { + solutionId: solutionId, + userId: this.loginUserId, + }, + request_from: 'string', + request_id: 'string', + }; + + if (!this.favoriteSolutionsMap[solutionId]) { + this.privateCatalogsService.createFavorite(dataObj).subscribe( + (response) => { + this.favoriteSolutionsMap[solutionId] = + !this.favoriteSolutionsMap[solutionId]; + console.log({ response }); + + // Handle response if needed + }, + (error) => { + // Handle error if needed + }, + ); + } else { + this.privateCatalogsService.deleteFavorite(dataObj).subscribe( + (response) => { + // Handle response if needed + console.log({ response }); + this.favoriteSolutionsMap[solutionId] = + !this.favoriteSolutionsMap[solutionId]; + }, + (error) => { + // Handle error if needed + }, + ); } } ngOnInit(): void { - this.loginUserID = this.jwtTokenService.getUserId() ?? ''; + this.destroy$.add( + this.browserStorageService.getUserDetails().subscribe((details) => { + this.loginUserId = details?.userId ?? ''; + }), + ); this.sortByFilter = environment.ui_system_config.marketplace_sort_by; this.loadPublicSolutions(); + this.subscribeToUserId(); this.filtersService.getModelTypes().subscribe( (res: CatalogFilter[]) => { this.categories = res.map((catalog: CatalogFilter) => ({ @@ -248,8 +315,63 @@ export class MarketplaceComponent { } onHomeClick() { - if (this.jwtTokenService.getUserId()) - this.router.navigate(['/dashboard/home']); - else this.router.navigate(['/home']); + this.userId$.subscribe((userId) => { + if (userId) { + this.router.navigate(['/dashboard/home']); + } else { + this.router.navigate(['/home']); + } + }); + } + + loadFavoriteCatalogs(userId: string) { + this.privateCatalogsService.getFavoriteSolutions(userId).subscribe( + (solutions) => { + this.favoriteSolutions = solutions; + this.favoriteSolutionsMap = {}; + solutions.forEach((solution) => { + this.favoriteSolutionsMap[solution.solutionId] = true; + }); + // Handle your solutions here + }, + (error) => console.error('Error loading favorite solutions:', error), + ); + } + + subscribeToUserId() { + this.userId$.subscribe((userId) => { + if (userId) { + this.loadFavoriteCatalogs(userId); + } + }); + } + + isFavoriteSolution(solutionId: string): boolean { + return !!this.favoriteSolutionsMap[solutionId]; + } + + onAllCatalogsClick() { + this.isOnMyFavoriteSelected = false; + this.loadPublicSolutions(); + } + onMyFavoriteCatalogsClick() { + this.isOnMyFavoriteSelected = true; + this.isLoading = true; + this.privateCatalogsService + .getFavoriteSolutions(this.loginUserId) + .subscribe( + (solutions) => { + this.favoriteSolutions = solutions; + this.publicSolutions = solutions; + // Update the favorite solutions map for quick lookup + this.favoriteSolutionsMap = {}; + solutions.forEach((solution) => { + this.favoriteSolutionsMap[solution.solutionId] = true; + }); + // Handle your solutions here + this.isLoading = false; + }, + (error) => console.error('Error loading favorite solutions:', error), + ); } } diff --git a/src/app/features/marketplace/marketplace.routes.ts b/src/app/features/marketplace/marketplace.routes.ts deleted file mode 100644 index 8c4ee1c879b587c4aeb63e7995f5999d7d7145ad..0000000000000000000000000000000000000000 --- a/src/app/features/marketplace/marketplace.routes.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Routes } from '@angular/router'; -import { MarketplaceComponent } from './marketplace.component'; -import { MarketplaceItemComponent } from './marketplace-item/marketplace-item.component'; - -export const MARKETPLACE_ROUTES: Routes = [ - { - path: '', - component: MarketplaceComponent, - }, - { - path: 'item/:id', - component: MarketplaceItemComponent, - }, -]; diff --git a/src/app/features/model-details/model-details.component.ts b/src/app/features/model-details/model-details.component.ts index ba1580ef2ed8697ffe2d3faf6da1388a1a3fa13b..6d12ee9288f9e0285c577ef64b65b15eddccdecd 100644 --- a/src/app/features/model-details/model-details.component.ts +++ b/src/app/features/model-details/model-details.component.ts @@ -1,7 +1,7 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; -import { Subscription } from 'rxjs'; +import { Observable, Subscription, map, take } from 'rxjs'; import { PublicSolutionsService } from 'src/app/core/services/public-solutions.service'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatMenuModule } from '@angular/material/menu'; @@ -71,7 +71,8 @@ export class ModelDetailsComponent implements OnInit, OnDestroy { version: string; revisionId: string; }; - loginUserID!: string; + userId$: Observable<string | undefined>; + private subscription = new Subscription(); authorList!: AuthorPublisherModel[]; editModel: boolean = false; totalCommentCount!: number; @@ -83,6 +84,7 @@ export class ModelDetailsComponent implements OnInit, OnDestroy { perRatingCounts: number[] = [0, 0, 0, 0, 0]; tags: Tag[] = []; relatedSolutions!: PublicSolution[]; + solution$!: Observable<any>; constructor( private activatedRoute: ActivatedRoute, @@ -91,14 +93,13 @@ export class ModelDetailsComponent implements OnInit, OnDestroy { private browserStorageService: BrowserStorageService, public dialog: MatDialog, private sharedDataService: SharedDataService, - private jwtTokenService: JwtTokenService, - ) {} + ) { + this.userId$ = this.browserStorageService + .getUserDetails() + .pipe(map((details) => details?.userId)); + } ngOnInit() { - const userDetailString = this.browserStorageService.getUserDetail(); - if (userDetailString) { - this.loginUserID = JSON.parse(userDetailString)['username']; - } this.routeSub = this.activatedRoute.params.subscribe((params) => { this.solutionId = params['solutionId']; this.revisionId = params['revisionId']; @@ -305,16 +306,28 @@ export class ModelDetailsComponent implements OnInit, OnDestroy { } } - onHomeClick() { - if (this.jwtTokenService.getUserId()) - this.router.navigate(['/dashboard/home']); - else this.router.navigate(['/home']); + onHomeClick(): void { + this.subscription.add( + this.userId$.pipe(take(1)).subscribe((userId) => { + if (userId) { + this.router.navigate(['/dashboard/home']); + } else { + this.router.navigate(['/home']); + } + }), + ); } - onMarketPlaceClick() { - if (this.jwtTokenService.getUserId()) - this.router.navigate(['/dashboard/marketplace']); - else this.router.navigate(['/marketplace']); + onMarketPlaceClick(): void { + this.subscription.add( + this.userId$.pipe(take(1)).subscribe((userId) => { + if (userId) { + this.router.navigate(['/dashboard/marketplace']); + } else { + this.router.navigate(['/marketplace']); + } + }), + ); } onChangeVersion(revision: { version: string; revisionId: string }) { diff --git a/src/app/routes.ts b/src/app/routes.ts index 864904b23c16463db721dca809243abc31a84831..96b3cc2626b00581e131816dc974ca763342171a 100644 --- a/src/app/routes.ts +++ b/src/app/routes.ts @@ -116,14 +116,7 @@ const routeConfig: Routes = [ { path: 'qAndA', component: QAndAComponent }, ], }, - { - path: 'login', - pathMatch: 'full', - loadComponent: () => - import('./features/login/login.component').then( - (mod) => mod.LoginComponent, - ), - }, + { path: '', redirectTo: '', pathMatch: 'full' }, { path: '**', redirectTo: '', pathMatch: 'full' }, ]; diff --git a/src/app/shared/common/shared-item-base/shared-item-base.component.ts b/src/app/shared/common/shared-item-base/shared-item-base.component.ts index 368b59f788c36bc0c029b1e3a668643e91a04da9..5373784fa4ff461b8e626d6c1e5a27273d0ae5cf 100644 --- a/src/app/shared/common/shared-item-base/shared-item-base.component.ts +++ b/src/app/shared/common/shared-item-base/shared-item-base.component.ts @@ -1,10 +1,11 @@ -import { Component, Input } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { CommonModule } from '@angular/common'; import { PublicSolutionsService } from 'src/app/core/services/public-solutions.service'; import { PrivateCatalogsService } from 'src/app/core/services/private-catalogs.service'; import { Router } from '@angular/router'; -import { JwtTokenService } from 'src/app/core/services/auth/jwt-token.service'; import { PublicSolution } from '../../models'; +import { BrowserStorageService } from 'src/app/core/services/storage/browser-storage.service'; +import { Subscription } from 'rxjs'; @Component({ selector: 'gp-shared-item-base', @@ -15,52 +16,67 @@ import { PublicSolution } from '../../models'; }) export abstract class SharedItemBaseComponent { @Input() item!: PublicSolution; + @Input() isFavoriteSolution!: boolean; @Input() items!: PublicSolution[]; @Input() isMarketPlacePage = false; @Input() viewTile!: boolean; imageToShow: any; isImageLoading = true; - loginUserID!: string; Tag: boolean = false; slnID!: string; selected: any[] = []; tagFilter: any[] = []; siteConfigTag: any[] = []; + private destroy$: Subscription = new Subscription(); + userId!: string; + + @Output() favoriteClicked = new EventEmitter<string>(); + + onFavoriteClick(solutionId: string): void { + console.log('SharedItemBaseComponent', solutionId); + this.favoriteClicked.emit(solutionId); + } + constructor( protected publicSolutionsService: PublicSolutionsService, protected privateCatalogsService: PrivateCatalogsService, protected router: Router, - private jwtTokenService: JwtTokenService, + private browserStorageService: BrowserStorageService, ) {} ngOnInit(): void { - const userDetailString = localStorage.getItem('userDetail'); - if (userDetailString) { - this.loginUserID = JSON.parse(userDetailString)[1]; - } + this.destroy$.add( + this.browserStorageService.getUserDetails().subscribe((details) => { + this.userId = details?.userId ?? ''; + this.initAfterUserId(); + }), + ); + } - if (this.loginUserID) { - this.getAllTags(); - } + private initAfterUserId(): void { if (this.item) { - this.publicSolutionsService - .getPictureOfSolution(this.item.solutionId) - .subscribe( - (data) => { - this.createImageFromBlob(data); - this.isImageLoading = false; - }, - (error) => { - this.isImageLoading = false; - console.log(error); - }, - ); + this.loadItemImage(); } } - createImageFromBlob(image: Blob) { + private loadItemImage(): void { + this.publicSolutionsService + .getPictureOfSolution(this.item.solutionId) + .subscribe( + (data) => { + this.createImageFromBlob(data); + this.isImageLoading = false; + }, + (error) => { + this.isImageLoading = false; + console.error('Error loading image', error); + }, + ); + } + + createImageFromBlob(image: Blob): void { let reader = new FileReader(); reader.addEventListener( 'load', @@ -75,57 +91,6 @@ export abstract class SharedItemBaseComponent { } } - getAllTags(): void { - this.selected = []; - this.tagFilter.length = 0; - this.privateCatalogsService.getPreferredTag(this.loginUserID).subscribe( - (response) => { - this.siteConfigTag = response?.data?.response_body?.prefTags || []; - console.log({ response }); - - for (let i = 0; i < Math.min(2, this.siteConfigTag.length); i++) { - if (this.siteConfigTag[i].preferred === 'Yes') { - this.selected.push(this.siteConfigTag[i]); - } - } - }, - (error) => { - console.log(error); - }, - ); - } - - updateFavorite(solutionId: string, key: number): void { - const dataObj = { - request_body: { - solutionId: solutionId, - userId: this.loginUserID, - }, - request_from: 'string', - request_id: 'string', - }; - - if (this.items[key].selectFav) { - this.publicSolutionsService.createFavorite(dataObj).subscribe( - (response) => { - // Handle response if needed - }, - (error) => { - // Handle error if needed - }, - ); - } else if (!this.items[key].selectFav) { - this.publicSolutionsService.deleteFavorite(dataObj).subscribe( - (response) => { - // Handle response if needed - }, - (error) => { - // Handle error if needed - }, - ); - } - } - getStarWidth(avgRatingValue: number | null): { width: string } { if (avgRatingValue !== null) { const starPercentage = (avgRatingValue / 5) * 100; @@ -140,18 +105,10 @@ export abstract class SharedItemBaseComponent { if (this.item && this.item.solutionId) { const solutionId = this.item.solutionId; const revisionId = this.item.latestRevisionId; - if (!this.jwtTokenService.getUserId()) - this.router.navigate([ - '/marketSolutions/model-details', - solutionId, - revisionId, - ]); - else - this.router.navigate([ - 'dashboard/marketSolutions/model-details', - solutionId, - revisionId, - ]); + const path = this.userId + ? '/dashboard/marketSolutions/model-details' + : '/marketSolutions/model-details'; + this.router.navigate([path, solutionId, revisionId]); } } } diff --git a/src/app/shared/components/card-item/card-item.component.html b/src/app/shared/components/card-item/card-item.component.html index c409b7d6322735945a91190045168052e4a2af8a..a3609241ca5ec9813fa2a229e522fc2cd14b12a2 100644 --- a/src/app/shared/components/card-item/card-item.component.html +++ b/src/app/shared/components/card-item/card-item.component.html @@ -28,7 +28,7 @@ height: 15px; " > - <span *ngIf="!item.sourceId"> + <span *ngIf="!item.sourceId && item.authors"> {{ item.authors.length > 0 ? item.authors[0]?.name : item.ownerName }} </span> <span>{{ item.mPeer?.name }}</span> | @@ -60,8 +60,9 @@ <gp-favorite [item]="item" [items]="items" - [loginUserID]="loginUserID" - (favoriteClicked)="updateFavorite($event.solutionId, $event.key)" + [loginUserID]="userId" + (favoriteClicked)="onFavoriteClick(item.solutionId)" + [isFavoriteSolution]="isFavoriteSolution" > </gp-favorite> </div> diff --git a/src/app/shared/components/card-item/card-item.component.ts b/src/app/shared/components/card-item/card-item.component.ts index 62a136f254e02c340411d3fe8c2f1596e598dfc9..46b2bb3512d7d579f9d744160fe99b9a3f4ed938 100644 --- a/src/app/shared/components/card-item/card-item.component.ts +++ b/src/app/shared/components/card-item/card-item.component.ts @@ -16,7 +16,7 @@ import { FavoriteComponent } from '../../icons/favorite/favorite.component'; import { Router } from '@angular/router'; import { PublicSolutionsService } from 'src/app/core/services/public-solutions.service'; import { PrivateCatalogsService } from 'src/app/core/services/private-catalogs.service'; -import { JwtTokenService } from 'src/app/core/services/auth/jwt-token.service'; +import { BrowserStorageService } from 'src/app/core/services/storage/browser-storage.service'; @Component({ selector: 'gp-card-item', @@ -44,13 +44,13 @@ export class CardItemComponent extends SharedItemBaseComponent { router: Router, publicSolutionsService: PublicSolutionsService, privateCatalogsService: PrivateCatalogsService, - jwtTokenService: JwtTokenService, + browserStorageService: BrowserStorageService, ) { super( publicSolutionsService, privateCatalogsService, router, - jwtTokenService, + browserStorageService, ); } } diff --git a/src/app/shared/components/catalog-name/catalog-name.component.html b/src/app/shared/components/catalog-name/catalog-name.component.html index 40e292bc540c4c61910344729858967f808918f8..e422cf7c92b12b62c55e6bb191c4415ed08404d6 100644 --- a/src/app/shared/components/catalog-name/catalog-name.component.html +++ b/src/app/shared/components/catalog-name/catalog-name.component.html @@ -1,3 +1,6 @@ -<label matTooltip="{{ item.catalogNames.join(', ') }}"> +<label + *ngIf="item.catalogNames" + matTooltip="{{ item.catalogNames.join(', ') }}" +> <img src="../../../../assets/images/tile_catalog.png" alt="Catalog Image" /> </label> diff --git a/src/app/shared/components/create-edit-license-profile/create-edit-license-profile.component.ts b/src/app/shared/components/create-edit-license-profile/create-edit-license-profile.component.ts index ed17c6bae1d2edcdf72ad3777ca67bdd14ceb89a..9dc835d3b49e08b6c5fad43f4af96acc063c804c 100644 --- a/src/app/shared/components/create-edit-license-profile/create-edit-license-profile.component.ts +++ b/src/app/shared/components/create-edit-license-profile/create-edit-license-profile.component.ts @@ -18,12 +18,22 @@ import { } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; import { JwtTokenService } from 'src/app/core/services/auth/jwt-token.service'; -import { Subscription, combineLatest } from 'rxjs'; +import { + Observable, + Subscription, + catchError, + combineLatest, + map, + switchMap, + take, + throwError, +} from 'rxjs'; import { AlertService } from 'src/app/core/services/alert.service'; import { PrivateCatalogsService } from 'src/app/core/services/private-catalogs.service'; import { Alert, AlertType, LicenseProfileModel } from '../../models'; import { HttpEventType } from '@angular/common/http'; import { SharedDataService } from 'src/app/core/services/shared-data/shared-data.service'; +import { BrowserStorageService } from 'src/app/core/services/storage/browser-storage.service'; @Component({ selector: 'gp-create-edit-license-profile', @@ -47,7 +57,7 @@ export class CreateEditLicenseProfileComponent implements OnInit { modelLicense!: LicenseProfileModel; form!: FormGroup; routeSub!: Subscription; - loginUserID: string = ''; + userId$: Observable<string | undefined>; solutionId: string = ''; revisionId: string = ''; versionId: string = ''; @@ -56,13 +66,17 @@ export class CreateEditLicenseProfileComponent implements OnInit { constructor( private formBuilder: FormBuilder, - private jwtTokenService: JwtTokenService, private sharedDataService: SharedDataService, private alertService: AlertService, private privateCatalogsService: PrivateCatalogsService, @Inject(MAT_DIALOG_DATA) public data: any, private dialogRef: MatDialogRef<CreateEditLicenseProfileComponent>, - ) {} + private browserStorageService: BrowserStorageService, + ) { + this.userId$ = this.browserStorageService + .getUserDetails() + .pipe(map((details) => details?.userId)); + } private setupFormValueChangesSubscription() { combineLatest([ @@ -99,7 +113,6 @@ export class CreateEditLicenseProfileComponent implements OnInit { } ngOnInit() { - this.jwtTokenService.getUserId(); this.modelLicense = this.data.dataKey.modelLicense; this.isEditMode = this.data.dataKey.isEditMode; this.solutionId = this.data.dataKey.solutionId; @@ -166,13 +179,12 @@ export class CreateEditLicenseProfileComponent implements OnInit { }); } - submit() { - this.loginUserID = this.jwtTokenService.getUserId() ?? ''; + submit(): void { if ( this.solutionId && - this.loginUserID && this.revisionId && - this.versionId + this.versionId && + this.form.valid ) { const licenseProfile = { $schema: @@ -195,13 +207,29 @@ export class CreateEditLicenseProfileComponent implements OnInit { additionalInfo: this.form.value.additionalInformation, rtuRequired: false, }; - this.privateCatalogsService - .createUpdateLicenseProfile( - this.loginUserID, - this.solutionId, - this.revisionId, - this.versionId, - licenseProfile, + + // Using the userId$ observable directly in the submission logic + this.userId$ + .pipe( + take(1), // Ensures the subscription gets only one value (the latest) and completes + switchMap((userId) => { + if (!userId) { + throw new Error('User ID is unavailable'); + } + return this.privateCatalogsService.createUpdateLicenseProfile( + userId, + this.solutionId, + this.revisionId, + this.versionId, + licenseProfile, + ); + }), + catchError((error) => { + console.error('Error updating license profile:', error); + return throwError( + () => new Error('Failed to update license profile'), + ); + }), ) .subscribe({ next: (event) => { @@ -210,7 +238,7 @@ export class CreateEditLicenseProfileComponent implements OnInit { this.dialogRef.close(); } }, - error: (error: any) => this.handleUploadError(error), + error: (error) => this.handleUploadError(error), }); } } diff --git a/src/app/shared/components/home/home.component.ts b/src/app/shared/components/home/home.component.ts index 9f4515649fe686ac04a465368685becd534f87d2..ef2cba0c75ace8116f2155924939562326d3d7fd 100644 --- a/src/app/shared/components/home/home.component.ts +++ b/src/app/shared/components/home/home.component.ts @@ -9,8 +9,8 @@ import { Router } from '@angular/router'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { UploadLicenseProfileComponent } from '../upload-license-profile/upload-license-profile.component'; import { BrowserStorageService } from 'src/app/core/services/storage/browser-storage.service'; -import { JwtTokenService } from 'src/app/core/services/auth/jwt-token.service'; import { PublicSolution } from '../../models'; +import { Observable, Subscription, map, take } from 'rxjs'; @Component({ selector: 'gp-home', @@ -29,15 +29,19 @@ export class HomeComponent { itemsToShowInCarousel = 4; publicSolutions: PublicSolution[] = []; visibleStartIndex = 0; - @Output() showMarketplace = new EventEmitter<void>(); + userId$: Observable<string | undefined>; + private subscription = new Subscription(); constructor( private publicSolutionsService: PublicSolutionsService, private router: Router, public dialog: MatDialog, private browserStorageService: BrowserStorageService, - private jwtTokenService: JwtTokenService, - ) {} + ) { + this.userId$ = this.browserStorageService + .getUserDetails() + .pipe(map((details) => details?.userId)); + } ngOnInit(): void { this.publicSolutionsService.getPublicSolutions().subscribe( @@ -63,14 +67,19 @@ export class HomeComponent { } onMarketPlaceClick() { - if (this.jwtTokenService.getUserId()) - this.router.navigate(['/dashboard/marketplace']); - else this.router.navigate(['/marketplace']); + this.subscription.add( + this.userId$.pipe(take(1)).subscribe((userId) => { + if (userId) { + this.router.navigate(['/dashboard/marketplace']); + } else { + this.router.navigate(['/marketplace']); + } + }), + ); } onClickUpload() { - const userDetails = this.browserStorageService.getUserDetail(); - if (userDetails) { + if (this.userId$) { const dialogRef: MatDialogRef<UploadLicenseProfileComponent> = this.dialog.open(UploadLicenseProfileComponent); } diff --git a/src/app/shared/components/list-item/list-item.component.html b/src/app/shared/components/list-item/list-item.component.html index caad9c72ad533633dca8153f0443c09d00a7630e..b9a04785696ff259f41945d10fb8cc4219f5f11b 100644 --- a/src/app/shared/components/list-item/list-item.component.html +++ b/src/app/shared/components/list-item/list-item.component.html @@ -51,8 +51,8 @@ <gp-favorite [item]="item" [items]="items" - [loginUserID]="loginUserID" - (favoriteClicked)="updateFavorite($event.solutionId, $event.key)" + [loginUserID]="userId" + (favoriteClicked)="onFavoriteClick(item.solutionId)" > </gp-favorite> </div> diff --git a/src/app/shared/components/model-details-artifacts/model-details-artifacts.component.html b/src/app/shared/components/model-details-artifacts/model-details-artifacts.component.html index 5ee7e52c71b54bc64893acbb79fc95eee8a9d77a..c0e7f134b9fdc73feeae4ea0c4e438203df1a0b7 100644 --- a/src/app/shared/components/model-details-artifacts/model-details-artifacts.component.html +++ b/src/app/shared/components/model-details-artifacts/model-details-artifacts.component.html @@ -55,7 +55,7 @@ <th mat-header-cell *matHeaderCellDef>Action</th> <td mat-cell *matCellDef="let element"> <button - *ngIf="!loginUserID" + *ngIf="!(isUserIdAvailable$ | async)" mat-button class="mdl-button mdl-js-button btn-grid-action" (click)="showAdvancedLogin()" @@ -64,7 +64,7 @@ </button> <button - *ngIf="loginUserID" + *ngIf="isUserIdAvailable$ | async" mat-button [disabled]="element.mask" class="mdl-button mdl-js-button btn-grid-action" diff --git a/src/app/shared/components/model-details-artifacts/model-details-artifacts.component.ts b/src/app/shared/components/model-details-artifacts/model-details-artifacts.component.ts index 13500347867a5f11fdf5e4d917f085b9f9db7b30..6c52caeb7479f9e74eebb722c21ce9299559fa0d 100644 --- a/src/app/shared/components/model-details-artifacts/model-details-artifacts.component.ts +++ b/src/app/shared/components/model-details-artifacts/model-details-artifacts.component.ts @@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'; import { MatTableModule } from '@angular/material/table'; import { ActivatedRoute } from '@angular/router'; import { PublicSolutionsService } from 'src/app/core/services/public-solutions.service'; -import { Subscription } from 'rxjs'; +import { Observable, Subscription, map } from 'rxjs'; import { SharedDataService } from 'src/app/core/services/shared-data/shared-data.service'; import { FormatBytesPipe } from '../../pipes/format-bytes.pipe'; import { BrowserStorageService } from 'src/app/core/services/storage/browser-storage.service'; @@ -20,12 +20,12 @@ import { ArtifactDownloadModel } from '../../models'; styleUrl: './model-details-artifacts.component.scss', }) export class ModelDetailsArtifactsComponent implements OnInit { - loginUserID: string = ''; solutionId: string = ''; revisionId: string = ''; versionId: string = ''; versionIdSubscription: Subscription | undefined; artifactDownload!: ArtifactDownloadModel[]; + isUserIdAvailable$: Observable<boolean | undefined>; displayedColumns: string[] = [ 'name', @@ -41,13 +41,13 @@ export class ModelDetailsArtifactsComponent implements OnInit { private sharedDataService: SharedDataService, private browserStorageService: BrowserStorageService, public dialog: MatDialog, - ) {} + ) { + this.isUserIdAvailable$ = this.browserStorageService + .getUserDetails() + .pipe(map((details) => !!details?.userId)); + } ngOnInit(): void { - const userDetailString = this.browserStorageService.getUserDetail(); - if (userDetailString) { - this.loginUserID = JSON.parse(userDetailString)['username']; - } this.activatedRoute.parent?.paramMap.subscribe((paramMap) => { this.solutionId = paramMap.get('solutionId') || ''; this.revisionId = paramMap.get('revisionId') || ''; diff --git a/src/app/shared/components/model-details-description/model-details-description.component.ts b/src/app/shared/components/model-details-description/model-details-description.component.ts index 4750e1dbd7f640d0b2c7eafb4b15758253f8c84e..f39c3d4e2f15f16ac33c8a5cb788d5f19b83c9f8 100644 --- a/src/app/shared/components/model-details-description/model-details-description.component.ts +++ b/src/app/shared/components/model-details-description/model-details-description.component.ts @@ -27,7 +27,6 @@ import { ArtifactDownloadModel, Revision } from '../../models'; export class ModelDetailsDescriptionComponent { solutionId: string = ''; revisionId: string = ''; - loginUserID: string = ''; selectedCatalogId = ''; selectedRevision: Revision = { version: '', @@ -52,11 +51,6 @@ export class ModelDetailsDescriptionComponent { ) {} ngOnInit() { - const userDetailString = this.browserStorageService.getUserDetail(); - if (userDetailString) { - this.loginUserID = JSON.parse(userDetailString)['username']; - } - this.activatedRoute.parent?.paramMap.subscribe((paramMap) => { this.solutionId = paramMap.get('solutionId') || ''; this.revisionId = paramMap.get('revisionId') || ''; diff --git a/src/app/shared/components/model-details-license-profile/model-details-license-profile.component.html b/src/app/shared/components/model-details-license-profile/model-details-license-profile.component.html index e355c29d7a8a8b9985b7e67cc3559d35361ce767..7e47f92b80f23e478b2684605c2201d0b7365606 100644 --- a/src/app/shared/components/model-details-license-profile/model-details-license-profile.component.html +++ b/src/app/shared/components/model-details-license-profile/model-details-license-profile.component.html @@ -8,14 +8,14 @@ class="spacebetween" > <button - [disabled]="!userDetail" + [disabled]="!(isUserIdAvailable$ | async)" mat-stroked-button (click)="onClickUpload()" > Upload </button> <button - [disabled]="!userDetail" + [disabled]="!(isUserIdAvailable$ | async)" *ngIf="modelLicense" mat-stroked-button (click)="onClickUpdate()" @@ -37,18 +37,18 @@ </div> <div style="margin-left: 20px"> <pre class="licensedisplay"> - <span><strong>keyword: </strong></span>{{ modelLicense?.keyword }} - <span><strong>licenseName: </strong></span>{{ modelLicense?.licenseName }} - <span><strong>copyright</strong> </span> - <span><strong>year: </strong></span>{{ modelLicense?.copyright?.year }} - <span><strong>company: </strong></span>{{ modelLicense?.copyright?.company }} - <span><strong>suffix: </strong></span>{{ modelLicense?.copyright?.suffix }} - <span><strong>softwareType: </strong></span> {{ modelLicense?.softwareType }} - <span><strong>companyName: </strong></span> {{ modelLicense?.companyName }} - <span><strong>contact</strong> </span> - <span><strong>name: </strong></span>{{ modelLicense?.contact?.name }} - <span><strong>URL: </strong></span>{{ modelLicense?.contact?.URL }} - <span><strong>email: </strong></span>{{ modelLicense?.contact?.email }} - <span><strong>additionalInfo: </strong></span>{{ modelLicense?.additionalInfo }} + <span *ngIf="modelLicense?.keyword"><strong>keyword: </strong></span>{{ modelLicense?.keyword }} + <span *ngIf="modelLicense?.licenseName"><strong>licenseName: </strong></span>{{ modelLicense?.licenseName }} + <span *ngIf="modelLicense?.copyright"><strong>copyright</strong> </span> + <span *ngIf="modelLicense?.copyright?.year"><strong>year: </strong></span>{{ modelLicense?.copyright?.year }} + <span *ngIf="modelLicense?.copyright?.company"><strong>company: </strong></span>{{ modelLicense?.copyright?.company }} + <span *ngIf="modelLicense?.copyright?.suffix"><strong>suffix: </strong></span>{{ modelLicense?.copyright?.suffix }} + <span *ngIf="modelLicense?.softwareType"><strong>softwareType: </strong></span> {{ modelLicense?.softwareType }} + <span *ngIf="modelLicense?.companyName"><strong>companyName: </strong></span> {{ modelLicense?.companyName }} + <span *ngIf="modelLicense?.contact"><strong>contact</strong> </span> + <span *ngIf="modelLicense?.contact?.name"><strong>name: </strong></span>{{ modelLicense?.contact?.name }} + <span *ngIf="modelLicense?.contact?.URL"><strong>URL: </strong></span>{{ modelLicense?.contact?.URL }} + <span *ngIf="modelLicense?.contact?.email"><strong>email: </strong></span>{{ modelLicense?.contact?.email }} + <span *ngIf="modelLicense?.additionalInfo"><strong>additionalInfo: </strong></span>{{ modelLicense?.additionalInfo }} </pre> </div> diff --git a/src/app/shared/components/model-details-license-profile/model-details-license-profile.component.ts b/src/app/shared/components/model-details-license-profile/model-details-license-profile.component.ts index 51c489c13c6439df76e1be1f9a45e642fa0e3288..138f5ed19a4a05db844c8b1b4433ca7601fecf54 100644 --- a/src/app/shared/components/model-details-license-profile/model-details-license-profile.component.ts +++ b/src/app/shared/components/model-details-license-profile/model-details-license-profile.component.ts @@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'; import { SharedDataService } from 'src/app/core/services/shared-data/shared-data.service'; import { PublicSolutionsService } from 'src/app/core/services/public-solutions.service'; import { ActivatedRoute } from '@angular/router'; -import { Subscription } from 'rxjs'; +import { Observable, Subscription, map } from 'rxjs'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { CreateEditLicenseProfileComponent } from '../create-edit-license-profile/create-edit-license-profile.component'; @@ -28,7 +28,7 @@ export class ModelDetailsLicenseProfileComponent { revisionId!: string; versionId: string = ''; versionIdSubscription: Subscription | undefined; - userDetail: string = ''; + isUserIdAvailable$: Observable<boolean | undefined>; constructor( private activatedRoute: ActivatedRoute, @@ -36,11 +36,13 @@ export class ModelDetailsLicenseProfileComponent { private sharedDataService: SharedDataService, public dialog: MatDialog, private browserStorageService: BrowserStorageService, - ) {} + ) { + this.isUserIdAvailable$ = this.browserStorageService + .getUserDetails() + .pipe(map((details) => !!details?.userId)); + } ngOnInit() { - const userId = this.browserStorageService.getUserDetail(); - if (userId) this.userDetail = userId; this.activatedRoute.parent?.paramMap.subscribe((paramMap) => { this.solutionId = paramMap.get('solutionId') || ''; this.revisionId = paramMap.get('revisionId') || ''; diff --git a/src/app/shared/components/tags-list/tags-list.component.html b/src/app/shared/components/tags-list/tags-list.component.html index 15d02fe6165ee48bd7b54af1fb54903b5635157b..98302e4db2552243305b9bbec0d9284db798f12a 100644 --- a/src/app/shared/components/tags-list/tags-list.component.html +++ b/src/app/shared/components/tags-list/tags-list.component.html @@ -1,4 +1,7 @@ -<div style="position: relative" *ngIf="item.solutionTagList.length > 0"> +<div + style="position: relative" + *ngIf="item.solutionTagList && item.solutionTagList.length > 0" +> <div class="manage-tagList"> <span class="mg-tag-circle"></span> <span class="mg-tag-text">{{ diff --git a/src/app/shared/components/upload-license-profile/upload-license-profile.component.ts b/src/app/shared/components/upload-license-profile/upload-license-profile.component.ts index 1ed6d843f94fe3e3f270fe7cd5c0bba2626ae58b..a8739d76110d0f0ccb93d74ca8aa558bc1227552 100644 --- a/src/app/shared/components/upload-license-profile/upload-license-profile.component.ts +++ b/src/app/shared/components/upload-license-profile/upload-license-profile.component.ts @@ -8,12 +8,7 @@ import { } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; -import { - MAT_DIALOG_DATA, - MatDialog, - MatDialogModule, - MatDialogRef, -} from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatIconModule } from '@angular/material/icon'; import { FormsModule } from '@angular/forms'; @@ -22,10 +17,21 @@ import { DndDirective } from '../../directives/dnd.directive'; import { AlertService } from 'src/app/core/services/alert.service'; import { Alert, AlertType, LicenseProfileModel } from '../../models'; import { PrivateCatalogsService } from 'src/app/core/services/private-catalogs.service'; -import { JwtTokenService } from 'src/app/core/services/auth/jwt-token.service'; -import { Subscription } from 'rxjs'; +import { + Observable, + Subject, + Subscription, + catchError, + combineLatest, + filter, + finalize, + map, + of, + switchMap, + takeUntil, + throwError, +} from 'rxjs'; import { BrowserStorageService } from 'src/app/core/services/storage/browser-storage.service'; -import { ActivatedRoute } from '@angular/router'; import { SharedDataService } from 'src/app/core/services/shared-data/shared-data.service'; @Component({ @@ -50,7 +56,6 @@ export class UploadLicenseProfileComponent implements OnInit, OnDestroy { filename: string | null = null; progressBar = 0; modelUploadError: boolean = false; - loginUserID: string = ''; solutionId: string = ''; revisionId: string = ''; versionId: string = ''; @@ -61,31 +66,34 @@ export class UploadLicenseProfileComponent implements OnInit, OnDestroy { progressBarValue = 0; modelUploadErrorMsg: string[] = []; fileSize = 0; - userDetail: string = ''; + userId$: Observable<string | undefined>; + private onDestroy = new Subject<void>(); constructor( private alertService: AlertService, private privateCatalogsService: PrivateCatalogsService, - private jwtTokenService: JwtTokenService, private browserStorageService: BrowserStorageService, private sharedDataService: SharedDataService, @Inject(MAT_DIALOG_DATA) public data: any, - private dialogRef: MatDialogRef<UploadLicenseProfileComponent>, - ) {} + ) { + this.userId$ = this.browserStorageService + .getUserDetails() + .pipe(map((details) => details?.userId)); + } ngOnDestroy(): void { + this.onDestroy.next(); + this.onDestroy.complete(); + if (this.routeSub) { this.routeSub.unsubscribe(); } - // Unsubscribe from versionIdSubscription if (this.versionIdSubscription) { this.versionIdSubscription.unsubscribe(); } } ngOnInit(): void { - const userId = this.browserStorageService.getUserDetail(); this.solutionId = this.data.dataKey.solutionId; this.revisionId = this.data.dataKey.revisionId; - if (userId) this.userDetail = userId; // Get the initial version ID this.versionId = this.sharedDataService.versionId; @@ -98,38 +106,44 @@ export class UploadLicenseProfileComponent implements OnInit, OnDestroy { ); } - uploadLicenseFile() { - this.loginUserID = this.jwtTokenService.getUserId() ?? ''; - - if ( - this.solutionId && - this.loginUserID && - this.revisionId && - this.versionId && - this.file - ) { - this.privateCatalogsService - .uploadFileToUrl( - this.loginUserID, - this.solutionId, - this.revisionId, - this.versionId, - this.file, - ) - .subscribe({ - next: (event) => { - if (event.type === HttpEventType.UploadProgress && event.total) { - this.progressBarValue = Math.round( - (100 * event.loaded) / event.total, - ); - } else if (event.type === HttpEventType.Response) { - this.handleUploadSuccess(event.body); - this.dialogRef.close(); - } - }, - error: (error: any) => this.handleUploadError(error), - }); - } + uploadLicenseFile(): void { + combineLatest([this.userId$, of(this.file)]) + .pipe( + filter(([userId, file]) => !!userId && !!file), + switchMap(([userId, file]) => { + if (userId && file) { + return this.privateCatalogsService.uploadFileToUrl( + userId, + this.solutionId, + this.revisionId, + this.versionId, + file, + ); + } else { + return throwError(() => new Error('Missing user ID or file')); + } + }), + takeUntil(this.onDestroy), + catchError((error) => { + console.error('Upload failed', error); + // Handle the error and continue + return of(null); + }), + finalize(() => this.resetProgress()), + ) + .subscribe((event) => { + if ( + event && + event.type === HttpEventType.UploadProgress && + event.total + ) { + this.progressBarValue = Math.round( + (100 * event.loaded) / event.total, + ); + } else if (event && event.type === HttpEventType.Response) { + this.handleUploadSuccess(event.body); + } + }); } private handleUploadSuccess(response: any) { diff --git a/src/app/shared/icons/favorite/favorite.component.html b/src/app/shared/icons/favorite/favorite.component.html index d68cfbc56653e0064f28002ba88b134a9ec23e3e..09e07a7f4dfd064beeff3c075601121c87547c77 100644 --- a/src/app/shared/icons/favorite/favorite.component.html +++ b/src/app/shared/icons/favorite/favorite.component.html @@ -1,15 +1,15 @@ <button class="mdl-button mdl-js-button mdl-button--icon mdl-button--colored" [disabled]="!loginUserID" - (click)="onFavoriteClick(item.solutionId, items.indexOf(item))" + (click)="onFavoriteClick(item.solutionId)" alt="Favorite" title="Favorite" > <i class="material-icons" [ngClass]="{ - 'unselected-fav': !item.selectFav, - 'selected-fav': item.selectFav + 'unselected-fav': !isFavoriteSolution, + 'selected-fav': isFavoriteSolution }" > favorite_border diff --git a/src/app/shared/icons/favorite/favorite.component.scss b/src/app/shared/icons/favorite/favorite.component.scss index f830640b6c57f1bfa718788130935f92573fc5af..dedfc4a141020a139be4f84de72e0ed0fbc26d59 100644 --- a/src/app/shared/icons/favorite/favorite.component.scss +++ b/src/app/shared/icons/favorite/favorite.component.scss @@ -1,6 +1,9 @@ .mdl-button { text-transform: none; font-weight: 600; + cursor: pointer; + background: none !important; + border: none !important; } button { diff --git a/src/app/shared/icons/favorite/favorite.component.ts b/src/app/shared/icons/favorite/favorite.component.ts index be6a29a01cf74e12ed7e1a09c6556e47010ef9c5..71509a66f14f890af2fb67b20fd0d5ea804f0582 100644 --- a/src/app/shared/icons/favorite/favorite.component.ts +++ b/src/app/shared/icons/favorite/favorite.component.ts @@ -1,4 +1,12 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { + Component, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from '@angular/core'; import { CommonModule } from '@angular/common'; import { PublicSolution } from '../../models'; @@ -9,16 +17,20 @@ import { PublicSolution } from '../../models'; templateUrl: './favorite.component.html', styleUrl: './favorite.component.scss', }) -export class FavoriteComponent { +export class FavoriteComponent implements OnInit, OnChanges { + ngOnChanges(changes: SimpleChanges): void {} @Input() loginUserID!: string; @Input() item!: PublicSolution; @Input() items!: PublicSolution[]; - @Output() favoriteClicked = new EventEmitter<{ - solutionId: string; - key: number; - }>(); + @Input() isFavoriteSolution!: boolean; + @Output() favoriteClicked = new EventEmitter<string>(); - onFavoriteClick(solutionId: string, key: number): void { - this.favoriteClicked.emit({ solutionId, key }); + ngOnInit(): void { + console.log('isFavoriteSolution', this.isFavoriteSolution); + } + + onFavoriteClick(solutionId: string): void { + console.log('FavoriteComponent', solutionId); + this.favoriteClicked.emit(solutionId); } } diff --git a/src/app/shared/models/user-details.ts b/src/app/shared/models/user-details.ts index 175df0335c9f6b2b9e13e4ab5b21f799c073e3b5..a9b685a7db2b3bbbcc347314b030c0236d155db3 100644 --- a/src/app/shared/models/user-details.ts +++ b/src/app/shared/models/user-details.ts @@ -1,9 +1,10 @@ export interface UserDetails { firstName: string; - lastName: string; + lastName?: string; emailId: string; + userId?: string; username: string; - password: string; + password?: string; active?: boolean; lastLogin?: string; created?: string;