import { Component, inject, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { Router } from '@angular/router'; 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 { MatCardModule } from '@angular/material/card'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; import { ApiService } from '../../services/api.service'; import { AuthService } from '../../services/auth.service'; import { Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-user-info', standalone: true, imports: [ CommonModule, FormsModule, MatFormFieldModule, MatInputModule, MatButtonModule, MatIconModule, MatProgressBarModule, MatCardModule, MatSnackBarModule, MatProgressSpinnerModule ], templateUrl: './user-info.component.html', styleUrls: ['./user-info.component.scss'] }) export class UserInfoComponent implements OnDestroy { private apiService = inject(ApiService); private authService = inject(AuthService); private snackBar = inject(MatSnackBar); private router = inject(Router); username = this.authService.getUsername() || ''; currentPassword = ''; newPassword = ''; confirmPassword = ''; saving = false; showCurrentPassword = false; showNewPassword = false; showConfirmPassword = false; // Memory leak prevention private destroyed$ = new Subject(); ngOnDestroy() { this.destroyed$.next(); this.destroyed$.complete(); } get passwordStrength(): { level: number; text: string; color: string } { if (!this.newPassword) { return { level: 0, text: '', color: '' }; } let strength = 0; // Length check if (this.newPassword.length >= 8) strength++; if (this.newPassword.length >= 12) strength++; // Character variety if (/[a-z]/.test(this.newPassword)) strength++; if (/[A-Z]/.test(this.newPassword)) strength++; if (/[0-9]/.test(this.newPassword)) strength++; if (/[^a-zA-Z0-9]/.test(this.newPassword)) strength++; if (strength <= 2) { return { level: 33, text: 'Weak', color: 'warn' }; } else if (strength <= 4) { return { level: 66, text: 'Medium', color: 'accent' }; } else { return { level: 100, text: 'Strong', color: 'primary' }; } } get isFormValid(): boolean { return !!this.currentPassword && !!this.newPassword && this.newPassword === this.confirmPassword && this.newPassword.length >= 8; } changePassword() { if (!this.isFormValid || this.saving) return; this.saving = true; this.apiService.changePassword(this.currentPassword, this.newPassword) .pipe(takeUntil(this.destroyed$)) .subscribe({ next: () => { this.saving = false; // Clear form this.currentPassword = ''; this.newPassword = ''; this.confirmPassword = ''; // Show success message this.snackBar.open( 'Password changed successfully. Please login again.', 'OK', { duration: 5000, panelClass: ['success-snackbar'] } ).afterDismissed().subscribe(() => { // Logout after password change this.authService.logout(); }); }, error: (error) => { this.saving = false; // Handle validation errors if (error.status === 422 && error.error?.details) { // Field-level errors const fieldErrors = error.error.details; if (fieldErrors.some((e: any) => e.field === 'current_password')) { this.snackBar.open( 'Current password is incorrect', 'Close', { duration: 5000, panelClass: ['error-snackbar'] } ); } else if (fieldErrors.some((e: any) => e.field === 'new_password')) { const pwError = fieldErrors.find((e: any) => e.field === 'new_password'); this.snackBar.open( pwError.message || 'New password does not meet requirements', 'Close', { duration: 5000, panelClass: ['error-snackbar'] } ); } } else { // Generic error this.snackBar.open( error.error?.detail || 'Failed to change password', 'Close', { duration: 5000, panelClass: ['error-snackbar'] } ); } } }); } togglePasswordVisibility(field: 'current' | 'new' | 'confirm') { switch(field) { case 'current': this.showCurrentPassword = !this.showCurrentPassword; break; case 'new': this.showNewPassword = !this.showNewPassword; break; case 'confirm': this.showConfirmPassword = !this.showConfirmPassword; break; } } }