Add login and logout routes
This commit is contained in:
@@ -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 { RecipeAddComponent } from './recipe-add/recipe-add.component';
|
||||||
import { RecipeComponent } from './recipe/recipe.component';
|
import { RecipeComponent } from './recipe/recipe.component';
|
||||||
import { RecipesComponent } from './recipes/recipes.component';
|
import { RecipesComponent } from './recipes/recipes.component';
|
||||||
|
|
||||||
|
const LoggedGuard: CanActivateFn = () => inject(LoginService).isLoggedIn();
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ path: 'recipes', component: RecipesComponent },
|
{ path: 'recipes', component: RecipesComponent },
|
||||||
{ path: 'recipe/add', component: RecipeAddComponent },
|
{ path: 'recipe/add', component: RecipeAddComponent },
|
||||||
{ path: 'recipe/:id', component: RecipeComponent },
|
{ path: 'recipe/:id', component: RecipeComponent },
|
||||||
{ path: 'recipe/:id/edit', component: RecipeAddComponent },
|
{ 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] },
|
||||||
];
|
];
|
||||||
|
19
src/app/auth/auth.component.html
Normal file
19
src/app/auth/auth.component.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<mat-card>
|
||||||
|
<mat-card-title>Login</mat-card-title>
|
||||||
|
<mat-card-content>
|
||||||
|
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
|
||||||
|
<div>
|
||||||
|
<mat-form-field>
|
||||||
|
<input mat-input type="text" matInput placeholder="Username" formControlName="login">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<mat-form-field>
|
||||||
|
<input mat-input type="password" matInput placeholder="Password" formControlName="password">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<button mat-button>Login</button>
|
||||||
|
</form>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
23
src/app/auth/auth.component.spec.ts
Normal file
23
src/app/auth/auth.component.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AuthComponent } from './auth.component';
|
||||||
|
|
||||||
|
describe('AuthComponent', () => {
|
||||||
|
let component: AuthComponent;
|
||||||
|
let fixture: ComponentFixture<AuthComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AuthComponent],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AuthComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
48
src/app/auth/auth.component.ts
Normal file
48
src/app/auth/auth.component.ts
Normal file
@@ -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!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
56
src/app/login.service.ts
Normal file
56
src/app/login.service.ts
Normal file
@@ -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<boolean> {
|
||||||
|
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<User | null> {
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
@@ -1,18 +1,27 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
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 { Router } from '@angular/router';
|
||||||
import { Ingredient, IngredientEntry, Recipe } from '../../cookbook/type';
|
import { Ingredient, IngredientEntry, Recipe } from '../../cookbook/type';
|
||||||
import { RecipeService } from '../recipe.service';
|
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 { MatSelect } from '@angular/material/select';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-recipe-add',
|
selector: 'app-recipe-add',
|
||||||
standalone: true,
|
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',
|
templateUrl: './recipe-add.component.html',
|
||||||
})
|
})
|
||||||
export class RecipeAddComponent {
|
export class RecipeAddComponent {
|
||||||
@@ -28,7 +37,7 @@ export class RecipeAddComponent {
|
|||||||
|
|
||||||
selectedFilename: string = '';
|
selectedFilename: string = '';
|
||||||
getIngredient(n: number): Ingredient {
|
getIngredient(n: number): Ingredient {
|
||||||
return this.ingredients.find(v => v.id === n)!
|
return this.ingredients.find(v => v.id === n)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
#recipeId: number = -1;
|
#recipeId: number = -1;
|
||||||
@@ -98,9 +107,9 @@ export class RecipeAddComponent {
|
|||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (event) => {
|
reader.onload = (event) => {
|
||||||
this.createForm.patchValue({
|
this.createForm.patchValue({
|
||||||
image: event.target!.result?.toString()
|
image: event.target!.result?.toString(),
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,10 +6,16 @@ import { Ingredient, Recipe } from '../cookbook/type';
|
|||||||
})
|
})
|
||||||
export class RecipeService {
|
export class RecipeService {
|
||||||
#recipes: Recipe[] = [
|
#recipes: Recipe[] = [
|
||||||
{ id: 0, name: 'crepe1', description: 'La meilleure recette de pâte à crêpes', image: '', ingredients: [
|
{
|
||||||
{idIngredient:1,idRecipe:0,quantity:10},
|
id: 0,
|
||||||
{idIngredient:2,idRecipe:0,quantity:15}
|
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: 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: 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: [] },
|
{ id: 3, name: 'crepe4', description: 'La meilleure recette de pâte à crêpes', image: '', ingredients: [] },
|
||||||
@@ -26,9 +32,9 @@ export class RecipeService {
|
|||||||
];
|
];
|
||||||
|
|
||||||
#ingredients: Ingredient[] = [
|
#ingredients: Ingredient[] = [
|
||||||
{ id:1, name:'Sucre'},
|
{ id: 1, name: 'Sucre' },
|
||||||
{ id:2, name:'Farine'}
|
{ id: 2, name: 'Farine' },
|
||||||
]
|
];
|
||||||
|
|
||||||
getAll(): Recipe[] {
|
getAll(): Recipe[] {
|
||||||
return this.#recipes;
|
return this.#recipes;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component, Input } from '@angular/core';
|
||||||
import { Recipe } from '../../cookbook/type';
|
import { Recipe } from '../../cookbook/type';
|
||||||
import { RecipeService } from '../recipe.service';
|
import { RecipeService } from '../recipe.service';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
@@ -16,3 +16,9 @@ export type IngredientEntry = {
|
|||||||
idRecipe: number;
|
idRecipe: number;
|
||||||
quantity: number;
|
quantity: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user