Implement user sign-in component

This commit is contained in:
Steven Zou 2017-02-15 11:02:59 +08:00
parent 640cce0066
commit 12eb4d3382
12 changed files with 295 additions and 98 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
coverage/
dist/
html-report/
node_modules/
typings/
**/*npm-debug.log.*
**/*yarn-error.log.*
.idea/
.DS_Store
proxy.config.json
*.log
**/*.log

View File

@ -1,58 +1,58 @@
{
"name": "clarity-seed",
"version": "0.8.0",
"description": "Angular-CLI starter for a Clarity project",
"angular-cli": {},
"scripts": {
"start": "ng serve --host 0.0.0.0",
"lint": "tslint \"src/**/*.ts\"",
"test": "ng test --single-run",
"pree2e": "webdriver-manager update",
"e2e": "protractor"
},
"private": true,
"dependencies": {
"@angular/common": "^2.4.1",
"@angular/compiler": "^2.4.1",
"@angular/core": "^2.4.1",
"@angular/forms": "^2.4.1",
"@angular/http": "^2.4.1",
"@angular/platform-browser": "^2.4.1",
"@angular/platform-browser-dynamic": "^2.4.1",
"@angular/router": "^3.4.1",
"@webcomponents/custom-elements": "1.0.0-alpha.3",
"clarity-angular": "^0.8.0",
"clarity-icons": "^0.8.0",
"clarity-ui": "^0.8.0",
"core-js": "^2.4.1",
"mutationobserver-shim": "^0.3.2",
"rxjs": "^5.0.1",
"ts-helpers": "^1.1.1",
"web-animations-js": "^2.2.1",
"zone.js": "^0.7.2"
},
"devDependencies": {
"@angular/compiler-cli": "^2.4.1",
"@types/core-js": "^0.9.34",
"@types/jasmine": "^2.2.30",
"@types/node": "^6.0.42",
"angular-cli": "^1.0.0-beta.24",
"bootstrap": "4.0.0-alpha.5",
"codelyzer": "~1.0.0-beta.3",
"enhanced-resolve": "^3.0.0",
"jasmine-core": "2.4.1",
"jasmine-spec-reporter": "2.5.0",
"karma": "1.2.0",
"karma-cli": "^1.0.1",
"karma-jasmine": "^1.0.2",
"karma-mocha-reporter": "^2.2.1",
"karma-phantomjs-launcher": "^1.0.0",
"karma-remap-istanbul": "^0.2.1",
"protractor": "4.0.9",
"ts-node": "1.2.1",
"tslint": "^4.1.1",
"typescript": "~2.0.3",
"typings": "^1.4.0",
"webdriver-manager": "10.2.5"
}
"name": "clarity-seed",
"version": "0.8.0",
"description": "Angular-CLI starter for a Clarity project",
"angular-cli": {},
"scripts": {
"start": "ng serve --host 0.0.0.0 --proxy-config proxy.config.json",
"lint": "tslint \"src/**/*.ts\"",
"test": "ng test --single-run",
"pree2e": "webdriver-manager update",
"e2e": "protractor"
},
"private": true,
"dependencies": {
"@angular/common": "^2.4.1",
"@angular/compiler": "^2.4.1",
"@angular/core": "^2.4.1",
"@angular/forms": "^2.4.1",
"@angular/http": "^2.4.1",
"@angular/platform-browser": "^2.4.1",
"@angular/platform-browser-dynamic": "^2.4.1",
"@angular/router": "^3.4.1",
"@webcomponents/custom-elements": "1.0.0-alpha.3",
"clarity-angular": "^0.8.0",
"clarity-icons": "^0.8.0",
"clarity-ui": "^0.8.0",
"core-js": "^2.4.1",
"mutationobserver-shim": "^0.3.2",
"rxjs": "^5.0.1",
"ts-helpers": "^1.1.1",
"web-animations-js": "^2.2.1",
"zone.js": "^0.7.2"
},
"devDependencies": {
"@angular/compiler-cli": "^2.4.1",
"@types/core-js": "^0.9.34",
"@types/jasmine": "^2.2.30",
"@types/node": "^6.0.42",
"angular-cli": "^1.0.0-beta.24",
"bootstrap": "4.0.0-alpha.5",
"codelyzer": "~1.0.0-beta.3",
"enhanced-resolve": "^3.0.0",
"jasmine-core": "2.4.1",
"jasmine-spec-reporter": "2.5.0",
"karma": "1.2.0",
"karma-cli": "^1.0.1",
"karma-jasmine": "^1.0.2",
"karma-mocha-reporter": "^2.2.1",
"karma-phantomjs-launcher": "^1.0.0",
"karma-remap-istanbul": "^0.2.1",
"protractor": "4.0.9",
"ts-node": "1.2.1",
"tslint": "^4.1.1",
"typescript": "~2.0.3",
"typings": "^1.4.0",
"webdriver-manager": "10.2.5"
}
}

