Tags (optional)
diff --git a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts
index 2d3cfd76e..d9266fb8b 100644
--- a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts
+++ b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts
@@ -12,6 +12,7 @@ import { Tooltip } from 'primeng/tooltip';
import { ChangeDetectionStrategy, Component, HostListener, OnInit, output } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
+import { PreprintsSubjectsComponent } from '@osf/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component';
import { formInputLimits } from '@osf/features/preprints/constants';
import { MetadataForm, Preprint } from '@osf/features/preprints/models';
import {
@@ -44,6 +45,7 @@ import { ContributorsComponent } from './contributors/contributors.component';
Tooltip,
LicenseComponent,
TagsInputComponent,
+ PreprintsSubjectsComponent,
],
templateUrl: './metadata-step.component.html',
styleUrl: './metadata-step.component.scss',
diff --git a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html
new file mode 100644
index 000000000..4b926a1c0
--- /dev/null
+++ b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html
@@ -0,0 +1,7 @@
+
diff --git a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.scss b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.spec.ts b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.spec.ts
new file mode 100644
index 000000000..dfc8c1641
--- /dev/null
+++ b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PreprintsSubjectsComponent } from './preprints-subjects.component';
+
+describe('RegistriesSubjectsComponent', () => {
+ let component: PreprintsSubjectsComponent;
+ let fixture: ComponentFixture
;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [PreprintsSubjectsComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(PreprintsSubjectsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts
new file mode 100644
index 000000000..23a7556d2
--- /dev/null
+++ b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts
@@ -0,0 +1,49 @@
+import { createDispatchMap, select } from '@ngxs/store';
+
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+
+import {
+ FetchPreprintsSubjects,
+ SubmitPreprintSelectors,
+ UpdatePreprintsSubjects,
+} from '@osf/features/preprints/store/submit-preprint';
+import { SubjectsComponent } from '@osf/shared/components';
+import { Subject } from '@osf/shared/models';
+import { FetchChildrenSubjects, FetchSubjects } from '@osf/shared/stores';
+
+@Component({
+ selector: 'osf-preprints-subjects',
+ imports: [SubjectsComponent],
+ templateUrl: './preprints-subjects.component.html',
+ styleUrl: './preprints-subjects.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class PreprintsSubjectsComponent implements OnInit {
+ private readonly selectedProviderId = select(SubmitPreprintSelectors.getSelectedProviderId);
+ protected selectedSubjects = select(SubmitPreprintSelectors.getSelectedSubjects);
+ protected isSubjectsUpdating = select(SubmitPreprintSelectors.isSubjectsUpdating);
+
+ protected actions = createDispatchMap({
+ fetchSubjects: FetchSubjects,
+ fetchPreprintsSubjects: FetchPreprintsSubjects,
+ fetchChildrenSubjects: FetchChildrenSubjects,
+ updatePreprintsSubjects: UpdatePreprintsSubjects,
+ });
+
+ ngOnInit(): void {
+ this.actions.fetchSubjects(this.selectedProviderId()!);
+ this.actions.fetchPreprintsSubjects();
+ }
+
+ getSubjectChildren(parentId: string) {
+ this.actions.fetchChildrenSubjects(parentId);
+ }
+
+ searchSubjects(search: string) {
+ this.actions.fetchSubjects(search);
+ }
+
+ updateSelectedSubjects(subjects: Subject[]) {
+ this.actions.updatePreprintsSubjects(subjects);
+ }
+}
diff --git a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html
new file mode 100644
index 000000000..b942bf210
--- /dev/null
+++ b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html
@@ -0,0 +1,84 @@
+Supplements (optional)
+
+
+ Connect an OSF project to share data, code, protocols, or other supplemental materials.
+
+
+@let createdPreprintNodeId = createdPreprint()?.nodeId;
+@if (!createdPreprintNodeId) {
+
+
+ @if (selectedSupplementOption() === SupplementOptions.ConnectExistingProject) {
+
+
+
This will make your project public, if it is not already
+
The projects and components for which you have admin access are listed below.
+
+
+
+
+ } @else if (selectedSupplementOption() === SupplementOptions.CreateNewProject) {
+
+
+
+ }
+} @else {
+
+ @if (isPreprintProjectLoading()) {
+
+ } @else {
+
+ {{ preprintProject()?.name }}
+
+
+
+ }
+
+}
+
+
diff --git a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.scss b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.scss
new file mode 100644
index 000000000..16b791219
--- /dev/null
+++ b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.scss
@@ -0,0 +1,21 @@
+@use "assets/styles/mixins" as mix;
+
+.supplement-option-button {
+ --p-button-secondary-border-color: var(--grey-2);
+ --p-button-secondary-background: transparent;
+ --p-button-secondary-hover-background: var(--bg-blue-3);
+ --p-button-padding-y: 0.75rem;
+ --p-button-secondary-color: var(--dark-blue-1);
+ --p-button-secondary-hover-color: var(--dark-blue-1);
+
+ &.active {
+ --p-button-secondary-background: var(--bg-blue-3);
+ }
+}
+
+.selected-project {
+ @include mix.flex-center-between;
+ padding: mix.rem(6px) mix.rem(12px);
+ border-bottom: 1px solid var(--grey-2);
+ border-top: 1px solid var(--grey-2);
+}
diff --git a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.spec.ts b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.spec.ts
new file mode 100644
index 000000000..2cc79b093
--- /dev/null
+++ b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SupplementsStepComponent } from './supplements-step.component';
+
+describe('SupplementsStepComponent', () => {
+ let component: SupplementsStepComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [SupplementsStepComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(SupplementsStepComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts
new file mode 100644
index 000000000..4d820d84a
--- /dev/null
+++ b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts
@@ -0,0 +1,224 @@
+import { createDispatchMap, select } from '@ngxs/store';
+
+import { Button } from 'primeng/button';
+import { Card } from 'primeng/card';
+import { Select, SelectChangeEvent } from 'primeng/select';
+import { Skeleton } from 'primeng/skeleton';
+
+import { debounceTime, distinctUntilChanged, map } from 'rxjs';
+
+import { NgClass, TitleCasePipe } from '@angular/common';
+import {
+ ChangeDetectionStrategy,
+ Component,
+ computed,
+ DestroyRef,
+ effect,
+ HostListener,
+ inject,
+ OnInit,
+ output,
+ signal,
+ untracked,
+} from '@angular/core';
+import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
+import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
+
+import { StringOrNull } from '@core/helpers';
+import { SupplementOptions } from '@osf/features/preprints/enums';
+import {
+ ConnectProject,
+ CreateNewProject,
+ DisconnectProject,
+ FetchPreprintProject,
+ GetAvailableProjects,
+ SubmitPreprintSelectors,
+} from '@osf/features/preprints/store/submit-preprint';
+import { AddProjectFormComponent } from '@shared/components';
+import { ProjectFormControls } from '@shared/enums';
+import { ProjectForm } from '@shared/models';
+import { CustomConfirmationService, ToastService } from '@shared/services';
+import { CustomValidators } from '@shared/utils';
+
+@Component({
+ selector: 'osf-supplements-step',
+ imports: [Button, TitleCasePipe, NgClass, Card, Select, AddProjectFormComponent, ReactiveFormsModule, Skeleton],
+ templateUrl: './supplements-step.component.html',
+ styleUrl: './supplements-step.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class SupplementsStepComponent implements OnInit {
+ private customConfirmationService = inject(CustomConfirmationService);
+ private readonly toastService = inject(ToastService);
+ private actions = createDispatchMap({
+ getAvailableProjects: GetAvailableProjects,
+ connectProject: ConnectProject,
+ disconnectProject: DisconnectProject,
+ fetchPreprintProject: FetchPreprintProject,
+ createNewProject: CreateNewProject,
+ });
+ private destroyRef = inject(DestroyRef);
+
+ readonly SupplementOptions = SupplementOptions;
+
+ createdPreprint = select(SubmitPreprintSelectors.getCreatedPreprint);
+ isPreprintSubmitting = select(SubmitPreprintSelectors.isPreprintSubmitting);
+ availableProjects = select(SubmitPreprintSelectors.getAvailableProjects);
+ areAvailableProjectsLoading = select(SubmitPreprintSelectors.areAvailableProjectsLoading);
+ preprintProject = select(SubmitPreprintSelectors.getPreprintProject);
+ isPreprintProjectLoading = select(SubmitPreprintSelectors.isPreprintProjectLoading);
+
+ selectedSupplementOption = signal(SupplementOptions.None);
+ selectedProjectId = signal(null);
+
+ readonly projectNameControl = new FormControl(null);
+ readonly createProjectForm = new FormGroup({
+ [ProjectFormControls.Title]: new FormControl('', {
+ nonNullable: true,
+ validators: [CustomValidators.requiredTrimmed()],
+ }),
+ [ProjectFormControls.StorageLocation]: new FormControl('', {
+ nonNullable: true,
+ validators: [Validators.required],
+ }),
+ [ProjectFormControls.Affiliations]: new FormControl([], {
+ nonNullable: true,
+ }),
+ [ProjectFormControls.Description]: new FormControl('', {
+ nonNullable: true,
+ }),
+ [ProjectFormControls.Template]: new FormControl('', {
+ nonNullable: true,
+ }),
+ });
+
+ createProjectFormValid = toSignal(this.createProjectForm.statusChanges.pipe(map((status) => status === 'VALID')), {
+ initialValue: this.createProjectForm.valid,
+ });
+
+ isNextButtonDisabled = computed(() => {
+ if (this.createdPreprint()?.nodeId) {
+ return false;
+ }
+
+ switch (this.selectedSupplementOption()) {
+ case SupplementOptions.None:
+ return true;
+ case SupplementOptions.ConnectExistingProject:
+ return !this.createdPreprint()?.nodeId;
+ case SupplementOptions.CreateNewProject:
+ return !this.createProjectFormValid();
+ default:
+ return false;
+ }
+ });
+
+ constructor() {
+ effect(() => {
+ const preprint = this.createdPreprint();
+ if (!preprint?.nodeId) {
+ return;
+ }
+
+ untracked(() => {
+ const preprintProject = this.preprintProject();
+ if (preprint.nodeId === preprintProject?.id) {
+ return;
+ }
+ });
+
+ this.actions.fetchPreprintProject();
+ });
+ }
+
+ nextClicked = output();
+
+ ngOnInit() {
+ this.projectNameControl.valueChanges
+ .pipe(debounceTime(500), distinctUntilChanged(), takeUntilDestroyed(this.destroyRef))
+ .subscribe((projectNameOrId) => {
+ if (this.selectedProjectId() === projectNameOrId) {
+ return;
+ }
+
+ this.actions.getAvailableProjects(projectNameOrId);
+ });
+ }
+
+ selectSupplementOption(supplementOption: SupplementOptions) {
+ this.selectedSupplementOption.set(supplementOption);
+
+ if (supplementOption === SupplementOptions.CreateNewProject) {
+ this.createProjectForm.reset();
+ }
+
+ this.actions.getAvailableProjects(null);
+ }
+
+ selectProject(event: SelectChangeEvent) {
+ if (!(event.originalEvent instanceof PointerEvent)) {
+ return;
+ }
+ this.selectedProjectId.set(event.value);
+
+ this.actions.connectProject(event.value).subscribe({
+ complete: () => {
+ this.toastService.showSuccess('Project connected successfully');
+ },
+ });
+ }
+
+ disconnectProject() {
+ this.customConfirmationService.confirmDelete({
+ headerKey: 'Disconnect supplemental material',
+ messageKey:
+ 'This will disconnect the selected project. You can select new supplemental material or re-add the same supplemental material at a later date.',
+ onConfirm: () => {
+ this.actions.disconnectProject().subscribe({
+ complete: () => {
+ this.selectedProjectId.set(null);
+ this.toastService.showSuccess('Project disconnected successfully');
+ },
+ });
+ },
+ });
+ }
+
+ submitCreateProjectForm() {
+ if (this.createProjectForm.invalid) {
+ return;
+ }
+
+ const rawData = this.createProjectForm.getRawValue();
+
+ this.actions
+ .createNewProject(
+ rawData.title,
+ rawData.description,
+ rawData.template,
+ rawData.storageLocation,
+ rawData.affiliations
+ )
+ .subscribe({
+ complete: () => {
+ this.toastService.showSuccess('Project created successfully');
+ this.nextClicked.emit();
+ },
+ });
+ }
+
+ nextButtonClicked() {
+ if (this.selectedSupplementOption() === SupplementOptions.CreateNewProject) {
+ this.submitCreateProjectForm();
+ return;
+ }
+
+ this.nextClicked.emit();
+ }
+
+ @HostListener('window:beforeunload', ['$event'])
+ public onBeforeUnload($event: BeforeUnloadEvent): boolean {
+ $event.preventDefault();
+ return false;
+ }
+}
diff --git a/src/app/features/preprints/enums/index.ts b/src/app/features/preprints/enums/index.ts
index bf1bf51c4..6e7a24296 100644
--- a/src/app/features/preprints/enums/index.ts
+++ b/src/app/features/preprints/enums/index.ts
@@ -2,3 +2,4 @@ export { ApplicabilityStatus } from './applicability-status.enum';
export { PreprintFileSource } from './preprint-file-source.enum';
export { PreregLinkInfo } from './prereg-link-info.enum';
export { SubmitSteps } from './submit-steps.enum';
+export { SupplementOptions } from './supplement-options.enum';
diff --git a/src/app/features/preprints/enums/supplement-options.enum.ts b/src/app/features/preprints/enums/supplement-options.enum.ts
new file mode 100644
index 000000000..0b3e50a73
--- /dev/null
+++ b/src/app/features/preprints/enums/supplement-options.enum.ts
@@ -0,0 +1,5 @@
+export enum SupplementOptions {
+ None,
+ ConnectExistingProject,
+ CreateNewProject,
+}
diff --git a/src/app/features/preprints/mappers/preprints.mapper.ts b/src/app/features/preprints/mappers/preprints.mapper.ts
index 2661e1388..5add26b6d 100644
--- a/src/app/features/preprints/mappers/preprints.mapper.ts
+++ b/src/app/features/preprints/mappers/preprints.mapper.ts
@@ -37,7 +37,8 @@ export class PreprintsMapper {
isPublic: response.attributes.public,
version: response.attributes.version,
isLatestVersion: response.attributes.is_latest_version,
- primaryFileId: response.relationships.primary_file?.links?.related?.href || null,
+ primaryFileId: response.relationships.primary_file?.data?.id || null,
+ nodeId: response.relationships.node?.data?.id,
licenseId: response.relationships.license?.data?.id || null,
licenseOptions: response.attributes.license_record
? {
diff --git a/src/app/features/preprints/models/preprint-json-api.models.ts b/src/app/features/preprints/models/preprint-json-api.models.ts
index 44ae1cd64..f09523304 100644
--- a/src/app/features/preprints/models/preprint-json-api.models.ts
+++ b/src/app/features/preprints/models/preprint-json-api.models.ts
@@ -36,10 +36,9 @@ export interface PreprintJsonApi {
export interface PreprintsRelationshipsJsonApi {
primary_file: {
- links: {
- related: {
- href: string;
- };
+ data: {
+ id: string;
+ type: 'files';
};
};
license: {
@@ -48,4 +47,10 @@ export interface PreprintsRelationshipsJsonApi {
type: 'licenses';
};
};
+ node: {
+ data: {
+ id: string;
+ type: 'nodes';
+ };
+ };
}
diff --git a/src/app/features/preprints/models/preprint.models.ts b/src/app/features/preprints/models/preprint.models.ts
index 7f797cd77..d791c2d23 100644
--- a/src/app/features/preprints/models/preprint.models.ts
+++ b/src/app/features/preprints/models/preprint.models.ts
@@ -16,6 +16,7 @@ export interface Preprint {
isPublic: boolean;
version: number;
isLatestVersion: boolean;
+ nodeId: StringOrNull;
primaryFileId: StringOrNull;
licenseId: StringOrNull;
licenseOptions: LicenseOptions | null;
diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html
index 5a0babd6c..10e735f39 100644
--- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html
+++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html
@@ -45,6 +45,9 @@ {{ 'Add a ' + preprintProvider()!.preprintWor
@case (SubmitStepsEnum.AuthorAssertions) {
}
+ @case (SubmitStepsEnum.Supplements) {
+
+ }
@default {
No such step
}
diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts
index 0aee68d95..fcb81928b 100644
--- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts
+++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts
@@ -18,11 +18,12 @@ import { toSignal } from '@angular/core/rxjs-interop';
import { ActivatedRoute } from '@angular/router';
import {
+ AuthorAssertionsStepComponent,
FileStepComponent,
MetadataStepComponent,
+ SupplementsStepComponent,
TitleAndAbstractStepComponent,
} from '@osf/features/preprints/components';
-import { AuthorAssertionsStepComponent } from '@osf/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component';
import { submitPreprintSteps } from '@osf/features/preprints/constants';
import { SubmitSteps } from '@osf/features/preprints/enums';
import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers';
@@ -44,6 +45,8 @@ import { BrowserTabHelper, HeaderStyleHelper, IS_WEB } from '@shared/utils';
FileStepComponent,
MetadataStepComponent,
AuthorAssertionsStepComponent,
+ SupplementsStepComponent,
+ AuthorAssertionsStepComponent,
],
templateUrl: './submit-preprint-stepper.component.html',
styleUrl: './submit-preprint-stepper.component.scss',
diff --git a/src/app/features/preprints/preprints.routes.ts b/src/app/features/preprints/preprints.routes.ts
index 3bb1126f2..f2caae2b0 100644
--- a/src/app/features/preprints/preprints.routes.ts
+++ b/src/app/features/preprints/preprints.routes.ts
@@ -3,12 +3,14 @@ import { provideStates } from '@ngxs/store';
import { Routes } from '@angular/router';
import { PreprintsComponent } from '@osf/features/preprints/preprints.component';
+import { PreprintSubjectsService } from '@osf/features/preprints/services';
import { PreprintProvidersState } from '@osf/features/preprints/store/preprint-providers';
import { PreprintsDiscoverState } from '@osf/features/preprints/store/preprints-discover';
import { PreprintsResourcesFiltersState } from '@osf/features/preprints/store/preprints-resources-filters';
import { PreprintsResourcesFiltersOptionsState } from '@osf/features/preprints/store/preprints-resources-filters-options';
import { SubmitPreprintState } from '@osf/features/preprints/store/submit-preprint';
-import { ContributorsState } from '@osf/shared/stores';
+import { ContributorsState, SubjectsState } from '@shared/stores';
+import { SUBJECTS_SERVICE } from '@shared/tokens/subjects.token';
export const preprintsRoutes: Routes = [
{
@@ -22,7 +24,12 @@ export const preprintsRoutes: Routes = [
PreprintsResourcesFiltersOptionsState,
SubmitPreprintState,
ContributorsState,
+ SubjectsState,
]),
+ {
+ provide: SUBJECTS_SERVICE,
+ useClass: PreprintSubjectsService,
+ },
],
children: [
{
diff --git a/src/app/features/preprints/services/index.ts b/src/app/features/preprints/services/index.ts
index 04a25545e..8d103600e 100644
--- a/src/app/features/preprints/services/index.ts
+++ b/src/app/features/preprints/services/index.ts
@@ -1,5 +1,7 @@
export { PreprintFilesService } from './preprint-files.service';
export { PreprintLicensesService } from './preprint-licenses.service';
export { PreprintProvidersService } from './preprint-providers.service';
+export { PreprintSubjectsService } from './preprint-subjects.service';
export { PreprintsService } from './preprints.service';
+export { PreprintsProjectsService } from './preprints-projects.service';
export { PreprintsFiltersOptionsService } from './preprints-resource-filters.service';
diff --git a/src/app/features/preprints/services/preprint-files.service.ts b/src/app/features/preprints/services/preprint-files.service.ts
index 141d255b8..5a8effc95 100644
--- a/src/app/features/preprints/services/preprint-files.service.ts
+++ b/src/app/features/preprints/services/preprint-files.service.ts
@@ -2,7 +2,6 @@ import { map, Observable, switchMap } from 'rxjs';
import { inject, Injectable } from '@angular/core';
-import { Primitive, StringOrNull } from '@core/helpers';
import { JsonApiService } from '@core/services';
import { ApiData, JsonApiResponse } from '@osf/core/models';
import { PreprintsMapper } from '@osf/features/preprints/mappers';
@@ -12,7 +11,7 @@ import {
PreprintJsonApi,
PreprintsRelationshipsJsonApi,
} from '@osf/features/preprints/models';
-import { GetFileResponse, GetFilesResponse, IdName, NodeData, OsfFile } from '@osf/shared/models';
+import { GetFileResponse, GetFilesResponse, OsfFile } from '@osf/shared/models';
import { FilesService } from '@shared/services';
import { environment } from 'src/environments/environment';
@@ -61,25 +60,6 @@ export class PreprintFilesService {
);
}
- getAvailableProjects(searchTerm: StringOrNull): Observable {
- const params: Record = {};
- params['page'] = 1;
- if (searchTerm) {
- params['filter[title]'] = searchTerm;
- }
-
- return this.jsonApiService
- .get>(`${environment.apiUrl}/users/me/nodes/`, params)
- .pipe(
- map((response) => {
- return response.data.map((item) => ({
- id: item.id,
- name: item.attributes.title,
- }));
- })
- );
- }
-
getProjectFiles(projectId: string): Observable {
return this.jsonApiService.get(`${environment.apiUrl}/nodes/${projectId}/files/`).pipe(
switchMap((response: GetFilesResponse) => {
diff --git a/src/app/features/preprints/services/preprint-subjects.service.ts b/src/app/features/preprints/services/preprint-subjects.service.ts
new file mode 100644
index 000000000..d41c3421a
--- /dev/null
+++ b/src/app/features/preprints/services/preprint-subjects.service.ts
@@ -0,0 +1,78 @@
+import { map, Observable } from 'rxjs';
+
+import { inject, Injectable } from '@angular/core';
+
+import { JsonApiService } from '@osf/core/services';
+import { SubjectMapper } from '@osf/shared/mappers';
+import { ISubjectsService, Subject, SubjectsResponseJsonApi } from '@osf/shared/models';
+
+import { environment } from 'src/environments/environment';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class PreprintSubjectsService implements ISubjectsService {
+ private apiUrl = environment.apiUrl;
+ private readonly jsonApiService = inject(JsonApiService);
+
+ getSubjects(providerId: string, search?: string): Observable {
+ const params: Record = {
+ 'page[size]': '100',
+ sort: 'text',
+ related_counts: 'children',
+ 'filter[parent]': 'null',
+ };
+ if (search) {
+ delete params['filter[parent]'];
+ params['filter[text]'] = search;
+ params['embed'] = 'parent';
+ }
+ return this.jsonApiService
+ .get(`${this.apiUrl}/providers/preprints/${providerId}/subjects/`, params)
+ .pipe(
+ map((response) => {
+ return SubjectMapper.fromSubjectsResponseJsonApi(response);
+ })
+ );
+ }
+
+ getChildrenSubjects(parentId: string): Observable {
+ const params: Record = {
+ 'page[size]': '100',
+ page: '1',
+ sort: 'text',
+ related_counts: 'children',
+ };
+
+ return this.jsonApiService
+ .get(`${this.apiUrl}/subjects/${parentId}/children/`, params)
+ .pipe(
+ map((response) => {
+ return SubjectMapper.fromSubjectsResponseJsonApi(response);
+ })
+ );
+ }
+
+ getPreprintSubjects(preprintId: string): Observable {
+ const params: Record = {
+ 'page[size]': '100',
+ page: '1',
+ };
+
+ return this.jsonApiService
+ .get(`${this.apiUrl}/preprints/${preprintId}/subjects/`, params)
+ .pipe(
+ map((response) => {
+ return SubjectMapper.fromSubjectsResponseJsonApi(response);
+ })
+ );
+ }
+
+ updatePreprintSubjects(preprintId: string, subjects: Subject[]): Observable {
+ const payload = {
+ data: subjects.map((item) => ({ id: item.id, type: 'subjects' })),
+ };
+
+ return this.jsonApiService.put(`${this.apiUrl}/preprints/${preprintId}/relationships/subjects/`, payload);
+ }
+}
diff --git a/src/app/features/preprints/services/preprints-projects.service.ts b/src/app/features/preprints/services/preprints-projects.service.ts
new file mode 100644
index 000000000..4c47f580f
--- /dev/null
+++ b/src/app/features/preprints/services/preprints-projects.service.ts
@@ -0,0 +1,123 @@
+import { map, Observable } from 'rxjs';
+
+import { inject, Injectable } from '@angular/core';
+
+import { Primitive, StringOrNull } from '@core/helpers';
+import { JsonApiService } from '@core/services';
+import { ApiData, JsonApiResponse } from '@osf/core/models';
+import { PreprintsMapper } from '@osf/features/preprints/mappers';
+import { Preprint, PreprintJsonApi, PreprintsRelationshipsJsonApi } from '@osf/features/preprints/models';
+import { CreateProjectPayloadJsoApi, IdName, NodeData } from '@osf/shared/models';
+
+import { environment } from 'src/environments/environment';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class PreprintsProjectsService {
+ private jsonApiService = inject(JsonApiService);
+
+ getAvailableProjects(searchTerm: StringOrNull): Observable {
+ const params: Record = {};
+ params['page'] = 1;
+ if (searchTerm) {
+ params['filter[title]'] = searchTerm;
+ }
+
+ return this.jsonApiService
+ .get>(`${environment.apiUrl}/users/me/nodes/`, params)
+ .pipe(
+ map((response) => {
+ return response.data.map((item) => ({
+ id: item.id,
+ name: item.attributes.title,
+ }));
+ })
+ );
+ }
+
+ getProjectById(projectId: string): Observable {
+ return this.jsonApiService.get>(`${environment.apiUrl}/nodes/${projectId}/`).pipe(
+ map((response) => {
+ return {
+ id: response.data.id,
+ name: response.data.attributes.title,
+ };
+ })
+ );
+ }
+
+ removePreprintProjectRelationship(preprintId: string) {
+ return this.jsonApiService.patch(`${environment.apiUrl}/preprints/${preprintId}/relationships/node/`, {
+ data: [],
+ });
+ }
+
+ updatePreprintProjectRelationship(preprintId: string, projectId: string): Observable {
+ return this.jsonApiService
+ .patch>(
+ `${environment.apiUrl}/preprints/${preprintId}/`,
+ {
+ data: {
+ type: 'preprints',
+ id: preprintId,
+ attributes: {},
+ relationships: {
+ node: {
+ data: {
+ type: 'nodes',
+ id: projectId,
+ },
+ },
+ },
+ },
+ }
+ )
+ .pipe(map((response) => PreprintsMapper.fromPreprintJsonApi(response)));
+ }
+
+ createProject(
+ title: string,
+ description: string,
+ templateFrom: string,
+ regionId: string,
+ affiliationsId: string[]
+ ): Observable {
+ const payload: CreateProjectPayloadJsoApi = {
+ data: {
+ type: 'nodes',
+ attributes: {
+ title,
+ ...(description && { description }),
+ category: 'project',
+ ...(templateFrom && { template_from: templateFrom }),
+ },
+ relationships: {
+ region: {
+ data: {
+ type: 'regions',
+ id: regionId,
+ },
+ },
+ ...(affiliationsId.length > 0 && {
+ affiliated_institutions: {
+ data: affiliationsId.map((id) => ({
+ type: 'institutions',
+ id,
+ })),
+ },
+ }),
+ },
+ },
+ };
+
+ return this.jsonApiService.post>(`${environment.apiUrl}/nodes/`, payload).pipe(
+ map((response) => {
+ return {
+ id: response.data.id,
+ name: response.data.attributes.title,
+ };
+ })
+ );
+ }
+}
diff --git a/src/app/features/preprints/store/preprints-discover/preprints-discover.state.ts b/src/app/features/preprints/store/preprints-discover/preprints-discover.state.ts
index 65167f668..b2731569b 100644
--- a/src/app/features/preprints/store/preprints-discover/preprints-discover.state.ts
+++ b/src/app/features/preprints/store/preprints-discover/preprints-discover.state.ts
@@ -13,7 +13,6 @@ import {
SetSortBy,
} from '@osf/features/preprints/store/preprints-discover/preprints-discover.actions';
import { PreprintsDiscoverStateModel } from '@osf/features/preprints/store/preprints-discover/preprints-discover.model';
-import { PreprintsDiscoverSelectors } from '@osf/features/preprints/store/preprints-discover/preprints-discover.selectors';
import { PreprintsResourcesFiltersSelectors } from '@osf/features/preprints/store/preprints-resources-filters';
import { ResourceFiltersStateModel } from '@osf/features/search/components/resource-filters/store';
import { GetResourcesRequestTypeEnum, ResourceTab } from '@shared/enums';
@@ -53,13 +52,11 @@ export class PreprintsDiscoverState implements NgxsOnInit {
if (query.type === GetResourcesRequestTypeEnum.GetResources) {
const filters = this.store.selectSnapshot(PreprintsResourcesFiltersSelectors.getAllFilters);
const filtersParams = addFiltersParams(filters as ResourceFiltersStateModel);
- const searchText = this.store.selectSnapshot(PreprintsDiscoverSelectors.getSearchText);
- const sortBy = this.store.selectSnapshot(PreprintsDiscoverSelectors.getSortBy);
+ const searchText = state.searchText;
+ const sortBy = state.sortBy;
const resourceTab = ResourceTab.Preprints;
const resourceTypes = getResourceTypes(resourceTab);
- filtersParams['cardSearchFilter[publisher][]'] = this.store.selectSnapshot(
- PreprintsDiscoverSelectors.getIri
- );
+ filtersParams['cardSearchFilter[publisher][]'] = state.providerIri;
return this.searchService.getResources(filtersParams, searchText, sortBy, resourceTypes).pipe(
tap((response) => {
@@ -97,8 +94,8 @@ export class PreprintsDiscoverState implements NgxsOnInit {
}
@Action(GetResources)
- getResources() {
- if (!this.store.selectSnapshot(PreprintsDiscoverSelectors.getIri)) {
+ getResources(ctx: StateContext) {
+ if (!ctx.getState().providerIri) {
return;
}
this.loadRequests.next({
diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts
index 51b493804..f379ce1bb 100644
--- a/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts
+++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts
@@ -1,7 +1,7 @@
import { StringOrNull } from '@core/helpers';
import { PreprintFileSource } from '@osf/features/preprints/enums';
import { Preprint } from '@osf/features/preprints/models';
-import { LicenseOptions, OsfFile } from '@shared/models';
+import { LicenseOptions, OsfFile, Subject } from '@shared/models';
export class SetSelectedPreprintProviderId {
static readonly type = '[Submit Preprint] Set Selected Preprint Provider Id';
@@ -91,6 +91,42 @@ export class SaveLicense {
) {}
}
+export class FetchPreprintsSubjects {
+ static readonly type = '[Submit Preprint] Fetch Registration Subjects';
+}
+
+export class UpdatePreprintsSubjects {
+ static readonly type = '[Submit Preprint] Update Registration Subject';
+
+ constructor(public subjects: Subject[]) {}
+}
+
+export class DisconnectProject {
+ static readonly type = '[Submit Preprint] Disconnect Preprint Project';
+}
+
+export class ConnectProject {
+ static readonly type = '[Submit Preprint] Connect Preprint Project';
+
+ constructor(public projectId: string) {}
+}
+
+export class FetchPreprintProject {
+ static readonly type = '[Submit Preprint] Fetch Preprint Project';
+}
+
+export class CreateNewProject {
+ static readonly type = '[Submit Preprint] Create Project';
+
+ constructor(
+ public title: string,
+ public description: string,
+ public templateFrom: string,
+ public regionId: string,
+ public affiliationsId: string[]
+ ) {}
+}
+
export class ResetStateAndDeletePreprint {
static readonly type = '[Submit Preprint] Reset State And Delete Preprint';
}
diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts
index 8ca3ff6cf..8d43c442c 100644
--- a/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts
+++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts
@@ -1,7 +1,7 @@
import { StringOrNull } from '@core/helpers';
import { PreprintFileSource } from '@osf/features/preprints/enums';
import { Preprint, PreprintFilesLinks } from '@osf/features/preprints/models';
-import { AsyncStateModel, IdName, OsfFile } from '@shared/models';
+import { AsyncStateModel, IdName, OsfFile, Subject } from '@shared/models';
import { License } from '@shared/models/license.model';
export interface SubmitPreprintStateModel {
@@ -13,4 +13,6 @@ export interface SubmitPreprintStateModel {
availableProjects: AsyncStateModel;
projectFiles: AsyncStateModel;
licenses: AsyncStateModel;
+ subjects: AsyncStateModel;
+ preprintProject: AsyncStateModel;
}
diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts
index 4f434547c..f26023140 100644
--- a/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts
+++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts
@@ -62,4 +62,24 @@ export class SubmitPreprintSelectors {
static getLicenses(state: SubmitPreprintStateModel) {
return state.licenses.data;
}
+
+ @Selector([SubmitPreprintState])
+ static getSelectedSubjects(state: SubmitPreprintStateModel) {
+ return state.subjects.data;
+ }
+
+ @Selector([SubmitPreprintState])
+ static isSubjectsUpdating(state: SubmitPreprintStateModel) {
+ return state.subjects.isLoading;
+ }
+
+ @Selector([SubmitPreprintState])
+ static getPreprintProject(state: SubmitPreprintStateModel) {
+ return state.preprintProject.data;
+ }
+
+ @Selector([SubmitPreprintState])
+ static isPreprintProjectLoading(state: SubmitPreprintStateModel) {
+ return state.preprintProject.isLoading;
+ }
}
diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts
index ac79fcf81..1cfd51b7c 100644
--- a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts
+++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts
@@ -1,22 +1,34 @@
import { Action, State, StateContext } from '@ngxs/store';
import { patch } from '@ngxs/store/operators';
-import { EMPTY, filter, switchMap, tap, throwError } from 'rxjs';
+import { EMPTY, filter, forkJoin, of, switchMap, tap, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { HttpEventType } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
+import { handleSectionError } from '@core/handlers';
import { PreprintFileSource } from '@osf/features/preprints/enums';
import { Preprint } from '@osf/features/preprints/models';
-import { PreprintFilesService, PreprintLicensesService, PreprintsService } from '@osf/features/preprints/services';
+import {
+ PreprintFilesService,
+ PreprintLicensesService,
+ PreprintsProjectsService,
+ PreprintsService,
+ PreprintSubjectsService,
+} from '@osf/features/preprints/services';
import { OsfFile } from '@shared/models';
import { FilesService } from '@shared/services';
import {
+ ConnectProject,
CopyFileFromProject,
+ CreateNewProject,
CreatePreprint,
+ DisconnectProject,
FetchLicenses,
+ FetchPreprintProject,
+ FetchPreprintsSubjects,
GetAvailableProjects,
GetPreprintFiles,
GetPreprintFilesLinks,
@@ -29,46 +41,59 @@ import {
SetSelectedPreprintProviderId,
SubmitPreprintStateModel,
UpdatePreprint,
+ UpdatePreprintsSubjects,
UploadFile,
} from './';
+const DefaultState: SubmitPreprintStateModel = {
+ selectedProviderId: null,
+ createdPreprint: {
+ data: null,
+ isLoading: false,
+ error: null,
+ isSubmitting: false,
+ },
+ fileSource: PreprintFileSource.None,
+ preprintFilesLinks: {
+ data: null,
+ isLoading: false,
+ error: null,
+ },
+ preprintFiles: {
+ data: [],
+ isLoading: false,
+ error: null,
+ },
+ availableProjects: {
+ data: [],
+ isLoading: false,
+ error: null,
+ },
+ projectFiles: {
+ data: [],
+ isLoading: false,
+ error: null,
+ },
+ licenses: {
+ data: [],
+ isLoading: false,
+ error: null,
+ },
+ subjects: {
+ data: [],
+ isLoading: false,
+ error: null,
+ },
+ preprintProject: {
+ data: null,
+ isLoading: false,
+ error: null,
+ },
+};
+
@State({
name: 'submitPreprint',
- defaults: {
- selectedProviderId: null,
- createdPreprint: {
- data: null,
- isLoading: false,
- error: null,
- isSubmitting: false,
- },
- fileSource: PreprintFileSource.None,
- preprintFilesLinks: {
- data: null,
- isLoading: false,
- error: null,
- },
- preprintFiles: {
- data: [],
- isLoading: false,
- error: null,
- },
- availableProjects: {
- data: [],
- isLoading: false,
- error: null,
- },
- projectFiles: {
- data: [],
- isLoading: false,
- error: null,
- },
- licenses: {
- data: [],
- isLoading: false,
- error: null,
- },
- },
+ defaults: { ...DefaultState },
})
@Injectable()
export class SubmitPreprintState {
@@ -76,6 +101,8 @@ export class SubmitPreprintState {
private preprintFilesService = inject(PreprintFilesService);
private fileService = inject(FilesService);
private licensesService = inject(PreprintLicensesService);
+ private subjectsService = inject(PreprintSubjectsService);
+ private preprintProjectsService = inject(PreprintsProjectsService);
@Action(SetSelectedPreprintProviderId)
setSelectedPreprintProviderId(ctx: StateContext, action: SetSelectedPreprintProviderId) {
@@ -200,7 +227,7 @@ export class SubmitPreprintState {
getAvailableProjects(ctx: StateContext, action: GetAvailableProjects) {
ctx.setState(patch({ availableProjects: patch({ isLoading: true }) }));
- return this.preprintFilesService.getAvailableProjects(action.searchTerm).pipe(
+ return this.preprintProjectsService.getAvailableProjects(action.searchTerm).pipe(
tap((projects) => {
ctx.setState(
patch({
@@ -266,41 +293,7 @@ export class SubmitPreprintState {
resetStateAndDeletePreprint(ctx: StateContext) {
const state = ctx.getState();
const createdPreprintId = state.createdPreprint.data?.id;
- ctx.setState({
- selectedProviderId: null,
- createdPreprint: {
- data: null,
- isLoading: false,
- error: null,
- isSubmitting: false,
- },
- fileSource: PreprintFileSource.None,
- preprintFilesLinks: {
- data: null,
- isLoading: false,
- error: null,
- },
- preprintFiles: {
- data: [],
- isLoading: false,
- error: null,
- },
- availableProjects: {
- data: [],
- isLoading: false,
- error: null,
- },
- projectFiles: {
- data: [],
- isLoading: false,
- error: null,
- },
- licenses: {
- data: [],
- isLoading: false,
- error: null,
- },
- });
+ ctx.setState({ ...DefaultState });
if (createdPreprintId) {
return this.preprintsService.deletePreprint(createdPreprintId);
}
@@ -377,6 +370,156 @@ export class SubmitPreprintState {
);
}
+ @Action(FetchPreprintsSubjects)
+ fetchPreprintsSubjects(ctx: StateContext) {
+ const createdPreprintId = ctx.getState().createdPreprint.data!.id;
+ if (!createdPreprintId) return EMPTY;
+
+ ctx.setState(patch({ subjects: patch({ isLoading: true }) }));
+
+ return this.subjectsService.getPreprintSubjects(createdPreprintId).pipe(
+ tap((subjects) => {
+ ctx.patchState({
+ subjects: {
+ data: subjects,
+ isLoading: false,
+ error: null,
+ },
+ });
+ }),
+ catchError((error) => handleSectionError(ctx, 'subjects', error))
+ );
+ }
+
+ @Action(UpdatePreprintsSubjects)
+ updatePreprintsSubjects(ctx: StateContext, { subjects }: UpdatePreprintsSubjects) {
+ const createdPreprintId = ctx.getState().createdPreprint.data?.id;
+ if (!createdPreprintId) return EMPTY;
+
+ ctx.setState(patch({ subjects: patch({ isLoading: true }) }));
+
+ return this.subjectsService.updatePreprintSubjects(createdPreprintId, subjects).pipe(
+ tap(() => {
+ ctx.patchState({
+ subjects: {
+ data: subjects,
+ isLoading: false,
+ error: null,
+ },
+ });
+ }),
+ catchError((error) => handleSectionError(ctx, 'subjects', error))
+ );
+ }
+
+ @Action(DisconnectProject)
+ disconnectProject(ctx: StateContext) {
+ const createdPreprintId = ctx.getState().createdPreprint.data?.id;
+ if (!createdPreprintId) return EMPTY;
+
+ ctx.setState(patch({ createdPreprint: patch({ isSubmitting: true }) }));
+
+ return this.preprintProjectsService.removePreprintProjectRelationship(createdPreprintId).pipe(
+ tap(() => {
+ ctx.patchState({
+ createdPreprint: {
+ ...ctx.getState().createdPreprint,
+ data: {
+ ...ctx.getState().createdPreprint.data!,
+ nodeId: null,
+ },
+ isSubmitting: false,
+ },
+ preprintProject: {
+ data: null,
+ isLoading: false,
+ error: null,
+ },
+ });
+ }),
+ catchError((error) => handleSectionError(ctx, 'createdPreprint', error))
+ );
+ }
+
+ @Action(ConnectProject)
+ connectProject(ctx: StateContext, { projectId }: ConnectProject) {
+ const createdPreprintId = ctx.getState().createdPreprint.data?.id;
+ if (!createdPreprintId) return EMPTY;
+
+ ctx.setState(patch({ createdPreprint: patch({ isSubmitting: true }) }));
+
+ return this.preprintProjectsService.updatePreprintProjectRelationship(createdPreprintId, projectId).pipe(
+ tap((preprint) => {
+ ctx.patchState({
+ createdPreprint: {
+ data: preprint,
+ isLoading: false,
+ isSubmitting: false,
+ error: null,
+ },
+ });
+ }),
+ catchError((error) => handleSectionError(ctx, 'createdPreprint', error))
+ );
+ }
+
+ @Action(FetchPreprintProject)
+ fetchPreprintProject(ctx: StateContext) {
+ const preprintProjectId = ctx.getState().createdPreprint.data?.nodeId;
+ if (!preprintProjectId) return EMPTY;
+
+ ctx.setState(patch({ preprintProject: patch({ isLoading: true }) }));
+
+ return this.preprintProjectsService.getProjectById(preprintProjectId).pipe(
+ tap((project) => {
+ ctx.patchState({
+ preprintProject: {
+ data: project,
+ isLoading: false,
+ error: null,
+ },
+ });
+ }),
+ catchError((error) => handleSectionError(ctx, 'preprintProject', error))
+ );
+ }
+
+ @Action(CreateNewProject)
+ createNewProject(ctx: StateContext, action: CreateNewProject) {
+ const createdPreprintId = ctx.getState().createdPreprint.data!.id;
+ ctx.setState(patch({ createdPreprint: patch({ isSubmitting: true }) }));
+ ctx.setState(patch({ preprintProject: patch({ isLoading: true }) }));
+
+ return this.preprintProjectsService
+ .createProject(action.title, action.description, action.templateFrom, action.regionId, action.affiliationsId)
+ .pipe(
+ switchMap((project) =>
+ forkJoin([
+ of(project),
+ this.preprintProjectsService.updatePreprintProjectRelationship(createdPreprintId, project.id),
+ ])
+ ),
+ tap(([project, preprint]) => {
+ ctx.patchState({
+ createdPreprint: {
+ ...ctx.getState().createdPreprint,
+ data: {
+ ...ctx.getState().createdPreprint.data!,
+ nodeId: preprint.nodeId,
+ },
+ isSubmitting: false,
+ },
+ preprintProject: {
+ data: project,
+ isLoading: false,
+ error: null,
+ },
+ });
+ }),
+ catchError((error) => this.handleError(ctx, 'preprintProject', error))
+ );
+ }
+
private handleError(
ctx: StateContext,
section: keyof SubmitPreprintStateModel,
diff --git a/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.html b/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.html
index 32e402b21..87384f84b 100644
--- a/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.html
+++ b/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.html
@@ -1,11 +1,7 @@
();
private readonly route = inject(ActivatedRoute);
private readonly draftId = this.route.snapshot.params['id'];
+ private readonly OSF_PROVIDER_ID = 'osf';
- protected subjects = select(SubjectsSelectors.getSubjects);
- protected subjectsLoading = select(SubjectsSelectors.getSubjectsLoading);
- protected searchedSubjects = select(SubjectsSelectors.getSearchedSubjects);
- protected isSearching = select(SubjectsSelectors.getSearchedSubjectsLoading);
protected selectedSubjects = select(RegistriesSelectors.getSelectedSubjects);
protected isSubjectsUpdating = select(RegistriesSelectors.isSubjectsUpdating);
@@ -41,7 +37,7 @@ export class RegistriesSubjectsComponent {
});
constructor() {
- this.actions.fetchSubjects();
+ this.actions.fetchSubjects(this.OSF_PROVIDER_ID);
this.actions.fetchRegistrationSubjects(this.draftId);
}
diff --git a/src/app/features/registries/services/registration-subjects.service.ts b/src/app/features/registries/services/registration-subjects.service.ts
index 2b6209ccd..697c5b656 100644
--- a/src/app/features/registries/services/registration-subjects.service.ts
+++ b/src/app/features/registries/services/registration-subjects.service.ts
@@ -13,7 +13,7 @@ export class RegistrationSubjectsService implements ISubjectsService {
private apiUrl = environment.apiUrl;
private readonly jsonApiService = inject(JsonApiService);
- getSubjects(search?: string): Observable {
+ getSubjects(providerId: string, search?: string): Observable {
const params: Record = {
'page[size]': '100',
sort: 'text',
@@ -26,7 +26,7 @@ export class RegistrationSubjectsService implements ISubjectsService {
params['embed'] = 'parent';
}
return this.jsonApiService
- .get(`${this.apiUrl}/providers/registrations/osf/subjects/`, params)
+ .get(`${this.apiUrl}/providers/registrations/${providerId}/subjects/`, params)
.pipe(
map((response) => {
return SubjectMapper.fromSubjectsResponseJsonApi(response);
@@ -52,8 +52,13 @@ export class RegistrationSubjectsService implements ISubjectsService {
}
getRegistrationSubjects(draftId: string): Observable {
+ const params: Record = {
+ 'page[size]': '100',
+ page: '1',
+ };
+
return this.jsonApiService
- .get(`${this.apiUrl}/draft_registrations/${draftId}/subjects/`)
+ .get(`${this.apiUrl}/draft_registrations/${draftId}/subjects/`, params)
.pipe(
map((response) => {
return SubjectMapper.fromSubjectsResponseJsonApi(response);
diff --git a/src/app/shared/components/add-project-form/add-project-form.component.html b/src/app/shared/components/add-project-form/add-project-form.component.html
index a39106ee3..67be60576 100644
--- a/src/app/shared/components/add-project-form/add-project-form.component.html
+++ b/src/app/shared/components/add-project-form/add-project-form.component.html
@@ -1,5 +1,4 @@
-
diff --git a/src/app/shared/components/add-project-form/add-project-form.component.ts b/src/app/shared/components/add-project-form/add-project-form.component.ts
index 561f0559b..ee4e95fbe 100644
--- a/src/app/shared/components/add-project-form/add-project-form.component.ts
+++ b/src/app/shared/components/add-project-form/add-project-form.component.ts
@@ -1,27 +1,21 @@
-import { Store } from '@ngxs/store';
+import { createDispatchMap, select } from '@ngxs/store';
import { TranslatePipe } from '@ngx-translate/core';
import { ButtonModule } from 'primeng/button';
import { CheckboxModule } from 'primeng/checkbox';
-import { DynamicDialogRef } from 'primeng/dynamicdialog';
import { InputTextModule } from 'primeng/inputtext';
import { Select } from 'primeng/select';
import { Textarea } from 'primeng/textarea';
import { CommonModule, NgOptimizedImage } from '@angular/common';
-import { ChangeDetectionStrategy, Component, computed, inject, OnInit, signal } from '@angular/core';
-import { toSignal } from '@angular/core/rxjs-interop';
-import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
+import { ChangeDetectionStrategy, Component, input, OnInit, signal } from '@angular/core';
+import { FormGroup, ReactiveFormsModule } from '@angular/forms';
-import { MY_PROJECTS_TABLE_PARAMS } from '@core/constants/my-projects-table.constants';
-import { STORAGE_LOCATIONS } from '@core/constants/storage-locations.constant';
-import { CreateProject, GetMyProjects, MyProjectsSelectors } from '@osf/features/my-projects/store';
-import { ProjectFormControls } from '@osf/shared/enums/create-project-form-controls.enum';
-import { ProjectForm } from '@osf/shared/models/create-project-form.model';
-import { CustomValidators } from '@osf/shared/utils';
-import { InstitutionsSelectors } from '@shared/stores/institutions';
-import { IS_XSMALL } from '@shared/utils/breakpoints.tokens';
+import { ProjectFormControls } from '@osf/shared/enums';
+import { IdName, ProjectForm } from '@osf/shared/models';
+import { FetchUserInstitutions, InstitutionsSelectors } from '@shared/stores/institutions';
+import { FetchRegions, RegionsSelectors } from '@shared/stores/regions';
@Component({
selector: 'osf-add-project-form',
@@ -41,93 +35,42 @@ import { IS_XSMALL } from '@shared/utils/breakpoints.tokens';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AddProjectFormComponent implements OnInit {
- #store = inject(Store);
- protected readonly projects = this.#store.selectSignal(MyProjectsSelectors.getProjects);
- protected readonly isMobile = toSignal(inject(IS_XSMALL));
- protected readonly dialogRef = inject(DynamicDialogRef);
- protected readonly ProjectFormControls = ProjectFormControls;
- protected readonly hasTemplateSelected = signal(false);
- protected readonly isSubmitting = signal(false);
-
- protected readonly storageLocations = STORAGE_LOCATIONS;
+ private actions = createDispatchMap({
+ fetchUserInstitutions: FetchUserInstitutions,
+ fetchRegions: FetchRegions,
+ });
- protected readonly affiliations = this.#store.selectSignal(InstitutionsSelectors.getUserInstitutions);
+ templates = input.required();
- protected projectTemplateOptions = computed(() => {
- return this.projects().map((project) => ({
- label: project.title,
- value: project.id,
- }));
- });
+ ProjectFormControls = ProjectFormControls;
- readonly projectForm = new FormGroup({
- [ProjectFormControls.Title]: new FormControl('', {
- nonNullable: true,
- validators: [CustomValidators.requiredTrimmed()],
- }),
- [ProjectFormControls.StorageLocation]: new FormControl('us', {
- nonNullable: true,
- validators: [Validators.required],
- }),
- [ProjectFormControls.Affiliations]: new FormControl([], {
- nonNullable: true,
- }),
- [ProjectFormControls.Description]: new FormControl('', {
- nonNullable: true,
- }),
- [ProjectFormControls.Template]: new FormControl('', {
- nonNullable: true,
- }),
- });
+ hasTemplateSelected = signal(false);
+ isSubmitting = signal(false);
+ storageLocations = select(RegionsSelectors.getRegions);
+ areStorageLocationsLoading = select(RegionsSelectors.areRegionsLoading);
+ affiliations = select(InstitutionsSelectors.getUserInstitutions);
- constructor() {
- this.projectForm.get(ProjectFormControls.Template)?.valueChanges.subscribe((value) => {
- this.hasTemplateSelected.set(!!value);
- });
- }
+ projectForm = input.required>();
ngOnInit(): void {
- this.#store.dispatch(new GetMyProjects(1, MY_PROJECTS_TABLE_PARAMS.rows, {}));
+ this.actions.fetchUserInstitutions();
+ this.actions.fetchRegions();
this.selectAllAffiliations();
+
+ this.projectForm()
+ .get(ProjectFormControls.Template)
+ ?.valueChanges.subscribe((value) => {
+ this.hasTemplateSelected.set(!!value);
+ });
}
selectAllAffiliations(): void {
const allAffiliationValues = this.affiliations().map((aff) => aff.id);
- this.projectForm.get(ProjectFormControls.Affiliations)?.setValue(allAffiliationValues);
+ this.projectForm().get(ProjectFormControls.Affiliations)?.setValue(allAffiliationValues);
}
removeAllAffiliations(): void {
- this.projectForm.get(ProjectFormControls.Affiliations)?.setValue([]);
- }
-
- submitForm(): void {
- if (!this.projectForm.valid) {
- this.projectForm.markAllAsTouched();
- return;
- }
-
- const formValue = this.projectForm.getRawValue();
- this.isSubmitting.set(true);
-
- this.#store
- .dispatch(
- new CreateProject(
- formValue.title,
- formValue.description,
- formValue.template,
- formValue.storageLocation,
- formValue.affiliations
- )
- )
- .subscribe({
- next: () => {
- this.#store.dispatch(new GetMyProjects(1, MY_PROJECTS_TABLE_PARAMS.rows, {}));
- this.dialogRef.close();
- },
- error: () => {
- this.isSubmitting.set(false);
- },
- });
+ this.projectForm().get(ProjectFormControls.Affiliations)?.setValue([]);
}
}
diff --git a/src/app/shared/components/subjects/subjects.component.html b/src/app/shared/components/subjects/subjects.component.html
index aeec5abdb..8698217b6 100644
--- a/src/app/shared/components/subjects/subjects.component.html
+++ b/src/app/shared/components/subjects/subjects.component.html
@@ -50,7 +50,7 @@ {{ 'shared.subjects.title' | translate }}
selectionMode="checkbox"
[selection]="selectedTree()"
[lazy]="true"
- [loading]="loading()"
+ [loading]="areSubjectsUpdating() || subjectsLoading()"
(onNodeExpand)="loadNode($event.node)"
(onNodeCollapse)="collapseNode($event.node)"
(onNodeSelect)="selectSubject($event.node.data)"
diff --git a/src/app/shared/components/subjects/subjects.component.ts b/src/app/shared/components/subjects/subjects.component.ts
index 387172bfd..d3e7ee0c0 100644
--- a/src/app/shared/components/subjects/subjects.component.ts
+++ b/src/app/shared/components/subjects/subjects.component.ts
@@ -1,3 +1,5 @@
+import { select } from '@ngxs/store';
+
import { TranslatePipe } from '@ngx-translate/core';
import { TreeNode } from 'primeng/api';
@@ -15,6 +17,7 @@ import { FormControl } from '@angular/forms';
import { INPUT_VALIDATION_MESSAGES } from '@osf/shared/constants';
import { Subject } from '@osf/shared/models';
+import { SubjectsSelectors } from '@shared/stores';
import { SearchInputComponent } from '../search-input/search-input.component';
@@ -26,17 +29,18 @@ import { SearchInputComponent } from '../search-input/search-input.component';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SubjectsComponent {
+ subjects = select(SubjectsSelectors.getSubjects);
+ subjectsLoading = select(SubjectsSelectors.getSubjectsLoading);
+ searchedSubjects = select(SubjectsSelectors.getSearchedSubjects);
+ areSubjectsUpdating = input(false);
+ isSearching = select(SubjectsSelectors.getSearchedSubjectsLoading);
control = input>();
- list = input([]);
- searchedSubjects = input([]);
- loading = input(false);
- isSearching = input(false);
selected = input([]);
searchChanged = output();
loadChildren = output();
updateSelection = output();
- subjectsTree = computed(() => this.list().map((subject: Subject) => this.mapSubjectToTreeNode(subject)));
+ subjectsTree = computed(() => this.subjects().map((subject: Subject) => this.mapSubjectToTreeNode(subject)));
selectedTree = computed(() => this.selected().map((subject: Subject) => this.mapSubjectToTreeNode(subject)));
searchedList = computed(() => this.searchedSubjects().map((subject: Subject) => this.mapParentsSubject(subject)));
expanded: Record = {};
diff --git a/src/app/shared/mappers/regions/index.ts b/src/app/shared/mappers/regions/index.ts
new file mode 100644
index 000000000..241a425e0
--- /dev/null
+++ b/src/app/shared/mappers/regions/index.ts
@@ -0,0 +1 @@
+export * from './regions-mapper';
diff --git a/src/app/shared/mappers/regions/regions-mapper.ts b/src/app/shared/mappers/regions/regions-mapper.ts
new file mode 100644
index 000000000..9af72bb54
--- /dev/null
+++ b/src/app/shared/mappers/regions/regions-mapper.ts
@@ -0,0 +1,11 @@
+import { IdName } from '@shared/models';
+import { RegionsResponseJsonApi } from '@shared/models/regions';
+
+export class RegionsMapper {
+ static fromRegionsResponseJsonApi(response: RegionsResponseJsonApi): IdName[] {
+ return response.data.map((data) => ({
+ id: data.id,
+ name: data.attributes.name,
+ }));
+ }
+}
diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts
index d42f12aa2..08e4a449a 100644
--- a/src/app/shared/models/index.ts
+++ b/src/app/shared/models/index.ts
@@ -5,7 +5,6 @@ export * from './charts';
export * from './confirmation-options.model';
export * from './contributors';
export * from './create-component-form.model';
-export * from './create-project-form.model';
export * from './file-menu-action.model';
export * from './files/file.model';
export * from './files/get-files-response.model';
@@ -19,8 +18,9 @@ export * from './license.model';
export * from './license.model';
export * from './licenses-json-api.model';
export * from './metadata-field.model';
-export * from './node-response.model';
export * from './node-subject.model';
+export * from './nodes/create-project-form.model';
+export * from './nodes/nodes-json-api.model';
export * from './paginated-data.model';
export * from './query-params.model';
export * from './resource-card';
@@ -37,7 +37,6 @@ export * from './table-parameters.model';
export * from './toolbar-resource.model';
export * from './tooltip-position.model';
export * from './tutorial-step.model';
-export * from './update-node-request.model';
export * from './user';
export * from './validation-params.model';
export * from './view-only-links';
diff --git a/src/app/shared/models/create-project-form.model.ts b/src/app/shared/models/nodes/create-project-form.model.ts
similarity index 86%
rename from src/app/shared/models/create-project-form.model.ts
rename to src/app/shared/models/nodes/create-project-form.model.ts
index d34ae3b27..9eb174c5d 100644
--- a/src/app/shared/models/create-project-form.model.ts
+++ b/src/app/shared/models/nodes/create-project-form.model.ts
@@ -1,6 +1,6 @@
import { FormControl } from '@angular/forms';
-import { ProjectFormControls } from '@osf/shared/enums';
+import { ProjectFormControls } from '@shared/enums';
export interface ProjectForm {
[ProjectFormControls.Title]: FormControl;
diff --git a/src/app/shared/models/node-response.model.ts b/src/app/shared/models/nodes/nodes-json-api.model.ts
similarity index 78%
rename from src/app/shared/models/node-response.model.ts
rename to src/app/shared/models/nodes/nodes-json-api.model.ts
index abb41506e..e760308f5 100644
--- a/src/app/shared/models/node-response.model.ts
+++ b/src/app/shared/models/nodes/nodes-json-api.model.ts
@@ -105,3 +105,46 @@ export interface NodeResponseModel {
data: NodeData;
meta: NodeMeta;
}
+
+export interface UpdateNodeAttributes {
+ description?: string;
+ tags?: string[];
+ public?: boolean;
+ title?: string;
+}
+
+export 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/regions/index.ts b/src/app/shared/models/regions/index.ts
new file mode 100644
index 000000000..a87050d50
--- /dev/null
+++ b/src/app/shared/models/regions/index.ts
@@ -0,0 +1 @@
+export * from './regions.json-api.model';
diff --git a/src/app/shared/models/regions/regions.json-api.model.ts b/src/app/shared/models/regions/regions.json-api.model.ts
new file mode 100644
index 000000000..305c32a53
--- /dev/null
+++ b/src/app/shared/models/regions/regions.json-api.model.ts
@@ -0,0 +1,9 @@
+export interface RegionsResponseJsonApi {
+ data: {
+ id: string;
+ type: 'regions';
+ attributes: {
+ name: string;
+ };
+ }[];
+}
diff --git a/src/app/shared/models/subject/subject-service.model.ts b/src/app/shared/models/subject/subject-service.model.ts
index b9b5ae941..56d8abcb8 100644
--- a/src/app/shared/models/subject/subject-service.model.ts
+++ b/src/app/shared/models/subject/subject-service.model.ts
@@ -3,6 +3,7 @@ import { Observable } from 'rxjs';
import { Subject } from './subject.model';
export interface ISubjectsService {
- getSubjects(search?: string): Observable;
+ getSubjects(providerId: string, search?: string): Observable;
+
getChildrenSubjects(parentId: string): Observable;
}
diff --git a/src/app/shared/models/update-node-request.model.ts b/src/app/shared/models/update-node-request.model.ts
deleted file mode 100644
index db2ee5627..000000000
--- a/src/app/shared/models/update-node-request.model.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-export interface UpdateNodeAttributes {
- description?: string;
- tags?: string[];
- public?: boolean;
- title?: string;
-}
-
-export interface UpdateNodeData {
- type: 'nodes';
- id: string;
- attributes: UpdateNodeAttributes;
-}
-
-export interface UpdateNodeRequestModel {
- data: UpdateNodeData;
-}
diff --git a/src/app/shared/services/index.ts b/src/app/shared/services/index.ts
index 836b6e4cd..0bcf7434b 100644
--- a/src/app/shared/services/index.ts
+++ b/src/app/shared/services/index.ts
@@ -7,6 +7,7 @@ export { FiltersOptionsService } from './filters-options.service';
export { InstitutionsService } from './institutions.service';
export { LicensesService } from './licenses.service';
export { LoaderService } from './loader.service';
+export { RegionsService } from './regions.service';
export { ResourceCardService } from './resource-card.service';
export { SearchService } from './search.service';
export { SubjectsService } from './subjects.service';
diff --git a/src/app/shared/services/regions.service.ts b/src/app/shared/services/regions.service.ts
new file mode 100644
index 000000000..4fb836929
--- /dev/null
+++ b/src/app/shared/services/regions.service.ts
@@ -0,0 +1,24 @@
+import { map, Observable } from 'rxjs';
+
+import { HttpClient } from '@angular/common/http';
+import { inject, Injectable } from '@angular/core';
+
+import { RegionsMapper } from '@shared/mappers/regions';
+import { IdName } from '@shared/models';
+import { RegionsResponseJsonApi } from '@shared/models/regions';
+
+import { environment } from 'src/environments/environment';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class RegionsService {
+ private readonly http = inject(HttpClient);
+ private readonly baseUrl = environment.apiUrl;
+
+ getAllRegions(): Observable {
+ return this.http
+ .get(`${this.baseUrl}/regions/`)
+ .pipe(map((regions) => RegionsMapper.fromRegionsResponseJsonApi(regions)));
+ }
+}
diff --git a/src/app/shared/stores/regions/index.ts b/src/app/shared/stores/regions/index.ts
new file mode 100644
index 000000000..a3bb15b1a
--- /dev/null
+++ b/src/app/shared/stores/regions/index.ts
@@ -0,0 +1,4 @@
+export * from './regions.actions';
+export * from './regions.model';
+export * from './regions.selectors';
+export * from './regions.state';
diff --git a/src/app/shared/stores/regions/regions.actions.ts b/src/app/shared/stores/regions/regions.actions.ts
new file mode 100644
index 000000000..b6df2f670
--- /dev/null
+++ b/src/app/shared/stores/regions/regions.actions.ts
@@ -0,0 +1,3 @@
+export class FetchRegions {
+ static readonly type = '[Regions] Fetch Regions';
+}
diff --git a/src/app/shared/stores/regions/regions.model.ts b/src/app/shared/stores/regions/regions.model.ts
new file mode 100644
index 000000000..c7ec6e970
--- /dev/null
+++ b/src/app/shared/stores/regions/regions.model.ts
@@ -0,0 +1,5 @@
+import { AsyncStateModel, IdName } from '@shared/models';
+
+export interface RegionsStateModel {
+ regions: AsyncStateModel;
+}
diff --git a/src/app/shared/stores/regions/regions.selectors.ts b/src/app/shared/stores/regions/regions.selectors.ts
new file mode 100644
index 000000000..38453ccd2
--- /dev/null
+++ b/src/app/shared/stores/regions/regions.selectors.ts
@@ -0,0 +1,16 @@
+import { Selector } from '@ngxs/store';
+
+import { RegionsStateModel } from './regions.model';
+import { RegionsState } from './regions.state';
+
+export class RegionsSelectors {
+ @Selector([RegionsState])
+ static getRegions(state: RegionsStateModel) {
+ return state.regions.data;
+ }
+
+ @Selector([RegionsState])
+ static areRegionsLoading(state: RegionsStateModel) {
+ return state.regions.isLoading;
+ }
+}
diff --git a/src/app/shared/stores/regions/regions.state.ts b/src/app/shared/stores/regions/regions.state.ts
new file mode 100644
index 000000000..177b873c7
--- /dev/null
+++ b/src/app/shared/stores/regions/regions.state.ts
@@ -0,0 +1,39 @@
+import { Action, State, StateContext } from '@ngxs/store';
+import { patch } from '@ngxs/store/operators';
+
+import { catchError, tap } from 'rxjs';
+
+import { inject, Injectable } from '@angular/core';
+
+import { handleSectionError } from '@core/handlers';
+import { RegionsService } from '@shared/services';
+
+import { FetchRegions } from './regions.actions';
+import { RegionsStateModel } from './regions.model';
+
+@State({
+ name: 'regions',
+ defaults: {
+ regions: {
+ data: [],
+ isLoading: false,
+ error: null,
+ },
+ },
+})
+@Injectable()
+export class RegionsState {
+ private readonly regionsService = inject(RegionsService);
+
+ @Action(FetchRegions)
+ fetchSubjects(ctx: StateContext) {
+ ctx.setState(patch({ regions: patch({ isLoading: true }) }));
+
+ return this.regionsService.getAllRegions().pipe(
+ tap((regions) => {
+ ctx.setState(patch({ regions: patch({ isLoading: false, data: regions }) }));
+ }),
+ catchError((error) => handleSectionError(ctx, 'regions', error))
+ );
+ }
+}
diff --git a/src/app/shared/stores/subjects/subjects.actions.ts b/src/app/shared/stores/subjects/subjects.actions.ts
index d2a92eda6..6be7d53ca 100644
--- a/src/app/shared/stores/subjects/subjects.actions.ts
+++ b/src/app/shared/stores/subjects/subjects.actions.ts
@@ -4,6 +4,7 @@ export class GetSubjects {
export class UpdateProjectSubjects {
static readonly type = '[Subjects] Update Project';
+
constructor(
public projectId: string,
public subjectIds: string[]
@@ -12,10 +13,15 @@ export class UpdateProjectSubjects {
export class FetchSubjects {
static readonly type = '[Subjects] Fetch Subjects';
- constructor(public search?: string) {}
+
+ constructor(
+ public providerId: string,
+ public search?: string
+ ) {}
}
export class FetchChildrenSubjects {
static readonly type = '[Subjects] Fetch Children Subjects';
+
constructor(public parentId: string) {}
}
diff --git a/src/app/shared/stores/subjects/subjects.state.ts b/src/app/shared/stores/subjects/subjects.state.ts
index c1cbfadf6..7b30563ef 100644
--- a/src/app/shared/stores/subjects/subjects.state.ts
+++ b/src/app/shared/stores/subjects/subjects.state.ts
@@ -39,7 +39,7 @@ export class SubjectsState {
private readonly subjectsService = inject(SUBJECTS_SERVICE);
@Action(FetchSubjects)
- fetchSubjects(ctx: StateContext, { search }: FetchSubjects) {
+ fetchSubjects(ctx: StateContext, { providerId, search }: FetchSubjects) {
ctx.patchState({
subjects: {
...ctx.getState().subjects,
@@ -53,7 +53,7 @@ export class SubjectsState {
},
});
- return this.subjectsService.getSubjects(search).pipe(
+ return this.subjectsService.getSubjects(providerId, search).pipe(
tap((subjects) => {
if (search) {
ctx.patchState({