add customer angular gui skeleton
@ -28,9 +28,11 @@
|
|||||||
"src/assets"
|
"src/assets"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
|
"./node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||||
"src/styles.scss"
|
"src/styles.scss"
|
||||||
],
|
],
|
||||||
"scripts": []
|
"scripts": [
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
|
|||||||
18
customer/app/package-lock.json
generated
@ -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": {
|
"@ngtools/webpack": {
|
||||||
"version": "8.0.6",
|
"version": "8.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.0.6.tgz",
|
||||||
@ -1533,6 +1525,11 @@
|
|||||||
"multicast-dns-service-types": "^1.1.0"
|
"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": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
@ -5991,6 +5988,11 @@
|
|||||||
"integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
|
"integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
|
||||||
"dev": true
|
"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": {
|
"nice-try": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
||||||
|
|||||||
@ -19,7 +19,8 @@
|
|||||||
"@angular/platform-browser": "~8.0.3",
|
"@angular/platform-browser": "~8.0.3",
|
||||||
"@angular/platform-browser-dynamic": "~8.0.3",
|
"@angular/platform-browser-dynamic": "~8.0.3",
|
||||||
"@angular/router": "~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",
|
"rxjs": "~6.4.0",
|
||||||
"tslib": "^1.9.0",
|
"tslib": "^1.9.0",
|
||||||
"zone.js": "~0.9.1"
|
"zone.js": "~0.9.1"
|
||||||
|
|||||||
28
customer/app/src/app/_guards/auth.guard.ts
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1
customer/app/src/app/_guards/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './auth.guard';
|
||||||
36
customer/app/src/app/_helpers/error.interceptor.ts
Normal 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);
|
||||||
|
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
110
customer/app/src/app/_helpers/fake-backend.ts
Normal 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
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
3
customer/app/src/app/_helpers/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './error.interceptor';
|
||||||
|
export * from './fake-backend';
|
||||||
|
export * from './jwt.interceptor';
|
||||||
31
customer/app/src/app/_helpers/jwt.interceptor.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,19 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
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({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes)],
|
imports: [RouterModule.forRoot(routes)],
|
||||||
|
|||||||
@ -1,21 +1,3 @@
|
|||||||
<!--The content below is only a placeholder and can be replaced.-->
|
<fit-navigation></fit-navigation>
|
||||||
<div style="text-align:center">
|
<!--<fit-slides></fit-slides>-->
|
||||||
<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>
|
|
||||||
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|||||||
@ -3,18 +3,46 @@ import { NgModule } from '@angular/core';
|
|||||||
|
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from './app.component';
|
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({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent
|
AppComponent,
|
||||||
|
FitNavigationComponent,
|
||||||
|
FitSlidesComponent,
|
||||||
|
HomeComponent,
|
||||||
|
LoginComponent,
|
||||||
|
ProfileComponent,
|
||||||
|
EventsComponent,
|
||||||
|
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
AppRoutingModule,
|
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]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule { }
|
||||||
|
|||||||
@ -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>
|
||||||
@ -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;
|
||||||
|
}
|
||||||
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -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[];
|
||||||
|
}
|
||||||
@ -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>
|
||||||
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1
customer/app/src/app/pages/events/events.component.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<p>events works!</p>
|
||||||
25
customer/app/src/app/pages/events/events.component.spec.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
15
customer/app/src/app/pages/events/events.component.ts
Normal 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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
1
customer/app/src/app/pages/home/home.component.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<p>home works!</p>
|
||||||
0
customer/app/src/app/pages/home/home.component.scss
Normal file
25
customer/app/src/app/pages/home/home.component.spec.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
15
customer/app/src/app/pages/home/home.component.ts
Normal 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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
28
customer/app/src/app/pages/login/login.component.html
Normal 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>
|
||||||
8
customer/app/src/app/pages/login/login.component.scss
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//.btn-primary{
|
||||||
|
// background-color: #e5ce48;
|
||||||
|
// color: orangered;
|
||||||
|
// border: 1px solid orangered;
|
||||||
|
//}
|
||||||
|
//.btn-primary:hover{
|
||||||
|
// background-color: orange;
|
||||||
|
//}
|
||||||
25
customer/app/src/app/pages/login/login.component.spec.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
72
customer/app/src/app/pages/login/login.component.ts
Normal 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');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
<p>profile works!</p>
|
||||||
25
customer/app/src/app/pages/profile/profile.component.spec.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
15
customer/app/src/app/pages/profile/profile.component.ts
Normal 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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
12
customer/app/src/app/services/authentication.service.spec.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
43
customer/app/src/app/services/authentication.service.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
customer/app/src/app/services/endpoints.ts
Normal 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`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
20
customer/app/src/app/services/login.service.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class LoginService {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public login(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public logout(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
BIN
customer/app/src/assets/images/about.jpg
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
customer/app/src/assets/images/bg_1.jpg
Normal file
|
After Width: | Height: | Size: 455 KiB |
BIN
customer/app/src/assets/images/bg_2.jpg
Normal file
|
After Width: | Height: | Size: 203 KiB |
BIN
customer/app/src/assets/images/bg_3.jpg
Normal file
|
After Width: | Height: | Size: 316 KiB |
BIN
customer/app/src/assets/images/bg_4.jpg
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
customer/app/src/assets/images/bg_4_1.jpg
Normal file
|
After Width: | Height: | Size: 618 KiB |
BIN
customer/app/src/assets/images/bg_5.jpg
Normal file
|
After Width: | Height: | Size: 221 KiB |
BIN
customer/app/src/assets/images/gallery-1.jpg
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
customer/app/src/assets/images/gallery-2.jpg
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
customer/app/src/assets/images/gallery-3.jpg
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
customer/app/src/assets/images/gallery-4.jpg
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
customer/app/src/assets/images/image_1.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
customer/app/src/assets/images/image_2.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
customer/app/src/assets/images/image_3.jpg
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
customer/app/src/assets/images/image_4.jpg
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
customer/app/src/assets/images/image_5.jpg
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
customer/app/src/assets/images/image_6.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
customer/app/src/assets/images/loc.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
customer/app/src/assets/images/person_1.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
customer/app/src/assets/images/person_2.jpg
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
customer/app/src/assets/images/person_3.jpg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
customer/app/src/assets/images/person_4.jpg
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
customer/app/src/assets/images/program-1.jpg
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
customer/app/src/assets/images/program-2.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
customer/app/src/assets/images/program-3.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
customer/app/src/assets/images/program-4.jpg
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
customer/app/src/assets/images/program-5.jpg
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
customer/app/src/assets/images/program-6.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
customer/app/src/assets/images/trainer-1.jpg
Normal file
|
After Width: | Height: | Size: 161 KiB |
BIN
customer/app/src/assets/images/trainer-2.jpg
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
customer/app/src/assets/images/trainer-3.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
customer/app/src/assets/images/trainer-4.jpg
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
customer/app/src/assets/images/trainer-5.jpg
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
customer/app/src/assets/images/trainer-6.jpg
Normal file
|
After Width: | Height: | Size: 148 KiB |
@ -1,13 +1,16 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
<title>Botond Fitness</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>App</title>
|
|
||||||
<base href="/">
|
<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">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
</head>
|
</head>
|
||||||
|
<body>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -1 +1,110 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
/* 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; }
|
||||||
|
|||||||