Skip to content

Commit 0e0e33b

Browse files
committed
feat(settings): Allow users to disconnect orcid in social tab
1 parent 3e0b111 commit 0e0e33b

4 files changed

Lines changed: 94 additions & 5 deletions

File tree

src/app/features/settings/profile-settings/components/social/social.component.html

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,31 @@
1+
<div class="flex flex-column row-gap-4 border-1 border-round-lg grey-border-color p-3 md:p-4 xl:p-5">
2+
<div class="flex flex-row justify-content-between">
3+
<h2>
4+
{{ 'settings.profileSettings.social.labels.authenticatedIdentity' | translate }}
5+
</h2>
6+
</div>
7+
<div class="flex flex-column row-gap-4 w-full md:flex-row md:align-items-end md:column-gap-3">
8+
<div class="w-full md:w-12">
9+
@if (existingOrcid) {
10+
<div class="flex flex-row align-items-center gap-2">
11+
<img ngSrc="assets/icons/colored/orcid.svg" width="16" height="16" alt="orcid" />
12+
<a class="font-bold" [href]="'https://orcid.org/' + existingOrcid"> https://orcid.org/{{ existingOrcid }} </a>
13+
<p-button
14+
icon="fas fa-times"
15+
class="w-6 md:w-auto"
16+
severity="danger"
17+
variant="text"
18+
[pTooltip]="'settings.profileSettings.social.disconnectOrcid' | translate"
19+
[ariaLabel]="'settings.profileSettings.social.disconnectOrcid' | translate"
20+
(onClick)="disconnectOrcid()"
21+
>
22+
</p-button>
23+
</div>
24+
}
25+
</div>
26+
</div>
27+
</div>
28+
129
<form [formGroup]="socialLinksForm">
230
<div formArrayName="links" class="flex flex-column row-gap-4 social">
331
@for (link of links.controls; track index; let index = $index) {

src/app/features/settings/profile-settings/components/social/social.component.spec.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { MockComponent, MockPipe, MockProvider } from 'ng-mocks';
44
import { ComponentFixture, TestBed } from '@angular/core/testing';
55

66
import { UserSelectors } from '@osf/core/store/user';
7+
import { AccountSettingsSelectors } from '@osf/features/settings/account-settings/store';
78
import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service';
89
import { LoaderService } from '@osf/shared/services/loader.service';
910
import { ToastService } from '@osf/shared/services/toast.service';
@@ -27,11 +28,23 @@ describe('SocialComponent', () => {
2728
imports: [SocialComponent, MockComponent(SocialFormComponent), MockPipe(TranslatePipe)],
2829
providers: [
2930
provideMockStore({
30-
signals: [{ selector: UserSelectors.getSocialLinks, value: MOCK_USER.social }],
31+
signals: [
32+
{ selector: UserSelectors.getSocialLinks, value: MOCK_USER.social },
33+
{
34+
selector: AccountSettingsSelectors.getExternalIdentities,
35+
value: [
36+
{
37+
id: 'ORCID',
38+
externalId: '0001-0002-0003-0004',
39+
status: 'VERIFIED',
40+
},
41+
],
42+
},
43+
],
3144
}),
3245
MockProvider(ToastService),
3346
MockProvider(LoaderService),
34-
{ provide: CustomConfirmationService, useValue: MockCustomConfirmationServiceProvider },
47+
{ provide: CustomConfirmationService, useValue: MockCustomConfirmationServiceProvider.useValue },
3548
],
3649
}).compileComponents();
3750

@@ -48,4 +61,10 @@ describe('SocialComponent', () => {
4861
it('should create', () => {
4962
expect(component).toBeTruthy();
5063
});
64+
65+
it('should show existing user ORCID when present in external identities', () => {
66+
expect(component.existingOrcid).toEqual('0001-0002-0003-0004');
67+
component.disconnectOrcid();
68+
expect(MockCustomConfirmationServiceProvider.useValue.confirmDelete).toHaveBeenCalled();
69+
});
5170
});

src/app/features/settings/profile-settings/components/social/social.component.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import { createDispatchMap, select } from '@ngxs/store';
33
import { TranslatePipe } from '@ngx-translate/core';
44

55
import { Button } from 'primeng/button';
6+
import { Tooltip } from 'primeng/tooltip';
7+
8+
import { finalize } from 'rxjs';
69

710
import {
811
ChangeDetectionStrategy,
@@ -24,12 +27,17 @@ import { ToastService } from '@osf/shared/services/toast.service';
2427
import { SocialModel } from '@shared/models/user/social.model';
2528
import { SocialLinksForm } from '@shared/models/user/social-links.model';
2629

30+
import {
31+
AccountSettingsSelectors,
32+
DeleteExternalIdentity,
33+
GetExternalIdentities,
34+
} from '../../../account-settings/store';
2735
import { hasSocialLinkChanges, mapSocialLinkToPayload } from '../../helpers';
2836
import { SocialFormComponent } from '../social-form/social-form.component';
2937

3038
@Component({
3139
selector: 'osf-social',
32-
imports: [Button, ReactiveFormsModule, SocialFormComponent, TranslatePipe],
40+
imports: [Button, ReactiveFormsModule, SocialFormComponent, Tooltip, TranslatePipe],
3341
templateUrl: './social.component.html',
3442
styleUrl: './social.component.scss',
3543
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -46,13 +54,21 @@ export class SocialComponent {
4654

4755
private readonly socials = SOCIAL_LINKS;
4856

49-
readonly actions = createDispatchMap({ updateProfileSettingsSocialLinks: UpdateProfileSettingsSocialLinks });
57+
readonly actions = createDispatchMap({
58+
updateProfileSettingsSocialLinks: UpdateProfileSettingsSocialLinks,
59+
deleteExternalIdentity: DeleteExternalIdentity,
60+
getExternalIdentities: GetExternalIdentities,
61+
});
5062
readonly socialLinks = select(UserSelectors.getSocialLinks);
63+
readonly externalIdentities = select(AccountSettingsSelectors.getExternalIdentities);
5164

5265
readonly socialLinksForm = this.fb.group({ links: this.fb.array<SocialLinksForm>([]) });
5366

5467
constructor() {
55-
effect(() => this.setInitialData());
68+
effect(() => {
69+
this.setInitialData();
70+
this.actions.getExternalIdentities();
71+
});
5672
}
5773

5874
get links(): FormArray<FormGroup> {
@@ -106,6 +122,30 @@ export class SocialComponent {
106122
return currentLinks.some((link, index) => hasSocialLinkChanges(link, initialSocialLinks, index, this.socials));
107123
}
108124

125+
get existingOrcid(): string | undefined {
126+
const externalIdentities = this.externalIdentities();
127+
const existingOrcid = externalIdentities?.find((identity) => identity.id === 'ORCID');
128+
if (existingOrcid && existingOrcid.status === 'VERIFIED') {
129+
return existingOrcid.externalId;
130+
}
131+
return undefined;
132+
}
133+
134+
disconnectOrcid(): void {
135+
this.customConfirmationService.confirmDelete({
136+
headerKey: 'settings.accountSettings.connectedIdentities.deleteDialog.header',
137+
messageParams: { name: 'ORCID' },
138+
messageKey: 'settings.accountSettings.connectedIdentities.deleteDialog.message',
139+
onConfirm: () => {
140+
this.loaderService.show();
141+
this.actions
142+
.deleteExternalIdentity('ORCID')
143+
.pipe(finalize(() => this.loaderService.hide()))
144+
.subscribe(() => this.toastService.showSuccess('settings.accountSettings.connectedIdentities.successDelete'));
145+
},
146+
});
147+
}
148+
109149
private setInitialData(): void {
110150
const socialLinks = this.socialLinks();
111151
this.links.clear();

src/assets/i18n/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,9 +1848,11 @@
18481848
"successUpdate": "Settings successfully updated."
18491849
},
18501850
"social": {
1851+
"disconnectOrcid": "Disconnect ORCID",
18511852
"title": "Social Link {{index}}",
18521853
"successUpdate": "Social successfully updated.",
18531854
"labels": {
1855+
"authenticatedIdentity": "Authenticated identity",
18541856
"researcherId": "ResearcherID",
18551857
"orcid": "ORCID",
18561858
"linkedIn": "LinkedIn",

0 commit comments

Comments
 (0)