#84 - Add login page as part of SPA instead of relying on external page
parent
aad0aea79a
commit
0fecb8a4ba
@ -1,23 +0,0 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { ListPage } from './list.page';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
component: ListPage
|
||||
}
|
||||
])
|
||||
],
|
||||
declarations: [ListPage]
|
||||
})
|
||||
export class ListPageModule {}
|
@ -1,27 +0,0 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-menu-button></ion-menu-button>
|
||||
</ion-buttons>
|
||||
<ion-title>
|
||||
List
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-item *ngFor="let item of items">
|
||||
<ion-icon [name]="item.icon" slot="start"></ion-icon>
|
||||
{{item.title}}
|
||||
<div class="item-note" slot="end">
|
||||
{{item.note}}
|
||||
</div>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
<!--
|
||||
<div *ngIf="selectedItem" padding>
|
||||
You navigated here from <b>{{selectedItem.title }}</b>
|
||||
</div>
|
||||
-->
|
||||
</ion-content>
|
@ -1 +0,0 @@
|
||||
|
@ -1,32 +0,0 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
|
||||
import { ListPage } from './list.page';
|
||||
|
||||
describe('ListPage', () => {
|
||||
let component: ListPage;
|
||||
let fixture: ComponentFixture<ListPage>;
|
||||
let listPage: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ListPage ],
|
||||
imports: [IonicModule.forRoot()]
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ListPage);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have a list of 10 elements', () => {
|
||||
listPage = fixture.nativeElement;
|
||||
const items = listPage.querySelectorAll('ion-item');
|
||||
expect(items.length).toEqual(10);
|
||||
});
|
||||
|
||||
});
|
@ -1,39 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list',
|
||||
templateUrl: 'list.page.html',
|
||||
styleUrls: ['list.page.scss']
|
||||
})
|
||||
export class ListPage implements OnInit {
|
||||
private selectedItem: any;
|
||||
private icons = [
|
||||
'flask',
|
||||
'wifi',
|
||||
'beer',
|
||||
'football',
|
||||
'basketball',
|
||||
'paper-plane',
|
||||
'american-football',
|
||||
'boat',
|
||||
'bluetooth',
|
||||
'build'
|
||||
];
|
||||
public items: Array<{ title: string; note: string; icon: string }> = [];
|
||||
constructor() {
|
||||
for (let i = 1; i < 11; i++) {
|
||||
this.items.push({
|
||||
title: 'Item ' + i,
|
||||
note: 'This is item #' + i,
|
||||
icon: this.icons[Math.floor(Math.random() * this.icons.length)]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
// add back when alpha.4 is out
|
||||
// navigate(item) {
|
||||
// this.router.navigate(['/list', JSON.stringify(item)]);
|
||||
// }
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
<div class="login-container">
|
||||
<ion-grid class="ion-align-items-center">
|
||||
<ion-row class="ion-align-items-center" style="height: 100%" *ngIf="step === 'greeter'">
|
||||
<ion-col class="ion-align-items-end" style="text-align: right">
|
||||
<img src="/assets/icon/logo_lines.svg" alt="Noded" width="200">
|
||||
</ion-col>
|
||||
<ion-col style="padding: 40px;">
|
||||
<div class="hero" style="font-size: 2em;"><b style="font-size: 1.5em;">Noded</b><br>is your information, organized.</div>
|
||||
<button
|
||||
(click)="advance()"
|
||||
title="Click to get started"
|
||||
style="padding: 20px 0; margin: 0; color: #3171e0"
|
||||
>Get started <i class="fa fa-chevron-right" style="font-size: 0.8em; margin-left: 10px;"></i></button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<ion-row class="ion-align-items-center" style="height: 100%" *ngIf="step === 'username' || step === 'password' || step === 'create-account'">
|
||||
<ion-col class="ion-align-items-center" style="text-align: center">
|
||||
<img src="/assets/icon/logo_lines.svg" alt="Noded" height="100" style="margin-bottom: 20px;">
|
||||
<p class="message">{{ stepMessages[step] }}</p>
|
||||
<ion-item>
|
||||
<ion-input
|
||||
#usernameInput
|
||||
[(ngModel)]="username"
|
||||
[disabled]="step !== 'username'"
|
||||
placeholder="Username"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="step === 'password'" style="padding-top: 10px;">
|
||||
<ion-input
|
||||
#passwordInput
|
||||
[(ngModel)]="password"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="step === 'create-account'" style="padding-top: 10px;">
|
||||
<ion-input
|
||||
#nameInput
|
||||
[(ngModel)]="fullName"
|
||||
placeholder="Full Name"
|
||||
type="text"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="step === 'create-account'" style="padding-top: 10px;">
|
||||
<ion-input
|
||||
[(ngModel)]="password"
|
||||
placeholder="Create a Password"
|
||||
type="password"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="step === 'create-account'" style="padding-top: 10px;">
|
||||
<ion-input
|
||||
[(ngModel)]="passwordConfirm"
|
||||
placeholder="Confirm Password"
|
||||
type="password"
|
||||
></ion-input>
|
||||
</ion-item>
|
||||
<div class="buttons" style="text-align: right;">
|
||||
<div class="button" style="display: flex; flex-direction: row;" *ngIf="step !== 'username'">
|
||||
<button
|
||||
(click)="back()"
|
||||
style="text-align: left; margin: 20px 0 0 0;"
|
||||
><i class="fa fa-chevron-left" style="font-size: 0.8em; margin-left: 10px; color: #3171e0"></i></button>
|
||||
<button
|
||||
(click)="advance()"
|
||||
title="Click to sign-in to the system"
|
||||
style="flex: 1; padding: 20px 0 0 0; margin: 0; text-align: right;"
|
||||
[ngStyle]="{'color': ((!password || (step !== 'password' && (password !== passwordConfirm || !fullName))) ? '#666' : '#3171e0')}"
|
||||
[disabled]="!password || (step !== 'password' && (password !== passwordConfirm || !fullName))"
|
||||
>{{ step === 'password' ? 'Sign-in' : 'Create account' }} <i class="fa fa-chevron-right" style="font-size: 0.8em; margin-left: 10px;"></i></button>
|
||||
</div>
|
||||
<div class="button" *ngIf="step === 'username'">
|
||||
<button
|
||||
(click)="advance()"
|
||||
title="Click to continue"
|
||||
style="padding: 20px 0 0 0; margin: 0;"
|
||||
[ngStyle]="{'color': (username ? '#3171e0' : '#666')}"
|
||||
[disabled]="!username"
|
||||
>Continue <i class="fa fa-chevron-right" style="font-size: 0.8em; margin-left: 10px;"></i></button>
|
||||
</div>
|
||||
<div class="button">
|
||||
<button
|
||||
(click)="coreid()"
|
||||
*ngIf="step === 'username'"
|
||||
title="Click to sign-in with Starship CoreID instead"
|
||||
style="padding: 20px 0 0 0; margin: 0; color: #3171e0;"
|
||||
>Sign-in with CoreID <i class="fa fa-chevron-right" style="font-size: 0.8em; margin-left: 10px;"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="error-message" *ngIf="errorMessage" style="color: darkred; font-size: 0.9em; margin-top: 30px;">{{ errorMessage }}</p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</div>
|
@ -0,0 +1,5 @@
|
||||
.login-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {AlertController, IonInput, LoadingController, ModalController, PopoverController} from '@ionic/angular';
|
||||
import {EditorService} from '../../service/editor.service';
|
||||
import {ApiService} from '../../service/api.service';
|
||||
import {NavigationService} from '../../service/navigation.service';
|
||||
import {AuthService} from '../../service/auth.service';
|
||||
|
||||
export type LoginPageStep = 'greeter' | 'username' | 'password' | 'create-account';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.page.html',
|
||||
styleUrls: ['./login.page.scss'],
|
||||
})
|
||||
export class LoginPage implements OnInit {
|
||||
public step: LoginPageStep = 'greeter';
|
||||
@ViewChild('usernameInput') usernameInput: IonInput;
|
||||
@ViewChild('passwordInput') passwordInput: IonInput;
|
||||
@ViewChild('nameInput') nameInput: IonInput;
|
||||
|
||||
stepMessages = {
|
||||
username: 'Enter a username to continue',
|
||||
password: 'Enter your password to sign-in',
|
||||
'create-account': 'Welcome! Create an account to continue',
|
||||
};
|
||||
|
||||
public username = '';
|
||||
public password = '';
|
||||
public fullName = '';
|
||||
public passwordConfirm = '';
|
||||
public userExists?: boolean;
|
||||
public userInfo: any = {};
|
||||
public errorMessage = '';
|
||||
|
||||
public readonly coreidUrl: string;
|
||||
|
||||
constructor(
|
||||
protected api: ApiService,
|
||||
protected route: ActivatedRoute,
|
||||
protected router: Router,
|
||||
protected loader: LoadingController,
|
||||
protected popover: PopoverController,
|
||||
protected alerts: AlertController,
|
||||
protected modals: ModalController,
|
||||
public readonly editorService: EditorService,
|
||||
public readonly navService: NavigationService,
|
||||
public readonly auth: AuthService,
|
||||
) {
|
||||
this.coreidUrl = this.api._build_url('/auth/starship_coreid/login', '/');
|
||||
}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
async ionViewDidEnter() {
|
||||
|
||||
}
|
||||
|
||||
coreid() {
|
||||
window.location.assign(this.coreidUrl);
|
||||
}
|
||||
|
||||
async back() {
|
||||
this.errorMessage = '';
|
||||
|
||||
if ( this.step === 'password' ) {
|
||||
this.password = '';
|
||||
this.step = 'username';
|
||||
|
||||
setTimeout(() => {
|
||||
if ( this.usernameInput ) {
|
||||
this.usernameInput.setFocus();
|
||||
}
|
||||
}, 150);
|
||||
} else if ( this.step === 'create-account' ) {
|
||||
this.fullName = '';
|
||||
this.password = '';
|
||||
this.passwordConfirm = '';
|
||||
this.step = 'username';
|
||||
|
||||
setTimeout(() => {
|
||||
if ( this.usernameInput ) {
|
||||
this.usernameInput.setFocus();
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
}
|
||||
|
||||
async advance() {
|
||||
this.errorMessage = '';
|
||||
|
||||
if ( this.step === 'greeter' ) {
|
||||
this.step = 'username';
|
||||
|
||||
setTimeout(() => {
|
||||
if ( this.usernameInput ) {
|
||||
this.usernameInput.setFocus();
|
||||
}
|
||||
}, 150);
|
||||
} else if ( this.step === 'username' ) {
|
||||
if ( !this.username ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loader = await this.loader.create({ message: 'Checking username...' });
|
||||
await loader.present();
|
||||
|
||||
this.userInfo = await this.api.getUserInfo(this.username);
|
||||
this.userExists = !!this.userInfo?.exists;
|
||||
if ( this.userExists ) {
|
||||
this.step = 'password';
|
||||
|
||||
setTimeout(() => {
|
||||
if ( this.passwordInput ) {
|
||||
this.passwordInput.setFocus();
|
||||
}
|
||||
}, 150);
|
||||
} else {
|
||||
this.step = 'create-account';
|
||||
|
||||
setTimeout(() => {
|
||||
if ( this.nameInput ) {
|
||||
this.nameInput.setFocus();
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
|
||||
await loader.dismiss();
|
||||
} else if ( this.step === 'password' ) {
|
||||
if ( !this.username || !this.password ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loader = await this.loader.create({ message: 'Signing you in...' });
|
||||
await loader.present();
|
||||
|
||||
const result = await this.api.attemptLogin(this.username, this.password);
|
||||
await loader.dismiss();
|
||||
|
||||
if ( !result.success ) {
|
||||
this.errorMessage = result.message || 'Un unknown error has occurred';
|
||||
} else {
|
||||
this.auth.authInProgress = true;
|
||||
this.navService.requestInitialization();
|
||||
this.reset();
|
||||
}
|
||||
} else if ( this.step === 'create-account' ) {
|
||||
if ( !this.username || !this.password || this.passwordConfirm !== this.password || !this.fullName ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loader = await this.loader.create({ message: 'Creating your account...' });
|
||||
await loader.present();
|
||||
|
||||
const result = await this.api.attemptRegistration(this.username, this.password, this.passwordConfirm, this.fullName);
|
||||
await loader.dismiss();
|
||||
|
||||
if ( !result.success ) {
|
||||
this.errorMessage = result.message || 'Un unknown error has occurred';
|
||||
} else {
|
||||
this.auth.authInProgress = true;
|
||||
this.navService.requestInitialization();
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.username = '';
|
||||
this.password = '';
|
||||
this.passwordConfirm = '';
|
||||
this.fullName = '';
|
||||
this.step = 'greeter';
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ApiService} from './api.service';
|
||||
import {NavigationService} from './navigation.service';
|
||||
import {DatabaseService} from './db/database.service';
|
||||
import {LoadingController} from '@ionic/angular';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class AuthService implements CanActivate {
|
||||
public authInProgress = false;
|
||||
|
||||
constructor(
|
||||
protected readonly loader: LoadingController,
|
||||
protected readonly api: ApiService,
|
||||
protected readonly router: Router,
|
||||
protected readonly nav: NavigationService,
|
||||
protected readonly db: DatabaseService,
|
||||
) { }
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
|
||||
const checkCanActivate = async () => {
|
||||
const isAuthenticated = (this.api.isAuthenticated && !this.api.isPublicUser);
|
||||
|
||||
if ( !isAuthenticated ) {
|
||||
await this.router.navigate(['/login']);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise(async res => {
|
||||
if ( !this.nav.initialized$.getValue() ) {
|
||||
const sub = this.nav.initialized$.subscribe(async initialized => {
|
||||
if ( initialized ) {
|
||||
sub.unsubscribe();
|
||||
return res(await checkCanActivate());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return res(await checkCanActivate());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async endSession() {
|
||||
const loader = await this.loader.create({ message: 'Logging you out...' });
|
||||
await loader.present();
|
||||
|
||||
await this.db.purge();
|
||||
await this.api.endSession();
|
||||
await this.router.navigate(['/login']);
|
||||
|
||||
await loader.dismiss();
|
||||
this.nav.requestInitialization();
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import {CanActivate, Router} from '@angular/router';
|
||||
import {ApiService} from '../api.service';
|
||||
import {NavigationService} from '../navigation.service';
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class GuestOnlyGuard implements CanActivate {
|
||||
constructor(
|
||||
protected readonly api: ApiService,
|
||||
protected readonly router: Router,
|
||||
protected readonly nav: NavigationService,
|
||||
) { }
|
||||
|
||||
async canActivate(): Promise<boolean> {
|
||||
const checkCanActivate = async () => {
|
||||
const isAuthenticated = (this.api.isAuthenticated && !this.api.isPublicUser);
|
||||
|
||||
if ( isAuthenticated ) {
|
||||
await this.router.navigate(['/home']);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise(async res => {
|
||||
if ( !this.nav.initialized$.getValue() ) {
|
||||
const sub = this.nav.initialized$.subscribe(async initialized => {
|
||||
if ( initialized ) {
|
||||
sub.unsubscribe();
|
||||
return res(await checkCanActivate());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return res(await checkCanActivate());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in new issue