View File

@ -1,5 +1,5 @@
import { NgModule } from '@angular/core';
import { SignInComponent } from './sign-in.component';
import { SignInComponent } from './sign-in/sign-in.component';
import { SharedModule } from '../shared/shared.module';
import { RouterModule } from '@angular/router';

View File

@ -1,29 +0,0 @@
<div class="login-wrapper">
<form class="login">
<label class="title">
VMware Harbor<span class="trademark">&#8482;</span>
</label>
<div class="login-group">
<div class="auth-source select">
<select id="login-auth-source-1">
<option>Local Users</option>
<option>Administrator</option>
</select>
</div>
<input class="username" type="text" id="login_username" placeholder="Username">
<input class="password" type="password" id="login_password" placeholder="Password">
<div class="checkbox">
<input type="checkbox" id="rememberme">
<label for="rememberme">
Remember me
</label>
</div>
<div class="error active">
Invalid user name or password
</div>
<button type="submit" class="btn btn-primary" [routerLink]="['/harbor', 'dashboard']">LOG IN</button>
<a href="..." class="signup">Sign up for an account</a>
</div>
</form>
</div>

View File

@ -1,10 +0,0 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'sign-in',
templateUrl: "sign-in.component.html"
})
export class SignInComponent {
// constructor(private router: Router){}
}

View File

@ -0,0 +1,15 @@
/**
* Declare class for store the sign in data,
* two prperties:
* principal: The username used to sign in
* password: The password used to sign in
*
* @export
* @class SignInCredential
*/
export class SignInCredential {
principal: string;
password: string;
}

View File

@ -0,0 +1,7 @@
.progress-size-small {
height: 0.5em !important;
}
.visibility-hidden {
visibility: hidden;
}

View File

@ -0,0 +1,39 @@
<div class="login-wrapper">
<form #signInForm="ngForm" class="login">
<label class="title">
VMware Harbor<span class="trademark">&#8482;</span>
</label>
<div class="login-group">
<label for="username" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]="userNameInput.invalid && (userNameInput.dirty || userNameInput.touched)">
<input class="username" type="text" required
[(ngModel)]="signInCredential.principal"
name="login_username" id="login_username" placeholder="Username"
#userNameInput='ngModel'>
<span class="tooltip-content">
Username is required!
</span>
</label>
<label for="username" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-left" [class.invalid]="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
<input class="password" type="password" required
[(ngModel)]="signInCredential.password"
name="login_password" id="login_password" placeholder="Password"
#passwordInput="ngModel">
<span class="tooltip-content">
Password is required!
</span>
</label>
<div class="checkbox">
<input type="checkbox" id="rememberme">
<label for="rememberme">
Remember me
</label>
</div>
<div [class.visibility-hidden]="signInStatus != statusError" class="error active">
Invalid user name or password
</div>
<button [class.visibility-hidden]="signInStatus === statusOnGoing" [disabled]="signInStatus === statusOnGoing" type="submit" class="btn btn-primary" (click)="signIn()">LOG IN</button>
<div [class.visibility-hidden]="signInStatus != statusOnGoing" class="progress loop progress-size-small"><progress></progress></div>
<a href="javascript:void(0)" class="signup" (click)="signUp()">Sign up for an account</a>
</div>
</form>
</div>

