diff --git a/angular.json b/angular.json index fb6d6d1..2fab785 100644 --- a/angular.json +++ b/angular.json @@ -40,7 +40,7 @@ "vendorChunk": true, "extractLicenses": false, "buildOptimizer": false, - "sourceMap": false, + "sourceMap": true, "optimization": false, "namedChunks": true }, diff --git a/package.json b/package.json index b93082c..572b917 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,16 @@ "version": "0.0.0", "scripts": { "ng": "ng", - "start": "ng serve --proxy-config proxy.conf.json", + "start": "ng serve --host 0.0.0.0 --proxy-config proxy.conf.json", "build": "ng build", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" }, "private": true, + "browser": { + "fs": false + }, "dependencies": { "@angular/animations": "~12.2.14", "@angular/cdk": "^12.2.13", @@ -21,10 +24,11 @@ "@angular/platform-browser": "~12.2.14", "@angular/platform-browser-dynamic": "~12.2.14", "@angular/router": "~12.2.14", + "@js-joda/core": "^4.3.1", "@mdi/angular-material": "^6.5.95", "bootstrap": "^4.5.2", "copy-webpack-plugin": "^10.0.0", - "@js-joda/core": "^4.3.1", + "jwt-decode": "^3.1.2", "material-design-icons": "^3.0.1", "ngx-material-file-input": "^2.1.1", "rxjs": "^7.4.0", diff --git a/src/app/modules/accounts/accounts-routing.module.ts b/src/app/modules/accounts/accounts-routing.module.ts index 843486b..006c93f 100644 --- a/src/app/modules/accounts/accounts-routing.module.ts +++ b/src/app/modules/accounts/accounts-routing.module.ts @@ -1,10 +1,18 @@ -import {NgModule} from '@angular/core'; -import {Routes, RouterModule} from '@angular/router'; +import {NgModule} from '@angular/core' +import {RouterModule, Routes} from '@angular/router' +import {LogoutComponent} from './pages/logout/logout.component' +import {Login} from './accounts' -import {LoginComponent} from './pages/login/login.component'; -import {LogoutComponent} from "./pages/logout/logout.component"; - -const routes: Routes = [{path: 'login', component: LoginComponent}, {path: 'logout', component: LogoutComponent}, {path: '', redirectTo: 'login'}]; +const routes: Routes = [{ + path: 'login', + component: Login +}, { + path: 'logout', + component: LogoutComponent +}, { + path: '', + redirectTo: 'login' +}] @NgModule({ imports: [RouterModule.forChild(routes)], diff --git a/src/app/modules/accounts/accounts.module.ts b/src/app/modules/accounts/accounts.module.ts index a935cd7..c8a05ec 100644 --- a/src/app/modules/accounts/accounts.module.ts +++ b/src/app/modules/accounts/accounts.module.ts @@ -1,18 +1,25 @@ -import {NgModule} from '@angular/core'; +import {NgModule} from '@angular/core' -import {AccountsRoutingModule} from './accounts-routing.module'; -import {LoginComponent} from './pages/login/login.component'; -import {SharedModule} from "../shared/shared.module"; -import {LogoutComponent} from './pages/logout/logout.component'; -import {CommonModule} from "@angular/common"; -import {BrowserModule} from "@angular/platform-browser"; +import {AccountsRoutingModule} from './accounts-routing.module' +import {LoginComponent} from './pages/login/login.component' +import {SharedModule} from '../shared/shared.module' +import {LogoutComponent} from './pages/logout/logout.component' +import {Login} from './accounts' +import {CreInputsModule} from '../shared/components/inputs/inputs.module' +import {CreButtonsModule} from '../shared/components/buttons/buttons.module' @NgModule({ - declarations: [LoginComponent, LogoutComponent], + declarations: [ + LoginComponent, + LogoutComponent, + Login + ], imports: [ SharedModule, AccountsRoutingModule, + CreInputsModule, + CreButtonsModule, ] }) export class AccountsModule { diff --git a/src/app/modules/accounts/accounts.ts b/src/app/modules/accounts/accounts.ts new file mode 100644 index 0000000..10f850b --- /dev/null +++ b/src/app/modules/accounts/accounts.ts @@ -0,0 +1,63 @@ +import {Component, HostListener, ViewChild} from '@angular/core' +import {FormControl, Validators} from '@angular/forms' +import {ErrorHandlingComponent} from '../shared/components/subscribing.component' +import {AccountService} from './services/account.service' +import {AppState} from '../shared/app-state' +import {ErrorHandler, ErrorService} from '../shared/service/error.service' +import {ActivatedRoute, Router} from '@angular/router' +import {CreForm, ICreForm} from "../shared/components/forms/forms"; +import {take, takeUntil} from "rxjs/operators"; +import {AlertService} from "../shared/service/alert.service"; + +@Component({ + selector: 'cre-login', + templateUrl: 'login.html', + styles: [ + 'cre-form { min-width: 25rem; margin-top: 50vh; transform: translateY(-70%) }' + ] +}) +export class Login extends ErrorHandlingComponent { + @ViewChild(CreForm) form: ICreForm + + userIdControl = new FormControl(null, Validators.compose([Validators.required, Validators.pattern(new RegExp('^[0-9]+$'))])) + passwordControl = new FormControl(null, Validators.required) + + errorHandlers: ErrorHandler[] = [{ + filter: error => error.status === 403, + messageProducer: () => 'Les identifiants entrés sont invalides' + }] + + constructor( + private accountService: AccountService, + private alertService: AlertService, + private appState: AppState, + errorService: ErrorService, + router: Router, + activatedRoute: ActivatedRoute + ) { + super(errorService, activatedRoute, router) + this.appState.title = 'Connexion' + } + + // Allows to send the form by pressing Enter + @HostListener('window:keyup.enter', ['$event']) + onEnterKeyEvent() { + if (this.form.formGroup) { + this.submit() + } + } + + submit() { + this.subscribeAndNavigate( + this.accountService.login(this.userIdControl.value, this.passwordControl.value), + '/color/list' + ) + } + + get controls(): { userId: FormControl, password: FormControl } { + return { + userId: this.userIdControl, + password: this.passwordControl + } + } +} diff --git a/src/app/modules/accounts/login.html b/src/app/modules/accounts/login.html new file mode 100644 index 0000000..7ec17e8 --- /dev/null +++ b/src/app/modules/accounts/login.html @@ -0,0 +1,28 @@ + + Connexion au système + + + + Le numéro d'utilisateur doit être un nombre + + + + + + + + + Connexion + + + diff --git a/src/app/modules/accounts/pages/login/login.component.ts b/src/app/modules/accounts/pages/login/login.component.ts index 1802a7c..919bed0 100644 --- a/src/app/modules/accounts/pages/login/login.component.ts +++ b/src/app/modules/accounts/pages/login/login.component.ts @@ -31,7 +31,7 @@ export class LoginComponent extends ErrorHandlingComponent implements OnInit { ngOnInit(): void { this.errorService.activeErrorHandler = this - if (this.accountService.isLoggedIn()) { + if (this.appState.isAuthenticated) { this.router.navigate(['/color']) } @@ -44,10 +44,12 @@ export class LoginComponent extends ErrorHandlingComponent implements OnInit { } submit() { - this.accountService.login( - this.idFormControl.value, - this.passwordFormControl.value, - () => this.router.navigate(['/color']) + this.subscribe( + this.accountService.login( + this.idFormControl.value, + this.passwordFormControl.value + ), + response => console.log(response) ) } } diff --git a/src/app/modules/accounts/pages/logout/logout.component.ts b/src/app/modules/accounts/pages/logout/logout.component.ts index 8b180bd..a1433c1 100644 --- a/src/app/modules/accounts/pages/logout/logout.component.ts +++ b/src/app/modules/accounts/pages/logout/logout.component.ts @@ -1,28 +1,35 @@ -import {Component, OnInit} from '@angular/core'; +import {Component} from '@angular/core'; import {AccountService} from "../../services/account.service"; -import {Router} from "@angular/router"; +import {ActivatedRoute, Router} from "@angular/router"; +import {AppState} from "../../../shared/app-state"; +import {SubscribingComponent} from "../../../shared/components/subscribing.component"; +import {ErrorService} from "../../../shared/service/error.service"; @Component({ selector: 'cre-logout', templateUrl: './logout.component.html', styleUrls: ['./logout.component.sass'] }) -export class LogoutComponent implements OnInit { +export class LogoutComponent extends SubscribingComponent { constructor( private accountService: AccountService, - private router: Router + private appState: AppState, + errorService: ErrorService, + router: Router, + activatedRoute: ActivatedRoute ) { + super(errorService, activatedRoute, router) } ngOnInit(): void { - if (!this.accountService.isLoggedIn()) { - this.router.navigate(['/account/login']) + if (!this.appState.isAuthenticated) { + this.urlUtils.navigateTo('/account/login') } - this.accountService.logout(() => { - this.router.navigate(['/account/login']) - }) + this.subscribeAndNavigate( + this.accountService.logout(), + '/account/login' + ) } - } diff --git a/src/app/modules/accounts/services/account.service.ts b/src/app/modules/accounts/services/account.service.ts index dbf3362..066973a 100644 --- a/src/app/modules/accounts/services/account.service.ts +++ b/src/app/modules/accounts/services/account.service.ts @@ -1,14 +1,14 @@ import {Injectable, OnDestroy} from '@angular/core' -import {Subject} from 'rxjs' +import {Observable, Subject} from 'rxjs' import {take, takeUntil} from 'rxjs/operators' import {AppState} from '../../shared/app-state' import {HttpClient, HttpResponse} from '@angular/common/http' import {environment} from '../../../../environments/environment' import {ApiService} from '../../shared/service/api.service' -import {User, Permission} from '../../shared/model/user' +import {Permission, User} from '../../shared/model/user' import {ErrorService} from '../../shared/service/error.service' -import {globalLoadingWheel} from '../../shared/components/loading-wheel/loading-wheel.component' import {AlertService} from '../../shared/service/alert.service' +import {JwtService} from "./jwt.service"; @Injectable({ providedIn: 'root' @@ -20,6 +20,7 @@ export class AccountService implements OnDestroy { private http: HttpClient, private api: ApiService, private appState: AppState, + private jwtService: JwtService, private errorService: ErrorService, private alertService: AlertService ) { @@ -30,20 +31,16 @@ export class AccountService implements OnDestroy { this.destroy$.complete() } - isLoggedIn(): boolean { - return this.appState.isAuthenticated - } - checkAuthenticationStatus() { - if (!this.appState.authenticatedUser) { + if (!this.appState.isAuthenticated) { // Try to get current default group user - this.http.get(`${environment.apiUrl}/user/current`, {withCredentials: true}) + this.http.get(`${environment.apiUrl}/user/group/currentuser`, {withCredentials: true}) .pipe( take(1), takeUntil(this.destroy$), ).subscribe( { - next: user => this.appState.authenticatedUser = user, + next: user => this.appState.authenticateGroupUser(user), error: err => { if (err.status === 404 || err.status === 403) { console.warn('No default user is defined on this computer') @@ -55,67 +52,74 @@ export class AccountService implements OnDestroy { } } - login(id: number, password: string, success: () => void) { - const loginForm = {id, password} - globalLoadingWheel.show() - this.http.post(`${environment.apiUrl}/login`, loginForm, { + login(userId: number, password: string): Observable { + const subject = new Subject() + + this.http.post(`${environment.apiUrl}/login`, {id: userId, password}, { withCredentials: true, observe: 'response' as 'body' - }) - .pipe( - take(1), - takeUntil(this.destroy$) - ) - .subscribe({ - next: (response: HttpResponse) => { - this.appState.authenticationExpiration = parseInt(response.headers.get('X-Authentication-Expiration')) - this.appState.isAuthenticated = true - this.setLoggedInUserFromApi() - success() - }, - error: err => { - globalLoadingWheel.hide() - if (err.status === 401 || err.status === 403) { - this.alertService.pushError('Les identifiants entrés sont invalides') - } else { - this.errorService.handleError(err) - } - } - }) - } - - logout(success: () => void) { - this.api.get('/logout', true).pipe( + }).pipe( take(1), takeUntil(this.destroy$) - ) - .subscribe({ - next: () => { - this.appState.resetAuthenticatedUser() - this.checkAuthenticationStatus() - success() - }, - error: err => this.errorService.handleError(err) - }) + ).subscribe({ + next: (response: HttpResponse) => { + this.loginUser(response) + + subject.next() + subject.complete() + }, + error: error => { + if (error.status === 403) { + this.alertService.pushError('Les identifiants entrés sont invalides') + } else { + this.errorService.handleError(error) + } + + subject.next() + subject.complete() + } + }) + + return subject + } + + private loginUser(response: HttpResponse) { + const authorization = response.headers.get("Authorization") + const user = this.jwtService.parseUser(authorization) + + this.appState.authenticateUser(user) + } + + logout(): Observable { + const subject = new Subject() + + this.api.get('/logout').pipe( + take(1), + takeUntil(this.destroy$) + ).subscribe({ + next: () => { + this.logoutUser() + + subject.next() + subject.complete() + }, + error: error => { + this.errorService.handleError(error) + + subject.next() + subject.complete() + } + }) + + return subject + } + + private logoutUser() { + this.appState.resetAuthenticatedUser() + this.checkAuthenticationStatus() } hasPermission(permission: Permission): boolean { return this.appState.authenticatedUser && this.appState.authenticatedUser.permissions.indexOf(permission) >= 0 } - - private setLoggedInUserFromApi() { - this.api.get('/user/current', true) - .pipe( - take(1), - takeUntil(this.destroy$) - ) - .subscribe({ - next: user => { - this.appState.authenticatedUser = user - // At this point the loading wheel should be visible - globalLoadingWheel.hide() - }, - error: err => this.errorService.handleError(err) - }) - } } diff --git a/src/app/modules/accounts/services/jwt.service.ts b/src/app/modules/accounts/services/jwt.service.ts new file mode 100644 index 0000000..6f67366 --- /dev/null +++ b/src/app/modules/accounts/services/jwt.service.ts @@ -0,0 +1,13 @@ +import {Injectable} from "@angular/core"; +import jwtDecode from "jwt-decode"; +import { User } from "../../shared/model/user"; + +@Injectable({ + providedIn: 'root' +}) +export class JwtService { + parseUser(jwt: string): User { + const decoded = jwtDecode(jwt) as any + return JSON.parse(decoded.user) + } +} diff --git a/src/app/modules/company/pages/list/list.component.html b/src/app/modules/company/pages/list/list.component.html index 1fc8be5..944d7d2 100644 --- a/src/app/modules/company/pages/list/list.component.html +++ b/src/app/modules/company/pages/list/list.component.html @@ -1,6 +1,6 @@ - Ajouter + Ajouter diff --git a/src/app/modules/material-type/pages/list/list.component.html b/src/app/modules/material-type/pages/list/list.component.html index bf32816..6555e6a 100644 --- a/src/app/modules/material-type/pages/list/list.component.html +++ b/src/app/modules/material-type/pages/list/list.component.html @@ -1,6 +1,6 @@ - Ajouter + Ajouter diff --git a/src/app/modules/recipes/add.html b/src/app/modules/recipes/add.html index aa827e1..a4eec0d 100644 --- a/src/app/modules/recipes/add.html +++ b/src/app/modules/recipes/add.html @@ -3,7 +3,7 @@ Retour - + diff --git a/src/app/modules/recipes/recipes.ts b/src/app/modules/recipes/recipes.ts index bdc619e..e1b6410 100644 --- a/src/app/modules/recipes/recipes.ts +++ b/src/app/modules/recipes/recipes.ts @@ -46,7 +46,7 @@ export class RecipeForm extends SubscribingComponent { } ngOnInit() { - super.ngOnInit(); + super.ngOnInit() this.fetchCompanies() diff --git a/src/app/modules/shared/app-state.ts b/src/app/modules/shared/app-state.ts index f850b3e..5b1c93e 100644 --- a/src/app/modules/shared/app-state.ts +++ b/src/app/modules/shared/app-state.ts @@ -8,7 +8,7 @@ import {Title} from '@angular/platform-browser' }) export class AppState { private readonly KEY_AUTHENTICATED = 'authenticated' - private readonly KEY_AUTHENTICATION_EXPIRATION = 'authentication-expiration' + private readonly KEY_DEFAULT_GROUP_USER_AUTHENTICATED = 'default-group-user-authenticated' private readonly KEY_LOGGED_IN_USER = 'logged-in-user' authenticatedUser$ = new Subject<{ authenticated: boolean, authenticatedUser: User }>() @@ -19,9 +19,19 @@ export class AppState { ) { } + authenticateUser(user: User) { + this.authenticatedUser = user + this.isAuthenticated = true + } + + authenticateGroupUser(user: User) { + this.authenticatedUser = user + this.isDefaultGroupUserAuthenticated = true + } + resetAuthenticatedUser() { this.isAuthenticated = false - this.authenticationExpiration = -1 + this.isDefaultGroupUserAuthenticated = false this.authenticatedUser = null } @@ -36,7 +46,7 @@ export class AppState { return sessionStorage.getItem(this.KEY_AUTHENTICATED) === 'true' } - set isAuthenticated(value: boolean) { + private set isAuthenticated(value: boolean) { sessionStorage.setItem(this.KEY_AUTHENTICATED, value.toString()) this.authenticatedUser$.next({ authenticated: value, @@ -44,12 +54,16 @@ export class AppState { }) } - get authenticationExpiration(): number { - return parseInt(sessionStorage.getItem(this.KEY_AUTHENTICATION_EXPIRATION)) + get isDefaultGroupUserAuthenticated(): boolean { + return sessionStorage.getItem(this.KEY_DEFAULT_GROUP_USER_AUTHENTICATED) === 'true' } - set authenticationExpiration(value: number) { - sessionStorage.setItem(this.KEY_AUTHENTICATION_EXPIRATION, value.toString()) + private set isDefaultGroupUserAuthenticated(value: boolean) { + sessionStorage.setItem(this.KEY_DEFAULT_GROUP_USER_AUTHENTICATED, value.toString()) + } + + get hasCredentials(): boolean { + return this.isAuthenticated || this.isDefaultGroupUserAuthenticated } get authenticatedUser(): User { @@ -57,9 +71,9 @@ export class AppState { return userString ? JSON.parse(userString) : null } - set authenticatedUser(value: User) { + private set authenticatedUser(value: User) { if (value === null) { - sessionStorage.removeItem(this.KEY_LOGGED_IN_USER) + // sessionStorage.removeItem(this.KEY_LOGGED_IN_USER) } else { sessionStorage.setItem(this.KEY_LOGGED_IN_USER, JSON.stringify(value)) } diff --git a/src/app/modules/shared/components/forms/buttons.ts b/src/app/modules/shared/components/forms/buttons.ts index 0cebdc4..ed1da16 100644 --- a/src/app/modules/shared/components/forms/buttons.ts +++ b/src/app/modules/shared/components/forms/buttons.ts @@ -1,13 +1,14 @@ -import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {Component, ContentChild, EventEmitter, Input, Output} from '@angular/core' import {ICreForm} from './forms'; @Component({ - selector: 'cre-submit-button', + selector: 'cre-form-submit-button', templateUrl: 'submit-button.html' }) export class CreSubmitButton { @Input() form: ICreForm @Input() valid: boolean | null + @Input() text = 'Enregistrer' @Output() submit = new EventEmitter() diff --git a/src/app/modules/shared/components/forms/form.html b/src/app/modules/shared/components/forms/form.html index 1d6e89b..4b966b8 100644 --- a/src/app/modules/shared/components/forms/form.html +++ b/src/app/modules/shared/components/forms/form.html @@ -5,7 +5,7 @@ -
+
diff --git a/src/app/modules/shared/components/forms/forms.ts b/src/app/modules/shared/components/forms/forms.ts index 7619f15..ae2f5d3 100644 --- a/src/app/modules/shared/components/forms/forms.ts +++ b/src/app/modules/shared/components/forms/forms.ts @@ -2,7 +2,7 @@ import {Component, ContentChild, Directive, Input, OnInit, ViewEncapsulation} fr import {FormBuilder, FormGroup} from '@angular/forms' export interface ICreForm { - form: FormGroup + formGroup: FormGroup valid: boolean invalid: boolean } @@ -35,7 +35,7 @@ export class CreForm implements ICreForm, OnInit { @ContentChild(CreFormActions) formActions: CreFormActions @Input() formControls: { [key: string]: any } - form: FormGroup + formGroup: FormGroup constructor( private formBuilder: FormBuilder @@ -43,7 +43,7 @@ export class CreForm implements ICreForm, OnInit { } ngOnInit(): void { - this.form = this.formBuilder.group(this.formControls) + this.formGroup = this.formBuilder.group(this.formControls) } get hasActions(): boolean { @@ -51,10 +51,10 @@ export class CreForm implements ICreForm, OnInit { } get valid(): boolean { - return this.form && this.form.valid + return this.formGroup && this.formGroup.valid } get invalid(): boolean { - return !this.form || this.form.invalid + return !this.formGroup || this.formGroup.invalid } } diff --git a/src/app/modules/shared/components/forms/submit-button.html b/src/app/modules/shared/components/forms/submit-button.html index 8e9289a..3d00a97 100644 --- a/src/app/modules/shared/components/forms/submit-button.html +++ b/src/app/modules/shared/components/forms/submit-button.html @@ -1 +1 @@ -Enregistrer +{{text}} diff --git a/src/app/modules/shared/components/header/header.component.ts b/src/app/modules/shared/components/header/header.component.ts index 6d113fd..abe4a17 100644 --- a/src/app/modules/shared/components/header/header.component.ts +++ b/src/app/modules/shared/components/header/header.component.ts @@ -58,9 +58,10 @@ export class HeaderComponent extends SubscribingComponent { } ngOnDestroy(): void { - this.accountService.logout(() => { - console.log('Successfully logged out') - }) + this.subscribe( + this.accountService.logout(), + () => console.info('Successfully logged out') + ) super.ngOnDestroy() } @@ -71,7 +72,7 @@ export class HeaderComponent extends SubscribingComponent { set activeLink(link: string) { this._activeLink = link - this.router.navigate([link]) + this.urlUtils.navigateTo(link) } get activeLink() { diff --git a/src/app/modules/shared/components/inputs/inputs.ts b/src/app/modules/shared/components/inputs/inputs.ts index cfca06f..7b53fef 100644 --- a/src/app/modules/shared/components/inputs/inputs.ts +++ b/src/app/modules/shared/components/inputs/inputs.ts @@ -1,5 +1,6 @@ import { - AfterViewInit, ChangeDetectorRef, + AfterViewInit, + ChangeDetectorRef, Component, ContentChild, Directive, @@ -16,7 +17,7 @@ import { import {AbstractControl, FormControl, ValidationErrors, ValidatorFn, Validators} from '@angular/forms' import {COMMA, ENTER} from '@angular/cdk/keycodes' import {isObservable, Observable, Subject} from 'rxjs' -import {map, startWith, takeUntil} from 'rxjs/operators' +import {map, takeUntil} from 'rxjs/operators' import {MatChipInputEvent} from '@angular/material/chips' import {MatAutocomplete, MatAutocompleteSelectedEvent} from '@angular/material/autocomplete' diff --git a/src/app/modules/shared/service/api.service.ts b/src/app/modules/shared/service/api.service.ts index 7c39b28..462531b 100644 --- a/src/app/modules/shared/service/api.service.ts +++ b/src/app/modules/shared/service/api.service.ts @@ -4,10 +4,9 @@ import {Observable, Subject} from 'rxjs' import {environment} from '../../../../environments/environment' import {AppState} from '../app-state' import {Router} from '@angular/router' -import {map, share, takeUntil, tap} from 'rxjs/operators' +import {map, share, takeUntil} from 'rxjs/operators' import {valueOr} from '../utils/utils' import {ErrorService} from './error.service' -import {globalLoadingWheel} from '../components/loading-wheel/loading-wheel.component' @Injectable({ providedIn: 'root' @@ -71,14 +70,13 @@ export class ApiService implements OnDestroy { observe: 'response' } if (needAuthentication) { - if (this.checkAuthenticated()) { + if (this.appState.hasCredentials) { if (httpOptions) { httpOptions.withCredentials = true } else { console.error('httpOptions need to be specified to use credentials in HTTP methods.') } } else { - this.appState.resetAuthenticatedUser() this.navigateToLogin() } } @@ -90,11 +88,6 @@ export class ApiService implements OnDestroy { .pipe(takeUntil(this._destroy$), map(r => r.body), share()) } - private checkAuthenticated(): boolean { - return (this.appState.isAuthenticated && Date.now() <= this.appState.authenticationExpiration) || - (this.appState.authenticatedUser && this.appState.authenticatedUser.group != null) - } - private navigateToLogin() { this.router.navigate(['/account/login']) } diff --git a/src/app/modules/touch-up-kit/pages/list.html b/src/app/modules/touch-up-kit/pages/list.html index 877aa6d..f5bacdf 100644 --- a/src/app/modules/touch-up-kit/pages/list.html +++ b/src/app/modules/touch-up-kit/pages/list.html @@ -1,7 +1,7 @@ - Ajouter + Ajouter