add customer angular gui skeleton

This commit is contained in:
Roland Schneider 2019-07-08 18:03:47 +02:00 committed by Roland Schneider
parent 5922ce776d
commit 25845cc006
76 changed files with 1044 additions and 38 deletions

View File

@ -28,9 +28,11 @@
"src/assets"
],
"styles": [
"./node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.scss"
],
"scripts": []
"scripts": [
]
},
"configurations": {
"production": {

View File

@ -498,14 +498,6 @@
}
}
},
"@ng-bootstrap/ng-bootstrap": {
"version": "5.0.0-rc.1",
"resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-5.0.0-rc.1.tgz",
"integrity": "sha512-LRbA+qCbsY3rnXQiFbDG6M0fyDOi6n4MadUUrrnI6DyNAsH+uAg2twxkjXhkizUD6+XihpvR6AMsQ91T3Bndaw==",
"requires": {
"tslib": "^1.9.0"
}
},
"@ngtools/webpack": {
"version": "8.0.6",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.0.6.tgz",
@ -1533,6 +1525,11 @@
"multicast-dns-service-types": "^1.1.0"
}
},
"bootstrap": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz",
"integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -5991,6 +5988,11 @@
"integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
"dev": true
},
"ngx-bootstrap": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ngx-bootstrap/-/ngx-bootstrap-5.0.0.tgz",
"integrity": "sha512-5TTFP9s3wfiRychGcdyvpCvvxtxW1Nf2Dqmk2YBzuIhHHLT6gRq1Fsic4lYrtAUwmy0PSLhOY/XW/saYKlrSJw=="
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",

View File

@ -19,7 +19,8 @@
"@angular/platform-browser": "~8.0.3",
"@angular/platform-browser-dynamic": "~8.0.3",
"@angular/router": "~8.0.3",
"@ng-bootstrap/ng-bootstrap": "^5.0.0-rc.1",
"bootstrap": "^4.3.1",
"ngx-bootstrap": "^5.0.0",
"rxjs": "~6.4.0",
"tslib": "^1.9.0",
"zone.js": "~0.9.1"

View File

@ -0,0 +1,28 @@
import { Injectable } from "@angular/core";
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";
/*
The auth guard is used to prevent unauthenticated users from accessing restricted routes.
The auth guard will return:
TRUE: If the user is logged in and is authenticated to access the route
FALSE: If the user is logged out, thus not authenticated to access the route
Here the route access condition is to be logged in (it works on the presence of a valid JWT token)
There can be other conditions too, like role based authentication
*/
@Injectable({providedIn: 'root'})
export class AuthGuard implements CanActivate{
constructor(private router: Router){}
canActivate(router: ActivatedRouteSnapshot, state: RouterStateSnapshot){
// check if the user is logged in
if(localStorage.getItem('currentUser')){
return true;
}
// not logged in so redirect to login page with the return url
this.router.navigate(['/login'], { queryParams: { returnUrl: state.url }});
return false;
}
}

View File

@ -0,0 +1 @@
export * from './auth.guard';

View File

@ -0,0 +1,36 @@
import { Injectable } from "@angular/core";
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from "@angular/common/http";
import { Observable, throwError } from "rxjs";
import { catchError } from "rxjs/operators";
import { AuthenticationService} from "../services/authentication.service";
/*
Http error interceptor works with the calling service and the API's
It intercepts the responses from the API and check for the status codes (if there were any errors).
Error Status 401: Unauthorized Response - the user will be automatically logged out
All other errors are RE-THROWN to be caught by the calling service so an alert can be displayed to the user
*/
@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 on unauthorized response
this.authenticationService.logout();
location.reload(true);
}
const error = err.error.message || err.statusText;
return throwError(error);
})
)
}
}

View File

