Skip to content

Commit 244ddf6

Browse files
committed
feat: add button to submit review
1 parent 06ed387 commit 244ddf6

16 files changed

Lines changed: 224 additions & 77 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,13 @@ Any property preceded by `opt` is optional and can be omitted. For example, to o
294294
dfx canister call backend update_user_profile '(record { user_id = "${userId}"; username = opt "${username}"; })'
295295
```
296296

297+
Or to only upgrade a user to a reviewer:
298+
299+
```bash
300+
301+
dfx canister call backend update_user_profile '(record { user_id = "${userId}"; config = opt variant { reviewer = record {} } })'
302+
```
303+
297304
### Listing open proposals
298305

299306
To list open replica version management proposals:

dfx.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"dfx": "0.21.0",
2+
"dfx": "0.24.3",
33
"output_env_file": ".env",
44
"version": 1,
55
"networks": {

lib/styles/global/buttons.scss

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,26 @@ $btn-disabled-color: rgba(colors.$primary, 0.45);
5858
border-color: $btn-disabled-color;
5959
background-color: transparent;
6060
}
61+
62+
&.btn--success {
63+
color: colors.$success;
64+
border-color: colors.$success;
65+
66+
&:hover:not(:disabled) {
67+
background-color: colors.$success;
68+
border-color: colors.$success;
69+
}
70+
}
71+
72+
&.btn--error {
73+
color: colors.$error;
74+
border-color: colors.$error;
75+
76+
&:hover:not(:disabled) {
77+
background-color: colors.$error;
78+
border-color: colors.$error;
79+
}
80+
}
6181
}
6282

6383
.btn-group {

src/frontend/src/app/core/api/review/review-api.mapper.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function mapCreateProposalReviewRequest(
3434
review_duration_mins: toCandidOpt(req.reviewDurationMins),
3535
summary: toCandidOpt(req.summary),
3636
build_reproduced: toCandidOpt(req.buildReproduced),
37-
vote: mapProposalVoteRequest(req.vote),
37+
vote: toCandidOpt(mapProposalVoteRequest(req.vote)),
3838
};
3939
}
4040

@@ -43,12 +43,11 @@ export function mapUpdateProposalReviewRequest(
4343
): UpdateProposalReviewApiRequest {
4444
return {
4545
proposal_id: req.proposalId,
46-
// [TODO] - connect with form once it's implemented
47-
status: [],
46+
status: toCandidOpt(mapProposalReviewStatusRequest(req.status)),
4847
review_duration_mins: toCandidOpt(req.reviewDurationMins),
4948
summary: toCandidOpt(req.summary),
5049
build_reproduced: toCandidOpt(req.buildReproduced),
51-
vote: mapProposalVoteRequest(req.vote),
50+
vote: toCandidOpt(mapProposalVoteRequest(req.vote)),
5251
};
5352
}
5453

@@ -101,6 +100,24 @@ export function mapGetProposalReviewResponse(
101100
};
102101
}
103102

103+
function mapProposalReviewStatusRequest(
104+
status?: ProposalReviewStatus | null,
105+
): ProposalReviewStatusApi | null {
106+
switch (status) {
107+
case ProposalReviewStatus.Published: {
108+
return { published: null };
109+
}
110+
111+
case ProposalReviewStatus.Draft: {
112+
return { draft: null };
113+
}
114+
115+
default: {
116+
return null;
117+
}
118+
}
119+
}
120+
104121
function mapProposalReviewStatusResponse(
105122
res: ProposalReviewStatusApi,
106123
): ProposalReviewStatus {
@@ -111,16 +128,16 @@ function mapProposalReviewStatusResponse(
111128
return ProposalReviewStatus.Draft;
112129
}
113130

114-
function mapProposalVoteRequest(vote?: boolean | null): [] | [ApiProposalVote] {
131+
function mapProposalVoteRequest(vote?: boolean | null): ApiProposalVote | null {
115132
switch (vote) {
116133
case true: {
117-
return [{ yes: null }];
134+
return { yes: null };
118135
}
119136
case false: {
120-
return [{ no: null }];
137+
return { no: null };
121138
}
122139
default: {
123-
return [];
140+
return null;
124141
}
125142
}
126143
}

src/frontend/src/app/core/api/review/review-api.model.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface CreateProposalReviewRequest {
1111

1212
export interface UpdateProposalReviewRequest {
1313
proposalId: string;
14+
status?: ProposalReviewStatus | null;
1415
reviewDurationMins?: number | null;
1516
summary?: string | null;
1617
buildReproduced?: boolean | null;
@@ -46,14 +47,14 @@ export interface GetProposalReviewResponse {
4647
}
4748

4849
export enum ProposalReviewStatus {
49-
Draft,
50-
Published,
50+
Draft = 'Draft',
51+
Published = 'Published',
5152
}
5253

5354
export enum ProposalReviewVote {
54-
Adopt,
55-
Reject,
56-
NoVote,
55+
Adopt = 'Adopt',
56+
Reject = 'Reject',
57+
NoVote = 'NoVote',
5758
}
5859

5960
export interface ProposalCommitReviewHighlight {

src/frontend/src/app/core/icons/loading-icon/loading-icon-component.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChangeDetectionStrategy, Component } from '@angular/core';
1+
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
22

33
@Component({
44
selector: 'app-loading-icon',
@@ -16,10 +16,17 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
1616
}
1717
1818
.loading-icon__svg {
19-
stroke: common.$primary;
2019
fill: none;
2120
stroke-width: 10px;
2221
}
22+
23+
.loading-icon__svg--primary {
24+
stroke: common.$primary;
25+
}
26+
27+
.loading-icon__svg--white {
28+
stroke: common.$white;
29+
}
2330
`,
2431
],
2532
template: `
@@ -32,7 +39,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
3239
preserveAspectRatio="xMidYMid"
3340
>
3441
<circle
35-
class="loading-icon__svg"
42+
[class]="'loading-icon__svg loading-icon__svg--' + theme()"
3643
cx="50"
3744
cy="50"
3845
r="32"
@@ -51,4 +58,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
5158
</svg>
5259
`,
5360
})
54-
export class LoadingIconComponent {}
61+
export class LoadingIconComponent {
62+
public readonly theme = input<'white' | 'primary'>('primary');
63+
}

