Skip to content

Commit 791903a

Browse files
committed
MOBILE-4329 dataprivacy: Implement data privacy pages
1 parent 84d83b0 commit 791903a

File tree

15 files changed

+778
-6
lines changed

15 files changed

+778
-6
lines changed

scripts/langindex.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,6 +1677,40 @@
16771677
"core.courses.totalcoursesearchresults": "local_moodlemobileapp",
16781678
"core.currentdevice": "local_moodlemobileapp",
16791679
"core.custom": "form",
1680+
"core.dataprivacy.cancelrequest": "tool_dataprivacy",
1681+
"core.dataprivacy.cancelrequestconfirmation": "tool_dataprivacy",
1682+
"core.dataprivacy.contactdataprotectionofficer": "tool_dataprivacy",
1683+
"core.dataprivacy.createnewdatarequest": "tool_dataprivacy",
1684+
"core.dataprivacy.datarequests": "tool_dataprivacy",
1685+
"core.dataprivacy.daterequested": "tool_dataprivacy",
1686+
"core.dataprivacy.deletemyaccount": "tool_dataprivacy",
1687+
"core.dataprivacy.message": "tool_dataprivacy",
1688+
"core.dataprivacy.newrequest": "tool_dataprivacy",
1689+
"core.dataprivacy.nodatarequests": "tool_dataprivacy",
1690+
"core.dataprivacy.pluginname": "tool_dataprivacy",
1691+
"core.dataprivacy.replyto": "tool_dataprivacy",
1692+
"core.dataprivacy.requestactions": "tool_dataprivacy",
1693+
"core.dataprivacy.requestby": "tool_dataprivacy",
1694+
"core.dataprivacy.requestcomments": "tool_dataprivacy",
1695+
"core.dataprivacy.requeststatus": "tool_dataprivacy",
1696+
"core.dataprivacy.requestsubmitted": "tool_dataprivacy",
1697+
"core.dataprivacy.requesttype": "tool_dataprivacy",
1698+
"core.dataprivacy.requesttype_help": "tool_dataprivacy",
1699+
"core.dataprivacy.requesttypedelete": "tool_dataprivacy",
1700+
"core.dataprivacy.requesttypeexport": "tool_dataprivacy",
1701+
"core.dataprivacy.requesttypeothers": "tool_dataprivacy",
1702+
"core.dataprivacy.send": "tool_dataprivacy",
1703+
"core.dataprivacy.statusapproved": "tool_dataprivacy",
1704+
"core.dataprivacy.statusawaitingapproval": "tool_dataprivacy",
1705+
"core.dataprivacy.statuscancelled": "tool_dataprivacy",
1706+
"core.dataprivacy.statuscomplete": "tool_dataprivacy",
1707+
"core.dataprivacy.statusdeleted": "tool_dataprivacy",
1708+
"core.dataprivacy.statusexpired": "tool_dataprivacy",
1709+
"core.dataprivacy.statuspending": "tool_dataprivacy",
1710+
"core.dataprivacy.statuspreprocessing": "tool_dataprivacy",
1711+
"core.dataprivacy.statusprocessing": "tool_dataprivacy",
1712+
"core.dataprivacy.statusready": "tool_dataprivacy",
1713+
"core.dataprivacy.statusrejected": "tool_dataprivacy",
16801714
"core.datastoredoffline": "local_moodlemobileapp",
16811715
"core.date": "moodle",
16821716
"core.datecreated": "repository",