@ -0,0 +1,110 @@
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';;
/*
FAKE BACKEND
This will serve as a standalone backend with delayed response so that it can imitate a real backend - using
DELAYED OBSERVABLE
1. It will check the user credentails that come from "Authentication Service" during login
2. It will also work as a fake database keeping the user details - The user can requests for the user details only when the requests has valid JWT Token in its request authorization header
API
1. To check credentials - /users/authenticate
2. To give back user details - /users
*/
@Injectable()
export class FakeBackendInterceptor implements HttpInterceptor{
constructor(){}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>{
// test user - (one of the users detais in database)
let testUser = {
id: 1,
username: 'test',
password: 'test',
firstName: 'Test',
lastName: 'User'
}
// wrapping the API's in delayed observable to simulate the Server API Calls
return of(null).pipe(
mergeMap(() => {
// API 1: Authenticate - Will be hit by Authentication Service - STARTS
if(request.url.endsWith('/users/authenticate') && request.method === 'POST'){
//check the credentials entered by the user with the data in database
if(request.body.username === testUser.username && request.body.password === testUser.password){
// if login details are valid, return status 200 with a Fake JWT Token
let body = {
id: testUser.id,
username: testUser.username,
firstName: testUser.firstName,
lastName: testUser.lastName,
token: '0000-fake-jwt-token-0000'
}
return of(new HttpResponse({status: 200, body}))
}
else {
// if the credentials by user doesn't match the data in the db, return Status 400 - Bad Request
return throwError({
error: {
message: 'Username or Password is incorrect.'
}
})
}
}
// API 1: Authenticate - Will be hit by Authentication Service - ENDS
// SECURE API END POINT - will check for valid JWT Token in Request
// API 2: Get all users data (we now have only 1 user - testUser) - STARTS
if(request.url.endsWith('/users') && request.method === 'GET'){
// check for a fake jwt token. If valid JWT token found, return the list of users, else throw error
if(request.headers.get('Authorization') === 'Bearer 0000-fake-jwt-token-0000'){
return of(new HttpResponse({status: 200, body: [testUser]}));
}
else{
// invalid JWT token found in request header
return throwError({
error: {
message: 'Unauthorized'
}
});
}
}
// API 2: Get all users data (we now have only 1 user - testUser) - ENDS
// Pass any other requests left (unhandled
return next.handle(request);
})
)
// call materialize and dematerialize to ensure delay even if an error is thrown
.pipe(materialize())
.pipe(delay(500))
.pipe(dematerialize());
}
}
// creating a PROVIDER
export let fakeBackendProvider = {
// use fake backend in place of Http service for backend-less development
provide: HTTP_INTERCEPTORS,
useClass: FakeBackendInterceptor,
multi: true
};

View File

@ -0,0 +1,3 @@
export * from './error.interceptor';
export * from './fake-backend';
export * from './jwt.interceptor';

View File

