Skip to content

Commit 5008f9b

Browse files
committed
feat(profile): manage consents
* user can manage his consents in consents page * the user is redirected from email url to page /profile/consents/:consentId BREAKING CHANGE: * add new item 'consents' to 'displayed_tabs' array if you want to show consents page in menu
1 parent e303077 commit 5008f9b

27 files changed

+1467
-5
lines changed

angular.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,6 @@
696696
},
697697
"schematics": {
698698
"@nrwl/angular:component": {
699-
"styleext": "scss"
700699
}
701700
},
702701
"tags": []

apps/user-profile/src/app/app-routing.module.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import {
2222
LoginScreenComponent,
2323
LoginScreenServiceAccessComponent,
2424
} from '@perun-web-apps/perun/login';
25+
import { ConsentsPageComponent } from './pages/consents-page/consents-page.component';
26+
import { ConsentRequestComponent } from './pages/consents-page/consent-request/consent-request.component';
27+
import { ConsentsPreviewComponent } from './pages/consents-page/consents-preview/consents-preview.component';
2528

2629
const routes: Routes = [
2730
{
@@ -72,6 +75,23 @@ const routes: Routes = [
7275
component: PrivacyPageComponent,
7376
data: { breadcrumb: 'MENU_ITEMS.PRIVACY' },
7477
},
78+
{
79+
path: 'consents',
80+
component: ConsentsPageComponent,
81+
data: { breadcrumb: 'MENU_ITEMS.CONSENTS' },
82+
children: [
83+
{
84+
path: '',
85+
component: ConsentsPreviewComponent,
86+
data: { breadcrumb: 'MENU_ITEMS.CONSENTS' },
87+
},
88+
{
89+
path: ':consentId',
90+
component: ConsentRequestComponent,
91+
data: { breadcrumb: 'MENU_ITEMS.CONSENT_REQUEST' },
92+
},
93+
],
94+
},
7595
{
7696
path: 'settings',
7797
component: SettingsPageComponent,

apps/user-profile/src/app/app.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ import { PerunLoginModule } from '@perun-web-apps/perun/login';
7575
import { PerunUtilsModule } from '@perun-web-apps/perun/utils';
7676
import { MatMenuModule } from '@angular/material/menu';
7777
import { OAuthModule } from 'angular-oauth2-oidc';
78+
import { ConsentsPageComponent } from './pages/consents-page/consents-page.component';
79+
import { ConsentRequestComponent } from './pages/consents-page/consent-request/consent-request.component';
80+
import { ConsentsPreviewComponent } from './pages/consents-page/consents-preview/consents-preview.component';
7881

7982
export const API_INTERCEPTOR_PROVIDER: Provider = {
8083
provide: HTTP_INTERCEPTORS,
@@ -129,6 +132,9 @@ const loadConfigs = (appConfig: UserProfileConfigService) => () => appConfig.ini
129132
BreadcrumbsComponent,
130133
SettingsAuthenticationComponent,
131134
AddAuthImgDialogComponent,
135+
ConsentsPageComponent,
136+
ConsentRequestComponent,
137+
ConsentsPreviewComponent,
132138
],
133139
imports: [
134140
BrowserModule,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<div class="d-flex">
2+
<div class="card p-4 mat-elevation-z3 ml-auto mr-auto">
3+
<mat-spinner class="ml-auto mr-auto" *ngIf="loading"></mat-spinner>
4+
<div *ngIf="!loading">
5+
<h1 class="page-title">
6+
{{ 'CONSENTS.REQUEST.TITLE' | customTranslate | translate }}
7+
</h1>
8+
<div class="page-subtitle">{{consent.consentHub.name}}</div>
9+
10+
<div>
11+
{{ 'CONSENTS.REQUEST.CONSENT_TEXT_UNSIGNED' | customTranslate | translate }}:
12+
<ul>
13+
<li *ngFor="let attribute of consent.attributes">
14+
{{ attribute.displayName }}
15+
</li>
16+
</ul>
17+
</div>
18+
<!-- <mat-checkbox class="minimize-checkbox mb-n2">{{ 'CONSENTS.REQUEST.CHECKBOX' | customTranslate | translate }}</mat-checkbox>-->
19+
<div class="d-flex">
20+
<button mat-flat-button color="warn" class="mr-2 ml-auto" (click)="rejectConsent()">
21+
{{ 'CONSENTS.REQUEST.REJECT_CONSENT_BUTTON' | customTranslate | translate }}
22+
</button>
23+
<button mat-flat-button color="accent" (click)="grantConsent()">
24+
{{ 'CONSENTS.REQUEST.GRANT_CONSENT_BUTTON' | customTranslate | translate }}
25+
</button>
26+
</div>
27+
</div>
28+
</div>
29+
</div>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.minimize-checkbox {
2+
font-size: 0.7rem;
3+
color: grey;
4+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
2+
import { ApiRequestConfigurationService, NotificatorService } from '@perun-web-apps/perun/services';
3+
import { TranslateService } from '@ngx-translate/core';
4+
import { ActivatedRoute, Router } from '@angular/router';
5+
import { Consent, ConsentsManagerService } from '@perun-web-apps/perun/openapi';
6+
7+
@Component({
8+
selector: 'perun-web-apps-consent-request',
9+
templateUrl: './consent-request.component.html',
10+
styleUrls: ['./consent-request.component.scss'],
11+
encapsulation: ViewEncapsulation.None,
12+
})
13+
export class ConsentRequestComponent implements OnInit {
14+
constructor(
15+
private notificator: NotificatorService,
16+
private translate: TranslateService,
17+
private consentService: ConsentsManagerService,
18+
private route: ActivatedRoute,
19+
private apiRequest: ApiRequestConfigurationService,
20+
private router: Router
21+
) {}
22+
23+
consent: Consent;
24+
loading = false;
25+
26+
ngOnInit(): void {
27+
this.loading = true;
28+
this.route.params.subscribe((params) => {
29+
const consentId = params['consentId'];
30+
this.apiRequest.dontHandleErrorForNext();
31+
this.consentService.getConsentById(consentId).subscribe(
32+
(consent) => {
33+
this.consent = consent;
34+
if (this.consent.status !== 'UNSIGNED') {
35+
this.router.navigate(['/profile', 'consents'], { queryParamsHandling: 'merge' });
36+
}
37+
this.loading = false;
38+
},
39+
(error) => {
40+
this.loading = false;
41+
if (error.error.name !== 'ConsentNotExistsException') {
42+
this.notificator.showRPCError(error.error);
43+
}
44+
this.router.navigate(['/profile', 'consents'], { queryParamsHandling: 'merge' });
45+
}
46+
);
47+
});
48+
}
49+
50+
grantConsent() {
51+
this.loading = true;
52+
this.consentService.changeConsentStatus(this.consent.id, 'GRANTED').subscribe(
53+
() => {
54+
this.notificator.showSuccess(
55+
this.translate.instant('CONSENTS.CONSENT_GRANTED') + this.consent.consentHub.name
56+
);
57+
this.router.navigate(['/profile', 'consents'], { queryParamsHandling: 'merge' });
58+
},
59+
() => (this.loading = false)
60+
);
61+
}
62+
63+
rejectConsent() {
64+
this.loading = true;
65+
this.consentService.changeConsentStatus(this.consent.id, 'REVOKED').subscribe(
66+
() => {
67+
this.notificator.showSuccess(
68+
this.translate.instant('CONSENTS.CONSENT_REJECTED') + this.consent.consentHub.name
69+
);
70+
this.router.navigate(['/profile', 'consents'], { queryParamsHandling: 'merge' });
71+
},
72+
() => (this.loading = false)
73+
);
74+
}
75+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<router-outlet></router-outlet>

apps/user-profile/src/app/pages/consents-page/consents-page.component.scss

Whitespace-only changes.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Component } from '@angular/core';
2+
3+
@Component({
4+
selector: 'perun-web-apps-consents-page',
5+
templateUrl: './consents-page.component.html',
6+
styleUrls: ['./consents-page.component.scss'],
7+
})
8+
export class ConsentsPageComponent {
9+
constructor() {}
10+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<h1 class="page-title mt-2">
2+
{{ 'CONSENTS.TITLE' | customTranslate | translate }}
3+
</h1>
4+
<div class="user-theme">
5+
<div *ngIf="!loading && unsignedConsents.length !== 0">
6+
<div class="page-subtitle">
7+
{{ 'CONSENTS.UNSIGNED_CONSENTS' | customTranslate | translate }}
8+
</div>
9+
10+
<perun-web-apps-immediate-filter
11+
[placeholder]="'CONSENTS.FILTER' | customTranslate | translate"
12+
(filter)="applyFilterUnsigned($event)">
13+
</perun-web-apps-immediate-filter>
14+
15+
<perun-web-apps-consents-list
16+
[consents]="unsignedConsents"
17+
[filterValue]="filterValueUnsigned"
18+
[tableId]="'TABLE_USER_CONSENTS'"
19+
(grantConsent)="grantConsent($event)"
20+
(rejectConsent)="rejectConsent($event)"
21+
[displayedColumns]="['status', 'name']"></perun-web-apps-consents-list>
22+
23+
<!-- <button mat-flat-button color="accent" class="mt-3" (click)="grantAll()">-->
24+
<!-- {{ 'CONSENTS.GRANT_ALL' | customTranslate | translate }}-->
25+
<!-- </button>-->
26+
</div>
27+
28+
<div *ngIf="!loading">
29+
<div class="page-subtitle mt-4">
30+
{{ 'CONSENTS.PROCESSED_CONSENTS' | customTranslate | translate }}
31+
</div>
32+
<perun-web-apps-immediate-filter
33+
[placeholder]="'CONSENTS.FILTER' | customTranslate | translate"
34+
(filter)="applyFilterSigned($event)">
35+
</perun-web-apps-immediate-filter>
36+
<perun-web-apps-consents-list
37+
[consents]="signedConsents"
38+
[tableId]="'TABLE_USER_CONSENTS'"
39+
[filterValue]="filterValueSigned"
40+
(grantConsent)="grantConsent($event)"
41+
(rejectConsent)="rejectConsent($event)"
42+
[displayedColumns]="['status', 'name']"></perun-web-apps-consents-list>
43+
</div>
44+
45+
<mat-spinner class="ml-auto mr-auto" *ngIf="loading"></mat-spinner>
46+
</div>

apps/user-profile/src/app/pages/consents-page/consents-preview/consents-preview.component.scss

Whitespace-only changes.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { Component, OnInit } from '@angular/core';
2+
import { Router } from '@angular/router';
3+
import { NotificatorService, StoreService } from '@perun-web-apps/perun/services';
4+
import { TranslateService } from '@ngx-translate/core';
5+
import { Consent, ConsentsManagerService } from '@perun-web-apps/perun/openapi';
6+
7+
@Component({
8+
selector: 'perun-web-apps-consents-preview',
9+
templateUrl: './consents-preview.component.html',
10+
styleUrls: ['./consents-preview.component.scss'],
11+
})
12+
export class ConsentsPreviewComponent implements OnInit {
13+
constructor(
14+
private router: Router,
15+
private notificator: NotificatorService,
16+
private translate: TranslateService,
17+
private storeService: StoreService,
18+
private consentService: ConsentsManagerService
19+
) {}
20+
21+
loading = false;
22+
unsignedConsents: Consent[] = [];
23+
signedConsents: Consent[] = [];
24+
filterValueUnsigned = '';
25+
filterValueSigned = '';
26+
27+
ngOnInit(): void {
28+
this.loading = true;
29+
this.consentService.getConsentsForUser(this.storeService.getPerunPrincipal().userId).subscribe(
30+
(consents) => {
31+
this.unsignedConsents = consents.filter((item) => item.status === 'UNSIGNED');
32+
this.signedConsents = consents.filter((item) => item.status !== 'UNSIGNED');
33+
this.loading = false;
34+
},
35+
() => (this.loading = false)
36+
);
37+
}
38+
39+
grantAll() {
40+
//call to backend
41+
this.loading = true;
42+
this.notificator.showSuccess(this.translate.instant('CONSENTS.GRANT_ALL_NOTIFICATION'));
43+
this.loading = false;
44+
}
45+
46+
rejectConsent(id: number) {
47+
this.loading = true;
48+
this.consentService.changeConsentStatus(id, 'REVOKED').subscribe(
49+
() => {
50+
const consent =
51+
this.unsignedConsents.find((c) => c.id === id) ??
52+
this.signedConsents.find((c) => c.id === id);
53+
this.moveConsent(consent);
54+
const translatedNotification =
55+
consent.status === 'GRANTED'
56+
? this.translate.instant('CONSENTS.CONSENT_REVOKED')
57+
: this.translate.instant('CONSENTS.CONSENT_REJECTED');
58+
consent.status = 'REVOKED';
59+
this.notificator.showSuccess(translatedNotification + consent.consentHub.name);
60+
this.loading = false;
61+
},
62+
() => (this.loading = false)
63+
);
64+
}
65+
66+
moveConsent(consent: Consent) {
67+
if (consent.status === 'UNSIGNED') {
68+
this.signedConsents = [...this.signedConsents, consent];
69+
this.unsignedConsents = this.unsignedConsents.filter((c) => c.id !== consent.id);
70+
}
71+
}
72+
73+
grantConsent(id: number) {
74+
this.loading = true;
75+
this.consentService.changeConsentStatus(id, 'GRANTED').subscribe(
76+
() => {
77+
const consent =
78+
this.unsignedConsents.find((c) => c.id === id) ??
79+
this.signedConsents.find((c) => c.id === id);
80+
this.moveConsent(consent);
81+
consent.status = 'GRANTED';
82+
this.notificator.showSuccess(
83+
this.translate.instant('CONSENTS.CONSENT_GRANTED') + consent.consentHub.name
84+
);
85+
this.loading = false;
86+
},
87+
() => (this.loading = false)
88+
);
89+
}
90+
91+
applyFilterUnsigned($event: string) {
92+
this.filterValueUnsigned = $event;
93+
}
94+
95+
applyFilterSigned($event: string) {
96+
this.filterValueSigned = $event;
97+
}
98+
}

apps/user-profile/src/app/services/side-menu-item.service.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ export class SideMenuItemService {
6666
tabName: 'privacy',
6767
});
6868
break;
69+
case 'consents':
70+
items.push({
71+
label: 'MENU_ITEMS.CONSENTS',
72+
icon: 'fact_check',
73+
link: '/profile/consents',
74+
activatedRegex: '^/profile/consents',
75+
tabName: 'consents',
76+
});
77+
break;
6978
case 'settings':
7079
items.push({
7180
label: 'MENU_ITEMS.SETTINGS',

apps/user-profile/src/assets/config/defaultConfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
"groups",
9595
"vos",
9696
"privacy",
97+
"consents",
9798
"settings",
9899
"data_quotas",
99100
"ssh_keys",

0 commit comments

Comments
 (0)