diff --git a/src/app/features/profile/components/profile-information/profile-information.component.html b/src/app/features/profile/components/profile-information/profile-information.component.html index b4636126f..80ae98a4a 100644 --- a/src/app/features/profile/components/profile-information/profile-information.component.html +++ b/src/app/features/profile/components/profile-information/profile-information.component.html @@ -35,6 +35,25 @@

{{ currentUser()?.fullName }}

} +
+ @for (institution of currentUserInstitutions(); track $index) { + + + + } +
+ @if (!isMedium() && showEdit()) {
{ it('should initialize with default inputs', () => { expect(component.currentUser()).toBeUndefined(); expect(component.showEdit()).toBe(false); + expect(component.currentUserInstitutions()).toBeUndefined(); }); it('should accept user input', () => { @@ -172,4 +175,28 @@ describe('ProfileInformationComponent', () => { component.toProfileSettings(); expect(component.editProfile.emit).toHaveBeenCalled(); }); + + it('should accept currentUserInstitutions input', () => { + const mockInstitutions: Institution[] = [MOCK_INSTITUTION]; + fixture.componentRef.setInput('currentUserInstitutions', mockInstitutions); + fixture.detectChanges(); + expect(component.currentUserInstitutions()).toEqual(mockInstitutions); + }); + + it('should not render institution logos when currentUserInstitutions is undefined', () => { + fixture.componentRef.setInput('currentUserInstitutions', undefined); + fixture.detectChanges(); + const logos = fixture.nativeElement.querySelectorAll('img.fit-contain'); + expect(logos.length).toBe(0); + }); + + it('should render institution logos when currentUserInstitutions is provided', () => { + const institutions: Institution[] = [MOCK_INSTITUTION]; + fixture.componentRef.setInput('currentUserInstitutions', institutions); + fixture.detectChanges(); + + const logos = fixture.nativeElement.querySelectorAll('img.fit-contain'); + expect(logos.length).toBe(institutions.length); + expect(logos[0].alt).toBe(institutions[0].name); + }); }); diff --git a/src/app/features/profile/components/profile-information/profile-information.component.ts b/src/app/features/profile/components/profile-information/profile-information.component.ts index 3304a8ce2..0740068b0 100644 --- a/src/app/features/profile/components/profile-information/profile-information.component.ts +++ b/src/app/features/profile/components/profile-information/profile-information.component.ts @@ -5,6 +5,7 @@ import { Button } from 'primeng/button'; import { DatePipe, NgOptimizedImage } from '@angular/common'; import { ChangeDetectionStrategy, Component, computed, inject, input, output } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; +import { RouterLink } from '@angular/router'; import { EducationHistoryComponent } from '@osf/shared/components/education-history/education-history.component'; import { EmploymentHistoryComponent } from '@osf/shared/components/employment-history/employment-history.component'; @@ -12,6 +13,7 @@ import { SOCIAL_LINKS } from '@osf/shared/constants/social-links.const'; import { IS_MEDIUM } from '@osf/shared/helpers/breakpoints.tokens'; import { UserModel } from '@osf/shared/models/user/user.models'; import { SortByDatePipe } from '@osf/shared/pipes/sort-by-date.pipe'; +import { Institution } from '@shared/models/institutions/institutions.models'; import { mapUserSocials } from '../../helpers'; @@ -25,6 +27,7 @@ import { mapUserSocials } from '../../helpers'; DatePipe, NgOptimizedImage, SortByDatePipe, + RouterLink, ], templateUrl: './profile-information.component.html', styleUrl: './profile-information.component.scss', @@ -32,6 +35,8 @@ import { mapUserSocials } from '../../helpers'; }) export class ProfileInformationComponent { currentUser = input(); + + currentUserInstitutions = input(); showEdit = input(false); editProfile = output(); diff --git a/src/app/features/profile/profile.component.html b/src/app/features/profile/profile.component.html index 4176e33fc..958d22e05 100644 --- a/src/app/features/profile/profile.component.html +++ b/src/app/features/profile/profile.component.html @@ -13,7 +13,12 @@ } - +
@if (defaultSearchFiltersInitialized()) { diff --git a/src/app/features/profile/profile.component.ts b/src/app/features/profile/profile.component.ts index fb7c186a8..86e1afa43 100644 --- a/src/app/features/profile/profile.component.ts +++ b/src/app/features/profile/profile.component.ts @@ -25,6 +25,7 @@ import { SEARCH_TAB_OPTIONS } from '@osf/shared/constants/search-tab-options.con import { ResourceType } from '@osf/shared/enums/resource-type.enum'; import { UserModel } from '@osf/shared/models/user/user.models'; import { SetDefaultFilterValue } from '@osf/shared/stores/global-search'; +import { FetchUserInstitutions, InstitutionsSelectors } from '@shared/stores/institutions'; import { ProfileInformationComponent } from './components'; import { FetchUserProfile, ProfileSelectors, SetUserProfile } from './store'; @@ -46,11 +47,13 @@ export class ProfileComponent implements OnInit, OnDestroy { fetchUserProfile: FetchUserProfile, setDefaultFilterValue: SetDefaultFilterValue, setUserProfile: SetUserProfile, + fetchUserInstitutions: FetchUserInstitutions, }); loggedInUser = select(UserSelectors.getCurrentUser); userProfile = select(ProfileSelectors.getUserProfile); isUserLoading = select(ProfileSelectors.isUserProfileLoading); + institutions = select(InstitutionsSelectors.getUserInstitutions); resourceTabOptions = SEARCH_TAB_OPTIONS.filter((x) => x.value !== ResourceType.Agent); @@ -67,6 +70,8 @@ export class ProfileComponent implements OnInit, OnDestroy { } else if (currentUser) { this.setupMyProfile(currentUser); } + + this.actions.fetchUserInstitutions(userId || currentUser?.id); } ngOnDestroy(): void { diff --git a/src/app/features/settings/account-settings/store/account-settings.actions.ts b/src/app/features/settings/account-settings/store/account-settings.actions.ts index db53fa534..e4ba12de7 100644 --- a/src/app/features/settings/account-settings/store/account-settings.actions.ts +++ b/src/app/features/settings/account-settings/store/account-settings.actions.ts @@ -24,6 +24,8 @@ export class DeleteExternalIdentity { export class GetUserInstitutions { static readonly type = '[AccountSettings] Get User Institutions'; + + constructor(public userId = 'me') {} } export class DeleteUserInstitution { diff --git a/src/app/features/settings/account-settings/store/account-settings.state.ts b/src/app/features/settings/account-settings/store/account-settings.state.ts index eee615405..9382c7ee0 100644 --- a/src/app/features/settings/account-settings/store/account-settings.state.ts +++ b/src/app/features/settings/account-settings/store/account-settings.state.ts @@ -84,8 +84,8 @@ export class AccountSettingsState { } @Action(GetUserInstitutions) - getUserInstitutions(ctx: StateContext) { - return this.institutionsService.getUserInstitutions().pipe( + getUserInstitutions(ctx: StateContext, action: GetUserInstitutions) { + return this.institutionsService.getUserInstitutions(action.userId).pipe( tap((userInstitutions) => ctx.patchState({ userInstitutions })), catchError((error) => throwError(() => error)) ); diff --git a/src/app/shared/components/add-project-form/add-project-form.component.spec.ts b/src/app/shared/components/add-project-form/add-project-form.component.spec.ts index 54336feae..ee325c8f8 100644 --- a/src/app/shared/components/add-project-form/add-project-form.component.spec.ts +++ b/src/app/shared/components/add-project-form/add-project-form.component.spec.ts @@ -9,11 +9,11 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; import { UserSelectors } from '@core/store/user'; import { ProjectFormControls } from '@osf/shared/enums/create-project-form-controls.enum'; import { CustomValidators } from '@osf/shared/helpers/custom-form-validators.helper'; -import { ProjectModel } from '@osf/shared/models/projects'; import { InstitutionsSelectors } from '@osf/shared/stores/institutions'; import { ProjectsSelectors } from '@osf/shared/stores/projects'; import { RegionsSelectors } from '@osf/shared/stores/regions'; import { ProjectForm } from '@shared/models/projects/create-project-form.model'; +import { ProjectModel } from '@shared/models/projects/projects.models'; import { AffiliatedInstitutionSelectComponent } from '../affiliated-institution-select/affiliated-institution-select.component'; import { ProjectSelectorComponent } from '../project-selector/project-selector.component'; diff --git a/src/app/shared/services/institutions.service.ts b/src/app/shared/services/institutions.service.ts index 9858f02f2..b90fd89ea 100644 --- a/src/app/shared/services/institutions.service.ts +++ b/src/app/shared/services/institutions.service.ts @@ -47,8 +47,8 @@ export class InstitutionsService { .pipe(map((response) => InstitutionsMapper.fromResponseWithMeta(response))); } - getUserInstitutions(): Observable { - const url = `${this.apiUrl}/users/me/institutions/`; + getUserInstitutions(userId: string): Observable { + const url = `${this.apiUrl}/users/${userId}/institutions/`; return this.jsonApiService .get(url) diff --git a/src/app/shared/stores/institutions/institutions.actions.ts b/src/app/shared/stores/institutions/institutions.actions.ts index 7645e7d6b..4e7790f79 100644 --- a/src/app/shared/stores/institutions/institutions.actions.ts +++ b/src/app/shared/stores/institutions/institutions.actions.ts @@ -3,6 +3,7 @@ import { Institution } from '@shared/models/institutions/institutions.models'; export class FetchUserInstitutions { static readonly type = '[Institutions] Fetch User Institutions'; + constructor(public userId = 'me') {} } export class FetchInstitutions { diff --git a/src/app/shared/stores/institutions/institutions.state.ts b/src/app/shared/stores/institutions/institutions.state.ts index 631f9ec56..757012656 100644 --- a/src/app/shared/stores/institutions/institutions.state.ts +++ b/src/app/shared/stores/institutions/institutions.state.ts @@ -25,10 +25,10 @@ export class InstitutionsState { private readonly institutionsService = inject(InstitutionsService); @Action(FetchUserInstitutions) - getUserInstitutions(ctx: StateContext) { + getUserInstitutions(ctx: StateContext, action: FetchUserInstitutions) { ctx.setState(patch({ userInstitutions: patch({ isLoading: true }) })); - return this.institutionsService.getUserInstitutions().pipe( + return this.institutionsService.getUserInstitutions(action.userId).pipe( tap((institutions) => { ctx.setState( patch({