add changes
This commit is contained in:
255
frontend/src/app/features/auth/register/register.ts
Normal file
255
frontend/src/app/features/auth/register/register.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
import { Component, inject, signal, computed } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, AbstractControl, ValidationErrors } from '@angular/forms';
|
||||
import { Router, RouterModule } from '@angular/router';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { AuthService } from '../../../core/services/auth.service';
|
||||
import { StorageService } from '../../../core/services/storage.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-register',
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
RouterModule,
|
||||
MatCardModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatProgressBarModule,
|
||||
MatProgressSpinnerModule
|
||||
],
|
||||
templateUrl: './register.html',
|
||||
styleUrl: './register.scss'
|
||||
})
|
||||
export class RegisterComponent {
|
||||
private fb = inject(FormBuilder);
|
||||
private authService = inject(AuthService);
|
||||
private storageService = inject(StorageService);
|
||||
private router = inject(Router);
|
||||
|
||||
// Signals
|
||||
isSubmitting = signal<boolean>(false);
|
||||
hidePassword = signal<boolean>(true);
|
||||
hideConfirmPassword = signal<boolean>(true);
|
||||
|
||||
// Form
|
||||
registerForm: FormGroup;
|
||||
|
||||
// Password strength computed signal
|
||||
passwordStrength = computed(() => {
|
||||
const password = this.registerForm?.get('password')?.value || '';
|
||||
return this.calculatePasswordStrength(password);
|
||||
});
|
||||
|
||||
constructor() {
|
||||
// Check if converting from guest
|
||||
const guestToken = this.storageService.getGuestToken();
|
||||
|
||||
// Initialize form
|
||||
this.registerForm = this.fb.group({
|
||||
username: ['', [
|
||||
Validators.required,
|
||||
Validators.minLength(3),
|
||||
Validators.maxLength(30),
|
||||
Validators.pattern(/^[a-zA-Z0-9_]+$/)
|
||||
]],
|
||||
email: ['', [
|
||||
Validators.required,
|
||||
Validators.email
|
||||
]],
|
||||
password: ['', [
|
||||
Validators.required,
|
||||
Validators.minLength(8),
|
||||
this.passwordStrengthValidator
|
||||
]],
|
||||
confirmPassword: ['', [Validators.required]]
|
||||
}, { validators: this.passwordMatchValidator });
|
||||
|
||||
// Redirect if already authenticated
|
||||
if (this.authService.isAuthenticated()) {
|
||||
this.router.navigate(['/dashboard']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Password strength validator
|
||||
*/
|
||||
private passwordStrengthValidator(control: AbstractControl): ValidationErrors | null {
|
||||
const password = control.value;
|
||||
|
||||
if (!password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasUpperCase = /[A-Z]/.test(password);
|
||||
const hasLowerCase = /[a-z]/.test(password);
|
||||
const hasNumber = /[0-9]/.test(password);
|
||||
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
|
||||
|
||||
const isValid = hasUpperCase && hasLowerCase && hasNumber && hasSpecialChar;
|
||||
|
||||
return isValid ? null : { weakPassword: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Password match validator
|
||||
*/
|
||||
private passwordMatchValidator(group: AbstractControl): ValidationErrors | null {
|
||||
const password = group.get('password')?.value;
|
||||
const confirmPassword = group.get('confirmPassword')?.value;
|
||||
|
||||
return password === confirmPassword ? null : { passwordMismatch: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate password strength
|
||||
*/
|
||||
private calculatePasswordStrength(password: string): {
|
||||
score: number;
|
||||
label: string;
|
||||
color: string;
|
||||
} {
|
||||
if (!password) {
|
||||
return { score: 0, label: '', color: '' };
|
||||
}
|
||||
|
||||
let score = 0;
|
||||
|
||||
// Length
|
||||
if (password.length >= 8) score += 25;
|
||||
if (password.length >= 12) score += 25;
|
||||
|
||||
// Character types
|
||||
if (/[a-z]/.test(password)) score += 15;
|
||||
if (/[A-Z]/.test(password)) score += 15;
|
||||
if (/[0-9]/.test(password)) score += 10;
|
||||
if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) score += 10;
|
||||
|
||||
let label = '';
|
||||
let color = '';
|
||||
|
||||
if (score < 40) {
|
||||
label = 'Weak';
|
||||
color = 'warn';
|
||||
} else if (score < 70) {
|
||||
label = 'Fair';
|
||||
color = 'accent';
|
||||
} else if (score < 90) {
|
||||
label = 'Good';
|
||||
color = 'primary';
|
||||
} else {
|
||||
label = 'Strong';
|
||||
color = 'primary';
|
||||
}
|
||||
|
||||
return { score, label, color };
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle password visibility
|
||||
*/
|
||||
togglePasswordVisibility(): void {
|
||||
this.hidePassword.update(val => !val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle confirm password visibility
|
||||
*/
|
||||
toggleConfirmPasswordVisibility(): void {
|
||||
this.hideConfirmPassword.update(val => !val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit registration form
|
||||
*/
|
||||
onSubmit(): void {
|
||||
if (this.registerForm.invalid || this.isSubmitting()) {
|
||||
this.registerForm.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isSubmitting.set(true);
|
||||
|
||||
const { username, email, password } = this.registerForm.value;
|
||||
const guestSessionId = this.storageService.getGuestToken() || undefined;
|
||||
|
||||
this.authService.register(username, email, password, guestSessionId).subscribe({
|
||||
next: () => {
|
||||
this.isSubmitting.set(false);
|
||||
// Navigation handled by service
|
||||
},
|
||||
error: () => {
|
||||
this.isSubmitting.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get form control error message
|
||||
*/
|
||||
getErrorMessage(controlName: string): string {
|
||||
const control = this.registerForm.get(controlName);
|
||||
|
||||
if (!control || !control.touched) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (control.hasError('required')) {
|
||||
return `${this.getFieldLabel(controlName)} is required`;
|
||||
}
|
||||
|
||||
if (control.hasError('email')) {
|
||||
return 'Please enter a valid email address';
|
||||
}
|
||||
|
||||
if (control.hasError('minlength')) {
|
||||
const minLength = control.getError('minlength').requiredLength;
|
||||
return `Must be at least ${minLength} characters`;
|
||||
}
|
||||
|
||||
if (control.hasError('maxlength')) {
|
||||
const maxLength = control.getError('maxlength').requiredLength;
|
||||
return `Must not exceed ${maxLength} characters`;
|
||||
}
|
||||
|
||||
if (control.hasError('pattern') && controlName === 'username') {
|
||||
return 'Username can only contain letters, numbers, and underscores';
|
||||
}
|
||||
|
||||
if (control.hasError('weakPassword')) {
|
||||
return 'Password must include uppercase, lowercase, number, and special character';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field label
|
||||
*/
|
||||
private getFieldLabel(controlName: string): string {
|
||||
const labels: { [key: string]: string } = {
|
||||
username: 'Username',
|
||||
email: 'Email',
|
||||
password: 'Password',
|
||||
confirmPassword: 'Confirm Password'
|
||||
};
|
||||
return labels[controlName] || controlName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if form has password mismatch error
|
||||
*/
|
||||
hasPasswordMismatch(): boolean {
|
||||
const confirmControl = this.registerForm.get('confirmPassword');
|
||||
return !!confirmControl?.touched && this.registerForm.hasError('passwordMismatch');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user