Skip to content

Commit 83bc244

Browse files
Merge pull request #187 from CenterForOpenScience/feat/274-select-components-before-registration
Feat/274 select components before registration
2 parents 08298b1 + b001334 commit 83bc244

21 files changed

+350
-42
lines changed

src/app/core/services/json-api.service.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ export class JsonApiService {
4343
});
4444
}
4545

46-
patch<T>(url: string, body: unknown): Observable<T> {
47-
return this.http.patch<JsonApiResponse<T, null>>(url, body).pipe(map((response) => response.data));
46+
patch<T>(url: string, body: unknown, params?: Record<string, unknown>): Observable<T> {
47+
return this.http
48+
.patch<JsonApiResponse<T, null>>(url, body, { params: this.buildHttpParams(params) })
49+
.pipe(map((response) => response.data));
4850
}
4951

5052
put<T>(url: string, body: unknown, params?: Record<string, unknown>): Observable<T> {

src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.html

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
1-
<p class="mb-2">{{ 'registries.review.confirmation.remember' | translate }}</p>
2-
<ul class="mb-4 pl-5">
3-
<li class="list-disc">{{ 'registries.review.confirmation.text1' | translate }}</li>
4-
<li class="list-disc">{{ 'registries.review.confirmation.text2' | translate }}</li>
5-
<li class="list-disc">{{ 'registries.review.confirmation.text3' | translate }}</li>
6-
<li class="list-disc">{{ 'registries.review.confirmation.text4' | translate }}</li>
7-
</ul>
1+
<p class="mb-2">
2+
{{ 'registries.review.confirmation.text1' | translate }}
3+
<strong>
4+
{{ 'registries.review.confirmation.text2' | translate }}
5+
</strong>
6+
</p>
7+
<p class="mb-2">{{ 'registries.review.confirmation.text3' | translate }}</p>
8+
<div class="mb-4">
9+
<p>{{ 'registries.review.confirmation.text4' | translate }}</p>
10+
<p>
11+
(
12+
<a href="https://help.osf.io/article/152-withdraw-a-registration" target="_blank" rel="noopener noreferrer">
13+
{{ 'common.labels.learnMore' | translate }}
14+
</a>
15+
)
16+
</p>
17+
</div>
18+
819
<form [formGroup]="form">
920
<div class="flex align-items-center gap-2 mb-2">
1021
<p-radioButton

src/app/features/registries/components/confirm-registration-dialog/confirm-registration-dialog.component.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,12 @@ export class ConfirmRegistrationDialogComponent {
6666
this.config.data.draftId,
6767
this.form.value.embargoDate,
6868
this.config.data.providerId,
69-
this.config.data.projectId
69+
this.config.data.projectId,
70+
this.config.data.components
7071
)
7172
.subscribe({
7273
complete: () => {
73-
this.dialogRef.close();
74+
this.dialogRef.close(true);
7475
},
7576
});
7677
}

src/app/features/registries/components/drafts/drafts.component.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@ import { TranslatePipe, TranslateService } from '@ngx-translate/core';
44

55
import { filter, tap } from 'rxjs';
66

7-
import { ChangeDetectionStrategy, Component, computed, effect, inject, Signal, signal } from '@angular/core';
7+
import { ChangeDetectionStrategy, Component, computed, effect, inject, OnDestroy, Signal, signal } from '@angular/core';
88
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
99
import { ActivatedRoute, NavigationEnd, Router, RouterOutlet } from '@angular/router';
1010

1111
import { StepperComponent, SubHeaderComponent } from '@osf/shared/components';
12+
import { ResourceType } from '@osf/shared/enums';
1213
import { StepOption } from '@osf/shared/models';
1314
import { LoaderService } from '@osf/shared/services';
14-
import { ContributorsSelectors, SubjectsSelectors } from '@osf/shared/stores';
15+
import {
16+
ContributorsSelectors,
17+
FetchSelectedSubjects,
18+
GetAllContributors,
19+
SubjectsSelectors,
20+
} from '@osf/shared/stores';
1521

1622
import { DEFAULT_STEPS } from '../../constants';
17-
import { FetchDraft, FetchSchemaBlocks, RegistriesSelectors, UpdateStepValidation } from '../../store';
23+
import { ClearState, FetchDraft, FetchSchemaBlocks, RegistriesSelectors, UpdateStepValidation } from '../../store';
1824

1925
@Component({
2026
selector: 'osf-drafts',
@@ -24,7 +30,7 @@ import { FetchDraft, FetchSchemaBlocks, RegistriesSelectors, UpdateStepValidatio
2430
changeDetection: ChangeDetectionStrategy.OnPush,
2531
providers: [TranslateService],
2632
})
27-
export class DraftsComponent {
33+
export class DraftsComponent implements OnDestroy {
2834
private readonly router = inject(Router);
2935
private readonly route = inject(ActivatedRoute);
3036
private readonly loaderService = inject(LoaderService);
@@ -36,11 +42,16 @@ export class DraftsComponent {
3642
protected readonly stepsData = select(RegistriesSelectors.getStepsData);
3743
protected selectedSubjects = select(SubjectsSelectors.getSelectedSubjects);
3844
protected initialContributors = select(ContributorsSelectors.getContributors);
45+
protected readonly contributors = select(ContributorsSelectors.getContributors);
46+
protected readonly subjects = select(SubjectsSelectors.getSelectedSubjects);
3947

4048
private readonly actions = createDispatchMap({
4149
getSchemaBlocks: FetchSchemaBlocks,
4250
getDraftRegistration: FetchDraft,
4351
updateStepValidation: UpdateStepValidation,
52+
clearState: ClearState,
53+
getContributors: GetAllContributors,
54+
getSubjects: FetchSelectedSubjects,
4455
});
4556

4657
get isReviewPage(): boolean {
@@ -116,6 +127,12 @@ export class DraftsComponent {
116127
if (!this.draftRegistration()) {
117128
this.actions.getDraftRegistration(this.registrationId);
118129
}
130+
if (!this.contributors()?.length) {
131+
this.actions.getContributors(this.registrationId, ResourceType.DraftRegistration);
132+
}
133+
if (!this.subjects()?.length) {
134+
this.actions.getSubjects(this.registrationId, ResourceType.DraftRegistration);
135+
}
119136
effect(() => {
120137
const registrationSchemaId = this.draftRegistration()?.registrationSchemaId;
121138
if (registrationSchemaId && !this.isLoaded) {
@@ -162,4 +179,8 @@ export class DraftsComponent {
162179
const pageLink = this.steps()[step.index].routeLink;
163180
this.router.navigate([`/registries/drafts/${this.registrationId}/`, pageLink]);
164181
}
182+
183+
ngOnDestroy(): void {
184+
this.actions.clearState();
185+
}
165186
}

src/app/features/registries/components/review/review.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ <h4 class="mb-2">{{ question.displayText }}</h4>
125125
<p-button
126126
type="button"
127127
[label]="'registries.review.register' | translate"
128-
[disabled]="isDraftInvalid()"
128+
[disabled]="isDraftLoading() || isDraftInvalid()"
129129
(click)="confirmRegistration()"
130130
></p-button>
131131
</div>

src/app/features/registries/components/review/review.component.ts

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { Tag } from 'primeng/tag';
1010

1111
import { map, of } from 'rxjs';
1212

13-
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
13+
import { ChangeDetectionStrategy, Component, computed, effect, inject } from '@angular/core';
1414
import { toSignal } from '@angular/core/rxjs-interop';
1515
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
1616

@@ -25,8 +25,9 @@ import {
2525
} from '@osf/shared/stores';
2626

2727
import { FieldType } from '../../enums';
28-
import { DeleteDraft, RegisterDraft, RegistriesSelectors } from '../../store';
28+
import { DeleteDraft, FetchProjectChildren, RegistriesSelectors } from '../../store';
2929
import { ConfirmRegistrationDialogComponent } from '../confirm-registration-dialog/confirm-registration-dialog.component';
30+
import { SelectComponentsDialogComponent } from '../select-components-dialog/select-components-dialog.component';
3031

3132
@Component({
3233
selector: 'osf-review',
@@ -46,17 +47,20 @@ export class ReviewComponent {
4647

4748
protected readonly pages = select(RegistriesSelectors.getPagesSchema);
4849
protected readonly draftRegistration = select(RegistriesSelectors.getDraftRegistration);
50+
protected readonly isDraftSubmitting = select(RegistriesSelectors.isDraftSubmitting);
51+
protected readonly isDraftLoading = select(RegistriesSelectors.isDraftLoading);
4952
protected readonly stepsData = select(RegistriesSelectors.getStepsData);
5053
protected readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES;
5154
protected readonly contributors = select(ContributorsSelectors.getContributors);
5255
protected readonly subjects = select(SubjectsSelectors.getSelectedSubjects);
56+
protected readonly components = select(RegistriesSelectors.getRegistrationComponents);
5357
protected readonly FieldType = FieldType;
5458

5559
protected actions = createDispatchMap({
5660
getContributors: GetAllContributors,
5761
getSubjects: FetchSelectedSubjects,
5862
deleteDraft: DeleteDraft,
59-
registerDraft: RegisterDraft,
63+
getProjectsComponents: FetchProjectChildren,
6064
});
6165

6266
private readonly draftId = toSignal(this.route.params.pipe(map((params) => params['id'])) ?? of(undefined));
@@ -74,6 +78,19 @@ export class ReviewComponent {
7478
if (!this.subjects()?.length) {
7579
this.actions.getSubjects(this.draftId(), ResourceType.DraftRegistration);
7680
}
81+
82+
let componentsLoaded = false;
83+
effect(() => {
84+
if (!this.isDraftSubmitting()) {
85+
const draftRegistrations = this.draftRegistration();
86+
if (draftRegistrations?.hasProject) {
87+
if (!componentsLoaded) {
88+
this.actions.getProjectsComponents(draftRegistrations?.branchedFrom?.id ?? '');
89+
componentsLoaded = true;
90+
}
91+
}
92+
}
93+
});
7794
}
7895

7996
goBack(): void {
@@ -97,6 +114,32 @@ export class ReviewComponent {
97114
}
98115

99116
confirmRegistration(): void {
117+
if (this.components()?.length) {
118+
this.openSelectComponentsForRegistrationDialog();
119+
} else {
120+
this.openConfirmRegistrationDialog();
121+
}
122+
}
123+
124+
openSelectComponentsForRegistrationDialog(): void {
125+
this.dialogService
126+
.open(SelectComponentsDialogComponent, {
127+
width: '552px',
128+
focusOnShow: false,
129+
header: this.translateService.instant('registries.review.selectComponents.title'),
130+
closeOnEscape: true,
131+
modal: true,
132+
data: {
133+
parent: this.draftRegistration()?.branchedFrom,
134+
components: this.components(),
135+
},
136+
})
137+
.onClose.subscribe((selectedComponents) => {
138+
this.openConfirmRegistrationDialog(selectedComponents);
139+
});
140+
}
141+
142+
openConfirmRegistrationDialog(components?: string[]): void {
100143
this.dialogService
101144
.open(ConfirmRegistrationDialogComponent, {
102145
width: '552px',
@@ -106,13 +149,21 @@ export class ReviewComponent {
106149
modal: true,
107150
data: {
108151
draftId: this.draftId(),
109-
projectId: this.draftRegistration()?.branchedFrom,
152+
projectId: this.draftRegistration()?.branchedFrom?.id,
110153
providerId: this.draftRegistration()?.providerId,
154+
components,
111155
},
112156
})
113-
.onClose.subscribe(() => {
114-
this.toastService.showSuccess('registries.review.confirmation.successMessage');
115-
// [NM] TODO: Navigate to the newly created registration page
157+
.onClose.subscribe((res) => {
158+
if (res) {
159+
this.toastService.showSuccess('registries.review.confirmation.successMessage');
160+
// [NM] TODO: Navigate to the newly created registration page
161+
this.router.navigate([`registries/my-registrations`]);
162+
} else {
163+
if (this.components()?.length) {
164+
this.openSelectComponentsForRegistrationDialog();
165+
}
166+
}
116167
});
117168
}
118169
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<p class="mb-2">{{ 'registries.review.selectComponents.description' | translate }}</p>
2+
3+
<p-tree [value]="components" selectionMode="checkbox" class="w-full md:w-[30rem]" [(selection)]="selectedComponents" />
4+
5+
<div class="flex justify-content-end gap-2 mt-4">
6+
<p-button
7+
class="w-12rem btn-full-width"
8+
[label]="'common.buttons.back' | translate"
9+
severity="info"
10+
(click)="dialogRef.close()"
11+
/>
12+
<p-button class="w-12rem btn-full-width" [label]="'common.buttons.continue' | translate" (click)="continue()" />
13+
</div>

src/app/features/registries/components/select-components-dialog/select-components-dialog.component.scss

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { SelectComponentsDialogComponent } from './select-components-dialog.component';
4+
5+
describe('SelectComponentsDialogComponent', () => {
6+
let component: SelectComponentsDialogComponent;
7+
let fixture: ComponentFixture<SelectComponentsDialogComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [SelectComponentsDialogComponent],
12+
}).compileComponents();
13+
14+
fixture = TestBed.createComponent(SelectComponentsDialogComponent);
15+
component = fixture.componentInstance;
16+
fixture.detectChanges();
17+
});
18+
19+
it('should create', () => {
20+
expect(component).toBeTruthy();
21+
});
22+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { TranslatePipe } from '@ngx-translate/core';
2+
3+
import { TreeNode } from 'primeng/api';
4+
import { Button } from 'primeng/button';
5+
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
6+
import { Tree } from 'primeng/tree';
7+
8+
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
9+
10+
import { Project } from '../../models';
11+
12+
@Component({
13+
selector: 'osf-select-components-dialog',
14+
imports: [TranslatePipe, Tree, Button],
15+
templateUrl: './select-components-dialog.component.html',
16+
styleUrl: './select-components-dialog.component.scss',
17+
changeDetection: ChangeDetectionStrategy.OnPush,
18+
})
19+
export class SelectComponentsDialogComponent {
20+
protected readonly dialogRef = inject(DynamicDialogRef);
21+
readonly config = inject(DynamicDialogConfig);
22+
selectedComponents: TreeNode[] = [];
23+
parent: Project = this.config.data.parent;
24+
components: TreeNode[] = [];
25+
26+
constructor() {
27+
this.components = [
28+
{
29+
label: this.parent.title,
30+
selectable: false,
31+
expanded: true,
32+
key: this.parent.id,
33+
data: this.parent.id,
34+
children: this.config.data.components.map(this.mapProjectToTreeNode),
35+
},
36+
];
37+
this.selectedComponents.push({ key: this.parent.id });
38+
}
39+
40+
private mapProjectToTreeNode = (project: Project): TreeNode => {
41+
this.selectedComponents.push({
42+
key: project.id,
43+
});
44+
return {
45+
label: project.title,
46+
data: project.id,
47+
key: project.id,
48+
expanded: true,
49+
children: project.children?.map(this.mapProjectToTreeNode) ?? [],
50+
};
51+
};
52+
53+
continue() {
54+
const selectedComponentsSet = new Set<string>([...this.selectedComponents.map((c) => c.key!), this.parent.id]);
55+
this.dialogRef.close([...selectedComponentsSet]);
56+
}
57+
}

0 commit comments

Comments
 (0)