#84 - Add login page as part of SPA instead of relying on external page
This commit is contained in:
parent
aad0aea79a
commit
0fecb8a4ba
@ -1,5 +1,8 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
|
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
|
||||||
|
import {LoginPage} from './pages/login/login.page';
|
||||||
|
import {AuthService} from './service/auth.service';
|
||||||
|
import {GuestOnlyGuard} from './service/guard/GuestOnly.guard';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@ -9,15 +12,18 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'home',
|
path: 'home',
|
||||||
|
canActivate: [AuthService],
|
||||||
loadChildren: () => import('./home/home.module').then(m => m.HomePageModule)
|
loadChildren: () => import('./home/home.module').then(m => m.HomePageModule)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'list',
|
path: 'editor',
|
||||||
loadChildren: () => import('./list/list.module').then(m => m.ListPageModule)
|
canActivate: [AuthService],
|
||||||
|
loadChildren: () => import('./components/components.module').then( m => m.ComponentsModule)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'editor',
|
path: 'login',
|
||||||
loadChildren: () => import('./components/components.module').then( m => m.ComponentsModule)
|
canActivate: [GuestOnlyGuard],
|
||||||
|
component: LoginPage,
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import {NavigationService} from './service/navigation.service';
|
|||||||
import {DatabaseService} from './service/db/database.service';
|
import {DatabaseService} from './service/db/database.service';
|
||||||
import {EditorService} from './service/editor.service';
|
import {EditorService} from './service/editor.service';
|
||||||
import {debug} from './utility';
|
import {debug} from './utility';
|
||||||
|
import {AuthService} from './service/auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@ -96,7 +97,6 @@ export class AppComponent implements OnInit {
|
|||||||
protected showedNewVersionAlert = false;
|
protected showedNewVersionAlert = false;
|
||||||
protected showedOfflineAlert = false;
|
protected showedOfflineAlert = false;
|
||||||
protected backbuttonSubscription: any;
|
protected backbuttonSubscription: any;
|
||||||
protected initialized$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private platform: Platform,
|
private platform: Platform,
|
||||||
@ -114,6 +114,7 @@ export class AppComponent implements OnInit {
|
|||||||
protected toasts: ToastController,
|
protected toasts: ToastController,
|
||||||
protected db: DatabaseService,
|
protected db: DatabaseService,
|
||||||
protected editor: EditorService,
|
protected editor: EditorService,
|
||||||
|
protected auth: AuthService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async checkNewVersion() {
|
async checkNewVersion() {
|
||||||
@ -479,9 +480,11 @@ export class AppComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async initializeApp() {
|
async initializeApp() {
|
||||||
|
const initializedOnce = this.navService.initialized$.getValue();
|
||||||
|
|
||||||
debug('app', this);
|
debug('app', this);
|
||||||
this.loader = await this.loading.create({
|
this.loader = await this.loading.create({
|
||||||
message: 'Starting up...',
|
message: 'Setting things up...',
|
||||||
cssClass: 'noded-loading-mask',
|
cssClass: 'noded-loading-mask',
|
||||||
showBackdrop: true,
|
showBackdrop: true,
|
||||||
});
|
});
|
||||||
@ -493,6 +496,7 @@ export class AppComponent implements OnInit {
|
|||||||
|
|
||||||
let toast: any;
|
let toast: any;
|
||||||
|
|
||||||
|
if ( !initializedOnce ) {
|
||||||
debug('Subscribing to offline changes...');
|
debug('Subscribing to offline changes...');
|
||||||
this.api.offline$.subscribe(async isOffline => {
|
this.api.offline$.subscribe(async isOffline => {
|
||||||
if ( isOffline && !this.showedOfflineAlert ) {
|
if ( isOffline && !this.showedOfflineAlert ) {
|
||||||
@ -511,19 +515,14 @@ export class AppComponent implements OnInit {
|
|||||||
await this.api.syncOfflineData();
|
await this.api.syncOfflineData();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
debug('Getting initial status...');
|
debug('Getting initial status...');
|
||||||
let stat: any = await this.session.stat();
|
let stat: any = await this.session.stat();
|
||||||
debug('Got stat:', stat);
|
debug('Got stat:', stat);
|
||||||
|
|
||||||
if ( stat.public_user ) {
|
this.api.isPublicUser = !!stat.public_user;
|
||||||
this.api.isPublicUser = true;
|
this.api.isAuthenticated = !!stat.authenticated_user;
|
||||||
}
|
|
||||||
|
|
||||||
if ( stat.authenticated_user ) {
|
|
||||||
this.api.isAuthenticated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.api.systemBase = stat.system_base;
|
this.api.systemBase = stat.system_base;
|
||||||
|
|
||||||
if ( !this.api.isAuthenticated || this.api.isPublicUser ) {
|
if ( !this.api.isAuthenticated || this.api.isPublicUser ) {
|
||||||
@ -558,7 +557,7 @@ export class AppComponent implements OnInit {
|
|||||||
debug('Initializing session...');
|
debug('Initializing session...');
|
||||||
await this.session.initialize();
|
await this.session.initialize();
|
||||||
|
|
||||||
if ( this.session.get('user.preferences.dark_mode') ) {
|
if ( this.session.get('user.preferences.dark_mode') && !this.darkMode ) {
|
||||||
this.toggleDark();
|
this.toggleDark();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -603,7 +602,7 @@ export class AppComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initialized$.next(true);
|
this.navService.initialized$.next(true);
|
||||||
|
|
||||||
if ( !this.api.isPublicUser && this.session.get('user.preferences.default_page') ) {
|
if ( !this.api.isPublicUser && this.session.get('user.preferences.default_page') ) {
|
||||||
debug('Navigating to default page!');
|
debug('Navigating to default page!');
|
||||||
@ -611,9 +610,14 @@ export class AppComponent implements OnInit {
|
|||||||
const node = this.findNode(id);
|
const node = this.findNode(id);
|
||||||
if ( node ) {
|
if ( node ) {
|
||||||
this.navigateEditorToNode(node);
|
this.navigateEditorToNode(node);
|
||||||
|
} else if ( this.auth.authInProgress ) {
|
||||||
|
await this.router.navigate(['/home']);
|
||||||
}
|
}
|
||||||
|
} else if ( this.auth.authInProgress ) {
|
||||||
|
await this.router.navigate(['/home']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( !initializedOnce ) {
|
||||||
debug('Creating menu subscription...');
|
debug('Creating menu subscription...');
|
||||||
this.navService.sidebarRefresh$.subscribe(([_, quiet]) => {
|
this.navService.sidebarRefresh$.subscribe(([_, quiet]) => {
|
||||||
this.onMenuRefresh(quiet);
|
this.onMenuRefresh(quiet);
|
||||||
@ -630,6 +634,15 @@ export class AppComponent implements OnInit {
|
|||||||
this.router.navigate(['/editor', { id: pageId }]);
|
this.router.navigate(['/editor', { id: pageId }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.navService.initializationRequest$.subscribe((count) => {
|
||||||
|
if ( count === 0 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initializeApp();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
debug('Reloading menu items...');
|
debug('Reloading menu items...');
|
||||||
this.reloadMenuItems().subscribe(() => {
|
this.reloadMenuItems().subscribe(() => {
|
||||||
debug('Reloaded menu items. Displaying interface.');
|
debug('Reloaded menu items. Displaying interface.');
|
||||||
@ -647,6 +660,8 @@ export class AppComponent implements OnInit {
|
|||||||
}, 1000 * 60 * 5); // Check for new version every 5 mins
|
}, 1000 * 60 * 5); // Check for new version every 5 mins
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.auth.authInProgress = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async doPrefetch() {
|
async doPrefetch() {
|
||||||
|
@ -34,6 +34,7 @@ import {MarkdownModule} from 'ngx-markdown';
|
|||||||
import {VersionModalComponent} from './version-modal/version-modal.component';
|
import {VersionModalComponent} from './version-modal/version-modal.component';
|
||||||
import {EditorPageRoutingModule} from '../pages/editor/editor-routing.module';
|
import {EditorPageRoutingModule} from '../pages/editor/editor-routing.module';
|
||||||
import {EditorPage} from '../pages/editor/editor.page';
|
import {EditorPage} from '../pages/editor/editor.page';
|
||||||
|
import {LoginPage} from '../pages/login/login.page';
|
||||||
import {WysiwygComponent} from './wysiwyg/wysiwyg.component';
|
import {WysiwygComponent} from './wysiwyg/wysiwyg.component';
|
||||||
import {WysiwygEditorComponent} from './editor/database/editors/wysiwyg/wysiwyg-editor.component';
|
import {WysiwygEditorComponent} from './editor/database/editors/wysiwyg/wysiwyg-editor.component';
|
||||||
import {WysiwygModalComponent} from './editor/database/editors/wysiwyg/wysiwyg-modal.component';
|
import {WysiwygModalComponent} from './editor/database/editors/wysiwyg/wysiwyg-modal.component';
|
||||||
@ -76,6 +77,7 @@ import {FileBoxPageComponent} from './nodes/file-box/file-box-page.component';
|
|||||||
MarkdownEditorComponent,
|
MarkdownEditorComponent,
|
||||||
VersionModalComponent,
|
VersionModalComponent,
|
||||||
EditorPage,
|
EditorPage,
|
||||||
|
LoginPage,
|
||||||
WysiwygComponent,
|
WysiwygComponent,
|
||||||
WysiwygEditorComponent,
|
WysiwygEditorComponent,
|
||||||
WysiwygModalComponent,
|
WysiwygModalComponent,
|
||||||
@ -130,6 +132,7 @@ import {FileBoxPageComponent} from './nodes/file-box/file-box-page.component';
|
|||||||
MarkdownEditorComponent,
|
MarkdownEditorComponent,
|
||||||
VersionModalComponent,
|
VersionModalComponent,
|
||||||
EditorPage,
|
EditorPage,
|
||||||
|
LoginPage,
|
||||||
WysiwygComponent,
|
WysiwygComponent,
|
||||||
WysiwygEditorComponent,
|
WysiwygEditorComponent,
|
||||||
WysiwygModalComponent,
|
WysiwygModalComponent,
|
||||||
@ -170,6 +173,7 @@ import {FileBoxPageComponent} from './nodes/file-box/file-box-page.component';
|
|||||||
MarkdownEditorComponent,
|
MarkdownEditorComponent,
|
||||||
VersionModalComponent,
|
VersionModalComponent,
|
||||||
EditorPage,
|
EditorPage,
|
||||||
|
LoginPage,
|
||||||
WysiwygComponent,
|
WysiwygComponent,
|
||||||
WysiwygEditorComponent,
|
WysiwygEditorComponent,
|
||||||
WysiwygModalComponent,
|
WysiwygModalComponent,
|
||||||
|
@ -33,7 +33,6 @@ export class BooleanRendererComponent implements ICellRendererAngularComp {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
agInit(params: ICellRendererParams): void {
|
agInit(params: ICellRendererParams): void {
|
||||||
console.log('bool renderer', this);
|
|
||||||
this.params = params;
|
this.params = params;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -3,6 +3,7 @@ import {Router} from '@angular/router';
|
|||||||
import {ApiService} from '../../service/api.service';
|
import {ApiService} from '../../service/api.service';
|
||||||
import {PopoverController} from '@ionic/angular';
|
import {PopoverController} from '@ionic/angular';
|
||||||
import {DatabaseService} from '../../service/db/database.service';
|
import {DatabaseService} from '../../service/db/database.service';
|
||||||
|
import {AuthService} from '../../service/auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-option-picker',
|
selector: 'app-option-picker',
|
||||||
@ -22,6 +23,7 @@ export class OptionPickerComponent implements OnInit {
|
|||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected popover: PopoverController,
|
protected popover: PopoverController,
|
||||||
protected db: DatabaseService,
|
protected db: DatabaseService,
|
||||||
|
protected auth: AuthService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {}
|
ngOnInit() {}
|
||||||
@ -30,8 +32,8 @@ export class OptionPickerComponent implements OnInit {
|
|||||||
if ( key === 'html_export' ) {
|
if ( key === 'html_export' ) {
|
||||||
window.open(this.api._build_url('/data/export/html'), '_blank');
|
window.open(this.api._build_url('/data/export/html'), '_blank');
|
||||||
} else if ( key === 'logout' ) {
|
} else if ( key === 'logout' ) {
|
||||||
await this.db.purge();
|
await this.popover.dismiss();
|
||||||
window.location.href = '/auth/logout';
|
await this.auth.endSession();
|
||||||
} else if ( key === 'toggle_darkmode' ) {
|
} else if ( key === 'toggle_darkmode' ) {
|
||||||
this.toggleDark();
|
this.toggleDark();
|
||||||
} else if ( key === 'search_everywhere' ) {
|
} else if ( key === 'search_everywhere' ) {
|
||||||
|
@ -8,7 +8,6 @@ import {isDebug, debug} from '../utility';
|
|||||||
styleUrls: ['home.page.scss'],
|
styleUrls: ['home.page.scss'],
|
||||||
})
|
})
|
||||||
export class HomePage implements OnInit {
|
export class HomePage implements OnInit {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly api: ApiService,
|
public readonly api: ApiService,
|
||||||
) {}
|
) {}
|
||||||
|
@ -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)]);
|
|
||||||
// }
|
|
||||||
}
|
|
94
src/app/pages/login/login.page.html
Normal file
94
src/app/pages/login/login.page.html
Normal file
@ -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>
|
5
src/app/pages/login/login.page.scss
Normal file
5
src/app/pages/login/login.page.scss
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.login-container {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
175
src/app/pages/login/login.page.ts
Normal file
175
src/app/pages/login/login.page.ts
Normal file
@ -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';
|
||||||
|
}
|
||||||
|
}
|
@ -1401,4 +1401,66 @@ export class ApiService {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getUserInfo(uid: string): Promise<any> {
|
||||||
|
return new Promise(async (res, rej) => {
|
||||||
|
if ( this.isOffline ) {
|
||||||
|
return rej(new ResourceNotAvailableOfflineError());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.get('/auth/user-info', { uid }).subscribe({
|
||||||
|
next: result => {
|
||||||
|
res(result.data);
|
||||||
|
},
|
||||||
|
error: rej,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public attemptLogin(uid: string, password: string): Promise<{ success: boolean, message?: string }> {
|
||||||
|
return new Promise(async (res, rej) => {
|
||||||
|
if ( this.isOffline ) {
|
||||||
|
return rej(new ResourceNotAvailableOfflineError());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.post('/auth/attempt', { uid, password }).subscribe({
|
||||||
|
next: result => {
|
||||||
|
res(result.data);
|
||||||
|
},
|
||||||
|
error: rej,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public attemptRegistration(uid: string, password: string, passwordConfirmation: string, fullName: string):
|
||||||
|
Promise<{ success: boolean, message?: string }> {
|
||||||
|
|
||||||
|
return new Promise(async (res, rej) => {
|
||||||
|
if ( this.isOffline ) {
|
||||||
|
return rej(new ResourceNotAvailableOfflineError());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.post('/auth/register', { uid, password, passwordConfirmation, fullName }).subscribe({
|
||||||
|
next: result => {
|
||||||
|
res(result.data);
|
||||||
|
},
|
||||||
|
error: rej,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public endSession(): Promise<any> {
|
||||||
|
return new Promise(async (res, rej) => {
|
||||||
|
if ( this.isOffline ) {
|
||||||
|
return rej(new ResourceNotAvailableOfflineError());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.post('/auth/end-session').subscribe({
|
||||||
|
next: result => {
|
||||||
|
return res(result.data);
|
||||||
|
},
|
||||||
|
error: rej,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
59
src/app/service/auth.service.ts
Normal file
59
src/app/service/auth.service.ts
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
41
src/app/service/guard/GuestOnly.guard.ts
Normal file
41
src/app/service/guard/GuestOnly.guard.ts
Normal file
@ -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());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,8 @@ export class NavigationService {
|
|||||||
protected refreshCount = 0;
|
protected refreshCount = 0;
|
||||||
public readonly sidebarRefresh$: BehaviorSubject<[number, boolean]> = new BehaviorSubject<[number, boolean]>([this.refreshCount, true]);
|
public readonly sidebarRefresh$: BehaviorSubject<[number, boolean]> = new BehaviorSubject<[number, boolean]>([this.refreshCount, true]);
|
||||||
public readonly navigationRequest$: BehaviorSubject<string> = new BehaviorSubject<string>('');
|
public readonly navigationRequest$: BehaviorSubject<string> = new BehaviorSubject<string>('');
|
||||||
|
public readonly initializationRequest$: BehaviorSubject<number> = new BehaviorSubject<number>(0);
|
||||||
|
public readonly initialized$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
requestSidebarRefresh({ quiet = false }) {
|
requestSidebarRefresh({ quiet = false }) {
|
||||||
this.refreshCount += 1;
|
this.refreshCount += 1;
|
||||||
@ -21,4 +23,9 @@ export class NavigationService {
|
|||||||
|
|
||||||
this.navigationRequest$.next(pageId);
|
this.navigationRequest$.next(pageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestInitialization() {
|
||||||
|
debug('Requesting application initialization');
|
||||||
|
this.initializationRequest$.next(this.initializationRequest$.getValue() + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user