diff --git a/libs/api/shared/feature-auth/src/lib/auth.service.ts b/libs/api/shared/feature-auth/src/lib/auth.service.ts index 01bf3ff5..ce5ea897 100644 --- a/libs/api/shared/feature-auth/src/lib/auth.service.ts +++ b/libs/api/shared/feature-auth/src/lib/auth.service.ts @@ -50,6 +50,7 @@ export class AuthService { const tokens = await this.createTokens(user); await this.updateRefreshTokenHash(user, tokens.refreshToken); const partialUser: UserEntity = { ...user }; + partialUser.id = null; partialUser.password = null; partialUser.refreshToken = null; const result = new AuthDto(partialUser, tokens.accessToken, tokens.refreshToken); diff --git a/libs/client/onboarding-client/business-owner/features/feature-view-projects/src/lib/view-projects/view-projects.component.ts b/libs/client/onboarding-client/business-owner/features/feature-view-projects/src/lib/view-projects/view-projects.component.ts index 003bb52b..9e7c7c25 100644 --- a/libs/client/onboarding-client/business-owner/features/feature-view-projects/src/lib/view-projects/view-projects.component.ts +++ b/libs/client/onboarding-client/business-owner/features/feature-view-projects/src/lib/view-projects/view-projects.component.ts @@ -12,16 +12,16 @@ import { selectProjStatusUpdated, updateProjStatus, } from '@tempus/client/onboarding-client/business-owner/data-access'; -import { Column, ViewProjects, ButtonType } from '@tempus/client/shared/ui-components/presentational'; -import { take, Subject, takeUntil, finalize, skip } from 'rxjs'; -import { CustomModalType, ModalService, ModalType } from '@tempus/client/shared/ui-components/modal'; -import { FormBuilder, Validators } from '@angular/forms'; -import { ErorType, ProjectStatus, RoleType } from '@tempus/shared-domain'; import { AsyncRequestState, OnboardingClientState, selectLoggedInUserNameEmail, } from '@tempus/client/onboarding-client/shared/data-access'; +import { Column, ViewProjects, ButtonType } from '@tempus/client/shared/ui-components/presentational'; +import { take, Subject, takeUntil, finalize, skip } from 'rxjs'; +import { CustomModalType, ModalService, ModalType } from '@tempus/client/shared/ui-components/modal'; +import { FormBuilder, Validators } from '@angular/forms'; +import { ErorType, ProjectStatus, RoleType } from '@tempus/shared-domain'; import { isValidRole } from '@tempus/client/shared/util'; @Component({ @@ -192,6 +192,7 @@ export class ViewProjectsComponent implements OnInit { }); } + // eslint-disable-next-line @typescript-eslint/member-ordering isValidRole = isValidRole; openErrorModal = (errorMessage: string) => { diff --git a/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/create-new-view/create-new-view.component.ts b/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/create-new-view/create-new-view.component.ts index 124954e8..19dd52e6 100644 --- a/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/create-new-view/create-new-view.component.ts +++ b/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/create-new-view/create-new-view.component.ts @@ -1,10 +1,18 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; +import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; -import { OnboardingClientResourceService } from '@tempus/client/onboarding-client/shared/data-access'; +import { + createResourceView, + getAllViewsByResourceId, + OnboardingClientResourceService, + OnboardingClientState, + selectResourceViews, + selectView, +} from '@tempus/client/onboarding-client/shared/data-access'; import { EditViewFormComponent } from '@tempus/onboarding-client/shared/feature-edit-view-form'; import { RevisionType, ViewType } from '@tempus/shared-domain'; -import { take } from 'rxjs'; +import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'tempus-business-owner-create-new-view', @@ -17,6 +25,7 @@ export class BusinessOwnerCreateNewViewComponent implements OnInit { private router: Router, private route: ActivatedRoute, private resourceService: OnboardingClientResourceService, + private sharedStore: Store, private translateService: TranslateService, ) { const { currentLang } = translateService; @@ -29,17 +38,30 @@ export class BusinessOwnerCreateNewViewComponent implements OnInit { resourceId = 0; + destroyed$ = new Subject(); + ngOnInit(): void { this.resourceId = parseInt(this.route.snapshot.paramMap.get('id') || '0', 10); // New view form loaded with latest approved Primary view - this.resourceService.getResourceProfileViews(this.resourceId).subscribe(data => { - const filteredViews = data.views.filter( - view => view.viewType === ViewType.PRIMARY && view.revisionType === RevisionType.APPROVED, - ); - this.newViewForm.setFormDataFromView(filteredViews[0]); - this.newViewForm.enableViewNameField(); - }); + this.sharedStore.dispatch(getAllViewsByResourceId({ resourceId: this.resourceId, pageNum: 0, pageSize: 1000 })); + this.sharedStore + .select(selectResourceViews) + .pipe(takeUntil(this.destroyed$)) + .subscribe(data => { + const filteredViews = data.views.filter( + view => view.viewType === ViewType.PRIMARY && view.revisionType === RevisionType.APPROVED, + ); + this.newViewForm.setFormDataFromView(filteredViews[0]); + this.newViewForm.enableViewNameField(); + }); + // this.resourceService.getResourceProfileViews(this.resourceId).subscribe(data => { + // const filteredViews = data.views.filter( + // view => view.viewType === ViewType.PRIMARY && view.revisionType === RevisionType.APPROVED, + // ); + // this.newViewForm.setFormDataFromView(filteredViews[0]); + // this.newViewForm.enableViewNameField(); + // }); } submitChanges() { @@ -50,15 +72,25 @@ export class BusinessOwnerCreateNewViewComponent implements OnInit { createNewView() { const newView = this.newViewForm.generateNewView(); - this.resourceService - .createSecondaryView(this.resourceId, newView) - .pipe(take(1)) - .subscribe(view => { + this.sharedStore.dispatch(createResourceView({ resourceId: this.resourceId, newView })); + this.sharedStore + .select(selectView) + .pipe(takeUntil(this.destroyed$)) + .subscribe(resourceView => { this.router.navigate(['../'], { - queryParams: { viewId: view.id }, + queryParams: { viewId: resourceView?.id }, relativeTo: this.route, }); }); + // this.resourceService + // .createSecondaryView(this.resourceId, newView) + // .pipe(take(1)) + // .subscribe(view => { + // this.router.navigate(['../'], { + // queryParams: { viewId: view.id }, + // relativeTo: this.route, + // }); + // }); } closeForm() { diff --git a/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/resource-profile-content/resource-profile-content.component.ts b/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/resource-profile-content/resource-profile-content.component.ts index 7e7b7ed8..6e3022be 100644 --- a/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/resource-profile-content/resource-profile-content.component.ts +++ b/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/resource-profile-content/resource-profile-content.component.ts @@ -21,13 +21,22 @@ import { ProjectResource, ViewType, } from '@tempus/shared-domain'; -import { OnboardingClientResourceService } from '@tempus/client/onboarding-client/shared/data-access'; +import { + approveOrDenyRevision, + getAllViewsByResourceId, + getViewById, + OnboardingClientResourceService, + OnboardingClientState, + selectResourceViews, + selectView, +} from '@tempus/client/onboarding-client/shared/data-access'; import { ModalService, CustomModalType, ModalType } from '@tempus/client/shared/ui-components/modal'; import { ActivatedRoute, Router } from '@angular/router'; import { FormBuilder, Validators } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; -import { take } from 'rxjs'; +import { Subject, take, takeUntil } from 'rxjs'; import { sortViewsByLatestUpdated } from '@tempus/client/shared/util'; +import { Store } from '@ngrx/store'; @Component({ selector: 'tempus-resource-profile-content', @@ -41,8 +50,8 @@ export class ResourceProfileContentComponent implements OnInit, OnChanges { private route: ActivatedRoute, private fb: FormBuilder, public modalService: ModalService, - private resourceService: OnboardingClientResourceService, private translateService: TranslateService, + private sharedStore: Store, ) { const { currentLang } = translateService; // eslint-disable-next-line no-param-reassign @@ -95,6 +104,8 @@ export class ResourceProfileContentComponent implements OnInit, OnChanges { @Input() projectResources: ProjectResource[] = []; + $destroyed = new Subject(); + experiencesSummary = ''; educationsSummary = ''; @@ -139,87 +150,174 @@ export class ResourceProfileContentComponent implements OnInit, OnChanges { ngOnInit() { const id = parseInt(this.route.snapshot.paramMap.get('id') || '0', 10); - this.resourceService.getResourceProfileViews(id).subscribe(data => { - this.viewID = data.views[0].id; - - // Load view that needs approval - const sortedViews = sortViewsByLatestUpdated(data.views); - let latestView = sortedViews[0]; - - // If no approvals, display approved Primary view - if (latestView.revisionType !== RevisionType.PENDING) { - const approvedViews = sortedViews.filter( - view => view.viewType === ViewType.PRIMARY && view.revisionType === RevisionType.APPROVED, - ); - const [view] = approvedViews; - latestView = view; - } - // if param is passed, we load the id - const currentViewId = parseInt(this.route.snapshot.queryParamMap.get('viewId') || '0', 10); - // if param is not passed, we load the latest id and set viewId to that param - if (!currentViewId) { - this.router.navigate([], { - relativeTo: this.route, - queryParams: { viewId: latestView.id }, - replaceUrl: true, - }); - } - - const set = new Set(); - const uniqueViewNames = sortedViews.filter(item => { - const viewExists = set.has(item.type); - // Filter out rejected views - if (item.revisionType === RevisionType.REJECTED) { - return false; + this.sharedStore.dispatch(getAllViewsByResourceId({ resourceId: id, pageNum: 0, pageSize: 1000 })); + this.sharedStore + .select(selectResourceViews) + .pipe(takeUntil(this.$destroyed)) + .subscribe(data => { + this.viewID = data?.views[0].id; + + // Load view that needs approval + const sortedViews = sortViewsByLatestUpdated(data?.views); + let latestView = sortedViews[0]; + + // If no approvals, display approved Primary view + if (latestView.revisionType !== RevisionType.PENDING) { + const approvedViews = sortedViews.filter( + view => view.viewType === ViewType.PRIMARY && view.revisionType === RevisionType.APPROVED, + ); + const [view] = approvedViews; + latestView = view; + } + // if param is passed, we load the id + const currentViewId = parseInt(this.route.snapshot.queryParamMap.get('viewId') || '0', 10); + // if param is not passed, we load the latest id and set viewId to that param + if (!currentViewId) { + this.router.navigate([], { + relativeTo: this.route, + queryParams: { viewId: latestView.id }, + replaceUrl: true, + }); } - // Filter out views that have been renamed - if (item.revision) { - const latestViews = sortViewsByLatestUpdated(item.revision.views); - if (latestViews[0].type !== item.type) { + const set = new Set(); + const uniqueViewNames = sortedViews.filter(item => { + const viewExists = set.has(item.type); + // Filter out rejected views + if (item.revisionType === RevisionType.REJECTED) { return false; } - } - set.add(item.type); - return !viewExists; + // Filter out views that have been renamed + if (item.revision) { + const latestViews = sortViewsByLatestUpdated(item.revision.views); + if (latestViews[0].type !== item.type) { + return false; + } + } + + set.add(item.type); + return !viewExists; + }); + this.loadView(currentViewId || latestView.id, uniqueViewNames); + this.dataLoaded = true; }); - this.loadView(currentViewId || latestView.id, uniqueViewNames); - this.dataLoaded = true; - }); + + // this.resourceService.getResourceProfileViews(id).subscribe(data => { + // this.viewID = data.views[0].id; + + // // Load view that needs approval + // const sortedViews = sortViewsByLatestUpdated(data.views); + // let latestView = sortedViews[0]; + + // // If no approvals, display approved Primary view + // if (latestView.revisionType !== RevisionType.PENDING) { + // const approvedViews = sortedViews.filter( + // view => view.viewType === ViewType.PRIMARY && view.revisionType === RevisionType.APPROVED, + // ); + // const [view] = approvedViews; + // latestView = view; + // } + // // if param is passed, we load the id + // const currentViewId = parseInt(this.route.snapshot.queryParamMap.get('viewId') || '0', 10); + // // if param is not passed, we load the latest id and set viewId to that param + // if (!currentViewId) { + // this.router.navigate([], { + // relativeTo: this.route, + // queryParams: { viewId: latestView.id }, + // replaceUrl: true, + // }); + // } + + // const set = new Set(); + // const uniqueViewNames = sortedViews.filter(item => { + // const viewExists = set.has(item.type); + // // Filter out rejected views + // if (item.revisionType === RevisionType.REJECTED) { + // return false; + // } + + // // Filter out views that have been renamed + // if (item.revision) { + // const latestViews = sortViewsByLatestUpdated(item.revision.views); + // if (latestViews[0].type !== item.type) { + // return false; + // } + // } + + // set.add(item.type); + // return !viewExists; + // }); + // this.loadView(currentViewId || latestView.id, uniqueViewNames); + // }); } loadView(viewID: number, profileViews?: ViewNames[]) { this.viewID = viewID; - this.resourceService.getViewById(viewID).subscribe(resourceView => { - this.certifications = resourceView.certifications; - this.educations = resourceView.educations; - this.educationsSummary = resourceView.educationsSummary; - this.workExperiences = resourceView.experiences; - this.experiencesSummary = resourceView.experiencesSummary; - this.profileSummary = resourceView.profileSummary; - this.skills = resourceView.skills.map((skill: Skill) => skill.skill.name); - this.skillsSummary = resourceView.skillsSummary; - this.viewName = resourceView.type; - - if (resourceView.revisionType === RevisionType.PENDING) { - this.isRevision = true; - } else { - this.isRevision = false; - } - if (profileViews) { - this.revisionViewLoaded.emit({ - isRevision: this.isRevision, - currentViewName: this.viewName, - resourceViews: profileViews, - }); - } else { - this.revisionViewLoaded.emit({ - isRevision: this.isRevision, - currentViewName: this.viewName, - }); - } - }); + this.sharedStore.dispatch(getViewById({ viewId: this.viewID })); + this.sharedStore + .select(selectView) + .pipe(takeUntil(this.$destroyed)) + .subscribe(resourceView => { + if (resourceView) { + this.certifications = resourceView.certifications; + this.educations = resourceView.educations; + this.educationsSummary = resourceView.educationsSummary; + this.workExperiences = resourceView.experiences; + this.experiencesSummary = resourceView.experiencesSummary; + this.profileSummary = resourceView.profileSummary; + this.skills = resourceView.skills.map((skill: Skill) => skill.skill.name); + this.skillsSummary = resourceView.skillsSummary; + this.viewName = resourceView.type; + + if (resourceView.revisionType === RevisionType.PENDING) { + this.isRevision = true; + } else { + this.isRevision = false; + } + if (profileViews) { + this.revisionViewLoaded.emit({ + isRevision: this.isRevision, + currentViewName: this.viewName, + resourceViews: profileViews, + }); + } else { + this.revisionViewLoaded.emit({ + isRevision: this.isRevision, + currentViewName: this.viewName, + }); + } + } + }); + // this.resourceService.getViewById(viewID).subscribe(resourceView => { + // this.certifications = resourceView.certifications; + // this.educations = resourceView.educations; + // this.educationsSummary = resourceView.educationsSummary; + // this.workExperiences = resourceView.experiences; + // this.experiencesSummary = resourceView.experiencesSummary; + // this.profileSummary = resourceView.profileSummary; + // this.skills = resourceView.skills.map((skill: Skill) => skill.skill.name); + // this.skillsSummary = resourceView.skillsSummary; + // this.viewName = resourceView.type; + + // if (resourceView.revisionType === RevisionType.PENDING) { + // this.isRevision = true; + // } else { + // this.isRevision = false; + // } + // if (profileViews) { + // this.revisionViewLoaded.emit({ + // isRevision: this.isRevision, + // currentViewName: this.viewName, + // resourceViews: profileViews, + // }); + // } else { + // this.revisionViewLoaded.emit({ + // isRevision: this.isRevision, + // currentViewName: this.viewName, + // }); + // } + // }); } openRejectionDialog() { @@ -243,14 +341,22 @@ export class ResourceProfileContentComponent implements OnInit, OnChanges { this.modalService.confirmEventSubject.subscribe(() => { this.modalService.close(); - this.resourceService - .approveOrDenyRevision( - this.viewID, + this.sharedStore.dispatch( + approveOrDenyRevision({ + viewId: this.viewID, // eslint-disable-next-line @typescript-eslint/dot-notation - this.viewResourceProfileForm.controls['rejectionComments'].value, - false, - ) - .subscribe(); + comment: this.viewResourceProfileForm.controls['rejectionComments'].value, + approval: false, + }), + ); + // this.resourceService + // .approveOrDenyRevision( + // this.viewID, + // // eslint-disable-next-line @typescript-eslint/dot-notation + // this.viewResourceProfileForm.controls['rejectionComments'].value, + // false, + // ) + // .subscribe(); this.modalService.confirmEventSubject.unsubscribe(); this.router.navigate(['../../manage-resources'], { relativeTo: this.route }).then(() => { window.location.reload(); @@ -279,7 +385,15 @@ export class ResourceProfileContentComponent implements OnInit, OnChanges { this.modalService.confirmEventSubject.subscribe(() => { this.modalService.close(); - this.resourceService.approveOrDenyRevision(this.viewID, '', true).subscribe(); + this.sharedStore.dispatch( + approveOrDenyRevision({ + viewId: this.viewID, + // eslint-disable-next-line @typescript-eslint/dot-notation + comment: '', + approval: true, + }), + ); + // this.resourceService.approveOrDenyRevision(this.viewID, '', true).subscribe(); this.modalService.confirmEventSubject.unsubscribe(); this.router.navigate(['../../manage-resources'], { relativeTo: this.route }).then(() => { window.location.reload(); diff --git a/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/resource-profile/resource-profile.component.ts b/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/resource-profile/resource-profile.component.ts index 5bfee5ce..19dc559a 100644 --- a/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/resource-profile/resource-profile.component.ts +++ b/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/resource-profile/resource-profile.component.ts @@ -1,13 +1,19 @@ -import { ChangeDetectorRef, Component, Input, OnInit, ViewChild } from '@angular/core'; +import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { OnboardingClientResourceService, OnboardingClientState, - selectLoggedInUserNameEmail, + getResourceInformationById, + selectResourceDetails, + selectView, + getViewById, + selectRevision, + editResourceView, + selectLoggedInUserNameEmail, } from '@tempus/client/onboarding-client/shared/data-access'; import { LoadView, ProjectResource, RoleType } from '@tempus/shared-domain'; -import { skip, take } from 'rxjs'; +import { skip, take, Subject, takeUntil } from 'rxjs'; import { BusinessOwnerState, getOriginalResume, @@ -20,7 +26,7 @@ import { EditViewFormComponent } from '@tempus/onboarding-client/shared/feature- templateUrl: './resource-profile.component.html', styleUrls: ['./resource-profile.component.scss'], }) -export class ResourceProfileComponent implements OnInit { +export class ResourceProfileComponent implements OnInit, OnDestroy { constructor( private route: ActivatedRoute, private router: Router, @@ -34,6 +40,8 @@ export class ResourceProfileComponent implements OnInit { @Input() editViewEnabled = false; + $destroyed = new Subject(); + resourceId = 0; resourceFirstName = ''; @@ -72,7 +80,7 @@ export class ResourceProfileComponent implements OnInit { isPrimaryView = false; - roles: RoleType[] = []; + roles: RoleType[] = []; childRevisionLoaded(loadedView: LoadView) { this.loadedView = loadedView; @@ -87,12 +95,23 @@ export class ResourceProfileComponent implements OnInit { this.changeDetector.detectChanges(); const viewId = parseInt(this.route.snapshot.queryParamMap.get('viewId') || '0', 10); if (viewId) { - this.resourceService - .getViewById(viewId) - .pipe(take(1)) + this.sharedStore + .select(selectView) + .pipe(takeUntil(this.$destroyed)) .subscribe(view => { - this.newViewForm.setFormDataFromView(view); + if (view) { + this.newViewForm.setFormDataFromView(view); + } else { + this.sharedStore.dispatch(getViewById({ viewId })); + } }); + + // this.resourceService + // .getViewById(viewId) + // .pipe(take(1)) + // .subscribe(view => { + // this.newViewForm.setFormDataFromView(view); + // }); } } @@ -104,38 +123,68 @@ export class ResourceProfileComponent implements OnInit { submitChanges() { const viewId = parseInt(this.route.snapshot.queryParamMap.get('viewId') || '0', 10); const newView = this.newViewForm.generateNewView(); - this.resourceService - .editResourceView(viewId, newView) - .pipe(take(1)) - .subscribe(view => { - this.router.navigate([], { queryParams: { viewId: view.id } }).then(() => window.location.reload()); + this.sharedStore.dispatch(editResourceView({ viewId, newView })); + this.sharedStore + .select(selectRevision) + .pipe(skip(1), takeUntil(this.$destroyed)) + .subscribe(revision => { + if (revision) { + this.router + .navigate([], { queryParams: { viewId: revision.id } }) + .then(() => window.location.reload()); + } }); + // this.resourceService + // .editResourceView(viewId, newView) + // .pipe(take(1)) + // .subscribe(view => { + // this.router.navigate([], { queryParams: { viewId: view.id } }).then(() => window.location.reload()); + // }); this.closeEditView(); } ngOnInit(): void { this.resourceId = parseInt(this.route.snapshot.paramMap.get('id') || '0', 10); - this.resourceService.getResourceInformationById(this.resourceId).subscribe(resourceInfo => { - this.resourceFirstName = resourceInfo.firstName; - this.resourceLastName = resourceInfo.lastName; - this.calEmail = resourceInfo.calEmail ? resourceInfo.calEmail : ''; - this.city = resourceInfo.location.city; - this.state = resourceInfo.location.province; - this.country = resourceInfo.location.country; - this.phoneNumber = resourceInfo.phoneNumber; - this.resourceEmail = resourceInfo.email; - this.linkedInLink = resourceInfo.linkedInLink; - this.githubLink = resourceInfo.githubLink; - this.otherLink = resourceInfo.otherLink; - this.projectResources = resourceInfo.projectResources; - }); + + this.sharedStore.dispatch(getResourceInformationById({ resourceId: this.resourceId })); + this.sharedStore + .select(selectResourceDetails) + .pipe(skip(1), takeUntil(this.$destroyed)) + .subscribe(data => { + this.resourceFirstName = data.firstName || ''; + this.resourceLastName = data.lastName || ''; + this.calEmail = data.calEmail ? data.calEmail : ''; + this.city = data.city || ''; + this.state = data.province || ''; + this.country = data.country || ''; + this.phoneNumber = data.phoneNumber || ''; + this.resourceEmail = data.email || ''; + this.linkedInLink = data.linkedInLink || ''; + this.githubLink = data.githubLink || ''; + this.otherLink = data.otherLink || ''; + this.projectResources = data.projectResources || []; + }); + + // this.resourceService.getResourceInformationById(this.resourceId).subscribe(resourceInfo => { + // this.resourceFirstName = resourceInfo.firstName; + // this.resourceLastName = resourceInfo.lastName; + // this.city = resourceInfo.location.city; + // this.state = resourceInfo.location.province; + // this.country = resourceInfo.location.country; + // this.phoneNumber = resourceInfo.phoneNumber; + // this.resourceEmail = resourceInfo.email; + // this.linkedInLink = resourceInfo.linkedInLink; + // this.githubLink = resourceInfo.githubLink; + // this.otherLink = resourceInfo.otherLink; + // this.projectResources = resourceInfo.projectResources; + // }); this.businessOwnerStore.dispatch(getOriginalResume({ resourceId: this.resourceId })); this.sharedStore .select(selectLoggedInUserNameEmail) .pipe(take(1)) .subscribe(data => { - this.roles = data.roles; + this.roles = data.roles; }); this.businessOwnerStore @@ -147,4 +196,9 @@ export class ResourceProfileComponent implements OnInit { } }); } + + ngOnDestroy(): void { + this.$destroyed.next(); + this.$destroyed.complete(); + } } diff --git a/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/user-bar/user-bar.component.ts b/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/user-bar/user-bar.component.ts index f22c96be..725148cb 100644 --- a/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/user-bar/user-bar.component.ts +++ b/libs/client/onboarding-client/business-owner/features/feature-view-resource-profile/src/lib/user-bar/user-bar.component.ts @@ -1,16 +1,19 @@ /* eslint-disable @typescript-eslint/dot-notation */ import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; -import { LoadView, ProjectResource, RoleType, ViewNames } from '@tempus/shared-domain'; import { - OnboardingClientResourceService, + downloadProfileByViewId, + OnboardingClientState, OnboardingClientViewsService, + selectDownloadProfile, } from '@tempus/client/onboarding-client/shared/data-access'; import { ActivatedRoute, Router } from '@angular/router'; import { FormBuilder } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { catchError, of, Subject, take, takeUntil } from 'rxjs'; +import { LoadView, ProjectResource, RoleType, ViewNames } from '@tempus/shared-domain'; import { ButtonType, SnackbarService } from '@tempus/client/shared/ui-components/presentational'; import { ModalService, ModalType, CustomModalType } from '@tempus/client/shared/ui-components/modal'; import { TranslateService } from '@ngx-translate/core'; -import { catchError, of, Subject, take, takeUntil } from 'rxjs'; import { isValidRole } from '@tempus/client/shared/util'; @Component({ @@ -46,6 +49,8 @@ export class UserBarComponent implements OnChanges { viewResourceProfilePrefx = 'viewResourceProfile.'; + destroyed$ = new Subject(); + // eslint-disable-next-line @typescript-eslint/lines-between-class-members isValidRole = isValidRole; roleType = RoleType; @@ -56,12 +61,12 @@ export class UserBarComponent implements OnChanges { constructor( private route: ActivatedRoute, - private resourceService: OnboardingClientResourceService, - private viewsService: OnboardingClientViewsService, private modalService: ModalService, private translateService: TranslateService, + private viewsService: OnboardingClientViewsService, private fb: FormBuilder, private router: Router, + private sharedStore: Store, private snackbar: SnackbarService, ) { const { currentLang } = translateService; @@ -70,8 +75,6 @@ export class UserBarComponent implements OnChanges { translateService.use(currentLang); } - destroyed$ = new Subject(); - ngOnChanges(): void { if (this.loadedView.resourceViews) { this.viewNames = this.loadedView.resourceViews.map((view: ViewNames) => view.type); @@ -87,14 +90,29 @@ export class UserBarComponent implements OnChanges { downloadProfile() { // Taken from https://stackoverflow.com/questions/52154874/angular-6-downloading-file-from-rest-api - this.resourceService.downloadProfile(this.currentViewID).subscribe(data => { - const downloadURL = window.URL.createObjectURL(data); - const link = document.createElement('a'); - link.href = downloadURL; - const index = this.viewIDs.indexOf(this.currentViewID); - link.download = `${this.resourceName}-${this.viewNames[index]}`; - link.click(); - }); + this.sharedStore.dispatch(downloadProfileByViewId({ viewId: this.currentViewID })); + + this.sharedStore + .select(selectDownloadProfile) + .pipe(takeUntil(this.destroyed$)) + .subscribe(data => { + if (data) { + const downloadURL = window.URL.createObjectURL(data); + const link = document.createElement('a'); + link.href = downloadURL; + const index = this.viewIDs.indexOf(this.currentViewID); + link.download = `${this.resourceName}-${this.viewNames[index]}`; + link.click(); + } + }); + // this.resourceService.downloadProfile(this.currentViewID).subscribe(data => { + // const downloadURL = window.URL.createObjectURL(data); + // const link = document.createElement('a'); + // link.href = downloadURL; + // const index = this.viewIDs.indexOf(this.currentViewID); + // link.download = `${this.resourceName}-${this.viewNames[index]}`; + // link.click(); + // }); this.translateService .get(`${this.viewResourceProfilePrefx}downloadDialog`) .pipe(take(1)) @@ -132,6 +150,7 @@ export class UserBarComponent implements OnChanges { this.modalService.confirmEventSubject.pipe(take(1)).subscribe(() => { this.modalService.close(); + // convert to store this.viewsService .deleteView(this.currentViewID) .pipe(catchError(error => of(error))) diff --git a/libs/client/onboarding-client/resource/data-access/src/lib/+state/resource/resource.reducers.ts b/libs/client/onboarding-client/resource/data-access/src/lib/+state/resource/resource.reducers.ts index bb738d91..d7981a84 100644 --- a/libs/client/onboarding-client/resource/data-access/src/lib/+state/resource/resource.reducers.ts +++ b/libs/client/onboarding-client/resource/data-access/src/lib/+state/resource/resource.reducers.ts @@ -12,6 +12,7 @@ export interface ResourceState { views: View[] | null; resume: Blob | null; totalViewsData: number; + error: Error | null; projectResources: ProjectResource[]; } @@ -22,6 +23,7 @@ export const initialState: ResourceState = { views: null, resume: null, totalViewsData: 0, + error: null, projectResources: [], }; @@ -62,24 +64,24 @@ export const resourceReducer = createReducer( error, status: AsyncRequestState.ERROR, })), - on(ResourceActions.getResourceOriginalResumeById, state => ({ ...state, status: AsyncRequestState.LOADING })), - on(ResourceActions.getResourceOriginalResumeByIdSuccess, (state, { resume }) => ({ - ...state, - resume, - status: AsyncRequestState.SUCCESS, - })), - on(ResourceActions.getResourceOriginalResumeByIdFailure, (state, { error }) => ({ - ...state, - error, - })), - on(ResourceActions.downloadProfileByViewId, state => ({ ...state, status: AsyncRequestState.LOADING })), - on(ResourceActions.downloadProfileByViewIdSuccess, (state, { resume }) => ({ - ...state, - resume, - status: AsyncRequestState.SUCCESS, - })), - on(ResourceActions.downloadProfileByViewIdFailure, (state, { error }) => ({ - ...state, - error, - })), + // on(ResourceActions.getResourceOriginalResumeById, state => ({ ...state, status: AsyncRequestState.LOADING })), + // on(ResourceActions.getResourceOriginalResumeByIdSuccess, (state, { resume }) => ({ + // ...state, + // resume, + // status: AsyncRequestState.SUCCESS, + // })), + // on(ResourceActions.getResourceOriginalResumeByIdFailure, (state, { error }) => ({ + // ...state, + // error, + // })), + // on(ResourceActions.downloadProfileByViewId, state => ({ ...state, status: AsyncRequestState.LOADING })), + // on(ResourceActions.downloadProfileByViewIdSuccess, (state, { resume }) => ({ + // ...state, + // resume, + // status: AsyncRequestState.SUCCESS, + // })), + // on(ResourceActions.downloadProfileByViewIdFailure, (state, { error }) => ({ + // ...state, + // error, + // })), ); diff --git a/libs/client/onboarding-client/resource/features/feature-personal-information/src/lib/personal-information-display/personal-information-display.component.ts b/libs/client/onboarding-client/resource/features/feature-personal-information/src/lib/personal-information-display/personal-information-display.component.ts index 4ae8c03b..50bd92ad 100644 --- a/libs/client/onboarding-client/resource/features/feature-personal-information/src/lib/personal-information-display/personal-information-display.component.ts +++ b/libs/client/onboarding-client/resource/features/feature-personal-information/src/lib/personal-information-display/personal-information-display.component.ts @@ -7,9 +7,15 @@ import { FormBuilder, FormGroup } from '@angular/forms'; import { ICreateLocationDto, IUpdateResourceDto } from '@tempus/shared-domain'; import { TempusResourceState, updateUserInfo } from '@tempus/client/onboarding-client/resource/data-access'; import { + getResourceInformation, + getResourceOriginalResumeById, OnboardingClientResourceService, + OnboardingClientState, + selectResourceDetails, + selectResourceOriginalResume, SessionStorageKey, } from '@tempus/client/onboarding-client/shared/data-access'; +import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'tempus-personal-information-display', @@ -17,6 +23,8 @@ import { styleUrls: ['./personal-information-display.component.scss'], }) export class PersonalInformationDisplayComponent implements OnInit { + $destroyed = new Subject(); + UserType = UserType; userId = 0; @@ -60,6 +68,7 @@ export class PersonalInformationDisplayComponent implements OnInit { private translateService: TranslateService, private fb: FormBuilder, private store: Store, + private sharedStore: Store, ) { const { currentLang } = translateService; // eslint-disable-next-line no-param-reassign @@ -68,27 +77,41 @@ export class PersonalInformationDisplayComponent implements OnInit { } ngOnInit(): void { - this.resourceService.getResourceInformation().subscribe(resData => { - this.userId = resData.id; - this.firstName = resData.firstName; - this.lastName = resData.lastName; - this.email = resData.email; - this.city = resData.location.city; - this.state = resData.location.province; - this.country = resData.location.country; - this.phoneNumber = resData.phoneNumber; - this.email = resData.email; - this.calEmail = resData.calEmail; - this.phoneNumber = resData.phoneNumber; - this.linkedInLink = resData.linkedInLink; - this.githubLink = resData.githubLink; - this.otherLink = resData.otherLink; - this.fullName = `${resData.firstName} ${resData.lastName}`; - }); - - this.resourceService.getResourceOriginalResumeById(this.userId).subscribe(resumeBlob => { - this.resume = new File([resumeBlob], 'original-resume.pdf'); - }); + this.sharedStore.dispatch(getResourceInformation()); + + this.sharedStore + .select(selectResourceDetails) + .pipe(takeUntil(this.$destroyed)) + .subscribe(data => { + this.userId = data.userId; + this.firstName = data.firstName || ''; + this.lastName = data.lastName || ''; + this.city = data.city || ''; + this.state = data.province || ''; + this.country = data.country || ''; + this.phoneNumber = data.phoneNumber || ''; + this.email = data.email || ''; + this.calEmail = data.calEmail || ''; + this.linkedInLink = data.linkedInLink || ''; + this.githubLink = data.githubLink || ''; + this.otherLink = data.otherLink || ''; + this.fullName = `${data.firstName} ${data.lastName}`; + }); + + this.sharedStore.dispatch(getResourceOriginalResumeById({ resourceId: this.userId })); + + this.sharedStore + .select(selectResourceOriginalResume) + .pipe(takeUntil(this.$destroyed)) + .subscribe(blob => { + if (blob) { + this.resume = new File([blob], 'original-resume.pdf'); + } + }); + + // this.resourceService.getResourceOriginalResumeById(this.userId).subscribe(resumeBlob => { + // this.resume = new File([resumeBlob], 'original-resume.pdf'); + // }); } loadPersonalInfo(eventData: FormGroup) { diff --git a/libs/client/onboarding-client/resource/features/feature-profile/src/lib/edit-profile/edit-profile.component.ts b/libs/client/onboarding-client/resource/features/feature-profile/src/lib/edit-profile/edit-profile.component.ts index 41721ffc..e19d6dcc 100644 --- a/libs/client/onboarding-client/resource/features/feature-profile/src/lib/edit-profile/edit-profile.component.ts +++ b/libs/client/onboarding-client/resource/features/feature-profile/src/lib/edit-profile/edit-profile.component.ts @@ -1,12 +1,24 @@ import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'; -import { OnboardingClientResourceService } from '@tempus/client/onboarding-client/shared/data-access'; -import { Subject, take, takeUntil } from 'rxjs'; +import { + editResourceView, + getAllViewsByResourceId, + getResourceInformation, + getViewById, + OnboardingClientResourceService, + OnboardingClientState, + selectResourceDetails, + selectResourceViews, + selectRevision, + selectView, +} from '@tempus/client/onboarding-client/shared/data-access'; +import { skip, Subject, take, takeUntil } from 'rxjs'; import { TranslateService } from '@ngx-translate/core'; import { ModalService, CustomModalType, ModalType } from '@tempus/client/shared/ui-components/modal'; import { EditViewFormComponent } from '@tempus/onboarding-client/shared/feature-edit-view-form'; import { ActivatedRoute, Router } from '@angular/router'; import { ViewType } from '@tempus/shared-domain'; import { sortViewsByLatestUpdated } from '@tempus/client/shared/util'; +import { Store } from '@ngrx/store'; @Component({ selector: 'tempus-edit-profile', @@ -20,6 +32,7 @@ export class EditProfileComponent implements OnInit, OnDestroy { private route: ActivatedRoute, private resourceService: OnboardingClientResourceService, private translateService: TranslateService, + private sharedStore: Store, ) { const { currentLang } = translateService; // eslint-disable-next-line no-param-reassign @@ -35,31 +48,64 @@ export class EditProfileComponent implements OnInit, OnDestroy { destroyed$ = new Subject(); ngOnInit(): void { - this.resourceService - .getResourceInformation() + this.sharedStore.dispatch(getResourceInformation()); + this.sharedStore + .select(selectResourceDetails) .pipe(take(1)) .subscribe(resData => { - const resourceId = resData.id; + const resourceId = resData.userId; // Load Secondary view from route const viewId = parseInt(this.route.snapshot.paramMap.get('id') || '0', 10); if (viewId) { - this.resourceService - .getViewById(viewId) - .pipe(take(1)) + this.sharedStore + .select(selectView) + .pipe(takeUntil(this.destroyed$)) .subscribe(view => { - this.newViewForm.setFormDataFromView(view); + if (view) { + this.newViewForm.setFormDataFromView(view); + } else { + this.sharedStore.dispatch(getViewById({ viewId })); + } }); + // this.resourceService + // .getViewById(viewId) + // .pipe(take(1)) + // .subscribe(view => { + // this.newViewForm.setFormDataFromView(view); + // }); } else { // Display latest Primary view - this.resourceService - .getResourceProfileViews(resourceId) + + this.sharedStore + .select(selectResourceViews) .pipe(takeUntil(this.destroyed$)) .subscribe(data => { - let filteredAndSortedViews = data.views.filter(view => view.viewType === ViewType.PRIMARY); - filteredAndSortedViews = sortViewsByLatestUpdated(filteredAndSortedViews); - this.newViewForm.setFormDataFromView(filteredAndSortedViews[0]); + if (data) { + let filteredAndSortedViews = data.views?.filter( + (view: { viewType: ViewType }) => view.viewType === ViewType.PRIMARY, + ); + filteredAndSortedViews = sortViewsByLatestUpdated(filteredAndSortedViews); + this.newViewForm.setFormDataFromView(filteredAndSortedViews[0]); + } else { + this.sharedStore.dispatch( + getAllViewsByResourceId({ + resourceId, + pageSize: 1000, + pageNum: 0, + }), + ); + } }); + + // this.resourceService + // .getResourceProfileViews(resourceId) + // .pipe(takeUntil(this.destroyed$)) + // .subscribe(data => { + // let filteredAndSortedViews = data.views.filter(view => view.viewType === ViewType.PRIMARY); + // filteredAndSortedViews = sortViewsByLatestUpdated(filteredAndSortedViews); + // this.newViewForm.setFormDataFromView(filteredAndSortedViews[0]); + // }); } }); } @@ -72,23 +118,43 @@ export class EditProfileComponent implements OnInit, OnDestroy { updateView() { const newView = this.newViewForm.generateNewView(); - this.resourceService - .editResourceView(this.newViewForm.currentViewId, newView) - .pipe(take(1)) + this.sharedStore.dispatch(editResourceView({ viewId: this.newViewForm.currentViewId, newView })); + this.sharedStore + .select(selectRevision) + .pipe(skip(1), takeUntil(this.destroyed$)) .subscribe(revision => { - if (newView.viewType === ViewType.PRIMARY) { - window.location.reload(); - } else { - // Navigate to new view - this.router - .navigate(['../', revision.views[revision.views.length - 1]?.id], { - relativeTo: this.route, - }) - .then(() => { - window.location.reload(); - }); + if (revision) { + if (newView.viewType === ViewType.PRIMARY) { + window.location.reload(); + } else { + // Navigate to new view + this.router + .navigate(['../', revision.views[revision.views.length - 1]?.id], { + relativeTo: this.route, + }) + .then(() => { + window.location.reload(); + }); + } } }); + // this.resourceService + // .editResourceView(this.newViewForm.currentViewId, newView) + // .pipe(take(1)) + // .subscribe(revision => { + // if (newView.viewType === ViewType.PRIMARY) { + // window.location.reload(); + // } else { + // // Navigate to new view + // this.router + // .navigate(['../', revision.views[revision.views.length - 1]?.id], { + // relativeTo: this.route, + // }) + // .then(() => { + // window.location.reload(); + // }); + // } + // }); } openSubmitConfirmation() { diff --git a/libs/client/onboarding-client/resource/features/feature-profile/src/lib/profile/profile.component.ts b/libs/client/onboarding-client/resource/features/feature-profile/src/lib/profile/profile.component.ts index e46b9ffa..258d6ab6 100644 --- a/libs/client/onboarding-client/resource/features/feature-profile/src/lib/profile/profile.component.ts +++ b/libs/client/onboarding-client/resource/features/feature-profile/src/lib/profile/profile.component.ts @@ -1,13 +1,17 @@ /* eslint-disable @typescript-eslint/dot-notation */ import { Component, OnDestroy, OnInit } from '@angular/core'; import { + getResourceInformation, + getResourceInformationById, + getViewById, OnboardingClientResourceService, OnboardingClientState, + selectResourceDetails, + selectResourceViews, + selectView, OnboardingClientViewsService, - selectLoggedInUserId, - selectLoggedInUserNameEmail, } from '@tempus/client/onboarding-client/shared/data-access'; -import { catchError, of, Subject, take, takeUntil } from 'rxjs'; +import { skip, catchError, of, Subject, take, takeUntil } from 'rxjs'; import { ButtonType, SnackbarService } from '@tempus/client/shared/ui-components/presentational'; import { ICreateExperienceDto, @@ -23,7 +27,6 @@ import { getResourceOriginalResumeById, selectDownloadProfile, selectResourceOriginalResume, - TempusResourceState, } from '@tempus/client/onboarding-client/resource/data-access'; import { TranslateService } from '@ngx-translate/core'; import { sortViewsByLatestUpdated } from '@tempus/client/shared/util'; @@ -46,11 +49,10 @@ export class ProfileComponent implements OnInit, OnDestroy { totalViews = 0; constructor( - private resourceService: OnboardingClientResourceService, - private viewsService: OnboardingClientViewsService, private translateService: TranslateService, private sharedStore: Store, - private resourceStore: Store, + private resourceService: OnboardingClientResourceService, + private viewsService: OnboardingClientViewsService, private modalService: ModalService, private snackbar: SnackbarService, private router: Router, @@ -113,25 +115,18 @@ export class ProfileComponent implements OnInit, OnDestroy { isPrimaryView = false; ngOnInit(): void { + this.sharedStore.dispatch(getResourceInformation()); this.sharedStore - .select(selectLoggedInUserId) - .pipe(take(1)) - .subscribe(data => { - if (data) { - this.userId = data; - } - }); - this.sharedStore - .select(selectLoggedInUserNameEmail) + .select(selectResourceDetails) .pipe(take(1)) .subscribe(data => { + this.userId = data.userId; this.firstName = data.firstName || ''; this.lastName = data.lastName || ''; }); - this.resourceStore.dispatch(getResourceOriginalResumeById({ resourceId: this.userId })); - - this.resourceStore + this.sharedStore.dispatch(getResourceOriginalResumeById({ resourceId: this.userId })); + this.sharedStore .select(selectResourceOriginalResume) .pipe(takeUntil(this.destroyed$)) .subscribe(blob => { @@ -140,38 +135,112 @@ export class ProfileComponent implements OnInit, OnDestroy { } }); - this.resourceStore.dispatch( - getAllViewsByResourceId({ resourceId: this.userId, pageSize: this.pageSize, pageNum: this.pageNum }), - ); - this.resourceService.getResourceInformation().subscribe(resData => { - this.userId = resData.id; - this.fullName = `${resData.firstName} ${resData.lastName}`; - - // Use viewId from route - const viewId = parseInt(this.route.snapshot.paramMap.get('id') || '0', 10); - if (viewId) { - this.resourceService.getViewById(viewId).subscribe(view => { - this.pageTitle = view.type; - this.loadView(view); - this.dataLoaded = true; + // display latest primary view + // this.resourceStore + // .select(selectResourceViews) + // .pipe(takeUntil(this.destroyed$)) + // .subscribe(data => { + // let filteredAndSortedViews = data?.views?.filter(view => view.viewType === ViewType.PRIMARY) || []; + // filteredAndSortedViews = sortViewsByLatestUpdated(filteredAndSortedViews); + + // const latestView = filteredAndSortedViews[0]; + // console.log(latestView); + // this.currentViewId = latestView.id; + // this.certifications = latestView.certifications; + // this.educations = latestView.educations; + // this.educationsSummary = latestView.educationsSummary; + // this.workExperiences = latestView.experiences; + // this.experiencesSummary = latestView.experiencesSummary; + // this.profileSummary = latestView.profileSummary; + // this.skills = latestView.skills.map(skill => skill.skill.name); + // this.skillsSummary = latestView.skillsSummary; + // this.isRejected = latestView.revisionType === RevisionType.REJECTED; + // this.isPendingApproval = latestView.revisionType === RevisionType.PENDING; + // this.rejectionComments = latestView.revision?.comment ? latestView.revision.comment : ''; + + // this.dataLoaded = true; + // }); + + const viewId = parseInt(this.route.snapshot.paramMap.get('id') || '0', 10); + if (viewId) { + this.sharedStore.dispatch(getViewById({ viewId })); + this.sharedStore + .select(selectView) + .pipe(takeUntil(this.destroyed$)) + .subscribe(resourceView => { + if (resourceView) { + this.pageTitle = resourceView?.type || ''; + this.loadView(resourceView); + this.dataLoaded = true; + } + }); + } else { + // Display Primary view + this.translateService + .get('onboardingResourceProfile.myProfile') + .pipe(take(1)) + .subscribe(data => { + this.pageTitle = data; }); - } else { - // Display Primary view - this.translateService - .get('onboardingResourceProfile.myProfile') - .pipe(take(1)) - .subscribe(data => { - this.pageTitle = data; - }); - this.resourceService.getResourceProfileViews(this.userId).subscribe(data => { + this.sharedStore.dispatch( + getAllViewsByResourceId({ resourceId: this.userId, pageSize: this.pageSize, pageNum: this.pageNum }), + ); + this.sharedStore + .select(selectResourceViews) + .pipe(takeUntil(this.destroyed$)) + .subscribe(data => { let filteredAndSortedViews = data.views?.filter(view => view.viewType === ViewType.PRIMARY); filteredAndSortedViews = sortViewsByLatestUpdated(filteredAndSortedViews); this.loadView(filteredAndSortedViews[0]); this.dataLoaded = true; }); - } - }); + + // this.resourceService.getResourceProfileViews(this.userId).subscribe(data => { + // let filteredAndSortedViews = data.views?.filter(view => view.viewType === ViewType.PRIMARY); + // filteredAndSortedViews = sortViewsByLatestUpdated(filteredAndSortedViews); + // this.loadView(filteredAndSortedViews[0]); + // this.dataLoaded = true; + // }); + } + + this.sharedStore.dispatch(getResourceInformationById({ resourceId: this.userId })); + this.sharedStore + .select(selectResourceDetails) + .pipe(skip(1), takeUntil(this.destroyed$)) + .subscribe(data => { + this.userId = data.userId; + this.fullName = `${data.firstName} ${data.lastName}`; + }); + // this.resourceService.getResourceInformation().subscribe(resData => { + // this.userId = resData.id; + // this.fullName = `${resData.firstName} ${resData.lastName}`; + + // Use viewId from route + // const viewId = parseInt(this.route.snapshot.paramMap.get('id') || '0', 10); + // if (viewId) { + // this.resourceService.getViewById(viewId).subscribe(view => { + // this.pageTitle = view.type; + // this.loadView(view); + // this.dataLoaded = true; + // }); + // } else { + // // Display Primary view + // this.translateService + // .get('onboardingResourceProfile.myProfile') + // .pipe(take(1)) + // .subscribe(data => { + // this.pageTitle = data; + // }); + + // this.resourceService.getResourceProfileViews(this.userId).subscribe(data => { + // let filteredAndSortedViews = data.views?.filter(view => view.viewType === ViewType.PRIMARY); + // filteredAndSortedViews = sortViewsByLatestUpdated(filteredAndSortedViews); + // this.loadView(filteredAndSortedViews[0]); + // this.dataLoaded = true; + // }); + // } + // }); } loadView(view: View) { @@ -273,7 +342,8 @@ export class ProfileComponent implements OnInit, OnDestroy { downloadProfile() { // Taken from https://stackoverflow.com/questions/52154874/angular-6-downloading-file-from-rest-api - this.resourceStore + this.sharedStore.dispatch(downloadProfileByViewId({ viewId: this.currentViewId })); + this.sharedStore .select(selectDownloadProfile) .pipe(takeUntil(this.destroyed$)) .subscribe(data => { diff --git a/libs/client/onboarding-client/resource/features/feature-views/src/lib/create-new-view/create-new-view.component.ts b/libs/client/onboarding-client/resource/features/feature-views/src/lib/create-new-view/create-new-view.component.ts index 8a7e64a3..0add5acc 100644 --- a/libs/client/onboarding-client/resource/features/feature-views/src/lib/create-new-view/create-new-view.component.ts +++ b/libs/client/onboarding-client/resource/features/feature-views/src/lib/create-new-view/create-new-view.component.ts @@ -1,7 +1,17 @@ import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; +import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; -import { OnboardingClientResourceService } from '@tempus/client/onboarding-client/shared/data-access'; +import { + createResourceView, + getAllViewsByResourceId, + getResourceInformation, + OnboardingClientResourceService, + OnboardingClientState, + selectResourceDetails, + selectResourceViews, + selectView, +} from '@tempus/client/onboarding-client/shared/data-access'; import { CustomModalType, ModalService, ModalType } from '@tempus/client/shared/ui-components/modal'; import { EditViewFormComponent } from '@tempus/onboarding-client/shared/feature-edit-view-form'; import { RevisionType, ViewType } from '@tempus/shared-domain'; @@ -20,6 +30,7 @@ export class CreateNewViewComponent implements OnInit, OnDestroy { public modalService: ModalService, private resourceService: OnboardingClientResourceService, private translateService: TranslateService, + private sharedStore: Store, ) { const { currentLang } = translateService; // eslint-disable-next-line no-param-reassign @@ -34,13 +45,21 @@ export class CreateNewViewComponent implements OnInit, OnDestroy { destroyed$ = new Subject(); ngOnInit(): void { - this.resourceService - .getResourceInformation() + this.sharedStore.dispatch(getResourceInformation()); + this.sharedStore + .select(selectResourceDetails) .pipe(take(1)) .subscribe(resData => { - this.userId = resData.id; - this.resourceService - .getResourceProfileViews(this.userId) + this.userId = resData.userId; + this.sharedStore.dispatch( + getAllViewsByResourceId({ + resourceId: this.userId, + pageSize: 1000, + pageNum: 0, + }), + ); + this.sharedStore + .select(selectResourceViews) .pipe(takeUntil(this.destroyed$)) .subscribe(data => { const approvedPrimaryView = data.views?.find( @@ -52,6 +71,25 @@ export class CreateNewViewComponent implements OnInit, OnDestroy { } }); }); + + // this.resourceService + // .getResourceInformation() + // .pipe(take(1)) + // .subscribe(resData => { + // this.userId = resData.id; + // this.resourceService + // .getResourceProfileViews(this.userId) + // .pipe(takeUntil(this.destroyed$)) + // .subscribe(data => { + // const approvedPrimaryView = data.views?.find( + // view => view.revisionType === RevisionType.APPROVED && view.viewType === ViewType.PRIMARY, + // ); + // if (approvedPrimaryView) { + // this.newViewForm.setFormDataFromView(approvedPrimaryView); + // this.newViewForm.enableViewNameField(); + // } + // }); + // }); } submitChanges() { @@ -62,14 +100,24 @@ export class CreateNewViewComponent implements OnInit, OnDestroy { createNewView() { const newView = this.newViewForm.generateNewView(); - this.resourceService - .createSecondaryView(this.userId, newView) - .pipe(take(1)) - .subscribe(view => { - this.router.navigate(['../', view.id], { + this.sharedStore.dispatch(createResourceView({ resourceId: this.userId, newView })); + this.sharedStore + .select(selectView) + .pipe(takeUntil(this.destroyed$)) + .subscribe(resourceView => { + this.router.navigate(['../', resourceView?.id], { relativeTo: this.route, }); }); + + // this.resourceService + // .createSecondaryView(this.userId, newView) + // .pipe(take(1)) + // .subscribe(view => { + // this.router.navigate(['../', view.id], { + // relativeTo: this.route, + // }); + // }); } openSubmitConfirmation() { diff --git a/libs/client/onboarding-client/resource/features/feature-views/src/lib/my-views/my-views.component.ts b/libs/client/onboarding-client/resource/features/feature-views/src/lib/my-views/my-views.component.ts index 702625dc..c0ea6363 100644 --- a/libs/client/onboarding-client/resource/features/feature-views/src/lib/my-views/my-views.component.ts +++ b/libs/client/onboarding-client/resource/features/feature-views/src/lib/my-views/my-views.component.ts @@ -6,8 +6,7 @@ import { TranslateService } from '@ngx-translate/core'; import { OnboardingClientResourceService, OnboardingClientState, - selectLoggedInUserId, - selectLoggedInUserNameEmail, + selectResourceDetails, } from '@tempus/client/onboarding-client/shared/data-access'; import { ButtonType, Column, MyViewsTableData } from '@tempus/client/shared/ui-components/presentational'; import { Subject, take, takeUntil } from 'rxjs'; @@ -92,17 +91,10 @@ export class MyViewsComponent implements OnInit, OnDestroy { ngOnInit(): void { this.sharedStore - .select(selectLoggedInUserId) - .pipe(take(1)) - .subscribe(data => { - if (data) { - this.userId = data; - } - }); - this.sharedStore - .select(selectLoggedInUserNameEmail) + .select(selectResourceDetails) .pipe(take(1)) .subscribe(data => { + this.userId = data.userId; this.firstName = data.firstName || ''; this.lastName = data.lastName || ''; }); diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.actions.ts b/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.actions.ts index bafd1fa6..7e521e31 100644 --- a/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.actions.ts +++ b/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.actions.ts @@ -10,18 +10,18 @@ export const loginSuccess = createAction( props<{ accessToken: string; refreshToken: string; - loggedInUserId: number; + // loggedInUserId: number; firstName: string; lastName: string; email: string; - roles: RoleType[]; + roles: RoleType[]; }>(), ); export const loginFailure = createAction('[Onboarding Client Auth Api] Login Failure', props<{ error: Error }>()); -export const updateInfoFailure = createAction( - '[Onboarding Client Auth Api] Update Info Failure', - props<{ error: Error }>(), -); +// export const updateInfoFailure = createAction( +// '[Onboarding Client Auth Api] Update Info Failure', +// props<{ error: Error }>(), +// ); export const logoutSuccess = createAction('[Onboarding Client Auth Api] Logout Success'); export const logoutFailure = createAction('[Onboarding Client Auth Api] Logout Failure', props<{ error: Error }>()); export const logout = createAction('[Onboarding Client Any Page] Logout', props<{ redirect: boolean }>()); diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.effects.ts b/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.effects.ts index ecb51723..bc456aeb 100644 --- a/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.effects.ts +++ b/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.effects.ts @@ -4,6 +4,8 @@ import { map, catchError } from 'rxjs/operators'; import { Store } from '@ngrx/store'; import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; +import { decodeJwt } from '@tempus/client/shared/util'; +import { RoleType } from '@tempus/shared-domain'; import { OnboardingClientState } from '../onboardingClient.state'; import { forgotPassword, @@ -20,8 +22,6 @@ import { resetPasswordSuccess, } from './auth.actions'; import { OnboardingClientAuthService, OnboardingClientResourceService } from '../../services'; -import { decodeJwt } from '@tempus/client/shared/util'; -import { RoleType } from '@tempus/shared-domain'; @Injectable() export class AuthEffects { @@ -39,16 +39,16 @@ export class AuthEffects { switchMap(action => this.authService.login(action.password, action.email).pipe( map(data => { - const { roles } = decodeJwt(data.accessToken); - const rolesTyped: RoleType[] = roles.map(role => RoleType[role as keyof typeof RoleType]); + const { roles } = decodeJwt(data.accessToken); + const rolesTyped: RoleType[] = roles.map(role => RoleType[role as keyof typeof RoleType]); return loginSuccess({ + // loggedInUserId: data.loggedInUserId, accessToken: data.accessToken, refreshToken: data.refreshToken, - loggedInUserId: data.user.id, firstName: data.user.firstName, lastName: data.user.lastName, email: data.user.email, - roles: rolesTyped + roles: rolesTyped, }); }), catchError(error => of(loginFailure({ error }))), @@ -62,8 +62,7 @@ export class AuthEffects { ofType(logout), switchMap(options => this.authService.logout().pipe( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - map(_ => { + map(() => { this.authService.resetSessionStorage(); if (options.redirect) { this.router.navigateByUrl('/signin'); diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.reducers.ts b/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.reducers.ts index 4ff8d611..2887edda 100644 --- a/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.reducers.ts +++ b/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.reducers.ts @@ -9,11 +9,11 @@ export const AUTH_FEATURE_KEY = 'auth'; export interface AuthState { accessToken: string | null; refreshToken: string | null; - loggedInUserId: number | null; firstName: string | null; lastName: string | null; + // loggedInUserId: number | null; email: string | null; - roles: RoleType[]; + roles: RoleType[]; error: Error | null; status: AsyncRequestState; } @@ -21,27 +21,27 @@ export interface AuthState { export const initialState: AuthState = { accessToken: null, refreshToken: null, - loggedInUserId: null, error: null, firstName: null, lastName: null, + // loggedInUserId: null, email: null, - roles: [], + roles: [], status: AsyncRequestState.IDLE, }; export const authReducer = createReducer( initialState, on(AuthActions.login, state => ({ ...state, status: AsyncRequestState.LOADING })), - on(AuthActions.loginSuccess, (state, { accessToken, refreshToken, loggedInUserId, firstName, lastName, email, roles }) => ({ + on(AuthActions.loginSuccess, (state, { accessToken, refreshToken, firstName, lastName, email, roles }) => ({ ...state, accessToken, refreshToken, - loggedInUserId, + // loggedInUserId, firstName, lastName, email, - roles, + roles, status: AsyncRequestState.SUCCESS, error: null, })), diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.selectors.ts b/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.selectors.ts index 0d751e41..acb74314 100644 --- a/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.selectors.ts +++ b/libs/client/onboarding-client/shared/data-access/src/lib/+state/auth/auth.selectors.ts @@ -12,7 +12,7 @@ export const selectAccessRefreshToken = createSelector(selectAuth, (state: AuthS accessToken: state.accessToken, refreshToken: state.refreshToken, })); -export const selectLoggedInUserId = createSelector(selectAuth, (state: AuthState) => state.loggedInUserId); +// export const selectLoggedInUserId = createSelector(selectAuth, (state: AuthState) => state.loggedInUserId); export const selectAuthStatus = createSelector(selectAuth, (state: AuthState) => { return { status: state.status, @@ -24,4 +24,4 @@ export const selectLoggedInUserNameEmail = createSelector(selectAuth, (state: Au }); export const selectLoggedInRoles = createSelector(selectAuth, (state: AuthState) => { return { roles: state.roles }; -}); \ No newline at end of file +}); diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/+state/hydration.reducer.ts b/libs/client/onboarding-client/shared/data-access/src/lib/+state/hydration.reducer.ts index 361e5b7c..84db1cab 100644 --- a/libs/client/onboarding-client/shared/data-access/src/lib/+state/hydration.reducer.ts +++ b/libs/client/onboarding-client/shared/data-access/src/lib/+state/hydration.reducer.ts @@ -11,21 +11,28 @@ export const hydrationMetaReducer = ( if (action.type === INIT || action.type === UPDATE || action.type === '@ngrx/router-store/navigated') { const accessToken: string | null = sessionStorage.getItem(SessionStorageKey.TEMPUS_ACCESS_TOKEN); const refreshToken: string | null = sessionStorage.getItem(SessionStorageKey.TEMPUS_REFRESH_TOKEN); - const userIdString: string | null = sessionStorage.getItem(SessionStorageKey.TEMPUS_USER_ID); - const userId: number | null = userIdString ? parseInt(userIdString, 10) : null; const firstName: string | null = sessionStorage.getItem(SessionStorageKey.TEMPUS_FIRST_NAME); const lastName: string | null = sessionStorage.getItem(SessionStorageKey.TEMPUS_LAST_NAME); const email: string | null = sessionStorage.getItem(SessionStorageKey.TEMPUS_EMAIL); - const rolesString: string | null = sessionStorage.getItem(SessionStorageKey.TEMPUS_ROLES); - let roles: RoleType[] = []; - if (rolesString) { - roles = JSON.parse(rolesString); - } + const rolesString: string | null = sessionStorage.getItem(SessionStorageKey.TEMPUS_ROLES); + let roles: RoleType[] = []; + if (rolesString) { + roles = JSON.parse(rolesString); + } if (newState.auth) { newState = { ...newState, - auth: { ...newState.auth, accessToken, refreshToken, loggedInUserId: userId, firstName, lastName, email, roles }, + auth: { + ...newState.auth, + accessToken, + refreshToken, + // loggedInUserId: userId, + firstName, + lastName, + email, + roles, + }, }; } return reducer(newState as OnboardingClientState, action); diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/+state/index.ts b/libs/client/onboarding-client/shared/data-access/src/lib/+state/index.ts index 0deabf84..38c1fe95 100644 --- a/libs/client/onboarding-client/shared/data-access/src/lib/+state/index.ts +++ b/libs/client/onboarding-client/shared/data-access/src/lib/+state/index.ts @@ -1,2 +1,3 @@ export * from './auth'; export * from './onboardingClient.state'; +export * from './resource'; diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/+state/onboardingClient.state.ts b/libs/client/onboarding-client/shared/data-access/src/lib/+state/onboardingClient.state.ts index 2884ca55..107bbfb2 100644 --- a/libs/client/onboarding-client/shared/data-access/src/lib/+state/onboardingClient.state.ts +++ b/libs/client/onboarding-client/shared/data-access/src/lib/+state/onboardingClient.state.ts @@ -1,14 +1,17 @@ import { ActionReducerMap, createFeatureSelector } from '@ngrx/store'; import { authReducer, AuthState, AUTH_FEATURE_KEY } from './auth/auth.reducers'; +import { resourceReducer, ResourceState, RESOURCE_INFO_FEATURE_KEY } from './resource/resource.reducers'; export const ONBOARDING_CLIENT_FEATURE_KEY = 'onboardingClient'; export interface OnboardingClientState { [AUTH_FEATURE_KEY]: AuthState; + [RESOURCE_INFO_FEATURE_KEY]: ResourceState; } export const reducers: ActionReducerMap = { [AUTH_FEATURE_KEY]: authReducer, + [RESOURCE_INFO_FEATURE_KEY]: resourceReducer, }; export const selectOnboardingClientState = createFeatureSelector(ONBOARDING_CLIENT_FEATURE_KEY); diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/index.ts b/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/index.ts new file mode 100644 index 00000000..9bcb86f6 --- /dev/null +++ b/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/index.ts @@ -0,0 +1,4 @@ +export * from './resource.actions'; +export * from './resource.selectors'; +export * from './resource.effects'; +export * from './resource.reducers'; diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/resource.actions.ts b/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/resource.actions.ts new file mode 100644 index 00000000..4e4f45c3 --- /dev/null +++ b/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/resource.actions.ts @@ -0,0 +1,190 @@ +import { createAction, props } from '@ngrx/store'; +import { ApproveViewDto } from '@tempus/api/shared/dto'; +import { ICreateViewDto, IUpdateResourceDto, ProjectResource, Revision, View } from '@tempus/shared-domain'; + +export const getResourceInformation = createAction('[Onboarding Client User Api] Get Resource Information'); + +export const getResourceInformationSuccess = createAction( + '[Onboarding Client User Api] Get Resource Information Success', + props<{ + userId: number; + firstName: string; + lastName: string; + email: string; + calEmail: string; + phoneNumber: string; + city: string; + province: string; + country: string; + linkedInLink: string; + githubLink: string; + otherLink: string; + projectResources: ProjectResource[]; + }>(), +); + +export const getResourceInformationFailure = createAction( + '[Onboarding Client User Api] Get Resource Information Failure', + props<{ error: Error }>(), +); + +export const getResourceInformationById = createAction( + '[Onboarding Client User Api] Get Resource Information By Id', + props<{ resourceId: number }>(), +); + +export const getResourceInformationByIdSuccess = createAction( + '[Onboarding Client User Api] Get Resource Information By Id Success', + props<{ + firstName: string; + lastName: string; + email: string; + calEmail: string; + phoneNumber: string; + city: string; + province: string; + country: string; + linkedInLink: string; + githubLink: string; + otherLink: string; + projectResources: ProjectResource[]; + }>(), +); + +export const getResourceInformationByIdFailure = createAction( + '[Onboarding Client User Api] Get Resource Information By Id Failure', + props<{ error: Error }>(), +); + +export const updateUserInfo = createAction( + '[Onboarding Client Api] Update Info', + props<{ + updatedPersonalInformation: IUpdateResourceDto; + }>(), +); + +export const updateUserInfoSuccess = createAction( + '[Onboarding Client Api] Update Info Success', + props<{ + firstName: string; + lastName: string; + email: string; + }>(), +); + +export const updateInfoFailure = createAction( + '[Onboarding Client Auth Api] Update Info Failure', + props<{ error: Error }>(), +); + +// get view by id +export const getViewById = createAction( + '[Onboarding Client Profile Views API] Get View By Id', + props<{ viewId: number }>(), +); + +export const getViewByIdSuccess = createAction( + '[Onboarding Client Profile Views API] Get View By Id Success', + props<{ view: View }>(), +); + +export const getViewByIdFailure = createAction( + '[Onboarding Client Profile Views API] Get View By Id Failure', + props<{ error: Error }>(), +); + +// get all views by resource id +export const getAllViewsByResourceId = createAction( + '[Onboarding Client Profile Views API] Get All Views By Resource Id', + props<{ resourceId: number; pageNum: number; pageSize: number }>(), +); + +export const getAllViewsByResourceIdSuccess = createAction( + '[Onboarding Client Profile Views API] Get All Views By Resource Id Success', + props<{ views: View[]; totalViews: number }>(), +); + +export const getAllViewsByResourceIdFailure = createAction( + '[Onboarding Client Profile Views API] Get All Views By Resource Id Failure', + props<{ error: Error }>(), +); + +// edit resource view +export const editResourceView = createAction( + '[Onboarding Client Profile Views API] Edit Resource View', + props<{ viewId: number; newView: ICreateViewDto }>(), +); + +export const editResourceViewSuccess = createAction( + '[Onboarding Client Profile Views API] Edit Resource View Success', + props<{ revision: Revision }>(), +); + +export const editResourceViewFailure = createAction( + '[Onboarding Client Profile Views API] Edit Resource View Failure', + props<{ error: Error }>(), +); + +// create resource view +export const createResourceView = createAction( + '[Onboarding Client Profile Views API] Create Resource View', + props<{ resourceId: number; newView: ICreateViewDto }>(), +); + +export const createResourceViewSuccess = createAction( + '[Onboarding Client Profile Views API] Create Resource View Success', + props<{ view: View }>(), +); + +export const createResourceViewFailure = createAction( + '[Onboarding Client Profile Views API] Create Resource View Failure', + props<{ error: Error }>(), +); + +// get original resume by resource id +export const getResourceOriginalResumeById = createAction( + '[Onboarding Client User API] Get Original Resume By Resource Id', + props<{ resourceId: number }>(), +); + +export const getResourceOriginalResumeByIdSuccess = createAction( + '[Onboarding Client User API] Get Original Resume By Resource Id Success', + props<{ resume: Blob }>(), +); + +export const getResourceOriginalResumeByIdFailure = createAction( + '[Onboarding Client User API] Get Original Resume By Resource Id Failure', + props<{ error: Error }>(), +); + +// download profile/resume +export const downloadProfileByViewId = createAction( + '[Onboarding Client Profile Views API] Download Profile By View Id', + props<{ viewId: number }>(), +); + +export const downloadProfileByViewIdSuccess = createAction( + '[Onboarding Client Profile Views API] Download Profile By View Id Success', + props<{ resume: Blob }>(), +); + +export const downloadProfileByViewIdFailure = createAction( + '[Onboarding Client Profile Views API] Download Profile By View Id Failure', + props<{ error: Error }>(), +); + +// approve or deny revision +export const approveOrDenyRevision = createAction( + '[Onboarding Client Profile Views API] Approve Or Deny Revision', + props<{ viewId: number; comment: string; approval: boolean }>(), +); + +export const approveOrDenyRevisionSuccess = createAction( + '[Onboarding Client Profile Views API] Approve Or Deny Revision Success', + props<{ approveOrDeny: ApproveViewDto }>(), +); + +export const approveOrDenyRevisionFailure = createAction( + '[Onboarding Client Profile Views API] Approve Or Deny Revision Failure', + props<{ error: Error }>(), +); diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/resource.effects.ts b/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/resource.effects.ts new file mode 100644 index 00000000..9b89ae77 --- /dev/null +++ b/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/resource.effects.ts @@ -0,0 +1,224 @@ +import { Actions, createEffect, ofType } from '@ngrx/effects'; +import { of, switchMap } from 'rxjs'; +import { map, catchError } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; +import { Injectable } from '@angular/core'; +import { + OnboardingClientAuthService, + OnboardingClientResourceService, + OnboardingClientViewsService, +} from '../../services'; +import { + approveOrDenyRevision, + approveOrDenyRevisionSuccess, + createResourceView, + createResourceViewFailure, + createResourceViewSuccess, + getAllViewsByResourceId, + getResourceInformation, + getResourceInformationById, + getResourceInformationByIdSuccess, + getResourceInformationFailure, + getResourceOriginalResumeById, + updateInfoFailure, + updateUserInfo, + updateUserInfoSuccess, +} from './resource.actions'; +import { + approveOrDenyRevisionFailure, + downloadProfileByViewId, + downloadProfileByViewIdFailure, + downloadProfileByViewIdSuccess, + editResourceView, + editResourceViewFailure, + editResourceViewSuccess, + getAllViewsByResourceIdFailure, + getAllViewsByResourceIdSuccess, + getResourceInformationByIdFailure, + getResourceInformationSuccess, + getResourceOriginalResumeByIdSuccess, + getViewById, + getViewByIdFailure, + getViewByIdSuccess, +} from '.'; +import { OnboardingClientState } from '../onboardingClient.state'; + +@Injectable() +export class ResourceEffects { + constructor( + private readonly actions$: Actions, + private sharedStore: Store, + private authService: OnboardingClientAuthService, + private resourceService: OnboardingClientResourceService, + private viewsService: OnboardingClientViewsService, + ) {} + + getResourceInformation$ = createEffect(() => + this.actions$.pipe( + ofType(getResourceInformation), + switchMap(() => + this.resourceService.getResourceInformation().pipe( + map(data => { + return getResourceInformationSuccess({ + userId: data.id, + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + phoneNumber: data.phoneNumber, + city: data.location.city, + province: data.location.province, + country: data.location.country, + linkedInLink: data.linkedInLink, + githubLink: data.githubLink, + otherLink: data.otherLink, + projectResources: data.projectResources, + }); + }), + catchError(error => of(getResourceInformationFailure({ error }))), + ), + ), + ), + ); + + getResourceInformationById$ = createEffect(() => + this.actions$.pipe( + ofType(getResourceInformationById), + switchMap(action => + this.resourceService.getResourceInformationById(action.resourceId).pipe( + map(data => { + return getResourceInformationByIdSuccess({ + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + phoneNumber: data.phoneNumber, + city: data.location.city, + province: data.location.province, + country: data.location.country, + linkedInLink: data.linkedInLink, + githubLink: data.githubLink, + otherLink: data.otherLink, + projectResources: data.projectResources, + }); + }), + catchError(error => of(getResourceInformationByIdFailure({ error }))), + ), + ), + ), + ); + + updateInfo$ = createEffect(() => + this.actions$.pipe( + ofType(updateUserInfo), + switchMap(action => + this.resourceService.editResourcePersonalInformation(action.updatedPersonalInformation).pipe( + map(data => { + return updateUserInfoSuccess({ + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + }); + }), + catchError(error => of(updateInfoFailure({ error }))), + ), + ), + ), + ); + + getViewById$ = createEffect(() => + this.actions$.pipe( + ofType(getViewById), + switchMap(data => + this.viewsService.getViewById(data.viewId).pipe( + map(res => { + return getViewByIdSuccess({ view: res }); + }), + catchError(error => of(getViewByIdFailure({ error }))), + ), + ), + ), + ); + + getAllViews$ = createEffect(() => + this.actions$.pipe( + ofType(getAllViewsByResourceId), + switchMap(data => + this.viewsService.getViewsByResourceId(data.resourceId, data.pageNum, data.pageSize).pipe( + map(res => { + return getAllViewsByResourceIdSuccess({ views: res.views, totalViews: res.totalViews }); + }), + catchError(error => of(getAllViewsByResourceIdFailure({ error }))), + ), + ), + ), + ); + + editResourceView$ = createEffect(() => + this.actions$.pipe( + ofType(editResourceView), + switchMap(data => + this.viewsService.editResourceView(data.viewId, data.newView).pipe( + map(res => { + return editResourceViewSuccess({ revision: res }); + }), + catchError(error => of(editResourceViewFailure({ error }))), + ), + ), + ), + ); + + createResourceView$ = createEffect(() => + this.actions$.pipe( + ofType(createResourceView), + switchMap(data => + this.viewsService.createSecondaryView(data.resourceId, data.newView).pipe( + map(res => { + return createResourceViewSuccess({ view: res }); + }), + catchError(error => of(createResourceViewFailure({ error }))), + ), + ), + ), + ); + + getResourceOriginalResume$ = createEffect(() => + this.actions$.pipe( + ofType(getResourceOriginalResumeById), + switchMap(data => + this.resourceService.getResourceOriginalResumeById(data.resourceId).pipe( + map(res => { + return getResourceOriginalResumeByIdSuccess({ resume: res }); + }), + catchError(error => of(getAllViewsByResourceIdFailure({ error }))), + ), + ), + ), + ); + + downloadProfile$ = createEffect(() => + this.actions$.pipe( + ofType(downloadProfileByViewId), + switchMap(data => + this.resourceService.downloadProfile(data.viewId).pipe( + map(res => { + return downloadProfileByViewIdSuccess({ resume: res }); + }), + catchError(error => of(downloadProfileByViewIdFailure({ error }))), + ), + ), + ), + ); + + approveOrDeny$ = createEffect(() => + this.actions$.pipe( + ofType(approveOrDenyRevision), + switchMap(data => + this.viewsService.approveOrDenyRevision(data.viewId, data.comment, data.approval).pipe( + map(res => { + return approveOrDenyRevisionSuccess({ approveOrDeny: res }); + }), + catchError(error => of(approveOrDenyRevisionFailure({ error }))), + ), + ), + ), + ); +} diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/resource.reducers.ts b/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/resource.reducers.ts new file mode 100644 index 00000000..a4623f31 --- /dev/null +++ b/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/resource.reducers.ts @@ -0,0 +1,237 @@ +import { createReducer, on } from '@ngrx/store'; +import { ApproveViewDto } from '@tempus/api/shared/dto'; +import { ProjectResource, Revision, View } from '@tempus/shared-domain'; +import { AsyncRequestState } from '../../enum'; +import * as ResourceActions from './resource.actions'; + +export const RESOURCE_INFO_FEATURE_KEY = 'resource'; + +export interface ResourceState { + userId: number; + firstName: string | null; + lastName: string | null; + email: string | null; + calEmail: string | null; + phoneNumber: string | null; + views: View[] | []; + view: View | null; + revision: Revision | null; + resume: Blob | null; + totalViewsData: number; + city: string | null; + province: string | null; + country: string | null; + linkedInLink: string | null; + githubLink: string | null; + otherLink: string | null; + projectResources: ProjectResource[] | []; + approveOrDeny: ApproveViewDto | null; + error: Error | null; +} + +export const initialState: ResourceState = { + userId: 0, + firstName: null, + lastName: null, + email: null, + calEmail: null, + phoneNumber: null, + views: [], + view: null, + revision: null, + resume: null, + totalViewsData: 0, + city: null, + province: null, + country: null, + linkedInLink: null, + githubLink: null, + otherLink: null, + projectResources: [], + approveOrDeny: null, + error: null, +}; + +export const resourceReducer = createReducer( + initialState, + // getResourceInformation + on(ResourceActions.getResourceInformation, state => ({ ...state, status: AsyncRequestState.LOADING })), + on( + ResourceActions.getResourceInformationSuccess, + ( + state, + { + userId, + firstName, + lastName, + email, + calEmail, + city, + province, + country, + phoneNumber, + linkedInLink, + githubLink, + otherLink, + projectResources, + }, + ) => ({ + ...state, + userId, + firstName, + lastName, + email, + calEmail, + city, + province, + country, + phoneNumber, + linkedInLink, + githubLink, + otherLink, + projectResources, + }), + ), + on(ResourceActions.getResourceInformationFailure, (state, { error }) => ({ + ...state, + status: AsyncRequestState.ERROR, + error, + })), + // getResourceInformationById + on(ResourceActions.getResourceInformationById, state => ({ ...state, status: AsyncRequestState.LOADING })), + on( + ResourceActions.getResourceInformationByIdSuccess, + ( + state, + { + firstName, + lastName, + email, + calEmail, + city, + province, + country, + phoneNumber, + linkedInLink, + githubLink, + otherLink, + projectResources, + }, + ) => ({ + ...state, + firstName, + lastName, + email, + calEmail, + city, + province, + country, + phoneNumber, + linkedInLink, + githubLink, + otherLink, + projectResources, + }), + ), + on(ResourceActions.getResourceInformationByIdFailure, (state, { error }) => ({ + ...state, + status: AsyncRequestState.ERROR, + error, + })), + + // updateUserInformation + on(ResourceActions.updateUserInfo, state => ({ ...state, status: AsyncRequestState.LOADING })), + on(ResourceActions.updateUserInfoSuccess, (state, { firstName, lastName, email }) => ({ + ...state, + firstName, + lastName, + email, + })), + on(ResourceActions.updateInfoFailure, (state, { error }) => ({ + ...state, + status: AsyncRequestState.ERROR, + error, + })), + // getViewById + on(ResourceActions.getViewById, state => ({ ...state, status: AsyncRequestState.LOADING })), + on(ResourceActions.getViewByIdSuccess, (state, { view }) => ({ + ...state, + view, + status: AsyncRequestState.SUCCESS, + error: null, + })), + on(ResourceActions.getViewByIdFailure, (state, { error }) => ({ + ...state, + error, + status: AsyncRequestState.ERROR, + })), + // getAllViewsByResourceId + on(ResourceActions.getAllViewsByResourceId, state => ({ ...state, status: AsyncRequestState.LOADING })), + on(ResourceActions.getAllViewsByResourceIdSuccess, (state, { views, totalViews }) => ({ + ...state, + views, + totalViewsData: totalViews, + status: AsyncRequestState.SUCCESS, + error: null, + })), + on(ResourceActions.getAllViewsByResourceIdFailure, (state, { error }) => ({ + ...state, + error, + status: AsyncRequestState.ERROR, + })), + // editResourceView + on(ResourceActions.editResourceView, state => ({ ...state, status: AsyncRequestState.LOADING })), + on(ResourceActions.editResourceViewSuccess, (state, { revision }) => ({ + ...state, + revision, + status: AsyncRequestState.SUCCESS, + })), + on(ResourceActions.editResourceViewFailure, (state, { error }) => ({ + ...state, + error, + })), + // createResourceView + on(ResourceActions.createResourceView, state => ({ ...state, status: AsyncRequestState.LOADING })), + on(ResourceActions.createResourceViewSuccess, (state, { view }) => ({ + ...state, + view, + status: AsyncRequestState.SUCCESS, + })), + on(ResourceActions.createResourceViewFailure, (state, { error }) => ({ + ...state, + error, + })), + // getResourceOriginalResumeById + on(ResourceActions.getResourceOriginalResumeById, state => ({ ...state, status: AsyncRequestState.LOADING })), + on(ResourceActions.getResourceOriginalResumeByIdSuccess, (state, { resume }) => ({ + ...state, + resume, + status: AsyncRequestState.SUCCESS, + })), + on(ResourceActions.getResourceOriginalResumeByIdFailure, (state, { error }) => ({ + ...state, + error, + })), + // downloadProfileByViewId + on(ResourceActions.downloadProfileByViewId, state => ({ ...state, status: AsyncRequestState.LOADING })), + on(ResourceActions.downloadProfileByViewIdSuccess, (state, { resume }) => ({ + ...state, + resume, + status: AsyncRequestState.SUCCESS, + })), + on(ResourceActions.downloadProfileByViewIdFailure, (state, { error }) => ({ + ...state, + error, + })), + // approveOrDenyView + on(ResourceActions.approveOrDenyRevision, state => ({ ...state, status: AsyncRequestState.LOADING })), + on(ResourceActions.approveOrDenyRevisionSuccess, (state, { approveOrDeny }) => ({ + ...state, + approveOrDeny, + status: AsyncRequestState.SUCCESS, + })), + on(ResourceActions.approveOrDenyRevisionFailure, (state, { error }) => ({ + ...state, + error, + })), +); diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/resource.selectors.ts b/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/resource.selectors.ts new file mode 100644 index 00000000..138925ab --- /dev/null +++ b/libs/client/onboarding-client/shared/data-access/src/lib/+state/resource/resource.selectors.ts @@ -0,0 +1,45 @@ +import { createSelector } from '@ngrx/store'; +import { OnboardingClientState, selectOnboardingClientState } from '../onboardingClient.state'; + +import { ResourceState, RESOURCE_INFO_FEATURE_KEY } from './resource.reducers'; + +export const selectState = createSelector( + selectOnboardingClientState, + (state: OnboardingClientState) => state[RESOURCE_INFO_FEATURE_KEY], +); + +export const selectResourceDetails = createSelector(selectState, (state: ResourceState) => { + return { + userId: state.userId, + firstName: state.firstName, + lastName: state.lastName, + email: state.email, + calEmail: state.calEmail, + phoneNumber: state.phoneNumber, + city: state.city, + province: state.province, + country: state.country, + linkedInLink: state.linkedInLink, + githubLink: state.githubLink, + otherLink: state.otherLink, + projectResources: state.projectResources, + }; +}); + +export const selectResourceBasicDetails = createSelector(selectState, (state: ResourceState) => { + return { firstName: state.firstName, lastName: state.lastName, email: state.email }; +}); + +export const selectView = createSelector(selectState, (state: ResourceState) => state.view); + +export const selectResourceViews = createSelector(selectState, (state: ResourceState) => { + return { views: state.views, totalViews: state.totalViewsData }; +}); + +export const selectRevision = createSelector(selectState, (state: ResourceState) => state.revision); + +export const selectResourceOriginalResume = createSelector(selectState, (state: ResourceState) => state.resume); + +export const selectApproveOrDeny = createSelector(selectState, (state: ResourceState) => state.approveOrDeny); + +export const selectDownloadProfile = createSelector(selectState, (state: ResourceState) => state.resume); diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/onboarding-client-shared-data-access.module.ts b/libs/client/onboarding-client/shared/data-access/src/lib/onboarding-client-shared-data-access.module.ts index e4bd940f..669e5c9d 100644 --- a/libs/client/onboarding-client/shared/data-access/src/lib/onboarding-client-shared-data-access.module.ts +++ b/libs/client/onboarding-client/shared/data-access/src/lib/onboarding-client-shared-data-access.module.ts @@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; -import { ONBOARDING_CLIENT_FEATURE_KEY, reducers } from './+state'; +import { ONBOARDING_CLIENT_FEATURE_KEY, reducers, ResourceEffects } from './+state'; import { hydrationMetaReducer } from './+state/hydration.reducer'; import { AuthEffects } from './+state/auth/auth.effects'; @@ -10,7 +10,7 @@ import { AuthEffects } from './+state/auth/auth.effects'; imports: [ CommonModule, StoreModule.forFeature(ONBOARDING_CLIENT_FEATURE_KEY, reducers, { metaReducers: [hydrationMetaReducer] }), - EffectsModule.forFeature([AuthEffects]), + EffectsModule.forFeature([AuthEffects, ResourceEffects]), ], }) export class OnboardingClientSharedDataAccessModule {} diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/services/onboarding-client-auth.service.ts b/libs/client/onboarding-client/shared/data-access/src/lib/services/onboarding-client-auth.service.ts index f0357bc8..60f3cac0 100644 --- a/libs/client/onboarding-client/shared/data-access/src/lib/services/onboarding-client-auth.service.ts +++ b/libs/client/onboarding-client/shared/data-access/src/lib/services/onboarding-client-auth.service.ts @@ -4,9 +4,9 @@ import { Inject, Injectable } from '@angular/core'; import { AppConfig, AuthDto, ResetPasswordDto, TokensDto } from '@tempus/shared-domain'; import { catchError, Observable, tap } from 'rxjs'; import { APP_CONFIG } from '@tempus/app-config'; +import { decodeJwt } from '@tempus/client/shared/util'; import { SessionStorageKey } from '../enum'; import { handleError } from './errorHandler'; -import { decodeJwt } from '@tempus/client/shared/util'; @Injectable({ providedIn: 'root' }) export class OnboardingClientAuthService { @@ -17,15 +17,14 @@ export class OnboardingClientAuthService { public login(password: string, email: string): Observable { return this.http.post(`${this.url}/login`, { password, email }).pipe( tap(data => { - const { roles } = decodeJwt(data.accessToken || ''); + const { roles } = decodeJwt(data.accessToken || ''); this.setUserDataInSessionStorage( data.accessToken, data.refreshToken, - data.user.id, data.user.firstName, data.user.lastName, data.user.email, - roles + roles, ); }), catchError(handleError), @@ -41,6 +40,29 @@ export class OnboardingClientAuthService { ); } + // UNUSED FOR NOW -- will need to figure out how to inject into hyrdationReducer to use this in there + public getUserDataFromSessionStorage(): { + accessToken: string | null; + refreshToken: string | null; + email: string | null; + firstName: string | null; + lastName: string | null; + } { + const accessToken: string | null = sessionStorage.getItem(SessionStorageKey.TEMPUS_ACCESS_TOKEN); + const refreshToken: string | null = sessionStorage.getItem(SessionStorageKey.TEMPUS_REFRESH_TOKEN); + const firstName: string | null = sessionStorage.getItem(SessionStorageKey.TEMPUS_FIRST_NAME); + const lastName: string | null = sessionStorage.getItem(SessionStorageKey.TEMPUS_LAST_NAME); + const email: string | null = sessionStorage.getItem(SessionStorageKey.TEMPUS_EMAIL); + + return { + accessToken, + refreshToken, + firstName, + lastName, + email, + }; + } + public forgotPassword(email: string): Observable { return this.http.post(`${this.url}/forgot-password?email=${email}`, {}).pipe(catchError(handleError)); } @@ -50,28 +72,26 @@ export class OnboardingClientAuthService { } public updateAccessAndRefreshTokenInStorage(accessToken: string, refreshToken: string) { - const { roles } = decodeJwt(accessToken || ''); + const { roles } = decodeJwt(accessToken || ''); sessionStorage.setItem(SessionStorageKey.TEMPUS_ACCESS_TOKEN, accessToken); sessionStorage.setItem(SessionStorageKey.TEMPUS_REFRESH_TOKEN, refreshToken); - sessionStorage.setItem(SessionStorageKey.TEMPUS_ROLES, JSON.stringify(roles)); + sessionStorage.setItem(SessionStorageKey.TEMPUS_ROLES, JSON.stringify(roles)); } public setUserDataInSessionStorage( accessToken: string, refreshToken: string, - userId: number, firstName: string, lastName: string, email: string, - roles: string[] + roles: string[], ) { sessionStorage.setItem(SessionStorageKey.TEMPUS_ACCESS_TOKEN, accessToken); sessionStorage.setItem(SessionStorageKey.TEMPUS_REFRESH_TOKEN, refreshToken); - sessionStorage.setItem(SessionStorageKey.TEMPUS_USER_ID, userId.toString()); sessionStorage.setItem(SessionStorageKey.TEMPUS_FIRST_NAME, firstName); sessionStorage.setItem(SessionStorageKey.TEMPUS_LAST_NAME, lastName); sessionStorage.setItem(SessionStorageKey.TEMPUS_EMAIL, email); - sessionStorage.setItem(SessionStorageKey.TEMPUS_ROLES, JSON.stringify(roles)); + sessionStorage.setItem(SessionStorageKey.TEMPUS_ROLES, JSON.stringify(roles)); } public logout() { @@ -80,11 +100,10 @@ export class OnboardingClientAuthService { public resetSessionStorage() { sessionStorage.removeItem(SessionStorageKey.TEMPUS_ACCESS_TOKEN); - sessionStorage.removeItem(SessionStorageKey.TEMPUS_USER_ID); sessionStorage.removeItem(SessionStorageKey.TEMPUS_FIRST_NAME); sessionStorage.removeItem(SessionStorageKey.TEMPUS_LAST_NAME); sessionStorage.removeItem(SessionStorageKey.TEMPUS_EMAIL); sessionStorage.removeItem(SessionStorageKey.TEMPUS_REFRESH_TOKEN); - sessionStorage.removeItem(SessionStorageKey.TEMPUS_ROLES); + sessionStorage.removeItem(SessionStorageKey.TEMPUS_ROLES); } } diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/services/onboarding-client-resource.service.ts b/libs/client/onboarding-client/shared/data-access/src/lib/services/onboarding-client-resource.service.ts index b4c53585..44bea5a9 100644 --- a/libs/client/onboarding-client/shared/data-access/src/lib/services/onboarding-client-resource.service.ts +++ b/libs/client/onboarding-client/shared/data-access/src/lib/services/onboarding-client-resource.service.ts @@ -107,6 +107,7 @@ export class OnboardingClientResourceService { } // TODO: look into moving to views service + // DELETE public getViewById(viewId: number): Observable { return this.http.get(`${this.url}/profile-view/view/${viewId}`).pipe(catchError(handleError)); } @@ -118,12 +119,14 @@ export class OnboardingClientResourceService { .pipe(catchError(handleError)); } + // DELETE public createSecondaryView(resourceId: number, newView: ICreateViewDto): Observable { return this.http .post(`${this.url}/profile-view/${resourceId}/new-view`, newView) .pipe(catchError(handleError)); } + // DELETE public editResourceView(viewId: number, newView: ICreateViewDto): Observable { return this.http.patch(`${this.url}/profile-view/${viewId}`, newView).pipe(catchError(handleError)); } @@ -141,6 +144,7 @@ export class OnboardingClientResourceService { return this.http.get(`${this.url}/profile-view/download-resume/${viewId}`, httpOptions); } + // DELETE public approveOrDenyRevision(id: number, comment: string, approval: boolean): Observable { return this.http .post(`${this.url}/profile-view/approve/${id}`, { comment, approval }) diff --git a/libs/client/onboarding-client/shared/data-access/src/lib/services/onboarding-client-views.service.ts b/libs/client/onboarding-client/shared/data-access/src/lib/services/onboarding-client-views.service.ts index 756d41d7..00e76a12 100644 --- a/libs/client/onboarding-client/shared/data-access/src/lib/services/onboarding-client-views.service.ts +++ b/libs/client/onboarding-client/shared/data-access/src/lib/services/onboarding-client-views.service.ts @@ -1,7 +1,8 @@ import { HttpClient } from '@angular/common/http'; import { Inject, Injectable } from '@angular/core'; +import { ApproveViewDto } from '@tempus/api/shared/dto'; import { APP_CONFIG } from '@tempus/app-config'; -import { AppConfig, RevisionType, View } from '@tempus/shared-domain'; +import { AppConfig, ICreateViewDto, Revision, RevisionType, View } from '@tempus/shared-domain'; import { catchError, Observable } from 'rxjs'; import { handleError } from './errorHandler'; @@ -41,7 +42,29 @@ export class OnboardingClientViewsService { .pipe(catchError(handleError)); } + public getViewById(viewId: number): Observable { + return this.http.get(`${this.url}/view/${viewId}`).pipe(catchError(handleError)); + } + + public editResourceView(viewId: number, newView: ICreateViewDto): Observable { + return this.http.patch(`${this.url}/${viewId}`, newView).pipe(catchError(handleError)); + } + + public createSecondaryView(resourceId: number, newView: ICreateViewDto): Observable { + return this.http + .post(`${this.url}/profile-view/${resourceId}/new-view`, newView) + .pipe(catchError(handleError)); + } + + public approveOrDenyRevision(id: number, comment: string, approval: boolean): Observable { + return this.http + .post(`${this.url}/profile-view/approve/${id}`, { comment, approval }) + .pipe(catchError(handleError)); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any public deleteView(viewId: number): Observable { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return this.http.delete(`${this.url}/${viewId}`).pipe(catchError(handleError)); } }