auth back
This commit is contained in:
@@ -27,7 +27,7 @@ export const register = async (req, res) => {
|
|||||||
// Hash du mot de passe
|
// Hash du mot de passe
|
||||||
const hashedPassword = await bcrypt.hash(password_hash, 10);
|
const hashedPassword = await bcrypt.hash(password_hash, 10);
|
||||||
const result = await conn.query(
|
const result = await conn.query(
|
||||||
'INSERT INTO users (email, firstname, lastname, password_hash) VALUES (?, ?, ?, ?, ?)',
|
'INSERT INTO users (email, firstname, lastname, password_hash) VALUES (?, ?, ?, ?)',
|
||||||
[email, firstname, lastname, hashedPassword]
|
[email, firstname, lastname, hashedPassword]
|
||||||
);
|
);
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
@@ -83,7 +83,7 @@ export const login = async (req, res) => {
|
|||||||
'UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
'UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||||
[user.id]
|
[user.id]
|
||||||
);
|
);
|
||||||
await setLogs(email + " s'est connecté !")
|
|
||||||
res.cookie('jwt', token, {
|
res.cookie('jwt', token, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: true,
|
secure: true,
|
||||||
|
|||||||
@@ -85,8 +85,8 @@
|
|||||||
</div>
|
</div>
|
||||||
Profile
|
Profile
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="user.id !== 0" role="button"
|
<div *ngIf="user.id !== 0" (click)="logout()" role="button"
|
||||||
class="flex items-center w-full p-3 leading-tight transition-all rounded-lg outline-none text-start hover:bg-blue-gray-50 hover:bg-opacity-80 hover:text-blue-gray-900 focus:bg-blue-gray-50 focus:bg-opacity-80 focus:text-blue-gray-900 active:bg-blue-gray-50 active:bg-opacity-80 active:text-blue-gray-900">
|
class="flex cursor-pointer items-center w-full p-3 leading-tight transition-all rounded-lg outline-none text-start hover:bg-blue-gray-50 hover:bg-opacity-80 hover:text-blue-gray-900 focus:bg-blue-gray-50 focus:bg-opacity-80 focus:text-blue-gray-900 active:bg-blue-gray-50 active:bg-opacity-80 active:text-blue-gray-900">
|
||||||
<div class="grid mr-4 place-items-center">
|
<div class="grid mr-4 place-items-center">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"
|
||||||
class="w-5 h-5">
|
class="w-5 h-5">
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||||
import {AuthService} from '../../_services/auth.service';
|
import {AuthService} from '../../_services/auth.service';
|
||||||
import {Users} from '../../_models/users';
|
import {Users} from '../../_models/users';
|
||||||
import {catchError, of} from 'rxjs';
|
import {catchError, of, Subscription} from 'rxjs';
|
||||||
import {NgIf} from '@angular/common';
|
import {NgIf} from '@angular/common';
|
||||||
import {RouterLink} from '@angular/router';
|
import {Router, RouterLink} from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-sidbar',
|
selector: 'app-sidbar',
|
||||||
@@ -14,20 +14,46 @@ import {RouterLink} from '@angular/router';
|
|||||||
templateUrl: './sidbar.component.html',
|
templateUrl: './sidbar.component.html',
|
||||||
styleUrl: './sidbar.component.css'
|
styleUrl: './sidbar.component.css'
|
||||||
})
|
})
|
||||||
export class SidbarComponent implements OnInit {
|
export class SidbarComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
user: Users = { id: 0, email: '', firstname: '', lastname: '', password_hash: '', updated_at: new Date(), created_at: new Date() };
|
user: Users = { id: 0, email: '', firstname: '', lastname: '', password_hash: '', updated_at: new Date(), created_at: new Date() };
|
||||||
|
|
||||||
constructor(private authService: AuthService) {
|
private authSubscription!: Subscription;
|
||||||
}
|
private authCheckInterval: any;
|
||||||
|
|
||||||
|
constructor(private authService: AuthService, private router: Router) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.checkAuthAndLoadUser();
|
||||||
|
|
||||||
|
this.authCheckInterval = setInterval(() => {
|
||||||
|
this.checkAuthAndLoadUser();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (this.authCheckInterval) {
|
||||||
|
clearInterval(this.authCheckInterval);
|
||||||
|
}
|
||||||
|
if (this.authSubscription) {
|
||||||
|
this.authSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAuthAndLoadUser(): void {
|
||||||
|
this.authService.isAuthenticated().subscribe(isAuthenticated => {
|
||||||
|
if (isAuthenticated) {
|
||||||
this.loadUsers();
|
this.loadUsers();
|
||||||
|
} else {
|
||||||
|
this.user = { id: 0, email: '', firstname: '', lastname: '', password_hash: '', updated_at: new Date(), created_at: new Date() };
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadUsers(): void {
|
loadUsers(): void {
|
||||||
this.authService.getProfil().pipe(
|
this.authService.getProfil().pipe(
|
||||||
catchError(error => {
|
catchError(error => {
|
||||||
|
console.error('Erreur lors du chargement du profil:', error);
|
||||||
return of(null);
|
return of(null);
|
||||||
})
|
})
|
||||||
).subscribe(users => {
|
).subscribe(users => {
|
||||||
@@ -37,4 +63,15 @@ export class SidbarComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logout(): void {
|
||||||
|
this.authService.logout().subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Logout failed:', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import {HttpClient, HttpHeaders} from '@angular/common/http';
|
import {HttpClient, HttpHeaders} from '@angular/common/http';
|
||||||
import {Users} from '../_models/users';
|
import {Users} from '../_models/users';
|
||||||
import {catchError, map, Observable, of} from 'rxjs';
|
import {BehaviorSubject, catchError, map, Observable, of, tap} from 'rxjs';
|
||||||
import {environment} from '../../environments/environment';
|
import {environment} from '../../environments/environment';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@@ -9,7 +9,14 @@ import {environment} from '../../environments/environment';
|
|||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
|
|
||||||
constructor(private http: HttpClient) { }
|
private authStateSubject = new BehaviorSubject<boolean>(false);
|
||||||
|
authStateChange$ = this.authStateSubject.asObservable();
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {
|
||||||
|
this.isAuthenticated().subscribe(isAuth => {
|
||||||
|
this.authStateSubject.next(isAuth);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
register(user: Users): Observable<any> {
|
register(user: Users): Observable<any> {
|
||||||
const headers = new HttpHeaders({
|
const headers = new HttpHeaders({
|
||||||
@@ -27,7 +34,13 @@ export class AuthService {
|
|||||||
return this.http.post(environment.apiurl + '/auth/login', user, {
|
return this.http.post(environment.apiurl + '/auth/login', user, {
|
||||||
headers,
|
headers,
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
});
|
}).pipe(
|
||||||
|
tap(response => {
|
||||||
|
if (response) {
|
||||||
|
this.authStateSubject.next(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
logout(): Observable<any> {
|
logout(): Observable<any> {
|
||||||
@@ -40,6 +53,10 @@ export class AuthService {
|
|||||||
headers,
|
headers,
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
}
|
}
|
||||||
|
).pipe(
|
||||||
|
tap(response => {
|
||||||
|
this.authStateSubject.next(false);
|
||||||
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Component } from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {Title} from '@angular/platform-browser';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
@@ -6,6 +7,13 @@ import { Component } from '@angular/core';
|
|||||||
templateUrl: './home.component.html',
|
templateUrl: './home.component.html',
|
||||||
styleUrl: './home.component.css'
|
styleUrl: './home.component.css'
|
||||||
})
|
})
|
||||||
export class HomeComponent {
|
export class HomeComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(private title: Title) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.title.setTitle('Accueil - YouVideo');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,21 +5,66 @@
|
|||||||
<p class="text-gray-600 mt-2">Entrez vos identifiants pour vous connecter</p>
|
<p class="text-gray-600 mt-2">Entrez vos identifiants pour vous connecter</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form>
|
<!-- Message de succès après inscription -->
|
||||||
<div class="mb-6">
|
<div *ngIf="registrationSuccess" class="mb-6 p-3 bg-green-100 border border-green-400 text-green-700 rounded">
|
||||||
<label for="email" class="block text-gray-700 text-sm font-medium mb-2">Email</label>
|
Inscription réussie ! Vous pouvez maintenant vous connecter.
|
||||||
<input type="email" id="email" name="email" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="votre@email.com" required>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Message d'erreur -->
|
||||||
|
<div *ngIf="loginError" class="mb-6 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||||
|
{{ loginError }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
|
||||||
|
<!-- Email -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="email" class="block text-gray-700 text-sm font-medium mb-2">Email</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
formControlName="email"
|
||||||
|
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="votre@email.com"
|
||||||
|
[ngClass]="{'border-red-500': loginForm.get('email')?.touched && loginForm.get('email')?.invalid}"
|
||||||
|
>
|
||||||
|
<div *ngIf="loginForm.get('email')?.touched && loginForm.get('email')?.invalid" class="text-red-500 text-sm mt-1">
|
||||||
|
<div *ngIf="loginForm.get('email')?.errors?.['required']">L'email est requis.</div>
|
||||||
|
<div *ngIf="loginForm.get('email')?.errors?.['email']">Veuillez entrer une adresse email valide.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mot de passe -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<div class="flex justify-between items-center mb-2">
|
<div class="flex justify-between items-center mb-2">
|
||||||
<label for="password" class="block text-gray-700 text-sm font-medium">Mot de passe</label>
|
<label for="password" class="block text-gray-700 text-sm font-medium">Mot de passe</label>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="password" name="password" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="••••••••" required>
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
formControlName="password"
|
||||||
|
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="••••••••"
|
||||||
|
[ngClass]="{'border-red-500': loginForm.get('password')?.touched && loginForm.get('password')?.invalid}"
|
||||||
|
>
|
||||||
|
<div *ngIf="loginForm.get('password')?.touched && loginForm.get('password')?.invalid" class="text-red-500 text-sm mt-1">
|
||||||
|
<div *ngIf="loginForm.get('password')?.errors?.['required']">Le mot de passe est requis.</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
|
<!-- Bouton de connexion -->
|
||||||
Se connecter
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
|
||||||
|
[disabled]="isSubmitting"
|
||||||
|
[ngClass]="{'opacity-70 cursor-not-allowed': isSubmitting}"
|
||||||
|
>
|
||||||
|
<span *ngIf="isSubmitting" class="inline-block mr-2">
|
||||||
|
<svg class="animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span>Se connecter</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,75 @@
|
|||||||
import { Component } from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {RouterLink} from '@angular/router';
|
import {ActivatedRoute, Router, RouterLink} from '@angular/router';
|
||||||
|
import {Title} from '@angular/platform-browser';
|
||||||
|
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||||
|
import {AuthService} from '../../../_services/auth.service';
|
||||||
|
import {NgClass, NgIf} from '@angular/common';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
imports: [
|
imports: [
|
||||||
RouterLink
|
RouterLink,
|
||||||
|
NgIf,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
NgClass
|
||||||
],
|
],
|
||||||
templateUrl: './login.component.html',
|
templateUrl: './login.component.html',
|
||||||
styleUrl: './login.component.css'
|
styleUrl: './login.component.css'
|
||||||
})
|
})
|
||||||
export class LoginComponent {
|
export class LoginComponent implements OnInit {
|
||||||
|
loginForm: FormGroup;
|
||||||
|
isSubmitting = false;
|
||||||
|
loginError = '';
|
||||||
|
registrationSuccess = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private title: Title,
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private authService: AuthService,
|
||||||
|
private router: Router,
|
||||||
|
private route: ActivatedRoute
|
||||||
|
) {
|
||||||
|
this.loginForm = this.fb.group({
|
||||||
|
email: ['', [Validators.required, Validators.email]],
|
||||||
|
password: ['', [Validators.required]]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.title.setTitle('Connexion - YouVideo');
|
||||||
|
this.route.queryParams.subscribe(params => {
|
||||||
|
if (params['registered'] === 'success') {
|
||||||
|
this.registrationSuccess = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.loginForm.invalid) {
|
||||||
|
Object.keys(this.loginForm.controls).forEach(key => {
|
||||||
|
const control = this.loginForm.get(key);
|
||||||
|
control?.markAsTouched();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isSubmitting = true;
|
||||||
|
this.loginError = '';
|
||||||
|
|
||||||
|
const loginData = {
|
||||||
|
email: this.loginForm.value.email,
|
||||||
|
password: this.loginForm.value.password
|
||||||
|
};
|
||||||
|
|
||||||
|
this.authService.login(loginData).subscribe({
|
||||||
|
next: (response) => {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
this.loginError = error.error?.message || 'Identifiants incorrects. Veuillez réessayer.';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Component } from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {Title} from '@angular/platform-browser';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-not-found',
|
selector: 'app-not-found',
|
||||||
@@ -6,6 +7,13 @@ import { Component } from '@angular/core';
|
|||||||
templateUrl: './not-found.component.html',
|
templateUrl: './not-found.component.html',
|
||||||
styleUrl: './not-found.component.css'
|
styleUrl: './not-found.component.css'
|
||||||
})
|
})
|
||||||
export class NotFoundComponent {
|
export class NotFoundComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(private title: Title) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.title.setTitle('404 - YouVideo');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,39 +5,124 @@
|
|||||||
<p class="text-gray-600 mt-2">Entrez vos informations pour vous inscrire</p>
|
<p class="text-gray-600 mt-2">Entrez vos informations pour vous inscrire</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form>
|
<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
|
||||||
|
<!-- Email -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<label for="email" class="block text-gray-700 text-sm font-medium mb-2">Email</label>
|
<label for="email" class="block text-gray-700 text-sm font-medium mb-2">Email</label>
|
||||||
<input type="email" id="email" name="email" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="votre@email.com" required>
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
formControlName="email"
|
||||||
|
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="votre@email.com"
|
||||||
|
[ngClass]="{'border-red-500': registerForm.get('email')?.touched && registerForm.get('email')?.invalid}"
|
||||||
|
>
|
||||||
|
<div *ngIf="registerForm.get('email')?.touched && registerForm.get('email')?.invalid" class="text-red-500 text-sm mt-1">
|
||||||
|
<div *ngIf="registerForm.get('email')?.errors?.['required']">L'email est requis.</div>
|
||||||
|
<div *ngIf="registerForm.get('email')?.errors?.['email']">Veuillez entrer une adresse email valide.</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Prénom -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<label for="firstname" class="block text-gray-700 text-sm font-medium mb-2">Prénom</label>
|
<label for="firstname" class="block text-gray-700 text-sm font-medium mb-2">Prénom</label>
|
||||||
<input type="text" id="firstname" name="firstname" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="votre@email.com" required>
|
<input
|
||||||
|
type="text"
|
||||||
|
id="firstname"
|
||||||
|
formControlName="firstname"
|
||||||
|
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="Prénom"
|
||||||
|
[ngClass]="{'border-red-500': registerForm.get('firstname')?.touched && registerForm.get('firstname')?.invalid}"
|
||||||
|
>
|
||||||
|
<div *ngIf="registerForm.get('firstname')?.touched && registerForm.get('firstname')?.invalid" class="text-red-500 text-sm mt-1">
|
||||||
|
<div *ngIf="registerForm.get('firstname')?.errors?.['required']">Le prénom est requis.</div>
|
||||||
|
<div *ngIf="registerForm.get('firstname')?.errors?.['minlength']">Le prénom doit contenir au moins 2 caractères.</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Nom -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<label for="lastname" class="block text-gray-700 text-sm font-medium mb-2">Nom</label>
|
<label for="lastname" class="block text-gray-700 text-sm font-medium mb-2">Nom</label>
|
||||||
<input type="text" id="lastname" name="lastname" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="votre@email.com" required>
|
<input
|
||||||
|
type="text"
|
||||||
|
id="lastname"
|
||||||
|
formControlName="lastname"
|
||||||
|
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="Nom"
|
||||||
|
[ngClass]="{'border-red-500': registerForm.get('lastname')?.touched && registerForm.get('lastname')?.invalid}"
|
||||||
|
>
|
||||||
|
<div *ngIf="registerForm.get('lastname')?.touched && registerForm.get('lastname')?.invalid" class="text-red-500 text-sm mt-1">
|
||||||
|
<div *ngIf="registerForm.get('lastname')?.errors?.['required']">Le nom est requis.</div>
|
||||||
|
<div *ngIf="registerForm.get('lastname')?.errors?.['minlength']">Le nom doit contenir au moins 2 caractères.</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mot de passe -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<div class="flex justify-between items-center mb-2">
|
<div class="flex justify-between items-center mb-2">
|
||||||
<label for="password" class="block text-gray-700 text-sm font-medium">Mot de passe</label>
|
<label for="password" class="block text-gray-700 text-sm font-medium">Mot de passe</label>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="password" name="password" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="••••••••" required>
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
formControlName="password"
|
||||||
|
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="••••••••"
|
||||||
|
[ngClass]="{'border-red-500': registerForm.get('password')?.touched && registerForm.get('password')?.invalid}"
|
||||||
|
>
|
||||||
|
<div *ngIf="registerForm.get('password')?.touched && registerForm.get('password')?.invalid" class="text-red-500 text-sm mt-1">
|
||||||
|
<div *ngIf="registerForm.get('password')?.errors?.['required']">Le mot de passe est requis.</div>
|
||||||
|
<div *ngIf="registerForm.get('password')?.errors?.['minlength']">Le mot de passe doit contenir au moins 8 caractères.</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Confirmer mot de passe -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<div class="flex justify-between items-center mb-2">
|
<div class="flex justify-between items-center mb-2">
|
||||||
<label for="confirmPassword" class="block text-gray-700 text-sm font-medium">Confirmer le mot de passe</label>
|
<label for="confirmPassword" class="block text-gray-700 text-sm font-medium">Confirmer le mot de passe</label>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="confirmPassword" name="confirmPassword" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" placeholder="••••••••" required>
|
<input
|
||||||
|
type="password"
|
||||||
|
id="confirmPassword"
|
||||||
|
formControlName="confirmPassword"
|
||||||
|
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="••••••••"
|
||||||
|
[ngClass]="{'border-red-500': registerForm.get('confirmPassword')?.touched && registerForm.get('confirmPassword')?.invalid}"
|
||||||
|
>
|
||||||
|
<div *ngIf="registerForm.get('confirmPassword')?.touched && registerForm.get('confirmPassword')?.invalid" class="text-red-500 text-sm mt-1">
|
||||||
|
<div *ngIf="registerForm.get('confirmPassword')?.errors?.['required']">La confirmation du mot de passe est requise.</div>
|
||||||
|
<div *ngIf="registerForm.get('confirmPassword')?.errors?.['passwordMismatch']">Les mots de passe ne correspondent pas.</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
|
<!-- Message d'erreur global -->
|
||||||
S'inscrire
|
<div *ngIf="submissionError" class="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
|
||||||
|
{{ submissionError }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bouton d'inscription -->
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"
|
||||||
|
[disabled]="isSubmitting"
|
||||||
|
[ngClass]="{'opacity-70 cursor-not-allowed': isSubmitting}"
|
||||||
|
>
|
||||||
|
<span *ngIf="isSubmitting" class="inline-block mr-2">
|
||||||
|
<svg class="animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span>S'inscrire</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Lien vers la page de connexion -->
|
||||||
|
<div class="text-center mt-4">
|
||||||
|
<p class="text-gray-600">
|
||||||
|
Déjà inscrit ?
|
||||||
|
<a routerLink="/login" class="text-blue-600 hover:underline">Connectez-vous</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,14 +1,98 @@
|
|||||||
import { Component } from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {RouterLink} from '@angular/router';
|
import {Router, RouterLink} from '@angular/router';
|
||||||
|
import {Title} from '@angular/platform-browser';
|
||||||
|
import {AuthService} from '../../../_services/auth.service';
|
||||||
|
import {
|
||||||
|
AbstractControl,
|
||||||
|
FormBuilder,
|
||||||
|
FormGroup,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
ValidationErrors,
|
||||||
|
Validators
|
||||||
|
} from '@angular/forms';
|
||||||
|
import {Users} from '../../../_models/users';
|
||||||
|
import {NgClass, NgIf} from '@angular/common';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-register',
|
selector: 'app-register',
|
||||||
imports: [
|
imports: [
|
||||||
RouterLink
|
RouterLink,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
NgClass,
|
||||||
|
NgIf
|
||||||
],
|
],
|
||||||
templateUrl: './register.component.html',
|
templateUrl: './register.component.html',
|
||||||
styleUrl: './register.component.css'
|
styleUrl: './register.component.css'
|
||||||
})
|
})
|
||||||
export class RegisterComponent {
|
export class RegisterComponent implements OnInit {
|
||||||
|
|
||||||
|
registerForm: FormGroup;
|
||||||
|
isSubmitting = false;
|
||||||
|
submissionError = '';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private title: Title,
|
||||||
|
private authService: AuthService,
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private router: Router
|
||||||
|
) {
|
||||||
|
// Initialiser le formulaire avec FormBuilder
|
||||||
|
this.registerForm = this.fb.group({
|
||||||
|
email: ['', [Validators.required, Validators.email]],
|
||||||
|
firstname: ['', [Validators.required, Validators.minLength(2)]],
|
||||||
|
lastname: ['', [Validators.required, Validators.minLength(2)]],
|
||||||
|
password: ['', [Validators.required, Validators.minLength(8)]],
|
||||||
|
confirmPassword: ['', [Validators.required]]
|
||||||
|
}, {
|
||||||
|
validators: this.passwordMatchValidator
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.title.setTitle('S\'inscrire - YouVideo');
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordMatchValidator(control: AbstractControl): ValidationErrors | null {
|
||||||
|
const password = control.get('password');
|
||||||
|
const confirmPassword = control.get('confirmPassword');
|
||||||
|
if (password && confirmPassword && password.value !== confirmPassword.value) {
|
||||||
|
confirmPassword.setErrors({ passwordMismatch: true });
|
||||||
|
return { passwordMismatch: true };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (this.registerForm.invalid) {
|
||||||
|
Object.keys(this.registerForm.controls).forEach(key => {
|
||||||
|
const control = this.registerForm.get(key);
|
||||||
|
control?.markAsTouched();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isSubmitting = true;
|
||||||
|
this.submissionError = '';
|
||||||
|
|
||||||
|
const user: Users = {
|
||||||
|
id: 0,
|
||||||
|
email: this.registerForm.value.email,
|
||||||
|
firstname: this.registerForm.value.firstname,
|
||||||
|
lastname: this.registerForm.value.lastname,
|
||||||
|
password_hash: this.registerForm.value.password,
|
||||||
|
updated_at: new Date(),
|
||||||
|
created_at: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.authService.register(user).subscribe({
|
||||||
|
next: (response) => {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
this.router.navigate(['/login'], { queryParams: { registered: 'success' } });
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
this.isSubmitting = false;
|
||||||
|
this.submissionError = error.error?.message || 'Une erreur est survenue lors de l\'inscription.';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import {ReactiveFormsModule} from '@angular/forms';
|
|
||||||
import {NgForOf, NgIf} from '@angular/common';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-search',
|
|
||||||
imports: [
|
|
||||||
NgIf,
|
|
||||||
NgForOf,
|
|
||||||
ReactiveFormsModule
|
|
||||||
],
|
|
||||||
templateUrl: './search.component.html',
|
|
||||||
styleUrl: './search.component.css'
|
|
||||||
})
|
|
||||||
export class SearchComponent {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -5,12 +5,13 @@ import {PublicLayoutComponent} from './public-layout/public-layout.component';
|
|||||||
import {HomeComponent} from './pages/home/home.component';
|
import {HomeComponent} from './pages/home/home.component';
|
||||||
import {LoginComponent} from './pages/login/login.component';
|
import {LoginComponent} from './pages/login/login.component';
|
||||||
import {RegisterComponent} from './pages/register/register.component';
|
import {RegisterComponent} from './pages/register/register.component';
|
||||||
|
import {noAuthGuard} from '../_guards/no-auth.guard';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: PublicLayoutComponent, children: [
|
{ path: '', component: PublicLayoutComponent, children: [
|
||||||
{ path: '', component: HomeComponent },
|
{ path: '', component: HomeComponent },
|
||||||
{ path: 'login', component: LoginComponent },
|
{ path: 'login', component: LoginComponent, canActivate: [noAuthGuard] },
|
||||||
{ path: 'register', component: RegisterComponent },
|
{ path: 'register', component: RegisterComponent, canActivate: [noAuthGuard] },
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ path: '**', component: NotFoundComponent },
|
{ path: '**', component: NotFoundComponent },
|
||||||
|
|||||||
Reference in New Issue
Block a user