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 { 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] },
|
||||
];
|
||||
|
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 { 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 { 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 { MatSelect } from '@angular/material/select';
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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';
|
||||
|
@@ -16,3 +16,9 @@ export type IngredientEntry = {
|
||||
idRecipe: number;
|
||||
quantity: number;
|
||||
};
|
||||
|
||||
export type User = {
|
||||
username: string;
|
||||
email: string;
|
||||
token: string;
|
||||
};
|
||||
|
Reference in New Issue
Block a user