View File

@ -0,0 +1,121 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Input, ViewChild, AfterViewChecked } from '@angular/core';
import { NgForm } from '@angular/forms';
import { SignInService } from './sign-in.service';
import { SignInCredential } from './sign-in-credential'
//Define status flags for signing in states
export const signInStatusNormal = 0;
export const signInStatusOnGoing = 1;
export const signInStatusError = -1;
@Component({
selector: 'sign-in',
templateUrl: "sign-in.component.html",
styleUrls: ['sign-in.component.css'],
providers: [SignInService]
})
export class SignInComponent implements AfterViewChecked {
//Form reference
signInForm: NgForm;
@ViewChild('signInForm') currentForm: NgForm;
//Status flag
signInStatus: number = 0;
//Initialize sign in credential
@Input() signInCredential: SignInCredential = {
principal: "",
password: ""
};
constructor(
private signInService: SignInService,
private router: Router
) { }
//For template accessing
get statusError(): number {
return signInStatusError;
}
get statusOnGoing(): number {
return signInStatusOnGoing;
}
//Validate the related fields
private validate(): boolean {
return true;
//return this.signInForm.valid;
}
//Hande form values changes
private formChanged() {
if (this.currentForm === this.signInForm) {
return;
}
this.signInForm = this.currentForm;
if (this.signInForm) {
this.signInForm.valueChanges
.subscribe(data => {
this.updateState();
});
}
}
//Implement interface
//Watch the view change only when view is in error state
ngAfterViewChecked() {
if (this.signInStatus === signInStatusError) {
this.formChanged();
}
}
//Update the status if we have done some changes
updateState(): void {
if (this.signInStatus === signInStatusError) {
this.signInStatus = signInStatusNormal; //reset
}
}
//Trigger the signin action
signIn(): void {
//Should validate input firstly
if (!this.validate()) {
console.info("return");
return;
}
//Start signing in progress
this.signInStatus = signInStatusOnGoing;
//Call the service to send out the http request
this.signInService.signIn(this.signInCredential)
.then(() => {
//Set status
this.signInStatus = signInStatusNormal;
//Routing to the right location
let nextRoute = ["/harbor", "dashboard"];
this.router.navigate(nextRoute);
})
.catch(error => {
//Set error status
this.signInStatus = signInStatusError;
let message = error.status ? error.status + ":" + error.statusText : error;
console.error("An error occurred when signing in:", message);
});
}
//Help user navigate to the sign up
signUp(): void {
let nextRoute = ["/harbor", "signup"];
this.router.navigate(nextRoute);
}
}

View File

@ -0,0 +1,41 @@
import { Injectable } from '@angular/core';
import { Headers, Http, URLSearchParams } from '@angular/http';
import 'rxjs/add/operator/toPromise';
import { SignInCredential } from './sign-in-credential';
const signInUrl = '/login';
/**
*
* Define a service to provide sign in methods
*
* @export
* @class SignInService
*/
@Injectable()
export class SignInService {
private headers = new Headers({
"Content-Type": 'application/x-www-form-urlencoded'
});
constructor(private http: Http) {}
//Handle the related exceptions
private handleError(error: any): Promise<any>{
return Promise.reject(error.message || error);
}
//Submit signin form to backend (NOT restful service)
signIn(signInCredential: SignInCredential): Promise<any>{
//Build the form package
const body = new URLSearchParams();
body.set('principal', signInCredential.principal);
body.set('password', signInCredential.password);
//Trigger Http
return this.http.post(signInUrl, body.toString(), { headers: this.headers })
.toPromise()
.then(()=>null)
.catch(this.handleError);
}
}

View File

@ -0,0 +1 @@
<p>Placeholder for signup</p>

View File

@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SignInComponent } from './account/sign-in.component';
import { SignInComponent } from './account/sign-in/sign-in.component';
const harborRoutes: Routes = [
{ path: '', redirectTo: '/sign-in', pathMatch: 'full' },