diff --git a/src/app/features/preprints/components/submit-steps/metadata/metadata.component.html b/src/app/features/preprints/components/submit-steps/metadata/metadata.component.html deleted file mode 100644 index 682a46bb2..000000000 --- a/src/app/features/preprints/components/submit-steps/metadata/metadata.component.html +++ /dev/null @@ -1,86 +0,0 @@ -

Metadata

- -
- -
- -
- -
- - -
-

Publication DOI

- - - @let doiControl = metadataForm.controls['doi']; - @if (doiControl.errors?.['required'] && (doiControl.touched || doiControl.dirty)) { - - {{ INPUT_VALIDATION_MESSAGES.required | translate }} - - } - @if (doiControl.errors?.['pattern'] && (doiControl.touched || doiControl.dirty)) { - Please use a valid DOI format (10.xxxx/xxxxx) - - } -
-
- - -
-

Tags (optional)

- -
- -
-
-
- - -

Publication Date (optional)

- - - - - - -
- - -

Publication Citation (optional)

- - -
- -
- - -
diff --git a/src/app/features/preprints/components/submit-steps/metadata/metadata.component.ts b/src/app/features/preprints/components/submit-steps/metadata/metadata.component.ts deleted file mode 100644 index c82bb1e9a..000000000 --- a/src/app/features/preprints/components/submit-steps/metadata/metadata.component.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { createDispatchMap, select } from '@ngxs/store'; - -import { TranslatePipe } from '@ngx-translate/core'; - -import { Button } from 'primeng/button'; -import { Card } from 'primeng/card'; -import { DatePicker } from 'primeng/datepicker'; -import { InputText } from 'primeng/inputtext'; -import { Message } from 'primeng/message'; -import { Tooltip } from 'primeng/tooltip'; - -import { ChangeDetectionStrategy, Component, HostListener, OnInit, output } from '@angular/core'; -import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; - -import { formInputLimits } from '@osf/features/preprints/constants'; -import { MetadataForm, Preprint } from '@osf/features/preprints/models'; -import { - CreatePreprint, - FetchLicenses, - SaveLicense, - SubmitPreprintSelectors, - UpdatePreprint, -} from '@osf/features/preprints/store/submit-preprint'; -import { IconComponent, LicenseComponent, TagsInputComponent, TextInputComponent } from '@shared/components'; -import { INPUT_VALIDATION_MESSAGES } from '@shared/constants'; -import { License, LicenseOptions } from '@shared/models'; -import { CustomValidators, findChangedFields } from '@shared/utils'; - -import { ContributorsComponent } from './contributors/contributors.component'; - -@Component({ - selector: 'osf-preprint-metadata', - imports: [ - ContributorsComponent, - Button, - Card, - ReactiveFormsModule, - Message, - TranslatePipe, - DatePicker, - IconComponent, - InputText, - TextInputComponent, - Tooltip, - LicenseComponent, - TagsInputComponent, - ], - templateUrl: './metadata.component.html', - styleUrl: './metadata.component.scss', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class MetadataComponent implements OnInit { - private actions = createDispatchMap({ - createPreprint: CreatePreprint, - updatePreprint: UpdatePreprint, - fetchLicenses: FetchLicenses, - saveLicense: SaveLicense, - }); - - protected metadataForm!: FormGroup; - protected inputLimits = formInputLimits; - protected readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; - - licences = select(SubmitPreprintSelectors.getLicenses); - createdPreprint = select(SubmitPreprintSelectors.getCreatedPreprint); - isUpdatingPreprint = select(SubmitPreprintSelectors.isPreprintSubmitting); - - nextClicked = output(); - - ngOnInit() { - this.actions.fetchLicenses(); - this.initForm(); - } - - initForm() { - const publicationDate = this.createdPreprint()?.originalPublicationDate; - this.metadataForm = new FormGroup({ - doi: new FormControl(this.createdPreprint()?.doi || '', { - nonNullable: true, - validators: [CustomValidators.requiredTrimmed(), Validators.pattern(this.inputLimits.doi.pattern)], - }), - originalPublicationDate: new FormControl(publicationDate ? new Date(publicationDate) : null, { - nonNullable: false, - validators: [], - }), - customPublicationCitation: new FormControl(this.createdPreprint()?.customPublicationCitation || null, { - nonNullable: false, - validators: [Validators.maxLength(this.inputLimits.citation.maxLength)], - }), - tags: new FormControl(this.createdPreprint()?.tags || [], { - nonNullable: true, - validators: [], - }), - }); - } - - nextButtonClicked() { - if (this.metadataForm.invalid) { - return; - } - - const model = this.metadataForm.value; - - const changedFields = findChangedFields(model, this.createdPreprint()!); - - this.actions.updatePreprint(this.createdPreprint()!.id, changedFields).subscribe({ - complete: () => { - this.nextClicked.emit(); - }, - }); - } - - @HostListener('window:beforeunload', ['$event']) - public onBeforeUnload($event: BeforeUnloadEvent): boolean { - $event.preventDefault(); - return false; - } - - createLicense(licenseDetails: { id: string; licenseOptions: LicenseOptions }) { - this.actions.saveLicense(licenseDetails.id, licenseDetails.licenseOptions); - } - - selectLicense(license: License) { - this.actions.saveLicense(license.id); - } - - updateTags(updatedTags: string[]) { - this.metadataForm.patchValue({ - tags: updatedTags, - }); - } -} diff --git a/src/app/features/registries/components/metadata/registries-license/registries-license.component.html b/src/app/features/registries/components/metadata/registries-license/registries-license.component.html index cc5f9c25a..3d5788724 100644 --- a/src/app/features/registries/components/metadata/registries-license/registries-license.component.html +++ b/src/app/features/registries/components/metadata/registries-license/registries-license.component.html @@ -1,47 +1,7 @@ - -

{{ 'shared.license.title' | translate }}

-

- {{ 'shared.license.description' | translate }} -

-

- {{ 'shared.license.helpText' | translate }} - {{ 'common.links.helpGuide' | translate }}. -

- - @if (selectedLicense) { - - @if (selectedLicense.requiredFields.length) { -
-
- - -
- - -
- } - -

- -

- } -
+ diff --git a/src/app/features/registries/components/metadata/registries-license/registries-license.component.ts b/src/app/features/registries/components/metadata/registries-license/registries-license.component.ts index d81e74502..90b22d921 100644 --- a/src/app/features/registries/components/metadata/registries-license/registries-license.component.ts +++ b/src/app/features/registries/components/metadata/registries-license/registries-license.component.ts @@ -1,37 +1,18 @@ import { createDispatchMap, select } from '@ngxs/store'; -import { TranslatePipe } from '@ngx-translate/core'; - -import { Card } from 'primeng/card'; -import { DatePicker } from 'primeng/datepicker'; -import { Divider } from 'primeng/divider'; -import { Select } from 'primeng/select'; - import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; -import { License } from '@osf/features/registries/models'; -import { FetchLicenses, RegistriesSelectors } from '@osf/features/registries/store'; -import { TextInputComponent, TruncatedTextComponent } from '@osf/shared/components'; +import { FetchLicenses, RegistriesSelectors, SaveLicense } from '@osf/features/registries/store'; +import { LicenseComponent } from '@osf/shared/components'; import { InputLimits } from '@osf/shared/constants'; -import { InterpolatePipe } from '@osf/shared/pipes'; +import { License, LicenseOptions } from '@osf/shared/models'; import { CustomValidators } from '@osf/shared/utils'; @Component({ selector: 'osf-registries-license', - imports: [ - Card, - TranslatePipe, - Select, - FormsModule, - Divider, - TruncatedTextComponent, - DatePicker, - TextInputComponent, - InterpolatePipe, - ReactiveFormsModule, - ], + imports: [FormsModule, ReactiveFormsModule, LicenseComponent], templateUrl: './registries-license.component.html', styleUrl: './registries-license.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, @@ -41,11 +22,11 @@ export class RegistriesLicenseComponent { private readonly draftId = this.route.snapshot.params['id']; private readonly fb = inject(FormBuilder); - protected actions = createDispatchMap({ fetchLicenses: FetchLicenses }); + protected actions = createDispatchMap({ fetchLicenses: FetchLicenses, saveLicense: SaveLicense }); protected licenses = select(RegistriesSelectors.getLicenses); protected inputLimits = InputLimits; - selectedLicense: License | null = null; + selectedLicense = select(RegistriesSelectors.getSelectedLicense); currentYear = new Date(); licenseYear = this.currentYear; licenseForm = this.fb.group({ @@ -57,7 +38,11 @@ export class RegistriesLicenseComponent { this.actions.fetchLicenses(); } - onSelectLicense(license: License): void { - console.log('Selected License:', license); + createLicense(licenseDetails: { id: string; licenseOptions: LicenseOptions }) { + this.actions.saveLicense(this.draftId, licenseDetails.id, licenseDetails.licenseOptions); + } + + selectLicense(license: License) { + this.actions.saveLicense(this.draftId, license.id); } } diff --git a/src/app/features/registries/mappers/licenses.mapper.ts b/src/app/features/registries/mappers/licenses.mapper.ts index 9ad0a8290..27e12fd9a 100644 --- a/src/app/features/registries/mappers/licenses.mapper.ts +++ b/src/app/features/registries/mappers/licenses.mapper.ts @@ -1,4 +1,4 @@ -import { License, LicensesResponseJsonApi } from '../models'; +import { License, LicensesResponseJsonApi } from '@osf/shared/models'; export class LicensesMapper { static fromLicensesResponse(response: LicensesResponseJsonApi): License[] { diff --git a/src/app/features/registries/mappers/registration.mapper.ts b/src/app/features/registries/mappers/registration.mapper.ts index 9ae2e1bd2..2620bc2c9 100644 --- a/src/app/features/registries/mappers/registration.mapper.ts +++ b/src/app/features/registries/mappers/registration.mapper.ts @@ -8,6 +8,10 @@ export class RegistrationMapper { title: response.attributes.title, description: response.attributes.description, registrationSchemaId: response.relationships.registration_schema?.data?.id || '', + license: { + id: response.relationships.license?.data?.id || '', + options: response.attributes.node_license, + }, }; } } diff --git a/src/app/features/registries/models/index.ts b/src/app/features/registries/models/index.ts index 69c4c8b7c..937fb5772 100644 --- a/src/app/features/registries/models/index.ts +++ b/src/app/features/registries/models/index.ts @@ -1,4 +1,3 @@ -export * from './license.model'; export * from './licenses-json-api.model'; export * from './page-schema.model'; export * from './project'; diff --git a/src/app/features/registries/models/license.model.ts b/src/app/features/registries/models/license.model.ts index c4a243a7f..e69de29bb 100644 --- a/src/app/features/registries/models/license.model.ts +++ b/src/app/features/registries/models/license.model.ts @@ -1,7 +0,0 @@ -export interface License { - id: string; - name: string; - requiredFields: string[]; - url: string; - text: string; -} diff --git a/src/app/features/registries/models/licenses-json-api.model.ts b/src/app/features/registries/models/licenses-json-api.model.ts index 32e8fc049..dde5dfe58 100644 --- a/src/app/features/registries/models/licenses-json-api.model.ts +++ b/src/app/features/registries/models/licenses-json-api.model.ts @@ -1,16 +1,21 @@ -import { ApiData, MetaJsonApi, PaginationLinksJsonApi } from '@osf/core/models'; +import { LicenseRecordJsonApi } from '@shared/models'; -export interface LicensesResponseJsonApi { - data: LicenseDataJsonApi[]; - meta: MetaJsonApi; - links: PaginationLinksJsonApi; +export interface LicenseRelationshipJsonApi { + license: { + data: { + id: string; + type: 'licenses'; + }; + }; } -export type LicenseDataJsonApi = ApiData; - -interface LicenseAttributesJsonApi { - name: string; - required_fields: string[]; - url: string; - text: string; +export interface LicensePayloadJsonApi { + data: { + type: 'draft_registrations'; + id: string; + relationships: LicenseRelationshipJsonApi; + attributes: { + node_license?: LicenseRecordJsonApi; + }; + }; } diff --git a/src/app/features/registries/models/registration-json-api.model.ts b/src/app/features/registries/models/registration-json-api.model.ts index f581282e6..4e4502248 100644 --- a/src/app/features/registries/models/registration-json-api.model.ts +++ b/src/app/features/registries/models/registration-json-api.model.ts @@ -1,4 +1,5 @@ import { ApiData, MetaJsonApi, PaginationLinksJsonApi } from '@osf/core/models'; +import { LicenseOptions } from '@osf/shared/models'; export interface RegistrationResponseJsonApi { data: RegistrationDataJsonApi; @@ -20,7 +21,7 @@ interface RegistrationAttributesJsonApi { datetime_updated: string; description: string; has_project: boolean; - node_license: string | null; + node_license: LicenseOptions; registration_metadata: Record; registration_responses: Record; tags: string[]; @@ -30,8 +31,14 @@ interface RegistrationAttributesJsonApi { interface RegistrationRelationshipsJsonApi { registration_schema: { data: { - id: '58fd62fcda3e2400012ca5c1'; + id: string; type: 'registration-schemas'; }; }; + license: { + data: { + id: string; + type: 'licenses'; + }; + }; } diff --git a/src/app/features/registries/models/registration.model.ts b/src/app/features/registries/models/registration.model.ts index 6a717fc94..7415a2156 100644 --- a/src/app/features/registries/models/registration.model.ts +++ b/src/app/features/registries/models/registration.model.ts @@ -1,6 +1,12 @@ +import { LicenseOptions } from '@osf/shared/models'; + export interface Registration { id: string; title: string; description: string; registrationSchemaId: string; + license: { + id: string; + options: LicenseOptions; + }; } diff --git a/src/app/features/registries/registries.routes.ts b/src/app/features/registries/registries.routes.ts index c21f128e8..4119f0c54 100644 --- a/src/app/features/registries/registries.routes.ts +++ b/src/app/features/registries/registries.routes.ts @@ -10,7 +10,14 @@ import { SUBJECTS_SERVICE } from '@osf/shared/tokens/subjects.token'; import { ModerationState } from '../moderation/store'; -import { RegistrationSubjectsService } from './services'; +import { + LicensesHandlers, + ProjectsHandlers, + ProvidersHandlers, + RegistrationContributorsHandlers, + SubjectsHandlers, +} from './store/handlers'; +import { LicensesService, RegistrationContributorsService, RegistrationSubjectsService } from './services'; export const registriesRoutes: Routes = [ { @@ -18,6 +25,14 @@ export const registriesRoutes: Routes = [ component: RegistriesComponent, providers: [ provideStates([RegistriesState, ContributorsState, SubjectsState]), + ProvidersHandlers, + ProjectsHandlers, + LicensesHandlers, + RegistrationContributorsHandlers, + SubjectsHandlers, + RegistrationSubjectsService, + RegistrationContributorsService, + LicensesService, { provide: SUBJECTS_SERVICE, useClass: RegistrationSubjectsService, diff --git a/src/app/features/registries/services/index.ts b/src/app/features/registries/services/index.ts index 99fc8de16..a81908564 100644 --- a/src/app/features/registries/services/index.ts +++ b/src/app/features/registries/services/index.ts @@ -1,5 +1,6 @@ export * from './licenses.service'; export * from './projects.service'; export * from './providers.service'; +export * from './registration-contributors.service'; export * from './registration-subjects.service'; export * from './registries.service'; diff --git a/src/app/features/registries/services/licenses.service.ts b/src/app/features/registries/services/licenses.service.ts index 8879ed1c7..f6b7d0f10 100644 --- a/src/app/features/registries/services/licenses.service.ts +++ b/src/app/features/registries/services/licenses.service.ts @@ -3,9 +3,10 @@ import { map, Observable } from 'rxjs'; import { inject, Injectable } from '@angular/core'; import { JsonApiService } from '@osf/core/services'; +import { License, LicenseOptions, LicensesResponseJsonApi } from '@osf/shared/models'; import { LicensesMapper } from '../mappers'; -import { License, LicensesResponseJsonApi } from '../models'; +import { LicensePayloadJsonApi, RegistrationDataJsonApi } from '../models'; import { environment } from 'src/environments/environment'; @@ -28,9 +29,7 @@ const data: any = { ], }; -@Injectable({ - providedIn: 'root', -}) +@Injectable() export class LicensesService { private apiUrl = environment.apiUrl; private readonly jsonApiService = inject(JsonApiService); @@ -49,4 +48,34 @@ export class LicensesService { }) ); } + + updateLicense(registrationId: string, licenseId: string, licenseOptions?: LicenseOptions) { + const payload: LicensePayloadJsonApi = { + data: { + type: 'draft_registrations', + id: registrationId, + relationships: { + license: { + data: { + id: licenseId, + type: 'licenses', + }, + }, + }, + attributes: { + ...(licenseOptions && { + node_license: { + copyright_holders: [licenseOptions.copyrightHolders], + year: licenseOptions.year, + }, + }), + }, + }, + }; + + return this.jsonApiService.patch( + `${this.apiUrl}/draft_registrations/${registrationId}/`, + payload + ); + } } diff --git a/src/app/features/registries/services/registration-contributors.service.ts b/src/app/features/registries/services/registration-contributors.service.ts new file mode 100644 index 000000000..b490ba880 --- /dev/null +++ b/src/app/features/registries/services/registration-contributors.service.ts @@ -0,0 +1,48 @@ +import { map, Observable } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { JsonApiResponse } from '@osf/core/models'; +import { JsonApiService } from '@osf/core/services'; +import { AddContributorType } from '@osf/shared/components/contributors/enums'; +import { ContributorsMapper } from '@osf/shared/components/contributors/mappers'; +import { ContributorAddModel, ContributorModel, ContributorResponse } from '@osf/shared/components/contributors/models'; + +import { environment } from 'src/environments/environment'; + +@Injectable() +export class RegistrationContributorsService { + private apiUrl = environment.apiUrl; + private readonly jsonApiService = inject(JsonApiService); + + getContributors(draftId: string): Observable { + return this.jsonApiService + .get>(`${this.apiUrl}/draft_registrations/${draftId}/contributors/`) + .pipe(map((contributors) => ContributorsMapper.fromResponse(contributors.data))); + } + + addContributor(draftId: string, data: ContributorAddModel): Observable { + const baseUrl = `${this.apiUrl}/draft_registrations/${draftId}/contributors/`; + const type = data.id ? AddContributorType.Registered : AddContributorType.Unregistered; + + const contributorData = { data: ContributorsMapper.toContributorAddRequest(data, type) }; + + return this.jsonApiService + .post(baseUrl, contributorData) + .pipe(map((contributor) => ContributorsMapper.fromContributorResponse(contributor))); + } + + updateContributor(draftId: string, data: ContributorModel): Observable { + const baseUrl = `${environment.apiUrl}/draft_registrations/${draftId}/contributors/${data.userId}`; + + const contributorData = { data: ContributorsMapper.toContributorAddRequest(data) }; + + return this.jsonApiService + .patch(baseUrl, contributorData) + .pipe(map((contributor) => ContributorsMapper.fromContributorResponse(contributor))); + } + + deleteContributor(draftId: string, contributorId: string): Observable { + return this.jsonApiService.delete(`${this.apiUrl}/draft_registrations/${draftId}/contributors/${contributorId}`); + } +} diff --git a/src/app/features/registries/services/registration-subjects.service.ts b/src/app/features/registries/services/registration-subjects.service.ts index 7f6f4f618..2b6209ccd 100644 --- a/src/app/features/registries/services/registration-subjects.service.ts +++ b/src/app/features/registries/services/registration-subjects.service.ts @@ -8,9 +8,7 @@ import { ISubjectsService, Subject, SubjectsResponseJsonApi } from '@osf/shared/ import { environment } from 'src/environments/environment'; -@Injectable({ - providedIn: 'root', -}) +@Injectable() export class RegistrationSubjectsService implements ISubjectsService { private apiUrl = environment.apiUrl; private readonly jsonApiService = inject(JsonApiService); diff --git a/src/app/features/registries/services/registries.service.ts b/src/app/features/registries/services/registries.service.ts index 5a89f9e4c..f5869b161 100644 --- a/src/app/features/registries/services/registries.service.ts +++ b/src/app/features/registries/services/registries.service.ts @@ -2,11 +2,7 @@ import { map, Observable } from 'rxjs'; import { inject, Injectable } from '@angular/core'; -import { JsonApiResponse } from '@osf/core/models'; import { JsonApiService } from '@osf/core/services'; -import { AddContributorType } from '@osf/shared/components/contributors/enums'; -import { ContributorsMapper } from '@osf/shared/components/contributors/mappers'; -import { ContributorAddModel, ContributorModel, ContributorResponse } from '@osf/shared/components/contributors/models'; import { PageSchemaMapper } from '../mappers'; import { RegistrationMapper } from '../mappers/registration.mapper'; @@ -81,35 +77,4 @@ export class RegistriesService { .get(`${this.apiUrl}/schemas/registrations/${registrationSchemaId}/schema_blocks/`) .pipe(map((response) => PageSchemaMapper.fromSchemaBlocksResponse(response))); } - - getContributors(draftId: string): Observable { - return this.jsonApiService - .get>(`${this.apiUrl}/draft_registrations/${draftId}/contributors/`) - .pipe(map((contributors) => ContributorsMapper.fromResponse(contributors.data))); - } - - addContributor(draftId: string, data: ContributorAddModel): Observable { - const baseUrl = `${this.apiUrl}/draft_registrations/${draftId}/contributors/`; - const type = data.id ? AddContributorType.Registered : AddContributorType.Unregistered; - - const contributorData = { data: ContributorsMapper.toContributorAddRequest(data, type) }; - - return this.jsonApiService - .post(baseUrl, contributorData) - .pipe(map((contributor) => ContributorsMapper.fromContributorResponse(contributor))); - } - - updateContributor(draftId: string, data: ContributorModel): Observable { - const baseUrl = `${environment.apiUrl}/draft_registrations/${draftId}/contributors/${data.userId}`; - - const contributorData = { data: ContributorsMapper.toContributorAddRequest(data) }; - - return this.jsonApiService - .patch(baseUrl, contributorData) - .pipe(map((contributor) => ContributorsMapper.fromContributorResponse(contributor))); - } - - deleteContributor(draftId: string, contributorId: string): Observable { - return this.jsonApiService.delete(`${this.apiUrl}/draft_registrations/${draftId}/contributors/${contributorId}`); - } } diff --git a/src/app/features/registries/store/default.state.ts b/src/app/features/registries/store/default.state.ts new file mode 100644 index 000000000..a9e3c4d4c --- /dev/null +++ b/src/app/features/registries/store/default.state.ts @@ -0,0 +1,45 @@ +import { RegistriesStateModel } from './registries.model'; + +export const DefaultState: RegistriesStateModel = { + providers: { + data: [], + isLoading: false, + error: null, + }, + projects: { + data: [], + isLoading: false, + error: null, + }, + draftRegistration: { + isLoading: false, + data: null, + isSubmitting: false, + error: null, + }, + contributorsList: { + data: [], + isLoading: false, + error: null, + }, + registries: { + data: [], + isLoading: false, + error: null, + }, + licenses: { + data: [], + isLoading: false, + error: null, + }, + registrationSubjects: { + data: [], + isLoading: false, + error: null, + }, + pagesSchema: { + data: [], + isLoading: false, + error: null, + }, +}; diff --git a/src/app/features/registries/store/handlers/contributors.handlers.ts b/src/app/features/registries/store/handlers/contributors.handlers.ts new file mode 100644 index 000000000..f228381c4 --- /dev/null +++ b/src/app/features/registries/store/handlers/contributors.handlers.ts @@ -0,0 +1,106 @@ +import { StateContext } from '@ngxs/store'; + +import { catchError, tap } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { handleSectionError } from '@osf/core/handlers/state-error.handler'; + +import { RegistrationContributorsService } from '../../services/registration-contributors.service'; +import { AddContributor, DeleteContributor, FetchContributors, UpdateContributor } from '../registries.actions'; +import { RegistriesStateModel } from '../registries.model'; + +@Injectable() +export class RegistrationContributorsHandlers { + contributorsService = inject(RegistrationContributorsService); + + fetchContributors(ctx: StateContext, action: FetchContributors) { + const state = ctx.getState(); + + ctx.patchState({ + contributorsList: { ...state.contributorsList, isLoading: true, error: null }, + }); + + return this.contributorsService.getContributors(action.draftId).pipe( + tap((contributors) => { + ctx.patchState({ + contributorsList: { + ...state.contributorsList, + data: contributors, + isLoading: false, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'contributorsList', error)) + ); + } + + addContributor(ctx: StateContext, action: AddContributor) { + const state = ctx.getState(); + + ctx.patchState({ + contributorsList: { ...state.contributorsList, isLoading: true, error: null }, + }); + + return this.contributorsService.addContributor(action.draftId, action.contributor).pipe( + tap((contributor) => { + const currentState = ctx.getState(); + + ctx.patchState({ + contributorsList: { + ...currentState.contributorsList, + data: [...currentState.contributorsList.data, contributor], + isLoading: false, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'contributorsList', error)) + ); + } + + updateContributor(ctx: StateContext, action: UpdateContributor) { + const state = ctx.getState(); + + ctx.patchState({ + contributorsList: { ...state.contributorsList, isLoading: true, error: null }, + }); + + return this.contributorsService.updateContributor(action.draftId, action.contributor).pipe( + tap((updatedContributor) => { + const currentState = ctx.getState(); + + ctx.patchState({ + contributorsList: { + ...currentState.contributorsList, + data: currentState.contributorsList.data.map((contributor) => + contributor.id === updatedContributor.id ? updatedContributor : contributor + ), + isLoading: false, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'contributorsList', error)) + ); + } + + deleteContributor(ctx: StateContext, action: DeleteContributor) { + const state = ctx.getState(); + + ctx.patchState({ + contributorsList: { ...state.contributorsList, isLoading: true, error: null }, + }); + + return this.contributorsService.deleteContributor(action.draftId, action.contributorId).pipe( + tap(() => { + ctx.patchState({ + contributorsList: { + ...state.contributorsList, + data: state.contributorsList.data.filter((contributor) => contributor.userId !== action.contributorId), + isLoading: false, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'contributorsList', error)) + ); + } +} diff --git a/src/app/features/registries/store/handlers/index.ts b/src/app/features/registries/store/handlers/index.ts new file mode 100644 index 000000000..383d0b6bc --- /dev/null +++ b/src/app/features/registries/store/handlers/index.ts @@ -0,0 +1,5 @@ +export * from './contributors.handlers'; +export * from './licenses.handlers'; +export * from './projects.handlers'; +export * from './providers.handlers'; +export * from './subjects.handlers'; diff --git a/src/app/features/registries/store/handlers/licenses.handlers.ts b/src/app/features/registries/store/handlers/licenses.handlers.ts new file mode 100644 index 000000000..28ee8b15d --- /dev/null +++ b/src/app/features/registries/store/handlers/licenses.handlers.ts @@ -0,0 +1,63 @@ +import { StateContext } from '@ngxs/store'; + +import { catchError, tap } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { handleSectionError } from '@osf/core/handlers'; + +import { LicensesService } from '../../services'; +import { SaveLicense } from '../registries.actions'; +import { RegistriesStateModel } from '../registries.model'; + +@Injectable() +export class LicensesHandlers { + licensesService = inject(LicensesService); + + fetchLicenses(ctx: StateContext) { + ctx.patchState({ + licenses: { + ...ctx.getState().licenses, + isLoading: true, + }, + }); + + return this.licensesService.getLicenses().pipe( + tap((licenses) => { + ctx.patchState({ + licenses: { + data: licenses, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'licenses', error)) + ); + } + + saveLicense(ctx: StateContext, { registrationId, licenseId, licenseOptions }: SaveLicense) { + const state = ctx.getState(); + ctx.patchState({ + licenses: { + ...state.licenses, + isLoading: true, + }, + }); + + return this.licensesService + .updateLicense(registrationId, licenseId, licenseOptions) + .pipe + // tap((response) => { + // ctx.patchState({ + // licenses: { + // data: response, + // isLoading: false, + // error: null, + // }, + // }); + // }), + // catchError((error) => handleSectionError(ctx, 'licenses', error)) + (); + } +} diff --git a/src/app/features/registries/store/handlers/projects.handlers.ts b/src/app/features/registries/store/handlers/projects.handlers.ts new file mode 100644 index 000000000..d0f161842 --- /dev/null +++ b/src/app/features/registries/store/handlers/projects.handlers.ts @@ -0,0 +1,38 @@ +import { StateContext } from '@ngxs/store'; + +import { inject, Injectable } from '@angular/core'; + +import { Project } from '../../models'; +import { ProjectsService } from '../../services'; +import { DefaultState } from '../default.state'; +import { RegistriesStateModel } from '../registries.model'; + +@Injectable() +export class ProjectsHandlers { + projectsService = inject(ProjectsService); + + getProjects({ patchState }: StateContext) { + patchState({ + projects: { + ...DefaultState.projects, + isLoading: true, + }, + }); + return this.projectsService.getProjects().subscribe({ + next: (projects: Project[]) => { + patchState({ + projects: { + data: projects, + isLoading: false, + error: null, + }, + }); + }, + error: (error) => { + patchState({ + projects: { ...DefaultState.projects, isLoading: false, error }, + }); + }, + }); + } +} diff --git a/src/app/features/registries/store/handlers/providers.handlers.ts b/src/app/features/registries/store/handlers/providers.handlers.ts new file mode 100644 index 000000000..c1c304065 --- /dev/null +++ b/src/app/features/registries/store/handlers/providers.handlers.ts @@ -0,0 +1,41 @@ +import { StateContext } from '@ngxs/store'; + +import { inject, Injectable } from '@angular/core'; + +import { ProvidersService } from '../../services'; +import { DefaultState } from '../default.state'; +import { RegistriesStateModel } from '../registries.model'; + +@Injectable() +export class ProvidersHandlers { + providersService = inject(ProvidersService); + + getProviders({ patchState }: StateContext) { + patchState({ + providers: { + ...DefaultState.providers, + isLoading: true, + }, + }); + return this.providersService.getProviders().subscribe({ + next: (providers) => { + patchState({ + providers: { + data: providers, + isLoading: false, + error: null, + }, + }); + }, + error: (error) => { + patchState({ + providers: { + ...DefaultState.providers, + isLoading: false, + error, + }, + }); + }, + }); + } +} diff --git a/src/app/features/registries/store/handlers/subjects.handlers.ts b/src/app/features/registries/store/handlers/subjects.handlers.ts new file mode 100644 index 000000000..7f06f3d63 --- /dev/null +++ b/src/app/features/registries/store/handlers/subjects.handlers.ts @@ -0,0 +1,64 @@ +import { StateContext } from '@ngxs/store'; + +import { catchError, tap } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { handleSectionError } from '@osf/core/handlers'; + +import { RegistrationSubjectsService } from '../../services'; +import { FetchRegistrationSubjects, UpdateRegistrationSubjects } from '../registries.actions'; +import { RegistriesStateModel } from '../registries.model'; + +@Injectable() +export class SubjectsHandlers { + subjectsService = inject(RegistrationSubjectsService); + + fetchRegistrationSubjects(ctx: StateContext, { registrationId }: FetchRegistrationSubjects) { + ctx.patchState({ + registrationSubjects: { + ...ctx.getState().registrationSubjects, + isLoading: true, + error: null, + }, + }); + + return this.subjectsService.getRegistrationSubjects(registrationId).pipe( + tap((subjects) => { + ctx.patchState({ + registrationSubjects: { + data: subjects, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'registrationSubjects', error)) + ); + } + + updateRegistrationSubjects( + ctx: StateContext, + { registrationId, subjects }: UpdateRegistrationSubjects + ) { + ctx.patchState({ + registrationSubjects: { + ...ctx.getState().registrationSubjects, + isLoading: true, + error: null, + }, + }); + return this.subjectsService.updateRegistrationSubjects(registrationId, subjects).pipe( + tap(() => { + ctx.patchState({ + registrationSubjects: { + data: subjects, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'registrationSubjects', error)) + ); + } +} diff --git a/src/app/features/registries/store/registries.actions.ts b/src/app/features/registries/store/registries.actions.ts index bc0e1579e..6de2aaa41 100644 --- a/src/app/features/registries/store/registries.actions.ts +++ b/src/app/features/registries/store/registries.actions.ts @@ -1,5 +1,5 @@ import { ContributorAddModel, ContributorModel } from '@osf/shared/components/contributors/models'; -import { Subject } from '@osf/shared/models'; +import { LicenseOptions, Subject } from '@osf/shared/models'; export class GetRegistries { static readonly type = '[Registries] Get Registries'; @@ -70,6 +70,15 @@ export class FetchLicenses { static readonly type = '[Registries] Fetch Licenses'; } +export class SaveLicense { + static readonly type = '[Registries] Save License'; + constructor( + public registrationId: string, + public licenseId: string, + public licenseOptions?: LicenseOptions + ) {} +} + export class FetchRegistrationSubjects { static readonly type = '[Registries] Fetch Registration Subjects'; constructor(public registrationId: string) {} diff --git a/src/app/features/registries/store/registries.model.ts b/src/app/features/registries/store/registries.model.ts index 2ce1f6202..b36e022de 100644 --- a/src/app/features/registries/store/registries.model.ts +++ b/src/app/features/registries/store/registries.model.ts @@ -1,7 +1,7 @@ import { ContributorModel } from '@osf/shared/components/contributors/models'; -import { AsyncStateModel, Resource, Subject } from '@shared/models'; +import { AsyncStateModel, License, Resource, Subject } from '@shared/models'; -import { License, PageSchema, Project, Provider } from '../models'; +import { PageSchema, Project, Provider } from '../models'; import { Registration } from '../models/registration.model'; export interface RegistriesStateModel { diff --git a/src/app/features/registries/store/registries.selectors.ts b/src/app/features/registries/store/registries.selectors.ts index 4ea91666a..c3fb6dcf2 100644 --- a/src/app/features/registries/store/registries.selectors.ts +++ b/src/app/features/registries/store/registries.selectors.ts @@ -1,8 +1,8 @@ import { Selector } from '@ngxs/store'; -import { Resource, Subject } from '@shared/models'; +import { License, Resource, Subject } from '@shared/models'; -import { License, PageSchema, Project, Provider, Registration } from '../models'; +import { PageSchema, Project, Provider, Registration } from '../models'; import { RegistriesStateModel } from './registries.model'; import { RegistriesState } from './registries.state'; @@ -58,6 +58,11 @@ export class RegistriesSelectors { return state.licenses.data; } + @Selector([RegistriesState]) + static getSelectedLicense(state: RegistriesStateModel) { + return state.draftRegistration.data?.license || null; + } + @Selector([RegistriesState]) static getPagesSchema(state: RegistriesStateModel): PageSchema[] { return state.pagesSchema.data; diff --git a/src/app/features/registries/store/registries.state.ts b/src/app/features/registries/store/registries.state.ts index 6f01d6cf9..4b04264e6 100644 --- a/src/app/features/registries/store/registries.state.ts +++ b/src/app/features/registries/store/registries.state.ts @@ -9,15 +9,14 @@ import { ResourceTab } from '@osf/shared/enums'; import { SearchService } from '@osf/shared/services'; import { getResourceTypes } from '@osf/shared/utils'; -import { Project } from '../models'; -import { - LicensesService, - ProjectsService, - ProvidersService, - RegistrationSubjectsService, - RegistriesService, -} from '../services'; - +import { RegistriesService } from '../services'; + +import { RegistrationContributorsHandlers } from './handlers/contributors.handlers'; +import { LicensesHandlers } from './handlers/licenses.handlers'; +import { ProjectsHandlers } from './handlers/projects.handlers'; +import { ProvidersHandlers } from './handlers/providers.handlers'; +import { SubjectsHandlers } from './handlers/subjects.handlers'; +import { DefaultState } from './default.state'; import { AddContributor, CreateDraft, @@ -31,55 +30,12 @@ import { GetProjects, GetProviders, GetRegistries, + SaveLicense, UpdateContributor, UpdateRegistrationSubjects, } from './registries.actions'; import { RegistriesStateModel } from './registries.model'; -const DefaultState: RegistriesStateModel = { - providers: { - data: [], - isLoading: false, - error: null, - }, - projects: { - data: [], - isLoading: false, - error: null, - }, - draftRegistration: { - isLoading: false, - data: null, - isSubmitting: false, - error: null, - }, - contributorsList: { - data: [], - isLoading: false, - error: null, - }, - registries: { - data: [], - isLoading: false, - error: null, - }, - licenses: { - data: [], - isLoading: false, - error: null, - }, - registrationSubjects: { - data: [], - isLoading: false, - error: null, - }, - pagesSchema: { - data: [], - isLoading: false, - error: null, - }, -}; - @State({ name: 'registries', defaults: { ...DefaultState }, @@ -87,11 +43,13 @@ const DefaultState: RegistriesStateModel = { @Injectable() export class RegistriesState { searchService = inject(SearchService); - providersService = inject(ProvidersService); - projectsService = inject(ProjectsService); registriesService = inject(RegistriesService); - licensesService = inject(LicensesService); - subjectsService = inject(RegistrationSubjectsService); + + providersHandler = inject(ProvidersHandlers); + projectsHandler = inject(ProjectsHandlers); + licensesHandler = inject(LicensesHandlers); + subjectsHandler = inject(SubjectsHandlers); + contributorsHandler = inject(RegistrationContributorsHandlers); @Action(GetRegistries) getRegistries(ctx: StateContext) { @@ -120,59 +78,13 @@ export class RegistriesState { } @Action(GetProjects) - getProjects({ patchState }: StateContext) { - patchState({ - projects: { - ...DefaultState.projects, - isLoading: true, - }, - }); - return this.projectsService.getProjects().subscribe({ - next: (projects: Project[]) => { - patchState({ - projects: { - data: projects, - isLoading: false, - error: null, - }, - }); - }, - error: (error) => { - patchState({ - projects: { ...DefaultState.projects, isLoading: false, error }, - }); - }, - }); + getProjects(ctx: StateContext) { + return this.projectsHandler.getProjects(ctx); } @Action(GetProviders) - getProviders({ patchState }: StateContext) { - patchState({ - providers: { - ...DefaultState.providers, - isLoading: true, - }, - }); - return this.providersService.getProviders().subscribe({ - next: (providers) => { - patchState({ - providers: { - data: providers, - isLoading: false, - error: null, - }, - }); - }, - error: (error) => { - patchState({ - providers: { - ...DefaultState.providers, - isLoading: false, - error, - }, - }); - }, - }); + getProviders(ctx: StateContext) { + return this.providersHandler.getProviders(ctx); } @Action(CreateDraft) @@ -279,143 +191,37 @@ export class RegistriesState { @Action(FetchContributors) fetchContributors(ctx: StateContext, action: FetchContributors) { - const state = ctx.getState(); - - ctx.patchState({ - contributorsList: { ...state.contributorsList, isLoading: true, error: null }, - }); - - return this.registriesService.getContributors(action.draftId).pipe( - tap((contributors) => { - ctx.patchState({ - contributorsList: { - ...state.contributorsList, - data: contributors, - isLoading: false, - }, - }); - }), - catchError((error) => handleSectionError(ctx, 'contributorsList', error)) - ); + return this.contributorsHandler.fetchContributors(ctx, action); } @Action(AddContributor) addContributor(ctx: StateContext, action: AddContributor) { - const state = ctx.getState(); - - ctx.patchState({ - contributorsList: { ...state.contributorsList, isLoading: true, error: null }, - }); - - return this.registriesService.addContributor(action.draftId, action.contributor).pipe( - tap((contributor) => { - const currentState = ctx.getState(); - - ctx.patchState({ - contributorsList: { - ...currentState.contributorsList, - data: [...currentState.contributorsList.data, contributor], - isLoading: false, - }, - }); - }), - catchError((error) => handleSectionError(ctx, 'contributorsList', error)) - ); + return this.contributorsHandler.addContributor(ctx, action); } @Action(UpdateContributor) updateContributor(ctx: StateContext, action: UpdateContributor) { - const state = ctx.getState(); - - ctx.patchState({ - contributorsList: { ...state.contributorsList, isLoading: true, error: null }, - }); - - return this.registriesService.updateContributor(action.draftId, action.contributor).pipe( - tap((updatedContributor) => { - const currentState = ctx.getState(); - - ctx.patchState({ - contributorsList: { - ...currentState.contributorsList, - data: currentState.contributorsList.data.map((contributor) => - contributor.id === updatedContributor.id ? updatedContributor : contributor - ), - isLoading: false, - }, - }); - }), - catchError((error) => handleSectionError(ctx, 'contributorsList', error)) - ); + return this.contributorsHandler.updateContributor(ctx, action); } @Action(DeleteContributor) deleteContributor(ctx: StateContext, action: DeleteContributor) { - const state = ctx.getState(); - - ctx.patchState({ - contributorsList: { ...state.contributorsList, isLoading: true, error: null }, - }); - - return this.registriesService.deleteContributor(action.draftId, action.contributorId).pipe( - tap(() => { - ctx.patchState({ - contributorsList: { - ...state.contributorsList, - data: state.contributorsList.data.filter((contributor) => contributor.userId !== action.contributorId), - isLoading: false, - }, - }); - }), - catchError((error) => handleSectionError(ctx, 'contributorsList', error)) - ); + return this.contributorsHandler.deleteContributor(ctx, action); } @Action(FetchLicenses) fetchLicenses(ctx: StateContext) { - ctx.patchState({ - licenses: { - ...ctx.getState().licenses, - isLoading: true, - }, - }); + return this.licensesHandler.fetchLicenses(ctx); + } - return this.licensesService.getLicenses().pipe( - tap((licenses) => { - ctx.patchState({ - licenses: { - data: licenses, - isLoading: false, - error: null, - }, - }); - }), - catchError((error) => handleSectionError(ctx, 'licenses', error)) - ); + @Action(SaveLicense) + saveLicense(ctx: StateContext, { registrationId, licenseId, licenseOptions }: SaveLicense) { + return this.licensesHandler.saveLicense(ctx, { registrationId, licenseId, licenseOptions }); } @Action(FetchRegistrationSubjects) fetchRegistrationSubjects(ctx: StateContext, { registrationId }: FetchRegistrationSubjects) { - ctx.patchState({ - registrationSubjects: { - ...ctx.getState().registrationSubjects, - isLoading: true, - error: null, - }, - }); - - return this.subjectsService.getRegistrationSubjects(registrationId).pipe( - tap((subjects) => { - ctx.patchState({ - registrationSubjects: { - data: subjects, - isLoading: false, - error: null, - }, - }); - }), - catchError((error) => handleSectionError(ctx, 'registrationSubjects', error)) - ); + return this.subjectsHandler.fetchRegistrationSubjects(ctx, { registrationId }); } @Action(UpdateRegistrationSubjects) @@ -423,24 +229,6 @@ export class RegistriesState { ctx: StateContext, { registrationId, subjects }: UpdateRegistrationSubjects ) { - ctx.patchState({ - registrationSubjects: { - ...ctx.getState().registrationSubjects, - isLoading: true, - error: null, - }, - }); - return this.subjectsService.updateRegistrationSubjects(registrationId, subjects).pipe( - tap(() => { - ctx.patchState({ - registrationSubjects: { - data: subjects, - isLoading: false, - error: null, - }, - }); - }), - catchError((error) => handleSectionError(ctx, 'registrationSubjects', error)) - ); + return this.subjectsHandler.updateRegistrationSubjects(ctx, { registrationId, subjects }); } }