diff --git a/src/app/features/moderation/collection-moderation.routes.ts b/src/app/features/moderation/collection-moderation.routes.ts
index 76d153d3b..eea233a4b 100644
--- a/src/app/features/moderation/collection-moderation.routes.ts
+++ b/src/app/features/moderation/collection-moderation.routes.ts
@@ -3,11 +3,12 @@ import { provideStates } from '@ngxs/store';
import { Routes } from '@angular/router';
import { CollectionsModerationState } from '@osf/features/moderation/store/collections-moderation';
-import { ResourceType } from '@osf/shared/enums/resource-type.enum';
+import { CurrentResourceType, ResourceType } from '@osf/shared/enums/resource-type.enum';
import { ActivityLogsState } from '@shared/stores/activity-logs';
import { CollectionsState } from '@shared/stores/collections';
import { ModeratorsState } from './store/moderators';
+import { ProviderSubscriptionsState } from './store/provider-subscriptions';
import { CollectionModerationTab } from './enums';
export const collectionModerationRoutes: Routes = [
@@ -46,7 +47,8 @@ export const collectionModerationRoutes: Routes = [
import('./components/notification-settings/notification-settings.component').then(
(m) => m.NotificationSettingsComponent
),
- data: { tab: CollectionModerationTab.Settings },
+ data: { tab: CollectionModerationTab.Settings, resourceType: CurrentResourceType.Collections },
+ providers: [provideStates([ProviderSubscriptionsState])],
},
],
},
diff --git a/src/app/features/moderation/components/notification-settings/notification-settings.component.html b/src/app/features/moderation/components/notification-settings/notification-settings.component.html
index 07e77467a..3ac4f1a3a 100644
--- a/src/app/features/moderation/components/notification-settings/notification-settings.component.html
+++ b/src/app/features/moderation/components/notification-settings/notification-settings.component.html
@@ -1,6 +1,42 @@
-
- {{ 'moderation.settingsMessage' | translate }}
-
+{{ 'moderation.notificationPreferences.title' | translate }}
+
+ {{ 'moderation.notificationPreferences.note' | translate }}
+
{{ 'moderation.userSettings' | translate }}
+
+@if (!isLoading()) {
+
+ @for (sub of subscriptions(); track sub.id) {
+
+
+
+
+ {{ selectedOption.label | translate }}
+
+
+
+ {{ item.label | translate }}
+
+
+ }
+
+} @else {
+
+ @for (_ of [1, 2]; track $index) {
+
+
+ }
+
+}
diff --git a/src/app/features/moderation/components/notification-settings/notification-settings.component.scss b/src/app/features/moderation/components/notification-settings/notification-settings.component.scss
index e69de29bb..2ce2707f4 100644
--- a/src/app/features/moderation/components/notification-settings/notification-settings.component.scss
+++ b/src/app/features/moderation/components/notification-settings/notification-settings.component.scss
@@ -0,0 +1,21 @@
+@use "styles/variables" as var;
+
+.notification-configuration {
+ display: grid;
+ gap: 12px;
+ align-items: center;
+ grid-template-columns: 0.5fr 2fr;
+
+ .dropdown {
+ width: 50%;
+ }
+
+ @media (max-width: var.$breakpoint-sm) {
+ grid-template-columns: 1fr;
+ row-gap: 0;
+
+ .dropdown {
+ width: 100%;
+ }
+ }
+}
diff --git a/src/app/features/moderation/components/notification-settings/notification-settings.component.spec.ts b/src/app/features/moderation/components/notification-settings/notification-settings.component.spec.ts
index a5411f8d7..9faeb1fd6 100644
--- a/src/app/features/moderation/components/notification-settings/notification-settings.component.spec.ts
+++ b/src/app/features/moderation/components/notification-settings/notification-settings.component.spec.ts
@@ -1,24 +1,137 @@
+import { Store } from '@ngxs/store';
+
+import { MockProvider } from 'ng-mocks';
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ActivatedRoute } from '@angular/router';
+
+import { SUBSCRIPTION_FREQUENCY_OPTIONS } from '@osf/shared/constants/subscription-options.const';
+import { CurrentResourceType } from '@osf/shared/enums/resource-type.enum';
+import { SubscriptionEvent } from '@osf/shared/enums/subscriptions/subscription-event.enum';
+import { SubscriptionFrequency } from '@osf/shared/enums/subscriptions/subscription-frequency.enum';
+import { NotificationSubscription } from '@osf/shared/models/notifications/notification-subscription.model';
+import { ToastService } from '@osf/shared/services/toast.service';
+
+import {
+ GetProviderSubscriptions,
+ ProviderSubscriptionsSelectors,
+ UpdateProviderSubscription,
+} from '../../store/provider-subscriptions';
import { NotificationSettingsComponent } from './notification-settings.component';
+import { ToastServiceMock } from '@testing/mocks/toast.service.mock';
import { OSFTestingModule } from '@testing/osf.testing.module';
+import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
+import { provideMockStore } from '@testing/providers/store-provider.mock';
+
+const MOCK_PROVIDER_SUBSCRIPTIONS: NotificationSubscription[] = [
+ {
+ id: 'sub-1',
+ event: SubscriptionEvent.ProviderNewPendingSubmissions,
+ frequency: SubscriptionFrequency.Instant,
+ },
+ {
+ id: 'sub-2',
+ event: SubscriptionEvent.ProviderNewPendingWithdrawRequests,
+ frequency: SubscriptionFrequency.Never,
+ },
+];
+
+async function createComponent(resourceType: CurrentResourceType, providerId = 'test-provider-123') {
+ const mockActivatedRoute = ActivatedRouteMockBuilder.create()
+ .withParams({ providerId })
+ .withData({ resourceType })
+ .build();
+
+ await TestBed.configureTestingModule({
+ imports: [NotificationSettingsComponent, OSFTestingModule],
+ providers: [
+ MockProvider(ActivatedRoute, mockActivatedRoute),
+ ToastServiceMock,
+ provideMockStore({
+ signals: [
+ { selector: ProviderSubscriptionsSelectors.getSubscriptions, value: MOCK_PROVIDER_SUBSCRIPTIONS },
+ { selector: ProviderSubscriptionsSelectors.isLoading, value: false },
+ ],
+ }),
+ ],
+ }).compileComponents();
+
+ const fixture = TestBed.createComponent(NotificationSettingsComponent);
+ const component = fixture.componentInstance;
+ const toastService = TestBed.inject(ToastService) as jest.Mocked;
+ const store = TestBed.inject(Store);
+
+ return { fixture, component, toastService, store };
+}
describe('NotificationSettingsComponent', () => {
let component: NotificationSettingsComponent;
let fixture: ComponentFixture;
+ let toastService: jest.Mocked;
+ let store: Store;
- beforeEach(async () => {
- await TestBed.configureTestingModule({
- imports: [NotificationSettingsComponent, OSFTestingModule],
- }).compileComponents();
+ const mockProviderId = 'test-provider-123';
- fixture = TestBed.createComponent(NotificationSettingsComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
+ beforeEach(async () => {
+ ({ fixture, component, toastService, store } = await createComponent(
+ CurrentResourceType.Preprints,
+ mockProviderId
+ ));
});
it('should create', () => {
+ fixture.detectChanges();
expect(component).toBeTruthy();
});
+
+ it('should read providerId and resourceType from route', () => {
+ fixture.detectChanges();
+ expect(component.providerId()).toBe(mockProviderId);
+ expect(component.resourceType()).toBe(CurrentResourceType.Preprints);
+ });
+
+ it('should dispatch GetProviderSubscriptions on init', () => {
+ fixture.detectChanges();
+ expect(store.dispatch as jest.Mock).toHaveBeenCalledWith(
+ new GetProviderSubscriptions(CurrentResourceType.Preprints, mockProviderId)
+ );
+ });
+
+ it('should dispatch UpdateProviderSubscription and show toast on frequency change', () => {
+ fixture.detectChanges();
+
+ component.onFrequencyChange(MOCK_PROVIDER_SUBSCRIPTIONS[0], SubscriptionFrequency.Daily);
+
+ expect(store.dispatch as jest.Mock).toHaveBeenCalledWith(
+ new UpdateProviderSubscription({
+ providerType: CurrentResourceType.Preprints,
+ providerId: mockProviderId,
+ subscriptionId: 'sub-1',
+ frequency: SubscriptionFrequency.Daily,
+ })
+ );
+ expect(toastService.showSuccess).toHaveBeenCalledWith('moderation.notificationPreferences.successUpdate');
+ });
+
+ it('should not dispatch UpdateProviderSubscription if frequency is unchanged', () => {
+ fixture.detectChanges();
+
+ component.onFrequencyChange(MOCK_PROVIDER_SUBSCRIPTIONS[0], SubscriptionFrequency.Instant);
+
+ expect(store.dispatch as jest.Mock).not.toHaveBeenCalledWith(expect.any(UpdateProviderSubscription));
+ });
+
+ it('should expose frequencyOptions from SUBSCRIPTION_FREQUENCY_OPTIONS', () => {
+ expect(component.frequencyOptions).toEqual(SUBSCRIPTION_FREQUENCY_OPTIONS);
+ });
+
+ it('should populate form controls when subscriptions load', () => {
+ fixture.detectChanges();
+ expect(component.form.contains('sub-1')).toBe(true);
+ expect(component.form.contains('sub-2')).toBe(true);
+ expect(component.form.get('sub-1')?.value).toBe(SubscriptionFrequency.Instant);
+ expect(component.form.get('sub-2')?.value).toBe(SubscriptionFrequency.Never);
+ });
});
diff --git a/src/app/features/moderation/components/notification-settings/notification-settings.component.ts b/src/app/features/moderation/components/notification-settings/notification-settings.component.ts
index ce5b3ccd3..0dbde36ec 100644
--- a/src/app/features/moderation/components/notification-settings/notification-settings.component.ts
+++ b/src/app/features/moderation/components/notification-settings/notification-settings.component.ts
@@ -1,13 +1,102 @@
+import { createDispatchMap, select } from '@ngxs/store';
+
import { TranslatePipe } from '@ngx-translate/core';
-import { ChangeDetectionStrategy, Component } from '@angular/core';
-import { RouterLink } from '@angular/router';
+import { Select } from 'primeng/select';
+import { Skeleton } from 'primeng/skeleton';
+
+import { of } from 'rxjs';
+import { map } from 'rxjs/operators';
+
+import { ChangeDetectionStrategy, Component, DestroyRef, effect, inject, OnInit, Signal } from '@angular/core';
+import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
+import { FormBuilder, FormControl, FormRecord, ReactiveFormsModule } from '@angular/forms';
+import { ActivatedRoute, RouterLink } from '@angular/router';
+
+import { SUBSCRIPTION_FREQUENCY_OPTIONS } from '@osf/shared/constants/subscription-options.const';
+import { CurrentResourceType } from '@osf/shared/enums/resource-type.enum';
+import { SubscriptionFrequency } from '@osf/shared/enums/subscriptions/subscription-frequency.enum';
+import { NotificationSubscription } from '@osf/shared/models/notifications/notification-subscription.model';
+import { ToastService } from '@osf/shared/services/toast.service';
+
+import {
+ GetProviderSubscriptions,
+ ProviderSubscriptionsSelectors,
+ UpdateProviderSubscription,
+} from '../../store/provider-subscriptions';
@Component({
selector: 'osf-notification-settings',
- imports: [TranslatePipe, RouterLink],
+ imports: [TranslatePipe, RouterLink, ReactiveFormsModule, Select, Skeleton],
templateUrl: './notification-settings.component.html',
styleUrl: './notification-settings.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class NotificationSettingsComponent {}
+export class NotificationSettingsComponent implements OnInit {
+ private readonly route = inject(ActivatedRoute);
+ private readonly fb = inject(FormBuilder);
+ private readonly toastService = inject(ToastService);
+ private readonly destroyRef = inject(DestroyRef);
+
+ readonly providerId = toSignal(
+ this.route.parent?.params.pipe(map((params) => params['providerId'])) ?? of(undefined)
+ );
+ readonly resourceType: Signal = toSignal(
+ this.route.data.pipe(map((params) => params['resourceType']))
+ );
+
+ subscriptions = select(ProviderSubscriptionsSelectors.getSubscriptions);
+ isLoading = select(ProviderSubscriptionsSelectors.isLoading);
+
+ readonly form = new FormRecord>({});
+
+ readonly frequencyOptions = SUBSCRIPTION_FREQUENCY_OPTIONS;
+
+ private readonly actions = createDispatchMap({
+ getProviderSubscriptions: GetProviderSubscriptions,
+ updateProviderSubscription: UpdateProviderSubscription,
+ });
+
+ constructor() {
+ effect(() => {
+ const subs = this.subscriptions();
+ subs.forEach((sub) => {
+ const control = this.form.controls[sub.id];
+ if (!control) {
+ this.form.addControl(sub.id, this.fb.control(sub.frequency, { nonNullable: true }), { emitEvent: false });
+ return;
+ }
+
+ if (control.value !== sub.frequency) {
+ control.setValue(sub.frequency, { emitEvent: false });
+ }
+ });
+ });
+ }
+
+ ngOnInit(): void {
+ const providerType = this.resourceType();
+ const providerId = this.providerId();
+ if (providerType && providerId) {
+ this.actions.getProviderSubscriptions(providerType, providerId);
+ }
+ }
+
+ onFrequencyChange(sub: NotificationSubscription, frequency: SubscriptionFrequency): void {
+ if (sub.frequency === frequency) return;
+
+ const providerType = this.resourceType();
+ const providerId = this.providerId();
+ if (!providerType || !providerId) return;
+
+ this.actions
+ .updateProviderSubscription({
+ providerType,
+ providerId,
+ subscriptionId: sub.id,
+ frequency,
+ })
+ .pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe(() => this.toastService.showSuccess('moderation.notificationPreferences.successUpdate'));
+ }
+}
diff --git a/src/app/features/moderation/preprint-moderation.routes.ts b/src/app/features/moderation/preprint-moderation.routes.ts
index be607da8e..943723162 100644
--- a/src/app/features/moderation/preprint-moderation.routes.ts
+++ b/src/app/features/moderation/preprint-moderation.routes.ts
@@ -2,10 +2,11 @@ import { provideStates } from '@ngxs/store';
import { Routes } from '@angular/router';
-import { ResourceType } from '@osf/shared/enums/resource-type.enum';
+import { CurrentResourceType, ResourceType } from '@osf/shared/enums/resource-type.enum';
import { ModeratorsState } from './store/moderators';
import { PreprintModerationState } from './store/preprint-moderation';
+import { ProviderSubscriptionsState } from './store/provider-subscriptions';
import { PreprintModerationTab } from './enums';
export const preprintModerationRoutes: Routes = [
@@ -51,7 +52,8 @@ export const preprintModerationRoutes: Routes = [
import('./components/notification-settings/notification-settings.component').then(
(m) => m.NotificationSettingsComponent
),
- data: { tab: PreprintModerationTab.Notifications },
+ data: { tab: PreprintModerationTab.Notifications, resourceType: CurrentResourceType.Preprints },
+ providers: [provideStates([ProviderSubscriptionsState])],
},
{
path: 'settings',
diff --git a/src/app/features/moderation/registry-moderation.routes.ts b/src/app/features/moderation/registry-moderation.routes.ts
index 7afada056..2cebabc30 100644
--- a/src/app/features/moderation/registry-moderation.routes.ts
+++ b/src/app/features/moderation/registry-moderation.routes.ts
@@ -2,9 +2,10 @@ import { provideStates } from '@ngxs/store';
import { Routes } from '@angular/router';
-import { ResourceType } from '@osf/shared/enums/resource-type.enum';
+import { CurrentResourceType, ResourceType } from '@osf/shared/enums/resource-type.enum';
import { ModeratorsState } from './store/moderators';
+import { ProviderSubscriptionsState } from './store/provider-subscriptions';
import { RegistryModerationState } from './store/registry-moderation';
import { RegistryModerationTab } from './enums';
@@ -48,8 +49,11 @@ export const registryModerationRoutes: Routes = [
{
path: 'settings',
loadComponent: () =>
- import('./components/registry-settings/registry-settings.component').then((m) => m.RegistrySettingsComponent),
- data: { tab: RegistryModerationTab.Settings },
+ import('./components/notification-settings/notification-settings.component').then(
+ (m) => m.NotificationSettingsComponent
+ ),
+ data: { tab: RegistryModerationTab.Settings, resourceType: CurrentResourceType.Registrations },
+ providers: [provideStates([ProviderSubscriptionsState])],
},
],
},
diff --git a/src/app/features/moderation/services/index.ts b/src/app/features/moderation/services/index.ts
index 9ef953442..6a70accb5 100644
--- a/src/app/features/moderation/services/index.ts
+++ b/src/app/features/moderation/services/index.ts
@@ -1,3 +1,4 @@
export { ModeratorsService } from './moderators.service';
export { PreprintModerationService } from './preprint-moderation.service';
+export { ProviderSubscriptionService } from './provider-subscription.service';
export { RegistryModerationService } from './registry-moderation.service';
diff --git a/src/app/features/moderation/services/provider-subscription.service.ts b/src/app/features/moderation/services/provider-subscription.service.ts
new file mode 100644
index 000000000..21277c50b
--- /dev/null
+++ b/src/app/features/moderation/services/provider-subscription.service.ts
@@ -0,0 +1,56 @@
+import { map, Observable } from 'rxjs';
+
+import { inject, Injectable } from '@angular/core';
+
+import { ENVIRONMENT } from '@core/provider/environment.provider';
+import { SubscriptionFrequency } from '@osf/shared/enums/subscriptions/subscription-frequency.enum';
+import { SubscriptionType } from '@osf/shared/enums/subscriptions/subscription-type.enum';
+import { NotificationSubscriptionMapper } from '@osf/shared/mappers/notification-subscription.mapper';
+import { JsonApiResponse } from '@osf/shared/models/common/json-api.model';
+import { NotificationSubscription } from '@osf/shared/models/notifications/notification-subscription.model';
+import { NotificationSubscriptionGetResponseJsonApi } from '@osf/shared/models/notifications/notification-subscription-json-api.model';
+import { JsonApiService } from '@osf/shared/services/json-api.service';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class ProviderSubscriptionService {
+ private readonly jsonApiService = inject(JsonApiService);
+ private readonly environment = inject(ENVIRONMENT);
+
+ private providerUrl(providerType: string, providerId: string): string {
+ return `${this.environment.apiDomainUrl}/v2/providers/${providerType}/${providerId}/subscriptions/`;
+ }
+
+ getProviderSubscriptions(providerType: string, providerId: string): Observable {
+ return this.jsonApiService
+ .get<
+ JsonApiResponse
+ >(this.providerUrl(providerType, providerId))
+ .pipe(
+ map((responses) => responses.data.map((response) => NotificationSubscriptionMapper.fromGetResponse(response)))
+ );
+ }
+
+ updateProviderSubscription(
+ providerType: string,
+ providerId: string,
+ subscriptionId: string,
+ frequency: SubscriptionFrequency
+ ): Observable {
+ const request = {
+ data: {
+ id: subscriptionId,
+ type: SubscriptionType.Node,
+ attributes: { frequency },
+ },
+ };
+
+ return this.jsonApiService
+ .patch(
+ `${this.providerUrl(providerType, providerId)}${subscriptionId}/`,
+ request
+ )
+ .pipe(map((response) => NotificationSubscriptionMapper.fromGetResponse(response)));
+ }
+}
diff --git a/src/app/features/moderation/store/provider-subscriptions/index.ts b/src/app/features/moderation/store/provider-subscriptions/index.ts
new file mode 100644
index 000000000..067ead0ef
--- /dev/null
+++ b/src/app/features/moderation/store/provider-subscriptions/index.ts
@@ -0,0 +1,4 @@
+export * from './provider-subscriptions.actions';
+export * from './provider-subscriptions.model';
+export * from './provider-subscriptions.selectors';
+export * from './provider-subscriptions.state';
diff --git a/src/app/features/moderation/store/provider-subscriptions/provider-subscriptions.actions.ts b/src/app/features/moderation/store/provider-subscriptions/provider-subscriptions.actions.ts
new file mode 100644
index 000000000..f286ba90f
--- /dev/null
+++ b/src/app/features/moderation/store/provider-subscriptions/provider-subscriptions.actions.ts
@@ -0,0 +1,23 @@
+import { SubscriptionFrequency } from '@osf/shared/enums/subscriptions/subscription-frequency.enum';
+
+export class GetProviderSubscriptions {
+ static readonly type = '[Provider Subscriptions] Get';
+
+ constructor(
+ public providerType: string,
+ public providerId: string
+ ) {}
+}
+
+export class UpdateProviderSubscription {
+ static readonly type = '[Provider Subscriptions] Update';
+
+ constructor(
+ public payload: {
+ providerType: string;
+ providerId: string;
+ subscriptionId: string;
+ frequency: SubscriptionFrequency;
+ }
+ ) {}
+}
diff --git a/src/app/features/moderation/store/provider-subscriptions/provider-subscriptions.model.ts b/src/app/features/moderation/store/provider-subscriptions/provider-subscriptions.model.ts
new file mode 100644
index 000000000..3e369a169
--- /dev/null
+++ b/src/app/features/moderation/store/provider-subscriptions/provider-subscriptions.model.ts
@@ -0,0 +1,14 @@
+import { NotificationSubscription } from '@osf/shared/models/notifications/notification-subscription.model';
+import { AsyncStateModel } from '@osf/shared/models/store/async-state.model';
+
+export interface ProviderSubscriptionsStateModel {
+ subscriptions: AsyncStateModel;
+}
+
+export const PROVIDER_SUBSCRIPTIONS_STATE_DEFAULTS: ProviderSubscriptionsStateModel = {
+ subscriptions: {
+ data: [],
+ isLoading: false,
+ error: '',
+ },
+};
diff --git a/src/app/features/moderation/store/provider-subscriptions/provider-subscriptions.selectors.ts b/src/app/features/moderation/store/provider-subscriptions/provider-subscriptions.selectors.ts
new file mode 100644
index 000000000..897122177
--- /dev/null
+++ b/src/app/features/moderation/store/provider-subscriptions/provider-subscriptions.selectors.ts
@@ -0,0 +1,18 @@
+import { Selector } from '@ngxs/store';
+
+import { NotificationSubscription } from '@osf/shared/models/notifications/notification-subscription.model';
+
+import { ProviderSubscriptionsStateModel } from './provider-subscriptions.model';
+import { ProviderSubscriptionsState } from './provider-subscriptions.state';
+
+export class ProviderSubscriptionsSelectors {
+ @Selector([ProviderSubscriptionsState])
+ static getSubscriptions(state: ProviderSubscriptionsStateModel): NotificationSubscription[] {
+ return state.subscriptions.data;
+ }
+
+ @Selector([ProviderSubscriptionsState])
+ static isLoading(state: ProviderSubscriptionsStateModel): boolean {
+ return state.subscriptions.isLoading;
+ }
+}
diff --git a/src/app/features/moderation/store/provider-subscriptions/provider-subscriptions.state.ts b/src/app/features/moderation/store/provider-subscriptions/provider-subscriptions.state.ts
new file mode 100644
index 000000000..1bacb2f53
--- /dev/null
+++ b/src/app/features/moderation/store/provider-subscriptions/provider-subscriptions.state.ts
@@ -0,0 +1,70 @@
+import { Action, State, StateContext } from '@ngxs/store';
+import { patch, updateItem } from '@ngxs/store/operators';
+
+import { catchError, tap } from 'rxjs';
+
+import { inject, Injectable } from '@angular/core';
+
+import { handleSectionError } from '@osf/shared/helpers/state-error.handler';
+import { NotificationSubscription } from '@osf/shared/models/notifications/notification-subscription.model';
+
+import { ProviderSubscriptionService } from '../../services';
+
+import { GetProviderSubscriptions, UpdateProviderSubscription } from './provider-subscriptions.actions';
+import { PROVIDER_SUBSCRIPTIONS_STATE_DEFAULTS, ProviderSubscriptionsStateModel } from './provider-subscriptions.model';
+
+@State({
+ name: 'providerSubscriptions',
+ defaults: PROVIDER_SUBSCRIPTIONS_STATE_DEFAULTS,
+})
+@Injectable()
+export class ProviderSubscriptionsState {
+ private readonly providerSubscriptionService = inject(ProviderSubscriptionService);
+
+ @Action(GetProviderSubscriptions)
+ getProviderSubscriptions(ctx: StateContext, action: GetProviderSubscriptions) {
+ ctx.setState(patch({ subscriptions: patch({ isLoading: true }) }));
+
+ return this.providerSubscriptionService.getProviderSubscriptions(action.providerType, action.providerId).pipe(
+ tap((subscriptions) => {
+ ctx.setState(
+ patch({
+ subscriptions: patch({
+ data: subscriptions,
+ isLoading: false,
+ }),
+ })
+ );
+ }),
+ catchError((error) => handleSectionError(ctx, 'subscriptions', error))
+ );
+ }
+
+ @Action(UpdateProviderSubscription)
+ updateProviderSubscription(ctx: StateContext, action: UpdateProviderSubscription) {
+ return this.providerSubscriptionService
+ .updateProviderSubscription(
+ action.payload.providerType,
+ action.payload.providerId,
+ action.payload.subscriptionId,
+ action.payload.frequency
+ )
+ .pipe(
+ tap((updatedSubscription) => {
+ ctx.setState(
+ patch({
+ subscriptions: patch({
+ data: updateItem(
+ (sub) => sub.id === action.payload.subscriptionId,
+ updatedSubscription
+ ),
+ error: null,
+ isLoading: false,
+ }),
+ })
+ );
+ }),
+ catchError((error) => handleSectionError(ctx, 'subscriptions', error))
+ );
+ }
+}
diff --git a/src/app/shared/constants/subscription-options.const.ts b/src/app/shared/constants/subscription-options.const.ts
new file mode 100644
index 000000000..b36694531
--- /dev/null
+++ b/src/app/shared/constants/subscription-options.const.ts
@@ -0,0 +1,7 @@
+import { SubscriptionFrequency } from '../enums/subscriptions/subscription-frequency.enum';
+
+export const SUBSCRIPTION_FREQUENCY_OPTIONS = [
+ { label: 'settings.notifications.frequency.never', value: SubscriptionFrequency.Never },
+ { label: 'settings.notifications.frequency.daily', value: SubscriptionFrequency.Daily },
+ { label: 'settings.notifications.frequency.instant', value: SubscriptionFrequency.Instant },
+];
diff --git a/src/app/shared/enums/subscriptions/subscription-event.enum.ts b/src/app/shared/enums/subscriptions/subscription-event.enum.ts
index 3ce040bfe..e5732ac55 100644
--- a/src/app/shared/enums/subscriptions/subscription-event.enum.ts
+++ b/src/app/shared/enums/subscriptions/subscription-event.enum.ts
@@ -2,4 +2,6 @@ export enum SubscriptionEvent {
GlobalFileUpdated = 'global_file_updated',
GlobalReviews = 'global_reviews',
FileUpdated = 'file_updated',
+ ProviderNewPendingSubmissions = 'provider_new_pending_submissions',
+ ProviderNewPendingWithdrawRequests = 'provider_new_pending_withdraw_requests',
}
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index e31380f1c..67594ed43 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -1567,6 +1567,15 @@
"titleZA": "Title: Z-A",
"oldest": "Date: oldest to newest",
"newest": "Date: newest to oldest"
+ },
+ "notificationPreferences": {
+ "title": "Configure reviews notification preferences",
+ "note": "To configure other notification preferences visit your",
+ "items": {
+ "provider_new_pending_submissions": "New pending submissions",
+ "provider_new_pending_withdraw_requests": "New pending withdraw requests"
+ },
+ "successUpdate": "Notification preference updated successfully"
}
},
"settings": {
@@ -1702,6 +1711,11 @@
"preprints": "Preprint submissions updated"
},
"successUpdate": "Notification preferences successfully updated."
+ },
+ "frequency": {
+ "daily": "Daily",
+ "instant": "Instant",
+ "never": "Never"
}
},
"addons": {