chal-2
This commit is contained in:
parent
126ce61670
commit
e4a91de738
13
front/.editorconfig
Normal file
13
front/.editorconfig
Normal file
@ -0,0 +1,13 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
46
front/.gitignore
vendored
Normal file
46
front/.gitignore
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events*.json
|
||||
speed-measure-plugin*.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
7934
front/package-lock.json
generated
Normal file
7934
front/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
front/package.json
Normal file
40
front/package.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "angular-8-registration-login-example",
|
||||
"version": "1.0.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cornflourblue/angular-8-registration-login-example.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --mode production",
|
||||
"start": "webpack-dev-server --mode development --open"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular/common": "^8.0.0",
|
||||
"@angular/compiler": "^8.0.0",
|
||||
"@angular/core": "^8.0.0",
|
||||
"@angular/forms": "^8.0.0",
|
||||
"@angular/platform-browser": "^8.0.0",
|
||||
"@angular/platform-browser-dynamic": "^8.0.0",
|
||||
"@angular/router": "^8.0.0",
|
||||
"core-js": "^3.1.3",
|
||||
"rxjs": "^6.3.3",
|
||||
"zone.js": "^0.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.0.7",
|
||||
"angular2-template-loader": "^0.6.2",
|
||||
"css-loader": "^2.1.1",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"less": "^3.0.4",
|
||||
"less-loader": "^5.0.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"ts-loader": "^6.0.1",
|
||||
"typescript": "^3.1.3",
|
||||
"webpack": "^4.32.2",
|
||||
"webpack-cli": "^3.1.2",
|
||||
"webpack-dev-server": "^3.7.0"
|
||||
}
|
||||
}
|
1
front/src/app/_components/alert.component.html
Normal file
1
front/src/app/_components/alert.component.html
Normal file
@ -0,0 +1 @@
|
||||
<div *ngIf="message" [ngClass]="message.cssClass">{{message.text}}</div>
|
32
front/src/app/_components/alert.component.ts
Normal file
32
front/src/app/_components/alert.component.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { AlertService } from '@/_services';
|
||||
|
||||
@Component({ selector: 'alert', templateUrl: 'alert.component.html' })
|
||||
export class AlertComponent implements OnInit, OnDestroy {
|
||||
private subscription: Subscription;
|
||||
message: any;
|
||||
|
||||
constructor(private alertService: AlertService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.subscription = this.alertService.getAlert()
|
||||
.subscribe(message => {
|
||||
switch (message && message.type) {
|
||||
case 'success':
|
||||
message.cssClass = 'alert alert-success';
|
||||
break;
|
||||
case 'error':
|
||||
message.cssClass = 'alert alert-danger';
|
||||
break;
|
||||
}
|
||||
|
||||
this.message = message;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
1
front/src/app/_components/index.ts
Normal file
1
front/src/app/_components/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './alert.component';
|
4
front/src/app/_content/app.less
Normal file
4
front/src/app/_content/app.less
Normal file
@ -0,0 +1,4 @@
|
||||
// global application styles
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
24
front/src/app/_helpers/auth.guard.ts
Normal file
24
front/src/app/_helpers/auth.guard.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||
|
||||
import { AuthenticationService } from '@/_services';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(
|
||||
private router: Router,
|
||||
private authenticationService: AuthenticationService
|
||||
) {}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
|
||||
const currentUser = this.authenticationService.currentUserValue;
|
||||
if (currentUser) {
|
||||
// authorised so return true
|
||||
return true;
|
||||
}
|
||||
|
||||
// not logged in so redirect to login page with the return url
|
||||
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
|
||||
return false;
|
||||
}
|
||||
}
|
24
front/src/app/_helpers/error.interceptor.ts
Normal file
24
front/src/app/_helpers/error.interceptor.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
|
||||
import { AuthenticationService } from '@/_services';
|
||||
|
||||
@Injectable()
|
||||
export class ErrorInterceptor implements HttpInterceptor {
|
||||
constructor(private authenticationService: AuthenticationService) {}
|
||||
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
return next.handle(request).pipe(catchError(err => {
|
||||
if (err.status === 401) {
|
||||
// auto logout if 401 response returned from api
|
||||
this.authenticationService.logout();
|
||||
location.reload(true);
|
||||
}
|
||||
|
||||
const error = err.error.message || err.statusText;
|
||||
return throwError(error);
|
||||
}))
|
||||
}
|
||||
}
|
109
front/src/app/_helpers/fake-backend.ts
Normal file
109
front/src/app/_helpers/fake-backend.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { Observable, of, throwError } from 'rxjs';
|
||||
import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators';
|
||||
|
||||
// array in local storage for registered users
|
||||
let users = JSON.parse(localStorage.getItem('users')) || [];
|
||||
|
||||
@Injectable()
|
||||
export class FakeBackendInterceptor implements HttpInterceptor {
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
const { url, method, headers, body } = request;
|
||||
|
||||
// wrap in delayed observable to simulate server api call
|
||||
return of(null)
|
||||
.pipe(mergeMap(handleRoute))
|
||||
.pipe(materialize()) // call materialize and dematerialize to ensure delay even if an error is thrown (https://github.com/Reactive-Extensions/RxJS/issues/648)
|
||||
.pipe(delay(500))
|
||||
.pipe(dematerialize());
|
||||
|
||||
function handleRoute() {
|
||||
switch (true) {
|
||||
case url.endsWith('/users/authenticate') && method === 'POST':
|
||||
return authenticate();
|
||||
case url.endsWith('/users/register') && method === 'POST':
|
||||
return register();
|
||||
case url.endsWith('/users') && method === 'GET':
|
||||
return getUsers();
|
||||
case url.match(/\/users\/\d+$/) && method === 'DELETE':
|
||||
return deleteUser();
|
||||
default:
|
||||
// pass through any requests not handled above
|
||||
return next.handle(request);
|
||||
}
|
||||
}
|
||||
|
||||
// route functions
|
||||
|
||||
function authenticate() {
|
||||
const { username, password } = body;
|
||||
const user = users.find(x => x.username === username && x.password === password);
|
||||
if (!user) return error('Username or password is incorrect');
|
||||
return ok({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
token: 'fake-jwt-token'
|
||||
})
|
||||
}
|
||||
|
||||
function register() {
|
||||
const user = body
|
||||
|
||||
if (users.find(x => x.username === user.username)) {
|
||||
return error('Username "' + user.username + '" is already taken')
|
||||
}
|
||||
|
||||
user.id = users.length ? Math.max(...users.map(x => x.id)) + 1 : 1;
|
||||
users.push(user);
|
||||
localStorage.setItem('users', JSON.stringify(users));
|
||||
|
||||
return ok();
|
||||
}
|
||||
|
||||
function getUsers() {
|
||||
if (!isLoggedIn()) return unauthorized();
|
||||
return ok(users);
|
||||
}
|
||||
|
||||
function deleteUser() {
|
||||
if (!isLoggedIn()) return unauthorized();
|
||||
|
||||
users = users.filter(x => x.id !== idFromUrl());
|
||||
localStorage.setItem('users', JSON.stringify(users));
|
||||
return ok();
|
||||
}
|
||||
|
||||
// helper functions
|
||||
|
||||
function ok(body?) {
|
||||
return of(new HttpResponse({ status: 200, body }))
|
||||
}
|
||||
|
||||
function error(message) {
|
||||
return throwError({ error: { message } });
|
||||
}
|
||||
|
||||
function unauthorized() {
|
||||
return throwError({ status: 401, error: { message: 'Unauthorised' } });
|
||||
}
|
||||
|
||||
function isLoggedIn() {
|
||||
return headers.get('Authorization') === 'Bearer fake-jwt-token';
|
||||
}
|
||||
|
||||
function idFromUrl() {
|
||||
const urlParts = url.split('/');
|
||||
return parseInt(urlParts[urlParts.length - 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const fakeBackendProvider = {
|
||||
// use fake backend in place of Http service for backend-less development
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: FakeBackendInterceptor,
|
||||
multi: true
|
||||
};
|
4
front/src/app/_helpers/index.ts
Normal file
4
front/src/app/_helpers/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './auth.guard';
|
||||
export * from './error.interceptor';
|
||||
export * from './jwt.interceptor';
|
||||
export * from './fake-backend';
|
24
front/src/app/_helpers/jwt.interceptor.ts
Normal file
24
front/src/app/_helpers/jwt.interceptor.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { AuthenticationService } from '@/_services';
|
||||
|
||||
@Injectable()
|
||||
export class JwtInterceptor implements HttpInterceptor {
|
||||
constructor(private authenticationService: AuthenticationService) {}
|
||||
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
// add authorization header with jwt token if available
|
||||
let currentUser = this.authenticationService.currentUserValue;
|
||||
if (currentUser && currentUser.token) {
|
||||
request = request.clone({
|
||||
setHeaders: {
|
||||
Authorization: `Bearer ${currentUser.token}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return next.handle(request);
|
||||
}
|
||||
}
|
1
front/src/app/_models/index.ts
Normal file
1
front/src/app/_models/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './user';
|
8
front/src/app/_models/user.ts
Normal file
8
front/src/app/_models/user.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export class User {
|
||||
id: number;
|
||||
username: string;
|
||||
password: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
token: string;
|
||||
}
|
43
front/src/app/_services/alert.service.ts
Normal file
43
front/src/app/_services/alert.service.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router, NavigationStart } from '@angular/router';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AlertService {
|
||||
private subject = new Subject<any>();
|
||||
private keepAfterRouteChange = false;
|
||||
|
||||
constructor(private router: Router) {
|
||||
// clear alert messages on route change unless 'keepAfterRouteChange' flag is true
|
||||
this.router.events.subscribe(event => {
|
||||
if (event instanceof NavigationStart) {
|
||||
if (this.keepAfterRouteChange) {
|
||||
// only keep for a single route change
|
||||
this.keepAfterRouteChange = false;
|
||||
} else {
|
||||
// clear alert message
|
||||
this.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getAlert(): Observable<any> {
|
||||
return this.subject.asObservable();
|
||||
}
|
||||
|
||||
success(message: string, keepAfterRouteChange = false) {
|
||||
this.keepAfterRouteChange = keepAfterRouteChange;
|
||||
this.subject.next({ type: 'success', text: message });
|
||||
}
|
||||
|
||||
error(message: string, keepAfterRouteChange = false) {
|
||||
this.keepAfterRouteChange = keepAfterRouteChange;
|
||||
this.subject.next({ type: 'error', text: message });
|
||||
}
|
||||
|
||||
clear() {
|
||||
// clear by calling subject.next() without parameters
|
||||
this.subject.next();
|
||||
}
|
||||
}
|
37
front/src/app/_services/authentication.service.ts
Normal file
37
front/src/app/_services/authentication.service.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { User } from '@/_models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AuthenticationService {
|
||||
private currentUserSubject: BehaviorSubject<User>;
|
||||
public currentUser: Observable<User>;
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
this.currentUserSubject = new BehaviorSubject<User>(JSON.parse(localStorage.getItem('currentUser')));
|
||||
this.currentUser = this.currentUserSubject.asObservable();
|
||||
}
|
||||
|
||||
public get currentUserValue(): User {
|
||||
return this.currentUserSubject.value;
|
||||
}
|
||||
|
||||
login(username, password) {
|
||||
return this.http.post<any>(`${config.apiUrl}/users/authenticate`, { username, password })
|
||||
.pipe(map(user => {
|
||||
// store user details and jwt token in local storage to keep user logged in between page refreshes
|
||||
localStorage.setItem('currentUser', JSON.stringify(user));
|
||||
this.currentUserSubject.next(user);
|
||||
return user;
|
||||
}));
|
||||
}
|
||||
|
||||
logout() {
|
||||
// remove user from local storage and set current user to null
|
||||
localStorage.removeItem('currentUser');
|
||||
this.currentUserSubject.next(null);
|
||||
}
|
||||
}
|
3
front/src/app/_services/index.ts
Normal file
3
front/src/app/_services/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './alert.service';
|
||||
export * from './authentication.service';
|
||||
export * from './user.service';
|
21
front/src/app/_services/user.service.ts
Normal file
21
front/src/app/_services/user.service.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { User } from '@/_models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class UserService {
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
getAll() {
|
||||
return this.http.get<User[]>(`${config.apiUrl}/users`);
|
||||
}
|
||||
|
||||
register(user: User) {
|
||||
return this.http.post(`${config.apiUrl}/users/register`, user);
|
||||
}
|
||||
|
||||
delete(id: number) {
|
||||
return this.http.delete(`${config.apiUrl}/users/${id}`);
|
||||
}
|
||||
}
|
20
front/src/app/app.component.html
Normal file
20
front/src/app/app.component.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!-- nav -->
|
||||
<nav class="navbar navbar-expand navbar-dark bg-dark" *ngIf="currentUser">
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-item nav-link" routerLink="/">Challenge accepted</a>
|
||||
<a class="nav-item nav-link" (click)="logout()">Logout</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- main app container -->
|
||||
<div class="jumbotron">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-sm-6 offset-sm-3">
|
||||
<alert></alert>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
24
front/src/app/app.component.ts
Normal file
24
front/src/app/app.component.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { AuthenticationService } from './_services';
|
||||
import { User } from './_models';
|
||||
|
||||
import './_content/app.less';
|
||||
|
||||
@Component({ selector: 'app', templateUrl: 'app.component.html' })
|
||||
export class AppComponent {
|
||||
currentUser: User;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private authenticationService: AuthenticationService
|
||||
) {
|
||||
this.authenticationService.currentUser.subscribe(x => this.currentUser = x);
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.authenticationService.logout();
|
||||
this.router.navigate(['/login']);
|
||||
}
|
||||
}
|
40
front/src/app/app.module.ts
Normal file
40
front/src/app/app.module.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
|
||||
// used to create fake backend
|
||||
import { fakeBackendProvider } from './_helpers';
|
||||
|
||||
import { appRoutingModule } from './app.routing';
|
||||
import { JwtInterceptor, ErrorInterceptor } from './_helpers';
|
||||
import { AppComponent } from './app.component';
|
||||
import { HomeComponent } from './home';
|
||||
import { LoginComponent } from './login';
|
||||
import { RegisterComponent } from './register';
|
||||
import { AlertComponent } from './_components';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientModule,
|
||||
appRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HomeComponent,
|
||||
LoginComponent,
|
||||
RegisterComponent,
|
||||
AlertComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
|
||||
|
||||
// provider used to create fake backend
|
||||
fakeBackendProvider
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { };
|
17
front/src/app/app.routing.ts
Normal file
17
front/src/app/app.routing.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { HomeComponent } from './home';
|
||||
import { LoginComponent } from './login';
|
||||
import { RegisterComponent } from './register';
|
||||
import { AuthGuard } from './_helpers';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: HomeComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: 'register', component: RegisterComponent },
|
||||
|
||||
// otherwise redirect to home
|
||||
{ path: '**', redirectTo: '' }
|
||||
];
|
||||
|
||||
export const appRoutingModule = RouterModule.forRoot(routes);
|
26
front/src/app/home/home.component.html
Normal file
26
front/src/app/home/home.component.html
Normal file
@ -0,0 +1,26 @@
|
||||
<h2>Hi {{currentUser.firstName}}, Challenge Accepted!</h2>
|
||||
<h3>Obecne wyzwania:</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<a>Dwa litry coli w 20 sekund!</a>
|
||||
<div class="btn-group" role="group" aria-label="..." style="padding: 10px;">
|
||||
<button type="button" class="btn btn-default" style="background-color: blue; color: white;">Obstaw</button>
|
||||
<button type="button" class="btn btn-default" style="background-color: green;">Podejmij</button>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a>Zdobądź numer swojej nauczycielki!</a>
|
||||
<div class="btn-group" role="group" aria-label="..." style="padding: 10px;">
|
||||
<button type="button" class="btn btn-default" style="background-color: blue; color: white;">Obstaw</button>
|
||||
<button type="button" class="btn btn-default" style="background-color: green;">Podejmij</button>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a>Pokaz dupę</a>
|
||||
<div class="btn-group" role="group" aria-label="..." style="padding: 10px;">
|
||||
<button type="button" class="btn btn-default" style="background-color: blue; color: white;">Obstaw</button>
|
||||
<button type="button" class="btn btn-default" style="background-color: green;">Podejmij</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
34
front/src/app/home/home.component.ts
Normal file
34
front/src/app/home/home.component.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { User } from '@/_models';
|
||||
import { UserService, AuthenticationService } from '@/_services';
|
||||
|
||||
@Component({ templateUrl: 'home.component.html' })
|
||||
export class HomeComponent implements OnInit {
|
||||
currentUser: User;
|
||||
users = [];
|
||||
|
||||
constructor(
|
||||
private authenticationService: AuthenticationService,
|
||||
private userService: UserService
|
||||
) {
|
||||
this.currentUser = this.authenticationService.currentUserValue;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadAllUsers();
|
||||
}
|
||||
|
||||
deleteUser(id: number) {
|
||||
this.userService.delete(id)
|
||||
.pipe(first())
|
||||
.subscribe(() => this.loadAllUsers());
|
||||
}
|
||||
|
||||
private loadAllUsers() {
|
||||
this.userService.getAll()
|
||||
.pipe(first())
|
||||
.subscribe(users => this.users = users);
|
||||
}
|
||||
}
|
1
front/src/app/home/index.ts
Normal file
1
front/src/app/home/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './home.component';
|
1
front/src/app/login/index.ts
Normal file
1
front/src/app/login/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './login.component';
|
25
front/src/app/login/login.component.html
Normal file
25
front/src/app/login/login.component.html
Normal file
@ -0,0 +1,25 @@
|
||||
<h1 style="padding: 70px;">Challenge Accepted</h1>
|
||||
<h2>Login</h2>
|
||||
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.username.errors }" />
|
||||
<div *ngIf="submitted && f.username.errors" class="invalid-feedback">
|
||||
<div *ngIf="f.username.errors.required">Username is required</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
|
||||
<div *ngIf="submitted && f.password.errors" class="invalid-feedback">
|
||||
<div *ngIf="f.password.errors.required">Password is required</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button [disabled]="loading" class="btn btn-primary">
|
||||
<span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
|
||||
Login
|
||||
</button>
|
||||
<a routerLink="/register" class="btn btn-link">Register</a>
|
||||
</div>
|
||||
</form>
|
64
front/src/app/login/login.component.ts
Normal file
64
front/src/app/login/login.component.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { AlertService, AuthenticationService } from '@/_services';
|
||||
|
||||
@Component({ templateUrl: 'login.component.html' })
|
||||
export class LoginComponent implements OnInit {
|
||||
loginForm: FormGroup;
|
||||
loading = false;
|
||||
submitted = false;
|
||||
returnUrl: string;
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private authenticationService: AuthenticationService,
|
||||
private alertService: AlertService
|
||||
) {
|
||||
// redirect to home if already logged in
|
||||
if (this.authenticationService.currentUserValue) {
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.loginForm = this.formBuilder.group({
|
||||
username: ['', Validators.required],
|
||||
password: ['', Validators.required]
|
||||
});
|
||||
|
||||
// get return url from route parameters or default to '/'
|
||||
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
|
||||
}
|
||||
|
||||
// convenience getter for easy access to form fields
|
||||
get f() { return this.loginForm.controls; }
|
||||
|
||||
onSubmit() {
|
||||
this.submitted = true;
|
||||
|
||||
// reset alerts on submit
|
||||
this.alertService.clear();
|
||||
|
||||
// stop here if form is invalid
|
||||
if (this.loginForm.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.authenticationService.login(this.f.username.value, this.f.password.value)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
data => {
|
||||
this.router.navigate([this.returnUrl]);
|
||||
},
|
||||
error => {
|
||||
this.alertService.error(error);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
1
front/src/app/register/index.ts
Normal file
1
front/src/app/register/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './register.component';
|
39
front/src/app/register/register.component.html
Normal file
39
front/src/app/register/register.component.html
Normal file
@ -0,0 +1,39 @@
|
||||
<h2>Register</h2>
|
||||
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
|
||||
<div class="form-group">
|
||||
<label for="firstName">First Name</label>
|
||||
<input type="text" formControlName="firstName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.firstName.errors }" />
|
||||
<div *ngIf="submitted && f.firstName.errors" class="invalid-feedback">
|
||||
<div *ngIf="f.firstName.errors.required">First Name is required</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lastName">Last Name</label>
|
||||
<input type="text" formControlName="lastName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.lastName.errors }" />
|
||||
<div *ngIf="submitted && f.lastName.errors" class="invalid-feedback">
|
||||
<div *ngIf="f.lastName.errors.required">Last Name is required</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.username.errors }" />
|
||||
<div *ngIf="submitted && f.username.errors" class="invalid-feedback">
|
||||
<div *ngIf="f.username.errors.required">Username is required</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f.password.errors }" />
|
||||
<div *ngIf="submitted && f.password.errors" class="invalid-feedback">
|
||||
<div *ngIf="f.password.errors.required">Password is required</div>
|
||||
<div *ngIf="f.password.errors.minlength">Password must be at least 6 characters</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button [disabled]="loading" class="btn btn-primary">
|
||||
<span *ngIf="loading" class="spinner-border spinner-border-sm mr-1"></span>
|
||||
Register
|
||||
</button>
|
||||
<a routerLink="/login" class="btn btn-link">Cancel</a>
|
||||
</div>
|
||||
</form>
|
63
front/src/app/register/register.component.ts
Normal file
63
front/src/app/register/register.component.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { AlertService, UserService, AuthenticationService } from '@/_services';
|
||||
|
||||
@Component({ templateUrl: 'register.component.html' })
|
||||
export class RegisterComponent implements OnInit {
|
||||
registerForm: FormGroup;
|
||||
loading = false;
|
||||
submitted = false;
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private router: Router,
|
||||
private authenticationService: AuthenticationService,
|
||||
private userService: UserService,
|
||||
private alertService: AlertService
|
||||
) {
|
||||
// redirect to home if already logged in
|
||||
if (this.authenticationService.currentUserValue) {
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.registerForm = this.formBuilder.group({
|
||||
firstName: ['', Validators.required],
|
||||
lastName: ['', Validators.required],
|
||||
username: ['', Validators.required],
|
||||
password: ['', [Validators.required, Validators.minLength(6)]]
|
||||
});
|
||||
}
|
||||
|
||||
// convenience getter for easy access to form fields
|
||||
get f() { return this.registerForm.controls; }
|
||||
|
||||
onSubmit() {
|
||||
this.submitted = true;
|
||||
|
||||
// reset alerts on submit
|
||||
this.alertService.clear();
|
||||
|
||||
// stop here if form is invalid
|
||||
if (this.registerForm.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.userService.register(this.registerForm.value)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
data => {
|
||||
this.alertService.success('Registration successful', true);
|
||||
this.router.navigate(['/login']);
|
||||
},
|
||||
error => {
|
||||
this.alertService.error(error);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
14
front/src/index.html
Normal file
14
front/src/index.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<base href="/" />
|
||||
<title>Angular 8 User Registration and Login Example</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- bootstrap css -->
|
||||
<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<app>Loading...</app>
|
||||
</body>
|
||||
</html>
|
6
front/src/main.ts
Normal file
6
front/src/main.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import './polyfills';
|
||||
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
2
front/src/polyfills.ts
Normal file
2
front/src/polyfills.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import 'core-js/features/reflect';
|
||||
import 'zone.js/dist/zone';
|
2
front/src/typings.d.ts
vendored
Normal file
2
front/src/typings.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
// so the typescript compiler doesn't complain about the global config object
|
||||
declare var config: any;
|
13
front/tsconfig.json
Normal file
13
front/tsconfig.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "ES5",
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"app/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
59
front/webpack.config.js
Normal file
59
front/webpack.config.js
Normal file
@ -0,0 +1,59 @@
|
||||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: './src/main.ts',
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js'],
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src/app/'),
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: ['ts-loader', 'angular2-template-loader']
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
use: 'html-loader'
|
||||
},
|
||||
{
|
||||
test: /\.less$/,
|
||||
use: ['style-loader', 'css-loader', 'less-loader']
|
||||
},
|
||||
|
||||
// workaround for warning: System.import() is deprecated and will be removed soon. Use import() instead.
|
||||
{
|
||||
test: /[\/\\]@angular[\/\\].+\.js$/,
|
||||
parser: { system: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({ template: './src/index.html' }),
|
||||
new webpack.DefinePlugin({
|
||||
// global app config object
|
||||
config: JSON.stringify({
|
||||
apiUrl: 'http://localhost:4000'
|
||||
})
|
||||
}),
|
||||
|
||||
// workaround for warning: Critical dependency: the request of a dependency is an expression
|
||||
new webpack.ContextReplacementPlugin(
|
||||
/\@angular(\\|\/)core(\\|\/)fesm5/,
|
||||
path.resolve(__dirname, 'src')
|
||||
)
|
||||
],
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
},
|
||||
runtimeChunk: true
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user