Reorganize app files, setup initial routing and auth checks
Log in/out is functional with notifications (modified from recipes project). Shared resources are now organized as such, with barrels to make imports simple.
This commit is contained in:
parent
ea7f18ef7b
commit
68d693307d
@ -32,6 +32,7 @@
|
||||
"@angular/common": "^2.0.0-rc.4",
|
||||
"@angular/compiler": "^2.0.0-rc.4",
|
||||
"@angular/core": "^2.0.0-rc.4",
|
||||
"@angular/forms": "^0.2.0",
|
||||
"@angular/http": "^2.0.0-rc.4",
|
||||
"@angular/platform-browser": "^2.0.0-rc.4",
|
||||
"@angular/platform-browser-dynamic": "^2.0.0-rc.4",
|
||||
|
@ -5,3 +5,11 @@ SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0
|
||||
AddOutputFilterByType DEFLATE text/text text/html text/plain text/xml text/css application/x-javascript application/javascript text/javascript
|
||||
</IfModule>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
RewriteCond %{REQUEST_URI}::$1 ^(.*?/)(.*)::\2$
|
||||
RewriteRule ^(.*)$ - [E=BASE:%1]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^(.*)$ %{ENV:BASE}index.html [QSA,L]
|
||||
|
||||
|
@ -124,8 +124,13 @@ class Auth extends BaseController {
|
||||
$user->last_login = time();
|
||||
$user->save();
|
||||
|
||||
$user->security_level = $user->security_level->getValue();
|
||||
unset($user->password_hash);
|
||||
unset($user->active_token);
|
||||
|
||||
$this->apiJson->setSuccess();
|
||||
$this->apiJson->addData($jwt);
|
||||
$this->apiJson->addData($user);
|
||||
|
||||
return $this->jsonResponse($response);
|
||||
}
|
||||
|
@ -1,2 +1,4 @@
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
<tb-notifications></tb-notifications>
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ROUTER_DIRECTIVES } from '@angular/router';
|
||||
|
||||
import { Notifications } from './shared/index';
|
||||
|
||||
@Component({
|
||||
selector: 'app-component',
|
||||
directives: [ ROUTER_DIRECTIVES ],
|
||||
directives: [ ROUTER_DIRECTIVES, Notifications ],
|
||||
templateUrl: 'app/app.component.html'
|
||||
})
|
||||
export class AppComponent {
|
||||
|
@ -1,16 +1,36 @@
|
||||
import { RouterConfig, provideRouter } from '@angular/router';
|
||||
|
||||
import { AuthGuard, AuthService } from './shared/index';
|
||||
import { Login } from './login/login.component';
|
||||
import { Board } from './board/board.component';
|
||||
import { Settings } from './settings/settings.component';
|
||||
import { Dashboard } from './dashboard/dashboard.component';
|
||||
|
||||
const ROUTES: RouterConfig = [
|
||||
{
|
||||
path: '',
|
||||
component: Login
|
||||
},
|
||||
{
|
||||
path: 'boards',
|
||||
component: Board,
|
||||
canActivate: [ AuthGuard ]
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
component: Settings,
|
||||
canActivate: [ AuthGuard ]
|
||||
},
|
||||
{
|
||||
path: 'dashboard',
|
||||
component: Dashboard,
|
||||
canActivate: [ AuthGuard ]
|
||||
}
|
||||
];
|
||||
|
||||
export const APP_ROUTER_PROVIDERS = [
|
||||
provideRouter(ROUTES)
|
||||
provideRouter(ROUTES),
|
||||
AuthGuard,
|
||||
AuthService
|
||||
];
|
||||
|
||||
|
@ -38,45 +38,53 @@
|
||||
<span class="right icon icon-angle-double-down"
|
||||
title="Collapse Column"></span>
|
||||
</h3>
|
||||
<div class="task">
|
||||
<h4>
|
||||
<span class="icon icon-minus-squared-alt" title="Collapse Task"></span>
|
||||
Something to Get Done
|
||||
<span class="badge right" title="Points">3</span>
|
||||
</h4>
|
||||
<div class="description">
|
||||
<p>This is the thing that needs to get done.</p>
|
||||
|
||||
<div class="tasks">
|
||||
<div class="task">
|
||||
<h4>
|
||||
<span class="icon icon-minus-squared-alt" title="Collapse Task"></span>
|
||||
Something to Get Done
|
||||
<span class="badge right" title="Points">3</span>
|
||||
</h4>
|
||||
<div class="description">
|
||||
<p>This is the thing that needs to get done.</p>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Assigned To: admin
|
||||
<span class="right">
|
||||
<span class="icon icon-chat-empty" title="4 Comments"></span>
|
||||
<span class="icon icon-attach" title="2 Attachments"></span>
|
||||
<span class="category" title="Category"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Assigned To: admin
|
||||
<span class="right">
|
||||
<span class="icon icon-chat-empty" title="4 Comments"></span>
|
||||
<span class="icon icon-attach" title="2 Attachments"></span>
|
||||
<span class="category" title="Category"></span>
|
||||
</span>
|
||||
<div class="task">
|
||||
<h4>
|
||||
<span class="icon icon-minus-squared-alt" title="Collapse Task"></span>
|
||||
A Task With a Very Long Title to Test How it Wraps
|
||||
<span class="badge right" title="Points">3</span>
|
||||
</h4>
|
||||
<div class="description">
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
||||
<p>Fusce ut commodo urna, vitae lacinia quam. Cras eleifend magna massa, vitae porttitor dolor lacinia quis.</p>
|
||||
<p>Nam tortor risus, faucibus nec lacinia ac, laoreet vitae diam.</p>
|
||||
<p>Nullam viverra elementum mi, at tincidunt nisl pretium a. Fusce risus risus, ornare facilisis leo a, dictum porta nibh.</p>
|
||||
<p>Quisque ut augue tortor. Phasellus hendrerit placerat molestie.</p>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Assigned To: admin
|
||||
<span class="right">
|
||||
<span class="icon icon-chat-empty" title="4 Comments"></span>
|
||||
<span class="icon icon-attach" title="2 Attachments"></span>
|
||||
<span class="category" title="Category">Front End</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task">
|
||||
<h4>
|
||||
<span class="icon icon-minus-squared-alt" title="Collapse Task"></span>
|
||||
A Task With a Very Long Title to Test How it Wraps
|
||||
<span class="badge right" title="Points">3</span>
|
||||
</h4>
|
||||
<div class="description">
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
|
||||
<p>Fusce ut commodo urna, vitae lacinia quam. Cras eleifend magna massa, vitae porttitor dolor lacinia quis.</p>
|
||||
<p>Nam tortor risus, faucibus nec lacinia ac, laoreet vitae diam.</p>
|
||||
<p>Nullam viverra elementum mi, at tincidunt nisl pretium a. Fusce risus risus, ornare facilisis leo a, dictum porta nibh.</p>
|
||||
<p>Quisque ut augue tortor. Phasellus hendrerit placerat molestie.</p>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Assigned To: admin
|
||||
<span class="right">
|
||||
<span class="icon icon-chat-empty" title="4 Comments"></span>
|
||||
<span class="icon icon-attach" title="2 Attachments"></span>
|
||||
<span class="category" title="Category">Front End</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="quick-add">
|
||||
<input type="text" placeholder="Quick Add Task - Title Only">
|
||||
<button class="flat"><i class="icon icon-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -90,23 +98,31 @@
|
||||
<span class="right icon icon-angle-double-down"
|
||||
title="Collapse Column"></span>
|
||||
</h3>
|
||||
<div class="task compact">
|
||||
<h4>
|
||||
<span class="icon icon-plus-squared-alt" title="Expand Task"></span>
|
||||
Something Else to Get Done
|
||||
<span class="badge right" title="Points">5</span>
|
||||
</h4>
|
||||
<div class="description">
|
||||
<p>This really needs to be taken care of as well.</p>
|
||||
<p>But probably not as quickly as the other one. Maybe.</p>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Assigned To: Unassigned
|
||||
<span class="right">
|
||||
<span class="category" title="Category">API</span>
|
||||
</span>
|
||||
|
||||
<div class="tasks">
|
||||
<div class="task compact">
|
||||
<h4>
|
||||
<span class="icon icon-plus-squared-alt" title="Expand Task"></span>
|
||||
Something Else to Get Done
|
||||
<span class="badge right" title="Points">5</span>
|
||||
</h4>
|
||||
<div class="description">
|
||||
<p>This really needs to be taken care of as well.</p>
|
||||
<p>But probably not as quickly as the other one. Maybe.</p>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Assigned To: Unassigned
|
||||
<span class="right">
|
||||
<span class="category" title="Category">API</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quick-add">
|
||||
<input type="text" placeholder="Quick Add Task - Title Only">
|
||||
<button class="flat"><i class="icon icon-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
@ -118,6 +134,11 @@
|
||||
<span class="right icon icon-angle-double-down"
|
||||
title="Collapse Column"></span>
|
||||
</h3>
|
||||
|
||||
<div class="quick-add">
|
||||
<input type="text" placeholder="Quick Add Task - Title Only">
|
||||
<button class="flat"><i class="icon icon-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column collapsed">
|
||||
@ -130,19 +151,27 @@
|
||||
<span class="right icon icon-angle-double-down"
|
||||
title="Collapse Column"></span>
|
||||
</h3>
|
||||
<div class="task">
|
||||
<h4>Something Else to Get Done <span class="badge right" title="Points">5</span></h4>
|
||||
<div class="description">
|
||||
<p>This really needs to be taken care of as well.</p>
|
||||
<p>But probably not as quickly as the other one. Maybe.</p>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Assigned To: Unassigned
|
||||
<span class="right">
|
||||
<span class="category" title="Category">API</span>
|
||||
</span>
|
||||
|
||||
<div class="tasks">
|
||||
<div class="task">
|
||||
<h4>Something Else to Get Done <span class="badge right" title="Points">5</span></h4>
|
||||
<div class="description">
|
||||
<p>This really needs to be taken care of as well.</p>
|
||||
<p>But probably not as quickly as the other one. Maybe.</p>
|
||||
</div>
|
||||
<div class="stats">
|
||||
Assigned To: Unassigned
|
||||
<span class="right">
|
||||
<span class="category" title="Category">API</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quick-add">
|
||||
<input type="text" placeholder="Quick Add Task - Title Only">
|
||||
<button class="flat"><i class="icon icon-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { TopNav } from '../top-nav/top-nav.component';
|
||||
import { TopNav } from '../shared/index';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-board',
|
||||
|
@ -1,3 +1,5 @@
|
||||
<tb-top-nav page-name="Dashboard"></tb-top-nav>
|
||||
|
||||
<div class="dashboard">
|
||||
<section>
|
||||
<h2>Boards and Tasks</h2>
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { TopNav } from '../shared/index';
|
||||
import { Charts } from './charts/charts.component';
|
||||
import { Calendar } from './calendar/calendar.component';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-dashboard',
|
||||
templateUrl: 'app/dashboard/dashboard.component.html',
|
||||
directives: [ Charts, Calendar ]
|
||||
directives: [ TopNav, Charts, Calendar ]
|
||||
})
|
||||
export class Dashboard {
|
||||
}
|
||||
|
@ -1,9 +1,15 @@
|
||||
<div class="login">
|
||||
<h1>TaskBoard</h1>
|
||||
<input type="text" placeholder="Username" title="Username" autofocus>
|
||||
<input type="password" placeholder="Password" title="Password">
|
||||
<label><input type="checkbox"> Remember Me</label>
|
||||
<button>Sign In</button>
|
||||
<form #f="ngForm">
|
||||
<h1>TaskBoard</h1>
|
||||
<input type="text" placeholder="Username" title="Username"
|
||||
[(ngModel)]="username" name="username" autofocus>
|
||||
<input type="password" placeholder="Password" title="Password"
|
||||
[(ngModel)]="password" name="password">
|
||||
<label><input type="checkbox"
|
||||
[(ngModel)]="remember" name="remember"> Remember Me</label>
|
||||
<button type="submit" [disabled]="isSubmitted"
|
||||
(click)="login()" >Sign In</button>
|
||||
</form>
|
||||
<p>v{{ version }}</p>
|
||||
</div>
|
||||
|
||||
|
@ -1,6 +1,13 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { Constants } from '../app.constants';
|
||||
import {
|
||||
AuthService,
|
||||
ApiResponse,
|
||||
Notification,
|
||||
NotificationsService
|
||||
} from '../shared/index';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-login',
|
||||
@ -8,9 +15,31 @@ import { Constants } from '../app.constants';
|
||||
})
|
||||
export class Login {
|
||||
version: string;
|
||||
username: string;
|
||||
password: string;
|
||||
remember: boolean;
|
||||
isSubmitted: boolean = false;
|
||||
|
||||
constructor(constants: Constants) {
|
||||
constructor(constants: Constants, private authService: AuthService,
|
||||
private router: Router, private notes: NotificationsService) {
|
||||
this.version = constants.VERSION;
|
||||
}
|
||||
|
||||
login(): void {
|
||||
this.isSubmitted = true;
|
||||
|
||||
this.authService.login(this.username, this.password, this.remember).
|
||||
subscribe((response: ApiResponse) => {
|
||||
response.alerts.forEach(msg => {
|
||||
this.notes.add(new Notification(msg.type, msg.text));
|
||||
});
|
||||
|
||||
if (response.status === 'success') {
|
||||
this.router.navigate(['/boards']);
|
||||
}
|
||||
|
||||
this.isSubmitted = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,23 @@
|
||||
import { bootstrap } from '@angular/platform-browser-dynamic';
|
||||
import { HTTP_PROVIDERS } from '@angular/http';
|
||||
import { disableDeprecatedForms, provideForms } from '@angular/forms';
|
||||
//import { enableProdMode } from '@angular/core';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
import { APP_ROUTER_PROVIDERS } from './app.routes';
|
||||
import { Constants } from './app.constants';
|
||||
import { NotificationsService } from './shared/index';
|
||||
|
||||
//enableProdMode();
|
||||
|
||||
bootstrap(AppComponent, [
|
||||
HTTP_PROVIDERS,
|
||||
APP_ROUTER_PROVIDERS,
|
||||
Constants
|
||||
]);
|
||||
bootstrap(AppComponent,
|
||||
[
|
||||
HTTP_PROVIDERS,
|
||||
disableDeprecatedForms(),
|
||||
provideForms(),
|
||||
APP_ROUTER_PROVIDERS,
|
||||
Constants,
|
||||
NotificationsService
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
<tb-top-nav page-name="Settings"></tb-top-nav>
|
||||
|
||||
<div class="settings">
|
||||
<div class="half-page">
|
||||
<section>
|
||||
@ -33,7 +35,7 @@
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Display two cards per column?
|
||||
Display tasks side-by-side in colums?
|
||||
<input type="checkbox">
|
||||
</label>
|
||||
</div>
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { TopNav } from '../shared/index';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-settings',
|
||||
templateUrl: 'app/settings/settings.component.html'
|
||||
templateUrl: 'app/settings/settings.component.html',
|
||||
directives: [ TopNav ]
|
||||
})
|
||||
export class Settings {
|
||||
}
|
||||
|
20
src/app/shared/auth/auth.guard.ts
Normal file
20
src/app/shared/auth/auth.guard.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanActivate, Router } from '@angular/router';
|
||||
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(private authService: AuthService, private router: Router) {
|
||||
}
|
||||
|
||||
canActivate() {
|
||||
if (this.authService.isLoggedIn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.router.navigate(['']);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
57
src/app/shared/auth/auth.service.ts
Normal file
57
src/app/shared/auth/auth.service.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Http, Response } from '@angular/http';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/observable/of';
|
||||
import 'rxjs/add/operator/catch';
|
||||
|
||||
import { User, ApiResponse } from '../index';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
activeUser: User;
|
||||
isLoggedIn: boolean = false;
|
||||
|
||||
constructor(private http: Http) {
|
||||
}
|
||||
|
||||
login(username: string, password: string,
|
||||
remember: boolean): Observable<ApiResponse> {
|
||||
// TODO Add remember flag to API
|
||||
let json = JSON.stringify({
|
||||
username: username,
|
||||
password: password
|
||||
});
|
||||
|
||||
return this.http.post('api/login', json).
|
||||
map(res => {
|
||||
let response: ApiResponse = res.json();
|
||||
|
||||
if (res.status === 200) {
|
||||
this.isLoggedIn = true;
|
||||
this.activeUser = response.data[1];
|
||||
|
||||
localStorage.setItem('jwt', response.data[0])
|
||||
}
|
||||
|
||||
return response;
|
||||
}).
|
||||
catch((res, caught) => {
|
||||
let response: ApiResponse = res.json();
|
||||
|
||||
if (res.status === 401) {
|
||||
this.activeUser = null;
|
||||
this.isLoggedIn = false;
|
||||
|
||||
localStorage.removeItem('jwt');
|
||||
}
|
||||
|
||||
return Observable.of(response);
|
||||
});
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.isLoggedIn = false;
|
||||
}
|
||||
}
|
||||
|
3
src/app/shared/auth/index.ts
Normal file
3
src/app/shared/auth/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './auth.guard';
|
||||
export * from './auth.service';
|
||||
|
5
src/app/shared/index.ts
Normal file
5
src/app/shared/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './top-nav/top-nav.component';
|
||||
export * from './auth/index';
|
||||
export * from './models/index';
|
||||
export * from './notifications/index';
|
||||
|
8
src/app/shared/models/api-response.model.ts
Normal file
8
src/app/shared/models/api-response.model.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Notification } from './notification.model';
|
||||
|
||||
export interface ApiResponse {
|
||||
alerts: Array<Notification>;
|
||||
data: Array<any>;
|
||||
status: string;
|
||||
}
|
||||
|
4
src/app/shared/models/index.ts
Normal file
4
src/app/shared/models/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './user.model';
|
||||
export * from './api-response.model';
|
||||
export * from './notification.model';
|
||||
|
10
src/app/shared/models/notification.model.ts
Normal file
10
src/app/shared/models/notification.model.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export class Notification {
|
||||
type: string;
|
||||
text: string;
|
||||
|
||||
constructor(type: string, text: string) {
|
||||
this.type = type;
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
10
src/app/shared/models/user.model.ts
Normal file
10
src/app/shared/models/user.model.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export interface User {
|
||||
default_board_id: number;
|
||||
email: string;
|
||||
id: number;
|
||||
last_login: Date;
|
||||
security_level: number;
|
||||
user_option_id: number;
|
||||
username: string;
|
||||
}
|
||||
|
3
src/app/shared/notifications/index.ts
Normal file
3
src/app/shared/notifications/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './notifications.component';
|
||||
export * from './notifications.service';
|
||||
|
@ -0,0 +1,7 @@
|
||||
<div class="notifications">
|
||||
<div (click)="hide(note)" class="{{ note.type }}"
|
||||
*ngFor="let note of notes">
|
||||
{{ note.text }}
|
||||
</div>
|
||||
</div>
|
||||
|
34
src/app/shared/notifications/notifications.component.ts
Normal file
34
src/app/shared/notifications/notifications.component.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Notification } from '../models/notification.model';
|
||||
import { NotificationsService } from './notifications.service';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-notifications',
|
||||
templateUrl: 'app/shared/notifications/notifications.component.html'
|
||||
})
|
||||
export class Notifications {
|
||||
private notes: Notification[];
|
||||
|
||||
constructor(private notifications: NotificationsService) {
|
||||
this.notes = new Array<Notification>();
|
||||
|
||||
notifications.noteAdded.subscribe(note => {
|
||||
this.notes.push(note);
|
||||
|
||||
setTimeout(() => { this.hide.bind(this)(note) }, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
private hide(note: Notification): void {
|
||||
let index = this.notes.indexOf(note);
|
||||
note.type = note.type + " clicked";
|
||||
|
||||
if (index >= 0) {
|
||||
setTimeout(() => {
|
||||
this.notes.splice(index, 1);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
17
src/app/shared/notifications/notifications.service.ts
Normal file
17
src/app/shared/notifications/notifications.service.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
import { Notification } from '../models/notification.model';
|
||||
|
||||
@Injectable()
|
||||
export class NotificationsService {
|
||||
private notifications = new Subject<Notification>();
|
||||
|
||||
public noteAdded = this.notifications.asObservable();
|
||||
|
||||
public add(notification: Notification): void {
|
||||
console.log(notification);
|
||||
this.notifications.next(notification);
|
||||
}
|
||||
}
|
||||
|
23
src/app/shared/top-nav/top-nav.component.html
Normal file
23
src/app/shared/top-nav/top-nav.component.html
Normal file
@ -0,0 +1,23 @@
|
||||
<nav class="nav-top">
|
||||
<h1>
|
||||
TaskBoard
|
||||
<span>
|
||||
<small>
|
||||
- {{ pageName }}
|
||||
<small class="dark small">(v{{ version }})</small>
|
||||
</small>
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<div class="buttons">
|
||||
<button [ngClass]="{ flat: !isActive('dashboard') }"
|
||||
(click)="navigateTo('dashboard')">Dashboard</button>
|
||||
<button [ngClass]="{ flat: !isActive('boards') }"
|
||||
(click)="navigateTo('boards')">Boards</button>
|
||||
<button [ngClass]="{ flat: !isActive('settings') }"
|
||||
(click)="navigateTo('settings')">Settings</button>
|
||||
<button class="flat"
|
||||
(click)="logout()">Logout (username)</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
34
src/app/shared/top-nav/top-nav.component.ts
Normal file
34
src/app/shared/top-nav/top-nav.component.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { ROUTER_DIRECTIVES, Router } from '@angular/router';
|
||||
|
||||
import { Constants } from '../../app.constants';
|
||||
import { AuthService } from '../auth/index';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-top-nav',
|
||||
templateUrl: 'app/shared/top-nav/top-nav.component.html',
|
||||
directives: [ ROUTER_DIRECTIVES ]
|
||||
})
|
||||
export class TopNav {
|
||||
@Input('page-name') pageName: string;
|
||||
version: string;
|
||||
|
||||
constructor(constants: Constants, private router: Router,
|
||||
private authService: AuthService) {
|
||||
this.version = constants.VERSION;
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.authService.logout();
|
||||
this.router.navigate(['']);
|
||||
}
|
||||
|
||||
isActive(route: string): boolean {
|
||||
return this.router.url.indexOf(route) !== -1;
|
||||
}
|
||||
|
||||
navigateTo(target: string): void {
|
||||
this.router.navigate(['/' + target]);
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +0,0 @@
|
||||
<nav class="nav-top">
|
||||
<h1>
|
||||
TaskBoard
|
||||
<span>
|
||||
<small>
|
||||
- {{ pageName }}
|
||||
<small class="dark small">(v{{ version }})</small>
|
||||
</small>
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<div class="buttons">
|
||||
<button class="flat">Dashboard</button>
|
||||
<button>Boards</button>
|
||||
<button class="flat">Settings</button>
|
||||
<button class="flat">Logout (username)</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -1,18 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { Constants } from '../app.constants';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-top-nav',
|
||||
templateUrl: 'app/top-nav/top-nav.component.html'
|
||||
})
|
||||
export class TopNav {
|
||||
pageName: string;
|
||||
version: string;
|
||||
|
||||
constructor(constants: Constants) {
|
||||
this.pageName = 'TaskBoard Re-Write';
|
||||
this.version = constants.VERSION;
|
||||
}
|
||||
}
|
||||
|
@ -45,10 +45,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.icon-angle-double-up {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon-angle-double-up,
|
||||
.badge {
|
||||
display: none;
|
||||
}
|
||||
@ -72,29 +69,51 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.icon-minus-squared-alt,
|
||||
.icon-plus-squared-alt {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
font-size: .6em;
|
||||
margin-left: 1em;
|
||||
margin-left: .9em;
|
||||
transform: rotate(-90deg) translateX(4px);
|
||||
}
|
||||
|
||||
.task {
|
||||
.icon-angle-double-down,
|
||||
.icon-minus-squared-alt,
|
||||
.icon-plus-squared-alt,
|
||||
.task,
|
||||
.quick-add {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon-angle-double-up {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-angle-double-down {
|
||||
display: none;
|
||||
}
|
||||
.quick-add {
|
||||
padding: 7px;
|
||||
|
||||
input {
|
||||
width: calc(100% - 42px);
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: $white;
|
||||
border-color: $color-border;
|
||||
color: $color-text;
|
||||
height: 36px;
|
||||
margin-left: 2px;
|
||||
padding: 9px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
|
||||
.tasks {
|
||||
padding: 7px;
|
||||
padding-bottom: 0;
|
||||
|
||||
div:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,7 +121,7 @@
|
||||
@include shadow-low();
|
||||
|
||||
background-color: #ffffe0;
|
||||
margin: 5px;
|
||||
margin-bottom: 7px;
|
||||
|
||||
h4 {
|
||||
border-bottom: 1px solid lighten($color-border, 25%);
|
||||
|
@ -2,12 +2,18 @@
|
||||
@include shadow-low();
|
||||
|
||||
background-color: $white;
|
||||
margin: 3em auto;
|
||||
display: inline-block;
|
||||
margin: 3em 41%;
|
||||
max-width: 350px;
|
||||
padding: 1em;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
form {
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 1em;
|
||||
margin-top: .5em;
|
||||
|
@ -28,6 +28,10 @@
|
||||
background-color: lighten($color-background, 10%);
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
54
src/scss/_notifications.scss
Normal file
54
src/scss/_notifications.scss
Normal file
@ -0,0 +1,54 @@
|
||||
$success: #dff2bf;
|
||||
$warn: #feefb3;
|
||||
$error: #ffbaba;
|
||||
$info: #bde5f8;
|
||||
|
||||
.notifications {
|
||||
bottom: 1%;
|
||||
left: calc(50% - 200px);
|
||||
position: absolute;
|
||||
|
||||
div {
|
||||
@include shadow-float;
|
||||
|
||||
border: 1px solid;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
margin-bottom: .5em;
|
||||
min-height: 3em;
|
||||
opacity: 1;
|
||||
padding: 1em;
|
||||
text-align: center;
|
||||
transition: opacity .5s linear;
|
||||
width: 400px;
|
||||
|
||||
&.clicked {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: $success;
|
||||
border-color: darken($success, 30%);
|
||||
color: darken($success, 60%);
|
||||
}
|
||||
|
||||
&.error {
|
||||
background-color: $error;
|
||||
border-color: darken($error, 10%);
|
||||
color: darken($error, 60%);
|
||||
}
|
||||
|
||||
&.warn {
|
||||
background-color: $warn;
|
||||
border-color: darken($warn, 30%);
|
||||
color: darken($warn, 57%);
|
||||
}
|
||||
|
||||
&.info {
|
||||
background-color: $info;
|
||||
border-color: darken($info, 30%);
|
||||
color: darken($info, 50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,4 +19,5 @@
|
||||
@import 'settings';
|
||||
@import 'dashboard';
|
||||
@import 'icons';
|
||||
@import 'notifications';
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
'@angular/common',
|
||||
'@angular/compiler',
|
||||
'@angular/core',
|
||||
'@angular/forms',
|
||||
'@angular/http',
|
||||
'@angular/platform-browser',
|
||||
'@angular/platform-browser-dynamic',
|
||||
|
Reference in New Issue
Block a user