diff --git a/src/app/features/profile/profile.component.html b/src/app/features/profile/profile.component.html
index 7ebc21851..7a009aa26 100644
--- a/src/app/features/profile/profile.component.html
+++ b/src/app/features/profile/profile.component.html
@@ -2,6 +2,16 @@
} @else {
@if (user()) {
+ @if (user()?.mergedBy) {
+
+
+
+ {{ 'profile.mergedAccount.message' | translate }}
+ {{ user()?.mergedBy }}
+
+
+
+ }
@if (defaultSearchFiltersInitialized()) {
diff --git a/src/app/features/profile/profile.component.spec.ts b/src/app/features/profile/profile.component.spec.ts
index 451e2c6e2..1853dc2eb 100644
--- a/src/app/features/profile/profile.component.spec.ts
+++ b/src/app/features/profile/profile.component.spec.ts
@@ -1,6 +1,7 @@
import { MockComponents, MockProvider } from 'ng-mocks';
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
import { PrerenderReadyService } from '@core/services/prerender-ready.service';
@@ -13,6 +14,7 @@ import { ProfileInformationComponent } from './components';
import { ProfileComponent } from './profile.component';
import { ProfileSelectors } from './store';
+import { MOCK_USER } from '@testing/mocks/data.mock';
import { OSFTestingModule } from '@testing/osf.testing.module';
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
import { RouterMockBuilder } from '@testing/providers/router-provider.mock';
@@ -78,4 +80,69 @@ describe('ProfileComponent', () => {
expect(component.resourceTabOptions).toBeDefined();
expect(component.resourceTabOptions.every((option) => option.value !== ResourceType.Agent)).toBe(true);
});
+
+ describe('merged user message', () => {
+ it('should display merged message when user has mergedBy property', async () => {
+ const mergedUser = { ...MOCK_USER, mergedBy: 'https://osf.io/user123/' };
+
+ await TestBed.configureTestingModule({
+ imports: [
+ ProfileComponent,
+ OSFTestingModule,
+ ...MockComponents(ProfileInformationComponent, GlobalSearchComponent, LoadingSpinnerComponent),
+ ],
+ providers: [
+ MockProvider(Router, routerMock),
+ MockProvider(ActivatedRoute, activatedRouteMock),
+ MockProvider(PrerenderReadyService),
+ provideMockStore({
+ signals: [
+ { selector: UserSelectors.getCurrentUser, value: mergedUser },
+ { selector: ProfileSelectors.getUserProfile, value: null },
+ { selector: ProfileSelectors.isUserProfileLoading, value: false },
+ ],
+ }),
+ ],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ProfileComponent);
+ fixture.detectChanges();
+
+ const messageElement = fixture.debugElement.query(By.css('p-message'));
+ expect(messageElement).toBeTruthy();
+
+ const linkElement = fixture.debugElement.query(By.css('p-message a'));
+ expect(linkElement.nativeElement.href).toContain('https://osf.io/user123/');
+ });
+
+ it('should not display merged message when user does not have mergedBy property', async () => {
+ const normalUser = { ...MOCK_USER, mergedBy: undefined };
+
+ await TestBed.configureTestingModule({
+ imports: [
+ ProfileComponent,
+ OSFTestingModule,
+ ...MockComponents(ProfileInformationComponent, GlobalSearchComponent, LoadingSpinnerComponent),
+ ],
+ providers: [
+ MockProvider(Router, routerMock),
+ MockProvider(ActivatedRoute, activatedRouteMock),
+ MockProvider(PrerenderReadyService),
+ provideMockStore({
+ signals: [
+ { selector: UserSelectors.getCurrentUser, value: normalUser },
+ { selector: ProfileSelectors.getUserProfile, value: null },
+ { selector: ProfileSelectors.isUserProfileLoading, value: false },
+ ],
+ }),
+ ],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ProfileComponent);
+ fixture.detectChanges();
+
+ const messageElement = fixture.debugElement.query(By.css('p-message'));
+ expect(messageElement).toBeFalsy();
+ });
+ });
});
diff --git a/src/app/features/profile/profile.component.ts b/src/app/features/profile/profile.component.ts
index 9aac4d808..fb7c186a8 100644
--- a/src/app/features/profile/profile.component.ts
+++ b/src/app/features/profile/profile.component.ts
@@ -1,5 +1,9 @@
import { createDispatchMap, select } from '@ngxs/store';
+import { TranslatePipe } from '@ngx-translate/core';
+
+import { Message } from 'primeng/message';
+
import {
ChangeDetectionStrategy,
Component,
@@ -30,7 +34,7 @@ import { FetchUserProfile, ProfileSelectors, SetUserProfile } from './store';
templateUrl: './profile.component.html',
styleUrl: './profile.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
- imports: [ProfileInformationComponent, GlobalSearchComponent, LoadingSpinnerComponent],
+ imports: [ProfileInformationComponent, GlobalSearchComponent, LoadingSpinnerComponent, Message, TranslatePipe],
})
export class ProfileComponent implements OnInit, OnDestroy {
private router = inject(Router);
diff --git a/src/app/shared/mappers/user/user.mapper.ts b/src/app/shared/mappers/user/user.mapper.ts
index 50b7b8b71..e55a1faac 100644
--- a/src/app/shared/mappers/user/user.mapper.ts
+++ b/src/app/shared/mappers/user/user.mapper.ts
@@ -36,6 +36,7 @@ export class UserMapper {
canViewReviews: user.attributes.can_view_reviews === true, // [NS] Do not simplify it
timezone: user.attributes.timezone,
locale: user.attributes.locale,
+ mergedBy: user.links.merged_by,
};
}
diff --git a/src/app/shared/models/user/user-json-api.model.ts b/src/app/shared/models/user/user-json-api.model.ts
index fd2fc083f..07ce651e0 100644
--- a/src/app/shared/models/user/user-json-api.model.ts
+++ b/src/app/shared/models/user/user-json-api.model.ts
@@ -70,6 +70,7 @@ interface UserLinksJsonApi {
iri: string;
profile_image: string;
self: string;
+ merged_by?: string;
}
interface UserRelationshipsJsonApi {
diff --git a/src/app/shared/models/user/user.models.ts b/src/app/shared/models/user/user.models.ts
index 2eadb1721..22ee27f4e 100644
--- a/src/app/shared/models/user/user.models.ts
+++ b/src/app/shared/models/user/user.models.ts
@@ -27,4 +27,5 @@ export interface UserModel {
defaultRegionId: string;
link?: string;
iri?: string;
+ mergedBy?: string;
}
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 4e1eafd6c..0386324d2 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -227,6 +227,11 @@
"institutions": "Institutions",
"recentActivity": "Recent Activity"
},
+ "profile": {
+ "mergedAccount": {
+ "message": "This account has been merged with "
+ }
+ },
"toast": {
"tos-consent": {
"message": "Notice: We've updated our",
diff --git a/src/testing/mocks/data.mock.ts b/src/testing/mocks/data.mock.ts
index 7a1fd3c37..6acecbf35 100644
--- a/src/testing/mocks/data.mock.ts
+++ b/src/testing/mocks/data.mock.ts
@@ -57,6 +57,7 @@ export const MOCK_USER: UserModel = {
defaultRegionId: 'us',
allowIndexing: true,
canViewReviews: true,
+ mergedBy: undefined,
};
export const MOCK_USER_RELATED_COUNTS: UserRelatedCounts = {