From 3e0b1113b234ab3beb84567e0f052a3d980c8195 Mon Sep 17 00:00:00 2001 From: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:28:06 -0400 Subject: [PATCH 1/6] feat(profile-settings): Add query-param to specify a tab (#906) --- .../profile-settings.component.spec.ts | 15 ++++++++++++++- .../profile-settings.component.ts | 19 +++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/app/features/settings/profile-settings/profile-settings.component.spec.ts b/src/app/features/settings/profile-settings/profile-settings.component.spec.ts index 59ceb5b48..b9f18bf4a 100644 --- a/src/app/features/settings/profile-settings/profile-settings.component.spec.ts +++ b/src/app/features/settings/profile-settings/profile-settings.component.spec.ts @@ -5,6 +5,7 @@ import { BehaviorSubject } from 'rxjs'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; +import { ActivatedRoute } from '@angular/router'; import { SelectComponent } from '@osf/shared/components/select/select.component'; import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component'; @@ -34,7 +35,11 @@ describe('ProfileSettingsComponent', () => { SelectComponent ), ], - providers: [MockProvider(IS_MEDIUM, isMedium), MockProvider(TranslateService)], + providers: [ + MockProvider(IS_MEDIUM, isMedium), + MockProvider(TranslateService), + { provide: ActivatedRoute, useValue: { snapshot: { queryParams: {} } } }, + ], }).compileComponents(); fixture = TestBed.createComponent(ProfileSettingsComponent); @@ -46,6 +51,14 @@ describe('ProfileSettingsComponent', () => { expect(component).toBeTruthy(); }); + it('should update selected tab on init based on query param', () => { + const testTabValue = 2; + component['route'] = { snapshot: { queryParams: { tab: testTabValue } } } as any; + + component.ngOnInit(); + expect(component['selectedTab']).toBe(testTabValue); + }); + it('should update selected tab when onTabChange is called', () => { const newTabIndex = 2; component.onTabChange(newTabIndex); diff --git a/src/app/features/settings/profile-settings/profile-settings.component.ts b/src/app/features/settings/profile-settings/profile-settings.component.ts index c82e2f10d..de5541852 100644 --- a/src/app/features/settings/profile-settings/profile-settings.component.ts +++ b/src/app/features/settings/profile-settings/profile-settings.component.ts @@ -2,9 +2,10 @@ import { TranslatePipe } from '@ngx-translate/core'; import { Tab, TabList, TabPanel, TabPanels, Tabs } from 'primeng/tabs'; -import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; import { SelectComponent } from '@osf/shared/components/select/select.component'; import { SubHeaderComponent } from '@osf/shared/components/sub-header/sub-header.component'; @@ -36,14 +37,28 @@ import { ProfileSettingsTabOption } from './enums'; styleUrl: './profile-settings.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ProfileSettingsComponent { +export class ProfileSettingsComponent implements OnInit { readonly isMedium = toSignal(inject(IS_MEDIUM)); readonly tabOptions = PROFILE_SETTINGS_TAB_OPTIONS; readonly tabOption = ProfileSettingsTabOption; + private router = inject(Router); + private route = inject(ActivatedRoute); selectedTab = this.tabOption.Name; + ngOnInit(): void { + const tabParam = Number(this.route.snapshot.queryParams['tab']); + const selectedTab = tabParam && Object.values(this.tabOption).includes(tabParam) ? tabParam : this.tabOption.Name; + + this.selectedTab = selectedTab; + } + onTabChange(index: number): void { this.selectedTab = index; + this.router.navigate([], { + queryParams: { tab: index }, + queryParamsHandling: 'merge', + replaceUrl: true, + }); } } From 10716b9a171a305766ba3663945329330ff98fc8 Mon Sep 17 00:00:00 2001 From: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:38:42 -0400 Subject: [PATCH 2/6] [ENG-10584][ENG-10585] Allow users to disconnect existing orcid in social tab (#912) * feat(settings): Allow users to disconnect orcid in social tab * feat(settings): Add dummy connect button when no orcid is associated with user * chore(settings): move authenticated identity to own component * refactor(settings): Implement CR suggestions; Update tests * refactor(settings): Update authenticated identity test * refactor(settings): Update authenticated identity test to use OSFTestingModule --- .../profile-information.component.ts | 3 +- .../authenticated-identity.component.html | 39 ++++++++++ .../authenticated-identity.component.scss | 0 .../authenticated-identity.component.spec.ts | 71 +++++++++++++++++ .../authenticated-identity.component.ts | 77 +++++++++++++++++++ .../components/social/social.component.html | 2 + .../social/social.component.spec.ts | 8 +- .../components/social/social.component.ts | 7 +- .../enums/external-identity-status.enum.ts | 5 ++ .../models/user/external-identity.model.ts | 4 +- src/assets/i18n/en.json | 4 + 11 files changed, 215 insertions(+), 5 deletions(-) create mode 100644 src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.html create mode 100644 src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.scss create mode 100644 src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.spec.ts create mode 100644 src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.ts create mode 100644 src/app/shared/enums/external-identity-status.enum.ts 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..382282167 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 @@ -9,6 +9,7 @@ import { toSignal } from '@angular/core/rxjs-interop'; import { EducationHistoryComponent } from '@osf/shared/components/education-history/education-history.component'; import { EmploymentHistoryComponent } from '@osf/shared/components/employment-history/employment-history.component'; import { SOCIAL_LINKS } from '@osf/shared/constants/social-links.const'; +import { ExternalIdentityStatus } from '@osf/shared/enums/external-identity-status.enum'; 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'; @@ -45,7 +46,7 @@ export class ProfileInformationComponent { orcidId = computed(() => { const orcid = this.currentUser()?.external_identity?.ORCID; - return orcid?.status?.toUpperCase() === 'VERIFIED' ? orcid.id : undefined; + return orcid?.status?.toUpperCase() === ExternalIdentityStatus.VERIFIED ? orcid.id : undefined; }); toProfileSettings() { diff --git a/src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.html b/src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.html new file mode 100644 index 000000000..0e10c22ea --- /dev/null +++ b/src/app/features/settings/profile-settings/components/authenticated-identity/authenticated-identity.component.html @@ -0,0 +1,39 @@ +