Spaces:
Paused
Paused
| 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'; | |
| ({ | |
| 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<void>(); | |
| 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; | |
| } | |
| } | |
| } |