From c75c85274ec6780f610af95e011e90d4d208ae53 Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Tue, 8 Jul 2025 18:47:47 +0300 Subject: [PATCH 1/8] feat(registries): work on review step --- .../custom-step/custom-step.component.html | 2 +- .../components/drafts/drafts.component.scss | 3 - .../new-registration.component.html | 126 ++++++++--------- .../components/review/review.component.html | 128 +++++++++++++++++- .../components/review/review.component.ts | 81 ++++++++++- .../registries/registries.component.html | 2 +- .../registries/registries.component.scss | 5 + .../registries/services/registries.service.ts | 31 +++++ .../registries/store/registries.actions.ts | 9 ++ .../registries/store/registries.state.ts | 25 ++++ src/assets/i18n/en.json | 10 +- 11 files changed, 349 insertions(+), 73 deletions(-) diff --git a/src/app/features/registries/components/custom-step/custom-step.component.html b/src/app/features/registries/components/custom-step/custom-step.component.html index f05e1d9c..45891a44 100644 --- a/src/app/features/registries/components/custom-step/custom-step.component.html +++ b/src/app/features/registries/components/custom-step/custom-step.component.html @@ -1,4 +1,4 @@ -
+
@if (currentPage()) {

{{ currentPage().title }}

diff --git a/src/app/features/registries/components/drafts/drafts.component.scss b/src/app/features/registries/components/drafts/drafts.component.scss index 683ae23f..e69de29b 100644 --- a/src/app/features/registries/components/drafts/drafts.component.scss +++ b/src/app/features/registries/components/drafts/drafts.component.scss @@ -1,3 +0,0 @@ -:host { - height: 100%; -} diff --git a/src/app/features/registries/components/new-registration/new-registration.component.html b/src/app/features/registries/components/new-registration/new-registration.component.html index d9b7ce6e..b324f4ef 100644 --- a/src/app/features/registries/components/new-registration/new-registration.component.html +++ b/src/app/features/registries/components/new-registration/new-registration.component.html @@ -1,73 +1,75 @@ - -
-

- {{ 'registries.new.infoText1' | translate }} - {{ 'common.links.clickHere' | translate }} - {{ 'registries.new.infoText2' | translate }} -

-
-
- -

{{ ('registries.new.steps.title' | translate) + '1' }}

-

{{ 'registries.new.steps.step1' | translate }}

-
- - -
-
-
- @if (fromProject) { +
+ +
+

+ {{ 'registries.new.infoText1' | translate }} + {{ 'common.links.clickHere' | translate }} + {{ 'registries.new.infoText2' | translate }} +

+
+
+ +

{{ ('registries.new.steps.title' | translate) + '1' }}

+

{{ 'registries.new.steps.step1' | translate }}

+
+ + +
+
+ + @if (fromProject) { + +

{{ ('registries.new.steps.title' | translate) + '2' }}

+

{{ 'registries.new.steps.step2' | translate }}

+

{{ 'registries.new.steps.step2InfoText' | translate }}

+
+ +
+
+ } -

{{ ('registries.new.steps.title' | translate) + '2' }}

-

{{ 'registries.new.steps.step2' | translate }}

-

{{ 'registries.new.steps.step2InfoText' | translate }}

+

{{ ('registries.new.steps.title' | translate) + (fromProject ? '3' : '2') }}

+

{{ 'registries.new.steps.step3' | translate }}

- } - -

{{ ('registries.new.steps.title' | translate) + (fromProject ? '3' : '2') }}

-

{{ 'registries.new.steps.step3' | translate }}

-
- +
-
-
- -
- + +
diff --git a/src/app/features/registries/components/review/review.component.html b/src/app/features/registries/components/review/review.component.html index 27a7cd2a..bbe4bd56 100644 --- a/src/app/features/registries/components/review/review.component.html +++ b/src/app/features/registries/components/review/review.component.html @@ -1 +1,127 @@ -

review works!

+
+ +

{{ 'navigation.registration.metadata' | translate }}

+ +
+

{{ 'common.labels.title' | translate }}

+

{{ draftRegistration()?.title }}

+ @if (!draftRegistration()?.title) { +

{{ 'common.labels.title' | translate }}

+

{{ 'common.labels.noData' | translate }}

+ + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + + } +
+
+

{{ 'common.labels.description' | translate }}

+

{{ draftRegistration()?.description }}

+ @if (!draftRegistration()?.description) { +

{{ 'common.labels.noData' | translate }}

+ + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + + } +
+ +
+

{{ 'navigation.registration.contributors' | translate }}

+
+ @for (contributor of contributors(); let last = $last; track contributor.id) { + + {{ contributor.fullName }} + @if (!last) { + , + } + + } +
+
+
+

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

+

{{ draftRegistration()?.license?.id }}

+ @if (!draftRegistration()?.license) { +

{{ 'common.labels.noData' | translate }}

+ + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + + } +
+
+

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

+
+ @for (subject of subjects(); track subject.id) { + + } +
+ @if (!subjects().length) { +

{{ 'common.labels.noData' | translate }}

+ + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + + } +
+ +
+

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

+
+ @for (tag of draftRegistration()?.tags; track tag) { + + } +
+ @if (!draftRegistration()?.tags?.length) { +

{{ 'common.labels.noData' | translate }}

+ } +
+
+ @for (page of pages(); track page.id) { + +

{{ page.title }}

+ + @if (page.description) { +

{{ page.description }}

+ } + + @if (page.questions?.length) { + @for (question of page.questions; track question.responseKey) { +
+

{{ question.displayText }}

+ @if (stepsData()[question.responseKey!]) { + @if (question.fieldType === FieldType.Checkbox) { + @for (option of stepsData()[question.responseKey!]; track option) { +

{{ option }}

+ } + } @else { +

{{ stepsData()[question.responseKey!] }}

+ } + } @else { + @if (question.fieldType === FieldType.File) { +

{{ 'common.labels.noFiles' | translate }}

+ } @else { +

{{ 'common.labels.noData' | translate }}

+ } + @if (question.required) { + + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + + } + } +
+ } + } +
+ } + +
+ + + + +
+
diff --git a/src/app/features/registries/components/review/review.component.ts b/src/app/features/registries/components/review/review.component.ts index e38cb2e4..0fab7c1e 100644 --- a/src/app/features/registries/components/review/review.component.ts +++ b/src/app/features/registries/components/review/review.component.ts @@ -1,10 +1,85 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { createDispatchMap, select } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; + +import { Button } from 'primeng/button'; +import { Card } from 'primeng/card'; +import { Message } from 'primeng/message'; +import { Tag } from 'primeng/tag'; + +import { map, of } from 'rxjs'; + +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { ActivatedRoute, Router, RouterLink } from '@angular/router'; + +import { INPUT_VALIDATION_MESSAGES } from '@osf/shared/constants'; +import { ResourceType } from '@osf/shared/enums'; +import { CustomConfirmationService } from '@osf/shared/services'; +import { ContributorsSelectors, GetAllContributors } from '@osf/shared/stores'; + +import { FieldType } from '../../enums'; +import { DeleteDraft, FetchRegistrationSubjects, RegisterDraft, RegistriesSelectors } from '../../store'; @Component({ selector: 'osf-review', - imports: [], + imports: [TranslatePipe, Card, Message, RouterLink, Tag, Button], templateUrl: './review.component.html', styleUrl: './review.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ReviewComponent {} +export class ReviewComponent { + private readonly router = inject(Router); + private readonly route = inject(ActivatedRoute); + private readonly customConfirmationService = inject(CustomConfirmationService); + + protected readonly pages = select(RegistriesSelectors.getPagesSchema); + protected readonly draftRegistration = select(RegistriesSelectors.getDraftRegistration); + protected readonly stepsData = select(RegistriesSelectors.getStepsData); + readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; + protected readonly contributors = select(ContributorsSelectors.getContributors); + protected readonly subjects = select(RegistriesSelectors.getSelectedSubjects); + protected readonly FieldType = FieldType; + + protected actions = createDispatchMap({ + getContributors: GetAllContributors, + getSubjects: FetchRegistrationSubjects, + deleteDraft: DeleteDraft, + registerDraft: RegisterDraft, + }); + private readonly draftId = toSignal(this.route.params.pipe(map((params) => params['id'])) ?? of(undefined)); + + constructor() { + if (!this.contributors()?.length) { + this.actions.getContributors(this.draftId(), ResourceType.DraftRegistration); + } + if (!this.subjects()?.length) { + this.actions.getSubjects(this.draftId()); + } + } + + goBack(): void { + const previousStep = this.pages().length; + this.router.navigate(['../', previousStep], { relativeTo: this.route }); + } + + deleteDraft(): void { + this.customConfirmationService.confirmDelete({ + headerKey: 'registries.deleteDraft', + messageKey: 'registries.confirmDeleteDraft', + onConfirm: () => { + this.actions.deleteDraft(this.draftId()).subscribe({ + next: () => { + this.router.navigateByUrl('/registries/new'); + }, + }); + }, + }); + } + + register() { + const draftId = this.draftId(); + console.log('Registering draft with ID:', draftId); + this.actions.registerDraft(draftId, ''); + } +} diff --git a/src/app/features/registries/registries.component.html b/src/app/features/registries/registries.component.html index 21c4dd5c..69117c1c 100644 --- a/src/app/features/registries/registries.component.html +++ b/src/app/features/registries/registries.component.html @@ -1,3 +1,3 @@ -
+
diff --git a/src/app/features/registries/registries.component.scss b/src/app/features/registries/registries.component.scss index e69de29b..da0c027b 100644 --- a/src/app/features/registries/registries.component.scss +++ b/src/app/features/registries/registries.component.scss @@ -0,0 +1,5 @@ +:host { + display: flex; + flex-direction: column; + flex: 1; +} diff --git a/src/app/features/registries/services/registries.service.ts b/src/app/features/registries/services/registries.service.ts index 1fc0c1c8..a3a58d9a 100644 --- a/src/app/features/registries/services/registries.service.ts +++ b/src/app/features/registries/services/registries.service.ts @@ -81,6 +81,37 @@ export class RegistriesService { return this.jsonApiService.delete(`${this.apiUrl}/draft_registrations/${draftId}/`); } + registerDraft(draftId: string, embargoDate: string, projectId?: string): Observable { + const payload = { + data: { + type: 'registrations', + attributes: { + embargo_end_date: embargoDate, + draft_registration_id: draftId, + }, + relationships: { + registered_from: projectId + ? { + data: { + type: 'nodes', + id: projectId, + }, + } + : undefined, + }, + provider: { + data: { + type: 'registration-providers', + id: 'osf', // Assuming 'osf' is the default provider + }, + }, + }, + }; + return this.jsonApiService + .post(`${this.apiUrl}/registrations`, payload) + .pipe(map((response) => RegistrationMapper.fromRegistrationResponse(response.data))); + } + getSchemaBlocks(registrationSchemaId: string): Observable { return this.jsonApiService .get(`${this.apiUrl}/schemas/registrations/${registrationSchemaId}/schema_blocks/`) diff --git a/src/app/features/registries/store/registries.actions.ts b/src/app/features/registries/store/registries.actions.ts index f17b3f58..cedc0103 100644 --- a/src/app/features/registries/store/registries.actions.ts +++ b/src/app/features/registries/store/registries.actions.ts @@ -38,6 +38,15 @@ export class DeleteDraft { constructor(public draftId: string) {} } +export class RegisterDraft { + static readonly type = '[Registries] Register Draft Registration'; + constructor( + public draftId: string, + public embargoDate: string, + public projectId?: string + ) {} +} + export class FetchSchemaBlocks { static readonly type = '[Registries] Fetch Schema Blocks'; constructor(public registrationSchemaId: string) {} diff --git a/src/app/features/registries/store/registries.state.ts b/src/app/features/registries/store/registries.state.ts index 0d11eed7..ba3db5f6 100644 --- a/src/app/features/registries/store/registries.state.ts +++ b/src/app/features/registries/store/registries.state.ts @@ -26,6 +26,7 @@ import { GetProjects, GetProviders, GetRegistries, + RegisterDraft, SaveLicense, UpdateDraft, UpdateRegistrationSubjects, @@ -189,6 +190,30 @@ export class RegistriesState { ); } + @Action(RegisterDraft) + registerDraft(ctx: StateContext, { draftId, embargoDate, projectId }: RegisterDraft) { + ctx.patchState({ + draftRegistration: { + ...ctx.getState().draftRegistration, + isSubmitting: true, + }, + }); + + return this.registriesService.registerDraft(draftId, embargoDate, projectId).pipe( + tap((registration) => { + ctx.patchState({ + draftRegistration: { + data: { ...registration }, + isLoading: false, + isSubmitting: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'draftRegistration', error)) + ); + } + @Action(FetchSchemaBlocks) fetchSchemaBlocks(ctx: StateContext, action: FetchSchemaBlocks) { const state = ctx.getState(); diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 4b49b8d7..dfb6aca4 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -69,7 +69,9 @@ "description": "Description", "year": "Year", "optional": "Optional", - "makePublic": "Make Public" + "makePublic": "Make Public", + "noData": "No data", + "noFiles": "No files selected" }, "deleteConfirmation": { "header": "Delete", @@ -1636,7 +1638,8 @@ }, "review": { "step": "Review", - "title": "Review Registration" + "title": "Review Registration", + "register": "Register" } }, "registry": { @@ -1759,6 +1762,9 @@ "noSelected": "No subjects selected", "searchSubjects": "Search subjects", "noSubject": "No subjects found" + }, + "tags": { + "title": "Tags" } }, "resourceCard": { From 69529a942a76a7af04e45a938c99e79d419be753 Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Wed, 9 Jul 2025 18:46:39 +0300 Subject: [PATCH 2/8] feat(registries): finish review step --- ...confirm-registration-dialog.component.html | 57 +++++++++++++++ ...confirm-registration-dialog.component.scss | 0 ...firm-registration-dialog.component.spec.ts | 22 ++++++ .../confirm-registration-dialog.component.ts | 72 +++++++++++++++++++ .../custom-step/custom-step.component.scss | 4 ++ .../components/drafts/drafts.component.scss | 5 ++ .../components/drafts/drafts.component.ts | 2 +- .../components/metadata/metadata.component.ts | 4 +- .../registries-subjects.component.html | 2 +- .../components/review/review.component.html | 6 +- .../components/review/review.component.ts | 46 +++++++++--- src/app/features/registries/enums/index.ts | 1 + .../registries/enums/submit-type.enum.ts | 4 ++ .../registries/mappers/registration.mapper.ts | 13 +++- src/app/features/registries/models/index.ts | 1 - .../models/registration-json-api.model.ts | 6 ++ .../registries/registries.component.html | 2 +- .../registries/services/registries.service.ts | 16 ++--- .../registries/store/default.state.ts | 6 ++ .../registries/store/registries.model.ts | 5 +- .../registries/store/registries.selectors.ts | 10 ++- .../registries/store/registries.state.ts | 6 +- .../registration/draft-registration.model.ts} | 5 +- src/app/shared/models/registration/index.ts | 2 + .../models/registration/registration.model.ts | 67 +++++++++++++++++ 25 files changed, 327 insertions(+), 37 deletions(-) create mode 100644 src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.html create mode 100644 src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.scss create mode 100644 src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.spec.ts create mode 100644 src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.ts create mode 100644 src/app/features/registries/enums/submit-type.enum.ts rename src/app/{features/registries/models/registration.model.ts => shared/models/registration/draft-registration.model.ts} (70%) create mode 100644 src/app/shared/models/registration/index.ts create mode 100644 src/app/shared/models/registration/registration.model.ts diff --git a/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.html b/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.html new file mode 100644 index 00000000..ce3f4ae6 --- /dev/null +++ b/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.html @@ -0,0 +1,57 @@ +

{{ 'registries.review.confirmation.remember' | translate }}

+
    +
  • {{ 'registries.review.confirmation.text1' | translate }}
  • +
  • {{ 'registries.review.confirmation.text2' | translate }}
  • +
  • {{ 'registries.review.confirmation.text3' | translate }}
  • +
  • {{ 'registries.review.confirmation.text4' | translate }}
  • +
+
+
+ + +
+
+ + +
+ @if (showDateControl) { + + } +
+ + +
+ diff --git a/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.scss b/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.spec.ts b/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.spec.ts new file mode 100644 index 00000000..37621d6e --- /dev/null +++ b/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConfirmRegistrationDialogComponent } from './confirm-registration-dialog.component'; + +describe('ConfirmRegistrationDialogComponent', () => { + let component: ConfirmRegistrationDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ConfirmRegistrationDialogComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ConfirmRegistrationDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.ts b/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.ts new file mode 100644 index 00000000..26a9fa96 --- /dev/null +++ b/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.ts @@ -0,0 +1,72 @@ +import { createDispatchMap, select } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; + +import { Button } from 'primeng/button'; +import { DatePicker } from 'primeng/datepicker'; +import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog'; +import { RadioButton } from 'primeng/radiobutton'; + +import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core'; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; + +import { SubmitType } from '../../enums'; +import { RegisterDraft, RegistriesSelectors } from '../../store'; + +@Component({ + selector: 'osf-confirm-registration-dialog', + imports: [Button, TranslatePipe, ReactiveFormsModule, RadioButton, DatePicker], + templateUrl: './confirm-registration-dialog.component.html', + styleUrl: './confirm-registration-dialog.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ConfirmRegistrationDialogComponent { + protected readonly dialogRef = inject(DynamicDialogRef); + private readonly fb = inject(FormBuilder); + readonly config = inject(DynamicDialogConfig); + + protected readonly isRegistrationSubmitting = select(RegistriesSelectors.isRegistrationSubmitting); + protected actions = createDispatchMap({ + registerDraft: RegisterDraft, + }); + SubmitType = SubmitType; + showDateControl = false; + minEmbargoDate = computed(() => { + const date = new Date(); + date.setDate(date.getDate() + 3); + return date; + }); + + form: FormGroup = this.fb.group({ + submitOption: [null, Validators.required], + embargoDate: [{ value: null, disabled: true }], + }); + + constructor() { + this.form.get('submitOption')!.valueChanges.subscribe((value) => { + this.showDateControl = value === SubmitType.Embargo; + const dateControl = this.form.get('embargoDate'); + + if (this.showDateControl) { + dateControl!.enable(); + dateControl!.setValidators(Validators.required); // make date required + } else { + dateControl!.disable(); + dateControl!.clearValidators(); + dateControl!.reset(); + } + + dateControl!.updateValueAndValidity(); + }); + } + + submit(): void { + this.actions + .registerDraft(this.config.data.draftId, this.form.value.embargoDate, this.config.data.projectId) + .subscribe({ + next: () => { + this.dialogRef.close(); + }, + }); + } +} diff --git a/src/app/features/registries/components/custom-step/custom-step.component.scss b/src/app/features/registries/components/custom-step/custom-step.component.scss index e69de29b..ffc4f1d0 100644 --- a/src/app/features/registries/components/custom-step/custom-step.component.scss +++ b/src/app/features/registries/components/custom-step/custom-step.component.scss @@ -0,0 +1,4 @@ +:host { + flex: 1; + background: var(--white); +} diff --git a/src/app/features/registries/components/drafts/drafts.component.scss b/src/app/features/registries/components/drafts/drafts.component.scss index e69de29b..da0c027b 100644 --- a/src/app/features/registries/components/drafts/drafts.component.scss +++ b/src/app/features/registries/components/drafts/drafts.component.scss @@ -0,0 +1,5 @@ +:host { + display: flex; + flex-direction: column; + flex: 1; +} diff --git a/src/app/features/registries/components/drafts/drafts.component.ts b/src/app/features/registries/components/drafts/drafts.component.ts index fffaa6c5..8898fcbb 100644 --- a/src/app/features/registries/components/drafts/drafts.component.ts +++ b/src/app/features/registries/components/drafts/drafts.component.ts @@ -102,7 +102,7 @@ export class DraftsComponent { } effect(() => { const registrationSchemaId = this.draftRegistration()?.registrationSchemaId; - if (registrationSchemaId && !this.pages().length) { + if (registrationSchemaId) { this.actions .getSchemaBlocks(registrationSchemaId || '') .pipe( diff --git a/src/app/features/registries/components/metadata/metadata.component.ts b/src/app/features/registries/components/metadata/metadata.component.ts index 55a52a0f..4e565600 100644 --- a/src/app/features/registries/components/metadata/metadata.component.ts +++ b/src/app/features/registries/components/metadata/metadata.component.ts @@ -16,11 +16,11 @@ import { ActivatedRoute, Router } from '@angular/router'; import { TextInputComponent } from '@osf/shared/components'; import { INPUT_VALIDATION_MESSAGES, InputLimits } from '@osf/shared/constants'; import { SubjectModel } from '@osf/shared/models'; +import { DraftRegistrationModel } from '@osf/shared/models/registration'; import { CustomConfirmationService } from '@osf/shared/services'; import { SubjectsSelectors } from '@osf/shared/stores'; import { CustomValidators, findChangedFields } from '@osf/shared/utils'; -import { Registration } from '../../models'; import { DeleteDraft, RegistriesSelectors, UpdateDraft, UpdateStepValidation } from '../../store'; import { ContributorsComponent } from './contributors/contributors.component'; @@ -92,7 +92,7 @@ export class MetadataComponent implements OnDestroy { }); } - private initForm(data: Registration): void { + private initForm(data: DraftRegistrationModel): void { this.metadataForm.patchValue({ title: data.title, description: data.description, 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 0136d2f4..17d8145e 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,4 +1,4 @@ - + {{ question.displayText }} > - +
diff --git a/src/app/features/registries/components/review/review.component.ts b/src/app/features/registries/components/review/review.component.ts index 0fab7c1e..4a2c3b11 100644 --- a/src/app/features/registries/components/review/review.component.ts +++ b/src/app/features/registries/components/review/review.component.ts @@ -1,9 +1,10 @@ import { createDispatchMap, select } from '@ngxs/store'; -import { TranslatePipe } from '@ngx-translate/core'; +import { TranslatePipe, TranslateService } from '@ngx-translate/core'; import { Button } from 'primeng/button'; import { Card } from 'primeng/card'; +import { DialogService } from 'primeng/dynamicdialog'; import { Message } from 'primeng/message'; import { Tag } from 'primeng/tag'; @@ -15,11 +16,17 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { INPUT_VALIDATION_MESSAGES } from '@osf/shared/constants'; import { ResourceType } from '@osf/shared/enums'; -import { CustomConfirmationService } from '@osf/shared/services'; -import { ContributorsSelectors, GetAllContributors } from '@osf/shared/stores'; +import { CustomConfirmationService, ToastService } from '@osf/shared/services'; +import { + ContributorsSelectors, + FetchSelectedSubjects, + GetAllContributors, + SubjectsSelectors, +} from '@osf/shared/stores'; import { FieldType } from '../../enums'; -import { DeleteDraft, FetchRegistrationSubjects, RegisterDraft, RegistriesSelectors } from '../../store'; +import { DeleteDraft, RegisterDraft, RegistriesSelectors } from '../../store'; +import { ConfirmRegistrationDialogComponent } from '../confirm-registration-dialog/confirm-registration-dialog.component'; @Component({ selector: 'osf-review', @@ -27,23 +34,27 @@ import { DeleteDraft, FetchRegistrationSubjects, RegisterDraft, RegistriesSelect templateUrl: './review.component.html', styleUrl: './review.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, + providers: [DialogService], }) export class ReviewComponent { private readonly router = inject(Router); private readonly route = inject(ActivatedRoute); private readonly customConfirmationService = inject(CustomConfirmationService); + private readonly dialogService = inject(DialogService); + private readonly translateService = inject(TranslateService); + private readonly toastService = inject(ToastService); protected readonly pages = select(RegistriesSelectors.getPagesSchema); protected readonly draftRegistration = select(RegistriesSelectors.getDraftRegistration); protected readonly stepsData = select(RegistriesSelectors.getStepsData); readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; protected readonly contributors = select(ContributorsSelectors.getContributors); - protected readonly subjects = select(RegistriesSelectors.getSelectedSubjects); + protected readonly subjects = select(SubjectsSelectors.getSelectedSubjects); protected readonly FieldType = FieldType; protected actions = createDispatchMap({ getContributors: GetAllContributors, - getSubjects: FetchRegistrationSubjects, + getSubjects: FetchSelectedSubjects, deleteDraft: DeleteDraft, registerDraft: RegisterDraft, }); @@ -54,7 +65,7 @@ export class ReviewComponent { this.actions.getContributors(this.draftId(), ResourceType.DraftRegistration); } if (!this.subjects()?.length) { - this.actions.getSubjects(this.draftId()); + this.actions.getSubjects(this.draftId(), ResourceType.DraftRegistration); } } @@ -77,9 +88,22 @@ export class ReviewComponent { }); } - register() { - const draftId = this.draftId(); - console.log('Registering draft with ID:', draftId); - this.actions.registerDraft(draftId, ''); + confirmRegistration(): void { + this.dialogService + .open(ConfirmRegistrationDialogComponent, { + width: '552px', + focusOnShow: false, + header: this.translateService.instant('registries.review.confirmation.title'), + closeOnEscape: true, + modal: true, + data: { + draftId: this.draftId(), + projectId: this.draftRegistration()?.branchedFrom, + }, + }) + .onClose.subscribe(() => { + this.toastService.showSuccess('registries.review.confirmation.successMessage'); + // [NM] TODO: Navigate to the newly created registration page + }); } } diff --git a/src/app/features/registries/enums/index.ts b/src/app/features/registries/enums/index.ts index 609b3286..f7bf7346 100644 --- a/src/app/features/registries/enums/index.ts +++ b/src/app/features/registries/enums/index.ts @@ -1,2 +1,3 @@ export * from './block-type.enum'; export * from './field-type.enum'; +export * from './submit-type.enum'; diff --git a/src/app/features/registries/enums/submit-type.enum.ts b/src/app/features/registries/enums/submit-type.enum.ts new file mode 100644 index 00000000..876d69e3 --- /dev/null +++ b/src/app/features/registries/enums/submit-type.enum.ts @@ -0,0 +1,4 @@ +export enum SubmitType { + Public = 'public', + Embargo = 'embargo', +} diff --git a/src/app/features/registries/mappers/registration.mapper.ts b/src/app/features/registries/mappers/registration.mapper.ts index 6111229f..8e548e51 100644 --- a/src/app/features/registries/mappers/registration.mapper.ts +++ b/src/app/features/registries/mappers/registration.mapper.ts @@ -1,8 +1,9 @@ +import { DraftRegistrationModel, RegistrationModel } from '@osf/shared/models/registration'; + import { RegistrationDataJsonApi } from '../models'; -import { Registration } from '../models/registration.model'; export class RegistrationMapper { - static fromRegistrationResponse(response: RegistrationDataJsonApi): Registration { + static fromDraftRegistrationResponse(response: RegistrationDataJsonApi): DraftRegistrationModel { return { id: response.id, title: response.attributes.title, @@ -19,6 +20,14 @@ export class RegistrationMapper { }, tags: response.attributes.tags || [], stepsData: response.attributes.registration_responses || {}, + branchedFrom: response.relationships.branched_from?.data?.id, }; } + + static fromRegistrationResponse(response: RegistrationDataJsonApi): RegistrationModel { + return { + id: response.id, + type: 'registration', + } as RegistrationModel; + } } diff --git a/src/app/features/registries/models/index.ts b/src/app/features/registries/models/index.ts index 4942a784..b912472d 100644 --- a/src/app/features/registries/models/index.ts +++ b/src/app/features/registries/models/index.ts @@ -3,6 +3,5 @@ export * from './project'; export * from './projects-json-api.model'; export * from './provider.model'; export * from './providers-json-api.model'; -export * from './registration.model'; export * from './registration-json-api.model'; export * from './schema-blocks-json-api.model'; 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 59742ccf..21c01315 100644 --- a/src/app/features/registries/models/registration-json-api.model.ts +++ b/src/app/features/registries/models/registration-json-api.model.ts @@ -41,6 +41,12 @@ export interface RegistrationRelationshipsJsonApi { type: 'licenses'; }; }; + branched_from?: { + data: { + id: string; + type: 'nodes'; + }; + }; } export interface RegistrationPayloadJsonApi { diff --git a/src/app/features/registries/registries.component.html b/src/app/features/registries/registries.component.html index 69117c1c..09e739f5 100644 --- a/src/app/features/registries/registries.component.html +++ b/src/app/features/registries/registries.component.html @@ -1,3 +1,3 @@ -
+
diff --git a/src/app/features/registries/services/registries.service.ts b/src/app/features/registries/services/registries.service.ts index a3a58d9a..62b72b6e 100644 --- a/src/app/features/registries/services/registries.service.ts +++ b/src/app/features/registries/services/registries.service.ts @@ -3,12 +3,12 @@ import { map, Observable } from 'rxjs'; import { inject, Injectable } from '@angular/core'; import { JsonApiService } from '@osf/core/services'; +import { DraftRegistrationModel, RegistrationModel } from '@osf/shared/models/registration'; import { PageSchemaMapper } from '../mappers'; import { RegistrationMapper } from '../mappers/registration.mapper'; import { PageSchema, - Registration, RegistrationAttributesJsonApi, RegistrationDataJsonApi, RegistrationRelationshipsJsonApi, @@ -25,7 +25,7 @@ export class RegistriesService { private apiUrl = environment.apiUrl; private readonly jsonApiService = inject(JsonApiService); - createDraft(registrationSchemaId: string, projectId?: string | undefined): Observable { + createDraft(registrationSchemaId: string, projectId?: string | undefined): Observable { const payload = { data: { type: 'draft_registrations', @@ -49,20 +49,20 @@ export class RegistriesService { }; return this.jsonApiService .post(`${this.apiUrl}/draft_registrations/`, payload) - .pipe(map((response) => RegistrationMapper.fromRegistrationResponse(response.data))); + .pipe(map((response) => RegistrationMapper.fromDraftRegistrationResponse(response.data))); } - getDraft(draftId: string): Observable { + getDraft(draftId: string): Observable { return this.jsonApiService .get(`${this.apiUrl}/draft_registrations/${draftId}/`) - .pipe(map((response) => RegistrationMapper.fromRegistrationResponse(response.data))); + .pipe(map((response) => RegistrationMapper.fromDraftRegistrationResponse(response.data))); } updateDraft( id: string, attributes: Partial, relationships?: Partial - ): Observable { + ): Observable { const payload = { data: { id, @@ -74,14 +74,14 @@ export class RegistriesService { return this.jsonApiService .patch(`${this.apiUrl}/draft_registrations/${id}/`, payload) - .pipe(map((response) => RegistrationMapper.fromRegistrationResponse(response))); + .pipe(map((response) => RegistrationMapper.fromDraftRegistrationResponse(response))); } deleteDraft(draftId: string): Observable { return this.jsonApiService.delete(`${this.apiUrl}/draft_registrations/${draftId}/`); } - registerDraft(draftId: string, embargoDate: string, projectId?: string): Observable { + registerDraft(draftId: string, embargoDate: string, projectId?: string): Observable { const payload = { data: { type: 'registrations', diff --git a/src/app/features/registries/store/default.state.ts b/src/app/features/registries/store/default.state.ts index 68ba15ee..348eb649 100644 --- a/src/app/features/registries/store/default.state.ts +++ b/src/app/features/registries/store/default.state.ts @@ -33,4 +33,10 @@ export const DefaultState: RegistriesStateModel = { error: null, }, stepsValidation: {}, + registration: { + data: null, + isLoading: false, + isSubmitting: false, + error: null, + }, }; diff --git a/src/app/features/registries/store/registries.model.ts b/src/app/features/registries/store/registries.model.ts index 73ab322d..96bce982 100644 --- a/src/app/features/registries/store/registries.model.ts +++ b/src/app/features/registries/store/registries.model.ts @@ -1,12 +1,13 @@ +import { DraftRegistrationModel, RegistrationModel } from '@osf/shared/models/registration'; import { AsyncStateModel, License, Resource } from '@shared/models'; import { PageSchema, Project, Provider } from '../models'; -import { Registration } from '../models/registration.model'; export interface RegistriesStateModel { providers: AsyncStateModel; projects: AsyncStateModel; - draftRegistration: AsyncStateModel; + draftRegistration: AsyncStateModel; + registration: AsyncStateModel; registries: AsyncStateModel; licenses: AsyncStateModel; pagesSchema: AsyncStateModel; diff --git a/src/app/features/registries/store/registries.selectors.ts b/src/app/features/registries/store/registries.selectors.ts index 8f92c627..c93296c4 100644 --- a/src/app/features/registries/store/registries.selectors.ts +++ b/src/app/features/registries/store/registries.selectors.ts @@ -1,8 +1,9 @@ import { Selector } from '@ngxs/store'; +import { DraftRegistrationModel } from '@osf/shared/models/registration'; import { License, Resource } from '@shared/models'; -import { PageSchema, Project, Provider, Registration } from '../models'; +import { PageSchema, Project, Provider } from '../models'; import { RegistriesStateModel } from './registries.model'; import { RegistriesState } from './registries.state'; @@ -29,7 +30,7 @@ export class RegistriesSelectors { } @Selector([RegistriesState]) - static getDraftRegistration(state: RegistriesStateModel): Registration | null { + static getDraftRegistration(state: RegistriesStateModel): DraftRegistrationModel | null { return state.draftRegistration.data; } @@ -77,4 +78,9 @@ export class RegistriesSelectors { static getStepsData(state: RegistriesStateModel) { return state.draftRegistration.data?.stepsData || {}; } + + @Selector([RegistriesState]) + static isRegistrationSubmitting(state: RegistriesStateModel): boolean { + return state.registration.isSubmitting || false; + } } diff --git a/src/app/features/registries/store/registries.state.ts b/src/app/features/registries/store/registries.state.ts index 48be1584..a600666f 100644 --- a/src/app/features/registries/store/registries.state.ts +++ b/src/app/features/registries/store/registries.state.ts @@ -189,8 +189,8 @@ export class RegistriesState { @Action(RegisterDraft) registerDraft(ctx: StateContext, { draftId, embargoDate, projectId }: RegisterDraft) { ctx.patchState({ - draftRegistration: { - ...ctx.getState().draftRegistration, + registration: { + ...ctx.getState().registration, isSubmitting: true, }, }); @@ -198,7 +198,7 @@ export class RegistriesState { return this.registriesService.registerDraft(draftId, embargoDate, projectId).pipe( tap((registration) => { ctx.patchState({ - draftRegistration: { + registration: { data: { ...registration }, isLoading: false, isSubmitting: false, diff --git a/src/app/features/registries/models/registration.model.ts b/src/app/shared/models/registration/draft-registration.model.ts similarity index 70% rename from src/app/features/registries/models/registration.model.ts rename to src/app/shared/models/registration/draft-registration.model.ts index 972d6256..bdeacc02 100644 --- a/src/app/features/registries/models/registration.model.ts +++ b/src/app/shared/models/registration/draft-registration.model.ts @@ -1,6 +1,6 @@ -import { LicenseOptions } from '@osf/shared/models'; +import { LicenseOptions } from '../license.model'; -export interface Registration { +export interface DraftRegistrationModel { id: string; title: string; description: string; @@ -12,4 +12,5 @@ export interface Registration { tags: string[]; // eslint-disable-next-line @typescript-eslint/no-explicit-any stepsData?: Record; + branchedFrom?: string; } diff --git a/src/app/shared/models/registration/index.ts b/src/app/shared/models/registration/index.ts new file mode 100644 index 00000000..58f03a09 --- /dev/null +++ b/src/app/shared/models/registration/index.ts @@ -0,0 +1,2 @@ +export * from './draft-registration.model'; +export * from './registration.model'; diff --git a/src/app/shared/models/registration/registration.model.ts b/src/app/shared/models/registration/registration.model.ts new file mode 100644 index 00000000..5bddecd6 --- /dev/null +++ b/src/app/shared/models/registration/registration.model.ts @@ -0,0 +1,67 @@ +import { RegistryStatus, RevisionReviewStates } from '@osf/shared/enums'; + +import { ContributorModel } from '../contributors'; +import { SubjectModel } from '../subject'; + +export type RegistrationQuestions = Record; + +export interface RegistrationModel { + id: string; + type: string; + isPublic: boolean; + forksCount: number; + title: string; + description: string; + dateModified: string; + dateCreated: string; + dateRegistered?: string; + registrationType: string; + doi: string; + tags: string[]; + contributors: ContributorModel[]; + citation: string; + category: string; + isFork: boolean; + accessRequestsEnabled: boolean; + nodeLicense?: { + copyrightHolders: string[]; + year: string; + }; + license?: { + name: string; + text: string; + url: string; + }; + identifiers?: { + id: string; + type: string; + category: string; + value: string; + }[]; + analyticsKey: string; + currentUserCanComment: boolean; + currentUserPermissions: string[]; + currentUserIsContributor: boolean; + currentUserIsContributorOrGroupMember: boolean; + wikiEnabled: boolean; + region?: { + id: string; + type: string; + }; + subjects?: SubjectModel[]; + hasData: boolean; + hasAnalyticCode: boolean; + hasMaterials: boolean; + hasPapers: boolean; + hasSupplements: boolean; + questions: RegistrationQuestions; + registrationSchemaLink: string; + associatedProjectId: string; + schemaResponses: { + id: string; + revisionResponses: RegistrationQuestions; + updatedResponseKeys: string[]; + }[]; + status: RegistryStatus; + revisionStatus: RevisionReviewStates; +} From 21fb84e5672d53c17d9ffa0945eb7875dcd321de Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Wed, 9 Jul 2025 21:49:09 +0300 Subject: [PATCH 3/8] feat(regiestries): fix translations --- src/assets/i18n/en.json | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 9b99b86e..376cb6ce 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -30,7 +30,8 @@ "no": "No", "update": "Update", "continue update": "Continue update", - "withdraw": "Withdraw" + "withdraw": "Withdraw", + "submit": "Submit" }, "search": { "title": "Search", @@ -1641,7 +1642,21 @@ "review": { "step": "Review", "title": "Review Registration", - "register": "Register" + "register": "Register", + "confirmation": { + "title": "Almost done...", + "remember": "Remember:", + "text1": "Do not edit any files until the registration has completely archived.", + "text2": "This will be permanent and cannot be deleted once submitted.", + "text3": "This registration will be copied to Internet Archive as a backup.", + "text4": "Title and contributors cannot be updated once submitted.", + "options": { + "public": "Make registration public immediately", + "embargo": "Enter registration into embargo" + }, + "embargoPlaceholder": "Choose embargo end date", + "successMessage": "Registration successfully created." + } } }, "registry": { From d782dade4284a2a345d724430af5c18e4b8cc268 Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Thu, 10 Jul 2025 09:39:32 +0300 Subject: [PATCH 4/8] feat(registries): fix submit request --- .../confirm-registration-dialog.component.ts | 2 +- .../components/review/review.component.html | 1 + .../components/review/review.component.ts | 29 +++++++++++++++++-- .../registries/services/registries.service.ts | 4 +-- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.ts b/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.ts index 26a9fa96..6d34799d 100644 --- a/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.ts +++ b/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.ts @@ -64,7 +64,7 @@ export class ConfirmRegistrationDialogComponent { this.actions .registerDraft(this.config.data.draftId, this.form.value.embargoDate, this.config.data.projectId) .subscribe({ - next: () => { + complete: () => { this.dialogRef.close(); }, }); diff --git a/src/app/features/registries/components/review/review.component.html b/src/app/features/registries/components/review/review.component.html index a4645cef..9c8aa098 100644 --- a/src/app/features/registries/components/review/review.component.html +++ b/src/app/features/registries/components/review/review.component.html @@ -125,6 +125,7 @@

{{ question.displayText }}

diff --git a/src/app/features/registries/components/review/review.component.ts b/src/app/features/registries/components/review/review.component.ts index 4a2c3b11..3ec9b43b 100644 --- a/src/app/features/registries/components/review/review.component.ts +++ b/src/app/features/registries/components/review/review.component.ts @@ -10,7 +10,7 @@ import { Tag } from 'primeng/tag'; import { map, of } from 'rxjs'; -import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; @@ -47,7 +47,7 @@ export class ReviewComponent { protected readonly pages = select(RegistriesSelectors.getPagesSchema); protected readonly draftRegistration = select(RegistriesSelectors.getDraftRegistration); protected readonly stepsData = select(RegistriesSelectors.getStepsData); - readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; + protected readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; protected readonly contributors = select(ContributorsSelectors.getContributors); protected readonly subjects = select(SubjectsSelectors.getSelectedSubjects); protected readonly FieldType = FieldType; @@ -58,7 +58,32 @@ export class ReviewComponent { deleteDraft: DeleteDraft, registerDraft: RegisterDraft, }); + private readonly draftId = toSignal(this.route.params.pipe(map((params) => params['id'])) ?? of(undefined)); + protected stepsValidation = select(RegistriesSelectors.getStepsValidation); + + isMetaDataInvalid = computed(() => { + return ( + !this.draftRegistration()?.title || + !this.draftRegistration()?.description || + !this.draftRegistration()?.license || + !this.subjects()?.length || + !this.contributors()?.length + ); + }); + + isStepsInvalid = computed(() => { + return this.pages().some((page) => { + return page.questions?.some((question) => { + const questionData = this.stepsData()[question.responseKey!]; + return question.required && (Array.isArray(questionData) ? !questionData.length : !questionData); + }); + }); + }); + + isDraftInvalid = computed(() => { + return this.isMetaDataInvalid() || this.isStepsInvalid(); + }); constructor() { if (!this.contributors()?.length) { diff --git a/src/app/features/registries/services/registries.service.ts b/src/app/features/registries/services/registries.service.ts index 62b72b6e..6000c35d 100644 --- a/src/app/features/registries/services/registries.service.ts +++ b/src/app/features/registries/services/registries.service.ts @@ -87,7 +87,7 @@ export class RegistriesService { type: 'registrations', attributes: { embargo_end_date: embargoDate, - draft_registration_id: draftId, + draft_registration: draftId, }, relationships: { registered_from: projectId @@ -108,7 +108,7 @@ export class RegistriesService { }, }; return this.jsonApiService - .post(`${this.apiUrl}/registrations`, payload) + .post(`${this.apiUrl}/registrations/`, payload) .pipe(map((response) => RegistrationMapper.fromRegistrationResponse(response.data))); } From aa83534e79c5989354f93bf17ff79120c166c0dc Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Fri, 11 Jul 2025 18:34:10 +0300 Subject: [PATCH 5/8] feat(registries): get provider from url params --- .../components/metadata/metadata.component.ts | 3 +- .../new-registration.component.html | 4 +-- .../new-registration.component.ts | 30 +++++++++++-------- .../components/review/review.component.ts | 4 ++- .../registries/mappers/providers.mapper.ts | 4 +-- src/app/features/registries/models/index.ts | 2 +- .../models/provider-schema.model.ts | 4 +++ .../registries/models/provider.model.ts | 4 --- .../registries-landing.component.ts | 3 +- .../features/registries/registries.routes.ts | 2 +- .../registries/services/providers.service.ts | 6 ++-- .../registries/store/default.state.ts | 2 +- .../store/handlers/providers.handlers.ts | 14 ++++----- .../registries/store/registries.actions.ts | 5 ++-- .../registries/store/registries.model.ts | 4 +-- .../registries/store/registries.selectors.ts | 8 ++--- .../registries/store/registries.state.ts | 8 ++--- 17 files changed, 58 insertions(+), 49 deletions(-) create mode 100644 src/app/features/registries/models/provider-schema.model.ts delete mode 100644 src/app/features/registries/models/provider.model.ts diff --git a/src/app/features/registries/components/metadata/metadata.component.ts b/src/app/features/registries/components/metadata/metadata.component.ts index e33940d4..86186196 100644 --- a/src/app/features/registries/components/metadata/metadata.component.ts +++ b/src/app/features/registries/components/metadata/metadata.component.ts @@ -54,6 +54,7 @@ export class MetadataComponent implements OnDestroy { private readonly customConfirmationService = inject(CustomConfirmationService); private readonly draftId = this.route.snapshot.params['id']; + protected readonly providerId = this.route.snapshot.params['providerId']; protected readonly draftRegistration = select(RegistriesSelectors.getDraftRegistration); protected selectedSubjects = select(SubjectsSelectors.getSelectedSubjects); @@ -118,7 +119,7 @@ export class MetadataComponent implements OnDestroy { onConfirm: () => { this.actions.deleteDraft(this.draftId).subscribe({ next: () => { - this.router.navigateByUrl('/registries/new'); + this.router.navigateByUrl(`/registries/${this.providerId}/new`); }, }); }, diff --git a/src/app/features/registries/components/new-registration/new-registration.component.html b/src/app/features/registries/components/new-registration/new-registration.component.html index b324f4ef..b061c864 100644 --- a/src/app/features/registries/components/new-registration/new-registration.component.html +++ b/src/app/features/registries/components/new-registration/new-registration.component.html @@ -53,11 +53,11 @@

{{ ('registries.new.steps.title' | translate) + (fromProject ?
diff --git a/src/app/features/registries/components/new-registration/new-registration.component.ts b/src/app/features/registries/components/new-registration/new-registration.component.ts index de7fbc31..49599a69 100644 --- a/src/app/features/registries/components/new-registration/new-registration.component.ts +++ b/src/app/features/registries/components/new-registration/new-registration.component.ts @@ -8,12 +8,12 @@ import { Select } from 'primeng/select'; import { ChangeDetectionStrategy, Component, effect, inject } from '@angular/core'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { SubHeaderComponent } from '@osf/shared/components'; import { ToastService } from '@osf/shared/services'; -import { CreateDraft, GetProjects, GetProviders, RegistriesSelectors } from '../../store'; +import { CreateDraft, GetProjects, GetProviderSchemas, RegistriesSelectors } from '../../store'; @Component({ selector: 'osf-new-registration', @@ -26,30 +26,34 @@ export class NewRegistrationComponent { private readonly fb = inject(FormBuilder); private readonly toastService = inject(ToastService); private readonly router = inject(Router); + private readonly route = inject(ActivatedRoute); protected readonly projects = select(RegistriesSelectors.getProjects); - protected readonly providers = select(RegistriesSelectors.getProviders); + protected readonly providerSchemas = select(RegistriesSelectors.getProviderSchemas); protected readonly isDraftSubmitting = select(RegistriesSelectors.isDraftSubmitting); protected readonly draftRegistration = select(RegistriesSelectors.getDraftRegistration); protected readonly isProvidersLoading = select(RegistriesSelectors.isProvidersLoading); protected actions = createDispatchMap({ getProjects: GetProjects, - getProviders: GetProviders, + getProviderSchemas: GetProviderSchemas, createDraft: CreateDraft, }); + + protected readonly providerId = this.route.snapshot.params['providerId']; + fromProject = false; draftForm = this.fb.group({ - provider: ['', Validators.required], + providerSchema: ['', Validators.required], project: [''], }); constructor() { this.actions.getProjects(); - this.actions.getProviders(); + this.actions.getProviderSchemas(this.providerId); effect(() => { - const provider = this.draftForm.get('provider')?.value; - if (!provider) { - this.draftForm.get('provider')?.setValue(this.providers()[0]?.id); + const providerSchema = this.draftForm.get('providerSchema')?.value; + if (!providerSchema) { + this.draftForm.get('providerSchema')?.setValue(this.providerSchemas()[0]?.id); } }); } @@ -60,9 +64,9 @@ export class NewRegistrationComponent { }); } - onSelectProvider(providerId: string) { + onSelectProviderSchema(providerSchemaId: string) { this.draftForm.patchValue({ - provider: providerId, + providerSchema: providerSchemaId, }); } @@ -73,12 +77,12 @@ export class NewRegistrationComponent { } createDraft() { - const { provider, project } = this.draftForm.value; + const { providerSchema, project } = this.draftForm.value; if (this.draftForm.valid) { this.actions .createDraft({ - registrationSchemaId: provider!, + registrationSchemaId: providerSchema!, projectId: this.fromProject ? (project ?? undefined) : undefined, }) .subscribe(() => { diff --git a/src/app/features/registries/components/review/review.component.ts b/src/app/features/registries/components/review/review.component.ts index 3ec9b43b..ccfb61c1 100644 --- a/src/app/features/registries/components/review/review.component.ts +++ b/src/app/features/registries/components/review/review.component.ts @@ -60,6 +60,8 @@ export class ReviewComponent { }); private readonly draftId = toSignal(this.route.params.pipe(map((params) => params['id'])) ?? of(undefined)); + protected readonly providerId = this.route.snapshot.params['providerId']; + protected stepsValidation = select(RegistriesSelectors.getStepsValidation); isMetaDataInvalid = computed(() => { @@ -106,7 +108,7 @@ export class ReviewComponent { onConfirm: () => { this.actions.deleteDraft(this.draftId()).subscribe({ next: () => { - this.router.navigateByUrl('/registries/new'); + this.router.navigateByUrl(`/registries/${this.providerId}new`); }, }); }, diff --git a/src/app/features/registries/mappers/providers.mapper.ts b/src/app/features/registries/mappers/providers.mapper.ts index 9442ade0..247cb494 100644 --- a/src/app/features/registries/mappers/providers.mapper.ts +++ b/src/app/features/registries/mappers/providers.mapper.ts @@ -1,7 +1,7 @@ -import { Provider, ProvidersResponseJsonApi } from '../models'; +import { ProviderSchema, ProvidersResponseJsonApi } from '../models'; export class ProvidersMapper { - static fromProvidersResponse(response: ProvidersResponseJsonApi): Provider[] { + static fromProvidersResponse(response: ProvidersResponseJsonApi): ProviderSchema[] { return response.data.map((item) => ({ id: item.id, name: item.attributes.name, diff --git a/src/app/features/registries/models/index.ts b/src/app/features/registries/models/index.ts index b912472d..74e5dbaf 100644 --- a/src/app/features/registries/models/index.ts +++ b/src/app/features/registries/models/index.ts @@ -1,7 +1,7 @@ export * from './page-schema.model'; export * from './project'; export * from './projects-json-api.model'; -export * from './provider.model'; +export * from './provider-schema.model'; export * from './providers-json-api.model'; export * from './registration-json-api.model'; export * from './schema-blocks-json-api.model'; diff --git a/src/app/features/registries/models/provider-schema.model.ts b/src/app/features/registries/models/provider-schema.model.ts new file mode 100644 index 00000000..1d920cc7 --- /dev/null +++ b/src/app/features/registries/models/provider-schema.model.ts @@ -0,0 +1,4 @@ +export interface ProviderSchema { + id: string; + name: string; +} diff --git a/src/app/features/registries/models/provider.model.ts b/src/app/features/registries/models/provider.model.ts deleted file mode 100644 index aa49d532..00000000 --- a/src/app/features/registries/models/provider.model.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface Provider { - id: string; - name: string; -} diff --git a/src/app/features/registries/pages/registries-landing/registries-landing.component.ts b/src/app/features/registries/pages/registries-landing/registries-landing.component.ts index aafc5fe6..e8f5a425 100644 --- a/src/app/features/registries/pages/registries-landing/registries-landing.component.ts +++ b/src/app/features/registries/pages/registries-landing/registries-landing.component.ts @@ -64,6 +64,7 @@ export class RegistriesLandingComponent implements OnInit { } goToCreateRegistration(): void { - this.router.navigate(['/registries/new']); + const providerId = 'osf'; + this.router.navigate([`/registries/${providerId}/new`]); } } diff --git a/src/app/features/registries/registries.routes.ts b/src/app/features/registries/registries.routes.ts index 60b2d2d2..5c815ae2 100644 --- a/src/app/features/registries/registries.routes.ts +++ b/src/app/features/registries/registries.routes.ts @@ -41,7 +41,7 @@ export const registriesRoutes: Routes = [ providers: [provideStates([ModeratorsState])], }, { - path: 'new', + path: ':providerId/new', loadComponent: () => import('./components/new-registration/new-registration.component').then( (mod) => mod.NewRegistrationComponent diff --git a/src/app/features/registries/services/providers.service.ts b/src/app/features/registries/services/providers.service.ts index 721234bd..5a08992e 100644 --- a/src/app/features/registries/services/providers.service.ts +++ b/src/app/features/registries/services/providers.service.ts @@ -5,7 +5,7 @@ import { inject, Injectable } from '@angular/core'; import { JsonApiService } from '@osf/core/services'; import { ProvidersMapper } from '../mappers/providers.mapper'; -import { Provider } from '../models'; +import { ProviderSchema } from '../models'; import { ProvidersResponseJsonApi } from '../models/providers-json-api.model'; import { environment } from 'src/environments/environment'; @@ -17,9 +17,9 @@ export class ProvidersService { private apiUrl = environment.apiUrl; private readonly jsonApiService = inject(JsonApiService); - getProviders(): Observable { + getProviderSchemas(providerId: string): Observable { return this.jsonApiService - .get(`${this.apiUrl}/providers/registrations/osf/schemas/`) + .get(`${this.apiUrl}/providers/registrations/${providerId}/schemas/`) .pipe(map((response) => ProvidersMapper.fromProvidersResponse(response))); } } diff --git a/src/app/features/registries/store/default.state.ts b/src/app/features/registries/store/default.state.ts index 348eb649..3ae396e3 100644 --- a/src/app/features/registries/store/default.state.ts +++ b/src/app/features/registries/store/default.state.ts @@ -1,7 +1,7 @@ import { RegistriesStateModel } from './registries.model'; export const DefaultState: RegistriesStateModel = { - providers: { + providerSchemas: { data: [], isLoading: false, error: null, diff --git a/src/app/features/registries/store/handlers/providers.handlers.ts b/src/app/features/registries/store/handlers/providers.handlers.ts index c1c30406..8b68e2a0 100644 --- a/src/app/features/registries/store/handlers/providers.handlers.ts +++ b/src/app/features/registries/store/handlers/providers.handlers.ts @@ -10,17 +10,17 @@ import { RegistriesStateModel } from '../registries.model'; export class ProvidersHandlers { providersService = inject(ProvidersService); - getProviders({ patchState }: StateContext) { + getProviderSchemas({ patchState }: StateContext, providerId: string) { patchState({ - providers: { - ...DefaultState.providers, + providerSchemas: { + ...DefaultState.providerSchemas, isLoading: true, }, }); - return this.providersService.getProviders().subscribe({ + return this.providersService.getProviderSchemas(providerId).subscribe({ next: (providers) => { patchState({ - providers: { + providerSchemas: { data: providers, isLoading: false, error: null, @@ -29,8 +29,8 @@ export class ProvidersHandlers { }, error: (error) => { patchState({ - providers: { - ...DefaultState.providers, + providerSchemas: { + ...DefaultState.providerSchemas, isLoading: false, error, }, diff --git a/src/app/features/registries/store/registries.actions.ts b/src/app/features/registries/store/registries.actions.ts index 1b7742f9..fc24a71a 100644 --- a/src/app/features/registries/store/registries.actions.ts +++ b/src/app/features/registries/store/registries.actions.ts @@ -6,8 +6,9 @@ export class GetRegistries { static readonly type = '[Registries] Get Registries'; } -export class GetProviders { - static readonly type = '[Registries] Get Providers'; +export class GetProviderSchemas { + static readonly type = '[Registries] Get Provider Schemas'; + constructor(public providerId: string) {} } export class GetProjects { diff --git a/src/app/features/registries/store/registries.model.ts b/src/app/features/registries/store/registries.model.ts index 96bce982..112b9abf 100644 --- a/src/app/features/registries/store/registries.model.ts +++ b/src/app/features/registries/store/registries.model.ts @@ -1,10 +1,10 @@ import { DraftRegistrationModel, RegistrationModel } from '@osf/shared/models/registration'; import { AsyncStateModel, License, Resource } from '@shared/models'; -import { PageSchema, Project, Provider } from '../models'; +import { PageSchema, Project, ProviderSchema } from '../models'; export interface RegistriesStateModel { - providers: AsyncStateModel; + providerSchemas: AsyncStateModel; projects: AsyncStateModel; draftRegistration: AsyncStateModel; registration: AsyncStateModel; diff --git a/src/app/features/registries/store/registries.selectors.ts b/src/app/features/registries/store/registries.selectors.ts index c93296c4..c73e45cf 100644 --- a/src/app/features/registries/store/registries.selectors.ts +++ b/src/app/features/registries/store/registries.selectors.ts @@ -3,20 +3,20 @@ import { Selector } from '@ngxs/store'; import { DraftRegistrationModel } from '@osf/shared/models/registration'; import { License, Resource } from '@shared/models'; -import { PageSchema, Project, Provider } from '../models'; +import { PageSchema, Project, ProviderSchema } from '../models'; import { RegistriesStateModel } from './registries.model'; import { RegistriesState } from './registries.state'; export class RegistriesSelectors { @Selector([RegistriesState]) - static getProviders(state: RegistriesStateModel): Provider[] { - return state.providers.data; + static getProviderSchemas(state: RegistriesStateModel): ProviderSchema[] { + return state.providerSchemas.data; } @Selector([RegistriesState]) static isProvidersLoading(state: RegistriesStateModel): boolean { - return state.providers.isLoading; + return state.providerSchemas.isLoading; } @Selector([RegistriesState]) diff --git a/src/app/features/registries/store/registries.state.ts b/src/app/features/registries/store/registries.state.ts index a600666f..ccc00206 100644 --- a/src/app/features/registries/store/registries.state.ts +++ b/src/app/features/registries/store/registries.state.ts @@ -22,7 +22,7 @@ import { FetchLicenses, FetchSchemaBlocks, GetProjects, - GetProviders, + GetProviderSchemas, GetRegistries, RegisterDraft, SaveLicense, @@ -75,9 +75,9 @@ export class RegistriesState { return this.projectsHandler.getProjects(ctx); } - @Action(GetProviders) - getProviders(ctx: StateContext) { - return this.providersHandler.getProviders(ctx); + @Action(GetProviderSchemas) + getProviders(ctx: StateContext, { providerId }: GetProviderSchemas) { + return this.providersHandler.getProviderSchemas(ctx, providerId); } @Action(CreateDraft) From 6ba878e45d44501df00d27f9985a6f4910a9b569 Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Fri, 11 Jul 2025 18:57:41 +0300 Subject: [PATCH 6/8] feat(registries): fix get provider --- .../confirm-registration-dialog.component.ts | 9 +++-- .../components/metadata/metadata.component.ts | 3 +- .../new-registration.component.html | 2 +- .../components/review/review.component.ts | 4 +-- .../registries/mappers/registration.mapper.ts | 30 +++++++++++++++++ .../models/registration-json-api.model.ts | 6 ++++ .../registries/services/registries.service.ts | 33 ++++--------------- .../registries/store/registries.actions.ts | 1 + .../registries/store/registries.state.ts | 7 ++-- .../registration/draft-registration.model.ts | 1 + 10 files changed, 61 insertions(+), 35 deletions(-) diff --git a/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.ts b/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.ts index 6d34799d..43379047 100644 --- a/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.ts +++ b/src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.ts @@ -49,7 +49,7 @@ export class ConfirmRegistrationDialogComponent { if (this.showDateControl) { dateControl!.enable(); - dateControl!.setValidators(Validators.required); // make date required + dateControl!.setValidators(Validators.required); } else { dateControl!.disable(); dateControl!.clearValidators(); @@ -62,7 +62,12 @@ export class ConfirmRegistrationDialogComponent { submit(): void { this.actions - .registerDraft(this.config.data.draftId, this.form.value.embargoDate, this.config.data.projectId) + .registerDraft( + this.config.data.draftId, + this.form.value.embargoDate, + this.config.data.providerId, + this.config.data.projectId + ) .subscribe({ complete: () => { this.dialogRef.close(); diff --git a/src/app/features/registries/components/metadata/metadata.component.ts b/src/app/features/registries/components/metadata/metadata.component.ts index 86186196..a57aa10c 100644 --- a/src/app/features/registries/components/metadata/metadata.component.ts +++ b/src/app/features/registries/components/metadata/metadata.component.ts @@ -54,7 +54,6 @@ export class MetadataComponent implements OnDestroy { private readonly customConfirmationService = inject(CustomConfirmationService); private readonly draftId = this.route.snapshot.params['id']; - protected readonly providerId = this.route.snapshot.params['providerId']; protected readonly draftRegistration = select(RegistriesSelectors.getDraftRegistration); protected selectedSubjects = select(SubjectsSelectors.getSelectedSubjects); @@ -119,7 +118,7 @@ export class MetadataComponent implements OnDestroy { onConfirm: () => { this.actions.deleteDraft(this.draftId).subscribe({ next: () => { - this.router.navigateByUrl(`/registries/${this.providerId}/new`); + this.router.navigateByUrl(`/registries/${this.draftRegistration()?.providerId}/new`); }, }); }, diff --git a/src/app/features/registries/components/new-registration/new-registration.component.html b/src/app/features/registries/components/new-registration/new-registration.component.html index b061c864..4fd357a0 100644 --- a/src/app/features/registries/components/new-registration/new-registration.component.html +++ b/src/app/features/registries/components/new-registration/new-registration.component.html @@ -52,7 +52,7 @@

{{ ('registries.new.steps.title' | translate) + (fromProject ?

{{ 'registries.new.steps.step3' | translate }}

params['id'])) ?? of(undefined)); - protected readonly providerId = this.route.snapshot.params['providerId']; protected stepsValidation = select(RegistriesSelectors.getStepsValidation); @@ -108,7 +107,7 @@ export class ReviewComponent { onConfirm: () => { this.actions.deleteDraft(this.draftId()).subscribe({ next: () => { - this.router.navigateByUrl(`/registries/${this.providerId}new`); + this.router.navigateByUrl(`/registries/${this.draftRegistration()?.providerId}new`); }, }); }, @@ -126,6 +125,7 @@ export class ReviewComponent { data: { draftId: this.draftId(), projectId: this.draftRegistration()?.branchedFrom, + providerId: this.draftRegistration()?.providerId, }, }) .onClose.subscribe(() => { diff --git a/src/app/features/registries/mappers/registration.mapper.ts b/src/app/features/registries/mappers/registration.mapper.ts index 8e548e51..5b6e5132 100644 --- a/src/app/features/registries/mappers/registration.mapper.ts +++ b/src/app/features/registries/mappers/registration.mapper.ts @@ -21,6 +21,7 @@ export class RegistrationMapper { tags: response.attributes.tags || [], stepsData: response.attributes.registration_responses || {}, branchedFrom: response.relationships.branched_from?.data?.id, + providerId: response.relationships.provider?.data?.id || '', }; } @@ -30,4 +31,33 @@ export class RegistrationMapper { type: 'registration', } as RegistrationModel; } + + static toRegistrationPayload(draftId: string, embargoDate: string, providerId: string, projectId?: string) { + return { + data: { + type: 'registrations', + attributes: { + embargo_end_date: embargoDate, + draft_registration: draftId, + }, + relationships: { + registered_from: projectId + ? { + data: { + type: 'nodes', + id: projectId, + }, + } + : undefined, + + provider: { + data: { + type: 'registration-providers', + id: providerId, + }, + }, + }, + }, + }; + } } 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 21c01315..255122fe 100644 --- a/src/app/features/registries/models/registration-json-api.model.ts +++ b/src/app/features/registries/models/registration-json-api.model.ts @@ -41,6 +41,12 @@ export interface RegistrationRelationshipsJsonApi { type: 'licenses'; }; }; + provider?: { + data: { + id: string; + type: 'registration-providers'; + }; + }; branched_from?: { data: { id: string; diff --git a/src/app/features/registries/services/registries.service.ts b/src/app/features/registries/services/registries.service.ts index 6000c35d..9efabfa6 100644 --- a/src/app/features/registries/services/registries.service.ts +++ b/src/app/features/registries/services/registries.service.ts @@ -81,32 +81,13 @@ export class RegistriesService { return this.jsonApiService.delete(`${this.apiUrl}/draft_registrations/${draftId}/`); } - registerDraft(draftId: string, embargoDate: string, projectId?: string): Observable { - const payload = { - data: { - type: 'registrations', - attributes: { - embargo_end_date: embargoDate, - draft_registration: draftId, - }, - relationships: { - registered_from: projectId - ? { - data: { - type: 'nodes', - id: projectId, - }, - } - : undefined, - }, - provider: { - data: { - type: 'registration-providers', - id: 'osf', // Assuming 'osf' is the default provider - }, - }, - }, - }; + registerDraft( + draftId: string, + embargoDate: string, + providerId: string, + projectId?: string + ): Observable { + const payload = RegistrationMapper.toRegistrationPayload(draftId, embargoDate, providerId, projectId); return this.jsonApiService .post(`${this.apiUrl}/registrations/`, payload) .pipe(map((response) => RegistrationMapper.fromRegistrationResponse(response.data))); diff --git a/src/app/features/registries/store/registries.actions.ts b/src/app/features/registries/store/registries.actions.ts index fc24a71a..de7b8090 100644 --- a/src/app/features/registries/store/registries.actions.ts +++ b/src/app/features/registries/store/registries.actions.ts @@ -44,6 +44,7 @@ export class RegisterDraft { constructor( public draftId: string, public embargoDate: string, + public providerId: string, public projectId?: string ) {} } diff --git a/src/app/features/registries/store/registries.state.ts b/src/app/features/registries/store/registries.state.ts index ccc00206..6bded34d 100644 --- a/src/app/features/registries/store/registries.state.ts +++ b/src/app/features/registries/store/registries.state.ts @@ -187,7 +187,10 @@ export class RegistriesState { } @Action(RegisterDraft) - registerDraft(ctx: StateContext, { draftId, embargoDate, projectId }: RegisterDraft) { + registerDraft( + ctx: StateContext, + { draftId, embargoDate, providerId, projectId }: RegisterDraft + ) { ctx.patchState({ registration: { ...ctx.getState().registration, @@ -195,7 +198,7 @@ export class RegistriesState { }, }); - return this.registriesService.registerDraft(draftId, embargoDate, projectId).pipe( + return this.registriesService.registerDraft(draftId, embargoDate, providerId, projectId).pipe( tap((registration) => { ctx.patchState({ registration: { diff --git a/src/app/shared/models/registration/draft-registration.model.ts b/src/app/shared/models/registration/draft-registration.model.ts index bdeacc02..19bfa8f4 100644 --- a/src/app/shared/models/registration/draft-registration.model.ts +++ b/src/app/shared/models/registration/draft-registration.model.ts @@ -13,4 +13,5 @@ export interface DraftRegistrationModel { // eslint-disable-next-line @typescript-eslint/no-explicit-any stepsData?: Record; branchedFrom?: string; + providerId: string; } From 1cd5405cddb7a4b1feb5b0530ecbc5d0b08dd550 Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Sat, 12 Jul 2025 13:25:38 +0300 Subject: [PATCH 7/8] feat(registries): update validation for metadata step --- .../components/drafts/drafts.component.ts | 5 +++- .../contributors/contributors.component.html | 9 ++---- .../contributors/contributors.component.ts | 11 +++++-- .../metadata/metadata.component.html | 2 +- .../components/metadata/metadata.component.ts | 13 +++++++-- .../registries-license.component.ts | 29 +++++++++++++++---- .../registries-subjects.component.ts | 16 ++++++---- .../registries/services/licenses.service.ts | 4 +-- .../store/handlers/licenses.handlers.ts | 4 +-- .../registries/store/registries.actions.ts | 1 + .../registries/store/registries.state.ts | 4 +-- 11 files changed, 67 insertions(+), 31 deletions(-) diff --git a/src/app/features/registries/components/drafts/drafts.component.ts b/src/app/features/registries/components/drafts/drafts.component.ts index 8898fcbb..b654a57d 100644 --- a/src/app/features/registries/components/drafts/drafts.component.ts +++ b/src/app/features/registries/components/drafts/drafts.component.ts @@ -45,6 +45,8 @@ export class DraftsComponent { defaultSteps: StepOption[] = []; + isLoaded = false; + steps: Signal = computed(() => { this.defaultSteps = defaultSteps.map((step) => ({ ...step, @@ -102,11 +104,12 @@ export class DraftsComponent { } effect(() => { const registrationSchemaId = this.draftRegistration()?.registrationSchemaId; - if (registrationSchemaId) { + if (registrationSchemaId && !this.isLoaded) { this.actions .getSchemaBlocks(registrationSchemaId || '') .pipe( tap(() => { + this.isLoaded = true; this.loaderService.hide(); }) ) diff --git a/src/app/features/registries/components/metadata/contributors/contributors.component.html b/src/app/features/registries/components/metadata/contributors/contributors.component.html index 5319ef73..ddd24e9f 100644 --- a/src/app/features/registries/components/metadata/contributors/contributors.component.html +++ b/src/app/features/registries/components/metadata/contributors/contributors.component.html @@ -1,8 +1,7 @@ - +

{{ 'project.overview.metadata.contributors' | translate }}

{{ 'project.overview.metadata.contributors' | translate }}

} - + diff --git a/src/app/features/registries/components/metadata/contributors/contributors.component.ts b/src/app/features/registries/components/metadata/contributors/contributors.component.ts index dd3854f4..6b406e56 100644 --- a/src/app/features/registries/components/metadata/contributors/contributors.component.ts +++ b/src/app/features/registries/components/metadata/contributors/contributors.component.ts @@ -9,9 +9,9 @@ import { TableModule } from 'primeng/table'; import { filter, forkJoin, map, of } from 'rxjs'; -import { ChangeDetectionStrategy, Component, DestroyRef, effect, inject, OnInit, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, DestroyRef, effect, inject, input, OnInit, signal } from '@angular/core'; import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; -import { FormsModule } from '@angular/forms'; +import { FormControl, FormsModule } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { @@ -41,6 +41,8 @@ import { findChangedItems } from '@osf/shared/utils'; providers: [DialogService], }) export class ContributorsComponent implements OnInit { + control = input.required(); + readonly destroyRef = inject(DestroyRef); readonly translateService = inject(TranslateService); readonly dialogService = inject(DialogService); @@ -84,6 +86,11 @@ export class ContributorsComponent implements OnInit { onFocusOut() { // [NM] TODO: make request to update contributor if changed console.log('Focus out event:', 'Changed:', this.hasChanges); + if (this.control()) { + this.control().markAsTouched(); + this.control().markAsDirty(); + this.control().updateValueAndValidity(); + } } cancel() { diff --git a/src/app/features/registries/components/metadata/metadata.component.html b/src/app/features/registries/components/metadata/metadata.component.html index 3acb4355..4adf7ea0 100644 --- a/src/app/features/registries/components/metadata/metadata.component.html +++ b/src/app/features/registries/components/metadata/metadata.component.html @@ -32,7 +32,7 @@

{{ 'registries.metadata.title' | translate }}

- + diff --git a/src/app/features/registries/components/metadata/metadata.component.ts b/src/app/features/registries/components/metadata/metadata.component.ts index a57aa10c..7e5f19e3 100644 --- a/src/app/features/registries/components/metadata/metadata.component.ts +++ b/src/app/features/registries/components/metadata/metadata.component.ts @@ -15,10 +15,10 @@ import { ActivatedRoute, Router } from '@angular/router'; import { TextInputComponent } from '@osf/shared/components'; import { INPUT_VALIDATION_MESSAGES, InputLimits } from '@osf/shared/constants'; -import { SubjectModel } from '@osf/shared/models'; +import { ContributorModel, SubjectModel } from '@osf/shared/models'; import { DraftRegistrationModel } from '@osf/shared/models/registration'; import { CustomConfirmationService } from '@osf/shared/services'; -import { SubjectsSelectors } from '@osf/shared/stores'; +import { ContributorsSelectors, SubjectsSelectors } from '@osf/shared/stores'; import { CustomValidators, findChangedFields } from '@osf/shared/utils'; import { DeleteDraft, RegistriesSelectors, UpdateDraft, UpdateStepValidation } from '../../store'; @@ -56,6 +56,8 @@ export class MetadataComponent implements OnDestroy { private readonly draftId = this.route.snapshot.params['id']; protected readonly draftRegistration = select(RegistriesSelectors.getDraftRegistration); protected selectedSubjects = select(SubjectsSelectors.getSelectedSubjects); + protected initialContributors = select(ContributorsSelectors.getContributors); + protected stepsValidation = select(RegistriesSelectors.getStepsValidation); protected actions = createDispatchMap({ deleteDraft: DeleteDraft, @@ -68,7 +70,7 @@ export class MetadataComponent implements OnDestroy { metadataForm = this.fb.group({ title: ['', CustomValidators.requiredTrimmed()], description: ['', CustomValidators.requiredTrimmed()], - // contributors: [[], Validators.required], + contributors: [[] as ContributorModel[], Validators.required], subjects: [[] as SubjectModel[], Validators.required], tags: [[]], license: this.fb.group({ @@ -90,7 +92,12 @@ export class MetadataComponent implements OnDestroy { title: data.title, description: data.description, license: data.license, + contributors: this.initialContributors(), + subjects: this.selectedSubjects(), }); + if (this.stepsValidation()?.[0]?.invalid) { + this.metadataForm.markAllAsTouched(); + } } submitMetadata(): void { 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 1ade2f24..a4f2c4e8 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 @@ -5,6 +5,8 @@ import { TranslatePipe } from '@ngx-translate/core'; import { Card } from 'primeng/card'; import { Message } from 'primeng/message'; +import { tap } from 'rxjs'; + import { ChangeDetectionStrategy, Component, effect, inject, input } from '@angular/core'; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; @@ -33,7 +35,11 @@ export class RegistriesLicenseComponent { protected licenses = select(RegistriesSelectors.getLicenses); protected inputLimits = InputLimits; - selectedLicense = select(RegistriesSelectors.getSelectedLicense); + protected selectedLicense = select(RegistriesSelectors.getSelectedLicense); + protected draftRegistration = select(RegistriesSelectors.getDraftRegistration); + + private readonly OSF_PROVIDER_ID = 'osf'; + currentYear = new Date(); licenseYear = this.currentYear; licenseForm = this.fb.group({ @@ -43,8 +49,15 @@ export class RegistriesLicenseComponent { readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; + private isLoaded = false; + constructor() { - this.actions.fetchLicenses(); + effect(() => { + if (this.draftRegistration() && !this.isLoaded) { + this.actions.fetchLicenses(this.draftRegistration()?.providerId ?? this.OSF_PROVIDER_ID); + this.isLoaded = true; + } + }); effect(() => { const selectedLicense = this.selectedLicense(); @@ -62,9 +75,15 @@ export class RegistriesLicenseComponent { } selectLicense(license: License) { - this.control().markAsDirty(); - this.control().updateValueAndValidity(); - this.actions.saveLicense(this.draftId, license.id); + this.actions + .saveLicense(this.draftId, license.id) + .pipe( + tap(() => { + this.control().markAsDirty(); + this.control().updateValueAndValidity(); + }) + ) + .subscribe(); } onFocusOut() { diff --git a/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts b/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts index 1ccb592a..9844053e 100644 --- a/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts +++ b/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts @@ -9,6 +9,7 @@ import { ChangeDetectionStrategy, Component, effect, inject, input } from '@angu import { FormControl } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; +import { RegistriesSelectors } from '@osf/features/registries/store'; import { SubjectsComponent } from '@osf/shared/components'; import { INPUT_VALIDATION_MESSAGES } from '@osf/shared/constants'; import { ResourceType } from '@osf/shared/enums'; @@ -32,10 +33,10 @@ export class RegistriesSubjectsComponent { control = input.required(); private readonly route = inject(ActivatedRoute); private readonly draftId = this.route.snapshot.params['id']; - private readonly OSF_PROVIDER_ID = 'osf'; protected selectedSubjects = select(SubjectsSelectors.getSelectedSubjects); protected isSubjectsUpdating = select(SubjectsSelectors.areSelectedSubjectsLoading); + protected draftRegistration = select(RegistriesSelectors.getDraftRegistration); protected actions = createDispatchMap({ fetchSubjects: FetchSubjects, @@ -46,13 +47,16 @@ export class RegistriesSubjectsComponent { readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; + private isLoaded = false; + constructor() { effect(() => { - this.updateControlState(this.selectedSubjects()); + if (this.draftRegistration() && !this.isLoaded) { + this.actions.fetchSubjects(ResourceType.Registration, this.draftRegistration()?.providerId); + this.actions.fetchSelectedSubjects(this.draftId, ResourceType.DraftRegistration); + this.isLoaded = true; + } }); - - this.actions.fetchSubjects(ResourceType.Registration, this.OSF_PROVIDER_ID); - this.actions.fetchSelectedSubjects(this.draftId, ResourceType.DraftRegistration); } getSubjectChildren(parentId: string) { @@ -60,7 +64,7 @@ export class RegistriesSubjectsComponent { } searchSubjects(search: string) { - this.actions.fetchSubjects(ResourceType.Registration, this.OSF_PROVIDER_ID, search); + this.actions.fetchSubjects(ResourceType.Registration, this.draftRegistration()?.providerId, search); } updateSelectedSubjects(subjects: SubjectModel[]) { diff --git a/src/app/features/registries/services/licenses.service.ts b/src/app/features/registries/services/licenses.service.ts index 03c0db32..77f059c0 100644 --- a/src/app/features/registries/services/licenses.service.ts +++ b/src/app/features/registries/services/licenses.service.ts @@ -34,9 +34,9 @@ export class LicensesService { private apiUrl = environment.apiUrl; private readonly jsonApiService = inject(JsonApiService); - getLicenses(): Observable { + getLicenses(providerId: string): Observable { return this.jsonApiService - .get(`${this.apiUrl}/providers/registrations/osf/licenses/`, { + .get(`${this.apiUrl}/providers/registrations/${providerId}/licenses/`, { params: { 'page[size]': 100, }, diff --git a/src/app/features/registries/store/handlers/licenses.handlers.ts b/src/app/features/registries/store/handlers/licenses.handlers.ts index 28ee8b15..ca1faab0 100644 --- a/src/app/features/registries/store/handlers/licenses.handlers.ts +++ b/src/app/features/registries/store/handlers/licenses.handlers.ts @@ -14,7 +14,7 @@ import { RegistriesStateModel } from '../registries.model'; export class LicensesHandlers { licensesService = inject(LicensesService); - fetchLicenses(ctx: StateContext) { + fetchLicenses(ctx: StateContext, providerId: string) { ctx.patchState({ licenses: { ...ctx.getState().licenses, @@ -22,7 +22,7 @@ export class LicensesHandlers { }, }); - return this.licensesService.getLicenses().pipe( + return this.licensesService.getLicenses(providerId).pipe( tap((licenses) => { ctx.patchState({ licenses: { diff --git a/src/app/features/registries/store/registries.actions.ts b/src/app/features/registries/store/registries.actions.ts index de7b8090..7932f383 100644 --- a/src/app/features/registries/store/registries.actions.ts +++ b/src/app/features/registries/store/registries.actions.ts @@ -56,6 +56,7 @@ export class FetchSchemaBlocks { export class FetchLicenses { static readonly type = '[Registries] Fetch Licenses'; + constructor(public providerId: string) {} } export class SaveLicense { diff --git a/src/app/features/registries/store/registries.state.ts b/src/app/features/registries/store/registries.state.ts index 6bded34d..a078bd6b 100644 --- a/src/app/features/registries/store/registries.state.ts +++ b/src/app/features/registries/store/registries.state.ts @@ -245,8 +245,8 @@ export class RegistriesState { } @Action(FetchLicenses) - fetchLicenses(ctx: StateContext) { - return this.licensesHandler.fetchLicenses(ctx); + fetchLicenses(ctx: StateContext, { providerId }: FetchLicenses) { + return this.licensesHandler.fetchLicenses(ctx, providerId); } @Action(SaveLicense) From 0eb71ef7e1cca5231a563d4544c3bbc64d754ab2 Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Sat, 12 Jul 2025 16:01:15 +0300 Subject: [PATCH 8/8] feat(registries): refactoring validation --- .../custom-step/custom-step.component.html | 16 +++++++++ .../custom-step/custom-step.component.ts | 8 +++-- .../components/drafts/drafts.component.ts | 33 ++++++++++++++++++- .../components/metadata/metadata.component.ts | 33 ++++++++++++------- .../registries-license.component.ts | 17 ++++------ .../components/review/review.component.ts | 22 ++----------- .../registries/services/licenses.service.ts | 15 ++++++--- .../store/handlers/licenses.handlers.ts | 30 ++++++++--------- 8 files changed, 107 insertions(+), 67 deletions(-) diff --git a/src/app/features/registries/components/custom-step/custom-step.component.html b/src/app/features/registries/components/custom-step/custom-step.component.html index 45891a44..690363ce 100644 --- a/src/app/features/registries/components/custom-step/custom-step.component.html +++ b/src/app/features/registries/components/custom-step/custom-step.component.html @@ -95,6 +95,14 @@

} + @if ( + stepForm.controls[question.responseKey!].errors?.['required'] && + (stepForm.controls[question.responseKey!].touched || stepForm.controls[question.responseKey!].dirty) + ) { + + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + + } } @case (FieldType.Checkbox) {
@@ -109,6 +117,14 @@

} + @if ( + stepForm.controls[question.responseKey!].errors?.['required'] && + (stepForm.controls[question.responseKey!].touched || stepForm.controls[question.responseKey!].dirty) + ) { + + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + + } } @case (FieldType.Text) { diff --git a/src/app/features/registries/components/custom-step/custom-step.component.ts b/src/app/features/registries/components/custom-step/custom-step.component.ts index d9c4fe06..51551eba 100644 --- a/src/app/features/registries/components/custom-step/custom-step.component.ts +++ b/src/app/features/registries/components/custom-step/custom-step.component.ts @@ -12,7 +12,7 @@ import { RadioButton } from 'primeng/radiobutton'; import { Textarea } from 'primeng/textarea'; import { NgTemplateOutlet } from '@angular/common'; -import { ChangeDetectionStrategy, Component, computed, effect, inject, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, effect, inject, OnDestroy, signal } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; @@ -47,7 +47,7 @@ import { RegistriesSelectors, UpdateDraft, UpdateStepValidation } from '../../st styleUrl: './custom-step.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class CustomStepComponent { +export class CustomStepComponent implements OnDestroy { private readonly route = inject(ActivatedRoute); private readonly router = inject(Router); private readonly fb = inject(FormBuilder); @@ -161,4 +161,8 @@ export class CustomStepComponent { this.router.navigate(['../', 'review'], { relativeTo: this.route }); } } + + ngOnDestroy(): void { + this.updateStepState(); + } } diff --git a/src/app/features/registries/components/drafts/drafts.component.ts b/src/app/features/registries/components/drafts/drafts.component.ts index b654a57d..6375e873 100644 --- a/src/app/features/registries/components/drafts/drafts.component.ts +++ b/src/app/features/registries/components/drafts/drafts.component.ts @@ -11,9 +11,10 @@ import { ActivatedRoute, NavigationEnd, Router, RouterOutlet } from '@angular/ro import { StepperComponent, SubHeaderComponent } from '@osf/shared/components'; import { StepOption } from '@osf/shared/models'; import { LoaderService } from '@osf/shared/services'; +import { ContributorsSelectors, SubjectsSelectors } from '@osf/shared/stores'; import { defaultSteps } from '../../constants'; -import { FetchDraft, FetchSchemaBlocks, RegistriesSelectors } from '../../store'; +import { FetchDraft, FetchSchemaBlocks, RegistriesSelectors, UpdateStepValidation } from '../../store'; @Component({ selector: 'osf-drafts', @@ -33,16 +34,29 @@ export class DraftsComponent { protected readonly draftRegistration = select(RegistriesSelectors.getDraftRegistration); protected stepsValidation = select(RegistriesSelectors.getStepsValidation); protected readonly stepsData = select(RegistriesSelectors.getStepsData); + protected selectedSubjects = select(SubjectsSelectors.getSelectedSubjects); + protected initialContributors = select(ContributorsSelectors.getContributors); private readonly actions = createDispatchMap({ getSchemaBlocks: FetchSchemaBlocks, getDraftRegistration: FetchDraft, + updateStepValidation: UpdateStepValidation, }); get isReviewPage(): boolean { return this.router.url.includes('/review'); } + isMetaDataInvalid = computed(() => { + return ( + !this.draftRegistration()?.title || + !this.draftRegistration()?.description || + !this.draftRegistration()?.license?.id || + !this.selectedSubjects()?.length || + !this.initialContributors()?.length + ); + }); + defaultSteps: StepOption[] = []; isLoaded = false; @@ -123,6 +137,23 @@ export class DraftsComponent { this.currentStepIndex.set(reviewStepIndex); } }); + + effect(() => { + if (this.currentStepIndex() > 0) { + this.actions.updateStepValidation('0', this.isMetaDataInvalid()); + } + if (this.pages().length && this.currentStepIndex() > 0 && this.stepsData()) { + for (let i = 1; i < this.currentStepIndex(); i++) { + const pageStep = this.pages()[i - 1]; + const isStepInvalid = + pageStep?.questions?.some((question) => { + const questionData = this.stepsData()[question.responseKey!]; + return question.required && (Array.isArray(questionData) ? !questionData.length : !questionData); + }) || false; + this.actions.updateStepValidation(i.toString(), isStepInvalid); + } + } + }); } stepChange(step: StepOption): void { diff --git a/src/app/features/registries/components/metadata/metadata.component.ts b/src/app/features/registries/components/metadata/metadata.component.ts index 7e5f19e3..d8c95bb7 100644 --- a/src/app/features/registries/components/metadata/metadata.component.ts +++ b/src/app/features/registries/components/metadata/metadata.component.ts @@ -78,16 +78,20 @@ export class MetadataComponent implements OnDestroy { }), }); + isDraftDeleted = false; + isFormUpdated = false; + constructor() { effect(() => { const draft = this.draftRegistration(); - if (draft) { - this.initForm(draft); + if (draft && !this.isFormUpdated) { + this.updateFormValue(draft); + this.isFormUpdated = true; } }); } - private initForm(data: DraftRegistrationModel): void { + private updateFormValue(data: DraftRegistrationModel): void { this.metadataForm.patchValue({ title: data.title, description: data.description, @@ -123,9 +127,12 @@ export class MetadataComponent implements OnDestroy { headerKey: 'registries.deleteDraft', messageKey: 'registries.confirmDeleteDraft', onConfirm: () => { + const providerId = this.draftRegistration()?.providerId; this.actions.deleteDraft(this.draftId).subscribe({ next: () => { - this.router.navigateByUrl(`/registries/${this.draftRegistration()?.providerId}/new`); + // [NM] TODO: clear validation state + this.isDraftDeleted = true; + this.router.navigateByUrl(`/registries/${providerId}/new`); }, }); }, @@ -133,14 +140,16 @@ export class MetadataComponent implements OnDestroy { } ngOnDestroy(): void { - this.actions.updateStepValidation('0', this.metadataForm.invalid); - const changedFields = findChangedFields( - { title: this.metadataForm.value.title!, description: this.metadataForm.value.description! }, - { title: this.draftRegistration()?.title, description: this.draftRegistration()?.description } - ); - if (Object.keys(changedFields).length > 0) { - this.metadataForm.markAllAsTouched(); - this.actions.updateDraft(this.draftId, changedFields); + if (!this.isDraftDeleted) { + this.actions.updateStepValidation('0', this.metadataForm.invalid); + const changedFields = findChangedFields( + { title: this.metadataForm.value.title!, description: this.metadataForm.value.description! }, + { title: this.draftRegistration()?.title, description: this.draftRegistration()?.description } + ); + if (Object.keys(changedFields).length > 0) { + this.actions.updateDraft(this.draftId, changedFields); + this.metadataForm.markAllAsTouched(); + } } } } 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 a4f2c4e8..cce3fe3e 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 @@ -5,8 +5,6 @@ import { TranslatePipe } from '@ngx-translate/core'; import { Card } from 'primeng/card'; import { Message } from 'primeng/message'; -import { tap } from 'rxjs'; - import { ChangeDetectionStrategy, Component, effect, inject, input } from '@angular/core'; import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; @@ -75,15 +73,12 @@ export class RegistriesLicenseComponent { } selectLicense(license: License) { - this.actions - .saveLicense(this.draftId, license.id) - .pipe( - tap(() => { - this.control().markAsDirty(); - this.control().updateValueAndValidity(); - }) - ) - .subscribe(); + this.control().patchValue({ + id: license.id, + }); + this.control().markAsTouched(); + this.control().updateValueAndValidity(); + this.actions.saveLicense(this.draftId, license.id); } onFocusOut() { diff --git a/src/app/features/registries/components/review/review.component.ts b/src/app/features/registries/components/review/review.component.ts index 4a301fb9..76ba8583 100644 --- a/src/app/features/registries/components/review/review.component.ts +++ b/src/app/features/registries/components/review/review.component.ts @@ -63,27 +63,8 @@ export class ReviewComponent { protected stepsValidation = select(RegistriesSelectors.getStepsValidation); - isMetaDataInvalid = computed(() => { - return ( - !this.draftRegistration()?.title || - !this.draftRegistration()?.description || - !this.draftRegistration()?.license || - !this.subjects()?.length || - !this.contributors()?.length - ); - }); - - isStepsInvalid = computed(() => { - return this.pages().some((page) => { - return page.questions?.some((question) => { - const questionData = this.stepsData()[question.responseKey!]; - return question.required && (Array.isArray(questionData) ? !questionData.length : !questionData); - }); - }); - }); - isDraftInvalid = computed(() => { - return this.isMetaDataInvalid() || this.isStepsInvalid(); + return Object.values(this.stepsValidation()).some((step) => step.invalid); }); constructor() { @@ -107,6 +88,7 @@ export class ReviewComponent { onConfirm: () => { this.actions.deleteDraft(this.draftId()).subscribe({ next: () => { + // [NM] TODO: clear validation state this.router.navigateByUrl(`/registries/${this.draftRegistration()?.providerId}new`); }, }); diff --git a/src/app/features/registries/services/licenses.service.ts b/src/app/features/registries/services/licenses.service.ts index 77f059c0..cb802388 100644 --- a/src/app/features/registries/services/licenses.service.ts +++ b/src/app/features/registries/services/licenses.service.ts @@ -4,8 +4,10 @@ import { inject, Injectable } from '@angular/core'; import { JsonApiService } from '@osf/core/services'; import { License, LicenseOptions, LicensesResponseJsonApi } from '@osf/shared/models'; +import { DraftRegistrationModel } from '@osf/shared/models/registration'; import { LicensesMapper } from '../mappers'; +import { RegistrationMapper } from '../mappers/registration.mapper'; import { RegistrationDataJsonApi, RegistrationPayloadJsonApi } from '../models'; import { environment } from 'src/environments/environment'; @@ -49,7 +51,11 @@ export class LicensesService { ); } - updateLicense(registrationId: string, licenseId: string, licenseOptions?: LicenseOptions) { + updateLicense( + registrationId: string, + licenseId: string, + licenseOptions?: LicenseOptions + ): Observable { const payload: RegistrationPayloadJsonApi = { data: { type: 'draft_registrations', @@ -73,9 +79,8 @@ export class LicensesService { }, }; - return this.jsonApiService.patch( - `${this.apiUrl}/draft_registrations/${registrationId}/`, - payload - ); + return this.jsonApiService + .patch(`${this.apiUrl}/draft_registrations/${registrationId}/`, payload) + .pipe(map((response) => RegistrationMapper.fromDraftRegistrationResponse(response))); } } diff --git a/src/app/features/registries/store/handlers/licenses.handlers.ts b/src/app/features/registries/store/handlers/licenses.handlers.ts index ca1faab0..6c4079aa 100644 --- a/src/app/features/registries/store/handlers/licenses.handlers.ts +++ b/src/app/features/registries/store/handlers/licenses.handlers.ts @@ -39,25 +39,23 @@ export class LicensesHandlers { saveLicense(ctx: StateContext, { registrationId, licenseId, licenseOptions }: SaveLicense) { const state = ctx.getState(); ctx.patchState({ - licenses: { - ...state.licenses, + draftRegistration: { + ...state.draftRegistration, 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)) - (); + return this.licensesService.updateLicense(registrationId, licenseId, licenseOptions).pipe( + tap((response) => { + ctx.patchState({ + draftRegistration: { + data: response, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'licenses', error)) + ); } }