no message
This commit is contained in:
parent
b58e059f73
commit
74f11f0f1b
|
@ -6,7 +6,6 @@ import { HomepageComponent } from './homepage/homepage.component';
|
|||
import { DynamicFormComponent } from './form/dynamic-form.component';
|
||||
import { AuthGuard } from './guards/auth.guard';
|
||||
import { AppComponent } from './app.component';
|
||||
import { MainSignInComponent } from './login/main-sign-in/main-sign-in.component';
|
||||
import { DmpDetailedComponent } from './viewers/dmp-detailed/dmp-detailed.component';
|
||||
import { ProjectDetailedComponent } from './viewers/project-detailed/project-detailed.component';
|
||||
import { ProjectListingModel } from './models/projects/ProjectListingModel';
|
||||
|
@ -15,6 +14,7 @@ import { DataManagementPlanListingComponent } from './dmps/dmp-listing.component
|
|||
import { ProjectEditorComponent } from './projects/editor/project-editor.component';
|
||||
import { DataManagementPlanEditorComponent } from './dmps/editor/dmp-editor.component';
|
||||
import { DataManagementPlanWizardComponent } from './dmp-wizard/dmp-wizard.component';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
|
||||
|
||||
const appRoutes: Routes = [
|
||||
|
@ -28,7 +28,7 @@ const appRoutes: Routes = [
|
|||
{ path: 'dmp/:id', component: DataManagementPlanEditorComponent, canActivate: [AuthGuard]},
|
||||
{ path: 'dmps/new', component: DataManagementPlanEditorComponent, canActivate: [AuthGuard]},
|
||||
|
||||
{ path: 'login', component: MainSignInComponent},
|
||||
{ path: 'login', component: LoginComponent},
|
||||
{ path: "unauthorized", loadChildren: './unauthorized/unauthorized.module#UnauthorizedModule' },
|
||||
{ path: 'welcome', component: HomepageComponent, canActivate: [AuthGuard]},
|
||||
{ path: '', redirectTo: '/welcome', pathMatch: 'full' },
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Component, OnInit, ViewEncapsulation} from '@angular/core';
|
|||
import { ServerService } from './services/server.service';
|
||||
import { JsonObjest } from '../app/entities/JsonObject.class';
|
||||
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, ActivatedRoute, NavigationExtras } from '@angular/router';
|
||||
import { MainSignInComponent } from './login/main-sign-in/main-sign-in.component';
|
||||
import {BreadcrumbModule,MenuItem} from 'primeng/primeng';
|
||||
|
||||
import { BreadcrumbComponent } from './widgets/breadcrumb/breadcrumb.component';
|
||||
|
|
|
@ -55,8 +55,6 @@ import { PDFService } from './services/transformers/pdf.service';
|
|||
import { TabModule } from './tabs/tab.module';
|
||||
import { AngularDraggableModule } from 'angular2-draggable';
|
||||
|
||||
import { GooggleSignInComponent } from './login/googgle-sign-in/googgle-sign-in.component';
|
||||
import { MainSignInComponent } from './login/main-sign-in/main-sign-in.component';
|
||||
import { NguiAutoCompleteModule } from '@ngui/auto-complete';
|
||||
|
||||
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
|
@ -110,6 +108,7 @@ import { DataManagementPlanEditorComponent } from './dmps/editor/dmp-editor.comp
|
|||
|
||||
import { FigurecardComponent } from './shared/components/figurecard/figurecard.component';
|
||||
import { DataManagementPlanWizardComponent } from './dmp-wizard/dmp-wizard.component';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
|
||||
|
||||
|
||||
|
@ -126,8 +125,7 @@ import { DataManagementPlanWizardComponent } from './dmp-wizard/dmp-wizard.compo
|
|||
TableOfContentsFieldSetComponent,
|
||||
TableOfContentsGroupComponent,
|
||||
TableOfContentsSectionComponent,
|
||||
GooggleSignInComponent,
|
||||
MainSignInComponent,
|
||||
LoginComponent,
|
||||
PageNotFoundComponent,
|
||||
HomepageComponent,
|
||||
ModalComponent,
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
.nomargin{
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.width-range {
|
||||
max-width: 500px;
|
||||
min-width: 300px;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr align="center">
|
||||
<button id="signinButton" (click)="signIn()">Sign in with Google</button>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
import { AuthService } from '../../services/auth/auth.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component, OnInit, ElementRef, AfterViewInit, VERSION, Injectable } from '@angular/core';
|
||||
import { Router, ActivatedRoute, Params } from "@angular/router";
|
||||
import { SnackBarNotificationComponent } from "../../shared/components/notificaiton/snack-bar-notification.component";
|
||||
import { MatPaginator, MatSort, MatSnackBar } from "@angular/material";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
|
||||
declare const gapi: any;
|
||||
|
||||
declare const auth2 :any;
|
||||
|
||||
declare function simple_notifier(type: string, title: string, message:string): any;
|
||||
|
||||
@Component({
|
||||
selector: 'googgle-sign-in',
|
||||
templateUrl: './googgle-sign-in.component.html',
|
||||
styleUrls: ['./googgle-sign-in.component.css']
|
||||
})
|
||||
export class GooggleSignInComponent implements OnInit, Injectable {
|
||||
|
||||
|
||||
constructor(private element: ElementRef, private router : Router,private authService:AuthService,private route:ActivatedRoute,
|
||||
public snackBar: MatSnackBar,public language: TranslateService
|
||||
) { }
|
||||
|
||||
|
||||
ngOnInit() {
|
||||
this.initiateExternalProviders();
|
||||
}
|
||||
|
||||
initiateExternalProviders(){
|
||||
|
||||
|
||||
|
||||
// if(gapi.auth2 == undefined){
|
||||
|
||||
// gapi.load('auth2', () => {
|
||||
|
||||
// this.auth2 = gapi.auth2.getAuthInstance({
|
||||
// client_id: clientId,
|
||||
// })
|
||||
// });
|
||||
|
||||
// }else{
|
||||
// this.auth2=gapi.auth2.getAuthInstance({
|
||||
// client_id: clientId,
|
||||
// })}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,23 +1,26 @@
|
|||
<div class="login">
|
||||
<div class="row">
|
||||
<div class="col-mat-4 col-sm-6 col-mat-offset-4 col-sm-offset-3">
|
||||
<div class="card">
|
||||
<div class="col-md-4 col-sm-6 col-md-offset-4 col-sm-offset-3">
|
||||
<div class="card col-md-offset-2 col-md-8">
|
||||
<div class="card-header">
|
||||
<h4>Login</h4>
|
||||
<div class="social-btns">
|
||||
<button mat-icon-button><i class="fa fa-facebook-square"></i></button>
|
||||
<button mat-icon-button><i class="fa fa-twitter"></i></button>
|
||||
<button mat-icon-button><i class="fa fa-google-plus" (click)="googleSignIn()"></i></button>
|
||||
<button mat-icon-button id="googleSignInButton">
|
||||
<i class="fa fa-google-plus"></i>
|
||||
</button>
|
||||
<button mat-icon-button>
|
||||
<i class="fa fa-linkedin"></i>
|
||||
</button>
|
||||
<button mat-icon-button>
|
||||
<i class="fa fa-facebook-square"></i>
|
||||
</button>
|
||||
<button mat-icon-button>
|
||||
<i class="fa fa-twitter"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="tip">Or Be Classical</p>
|
||||
<div class="card-form">
|
||||
<div class="form-row">
|
||||
<i class="material-icons">face</i>
|
||||
<mat-input-container color="accent">
|
||||
<input type="text" matInput placeholder="First Name" />
|
||||
</mat-input-container>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<i class="material-icons">email</i>
|
||||
<mat-input-container color="accent">
|
|
@ -0,0 +1,101 @@
|
|||
.container{
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card{
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 90px;
|
||||
top: -90px;
|
||||
-webkit-animation-name: card;
|
||||
-moz-animation-name: card;
|
||||
-o-animation-name: card;
|
||||
animation-name: card;
|
||||
-webkit-animation-duration: 600ms;
|
||||
-moz-animation-duration: 600ms;
|
||||
-o-animation-duration: 600ms;
|
||||
animation-duration: 600ms;
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
-moz-animation-fill-mode: forwards;
|
||||
-o-animation-fill-mode: forwards;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@-webkit-keyframes card {
|
||||
from {top: -40px;}
|
||||
to {top: 0;}
|
||||
}
|
||||
|
||||
@keyframes card {
|
||||
from {top: -40px;}
|
||||
to {top: 0;}
|
||||
}
|
||||
|
||||
.card-header{
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
top: -40px;
|
||||
width: 100%;
|
||||
padding: 25px;
|
||||
border-radius: 3px;
|
||||
background: linear-gradient(60deg, #ec407a, #d81b60);
|
||||
box-shadow: 0 4px 20px 0px rgba(0, 0, 0, 0.14), 0 7px 10px -5px rgba(233, 30, 99, 0.4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-header h4{
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
margin-bottom: 25px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.social-btns i{
|
||||
font-size: 21px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.social-btns button{
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.tip{
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.form-row, .card-form, .mat-input-container{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-form{
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.form-row{
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 13px;
|
||||
}
|
||||
|
||||
.form-row i{
|
||||
position: relative;
|
||||
top: -5px;
|
||||
margin-right: 15px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.card-footer{
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.card-footer button{
|
||||
color: #e91e63;
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component, OnInit, ElementRef, AfterViewInit, VERSION, Injectable } from '@angular/core';
|
||||
import { Router, ActivatedRoute, Params } from "@angular/router";
|
||||
import { MatPaginator, MatSort, MatSnackBar } from "@angular/material";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { AuthService } from '../services/auth/auth.service';
|
||||
import { SnackBarNotificationComponent } from '../shared/components/notificaiton/snack-bar-notification.component';
|
||||
|
||||
declare const gapi: any;
|
||||
|
||||
@Component({
|
||||
selector: 'login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.scss']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
|
||||
public auth2: any;
|
||||
|
||||
constructor(private router: Router,
|
||||
public authService: AuthService,
|
||||
public route: ActivatedRoute,
|
||||
public snackBar: MatSnackBar,
|
||||
public language: TranslateService) { }
|
||||
|
||||
ngOnInit() {
|
||||
gapi.load('auth2', () => {
|
||||
this.auth2 = gapi.auth2.init({
|
||||
client_id: '524432312250-sc9qsmtmbvlv05r44onl6l93ia3k9deo.apps.googleusercontent.com',
|
||||
cookiepolicy: 'single_host_origin',
|
||||
scope: 'profile email'
|
||||
});
|
||||
this.attachGoogleSignin(document.getElementById('googleSignInButton'));
|
||||
});
|
||||
}
|
||||
|
||||
public attachGoogleSignin(element) {
|
||||
this.auth2.attachClickHandler(element, {},
|
||||
(googleUser) => {
|
||||
|
||||
var id_token = googleUser.getAuthResponse().id_token;
|
||||
if (id_token) {
|
||||
this.authService.login({ ticket: id_token, service: "google" }).subscribe(
|
||||
res => this.onLogInSuccess(res),
|
||||
error => this.onLogInError(error)
|
||||
)
|
||||
}
|
||||
|
||||
}, (error) => {
|
||||
alert(JSON.stringify(error, undefined, 2));
|
||||
});
|
||||
}
|
||||
|
||||
public onLogInSuccess(logoutMessage: any) {
|
||||
this.snackBar.openFromComponent(SnackBarNotificationComponent, {
|
||||
data: { message: 'GENERAL.SNACK-BAR.SUCCESSFUL-LOGIN', language: this.language },
|
||||
duration: 3000,
|
||||
extraClasses: ['snackbar-success']
|
||||
});
|
||||
this.route.queryParams.subscribe((params: Params) => {
|
||||
let redirectUrl = params['returnUrl'] ? params['returnUrl'] : '/';
|
||||
this.router.navigate([redirectUrl]);
|
||||
})
|
||||
}
|
||||
|
||||
public onLogInError(errorMessage: string) {
|
||||
console.log(errorMessage);
|
||||
this.snackBar.openFromComponent(SnackBarNotificationComponent, {
|
||||
data: { message: 'GENERAL.SNACK-BAR.UNSUCCESSFUL-LOGIN', language: this.language },
|
||||
duration: 3000,
|
||||
extraClasses: ['snackbar-warning']
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
.login{
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.card{
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 90px;
|
||||
top: -90px;
|
||||
-webkit-animation-name: card;
|
||||
-moz-animation-name: card;
|
||||
-o-animation-name: card;
|
||||
animation-name: card;
|
||||
-webkit-animation-duration: 600ms;
|
||||
-moz-animation-duration: 600ms;
|
||||
-o-animation-duration: 600ms;
|
||||
animation-duration: 600ms;
|
||||
-webkit-animation-fill-mode: forwards;
|
||||
-moz-animation-fill-mode: forwards;
|
||||
-o-animation-fill-mode: forwards;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@-webkit-keyframes card {
|
||||
from {top: -40px;}
|
||||
to {top: 0;}
|
||||
}
|
||||
|
||||
@keyframes card {
|
||||
from {top: -40px;}
|
||||
to {top: 0;}
|
||||
}
|
||||
|
||||
.card-header{
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
top: -40px;
|
||||
width: 100%;
|
||||
padding: 25px;
|
||||
border-radius: 3px;
|
||||
background: linear-gradient(60deg, #ec407a, #d81b60);
|
||||
box-shadow: 0 4px 20px 0px rgba(0, 0, 0, 0.14), 0 7px 10px -5px rgba(233, 30, 99, 0.4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-header h4{
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
margin-bottom: 25px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.social-btns i{
|
||||
font-size: 21px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.social-btns button{
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.tip{
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
.form-row, .card-form, .mat-input-container{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-form{
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.form-row{
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 13px;
|
||||
}
|
||||
|
||||
.form-row i{
|
||||
position: relative;
|
||||
top: -5px;
|
||||
margin-right: 15px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.card-footer{
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.card-footer button{
|
||||
color: #e91e63;
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
import { AuthService } from '../../services/auth/auth.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component, OnInit, ElementRef, AfterViewInit, VERSION, Injectable } from '@angular/core';
|
||||
import { Router, ActivatedRoute, Params } from "@angular/router";
|
||||
import { SnackBarNotificationComponent } from "../../shared/components/notificaiton/snack-bar-notification.component";
|
||||
import { MatPaginator, MatSort, MatSnackBar } from "@angular/material";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
declare const gapi: any;
|
||||
|
||||
declare const auth2 :any;
|
||||
@Component({
|
||||
selector: 'app-main-sign-in',
|
||||
templateUrl: './main-sign-in.component.html',
|
||||
styleUrls: ['./main-sign-in.component.css']
|
||||
})
|
||||
export class MainSignInComponent implements OnInit {
|
||||
|
||||
constructor(private element: ElementRef, private router : Router,private authService:AuthService,private route:ActivatedRoute,
|
||||
public snackBar: MatSnackBar,public language: TranslateService) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
loginBtn() {
|
||||
}
|
||||
|
||||
|
||||
public googleSignIn() {
|
||||
auth2.grantOfflineAccess().then((authResult)=>this.signInCallback(authResult))
|
||||
}
|
||||
|
||||
signInCallback(authResult){
|
||||
if (authResult['code']) {
|
||||
this.authService.login({ticket:authResult['code'],service:"google"}).subscribe(
|
||||
res => this.onLogInSuccess(res),
|
||||
error => this.onLogInError(error)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public onLogInSuccess(logoutMessage: any) {
|
||||
this.route.queryParams.subscribe((params: Params) => {
|
||||
let redirectUrl = params['returnUrl'] ? params['returnUrl'] : '/';
|
||||
this.router.navigate([redirectUrl]);
|
||||
})
|
||||
}
|
||||
|
||||
public onLogInError(errorMessage: string) {
|
||||
console.log(errorMessage);
|
||||
this.snackBar.openFromComponent(SnackBarNotificationComponent, {
|
||||
data: { message: 'GENERAL.SNACK-BAR.UNSUCCESSFUL-LOGIN', language: this.language },
|
||||
duration: 3000,
|
||||
extraClasses: ['snackbar-warning']
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,12 +1 @@
|
|||
|
||||
<div class="jumbotron">
|
||||
<h2>Hello {{ userInfo?.name!=null ? userInfo?.name : userInfo?.email }}</h2>
|
||||
<p>Welcome {{ userInfo?.created != userInfo?.lastloggedin ? "back" : "" }} to the <a href="https://en.wikipedia.org/wiki/Data_management_plan">Data Management Plans</a> composer application. </p>
|
||||
<!--
|
||||
<p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a></p>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<!--
|
||||
{{userInfo | json}}
|
||||
-->
|
||||
{{message}}
|
|
@ -1,69 +0,0 @@
|
|||
var clientId = '1010962018903-glegmqudqtl1lub0150vacopbu06lgsg.apps.googleusercontent.com';
|
||||
var scope = [
|
||||
'profile',
|
||||
'email'
|
||||
].join(' ');
|
||||
|
||||
if(gapi.auth2 == undefined){
|
||||
|
||||
gapi.load('auth2', () => {
|
||||
|
||||
gapi.auth2.init({
|
||||
client_id: clientId,
|
||||
cookiepolicy: 'single_host_origin',
|
||||
scope: scope
|
||||
});
|
||||
|
||||
//RE-Render the button (due to known issues of google-button with angular's lifecycle)
|
||||
gapi.signin2.render('googleBtn');
|
||||
|
||||
//var buttonElement = this.element.nativeElement.querySelector('#googleBtn');
|
||||
//this.attachSignin(buttonElement);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
var sign_out_google = (function() {
|
||||
var auth2 = gapi.auth2.getAuthInstance();
|
||||
auth2.signOut().then(function () {
|
||||
console.log('User signed out from google.');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
var simple_notifier = (function(type, title, message) {
|
||||
|
||||
setTimeout(function() {
|
||||
$(".alert").remove();
|
||||
}, 11000);
|
||||
|
||||
return notify(type, title, message, null, null, null, null, null, null, null, null, null, null);
|
||||
});
|
||||
|
||||
|
||||
|
||||
function notify(type, title, message, delay, icon, url, target, allow_dismiss, offset_x, offset_y, animate_enter, animate_exit, newest_on_top){
|
||||
|
||||
var options = {};
|
||||
if(icon!=null) options.icon = icon;
|
||||
if(title!=null) options.title = title;
|
||||
if(message!=null) options.message = message;
|
||||
if(url!=null) options.url = url;
|
||||
if(target!=null) options.target = target;
|
||||
|
||||
var settings = {};
|
||||
if(type!=null) settings.type = type;
|
||||
if(allow_dismiss!=null) settings.allow_dismiss = allow_dismiss;
|
||||
settings.delay = 5;
|
||||
if(delay!=null) settings.delay = delay;
|
||||
|
||||
return $.notify(options,settings, delay);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -31,9 +31,7 @@
|
|||
<!-- Nice BS notifications -->
|
||||
<script src="assets/bootstrap-notify.min.js"></script>
|
||||
|
||||
<script src="https://apis.google.com/js/client:platform.js?onload=start" async defer>
|
||||
|
||||
</script>
|
||||
<script src="https://apis.google.com/js/platform.js" async defer></script>
|
||||
|
||||
<!-- font-awesome css -->
|
||||
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
|
|
Loading…
Reference in New Issue