src/core/components/empty-box/empty-box.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
:host {
44
--image-size: 120px;
5-
--icon-color: var(--text-color);
5+
--icon-color: var(--subdued-text-color);
66

77
display: flex;
88
flex-direction: column;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// (C) Copyright 2015 Moodle Pty Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { NgModule } from '@angular/core';
16+
import { CoreSharedModule } from '@/core/shared.module';
17+
import { CoreDataPrivacyContactDPOComponent } from './contactdpo/contactdpo';
18+
import { CoreDataPrivacyNewRequestComponent } from './newrequest/newrequest';
19+
20+
@NgModule({
21+
declarations: [
22+
CoreDataPrivacyContactDPOComponent,
23+
CoreDataPrivacyNewRequestComponent,
24+
],
25+
imports: [
26+
CoreSharedModule,
27+
],
28+
exports: [
29+
CoreDataPrivacyContactDPOComponent,
30+
CoreDataPrivacyNewRequestComponent,
31+
],
32+
})
33+
export class CoreDataPrivacyComponentsModule {}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<ion-header>
2+
<ion-toolbar>
3+
<ion-title>
4+
<h1>{{ 'core.dataprivacy.contactdataprotectionofficer' | translate }}</h1>
5+
</ion-title>
6+
<ion-buttons slot="end">
7+
<ion-button fill="clear" (click)="close()" [attr.aria-label]="'core.close' | translate">
8+
<ion-icon slot="icon-only" name="fas-xmark" aria-hidden="true" />
9+
</ion-button>
10+
</ion-buttons>
11+
</ion-toolbar>
12+
</ion-header>
13+
<ion-content>
14+
<form [formGroup]="form" name="contactDPO" (ngSubmit)="send($event)">
15+
<ion-item *ngIf="email">
16+
<ion-label>
17+
<p class="item-heading">
18+
{{ 'core.dataprivacy.replyto' | translate }}
19+
</p>
20+
<p>{{ email }}</p>
21+
</ion-label>
22+
</ion-item>
23+
<ion-item>
24+
<ion-textarea labelPlacement="floating" placeholder="{{ 'core.dataprivacy.message' | translate }}" rows="5"
25+
[(ngModel)]="message" name="text" [required]="true" formControlName="message">
26+
<div [core-mark-required]="true" slot="label">
27+
{{ 'core.dataprivacy.message' | translate }}
28+
</div>
29+
</ion-textarea>
30+
</ion-item>
31+
</form>
32+
</ion-content>
33+
<ion-footer slot="fixed" class="ion-padding">
34+
<ion-button expand="block" (click)="send($event)" [disabled]="!form.valid">
35+
{{ 'core.dataprivacy.send' | translate }}
36+
</ion-button>
37+
</ion-footer>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// (C) Copyright 2015 Moodle Pty Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { Component, OnInit } from '@angular/core';
16+
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
17+
import { CoreDataPrivacy } from '@features/dataprivacy/services/dataprivacy';
18+
import { CoreUser } from '@features/user/services/user';
19+
import { CoreSites } from '@services/sites';
20+
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
21+
import { CoreUtils } from '@services/utils/utils';
22+
23+
import { ModalController } from '@singletons';
24+
25+
/**
26+
* Component that displays the contact DPO page.
27+
*/
28+
@Component({
29+
selector: 'core-data-privacy-contact-dpo',
30+
templateUrl: 'contactdpo.html',
31+
})
32+
export class CoreDataPrivacyContactDPOComponent implements OnInit {
33+
34+
message = '';
35+
email = '';
36+
37+
// Form variables.
38+
form: FormGroup;
39+
40+
constructor(
41+
protected fb: FormBuilder,
42+
) {
43+
this.form = new FormGroup({});
44+
45+
// Initialize form variables.
46+
this.form.addControl('message', this.fb.control('', Validators.required));
47+
}
48+
49+
/**
50+
* @inheritdoc
51+
*/
52+
async ngOnInit(): Promise<void> {
53+
const modal = await CoreDomUtils.showModalLoading();
54+
55+
// Get current user email.
56+
const userId = CoreSites.getCurrentSiteUserId();
57+
const user = await CoreUtils.ignoreErrors(CoreUser.getProfile(userId));
58+
59+
this.email = user?.email || '';
60+
61+
modal.dismiss();
62+
}
63+
64+
/**
65+
* Sends the message.
66+
*/
67+
async send(event: Event): Promise<void> {
68+
event.preventDefault();
69+
event.stopPropagation();
70+
71+
const modal = await CoreDomUtils.showModalLoading();
72+
73+
try {
74+
// Send the message.
75+
const succeed = await CoreDataPrivacy.contactDPO(this.message);
76+
if (succeed) {
77+
CoreDomUtils.showToast('core.dataprivacy.requestsubmitted', true, ToastDuration.LONG);
78+
ModalController.dismiss(true);
79+
}
80+
} catch (error) {
81+
CoreDomUtils.showErrorModalDefault(error, 'Error sending data privacy request');
82+
} finally {
83+
modal.dismiss();
84+
}
85+
}
86+
87+
/**
88+
* Close modal.
89+
*/
90+
close(): void {
91+
ModalController.dismiss();
92+
}
93+
94+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<ion-header>
2+
<ion-toolbar>
3+
<ion-title>
4+
<h1>{{ 'core.dataprivacy.createnewdatarequest' | translate }}</h1>
5+
</ion-title>
6+
<ion-buttons slot="end">
7+
<ion-button fill="clear" (click)="close()" [attr.aria-label]="'core.close' | translate">
8+
<ion-icon slot="icon-only" name="fas-xmark" aria-hidden="true" />
9+
</ion-button>
10+
</ion-buttons>
11+
</ion-toolbar>
12+
</ion-header>
13+
<ion-content>
14+
<form [formGroup]="form" name="newRequest" (ngSubmit)="send($event)">
15+
<ion-item class="ion-text-wrap">
16+
<ion-label>
17+
<p>
18+
{{ 'core.dataprivacy.requesttype_help' | translate }}
19+
</p>
20+
<p class="item-heading" [core-mark-required]="true">
21+
{{ 'core.dataprivacy.requesttype' | translate }}
22+
</p>
23+
</ion-label>
24+
</ion-item>
25+
<ion-item class="ion-text-wrap">
26+
<ion-radio-group name="type" formControlName="type">
27+
<ion-radio [value]="1" *ngIf="accessInfo?.cancreatedatadownloadrequest">
28+
{{ 'core.dataprivacy.requesttypeexport' | translate }}
29+
</ion-radio>
30+
<ion-radio [value]="2" *ngIf="accessInfo?.cancreatedatadeletionrequest">
31+
{{ 'core.dataprivacy.requesttypedelete' | translate }}
32+
</ion-radio>
33+
</ion-radio-group>
34+
</ion-item>
35+
<ion-item>
36+
<ion-textarea labelPlacement="stacked" placeholder="{{ 'core.dataprivacy.requestcomments' | translate }}" rows="5"
37+
[(ngModel)]="message" name="text" formControlName="message">
38+
<div slot="label">
39+
{{ 'core.dataprivacy.requestcomments' | translate }}
40+
</div>
41+
</ion-textarea>
42+
</ion-item>
43+
44+
</form>
45+
</ion-content>
46+
<ion-footer slot="fixed" class="ion-padding">
47+
<ion-button expand="block" (click)="send($event)" [disabled]="!form.valid">
48+
{{ 'core.dataprivacy.send' | translate }}
49+
</ion-button>
50+
</ion-footer>
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// (C) Copyright 2015 Moodle Pty Ltd.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { Component, Input, OnInit } from '@angular/core';
16+
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
17+
import {
18+
CoreDataPrivacy,
19+
CoreDataPrivacyDataRequestType,
20+
CoreDataPrivacyGetAccessInformationWSResponse,
21+
} from '@features/dataprivacy/services/dataprivacy';
22+
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
23+
24+
import { ModalController } from '@singletons';
25+
26+
/**
27+
* Component that displays the new request page.
28+
*/
29+
@Component({
30+
selector: 'core-data-privacy-new-request',
31+
templateUrl: 'newrequest.html',
32+
})
33+
export class CoreDataPrivacyNewRequestComponent implements OnInit {
34+
35+
@Input() accessInfo?: CoreDataPrivacyGetAccessInformationWSResponse;
36+
37+
message = '';
38+
39+
// Form variables.
40+
form: FormGroup;
41+
typeControl: FormControl<CoreDataPrivacyDataRequestType>;
42+
43+
constructor(
44+
protected fb: FormBuilder,
45+
) {
46+
this.form = new FormGroup({});
47+
48+
// Initialize form variables.
49+
this.typeControl = this.fb.control(
50+
CoreDataPrivacyDataRequestType.DATAREQUEST_TYPE_EXPORT,
51+
{ validators: Validators.required, nonNullable: true },
52+
);
53+
this.form.addControl('type', this.typeControl);
54+
this.form.addControl('message', this.fb.control(''));
55+
}
56+
57+
/**
58+
* @inheritdoc
59+
*/
60+
async ngOnInit(): Promise<void> {
61+
// It should not happen. If there's no access info, close the modal.
62+
if (!this.accessInfo) {
63+
ModalController.dismiss();
64+
65+
return;
66+
}
67+
68+
// Just in case only deleting is allowed, change the default type.
69+
if (!this.accessInfo.cancreatedatadownloadrequest && this.accessInfo.cancreatedatadeletionrequest){
70+
this.typeControl.setValue(CoreDataPrivacyDataRequestType.DATAREQUEST_TYPE_DELETE);
71+
}
72+
}
73+
74+
/**
75+
* Sends the request.
76+
*/
77+
async send(event: Event): Promise<void> {
78+
event.preventDefault();
79+
event.stopPropagation();
80+
81+
const modal = await CoreDomUtils.showModalLoading();
82+
83+
try {
84+
// Send the message.
85+
const requestId = await CoreDataPrivacy.createDataRequest(this.typeControl.value, this.message);
86+
if (requestId) {
87+
CoreDomUtils.showToast('core.dataprivacy.requestsubmitted', true, ToastDuration.LONG);
88+
ModalController.dismiss(true);
89+
}
90+
} catch (error) {
91+
CoreDomUtils.showErrorModalDefault(error, 'Error sending data privacy request');
92+
} finally {
93+
modal.dismiss();
94+
}
95+
}
96+
97+
/**
98+
* Close modal.
99+
*/
100+
close(): void {
101+
ModalController.dismiss();
102+
}
103+
104+
}

0 commit comments

Comments
 (0)