mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-20 00:23:03 +00:00
feat: add feedback module
This commit is contained in:
@@ -38,6 +38,7 @@ import {SampleThings} from './data/sample-things';
|
||||
/* eslint-disable */
|
||||
export const sampleIndexResponse: SCIndexResponse = {
|
||||
app: {
|
||||
aboutPages: {},
|
||||
campusPolygon: {
|
||||
coordinates: [
|
||||
[
|
||||
|
||||
@@ -56,6 +56,8 @@ import {UtilModule} from './util/util.module';
|
||||
import {initLogger} from './_helpers/ts-logger';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {FavoritesModule} from './modules/favorites/favorites.module';
|
||||
import {FeedbackModule} from './modules/feedback/feedback.module';
|
||||
import {DebugDataCollectorService} from './modules/data/debug-data-collector.service';
|
||||
|
||||
registerLocaleData(localeDe);
|
||||
|
||||
@@ -144,6 +146,7 @@ const providers: Provider[] = [
|
||||
DataModule,
|
||||
IonicModule.forRoot(),
|
||||
FavoritesModule,
|
||||
FeedbackModule,
|
||||
MapModule,
|
||||
MenuModule,
|
||||
NewsModule,
|
||||
@@ -171,4 +174,6 @@ const providers: Provider[] = [
|
||||
? [providers, fakeBackendProvider]
|
||||
: providers,
|
||||
})
|
||||
export class AppModule {}
|
||||
export class AppModule {
|
||||
constructor(public debugDataCollectorService: DebugDataCollectorService) {}
|
||||
}
|
||||
|
||||
@@ -220,6 +220,7 @@ const scVersion = packageJson.dependencies['@openstapps/core'];
|
||||
|
||||
const sampleIndexResponse: SCIndexResponse = {
|
||||
app: {
|
||||
aboutPages: {},
|
||||
campusPolygon: {
|
||||
coordinates: [[[1, 2]], [[1, 2]]],
|
||||
type: 'Polygon',
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
SCThingsField,
|
||||
SCThingType,
|
||||
SCSaveableThing,
|
||||
SCFeedbackRequest,
|
||||
} from '@openstapps/core';
|
||||
import {chunk, fromPairs, toPairs} from 'lodash-es';
|
||||
import {environment} from '../../../environments/environment';
|
||||
@@ -292,6 +293,15 @@ export class DataProvider {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a feedback message (request)
|
||||
*
|
||||
* @param feedback Feedback message to be sent to the backend
|
||||
*/
|
||||
async sendFeedback(feedback: SCFeedbackRequest) {
|
||||
return this.client.feedback(feedback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the backend using the provided query and returns response
|
||||
*
|
||||
|
||||
68
src/app/modules/data/debug-data-collector.service.ts
Normal file
68
src/app/modules/data/debug-data-collector.service.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Injectable} from '@angular/core';
|
||||
import {SCFeedbackRequestMetaData} from '@openstapps/core';
|
||||
import {Platform} from '@ionic/angular';
|
||||
import {DataProvider} from './data.provider';
|
||||
import {NavigationEnd, Router} from '@angular/router';
|
||||
import {SettingsProvider} from '../settings/settings.provider';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DebugDataCollectorService {
|
||||
/**
|
||||
* Previously visited route
|
||||
*/
|
||||
previousRoute: string;
|
||||
|
||||
/**
|
||||
* Current route
|
||||
*/
|
||||
currentRoute: string;
|
||||
|
||||
constructor(
|
||||
private platform: Platform,
|
||||
private dataProvider: DataProvider,
|
||||
private router: Router,
|
||||
private settingsProvider: SettingsProvider,
|
||||
) {
|
||||
this.currentRoute = this.router.url;
|
||||
router.events.subscribe(event => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
this.previousRoute = this.currentRoute;
|
||||
this.currentRoute = event.url;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides meta data for a feedback
|
||||
*/
|
||||
async getFeedbackMetaData(): Promise<SCFeedbackRequestMetaData> {
|
||||
return {
|
||||
debug: false,
|
||||
platform: this.platform.platforms().join(','),
|
||||
scope: {},
|
||||
state: {
|
||||
route: this.previousRoute,
|
||||
settings: await this.settingsProvider.getCache(),
|
||||
},
|
||||
userAgent: window.navigator.userAgent,
|
||||
version: this.dataProvider.appVersion,
|
||||
sendable: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
139
src/app/modules/feedback/feedback-page.component.ts
Normal file
139
src/app/modules/feedback/feedback-page.component.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component} from '@angular/core';
|
||||
import {
|
||||
SCFeedbackRequest,
|
||||
SCFeedbackRequestMetaData,
|
||||
SCMessage,
|
||||
SCPersonWithoutReferences,
|
||||
SCThingOriginType,
|
||||
SCThingType,
|
||||
} from '@openstapps/core';
|
||||
import {DataProvider} from '../data/data.provider';
|
||||
import {DebugDataCollectorService} from '../data/debug-data-collector.service';
|
||||
import {AlertController, ToastController} from '@ionic/angular';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
templateUrl: './feedback-page.html',
|
||||
styleUrls: ['./feedback-page.scss'],
|
||||
})
|
||||
export class FeedbackPageComponent {
|
||||
constructor(
|
||||
private readonly dataProvider: DataProvider,
|
||||
private readonly debugDataCollector: DebugDataCollectorService,
|
||||
private readonly toastController: ToastController,
|
||||
private readonly alertController: AlertController,
|
||||
private readonly translateService: TranslateService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Sender of the feedback message
|
||||
*/
|
||||
author: SCPersonWithoutReferences = {
|
||||
uid: '0f53f16a-e618-5ae0-a1b6-336e34f0d4d1',
|
||||
name: '',
|
||||
type: SCThingType.Person,
|
||||
};
|
||||
|
||||
/**
|
||||
* The message to be sent
|
||||
*/
|
||||
message: SCMessage = {
|
||||
uid: '0f53f16a-e618-5ae0-a1b6-336e34f0d4d1',
|
||||
name: 'Bug',
|
||||
type: SCThingType.Message,
|
||||
audiences: [],
|
||||
categories: [],
|
||||
origin: {
|
||||
type: SCThingOriginType.User,
|
||||
created: new Date().toISOString(),
|
||||
},
|
||||
messageBody: '',
|
||||
};
|
||||
|
||||
/**
|
||||
* Terms of feedback accepted or not
|
||||
*/
|
||||
termsAgree = false;
|
||||
|
||||
/**
|
||||
* Show meta data or not
|
||||
*/
|
||||
showMetaData = false;
|
||||
|
||||
/**
|
||||
* Feedback successfully sent
|
||||
*/
|
||||
submitSuccess = false;
|
||||
|
||||
/**
|
||||
* Terms of feedback accepted or not
|
||||
*/
|
||||
metaData: SCFeedbackRequestMetaData;
|
||||
|
||||
async ionViewDidEnter() {
|
||||
this.metaData = await this.debugDataCollector.getFeedbackMetaData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assemble and send the feedback
|
||||
*/
|
||||
async onSubmit() {
|
||||
if (this.author.name !== '') {
|
||||
this.message.authors = [this.author];
|
||||
}
|
||||
|
||||
const feedbackRequest: SCFeedbackRequest = {
|
||||
...this.message,
|
||||
metaData: this.metaData,
|
||||
};
|
||||
|
||||
try {
|
||||
await this.dataProvider.sendFeedback(feedbackRequest);
|
||||
void this.onSuccess();
|
||||
} catch {
|
||||
void this.onError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show/hide the meta data
|
||||
*/
|
||||
toggleShowMetaData() {
|
||||
this.showMetaData = !this.showMetaData;
|
||||
}
|
||||
|
||||
async onSuccess() {
|
||||
this.submitSuccess = true;
|
||||
const toast = await this.toastController.create({
|
||||
message: this.translateService.instant(
|
||||
'feedback.system_messages.success',
|
||||
),
|
||||
duration: 2000,
|
||||
});
|
||||
await toast.present();
|
||||
}
|
||||
|
||||
async onError() {
|
||||
const alert: HTMLIonAlertElement = await this.alertController.create({
|
||||
buttons: [this.translateService.instant('app.ui.CLOSE')],
|
||||
header: this.translateService.instant('app.ui.ERROR'),
|
||||
message: this.translateService.instant('app.errors.UNKNOWN'),
|
||||
});
|
||||
|
||||
await alert.present();
|
||||
}
|
||||
}
|
||||
100
src/app/modules/feedback/feedback-page.html
Normal file
100
src/app/modules/feedback/feedback-page.html
Normal file
@@ -0,0 +1,100 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button></ion-back-button>
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'feedback.page.TITLE' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<form #feedbackForm="ngForm" (ngSubmit)="onSubmit()">
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{
|
||||
'feedback.form.name.label' | translate
|
||||
}}</ion-label>
|
||||
<ion-input
|
||||
placeholder="{{ 'feedback.form.name.placeholder' | translate }}"
|
||||
[(ngModel)]="author.name"
|
||||
name="name"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{
|
||||
'feedback.form.type.label' | translate
|
||||
}}</ion-label>
|
||||
<ion-select
|
||||
[(ngModel)]="message.name"
|
||||
value="comment"
|
||||
name="title"
|
||||
interface="popover"
|
||||
required="true"
|
||||
>
|
||||
<ion-select-option value="Comment">{{
|
||||
'feedback.form.type.values.comment' | translate
|
||||
}}</ion-select-option>
|
||||
<ion-select-option value="Bug">{{
|
||||
'feedback.form.type.values.bug' | translate
|
||||
}}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{
|
||||
'feedback.form.email.label' | translate
|
||||
}}</ion-label>
|
||||
<ion-input
|
||||
placeholder="{{ 'feedback.form.email.placeholder' | translate }}"
|
||||
[(ngModel)]="author.email"
|
||||
type="email"
|
||||
name="email"
|
||||
pattern="[A-Za-z0-9._%+-]{3,}@[a-zA-Z]{3,}([.]{1}[a-zA-Z]{2,}|[.]{1}[a-zA-Z]{2,}[.]{1}[a-zA-Z]{2,})"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label position="stacked">{{
|
||||
'feedback.form.message.label' | translate
|
||||
}}</ion-label>
|
||||
<ion-textarea
|
||||
[(ngModel)]="message.messageBody"
|
||||
placeholder="{{ 'feedback.form.message.placeholder' | translate }}"
|
||||
name="message"
|
||||
required="true"
|
||||
minlength="30"
|
||||
autoGrow="true"
|
||||
></ion-textarea>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label class="ion-text-wrap">{{
|
||||
'feedback.form.termsAgree' | translate
|
||||
}}</ion-label>
|
||||
<ion-checkbox
|
||||
color="primary"
|
||||
slot="start"
|
||||
[(ngModel)]="termsAgree"
|
||||
name="termsAgree"
|
||||
></ion-checkbox>
|
||||
</ion-item>
|
||||
<ion-button
|
||||
type="submit"
|
||||
color="primary"
|
||||
expand="block"
|
||||
[disabled]="!feedbackForm.valid || !termsAgree || submitSuccess"
|
||||
>{{ 'feedback.form.submit' | translate }}</ion-button
|
||||
>
|
||||
<ion-card>
|
||||
<ion-card-title>
|
||||
<ion-button expand="block" fill="clear" (click)="toggleShowMetaData()">
|
||||
<ng-container *ngIf="!showMetaData; else hide">{{
|
||||
'feedback.form.protocolData.show' | translate
|
||||
}}</ng-container>
|
||||
<ng-template #hide>{{
|
||||
'feedback.form.protocolData.hide' | translate
|
||||
}}</ng-template>
|
||||
</ion-button>
|
||||
</ion-card-title>
|
||||
<ion-card-content *ngIf="metaData && showMetaData">
|
||||
<pre>{{ metaData | json }}</pre>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</form>
|
||||
</ion-content>
|
||||
6
src/app/modules/feedback/feedback-page.scss
Normal file
6
src/app/modules/feedback/feedback-page.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
ion-button {
|
||||
margin: 10px;
|
||||
}
|
||||
42
src/app/modules/feedback/feedback.module.ts
Normal file
42
src/app/modules/feedback/feedback.module.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2021 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {IonicModule} from '@ionic/angular';
|
||||
import {FeedbackPageComponent} from './feedback-page.component';
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {MarkdownModule} from 'ngx-markdown';
|
||||
|
||||
const feedbackRoutes: Routes = [
|
||||
{
|
||||
path: 'feedback',
|
||||
component: FeedbackPageComponent,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(feedbackRoutes),
|
||||
TranslateModule,
|
||||
MarkdownModule,
|
||||
],
|
||||
declarations: [FeedbackPageComponent],
|
||||
})
|
||||
export class FeedbackModule {}
|
||||
Reference in New Issue
Block a user