@ -0,0 +1,31 @@
import { Injectable } from "@angular/core";
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from "@angular/common/http";
import { Observable } from "rxjs";
/*
The JWT interceptor intercepts the incoming requests from the application/user and adds JWT token to the request's Authorization header, only if the user is logged in.
This JWT token in the request header is required to access the SECURE END API POINTS on the server
*/
@Injectable()
export class JwtInterceptor implements HttpInterceptor{
constructor(){}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>{
// check if the current user is logged in
// if the user making the request is logged in, he will have JWT token in it's local storage, which is set by Authorization Service during login process
let currentUser = JSON.parse(localStorage.getItem('currentUser'));
if(currentUser && currentUser.token){
// clone the incoming request and add JWT token in the cloned request's Authorization Header
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.token}`
}
});
}
// handle any other requests which went unhandled
return next.handle(request);
}
}

View File

@ -1,7 +1,19 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {HomeComponent} from "./pages/home/home.component";
import {LoginComponent} from "./pages/login/login.component";
import {ProfileComponent} from "./pages/profile/profile.component";
import {AuthGuard} from "./_guards";
import {EventsComponent} from "./pages/events/events.component";
const routes: Routes = [];
const routes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: '', redirectTo: '/home' , pathMatch: 'full'},
{ path: 'login', component: LoginComponent },
{ path: 'logout', redirectTo: '/login' , pathMatch: 'full'},
{ path: 'profile', component: ProfileComponent, canActivate: [AuthGuard] },
{ path: 'events', component: EventsComponent, canActivate: [AuthGuard] },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],

View File

@ -1,21 +1,3 @@
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>
Welcome to {{ title }}!
</h1>
<img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<h2>Here are some links to help you start: </h2>
<ul>
<li>
<h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://angular.io/cli">CLI Documentation</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
</li>
</ul>
<fit-navigation></fit-navigation>
<!--<fit-slides></fit-slides>-->
<router-outlet></router-outlet>

View File

@ -3,18 +3,46 @@ import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
import { FitNavigationComponent } from './components/fit-navigation/fit-navigation.component';
import { FitSlidesComponent } from './components/fit-slides/fit-slides.component';
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {CollapseModule} from "ngx-bootstrap";
import { HomeComponent } from './pages/home/home.component';
import { LoginComponent } from './pages/login/login.component';
import { ProfileComponent } from './pages/profile/profile.component';
import { JwtInterceptor, ErrorInterceptor } from './_helpers';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { fakeBackendProvider } from './_helpers';
import {ReactiveFormsModule} from "@angular/forms";
import { EventsComponent } from './pages/events/events.component';
@NgModule({
declarations: [
AppComponent
AppComponent,
FitNavigationComponent,
FitSlidesComponent,
HomeComponent,
LoginComponent,
ProfileComponent,
EventsComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
NgbModule
ReactiveFormsModule,
BrowserAnimationsModule,
CollapseModule.forRoot(),
HttpClientModule
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true },
// provider used to create fake backend
fakeBackendProvider
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@ -0,0 +1,19 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark fit-navbar " [class.fit-navbar-absolute]="isAbsolute()">
<div class="container">
<a class="navbar-brand" [routerLink]="'/'" >Botond<small>Fitness</small></a>
<button class="navbar-toggler" type="button" (click)="isCollapsed = !isCollapsed" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" [collapse]="isCollapsed" >
<ul class="navbar-nav ml-auto">
<ng-container *ngFor="let item of items" >
<li *ngIf="isVisible(item)" class="nav-item " [routerLinkActive]="'active'">
<a class="nav-link" [routerLink]="item.route" >{{item.label}}</a>
</li>
</ng-container>
</ul>
</div>
</div>
</nav>

View File

@ -0,0 +1,122 @@
.fit-navbar {
background: transparent !important;
}
.fit-navbar-absolute {
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 3;
}
@media (max-width: 991.98px) {
.fit-navbar {
background: #000 !important;
position: relative;
top: 0;
}
}
.fit-navbar .navbar-brand {
color: #fff;
}
.fit-navbar .navbar-nav > .nav-item > .nav-link {
font-size: 13px;
padding: .7rem 0;
color: rgba(255, 255, 255, 0.9);
font-weight: 400;
position: relative;
text-transform: uppercase;
letter-spacing: 2px;
opacity: 1 !important;
}
@media (min-width: 768px) {
.fit-navbar .navbar-nav > .nav-item > .nav-link {
padding: 1.5rem 20px;
color: rgba(255, 255, 255, 0.9);
}
}
.fit-navbar .navbar-nav > .nav-item > .nav-link:hover, .fit-navbar .navbar-nav > .nav-item > .nav-link:focus {
color: #e5ce48;
}
.fit-navbar .navbar-nav > .nav-item.active > a {
color: #e5ce48;
}
.fit-navbar .navbar-nav > .nav-item.active > a:after {
opacity: 1;
background: #e5ce48;
}
.fit-navbar .navbar-nav > .nav-item > .nav-link:hover:after, .fit-navbar .navbar-nav > .nav-item > .nav-link:focus:after {
opacity: 1;
}
.fit-navbar .navbar-nav > .nav-item .dropdown-menu {
border: none;
background: #000;
border-radius: 0;
-webkit-box-shadow: 0px 10px 34px -20px rgba(0, 0, 0, 0.41);
-moz-box-shadow: 0px 10px 34px -20px rgba(0, 0, 0, 0.41);
box-shadow: 0px 10px 34px -20px rgba(0, 0, 0, 0.41);
}
.fit-navbar .navbar-nav > .nav-item .dropdown-menu .dropdown-item {
font-size: 13px;
color: rgba(255, 255, 255, 0.5);
background: #0d0d0d;
}
.fit-navbar .navbar-nav > .nav-item .dropdown-menu .dropdown-item:hover, .fit-navbar .navbar-nav > .nav-item .dropdown-menu .dropdown-item:focus {
color: #fff;
}
.fit-navbar .navbar-nav > .nav-item.active > a {
color: #e5ce48;
}
.fit-navbar .navbar-nav > .nav-item.active > a:after {
opacity: 1;
background: #e5ce48;
}
.fit-navbar .navbar-toggler {
border: none;
color: rgba(255, 255, 255, 0.8) !important;
cursor: pointer;
padding-right: 0;
text-transform: uppercase;
font-size: 16px;
letter-spacing: .1em;
}
.fit-navbar .navbar-toggler:hover, .ftco-navbar-light .navbar-toggler:focus {
outline: none !important;
}
.navbar-brand {
font-size: 22px;
line-height: 1;
text-transform: uppercase;
display: block;
font-weight: 900;
}
.navbar-brand small {
text-transform: uppercase;
font-size: 12px;
display: block;
color: #fff;
letter-spacing: 8px;
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FitNavigationComponent } from './fit-navigation.component';
describe('FitNavigationComponent', () => {
let component: FitNavigationComponent;
let fixture: ComponentFixture<FitNavigationComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FitNavigationComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FitNavigationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,81 @@
import { Component, OnInit } from '@angular/core';
import {Router} from "@angular/router";
import {AuthenticationService} from "../../services/authentication.service";
@Component({
selector: 'fit-navigation',
templateUrl: './fit-navigation.component.html',
styleUrls: ['./fit-navigation.component.scss']
})
export class FitNavigationComponent implements OnInit {
isCollapsed = true;
items: MenuItem[] = [
{
route: '/home',
label: 'Home',
roles: ['*']
},
{
route: '/profile',
label: 'Profil',
roles: ['@']
},
{
route: '/events',
label: 'Események',
roles: ['@']
},
{
route: '/login',
label: 'Bejelentkezés',
roles: ['!']
},
{
route: '/logout',
label: 'Kijelentkezés',
roles: ['@']
}
];
constructor(private router: Router , private authenticationService: AuthenticationService) { }
ngOnInit() {
}
/**
* Return true, if content css position must be absolute .
* If content css is absolute, it means, that the content occupies the whole page.
* If not absolute, it means, that the content starts after the menubar.
*/
isAbsolute( ){
return '/home' == this.router.url;
}
isVisible(item: MenuItem){
if ( item.roles && item.roles.length ){
let firstRole = item.roles[0];
if ( firstRole == '!'){
return !this.authenticationService.user.value;
}else if ( firstRole == '*'){
return true;
}else if ( firstRole == '@'){
return this.authenticationService.user.value;
}
}
return true;
}
}
export interface MenuItem {
route: string;
label: string;
/**
* ['*'] -> guests
* ['!'] -> only guests
* ['@'] -> authenticated
*/
roles: string[];
}

View File

@ -0,0 +1,31 @@
<section class="home-slider owl-carousel">
<div class="slider-item" style="background-image: url(images/bg_1.jpg);">
<div class="overlay"></div>
<div class="container">
<div class="row slider-text align-items-center" data-scrollax-parent="true">
<div class="col-md-5 col-sm-12 ftco-animate">
<h1 class="mb-4">We are the BodyFit Gym</h1>
<p class="mb-4 mb-md-5">A small river named Duden flows by their place and supplies it with the necessary regelialia.</p>
<p><a href="#" class="btn btn-primary p-3 px-xl-4 py-xl-3">Get Started Now</a></p>
</div>
</div>
</div>
</div>
<div class="slider-item" style="background-image: url(images/bg_2.jpg);">
<div class="overlay"></div>
<div class="container">
<div class="row slider-text align-items-center" data-scrollax-parent="true">
<div class="col-md-5 col-sm-12 ftco-animate">
<h1 class="mb-4">Challenge Yourself</h1>
<p class="mb-4 mb-md-5">A small river named Duden flows by their place and supplies it with the necessary regelialia.</p>
<p><a href="#" class="btn btn-primary p-3 px-xl-4 py-xl-3">Get Started Now</a></p>
</div>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FitSlidesComponent } from './fit-slides.component';
describe('FitSlidesComponent', () => {
let component: FitSlidesComponent;
let fixture: ComponentFixture<FitSlidesComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FitSlidesComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FitSlidesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'fit-slides',
templateUrl: './fit-slides.component.html',
styleUrls: ['./fit-slides.component.scss']
})
export class FitSlidesComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1 @@
<p>events works!</p>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { EventsComponent } from './events.component';
describe('EventsComponent', () => {
let component: EventsComponent;
let fixture: ComponentFixture<EventsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ EventsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EventsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-events',
templateUrl: './events.component.html',
styleUrls: ['./events.component.scss']
})
export class EventsComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1 @@
<p>home works!</p>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HomeComponent } from './home.component';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HomeComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1,28 @@
<div class="container fit-content">
<div class="row">
<div class="col-lg-6 col-sm-12 offset-lg-3 pt-2 pb-3 mt-lg-5 "
style=" background-color: #0a0a0b; border: 1px solid gray; ">
<form class="login-form" [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="inputUsername">Felhasználónév</label>
<input type="text" formControlName="username" class="form-control" id="inputUsername"
placeholder="E-mail cím megadása">
<div *ngIf="submitted && username.errors" class="text-danger">
<div *ngIf="username.errors.required">Felhasználónév megadása kötelező</div>
</div>
</div>
<div class="form-group">
<label for="inputPassword1">Jelszó</label>
<input type="password" formControlName="password" class="form-control" id="inputPassword1"
placeholder="Jelszó">
<div *ngIf="submitted && f.password.errors" class="text-danger">
<div *ngIf="f.password.errors.required">Jelszó megadása kötelezú</div>
</div>
</div>
<button [disabled]="loading" class="btn btn-primary ">Bejelentkezés</button>
<div *ngIf="error" class="alert alert-danger mt-2">{{error}}</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,8 @@
//.btn-primary{
// background-color: #e5ce48;
// color: orangered;
// border: 1px solid orangered;
//}
//.btn-primary:hover{
// background-color: orange;
//}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LoginComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,72 @@
import { Component, OnInit } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
import {ActivatedRoute, Router} from "@angular/router";
import {AuthenticationService} from "../../services/authentication.service";
import { first } from 'rxjs/operators';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
loginForm: FormGroup;
submitted = false;
loading = false;
returnUrl: string;
error = '';
constructor(private formBuilder: FormBuilder,
private route: ActivatedRoute,
private router: Router,
private authenticationService: AuthenticationService) { }
ngOnInit() {
this.loginForm = this.formBuilder.group({
username: ['', [Validators.required]],
password: ['', Validators.required]
});
// logout the person when he opens the app for the first time
this.authenticationService.logout();
// 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;
}
// on submit
onSubmit(){
this.submitted = true;
// stop 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.error = error;
this.loading = false;
}
)
}
get username(){
return this.loginForm.get('username');
}
}

View File

@ -0,0 +1 @@
<p>profile works!</p>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ProfileComponent } from './profile.component';
describe('ProfileComponent', () => {
let component: ProfileComponent;
let fixture: ComponentFixture<ProfileComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ProfileComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProfileComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss']
})
export class ProfileComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { AuthenticationService } from './authentication.service';
describe('AuthenticationService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: AuthenticationService = TestBed.get(AuthenticationService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,43 @@
import { Injectable } from '@angular/core';
import { HttpClient } from "@angular/common/http";
import { map } from 'rxjs/operators';
import {Endpoints} from "./endpoints";
import {BehaviorSubject} from "rxjs";
@Injectable({
providedIn: 'root'
})
export class AuthenticationService {
private _user: BehaviorSubject<any> = new BehaviorSubject(null);
constructor(private http: HttpClient){}
// login
login(username: string, password:string){
return this.http.post<any>(Endpoints.POST_USERS_AUTHENTICATE(), {username,password})
.pipe(
// the backend service sends an instance of the user
// user: any (because .post<any>)
map(user => {
// login successful if the response has jwt token
if(user && user.token){
// store user details and jwt token in the local storage to keep the user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify(user));
this.user.next(user);
}
return user;
})
);
}
get user() {
return this._user;
}
// logout
logout(){
// remove user from local storage
localStorage.removeItem('currentUser');
this.user.next(null);
}
}

View File

@ -0,0 +1,9 @@
export class Endpoints {
private static contextPath = "/api";
private static baseUrl: string = Endpoints.contextPath + "/rest";
public static POST_USERS_AUTHENTICATE(){
return `${this.baseUrl}/users/authenticate`;
}
}

View File

@ -0,0 +1,20 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class LoginService {
constructor() {
}
public login(){
}
public logout(){
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

View File

@ -1,13 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<title>Botond Fitness</title>
<meta charset="utf-8">
<title>App</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- <link href="https://fonts.googleapis.com/css?family=Poppins:300,400,500,600,700,800" rel="stylesheet">-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<body>
<app-root></app-root>
</body>

View File

@ -1 +1,110 @@
/* You can add global styles to this file, and also import other style files */
body{
font-family: "Poppins", Arial, sans-serif;
background: #151111;
font-size: 15px;
line-height: 1.8;
font-weight: 300;
color: gray;
background: url("/assets/images/bg_4.jpg") no-repeat fixed;
background-size: cover;
}
.fit-content{
.fit-navbar{
position: relative;
}
}
.btn {
cursor: pointer;
-webkit-border-radius: 0;
-moz-border-radius: 0;
-ms-border-radius: 0;
border-radius: 0;
-webkit-box-shadow: none !important;
box-shadow: none !important;
font-size: 13px; }
.btn:hover, .btn:active, .btn:focus {
outline: none; }
.btn.btn-primary {
background: #e5ce48;
border: 1px solid #e5ce48;
color: #000; }
.btn.btn-primary:hover {
border: 1px solid #e5ce48;
background: transparent;
color: #e5ce48; }
.btn.btn-primary.btn-outline-primary {
border: 1px solid #e5ce48;
background: transparent;
color: #e5ce48; }
.btn.btn-primary.btn-outline-primary:hover {
border: 1px solid transparent;
background: #e5ce48;
color: #fff; }
.btn.btn-white {
background: #fff;
border: 1px solid #fff;
color: #000; }
.btn.btn-white:hover {
border: 1px solid #000;
background: #000;
color: #fff; }
.btn.btn-white.btn-outline-white {
border-color: rgba(255, 255, 255, 0.8);
background: none;
border-width: 1px;
color: #fff; }
.btn.btn-white.btn-outline-white:hover, .btn.btn-white.btn-outline-white:focus, .btn.btn-white.btn-outline-white:active {
background: #e5ce48;
border-color: #e5ce48;
color: #000; }
.btn.btn-outline-black {
border-color: black;
background: none;
border-width: 1px;
color: #000; }
.btn.btn-outline-black:hover, .btn.btn-outline-black:focus, .btn.btn-outline-black:active {
background: #000;
border-color: #000;
color: #fff; }
.login-form .form-group {
position: relative; }
.login-form .form-control {
border: transparent !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.08) !important;
height: 58px !important;
padding-left: 0;
padding-right: 0;
background: transparent !important;
color: rgba(255, 255, 255, 0.9) !important;
font-size: 13px;
border-radius: 0px;
-webkit-box-shadow: none !important;
box-shadow: none !important; }
.login-form .form-control::-webkit-input-placeholder {
/* Chrome/Opera/Safari */
color: rgba(255, 255, 255, 0.9); }
.login-form .form-control::-moz-placeholder {
/* Firefox 19+ */
color: rgba(255, 255, 255, 0.9); }
.login-form .form-control:-ms-input-placeholder {
/* IE 10+ */
color: rgba(255, 255, 255, 0.9); }
.login-form .form-control:-moz-placeholder {
/* Firefox 18- */
color: rgba(255, 255, 255, 0.9); }
.login-form .form-control:focus, .login-form .form-control:active {
border-color: #e5ce48 !important; }
.login-form textarea.form-control {
height: inherit !important; }