src/frontend/src/app/core/state/review-submission/review-submission.service.ts

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
GetProposalReviewCommitResponse,
1616
UpdateProposalReviewRequest,
1717
ReviewCommitDetails,
18+
ProposalReviewStatus,
1819
} from '../../api';
1920
import { batchApiCall, filterNotNil, isNil, isNotNil } from '../../utils';
2021

@@ -79,15 +80,7 @@ export class ReviewSubmissionService {
7980
takeUntilDestroyed(),
8081
filterNotNil(),
8182
batchApiCall(review =>
82-
from(
83-
this.reviewApiService.updateProposalReview({
84-
proposalId: this.proposalId!,
85-
summary: review.summary,
86-
reviewDurationMins: review.reviewDurationMins,
87-
buildReproduced: review.buildReproduced,
88-
vote: review.vote,
89-
}),
90-
),
83+
from(this.reviewApiService.updateProposalReview(review)),
9184
),
9285
)
9386
.subscribe({});
@@ -135,6 +128,38 @@ export class ReviewSubmissionService {
135128
this.pendingReviewSubject.next(updatedReview);
136129
}
137130

131+
public async publishReview(): Promise<void> {
132+
await this.updateStatus(ProposalReviewStatus.Published);
133+
}
134+
135+
public async editReview(): Promise<void> {
136+
await this.updateStatus(ProposalReviewStatus.Draft);
137+
}
138+
139+
private async updateStatus(status: ProposalReviewStatus): Promise<void> {
140+
if (isNil(this.proposalId)) {
141+
throw new Error(
142+
'Tried to update review status without selecting a proposal',
143+
);
144+
}
145+
146+
const currentReview = this.reviewSubject.value;
147+
if (isNil(currentReview)) {
148+
throw new Error('Tried to update review status before review was loaded');
149+
}
150+
151+
await this.reviewApiService.updateProposalReview({
152+
proposalId: this.proposalId,
153+
status,
154+
});
155+
156+
this.reviewSubject.next({
157+
...currentReview,
158+
...(this.pendingReviewSubject.value || {}),
159+
status,
160+
});
161+
}
162+
138163
public addCommit(): void {
139164
if (isNil(this.proposalId)) {
140165
throw new Error(

src/frontend/src/app/core/ui/loading-button/loading-button.component.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,29 @@ import { LoadingIconComponent } from '../../icons';
88
changeDetection: ChangeDetectionStrategy.OnPush,
99
styles: [
1010
`
11+
.loading-button {
12+
&:disabled {
13+
cursor: not-allowed;
14+
}
15+
}
16+
1117
.loading-button__text--transparent {
1218
color: transparent;
1319
}
1420
`,
1521
],
1622
template: `
17-
<button [type]="type()" [disabled]="disabled()" [class]="btnClass()">
23+
<button
24+
[type]="type()"
25+
[disabled]="isSaving() || disabled()"
26+
[class]="'loading-button ' + btnClass()"
27+
>
1828
@if (isSaving()) {
19-
<app-loading-icon class="btn--loading" aria-label="Saving" />
29+
<app-loading-icon
30+
class="btn--loading"
31+
aria-label="Saving"
32+
[theme]="theme()"
33+
/>
2034
}
2135
2236
<div
@@ -31,6 +45,8 @@ import { LoadingIconComponent } from '../../icons';
3145
export class LoadingButtonComponent {
3246
public readonly type = input<'submit' | 'button'>('button');
3347

48+
public readonly theme = input<'primary' | 'white'>('primary');
49+
3450
public readonly disabled = input(false);
3551

3652
public readonly btnClass = input('');
Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import { inject } from '@angular/core';
2-
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
1+
import { inject, Signal } from '@angular/core';
2+
import { toSignal } from '@angular/core/rxjs-interop';
33
import { ActivatedRoute } from '@angular/router';
4-
import { Observable, map } from 'rxjs';
4+
import { map } from 'rxjs';
55

66
import { filterNotNil } from './nil';
77

8-
export function routeParam(name: string): Observable<string> {
8+
export function routeParamSignal(name: string): Signal<string | undefined> {
99
const route = inject(ActivatedRoute);
1010

11-
return route.paramMap.pipe(
12-
map(params => params.get(name)),
13-
filterNotNil(),
14-
takeUntilDestroyed(),
11+
return toSignal(
12+
route.paramMap.pipe(
13+
map(params => params.get(name)),
14+
filterNotNil(),
15+
),
1516
);
1617
}

src/frontend/src/app/pages/profile-edit/admin-personal-info-form/admin-personal-info-form.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ interface AdminProfileForm {
110110
<app-loading-button
111111
btnClass="btn"
112112
type="submit"
113-
[disabled]="profileForm().invalid || isSaving()"
113+
[disabled]="profileForm().invalid"
114114
[isSaving]="isSaving()"
115115
>
116116
Save

0 commit comments

Comments
 (0)