diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.html b/src/app/features/preprints/components/stepper/review-step/review-step.component.html
index 42b38be03..63e7994a6 100644
--- a/src/app/features/preprints/components/stepper/review-step/review-step.component.html
+++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.html
@@ -28,7 +28,13 @@
+
}
diff --git a/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.ts b/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.ts
index 1ad7cb38e..0d24079e9 100644
--- a/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.ts
+++ b/src/app/features/project/settings/components/project-detail-setting-accordion/project-detail-setting-accordion.component.ts
@@ -1,16 +1,17 @@
import { TranslatePipe } from '@ngx-translate/core';
import { Button } from 'primeng/button';
-import { SelectModule } from 'primeng/select';
import { ChangeDetectionStrategy, Component, input, output, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
+import { SelectComponent } from '@osf/shared/components';
+
import { RightControl } from '../../models';
@Component({
selector: 'osf-project-detail-setting-accordion',
- imports: [SelectModule, FormsModule, Button, TranslatePipe],
+ imports: [FormsModule, Button, SelectComponent, TranslatePipe],
templateUrl: './project-detail-setting-accordion.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
@@ -18,6 +19,8 @@ export class ProjectDetailSettingAccordionComponent {
emitValueChange = output<{ index: number; value: boolean | string }>();
title = input
();
rightControls = input.required();
+ disabledRightControls = input(false);
+
expanded = signal(false);
toggle() {
diff --git a/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.html b/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.html
index e17e76c34..a5ea29bdf 100644
--- a/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.html
+++ b/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.html
@@ -3,23 +3,18 @@ {{ 'myProjects.settings.emailNotifications' | translate }}
{{ 'myProjects.settings.emailNotificationsText' | translate }}
-
-
-
-
- {{ subscriptionEvent.Comments | notificationDescription: notifications()?.[0]?.frequency | translate }}
-
-
-
-
-
-
- {{ subscriptionEvent.FileUpdated | notificationDescription: notifications()?.[1]?.frequency | translate }}
-
-
-
+ @if (notifications().length) {
+
+
+
+
+ {{ subscriptionEvent.FileUpdated | notificationDescription: notifications()[0].frequency | translate }}
+
+
+
+ }
diff --git a/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.ts b/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.ts
index f70feaeba..f915fabbc 100644
--- a/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.ts
+++ b/src/app/features/project/settings/components/project-setting-notifications/project-setting-notifications.component.ts
@@ -4,8 +4,8 @@ import { Card } from 'primeng/card';
import { ChangeDetectionStrategy, Component, effect, input, output } from '@angular/core';
-import { NotificationSubscription } from '@osf/features/settings/notifications/models';
import { SubscriptionEvent, SubscriptionFrequency } from '@osf/shared/enums';
+import { NotificationSubscription } from '@osf/shared/models';
import { RightControl } from '../../models';
import { NotificationDescriptionPipe } from '../../pipes';
@@ -19,9 +19,9 @@ import { ProjectDetailSettingAccordionComponent } from '../project-detail-settin
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProjectSettingNotificationsComponent {
- notificationEmitValue = output<{ event: SubscriptionEvent; frequency: SubscriptionFrequency }>();
+ notifications = input.required();
title = input();
- notifications = input();
+ notificationEmitValue = output();
allAccordionData: RightControl[] | undefined = [];
@@ -33,24 +33,14 @@ export class ProjectSettingNotificationsComponent {
constructor() {
effect(() => {
- this.allAccordionData = this.notifications()?.map((notification) => {
- if (notification.event === SubscriptionEvent.Comments) {
- return {
- label: 'settings.notifications.notificationPreferences.items.comments',
- value: notification.frequency as string,
- type: 'dropdown',
- options: this.subscriptionFrequencyOptions,
- event: notification.event,
- } as RightControl;
- } else {
- return {
- label: 'settings.notifications.notificationPreferences.items.files',
- value: notification.frequency as string,
- type: 'dropdown',
- options: this.subscriptionFrequencyOptions,
- event: notification.event,
- } as RightControl;
- }
+ this.allAccordionData = this.notifications().map((notification) => {
+ return {
+ label: 'settings.notifications.notificationPreferences.items.files',
+ value: notification.frequency as string,
+ type: 'dropdown',
+ options: this.subscriptionFrequencyOptions,
+ event: notification.event,
+ } as RightControl;
});
});
}
@@ -58,6 +48,7 @@ export class ProjectSettingNotificationsComponent {
changeEmittedValue(emittedValue: { index: number; value: boolean | string }): void {
if (this.allAccordionData) {
this.notificationEmitValue.emit({
+ id: this.notifications()[0].id,
event: this.allAccordionData[emittedValue.index].event as SubscriptionEvent,
frequency: emittedValue.value as SubscriptionFrequency,
});
diff --git a/src/app/features/project/settings/components/settings-access-requests-card/settings-access-requests-card.component.html b/src/app/features/project/settings/components/settings-access-requests-card/settings-access-requests-card.component.html
index c41274a91..78c3a9042 100644
--- a/src/app/features/project/settings/components/settings-access-requests-card/settings-access-requests-card.component.html
+++ b/src/app/features/project/settings/components/settings-access-requests-card/settings-access-requests-card.component.html
@@ -9,6 +9,7 @@ {{ 'myProjects.settings.accessRequests' | translate }}
id="accessRequest"
name="ongoing"
>
+
diff --git a/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.html b/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.html
deleted file mode 100644
index 39351e990..000000000
--- a/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.html
+++ /dev/null
@@ -1,28 +0,0 @@
-
- {{ 'myProjects.settings.commenting' | translate }}
-
-
-
-
-
-
{{ 'myProjects.settings.contributorsCanPost' | translate }}
-
-
-
-
-
-
-
{{ 'myProjects.settings.osfUserCanPost' | translate }}
-
-
-
diff --git a/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.spec.ts b/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.spec.ts
deleted file mode 100644
index 639c49a14..000000000
--- a/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.spec.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { SettingsCommentingCardComponent } from './settings-commenting-card.component';
-
-describe('SettingsCommentingCardComponent', () => {
- let component: SettingsCommentingCardComponent;
- let fixture: ComponentFixture;
-
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [SettingsCommentingCardComponent],
- }).compileComponents();
-
- fixture = TestBed.createComponent(SettingsCommentingCardComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
diff --git a/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.ts b/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.ts
deleted file mode 100644
index 4d46cc4a3..000000000
--- a/src/app/features/project/settings/components/settings-commenting-card/settings-commenting-card.component.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { TranslatePipe } from '@ngx-translate/core';
-
-import { Card } from 'primeng/card';
-import { RadioButton } from 'primeng/radiobutton';
-
-import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
-import { FormsModule } from '@angular/forms';
-
-@Component({
- selector: 'osf-settings-commenting-card',
- imports: [Card, RadioButton, TranslatePipe, FormsModule],
- templateUrl: './settings-commenting-card.component.html',
- styleUrl: '../../settings.component.scss',
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class SettingsCommentingCardComponent {
- anyoneCanComment = input.required();
- readonly anyoneCanCommentChange = output();
-}
diff --git a/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.html b/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.html
new file mode 100644
index 000000000..af179c1e5
--- /dev/null
+++ b/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.html
@@ -0,0 +1,40 @@
+
+ {{ 'myProjects.settings.projectAffiliation' | translate }}
+
+ {{ 'myProjects.settings.projectsCanBeAffiliated' | translate }}
+
+
+ - {{ 'myProjects.settings.institutionalLogos' | translate }}
+ - {{ 'myProjects.settings.publicProjectsToBeDiscoverable' | translate }}
+ - {{ 'myProjects.settings.singleSignInToTHeOSF' | translate }}
+ -
+
+ {{ 'myProjects.settings.faq' | translate }}
+
+
+
+
+ @if (affiliations().length > 0) {
+
+ @for (affiliation of affiliations(); track $index) {
+
+
+
![Affiliation logo]()
+
+
{{ affiliation.name }}
+
+
+
+
+ }
+
+ }
+
diff --git a/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.scss b/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.spec.ts b/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.spec.ts
new file mode 100644
index 000000000..685bcc895
--- /dev/null
+++ b/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SettingsProjectAffiliationComponent } from './settings-project-affiliation.component';
+
+describe('SettingsProjectAffiliationComponent', () => {
+ let component: SettingsProjectAffiliationComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [SettingsProjectAffiliationComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(SettingsProjectAffiliationComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.ts b/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.ts
new file mode 100644
index 000000000..693cadb10
--- /dev/null
+++ b/src/app/features/project/settings/components/settings-project-affiliation/settings-project-affiliation.component.ts
@@ -0,0 +1,25 @@
+import { TranslatePipe } from '@ngx-translate/core';
+
+import { Button } from 'primeng/button';
+import { Card } from 'primeng/card';
+
+import { NgOptimizedImage } from '@angular/common';
+import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
+
+import { Institution } from '@osf/shared/models';
+
+@Component({
+ selector: 'osf-settings-project-affiliation',
+ imports: [Card, Button, NgOptimizedImage, TranslatePipe],
+ templateUrl: './settings-project-affiliation.component.html',
+ styleUrl: './settings-project-affiliation.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class SettingsProjectAffiliationComponent {
+ affiliations = input([]);
+ removed = output();
+
+ removeAffiliation(affiliation: Institution) {
+ this.removed.emit(affiliation);
+ }
+}
diff --git a/src/app/features/project/settings/components/settings-project-form-card/settings-project-form-card.component.html b/src/app/features/project/settings/components/settings-project-form-card/settings-project-form-card.component.html
index 11332963b..389147fb2 100644
--- a/src/app/features/project/settings/components/settings-project-form-card/settings-project-form-card.component.html
+++ b/src/app/features/project/settings/components/settings-project-form-card/settings-project-form-card.component.html
@@ -1,25 +1,20 @@
{{ 'myProjects.settings.project' | translate }}
-
diff --git a/src/app/features/project/settings/components/settings-redirect-link/settings-redirect-link.component.ts b/src/app/features/project/settings/components/settings-redirect-link/settings-redirect-link.component.ts
index 852f3b17e..c208f2c75 100644
--- a/src/app/features/project/settings/components/settings-redirect-link/settings-redirect-link.component.ts
+++ b/src/app/features/project/settings/components/settings-redirect-link/settings-redirect-link.component.ts
@@ -3,58 +3,90 @@ import { TranslatePipe } from '@ngx-translate/core';
import { Button } from 'primeng/button';
import { Card } from 'primeng/card';
import { Checkbox } from 'primeng/checkbox';
-import { InputText } from 'primeng/inputtext';
-
-import { TitleCasePipe, UpperCasePipe } from '@angular/common';
-import {
- ChangeDetectionStrategy,
- Component,
- effect,
- input,
- model,
- output,
- signal,
- WritableSignal,
-} from '@angular/core';
-import { FormsModule } from '@angular/forms';
-
-import { RedirectUrlDataModel } from '@osf/features/project/settings/models';
+
+import { ChangeDetectionStrategy, Component, DestroyRef, effect, inject, input, output } from '@angular/core';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
+
+import { TextInputComponent } from '@osf/shared/components';
+import { InputLimits } from '@osf/shared/constants';
+import { CustomValidators } from '@osf/shared/helpers';
+
+import { RedirectLinkDataModel, RedirectLinkForm } from '../../models';
@Component({
selector: 'osf-settings-redirect-link',
- imports: [Card, Checkbox, TranslatePipe, FormsModule, InputText, TitleCasePipe, UpperCasePipe, Button],
+ imports: [Card, Checkbox, TranslatePipe, ReactiveFormsModule, TextInputComponent, Button],
templateUrl: './settings-redirect-link.component.html',
styleUrl: '../../settings.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SettingsRedirectLinkComponent {
- redirectUrlDataChange = output();
- redirectUrlDataInput = input.required();
- redirectLink = model();
+ private readonly destroyRef = inject(DestroyRef);
+
+ redirectUrlDataInput = input.required();
+ redirectUrlDataChange = output();
- redirectUrlData: WritableSignal = signal({ url: '', label: '' });
+ inputLimits = InputLimits;
+
+ redirectForm = new FormGroup({
+ isEnabled: new FormControl(false, {
+ nonNullable: true,
+ validators: [Validators.required],
+ }),
+ url: new FormControl('', [CustomValidators.requiredTrimmed(), CustomValidators.linkValidator()]),
+ label: new FormControl('', [CustomValidators.requiredTrimmed()]),
+ });
constructor() {
+ this.setupFormSubscriptions();
+ this.setupInputEffects();
+ }
+
+ private setupFormSubscriptions(): void {
+ this.redirectForm.controls.isEnabled?.valueChanges
+ .pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe((isEnabled) => {
+ if (!isEnabled) {
+ this.redirectForm.get('url')?.setValue('');
+ this.redirectForm.get('label')?.setValue('');
+ this.emitFormData();
+ }
+ });
+ }
+
+ saveRedirectSettings(): void {
+ if (this.redirectForm.valid) {
+ this.emitFormData();
+ }
+ }
+
+ private setupInputEffects(): void {
effect(() => {
- this.redirectUrlData.set(this.redirectUrlDataInput());
- const { url, label } = this.redirectUrlDataInput();
- if (url || label) {
- this.redirectLink.set(true);
- }
+ const inputData = this.redirectUrlDataInput();
+ this.redirectForm.patchValue(
+ {
+ isEnabled: inputData.isEnabled,
+ url: inputData.url,
+ label: inputData.label,
+ },
+ { emitEvent: false }
+ );
+
+ this.redirectForm.markAsPristine();
});
}
- onToggleRedirectLink(checked: boolean): void {
- this.redirectLink.set(checked);
- if (!checked) {
- this.redirectUrlData.set({ url: '', label: '' });
- this.redirectUrlDataChange.emit(this.redirectUrlData());
- }
+ get hasChanges(): boolean {
+ return this.redirectForm.dirty;
}
- emitIfChecked(): void {
- if (this.redirectLink()) {
- this.redirectUrlDataChange.emit(this.redirectUrlData());
- }
+ private emitFormData(): void {
+ const formValue = this.redirectForm.value;
+ this.redirectUrlDataChange.emit({
+ isEnabled: formValue.isEnabled || false,
+ url: formValue.url || '',
+ label: formValue.label || '',
+ });
}
}
diff --git a/src/app/features/project/settings/components/settings-wiki-card/settings-wiki-card.component.html b/src/app/features/project/settings/components/settings-wiki-card/settings-wiki-card.component.html
index c3ac8b48b..0a70898ab 100644
--- a/src/app/features/project/settings/components/settings-wiki-card/settings-wiki-card.component.html
+++ b/src/app/features/project/settings/components/settings-wiki-card/settings-wiki-card.component.html
@@ -23,6 +23,7 @@ {{ 'myProjects.settings.wikiConfigureTitle' | translate }}
(emitValueChange)="changeEmittedValue($event)"
[title]="title()"
[rightControls]="allAccordionData"
+ [disabledRightControls]="!isPublic()"
>
diff --git a/src/app/features/project/settings/components/settings-wiki-card/settings-wiki-card.component.ts b/src/app/features/project/settings/components/settings-wiki-card/settings-wiki-card.component.ts
index 919c34137..d6203915f 100644
--- a/src/app/features/project/settings/components/settings-wiki-card/settings-wiki-card.component.ts
+++ b/src/app/features/project/settings/components/settings-wiki-card/settings-wiki-card.component.ts
@@ -23,6 +23,7 @@ export class SettingsWikiCardComponent {
wikiEnabled = input.required
();
anyoneCanEditWiki = input.required();
title = input.required();
+ isPublic = input(false);
allAccordionData: RightControl[] = [];
@@ -35,8 +36,8 @@ export class SettingsWikiCardComponent {
value: anyoneCanEditWiki,
type: 'dropdown',
options: [
- { label: 'myProjects.settings.contributorsOption', value: true },
- { label: 'myProjects.settings.anyoneOption', value: false },
+ { label: 'myProjects.settings.contributorsOption', value: false },
+ { label: 'myProjects.settings.anyoneOption', value: true },
],
},
];
diff --git a/src/app/features/project/settings/mappers/settings.mapper.ts b/src/app/features/project/settings/mappers/settings.mapper.ts
index 396e28617..7ffca3f16 100644
--- a/src/app/features/project/settings/mappers/settings.mapper.ts
+++ b/src/app/features/project/settings/mappers/settings.mapper.ts
@@ -1,4 +1,12 @@
-import { ProjectSettingsData, ProjectSettingsModel, ProjectSettingsResponseModel } from '../models';
+import { InstitutionsMapper } from '@osf/shared/mappers';
+
+import {
+ NodeDataJsonApi,
+ NodeDetailsModel,
+ ProjectSettingsData,
+ ProjectSettingsModel,
+ ProjectSettingsResponseModel,
+} from '../models';
export class SettingsMapper {
static fromResponse(
@@ -20,4 +28,19 @@ export class SettingsMapper {
},
} as ProjectSettingsModel;
}
+
+ static fromNodeResponse(data: NodeDataJsonApi): NodeDetailsModel {
+ return {
+ id: data.id,
+ title: data.attributes.title,
+ description: data.attributes.description,
+ isPublic: data.attributes.public,
+ region: {
+ id: data.embeds.region.data.id,
+ name: data.embeds.region.data.attributes.name,
+ },
+ affiliatedInstitutions: InstitutionsMapper.fromInstitutionsResponse(data.embeds.affiliated_institutions),
+ lastFetched: Date.now(),
+ };
+ }
}
diff --git a/src/app/features/project/settings/models/index.ts b/src/app/features/project/settings/models/index.ts
index 57b6ecf76..282b281d1 100644
--- a/src/app/features/project/settings/models/index.ts
+++ b/src/app/features/project/settings/models/index.ts
@@ -1,5 +1,8 @@
+export * from './node-details.model';
export * from './project-details.model';
+export * from './project-details-json-api.model';
export * from './project-settings.model';
export * from './project-settings-response.model';
-export * from './redirect-url-data.model';
+export * from './redirect-link-data.model';
+export * from './redirect-link-form.model';
export * from './right-control.model';
diff --git a/src/app/features/project/settings/models/node-details.model.ts b/src/app/features/project/settings/models/node-details.model.ts
new file mode 100644
index 000000000..252e05426
--- /dev/null
+++ b/src/app/features/project/settings/models/node-details.model.ts
@@ -0,0 +1,11 @@
+import { IdName, Institution } from '@osf/shared/models';
+
+export interface NodeDetailsModel {
+ id: string;
+ title: string;
+ description: string;
+ isPublic: boolean;
+ region: IdName;
+ affiliatedInstitutions: Institution[];
+ lastFetched: number;
+}
diff --git a/src/app/features/project/settings/models/project-details-json-api.model.ts b/src/app/features/project/settings/models/project-details-json-api.model.ts
new file mode 100644
index 000000000..bbfff660f
--- /dev/null
+++ b/src/app/features/project/settings/models/project-details-json-api.model.ts
@@ -0,0 +1,19 @@
+import { InstitutionsJsonApiResponse, NodeData, ResponseDataJsonApi } from '@osf/shared/models';
+
+export type NodeResponseJsonApi = ResponseDataJsonApi;
+
+export interface NodeDataJsonApi extends NodeData {
+ embeds: NodeEmbedsJsonApi;
+}
+
+interface NodeEmbedsJsonApi {
+ region: {
+ data: {
+ id: string;
+ attributes: {
+ name: string;
+ };
+ };
+ };
+ affiliated_institutions: InstitutionsJsonApiResponse;
+}
diff --git a/src/app/features/project/settings/models/redirect-link-data.model.ts b/src/app/features/project/settings/models/redirect-link-data.model.ts
new file mode 100644
index 000000000..f85e7b63b
--- /dev/null
+++ b/src/app/features/project/settings/models/redirect-link-data.model.ts
@@ -0,0 +1,5 @@
+export interface RedirectLinkDataModel {
+ isEnabled: boolean;
+ url: string;
+ label: string;
+}
diff --git a/src/app/features/project/settings/models/redirect-link-form.model.ts b/src/app/features/project/settings/models/redirect-link-form.model.ts
new file mode 100644
index 000000000..1e9deaa7e
--- /dev/null
+++ b/src/app/features/project/settings/models/redirect-link-form.model.ts
@@ -0,0 +1,7 @@
+import { FormControl } from '@angular/forms';
+
+export interface RedirectLinkForm {
+ isEnabled: FormControl;
+ url: FormControl;
+ label: FormControl;
+}
diff --git a/src/app/features/project/settings/models/redirect-url-data.model.ts b/src/app/features/project/settings/models/redirect-url-data.model.ts
deleted file mode 100644
index 10ba37999..000000000
--- a/src/app/features/project/settings/models/redirect-url-data.model.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export interface RedirectUrlDataModel {
- url: string;
- label: string;
-}
diff --git a/src/app/features/project/settings/pipes/notification-description.pipe.ts b/src/app/features/project/settings/pipes/notification-description.pipe.ts
index 48e707f84..f57499a6c 100644
--- a/src/app/features/project/settings/pipes/notification-description.pipe.ts
+++ b/src/app/features/project/settings/pipes/notification-description.pipe.ts
@@ -9,7 +9,6 @@ export class NotificationDescriptionPipe implements PipeTransform {
transform(event: SubscriptionEvent, frequency?: SubscriptionFrequency): string {
if (!event || !frequency) return '';
- const key = `myProjects.settings.descriptions.${event}.${frequency}`;
- return key;
+ return `myProjects.settings.descriptions.${event}.${frequency}`;
}
}
diff --git a/src/app/features/project/settings/services/settings.service.ts b/src/app/features/project/settings/services/settings.service.ts
index 07c040ebb..5a5cb8981 100644
--- a/src/app/features/project/settings/services/settings.service.ts
+++ b/src/app/features/project/settings/services/settings.service.ts
@@ -2,10 +2,24 @@ import { map, Observable } from 'rxjs';
import { inject, Injectable } from '@angular/core';
+import { SubscriptionFrequency } from '@osf/shared/enums';
+import { NotificationSubscriptionMapper } from '@osf/shared/mappers';
+import {
+ NotificationSubscription,
+ NotificationSubscriptionGetResponseJsonApi,
+ ResponseJsonApi,
+ UpdateNodeRequestModel,
+} from '@osf/shared/models';
import { JsonApiService } from '@shared/services';
import { SettingsMapper } from '../mappers';
-import { ProjectSettingsData, ProjectSettingsModel, ProjectSettingsResponseModel } from '../models';
+import {
+ NodeDetailsModel,
+ NodeResponseJsonApi,
+ ProjectSettingsData,
+ ProjectSettingsModel,
+ ProjectSettingsResponseModel,
+} from '../models';
import { environment } from 'src/environments/environment';
@@ -29,7 +43,55 @@ export class SettingsService {
.pipe(map((response) => SettingsMapper.fromResponse(response, model.id)));
}
+ getNotificationSubscriptions(nodeId?: string): Observable {
+ const params: Record = {
+ 'filter[id]': `${nodeId}_file_updated`,
+ };
+
+ return this.jsonApiService
+ .get>(`${this.baseUrl}/subscriptions/`, params)
+ .pipe(
+ map((responses) => responses.data.map((response) => NotificationSubscriptionMapper.fromGetResponse(response)))
+ );
+ }
+
+ updateSubscription(id: string, frequency: SubscriptionFrequency): Observable {
+ const request = NotificationSubscriptionMapper.toUpdateRequest(id, frequency, false);
+
+ return this.jsonApiService
+ .patch(`${this.baseUrl}/subscriptions/${id}/`, request)
+ .pipe(map((response) => NotificationSubscriptionMapper.fromGetResponse(response)));
+ }
+
+ getProjectById(projectId: string): Observable {
+ const params = {
+ 'embed[]': ['affiliated_institutions', 'region'],
+ };
+ return this.jsonApiService
+ .get(`${this.baseUrl}/nodes/${projectId}/`, params)
+ .pipe(map((response) => SettingsMapper.fromNodeResponse(response.data)));
+ }
+
+ updateProjectById(model: UpdateNodeRequestModel): Observable {
+ return this.jsonApiService
+ .patch(`${this.baseUrl}/nodes/${model?.data?.id}/`, model)
+ .pipe(map((response) => SettingsMapper.fromNodeResponse(response.data)));
+ }
+
deleteProject(projectId: string): Observable {
return this.jsonApiService.delete(`${this.baseUrl}/nodes/${projectId}/`);
}
+
+ deleteInstitution(institutionId: string, projectId: string): Observable {
+ const data = {
+ data: [
+ {
+ type: 'nodes',
+ id: projectId,
+ },
+ ],
+ };
+
+ return this.jsonApiService.delete(`${this.baseUrl}/institutions/${institutionId}/relationships/nodes/`, data);
+ }
}
diff --git a/src/app/features/project/settings/settings.component.html b/src/app/features/project/settings/settings.component.html
index bf82bc5dc..d9ef31199 100644
--- a/src/app/features/project/settings/settings.component.html
+++ b/src/app/features/project/settings/settings.component.html
@@ -1,87 +1,56 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ 'myProjects.settings.projectAffiliation' | translate }}
-
- {{ 'myProjects.settings.projectsCanBeAffiliated' | translate }}
-
-
- - {{ 'myProjects.settings.institutionalLogos' | translate }}
- - {{ 'myProjects.settings.publicProjectsToBeDiscoverable' | translate }}
- - {{ 'myProjects.settings.singleSignInToTHeOSF' | translate }}
- -
-
- {{ 'myProjects.settings.faq' | translate }}
-
-
-
-
- @if (affiliations.length > 0) {
-
- @for (affiliation of affiliations; track $index) {
-
-
-
![cos-shield]()
-
-
{{ affiliation }}
-
-
-
-
- }
-
- }
-
-
+ @if (areProjectDetailsLoading() || areNotificationsLoading()) {
+
+ } @else {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
diff --git a/src/app/features/project/settings/settings.component.ts b/src/app/features/project/settings/settings.component.ts
index ce5691c53..c371935b5 100644
--- a/src/app/features/project/settings/settings.component.ts
+++ b/src/app/features/project/settings/settings.component.ts
@@ -2,46 +2,39 @@ import { createDispatchMap, select } from '@ngxs/store';
import { TranslatePipe } from '@ngx-translate/core';
-import { Button } from 'primeng/button';
-import { Card } from 'primeng/card';
-
import { map, of } from 'rxjs';
-import { NgOptimizedImage } from '@angular/common';
import { ChangeDetectionStrategy, Component, effect, inject, OnInit, signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
-import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
-import {
- GetNotificationSubscriptionsByNodeId,
- NotificationSubscriptionSelectors,
- UpdateNotificationSubscriptionForNodeId,
-} from '@osf/features/settings/notifications/store';
-import { SubHeaderComponent } from '@osf/shared/components';
-import { ProjectFormControls, ResourceType, SubscriptionEvent, SubscriptionFrequency } from '@osf/shared/enums';
-import { CustomValidators } from '@osf/shared/helpers';
-import { UpdateNodeRequestModel, ViewOnlyLinkModel } from '@osf/shared/models';
+import { LoadingSpinnerComponent, SubHeaderComponent } from '@osf/shared/components';
+import { ResourceType, SubscriptionEvent, SubscriptionFrequency } from '@osf/shared/enums';
+import { Institution, UpdateNodeRequestModel, ViewOnlyLinkModel } from '@osf/shared/models';
import { CustomConfirmationService, LoaderService, ToastService } from '@osf/shared/services';
import { DeleteViewOnlyLink, FetchViewOnlyLinks, ViewOnlyLinkSelectors } from '@osf/shared/stores';
import {
ProjectSettingNotificationsComponent,
SettingsAccessRequestsCardComponent,
- SettingsCommentingCardComponent,
+ SettingsProjectAffiliationComponent,
SettingsProjectFormCardComponent,
SettingsRedirectLinkComponent,
SettingsStorageLocationCardComponent,
SettingsViewOnlyLinksCardComponent,
SettingsWikiCardComponent,
} from './components';
-import { ProjectDetailsModel, ProjectSettingsAttributes, ProjectSettingsData } from './models';
+import { ProjectDetailsModel, ProjectSettingsAttributes, ProjectSettingsData, RedirectLinkDataModel } from './models';
import {
+ DeleteInstitution,
DeleteProject,
GetProjectDetails,
+ GetProjectNotificationSubscriptions,
GetProjectSettings,
SettingsSelectors,
UpdateProjectDetails,
+ UpdateProjectNotificationSubscription,
UpdateProjectSettings,
} from './store';
@@ -52,17 +45,15 @@ import {
SubHeaderComponent,
FormsModule,
ReactiveFormsModule,
- Card,
- Button,
- NgOptimizedImage,
SettingsProjectFormCardComponent,
SettingsStorageLocationCardComponent,
SettingsViewOnlyLinksCardComponent,
SettingsAccessRequestsCardComponent,
SettingsWikiCardComponent,
- SettingsCommentingCardComponent,
SettingsRedirectLinkComponent,
+ SettingsProjectAffiliationComponent,
ProjectSettingNotificationsComponent,
+ LoadingSpinnerComponent,
],
templateUrl: './settings.component.html',
styleUrl: './settings.component.scss',
@@ -77,38 +68,34 @@ export class SettingsComponent implements OnInit {
readonly projectId = toSignal(this.route.parent?.params.pipe(map((params) => params['id'])) ?? of(undefined));
- protected settings = select(SettingsSelectors.getSettings);
- protected notifications = select(NotificationSubscriptionSelectors.getNotificationSubscriptionsByNodeId);
- protected projectDetails = select(SettingsSelectors.getProjectDetails);
- protected viewOnlyLinks = select(ViewOnlyLinkSelectors.getViewOnlyLinks);
- protected isViewOnlyLinksLoading = select(ViewOnlyLinkSelectors.isViewOnlyLinksLoading);
+ settings = select(SettingsSelectors.getSettings);
+ notifications = select(SettingsSelectors.getNotificationSubscriptions);
+ areNotificationsLoading = select(SettingsSelectors.areNotificationsLoading);
+ projectDetails = select(SettingsSelectors.getProjectDetails);
+ areProjectDetailsLoading = select(SettingsSelectors.areProjectDetailsLoading);
+ viewOnlyLinks = select(ViewOnlyLinkSelectors.getViewOnlyLinks);
+ isViewOnlyLinksLoading = select(ViewOnlyLinkSelectors.isViewOnlyLinksLoading);
- protected actions = createDispatchMap({
+ actions = createDispatchMap({
getSettings: GetProjectSettings,
- getNotifications: GetNotificationSubscriptionsByNodeId,
+ getNotifications: GetProjectNotificationSubscriptions,
getProjectDetails: GetProjectDetails,
getViewOnlyLinks: FetchViewOnlyLinks,
updateProjectDetails: UpdateProjectDetails,
updateProjectSettings: UpdateProjectSettings,
- updateNotificationSubscriptionForNodeId: UpdateNotificationSubscriptionForNodeId,
+ updateNotificationSubscription: UpdateProjectNotificationSubscription,
deleteViewOnlyLink: DeleteViewOnlyLink,
deleteProject: DeleteProject,
+ deleteInstitution: DeleteInstitution,
});
- projectForm = new FormGroup({
- [ProjectFormControls.Title]: new FormControl('', CustomValidators.requiredTrimmed()),
- [ProjectFormControls.Description]: new FormControl(''),
- });
-
- redirectUrlData = signal<{ url: string; label: string }>({ url: '', label: '' });
+ redirectUrlData = signal({ isEnabled: false, url: '', label: '' });
accessRequest = signal(false);
wikiEnabled = signal(false);
anyoneCanEditWiki = signal(false);
anyoneCanComment = signal(false);
title = signal('');
- affiliations = [];
-
constructor() {
this.setupEffects();
}
@@ -124,7 +111,7 @@ export class SettingsComponent implements OnInit {
}
submitForm({ title, description }: ProjectDetailsModel): void {
- const current = this.projectDetails().attributes;
+ const current = this.projectDetails();
if (title === current.title && description === current.description) return;
@@ -159,22 +146,17 @@ export class SettingsComponent implements OnInit {
this.syncSettingsChanges('anyone_can_edit_wiki', newValue);
}
- onAnyoneCanCommentRequestChange(newValue: boolean): void {
- this.anyoneCanComment.set(newValue);
- this.syncSettingsChanges('anyone_can_comment', newValue);
- }
-
- onRedirectUrlDataRequestChange(data: { url: string; label: string }): void {
+ onRedirectUrlDataRequestChange(data: RedirectLinkDataModel): void {
this.redirectUrlData.set(data);
this.syncSettingsChanges('redirectUrl', data);
}
onNotificationRequestChange(data: { event: SubscriptionEvent; frequency: SubscriptionFrequency }): void {
- const id = `${'n5str'}_${data.event}`;
+ const id = `${this.projectId()}_${data.event}`;
const frequency = data.frequency;
this.loaderService.show();
- this.actions.updateNotificationSubscriptionForNodeId({ id, frequency }).subscribe(() => {
+ this.actions.updateNotificationSubscription({ id, frequency }).subscribe(() => {
this.toastService.showSuccess('myProjects.settings.updateProjectSettingsMessage');
this.loaderService.hide();
});
@@ -197,7 +179,7 @@ export class SettingsComponent implements OnInit {
deleteProject(): void {
this.customConfirmationService.confirmDelete({
headerKey: 'project.deleteProject.title',
- messageParams: { name: this.projectDetails().attributes.title },
+ messageParams: { name: this.projectDetails().title },
messageKey: 'project.deleteProject.message',
onConfirm: () => {
this.loaderService.show();
@@ -210,7 +192,23 @@ export class SettingsComponent implements OnInit {
});
}
- private syncSettingsChanges(changedField: string, value: boolean | { url: string; label: string }): void {
+ removeAffiliation(affiliation: Institution): void {
+ this.customConfirmationService.confirmDelete({
+ headerKey: 'project.deleteInstitution.title',
+ messageParams: { name: affiliation.name },
+ messageKey: 'project.deleteInstitution.message',
+ onConfirm: () => {
+ this.loaderService.show();
+ this.actions.deleteInstitution(affiliation.id, this.projectId()).subscribe(() => {
+ this.loaderService.hide();
+ this.toastService.showSuccess('project.deleteInstitution.success');
+ this.actions.getProjectDetails(this.projectId());
+ });
+ },
+ });
+ }
+
+ private syncSettingsChanges(changedField: string, value: boolean | RedirectLinkDataModel): void {
const payload: Partial = {};
switch (changedField) {
@@ -223,9 +221,9 @@ export class SettingsComponent implements OnInit {
break;
case 'redirectUrl':
if (typeof value === 'object') {
- payload['redirect_link_enabled'] = true;
- payload['redirect_link_url'] = value.url ?? null;
- payload['redirect_link_label'] = value.label ?? null;
+ payload['redirect_link_enabled'] = value.isEnabled;
+ payload['redirect_link_url'] = value.isEnabled ? value.url : undefined;
+ payload['redirect_link_label'] = value.isEnabled ? value.label : undefined;
}
break;
}
@@ -253,7 +251,9 @@ export class SettingsComponent implements OnInit {
this.wikiEnabled.set(settings.attributes.wikiEnabled);
this.anyoneCanEditWiki.set(settings.attributes.anyoneCanEditWiki);
this.anyoneCanComment.set(settings.attributes.anyoneCanComment);
+
this.redirectUrlData.set({
+ isEnabled: settings.attributes.redirectLinkEnabled,
url: settings.attributes.redirectLinkUrl,
label: settings.attributes.redirectLinkLabel,
});
@@ -262,12 +262,9 @@ export class SettingsComponent implements OnInit {
effect(() => {
const project = this.projectDetails();
- if (project?.attributes) {
- this.projectForm.patchValue({
- [ProjectFormControls.Title]: project.attributes.title,
- [ProjectFormControls.Description]: project.attributes.description,
- });
- this.title.set(project.attributes.title);
+
+ if (project) {
+ this.title.set(project.title);
}
});
}
diff --git a/src/app/features/project/settings/store/settings.actions.ts b/src/app/features/project/settings/store/settings.actions.ts
index a8749e66c..ae0a30e0b 100644
--- a/src/app/features/project/settings/store/settings.actions.ts
+++ b/src/app/features/project/settings/store/settings.actions.ts
@@ -1,9 +1,10 @@
+import { SubscriptionFrequency } from '@osf/shared/enums';
import { UpdateNodeRequestModel } from '@shared/models';
import { ProjectSettingsData } from '../models';
export class GetProjectSettings {
- static readonly type = '[Settings] Get Project Settings';
+ static readonly type = '[Project Settings] Get Project Settings';
constructor(public projectId: string) {}
}
@@ -15,19 +16,40 @@ export class GetProjectDetails {
}
export class UpdateProjectSettings {
- static readonly type = '[Settings] Update Project Settings';
+ static readonly type = '[Project Settings] Update Project Settings';
constructor(public payload: ProjectSettingsData) {}
}
export class UpdateProjectDetails {
- static readonly type = '[Settings] Update Project Details';
+ static readonly type = '[Project Settings] Update Project Details';
constructor(public payload: UpdateNodeRequestModel) {}
}
+export class GetProjectNotificationSubscriptions {
+ static readonly type = '[Project Settings] Get Project Notification Subscriptions';
+
+ constructor(public nodeId: string) {}
+}
+
+export class UpdateProjectNotificationSubscription {
+ static readonly type = '[Project Settings] Update Project Notification Subscription';
+
+ constructor(public payload: { id: string; frequency: SubscriptionFrequency }) {}
+}
+
export class DeleteProject {
- static readonly type = '[Settings] Delete Project';
+ static readonly type = '[Project Settings] Delete Project';
constructor(public projectId: string) {}
}
+
+export class DeleteInstitution {
+ static readonly type = '[Project Settings] Delete Institution';
+
+ constructor(
+ public institutionId: string,
+ public projectId: string
+ ) {}
+}
diff --git a/src/app/features/project/settings/store/settings.model.ts b/src/app/features/project/settings/store/settings.model.ts
index 8137b3961..691010265 100644
--- a/src/app/features/project/settings/store/settings.model.ts
+++ b/src/app/features/project/settings/store/settings.model.ts
@@ -1,10 +1,11 @@
-import { AsyncStateModel, NodeData } from '@osf/shared/models';
+import { AsyncStateModel, NotificationSubscription } from '@osf/shared/models';
-import { ProjectSettingsModel } from '../models';
+import { NodeDetailsModel, ProjectSettingsModel } from '../models';
export interface SettingsStateModel {
settings: AsyncStateModel;
- projectDetails: AsyncStateModel;
+ projectDetails: AsyncStateModel;
+ notifications: AsyncStateModel;
}
export const SETTINGS_STATE_DEFAULTS: SettingsStateModel = {
@@ -14,8 +15,13 @@ export const SETTINGS_STATE_DEFAULTS: SettingsStateModel = {
error: null,
},
projectDetails: {
- data: {} as NodeData,
+ data: {} as NodeDetailsModel,
isLoading: false,
error: null,
},
+ notifications: {
+ data: [],
+ isLoading: false,
+ error: '',
+ },
};
diff --git a/src/app/features/project/settings/store/settings.selectors.ts b/src/app/features/project/settings/store/settings.selectors.ts
index de6a8ec5e..4a46ce241 100644
--- a/src/app/features/project/settings/store/settings.selectors.ts
+++ b/src/app/features/project/settings/store/settings.selectors.ts
@@ -1,5 +1,7 @@
import { Selector } from '@ngxs/store';
+import { NotificationSubscription } from '@osf/shared/models';
+
import { SettingsStateModel } from './settings.model';
import { SettingsState } from './settings.state';
@@ -13,4 +15,19 @@ export class SettingsSelectors {
static getProjectDetails(state: SettingsStateModel) {
return state.projectDetails.data;
}
+
+ @Selector([SettingsState])
+ static areProjectDetailsLoading(state: SettingsStateModel) {
+ return state.projectDetails.isLoading;
+ }
+
+ @Selector([SettingsState])
+ static getNotificationSubscriptions(state: SettingsStateModel): NotificationSubscription[] {
+ return state.notifications.data;
+ }
+
+ @Selector([SettingsState])
+ static areNotificationsLoading(state: SettingsStateModel): boolean {
+ return state.notifications.isLoading;
+ }
}
diff --git a/src/app/features/project/settings/store/settings.state.ts b/src/app/features/project/settings/store/settings.state.ts
index 218738d60..4e3698001 100644
--- a/src/app/features/project/settings/store/settings.state.ts
+++ b/src/app/features/project/settings/store/settings.state.ts
@@ -1,21 +1,24 @@
import { Action, State, StateContext } from '@ngxs/store';
+import { patch, updateItem } from '@ngxs/store/operators';
-import { map, of } from 'rxjs';
+import { of } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';
import { inject, Injectable } from '@angular/core';
import { handleSectionError } from '@osf/shared/helpers';
-import { NodeData } from '@osf/shared/models';
-import { MyResourcesService } from '@osf/shared/services';
+import { NotificationSubscription } from '@osf/shared/models';
import { SettingsService } from '../services';
import {
+ DeleteInstitution,
DeleteProject,
GetProjectDetails,
+ GetProjectNotificationSubscriptions,
GetProjectSettings,
UpdateProjectDetails,
+ UpdateProjectNotificationSubscription,
UpdateProjectSettings,
} from './settings.actions';
import { SETTINGS_STATE_DEFAULTS, SettingsStateModel } from './settings.model';
@@ -27,8 +30,6 @@ import { SETTINGS_STATE_DEFAULTS, SettingsStateModel } from './settings.model';
@Injectable()
export class SettingsState {
private readonly settingsService = inject(SettingsService);
- private readonly myProjectService = inject(MyResourcesService);
-
private readonly REFRESH_INTERVAL = 5 * 60 * 1000;
private shouldRefresh(lastFetched?: number): boolean {
@@ -70,23 +71,10 @@ export class SettingsState {
@Action(GetProjectDetails)
getProjectDetails(ctx: StateContext, action: GetProjectDetails) {
const state = ctx.getState();
- const cached = state.projectDetails.data;
- const shouldRefresh = this.shouldRefresh(cached.lastFetched);
-
- if (cached.id === action.projectId && !shouldRefresh) {
- return of(cached).pipe(
- tap(() =>
- ctx.patchState({
- projectDetails: { ...state.projectDetails, isLoading: false, error: null },
- })
- )
- );
- }
ctx.patchState({ projectDetails: { ...state.projectDetails, isLoading: true, error: null } });
- return this.myProjectService.getProjectById(action.projectId).pipe(
- map((response) => response?.data as NodeData),
+ return this.settingsService.getProjectById(action.projectId).pipe(
tap((details) => {
const updatedDetails = {
...details,
@@ -107,7 +95,7 @@ export class SettingsState {
@Action(UpdateProjectDetails)
updateProjectDetails(ctx: StateContext, action: UpdateProjectDetails) {
- return this.myProjectService.updateProjectById(action.payload).pipe(
+ return this.settingsService.updateProjectById(action.payload).pipe(
tap((updatedProject) => {
ctx.patchState({
projectDetails: {
@@ -152,8 +140,54 @@ export class SettingsState {
);
}
+ @Action(GetProjectNotificationSubscriptions)
+ getNotificationSubscriptionsByNodeId(
+ ctx: StateContext,
+ action: GetProjectNotificationSubscriptions
+ ) {
+ return this.settingsService.getNotificationSubscriptions(action.nodeId).pipe(
+ tap((notificationSubscriptions) => {
+ ctx.setState(
+ patch({
+ notifications: patch({
+ data: notificationSubscriptions,
+ isLoading: false,
+ }),
+ })
+ );
+ }),
+ catchError((error) => handleSectionError(ctx, 'notifications', error))
+ );
+ }
+
+ @Action(UpdateProjectNotificationSubscription)
+ updateNotificationSubscriptionForNodeId(
+ ctx: StateContext,
+ action: UpdateProjectNotificationSubscription
+ ) {
+ return this.settingsService.updateSubscription(action.payload.id, action.payload.frequency).pipe(
+ tap((updatedSubscription) => {
+ ctx.setState(
+ patch({
+ notifications: patch({
+ data: updateItem((app) => app.id === action.payload.id, updatedSubscription),
+ error: null,
+ isLoading: false,
+ }),
+ })
+ );
+ }),
+ catchError((error) => handleSectionError(ctx, 'notifications', error))
+ );
+ }
+
@Action(DeleteProject)
deleteProject(ctx: StateContext, action: DeleteProject) {
return this.settingsService.deleteProject(action.projectId);
}
+
+ @Action(DeleteInstitution)
+ deleteInstitution(ctx: StateContext, action: DeleteInstitution) {
+ return this.settingsService.deleteInstitution(action.institutionId, action.projectId);
+ }
}
diff --git a/src/app/features/settings/notifications/mappers/index.ts b/src/app/features/settings/notifications/mappers/index.ts
deleted file mode 100644
index a10b45bdd..000000000
--- a/src/app/features/settings/notifications/mappers/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './notification-subscription.mapper';
diff --git a/src/app/features/settings/notifications/models/index.ts b/src/app/features/settings/notifications/models/index.ts
index a62c932ec..f306b2228 100644
--- a/src/app/features/settings/notifications/models/index.ts
+++ b/src/app/features/settings/notifications/models/index.ts
@@ -1,4 +1,2 @@
-export * from './notification-subscription.models';
-export * from './notification-subscription-json-api.models';
export * from './notifications-form.model';
export * from './subscription-event.model';
diff --git a/src/app/features/settings/notifications/services/notification-subscription.service.ts b/src/app/features/settings/notifications/services/notification-subscription.service.ts
index b92219ea0..576e1a775 100644
--- a/src/app/features/settings/notifications/services/notification-subscription.service.ts
+++ b/src/app/features/settings/notifications/services/notification-subscription.service.ts
@@ -2,12 +2,14 @@ import { map, Observable } from 'rxjs';
import { inject, Injectable } from '@angular/core';
-import { JsonApiResponse } from '@osf/shared/models';
+import { SubscriptionFrequency } from '@osf/shared/enums';
+import { NotificationSubscriptionMapper } from '@osf/shared/mappers';
+import {
+ JsonApiResponse,
+ NotificationSubscription,
+ NotificationSubscriptionGetResponseJsonApi,
+} from '@osf/shared/models';
import { JsonApiService } from '@osf/shared/services';
-import { SubscriptionFrequency } from '@shared/enums';
-
-import { NotificationSubscriptionMapper } from '../mappers';
-import { NotificationSubscription, NotificationSubscriptionGetResponseJsonApi } from '../models';
import { environment } from 'src/environments/environment';
@@ -18,18 +20,10 @@ export class NotificationSubscriptionService {
private readonly jsonApiService = inject(JsonApiService);
private readonly baseUrl = `${environment.apiUrl}/subscriptions/`;
- getAllGlobalNotificationSubscriptions(nodeId?: string): Observable {
- let params: Record;
- if (nodeId) {
- params = {
- 'filter[id]': `${nodeId}_file_updated,${nodeId}_comments`,
- };
- } else {
- params = {
- 'filter[event_name]':
- 'global_reviews,global_comments,global_comment_replies,global_file_updated,global_mentions',
- };
- }
+ getAllGlobalNotificationSubscriptions(): Observable {
+ const params: Record = {
+ 'filter[event_name]': 'global_reviews,global_comments,global_comment_replies,global_file_updated,global_mentions',
+ };
return this.jsonApiService
.get>(this.baseUrl, params)
@@ -38,15 +32,11 @@ export class NotificationSubscriptionService {
);
}
- updateSubscription(
- id: string,
- frequency: SubscriptionFrequency,
- isNodeSubscription?: boolean
- ): Observable {
- const request = NotificationSubscriptionMapper.toUpdateRequest(id, frequency, isNodeSubscription);
+ updateSubscription(id: string, frequency: SubscriptionFrequency): Observable {
+ const request = NotificationSubscriptionMapper.toUpdateRequest(id, frequency);
return this.jsonApiService
- .patch(`${this.baseUrl}/${id}/`, request)
+ .patch(`${this.baseUrl}${id}/`, request)
.pipe(map((response) => NotificationSubscriptionMapper.fromGetResponse(response)));
}
}
diff --git a/src/app/features/settings/notifications/store/notification-subscription.actions.ts b/src/app/features/settings/notifications/store/notification-subscription.actions.ts
index 22380163a..dd987411f 100644
--- a/src/app/features/settings/notifications/store/notification-subscription.actions.ts
+++ b/src/app/features/settings/notifications/store/notification-subscription.actions.ts
@@ -4,20 +4,8 @@ export class GetAllGlobalNotificationSubscriptions {
static readonly type = '[Notification Subscriptions] Get All Global';
}
-export class GetNotificationSubscriptionsByNodeId {
- static readonly type = '[Notification Subscriptions] Get By Node Id';
-
- constructor(public nodeId: string) {}
-}
-
export class UpdateNotificationSubscription {
static readonly type = '[Notification Subscriptions] Update';
constructor(public payload: { id: string; frequency: SubscriptionFrequency }) {}
}
-
-export class UpdateNotificationSubscriptionForNodeId {
- static readonly type = '[Notification Subscriptions] Update For Node';
-
- constructor(public payload: { id: string; frequency: SubscriptionFrequency }) {}
-}
diff --git a/src/app/features/settings/notifications/store/notification-subscription.model.ts b/src/app/features/settings/notifications/store/notification-subscription.model.ts
index 129edd72f..b76894743 100644
--- a/src/app/features/settings/notifications/store/notification-subscription.model.ts
+++ b/src/app/features/settings/notifications/store/notification-subscription.model.ts
@@ -1,10 +1,7 @@
-import { AsyncStateModel } from '@osf/shared/models';
-
-import { NotificationSubscription } from '../models';
+import { AsyncStateModel, NotificationSubscription } from '@osf/shared/models';
export interface NotificationSubscriptionStateModel {
notificationSubscriptions: AsyncStateModel;
- notificationSubscriptionsByNodeId: AsyncStateModel;
}
export const NOTIFICATION_SUBSCRIPTION_STATE_DEFAULTS: NotificationSubscriptionStateModel = {
@@ -13,9 +10,4 @@ export const NOTIFICATION_SUBSCRIPTION_STATE_DEFAULTS: NotificationSubscriptionS
isLoading: false,
error: '',
},
- notificationSubscriptionsByNodeId: {
- data: [],
- isLoading: false,
- error: '',
- },
};
diff --git a/src/app/features/settings/notifications/store/notification-subscription.selectors.ts b/src/app/features/settings/notifications/store/notification-subscription.selectors.ts
index 629d72796..caf6c19e5 100644
--- a/src/app/features/settings/notifications/store/notification-subscription.selectors.ts
+++ b/src/app/features/settings/notifications/store/notification-subscription.selectors.ts
@@ -1,6 +1,6 @@
import { Selector } from '@ngxs/store';
-import { NotificationSubscription } from '../models';
+import { NotificationSubscription } from '@osf/shared/models';
import { NotificationSubscriptionStateModel } from './notification-subscription.model';
import { NotificationSubscriptionState } from './notification-subscription.state';
@@ -11,11 +11,6 @@ export class NotificationSubscriptionSelectors {
return state.notificationSubscriptions.data;
}
- @Selector([NotificationSubscriptionState])
- static getNotificationSubscriptionsByNodeId(state: NotificationSubscriptionStateModel): NotificationSubscription[] {
- return state.notificationSubscriptionsByNodeId.data;
- }
-
@Selector([NotificationSubscriptionState])
static isLoading(state: NotificationSubscriptionStateModel): boolean {
return state.notificationSubscriptions.isLoading;
diff --git a/src/app/features/settings/notifications/store/notification-subscription.state.ts b/src/app/features/settings/notifications/store/notification-subscription.state.ts
index 41deb5474..daad47f5c 100644
--- a/src/app/features/settings/notifications/store/notification-subscription.state.ts
+++ b/src/app/features/settings/notifications/store/notification-subscription.state.ts
@@ -6,15 +6,13 @@ import { catchError, tap } from 'rxjs';
import { inject, Injectable } from '@angular/core';
import { handleSectionError } from '@osf/shared/helpers';
+import { NotificationSubscription } from '@osf/shared/models';
-import { NotificationSubscription } from '../models';
import { NotificationSubscriptionService } from '../services';
import {
GetAllGlobalNotificationSubscriptions,
- GetNotificationSubscriptionsByNodeId,
UpdateNotificationSubscription,
- UpdateNotificationSubscriptionForNodeId,
} from './notification-subscription.actions';
import {
NOTIFICATION_SUBSCRIPTION_STATE_DEFAULTS,
@@ -48,26 +46,6 @@ export class NotificationSubscriptionState {
);
}
- @Action(GetNotificationSubscriptionsByNodeId)
- getNotificationSubscriptionsByNodeId(
- ctx: StateContext,
- action: GetNotificationSubscriptionsByNodeId
- ) {
- return this.notificationSubscriptionService.getAllGlobalNotificationSubscriptions(action.nodeId).pipe(
- tap((notificationSubscriptions) => {
- ctx.setState(
- patch({
- notificationSubscriptionsByNodeId: patch({
- data: notificationSubscriptions,
- isLoading: false,
- }),
- })
- );
- }),
- catchError((error) => handleSectionError(ctx, 'notificationSubscriptionsByNodeId', error))
- );
- }
-
@Action(UpdateNotificationSubscription)
updateNotificationSubscription(
ctx: StateContext,
@@ -88,27 +66,4 @@ export class NotificationSubscriptionState {
catchError((error) => handleSectionError(ctx, 'notificationSubscriptions', error))
);
}
-
- @Action(UpdateNotificationSubscriptionForNodeId)
- updateNotificationSubscriptionForNodeId(
- ctx: StateContext,
- action: UpdateNotificationSubscription
- ) {
- return this.notificationSubscriptionService
- .updateSubscription(action.payload.id, action.payload.frequency, true)
- .pipe(
- tap((updatedSubscription) => {
- ctx.setState(
- patch({
- notificationSubscriptionsByNodeId: patch({
- data: updateItem((app) => app.id === action.payload.id, updatedSubscription),
- error: null,
- isLoading: false,
- }),
- })
- );
- }),
- catchError((error) => handleSectionError(ctx, 'notificationSubscriptionsByNodeId', error))
- );
- }
}
diff --git a/src/app/features/settings/tokens/components/token-add-edit-form/token-add-edit-form.component.ts b/src/app/features/settings/tokens/components/token-add-edit-form/token-add-edit-form.component.ts
index 32111c9e1..cf2a5d7d1 100644
--- a/src/app/features/settings/tokens/components/token-add-edit-form/token-add-edit-form.component.ts
+++ b/src/app/features/settings/tokens/components/token-add-edit-form/token-add-edit-form.component.ts
@@ -45,13 +45,13 @@ export class TokenAddEditFormComponent implements OnInit {
isEditMode = input(false);
initialValues = input(null);
- inputLimits = InputLimits.fullName;
+ readonly inputLimits = InputLimits.fullName;
- protected readonly tokenId = toSignal(this.route.params.pipe(map((params) => params['id'])));
- protected readonly dialogRef = inject(DynamicDialogRef);
- protected readonly TokenFormControls = TokenFormControls;
- protected readonly tokenScopes = select(TokensSelectors.getScopes);
- protected readonly isLoading = select(TokensSelectors.isTokensLoading);
+ readonly tokenId = toSignal(this.route.params.pipe(map((params) => params['id'])));
+ readonly dialogRef = inject(DynamicDialogRef);
+ readonly TokenFormControls = TokenFormControls;
+ readonly tokenScopes = select(TokensSelectors.getScopes);
+ readonly isLoading = select(TokensSelectors.isTokensLoading);
readonly tokenForm: TokenForm = new FormGroup({
[TokenFormControls.TokenName]: new FormControl('', {
@@ -98,14 +98,14 @@ export class TokenAddEditFormComponent implements OnInit {
const tokens = this.store.selectSignal(TokensSelectors.getTokens);
const newToken = tokens()[0];
this.dialogRef.close();
- this.showTokenCreatedDialog(newToken.name, newToken.tokenId);
+ this.showTokenCreatedDialog(newToken.name, newToken.id);
},
});
} else {
this.actions.updateToken(this.tokenId(), tokenName, scopes).subscribe({
complete: () => {
- this.toastService.showSuccess('settings.tokens.toastMessage.successEdit');
this.router.navigate(['settings/tokens']);
+ this.toastService.showSuccess('settings.tokens.toastMessage.successEdit');
},
});
}
diff --git a/src/app/features/settings/tokens/components/token-created-dialog/token-created-dialog.component.ts b/src/app/features/settings/tokens/components/token-created-dialog/token-created-dialog.component.ts
index 954116dae..e377692b3 100644
--- a/src/app/features/settings/tokens/components/token-created-dialog/token-created-dialog.component.ts
+++ b/src/app/features/settings/tokens/components/token-created-dialog/token-created-dialog.component.ts
@@ -26,8 +26,8 @@ import { CopyButtonComponent } from '@shared/components';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TokenCreatedDialogComponent {
- protected readonly dialogRef = inject(DynamicDialogRef);
- protected readonly config = inject(DynamicDialogConfig);
+ readonly dialogRef = inject(DynamicDialogRef);
+ readonly config = inject(DynamicDialogConfig);
readonly tokenInput = viewChild>('tokenInput');
readonly tokenName = input(this.config.data?.tokenName ?? '');
diff --git a/src/app/features/settings/tokens/mappers/token.mapper.ts b/src/app/features/settings/tokens/mappers/token.mapper.ts
index b3f0ae3ff..92451a3cf 100644
--- a/src/app/features/settings/tokens/mappers/token.mapper.ts
+++ b/src/app/features/settings/tokens/mappers/token.mapper.ts
@@ -1,4 +1,4 @@
-import { TokenCreateRequestJsonApi, TokenCreateResponseJsonApi, TokenGetResponseJsonApi, TokenModel } from '../models';
+import { TokenCreateRequestJsonApi, TokenGetResponseJsonApi, TokenModel } from '../models';
export class TokenMapper {
static toRequest(name: string, scopes: string[]): TokenCreateRequestJsonApi {
@@ -13,23 +13,11 @@ export class TokenMapper {
};
}
- static fromCreateResponse(response: TokenCreateResponseJsonApi): TokenModel {
- return {
- id: response.id,
- name: response.attributes.name,
- tokenId: response.attributes.token_id,
- scopes: response.attributes.scopes.split(' '),
- ownerId: response.attributes.owner,
- };
- }
-
static fromGetResponse(response: TokenGetResponseJsonApi): TokenModel {
return {
id: response.id,
name: response.attributes.name,
- tokenId: response.id,
- scopes: response.attributes.scopes.split(' '),
- ownerId: response.attributes.owner,
+ scopes: response.embeds.scopes.data.map((item) => item.id),
};
}
}
diff --git a/src/app/features/settings/tokens/models/add-edit-token.model.ts b/src/app/features/settings/tokens/models/add-edit-token.model.ts
deleted file mode 100644
index f7cfdac4e..000000000
--- a/src/app/features/settings/tokens/models/add-edit-token.model.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export interface AddEditTokenModel {
- name: string;
- scopes: string[];
-}
diff --git a/src/app/features/settings/tokens/models/scope-json-api.model.ts b/src/app/features/settings/tokens/models/scope-json-api.model.ts
index 443b1a22f..8933a7ce3 100644
--- a/src/app/features/settings/tokens/models/scope-json-api.model.ts
+++ b/src/app/features/settings/tokens/models/scope-json-api.model.ts
@@ -4,7 +4,4 @@ export interface ScopeJsonApi {
attributes: {
description: string;
};
- links: {
- self: string;
- };
}
diff --git a/src/app/features/settings/tokens/models/token-json-api.model.ts b/src/app/features/settings/tokens/models/token-json-api.model.ts
index 657116c8b..66bc0ac31 100644
--- a/src/app/features/settings/tokens/models/token-json-api.model.ts
+++ b/src/app/features/settings/tokens/models/token-json-api.model.ts
@@ -8,23 +8,22 @@ export interface TokenCreateRequestJsonApi {
};
}
-export interface TokenCreateResponseJsonApi {
+export interface TokenGetResponseJsonApi {
id: string;
- type: 'tokens';
- attributes: {
- name: string;
- token_id: string;
- scopes: string;
- owner: string;
+ attributes: TokenAttributesJsonApi;
+ embeds: TokenEmbedsJsonApi;
+}
+
+interface TokenAttributesJsonApi {
+ name: string;
+}
+
+interface TokenEmbedsJsonApi {
+ scopes: {
+ data: TokenEmbedsDataItemJsonApi[];
};
}
-export interface TokenGetResponseJsonApi {
+interface TokenEmbedsDataItemJsonApi {
id: string;
- type: 'tokens';
- attributes: {
- name: string;
- scopes: string;
- owner: string;
- };
}
diff --git a/src/app/features/settings/tokens/models/token.model.ts b/src/app/features/settings/tokens/models/token.model.ts
index f4b5a3748..735b29bcd 100644
--- a/src/app/features/settings/tokens/models/token.model.ts
+++ b/src/app/features/settings/tokens/models/token.model.ts
@@ -1,7 +1,5 @@
export interface TokenModel {
id: string;
name: string;
- tokenId: string;
scopes: string[];
- ownerId: string;
}
diff --git a/src/app/features/settings/tokens/services/tokens.service.ts b/src/app/features/settings/tokens/services/tokens.service.ts
index c29cb06f4..2735c5bad 100644
--- a/src/app/features/settings/tokens/services/tokens.service.ts
+++ b/src/app/features/settings/tokens/services/tokens.service.ts
@@ -7,7 +7,7 @@ import { JsonApiResponse } from '@osf/shared/models';
import { JsonApiService } from '@osf/shared/services';
import { ScopeMapper, TokenMapper } from '../mappers';
-import { ScopeJsonApi, ScopeModel, TokenCreateResponseJsonApi, TokenGetResponseJsonApi, TokenModel } from '../models';
+import { ScopeJsonApi, ScopeModel, TokenGetResponseJsonApi, TokenModel } from '../models';
import { environment } from 'src/environments/environment';
@@ -39,16 +39,16 @@ export class TokensService {
const request = TokenMapper.toRequest(name, scopes);
return this.jsonApiService
- .post>(environment.apiUrl + '/tokens/', request)
- .pipe(map((response) => TokenMapper.fromCreateResponse(response.data)));
+ .post>(environment.apiUrl + '/tokens/', request)
+ .pipe(map((response) => TokenMapper.fromGetResponse(response.data)));
}
updateToken(tokenId: string, name: string, scopes: string[]): Observable {
const request = TokenMapper.toRequest(name, scopes);
return this.jsonApiService
- .patch(`${environment.apiUrl}/tokens/${tokenId}/`, request)
- .pipe(map((response) => TokenMapper.fromCreateResponse(response)));
+ .patch(`${environment.apiUrl}/tokens/${tokenId}/`, request)
+ .pipe(map((response) => TokenMapper.fromGetResponse(response)));
}
deleteToken(tokenId: string): Observable {
diff --git a/src/app/features/settings/tokens/store/tokens.models.ts b/src/app/features/settings/tokens/store/tokens.models.ts
index 3f5a6a8b5..fcbd2c285 100644
--- a/src/app/features/settings/tokens/store/tokens.models.ts
+++ b/src/app/features/settings/tokens/store/tokens.models.ts
@@ -6,3 +6,16 @@ export interface TokensStateModel {
scopes: AsyncStateModel;
tokens: AsyncStateModel;
}
+
+export const TOKENS_STATE_DEFAULTS: TokensStateModel = {
+ scopes: {
+ data: [],
+ isLoading: false,
+ error: null,
+ },
+ tokens: {
+ data: [],
+ isLoading: false,
+ error: null,
+ },
+};
diff --git a/src/app/features/settings/tokens/store/tokens.state.ts b/src/app/features/settings/tokens/store/tokens.state.ts
index a955fcb41..2230aab44 100644
--- a/src/app/features/settings/tokens/store/tokens.state.ts
+++ b/src/app/features/settings/tokens/store/tokens.state.ts
@@ -1,33 +1,24 @@
import { Action, State, StateContext } from '@ngxs/store';
-import { catchError, of, tap, throwError } from 'rxjs';
+import { catchError, of, tap } from 'rxjs';
import { inject, Injectable } from '@angular/core';
+import { handleSectionError } from '@osf/shared/helpers';
+
import { TokenModel } from '../models';
import { TokensService } from '../services';
import { CreateToken, DeleteToken, GetScopes, GetTokenById, GetTokens, UpdateToken } from './tokens.actions';
-import { TokensStateModel } from './tokens.models';
+import { TOKENS_STATE_DEFAULTS, TokensStateModel } from './tokens.models';
@State({
name: 'tokens',
- defaults: {
- scopes: {
- data: [],
- isLoading: false,
- error: null,
- },
- tokens: {
- data: [],
- isLoading: false,
- error: null,
- },
- },
+ defaults: TOKENS_STATE_DEFAULTS,
})
@Injectable()
export class TokensState {
- tokensService = inject(TokensService);
+ private readonly tokensService = inject(TokensService);
@Action(GetScopes)
getScopes(ctx: StateContext) {
@@ -39,7 +30,7 @@ export class TokensState {
tap((scopes) => {
ctx.patchState({ scopes: { data: scopes, isLoading: false, error: null } });
}),
- catchError((error) => this.handleError(ctx, 'scopes', error))
+ catchError((error) => handleSectionError(ctx, 'scopes', error))
);
}
@@ -51,7 +42,7 @@ export class TokensState {
tap((tokens) => {
ctx.patchState({ tokens: { data: tokens, isLoading: false, error: null } });
}),
- catchError((error) => this.handleError(ctx, 'tokens', error))
+ catchError((error) => handleSectionError(ctx, 'tokens', error))
);
}
@@ -71,7 +62,22 @@ export class TokensState {
const updatedTokens = [...state.tokens.data, token];
ctx.patchState({ tokens: { data: updatedTokens, isLoading: false, error: null } });
}),
- catchError((error) => this.handleError(ctx, 'tokens', error))
+ catchError((error) => handleSectionError(ctx, 'tokens', error))
+ );
+ }
+
+ @Action(CreateToken)
+ createToken(ctx: StateContext, action: CreateToken) {
+ const state = ctx.getState();
+ ctx.patchState({ tokens: { ...state.tokens, isLoading: true, error: null } });
+
+ return this.tokensService.createToken(action.name, action.scopes).pipe(
+ tap((newToken) => {
+ const state = ctx.getState();
+ const updatedTokens = [newToken, ...state.tokens.data];
+ ctx.patchState({ tokens: { data: updatedTokens, isLoading: false, error: null } });
+ }),
+ catchError((error) => handleSectionError(ctx, 'tokens', error))
);
}
@@ -88,7 +94,7 @@ export class TokensState {
);
ctx.patchState({ tokens: { data: updatedTokens, isLoading: false, error: null } });
}),
- catchError((error) => this.handleError(ctx, 'tokens', error))
+ catchError((error) => handleSectionError(ctx, 'tokens', error))
);
}
@@ -103,36 +109,7 @@ export class TokensState {
const updatedTokens = state.tokens.data.filter((token: TokenModel) => token.id !== action.tokenId);
ctx.patchState({ tokens: { data: updatedTokens, isLoading: false, error: null } });
}),
- catchError((error) => this.handleError(ctx, 'tokens', error))
+ catchError((error) => handleSectionError(ctx, 'tokens', error))
);
}
-
- @Action(CreateToken)
- createToken(ctx: StateContext, action: CreateToken) {
- const state = ctx.getState();
- ctx.patchState({ tokens: { ...state.tokens, isLoading: true, error: null } });
-
- return this.tokensService.createToken(action.name, action.scopes).pipe(
- tap((newToken) => {
- const state = ctx.getState();
- const updatedTokens = [newToken, ...state.tokens.data];
- ctx.patchState({ tokens: { data: updatedTokens, isLoading: false, error: null } });
-
- return newToken;
- }),
- catchError((error) => this.handleError(ctx, 'tokens', error))
- );
- }
-
- private handleError(ctx: StateContext, key: keyof TokensStateModel, error: Error) {
- const state = ctx.getState();
- ctx.patchState({
- [key]: {
- ...state[key],
- isLoading: false,
- error: error.message,
- },
- });
- return throwError(() => error);
- }
}
diff --git a/src/app/features/settings/tokens/tokens.component.ts b/src/app/features/settings/tokens/tokens.component.ts
index f2057e31d..194bfd636 100644
--- a/src/app/features/settings/tokens/tokens.component.ts
+++ b/src/app/features/settings/tokens/tokens.component.ts
@@ -30,11 +30,10 @@ export class TokensComponent implements OnInit {
private readonly translateService = inject(TranslateService);
private readonly actions = createDispatchMap({ getScopes: GetScopes });
- protected readonly isSmall = toSignal(inject(IS_SMALL));
- protected readonly isBaseRoute = toSignal(
- this.router.events.pipe(map(() => this.router.url === '/settings/tokens')),
- { initialValue: this.router.url === '/settings/tokens' }
- );
+ readonly isSmall = toSignal(inject(IS_SMALL));
+ readonly isBaseRoute = toSignal(this.router.events.pipe(map(() => this.router.url === '/settings/tokens')), {
+ initialValue: this.router.url === '/settings/tokens',
+ });
ngOnInit() {
this.actions.getScopes();
diff --git a/src/app/shared/enums/subscriptions/index.ts b/src/app/shared/enums/subscriptions/index.ts
index c76c1b642..64dbd8be9 100644
--- a/src/app/shared/enums/subscriptions/index.ts
+++ b/src/app/shared/enums/subscriptions/index.ts
@@ -1,3 +1,3 @@
-export * from '@shared/enums/subscriptions/subscription-event.enum';
-export * from '@shared/enums/subscriptions/subscription-frequency.enum';
-export * from '@shared/enums/subscriptions/subscription-type.enum';
+export * from './subscription-event.enum';
+export * from './subscription-frequency.enum';
+export * from './subscription-type.enum';
diff --git a/src/app/shared/mappers/index.ts b/src/app/shared/mappers/index.ts
index 893f2d106..8f9023fad 100644
--- a/src/app/shared/mappers/index.ts
+++ b/src/app/shared/mappers/index.ts
@@ -8,6 +8,7 @@ export * from './files/files.mapper';
export * from './filters';
export * from './institutions';
export * from './licenses.mapper';
+export * from './notification-subscription.mapper';
export * from './registry';
export * from './resource-card';
export * from './resource-overview.mappers';
diff --git a/src/app/features/settings/notifications/mappers/notification-subscription.mapper.ts b/src/app/shared/mappers/notification-subscription.mapper.ts
similarity index 89%
rename from src/app/features/settings/notifications/mappers/notification-subscription.mapper.ts
rename to src/app/shared/mappers/notification-subscription.mapper.ts
index 387603e99..bd0bcc6c9 100644
--- a/src/app/features/settings/notifications/mappers/notification-subscription.mapper.ts
+++ b/src/app/shared/mappers/notification-subscription.mapper.ts
@@ -1,5 +1,4 @@
-import { SubscriptionEvent, SubscriptionFrequency, SubscriptionType } from '@shared/enums';
-
+import { SubscriptionEvent, SubscriptionFrequency, SubscriptionType } from '../enums';
import {
NotificationSubscription,
NotificationSubscriptionGetResponseJsonApi,
@@ -26,7 +25,7 @@ export class NotificationSubscriptionMapper {
return {
data: {
- type: isNodeSubscription ? SubscriptionType.Node : SubscriptionType.Global,
+ type: SubscriptionType.Global,
attributes: baseAttributes,
...(isNodeSubscription ? {} : { id }),
},
diff --git a/src/app/shared/models/id-name.model.ts b/src/app/shared/models/common/id-name.model.ts
similarity index 100%
rename from src/app/shared/models/id-name.model.ts
rename to src/app/shared/models/common/id-name.model.ts
diff --git a/src/app/shared/models/common/id-type.model.ts b/src/app/shared/models/common/id-type.model.ts
new file mode 100644
index 000000000..c01fe4dc5
--- /dev/null
+++ b/src/app/shared/models/common/id-type.model.ts
@@ -0,0 +1,4 @@
+export interface IdTypeModel {
+ id: string;
+ type: string;
+}
diff --git a/src/app/shared/models/common/index.ts b/src/app/shared/models/common/index.ts
index 5595a8933..3a7369809 100644
--- a/src/app/shared/models/common/index.ts
+++ b/src/app/shared/models/common/index.ts
@@ -1 +1,3 @@
+export * from './id-name.model';
+export * from './id-type.model';
export * from './json-api.model';
diff --git a/src/app/shared/models/common/json-api.model.ts b/src/app/shared/models/common/json-api.model.ts
index f1a7ff950..6a6b8fe01 100644
--- a/src/app/shared/models/common/json-api.model.ts
+++ b/src/app/shared/models/common/json-api.model.ts
@@ -13,6 +13,11 @@ export interface ResponseJsonApi {
meta: MetaJsonApi;
}
+export interface ResponseDataJsonApi {
+ data: Data;
+ meta: MetaJsonApi;
+}
+
export interface ApiData {
id: string;
attributes: Attributes;
@@ -25,7 +30,7 @@ export interface ApiData {
export interface MetaJsonApi {
total: number;
per_page: number;
- version?: string;
+ version: string;
}
export interface PaginationLinksJsonApi {
diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts
index 74b2fb24e..dfa9a7433 100644
--- a/src/app/shared/models/index.ts
+++ b/src/app/shared/models/index.ts
@@ -17,7 +17,6 @@ export * from './filter-labels.model';
export * from './filters';
export * from './google-drive-folder.model';
export * from './guid-response-json-api.model';
-export * from './id-name.model';
export * from './institutions';
export * from './language-code.model';
export * from './license';
@@ -29,6 +28,7 @@ export * from './metadata-field.model';
export * from './my-resources';
export * from './nodes/create-project-form.model';
export * from './nodes/nodes-json-api.model';
+export * from './notifications';
export * from './paginated-data.model';
export * from './pagination-links.model';
export * from './profile-settings-update.model';
diff --git a/src/app/shared/models/nodes/nodes-json-api.model.ts b/src/app/shared/models/nodes/nodes-json-api.model.ts
index e760308f5..7a87a5b8c 100644
--- a/src/app/shared/models/nodes/nodes-json-api.model.ts
+++ b/src/app/shared/models/nodes/nodes-json-api.model.ts
@@ -1,4 +1,48 @@
-export interface NodeAttributes {
+export interface NodeData {
+ id: string;
+ type: 'nodes';
+ attributes: NodeAttributes;
+ relationships: NodeRelationships;
+ links: NodeLinks;
+ lastFetched?: number;
+}
+
+export interface NodeResponseModel {
+ data: NodeData;
+ meta: NodeMeta;
+}
+
+export interface UpdateNodeRequestModel {
+ data: UpdateNodeData;
+}
+
+export interface CreateProjectPayloadJsoApi {
+ data: {
+ type: 'nodes';
+ attributes: {
+ title: string;
+ description?: string;
+ category: 'project';
+ template_from?: string;
+ };
+ relationships: {
+ region: {
+ data: {
+ type: 'regions';
+ id: string;
+ };
+ };
+ affiliated_institutions?: {
+ data: {
+ type: 'institutions';
+ id: string;
+ }[];
+ };
+ };
+ };
+}
+
+interface NodeAttributes {
title: string;
description: string;
category: string;
@@ -21,7 +65,7 @@ export interface NodeAttributes {
subjects: unknown[];
}
-export interface RelationshipLinks {
+interface RelationshipLinks {
related: {
href: string;
meta: Record;
@@ -32,7 +76,7 @@ export interface RelationshipLinks {
};
}
-export interface NodeRelationships {
+interface NodeRelationships {
children: { links: RelationshipLinks };
comments: { links: RelationshipLinks };
contributors: { links: RelationshipLinks };
@@ -82,69 +126,25 @@ export interface NodeRelationships {
subjects_acceptable: { links: RelationshipLinks };
}
-export interface NodeLinks {
+interface NodeLinks {
html: string;
self: string;
iri: string;
}
-export interface NodeData {
- id: string;
- type: 'nodes';
- attributes: NodeAttributes;
- relationships: NodeRelationships;
- links: NodeLinks;
- lastFetched?: number;
-}
-
-export interface NodeMeta {
+interface NodeMeta {
version: string;
}
-export interface NodeResponseModel {
- data: NodeData;
- meta: NodeMeta;
-}
-
-export interface UpdateNodeAttributes {
+interface UpdateNodeAttributes {
description?: string;
tags?: string[];
public?: boolean;
title?: string;
}
-export interface UpdateNodeData {
+interface UpdateNodeData {
type: 'nodes';
id: string;
attributes: UpdateNodeAttributes;
}
-
-export interface UpdateNodeRequestModel {
- data: UpdateNodeData;
-}
-
-export interface CreateProjectPayloadJsoApi {
- data: {
- type: 'nodes';
- attributes: {
- title: string;
- description?: string;
- category: 'project';
- template_from?: string;
- };
- relationships: {
- region: {
- data: {
- type: 'regions';
- id: string;
- };
- };
- affiliated_institutions?: {
- data: {
- type: 'institutions';
- id: string;
- }[];
- };
- };
- };
-}
diff --git a/src/app/shared/models/notifications/index.ts b/src/app/shared/models/notifications/index.ts
new file mode 100644
index 000000000..91dcdba7c
--- /dev/null
+++ b/src/app/shared/models/notifications/index.ts
@@ -0,0 +1,2 @@
+export * from './notification-subscription.model';
+export * from './notification-subscription-json-api.model';
diff --git a/src/app/features/settings/notifications/models/notification-subscription-json-api.models.ts b/src/app/shared/models/notifications/notification-subscription-json-api.model.ts
similarity index 87%
rename from src/app/features/settings/notifications/models/notification-subscription-json-api.models.ts
rename to src/app/shared/models/notifications/notification-subscription-json-api.model.ts
index 5d7714c85..a3afd5695 100644
--- a/src/app/features/settings/notifications/models/notification-subscription-json-api.models.ts
+++ b/src/app/shared/models/notifications/notification-subscription-json-api.model.ts
@@ -1,4 +1,4 @@
-import { SubscriptionFrequency } from '@shared/enums';
+import { SubscriptionFrequency } from '@osf/shared/enums';
export interface NotificationSubscriptionGetResponseJsonApi {
id: string;
diff --git a/src/app/features/settings/notifications/models/notification-subscription.models.ts b/src/app/shared/models/notifications/notification-subscription.model.ts
similarity index 61%
rename from src/app/features/settings/notifications/models/notification-subscription.models.ts
rename to src/app/shared/models/notifications/notification-subscription.model.ts
index 9ad8d3701..7cbefa654 100644
--- a/src/app/features/settings/notifications/models/notification-subscription.models.ts
+++ b/src/app/shared/models/notifications/notification-subscription.model.ts
@@ -1,4 +1,4 @@
-import { SubscriptionEvent, SubscriptionFrequency } from '@shared/enums';
+import { SubscriptionEvent, SubscriptionFrequency } from '@osf/shared/enums';
export interface NotificationSubscription {
id: string;
diff --git a/src/app/shared/services/my-resources.service.ts b/src/app/shared/services/my-resources.service.ts
index c63d1f178..d7cb7b931 100644
--- a/src/app/shared/services/my-resources.service.ts
+++ b/src/app/shared/services/my-resources.service.ts
@@ -14,9 +14,6 @@ import {
MyResourcesItemResponseJsonApi,
MyResourcesResponseJsonApi,
MyResourcesSearchFilters,
- NodeData,
- NodeResponseModel,
- UpdateNodeRequestModel,
} from '@shared/models';
import { JsonApiService } from '@shared/services';
@@ -211,12 +208,4 @@ export class MyResourcesService {
.post>(`${environment.apiUrl}/nodes/`, payload, params)
.pipe(map((response) => MyResourcesMapper.fromResponse(response.data)));
}
-
- getProjectById(projectId: string): Observable {
- return this.jsonApiService.get(`${this.apiUrl}/nodes/${projectId}/`);
- }
-
- updateProjectById(model: UpdateNodeRequestModel): Observable {
- return this.jsonApiService.patch(`${this.apiUrl}/nodes/${model?.data?.id}/`, model);
- }
}
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index f8b1f82b4..4c408ceea 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -415,10 +415,10 @@
"contributorsOption": "Contributors (with write access)",
"anyoneOption": "Anyone with link",
"whoCanEdit": "Who can edit:",
- "url": "url",
- "label": "label",
+ "url": "URL",
+ "label": "Label",
+ "storageLocationMessage": "Storage location cannot be changed after project is created.",
"redirectUrlPlaceholder": "Send people who visit your OSF project page to this link instead",
- "redirectLabelPlaceholder": "Optional",
"invalidUrl": "Please enter a valid URL, such as: https://example.com",
"disabledForWiki": "This feature is disabled for wikis of private projects.",
"enabledForWiki": "This feature is enabled for wikis of private projects.",
@@ -918,6 +918,11 @@
"title": "Delete project",
"message": "Are you sure you want to delete {{name}} project?",
"success": "Project has been successfully deleted."
+ },
+ "deleteInstitution": {
+ "title": "Delete institution",
+ "message": "Are you sure you want to delete {{name}} from this project?",
+ "success": "Institution has been successfully deleted."
}
},
"files": {