From 11a8b6b14c0bd8a95e8583148227c9ccea4f402f Mon Sep 17 00:00:00 2001 From: clfreville2 Date: Sat, 29 Jun 2024 15:15:39 +0200 Subject: [PATCH] Add login and logout routes --- src/app/app.routes.ts | 9 +++- src/app/auth/auth.component.html | 19 ++++++++ src/app/auth/auth.component.spec.ts | 23 +++++++++ src/app/auth/auth.component.ts | 48 +++++++++++++++++++ src/app/login.service.ts | 56 ++++++++++++++++++++++ src/app/recipe-add/recipe-add.component.ts | 27 +++++++---- src/app/recipe.service.ts | 20 +++++--- src/app/recipe/recipe.component.ts | 2 +- src/cookbook/type.ts | 6 +++ 9 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 src/app/auth/auth.component.html create mode 100644 src/app/auth/auth.component.spec.ts create mode 100644 src/app/auth/auth.component.ts create mode 100644 src/app/login.service.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 775fbbb..1180ca1 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -1,11 +1,18 @@ -import { Routes } from '@angular/router'; +import { inject } from '@angular/core'; +import { CanActivateFn, Routes } from '@angular/router'; +import { AuthComponent } from './auth/auth.component'; +import { LoginService } from './login.service'; import { RecipeAddComponent } from './recipe-add/recipe-add.component'; import { RecipeComponent } from './recipe/recipe.component'; import { RecipesComponent } from './recipes/recipes.component'; +const LoggedGuard: CanActivateFn = () => inject(LoginService).isLoggedIn(); export const routes: Routes = [ { path: 'recipes', component: RecipesComponent }, { path: 'recipe/add', component: RecipeAddComponent }, { path: 'recipe/:id', component: RecipeComponent }, { path: 'recipe/:id/edit', component: RecipeAddComponent }, + { path: 'ingredients', component: RecipesComponent, canActivate: [LoggedGuard] }, + { path: 'login', component: AuthComponent }, + { path: 'logout', component: AuthComponent, data: { registering: false }, canActivate: [LoggedGuard] }, ]; diff --git a/src/app/auth/auth.component.html b/src/app/auth/auth.component.html new file mode 100644 index 0000000..135e858 --- /dev/null +++ b/src/app/auth/auth.component.html @@ -0,0 +1,19 @@ + + Login + +
+
+ + + +
+ +
+ + + +
+ +
+
+
diff --git a/src/app/auth/auth.component.spec.ts b/src/app/auth/auth.component.spec.ts new file mode 100644 index 0000000..e597a45 --- /dev/null +++ b/src/app/auth/auth.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AuthComponent } from './auth.component'; + +describe('AuthComponent', () => { + let component: AuthComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AuthComponent], + }) + .compileComponents(); + + fixture = TestBed.createComponent(AuthComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/auth/auth.component.ts b/src/app/auth/auth.component.ts new file mode 100644 index 0000000..7a3ca95 --- /dev/null +++ b/src/app/auth/auth.component.ts @@ -0,0 +1,48 @@ +import { Component } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { MatCard, MatCardContent, MatCardTitle } from '@angular/material/card'; +import { MatFormField } from '@angular/material/form-field'; +import { MatInput } from '@angular/material/input'; +import { ActivatedRoute, Router } from '@angular/router'; +import { LoginService } from '../login.service'; + +@Component({ + selector: 'app-auth', + standalone: true, + imports: [ReactiveFormsModule, MatCard, MatCardTitle, MatCardContent, MatFormField, MatInput], + templateUrl: './auth.component.html', +}) +export class AuthComponent { + loginForm = this.formBuilder.group({ + login: '', + password: '', + }); + + constructor( + private loginService: LoginService, + private formBuilder: FormBuilder, + private router: Router, + activatedRoute: ActivatedRoute, + ) { + activatedRoute.data.subscribe(({ registering }) => { + if (!registering) { + loginService.logOut(); + } + }); + } + + onSubmit(): void { + if (this.loginForm.invalid) { + return; + } + const value = this.loginForm.value; + this.loginService.logIn(value.login!, value.password!) + .then((logged) => { + if (logged) { + this.router.navigateByUrl('/ingredients'); + } else { + alert('Invalid login!'); + } + }); + } +} diff --git a/src/app/login.service.ts b/src/app/login.service.ts new file mode 100644 index 0000000..c21beb0 --- /dev/null +++ b/src/app/login.service.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@angular/core'; +import { User } from '../cookbook/type'; + +@Injectable({ + providedIn: 'root', +}) +export class LoginService { + async logIn(username: string, password: string): Promise { + const res = await fetch('https://dummyjson.com/user/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify({ + username, + password, + expiresInMins: 30, + }), + }); + if (res.status !== 200) { + return false; + } + const user: User = await res.json(); + localStorage.setItem('user', JSON.stringify(user)); + return true; + } + + me(): Promise { + const token = this.currentToken(); + if (!token) { + return Promise.resolve(null); + } + return fetch('http://dummyjson.com/user/me', { + method: 'GET', + headers: { + 'Authorization': `Bearer ${token}`, + }, + }) + .then(res => res.json()); + } + + isLoggedIn(): boolean { + return this.currentToken() !== null; + } + + currentToken(): string | null { + const json = localStorage?.getItem('user'); + if (!json) return null; + return JSON.parse(json).token; + } + + logOut(): void { + localStorage?.removeItem('user'); + } +} diff --git a/src/app/recipe-add/recipe-add.component.ts b/src/app/recipe-add/recipe-add.component.ts index 1e0e2d7..9e23cb2 100644 --- a/src/app/recipe-add/recipe-add.component.ts +++ b/src/app/recipe-add/recipe-add.component.ts @@ -1,18 +1,27 @@ import { Component, Input } from '@angular/core'; import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; -import { Router } from '@angular/router'; -import { Ingredient, IngredientEntry, Recipe } from '../../cookbook/type'; -import { RecipeService } from '../recipe.service'; -import { MatInputModule } from '@angular/material/input'; -import { MatFormFieldModule } from '@angular/material/form-field'; import { MatButton } from '@angular/material/button'; import { MatOption } from '@angular/material/core'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; import { MatSelect } from '@angular/material/select'; +import { Router } from '@angular/router'; +import { Ingredient, IngredientEntry, Recipe } from '../../cookbook/type'; +import { RecipeService } from '../recipe.service'; @Component({ selector: 'app-recipe-add', standalone: true, - imports: [ReactiveFormsModule, FormsModule, MatFormFieldModule, MatOption, MatSelect, MatInputModule, MatButton, MatFormFieldModule], + imports: [ + ReactiveFormsModule, + FormsModule, + MatFormFieldModule, + MatOption, + MatSelect, + MatInputModule, + MatButton, + MatFormFieldModule, + ], templateUrl: './recipe-add.component.html', }) export class RecipeAddComponent { @@ -28,7 +37,7 @@ export class RecipeAddComponent { selectedFilename: string = ''; getIngredient(n: number): Ingredient { - return this.ingredients.find(v => v.id === n)! + return this.ingredients.find(v => v.id === n)!; } #recipeId: number = -1; @@ -98,9 +107,9 @@ export class RecipeAddComponent { const reader = new FileReader(); reader.onload = (event) => { this.createForm.patchValue({ - image: event.target!.result?.toString() + image: event.target!.result?.toString(), }); - } + }; reader.readAsDataURL(file); } } diff --git a/src/app/recipe.service.ts b/src/app/recipe.service.ts index 4b40f6a..eea5f4e 100644 --- a/src/app/recipe.service.ts +++ b/src/app/recipe.service.ts @@ -6,10 +6,16 @@ import { Ingredient, Recipe } from '../cookbook/type'; }) export class RecipeService { #recipes: Recipe[] = [ - { id: 0, name: 'crepe1', description: 'La meilleure recette de pâte à crêpes', image: '', ingredients: [ - {idIngredient:1,idRecipe:0,quantity:10}, - {idIngredient:2,idRecipe:0,quantity:15} - ] }, + { + id: 0, + name: 'crepe1', + description: 'La meilleure recette de pâte à crêpes', + image: '', + ingredients: [ + { idIngredient: 1, idRecipe: 0, quantity: 10 }, + { idIngredient: 2, idRecipe: 0, quantity: 15 }, + ], + }, { id: 1, name: 'crepe2', description: 'La meilleure recette de pâte à crêpes', image: '', ingredients: [] }, { id: 2, name: 'crepe3', description: 'La meilleure recette de pâte à crêpes', image: '', ingredients: [] }, { id: 3, name: 'crepe4', description: 'La meilleure recette de pâte à crêpes', image: '', ingredients: [] }, @@ -26,9 +32,9 @@ export class RecipeService { ]; #ingredients: Ingredient[] = [ - { id:1, name:'Sucre'}, - { id:2, name:'Farine'} - ] + { id: 1, name: 'Sucre' }, + { id: 2, name: 'Farine' }, + ]; getAll(): Recipe[] { return this.#recipes; diff --git a/src/app/recipe/recipe.component.ts b/src/app/recipe/recipe.component.ts index 4a54e03..d566f26 100644 --- a/src/app/recipe/recipe.component.ts +++ b/src/app/recipe/recipe.component.ts @@ -1,5 +1,5 @@ -import { Component, Input } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { Component, Input } from '@angular/core'; import { Recipe } from '../../cookbook/type'; import { RecipeService } from '../recipe.service'; import { MatIconModule } from '@angular/material/icon'; diff --git a/src/cookbook/type.ts b/src/cookbook/type.ts index 7e12ef2..3003ec5 100644 --- a/src/cookbook/type.ts +++ b/src/cookbook/type.ts @@ -16,3 +16,9 @@ export type IngredientEntry = { idRecipe: number; quantity: number; }; + +export type User = { + username: string; + email: string; + token: string; +};