From 0f470abfcd1946854dd0b49ce36e5182a4ea6e02 Mon Sep 17 00:00:00 2001 From: F_Vinogradov Date: Wed, 11 Nov 2020 12:00:10 +0300 Subject: [PATCH 1/3] connect to asp.net core backend --- package-lock.json | 15 + package.json | 1 + src/app/app-routing.module.ts | 2 +- src/app/app.config.ts | 31 ++ src/app/app.module.ts | 11 +- src/app/common-services/index.ts | 2 + .../common-services/local-storage.service.ts | 17 + src/app/common-services/navigation.service.ts | 25 + src/app/guards/auth.guard.ts | 27 + src/app/interceptors/error.interceptor.ts | 22 + src/app/interceptors/index.ts | 1 + src/app/interceptors/jwt.interceptor.ts | 35 ++ src/app/interfaces/LoginFormCreds.ts | 4 + src/app/interfaces/User.ts | 6 + src/app/interfaces/index.ts | 1 + src/app/modules/auth/auth.module.ts | 10 +- .../login-form/login-form.component.ts | 22 +- .../auth-page/auth-page.component.html | 24 +- .../auth-page/auth-page.component.ts | 11 +- src/app/modules/auth/services/auth.service.ts | 116 +++- .../product-card/product-card.component.html | 4 +- .../product-card/product-card.component.ts | 8 +- .../product-edit-form.component.html | 19 +- .../product-edit-form.component.scss | 1 + .../product-edit-form.component.ts | 91 ++-- .../management-page.component.html | 231 ++++---- .../management-page.component.scss | 6 + .../management-page.component.ts | 53 +- .../product-create-page.component.html | 4 +- .../product-create-page.component.ts | 15 +- .../product-edit-page.component.html | 3 +- .../product-edit-page.component.ts | 16 +- .../product-page/product-page.component.html | 24 +- .../product-page/product-page.component.ts | 31 +- .../products-page.component.html | 9 +- .../products-page.component.scss | 6 + .../products-page/products-page.component.ts | 15 +- .../modules/e-commerce/e-commerce.module.ts | 10 +- src/app/modules/e-commerce/models/index.ts | 1 - src/app/modules/e-commerce/services/index.ts | 1 - .../e-commerce/services/product.service.ts | 506 +++++++----------- .../account-edit-form.component.ts | 2 +- .../containers/header/header.component.html | 2 +- .../containers/header/header.component.ts | 4 +- src/custom-material-styles.scss | 13 + src/custom-theme.scss | 3 +- 46 files changed, 835 insertions(+), 626 deletions(-) create mode 100644 src/app/app.config.ts create mode 100644 src/app/common-services/index.ts create mode 100644 src/app/common-services/local-storage.service.ts create mode 100644 src/app/common-services/navigation.service.ts create mode 100644 src/app/guards/auth.guard.ts create mode 100644 src/app/interceptors/error.interceptor.ts create mode 100644 src/app/interceptors/index.ts create mode 100644 src/app/interceptors/jwt.interceptor.ts create mode 100644 src/app/interfaces/LoginFormCreds.ts create mode 100644 src/app/interfaces/User.ts create mode 100644 src/app/interfaces/index.ts create mode 100644 src/custom-material-styles.scss diff --git a/package-lock.json b/package-lock.json index 1771509..df41f3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -591,6 +591,21 @@ "integrity": "sha1-NOZY7T2jfyOwogDi2lqJvpK7IJ8=", "dev": true }, + "@auth0/angular-jwt": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@auth0/angular-jwt/-/angular-jwt-5.0.1.tgz", + "integrity": "sha512-djllMh6rthPscEj5n5T9zF223q8t+sDqnUuAYTJjdKoHvMAzYwwi2yP67HbojqjODG4ZLFAcPtRuzGgp+r7nDQ==", + "requires": { + "tslib": "^2.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + } + } + }, "@babel/code-frame": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", diff --git a/package.json b/package.json index dd1b4c5..e89c8ed 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@angular/platform-browser": "~9.1.12", "@angular/platform-browser-dynamic": "~9.1.12", "@angular/router": "~9.1.12", + "@auth0/angular-jwt": "5.0.1", "@fullcalendar/angular": "4.4.5-beta", "@fullcalendar/core": "4.4.2", "@fullcalendar/daygrid": "4.4.2", diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 32445bb..1bf979b 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -3,8 +3,8 @@ import { NgModule } from '@angular/core'; import { DashboardPageComponent } from './modules/dashboard/containers'; import { NotFoundComponent } from './shared/not-found/not-found.component'; -import { AuthGuard } from './modules/auth/guards'; import { LayoutComponent } from './shared/layout/layout.component'; +import { AuthGuard } from './guards/auth.guard'; const routes: Routes = [ { diff --git a/src/app/app.config.ts b/src/app/app.config.ts new file mode 100644 index 0000000..9ce5278 --- /dev/null +++ b/src/app/app.config.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; + +const hostApi = process.env.NODE_ENV === 'development' ? 'http://localhost' : 'https://flatlogic-node-backend.herokuapp.com'; +const portApi = process.env.NODE_ENV === 'development' ? 5001 : ''; +const baseURLApi = `${hostApi}${portApi ? `:${portApi}/api` : ``}`; +const resourceApi = `${hostApi}${portApi ? `:${portApi}/Resources/` : ``}`; +const tokenRoleField = 'http://schemas.microsoft.com/ws/2008/06/identity/claims/role'; +const tokenEmailField = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'; +const tokenSidField = 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid'; + +@Injectable() +export class AppConfig { + config = { + hostApi, + portApi, + baseURLApi, + resourceApi, + tokenEmailField, + tokenRoleField, + tokenSidField, + auth: { + email: 'admin@flatlogic.com', + password: 'password' + }, + }; + + getConfig(): Object { + return this.config; + } +} + diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d4668bc..2f6a9c1 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -20,6 +20,10 @@ import {MatMenuModule} from '@angular/material/menu'; import {MatRadioModule} from '@angular/material/radio'; import {MatSlideToggleModule} from '@angular/material/slide-toggle'; import {FormsModule} from '@angular/forms'; +import { AppConfig } from './app.config'; +import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; +import { JwtInterceptor } from './interceptors'; +import { ErrorInterceptor } from './interceptors/error.interceptor'; @NgModule({ declarations: [ @@ -27,6 +31,7 @@ import {FormsModule} from '@angular/forms'; NotFoundComponent ], imports: [ + HttpClientModule, BrowserModule, SharedModule, CoreModule, @@ -48,7 +53,11 @@ import {FormsModule} from '@angular/forms'; MatSlideToggleModule, FormsModule, ], - providers: [], + providers: [ + { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }, + AppConfig + ], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/common-services/index.ts b/src/app/common-services/index.ts new file mode 100644 index 0000000..f56b9ed --- /dev/null +++ b/src/app/common-services/index.ts @@ -0,0 +1,2 @@ +export * from './navigation.service'; +export * from './local-storage.service'; \ No newline at end of file diff --git a/src/app/common-services/local-storage.service.ts b/src/app/common-services/local-storage.service.ts new file mode 100644 index 0000000..7f0b052 --- /dev/null +++ b/src/app/common-services/local-storage.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class LocalStorageService { + public readonly tokenFieldName: string = "token"; + public readonly userFieldName: string = "user"; + + checkLocalStorageExistence() { + if(window && window.localStorage) { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/src/app/common-services/navigation.service.ts b/src/app/common-services/navigation.service.ts new file mode 100644 index 0000000..caa612a --- /dev/null +++ b/src/app/common-services/navigation.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; +import { NavigationExtras, Router } from '@angular/router'; + +@Injectable({ + providedIn: 'root' +}) +export class NavigationService { + private readonly _defaultPath = [ '/' ]; + + constructor(private _router: Router) { } + + public navigateTo(path: string[], navigationExtras?: NavigationExtras): Promise { + let pathToNavigate = [ ...this._defaultPath ]; + + if (path && path.length > 0) { + pathToNavigate = [ ...path ]; + } + + if(navigationExtras) { + return this._router.navigate(path, navigationExtras); + } + + return this._router.navigate(path); + } +} \ No newline at end of file diff --git a/src/app/guards/auth.guard.ts b/src/app/guards/auth.guard.ts new file mode 100644 index 0000000..9a23b4c --- /dev/null +++ b/src/app/guards/auth.guard.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; +import { LocalStorageService } from '../common-services'; +import { routes } from '../consts'; +import { AuthService } from '../modules/auth/services'; + +@Injectable({ providedIn: 'root' }) +export class AuthGuard implements CanActivate { + public routes: typeof routes = routes; + constructor( + private _authService: AuthService + ) { } + + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + // not logged in so redirect to login page with the return url + if (!window || !window.document || !window.localStorage){ + return true; + } + + const isAuthenticated: boolean = this._authService.isAuthenticated(); + + if(!isAuthenticated) { + this._authService.logout(); + } + return isAuthenticated; + } +} \ No newline at end of file diff --git a/src/app/interceptors/error.interceptor.ts b/src/app/interceptors/error.interceptor.ts new file mode 100644 index 0000000..30dc96a --- /dev/null +++ b/src/app/interceptors/error.interceptor.ts @@ -0,0 +1,22 @@ +import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { AuthService } from '../modules/auth/services'; + +@Injectable() +export class ErrorInterceptor implements HttpInterceptor { + constructor(private _authService: AuthService) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next.handle(request).pipe(catchError(err => { + if (err.status === 401) { + // auto logout if 401 response returned from api + this._authService.logout(); + } + + const error = err.error.message || err.statusText; + return throwError(error); + })) + } +} \ No newline at end of file diff --git a/src/app/interceptors/index.ts b/src/app/interceptors/index.ts new file mode 100644 index 0000000..a4ae935 --- /dev/null +++ b/src/app/interceptors/index.ts @@ -0,0 +1 @@ +export * from './jwt.interceptor'; \ No newline at end of file diff --git a/src/app/interceptors/jwt.interceptor.ts b/src/app/interceptors/jwt.interceptor.ts new file mode 100644 index 0000000..f6e4761 --- /dev/null +++ b/src/app/interceptors/jwt.interceptor.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@angular/core'; +import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { LocalStorageService } from '../common-services'; +import { AppConfig } from '../app.config'; + + +@Injectable() +export class JwtInterceptor implements HttpInterceptor { + + config: any; + private readonly _defaultTokenValue = ''; + + constructor(appConfig: AppConfig, private _localStorageService: LocalStorageService) { + this.config = appConfig.getConfig(); + } + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + let token = this._defaultTokenValue; + + // add authorization header with jwt token if available + if (this._localStorageService.checkLocalStorageExistence()) { + token = window.localStorage[this._localStorageService.tokenFieldName]; + } + + request = request.clone({ + url: this.config.baseURLApi + request.url, + setHeaders: { + Authorization: `Bearer ${token}` + } + }); + + return next.handle(request); + } +} \ No newline at end of file diff --git a/src/app/interfaces/LoginFormCreds.ts b/src/app/interfaces/LoginFormCreds.ts new file mode 100644 index 0000000..d92db62 --- /dev/null +++ b/src/app/interfaces/LoginFormCreds.ts @@ -0,0 +1,4 @@ +export interface LoginFormCreds { + email: string; + password: string; +} \ No newline at end of file diff --git a/src/app/interfaces/User.ts b/src/app/interfaces/User.ts new file mode 100644 index 0000000..e52f43f --- /dev/null +++ b/src/app/interfaces/User.ts @@ -0,0 +1,6 @@ +export interface User { + email: string; + token?: string; + name: string; + lastName: string; +} \ No newline at end of file diff --git a/src/app/interfaces/index.ts b/src/app/interfaces/index.ts new file mode 100644 index 0000000..6e6580c --- /dev/null +++ b/src/app/interfaces/index.ts @@ -0,0 +1 @@ +export * from "./User" \ No newline at end of file diff --git a/src/app/modules/auth/auth.module.ts b/src/app/modules/auth/auth.module.ts index 535a5c9..d3208ce 100644 --- a/src/app/modules/auth/auth.module.ts +++ b/src/app/modules/auth/auth.module.ts @@ -10,7 +10,8 @@ import { AuthRoutingModule } from './auth-routing.module'; import { YearPipe } from './pipes'; import { AuthService, EmailService } from './services'; import { LoginFormComponent, SignFormComponent } from './components'; -import { AuthGuard } from './guards'; +import { JwtHelperService, JWT_OPTIONS } from '@auth0/angular-jwt'; +import { AuthGuard } from 'src/app/guards/auth.guard'; @NgModule({ declarations: [ @@ -31,7 +32,10 @@ import { AuthGuard } from './guards'; providers: [ AuthService, EmailService, - AuthGuard + AuthGuard, + // for jwt helper + { provide: JWT_OPTIONS, useValue: JWT_OPTIONS }, + JwtHelperService ] }) -export class AuthModule { } +export class AuthModule { } \ No newline at end of file diff --git a/src/app/modules/auth/components/login-form/login-form.component.ts b/src/app/modules/auth/components/login-form/login-form.component.ts index 50293f2..557df0d 100644 --- a/src/app/modules/auth/components/login-form/login-form.component.ts +++ b/src/app/modules/auth/components/login-form/login-form.component.ts @@ -1,5 +1,8 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { AppConfig } from 'src/app/app.config'; +import { LoginFormCreds } from 'src/app/interfaces/LoginFormCreds'; +import { AuthService } from '../../services'; @Component({ selector: 'app-login-form', @@ -7,10 +10,17 @@ import { FormControl, FormGroup, Validators } from '@angular/forms'; styleUrls: ['./login-form.component.scss'] }) export class LoginFormComponent implements OnInit { - @Output() sendLoginForm = new EventEmitter(); + @Output() sendLoginForm = new EventEmitter(); + config: any; public form: FormGroup; - public flatlogicEmail = 'admin@flatlogic.com'; - public flatlogicPassword = 'admin'; + public flatlogicEmail = ''; + public flatlogicPassword = ''; + + constructor(private _appConfig: AppConfig, private _authService: AuthService) { + this.config = this._appConfig.getConfig(); + this.flatlogicEmail = this.config.auth.email; + this.flatlogicPassword = this.config.auth.password; + } public ngOnInit(): void { this.form = new FormGroup({ @@ -20,8 +30,12 @@ export class LoginFormComponent implements OnInit { } public login(): void { + const creds: LoginFormCreds = { + email: this.flatlogicEmail, + password: this.flatlogicPassword + } if (this.form.valid) { - this.sendLoginForm.emit(); + this._authService.login(creds); } } } diff --git a/src/app/modules/auth/containers/auth-page/auth-page.component.html b/src/app/modules/auth/containers/auth-page/auth-page.component.html index a607cdf..e2fc2b2 100644 --- a/src/app/modules/auth/containers/auth-page/auth-page.component.html +++ b/src/app/modules/auth/containers/auth-page/auth-page.component.html @@ -4,19 +4,8 @@

Good Morning, User

-
- -
-
-
-

or

-
-
- - +
@@ -25,17 +14,6 @@

Welcome!

-
-
-

or

-
-
- -
- -
diff --git a/src/app/modules/auth/containers/auth-page/auth-page.component.ts b/src/app/modules/auth/containers/auth-page/auth-page.component.ts index dc07cd2..495dac1 100644 --- a/src/app/modules/auth/containers/auth-page/auth-page.component.ts +++ b/src/app/modules/auth/containers/auth-page/auth-page.component.ts @@ -3,6 +3,7 @@ import { Router } from '@angular/router'; import { AuthService } from '../../services'; import { routes } from '../../../../consts'; +import { LoginFormCreds } from 'src/app/interfaces/LoginFormCreds'; @Component({ selector: 'app-auth-page', @@ -14,18 +15,16 @@ export class AuthPageComponent { public routers: typeof routes = routes; constructor( - private service: AuthService, + private _authService: AuthService, private router: Router ) { } - public sendLoginForm(): void { - this.service.login(); - - this.router.navigate([this.routers.PROFILE]).then(); + public sendLoginForm(loginFormCreds: LoginFormCreds): void { + this._authService.login(loginFormCreds); } public sendSignForm(): void { - this.service.sign(); + this._authService.sign(); this.router.navigate([this.routers.PROFILE]).then(); } diff --git a/src/app/modules/auth/services/auth.service.ts b/src/app/modules/auth/services/auth.service.ts index 5d5df1f..eb0ed12 100644 --- a/src/app/modules/auth/services/auth.service.ts +++ b/src/app/modules/auth/services/auth.service.ts @@ -2,21 +2,52 @@ import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; import { User } from '../models'; +import { JwtHelperService } from '@auth0/angular-jwt'; +import { routes } from 'src/app/consts'; +import { HttpClient } from '@angular/common/http'; +import { AppConfig } from 'src/app/app.config'; +import { Router } from '@angular/router'; +import { LocalStorageService } from 'src/app/common-services'; @Injectable({ providedIn: 'root' }) export class AuthService { - public login(): void { - localStorage.setItem('token', 'token'); + public routes: typeof routes = routes; + + _isFetching: boolean = false; + _errorMessage: string = ''; + + get isFetching() { + return this._isFetching; } - public sign(): void { - localStorage.setItem('token', 'token'); + set isFetching(val: boolean) { + this._isFetching = val; + } + + get errorMessage() { + return this._errorMessage; + } + + set errorMessage(val: string) { + this._errorMessage = val; } - public signOut(): void { - localStorage.removeItem('token'); + config: any; + + constructor( + private _localStorageService: LocalStorageService, + private _appConfig: AppConfig, + private _jwtService: JwtHelperService, + private _httpClient: HttpClient, + private _router: Router + ) { + this.config = this._appConfig.getConfig(); + } + + public sign(): void { + localStorage.setItem(this._localStorageService.tokenFieldName, 'token'); } public getUser(): Observable { @@ -25,4 +56,75 @@ export class AuthService { lastName: 'Smith' }); } -} + + isAuthenticated() { + const token = localStorage.getItem(this._localStorageService.tokenFieldName); + let data = null; + + if (!token) return false; + const date = new Date().getTime() / 1000; + try { + data = this._jwtService.decodeToken(token); + } catch (e) { + return false; + } + if (!data) return false; + return date < data.exp; + } + + login(creds) { + // We check if app runs with backend mode + this.requestLogin(); + + if (creds.email.length > 0 && creds.password.length > 0) { + this._httpClient.post(`/account/login`, creds).subscribe((res: any) => { + const token = res.accessToken; + if (!token) { + throw new Error("There is no token!"); + } + + this.receiveToken(token); + }, err => { + this.loginError('Something was wrong. Try again'); + }); + } + } + + receiveToken(token) { + let decodedToken = null; + let user: any = {}; + + decodedToken = this._jwtService.decodeToken(token); + if (decodedToken) { + user = { + email: decodedToken[this.config.tokenEmailField] + }; + } + + localStorage.setItem(this._localStorageService.tokenFieldName, token); + localStorage.setItem(this._localStorageService.userFieldName, JSON.stringify(user)); + this.receiveLogin(); + } + + logout() { + localStorage.removeItem(this._localStorageService.tokenFieldName); + localStorage.removeItem(this._localStorageService.userFieldName); + document.cookie = 'token=;expires=Thu, 01 Jan 1970 00:00:01 GMT;'; + this._router.navigate([this.routes.LOGIN]); + } + + loginError(payload) { + this.isFetching = false; + this.errorMessage = payload; + } + + receiveLogin() { + this.isFetching = false; + this.errorMessage = ''; + this._router.navigate([this.routes.DASHBOARD]); + } + + requestLogin() { + this.isFetching = true; + } +} \ No newline at end of file diff --git a/src/app/modules/e-commerce/components/product-card/product-card.component.html b/src/app/modules/e-commerce/components/product-card/product-card.component.html index 12c0558..234a17f 100644 --- a/src/app/modules/e-commerce/components/product-card/product-card.component.html +++ b/src/app/modules/e-commerce/components/product-card/product-card.component.html @@ -1,8 +1,8 @@ - {{product.title}} + {{product.title}} - {{product.status}} + {{(product.status) ? product.status : 'Some'}}
diff --git a/src/app/modules/e-commerce/components/product-card/product-card.component.ts b/src/app/modules/e-commerce/components/product-card/product-card.component.ts index ba62119..0050cc4 100644 --- a/src/app/modules/e-commerce/components/product-card/product-card.component.ts +++ b/src/app/modules/e-commerce/components/product-card/product-card.component.ts @@ -1,6 +1,6 @@ -import {Component, Input} from '@angular/core'; -import {ProductCard} from '../../models'; -import {routes} from '../../../../consts'; +import { Component, Input } from '@angular/core'; +import { routes } from '../../../../consts'; +import { Product } from '../../services'; @Component({ selector: 'app-product-card', @@ -8,6 +8,6 @@ import {routes} from '../../../../consts'; styleUrls: ['./product-card.component.scss'] }) export class ProductCardComponent { - @Input() product: ProductCard; + @Input() product: Product; public routes: typeof routes = routes; } diff --git a/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.html b/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.html index 8a52dcc..003c3e9 100644 --- a/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.html +++ b/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.html @@ -4,15 +4,16 @@ - -
+

Image

- - None - Option 1 - Option 2 - Option 3 + + + + + + +
@@ -48,14 +49,14 @@

Description 1

- +

Description 2

- +
diff --git a/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.scss b/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.scss index 0b5a212..d018c53 100644 --- a/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.scss +++ b/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.scss @@ -2,6 +2,7 @@ @import "src/app/styles/colors"; @import "src/app/styles/variables"; + .card { margin-top: 24px; box-shadow: 0 3px 11px 0 $shadow-white, 0 3px 3px -2px $shadow-grey, 0 1px 8px 0 $shadow-dark-grey; diff --git a/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.ts b/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.ts index c07c7fe..2a67be6 100644 --- a/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.ts +++ b/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.ts @@ -1,79 +1,82 @@ import {Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation} from '@angular/core'; import {routes} from '../../../../consts'; -import {ProductDetails} from '../../models/product-details'; import {FormControl, FormGroup, Validators} from '@angular/forms'; +import { Product, ProductService } from '../../services'; +import { Observable } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-product-edit-form', templateUrl: './product-edit-form.component.html', styleUrls: ['./product-edit-form.component.scss'], }) -export class ProductEditFormComponent implements OnInit { - @Input() product: ProductDetails; - @Output() editProduct: EventEmitter = new EventEmitter(); +export class ProductEditFormComponent { + @Output() editProduct: EventEmitter = new EventEmitter(); public router: typeof routes = routes; public form: FormGroup; + public imagesObservable$: Observable; + public retrievedId; + + constructor(public productService: ProductService, private _activatedRoute: ActivatedRoute) { + this.imagesObservable$ = this.productService.getProductImages(); - selected = 'option'; - - constructor() { this.form = new FormGroup({ - image: new FormControl('', Validators['required']), + img: new FormControl('', Validators['required']), title: new FormControl('', Validators['required']), subtitle: new FormControl('', Validators['required']), - price: new FormControl('', Validators['required']), - discount: new FormControl('', Validators['required']), - description1: new FormControl('', Validators['required']), - description2: new FormControl('', Validators['required']), - code: new FormControl('', Validators['required']), + price: new FormControl(0, Validators['required']), + discount: new FormControl(0, Validators['required']), + description_1: new FormControl('', Validators['required']), + description_2: new FormControl('', Validators['required']), + code: new FormControl(0, Validators['required']), hashtag: new FormControl('', Validators['required']), technology: new FormControl('', Validators['required']), - rating: new FormControl('', Validators['required']), - status: new FormControl('', Validators['required']), + rating: new FormControl(0, Validators['required']), }); - } - ngOnInit(): void { - if (this.product) { - this.image.setValue('option'); - this.title.setValue(this.product.title); - this.subtitle.setValue(this.product.subtitle); - this.price.setValue(this.product.price); - this.discount.setValue(this.product.discount); - this.description1.setValue(this.product.description1); - this.description2.setValue(this.product.description2); - this.code.setValue(this.product.code); - this.hashtag.setValue(this.product.hashtag); - this.technology.setValue(this.product.technology); - this.rating.setValue(this.product.rating); - this.status.setValue(this.product.status); - } + this._activatedRoute.params.subscribe(params => { + this.retrievedId = params.id; + if(this.retrievedId) { + this.productService.getProduct(this.retrievedId).subscribe((pr: Product) => { + this.img.setValue(pr.img); + this.title.setValue(pr.title); + this.subtitle.setValue(pr.subtitle); + this.price.setValue(pr.price); + this.discount.setValue(pr.discount); + this.description_1.setValue(pr.description_1); + this.description_2.setValue(pr.description_2); + this.code.setValue(pr.code); + this.hashtag.setValue(pr.hashtag); + this.technology.setValue(pr.technology); + this.rating.setValue(pr.rating); + }); + } + }) } public save(): void { this.editProduct.emit({ id: this.id, - image: this.image.value, + img: this.img.value, title: this.title.value, subtitle: this.subtitle.value, price: this.price.value, discount: this.discount.value, - description1: this.description1.value, - description2: this.description2.value, + description_1: this.description_1.value, + description_2: this.description_2.value, code: this.code.value, hashtag: this.hashtag.value, technology: this.technology.value, rating: this.rating.value, - status: this.status.value, }) } get id() { - return this.product && this.product.id ? this.product.id : '77'; + return this.retrievedId ? this.retrievedId : +'77'; } - get image() { - return this.form.get('image') as FormControl; + get img() { + return this.form.get('img') as FormControl; } get title() { @@ -92,12 +95,12 @@ export class ProductEditFormComponent implements OnInit { return this.form.get('discount') as FormControl; } - get description1() { - return this.form.get('description1') as FormControl; + get description_1() { + return this.form.get('description_1') as FormControl; } - get description2() { - return this.form.get('description2') as FormControl; + get description_2() { + return this.form.get('description_2') as FormControl; } get code() { @@ -115,8 +118,4 @@ export class ProductEditFormComponent implements OnInit { get rating() { return this.form.get('rating') as FormControl; } - - get status() { - return this.form.get('status') as FormControl; - } } diff --git a/src/app/modules/e-commerce/containers/management-page/management-page.component.html b/src/app/modules/e-commerce/containers/management-page/management-page.component.html index 737a879..96b0129 100644 --- a/src/app/modules/e-commerce/containers/management-page/management-page.component.html +++ b/src/app/modules/e-commerce/containers/management-page/management-page.component.html @@ -1,118 +1,121 @@ - - -

Products {{(products$ | async).length}} total

- - - Search - - search - - -
- - - - - -
-

Products

- filter_list -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - ID {{element.id}} Image - logo - Title {{element.title}} Subtitle {{element.subtitle}} Price {{element.price}} Rating -
-

{{element.rating}}

- star -
-
Actions -
- - -
-
No data matching the filter "{{input.value}}"
-
- - -
-
- +
+ +
+ + + +

Products {{products.length}} total

+ + + Search + + search + + +
+ + + + +
+

Products

+ filter_list +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + ID {{element.id}} Image + logo + Title {{element.title}} Subtitle {{element.subtitle}} Price {{element.price}} Rating +
+

{{element.rating}}

+ star +
+
Actions +
+ + +
+
No data matching the filter "{{input.value}}"
+
+ + +
+
+
\ No newline at end of file diff --git a/src/app/modules/e-commerce/containers/management-page/management-page.component.scss b/src/app/modules/e-commerce/containers/management-page/management-page.component.scss index 72f7f66..99c7bab 100644 --- a/src/app/modules/e-commerce/containers/management-page/management-page.component.scss +++ b/src/app/modules/e-commerce/containers/management-page/management-page.component.scss @@ -6,6 +6,12 @@ margin-top: 24px; } +.spinner-aligner_center { + display: flex; + justify-content: center; + margin-top: 40px; +} + .card-title-wrapper { display: flex; justify-content: space-between; diff --git a/src/app/modules/e-commerce/containers/management-page/management-page.component.ts b/src/app/modules/e-commerce/containers/management-page/management-page.component.ts index 167c0cb..9fae156 100644 --- a/src/app/modules/e-commerce/containers/management-page/management-page.component.ts +++ b/src/app/modules/e-commerce/containers/management-page/management-page.component.ts @@ -4,26 +4,32 @@ import {MatTableDataSource} from '@angular/material/table'; import {SelectionModel} from '@angular/cdk/collections'; import {MatSort} from '@angular/material/sort'; import {MatPaginator} from '@angular/material/paginator'; -import {ProductService} from '../../services'; -import {Observable} from 'rxjs'; -import {ProductDetails} from '../../models/product-details'; -import {take} from 'rxjs/operators'; +import {Product, ProductService} from '../../services'; @Component({ selector: 'app-management-page', templateUrl: './management-page.component.html', styleUrls: ['./management-page.component.scss'] }) -export class ManagementPageComponent implements OnInit { +export class ManagementPageComponent { @ViewChild(MatSort, {static: true}) sort: MatSort; @ViewChild(MatPaginator, {static: true}) paginator: MatPaginator; public routes: typeof routes = routes; - public products$: Observable; + public products: Product[] = []; public displayedColumns: string[] = ['select', 'id', 'image', 'title', 'subtitle', 'price', 'rating', 'actions']; - public dataSource: MatTableDataSource; - + public dataSource: MatTableDataSource; selection = new SelectionModel(true, []); + constructor(public productService: ProductService) { + this.productService.getProducts().subscribe((products: Product[]) => { + this.dataSource = new MatTableDataSource(products); + this.dataSource.sort = this.sort; + this.dataSource.paginator = this.paginator; + this.products = products; + this.productService.finishGetProducts(); + }); + } + applyFilter(event: Event) { const filterValue = (event.target as HTMLInputElement).value; this.dataSource.filter = filterValue.trim().toLowerCase(); @@ -44,37 +50,18 @@ export class ManagementPageComponent implements OnInit { } /** The label for the checkbox on the passed row */ - checkboxLabel(row?: ProductDetails): string { + checkboxLabel(row?: Product): string { if (!row) { return `${this.isAllSelected() ? 'select' : 'deselect'} all`; } return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${row.id + 1}`; } - constructor(private service: ProductService) { - this.products$ = this.service.getProducts(); - - this.products$.pipe( - take(1) - ).subscribe((products: ProductDetails[]) => { - this.dataSource = new MatTableDataSource(products); - }); - } - - ngOnInit() { - this.dataSource.sort = this.sort; - this.dataSource.paginator = this.paginator; - } - public delete(id: string) { - this.service.deleteProduct(id); - - this.products$ = this.service.getProducts(); - - this.products$.pipe( - take(1) - ).subscribe((products: ProductDetails[]) => { - this.dataSource = new MatTableDataSource(products); - }); + this.productService.deleteProduct(id) + .subscribe(() => { + this.products = this.products.filter(product => product.id !== +id); + this.dataSource = new MatTableDataSource(this.products); + }); } } diff --git a/src/app/modules/e-commerce/containers/product-create-page/product-create-page.component.html b/src/app/modules/e-commerce/containers/product-create-page/product-create-page.component.html index 8992843..30d4c5a 100644 --- a/src/app/modules/e-commerce/containers/product-create-page/product-create-page.component.html +++ b/src/app/modules/e-commerce/containers/product-create-page/product-create-page.component.html @@ -1,5 +1,3 @@ - - - + \ No newline at end of file diff --git a/src/app/modules/e-commerce/containers/product-create-page/product-create-page.component.ts b/src/app/modules/e-commerce/containers/product-create-page/product-create-page.component.ts index 738b729..d46efe8 100644 --- a/src/app/modules/e-commerce/containers/product-create-page/product-create-page.component.ts +++ b/src/app/modules/e-commerce/containers/product-create-page/product-create-page.component.ts @@ -1,9 +1,7 @@ import { Component } from '@angular/core'; import {routes} from '../../../../consts'; import {ActivatedRoute, Router} from '@angular/router'; -import {switchMap} from 'rxjs/operators'; -import {ProductDetails} from '../../models/product-details'; -import {ProductService} from '../../services'; +import {Product, ProductService} from '../../services'; @Component({ selector: 'app-product-create-page', @@ -14,14 +12,15 @@ export class ProductCreatePageComponent { public routes: typeof routes = routes; constructor( - private service: ProductService, + private _productService: ProductService, private router: Router ) { } - public createProduct(product: ProductDetails): void { - this.service.createProduct(product); - - this.router.navigate([this.routes.MANAGEMENT]).then(); + public createProduct(product: Product): void { + this._productService.createProduct(product).subscribe(() => { + this._productService.finishCreatingProduct(); + this.router.navigate([this.routes.MANAGEMENT]).then(); + }); } } diff --git a/src/app/modules/e-commerce/containers/product-edit-page/product-edit-page.component.html b/src/app/modules/e-commerce/containers/product-edit-page/product-edit-page.component.html index 0ed813c..f410123 100644 --- a/src/app/modules/e-commerce/containers/product-edit-page/product-edit-page.component.html +++ b/src/app/modules/e-commerce/containers/product-edit-page/product-edit-page.component.html @@ -1,4 +1,3 @@ - - + \ No newline at end of file diff --git a/src/app/modules/e-commerce/containers/product-edit-page/product-edit-page.component.ts b/src/app/modules/e-commerce/containers/product-edit-page/product-edit-page.component.ts index 0f1aec4..42ae306 100644 --- a/src/app/modules/e-commerce/containers/product-edit-page/product-edit-page.component.ts +++ b/src/app/modules/e-commerce/containers/product-edit-page/product-edit-page.component.ts @@ -2,8 +2,7 @@ import {Component, OnInit} from '@angular/core'; import {routes} from '../../../../consts'; import {ActivatedRoute, Router} from '@angular/router'; import {Observable} from 'rxjs'; -import {ProductService} from '../../services'; -import {ProductDetails} from '../../models/product-details'; +import {Product, ProductService} from '../../services'; @Component({ selector: 'app-product-edit-page', @@ -16,21 +15,22 @@ export class ProductEditPageComponent implements OnInit { constructor( private route: ActivatedRoute, - private service: ProductService, + private _productService: ProductService, private router: Router ) {} ngOnInit() { this.route.paramMap.subscribe((params: any) => { if (params.params.id) { - this.product$ = this.service.getProduct(params.params.id); + this.product$ = this._productService.getProduct(params.params.id); } }) } - public saveEditProduct(product: ProductDetails) { - this.service.saveChangedProduct(product); - - this.router.navigate([this.routes.MANAGEMENT]).then(); + public saveEditProduct(product: Product) { + this._productService.updateProduct(product).subscribe(() => { + this._productService.finishUpdatingProduct(); + this.router.navigate([this.routes.MANAGEMENT]).then(); + }); } } diff --git a/src/app/modules/e-commerce/containers/product-page/product-page.component.html b/src/app/modules/e-commerce/containers/product-page/product-page.component.html index c02ef75..21eb4d5 100644 --- a/src/app/modules/e-commerce/containers/product-page/product-page.component.html +++ b/src/app/modules/e-commerce/containers/product-page/product-page.component.html @@ -1,22 +1,22 @@ - product-image + product-image
-

{{(product$ | async).title}}

-

{{(product$ | async).subtitle}}

+

{{currentProduct.title}}

+

{{currentProduct.subtitle}}

-

{{(product$ | async).rating}}

+

{{currentProduct.rating}}

star
-

{{(product$ | async).price}}

+

{{currentProduct.price}}

Size Guide

@@ -81,21 +81,21 @@

PRODUCT DESCRIPTION

    -
  • {{(product$ | async).description1}}
  • -
  • {{(product$ | async).description2}}
  • +
  • {{currentProduct.description_1}}
  • +
  • {{currentProduct.description_2}}

PRODUCT CODE

-

{{(product$ | async).code}}

+

{{currentProduct.code}}

TECHNOLOGY

    -
  • {{technology}}
  • +
  • {{technology}}
@@ -103,7 +103,7 @@

SHARE

-

Share photo with a tag {{(product$ | async).hashtag}}

+

Share photo with a tag {{currentProduct.hashtag}}

@@ -122,7 +122,7 @@

RATING & REVIEWS

-

{{(product$ | async).rating}}

+

{{currentProduct.rating}}

star
@@ -137,5 +137,5 @@

You may also like

- +
diff --git a/src/app/modules/e-commerce/containers/product-page/product-page.component.ts b/src/app/modules/e-commerce/containers/product-page/product-page.component.ts index 24a3a35..1a9b917 100644 --- a/src/app/modules/e-commerce/containers/product-page/product-page.component.ts +++ b/src/app/modules/e-commerce/containers/product-page/product-page.component.ts @@ -1,10 +1,7 @@ import {Component, OnInit} from '@angular/core'; import {routes} from '../../../../consts'; import {FormControl, FormGroup} from '@angular/forms'; -import {ProductService, ProductsService} from '../../services'; -import {Observable} from 'rxjs'; -import {ProductCard} from '../../models'; -import {ProductDetails} from '../../models/product-details'; +import {Product, ProductService} from '../../services'; import {ActivatedRoute} from '@angular/router'; @Component({ @@ -12,31 +9,29 @@ import {ActivatedRoute} from '@angular/router'; templateUrl: './product-page.component.html', styleUrls: ['./product-page.component.scss'] }) -export class ProductPageComponent implements OnInit { +export class ProductPageComponent { public routes: typeof routes = routes; public form: FormGroup; - public products$: Observable - public product$: Observable + public additionalProducts: Product[] = []; + public currentProduct: Product = new Product(); constructor( - private service: ProductService, + public productService: ProductService, private route: ActivatedRoute ) { - this.products$ = this.service.getSimilarProducts(); - } - - public ngOnInit() { this.form = new FormGroup({ size: new FormControl('2'), value: new FormControl('2'), }); - this.route.paramMap.subscribe((params: any) => { - if (params.params.id) { - this.product$ = this.service.getProduct(params.params.id); - } else { - this.product$ = this.service.getProduct('1'); - } + this.route.params.subscribe((params: any) => { + this.productService.getProduct(params.id || '1').subscribe((product: Product) => { + this.currentProduct = product; + }); + this.productService.getProducts().subscribe((products: Product[]) => { + this.additionalProducts = products; + this.productService.finishGetProducts(); + }) }); } diff --git a/src/app/modules/e-commerce/containers/products-page/products-page.component.html b/src/app/modules/e-commerce/containers/products-page/products-page.component.html index bac0415..a344914 100644 --- a/src/app/modules/e-commerce/containers/products-page/products-page.component.html +++ b/src/app/modules/e-commerce/containers/products-page/products-page.component.html @@ -62,6 +62,11 @@
-
- +
+
+ +
+ +
+
diff --git a/src/app/modules/e-commerce/containers/products-page/products-page.component.scss b/src/app/modules/e-commerce/containers/products-page/products-page.component.scss index d6a40e4..df78172 100644 --- a/src/app/modules/e-commerce/containers/products-page/products-page.component.scss +++ b/src/app/modules/e-commerce/containers/products-page/products-page.component.scss @@ -1,5 +1,11 @@ @import "src/app/styles/variables"; +.spinner-aligner_center { + display: flex; + justify-content: center; + margin-top: 40px; +} + .selects-wrapper { margin-top: 24px; display: flex; diff --git a/src/app/modules/e-commerce/containers/products-page/products-page.component.ts b/src/app/modules/e-commerce/containers/products-page/products-page.component.ts index 8272969..707442b 100644 --- a/src/app/modules/e-commerce/containers/products-page/products-page.component.ts +++ b/src/app/modules/e-commerce/containers/products-page/products-page.component.ts @@ -1,23 +1,24 @@ import {Component, OnInit} from '@angular/core'; import { routes } from '../../../../consts'; -import {ProductsService} from '../../services'; -import {Observable} from 'rxjs'; -import {ProductCard} from '../../models'; import {FormControl, FormGroup } from '@angular/forms'; +import { Product, ProductService } from '../../services'; @Component({ - selector: 'app-products-page', + selector: 'app-product-grid-page', templateUrl: './products-page.component.html', styleUrls: ['./products-page.component.scss'] }) export class ProductsPageComponent implements OnInit { public routes: typeof routes = routes; - public products$: Observable + public products: Product[] = []; public form: FormGroup; - constructor(private service: ProductsService) { - this.products$ = this.service.getProducts(); + constructor(public productService: ProductService) { + this.productService.getProducts().subscribe((products: Product[]) => { + this.products = products; + this.productService.finishGetProducts(); + }); } public ngOnInit() { diff --git a/src/app/modules/e-commerce/e-commerce.module.ts b/src/app/modules/e-commerce/e-commerce.module.ts index 3e7d957..5ccd6e8 100644 --- a/src/app/modules/e-commerce/e-commerce.module.ts +++ b/src/app/modules/e-commerce/e-commerce.module.ts @@ -14,8 +14,6 @@ import { MatTooltipModule } from '@angular/material/tooltip'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatSortModule } from '@angular/material/sort'; import { MatPaginatorModule } from '@angular/material/paginator'; - -import { ProductsService } from './services'; import { ECommerceRoutingModule } from './e-commerce-routing.module'; import { SharedModule } from '../../shared/shared.module'; import { ProductEditFormComponent, ProductCardComponent } from './components'; @@ -26,6 +24,7 @@ import { ProductEditPageComponent, ProductCreatePageComponent } from './containers'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; @NgModule({ declarations: [ @@ -53,10 +52,9 @@ import { MatTooltipModule, MatCheckboxModule, MatSortModule, - MatPaginatorModule + MatPaginatorModule, + MatProgressSpinnerModule, ], - providers: [ - ProductsService - ] + providers: [] }) export class ECommerceModule { } diff --git a/src/app/modules/e-commerce/models/index.ts b/src/app/modules/e-commerce/models/index.ts index 82c86b5..e69de29 100644 --- a/src/app/modules/e-commerce/models/index.ts +++ b/src/app/modules/e-commerce/models/index.ts @@ -1 +0,0 @@ -export * from './product-card'; diff --git a/src/app/modules/e-commerce/services/index.ts b/src/app/modules/e-commerce/services/index.ts index 2054e36..139d67e 100644 --- a/src/app/modules/e-commerce/services/index.ts +++ b/src/app/modules/e-commerce/services/index.ts @@ -1,2 +1 @@ -export * from './products.service'; export * from './product.service' diff --git a/src/app/modules/e-commerce/services/product.service.ts b/src/app/modules/e-commerce/services/product.service.ts index c0f819c..c7a9c47 100644 --- a/src/app/modules/e-commerce/services/product.service.ts +++ b/src/app/modules/e-commerce/services/product.service.ts @@ -1,321 +1,227 @@ import {Injectable} from '@angular/core'; -import {Observable, of} from 'rxjs'; -import {ProductDetails} from '../models/product-details'; -import {ProductCard} from '../models'; - -let products: ProductDetails[] = [ - { - id: '1', - image: './assets/e-commerce/products/1.png', - imageLarge: './assets/e-commerce/products/large-1.png', - imageSmall: './assets/e-commerce/managment/1.png', - title: 'Trainers', - subtitle: 'Trainers in white', - description1: 'Sneakers (also known as athletic shoes, tennis shoes,gym shoes, runners, takkies,' + - ' or trainers) are shoes primarily designed for sports or other forms of physical exercise, but' + - ' which are now also often used for everyday wear.', - description2: 'The term generally describes a type of footwear with a flexible sole made of rubber' + - ' or synthetic material and an upper part made of leather or synthetic materials.', - technology: ['Ollie patch', 'Cup soles', 'Vulcanized rubber soles'], - hashtag: '#whitetrainers', - code: '135234', - discount: '20', - price: '$80', - rating: '4.6', - status: 'New' - }, - { - id: '2', - image: './assets/e-commerce/products/2.png', - imageLarge: './assets/e-commerce/products/large-2.png', - imageSmall: './assets/e-commerce/managment/2.png', - title: 'Boots', - subtitle: 'Trainers in blue', - description1: 'Sneakers (also known as athletic shoes, tennis shoes,gym shoes, runners, takkies,' + - ' or trainers) are shoes primarily designed for sports or other forms of physical exercise, but' + - ' which are now also often used for everyday wear.', - description2: 'The term generally describes a type of footwear with a flexible sole made of rubber' + - ' or synthetic material and an upper part made of leather or synthetic materials.', - technology: ['Ollie patch', 'Cup soles', 'Vulcanized rubber soles'], - hashtag: '#whitetrainers', - code: '135264', - discount: '20', - price: '$37', - rating: '4.6', - status: 'Sale' - }, - { - id: '3', - image: './assets/e-commerce/products/3.png', - imageLarge: './assets/e-commerce/products/large-3.png', - imageSmall: './assets/e-commerce/managment/3.png', - title: 'Flat sandals', - subtitle: 'Trainers in white', - description1: 'Sneakers (also known as athletic shoes, tennis shoes,gym shoes, runners, takkies,' + - ' or trainers) are shoes primarily designed for sports or other forms of physical exercise, but' + - ' which are now also often used for everyday wear.', - description2: 'The term generally describes a type of footwear with a flexible sole made of rubber' + - ' or synthetic material and an upper part made of leather or synthetic materials.', - technology: ['Ollie patch', 'Cup soles', 'Vulcanized rubber soles'], - hashtag: '#whitetrainers', - code: '125234', - discount: '20', - price: '$70', - rating: '4.6', - status: 'New' - }, - { - id: '4', - image: './assets/e-commerce/products/4.png', - imageLarge: './assets/e-commerce/products/large-4.png', - imageSmall: './assets/e-commerce/managment/4.png', - title: 'Trainers', - subtitle: 'Trainers in blue', - description1: 'Sneakers (also known as athletic shoes, tennis shoes,gym shoes, runners, takkies,' + - ' or trainers) are shoes primarily designed for sports or other forms of physical exercise, but' + - ' which are now also often used for everyday wear.', - description2: 'The term generally describes a type of footwear with a flexible sole made of rubber' + - ' or synthetic material and an upper part made of leather or synthetic materials.', - technology: ['Ollie patch', 'Cup soles', 'Vulcanized rubber soles'], - hashtag: '#whitetrainers', - code: '133234', - discount: '20', - price: '$85', - rating: '4.6', - status: 'Sale' - }, - { - id: '5', - image: './assets/e-commerce/products/5.png', - imageLarge: './assets/e-commerce/products/large-5.png', - imageSmall: './assets/e-commerce/managment/5.png', - title: 'Flat sandals', - subtitle: 'Trainers in white', - description1: 'Sneakers (also known as athletic shoes, tennis shoes,gym shoes, runners, takkies,' + - ' or trainers) are shoes primarily designed for sports or other forms of physical exercise, but' + - ' which are now also often used for everyday wear.', - description2: 'The term generally describes a type of footwear with a flexible sole made of rubber' + - ' or synthetic material and an upper part made of leather or synthetic materials.', - technology: ['Ollie patch', 'Cup soles', 'Vulcanized rubber soles'], - hashtag: '#whitetrainers', - code: '138234', - discount: '20', - price: '$12', - rating: '4.6', - status: 'New' - }, - { - id: '6', - image: './assets/e-commerce/products/6.png', - imageLarge: './assets/e-commerce/products/large-6.png', - imageSmall: './assets/e-commerce/managment/6.png', - title: 'Flat sandals', - subtitle: 'Trainers in blue', - description1: 'Sneakers (also known as athletic shoes, tennis shoes,gym shoes, runners, takkies,' + - ' or trainers) are shoes primarily designed for sports or other forms of physical exercise, but' + - ' which are now also often used for everyday wear.', - description2: 'The term generally describes a type of footwear with a flexible sole made of rubber' + - ' or synthetic material and an upper part made of leather or synthetic materials.', - technology: ['Ollie patch', 'Cup soles', 'Vulcanized rubber soles'], - hashtag: '#whitetrainers', - code: '135237', - discount: '20', - price: '$76', - rating: '4.6', - status: 'Sale' - }, - { - id: '7', - image: './assets/e-commerce/products/1.png', - imageLarge: './assets/e-commerce/products/large-1.png', - imageSmall: './assets/e-commerce/managment/1.png', - title: 'Trainers', - subtitle: 'Trainers in white', - description1: 'Sneakers (also known as athletic shoes, tennis shoes,gym shoes, runners, takkies,' + - ' or trainers) are shoes primarily designed for sports or other forms of physical exercise, but' + - ' which are now also often used for everyday wear.', - description2: 'The term generally describes a type of footwear with a flexible sole made of rubber' + - ' or synthetic material and an upper part made of leather or synthetic materials.', - technology: ['Ollie patch', 'Cup soles', 'Vulcanized rubber soles'], - hashtag: '#whitetrainers', - code: '135234', - discount: '20', - price: '$80', - rating: '4.6', - status: 'New' - }, - { - id: '8', - image: './assets/e-commerce/products/2.png', - imageLarge: './assets/e-commerce/products/large-2.png', - imageSmall: './assets/e-commerce/managment/2.png', - title: 'Boots', - subtitle: 'Trainers in blue', - description1: 'Sneakers (also known as athletic shoes, tennis shoes,gym shoes, runners, takkies,' + - ' or trainers) are shoes primarily designed for sports or other forms of physical exercise, but' + - ' which are now also often used for everyday wear.', - description2: 'The term generally describes a type of footwear with a flexible sole made of rubber' + - ' or synthetic material and an upper part made of leather or synthetic materials.', - technology: ['Ollie patch', 'Cup soles', 'Vulcanized rubber soles'], - hashtag: '#whitetrainers', - code: '135264', - discount: '20', - price: '$37', - rating: '4.6', - status: 'Sale' - }, - { - id: '9', - image: './assets/e-commerce/products/3.png', - imageLarge: './assets/e-commerce/products/large-3.png', - imageSmall: './assets/e-commerce/managment/3.png', - title: 'Flat sandals', - subtitle: 'Trainers in white', - description1: 'Sneakers (also known as athletic shoes, tennis shoes,gym shoes, runners, takkies,' + - ' or trainers) are shoes primarily designed for sports or other forms of physical exercise, but' + - ' which are now also often used for everyday wear.', - description2: 'The term generally describes a type of footwear with a flexible sole made of rubber' + - ' or synthetic material and an upper part made of leather or synthetic materials.', - technology: ['Ollie patch', 'Cup soles', 'Vulcanized rubber soles'], - hashtag: '#whitetrainers', - code: '125234', - discount: '20', - price: '$70', - rating: '4.6', - status: 'New' - }, - { - id: '10', - image: './assets/e-commerce/products/4.png', - imageLarge: './assets/e-commerce/products/large-4.png', - imageSmall: './assets/e-commerce/managment/4.png', - title: 'Trainers', - subtitle: 'Trainers in blue', - description1: 'Sneakers (also known as athletic shoes, tennis shoes,gym shoes, runners, takkies,' + - ' or trainers) are shoes primarily designed for sports or other forms of physical exercise, but' + - ' which are now also often used for everyday wear.', - description2: 'The term generally describes a type of footwear with a flexible sole made of rubber' + - ' or synthetic material and an upper part made of leather or synthetic materials.', - technology: ['Ollie patch', 'Cup soles', 'Vulcanized rubber soles'], - hashtag: '#whitetrainers', - code: '133234', - discount: '20', - price: '$85', - rating: '4.6', - status: 'Sale' - }, - { - id: '11', - image: './assets/e-commerce/products/5.png', - imageLarge: './assets/e-commerce/products/large-5.png', - imageSmall: './assets/e-commerce/managment/5.png', - title: 'Flat sandals', - subtitle: 'Trainers in white', - description1: 'Sneakers (also known as athletic shoes, tennis shoes,gym shoes, runners, takkies,' + - ' or trainers) are shoes primarily designed for sports or other forms of physical exercise, but' + - ' which are now also often used for everyday wear.', - description2: 'The term generally describes a type of footwear with a flexible sole made of rubber' + - ' or synthetic material and an upper part made of leather or synthetic materials.', - technology: ['Ollie patch', 'Cup soles', 'Vulcanized rubber soles'], - hashtag: '#whitetrainers', - code: '138234', - discount: '20', - price: '$12', - rating: '4.6', - status: 'New' - }, - { - id: '12', - image: './assets/e-commerce/products/6.png', - imageLarge: './assets/e-commerce/products/large-6.png', - imageSmall: './assets/e-commerce/managment/6.png', - title: 'Flat sandals', - subtitle: 'Trainers in blue', - description1: 'Sneakers (also known as athletic shoes, tennis shoes,gym shoes, runners, takkies,' + - ' or trainers) are shoes primarily designed for sports or other forms of physical exercise, but' + - ' which are now also often used for everyday wear.', - description2: 'The term generally describes a type of footwear with a flexible sole made of rubber' + - ' or synthetic material and an upper part made of leather or synthetic materials.', - technology: ['Ollie patch', 'Cup soles', 'Vulcanized rubber soles'], - hashtag: '#whitetrainers', - code: '135237', - discount: '20', - price: '$76', - rating: '4.6', - status: 'Sale' +import {from, Observable, of} from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { Router } from '@angular/router'; +import { AppConfig } from 'src/app/app.config'; +import { flatMap, map, share, toArray } from 'rxjs/operators'; +import { string } from '@amcharts/amcharts4/core'; + +export class Product { + id?: number; + img?: string; + title?: string; + subtitle?: string; + price?: number; + rating?: number; + description_1?: string; + description_2?: string; + code?: number; + hashtag?: string; + technology?: string[]; + discount?: number; + status?: string = "New"; + + constructor(id?) { + if (id) { + this.id = id; + } + this.img = ''; + this.title = ''; + this.subtitle = ''; + this.price = 0.01; + this.rating = 5; + this.description_1 = ''; + this.description_2 = ''; + this.code = null; + this.hashtag = ''; + this.technology = []; + this.discount = 0; + this.status = "New"; } -] +} @Injectable({ providedIn: 'root' }) export class ProductService { - public getProducts(): Observable { - return of(products); + config: any; + _isCreating: boolean = false; + _isReceiving: boolean = false; + _isUpdating: boolean = false; + _isDeleting: boolean = false; + _isRetrievingProductImage: boolean = false; + + public products$: Observable; + public product$: Observable; + + constructor( + private http: HttpClient, + private router: Router, + appConfig: AppConfig + ) { + this.config = appConfig.getConfig(); } + + public setImgField(product: any) { + if(!product) return {}; + let imgString = ''; + let resourceApi = (this.config.resourceApi) ? this.config.resourceApi : ''; - public getProduct(id: string): Observable { - return of(products.find((product: ProductDetails) => product.id === id)); + if(product.img) { + imgString = product.img; + } + + return { + ...product, + img: resourceApi + imgString + } + } + + getProducts(isForced: boolean = false) { + this.startGetProducts(); + this.products$ = this.http.get>('/products') + .pipe( + flatMap(data => data), + map(product => this.setImgField(product)), + toArray(), + share() + ); + return this.products$; } - public saveChangedProduct(editProduct: ProductDetails) { - products.map((product: ProductDetails, i: number) => { - if (product.id === editProduct.id) { - products.splice(i, 1, editProduct); - } - }); + getProduct(id, isForced: boolean = false) { + this.finishGetProducts(); + return this.product$ = this.http.get('/products/' + id) + .pipe( + map(product => this.setImgField(product)), + share() + ); } - public deleteProduct(id: string): void { - products.map((product: ProductDetails, i: number) => { - if (product.id === id) { - products.splice(i, 1); + updateProduct(payload: Product) { + if(!payload) return; + + if(!Array.isArray(payload.technology)) { + if(typeof(payload.technology) === "string") { + payload.technology = (payload.technology as string).split(','); + } else { + payload.technology = []; } - }); + } + + this.startUpdatingProduct(); + return this.http.put('/products/' + payload.id, payload); } - public getSimilarProducts(): Observable { - return of([ - { - id: '1', - image: './assets/e-commerce/products/1.png', - title: 'Trainers', - subtitle: 'Trainers in white', - price: '$80', - rating: '4.6', - status: 'New' - }, - { - id: '2', - image: './assets/e-commerce/products/2.png', - title: 'Boots', - subtitle: 'Trainers in blue', - price: '$37', - rating: '4.6', - status: 'Sale' - }, - { - id: '3', - image: './assets/e-commerce/products/3.png', - title: 'Flat sandals', - subtitle: 'Trainers in white', - price: '$70', - rating: '4.6', - status: 'New' - }, - { - id: '4', - image: './assets/e-commerce/products/4.png', - title: 'Trainers', - subtitle: 'Trainers in blue', - price: '$85', - rating: '4.6', - status: 'Sale' + createProduct(payload: Product) { + if(!payload) return; + + if(!Array.isArray(payload.technology)) { + if(typeof(payload.technology) === "string") { + payload.technology = (payload.technology as string).split(','); + } else { + payload.technology = []; } - ]); + } + + this.startCreatingProduct(); + return this.http.post('/products', payload); + } + + deleteProduct(id) { + this.startDeletingProduct(); + return this.http.delete('/products/' + id); + } + + getProductImages() { + this.startRetrievingProductImages() + return this.http.get>('/products/images-list') + .pipe( + flatMap(data => data), + map(img => { + if(!img) return ''; + + let resourceApi = (this.config.resourceApi) ? this.config.resourceApi : ''; + return `${resourceApi}${img}`; + }), + toArray(), + share() + ); + } + + startGetProducts() { + this.isReceiving = true; + } + + startRetrievingProductImages() { + this.isRetrievingProductImage = true; + } + + startUpdatingProduct() { + this.isUpdating = true; + } + + startDeletingProduct() { + this.isDeleting = true; + } + + startCreatingProduct() { + this.isCreating = true; + } + + finishGetProducts() { + this.isReceiving = false; + } + + finishRetrievingProductImages() { + this.isRetrievingProductImage = false; + } + + finishUpdatingProduct() { + this.isUpdating = false; + } + + finishCreatingProduct() { + this.isCreating = false; + } + + get isCreating() { + return this._isCreating; + } + + set isCreating(isCreating) { + this._isCreating = isCreating; + } + + get isReceiving() { + return this._isReceiving; + } + + set isReceiving(isReceiving) { + this._isReceiving = isReceiving; + } + + get isUpdating() { + return this._isUpdating; + } + + set isUpdating(isUpdating) { + this._isUpdating = isUpdating; + } + + get isDeleting() { + return this._isDeleting; + } + + set isDeleting(isDeleting) { + this._isDeleting = isDeleting; + } + + get isRetrievingProductImage() { + return this._isRetrievingProductImage; } - public createProduct(product: ProductDetails): void { - products.push(product); + set isRetrievingProductImage(newIsRetrievingProductImage) { + this._isRetrievingProductImage = newIsRetrievingProductImage; } } diff --git a/src/app/modules/user/components/account-edit-form/account-edit-form.component.ts b/src/app/modules/user/components/account-edit-form/account-edit-form.component.ts index 55dde45..25709fd 100644 --- a/src/app/modules/user/components/account-edit-form/account-edit-form.component.ts +++ b/src/app/modules/user/components/account-edit-form/account-edit-form.component.ts @@ -16,7 +16,7 @@ export class AccountEditFormComponent implements OnInit { this.editForm = new FormGroup({ userName: new FormControl('user'), userEmail: new FormControl('user@mail.com'), - userStatus: new FormControl('admin'), + userStatus: new FormControl('password'), }); } diff --git a/src/app/shared/header/containers/header/header.component.html b/src/app/shared/header/containers/header/header.component.html index 3386549..f20fec3 100644 --- a/src/app/shared/header/containers/header/header.component.html +++ b/src/app/shared/header/containers/header/header.component.html @@ -12,6 +12,6 @@
Angular Material Admin Full
- +
diff --git a/src/app/shared/header/containers/header/header.component.ts b/src/app/shared/header/containers/header/header.component.ts index 7d88c16..118eaa1 100644 --- a/src/app/shared/header/containers/header/header.component.ts +++ b/src/app/shared/header/containers/header/header.component.ts @@ -33,8 +33,8 @@ export class HeaderComponent { this.isShowSidebar.emit(this.isMenuOpened); } - public signOut(): void { - this.userService.signOut(); + public logout(): void { + this.userService.logout(); this.router.navigate([this.routers.LOGIN]); } diff --git a/src/custom-material-styles.scss b/src/custom-material-styles.scss new file mode 100644 index 0000000..377de20 --- /dev/null +++ b/src/custom-material-styles.scss @@ -0,0 +1,13 @@ +// .special-material-behaviour { + .mat-select-panel { + min-height: 500px; + .mat-option { + height: max-content !important; + .mat-option-text { + padding: 10px 0; + display: flex !important; + justify-content: center !important; + } + } + } +// } diff --git a/src/custom-theme.scss b/src/custom-theme.scss index 5cb2297..ad6321d 100644 --- a/src/custom-theme.scss +++ b/src/custom-theme.scss @@ -4,6 +4,7 @@ @import '~@fullcalendar/timegrid/main.css'; @import "../node_modules/angular-calendar/css/angular-calendar.css"; +@import "./custom-material-styles.scss"; @import "src/app/styles/colors"; @import "src/app/styles/variables"; @import "src/app/styles/font"; @@ -401,4 +402,4 @@ body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } .mat-menu-panel.settings-theme-menu { margin-right: 64px; margin-top: -55px; -} +} \ No newline at end of file From 54874ba05ac04865a7f9171f1a99c723a541f3b5 Mon Sep 17 00:00:00 2001 From: F_Vinogradov Date: Thu, 12 Nov 2020 11:35:28 +0300 Subject: [PATCH 2/3] remove comments --- .../product-edit-form.component.scss | 15 +------------ .../templates/core/core-routing.module.ts | 4 ---- .../vector-map-page.component.ts | 19 +---------------- .../user/service/custom-date-formatter.ts | 4 ---- src/custom-material-styles.scss | 21 +++++++++---------- 5 files changed, 12 insertions(+), 51 deletions(-) diff --git a/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.scss b/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.scss index d018c53..4b6fb6c 100644 --- a/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.scss +++ b/src/app/modules/e-commerce/components/product-edit-form/product-edit-form.component.scss @@ -66,17 +66,4 @@ button.mat-flat-button + button.mat-flat-button { margin-left: 8px; -} - -//.option1{ -// background: url("https://upload.wikimedia.org/wikipedia/commons/f/f9/Phoenicopterus_ruber_in_S%C3%A3o_Paulo_Zoo.jpg") center / contain no-repeat; -// white-space: nowrap; -// display:inline -//} -// -// -//.option2{ -// background: url("src/assets/e-commerce/products/1.jpg") center / contain no-repeat; -// white-space: nowrap; -// display:inline; -//} +} \ No newline at end of file diff --git a/src/app/modules/templates/core/core-routing.module.ts b/src/app/modules/templates/core/core-routing.module.ts index 03b67a0..6f137ec 100644 --- a/src/app/modules/templates/core/core-routing.module.ts +++ b/src/app/modules/templates/core/core-routing.module.ts @@ -4,10 +4,6 @@ import { NgModule } from '@angular/core'; import { ColorsPageComponent, TypographyPageComponent, GridPageComponent } from './components'; const routes: Routes = [ - // { - // path: '', - // redirectTo: 'typography' - // }, { path: 'typography', component: TypographyPageComponent diff --git a/src/app/modules/templates/maps/components/vector-map-page/vector-map-page.component.ts b/src/app/modules/templates/maps/components/vector-map-page/vector-map-page.component.ts index b5f8fe0..57a4363 100644 --- a/src/app/modules/templates/maps/components/vector-map-page/vector-map-page.component.ts +++ b/src/app/modules/templates/maps/components/vector-map-page/vector-map-page.component.ts @@ -58,24 +58,7 @@ export class VectorMapPageComponent implements AfterViewInit { circle.fill = color(colors.BLUE); circle.stroke = color('#fff'); circle.strokeWidth = 2; - - // let circle2 = imageSeries.mapImages.template.createChild(am4core.Circle); - // circle2.radius = 3; - // circle2.propertyFields.fill = "color"; - // - // - // circle2.events.on("inited", function(event){ - // animateBullet(event.target); - // }) - // - // - // function animateBullet(circle) { - // let animation = circle.animate([{ property: "scale", from: 1, to: 5 }, { property: "opacity", from: 1, to: 0 }], 1000, am4core.ease.circleOut); - // animation.events.on("animationended", function(event){ - // animateBullet(event.target.object); - // }) - // } - + imageSeries.data = [ { "title": "Brussels", "latitude": 50.8371, diff --git a/src/app/modules/user/service/custom-date-formatter.ts b/src/app/modules/user/service/custom-date-formatter.ts index ca63f16..7acdbd9 100644 --- a/src/app/modules/user/service/custom-date-formatter.ts +++ b/src/app/modules/user/service/custom-date-formatter.ts @@ -9,8 +9,4 @@ export class CustomDateFormatter extends CalendarDateFormatter { public monthViewColumnHeader({ date, locale }: DateFormatterParams): string { return new DatePipe(locale).transform(date, 'EEEEE', locale); } - - // public monthViewTitle({ date, locale }: DateFormatterParams): string { - // return new DatePipe(locale).transform(date, 'MMM y', locale); - // } } diff --git a/src/custom-material-styles.scss b/src/custom-material-styles.scss index 377de20..785996c 100644 --- a/src/custom-material-styles.scss +++ b/src/custom-material-styles.scss @@ -1,13 +1,12 @@ -// .special-material-behaviour { - .mat-select-panel { - min-height: 500px; - .mat-option { - height: max-content !important; - .mat-option-text { - padding: 10px 0; - display: flex !important; - justify-content: center !important; - } +// TODO: need to be changed/removed, if you do not want these styles to applied to whole materials in application +.mat-select-panel { + min-height: 500px; + .mat-option { + height: max-content !important; + .mat-option-text { + padding: 10px 0; + display: flex !important; + justify-content: center !important; } } -// } +} From fc64dd9909f0b9a615374f75e2b1eec83ae93686 Mon Sep 17 00:00:00 2001 From: F_Vinogradov Date: Thu, 12 Nov 2020 13:44:30 +0300 Subject: [PATCH 3/3] refactor (adding finalize to pipes) --- .../management-page.component.ts | 1 - .../product-create-page.component.ts | 1 - .../product-edit-page.component.ts | 1 - .../product-page/product-page.component.ts | 1 - .../products-page/products-page.component.ts | 1 - .../e-commerce/services/product.service.ts | 90 +++++++++++-------- 6 files changed, 53 insertions(+), 42 deletions(-) diff --git a/src/app/modules/e-commerce/containers/management-page/management-page.component.ts b/src/app/modules/e-commerce/containers/management-page/management-page.component.ts index 9fae156..4340f31 100644 --- a/src/app/modules/e-commerce/containers/management-page/management-page.component.ts +++ b/src/app/modules/e-commerce/containers/management-page/management-page.component.ts @@ -26,7 +26,6 @@ export class ManagementPageComponent { this.dataSource.sort = this.sort; this.dataSource.paginator = this.paginator; this.products = products; - this.productService.finishGetProducts(); }); } diff --git a/src/app/modules/e-commerce/containers/product-create-page/product-create-page.component.ts b/src/app/modules/e-commerce/containers/product-create-page/product-create-page.component.ts index d46efe8..8169e7b 100644 --- a/src/app/modules/e-commerce/containers/product-create-page/product-create-page.component.ts +++ b/src/app/modules/e-commerce/containers/product-create-page/product-create-page.component.ts @@ -19,7 +19,6 @@ export class ProductCreatePageComponent { public createProduct(product: Product): void { this._productService.createProduct(product).subscribe(() => { - this._productService.finishCreatingProduct(); this.router.navigate([this.routes.MANAGEMENT]).then(); }); } diff --git a/src/app/modules/e-commerce/containers/product-edit-page/product-edit-page.component.ts b/src/app/modules/e-commerce/containers/product-edit-page/product-edit-page.component.ts index 42ae306..6e9691e 100644 --- a/src/app/modules/e-commerce/containers/product-edit-page/product-edit-page.component.ts +++ b/src/app/modules/e-commerce/containers/product-edit-page/product-edit-page.component.ts @@ -29,7 +29,6 @@ export class ProductEditPageComponent implements OnInit { public saveEditProduct(product: Product) { this._productService.updateProduct(product).subscribe(() => { - this._productService.finishUpdatingProduct(); this.router.navigate([this.routes.MANAGEMENT]).then(); }); } diff --git a/src/app/modules/e-commerce/containers/product-page/product-page.component.ts b/src/app/modules/e-commerce/containers/product-page/product-page.component.ts index 1a9b917..af3b8d4 100644 --- a/src/app/modules/e-commerce/containers/product-page/product-page.component.ts +++ b/src/app/modules/e-commerce/containers/product-page/product-page.component.ts @@ -30,7 +30,6 @@ export class ProductPageComponent { }); this.productService.getProducts().subscribe((products: Product[]) => { this.additionalProducts = products; - this.productService.finishGetProducts(); }) }); } diff --git a/src/app/modules/e-commerce/containers/products-page/products-page.component.ts b/src/app/modules/e-commerce/containers/products-page/products-page.component.ts index 707442b..abce40a 100644 --- a/src/app/modules/e-commerce/containers/products-page/products-page.component.ts +++ b/src/app/modules/e-commerce/containers/products-page/products-page.component.ts @@ -17,7 +17,6 @@ export class ProductsPageComponent implements OnInit { constructor(public productService: ProductService) { this.productService.getProducts().subscribe((products: Product[]) => { this.products = products; - this.productService.finishGetProducts(); }); } diff --git a/src/app/modules/e-commerce/services/product.service.ts b/src/app/modules/e-commerce/services/product.service.ts index c7a9c47..aa7b742 100644 --- a/src/app/modules/e-commerce/services/product.service.ts +++ b/src/app/modules/e-commerce/services/product.service.ts @@ -1,9 +1,9 @@ -import {Injectable} from '@angular/core'; -import {from, Observable, of} from 'rxjs'; +import { Injectable } from '@angular/core'; +import { from, Observable, of } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { Router } from '@angular/router'; import { AppConfig } from 'src/app/app.config'; -import { flatMap, map, share, toArray } from 'rxjs/operators'; +import { finalize, flatMap, map, share, toArray } from 'rxjs/operators'; import { string } from '@amcharts/amcharts4/core'; export class Product { @@ -50,7 +50,7 @@ export class ProductService { _isUpdating: boolean = false; _isDeleting: boolean = false; _isRetrievingProductImage: boolean = false; - + public products$: Observable; public product$: Observable; @@ -61,13 +61,13 @@ export class ProductService { ) { this.config = appConfig.getConfig(); } - + public setImgField(product: any) { - if(!product) return {}; + if (!product) return {}; let imgString = ''; let resourceApi = (this.config.resourceApi) ? this.config.resourceApi : ''; - if(product.img) { + if (product.img) { imgString = product.img; } @@ -76,33 +76,35 @@ export class ProductService { img: resourceApi + imgString } } - + getProducts(isForced: boolean = false) { this.startGetProducts(); this.products$ = this.http.get>('/products') - .pipe( + .pipe( flatMap(data => data), map(product => this.setImgField(product)), toArray(), - share() - ); + share(), + finalize(() => this.finishGetProducts()) + ); return this.products$; } getProduct(id, isForced: boolean = false) { - this.finishGetProducts(); + this.startGetProducts(); return this.product$ = this.http.get('/products/' + id) - .pipe( - map(product => this.setImgField(product)), - share() - ); + .pipe( + map(product => this.setImgField(product)), + share(), + finalize(() => this.finishGetProducts()) + ); } updateProduct(payload: Product) { - if(!payload) return; + if (!payload) return; - if(!Array.isArray(payload.technology)) { - if(typeof(payload.technology) === "string") { + if (!Array.isArray(payload.technology)) { + if (typeof (payload.technology) === "string") { payload.technology = (payload.technology as string).split(','); } else { payload.technology = []; @@ -110,14 +112,17 @@ export class ProductService { } this.startUpdatingProduct(); - return this.http.put('/products/' + payload.id, payload); + return this.http.put('/products/' + payload.id, payload) + .pipe( + finalize(() => this.finishUpdatingProduct()) + ); } createProduct(payload: Product) { - if(!payload) return; + if (!payload) return; - if(!Array.isArray(payload.technology)) { - if(typeof(payload.technology) === "string") { + if (!Array.isArray(payload.technology)) { + if (typeof (payload.technology) === "string") { payload.technology = (payload.technology as string).split(','); } else { payload.technology = []; @@ -125,28 +130,35 @@ export class ProductService { } this.startCreatingProduct(); - return this.http.post('/products', payload); + return this.http.post('/products', payload) + .pipe( + finalize(() => this.finishCreatingProduct()) + ); } deleteProduct(id) { this.startDeletingProduct(); - return this.http.delete('/products/' + id); + return this.http.delete('/products/' + id) + .pipe( + finalize(() => this.finishDeletingProduct()) + ); } getProductImages() { this.startRetrievingProductImages() return this.http.get>('/products/images-list') - .pipe( - flatMap(data => data), - map(img => { - if(!img) return ''; - - let resourceApi = (this.config.resourceApi) ? this.config.resourceApi : ''; - return `${resourceApi}${img}`; - }), - toArray(), - share() - ); + .pipe( + flatMap(data => data), + map(img => { + if (!img) return ''; + + let resourceApi = (this.config.resourceApi) ? this.config.resourceApi : ''; + return `${resourceApi}${img}`; + }), + toArray(), + share(), + finalize(() => this.finishRetrievingProductImages()) + ); } startGetProducts() { @@ -156,7 +168,7 @@ export class ProductService { startRetrievingProductImages() { this.isRetrievingProductImage = true; } - + startUpdatingProduct() { this.isUpdating = true; } @@ -185,6 +197,10 @@ export class ProductService { this.isCreating = false; } + finishDeletingProduct() { + this.isDeleting = false; + } + get isCreating() { return this._isCreating; }